ASP.NET Web API Demo OwinSelfHost 自宿主 Swagger Swashbuckle 在线文档

新建Web API工程

 

选Empty,勾选Web API,不要选择Web API,那样会把MVC勾上,这里不需要MVC

Web API工程属性

 XML文件用于生成在线文档

  新建Windows服务作为Web API的宿主

 

WebApiHost工程属性

 控制台应用程序方便调试

 Windows服务安装Microsoft.AspNet.WebApi.OwinSelfHost

 

工程WebApiDemo需要引用Microsoft.Owin.dll

 WebApiDemo安装Swashbuckle

 应用程序入口

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.ServiceProcess;
using System.Text;
using System.Threading.Tasks;

namespace WebApiHost
{
    static class Program
    {
        /// <summary>
        /// 应用程序的主入口点。
        /// </summary>
        static void Main(string[] args)
        {
            RunDebug();
            StartService();
        }

        private static void StartService()
        {
            ServiceBase[] ServicesToRun;
            ServicesToRun = new ServiceBase[]
            {
                new WebApiHostService()
            };
            ServiceBase.Run(ServicesToRun);
        }

        [Conditional("DEBUG")]
        private static void RunDebug()
        {
            new WebApiHostService().Start();
            Console.WriteLine("启动成功");
            Console.ReadLine();
        }
    }
}
View Code

 启动Web API服务

using Microsoft.Owin.Hosting;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Configuration;
using System.Data;
using System.Diagnostics;
using System.Linq;
using System.ServiceProcess;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Utils;

namespace WebApiHost
{
    public partial class WebApiHostService : ServiceBase
    {
        #region 构造函数
        public WebApiHostService()
        {
            InitializeComponent();
        }
        #endregion

        #region OnStart 启动服务
        protected override void OnStart(string[] args)
        {
            int port = int.Parse(ConfigurationManager.AppSettings["WebApiServicePort"]);
            StartOptions options = new StartOptions();
            options.Urls.Add("http://127.0.0.1:" + port);
            options.Urls.Add("http://localhost:" + port);
            options.Urls.Add("http://+:" + port);
            WebApp.Start<Startup>(options);
            LogUtil.Log("Web API 服务 启动成功");
        }
        #endregion

        #region OnStop 停止服务
        protected override void OnStop()
        {
            LogUtil.Log("Web API 服务 停止成功");
            Thread.Sleep(100); //等待一会,待日志写入文件
        }
        #endregion

        #region Start 启动服务
        public void Start()
        {
            OnStart(null);
        }
        #endregion

    }
}
View Code

 配置Web API路由、拦截器以及初始化Swagger在线文档

using Owin;
using WebApiDemo;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Web.Http;

namespace WebApiHost
{
    class Startup
    {
        public void Configuration(IAppBuilder appBuilder)
        {
            HttpConfiguration config = new HttpConfiguration();

            config.MapHttpAttributeRoutes();

            config.Routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "api/{controller}/{id}",
                defaults: new { id = RouteParameter.Optional }
            );

            config.Filters.Add(new MyExceptionFilter());
            config.Filters.Add(new MyActionFilter());

            SwaggerConfig.Register(config);

            appBuilder.UseWebApi(config);
        }
    }
}
View Code

接口实现

1、继承ApiController

2、RoutePrefix设置路由前缀

3、SwaggerResponse用于生成在线文档描述

using Models;
using Swashbuckle.Swagger.Annotations;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http;
using Utils;

namespace WebApiDemo.Controllers
{
    /// <summary>
    /// 测试接口
    /// </summary>
    [RoutePrefix("api/test")]
    public class TestController : ApiController
    {
        #region TestGet 测试GET请求
        /// <summary>
        /// 测试GET请求
        /// </summary>
        /// <param name="val">测试参数</param>
        [HttpGet]
        [Route("TestGet")]
        [SwaggerResponse(HttpStatusCode.OK, "返回JSON", typeof(JsonListResult<TestGetResult>))]
        public HttpResponseMessage TestGet(string val)
        {
            List<TestGetResult> list = new List<TestGetResult>();

            for (int i = 1; i <= 10; i++)
            {
                TestGetResult item = new TestGetResult();
                item.testValue1 = i.ToString();
                item.testValue2 = i;
                item.testValue3 = "这是传入参数:" + val;
                list.Add(item);
            }

            var jsonResult = new JsonListResult<TestGetResult>(list, list.Count);

            return ApiHelper.ToJson(jsonResult);
        }
        #endregion

        #region TestPost 测试POST请求
        /// <summary>
        /// 测试POST请求
        /// </summary>
        /// <param name="data">POST数据</param>
        [HttpPost]
        [Route("TestPost")]
        [SwaggerResponse(HttpStatusCode.OK, "返回JSON", typeof(JsonResult<CommonSubmitResult>))]
        public HttpResponseMessage TestPost([FromBody] TestPostData data)
        {
            JsonResult jsonResult = null;

            if (data == null) return ApiHelper.ToJson(new JsonResult("请检查参数格式", ResultCode.参数不正确));

            string msg = "操作成功,这是您传入的参数:" + data.testArg;

            jsonResult = new JsonResult<CommonSubmitResult>(new CommonSubmitResult()
            {
                msg = msg,
                id = "1"
            });

            return ApiHelper.ToJson(jsonResult);
        }
        #endregion

    }

    #region 数据类
    /// <summary>
    /// TestGet接口返回结果
    /// </summary>
    public class TestGetResult
    {
        /// <summary>
        /// 测试数据1
        /// </summary>
        public string testValue1 { get; set; }

        /// <summary>
        /// 测试数据2
        /// </summary>
        public int testValue2 { get; set; }

