ASP.NET Core 6 Minimal API的模擬實現

語言: CN / TW / HK

Minimal API僅僅是在基於IHost/IHostBuilder的服務承載系統上作了小小的封裝而已,它利用WebApplication和WebApplicationBuilder這兩個型別提供了更加簡潔的API,同時提供了與現有API的相容。要成分理解Minimal API的實現原理,得先對服務承載系統有基本的理解,對此不瞭解的可以參閱《服務承載模型[上篇]》、《服務承載模型[下篇]》、《 承載服務啟動流程[上篇]》 和《 承載服務啟動流程[下篇] 》。對於本篇提供的模擬程式碼,可以從 這裡 下載。

一、基礎模型

二、WebApplication

三、WebApplication的構建

1. BootstrapHostBuilder

2. ConfigureHostBuilder

3. ConfigureWebHostBuilder

4. WebApplicationBuilder

四、 工廠方法

一、基礎模型

對於由WebApplication和WebApplicationBuilder構建的承載模型,我們沒有必要了解其實現的每一個細節,知道其大致的設計和實現原理就可以了,所以本節會採用最簡潔的程式碼模擬這兩個型別的實現。如圖1所示,代表承載應用的WebApplication物件是對一個IHost物件的封裝,而且該型別自身也實現了IHost介面, WebApplication物件其實還是作為一個IHost物件被啟動的。作為構建這的WebApplicationBuilder則是對一個IHostBuilder物件的封裝,它對WebApplication物件的構建體現在利用封裝的IHostBuilder物件構建一個對應的IHost物件,最終利用後者將WebApplication物件創建出來。

圖17-8完整的請求處理管道

二、WebApplication

WebApplication型別不僅僅實現了IHost介面,還同時實現IApplicationBuilder介面,所以中介軟體可以直接註冊到這個物件上的。該型別還實現了IEndpointRouteBuilder介面,所以我們還能利用它進行路由註冊,我們在20章才會涉及到路由,所以我們現在先忽略針對該介面的實現。下面的程式碼模擬WebApplication型別的實現。如程式碼片段所示,WebApplication的建構函式定義了一個IHost型別的引數,它利用這個物件完成了對IHost介面所有成員的實現,針對IApplicationBuilder介面成員的實現則利用建立的ApplicationBuilder物件來完成。WebApplication還提供了一個BuildRequestDelegate方法利用這個ApplicationBuilder物件完成了對中介軟體管道的構建。

public class WebApplication : IApplicationBuilder, IHost
{
    private readonly IHost 		    _host;
    private readonly ApplicationBuilder    _app;

    public WebApplication(IHost host)
    {
        _host 	= host;
        _app 	= new ApplicationBuilder(host.Services);
    }

    IServiceProvider IHost.Services => _host.Services;
    Task IHost.StartAsync(CancellationToken cancellationToken) => _host.StartAsync(cancellationToken);
    Task IHost.StopAsync(CancellationToken cancellationToken) => _host.StopAsync(cancellationToken);

    IServiceProvider IApplicationBuilder.ApplicationServices { get => _app.ApplicationServices; set => _app.ApplicationServices = value; }
    IFeatureCollection IApplicationBuilder.ServerFeatures => _app.ServerFeatures;
    IDictionary<string, object?> IApplicationBuilder.Properties => _app.Properties;
    RequestDelegate IApplicationBuilder.Build() => _app.Build();
    IApplicationBuilder IApplicationBuilder.New() => _app.New();
    IApplicationBuilder IApplicationBuilder.Use(Func<RequestDelegate, RequestDelegate> middleware) => _app.Use(middleware);

    void IDisposable.Dispose() => _host.Dispose();
    public IServiceProvider Services => _host.Services;
    internal RequestDelegate BuildRequestDelegate() => _app.Build();
    ...
}

WebApplication額外定義瞭如下的RunAsync和Run方法,它們分別以非同步和同步方式啟動承載的應用。呼叫這兩個方法的時候可以指定監聽地址,指定的地址被新增到IServerAddressesFeature特性中,而伺服器正式利用這個特性來提供監聽地址的。

