Startup介绍

Startup是Asp.net Core的应用启动入口。在.NET5及之前一般会使用startup.cs类进行程序初始化构造。如下:

public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
} public IConfiguration Configuration { get; } public void ConfigureServices(IServiceCollection services)
{
services.AddRazorPages();
} public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
app.UseHsts();
} app.UseHttpsRedirection();
app.UseStaticFiles(); app.UseRouting(); app.UseAuthorization(); app.UseEndpoints(endpoints =>
{
endpoints.MapRazorPages();
});
}
}

并在Program中使用IHostBuilder构造Host程序:

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>();
});
}

在.NET5之后的版本中,简化了这一操作(当然也可以继续保留这种方式),我们可以直接在Program的程序入口Main函数中直接构造配置我们的Startup,或者直接使用顶级语句的方式,在Program类中直接编写。

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddRazorPages();
builder.Services.AddControllersWithViews(); var app = builder.Build(); // Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
} app.UseHttpsRedirection();
app.UseStaticFiles(); app.UseAuthorization(); app.MapGet("/hi", () => "Hello!"); app.MapDefaultControllerRoute();
app.MapRazorPages(); app.Run();

对比之下,很容易发现,其中在var app = builder.Build();之前的代码这是做我们应用的初始化,比如依赖注入,配置加载等等操作,相当于Startup.cs中的ConfigureServices方法。
对应的,下面的操作就是我们的中间件配置,对应Startup.cs中的Configure方法。
同时我们可以发现,在新版的中间件配置中,少了UseRouting和UseEndpoints用来注册路由的中间件,是因为使用最小托管模型时,终结点路由中间件会包装整个中间件管道,因此无需显式调用 UseRouting 或 UseEndpoints 来注册路由。 UseRouting 仍可用于指定进行路由匹配的位置,但如果应在中间件管道开头匹配路由,则无需显式调用 UseRouting。
app.MapRazorPages(); 就相当于
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapRazorPages();
});

扩展Startup

在asp.net core中有一个IStartupFilter的接口,用于扩展Startup。
IStartupFilter 在不显式注册默认中间件的情况下将默认值添加到管道的开头。
IStartupFilter 实现 Configure,即接收并返回 Action。 IApplicationBuilder 定义用于配置应用请求管道的类。
每个 IStartupFilter 可以在请求管道中添加一个或多个中间件。 筛选器按照添加到服务容器的顺序调用。 筛选器可在将控件传递给下一个筛选器之前或之后添加中间件,从而附加到应用管道的开头或末尾。
我们来实践一下,首先创建一个空的asp.net core模板很简单,只有一个Program文件。

再来添加一个IStartupFilter的实现,只用于控制台输出执行内容。

using Microsoft.AspNetCore.Hosting;

namespace LearnStartup
{
public class StartupFilterOne : IStartupFilter
{
public Action<IApplicationBuilder> Configure(Action<IApplicationBuilder> next)
{
return builder =>
{
builder.Use(async (httpContext, _next) =>
{
Console.WriteLine("-----StartupFilterOne-----");
await _next(httpContext);
});
next(builder);
};
}
}
}

在Program中添加一行代码注册StartupFilterOne

using LearnStartup;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddTransient<IStartupFilter, StartupFilterOne>(); //注入StartupFilterOne

var app = builder.Build();

app.MapGet("/", () => "Hello World!");

app.Run();

启动程序,可以看到如下结果,中间件正常执行:

当我们有多个IStartupFilter时,我们怎么控制中间件执行顺序呢?上面所说,跟我们注入的顺序有关。
新增一个StartupFilterTwo,在修改一下Program

using LearnStartup;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddTransient<IStartupFilter, StartupFilterTwo>();
builder.Services.AddTransient<IStartupFilter, StartupFilterOne>(); var app = builder.Build(); app.MapGet("/", () => "Hello World!"); app.Run();

可以看到是先执行StartupFilterTwo中的中间件,然后再执行StartupFilterOne的中间件。

以上写法都是把中间件注册在中间件管道头部,那么如何让他在尾部执行呢?
在IStartupFilter.Configure(Action next)中的参数next,本质其实就是Startup中的Configure(感兴趣可以翻源码查看),只要调整next的执行顺序即可。
我们调整一下StartupFilterTwo的代码

public class StartupFilterTwo : IStartupFilter
{
public Action<IApplicationBuilder> Configure(Action<IApplicationBuilder> next)
{
return builder =>
{
next(builder);
builder.Use(async (httpContext, _next) =>
{
Console.WriteLine("-----StartupFilterTwo-----");
await _next(httpContext);
});
};
}
}

将next(builder)放在前面执行,我们来看看效果

此时,发现我们StartupFilterTwo并没有执行,那是因为app.MapGet("/", () => "Hello World!");是一个终结点中间件,而StartupFilterTwo注册到了中间件末尾,执行到这个中间件时就直接返回没有继续执行下一个中间件。
当我们修改Url路径为/test时,没有匹配到HelloWorld的中间件,StartupFilterTwo中的内容成功输出。

