1.前言

本文主要是以Visual Studio 2017 默认的 WebApi 模板作为基架,基于Asp .Net Core 1.0,本文面向的是初学者,如果你有 ASP.NET Core 相关实践经验,欢迎在评论区补充。
与早期版本的 ASP.NET 对比,最显著的变化之一就是配置应用程序的方式, Global.asax、FilterConfig.cs 和 RouteConfig.cs 统统消失了,取而代之的是 Program.cs 和 Startup.cs。Program.cs 作为 Web 应用程序的默认入口,不做任何修改的情况下,会调用同目录下 Startup.cs 中的 ConfigureServices 方法 和 Configure 方法。

应用启动的流程

对于初学者来说,第一次面对 Startup.cs 往往无从下手,本文将一步步介绍作者的经验,但是不会涉入到内部的代码实现以及相关的原理,那并不是本文想要讨论的范畴。

默认的Startup.cs

相信我,这将是你迈出构建灵活而强大的ASP.NET Core 应用程序的第一步。

2.配置参数选项

在官方文档中提供多种方式来配置参数选项:

  • 文件格式(INI,JSON和XML)
  • 命令行参数
  • 环境变量
  • 内存中的 .NET 对象
  • 用户机密存储
  • Azure 键值
  • 自定义提供程序

虽然提供了很多选择,但是我们只选择其中的JSON文件和环境变量来提供配置参数。

2.1 Json配置参数选项

参考官方文档的示例,我们在 appsettings.json 加入如下的参数:

appsettings.json

与此同时,我们还需要一个类来映射这些配置参数:

MyOptions.cs

思考一下 subsection 应该是字典还是一个对象?如果是字典,是否可以为<string,dynamic>或者<string,object>?

好了,现在就差怎么让他们联系起来,只需在 ConfigureServices 方法中将他们配对:

  services.Configure<MyOptions>(Configuration);

最后就是解决怎么使用这些配置参数的问题了,举个最简单的例子,我们可以在某个控制器中把我们的所有参数打印出来:

 
 

 

不知道你有没有发现 MyOptions 类中有些属性首字母大写,有些属性没有,并不是与json文件中完全一致,也就是说可以忽略大小写的。

回到之前的匹配环节,我们可以发现 services.Configure 的方法中似乎还有更多选择,比如我们把之前的那一行代码替换为:

services.Configure<MyOptions>(Configuration.GetSection("wizards"));//选择wizards节点

 

我们可以发现返回的对象里面的属性都为null,这是因为json中的 "wizards"的节点并不能与我们的类匹配。
那么问题来了,如果匹配的代码如下,又会产生什么样的结果呢?先别急着回答,我会在下一节中给出答案。

2.2环境变量

环境变量,或者说系统变量,在windows中我们可以在系统属性中配置:

 

在Linux环境中也有相应的配置,但是在开发过程中,我们可以在 Visual Studio 中配置:

 

在这之前,我们的Json文件中已经有 "option1" 和 "option2"的参数选项,那么会产生什么样的结果呢?

显然我们可以看到环境变量的参数覆盖了Json文件的参数。而引起这种变化的原因还是需要回到Startup的初始化:

    public Startup(IHostingEnvironment env)
{
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)//必须的json文件,并且自动重载
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)//不必须的json文件
.AddEnvironmentVariables();//启用环境变量
Configuration = builder.Build();
}

从中我们可以看出环境变量的配置在读取 Json 文件参数之后,这样就会覆盖已经存在的同名参数,而已经从 Json 文件被匹配的参数并不会被清空(同样适用于前一节提出的问题)。从另一方面来说,如果你不需要环境变量,则需要去掉 "AddEnvironmentVariables() ",以免覆盖预期参数。
回到本节的中心,我们为什么会需要环境变量呢?我个人会在Dockerfile中配置一些环境变量,比如某种服务的访问地址、某中功能的开关等等。下面举例说说两个常用的环境变量:
ASPNETCORE_URLS 如果你没有指定对应的 Url 监听地址,可以通过该参数修改,如设置为 "http://*:80"。
ASPNETCORE_ENVIRONMENT 开发环境(Development)、预演环境(Staging)、生产环境(Production),将在工作环境一节中做详细介绍。不同的工作环境将使得整个软件流程变得清晰。

2.3配置参数小贴士

  • 参数有多种来源,如不需要勿增来源。
  • 要注意"最近原则",避免参数同名引起冲突。
  • 参数的key可以忽略大小写,所以环境变量中的 "OPTION2" 可以引起覆盖 Json文件中的 "option2" 的效果。
  • 为复杂参数选择合适的类型很重要,比如字典还是对象的取舍。

3.依赖注入

依赖注入在 ASP.NET Core 中无处不存在,在之前打印参数的例子中同样用到。依赖注入好处都有啥?为什么我们需要依赖注入?在 ASP .NET Core 中文文档中依赖注入的章节 很好地解释了:

你应该设计你的依赖注入服务来获取它们的合作者。这意味着在你的服务中避免使用有状态的静态方法调用(代码被称为 static cling)和直接实例化依赖的类型。当选择实例化一个类型还是通过依赖注入请求它时,它可以帮助记住这句话, New is Glue。通过遵循 面向对象设计的 SOLID 原则,你的类将倾向于小、易于分解及易于测试。

