源碼分析MinimalApi是如何在Swagger中展示
目錄
- 前言
- 使用方式
- 源碼探究
- swagger的數(shù)據(jù)源
- ASP.Net Core如何提供
- 源碼小結(jié)
- 使用擴(kuò)展
- 總結(jié)
前言
之前看到技術(shù)群里有同學(xué)討論說(shuō)對(duì)于MinimalApi能接入到Swagger中感到很神奇,加上Swagger的數(shù)據(jù)本身是支持OpenApi2.0
和OpenApi3.0
使得swagger.json
成為了許多接口文檔管理工具的標(biāo)準(zhǔn)數(shù)據(jù)源。
ASP.NET Core能夠輕松快速的集成Swagger得益于微軟對(duì)OpenApi的大力支持,大部分情況下幾乎是添加默認(rèn)配置,就能很好的工作了。這一切都是得益于ASP.NET Core底層提供了對(duì)接口元數(shù)據(jù)的描述和對(duì)終結(jié)點(diǎn)的相關(guān)描述。本文我們就通過(guò)MinimalApi來(lái)了解一下ASP.NET Core為何能更好的集成Swagger。
使用方式
雖然我們討論的是MInimalApi與Swagger數(shù)據(jù)源的關(guān)系,但是為了使得看起來(lái)更清晰,我們還是先看一下MinimalApi如何集成到Swagger,直接上代碼
var builder = WebApplication.CreateBuilder(args); //這是重點(diǎn),是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終結(jié)點(diǎn) 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中展示就必須要添加這個(gè)服務(wù)。所以Swagger還是那個(gè)Swagger,變的是ASP.NET Core本身,但是變化是如何適配數(shù)據(jù)源的問(wèn)題,Swagger便是建立在這個(gè)便利基礎(chǔ)上。接下來(lái)咱們就通過(guò)源碼看一下它們之間的關(guān)系。
源碼探究
想了解它們的關(guān)系就會(huì)涉及到兩個(gè)主角,一個(gè)是swagger的數(shù)據(jù)源來(lái)自何處,另一個(gè)是ASP.NET Core是如何提供這個(gè)數(shù)據(jù)源的。首先我們來(lái)看一下Swagger的數(shù)據(jù)源來(lái)自何處。
swagger的數(shù)據(jù)源
熟悉Swashbuckle.AspNetCore
的應(yīng)該知道它其實(shí)是由幾個(gè)程序集一起構(gòu)建的,也就是說(shuō)Swashbuckle.AspNetCore
本身是一個(gè)解決方案,不過(guò)這不是重點(diǎn),其中生成Swagger.json
的是在Swashbuckle.AspNetCore.SwaggerGen
程序集中,直接找到位置在SwaggerGenerator
類中[點(diǎn)擊查看源碼]只摘要我們關(guān)注的地方即可
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核心數(shù)據(jù)源源來(lái)自_apiDescriptionsProvider var applicableApiDescriptions = _apiDescriptionsProvider.ApiDescriptionGroups.Items .SelectMany(group => group.Items) .Where(apiDesc => !(_options.IgnoreObsoleteActions && apiDesc.CustomAttributes().OfType<ObsoleteAttribute().Any())) .Where(apiDesc => _options.DocInclusionPredicate(documentName, apiDesc)); var schemaRepository = new SchemaRepository(documentName); var swaggerDoc = new OpenApiDocument { Info = info, Servers = GenerateServers(host, basePath), // Paths組裝是來(lái)自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; } }
如果你比較了解Swagger.json
的話那么對(duì)OpenApiDocument
這個(gè)類的結(jié)構(gòu)一定是一目了然,不信的話你可以自行看看它的結(jié)構(gòu)
{ "openapi": "3.0.1", "info": { "title": "MyTest.WebApi", "description": "測(cè)試接口", "version": "v1" }, "paths": { "/": { "get": { "tags": [ "MyTest.WebApi" ], "responses": { "200": { "description": "Success", "content": { "text/plain": { "schema": { "type": "string" } } } } } } } }, "components": {} }
這么看清晰了吧OpenApiDocument
這個(gè)類就是返回Swagger.json
的模型類,而承載描述接口信息的核心字段paths
正是來(lái)自IApiDescriptionGroupCollectionProvider
。所以小結(jié)一下,Swagger接口的文檔信息的數(shù)據(jù)源來(lái)自于IApiDescriptionGroupCollectionProvider
。
ASP.Net Core如何提供
通過(guò)上面在Swashbuckle.AspNetCore.SwaggerGen
程序集中,我們看到了真正組裝Swagger接口文檔部分的數(shù)據(jù)源來(lái)自于IApiDescriptionGroupCollectionProvider
,但是這個(gè)接口并非來(lái)自Swashbuckle而是來(lái)自ASP.NET Core。這就引入了另一個(gè)主角,也是我們上面提到的AddEndpointsApiExplorer
方法。直接在dotnet/aspnetcore
倉(cāng)庫(kù)里找到方法位置[點(diǎn)擊查看源碼]看一下方法實(shí)現(xiàn)
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; }
看到了AddEndpointsApiExplorer方法相信就明白了為啥要添加這個(gè)方法了吧,那你就有疑問(wèn)了為啥不使用MinimalApi的時(shí)候就不用引入AddEndpointsApiExplorer這個(gè)方法了,況且也能使用swagger。這是因?yàn)樵?code>AddControllers方法里添加了AddApiExplorer
方法,這個(gè)方法里包含了針對(duì)Controller的接口描述信息,這里就不過(guò)多說(shuō)了,畢竟這種的核心是MinimalApi。接下來(lái)就看下IApiDescriptionGroupCollectionProvider接口的默認(rèn)實(shí)現(xiàn)ApiDescriptionGroupCollectionProvider
類里的實(shí)現(xiàn)[點(diǎn)擊查看源碼]
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方法返回的數(shù)據(jù) _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[]
通過(guò)上面的方法我們可以知道IApiDescriptionProvider默認(rèn)實(shí)現(xiàn)是EndpointMetadataApiDescriptionProvider
類[點(diǎn)擊查看源碼]看一下相實(shí)現(xiàn)
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) { //核心數(shù)據(jù)來(lái)自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) { //實(shí)現(xiàn)代碼省略 } }
這個(gè)類里還有其他方法代碼也非常多,都是在組裝ApiDescription里的數(shù)據(jù),通過(guò)名稱可以得知,這個(gè)類是為了描述API接口信息用的,但是我們了解到的是它的數(shù)據(jù)源都來(lái)自EndpointDataSource
類的實(shí)例。我們都知道MinimalApi提供的操作方法就是MapGet
、MapPost
、MapPut
、MapDelete
等等,這些方法的本質(zhì)都是在調(diào)用Map
方法[點(diǎn)擊查看源碼],看一下核心實(shí)現(xiàn)
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)); }
通過(guò)Map
方法我們可以看到每次添加一個(gè)MinimalApi終結(jié)點(diǎn)都會(huì)給ModelEndpointDataSource
實(shí)例添加一個(gè)EndpointBuilder
實(shí)例,EndPointBuilder里承載著MinimalApi終結(jié)點(diǎn)的信息,而ModelEndpointDataSource則是繼承了EndpointDataSource
類,這個(gè)可以看它的定義[點(diǎn)擊查看源碼]
internal class ModelEndpointDataSource : EndpointDataSource { }
這就和上面提到的EndpointMetadataApiDescriptionProvider
里的EndpointDataSource聯(lián)系起來(lái)了,但是我們這里看到的是IEndpointRouteBuilder
的DataSources
屬性,從名字看這明顯是一個(gè)集合,我們可以找到定義的地方看一下[點(diǎn)擊查看源碼]
public interface IEndpointRouteBuilder { IApplicationBuilder CreateApplicationBuilder(); IServiceProvider ServiceProvider { get; } //這里是一個(gè)EndpointDataSource的集合 ICollection<EndpointDataSource> DataSources { get; } }
這里既然是一個(gè)集合那如何和EndpointDataSource聯(lián)系起來(lái)呢,接下來(lái)我們就得去看EndpointDataSource是如何被注冊(cè)的即可,找到EndpointDataSource
注冊(cè)的地方[點(diǎn)擊查看源碼]查看一下注冊(cè)代碼
var dataSources = new ObservableCollection<EndpointDataSource>(); services.TryAddEnumerable(ServiceDescriptor.Transient<IConfigureOptions<RouteOptions>, ConfigureRouteOptions>( serviceProvider => new ConfigureRouteOptions(dataSources))); services.TryAddSingleton<EndpointDataSource>(s => { return new CompositeEndpointDataSource(dataSources); });
通過(guò)這段代碼我們可以得到兩點(diǎn)信息
- 一是EndpointDataSource這個(gè)抽象類,系統(tǒng)給他注冊(cè)的是
CompositeEndpointDataSource
這個(gè)子類,看名字可以看出是組合的EndpointDataSource - 二是
CompositeEndpointDataSource
是通過(guò)ObservableCollection<EndpointDataSource>
這么一個(gè)集合來(lái)初始化的
我們可以簡(jiǎn)單的來(lái)看下CompositeEndpointDataSource傳遞的dataSources是如何被接收的[點(diǎn)擊查看源碼]咱們只關(guān)注他說(shuō)如何被接收的
public sealed class CompositeEndpointDataSource : EndpointDataSource { private readonly ICollection<EndpointDataSource> _dataSources = default!; internal CompositeEndpointDataSource(ObservableCollection<EndpointDataSource> dataSources) : this() { _dataSources = dataSources; } public IEnumerable<EndpointDataSource> DataSources => _dataSources; }
通過(guò)上面我們可以看到,系統(tǒng)默認(rèn)為EndpointDataSource抽象類注冊(cè)了CompositeEndpointDataSource
實(shí)現(xiàn)類,而這個(gè)實(shí)現(xiàn)類是一個(gè)組合類,它組合了一個(gè)EndpointDataSource的集合。那么到了這里就只剩下一個(gè)問(wèn)題了,那就是EndpointDataSource
是如何和IEndpointRouteBuilder
的DataSources
屬性關(guān)聯(lián)起來(lái)的。現(xiàn)在有了提供數(shù)據(jù)源的IEndpointRouteBuilder,有承載數(shù)據(jù)的EndpointDataSource。這個(gè)地方呢大家也比較熟悉那就是UseEndpoints
中間件里,我們來(lái)看下是如何實(shí)現(xiàn)的[點(diǎn)擊查看源碼]
public static IApplicationBuilder UseEndpoints(this IApplicationBuilder builder, Action<IEndpointRouteBuilder> configure) { // 省略一堆代碼 //得到IEndpointRouteBuilder實(shí)例 VerifyEndpointRoutingMiddlewareIsRegistered(builder, out var endpointRouteBuilder); //獲取RouteOptions var routeOptions = builder.ApplicationServices.GetRequiredService<IOptions<RouteOptions>>(); //遍歷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(); } }
這里我們看到是獲取的IOptions<RouteOptions>
里的EndpointDataSources,怎么和預(yù)想的劇本不一樣呢?并非如此,你看上面咱們說(shuō)的這段代碼
var dataSources = new ObservableCollection<EndpointDataSource>(); services.TryAddEnumerable(ServiceDescriptor.Transient<IConfigureOptions<RouteOptions>, ConfigureRouteOptions>( serviceProvider => new ConfigureRouteOptions(dataSources)));
上面的dataSources同時(shí)傳遞給了CompositeEndpointDataSource
和ConfigureRouteOptions
,而ConfigureRouteOptions則正是IConfigureOptions<RouteOptions>
類型的,所以獲取IOptions<RouteOptions>
就是獲取的ConfigureRouteOptions的實(shí)例,咱們來(lái)看一下ConfigureRouteOptions類的實(shí)現(xiàn)[點(diǎn)擊查看源碼]
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; } }
它的本質(zhì)操作就是對(duì)RouteOptions
的EndpointDataSources的屬性進(jìn)行操作,因?yàn)?code>ICollection<EndpointDataSource>是引用類型,所以這個(gè)集合是共享的,因此IEndpointRouteBuilder
的DataSources
和IConfigureOptions<RouteOptions>
本質(zhì)是使用了同一個(gè)ICollection<EndpointDataSource>
集合,所以上面的UseEndpoints
里獲取RouteOptions選項(xiàng)的本質(zhì)正是獲取的EndpointDataSource集合。
每次對(duì)IEndpointRouteBuilder
的DataSources
集合Add的時(shí)候其實(shí)是在為ICollection<EndpointDataSource>
集合添加數(shù)據(jù),而IConfigureOptions<RouteOptions>
也使用了這個(gè)集合,所以它們的數(shù)據(jù)是互通的。
許多同學(xué)都很好強(qiáng),默認(rèn)并沒(méi)在MinimalApi看到注冊(cè)UseEndpoints
,但是在ASP.NET Core6.0之前還是需要注冊(cè)UseEndpoints
中間件的。這其實(shí)是ASP.NET Core6.0進(jìn)行的一次升級(jí)優(yōu)化,因?yàn)楹芏嗖僮髂J(rèn)都得添加,所以把它統(tǒng)一封裝起來(lái)了,這個(gè)可以在WebApplicationBuilder
類中看到[點(diǎn)擊查看源碼]在ConfigureApplication
方法中的代碼
private void ConfigureApplication(WebHostBuilderContext context, IApplicationBuilder app) { // 省略部分代碼 // 注冊(cè)UseDeveloperExceptionPage全局異常中間件 if (context.HostingEnvironment.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.Properties.Add(WebApplication.GlobalEndpointRouteBuilderKey, _builtApplication); if (_builtApplication.DataSources.Count > 0) { // 注冊(cè)UseRouting中間件 if (!_builtApplication.Properties.TryGetValue(EndpointRouteBuilderKey, out var localRouteBuilder)) { app.UseRouting(); } else { app.Properties[EndpointRouteBuilderKey] = localRouteBuilder; } } app.Use(next => { //調(diào)用WebApplication的Run方法 _builtApplication.Run(next); return _builtApplication.BuildRequestDelegate(); }); // 如果DataSources集合有數(shù)據(jù)則注冊(cè)UseEndpoints if (_builtApplication.DataSources.Count > 0) { app.UseEndpoints(_ => { }); } // 省略部分代碼 }
相信大家通過(guò)ConfigureApplication
這個(gè)方法大家就了解了吧,之前我們能看到的熟悉方法UseDeveloperExceptionPage
、UseRouting
、UseEndpoints
方法都在這里,畢竟之前這幾個(gè)方法幾乎也成了新建項(xiàng)目時(shí)候必須要添加的,所以微軟干脆就在內(nèi)部統(tǒng)一封裝起來(lái)了。
源碼小結(jié)
上面咱們分析了相關(guān)的源碼,整理起來(lái)就是這么一個(gè)思路。
Swashbuckle.AspNetCore.SwaggerGen
用來(lái)生成swagger的數(shù)據(jù)源來(lái)自IApiDescriptionGroupCollectionProvider
- IApiDescriptionGroupCollectionProvider實(shí)例的數(shù)據(jù)來(lái)自
EndpointDataSource
- 因?yàn)?code>EndpointDataSource的
DataSources
和IConfigureOptions<RouteOptions>
本質(zhì)是使用了同一個(gè)ICollection<EndpointDataSource>
集合,所以它們是同一份數(shù)據(jù) - 每次使用MinimalApi的Map相關(guān)的方法的是會(huì)給
IEndpointRouteBuilder
的DataSources
集合添加數(shù)據(jù) - 在
UseEndpoints
中間件里獲取IEndpointRouteBuilder
的DataSources
數(shù)據(jù)給RouteOptions選項(xiàng)的EndpointDataSources集合屬性添加數(shù)據(jù),本質(zhì)則是給ICollection<EndpointDataSource>
集合賦值,自然也就是給EndpointDataSource
的DataSources
屬性賦值
這也給我們提供了一個(gè)思路,如果你想自己去適配swagger數(shù)據(jù)源的話完全也可以參考這個(gè)思路,想辦法把你要提供的接口信息放到EndpointDataSource的DataSources集合屬性里即可,或者直接適配IApiDescriptionGroupCollectionProvider里的數(shù)據(jù),有興趣的同學(xué)可以自行研究一下。
使用擴(kuò)展
我們看到了微軟給我們提供了IApiDescriptionGroupCollectionProvider
這個(gè)便利條件,所以如果以后有獲取接口信息的時(shí)候則可以直接使用了,很多時(shí)候比如寫(xiě)監(jiān)控程序或者寫(xiě)Api接口調(diào)用的代碼生成器的時(shí)候都可以考慮一下,咱們簡(jiǎn)單的示例一下如何使用,首先定義個(gè)模型類來(lái)承載接口信息
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; } }
這個(gè)類非常簡(jiǎn)單只做演示使用,然后我們?cè)贗ApiDescriptionGroupCollectionProvider里獲取信息來(lái)填充這個(gè)集合,這里我們寫(xiě)一個(gè)htt接口來(lái)展示
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; });
這個(gè)時(shí)候當(dāng)你在瀏覽器里請(qǐng)求/apiinfo
路徑的時(shí)候會(huì)返回你的webapi包含的接口相關(guān)的信息。咱們的示例是非常簡(jiǎn)單的,實(shí)際上IApiDescriptionGroupCollectionProvider包含的接口信息是非常多的包含請(qǐng)求參數(shù)信息、輸出返回信息等很全面,這也是swagger可以完全依賴它的原因,有興趣的同學(xué)可以自行的了解一下,這里就不過(guò)多講解了。
總結(jié)
本文咱們主要通過(guò)MinimalApi如何適配swagger的這么一個(gè)過(guò)程來(lái)講解了ASP.NET Core是如何給Swagger提供了數(shù)據(jù)的。本質(zhì)是微軟在ASP.NET Core本身提供了IApiDescriptionGroupCollectionProvider
這么一個(gè)數(shù)據(jù)源,Swagger借助這個(gè)數(shù)據(jù)源生成了swagger文檔,IApiDescriptionGroupCollectionProvider來(lái)自聲明終結(jié)點(diǎn)的時(shí)候往EndpointDataSource
的DataSources
集合里添加的接口信息等。其實(shí)它內(nèi)部比這個(gè)還要復(fù)雜一點(diǎn),不過(guò)如果我們用來(lái)獲取接口信息的話,大部分時(shí)候使用IApiDescriptionGroupCollectionProvider應(yīng)該就足夠了。
分享一段我個(gè)人比較認(rèn)可的話,與其天天鉆頭覓縫、找各種機(jī)會(huì),不如把這些時(shí)間和金錢投入到自己的能力建設(shè)上。機(jī)會(huì)稍縱即逝,而且別人給你的機(jī)會(huì),沒(méi)準(zhǔn)兒反而是陷阱。而投資個(gè)人能力就是積累一個(gè)資產(chǎn)賬戶,只能越存越多,看起來(lái)慢,但是你永遠(yuǎn)在享受時(shí)間帶來(lái)的復(fù)利,其實(shí)快得很,收益也穩(wěn)定得多。有了能力之后,機(jī)會(huì)也就來(lái)了。
以上就是源碼分析MinimalApi是如何在Swagger中展示的詳細(xì)內(nèi)容,更多關(guān)于MinimalApi在Swagger展示的資料請(qǐng)關(guān)注其它相關(guān)文章!