        /// <summary>
        /// 测试数据3
        /// </summary>
        public string testValue3 { get; set; }
    }

    /// <summary>
    /// TestPost接口参数
    /// </summary>
    [MyValidate]
    public class TestPostData
    {
        /// <summary>
        /// 测试参数1
        /// </summary>
        [Required]
        public string testArg { get; set; }

        /// <summary>
        /// 测试日期参数
        /// </summary>
        [Required]
        [DateTime(Format = "yyyyMMddHHmmss")]
        public string testTime { get; set; }
    }

    /// <summary>
    /// TestPost接口返回结果
    /// </summary>
    public class TestPostResult
    {
        /// <summary>
        /// 测试数据1
        /// </summary>
        public string testValue1 { get; set; }
    }
    #endregion

}
View Code

MyValidate属性表示该数据需要校验

Required必填校验
DateTime日期输入格式校验

辅助类ApiHelper.cs

using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Web;

namespace Utils
{
    public class ApiHelper
    {
        public static HttpResponseMessage ToJson(object obj)
        {
            string str = JsonConvert.SerializeObject(obj);
            HttpResponseMessage result = new HttpResponseMessage { Content = new StringContent(str, Encoding.UTF8, "application/json") };
            return result;
        }

    }
}
View Code

 辅助类ServiceHelper.cs

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace Utils
{
    /// <summary>
    /// 服务帮助类
    /// </summary>
    public class ServiceHelper
    {
        public static ConcurrentDictionary<Type, object> _dict = new ConcurrentDictionary<Type, object>();

        /// <summary>
        /// 获取对象
        /// </summary>
        public static T Get<T>() where T : new()
        {
            Type type = typeof(T);
            object obj = _dict.GetOrAdd(type, (key) => new T());

            return (T)obj;
        }

        /// <summary>
        /// 获取对象
        /// </summary>
        public static T Get<T>(Func<T> func) where T : new()
        {
            Type type = typeof(T);
            object obj = _dict.GetOrAdd(type, (key) => func());

            return (T)obj;
        }

    }
}
View Code

 JsonResult类

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Net;
using System.Web;

namespace Models
{
    /// <summary>
    /// Json返回
    /// </summary>
    public class JsonResult
    {
        /// <summary>
        /// 接口是否成功
        /// </summary>
        [Required]
        public virtual bool success { get; set; }

        /// <summary>
        /// 结果编码
        /// </summary>
        [Required]
        public virtual ResultCode resultCode { get; set; }

        /// <summary>
        /// 接口错误信息
        /// </summary>
        public virtual string errorMsg { get; set; }

        /// <summary>
        /// 记录总数(可空类型)
        /// </summary>
        public virtual int? total { get; set; }

        /// <summary>
        /// 默认构造函数
        /// </summary>
        public JsonResult() { }

        /// <summary>
        /// 接口失败返回数据
        /// </summary>
        public JsonResult(string errorMsg, ResultCode resultCode)
        {
            this.success = false;
            this.resultCode = resultCode;
            this.errorMsg = errorMsg;
        }

    }

    /// <summary>
    /// Json返回
    /// </summary>
    public class JsonResult<T> : JsonResult
    {
        /* 子类重写属性解决JSON序列化属性顺序问题 */

        /// <summary>
        /// 接口是否成功
        /// </summary>
        [Required]
        public override bool success { get; set; }

        /// <summary>
        /// 结果编码
        /// </summary>
        [Required]
        public override ResultCode resultCode { get; set; }

        /// <summary>
        /// 接口错误信息
        /// </summary>
        public override string errorMsg { get; set; }

        /// <summary>
        /// 记录总数(可空类型)
        /// </summary>
        public override int? total { get; set; }

        /// <summary>
        /// 数据
        /// </summary>
        public T info { get; set; }

        /// <summary>
        /// 接口成功返回数据
        /// </summary>
        public JsonResult(T info)
        {
            this.success = true;
            this.resultCode = ResultCode.OK;
            this.info = info;
            this.total = null;
        }

    }

    /// <summary>
    /// Json返回
    /// </summary>
    public class JsonListResult<T> : JsonResult
    {
        /* 子类重写属性解决JSON序列化属性顺序问题 */

        /// <summary>
        /// 接口是否成功
        /// </summary>
        [Required]
        public override bool success { get; set; }

        /// <summary>
        /// 结果编码
        /// </summary>
        [Required]
        public override ResultCode resultCode { get; set; }

        /// <summary>
        /// 接口错误信息
        /// </summary>
        public override string errorMsg { get; set; }

        /// <summary>
        /// 记录总数(可空类型)
        /// </summary>
        public override int? total { get; set; }

        /// <summary>
        /// 数据
        /// </summary>
        public List<T> info { get; set; }

        /// <summary>
        /// 接口成功返回数据
        /// </summary>
        public JsonListResult(List<T> list, int total)
        {
            this.success = true;
            this.resultCode = ResultCode.OK;
            this.info = list;
            this.total = total;
        }

        /// <summary>
        /// 接口成功返回数据
        /// </summary>
        public JsonListResult(List<T> list, PagerModel pager)
        {
            this.success = true;
            this.resultCode = ResultCode.OK;
            this.info = list;
            this.total = pager.totalRows;
        }

    }

    /// <summary>
    /// 结果编码
    /// </summary>
    public enum ResultCode
    {
        OK = 200,

        token不匹配或已过期 = 1001,
        请求头中不存在token = 1002,
        用户不存在 = 1101,
        密码不正确 = 1102,
        参数不正确 = 1201,
        操作失败 = 1301,
        资源不存在 = 1302,
        其他错误 = 1401,

        服务器内部错误 = 1501
    }

