使用 OWIN Self-Host ASP.NET Web API
https://learn.microsoft.com/zh-cn/aspnet/web-api/overview/hosting-aspnet-web-api/use-owin-to-self-host-web-api
https://www.cnblogs.com/s0611163/p/13175721.html

新建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();
}
}
}

 启动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 }
}

配置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);
}
}
}

接口实现

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 }

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;
} }
}

 辅助类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;
} }
}

 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; }
}
}

异常拦截器

异常在这里统一处理,接口方法中不需要再加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);
}
}
}

方法拦截器

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 }
}

参数校验工具类

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

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;
}
}
}

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();

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;
} }
}

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" });
}
}
}
}

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两步:第二步
});
}
}
}

辅助类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;
}
}
}

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>

在线文档截图

使用 OWIN Self-Host ASP.NET Web API 自宿主 Swagger Swashbuckle 在线文档的更多相关文章

  1. ASP.NET Web API根据代码注释生成Help文档

    使用Visual Studio新建一个ASP.NET Web API项目,直接运行,查看Help文档可以看到如下的API帮助说明 如何在Description中显示描述. 1. 打开Controlle ...

  2. 使用 OWIN 作为 ASP.NET Web API 的宿主

    使用 OWIN 作为 ASP.NET Web API 的宿主 ASP.NET Web API 是一种框架,用于轻松构建可以访问多种客户端(包括浏览器和移动 设备)的 HTTP 服务. ASP.NET ...

  3. Use OWIN to Self-Host ASP.NET Web API 2

      Open Web Interface for .NET (OWIN)在Web服务器和Web应用程序之间建立一个抽象层.OWIN将网页应用程序从网页服务器分离出来,然后将应用程序托管于OWIN的程序 ...

  4. ASP.NET Web API WebHost宿主环境中管道、路由

    ASP.NET Web API WebHost宿主环境中管道.路由 前言 上篇中说到ASP.NET Web API框架在SelfHost环境中管道.路由的一个形态,本篇就来说明一下在WebHost环境 ...

  5. ASP.NET Web API Selfhost宿主环境中管道、路由

    ASP.NET Web API Selfhost宿主环境中管道.路由 前言 前面的几个篇幅对Web API中的路由和管道进行了简单的介绍并没有详细的去说明一些什么,然而ASP.NET Web API这 ...

  6. ASP.NET Web API 中使用 swagger 来管理 API 文档

    本文以 ASP.NET Web API 为后台框架,利用 EF6 连接 postgreSQL 数据库,使用 swagger 来生成 REST APIs文档.文章分二个部分,第一部分主要讲如何用 EF6 ...

  7. Asp.Net Web Api中使用Swagger

    关于swagger 设计是API开发的基础.Swagger使API设计变得轻而易举,为开发人员.架构师和产品所有者提供了易于使用的工具. 官方网址:https://swagger.io/solutio ...

  8. 为IIS Host ASP.NET Web Api添加Owin Middleware

    将OWIN App部署在IIS上 要想将Owin App部署在IIS上,只添加Package:Microsoft.OWIN.Host.SystemWeb包即可.它提供了所有Owin配置,Middlew ...

  9. Owin 自寄宿 asp.net web api

    http://owin.org/ Owin 定义了webserver和webapplication之间的标准接口,目标就是为了解耦webapplication对webserver的依赖, 就是说以后可 ...

  10. Use OWIN to Self-Host ASP.NET Web API 2 来访问我的webapi

    就是说我们本地的http://localhost:49708/api/test可以通过 这个东西来访问(懒得挂载iis,当然它的强大可不这些,由于测试出了问题 出记录一下) 首先去Nuget包里找到M ...

随机推荐

  1. HDD杭州站•ArkUI让开发更灵活

    原文:https://mp.weixin.qq.com/s/cX48CPs61daKOC2J5znyJw,点击链接查看更多技术内容. 7月15日的HUAWEI Developer Day(简称HDD) ...

  2. android android7以上无法连接蓝牙

    前言 在开发android 蓝牙的时候,发现一个问题,那就是android7无法连接上蓝牙. 原因 <!-- 管理蓝牙设备的权限 --> <uses-permission andro ...

  3. mysql 必知必会整理—sql 排序与过滤[三]

    前言 简单整理一下MySQL的排序与过滤. 正文 我们查询出来的结果有时候是希望进行排序的,比如说: select product_name from products order by prod_n ...

  4. 如何快速实现Prometheus监控Kubernetes集群

    Prometheus K8S集群中常见的监控工具有哪些: Kubernetes Dashboard Kube-monkey K8s-testsuite Kubespray Minikube Prome ...

  5. 国内首家!百度智能云宣布支持Llama3全系列训练推理

    继18日Llama3的8B.70B大模型发布后,百度智能云千帆大模型平台19日宣布在国内首家推出针对Llama3全系列版本的训练推理方案,便于开发者进行再训练,搭建专属大模型,现已开放邀约测试. 目前 ...

  6. 力扣38(java)-外观数列(中等)

    题目: 给定一个正整数 n ,输出外观数列的第 n 项. 「外观数列」是一个整数序列,从数字 1 开始,序列中的每一项都是对前一项的描述. 你可以将其视作是由递归公式定义的数字字符串序列: count ...

  7. RAG 工具和框架介绍: Haystack、 LangChain 和 LlamaIndex

    Haystack. LangChain 和 LlamaIndex,以及这些工具是如何让我们轻松地构建 RAG 应用程序的? 我们将重点关注以下内容: Haystack LangChain LlamaI ...

  8. WPF 触摸底层 PenImc 是如何工作的

    在 WPF 里面有其他软件完全比不上的超快速的触摸,这个触摸是通过 PenImc 获取的.现在 WPF 开源了,本文就带大家来阅读触摸底层的代码,阅读本文需要一点 C# 和 C++ 基础 现在 WPF ...

  9. 2019-9-30-dotnet-枚举当前设备wifi热点

    title author date CreateTime categories dotnet 枚举当前设备wifi热点 lindexi 2019-09-30 14:42:18 +0800 2019-9 ...

  10. JavaScript字符串String方法介绍及使用示例

    实例方法 charAt() charCodeAt() 返回索引位置的字符 'hello'.charAt(0) //h 等价 'hello'.[0] //返回索引位置的字符的Unicode码点 'hel ...