我的编程空间,编程开发者的网络收藏夹
学习永远不晚

源码分析MinimalApi是如何在Swagger中展示

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

源码分析MinimalApi是如何在Swagger中展示

前言

之前看到技术群里有同学讨论说对于MinimalApi能接入到Swagger中感到很神奇,加上Swagger的数据本身是支持OpenApi2.0OpenApi3.0使得swagger.json成为了许多接口文档管理工具的标准数据源。

ASP.NET Core能够轻松快速的集成Swagger得益于微软对OpenApi的大力支持,大部分情况下几乎是添加默认配置,就能很好的工作了。这一切都是得益于ASP.NET Core底层提供了对接口元数据的描述和对终结点的相关描述。本文我们就通过MinimalApi来了解一下ASP.NET Core为何能更好的集成Swagger。

使用方式

虽然我们讨论的是MInimalApi与Swagger数据源的关系,但是为了使得看起来更清晰,我们还是先看一下MinimalApi如何集成到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程序集中,直接找到位置在SwaggerGenerator类中[点击查看源码?]只摘要我们关注的地方即可

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;
    }
}

如果你比较了解Swagger.json的话那么对OpenApiDocument这个类的结构一定是一目了然,不信的话你可以自行看看它的结构

{
  "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仓库里找到方法位置[点击查看源码?]看一下方法实现

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方法相信就明白了为啥要添加这个方法了吧,那你就有疑问了为啥不使用MinimalApi的时候就不用引入AddEndpointsApiExplorer这个方法了,况且也能使用swagger。这是因为在AddControllers方法里添加了AddApiExplorer方法,这个方法里包含了针对Controller的接口描述信息,这里就不过多说了,毕竟这种的核心是MinimalApi。接下来就看下IApiDescriptionGroupCollectionProvider接口的默认实现ApiDescriptionGroupCollectionProvider类里的实现[点击查看源码?]

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默认实现是EndpointMetadataApiDescriptionProvider类[点击查看源码?]看一下相实现

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)
    {
        //实现代码省略	
    }
}

这个类里还有其他方法代码也非常多,都是在组装ApiDescription里的数据,通过名称可以得知,这个类是为了描述API接口信息用的,但是我们了解到的是它的数据源都来自EndpointDataSource类的实例。我们都知道MinimalApi提供的操作方法就是MapGetMapPostMapPutMapDelete等等,这些方法的本质都是在调用Map方法[点击查看源码?],看一下核心实现

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

通过Map方法我们可以看到每次添加一个MinimalApi终结点都会给ModelEndpointDataSource实例添加一个EndpointBuilder实例,EndPointBuilder里承载着MinimalApi终结点的信息,而ModelEndpointDataSource则是继承了EndpointDataSource类,这个可以看它的定义[点击查看源码?]

internal class ModelEndpointDataSource : EndpointDataSource
{
}

这就和上面提到的EndpointMetadataApiDescriptionProvider里的EndpointDataSource联系起来了,但是我们这里看到的是IEndpointRouteBuilderDataSources属性,从名字看这明显是一个集合,我们可以找到定义的地方看一下[点击查看源码?]

public interface IEndpointRouteBuilder
{
    IApplicationBuilder CreateApplicationBuilder();
    IServiceProvider ServiceProvider { get; }
    //这里是一个EndpointDataSource的集合
    ICollection<EndpointDataSource> DataSources { get; }
}

这里既然是一个集合那如何和EndpointDataSource联系起来呢,接下来我们就得去看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是如何和IEndpointRouteBuilderDataSources属性关联起来的。现在有了提供数据源的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同时传递给了CompositeEndpointDataSourceConfigureRouteOptions,而ConfigureRouteOptions则正是IConfigureOptions<RouteOptions>类型的,所以获取IOptions<RouteOptions>就是获取的ConfigureRouteOptions的实例,咱们来看一下ConfigureRouteOptions类的实现[点击查看源码?]

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;
    }
}

它的本质操作就是对RouteOptions的EndpointDataSources的属性进行操作,因为ICollection<EndpointDataSource>是引用类型,所以这个集合是共享的,因此IEndpointRouteBuilderDataSourcesIConfigureOptions<RouteOptions>本质是使用了同一个ICollection<EndpointDataSource>集合,所以上面的UseEndpoints里获取RouteOptions选项的本质正是获取的EndpointDataSource集合。

