前言
之前看到技术群里有同学讨论说对于MinimalApi能接入到Swagger中感到很神奇,加上Swagger的数据本身是支持OpenApi2.0
和OpenApi3.0
使得swagger.json
成为了许多接口文档管理工具的标准数据源。
ASP.NET Core能够轻松快速的集成Swagger得益于微软对OpenApi的大力支持,大部分情况下几乎是添加默认配置,就能很好的工作了。这一切都是得益于ASP.NET Core底层提供了对接口元数据的描述和对终结点的相关描述。本文我们就通过MinimalApi来了解一下ASP.NET Core为何能更好的集成Swagger。
使用方式
虽然我们讨论的是MInimalApi与Swagger数据源的关系,但是为了使得看起来更清晰,我们还是先看一下MinimalApi如何集成到Swagger,直接上代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | var builder = WebApplication.CreateBuilder(args); //这是重点,是ASP.NET Core自身提供的 builder.Services.AddEndpointsApiExplorer(); //添加swagger配置 builder.Services.AddSwaggerGen(c => { c.SwaggerDoc( "v1" , new () { Title = builder.Environment.ApplicationName, Version = "v1" }); }); var app = builder.Build(); if (app.Environment.IsDevelopment()) { //swagger终结点 app.UseSwagger(); app.UseSwaggerUI(c => c.SwaggerEndpoint( "/swagger/v1/swagger.json" , $ "{builder.Environment.ApplicationName} v1" )); } app.MapGet( "/swag" , () => "Hello Swagger!" ); app.Run(); |
上面我们提到了AddEndpointsApiExplorer
是ASP.NET Core自身提供的,但是如果使得MinimalApi能在Swagger中展示就必须要添加这个服务。所以Swagger还是那个Swagger,变的是ASP.NET Core本身,但是变化是如何适配数据源的问题,Swagger便是建立在这个便利基础上。接下来咱们就通过源码看一下它们之间的关系。
源码探究
想了解它们的关系就会涉及到两个主角,一个是swagger的数据源来自何处,另一个是ASP.NET Core是如何提供这个数据源的。首先我们来看一下Swagger的数据源来自何处。
swagger的数据源
熟悉Swashbuckle.AspNetCore
的应该知道它其实是由几个程序集一起构建的,也就是说Swashbuckle.AspNetCore
本身是一个解决方案,不过这不是重点,其中生成Swagger.json
的是在Swashbuckle.AspNetCore.SwaggerGen
程序集中,直接找到位置在SwaggerGenerator
类中[点击查看源码]只摘要我们关注的地方即可
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | public class SwaggerGenerator : ISwaggerProvider { private readonly IApiDescriptionGroupCollectionProvider _apiDescriptionsProvider; private readonly ISchemaGenerator _schemaGenerator; private readonly SwaggerGeneratorOptions _options; public SwaggerGenerator( SwaggerGeneratorOptions options, IApiDescriptionGroupCollectionProvider apiDescriptionsProvider, ISchemaGenerator schemaGenerator) { _options = options ?? new SwaggerGeneratorOptions(); _apiDescriptionsProvider = apiDescriptionsProvider; _schemaGenerator = schemaGenerator; } /// <summary> /// 获取Swagger文档的核心方法 /// </summary> public OpenApiDocument GetSwagger( string documentName, string host = null , string basePath = null ) { if (!_options.SwaggerDocs.TryGetValue(documentName, out OpenApiInfo info)) throw new UnknownSwaggerDocument(documentName, _options.SwaggerDocs.Select(d => d.Key)); //组装OpenApiDocument核心数据源源来自_apiDescriptionsProvider var applicableApiDescriptions = _apiDescriptionsProvider.ApiDescriptionGroups.Items .SelectMany( group => group .Items) .Where(apiDesc => !(_options.IgnoreObsoleteActions && apiDesc.CustomAttributes().OfType<obsoleteattribute . where = "" > _options.DocInclusionPredicate(documentName, apiDesc)); var schemaRepository = new SchemaRepository(documentName); var swaggerDoc = new OpenApiDocument { Info = info, Servers = GenerateServers(host, basePath), // Paths组装是来自applicableApiDescriptions Paths = GeneratePaths(applicableApiDescriptions, schemaRepository), Components = new OpenApiComponents { Schemas = schemaRepository.Schemas, SecuritySchemes = new Dictionary< string openapisecurityscheme= "" >(_options.SecuritySchemes) }, SecurityRequirements = new List<openapisecurityrequirement>(_options.SecurityRequirements) }; //省略其他代码 return swaggerDoc; } } </openapisecurityrequirement></ string ></obsoleteattribute> |
如果你比较了解Swagger.json
的话那么对OpenApiDocument
这个类的结构一定是一目了然,不信的话你可以自行看看它的结构
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | { "openapi" : "3.0.1" , "info" : { "title" : "MyTest.WebApi" , "description" : "测试接口" , "version" : "v1" }, "paths" : { "/" : { "get" : { "tags" : [ "MyTest.WebApi" ], "responses" : { "200" : { "description" : "Success" , "content" : { "text/plain" : { "schema" : { "type" : "string" } } } } } } } }, "components" : {} } |
这么看清晰了吧OpenApiDocument
这个类就是返回Swagger.json
的模型类,而承载描述接口信息的核心字段paths
正是来自IApiDescriptionGroupCollectionProvider
。所以小结一下,Swagger接口的文档信息的数据源来自于IApiDescriptionGroupCollectionProvider
。
ASP.Net Core如何提供
通过上面在Swashbuckle.AspNetCore.SwaggerGen
程序集中,我们看到了真正组装Swagger接口文档部分的数据源来自于IApiDescriptionGroupCollectionProvider
,但是这个接口并非来自Swashbuckle而是来自ASP.NET Core。这就引入了另一个主角,也是我们上面提到的AddEndpointsApiExplorer
方法。直接在dotnet/aspnetcore
仓库里找到方法位置[点击查看源码]看一下方法实现
1 2 3 4 5 6 7 8 9 10 | public static IServiceCollection AddEndpointsApiExplorer( this IServiceCollection services) { services.TryAddSingleton<iactiondescriptorcollectionprovider defaultactiondescriptorcollectionprovider= "" >(); //swagger用到的核心操作IApiDescriptionGroupCollectionProvider services.TryAddSingleton<iapidescriptiongroupcollectionprovider apidescriptiongroupcollectionprovider= "" >(); services.TryAddEnumerable( ServiceDescriptor.Transient<iapidescriptionprovider endpointmetadataapidescriptionprovider= "" >()); return services; } </iapidescriptionprovider></iapidescriptiongroupcollectionprovider></iactiondescriptorcollectionprovider> |
看到了AddEndpointsApiExplorer方法相信就明白了为啥要添加这个方法了吧,那你就有疑问了为啥不使用MinimalApi的时候就不用引入AddEndpointsApiExplorer这个方法了,况且也能使用swagger。这是因为在AddControllers
方法里添加了AddApiExplorer
方法,这个方法里包含了针对Controller的接口描述信息,这里就不过多说了,毕竟这种的核心是MinimalApi。接下来就看下IApiDescriptionGroupCollectionProvider接口的默认实现ApiDescriptionGroupCollectionProvider
类里的实现[点击查看源码]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 | public class ApiDescriptionGroupCollectionProvider : IApiDescriptionGroupCollectionProvider { private readonly IActionDescriptorCollectionProvider _actionDescriptorCollectionProvider; private readonly IApiDescriptionProvider[] _apiDescriptionProviders; private ApiDescriptionGroupCollection? _apiDescriptionGroups; public ApiDescriptionGroupCollectionProvider( IActionDescriptorCollectionProvider actionDescriptorCollectionProvider, IEnumerable<iapidescriptionprovider> apiDescriptionProviders) { _actionDescriptorCollectionProvider = actionDescriptorCollectionProvider; _apiDescriptionProviders = apiDescriptionProviders.OrderBy(item => item.Order).ToArray(); } public ApiDescriptionGroupCollection ApiDescriptionGroups { get { var actionDescriptors = _actionDescriptorCollectionProvider.ActionDescriptors; if (_apiDescriptionGroups == null || _apiDescriptionGroups.Version != actionDescriptors.Version) { //如果_apiDescriptionGroups为null则使用GetCollection方法返回的数据 _apiDescriptionGroups = GetCollection(actionDescriptors); } return _apiDescriptionGroups; } } private ApiDescriptionGroupCollection GetCollection(ActionDescriptorCollection actionDescriptors) { var context = new ApiDescriptionProviderContext(actionDescriptors.Items); //这里使用了_apiDescriptionProviders foreach ( var provider in _apiDescriptionProviders) { provider.OnProvidersExecuting(context); } for ( var i = _apiDescriptionProviders.Length - 1; i >= 0; i--) { _apiDescriptionProviders[i].OnProvidersExecuted(context); } var groups = context.Results .GroupBy(d => d.GroupName) .Select(g => new ApiDescriptionGroup(g.Key, g.ToArray())) .ToArray(); return new ApiDescriptionGroupCollection(groups, actionDescriptors.Version); } } </iapidescriptionprovider> |
这里我们看到了IApiDescriptionProvider[]
通过上面的方法我们可以知道IApiDescriptionProvider默认实现是EndpointMetadataApiDescriptionProvider
类[点击查看源码]看一下相实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | internal class EndpointMetadataApiDescriptionProvider : IApiDescriptionProvider { private readonly EndpointDataSource _endpointDataSource; private readonly IHostEnvironment _environment; private readonly IServiceProviderIsService? _serviceProviderIsService; private readonly ParameterBindingMethodCache ParameterBindingMethodCache = new (); public EndpointMetadataApiDescriptionProvider( EndpointDataSource endpointDataSource, IHostEnvironment environment, IServiceProviderIsService? serviceProviderIsService) { _endpointDataSource = endpointDataSource; _environment = environment; _serviceProviderIsService = serviceProviderIsService; } public void OnProvidersExecuting(ApiDescriptionProviderContext context) { //核心数据来自EndpointDataSource类 foreach ( var endpoint in _endpointDataSource.Endpoints) { if (endpoint is RouteEndpoint routeEndpoint && routeEndpoint.Metadata.GetMetadata<methodinfo>() is { } methodInfo && routeEndpoint.Metadata.GetMetadata<ihttpmethodmetadata>() is { } httpMethodMetadata && routeEndpoint.Metadata.GetMetadata<iexcludefromdescriptionmetadata>() is null or { ExcludeFromDescription: false }) { foreach ( var httpMethod in httpMethodMetadata.HttpMethods) { context.Results.Add(CreateApiDescription(routeEndpoint, httpMethod, methodInfo)); } } } } private ApiDescription CreateApiDescription(RouteEndpoint routeEndpoint, string httpMethod, MethodInfo methodInfo) { //实现代码省略 } } </iexcludefromdescriptionmetadata></ihttpmethodmetadata></methodinfo> |
这个类里还有其他方法代码也非常多,都是在组装ApiDescription里的数据,通过名称可以得知,这个类是为了描述API接口信息用的,但是我们了解到的是它的数据源都来自EndpointDataSource
类的实例。我们都知道MinimalApi提供的操作方法就是MapGet
、MapPost
、MapPut
、MapDelete
等等,这些方法的本质都是在调用Map
方法[点击查看源码],看一下核心实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | private static RouteHandlerBuilder Map( this IEndpointRouteBuilder endpoints, RoutePattern pattern, Delegate handler, bool disableInferBodyFromParameters) { //省略部分代码 var requestDelegateResult = RequestDelegateFactory.Create(handler, options); var builder = new RouteEndpointBuilder(requestDelegateResult.RequestDelegate,pattern,defaultOrder) { //路由名称 DisplayName = pattern.RawText ?? pattern.DebuggerToString(), }; //获得httpmethod builder.Metadata.Add(handler.Method); if (GeneratedNameParser.TryParseLocalFunctionName(handler.Method.Name, out var endpointName) || !TypeHelper.IsCompilerGeneratedMethod(handler.Method)) { endpointName ??= handler.Method.Name; builder.DisplayName = $ "{builder.DisplayName} => {endpointName}" ; } var attributes = handler.Method.GetCustomAttributes(); foreach ( var metadata in requestDelegateResult.EndpointMetadata) { builder.Metadata.Add(metadata); } if (attributes is not null ) { foreach ( var attribute in attributes) { builder.Metadata.Add(attribute); } } // 添加ModelEndpointDataSource var dataSource = endpoints.DataSources.OfType<modelendpointdatasource>().FirstOrDefault(); if (dataSource is null ) { dataSource = new ModelEndpointDataSource(); endpoints.DataSources.Add(dataSource); } //将RouteEndpointBuilder添加到ModelEndpointDataSource return new RouteHandlerBuilder(dataSource.AddEndpointBuilder(builder)); } </modelendpointdatasource> |
通过Map
方法我们可以看到每次添加一个MinimalApi终结点都会给ModelEndpointDataSource
实例添加一个EndpointBuilder
实例,EndPointBuilder里承载着MinimalApi终结点的信息,而ModelEndpointDataSource则是继承了EndpointDataSource
类,这个可以看它的定义[点击查看源码]
1 2 3 | internal class ModelEndpointDataSource : EndpointDataSource { } |
这就和上面提到的EndpointMetadataApiDescriptionProvider
里的EndpointDataSource联系起来了,但是我们这里看到的是IEndpointRouteBuilder
的DataSources
属性,从名字看这明显是一个集合,我们可以找到定义的地方看一下[点击查看源码]
1 2 3 4 5 6 7 8 | public interface IEndpointRouteBuilder { IApplicationBuilder CreateApplicationBuilder(); IServiceProvider ServiceProvider { get ; } //这里是一个EndpointDataSource的集合 ICollection<endpointdatasource> DataSources { get ; } } </endpointdatasource> |
这里既然是一个集合那如何和EndpointDataSource联系起来呢,接下来我们就得去看EndpointDataSource是如何被注册的即可,找到EndpointDataSource
注册的地方[点击查看源码]查看一下注册代码
1 2 3 4 5 6 7 8 | var dataSources = new ObservableCollection<endpointdatasource>(); services.TryAddEnumerable(ServiceDescriptor.Transient<iconfigureoptions>, ConfigureRouteOptions>( serviceProvider => new ConfigureRouteOptions(dataSources))); services.TryAddSingleton<endpointdatasource>(s => { return new CompositeEndpointDataSource(dataSources); }); </endpointdatasource></iconfigureoptions></endpointdatasource> |
通过这段代码我们可以得到两点信息
- 一是EndpointDataSource这个抽象类,系统给他注册的是
CompositeEndpointDataSource
这个子类,看名字可以看出是组合的EndpointDataSource - 二是
CompositeEndpointDataSource
是通过ObservableCollection
这么一个集合来初始化的
我们可以简单的来看下CompositeEndpointDataSource传递的dataSources是如何被接收的[点击查看源码]咱们只关注他说如何被接收的
1 2 3 4 5 6 7 8 9 10 | public sealed class CompositeEndpointDataSource : EndpointDataSource { private readonly ICollection<endpointdatasource> _dataSources = default !; internal CompositeEndpointDataSource(ObservableCollection<endpointdatasource> dataSources) : this () { _dataSources = dataSources; } public IEnumerable<endpointdatasource> DataSources => _dataSources; } </endpointdatasource></endpointdatasource></endpointdatasource> |
通过上面我们可以看到,系统默认为EndpointDataSource抽象类注册了CompositeEndpointDataSource
实现类,而这个实现类是一个组合类,它组合了一个EndpointDataSource的集合。那么到了这里就只剩下一个问题了,那就是EndpointDataSource
是如何和IEndpointRouteBuilder
的DataSources
属性关联起来的。现在有了提供数据源的IEndpointRouteBuilder,有承载数据的EndpointDataSource。这个地方呢大家也比较熟悉那就是UseEndpoints
中间件里,我们来看下是如何实现的[点击查看源码]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | public static IApplicationBuilder UseEndpoints( this IApplicationBuilder builder, Action<iendpointroutebuilder> configure) { // 省略一堆代码 //得到IEndpointRouteBuilder实例 VerifyEndpointRoutingMiddlewareIsRegistered(builder, out var endpointRouteBuilder); //获取RouteOptions var routeOptions = builder.ApplicationServices.GetRequiredService<ioptions>>(); //遍历IEndpointRouteBuilder的DataSources foreach ( var dataSource in endpointRouteBuilder.DataSources) { if (!routeOptions.Value.EndpointDataSources.Contains(dataSource)) { //dataSource放入RouteOptions的EndpointDataSources集合 routeOptions.Value.EndpointDataSources.Add(dataSource); } } return builder.UseMiddleware<endpointmiddleware>(); } private static void VerifyEndpointRoutingMiddlewareIsRegistered(IApplicationBuilder app, out IEndpointRouteBuilder endpointRouteBuilder) { if (!app.Properties.TryGetValue(EndpointRouteBuilder, out var obj)) { throw new InvalidOperationException(); } endpointRouteBuilder = (IEndpointRouteBuilder)obj!; if (endpointRouteBuilder is DefaultEndpointRouteBuilder defaultRouteBuilder && ! object .ReferenceEquals(app, defaultRouteBuilder.ApplicationBuilder)) { throw new InvalidOperationException(); } } </endpointmiddleware></ioptions></iendpointroutebuilder> |
这里我们看到是获取的IOptions
里的EndpointDataSources,怎么和预想的剧本不一样呢?并非如此,你看上面咱们说的这段代码
1 2 3 4 | var dataSources = new ObservableCollection<endpointdatasource>(); services.TryAddEnumerable(ServiceDescriptor.Transient<iconfigureoptions>, ConfigureRouteOptions>( serviceProvider => new ConfigureRouteOptions(dataSources))); </iconfigureoptions></endpointdatasource> |
上面的dataSources同时传递给了CompositeEndpointDataSource
和ConfigureRouteOptions
,而ConfigureRouteOptions则正是IConfigureOptions
类型的,所以获取IOptions
就是获取的ConfigureRouteOptions的实例,咱们来看一下ConfigureRouteOptions类的实现[点击查看源码]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | internal class ConfigureRouteOptions : IConfigureOptions<routeoptions> { private readonly ICollection<endpointdatasource> _dataSources; public ConfigureRouteOptions(ICollection<endpointdatasource> dataSources) { if (dataSources == null ) { throw new ArgumentNullException(nameof(dataSources)); } _dataSources = dataSources; } public void Configure(RouteOptions options) { if (options == null ) { throw new ArgumentNullException(nameof(options)); } options.EndpointDataSources = _dataSources; } } </endpointdatasource></endpointdatasource></routeoptions> |
它的本质操作就是对RouteOptions
的EndpointDataSources的属性进行操作,因为ICollection
是引用类型,所以这个集合是共享的,因此IEndpointRouteBuilder
的DataSources
和IConfigureOptions
本质是使用了同一个ICollection
集合,所以上面的UseEndpoints
里获取RouteOptions选项的本质正是获取的EndpointDataSource集合。
每次对IEndpointRouteBuilder
的DataSources
集合Add的时候其实是在为ICollection
集合添加数据,而IConfigureOptions
也使用了这个集合,所以它们的数据是互通的。
许多同学都很好强,默认并没在MinimalApi看到注册UseEndpoints
,但是在ASP.NET Core6.0之前还是需要注册UseEndpoints
中间件的。这其实是ASP.NET Core6.0进行的一次升级优化,因为很多操作默认都得添加,所以把它统一封装起来了,这个可以在WebApplicationBuilder
类中看到[点击查看源码]在
ConfigureApplication
方法中的代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | private void ConfigureApplication(WebHostBuilderContext context, IApplicationBuilder app) { // 省略部分代码 // 注册UseDeveloperExceptionPage全局异常中间件 if (context.HostingEnvironment.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.Properties.Add(WebApplication.GlobalEndpointRouteBuilderKey, _builtApplication); if (_builtApplication.DataSources.Count > 0) { // 注册UseRouting中间件 if (!_builtApplication.Properties.TryGetValue(EndpointRouteBuilderKey, out var localRouteBuilder)) { app.UseRouting(); } else { app.Properties[EndpointRouteBuilderKey] = localRouteBuilder; } } app.Use(next => { //调用WebApplication的Run方法 _builtApplication.Run(next); return _builtApplication.BuildRequestDelegate(); }); // 如果DataSources集合有数据则注册UseEndpoints if (_builtApplication.DataSources.Count > 0) { app.UseEndpoints(_ => { }); } // 省略部分代码 } |
相信大家通过ConfigureApplication
这个方法大家就了解了吧,之前我们能看到的熟悉方法UseDeveloperExceptionPage
、UseRouting
、UseEndpoints
方法都在这里,毕竟之前这几个方法几乎也成了新建项目时候必须要添加的,所以微软干脆就在内部统一封装起来了。
源码小结
上面咱们分析了相关的源码,整理起来就是这么一个思路。
-
Swashbuckle.AspNetCore.SwaggerGen
用来生成swagger的数据源来自IApiDescriptionGroupCollectionProvider
- IApiDescriptionGroupCollectionProvider实例的数据来自
EndpointDataSource
- 因为
EndpointDataSource
的DataSources
和IConfigureOptions
本质是使用了同一个ICollection
集合,所以它们是同一份数据 - 每次使用MinimalApi的Map相关的方法的是会给
IEndpointRouteBuilder
的DataSources
集合添加数据 - 在
UseEndpoints
中间件里获取IEndpointRouteBuilder
的DataSources
数据给RouteOptions选项的EndpointDataSources集合属性添加数据,本质则是给ICollection
集合赋值,自然也就是给EndpointDataSource
的DataSources
属性赋值
这也给我们提供了一个思路,如果你想自己去适配swagger数据源的话完全也可以参考这个思路,想办法把你要提供的接口信息放到EndpointDataSource的DataSources集合属性里即可,或者直接适配IApiDescriptionGroupCollectionProvider里的数据,有兴趣的同学可以自行研究一下。
使用扩展
我们看到了微软给我们提供了IApiDescriptionGroupCollectionProvider
这个便利条件,所以如果以后有获取接口信息的时候则可以直接使用了,很多时候比如写监控程序或者写Api接口调用的代码生成器的时候都可以考虑一下,咱们简单的示例一下如何使用,首先定义个模型类来承载接口信息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | public class ApiDoc { /// <summary> /// 接口分组 /// </summary> public string Group { get ; set ; } /// <summary> /// 接口路由 /// </summary> public string Route { get ; set ; } /// <summary> /// http方法 /// </summary> public string HttpMethod { get ; set ; } } |
这个类非常简单只做演示使用,然后我们在IApiDescriptionGroupCollectionProvider里获取信息来填充这个集合,这里我们写一个htt接口来展示
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | app.MapGet( "/apiinfo" , (IApiDescriptionGroupCollectionProvider provider) => { List<ApiDoc> docs = new List<ApiDoc>(); foreach ( var group in provider.ApiDescriptionGroups.Items) { foreach ( var apiDescription in group .Items) { docs.Add( new ApiDoc { Group = group .GroupName, Route = apiDescription.RelativePath, HttpMethod = apiDescription.HttpMethod }); } } return docs; }); |
这个时候当你在浏览器里请求/apiinfo
路径的时候会返回你的webapi包含的接口相关的信息。咱们的示例是非常简单的,实际上IApiDescriptionGroupCollectionProvider包含的接口信息是非常多的包含请求参数信息、输出返回信息等很全面,这也是swagger可以完全依赖它的原因,有兴趣的同学可以自行的了解一下,这里就不过多讲解了。
总结
本文咱们主要通过MinimalApi如何适配swagger的这么一个过程来讲解了ASP.NET Core是如何给Swagger提供了数据的。本质是微软在ASP.NET Core本身提供了IApiDescriptionGroupCollectionProvider
这么一个数据源,Swagger借助这个数据源生成了swagger文档,IApiDescriptionGroupCollectionProvider来自声明终结点的时候往EndpointDataSource
的DataSources
集合里添加的接口信息等。其实它内部比这个还要复杂一点,不过如果我们用来获取接口信息的话,大部分时候使用IApiDescriptionGroupCollectionProvider应该就足够了。
分享一段我个人比较认可的话,与其天天钻头觅缝、找各种机会,不如把这些时间和金钱投入到自己的能力建设上。机会稍纵即逝,而且别人给你的机会,没准儿反而是陷阱。而投资个人能力就是积累一个资产账户,只能越存越多,看起来慢,但是你永远在享受时间带来的复利,其实快得很,收益也稳定得多。有了能力之后,机会也就来了。
以上就是源码分析MinimalApi是如何在Swagger中展示的详细内容,更多关于MinimalApi在Swagger展示的资料请关注IT俱乐部其它相关文章!