加入收藏 | 设为首页 | 会员中心 | 我要投稿 驾考网 (https://www.jiakaowang.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 站长学院 > Asp教程 > 正文

源码分析MinimalApi是怎么在Swagger中展示

发布时间:2023-03-02 13:05:07 所属栏目:Asp教程 来源:
导读:之前看到技术群里有同学讨论说对于MinimalApi能接入到Swagger中感到很神奇,加上Swagger的数据本身是支持OpenApi2.0和OpenApi3.0使得swagger.json成为了许多接口文档管理工具的标准数据源。

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>类型的,所以获取。

(编辑:驾考网)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章