    /// <summary>
    /// 通用返回数据
    /// </summary>
    public class CommonSubmitResult
    {
        /// <summary>
        /// 提示信息
        /// </summary>
        public string msg { get; set; }

        /// <summary>
        /// 记录ID
        /// </summary>
        public string id { get; set; }
    }

    /// <summary>
    /// 通用返回数据
    /// </summary>
    public class CommonMsgResult
    {
        /// <summary>
        /// 提示信息
        /// </summary>
        public string msg { get; set; }
    }
}
View Code

异常拦截器

异常在这里统一处理,接口方法中不需要再加try catch

using Models;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Web;
using System.Web.Http.Filters;
using Utils;

namespace WebApiDemo
{
    public class MyExceptionFilter : ExceptionFilterAttribute
    {
        //重写基类的异常处理方法
        public override void OnException(HttpActionExecutedContext actionExecutedContext)
        {
            var result = new JsonResult("拦截到异常:" + actionExecutedContext.Exception.Message, ResultCode.服务器内部错误);

            LogUtil.Error(actionExecutedContext.Exception);

            actionExecutedContext.Response = ApiHelper.ToJson(result);

            base.OnException(actionExecutedContext);
        }
    }
}
View Code

方法拦截器

1、在拦截器中校验证token
2、在拦截器中校验POST和GET参数
3、在拦截器中写操作日志

using Microsoft.Owin;
using Models;
using Newtonsoft.Json;
using Swashbuckle.Swagger;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Web;
using System.Web.Http;
using System.Web.Http.Controllers;
using System.Web.Http.Filters;
using Utils;

namespace WebApiDemo
{
    /// <summary>
    /// 拦截器
    /// </summary>
    public class MyActionFilter : ActionFilterAttribute
    {
        #region 变量
        private Dictionary<string, string> _dictActionDesc = ServiceHelper.Get<Dictionary<string, string>>(() => XmlUtil.GetActionDesc());
        #endregion

        #region OnActionExecuting 执行方法前
        /// <summary>
        /// 执行方法前
        /// </summary>
        public override void OnActionExecuting(HttpActionContext actionContext)
        {
            base.OnActionExecuting(actionContext);

            //token验证
            Collection<AllowAnonymousAttribute> attributes = actionContext.ActionDescriptor.GetCustomAttributes<AllowAnonymousAttribute>();
            if (attributes.Count == 0)
            {
                IEnumerable<string> value;
                if (actionContext.Request.Headers.TryGetValues("token", out value))
                {
                    string token = value.ToArray()[0];

                    if (false) //todo:token验证
                    {
                        actionContext.Response = ApiHelper.ToJson(new JsonResult("token不匹配或已过期", ResultCode.token不匹配或已过期));
                        return;
                    }
                }
                else
                {
                    actionContext.Response = ApiHelper.ToJson(new JsonResult("请求头中不存在token", ResultCode.请求头中不存在token));
                    return;
                }
            }

            //post参数验证
            if (actionContext.Request.Method == HttpMethod.Post)
            {
                foreach (string key in actionContext.ActionArguments.Keys)
                {
                    object value = actionContext.ActionArguments[key];
                    if (value != null)
                    {
                        if (value.GetType().GetCustomAttributes(typeof(MyValidateAttribute), false).Length > 0)
                        {
                            string errMsg = null;
                            if (!ValidatePropertyUtil.Validate(value, out errMsg))
                            {
                                JsonResult jsonResult = new JsonResult(errMsg, ResultCode.参数不正确);
                                actionContext.Response = ApiHelper.ToJson(jsonResult);
                                return;
                            }
                        }
                    }
                }
            }

            //get参数验证
            if (actionContext.Request.Method == HttpMethod.Get)
            {
                foreach (string key in actionContext.ActionArguments.Keys)
                {
                    object value = actionContext.ActionArguments[key];
                    if (value != null)
                    {
                        if (key == "page")
                        {
                            if ((int)value <= 0)
                            {
                                JsonResult jsonResult = new JsonResult("page必须大于0", ResultCode.参数不正确);
                                actionContext.Response = ApiHelper.ToJson(jsonResult);
                                return;
                            }
                        }

                        if (key == "pageSize")
                        {
                            if ((int)value > 10000)
                            {
                                JsonResult jsonResult = new JsonResult("pageSize大于10000,请分页查询", ResultCode.参数不正确);
                                actionContext.Response = ApiHelper.ToJson(jsonResult);
                                return;
                            }
                        }
                    }
                }
            }
        }
        #endregion