每次对IEndpointRouteBuilderDataSources集合Add的时候其实是在为ICollection<EndpointDataSource>集合添加数据,而IConfigureOptions<RouteOptions>也使用了这个集合,所以它们的数据是互通的。

许多同学都很好强,默认并没在MinimalApi看到注册UseEndpoints,但是在ASP.NET Core6.0之前还是需要注册UseEndpoints中间件的。这其实是ASP.NET Core6.0进行的一次升级优化,因为很多操作默认都得添加,所以把它统一封装起来了,这个可以在WebApplicationBuilder类中看到[点击查看源码?]在ConfigureApplication方法中的代码

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这个方法大家就了解了吧,之前我们能看到的熟悉方法UseDeveloperExceptionPageUseRoutingUseEndpoints方法都在这里,毕竟之前这几个方法几乎也成了新建项目时候必须要添加的,所以微软干脆就在内部统一封装起来了。

源码小结

上面咱们分析了相关的源码,整理起来就是这么一个思路。

  • Swashbuckle.AspNetCore.SwaggerGen用来生成swagger的数据源来自IApiDescriptionGroupCollectionProvider
  • IApiDescriptionGroupCollectionProvider实例的数据来自EndpointDataSource
  • 因为EndpointDataSourceDataSourcesIConfigureOptions<RouteOptions>本质是使用了同一个ICollection<EndpointDataSource>集合,所以它们是同一份数据
  • 每次使用MinimalApi的Map相关的方法的是会给IEndpointRouteBuilderDataSources集合添加数据
  • UseEndpoints中间件里获取IEndpointRouteBuilderDataSources数据给RouteOptions选项的EndpointDataSources集合属性添加数据,本质则是给ICollection<EndpointDataSource>集合赋值,自然也就是给EndpointDataSourceDataSources属性赋值

这也给我们提供了一个思路,如果你想自己去适配swagger数据源的话完全也可以参考这个思路,想办法把你要提供的接口信息放到EndpointDataSource的DataSources集合属性里即可,或者直接适配IApiDescriptionGroupCollectionProvider里的数据,有兴趣的同学可以自行研究一下。

使用扩展

我们看到了微软给我们提供了IApiDescriptionGroupCollectionProvider这个便利条件,所以如果以后有获取接口信息的时候则可以直接使用了,很多时候比如写监控程序或者写Api接口调用的代码生成器的时候都可以考虑一下,咱们简单的示例一下如何使用,首先定义个模型类来承载接口信息

public class ApiDoc
{
    /// &lt;summary&gt;
    /// 接口分组
    /// &lt;/summary&gt;
    public string Group { get; set; }
    /// &lt;summary&gt;
    /// 接口路由
    /// &lt;/summary&gt;
    public string Route { get; set; }
    /// &lt;summary&gt;
    /// http方法
    /// &lt;/summary&gt;
    public string HttpMethod { get; set; }
}

这个类非常简单只做演示使用,然后我们在IApiDescriptionGroupCollectionProvider里获取信息来填充这个集合,这里我们写一个htt接口来展示