3.1注册服务以及简单使用

为了方便下一节测试,我准备三个文件,简单的接口、该接口的实现类,拥有接口成员的类:

IRepository

MemoryRepository

ProductTotalizer

接下来,我们使用 ASP.NET Core 自带的 DI 来注册服务:

 

可以看到,注册对象有很多种方法,并且我们可以管理对象的生命周期。注册完对象,我们就可以在我们需要的地方注入对应的对象了,还是最简单的例子——在控制器中使用:

 
 

对于控制器,我们有三种方式注入对象:构造函数、控制器动作、属性注入。然而,在一般的类中,使用自带的 DI 只能是构造函数注入。到底是哪种方式好,见仁见智。

3.2生命周期

ASP.NET Core 服务可以被配置为以下生命周期:

  • 瞬时(Transient) 在它们每次请求时都会被创建。这一生命周期适合轻量级的,无状态的服务。
  • 作用域 (Scoped) 在每次请求中只创建一次。
  • 单例(Singleton) 在它们第一次被请求时创建(或者如果你在 ConfigureServices运行时指定一个实例)并且每个后续请求将使用相同的实例。

我们将通过逐步更改 IRepository 的生命周期来看看会发生什么事情。
首先是瞬时:

 

接着是作用域:

 

最后是单例:

 

瞬时很好理解,类似每次都会new了一个对象。而对于作用域,如果一次请求中,有两个甚至多个非单例对象引用到同一个作用域类型时,他们将会收获同一个实例。单例也很好理解,从头到尾都是同一个实例。

控制单一变量,如果只是改变 ProductTotalizer 的生命周期而不是改变 IRepository 的生命周期的话,会发生什么情况呢?

3.3依赖注入小贴士

  • 遵循 SOLID 原则,考虑一下哪些是需要依赖注入的。
  • 合理考虑生命周期,假如某个类型在不同上下文中需要不同生命周期时,是否需要显式命名区分?还是考虑结构是否合理?
  • 避免静态访问服务。
  • 避免静态访问 HttpContext 。

4.启用扩展

在项目中我们往往会添加许多扩展,比如用于API文档说明的Swagger、计划任务的Hangfire、压缩响应的GZIP、跨域访问、日志扩展等等。他们的共同点就是需要先安装相应的nuget包,然后在 ConfigureServices() 方法中配置服务,最后在 Configure() 方法中启用。
我们以Swagger为例,首先是安装对应的 nuget 包—— Swashbuckle。
接着是配置扩展:

最后就是启用 Swagger 了:

 

我们访问 Swagger 的地址看看效果:

 

5.中间件

中间件是用于组成应用程序管道来处理请求和响应的组件。管道内的每一个组件都可以选择是否将请求交给下一个组件、并在管道中调用下一个组件之前和之后执行某些操作。请求委托被用来建立请求管道,请求委托处理每一个 HTTP 请求。

中间件处理请求

举一个简单的例子(更复杂的可以在中间件依赖注入对象),从 cookie 中获取 token 并附加到请求头中:

与启用扩展一样,我们同样是需要在 Configure()方法中启用中间件:

 app.UseMiddleware<JWTInHeaderMiddleware>();

如果我们有多个中间件呢,中间件的顺序可能会影响到响应结果,但并不是总是线性相关的。例如,我们新增一个对响应状态码处理的中间件:

我们把它加到其他中间件的最前面:
app.UseMiddleware<StatusCodeMiddleware>();
//....
app.UseMiddleware<JWTInHeaderMiddleware>();

虽然对状态码处理的中间件是最前面,但可以在请求的最后关头对请求结果进行处理。当然,如果中间有某个中间件短路了(没有传递到下一个中间件),就会让我们前功尽弃。

测试多个中间件处理请求

6.过滤器

与中间相似,过滤器同样可以对请求的前后执行特定代码,但是过滤器可以配置为全局有效、仅对控制器有效或是仅对 Action 有效,比中间件更具有灵活性。

过滤器处理请求

另外,过滤器从类型上还能分为:授权过滤器、资源过滤器、Action过滤器、结果过滤器。很容易实现面向切面编程,降低了耦合。
这里举一个我最喜欢的过滤器——对请求的模型进行验证:

 
在控制器或 Action 使用,只需加上特性即可:
 
 

当然,模型验证的过滤器往往具有全局性,所以我一般是加在 services.AddMvc 中:

services.AddMvc(config=>
{
config.Filters.Add(new ValidateModel());
});

 

7.工作环境

ASP.NET Core 提供了许多功能和约定来允许开发者更容易的控制在不同的环境中他们的应用程序的行为。当发布一个应用程序从开发到预演再到生产,为环境设置适当的环境变量允许对应用程序的调试,测试或生产使用进行适当的优化。