        #region OnActionExecutedAsync 执行方法后
        /// <summary>
        /// 执行方法后
        /// </summary>
        public override Task OnActionExecutedAsync(HttpActionExecutedContext actionExecutedContext, CancellationToken cancellationToken)
        {
            return Task.Factory.StartNew(async () =>
            {
                try
                {
                    Type controllerType = actionExecutedContext.ActionContext.ControllerContext.Controller.GetType();
                    MethodInfo methodInfo = controllerType.GetMethod(actionExecutedContext.ActionContext.ActionDescriptor.ActionName);

                    string action = controllerType.FullName + "." + methodInfo.Name;

                    if (_dictActionDesc.ContainsKey(action))
                    {
                        string jsonResult = null;
                        List<string> paramList = new List<string>();
                        string param = string.Empty;
                        if (actionExecutedContext.Request.Method == HttpMethod.Post)
                        {
                            foreach (string key in actionExecutedContext.ActionContext.ActionArguments.Keys)
                            {
                                object value = actionExecutedContext.ActionContext.ActionArguments[key];
                                if (value != null && value as HttpRequestMessage == null)
                                {
                                    paramList.Add(JsonConvert.SerializeObject(value));
                                }
                            }
                            param = string.Join(",", paramList);

                            if (actionExecutedContext.Exception == null)
                            {
                                byte[] bArr = await actionExecutedContext.ActionContext.Response.Content.ReadAsByteArrayAsync();
                                jsonResult = Encoding.UTF8.GetString(bArr);
                            }
                            else
                            {
                                JsonResult jr = new JsonResult(actionExecutedContext.Exception.Message + "\r\n" + actionExecutedContext.Exception.StackTrace, ResultCode.服务器内部错误);
                                jsonResult = JsonConvert.SerializeObject(jr);
                            }
                        }
                        else
                        {
                            foreach (string key in actionExecutedContext.ActionContext.ActionArguments.Keys)
                            {
                                object value = actionExecutedContext.ActionContext.ActionArguments[key];
                                if (value != null)
                                {
                                    paramList.Add(key + "=" + value.ToString());
                                }
                                else
                                {
                                    paramList.Add(key + "=");
                                }
                            }
                            param = string.Join("&", paramList);

                            if (actionExecutedContext.Exception == null)
                            {
                                if (actionExecutedContext.ActionContext.Response.Content is StringContent)
                                {
                                    byte[] bArr = await actionExecutedContext.ActionContext.Response.Content.ReadAsByteArrayAsync();
                                    jsonResult = Encoding.UTF8.GetString(bArr);
                                }
                                else
                                {
                                    jsonResult = JsonConvert.SerializeObject(new JsonResult<object>(null));
                                }
                            }
                            else
                            {
                                JsonResult jr = new JsonResult(actionExecutedContext.Exception.Message + "\r\n" + actionExecutedContext.Exception.StackTrace, ResultCode.服务器内部错误);
                                jsonResult = JsonConvert.SerializeObject(jr);
                            }
                        }

                        string ip = null;
                        if (actionExecutedContext.Request.Properties.ContainsKey("MS_OwinContext"))
                        {
                            OwinContext owinContext = actionExecutedContext.Request.Properties["MS_OwinContext"] as OwinContext;
                            if (owinContext != null)
                            {
                                try
                                {
                                    ip = owinContext.Request.RemoteIpAddress;
                                }
                                catch { }
                            }
                        }

                        //todo:写操作日志
                        /*
                        ServiceHelper.Get<SysOperLogDal>().Log(action, //方法名
                            actionExecutedContext.Request.Method, //请求类型
                            _dictActionDesc[action], //方法注释
                            ip, //IP
                            actionExecutedContext.Request.RequestUri.LocalPath, //URL
                            param, //请求参数
                            jsonResult); //操作结果
                        */
                    }
                }
                catch (Exception ex)
                {
                    LogUtil.Error(ex, "MyActionFilter OnActionExecutedAsync 写操作日志出错");
                }
            });
        }
        #endregion

    }
}
View Code

参数校验工具类

这里只做了必填和日期校验,且字段类型只是基础类型,有待完善

using Models;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Web;

namespace Utils
{
    /// <summary>
    /// 字段属性验证工具类
    /// </summary>
    public class ValidatePropertyUtil
    {
        /// <summary>
        /// 验证数据 
        /// true:验证通过 false 验证不通过
        /// </summary>
        /// <param name="data">数据</param>
        /// <param name="errMsg">错误信息</param>
        public static bool Validate(object data, out string errMsg)
        {
            PropertyInfo[] propertyInfoList = data.GetType().GetProperties();
            foreach (PropertyInfo propertyInfo in propertyInfoList)
            {
                if (propertyInfo.GetCustomAttributes(typeof(RequiredAttribute), false).Length > 0)
                {
                    object value = propertyInfo.GetValue(data);
                    if (value == null)
                    {
                        errMsg = "属性 " + propertyInfo.Name + " 必填";
                        return false;
                    }
                }

                object[] attrArr = propertyInfo.GetCustomAttributes(typeof(DateTimeAttribute), false);
                if (attrArr.Length > 0)
                {
                    DateTimeAttribute attr = attrArr[0] as DateTimeAttribute;
                    object value = propertyInfo.GetValue(data);
                    if (value == null)
                    {
                        errMsg = "属性 " + propertyInfo.Name + " 是日期时间格式,格式:" + attr.Format;
                        return false;
                    }
                    else
                    {
                        DateTime dt;
                        if (!DateTime.TryParseExact(value.ToString(), attr.Format, CultureInfo.InvariantCulture, DateTimeStyles.None, out dt))
                        {
                            errMsg = "属性 " + propertyInfo.Name + " 是日期时间格式,格式:" + attr.Format;
                            return false;
                        }
                    }
                }
            }

            errMsg = null;
            return true;
        }
    }
}
View Code

swagger.js

复制到输出目录:不复制

生成操作:嵌入的资源

