WebApi+Swagger多版本控制

    在写webapi接口时经常会有多个版本的需求,前端用swagger来做在线测试是很好的选择,但对于有多个版本的话,swagger的显示经常会是个问题,在查阅不少资料后自己实现了,webapi多版本统一显示,统一调用入口;和分版本显示。各有各的优点,统一显示的话可以统一查阅;分版本的话,对查找实现版本比较轻松。

统一显示:

分版本显示

 

控制器版本选择器自定义
 /// <summary>
    /// 版本控制选择器
    /// </summary>
    public class VersionControllerSelector : IHttpControllerSelector
    {
        // Fields
        private readonly HttpConfiguration _configuration;
        private readonly Lazy<ConcurrentDictionary<string, HttpControllerDescriptor>> _controllerInfoCache;
        private readonly Lazy<ConcurrentDictionary<string, HttpActionDescriptor>> _actionInfoCache;
        private const string ControllerKey = "controller";
        private const string ActionKey = "action";
        private const string DefaultVersion = "v1_2"; //默认版本号,因为之前的api我们没有版本号的概念
        private const string DefaultNamespaces = "Manjinba.Communication.WebApi"; //为了演示方便,这里就用到一个命名空间
        private const string RouteVersionKey = "version"; //路由规则中Version的字符串
        private const string DictKeyFormat = "{0}.{1}";
        private const string ActionDictKeyFormat = "{0}.{1}.{2}";
        private readonly HashSet<string> _duplicates;
        // Methods
        /// <summary>
        /// 初始化
        /// </summary>
        /// <param name="configuration"></param>
        public VersionControllerSelector(HttpConfiguration configuration)
        {
            if (configuration == null)
            {
                throw new ArgumentNullException("configuration");
            }
            this._controllerInfoCache = new Lazy<ConcurrentDictionary<string, HttpControllerDescriptor>>(new Func<ConcurrentDictionary<string, HttpControllerDescriptor>>(this.InitializeControllerInfoCache));
            this._actionInfoCache = new Lazy<ConcurrentDictionary<string, HttpActionDescriptor>>(new Func<ConcurrentDictionary<string, HttpActionDescriptor>>(this.InitializeActionInfoCache));
            this._configuration = configuration;
            this._duplicates = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
        }
        /// <summary>
        /// 获取和筛选控制器路由映射
        /// </summary>
        /// <returns></returns>
        public virtual IDictionary<string, HttpControllerDescriptor> GetControllerMapping()
        {
            //var controllerMapping = new Dictionary<string, HttpControllerDescriptor>();
            //foreach (var p in _controllerInfoCache.Value)
            //{
            //    var controllerKey = p.Key?.Split('.')?.Count() > 1 ? p.Key.Split('.')[1] : p.Key.Split('.').FirstOrDefault();
            //    if (!controllerMapping.Keys.Any(a => a.Equals(controllerKey, StringComparison.OrdinalIgnoreCase)))
            //    {
            //        controllerMapping.Add(controllerKey, p.Value);
            //    }
            //}
            //return controllerMapping;
            return _controllerInfoCache.Value;
        }
        /// <summary>
        /// 获取控制器名称
        /// </summary>
        /// <param name="request"></param>
        /// <returns></returns>
        public virtual string GetControllerName(HttpRequestMessage request)
        {
            if (request == null)
            {
                throw new ArgumentNullException("request");
            }
            IHttpRouteData routeData = request.GetRouteData();
            if (routeData == null)
            {
                throw new ArgumentNullException("routeData");
            }
            object str = null;
            routeData.Values.TryGetValue(ControllerKey, out str);
            return str.ToString();
        }
        /// <summary>
        /// 获取路由映射
        /// </summary>
        /// <returns></returns>
        private ConcurrentDictionary<string, HttpControllerDescriptor> InitializeControllerInfoCache()
        {
            ConcurrentDictionary<string, HttpControllerDescriptor> dictionary = new ConcurrentDictionary<string, HttpControllerDescriptor>(StringComparer.OrdinalIgnoreCase);
            var assemblies = _configuration.Services.GetAssembliesResolver();//解析程序集
            var controllerResolver = _configuration.Services.GetHttpControllerTypeResolver();
            var controllerTypes = controllerResolver.GetControllerTypes(assemblies);
            foreach (var t in controllerTypes)
            {
                if (t.Namespace.Contains(DefaultNamespaces)) //符合NameSpace规则
                {
                    var segments = t.Namespace.Split(Type.Delimiter);
                    var version = t.Namespace.Equals(DefaultNamespaces, StringComparison.OrdinalIgnoreCase) ?
                        DefaultVersion : segments[segments.Length - 1];
                    var controllerName = t.Name.Remove(t.Name.Length - DefaultHttpControllerSelector.ControllerSuffix.Length);
                    var key = string.Format(DictKeyFormat, version, controllerName);
                    if (!dictionary.ContainsKey(key))
                    {
                        dictionary.TryAdd(key, new HttpControllerDescriptor(_configuration, t.Name, t));
                    }
                    else
                    {
                        _duplicates.Add(key);
                    }
                }
            }
            return dictionary;
        }
        /// <summary>
        /// 获取路由映射
        /// </summary>
        /// <returns></returns>
        private ConcurrentDictionary<string, HttpActionDescriptor> InitializeActionInfoCache()
        {
            ConcurrentDictionary<string, HttpActionDescriptor> dictionary = new ConcurrentDictionary<string, HttpActionDescriptor>(StringComparer.OrdinalIgnoreCase);
            var assemblies = _configuration.Services.GetAssembliesResolver();//解析程序集
            var controllerResolver = _configuration.Services.GetHttpControllerTypeResolver();
            var controllerTypes = controllerResolver.GetControllerTypes(assemblies);
            foreach (var t in controllerTypes)
            {
                if (t.Namespace.Contains(DefaultNamespaces)) //符合NameSpace规则
                {
                    var segments = t.Namespace.Split(Type.Delimiter);
                    var version = t.Namespace.Equals(DefaultNamespaces, StringComparison.OrdinalIgnoreCase) ?
                        DefaultVersion : segments[segments.Length - 1];
                    var controllerName = t.Name.Remove(t.Name.Length - DefaultHttpControllerSelector.ControllerSuffix.Length);
                    var methodTypes = t.GetMethods().Where(w =>
                        "JsonResult`1".Equals(w.GetRuntimeBaseDefinition()?.ReturnParameter?.ParameterType?.Name)
                        || "Task`1".Equals(w.GetRuntimeBaseDefinition()?.ReturnParameter?.ParameterType?.Name)
                    )?.ToList();
                    foreach (var method in methodTypes)
                    {
                        var key = string.Format(ActionDictKeyFormat, version, controllerName, method.Name);
                        if (!dictionary.ContainsKey(key))
                        {
                            dictionary.TryAdd(key, new ReflectedHttpActionDescriptor(new HttpControllerDescriptor(_configuration, t.Name, t), method));
                        }
                    }
                }
            }
            return dictionary;
        }
        /// <summary>
        /// 定位控制器
        /// </summary>
        /// <param name="request"></param>
        /// <returns></returns>
        public virtual HttpControllerDescriptor SelectController(HttpRequestMessage request)
        {
            Stopwatch sw = new Stopwatch();
            sw.Start();
            if (request == null)
            {
                throw new ArgumentNullException("request");
            }
            IHttpRouteData routeData = request.GetRouteData();
            if (routeData.Values.Keys.Count == 1 && routeData.Values.Keys.FirstOrDefault().Equals("MS_SubRoutes"))
            {
                sw.Stop();
                LogHelper.GetLog().Debug("路由查找");
                return new DefaultHttpControllerSelector(_configuration).SelectController(request);
            }
            string version = GetRouteVariable<string>(routeData, RouteVersionKey);
            if (version == null)
            {
                version = DefaultVersion;
            }
            else
            {
                version = version.Replace('.', '_');
                if (version.Equals("api", StringComparison.OrdinalIgnoreCase))
                {
                    version = "V1_2";
                }
            }
            string controllerName = GetRouteVariable<string>(routeData, ControllerKey);
            var actionName = GetRouteVariable<string>(routeData, ActionKey);
            if (controllerName.Equals("search", StringComparison.OrdinalIgnoreCase))
            {
                version = "V1_4";
            }
            if (controllerName.Equals("api", StringComparison.OrdinalIgnoreCase))
            {
                if (actionName.Equals("search", StringComparison.OrdinalIgnoreCase))
                {
                    version = "V1_4";
                    controllerName = "Search";
                }
                else
                {
                    controllerName = "IMMessage";
                }
            }
            if (controllerName.Equals("Agreement", StringComparison.OrdinalIgnoreCase) || controllerName.Equals("Help", StringComparison.OrdinalIgnoreCase)
                || controllerName.Equals("App", StringComparison.OrdinalIgnoreCase))
            {
                return new DefaultHttpControllerSelector(_configuration).SelectController(request);
            }
            if (controllerName == null)
            {
                throw new HttpResponseException(HttpStatusCode.NotFound);
            }
            var key = string.Empty;
            var isMatched = false;// 路由是否匹配到
            var isToLowerMatchStart = false;
            version = version.ToUpper();
            try
            {
                var versionList = GetAllVersion();
                if (versionList != null && !versionList.Contains(version))// 如果请求的版本号不在版本列表中,则从最后版本向老版本追溯
                {
                    isToLowerMatchStart = true;
                }
                foreach (var v in versionList)
                {
                    if (v.Equals(version))
                    {
                        isToLowerMatchStart = true;
                    }
                    if (isToLowerMatchStart)// 只往更老版本追溯
                    {
                        // 匹配控制器
                        key = string.Format(DictKeyFormat, v, controllerName);
                        var actionKey = string.Format(ActionDictKeyFormat, v, controllerName, actionName);
                        HttpControllerDescriptor controllerDescriptor;
                        HttpActionDescriptor actionDescriptor;
                        if (_actionInfoCache.Value.TryGetValue(actionKey, out actionDescriptor))
                        {
                            if (_controllerInfoCache.Value.TryGetValue(key, out controllerDescriptor))
                            {
                                isMatched = true;
                                return controllerDescriptor;
                            }
                        }
                        else
                        {
                            continue;
                        }
                    }
                }
            }
            catch (Exception e)
            {
                LogHelper.GetLog().Error(e.Message + e.StackTrace);
                throw new HttpResponseException(
                    request.CreateErrorResponse(HttpStatusCode.InternalServerError,
                    "Multiple controllers were found that match this request."));
            }
            if (!isMatched && _duplicates.Contains(key))
            {
                throw new HttpResponseException(
                    request.CreateErrorResponse(HttpStatusCode.InternalServerError,
                    "Multiple controllers were found that match this request."));
            }
            else
            {
                throw new HttpResponseException(HttpStatusCode.NotFound);
            }
        }
        /// <summary>
        /// Get a value from the route data, if present.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="routeData"></param>
        /// <param name="name"></param>
        /// <returns></returns>
        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);
        }
        /// <summary>
        /// 获取当前执行程序控制层的所有版本号
        /// </summary>
        /// <returns></returns>
        private static List<string> GetAllVersion()
        {
            var versionList = new List<string>();
            var assembly = Assembly.GetExecutingAssembly();
            if (assembly != null)
            {
                var types = assembly.GetTypes();
                foreach (var t in types)
                {
                    if (t.FullName.StartsWith("Manjinba.Communication.WebApi.Controllers.V") || t.FullName.StartsWith("Manjinba.Communication.WebApi.Controllers.api.V"))
                    {
                        var cversion = t.Namespace.Substring(t.Namespace.LastIndexOf('.') + 1);
                        if (!versionList.Contains(cversion))
                        {
                            versionList.Add(t.Namespace.Substring(t.Namespace.LastIndexOf('.') + 1));
                        }
                    }
                }
                versionList.Sort();
                versionList.Reverse();
            }
            return versionList;
        }
    }

 

