ASP.NET Core 2 preview 1中Program.cs,Startup.cs和CreateDefaultBuilder的探索

时间:2024-10-16 16:03:14

Exploring Program.cs, Startup.cs and CreateDefaultBuilder in ASP.NET Core 2 preview 1

ASP.NET Core 2.0 的目标之一是已经被简洁化的基础模板。简化了其基本使用,并且让开始一个新项目变得更加简单。

明显从表面上来看,新的 ProgramStartup 类型相比于 ASP.NET Core 1.0 更加简单。现在,我将从新的 WebHost.CreateDefaultBuilder() 方法出发,看看它是如何引导你的应用程序的。

Program和Startup在ASP.NET Core 1.X中的职责


在ASP.NET Core 1.X中, Program类用来建立IWebHost。一个基础的web app模板就像这样:

public class Program
{
    public static void Main(string[] args)
    {
        var host = new WebHostBuilder()
            .UseKestrel()
            .UseContentRoot(Directory.GetCurrentDirectory())
            .UseIISIntegration()
            .UseStartup<Startup>()
            .Build();

        host.Run();
    }
}

这个相对紧凑的文件做了下面这些事情:

  • 配置一个Web服务器(Kestrel)
  • 设置内容目录(这个目录包括appsettings.json文件等)
  • 建立IIS集
  • 定义Startup类
  • Build()和Run IWebHost

Startup的实现可能存在很大的差别,这取决于你建立的应用程序。 MVC模板展现的是一个典型的启动模板

public class Startup
{
    public Startup(IHostingEnviroment env)
    {
        var builder = new ConfigurationBuilder()
            .SetBasePath(env.ContentRootPath)
            .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
            .AddJsonFile($"appsetting.{env.EnvironmentName}.json", optional: true)
            .AddEnvironmentVariables();
        Configuration = builder.Build();
    }

    public IConfigurationRoot Configuration { get; }

    public void ConfigureServices(IServiceCollection services)
    {
        // add framework services
        services.AddMvc();
    }

    public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
    {
        loggerFactory.AddConsole(Configuration.GetSection("Logging"));
        loggerFactory.AddDebug();

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

        app.UseStaticFiles();

        app.UseMvc(routes =>
        {
            routes.MapRoute(
                name: "default",
                template: "{Controller=Home}/{action=Index}/{id?}"
            );
        });
    }
}

这个文件主要有以下四个职责:

  • 在Startup构造函数中建立配置
  • 在ConfigureService中建立依赖注入关系
  • 在Configure中启动Logging
  • 在Configure中建立中间件管道

这些都运行的不错,但是ASP.NET团队认为还是有很多地方考虑的不够全面。

首先,设置配置比较冗长,但也很标准,总的来说不太需要过多的调整。

其次,Logging是在Startup的Configure中配置的,它建立在Configuration和DI配置完成之后。这里有两个缺点。一方面,这使Logging看上去像个二等公民-Configure通常被用来配置中间件管道,这样使得Logging配置在这里意义不大。同样也意味着你无法记录应用程序本身引导过程的日志。有很多方法能解决这个问题,但都不是特别有效。

在ASP.NET Core 2.0 preview 1中,这两点已经通过修改IWebHost角色,通过创建一个辅助方法配置应用程序。

Program和Startup在ASP.NET Core 2.0 preview 1中的责任


在ASP.NET Core 2.0 previe 1中,IWebHost的职责发生了一些改变。在之前拥有的职责上,增加了额外两点:

  • Setup Configuration
  • Setup Logging

此外,ASP.NET Core 2.0引入了一个辅助方法CreateDefaultBuilder。它封装了Program.cs中大部分常见的代码,以及configuration和logging!

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

    public static IWebHost BuildWebHost(string[] args) =>
        WebHost.CreateDefaultBuilder(args)
            .UseStartup<Startup>()
            .Build();
}

正如你所看到的,这里没有提及Kestrel,IIS,configuration等-这些全部放在CreateDefaultBuilder方法中处理。

将configuration和logging代码转移到这个方法中处理简化了Startup文件:

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

    public IConfiguration Configuration { get; }

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddMvc();
    }

    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    {
        if(env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }
        else
        {
            app.UseExceptionHandler("/Home/Error");
        }

        app.UseStaticFiles();

        app.UseMvc(routes =>
        {
            routes.MapRoute(
                name: "default",
                template: "{controller=Home}/{action=Index}/{id?}"
            );
        });
    }
}

这个类除了logging和许多configuration代码被移除外,和1.0的非常相似。注意IConfiguration对象注入到类型的一个属性,而不是创建配置构造函数本身。

这是ASP.NET 2.0新引入的-IConfiguration对象默认通过DI注册。在1.X中,如果你需要从DI中获取IConfiguration对象,你必须自己注册。

最初,我认为CreateDefaultBuilder只是进行了模糊的设置,感觉好像有点退步,但是回想起来,这里更多的只是一个“谁动了我的奶酪”的感觉。CreateDefaultBuilder没有什么奇特的地方,他只是隐藏了一些通常保持不变的标准代码。

WebHost.CreateDefaultBuilder辅助方法


为了能确切地弄懂静态辅助方法CreateDefaultBuilder方法,我决定看一看GitHub上的源代码! 你会惊喜地发现,如果你使用过ASP.NET Core 1.X, 这些看起来都很相似。