浅谈一下IStartupFilter的应用场景

IStartupFilter可以用于模块化开发的方案,在各自类库中加载对应的中间件。
在请求头部管道做一些请求的校验or数据处理。
在请求管道尾部时,如上图404,无法匹配到路由,我们可以做哪些处理。

注意事项:

IStartupFilter只能注册在中间件管道头部或者尾部,请确保中间件的使用顺序。
若中间件需要在管道中间插入使用,请使用正常的app.use在startup中正确配置。

IHostingStartup

可在启动时从应用的 Program.cs 文件之外的外部程序集向应用添加增强功能,比如我们一些0代码侵入的扩展服务,在SkyApm中的.NET实现就是基于这种方式。
我们新建一个StartupHostLib类库,添加一下Microsoft.AspNetCore.Hosting的nuget包
然后新增一个Startup类库实现IHostingStartup。
注意,必须需要添加标记,否则无法识别HostingStartup
[assembly: HostingStartup(typeof(LearnStartup.OneHostingStartup))]

using Microsoft.AspNetCore.Hosting;

[assembly: HostingStartup(typeof(LearnStartup.OneHostingStartup))]
namespace StartupHostLib
{ public class OneHostingStartup : IHostingStartup
{
public void Configure(IWebHostBuilder builder)
{
builder.ConfigureAppConfiguration((config) =>
{
Console.WriteLine("ConfigureAppConfiguration");
}); builder.ConfigureServices(services =>
{ Console.WriteLine("ConfigureServices");
}); builder.Configure(app =>
{
Console.WriteLine("Configure");
});
}
}
}

在LearnStartup中引用项目,并在launchSettings的环境变量中添加
"ASPNETCORE_HOSTINGSTARTUPASSEMBLIES": "StartupHostLib"
然后启动项目

这里可以发现,HostingStartup的执行顺序是优于应用的。
但是出现一个问题,发现原本的HelloWorld中间件消失了,但是我们依赖注入加载的中间件依旧生效。我们注释builder.Configure方法之后再启动程序。

public void Configure(IWebHostBuilder builder)
{
builder.ConfigureAppConfiguration((config) =>
{
Console.WriteLine("ConfigureAppConfiguration");
}); builder.ConfigureServices(services =>
{ Console.WriteLine("ConfigureServices");
}); //builder.Configure(app =>
//{
// Console.WriteLine("Configure");
//});
}


可以发现,应用中间件正常了。说明HostingStartup中配置中间件和应用的中间件配置冲突,并覆盖应用中间件。
我们将StartupFilterOne和StartupFilterTwo放到OneHostingStartup中去配置依赖注入,再次启动项目观察。

public void Configure(IWebHostBuilder builder)
{
builder.ConfigureAppConfiguration((config) =>
{
Console.WriteLine("ConfigureAppConfiguration");
});
builder.ConfigureServices(services =>
{
services.AddTransient<IStartupFilter, StartupFilterTwo>();
services.AddTransient<IStartupFilter, StartupFilterOne>();
Console.WriteLine("ConfigureServices");
}); //builder.Configure(app =>
//{
// Console.WriteLine("Configure");
//});
}


可以发现,依赖注入中加载的中间件是生效的。

浅谈IHostingStartup应用场景

由上面表现可以发现
IHostingStartup执行顺序由于应用执行顺序。
IHostingStartup中配置中间件管道会覆盖应用中间件管道。
依赖注入中IStartupFilter配置中间件可以正常使用不覆盖应用中间件。
所以我们使用HostingStartup的场景可以为:
对代码0侵入的场景,比如AOP数据收集(如SkyApm)。
没有中间件的场景OR符合IStartupFilter中间件的场景。
想深入了解的可以自行翻看源码,本文浅尝即止。

欢迎进群催更。