在软件开发的生命周期中,在不同的工作环境中往往是不同的状态。比如说,在测试或者预演环境中,启用Swagger扩展、控制台打印日志、允许跨域,而在生产环境中,往往处于安全、性能或者其他考虑,这些功能是需要禁止的。对于 MVC 开发者来说,在开发过程中会使用本地的JS、Css、图片等文件,而在生产环境中这些文件往往是从CDN中获取。

 

8.结语

ASP.NET Core 提供了强大而灵活的配置机制,每个点展开都像是一片新的天地。即使是经验丰富的开发者,也不能自称完全掌握全部机制。随着 .NET Core 的完善和发展,Startup.cs 也将越来越复杂。越是复杂,就越是要小心,如无需要,勿增实体!

9.相关引用

ASP.NET Core 菜鸟之路:从Startup.cs说起的更多相关文章

  1. 探索ASP.Net Core 3.0系列二:聊聊ASP.Net Core 3.0 中的Startup.cs

    原文:探索ASP.Net Core 3.0系列二:聊聊ASP.Net Core 3.0 中的Startup.cs 前言:.NET Core 3.0 SDK包含比以前版本更多的现成模板. 在本文中,我将 ...

  2. ASP.NET Core 菜鸟之路:从Startup.cs说起 转发https://www.cnblogs.com/chenug/p/6869109.html

    1.前言 本文主要是以Visual Studio 2017 默认的 WebApi 模板作为基架,基于Asp .Net Core 1.0,本文面向的是初学者,如果你有 ASP.NET Core 相关实践 ...

  3. Asp.Net Core 入门(二)——Startup.cs做了什么

    上篇介绍了Program.cs中Main做了什么,这篇我们来讨论下Startup.cs它又做了什么呢? 我们新建一个Asp.Net Core Mvc项目,先来开一下Startup的代码 public ...

  4. asp.net core 系列 2 启动类 Startup.CS

    学无止境,精益求精 十年河东,十年河西,莫欺少年穷 学历代表你的过去,能力代表你的现在,学习代表你的将来 在探讨Startup启动类之前,我们先来了解下Asp.NET CORE 配置应用程序的执行顺序 ...

  5. ASP.NET Core 运行原理剖析2:Startup 和 Middleware(中间件)

    ASP.NET Core 运行原理剖析2:Startup 和 Middleware(中间件) Startup Class 1.Startup Constructor(构造函数) 2.Configure ...

  6. ASP.NET Core 1.0 入门——Application Startup

    var appInsights=window.appInsights||function(config){ function r(config){t[config]=function(){var i= ...

  7. ASP.NET Core[源码分析篇] - Startup

    应用启动的重要类 - Startup 在ASP.NET Core - 从Program和Startup开始这篇文章里面,我们知道了Startup这个类的重要性,它主要负责了: 配置应用需要的服务(服务 ...

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

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

  9. Asp.Net Core 第03局:Startup

    总目录 前言 本文介绍Startup,它主要用于配置应用使用的服务和应用的请求管道. 环境 1.Visual Studio 2017 2.Asp.Net Core 2.2 开局 第一手:Startup ...

随机推荐

  1. 从今天开始学习Java了

    //今天第一天学习了Java的起源兴起和用途,Java的特点和优势,又重新练习了hello world, public class Hello{ public static void main(Str ...

  2. UT源代码123

    (3)设计佣金问题的程序 commission方法是用来计算销售佣金的需求,手机配件的销售商,手机配件有耳机(headphone).手机壳(Mobile phone shell).手机贴膜(Cellp ...

  3. vuejs单一事件管理组件间的通信

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  4. 20155304 2016-2017-2 《Java程序设计》第六周学习总结

    20155304 2016-2017-2 <Java程序设计>第六周学习总结 教材学习内容总结 第十章 串流设计的概念 无论来源和目的地实体形式是什么,只要取得InputStream和Ou ...

  5. 手机自动化测试:appium源码分析之bootstrap四

    手机自动化测试:appium源码分析之bootstrap四   Orientation是调整屏幕方向的操作 package io.appium.android.bootstrap.handler; i ...

  6. AngularJS1.X学习笔记1-整体看看

    听说 明天是愚人节,这与我有什么关系呢!我可 不想被愚弄,但是但是,我这么笨怎么才能不被愚弄呢?左思右想,我决定从现在开始闭关,闭关干啥哩?学习!学习AngularJS.以前学习过Angular的,不 ...

  7. Java ClassLoader加载机制

    一.体系结构(自上向下) 1.Bootstrap ClassLoader(BootStrapClassLoader) --- 启动类加载器或者叫引导类加载器,加载jdk核心的APIs,这些APIs一般 ...

  8. C#7的新语法

    阅读目录 out变量 元组(Tuples) 模式匹配(Pattern matching) 本地引用和返回(Ref locals and returns) 本地函数(Local functions) 表 ...

  9. ThinkPHP框架前后台的分页调用

    一般ThinkPHP框架在底层给开发者提供了一个基本的分页类Page.class.php里面规定了Page类的一些基本的参数和结构. 2.应用分页 1> 调取文件use Think\Page; ...

  10. 函数求值(swust oj0274)

    函数求值(0274) Time limit(ms): 1000 Memory limit(kb): 65535 Submission: 1767 Accepted: 324 Accepted 14级卓 ...