public class WebApplication : IApplicationBuilder, IHost
{
    private readonly IHost _host;

    public ICollection<string> Urls => _host.Services.GetRequiredService<IServer>().Features.Get<IServerAddressesFeature>()?.Addresses ?? throw new InvalidOperationException("IServerAddressesFeature is not found.");

    public Task RunAsync(string? url = null)
    {
        Listen(url);
        return HostingAbstractionsHostExtensions.RunAsync(this);
    }

    public void Run(string? url = null)
    {
        Listen(url);
        HostingAbstractionsHostExtensions.Run(this);
    }

    private void Listen(string? url)
    {
        if (url is not null)
        {
            var addresses = _host.Services.GetRequiredService<IServer>().Features.Get<IServerAddressesFeature>()?.Addresses ?? throw new InvalidOperationException("IServerAddressesFeature is not found.");
            addresses.Clear();
            addresses.Add(url);
        }
    }
    ...
}

三、WebApplication的構建

要建立一個WebApplication物件,只需要提供一個對應的IHost物件即可。IHost物件是通過IHostBuilder物件構建的,所以WebApplicationBuilder需要一個IHostBuilder物件,具體來說是一個HostBuilder物件。我們針對WebApplicationBuilder物件所作的一切設定最終都需要轉移到這個HostBuilder物件上才能生效。為了提供更加簡潔的API,WebApplicationBuilder型別提供了一系列的屬性。比如它利用Serrvices屬性提供了可以直接進行服務註冊的IServiceCollection集合,利用Environment屬性提供了表示當前承載環境的IWebHostEnvironment物件,利用Configuration屬性提供的ConfigurationManager物件不僅可以作為IConfigurationBuilder物件幫助我們完成對配置系統的一切設定,它自身也可以作為IConfiguration物件為我們提供配置。

WebApplicationBuilder還定義了Host和WebHost屬性,對應型別為ConfigureHostBuilder和ConfigureWebHostBuilder,它們分別實現了IHostBuilder和IWebHostBuilder介面,其目的是為了複用IHostBuilder和IWebHostBuilder介面承載的API(主要是擴充套件方法)。為了會盡可能使用現有方法對IHostBuilder物件進行初始化設定,它還使用了一個實現了IHostBuilder介面的BootstrapHostBuilder型別。有這些物件組成了WebApplicationBuilder針對HostBuilder的構建模型。如圖2所示,WebApplicationBuilder的所有工作都是為了構建它封裝的HostBuilder物件。

當WebApplicationBuilder初始化的時候,它除了會建立這個HostBuilder物件,還會建立儲存服務註冊的IServiceCollection物件,以及用來對配置進行設定的ConfigurationManager物件。接下來它會建立一個BootstrapHostBuilder物件,並將它引數呼叫相應的方法(比如ConfigureWebHostDefaults方法)將初始化設定收集起來,收集的服務註冊和針對配置系統的設定分別轉移到建立的IServiceCollection和ConfigurationManager物件中,其他設定直接應用到封裝的HostBuilder物件上。

圖2 HostBuilder構建模型

WebApplicationBuilder在此之後會創建出代表承載環境的IWebHostEnvironment物件,並對Environment屬性進行初始化。在得到表示承載上下文的WebHostBuilderContext物件之後,上述的ConfigureHostBuilder和ConfigureWebHostBuilder物件被創建出來,並賦值給Host和WebHost屬性。與BootstrapHostBuilder作用類似,我們利用這兩個物件所作的設定最終都會轉移到上述的三個物件中。當WebApplicationBuilder進行WebApplication物件構建的時候,IServiceCollection物件儲存的服務註冊和ConfigurationManager物件承載配置最終轉移到HostBuilder物件上。此時再利用後者構建出對應的IHost物件,代表承載應用的WebApplication物件最終由該物件構建出來。