WebApiConfig

/// <summary>
    /// WebApi配置
    /// </summary>
    public static class WebApiConfig
    {
        /// <summary>
        /// 注册
        /// </summary>
        /// <param name="config"></param>
        public static void Register(HttpConfiguration config)
        {
            // Web API configuration and services
            // Web API routes
            config.Formatters.JsonFormatter.SerializerSettings = new Newtonsoft.Json.JsonSerializerSettings()
            {
                NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore
            };

            //以下跨域请求在Nginx中配置
            //var allowOrigins = ConfigurationManager.AppSettings["cors_allowOrigins"] ?? "*";
            //var allowHeaders = ConfigurationManager.AppSettings["cors_allowHeaders"] ?? "*";
            //var allowMethods = ConfigurationManager.AppSettings["cors_allowMethods"] ?? "*";
            //var globalCors = new EnableCorsAttribute(allowOrigins, allowHeaders, allowMethods);
            //config.EnableCors(globalCors);
            config.MapHttpAttributeRoutes();
#if !DEBUG
            config.Filters.Add(new ApiSecurityFilter());
#endif
            config.Filters.Add(new ValidateModelFilter());
            config.Routes.MapHttpRoute(
                name: "VersionApi",
                routeTemplate: "{version}/{controller}/{action}",
                defaults: new { id = RouteParameter.Optional }
            );

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

            //版本信息在Uri中
            config.Services.Replace(typeof(IHttpControllerSelector), new VersionControllerSelector(config));
        }
    }

 

