对外提供的接口在实际生成过程中,可能是需要一个接口版本的,比如说v1,manage。效果如下:
在swagger中怎么实现呢?
1. 添加SwaggerVersionHelper.cs
public class SwaggerVersionHelper { public static bool ResolveVersionSupportByRouteConstraint(ApiDescription apiDesc, string targetApiVersion) { var attr = apiDesc.ActionDescriptor.ControllerDescriptor.GetCustomAttributes<VersionedRoute>().FirstOrDefault(); if (attr == null) { if (targetApiVersion == "manage") { return true; } else { return false; } } int targetVersion; targetApiVersion = targetApiVersion.TrimStart('v'); if (attr.Version != 0 && int.TryParse(targetApiVersion, out targetVersion)) { return attr.Version == targetVersion; }; return false; } }
2. 添加VersionedRoute.cs
[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; } }
3. swagger配置多版本接口说明文档。
4. 在Controller中添加VersionedRoute特性。
上面个四个步骤配置好之后,就实现了接口多版本发布了。
最后还有一个问题,就是我想发布v1, v2...这些版本的接口的时候,一般来说不同的版本之间接口Controller的命名空间不同,但是ControllerName是一样的,并且AbpHttpControllerSelector在解析路由的时候是忽略命名空间的,那么错误就来了:Swagger不能准确的去解析多个一样的Controller。
解决办法:
添加一个ControllerSelector,他的作用就是通过命名空间来区分不同的Controller。
1. 添加一个NamespaceHttpControllerSelector.cs。
public class NamespaceHttpControllerSelector : AbpHttpControllerSelector, ISingletonDependency { private const string NamespaceKey = "namespace"; private const string ControllerKey = "controller"; private readonly HttpConfiguration _configuration; private readonly Lazy<Dictionary<string, HttpControllerDescriptor>> _controllers; private readonly HashSet<string> _duplicates; public NamespaceHttpControllerSelector(HttpConfiguration config, DynamicApiControllerManager dynamicApiControllerManager) : base(config, dynamicApiControllerManager) { _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); // Create a lookup table where key is "namespace.controller". The value of "namespace" is the last // segment of the full namespace. For example: // MyApplication.Controllers.V1.ProductsController => "V1.Products" 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); // For the dictionary key, strip "Controller" from the end of the type name. // This matches the behavior of DefaultHttpControllerSelector. var controllerName = t.Name.Remove(t.Name.Length - DefaultHttpControllerSelector.ControllerSuffix.Length); var spacename = segments[segments.Length - 1]; if (new Regex("v\\d+").IsMatch(spacename)) { var key = String.Format(CultureInfo.InvariantCulture, "{0}.{1}", segments[segments.Length - 1], controllerName); // Check for duplicate keys. if (dictionary.Keys.Contains(key)) { _duplicates.Add(key); } else { dictionary[key] = new HttpControllerDescriptor(_configuration, t.Name, t); } } else { dictionary[controllerName] = new HttpControllerDescriptor(_configuration, t.Name, t); } } // Remove any duplicates from the dictionary, because these create ambiguous matches. // For example, "Foo.V1.ProductsController" and "Bar.V1.ProductsController" both map to "v1.products". foreach (string s in _duplicates) { dictionary.Remove(s); } return dictionary; } // Get a value from the route data, if present. private static T GetRouteVariable<T>(IHttpRouteData routeData, string name) { object result = null; if (routeData.Values.TryGetValue(name, out result)) { return (T)result; } return default(T); } public override HttpControllerDescriptor SelectController(HttpRequestMessage request) { IHttpRouteData routeData = request.GetRouteData(); if (routeData == null) { throw new HttpResponseException(HttpStatusCode.NotFound); } // Get the namespace and controller variables from the route data. string namespaceName = GetRouteVariable<string>(routeData, NamespaceKey); if (namespaceName == null) { return base.SelectController(request); } string controllerName = GetRouteVariable<string>(routeData, ControllerKey); if (controllerName == null) { return base.SelectController(request); } if (!new Regex("v\\d+").IsMatch(namespaceName)) { return base.SelectController(request); } // Find a matching controller. string key = String.Format(CultureInfo.InvariantCulture, "{0}.{1}", namespaceName, 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 override IDictionary<string, HttpControllerDescriptor> GetControllerMapping() { return _controllers.Value; } public void UseThis() { _configuration.Services.Replace(typeof(IHttpControllerSelector), this); } }
2. 在WebApi中配置这个ControllerSelector。