久久福利_99r_国产日韩在线视频_直接看av的网站_中文欧美日韩_久久一

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

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

瀏覽:270日期: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
主站蜘蛛池模板: 日韩手机在线观看 | 欧美aaa一级片 | 亚洲一区二区三区爽爽爽爽爽 | 久综合在线 | 在线观看免费黄色小视频 | 国产精品极品美女在线观看免费 | 亚洲精选免费视频 | 久久精品电影网 | 久久久久香蕉视频 | 色性网| 黄色国产大片 | 久久久成人精品视频 | 在线视频 欧美日韩 | 欧美极品一区二区三区 | 亚洲精品国产setv | 一区二区三区在线 | 欧美jizzhd精品欧美巨大免费 | 香蕉视频在线看 | 亚洲成人日韩 | 亚洲一区日韩 | 在线视频久久 | 久久在线视频 | 狠狠操夜夜操 | 一区二区视频在线 | 亚洲免费资源 | 91精品国产乱码久久久久久 | 精品在线看 | 欧美伦理一区二区 | 欧美日韩亚洲高清 | 国产精品无码专区在线观看 | 国产免费一区二区三区网站免费 | 亚洲一区精品在线 | 成人免费毛片aaaaaa片 | 激情网在线观看 | 91免费在线播放 | 特级a做爰全过程片 | 综合网在线 | 久草新视频在线观看 | 久久久久国产一区二区三区 | 久久久精品国产 | 成人a级片在线观看 | 精品亚洲一区二区三区在线观看 | 国产小视频在线播放 | 黄色片在线免费看 | 午夜剧| 中文字幕在线精品 | 国内精品久久久久久影视8 91一区二区在线观看 | 亚洲国产高清视频 | 欧美日韩国产综合网 | 久久伊人精品视频 | 欧美久久久精品 | 日韩专区中文字幕 | 日韩欧美精品区 | 日韩激情欧美 | 精品一区免费观看 | 国产二区视频 | 日本在线小视频 | 国精日本亚洲欧州国产中文久久 | 亚洲精品国产剧情久久9191 | 国产精品久久久久久福利一牛影视 | 高清精品一区二区 | 国产一区日韩在线 | 欧美在线a | 国产猛男猛女超爽免费视频网站 | 日韩免费视频 | 九九热这里都是精品 | 精品国产一区二区三区久久久蜜月 | 精品久久一区二区 | 亚洲国产精品99久久久久久久久 | 欧美激情欧美激情在线五月 | 日韩a∨精品日韩在线观看 山岸逢花在线 | 欧美亚洲国产一区二区三区 | 欧美福利视频 | 欧美日韩免费在线 | 99久久久精品 | 黄色毛片在线观看 | 91精品国产综合久久久久久 | 91成人免费看片 | 亚洲欧美在线观看 | 成人片网址 | 日韩成人免费 | av在线一区二区三区 | 在线精品亚洲 | 一级全黄少妇性色生活片免费 | 一区二区三区日韩 | 成人av网站免费观看 | 亚洲国产日韩在线 | 特级毛片www | 永久免费精品视频 | 中文字幕亚洲区 | 亚洲精品中文字幕乱码无线 | 久久综合九色综合欧美狠狠 | 日韩一区在线观看视频 | 一区二区三区免费看 | 国产伊人一区 | 国产一区亚洲二区三区 | 成人精品一区二区三区电影黑人 | 久久生活片 | 天天精品视频免费观看 | 一级做a爰性色毛片免费1 | 国产一区二区三区免费看 | 亚洲电影免费 | 亚洲精品在线成人 | 国产成人精品免费 | 中文日韩av | 色欧美片视频在线观看 | 欧美精品免费在线观看 | 国产91网| 久久国产传媒 | 日韩啊啊啊 | 国产免费一区二区三区 | 99久久夜色精品国产网站 | 欧美一区2区三区3区公司 | 午夜精品在线 | 国产精品久久久久久久久动漫 | 国产精品久久久久久久久久ktv | 欧美在线观看一区 | 黄色免费在线观看 | 久久国产欧美日韩精品 | 久久久久久影院 | 日韩一区在线观看视频 | 国产精品99久久免费观看 | 久久久久久婷婷 | 精品在线播放 | 经典法国性xxxx精品 | 91中文 | h免费观看 | 精品久久一级片 | 亚洲精彩视频 | 国产精品久久天天躁 | 亚洲综合成人网 | 午夜婷婷丁香 | 亚洲一区二区三区四区在线观看 | 国产精品免费一区二区三区四区 | 亚洲中字幕女 | 成人免费xxxxxxx | 91伊人| 乳色吐息在线观看 | 日韩成人国产 | 国产一区二区三区精品久久久 | 亚洲一区二区三区免费 | 国产小视频在线 | 国产一区二区自拍 | 国产福利精品一区 | 午夜免费小视频 | www.久久 | 91精品国产乱码久久蜜臀 | 国产精品一区二区三区在线 | 国产精品一区二区三区四区 | 一区二区av在线 | 亚洲精品综合 | a一级片在线观看 | 最新av片| 精品国产欧美 | 亚洲在线 | 色吊丝在线永久观看最新版本 | 高清久久| 午夜在线影院 | 国产欧美综合一区二区三区 | 中字一区 | 欧美一区二区三区精品 | 亚洲a人 | 国产伦精品一区二区三区四区视频 | 免费看黄视频网站 | 久久精品成人一区二区三区蜜臀 | 久久精品视频免费看 | 日韩中文字幕免费观看 | 在线视频久 | 百性阁综合另类 | 精品国产乱码久久久久久88av | 欧美1区 | 黄色大片成人 | 九九热精品视频在线观看 | 国产女人和拘做受视频 | 国产精品视频久久 | 欧美激情视频一区二区三区在线播放 | 麻豆freexxxx性91精品 | 毛片视频网站 | 在线免费视频一区二区 | 亚洲 中文 欧美 日韩 在线观看 | 日韩视频一区二区三区在线观看 | 毛片搜索 | 成人一区二区三区 | 可以免费观看的av | 日韩色在线 | 五月天婷婷激情视频 | 国产精品视频一区二区三区 | 国内精品亚洲 | 免费观看视频www | 天天艹视频 | 国产精品国产成人国产三级 | 小情侣高清国产在线播放 | 亚洲日韩中文字幕 | 久久久精品视频免费观看 | 国产精品久久久久久久久免费 | 日本电影www | 免费黄在线观看 | 欧美性一区二区三区 | 精品婷婷 | 亚洲一区二区在线 | 色综合天天天天做夜夜夜夜做 | 黄色最新网站 | 午夜欧美 | 国产日韩欧美91 | 国产欧美专区 | 永久av| 欧美在线三级 | 中文字幕在线观看 | gav成人免费播放视频 | 国产女爽123视频.cno | 午夜欧美| 欧美亚洲综合久久 | 欧美一级一区 | 久久久国产精品视频 | 免费看国产一级特黄aaaa大片 | 久久亚洲精品综合 | 久久99国产精品久久99果冻传媒 | 成人深夜在线观看 | 免费观看的av | 亚洲精品二三区 | 欧美一级网站 | 亚洲国产成人在线 | 欧美一区二区三区精品免费 | 1级毛片| 免费黄色在线 | 资源av| 97久久超碰 | 亚洲国产精品一区二区三区 | 亚洲一区二区在线播放 | 成人精品网 | 国产精品久久久久久久免费大片 | 国产视频一区二区 | 精品久久97 | 97国产一区二区 | 日本三级做a全过程在线观看 | 亚洲成人久久久 | 嫩草视频网 | 在线免费av观看 | 国产在线一区二区三区 | 欧美亚洲激情 | 欧洲在线一区 | 欧美黄视频在线观看 | 97久久久| 成人高清视频在线观看 | 国产中文字幕一区 | 欧美日韩成人在线 | 亚洲网站在线观看 | 99这里只有精品视频 | 中文字幕精品三级久久久 | 午夜精品久久久久久久白皮肤 | 亚洲欧美视频一区 | 5060毛片 | 亚洲网站免费看 | 国产精品日韩精品 | 国产一区在线免费 | 国产精品久久久久久亚洲调教 | 美女视频黄色 | 美女天堂| 午夜电影在线看 | 在线观看成人 | 日韩视频在线一区 | 中文字幕在线观看 | 日韩视频在线观看 | 国产三级日本三级美三级 | 精品国产三级 | a在线观看免费视频 | 欧美嘿咻 | 一区二区三区国产视频 | 国产情侣在线视频 | 九九热免费精品视频 | 毛片在线视频 | 久久久久久久久中文字幕 | 毛片入口| 国内精品99 | 视频二区| 91网站在线播放 | 国产欧美日韩中文字幕 | 精品久久久久久久久久久久 | 九一精品 | 亚洲综合视频一区 | 91精品国产综合久久久久久丝袜 | 三级网站在线播放 | 久久99精品久久久 | 国产91亚洲精品 | 欧美性一区二区三区 | 国产成人精品一区 | 日本成人福利视频 | 久久久久91 | 国产成人精品综合 | 91久久精品久久国产性色也91 | 亚洲狠狠爱 | 殴美一区| 国产精品资源在线 | 国产欧美日韩精品一区 | 久久久精品影院 | 欧美日韩综合视频 | 精品国产乱码久久久久久闺蜜 | 国产视频一区二区三区四区 | h片免费观看| 91性高湖久久久久久久久_久久99 | 欧美日韩一区二区三区在线观看 | 欧美日韩在线播放 | 999久久久国产999久久久 | 黄色片视频在线观看 | 精品国产一区二区在线 | 羞羞羞网站| 天天夜夜操 | 欧美日韩在线一区 | 日韩欧美在线观看一区二区 | 日韩在线不卡 | 国产成人午夜 | 亚洲欧美日韩在线 | 色欧美日韩 | 精品久久久久久久久久久久久久 | 亚洲国产精品麻豆 | 羞羞的视频在线免费观看 | 91佛爷在线观看 | 久久亚洲二区 | 国产精品久久久久久久午夜 | 国产精品视频播放 | 精品国产一区二区三区性色av | 亚洲精品一区二区三区 | 精品久久久久久久久久久久久久 | 激情一区二区三区 | 欧美成人一区二区三区片免费 | 999国产在线观看 | 国产三级自拍 | 久久精品首页 | 色婷婷综合久久久中字幕精品久久 | 亚洲国产情侣自拍 | ririsao亚洲国产中文 | 久久成人免费视频 | 在线一区二区三区 | 天天操天天干天天插 | avmans最新导航地址 | 精品国产精品三级精品av网址 | 伊人免费在线观看高清版 | 国产美女高潮视频 | 午夜小电影 | 国产精品久久久久久福利一牛影视 | 天天操天天摸天天干 | 二区三区在线观看 | 成人h视频在线观看 | 色婷婷在线视频观看 | 亚洲免费在线播放 | 国产一区二区三区在线视频 | 日韩在线观看中文字幕 | 国产超碰人人模人人爽人人添 | 日韩avav| 一区二区三区国产 | 午夜久久 | 欧美激情一区二区三区在线视频 | 天堂一区二区三区 | av网站免费观看 | 日韩在线精品 | 久久国产区| 91免费观看视频 | 综合久草| 午夜免费小视频 | 国产一区二区在线免费 | 99久久精品免费看国产一区二区三区 | 99久久婷婷国产综合精品 | 欧美精品综合在线 | 91久久久久久久久 | 久久a国产 | 九九综合 | 国内精品视频一区二区三区 | 久久久免费观看 | 国产成人一区二区三区 | 综合99| 日韩一区欧美一区 | 日韩成人一区 | julia中文字幕久久一区二区 | 91精品国产色综合久久不卡98口 | 亚洲视频在线免费观看 | 精品国产31久久久久久 | 国产欧美综合一区二区三区 | 亚洲精品一区二区三区在线 | 欧美日韩中文字幕 | 黄色地址 | av片免费 | 四虎5151久久欧美毛片 | ririsao久久精品一区 | 爱爱日韩| 亚洲久久 | 免费国产视频 | 精品久久久网站 | 久久久精品综合 | 一级色视频 | 午夜免费视频网站 | 精品美女久久久 | 最新的黄色网址 | 欧美一区在线视频 | 97人人做人人人难人人做 | 一二三区视频 | 国产高清自拍 | 精品中文字幕在线 | 成人激情免费视频 | 在线草| 五月激情综合网 | 99精品国产热久久91蜜凸 | 一级视频黄色 | 日韩精品视频在线观看免费 | 亚洲不卡在线 | 干狠狠| 午夜欧美 | 国产免费一区二区 | 香蕉久久久久久 | 国产成人av在线播放 | 日韩视频精品在线观看 | 欧美一级播放 | 先锋影音在线观看 | 在线观看国产视频 | 久久久网 | 国内精品亚洲 | 精品中文字幕一区二区 | 免费一区二区三区 | 国产成人一区 | 蜜桃一区二区三区 | 91精品一区二区 | 亚洲男人天堂2024 | 国产一区二区视频在线 | 中文成人在线 | av网站免费 | 日韩免费视频 | 日韩在线精品 | 国产一区二区三区四区在线观看 | 日韩欧美二区 | 免费一级毛片 | 国产精品成人一区二区三区夜夜夜 | 欧美一区二区激情三区 | 午夜影院在线看 | 日韩不卡一区 | 日韩在线小视频 | 麻豆久久久久久 | 亚洲精品视频在线播放 | 黑人巨大精品欧美一区二区免费 | 亚洲日本中文 | 国产精品久久久av | 狠狠操网站| 99久久婷婷国产精品综合 | 日干夜干天天干 | 精久久 | 亚洲女人天堂网 | 欧美三级视频在线播放 | 日韩在线播放一区 | 欧美精品一区二区三区在线 | www.久久久.com| 久久久久女人精品毛片九一韩国 | 在线观看中文字幕 | 精品一区二区三区四区 | 91精品日韩| 色一情一乱一伦一区二区三区 | 国产精品视频一二三 | 欧美亚洲国产一区 | 夜夜操天天操 | 天天想天天干 | 久久综合狠狠综合久久 | 欧美成人精品激情在线观看 | 国产精品视频一二三区 | 日韩成人在线播放 | 亚洲网色| 欧美日韩一区二区三区在线观看 | 琪琪午夜伦伦电影福利片 | 国产精品日韩欧美一区二区三区 | 色伊人久久 | 在线视频成人 | 欧美三级电影在线播放 | 99精品久久久 | 日本黄色影片在线观看 | 天天亚洲综合 | 日韩欧美大片在线观看 | 亚洲成人基地 | 国产成人高清 | 99精品视频免费 | 综合久久亚洲 | av色伊人久久综合一区二区 | 99久久婷婷国产精品综合 | 君岛美绪一区二区三区在线视频 | 欧美精品亚洲精品 | 欧美日韩综合视频 | 日韩精品一区二区三区中文字幕 | 国产成人久久精品一区二区三区 | 中文字幕国产视频 | 韩国一区二区视频 | 久久99久久99精品免观看粉嫩 | 91精品啪aⅴ在线观看国产 | av在线一区二区三区 | 毛片国产 | 日韩免费在线观看视频 | 成人午夜性a一级毛片免费看 | 成人精品一区二区三区中文字幕 | 91亚洲国产成人久久精品网站 | 2020亚洲视频 | 亚洲精品在线视频 | 男女国产网站 | 青青草亚洲| 黄色永久网站 | 一级人爱视频 | 97伦理电影院| 国产精品久久久久久久久久妇女 | 国产一区二区三区av在线 | 成人免费一区二区三区视频网站 | 中文一区 | 日韩在线视频免费 | vagaa欧洲色爽免影院 | 成人精品国产 | 91在线免费观看 | 日韩久久精品电影 | 久久精品视频在线播放 | 91中文| 中文字幕一区二区三区四区 | 亚洲国产成人在线观看 | 欧美午夜寂寞影院 | 热re99久久精品国产99热 | 亚洲欧美国产毛片在线 | 国产精品久久久爽爽爽麻豆色哟哟 | 日韩欧美~中文字幕 | 在线欧美日韩 | 免费观看日韩av | 婷婷色在线 | 久久小视频 | 日韩视频在线观看 | 亚洲一区在线视频 | 国产成人精品综合 | 日韩一片| 999久久国产 | 狠狠干av| 杨门女将寡妇一级裸片看 | 成人精品视频 | 亚洲欧美一区二区精品中文字幕 | 欧美日韩一区二区在线观看 | 国产欧美日韩一区 | 久久男人天堂 | 美女国产| 国产中文字幕在线 | 麻豆一区一区三区四区 | 婷婷激情综合 | 久久com| 三区在线视频 | 欧美精品成人一区二区在线 | 久久久久国产精品 | 欧美一区2区三区4区公司二百 | 日本午夜在线 | 国产欧美精品 | 影视在线观看 | 精品视频免费观看 | 一区二区日本 | 国产视频福利在线观看 | 偷拍做爰吃奶视频免费看 | 亚洲精品久久久一区二区三区 | 在线观看a视频 | 精品欧美一区二区三区久久久 | 国产精品久久久久久久一区探花 | 日本一区二区三区免费观看 | 天天亚洲综合 | 欧美一级一区 | 成人欧美一区二区三区黑人孕妇 | av男人天堂网 | 综合国产| 伊人干| 欧美一级淫片免费视频黄 | 久久精品网 | 一区二区三区在线看 | 爱爱视频在线 | 久久作爱视频 | 99国产精品99久久久久久 | 欧美日本免费一区二区三区 | 亚洲网在线 | 日韩欧美一级精品久久 | 欧美一级片 | 人人人人人你人人人人人 | 三级免费毛片 | av天天干 | 欧美一级做a爰片免费视频 亚洲精品一区在线观看 | 91久色| 亚洲国产精品一区二区三区 | 天天精品视频免费观看 | 成人一级片 | 久久国产精品久久 | 女男羞羞视频网站免费 | 欧美一区二区在线观看 | 综合色成人 | 亚洲h| 色婷婷国产精品 | 国产精品视频免费观看 | 99精品欧美一区二区三区 | 精品久久久久久 | 韩国毛片在线观看 | 青娱乐99| 久久成人一区 | 亚洲天堂一区二区 | 色婷婷综合久久久中文字幕 | 欧美 国产精品 | 久草视| 香蕉久久av一区二区三区 | 天天爽夜夜春 | 色综合社区 | 不卡久久| 中文字幕在线观看第一页 | 91国在线高清视频 | 色综合视频 | 国产精品久久免费观看spa | 久热中文在线 | 亚洲欧美日韩国产 | 毛片99| 精品一区二区三区蜜桃 | 女人夜夜春高潮爽av片 | 国产毛片网站 | 中文字幕在线观看av | 午夜一级黄色片 | 欧美一级黄色片免费看 | 夜夜操导航| 国产精品视频99 | 欧美大片一区二区 |