var SwaggerTranslator = (function () {
    //定时执行检测是否转换成中文,最多执行500次  即500*50/1000=25s
    var _iexcute = 0;

    var _lock = false;

    //中文语言包
    var _words = {
        "Warning: Deprecated": "警告:已过时",
        "Implementation Notes": "实现备注",
        "Response Class": "响应类",
        "Status": "状态",
        "Parameters": "参数",
        "Parameter": "参数",
        "Value": "值",
        "Description": "描述",
        "Parameter Type": "参数类型",
        "Data Type": "数据类型",
        "Response Messages": "响应消息",
        "HTTP Status Code": "HTTP状态码",
        "Reason": "原因",
        "Response Model": "响应模型",
        "Request URL": "请求URL",
        "Response Body": "响应体",
        "Response Code": "响应码",
        "Response Headers": "响应头",
        "Hide Response": "隐藏响应",
        "Headers": "头",
        "Try it out!": "试一下!",
        "Example Value": "示例值",
        "Show/Hide": "显示/隐藏",
        "List Operations": "显示操作",
        "Expand Operations": "展开操作",
        "Raw": "原始",
        "can't parse JSON.  Raw result": "无法解析JSON. 原始结果",
        "Model Schema": "模型架构",
        "Model": "模型",
        "apply": "应用",
        "Username": "用户名",
        "Password": "密码",
        "Terms of service": "服务条款",
        "Created by": "创建者",
        "See more at": "查看更多:",
        "Contact the developer": "联系开发者",
        "api version": "api版本",
        "Response Content Type": "响应Content Type",
        "fetching resource": "正在获取资源",
        "fetching resource list": "正在获取资源列表",
        "Explore": "浏览",
        "Show Swagger Petstore Example Apis": "显示 Swagger Petstore 示例 Apis",
        "Can't read from server.  It may not have the appropriate access-control-origin settings.": "无法从服务器读取。可能没有正确设置access-control-origin。",
        "Please specify the protocol for": "请指定协议:",
        "Can't read swagger JSON from": "无法读取swagger JSON于",
        "Finished Loading Resource Information. Rendering Swagger UI": "已加载资源信息。正在渲染Swagger UI",
        "Unable to read api": "无法读取api",
        "from path": "从路径",
        "Click to set as parameter value": "点击设置参数",
        "server returned": "服务器返回"
    };

    //定时执行转换
    var _translator2Cn = function () {
        if ($("#resources_container .resource").length > 0) {
            _tryTranslate();
        }

        if ($("#explore").text() == "Explore" && _iexcute < 500) {
            _iexcute++;
            setTimeout(_translator2Cn, 50);
        }
    };

    //设置控制器注释
    var _setControllerSummary = function () {
        if (!_lock) {
            _lock = true;
            $.ajax({
                type: "get",
                async: true,
                url: $("#input_baseUrl").val(),
                dataType: "json",
                success: function (data) {
                    var summaryDict = data.ControllerDesc;
                    var id, controllerName, strSummary;
                    $("#resources_container .resource").each(function (i, item) {
                        id = $(item).attr("id");
                        if (id) {
                            controllerName = id.substring(9);
                            strSummary = summaryDict[controllerName];
                            if (strSummary) {
                                $(item).children(".heading").children(".options").prepend('<li class="controller-summary" title="' + strSummary + '">' + strSummary + '</li>');
                            }
                        }
                    });

                    setTimeout(function () {
                        _lock = false;
                    }, 100);
                }
            });
        }
    };

    //尝试将英文转换成中文
    var _tryTranslate = function () {
        $('[data-sw-translate]').each(function () {
            $(this).html(_getLangDesc($(this).html()));
            $(this).val(_getLangDesc($(this).val()));
            $(this).attr('title', _getLangDesc($(this).attr('title')));
        });
    };

    var _getLangDesc = function (word) {
        return _words[$.trim(word)] !== undefined ? _words[$.trim(word)] : word;
    };

    return {
        translate: function () {
            document.title = "API描述文档";
            $('body').append('<style type="text/css">.controller-summary{color:#10a54a !important;word-break:keep-all;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:250px;text-align:right;cursor:default;} </style>');
            $("#logo").html("接口描述").attr("href", "/swagger/ui/index");
            //设置控制器描述
            _setControllerSummary();
            _translator2Cn();
        }
    };
})();

//执行转换
SwaggerTranslator.translate();
View Code

CachingSwaggerProvider.cs

using Swashbuckle.Swagger;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Web;
using System.Xml;

namespace WebApiDemo
{
    /// <summary>
    /// 用于汉化Swagger
    /// </summary>
    public class CachingSwaggerProvider : ISwaggerProvider
    {
        private static ConcurrentDictionary<string, SwaggerDocument> _cache = new ConcurrentDictionary<string, SwaggerDocument>();

        private readonly ISwaggerProvider _swaggerProvider;

        /// <summary>
        /// 构造函数
        /// </summary>
        public CachingSwaggerProvider(ISwaggerProvider swaggerProvider)
        {
            _swaggerProvider = swaggerProvider;
        }

        /// <summary>
        /// GetSwagger
        /// </summary>
        public SwaggerDocument GetSwagger(string rootUrl, string apiVersion)
        {
            try
            {
                var cacheKey = string.Format("{0}_{1}", rootUrl, apiVersion);
                SwaggerDocument srcDoc = null;
                //只读取一次
                if (!_cache.TryGetValue(cacheKey, out srcDoc))
                {
                    srcDoc = _swaggerProvider.GetSwagger(rootUrl, apiVersion);

                    srcDoc.vendorExtensions = new Dictionary<string, object> { { "ControllerDesc", GetControllerDesc() } };
                    _cache.TryAdd(cacheKey, srcDoc);
                }
                return srcDoc;
            }
            catch
            {
                SwaggerDocument doc = new SwaggerDocument();
                doc.info = new Info();
                doc.info.title = "接口不存在";
                return doc;
            }
        }