SwaggerConfig
 

/// <summary>
    /// Swagger配置
    /// </summary>
    public class SwaggerConfig
    {
        /// <summary>
        /// 注册
        /// </summary>
        public static void Register()
        {
            if ("true".Equals(ConfigVal.SwaggerEnabled, StringComparison.CurrentCultureIgnoreCase))
            {
                var thisAssembly = typeof(SwaggerConfig).Assembly;
                GlobalConfiguration.Configuration
                    .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", "Manjinba.Communication.WebApi");

                            // 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("v1_4", "Manjinba.Communication.WebApi.Controllers.V1_4");
                                    vc.Version("v1_3", "Manjinba.Communication.WebApi.Controllers.V1_3");
                                    vc.Version("v1_2", "Manjinba.Communication.WebApi.Controllers.V1_2");
                                });

                            // 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.
                            //
                            if (System.IO.File.Exists(GetXmlControllersCommentsPath()))
                                c.IncludeXmlComments(GetXmlControllersCommentsPath());
                            if (System.IO.File.Exists(GetXmlModelCommentsPath()))
                                c.IncludeXmlComments(GetXmlModelCommentsPath());
                            if (System.IO.File.Exists(GetXmlCommonCommentsPath()))
                                c.IncludeXmlComments(GetXmlCommonCommentsPath());
                            if (System.IO.File.Exists(GetXmlDomainCommentsPath()))
                                c.IncludeXmlComments(GetXmlDomainCommentsPath());
                            if (System.IO.File.Exists(GetXmlIRepositoryCommentsPath()))
                                c.IncludeXmlComments(GetXmlIRepositoryCommentsPath());
                            if (System.IO.File.Exists(GetXmlIServiceCommentsPath()))
                                c.IncludeXmlComments(GetXmlIServiceCommentsPath());

                            // 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.
                            // 打开swagger文档过滤
                            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.FirstOrDefault());

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

                            c.OperationFilter<Filters.GlobalHttpHeaderFilter>();/*添加此行*/
                        })
                    .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");
                        });
            }
        }

        /// <summary>
        /// Controllers xml文件
        /// </summary>
        /// <returns></returns>
        protected static string GetXmlControllersCommentsPath()
        {
            var re = System.String.Format(@"{0}\bin\Manjinba.Communication.WebApi.xml", System.AppDomain.CurrentDomain.BaseDirectory);
            return re;
        }
        /// <summary>
        /// Model xml文件
        /// </summary>
        /// <returns></returns>
        protected static string GetXmlModelCommentsPath()
        {
            var re = System.String.Format(@"{0}\bin\Manjinba.Communication.Model.xml", System.AppDomain.CurrentDomain.BaseDirectory);
            return re;
        }
        /// <summary>
        /// Common xml文件
        /// </summary>
        /// <returns></returns>
        protected static string GetXmlCommonCommentsPath()
        {
            var re = System.String.Format(@"{0}\bin\Manjinba.Communication.Common.xml", System.AppDomain.CurrentDomain.BaseDirectory);
            return re;
        }
        /// <summary>
        /// Domain xml文件
        /// </summary>
        /// <returns></returns>
        protected static string GetXmlDomainCommentsPath()
        {
            var re = System.String.Format(@"{0}\bin\Manjinba.Communication.Domain.xml", System.AppDomain.CurrentDomain.BaseDirectory);
            return re;
        }
        /// <summary>
        /// IRepository xml文件
        /// </summary>
        /// <returns></returns>
        protected static string GetXmlIRepositoryCommentsPath()
        {
            var re = System.String.Format(@"{0}\bin\Manjinba.Communication.IRepository.xml", System.AppDomain.CurrentDomain.BaseDirectory);
            return re;
        }
        /// <summary>
        /// IService xml文件
        /// </summary>
        /// <returns></returns>
        protected static string GetXmlIServiceCommentsPath()
        {
            var re = System.String.Format(@"{0}\bin\Manjinba.Communication.IService.xml", System.AppDomain.CurrentDomain.BaseDirectory);
            return re;
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="apiDesc"></param>
        /// <param name="targetApiVersion"></param>
        /// <returns></returns>
        public static bool ResolveVersionSupportByRouteConstraint(ApiDescription apiDesc, string targetApiVersion)
        {
            var controllerFullName = apiDesc.ActionDescriptor.ControllerDescriptor.ControllerType.FullName;
            var splitArray = controllerFullName.Split('.');
            return splitArray.Contains(targetApiVersion, StringComparer.OrdinalIgnoreCase);
        }
    }

 

