国产成人精品久久免费动漫-国产成人精品天堂-国产成人精品区在线观看-国产成人精品日本-a级毛片无码免费真人-a级毛片毛片免费观看久潮喷

您的位置:首頁技術文章
文章詳情頁

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

瀏覽:188日期:2022-06-04 15:04:03
目錄
  • 前言
  • 使用方式
  • 源碼探究
  • swagger的數據源
  • ASP.Net Core如何提供
  • 源碼小結
  • 使用擴展
  • 總結

前言

之前看到技術群里有同學討論說對于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展示的資料請關注其它相關文章!

標簽: ASP
主站蜘蛛池模板: 免费刺激视频 | 在线观看视频国产 | 97超级碰碰碰碰在线视频 | 美女扒开腿让男生桶爽网站 | 久久久这里只有精品免费 | 国产欧美曰韩一区二区三区 | 成人爱做日本视频免费 | 成人午夜影视全部免费看 | 理论在线看 | 久久久久综合 | 日本www高清免费视频观看 | 一区二区三区四区五区六区 | 欧美13一14sexvideo欧 | 久久精品国产这里是免费 | 四虎免费大片aⅴ入口 | 国产一久久香蕉国产线看观看 | 毛片1级 | 久久99久久99精品 | 亚洲欧美视频在线播放 | 九九热视频精品 | 色哟哟国产成人精品 | 在线欧美日韩精品一区二区 | bt天堂国产亚洲欧美在线 | 国产日韩欧美在线 | 目韩一区二区三区系列片丶 | 久久久久亚洲国产 | 亚洲伊人色一综合网 | 日本www高清免费视频观看 | 天干天干天啪啪夜爽爽色 | 成人在线观看网址 | 玖草| 一区二区三区欧美日韩国产 | 一级a性色生活片久久毛片 一级a做爰片欧欧美毛片4 | 国产成年人视频 | 泰国情欲片寂寞的寡妇在线观看 | 国产成人精品一区二区三区 | 香港经典a毛片免费观看爽爽影院 | 国产自一区 | 国产免费爱在线观看视频 | 97视频在线观看免费 | 996热这里有精品青青草原 |