注:本文隶属于《理解ASP.NET Core》系列文章,请查看置顶博客或点击此处查看全文目录
准备工作:一份ASP.NET Core Web API应用程序
当我们来到一个陌生的环境,第一件事就是找到厕所在哪。
当我们接触一份新框架时,第一件事就是找到程序入口,即Main方法
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>();
});
}
代码很简单,典型的建造者模式:通过IHostBuilder
创建一个通用主机(Generic Host)
,然后启动它(至于什么是通用主机,咱们后续的文章会说到)。咱们不要一上来就去研究CreateDefaultBuilder
、ConfigureWebHostDefaults
这些方法的源代码,应该去寻找能看的见、摸得着的,很明显,只有Startup
。
Startup类
Startup
类承担应用的启动任务,所以按照约定,起名为Startup
,不过你可以修改为任意类名(强烈建议类名为Startup)。
默认的Startup
结构很简单,包含:
- 构造函数
-
Configuration
属性 -
ConfigureServices
方法 -
Configure
方法
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
// 该方法由运行时调用,使用该方法向DI容器添加服务
public void ConfigureServices(IServiceCollection services)
{
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
// 该方法由运行时调用,使用该方法配置HTTP请求管道
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
}
}
Startup构造函数
当使用通用主机(Generic Host)时,Startup构造函数支持注入以下三种服务类型:
IConfiguration
IWebHostEnvironment
IHostEnvironment
public Startup(
IConfiguration configuration,
IHostEnvironment hostEnvironment,
IWebHostEnvironment webHostEnvironment)
{
Configuration = configuration;
HostEnvironment = hostEnvironment;
WebHostEnvironment = webHostEnvironment;
}
public IConfiguration Configuration { get; }
public IHostEnvironment HostEnvironment { get; set; }
public IWebHostEnvironment WebHostEnvironment { get; set; }
这里你会发现
HostEnvironment
和WebHostEnvironment
的实例是同一个。别着急,后续文章我们聊到Host的时候,你就明白了。
ConfigureServices
- 该方法是可选的
- 该方法用于添加服务到DI容器中
- 该方法在
Configure
方法之前被调用 - 该方法要么无参数,要么只能有一个参数且类型必须为
IServiceCollection
- 该方法内的代码大多是形如
Add{Service}
的扩展方法
常用的服务有(部分服务框架已默认注册):
-
AddControllers
:注册Controller相关服务,内部调用了AddMvcCore
、AddApiExplorer
、AddAuthorization
、AddCors
、AddDataAnnotations
、AddFormatterMappings
等多个扩展方法 -
AddOptions
:注册Options相关服务,如IOptions<>
、IOptionsSnapshot<>
、IOptionsMonitor<>
、IOptionsFactory<>
、IOptionsMonitorCache<>
等。很多服务都需要Options,所以很多服务注册的扩展方法会在内部调用AddOptions
-
AddRouting
:注册路由相关服务,如IInlineConstraintResolver
、LinkGenerator
、IConfigureOptions<RouteOptions>
、RoutePatternTransformer
等 -
AddAddLogging
:注册Logging相关服务,如ILoggerFactory
、ILogger<>
、IConfigureOptions<LoggerFilterOptions>>
等 -
AddAuthentication
:注册身份认证相关服务,以方便后续注册JwtBearer、Cookie等服务 -
AddAuthorization
:注册用户授权相关服务 -
AddMvc
:注册Mvc相关服务,比如Controllers、Views、RazorPages等 -
AddHealthChecks
:注册健康检查相关服务,如HealthCheckService
、IHostedService
等
Configure
- 该方法是必须的
- 该方法用于配置HTTP请求管道,通过向管道添加中间件,应用不同的响应方式。
- 该方法在
ConfigureServices
方法之后被调用 - 该方法中的参数可以接受任何已注入到DI容器中的服务
- 该方法内的代码大多是形如
Use{Middleware}
的扩展方法 - 该方法内中间件的注册顺序与代码的书写顺序是一致的,先注册的先执行,后注册的后执行
常用的中间件有
-
UseDeveloperExceptionPage
:当发生异常时,展示开发人员异常信息页。如图
-
UseRouting
:路由中间件,根据Url中的路径导航到对应的Endpoint。必须与UseEndpoints
搭配使用。 -
UseEndpoints
:执行路由所选择的Endpoint对应的委托。 -
UseAuthentication
:身份认证中间件,用于对请求用户的身份进行认证。比如,早晨上班打卡时,管理员认出你是公司员工,那么才允许你进入公司。 -
UseAuthorization
:用户授权中间件,用于对请求用户进行授权。比如,虽然你是公司员工,但是你是一名.NET开发工程师,那么你只允许坐在.NET开发工程师区域的工位上,而不能坐在老总的办公室里。 -
UseMvc
:Mvc中间件。 -
UseHealthChecks
:健康检查中间件。 -
UseMiddleware
:用来添加匿名中间件的,通过该方法,可以方便的添加自定义中间件。
省略Startup类
另外,Startup
类也可以省略,直接进行如下配置即可(虽然可以这样做,但是不推荐):
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
// ConfigureServices 可以调用多次,最终会将结果聚合
webBuilder.ConfigureServices(services =>
{
})
// Configure 如果调用多次,则只有最后一次生效
.Configure(app =>
{
var env = app.ApplicationServices.GetRequiredService<IWebHostEnvironment>();
});
});
IStartupFilter
public interface IStartupFilter
{
Action<IApplicationBuilder> Configure(Action<IApplicationBuilder> next);
}
有时,我们想要将一系列相关中间件的注册封装到一起,那么我们只需要通过实现IStartupFilter
,并在Startup.ConfigureServices
中配置IStartupFilter
的依赖注入即可。
- 在
IStartupFilter
中配置的中间件,总是比Startup
类中Configure
方法中的中间件先注册;对于多个IStartupFilter
实现,执行顺序与服务注册时的顺序一致
我们可以通过一个例子来验证一下中间件的注册顺序。
首先是三个IStartupFilter
的实现类:
public class FirstStartupFilter : IStartupFilter
{
public Action<IApplicationBuilder> Configure(Action<IApplicationBuilder> next)
=> app =>
{
app.Use((context, next) =>
{
Console.WriteLine("First");
return next();
});
next(app);
};
}
public class SecondStartupFilter : IStartupFilter
{
public Action<IApplicationBuilder> Configure(Action<IApplicationBuilder> next)
=> app =>
{
app.Use((context, next) =>
{
Console.WriteLine("Second");
return next();
});
next(app);
};
}
public class ThirdStartupFilter : IStartupFilter
{
public Action<IApplicationBuilder> Configure(Action<IApplicationBuilder> next)
=> app =>
{
app.Use((context, next) =>
{
Console.WriteLine("Third");
return next();
});
next(app);
};
}
接下来进行注册:
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureServices(services =>
{
// 第一个被注册
services.AddTransient<IStartupFilter, FirstStartupFilter>();
})
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
})
.ConfigureServices(services =>
{
// 第三个被注册
services.AddTransient<IStartupFilter, ThirdStartupFilter>();
});
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
// 第二个被注册
services.AddTransient<IStartupFilter, SecondStartupFilter>();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
// 第四个被注册
app.Use((context, next) =>
{
Console.WriteLine("Forth");
return next();
});
}
}
最后通过输出可以看到,执行顺序的确是这样子的。
First
Second
Third
Forth
IHostingStartup
与IStartupFilter
不同的是,IHostingStartup
可以在启动时通过外部程序集向应用增加更多功能。不过这要求必须调用ConfigureWebHostDefaults
扩展方法
我们经常使用的Nuget包
SkyApm.Agent.AspNetCore
就使用了该特性。
下面我们就来看一下该如何使用它。
HostingStartup 程序集
要创建HostingStartup程序集,可以通过创建类库项目或无入口点的控制台应用来实现。
接下来咱们还是看一下上面提到过的SkyApm.Agent.AspNetCore
:
using SkyApm.Agent.AspNetCore;
[assembly: HostingStartup(typeof(SkyApmHostingStartup))]
namespace SkyApm.Agent.AspNetCore
{
internal class SkyApmHostingStartup : IHostingStartup
{
public void Configure(IWebHostBuilder builder)
{
builder.ConfigureServices(services => services.AddSkyAPM(ext => ext.AddAspNetCoreHosting()));
}
}
}
该HostingStartup类:
- 实现了
IHostingStartup
接口 -
Configure
方法中使用IWebHostBuilder
来添加增强功能 - 配置了
HostingStartup
特性
HostingStartup 特性
HostingStartup
特性用于标识哪个类是HostingStartup类,HostingStartup类需要实现IHostingStartup
接口。
当程序启动时,会自动扫描入口程序集和配置的待激活的的程序集列表(参见下方:激活HostingStarup程序集),来找到所有的HostingStartup
特性,并通过反射的方式创建Startup
并调用Configure
方法。
using SkyApm.Agent.AspNetCore;
[assembly: HostingStartup(typeof(SkyApmHostingStartup))]
namespace SkyApm.Agent.AspNetCore
{
internal class SkyApmHostingStartup : IHostingStartup
{
public void Configure(IWebHostBuilder builder)
{
builder.ConfigureServices(services => services.AddSkyAPM(ext => ext.AddAspNetCoreHosting()));
}
}
}
激活HostingStarup程序集
要激活HostingStarup程序集,我们有两种配置方式:
1.使用环境变量(推荐)
使用环境变量,无需侵入程序代码,所以我更推荐大家使用这种方式。
配置环境变量ASPNETCORE_HOSTINGSTARTUPASSEMBLIES
,多个程序集使用分号(;)进行分隔,用于添加要激活的程序集。变量WebHostDefaults.HostingStartupAssembliesKey
就是指代这个环境变量的Key。
另外,还有一个环境变量,叫做ASPNETCORE_HOSTINGSTARTUPEXCLUDEASSEMBLIES
,多个程序集使用分号(;)进行分隔,用于排除要激活的程序集。变量WebHostDefaults.HostingStartupExcludeAssembliesKey
就是指代这个环境变量的Key。
我们在 launchSettings.json 中添加两个程序集:
"environmentVariables": {
"ASPNETCORE_HOSTINGSTARTUPASSEMBLIES": "SkyAPM.Agent.AspNetCore;HostingStartupLibrary"
}
2.在程序中配置
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseSetting(
WebHostDefaults.HostingStartupAssembliesKey,
"SkyAPM.Agent.AspNetCore;HostingStartupLibrary")
.UseStartup<Startup>();
});
这样就配置完成了,很
相关文章
- 理解ASP.NET Core - [01] Startup
- ASP.NET Core Startup类 Configure()方法 | ASP.NET Core 中间件详细说明
- 【转】.NET(C#):浅谈程序集清单资源和RESX资源 关于单元测试的思考--Asp.Net Core单元测试最佳实践 封装自己的dapper lambda扩展-设计篇 编写自己的dapper lambda扩展-使用篇 正确理解CAP定理 Quartz.NET的使用(附源码) 整理自己的.net工具库 GC的前世与今生 Visual Studio Package 插件开发之自动生
- Webservice WCF WebApi 前端数据可视化 前端数据可视化 C# asp.net PhoneGap html5 C# Where 网站分布式开发简介 EntityFramework Core依赖注入上下文方式不同造成内存泄漏了解一下? SQL Server之深入理解STUFF 你必须知道的EntityFramework 6.x和EntityFramework Cor
- .NET CORE学习笔记系列(5)——ASP.NET CORE的运行原理解析
- 网络游戏开发-服务器(01)Asp.Net Core中的websocket,并封装一个简单的中间件
- 一个由正则表达式引发的血案 vs2017使用rdlc实现批量打印 vs2017使用rdlc [asp.net core 源码分析] 01 - Session SignalR sql for xml path用法 MemCahe C# 操作Excel图形——绘制、读取、隐藏、删除图形 IOC,DIP,DI,IoC容器
- ASP.NET Core中间件之理解
- ASP.NET Core RESTful学习理解
- ASP.NET Core 运行原理解剖[1]:Hosting