afresh/ASP.NET-Web-Api-Swagger
using System.Collections.Generic;
using System.Text.RegularExpressions;
using System.Web.Http.Description;
using Swashbuckle.Swagger;
namespace WebYKT.Web.Swaggers
{
internal class ApplyDocumentVendorExtensions : IDocumentFilter
{
///
/// //swagger版本控制过滤
///
/// 文档
/// schema注册
/// api概览
public void Apply(SwaggerDocument swaggerDoc, SchemaRegistry schemaRegistry, IApiExplorer apiExplorer)
{
//缓存目标路由api
IDictionary<string, PathItem> match = new Dictionary<string, PathItem>();
//取版本
var version = swaggerDoc.info.version;
foreach (var path in swaggerDoc.paths)
{
//过滤命名空间 按名称空间区分版本
if (path.Key.Contains(string.Format("/{0}/", version)))
{
//匹配controller descript中的版本信息
Regex r = new Regex("/\\w+" + version, RegexOptions.IgnoreCase);
string newKey = path.Key;
if (r.IsMatch(path.Key))
{
var routeinfo = r.Match(path.Key).Value;
//修正controller别名路由符合RoutePrefix配置的路由 如api/v2/ValuesV2 修正为 api/v2/Values
newKey = path.Key.Replace(routeinfo, routeinfo.Replace(version.ToLower(), "")).Replace(
routeinfo, routeinfo.Replace(version.ToUpper(), ""));
}
//保存修正的path
match.Add(newKey, path.Value);
}
}
//当前版本的swagger document
swaggerDoc.paths = match;
}
}
}
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Xml;
using Swashbuckle.Swagger;
namespace WebYKT.Web.Swaggers
{
public class CachingSwaggerProvider : ISwaggerProvider
{
private static ConcurrentDictionary<string, SwaggerDocument> _cache =
new ConcurrentDictionary<string, SwaggerDocument>();
private readonly ISwaggerProvider _swaggerProvider;
public CachingSwaggerProvider(ISwaggerProvider swaggerProvider)
{
_swaggerProvider = swaggerProvider;
}
public SwaggerDocument GetSwagger(string rootUrl, string apiVersion)
{
var cacheKey = String.Format("{0}_{1}", rootUrl, apiVersion);
SwaggerDocument srcDoc;
//只读取一次
if (!_cache.TryGetValue(cacheKey, out srcDoc))
{
srcDoc = _swaggerProvider.GetSwagger(rootUrl, apiVersion);
var patht = new Dictionary<string, PathItem>();
foreach (var item in srcDoc.paths)
{
var arr = item.Key.Split('/');
var i = arr[3].LastIndexOf('.') + 1;
if (i != -1)
{
arr[3] = arr[3].Substring(i);
}
patht.Add(string.Join("/", arr), item.Value);
}
srcDoc.paths = patht;
HashSet<string> moduleList = new HashSet<string>();
srcDoc.vendorExtensions = new Dictionary<string, object>
{
{"ControllerDesc", GetControllerDesc(moduleList)},
{"AreaDescription", moduleList}
};
_cache.TryAdd(cacheKey, srcDoc);
}
return srcDoc;
}
///
/// 从API文档中读取控制器描述
///
/// 所有控制器描述
public static ConcurrentDictionary<string, string> GetControllerDesc(HashSet<string> moduleList)
{
string xmlpath = String.Format("{0}/bin/swagger.XML", AppDomain.CurrentDomain.BaseDirectory);
ConcurrentDictionary<string, string> controllerDescDict = new ConcurrentDictionary<string, string>();
if (File.Exists(xmlpath))
{
XmlDocument xmldoc = new XmlDocument();
xmldoc.Load(xmlpath);
int cCount = "Controller".Length;
foreach (XmlNode node in xmldoc.SelectNodes("//member"))
{
var type = node.Attributes["name"].Value;
if (type.StartsWith("T:"))
{
//控制器
var arrPath = type.Split('.');
var length = arrPath.Length;
var controllerName = arrPath[length - 1];
if (controllerName.EndsWith("Controller"))
{
//模块信息
var moduleName = arrPath[length - 2];
moduleList.Add(moduleName);
//获取控制器注释
var 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;
}
}
}
using System;
using System.Linq;
using System.Web.Http.Description;
namespace WebApiSwagger.Swaggers
{
public class SwaggerVersionHelper
{
public static bool ResolveVersionSupportByRouteConstraint(ApiDescription apiDesc, string targetApiVersion)
{
var attr = apiDesc.ActionDescriptor.ControllerDescriptor.GetCustomAttributes<VersionedRoute>().FirstOrDefault();
return attr != null && attr.Version == Convert.ToInt32(targetApiVersion.TrimStart('v'));
}
}
}
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http;
using System.Web.Http.Controllers;
using System.Web.Http.Dispatcher;
using System.Web.Http.Routing;
namespace WebYKT.Web.Swaggers
{
public class VersionControllerSelector : IHttpControllerSelector
{
private const string VersionKey = "version";
private const string ControllerKey = "controller";
private readonly HttpConfiguration _configuration;
private readonly Lazy<Dictionary<string, HttpControllerDescriptor>> _controllers;
private readonly HashSet<string> _duplicates;
public VersionControllerSelector(HttpConfiguration config)
{
_configuration = config;
_duplicates = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
_controllers = new Lazy<Dictionary<string, HttpControllerDescriptor>>(InitializeControllerDictionary);
}
private Dictionary<string, HttpControllerDescriptor> InitializeControllerDictionary()
{
var dictionary = new Dictionary<string, HttpControllerDescriptor>(StringComparer.OrdinalIgnoreCase);
IAssembliesResolver assembliesResolver = _configuration.Services.GetAssembliesResolver();
IHttpControllerTypeResolver controllersResolver = _configuration.Services.GetHttpControllerTypeResolver();
ICollection<Type> controllerTypes = controllersResolver.GetControllerTypes(assembliesResolver);
foreach (Type t in controllerTypes)
{
var segments = t.Namespace?.Split(Type.Delimiter);
var controllerName = t.Name.Remove(t.Name.Length - DefaultHttpControllerSelector.ControllerSuffix.Length);
string version = segments?[segments.Length - 1];
var key = String.Format(CultureInfo.InvariantCulture, "{0}.{1}", version, controllerName);
if (version == "Controllers")
{
key = String.Format(CultureInfo.InvariantCulture, "{0}", controllerName);
}
if (dictionary.Keys.Contains(key))
{
_duplicates.Add(key);
}
else
{
dictionary[key] = new HttpControllerDescriptor(_configuration, t.Name, t);
}
}
foreach (string s in _duplicates)
{
dictionary.Remove(s);
}
return dictionary;
}
private static T GetRouteVariable<T>(IHttpRouteData routeData, string name)
{
object result;
if (routeData.Values.TryGetValue(name, out result))
{
return (T) result;
}
return default(T);
}
public HttpControllerDescriptor SelectController(HttpRequestMessage request)
{
IHttpRouteData routeData = request.GetRouteData();
if (routeData == null)
{
throw new HttpResponseException(HttpStatusCode.NotFound);
}
string version = GetRouteVariable<string>(routeData, VersionKey);
if (string.IsNullOrEmpty(version))
{
version = GetVersionFromHTTPHeaderAndAcceptHeader(request);
}
string controllerName = GetRouteVariable<string>(routeData, ControllerKey);
if (controllerName == null)
{
throw new HttpResponseException(HttpStatusCode.NotFound);
}
string key = String.Format(CultureInfo.InvariantCulture, "{0}", controllerName);
if (!string.IsNullOrEmpty(version))
{
key = String.Format(CultureInfo.InvariantCulture, "{0}.{1}", version, controllerName);
}
HttpControllerDescriptor controllerDescriptor;
if (_controllers.Value.TryGetValue(key, out controllerDescriptor))
{
return controllerDescriptor;
}
else if (_duplicates.Contains(key))
{
throw new HttpResponseException(
request.CreateErrorResponse(HttpStatusCode.InternalServerError,
"Multiple controllers were found that match this request."));
}
else
{
throw new HttpResponseException(HttpStatusCode.NotFound);
}
}
public IDictionary<string, HttpControllerDescriptor> GetControllerMapping()
{
return _controllers.Value;
}
private string GetVersionFromHTTPHeaderAndAcceptHeader(HttpRequestMessage request)
{
if (request.Headers.Contains(VersionKey))
{
var versionHeader = request.Headers.GetValues(VersionKey).FirstOrDefault();
if (versionHeader != null)
{
return versionHeader;
}
}
var acceptHeader = request.Headers.Accept;
foreach (var mime in acceptHeader)
{
if (mime.MediaType == "application/json" || mime.MediaType == "text/html")
{
var version = mime.Parameters
.Where(v => v.Name.Equals(VersionKey, StringComparison.OrdinalIgnoreCase))
.FirstOrDefault();
if (version != null)
{
return version.Value;
}
return string.Empty;
}
}
return string.Empty;
}
}
}
using System;
namespace WebApiSwagger.Swaggers
{
[AttributeUsage(AttributeTargets.All)]
public class VersionedRoute : Attribute
{
public VersionedRoute(string name, int version)
{
Name = name;
Version = version;
}
public string Name { get; set; }
public int Version { get; set; }
}
}
c.MultipleApiVersions(
SwaggerVersionHelper.ResolveVersionSupportByRouteConstraint,
(vc) =>
{
vc.Version("v2", "Swashbuckle Dummy API V2");
vc.Version("v1", "Swashbuckle Dummy API V1");
});
c.IncludeXmlComments($"{AppDomain.CurrentDomain.BaseDirectory}/bin/WebApiSwagger.XML");
c.DocumentFilter<ApplyDocumentVendorExtensions>();
c.CustomProvider((defaultProvider) => new CachingSwaggerProvider(defaultProvider));
config.Services.Replace(typeof(IHttpControllerSelector), new VersionControllerSelector(config));
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/v2/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
config.Routes.MapHttpRoute(
name: "DefaultApiV1",
routeTemplate: "api/v1/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
//[assembly: WebActivator.PreApplicationStartMethod(typeof(WebApiSwagger.App_Start.SwaggerNet), "PreStart")]
//[assembly: WebActivator.PostApplicationStartMethod(typeof(WebApiSwagger.App_Start.SwaggerNet), "PostStart")]
using System.Web.Http;
using WebApiSwagger.Swagger;
namespace WebApiSwagger.Controllers.v2
{
///
/// 测试
///
[VersionedRoute("api/version", 2)]
public class TestController : ApiController
{
///
///
///
///
[HttpGet]
public string Test()
{
return "测试";
}
}
}
http://localhost:11124/swagger/ui/index