app.MapGet("/apiinfo", (IApiDescriptionGroupCollectionProvider provider) =&gt; {
    List&lt;ApiDoc&gt; docs = new List&lt;ApiDoc&gt;();
    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来自声明终结点的时候往EndpointDataSourceDataSources集合里添加的接口信息等。其实它内部比这个还要复杂一点,不过如果我们用来获取接口信息的话,大部分时候使用IApiDescriptionGroupCollectionProvider应该就足够了。    

分享一段我个人比较认可的话,与其天天钻头觅缝、找各种机会,不如把这些时间和金钱投入到自己的能力建设上。机会稍纵即逝,而且别人给你的机会,没准儿反而是陷阱。而投资个人能力就是积累一个资产账户,只能越存越多,看起来慢,但是你永远在享受时间带来的复利,其实快得很,收益也稳定得多。有了能力之后,机会也就来了。

以上就是源码分析MinimalApi是如何在Swagger中展示的详细内容,更多关于MinimalApi在Swagger展示的资料请关注编程网其它相关文章!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

源码分析MinimalApi是如何在Swagger中展示

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

如何在Prometheus中展示历史数据和趋势分析

要在Prometheus中展示历史数据和趋势分析,可以通过Prometheus提供的查询语言PromQL来实现。以下是一些步骤可以帮助您展示历史数据和趋势分析:使用PromQL查询历史数据:您可以使用PromQL查询语言来检索历史数据。例如
如何在Prometheus中展示历史数据和趋势分析
2024-03-04

源码分析C++是如何实现string的

我们平时使用C++开发过程中或多或少都会使用std::string,但您了解string具体是如何实现的吗,本文小编就带大家从源码角度分析一下
2023-05-14

如何用源码分析在linux上的safe point

这篇文章将为大家详细讲解有关如何用源码分析在linux上的safe point,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。safe point 顾明思意,就是安全点,当需要jvm做一些操作
2023-06-17

在浏览器中如何展示PHP代码源文件而不让其运行?

标题:如何在浏览器中展示PHP代码源文件而不让其运行?PHP是一种广泛使用的服务器端脚本语言,常用于开发动态网页。然而,在某些情况下,我们可能希望在浏览器中展示PHP代码源文件,而不让其运行。这篇文章将介绍如何实现这一目标,以及提供具体的
在浏览器中如何展示PHP代码源文件而不让其运行?
2024-03-09

如何理解Java容器中ArrayList的源码分析

这篇文章给大家介绍如何理解Java容器中List的源码分析,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。如果没有特别说明,以下源码分析基于 JDK 1.8。一、ArrayList1. 概览实现了 RandomAcces
2023-06-05

如何理解Java容器中Map的源码分析

本篇文章为大家展示了如何理解Java容器中Map的源码分析,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。如果没有特别说明,以下源码分析基于 JDK 1.8。一、HashMap为了便于理解,以下源码分
2023-06-05

Android源码分析——View是如何被添加到屏幕的?

我们看到R.id.content确实是存在的,并且它是一个FrameLayout。到这里我们来看下现在前面这些具体做了哪些? 如下图所示,到这里 Activity 持有一个 PhoneWindow 对象,PhoneWindow 中有一个 D
2022-06-06

Java异步编程中如何进行FutureTask源码分析

本篇文章给大家分享的是有关Java异步编程中如何进行FutureTask源码分析,小编觉得挺实用的,因此分享给大家学习,希望大家阅读完这篇文章后可以有所收获,话不多说,跟着小编一起来看看吧。Java的异步编程是一项非常常用的多线程技术。但之
2023-06-19

如何理解Java 容器中并发容器的源码分析

这期内容当中小编将会给大家带来有关如何理解Java 容器中并发容器的源码分析,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。如果没有特别说明,以下源码分析基于 JDK 1.8。CopyOnWriteArra
2023-06-05

PHP代码在浏览器中如何显示源码而不被解释执行?

PHP代码在浏览器中如何显示源码而不被解释执行?PHP是一种服务器端脚本语言,通常用于开发动态网页。当PHP文件在服务器上被请求时,服务器会解释执行其中的PHP代码,并将最终的HTML内容发送到浏览器以供显示。然而,有时我们希望在浏览器中
PHP代码在浏览器中如何显示源码而不被解释执行?
2024-03-11

如何在C++中集成外部数据源以丰富分析过程?

c++++ 中集成外部数据源可大幅拓展数据分析能力。步驟包括:選擇與目標數據源兼容的連接器,根據數據源要求建立連接,並使用 sql 進行查詢。一個使用 odbc 連接器連接 mysql 的實例顯示如何提取數據結果。集成外部數據源可豐富分析過
如何在C++中集成外部数据源以丰富分析过程?
2024-05-15

如何在GitLab中进行代码质量分析和度量

如何在GitLab中进行代码质量分析和度量引言:在软件开发过程中,代码质量是一个非常重要的指标。良好的代码质量可以确保代码的可维护性、可扩展性和稳定性。而度量代码质量可以帮助团队发现和解决潜在的问题,提升整体的开发效率和质量。本文将介绍如何
2023-10-22

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录