public static IWebHostBuilder CreateDefaultBulder(string[] args)
{
    var builder = new WebHostBuilder()
        .UseKestrel()
        .UseContentRoot(Directory.GetCurrentDirectory())
        .ConfigureAppConfiguration((hostingContext, config) => {/* setup config */})
        .ConfigureLogging((hostingContext, config) => {/* setup logging */})
        .UseIISIntegration()
        .UseDefaultServiceProvider((context, options) => {/* setup the DI container to use */})
        .ConfigureServices(services =>
        {
            services.AddTransient<IConfigureOptions<KestrelServerOptions>, KestrelServerOptionsSetup>();
        });

    return builder;
}

这里一些新的方法被我省略了,我将在以后的帖子对这些方法进行解析。你能看出来这些方法和ASP.NET Core 1.0中很大程度上做着相同的工作-建立Kestrel服务器,定义ContentRoot,建立IIS集。另外,它也做了一些其他的事情

  • ConfigureAppConfiguration-这里包含过去编写在Startup的配置代码
  • ConfigureLogging-建立过去在Startup.Configure中的logging配置
  • UseDefaultServiceProvider-后面我会就此深入讨论, 但是这里设置内置的DI容器,并且允许你定制自定义行为
  • ConfigureServices-向IWebHost中添加额外所需的组件服务。特别地,可以配置Kestrel服务的选项,允许你可以轻松定义你的Web主机就像普通配置的一部分一样。

这篇帖子中,我将仔细看看configuration和logging,其他方法的深入将在以后的帖子中展现。

在ConfigureAppConfiguration中设置app configuration


ConfigureAppConfiguration方法中是一个包含两个参数的lambda表达式-WebHostBuilderContext对象hostingContext,和IConfigurationBuilder实例config:

ConfigureAppConfiguration((hostingContext, config) =>
{
    var env = hostingContext.HostingEnvironment;

    config.AddJsonFile("appsetting.json, optional:true, reloadOnChange: true")
        .AddJsonFile($"appsetting.{env.Environment}.json", optional: true, reloadOnChange: true);

    if(env.IsDevelopment())
    {
        var appAssembly = Assembly.Load(new Assembly(env.ApplicationName));
        if(appAssembly != null)
        {
            config.AddUserSecrets(appAssembly, optional: true);
        }
    }

    config.AddEnvironmentVariables();

    if(args != null)
    {
        config.AddCommandLine(args);
    }
});

可以看到,参数hostingContext暴露了IHostingEnvironment(不管是运行在开发环境还是生产环境)作为一个属性,HostingEnvironment。如果你使用过ASP.NET Core 2.0, 除了形式上意外,大部分代码都非常相似。

一个特例是在建立User Secret时候,做法有一点点不同。它通过一个程序集引用加载User Secret,但你仍然可以使用通用config.AddUserSecrets<T>进行配置。

在ASP.NET Core 2.0中, UserSecretsId被存储在一个Assembly Attribute中,因此需要先引入Assembly中。你仍然可以使用csproj文件定义id-它将会在编译时被嵌入到assembly level attribute中。

这些都是标准的东西。它通过下面的提供者,通过下面的顺序加载配置:

  • appsettings.json(optional)
  • appsettings.{env.EnvironmentName}.json(optional)
  • User Secrets
  • Environment Variables
  • Command line arguments

这个方法与ASP.NET Core 1.X的主要不同在于所处的位置-config现在作为WebHost的一部分,而不是通过构造函数植入。同时,最初的建立和最终的调用通过Web Host本身的IConfigurationBuilder,而不是你处理。

在ConfigureLogging中配置logging


ConfigureLogging方法同样是两个参数的lambda表达式-一个WebHostBuilderContext对象hostingContext,一个和configuration方法一样的LoggerFactory实例logging:

ConfigureLogging((hostingContext, logging) =>
{
    logging.UseConfiguration(hostingContext.Configuration.GetSection("Logging"));
    logging.AddConsole();
    logging.AddDebug();
});

logging基础设施在ASP.NET Core 2.0中有一些改变。但是一般来说,这些代码与你在ASP.NET Core1.0应用程序的Configure方法中发现的建立Console和Debug日志方式遥相呼应。你可以用UseConfiguration方法设置日志级别,通过访问已定义好的暴露在hostingContext.Configuration的IConfiguration接口。

自定义WebHostBuilder


我希望通过对WebHost.CreateDefaultBuilder的深入探讨能够表达ASP.NET团队之所以使用它的原因。有许多方法可以建立并运行一个app,但是这种方式使得它更加简单。

但是,假如这不是你想要的启动方式?你可以不适用它!没有什么特殊的辅助,你可以复制粘贴实现代码到你自己的app中,定制它,同样也可以很好的工作。

这不是很确切-KestrelServerOptionsSetup类在ConfigureServices内部引用,因此你将它删除。我将在后面的帖子中进行深入分析。

总结

这篇帖子看起来像是介绍从ASP.NET Core 1.X向ASP.NET Core 2.0 preview 1转移中Program.cs和Startup.cs的不同。特别地,我稍微深入了解了一下WebHost.CreateDefaultBuilder方法,它的作用实际上只是使建立app更加简单。如果你不喜欢他给你做的选择,或者需要自己定制。你仍然可以做到这点,就像以前一样。选择在于你自己!