最后添加swagger文档过滤

/// <summary>
    /// 
    /// </summary>
    internal class ApplyDocumentVendorExtensions : IDocumentFilter
    {
        /// <summary>
        /// //swagger版本控制过滤
        /// </summary>
        /// <param name="swaggerDoc">文档</param>
        /// <param name="schemaRegistry">schema注册</param>
        /// <param name="apiExplorer">api概览</param>
        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)
            {
                //Regex vr = new Regex("/v\\d_\\d.", RegexOptions.IgnoreCase);
                //过滤命名空间 按名称空间区分版本
                //if (vr.IsMatch(path.Key))
                if (path.Key.ToUpper().Contains(string.Format("/{0}.", version.ToUpper())))
                {
                    //匹配controller descript中的版本信息
                    //Regex r = new Regex("/{version}/v\\d_\\d.", RegexOptions.IgnoreCase);
                    Regex r = new Regex("/{version}/" + 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, "/{version}/");
                        //newKey = path.Key.Replace(routeinfo, routeinfo.Replace(version.ToLower() + ".", "")).Replace(routeinfo, routeinfo.Replace(version.ToUpper() + ".", ""));
                        //保存修正的path
                        if (!match.ContainsKey(newKey))
                            match.Add(newKey, path.Value);
                    }
                }
            }
            //当前版本的swagger document
            swaggerDoc.paths = match;
        }
    }

 

参考文章
https://www.cnblogs.com/johnx/p/10150671.html
https://blog.csdn.net/qq_32109957/article/details/81128805

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值