1. BootstrapHostBuilder

如下所示的是我們模擬的BootstrapHostBuilder型別的定義。正如上面所說,這個它的作用是收集初始化IHostBuilder物件提供的設定並將它們分別應用到指定的IServiceCollection、ConfigurationManager和IHostBuilder物件上。這一使命體現在BootstrapHostBuilder的Apply方法上,該方法還通過一個輸出引數返回建立的HostBuilderContext上下文。

public class BootstrapHostBuilder : IHostBuilder
{
    private readonly List<Action<IConfigurationBuilder>> _configureHostConfigurations = new();
    private readonly List<Action<HostBuilderContext, IConfigurationBuilder>> _configureAppConfigurations = new();
    private readonly List<Action<HostBuilderContext, IServiceCollection>> _configureServices = new();
    private readonly List<Action<IHostBuilder>> _others = new();

    public IDictionary<object, object> Properties { get; } = new Dictionary<object, object>();
    public IHost Build() => throw new NotImplementedException();
    public IHostBuilder ConfigureHostConfiguration(Action<IConfigurationBuilder> configureDelegate)
    {
        _configureHostConfigurations.Add(configureDelegate);
        return this;
    }
    public IHostBuilder ConfigureAppConfiguration(Action<HostBuilderContext, IConfigurationBuilder> configureDelegate)
    {
        _configureAppConfigurations.Add(configureDelegate);
        return this;
    }
    public IHostBuilder ConfigureServices(Action<HostBuilderContext, IServiceCollection> configureDelegate)
    {
        _configureServices.Add(configureDelegate);
        return this;
    }
    public IHostBuilder UseServiceProviderFactory<TContainerBuilder>(IServiceProviderFactory<TContainerBuilder> factory)
    {
        _others.Add(builder => builder.UseServiceProviderFactory(factory));
        return this;
    }
    public IHostBuilder UseServiceProviderFactory<TContainerBuilder>(Func<HostBuilderContext, IServiceProviderFactory<TContainerBuilder>> factory)
    {
        _others.Add(builder => builder.UseServiceProviderFactory(factory));
        return this;
    }
    public IHostBuilder ConfigureContainer<TContainerBuilder>(Action<HostBuilderContext, TContainerBuilder> configureDelegate)
    {
        _others.Add(builder => builder.ConfigureContainer(configureDelegate));
        return this;
    }

    internal void Apply(IHostBuilder hostBuilder, ConfigurationManager configuration, IServiceCollection services, out HostBuilderContext builderContext)
    {
        // 初始化針對宿主的配置
        var hostConfiguration = new ConfigurationManager();
        _configureHostConfigurations.ForEach(it => it(hostConfiguration));

        // 建立承載環境
        var environment = new HostingEnvironment()
        {
            ApplicationName = hostConfiguration[HostDefaults.ApplicationKey],
            EnvironmentName = hostConfiguration[HostDefaults.EnvironmentKey] ?? Environments.Production,
            ContentRootPath = HostingPathResolver.ResolvePath(hostConfiguration[HostDefaults.ContentRootKey])
        };
        environment.ContentRootFileProvider = new PhysicalFileProvider(environment.ContentRootPath);

        // 建立HostBuilderContext上下文
        var hostContext = new HostBuilderContext(Properties)
        {
            Configuration = hostConfiguration,
            HostingEnvironment = environment,
        };

        // 將針對宿主的配置新增到ConfigurationManager中
        configuration.AddConfiguration(hostConfiguration, true);

        // 初始化針對應用的配置
        _configureAppConfigurations.ForEach(it => it(hostContext, configuration));

        // 收集服務註冊
        _configureServices.ForEach(it => it(hostContext, services));

        // 將針對依賴注入容器的設定應用到指定的IHostBuilder物件上
        _others.ForEach(it => it(hostBuilder));

        // 將自定義屬性轉移到指定的IHostBuilder物件上
        foreach (var kv in Properties)
        {
            hostBuilder.Properties[kv.Key] = kv.Value;
        }

        builderContext = hostContext;
    }
}