        /// <summary>
        /// 从API文档中读取控制器描述
        /// </summary>
        /// <returns>所有控制器描述</returns>
        public static ConcurrentDictionary<string, string> GetControllerDesc()
        {
            string xmlpath = string.Format("{0}/{1}.XML", System.AppDomain.CurrentDomain.BaseDirectory, typeof(SwaggerConfig).Assembly.GetName().Name);
            ConcurrentDictionary<string, string> controllerDescDict = new ConcurrentDictionary<string, string>();
            if (File.Exists(xmlpath))
            {
                XmlDocument xmldoc = new XmlDocument();
                xmldoc.Load(xmlpath);
                string type = string.Empty, path = string.Empty, controllerName = string.Empty;

                string[] arrPath;
                int length = -1, cCount = "Controller".Length;
                XmlNode summaryNode = null;
                foreach (XmlNode node in xmldoc.SelectNodes("//member"))
                {
                    type = node.Attributes["name"].Value;
                    if (type.StartsWith("T:"))
                    {
                        //控制器
                        arrPath = type.Split('.');
                        length = arrPath.Length;
                        controllerName = arrPath[length - 1];
                        if (controllerName.EndsWith("Controller"))
                        {
                            //获取控制器注释
                            summaryNode = node.SelectSingleNode("summary");
                            string key = controllerName.Remove(controllerName.Length - cCount, cCount);
                            if (summaryNode != null && !string.IsNullOrEmpty(summaryNode.InnerText) && !controllerDescDict.ContainsKey(key))
                            {
                                controllerDescDict.TryAdd(key, summaryNode.InnerText.Trim());
                            }
                        }
                    }
                }
            }
            return controllerDescDict;
        }

    }
}
View Code

SwaggerOperationFilter.cs

文件上传与token参数

using Swashbuckle.Swagger;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Web;
using System.Web.Http;
using System.Web.Http.Description;

namespace WebApiDemo
{
    public class SwaggerOperationFilter : IOperationFilter
    {
        public void Apply(Operation operation, SchemaRegistry schemaRegistry, ApiDescription apiDescription)
        {
            if (operation.parameters == null) operation.parameters = new List<Parameter>();

            if (apiDescription.RelativePath.Contains("/UploadFile"))
            {
                operation.parameters.RemoveAt(0);

                operation.parameters.Add(new Parameter
                {
                    name = "folder",
                    @in = "formData",
                    description = "文件夹",
                    required = false,
                    type = "string"
                });

                operation.parameters.Add(new Parameter
                {
                    name = "file",
                    @in = "formData",
                    description = "文件",
                    required = true,
                    type = "file"
                });

                operation.consumes.Add("multipart/form-data");
            }

            Collection<AllowAnonymousAttribute> attributes = apiDescription.ActionDescriptor.GetCustomAttributes<AllowAnonymousAttribute>();
            if (attributes.Count == 0)
            {
                operation.parameters.Insert(0, new Parameter { name = "token", @in = "header", description = "Token", required = true, type = "string" });
            }
        }
    }
}
View Code

SwaggerConfig.cs

using System.Web.Http;
using Swashbuckle.Application;
using System.IO;
using WebApiDemo;
using System.Web;

[assembly: PreApplicationStartMethod(typeof(SwaggerConfig), "Register")]