asp.net core之Startup的更多相关文章

  1. 在ASP.NET Core的startup类中如何使用MemoryCache

    问: 下面的代码,在ASP.NET Core的startup类中创建了一个MemoryCache并且存储了三个键值“entryA”,“entryB”,“entryC”,之后想在Controller中再 ...

  2. 理解ASP.NET Core - [01] Startup

    注:本文隶属于<理解ASP.NET Core>系列文章,请查看置顶博客或点击此处查看全文目录 准备工作:一份ASP.NET Core Web API应用程序 当我们来到一个陌生的环境,第一 ...

  3. ASP.NET Core教程【一】关于Razor Page的知识

    关键文件和目录结构 按照asp.net core WEB应用程序向导,创建一个工程之后 你会发现如下几个目录和文件 wwwroot:放置网站的静态文件的目录 Pages:放置razor页面的目录 ap ...

  4. ASP.Net Core开发(踩坑)指南

    ASP.NET与ASP.NET Core很类似,但它们之间存在一些细微区别以及ASP.NET Core中新增特性的使用方法,在此之前也写过一篇简单的对比文章ASP.NET MVC应用迁移到ASP.NE ...

  5. 在Asp.Net Core中集成Kafka

    在我们的业务中,我们通常需要在自己的业务子系统之间相互发送消息,一端去发送消息另一端去消费当前消息,这就涉及到使用消息队列MQ的一些内容,消息队列成熟的框架有多种,这里你可以读这篇文章来了解这些MQ的 ...

  6. asp.net core 2.1认证

    asp.net core 2.1认证 这篇文章基于asp.net core的CookieAuthenticationHandler来讲述. 认证和授权很相似,他们的英文也很相似,一个是Authenti ...

  7. ASP.NET Core如何使用WSFederation身份认证集成ADFS

    如果要在ASP.NET Core项目中使用WSFederation身份认证,首先需要在项目中引入NuGet包: Microsoft.AspNetCore.Authentication.WsFedera ...

  8. ASP.NET Core读取AppSettings (转载)

    今天在把之前一个ASP.NET MVC5的Demo项目重写成ASP.NET Core,发现原先我们一直用的ConfigurationManager.AppSettings[]读取Web.config中 ...

  9. ASP.NET Core 使用外部登陆提供程序登陆的流程,以及身份认证的流程 (转载)

    阅读目录 在Asp.Net Core 中使用外部登陆(google.微博...) 中间件管道 The Authentication Middleware The Challenge 与认证中间件进行交 ...

  10. 用分布式缓存提升ASP.NET Core性能

    得益于纯净.轻量化并且跨平台支持的特性,ASP.NET Core作为热门Web应用开发框架,其高性能传输和负载均衡的支持已广受青睐.实际上,10-20台Web服务器还是轻松驾驭的.有了多服务器负载的支 ...

随机推荐

  1. Network Science:巴拉巴西网络科学学习笔记3——第二章随机网络

    第二章:随机网络Erdős-Rényi Network (ER网络) 随机网络的两种定义形式: \(G(N,L)\)模型:N个节点,L条边随机链接. \(G(N,p)\)模型:N个节点,每个节点之间以 ...

  2. 对dubbo的DubboReference.check的参数进行剖析

    背景 在使用dubbo的时候,发现当消费者启动的时候,如果提供者没有启动,即使提供者后来启动了,消费者也调不通提供者提供的接口了. 注册中心使用都是nacos dubbo版本是3.0.4 例子 接口 ...

  3. vue将页面(dom元素)转换成图片,并保存到本地

    1 npm install html2canvas --save <template> <div class="QRCode-box"> <img i ...

  4. [双目视差] 立体匹配算法推理 - SGBM算法(二)

    文章目录 立体匹配算法推理 - SGBM算法(二) 一.SGM算法 二. 后处理 立体匹配算法推理 - SGBM算法(二) 一.SGM算法 SGM算法的全称为Semi-Global Matching, ...

  5. 通过Serverless私有化部署ChatGPT Web

    作为开发人员,想要自己部署一个渠道访问或是想随时访问但是奈何魔法有限,又或是海外服务器太贵,不想耗费这个钱,本文借助 Serverless 来搭建一下私有 ChatGPT 服务,Serverless ...

  6. 2022-12-22:给定一个数字n,代表数组的长度, 给定一个数字m,代表数组每个位置都可以在1~m之间选择数字, 所有长度为n的数组中,最长递增子序列长度为3的数组,叫做达标数组。 返回达标数组的

    2022-12-22:给定一个数字n,代表数组的长度, 给定一个数字m,代表数组每个位置都可以在1~m之间选择数字, 所有长度为n的数组中,最长递增子序列长度为3的数组,叫做达标数组. 返回达标数组的 ...

  7. 2020-11-27:go中,map的读流程是什么?

    福哥答案2020-11-27:[答案来自此链接:](https://www.bilibili.com/video/BV1Nr4y1w7aa?p=12)源码位于runtime/map.go文件中的map ...

  8. 2022-02-03:有一队人(两人或以上)想要在一个地方碰面,他们希望能够最小化他们的总行走距离。 给你一个 2D 网格,其中各个格子内的值要么是 0,要么是

    2022-02-03:最佳的碰头地点. 有一队人(两人或以上)想要在一个地方碰面,他们希望能够最小化他们的总行走距离. 给你一个 2D 网格,其中各个格子内的值要么是 0,要么是 1. 1 表示某个人 ...

  9. django--循环调用的解决办法

    不要导入该APP的包,而是加上双引号写入"App.xxx"

  10. Rust 声明式宏中的 Metavariables 有哪些

    Metavariables 官方文档确实写得很好,但是缺少一些风味,容易催眠‍ 还是直接看例子更爽一些,通常我们可以从示例代码中之间看出官方文档要表达的意思,而且很多时候我们可以直接在示例代码的基础上 ...