除了Build方法,IHostBuilder介面中定義的所有方法的引數都是委託,所以實現的這些方法將提供的委託收集起來。在Apply方法中,我們通過執行這些委託物件,將初始化設定應用到指定的IServiceCollection、ConfigurationManager和IHostBuilder物件上,並根據初始化宿主配置構建出代表承載環境的HostingEnvironment物件。該方法最後根據承載環境結合配置將HostBuilderContext上下文創建出來,並以輸出引數的形式返回。

internal static class HostingPathResolver
{
    public static string ResolvePath(string? contentRootPath)     => ResolvePath(contentRootPath, .BaseDirectory);
    public static string ResolvePath(string? contentRootPath, string basePath) => string.IsNullOrEmpty(contentRootPath)
        ? Path.GetFullPath(basePath): Path.IsPathRooted(contentRootPath)? Path.GetFullPath(contentRootPath)
        : Path.GetFullPath(Path.Combine(Path.GetFullPath(basePath), contentRootPath));
}

2. ConfigureHostBuilder

ConfigureHostBuilder是在應用了BootstrapHostBuilder收集的初始化設定之後建立的,在建立該物件時提供了HostBuilderContext上下文, ConfigurationManager和IServiceCollection物件。提供的服務註冊直接新增到IServiceCollection物件中,針對配置的設定已經應用到ConfigurationManager物件,直接針對IHostBuilder物件的設定則利用_configureActions欄位暫存起來。

public class ConfigureHostBuilder : IHostBuilder
{
    private readonly ConfigurationManager _configuration;
    private readonly IServiceCollection _services;
    private readonly HostBuilderContext _context;
    private readonly List<Action<IHostBuilder>> _configureActions = new();

    internal ConfigureHostBuilder(HostBuilderContext context, ConfigurationManager configuration, IServiceCollection services)
    {
        _configuration = configuration;
        _services = services;
        _context = context;
    }

    public IDictionary<object, object> Properties => _context.Properties;
    public IHost Build() => throw new NotImplementedException();
    public IHostBuilder ConfigureAppConfiguration(Action<HostBuilderContext, IConfigurationBuilder> configureDelegate)
        => Configure(() => configureDelegate(_context, _configuration));

    public IHostBuilder ConfigureHostConfiguration(Action<IConfigurationBuilder> configureDelegate)
    {
        var applicationName = _configuration[HostDefaults.ApplicationKey];
        var contentRoot = _context.HostingEnvironment.ContentRootPath;
        var environment = _configuration[HostDefaults.EnvironmentKey];

        configureDelegate(_configuration);

        // 與環境相關的三個配置不允許改變
        Validate(applicationName, HostDefaults.ApplicationKey, "Application name cannot be changed.");
        Validate(contentRoot, HostDefaults.ContentRootKey, "Content root cannot be changed.");
        Validate(environment, HostDefaults.EnvironmentKey, "Environment name cannot be changed.");

        return this;

        void Validate(string previousValue, string key, string message)
        {
            if (!string.Equals(previousValue, _configuration[key], StringComparison.OrdinalIgnoreCase))
            {
                throw new NotSupportedException(message);
            }
        }
    }

    public IHostBuilder ConfigureServices(Action<HostBuilderContext, IServiceCollection> configureDelegate)
        => Configure(() => configureDelegate(_context, _services));

    public IHostBuilder UseServiceProviderFactory<TContainerBuilder>(IServiceProviderFactory<TContainerBuilder> factory)
        => Configure(() => _configureActions.Add(b => b.UseServiceProviderFactory(factory)));

    public IHostBuilder UseServiceProviderFactory<TContainerBuilder>(Func<HostBuilderContext, IServiceProviderFactory<TContainerBuilder>> factory)
        => Configure(() => _configureActions.Add(b => b.UseServiceProviderFactory(factory)));