namespace WebApiDemo
{
    public class SwaggerConfig
    {
        public static void Register(HttpConfiguration config)
        {
            var thisAssembly = typeof(SwaggerConfig).Assembly;

            config
                .EnableSwagger(c =>
                    {
                        // By default, the service root url is inferred from the request used to access the docs.
                        // However, there may be situations (e.g. proxy and load-balanced environments) where this does not
                        // resolve correctly. You can workaround this by providing your own code to determine the root URL.
                        //
                        //c.RootUrl(req => GetRootUrlFromAppConfig());

                        // If schemes are not explicitly provided in a Swagger 2.0 document, then the scheme used to access
                        // the docs is taken as the default. If your API supports multiple schemes and you want to be explicit
                        // about them, you can use the "Schemes" option as shown below.
                        //
                        //c.Schemes(new[] { "http", "https" });

                        // Use "SingleApiVersion" to describe a single version API. Swagger 2.0 includes an "Info" object to
                        // hold additional metadata for an API. Version and title are required but you can also provide
                        // additional fields by chaining methods off SingleApiVersion.
                        //
                        c.SingleApiVersion("v1", "WebApiDemo 测试接口文档");

                        c.OperationFilter<SwaggerOperationFilter>(); //添加过滤器,增加Token令牌验证

                        c.IncludeXmlComments(Path.Combine(System.AppDomain.CurrentDomain.BaseDirectory, @"WebApiDemo.XML"));

                        c.CustomProvider((defaultProvider) => new CachingSwaggerProvider(defaultProvider)); //汉化Swagger两步:第一步

                        // If you want the output Swagger docs to be indented properly, enable the "PrettyPrint" option.
                        //
                        //c.PrettyPrint();

                        // If your API has multiple versions, use "MultipleApiVersions" instead of "SingleApiVersion".
                        // In this case, you must provide a lambda that tells Swashbuckle which actions should be
                        // included in the docs for a given API version. Like "SingleApiVersion", each call to "Version"
                        // returns an "Info" builder so you can provide additional metadata per API version.
                        //
                        //c.MultipleApiVersions(
                        //    (apiDesc, targetApiVersion) => ResolveVersionSupportByRouteConstraint(apiDesc, targetApiVersion),
                        //    (vc) =>
                        //    {
                        //        vc.Version("v2", "Swashbuckle Dummy API V2");
                        //        vc.Version("v1", "Swashbuckle Dummy API V1");
                        //    });

                        // You can use "BasicAuth", "ApiKey" or "OAuth2" options to describe security schemes for the API.
                        // See https://github.com/swagger-api/swagger-spec/blob/master/versions/2.0.md for more details.
                        // NOTE: These only define the schemes and need to be coupled with a corresponding "security" property
                        // at the document or operation level to indicate which schemes are required for an operation. To do this,
                        // you'll need to implement a custom IDocumentFilter and/or IOperationFilter to set these properties
                        // according to your specific authorization implementation
                        //
                        //c.BasicAuth("basic")
                        //    .Description("Basic HTTP Authentication");
                        //
                        // NOTE: You must also configure 'EnableApiKeySupport' below in the SwaggerUI section
                        //c.ApiKey("apiKey")
                        //    .Description("API Key Authentication")
                        //    .Name("apiKey")
                        //    .In("header");
                        //
                        //c.OAuth2("oauth2")
                        //    .Description("OAuth2 Implicit Grant")
                        //    .Flow("implicit")
                        //    .AuthorizationUrl("http://petstore.swagger.wordnik.com/api/oauth/dialog")
                        //    //.TokenUrl("https://tempuri.org/token")
                        //    .Scopes(scopes =>
                        //    {
                        //        scopes.Add("read", "Read access to protected resources");
                        //        scopes.Add("write", "Write access to protected resources");
                        //    });

                        // Set this flag to omit descriptions for any actions decorated with the Obsolete attribute
                        //c.IgnoreObsoleteActions();

                        // Each operation be assigned one or more tags which are then used by consumers for various reasons.
                        // For example, the swagger-ui groups operations according to the first tag of each operation.
                        // By default, this will be controller name but you can use the "GroupActionsBy" option to
                        // override with any value.
                        //
                        //c.GroupActionsBy(apiDesc => apiDesc.HttpMethod.ToString());

                        // You can also specify a custom sort order for groups (as defined by "GroupActionsBy") to dictate
                        // the order in which operations are listed. For example, if the default grouping is in place
                        // (controller name) and you specify a descending alphabetic sort order, then actions from a
                        // ProductsController will be listed before those from a CustomersController. This is typically
                        // used to customize the order of groupings in the swagger-ui.
                        //
                        //c.OrderActionGroupsBy(new DescendingAlphabeticComparer());

                        // If you annotate Controllers and API Types with
                        // Xml comments (http://msdn.microsoft.com/en-us/library/b2s063f7(v=vs.110).aspx), you can incorporate
                        // those comments into the generated docs and UI. You can enable this by providing the path to one or
                        // more Xml comment files.
                        //
                        //c.IncludeXmlComments(GetXmlCommentsPath());

                        // Swashbuckle makes a best attempt at generating Swagger compliant JSON schemas for the various types
                        // exposed in your API. However, there may be occasions when more control of the output is needed.
                        // This is supported through the "MapType" and "SchemaFilter" options:
                        //
                        // Use the "MapType" option to override the Schema generation for a specific type.
                        // It should be noted that the resulting Schema will be placed "inline" for any applicable Operations.
                        // While Swagger 2.0 supports inline definitions for "all" Schema types, the swagger-ui tool does not.
                        // It expects "complex" Schemas to be defined separately and referenced. For this reason, you should only
                        // use the "MapType" option when the resulting Schema is a primitive or array type. If you need to alter a
                        // complex Schema, use a Schema filter.
                        //
                        //c.MapType<ProductType>(() => new Schema { type = "integer", format = "int32" });

                        // If you want to post-modify "complex" Schemas once they've been generated, across the board or for a
                        // specific type, you can wire up one or more Schema filters.
                        //
                        //c.SchemaFilter<ApplySchemaVendorExtensions>();

                        // In a Swagger 2.0 document, complex types are typically declared globally and referenced by unique
                        // Schema Id. By default, Swashbuckle does NOT use the full type name in Schema Ids. In most cases, this
                        // works well because it prevents the "implementation detail" of type namespaces from leaking into your
                        // Swagger docs and UI. However, if you have multiple types in your API with the same class name, you'll
                        // need to opt out of this behavior to avoid Schema Id conflicts.
                        //
                        //c.UseFullTypeNameInSchemaIds();

                        // Alternatively, you can provide your own custom strategy for inferring SchemaId's for
                        // describing "complex" types in your API.
                        //
                        //c.SchemaId(t => t.FullName.Contains('`') ? t.FullName.Substring(0, t.FullName.IndexOf('`')) : t.FullName);

                        // Set this flag to omit schema property descriptions for any type properties decorated with the
                        // Obsolete attribute
                        //c.IgnoreObsoleteProperties();

                        // In accordance with the built in JsonSerializer, Swashbuckle will, by default, describe enums as integers.
                        // You can change the serializer behavior by configuring the StringToEnumConverter globally or for a given
                        // enum type. Swashbuckle will honor this change out-of-the-box. However, if you use a different
                        // approach to serialize enums as strings, you can also force Swashbuckle to describe them as strings.
                        //
                        //c.DescribeAllEnumsAsStrings();

                        // Similar to Schema filters, Swashbuckle also supports Operation and Document filters:
                        //
                        // Post-modify Operation descriptions once they've been generated by wiring up one or more
                        // Operation filters.
                        //
                        //c.OperationFilter<AddDefaultResponse>();
                        //
                        // If you've defined an OAuth2 flow as described above, you could use a custom filter
                        // to inspect some attribute on each action and infer which (if any) OAuth2 scopes are required
                        // to execute the operation
                        //
                        //c.OperationFilter<AssignOAuth2SecurityRequirements>();

                        // Post-modify the entire Swagger document by wiring up one or more Document filters.
                        // This gives full control to modify the final SwaggerDocument. You should have a good understanding of
                        // the Swagger 2.0 spec. - https://github.com/swagger-api/swagger-spec/blob/master/versions/2.0.md
                        // before using this option.
                        //
                        //c.DocumentFilter<ApplyDocumentVendorExtensions>();

                        // In contrast to WebApi, Swagger 2.0 does not include the query string component when mapping a URL
                        // to an action. As a result, Swashbuckle will raise an exception if it encounters multiple actions
                        // with the same path (sans query string) and HTTP method. You can workaround this by providing a
                        // custom strategy to pick a winner or merge the descriptions for the purposes of the Swagger docs
                        //
                        //c.ResolveConflictingActions(apiDescriptions => apiDescriptions.First());

                        // Wrap the default SwaggerGenerator with additional behavior (e.g. caching) or provide an
                        // alternative implementation for ISwaggerProvider with the CustomProvider option.
                        //
                        //c.CustomProvider((defaultProvider) => new CachingSwaggerProvider(defaultProvider));
                    })
                .EnableSwaggerUi(c =>
                    {
                        // Use the "DocumentTitle" option to change the Document title.
                        // Very helpful when you have multiple Swagger pages open, to tell them apart.
                        //
                        //c.DocumentTitle("My Swagger UI");

                        // Use the "InjectStylesheet" option to enrich the UI with one or more additional CSS stylesheets.
                        // The file must be included in your project as an "Embedded Resource", and then the resource's
                        // "Logical Name" is passed to the method as shown below.
                        //
                        //c.InjectStylesheet(containingAssembly, "Swashbuckle.Dummy.SwaggerExtensions.testStyles1.css");

                        // Use the "InjectJavaScript" option to invoke one or more custom JavaScripts after the swagger-ui
                        // has loaded. The file must be included in your project as an "Embedded Resource", and then the resource's
                        // "Logical Name" is passed to the method as shown above.
                        //
                        //c.InjectJavaScript(thisAssembly, "Swashbuckle.Dummy.SwaggerExtensions.testScript1.js");

                        // The swagger-ui renders boolean data types as a dropdown. By default, it provides "true" and "false"
                        // strings as the possible choices. You can use this option to change these to something else,
                        // for example 0 and 1.
                        //
                        //c.BooleanValues(new[] { "0", "1" });

                        // By default, swagger-ui will validate specs against swagger.io's online validator and display the result
                        // in a badge at the bottom of the page. Use these options to set a different validator URL or to disable the
                        // feature entirely.
                        //c.SetValidatorUrl("http://localhost/validator");
                        //c.DisableValidator();

                        // Use this option to control how the Operation listing is displayed.
                        // It can be set to "None" (default), "List" (shows operations for each resource),
                        // or "Full" (fully expanded: shows operations and their details).
                        //
                        //c.DocExpansion(DocExpansion.List);

                        // Specify which HTTP operations will have the 'Try it out!' option. An empty paramter list disables
                        // it for all operations.
                        //
                        //c.SupportedSubmitMethods("GET", "HEAD");

                        // Use the CustomAsset option to provide your own version of assets used in the swagger-ui.
                        // It's typically used to instruct Swashbuckle to return your version instead of the default
                        // when a request is made for "index.html". As with all custom content, the file must be included
                        // in your project as an "Embedded Resource", and then the resource's "Logical Name" is passed to
                        // the method as shown below.
                        //
                        //c.CustomAsset("index", containingAssembly, "YourWebApiProject.SwaggerExtensions.index.html");

                        // If your API has multiple versions and you've applied the MultipleApiVersions setting
                        // as described above, you can also enable a select box in the swagger-ui, that displays
                        // a discovery URL for each version. This provides a convenient way for users to browse documentation
                        // for different API versions.
                        //
                        //c.EnableDiscoveryUrlSelector();

                        // If your API supports the OAuth2 Implicit flow, and you've described it correctly, according to
                        // the Swagger 2.0 specification, you can enable UI support as shown below.
                        //
                        //c.EnableOAuth2Support(
                        //    clientId: "test-client-id",
                        //    clientSecret: null,
                        //    realm: "test-realm",
                        //    appName: "Swagger UI"
                        //    //additionalQueryStringParams: new Dictionary<string, string>() { { "foo", "bar" } }
                        //);

                        // If your API supports ApiKey, you can override the default values.
                        // "apiKeyIn" can either be "query" or "header"
                        //
                        //c.EnableApiKeySupport("apiKey", "header");

                        c.InjectJavaScript(thisAssembly, "WebApiDemo.Swagger.swagger.js"); //汉化Swagger两步:第二步
                    });
        }
    }
}
View Code

