源码分析MinimalApi是怎么在Swagger中展示
发布时间:2023-03-02 13:05:07 所属栏目:Asp教程 来源:
导读:之前看到技术群里有同学讨论说对于MinimalApi能接入到Swagger中感到很神奇,加上Swagger的数据本身是支持OpenApi2.0和OpenApi3.0使得swagger.json成为了许多接口文档管理工具的标准数据源。
ASP.NET Core能够轻松快
ASP.NET Core能够轻松快
|
之前看到技术群里有同学讨论说对于MinimalApi能接入到Swagger中感到很神奇,加上Swagger的数据本身是支持OpenApi2.0和OpenApi3.0使得swagger.json成为了许多接口文档管理工具的标准数据源。 ASP.NET Core能够轻松快速的集成Swagger得益于微软对OpenApi的大力支持,大部分情况下几乎是添加默认配置,就能很好的工作了。这一切都是得益于ASP.NET Core底层提供了对接口元数据的描述和对终结点的相关描述。本文我们就通过MinimalApi来了解一下ASP.NET Core为何能更好的集成Swagger。 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程序集中。 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().Any())) .Where(apiDesc => _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; { "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。 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; } 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); } } 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) { //实现代码省略 } } 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)); } public interface IEndpointRouteBuilder { IApplicationBuilder CreateApplicationBuilder(); IServiceProvider ServiceProvider { get; } //这里是一个EndpointDataSource的集合 ICollection<EndpointDataSource> DataSources { get; } } 这里既然是一个集合那如何和EndpointDataSource联系起来呢,接下来我们就得去看EndpointDataSource是如何被注册的即可。 var dataSources = new ObservableCollection<EndpointDataSource>(); services.TryAddEnumerable(ServiceDescriptor.Transient<IConfigureOptions<RouteOptions>, ConfigureRouteOptions>( serviceProvider => new ConfigureRouteOptions(dataSources))); services.TryAddSingleton<EndpointDataSource>(s => { return new CompositeEndpointDataSource(dataSources); }); 通过这段代码我们可以得到两点信息 一是EndpointDataSource这个抽象类,系统给他注册的是CompositeEndpointDataSource这个子类,看名字可以看出是组合的EndpointDataSource 二是CompositeEndpointDataSource是通过ObservableCollection<EndpointDataSource>这么一个集合来初始化的 我们可以简单的来看下CompositeEndpointDataSource传递的dataSources是如何被接收的[点击查看源码👈]咱们只关注他说如何被接收的 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抽象类注册了CompositeEndpointDataSource实现类,而这个实现类是一个组合类,它组合了一个EndpointDataSource的集合。那么到了这里就只剩下一个问题了,那就是EndpointDataSource是如何和IEndpointRouteBuilder的DataSources属性关联起来的。现在有了提供数据源的IEndpointRouteBuilder,有承载数据的EndpointDataSource。这个地方呢大家也比较熟悉那就是UseEndpoints中间件里,我们来看下是如何实现的[点击查看源码👈] public static IApplicationBuilder UseEndpoints(this IApplicationBuilder builder, Action<IEndpointRouteBuilder> configure) { // 省略一堆代码 //得到IEndpointRouteBuilder实例 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,怎么和预想的剧本不一样呢?并非如此。 var dataSources = new ObservableCollection<EndpointDataSource>(); services.TryAddEnumerable(ServiceDescriptor.Transient<IConfigureOptions<RouteOptions>, ConfigureRouteOptions>( serviceProvider => new ConfigureRouteOptions(dataSources))); 上面的dataSources同时传递给了CompositeEndpointDataSource和ConfigureRouteOptions,而ConfigureRouteOptions则正是IConfigureOptions<RouteOptions>类型的,所以获取。 (编辑:驾考网) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |
推荐文章
站长推荐