    public IHostBuilder ConfigureContainer<TContainerBuilder>(Action<HostBuilderContext, TContainerBuilder> configureDelegate)
        => Configure(() => _configureActions.Add(b => b.ConfigureContainer(configureDelegate)));

    private IHostBuilder Configure(Action configure)
    {
        configure();
        return this;
    }

    internal void Apply(IHostBuilder hostBuilder) => _configureActions.ForEach(op => op(hostBuilder));
}

WebApplicationBuilder物件一旦被創建出來後,針對承載環境的配置是不能改變的,所以ConfigureHostBuilder的ConfigureHostConfiguration方法針對此添加了相應的驗證。兩個UseServiceProviderFactory方法和ConfigureContainer方法針對依賴注入容器的設定最終需要應用到IHostBuilder物件上,所以我們將方法中提供的委託物件利用configureActions欄位存起來,並最終利用Apply方法應用到指定的IHostBuilder物件上。

3. ConfigureWebHostBuilder

ConfigureWebHostBuilder同樣是在應用了BootstrapHostBuilder提供的初始化設定後建立的,建立該物件時能夠提供WebHostBuilderContext上下文和承載配置和服務註冊的ConfigurationManager和IServiceCollection物件。由於IWebHostBuilder介面定義的方法只涉及服務註冊和針對配置的設定,所以方法提供的委託物件可以直接應用到這兩個物件上。

public class ConfigureWebHostBuilder : IWebHostBuilder, ISupportsStartup
{
    private readonly WebHostBuilderContext _builderContext;
    private readonly IServiceCollection _services;
    private readonly ConfigurationManager _configuration;

    public ConfigureWebHostBuilder(WebHostBuilderContext builderContext, ConfigurationManager configuration, IServiceCollection services)
    {
        _builderContext = builderContext;
        _services = services;
        _configuration = configuration;
    }

    public IWebHost Build() => throw new NotImplementedException();
    public IWebHostBuilder ConfigureAppConfiguration(Action<WebHostBuilderContext, IConfigurationBuilder> configureDelegate) => Configure(() => configureDelegate(_builderContext, _configuration));
    public IWebHostBuilder ConfigureServices(Action<IServiceCollection> configureServices) => Configure(() => configureServices(_services));
    public IWebHostBuilder ConfigureServices(Action<WebHostBuilderContext, IServiceCollection> configureServices) => Configure(() => configureServices(_builderContext, _services));
    public string? GetSetting(string key) => _configuration[key];
    public IWebHostBuilder UseSetting(string key, string? value) => Configure(() => _configuration[key] = value);

    IWebHostBuilder ISupportsStartup.UseStartup(Type startupType) => throw new NotImplementedException();
    IWebHostBuilder ISupportsStartup.UseStartup<TStartup>(Func<WebHostBuilderContext, TStartup> startupFactory) => throw new NotImplementedException();
    IWebHostBuilder ISupportsStartup.Configure(Action<IApplicationBuilder> configure) => throw new NotImplementedException();
    IWebHostBuilder ISupportsStartup.Configure(Action<WebHostBuilderContext, IApplicationBuilder> configure) => throw new NotImplementedException();

    private IWebHostBuilder Configure(Action configure)
    {
        configure();
        return this;
    }
}

我們在前面說過,傳統承載方式將初始化操作定義在註冊的Startup型別的程式設計方式在Minima API中已經不再被支援了,所以WebApplicationBuilder本不該實現ISupportsStartup介面,但是我們希望使用者在採用這種程式設計方式時得到顯式的提醒,所以依然讓它實現該介面,並在實現的方法中丟擲NotImplementedException型別的異常。

4. WebApplicationBuilder