辅助类XmlUtil.cs

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Web;
using System.Xml;

namespace Utils
{
    /// <summary>
    /// XML工具类
    /// </summary>
    public class XmlUtil
    {
        /// <summary>
        /// 从XML读取注释
        /// </summary>
        /// <returns></returns>
        public static Dictionary<string, string> GetActionDesc()
        {
            Dictionary<string, string> result = new Dictionary<string, string>();

            string xmlPath = string.Format("{0}/{1}.XML", System.AppDomain.CurrentDomain.BaseDirectory, typeof(XmlUtil).Assembly.GetName().Name);
            if (File.Exists(xmlPath))
            {
                XmlDocument xmlDoc = new XmlDocument();
                xmlDoc.Load(xmlPath);

                XmlNode summaryNode; string type; string desc; int pos; string key;
                foreach (XmlNode node in xmlDoc.SelectNodes("//member"))
                {
                    type = type = node.Attributes["name"].Value;
                    if (type.StartsWith("M:PrisonWebApi.Controllers"))
                    {
                        pos = type.IndexOf("(");
                        if (pos == -1) pos = type.Length;
                        key = type.Substring(2, pos - 2);
                        summaryNode = node.SelectSingleNode("summary");
                        desc = summaryNode.InnerText.Trim();
                        result.Add(key, desc);
                    }
                }
            }

            return result;
        }
    }
}
View Code

WebApiHost工程的App.config

WebApiDemo工程的Global.asax.cs和Web.config文件没有用了 

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <startup>
    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2" />
  </startup>
  <appSettings>
    <!--Web API 服务端口号-->
    <add key="WebApiServicePort" value="8500" />
  </appSettings>
</configuration>
View Code

 在线文档截图

 

posted @ 2020-06-23 19:20  0611163  阅读(1231)  评论(5编辑  收藏  举报