Skip to main content

Startup类概述

分类:  Asp.net Core入门 标签:  #Asp.Net core基础 #基础 #Web 发布于: 2023-06-04 20:08:02

写在这个系列的前面,我购买了蒋金楠的书《ASP.net Core 3框架揭秘》这套书分为上下两册,在学习这套书之前我浏览了一遍这套书的内容,这套书的基本架构和微软的官方文档大致相同,主要是作为框架ASP.net Core的基本架构组件的学习,应该说结合微软的官方文档是一个非常好的补充,但是非常可惜的是,当我开始认真的阅读这套书时,我发现这套书的有些内容确实不错,但是阅读到一定的地方我不得不跳过很多内容,主要原因是书的组织上还是有些问题的,另外作者技术水平不用怀疑,但是作者的写作水品是真的有值得提高的地方,虽然这套书的阅读对象肯定不是初入门者,但是书里很多内容立意也是向从易到难,从代码到设计详细的讲清楚,但是很可惜,这方面我认为作者真的要提高,很多内容看了一下就很难看下去。很多知识其实不用这么详细的列代码,只要把内容讲清楚,代码部分只需指出来就可以了,有需要的读者自然会根据提示进一步研究,没有需要的读者也不用迷失在大段的代码中,这完全没必要,毕竟这是一套数,而不是API参考。鉴于这个原因我不得不重新找到微软的官方文档再重看一遍,实际上微软的官方文档是非常好的教材,很多东西都讲得很清楚,有不清楚得地方只需要google一下,然后再结合这套书看一看补充一下。

因此我也决定记录一下这个学习过程,哪怕仅仅是翻译也是可以的。

希望可以帮助到大家。

HongWei 2021/7/26

现在开始今天这个系类的第一篇关于ASP.net Core的Startup类。

Startup类实际上只是一个约定俗称,按照约定大家一般把这个类都起名叫Startup, 那么这个类的作用是什么呢?

  • 包含一个服务配置的方法,需要注意的是这个方法其实是可选的,这个方法的名称是:ConfigureService
  • 包含一个Middleware定义的配置方法Configure, 这个方法的主要功能是给用户的请求配置一个处理的pipeline(管道)

所以实际上类Startup的定义也比较简单,大部分情况下只需要包含这个两个方法,而且每个应用的很多配置和初始化工作都是应该在这个类里完成。那么这个类是怎么集成到应用框架里呢?
我们先看一下每个ASP.net Core应用的主要程序入口:Program.cs, 在Program类的定义如下:

public class Program
{
    public static void Main(string[] args)
    {
        CreateHostBuilder(args).Build().Run();
    }

    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .ConfigureWebHostDefaults(webBuilder =>
            {
                webBuilder.UseStartup<Startup>();
            });
}

从这段代码里可以看到:

  • Startup这个类名真的只是一个惯例,你完全可以用其他的名字,例如:webBuilder.UseStartup<MyBegin>();, 这样这个类就可以定义为名子:MyBegin
  • 从这段代码里可以看到类Startup的调用是通过WebHostBuilderExtensions.UseStartup<TStartup>来调用的。

这个是Startup类的本质。

Startup类的构造函数

我们可以在Startup类里定义构造函数,我们前面说过了,Startup类的调用时通过HostBuilder的扩展方法进行调用的,在调用Startup之前,Host已经初始化了一些必要的服务,特别是自动依赖注入的容器和ApplicationService(关于什么是ApplicationService以及它和自动依赖注入之间的区别,我们后面也重新学习), 所以在Startup类里实际上我们已经可以使用自动注入的功能了,但是有限制(这是必然的,因为很多还没有注册到系统里), 那么在Startup类的构造函数里能够自动注入并使用的只能是这样几个:

  • IWebHostEnviroment, 表示WebHost当前的环境。
  • IHostEnviroment, Host的环境
  • IConfiguration, 配置。

例如:

public class Startup
{
    private readonly IWebHostEnvironment _env;

    public Startup(IConfiguration configuration, IWebHostEnvironment env)
    {
        Configuration = configuration;
        _env = env;
    }

    public IConfiguration Configuration { get; }

    public void ConfigureServices(IServiceCollection services)
    {
        if (_env.IsDevelopment())
        {
        }
        else
        {
        }
    }
}

这样就可以在后面的方法中使用和配置,环境相关的变量以及配置等等。

使用多个Startup

有时候需要根据应用运行的环境,选择多个Startup类,例如开发环境一个Startup类,产线环境一个Startup类, 这个也是很容易实现的,不过一般情况下并不需要使用到这个方案,一般情况下还是通过不同环境的配置来解决不同环境的问题,但是如果要这样做也是可以的,如下是这么做的步骤:

  • 定义多个Startup类,类名的定义形式如下:Startup{EnviromentName}, 例如Development环境,那么类名就是StartupDevelopment, 如果是Produciton,那么类型就是StartupProduction
  • 在使用WebHostBuilder.Startup<>时,不用具体的类名,而是使用程序集的名称。

如下是一个实例:

public class StartupDevelopment
{
    public void ConfigureServices(IServiceCollection services)
    {
    }

    public void Configure(IApplicationBuilder app)
    {
    }
}

public class StartupProduction
{
    public void ConfigureServices(IServiceCollection services)
    {
    }

    public void Configure(IApplicationBuilder app)
    {
    }
}

public class Startup
{