如下的程式碼片段模擬了WebApplicationBuilder針對WebApplication的構建。它的建構函式會建立一個BootstrapHostBuilder物件,呼叫它的ConfigureDefaults和ConfigureWebHostDefaults擴充套件方法將初始化設定收集起來。ConfigureWebHostDefaults方法會利用提供的Action<IWebHostBuilder>委託進行中介軟體的註冊,由於中介軟體的註冊被轉移到WebApplication物件上,並且它提供了一個BuildRequestDelegate方法返回由註冊中介軟體組成的管道,所以在這裡只需呼叫構建的WebApplication物件(通過_application欄位表示,雖然此時尚未建立,但是中介軟體真正被註冊時會被創建出來)的這個方法,並將返回的RequestDelegate物件作為引數呼叫IApplicationBuilder介面的Run方法將中介軟體管道註冊為請求處理器。

public class WebApplicationBuilder
{
    private readonly HostBuilder _hostBuilder = new HostBuilder();
    private WebApplication _application;

    public ConfigurationManager Configuration { get; } = new ConfigurationManager();
    public IServiceCollection Services { get; } = new ServiceCollection();
    public IWebHostEnvironment Environment { get; }
    public ConfigureHostBuilder Host { get; }
    public ConfigureWebHostBuilder WebHost { get; }
    public ILoggingBuilder Logging { get; }

    public WebApplicationBuilder(WebApplicationOptions options)
    {
        //建立BootstrapHostBuilder並利用它收集初始化過程中設定的配置、服務和針對依賴注入容器的設定
        var args = options.Args;
        var bootstrap = new BootstrapHostBuilder();
        bootstrap
            .ConfigureDefaults(null)
            .ConfigureWebHostDefaults(webHostBuilder => webHostBuilder.Configure(app => app.Run(_application.BuildRequestDelegate())))
            .ConfigureHostConfiguration(config => {
                // 新增命令列配置源
                if (args?.Any() == true)
                {
                    config.AddCommandLine(args);
                }

                // 將WebApplicationOptions配置選項轉移到配置中
                Dictionary<string, string>? settings = null;
                if (options.EnvironmentName is not null) (settings ??= new())[HostDefaults.EnvironmentKey] = options.EnvironmentName;
                if (options.ApplicationName is not null) (settings ??= new())[HostDefaults.ApplicationKey] = options.ApplicationName;
                if (options.ContentRootPath is not null) (settings ??= new())[HostDefaults.ContentRootKey] = options.ContentRootPath;
                if (options.WebRootPath is not null) (settings ??= new())[WebHostDefaults.WebRootKey] = options.EnvironmentName;
                if (settings != null)
                {
                    config.AddInMemoryCollection(settings);
                }
            });

        // 將BootstrapHostBuilder收集到配置和服務轉移到Configuration和Services上
        // 將應用到BootstrapHostBuilder上針對依賴注入溶質的設定轉移到_hostBuilder上
        // 得到BuilderContext上下文
        bootstrap.Apply(_hostBuilder, Configuration, Services, out var builderContext);

        // 如果提供了命令列引數,在Configuration上新增對應配置源
        if (options.Args?.Any() == true)
        {
            Configuration.AddCommandLine(options.Args);
        }

        // 構建WebHostBuilderContext上下文
        // 初始化Host、WebHost和Logging屬性
        var webHostContext = (WebHostBuilderContext)builderContext.Properties[typeof(WebHostBuilderContext)];
        Environment = webHostContext.HostingEnvironment;
        Host = new ConfigureHostBuilder(builderContext, Configuration, Services);
        WebHost = new ConfigureWebHostBuilder(webHostContext, Configuration, Services);
        Logging = new LogginigBuilder(Services);
    }

    public WebApplication Build()
    {
        // 將ConfigurationManager的配置轉移到_hostBuilder
        _hostBuilder.ConfigureAppConfiguration(builder =>
        {
            builder.AddConfiguration(Configuration);
            foreach (var kv in ((IConfigurationBuilder)Configuration).Properties)
            {
                builder.Properties[kv.Key] = kv.Value;
            }
        });

        // 將新增的服務註冊轉移到_hostBuilder
        _hostBuilder.ConfigureServices((_, services) =>
        {
            foreach (var service in Services)
            {
                services.Add(service);
            }
        });

        // 將應用到Host屬性上的設定轉移到_hostBuilder
        Host.Apply(_hostBuilder);

        // 利用_hostBuilder構建的IHost物件建立WebApplication
        return _application = new WebApplication(_hostBuilder.Build());
    }
}

接下來BootstrapHostBuilder的ConfigureHostConfiguration方法被呼叫,我們利用它將提供的WebApplicationOptions配置選項轉移到BootstrapHostBuilder針對宿主的配置上。針對IHostBuilder初始化設定應用到BootstrapHostBuilder物件上之後,我們呼叫其Apply方法將這些設定分別轉移到承載服務註冊和配置的IServiceCollection和ConfigurationManager物件,以及封裝的HostBuilder物件上。Apply方法利用輸出引數提供了HostBuilderContext上下文,我們進一步從中提取出WebHostBuilderContext上下文(GenericWebHostBuilder會將構建的WebHostBuilderContext上下文置於HostBuilderContext物件的屬性字典中)。我們利用這個上下文將ConfigureHostBuilder和ConfigureWebHostBuilder物件創建出來,並作為Host和WebHost屬性。用於對日誌做進一步設定的Logging屬性也在這裡被初始化,返回的LoggingBuilder物件僅僅是對IServiceCollection物件的簡單封裝而已。

構建WebApplication物件的Build方法分別呼叫ConfigureAppConfiguration和ConfigureServices方法將ConfigurationManager和IServiceCollection物件承載的配置和服務註冊轉移到HostBuilder物件上。它接下來提取出Host屬性返回的ConfigureHostBuilder物件,並呼叫其Apply方法將應用在該物件上針對依賴注入容器的設定轉移到HostBuilder物件上。至此所有的設定全部轉移到了HostBuilder物件上,我們呼叫其Build方法構建出對應的IHost物件後,最後利用後者將程式碼承載應用的WebApplication物件構建出來。我們將這個物件賦值到_application欄位上,前面呼叫ConfigureWebHostDefaults擴充套件方法提供的委託會將它的BuildRequestDelegate方法構建的中介軟體管道作為請求處理器。

四、 工廠方法

代表承載應用的WebApplication物件由WebApplicationBuilder構建的,但是我們一般不會通過呼叫建構函式的方式來建立WebApplicationBuilder物件,這有違“面向介面”程式設計的原則,所以我們都會使用WebApplication型別提供的靜態工廠方法來建立它。WebApplication除了提供了三個用於建立WebApplicationBuilder的CreateBuilder方法過載,還提供了一個直接建立WebApplication物件的Create方法。

public sealed class WebApplication
{
    public static WebApplicationBuilder CreateBuilder() => new WebApplicationBuilder(new WebApplicationOptions());

    public static WebApplicationBuilder CreateBuilder(string[] args)
    {
        var options = new WebApplicationOptions();
        options.Args = args;
        return new WebApplicationBuilder(options);
    }

    public static WebApplicationBuilder CreateBuilder(WebApplicationOptions options) => new WebApplicationBuilder(options, null);

    public static WebApplication Create(string[]? args = null)
    {
        var options = new WebApplicationOptions();
        options.Args = args;
        return new WebApplicationBuilder(options).Build();
    }
}

本節內容通過針對WebApplication和WebApplicationBuilder這兩個型別的實現模擬來講解Minimal API的實現原理。一方面為了讓講解更加清晰,另一方面也出於篇幅的限制,不得不省去很多細枝末節的內容,但是設計思想和實現原理別無二致。上面提供的原始碼也不是虛擬碼,如下所示的就是在“模擬的Minimal API”構建的ASP.NET Core應用,它是可以正常執行的。如果讀者朋友們對真實的實現感興趣,可以將它作為一個“嚮導”去探尋“真實的Minimal API”。

var app = App.WebApplication.Create();
app.Run(httpContext => httpContext.Response.WriteAsync("Hello World!"));
app.Run();