    public void ConfigureServices(IServiceCollection services)
    {
    }

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
    }
}

注意上述定义只是为了描述大致的意思,大家可以自行定义相关的内容,我们用如下的代码启用多个Startup类:

public class Program
{
    public static void Main(string[] args)
    {
        CreateHostBuilder(args).Build().Run();
    }

    public static IHostBuilder CreateHostBuilder(string[] args)
    {
        var assemblyName = typeof(Startup).GetTypeInfo().Assembly.FullName;

        return   Host.CreateDefaultBuilder(args)
            .ConfigureWebHostDefaults(webBuilder =>
            {
                webBuilder.UseStartup(assemblyName);
            });
    }
}

注意这里的webBuilder.UseStartup(assemblyName);, 即可以根据环境变量载入多个Startup类,需要注意还是这个方案的应用场景。一般的应用无需使用这个方案。

方法ConfigureService

这个方法在Startup类是可选的,这个方法的主要特征:

  • 可选方法
  • Host在调用Configure方法之前调用这个方法, 这个方法主要用于向Host以及应用注册应用中需要使用到的服务,关于什么服务,我们后面再来讨论,其实质就是向自动注册依赖服务器注册需要的服务。
  • 按照惯例,配置绑定到IOptions, 也在这个方法里完成。

我们之前也讨论过了在Host调用startup类之前,具体来说在调用ConfigureService方法之前,Host已经初始化了部分的服务,具体来说,默认情况下,Host已经初始化了如下的一些服务, 前面我们讨论过了,在构造函数里我们能够使用的是IEnviormentIConfiguration, 在方法ConfigureService主要的参数是IServiceCollection, 用于向DI添加必要的服务。

public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    public void ConfigureServices(IServiceCollection services)
    {

        services.AddDbContext<ApplicationDbContext>(options =>
            options.UseSqlServer(
                Configuration.GetConnectionString("DefaultConnection")));
        services.AddDefaultIdentity<IdentityUser>(
            options => options.SignIn.RequireConfirmedAccount = true)
            .AddEntityFrameworkStores<ApplicationDbContext>();

        services.AddRazorPages();
    }

关于绑定配置到IOptions, 也可以参考如下代码:

public void ConfigureServices(IServiceCollection services)
{
    services.Configure<PositionOptions>(
        Configuration.GetSection(PositionOptions.Position));
    services.Configure<ColorOptions>(
        Configuration.GetSection(ColorOptions.Color));

    services.AddScoped<IMyDependency, MyDependency>();
    services.AddScoped<IMyDependency2, MyDependency2>();

    services.AddRazorPages();
}

Configure方法

在Startup类的Configure方法里我们主要是向应用注册需要使用到的中间件(middleware), 所有的middleware串联起来形成一个pipeline, 主要的作用是定义系统如何处理用户的请求,本质上是通过IApplicationBuilder添加middlewarepiplein上,习惯上我们使用Use{middleware}的扩展方法来完成向pipeline上添加中间件。

默认的ASP.net Core模板启用了如下的middleware

  • 开发异常页面(Developer Exception Page)
  • 异常处理(Exception handler)
  • HTTP Strict Transport Security (HSTS)
  • HTTPS redirection
  • 静态文件支持(Static Files)
  • MVC或者Razor Pages

关于如何处理Middleware,我们后面的文章也会一一来学习。

不使用Startup类来配置服务和中间件

我们前面也学习了Startup主要是通过扩展方法useStart<>(), 来定义的,那么如果我们不使用Startup如何定义服务和中间件的方法呢?

public class Program
{
    public static void Main(string[] args)
    {
        CreateHostBuilder(args).Build().Run();
    }

    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .ConfigureAppConfiguration((hostingContext, config) =>
            {
            })
            .ConfigureWebHostDefaults(webBuilder =>
            {
                webBuilder.ConfigureServices(services =>
                {
                    services.AddControllersWithViews();
                })
                .Configure(app =>
                {
                    var loggerFactory = app.ApplicationServices
                        .GetRequiredService<ILoggerFactory>();
                    var logger = loggerFactory.CreateLogger<Program>();
                    var env = app.ApplicationServices.GetRequiredService<IWebHostEnvironment>();
                    var config = app.ApplicationServices.GetRequiredService<IConfiguration>();

                    logger.LogInformation("Logged in Configure");

                    if (env.IsDevelopment())
                    {
                        app.UseDeveloperExceptionPage();
                    }
                    else
                    {
                        app.UseExceptionHandler("/Home/Error");
                        app.UseHsts();
                    }

                    var configValue = config["MyConfigKey"];
                });
            });
        });
}

可以看到实际上也就是在配置host的方法里,通过hostbuild直接调用方法ConfigureServiceConfigure就可以完成这部分的工作,需要注意的是,要注意调用这个两个方法的顺序。

IStartFilter接口

关于这个接口的详细讨论,您可以参考文章:https://andrewlock.net/exploring-istartupfilter-in-asp-net-core/, 对于这个接口我的总结是:这个接口是另外一条给应用添加中间件的方法,但是它的应用场景是有限的,用法也很简单,只需要在Startup类的ClonfigureService方法里添加必要的服务就可以了,ASP.net Core的runtime会自动调用。一般用户无需关心,如果您确实需要这个功能,请仔细阅读前面给出的文章。

在Startup类中从外部程序集中添加配置

这个部分我们用下一章的内容来学习。