翻看以前的 markdown 发现还有一篇文章未发表,为2年前写的东西了,幸好发现了不然都忘记了,文章内的内容我也都记不清楚了,一起学习。
.NET Core是一个可以用来构建现代、可伸缩和高性能的跨平台软件应用程序的通用开发框架。可用于为Windows、Linux和MacOS构建软件应用程序。
ASP.NET Core本质上是一个独立的控制台应用,它并不是必需在IIS内部托管且并不需要IIS来启动运行(而这正是ASP.NET Core跨平台的基石)
启动流程:
当ASP.NET Core应用程序启动时,他首先会配置并运行其宿主(Host),宿主主要用来启动、初始化应用程序,管理其生命周期。
可以看到Program类是程序的入口,与控制台应用程序一样。
public class Program
{
public static void Main(string[] args)
{
CreateWebHostBuilder(args).Build().Run();
} public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
Builder.UseStartup<Startup>();
});
}
程序首先由CreateWebHostBuilder方法创建一个IWebHostBuilder对象,并调用Build方法得到IWebHost对象的实例,然后调用对象的Run()方法运行;还有Start()方法也可以运行,区别是前者以阻塞的方式运行。
CreateWebHostBuilder方法内部,调用WebHost类的静态方法CreateDefaultBuilder,返回IWebHostBuilder类型的对象,最后调用UseStartup方法进一步配置应用程序的启动。
用CreateDefaultBuilder方法创建IWebHostBuilder对象时所包含的主要默认选项如下:
CreateDefaultBuilder的代码大致如下:
public static IWebHostBuilder CreateDefaultBuilder(string[] args)
{
var builder = new WebHostBuilder(); if (string.IsNullOrEmpty(builder.GetSetting(WebHostDefaults.ContentRootKey)))
{
builder.UseContentRoot(Directory.GetCurrentDirectory());
}
if (args != null)
{
builder.UseConfiguration(new ConfigurationBuilder().AddCommandLine(args).Build());
}
builder.ConfigureAppConfiguration((hostingContext, config) =>
{
var env = hostingContext.HostingEnvironment;
config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true);
if (env.IsDevelopment())
{
var appAssembly = Assembly.Load(new AssemblyName(env.ApplicationName));
if (appAssembly != null)
{
config.AddUserSecrets(appAssembly, optional: true);
}
}
config.AddEnvironmentVariables();
if (args != null)
{
config.AddCommandLine(args);
}
})
.ConfigureLogging((hostingContext, loggingBuilder) =>
{
loggingBuilder.Configure(options =>
{
options.ActivityTrackingOptions = ActivityTrackingOptions.SpanId
| ActivityTrackingOptions.TraceId
| ActivityTrackingOptions.ParentId;
});
loggingBuilder.AddConfiguration(hostingContext.Configuration.GetSection("Logging"));
loggingBuilder.AddConsole();
loggingBuilder.AddDebug();
loggingBuilder.AddEventSourceLogger();
}).
UseDefaultServiceProvider((context, options) =>
{
options.ValidateScopes = context.HostingEnvironment.IsDevelopment();
});
ConfigureWebDefaults(builder);
return builder;
}
里面的默认配置也能通过IWebHostBuilder接口提供的方法进行修改。
完整版WebHost代码:https://github.com/dotnet/aspnetcore/blob/18a926850f7374248e687ee64390e7f10514403f/src/DefaultBuilder/src/WebHost.cs
IWebHostBuilder接口有多个扩展方法,一个重要的方法就是UseStartup方法,主要功能是向应用程序提供用于配制启动的类,应该具有两个方法
两个方法都会在运行时被调用,且在应用程序的整个生命周期内,只执行一次。其中ConfigureServices是可选的Configure是必选
ConfigureServices方法有一个参数,为IServiceCollection类型,使用它能够将应用程序级别的服务注册到ASP.NET Core 默认的依赖注入容器中
Configure方法默认包函的参数类型为IApplicationBuilder,通过这个可以添加中间件
比如一个Startup类:https://github.com/dotnet/aspnetcore/blob/8b30d862de6c9146f466061d51aa3f1414ee2337/src/Mvc/test/WebSites/RazorPagesWebSite/Startup.cs
ASP.NET Core 中还有一个中间件的概念,所谓中间件,就是处理 HTTP 请求和响应的组件,它本质上是一段用来处理请求与响应的代码。多个中间件之间的链式关系使之形成了管道(Pipeline)或者请求管道。管道意味着请求将从一端进入,并按照一定的顺序由每一个中间件处理,最后由另一端出来。
ASP.NET Core 中内置了多个中间件,主要有MVC、认证、错误、静态文件、HTTPS和CORS等,也允许自定义中间件。
上一节提到的Startup类的Configure方法就是用来添加中间件的地方,通过调用IApplicationBuilder接口中以Use开头的扩展方法即可添加系统内置的中间件,比如:
public void Configure(IApplicationBuilder app)
{
app.Mvc();
app.UseAuthentication(););
}
在这些系统内置中间件的内部实现中,每个中间件都是通过调用IApplicationBuilder接口的Use和Run方法添加到请求管道中的。
Run方法,接受一个RequestDelegate类型的参数,它是一个委托,用来处理传入的HTTP请求,定义如下:
public delegate Task RequestDelegate(HttpContext context);
Use方法不同的是,它会在处理完请求之后还会将请求传入下一个中间件做处理。
例子:
app.Use(async (context, next) =>
{
Console.WriteLine("中间件 A:开始");
await next();// 下一个中间件
Console.WriteLine("中间件 A:结束");
});app.Run(async (context) =>
{
Console.WriteLine("中间件 B");
await context.Response.WriteAsync("Hello, world");
});
运行结果
中间件 A:开始
中间件 B
中间件 A:结束
除了Run和Use外,IApplicationBuilder接口还提供了其他方法,可以指定一些条件来判断是否进入下一个分支管道,比如有Map、MapWhen和UseWhen。
比如,Map会根据是否匹配指定的请求路径来决定是否在一个新的分支上继续执行后续的中间件;MapWhen会对传入的HttpContext对象进行更细致的判断,比如是否包含制定的消息头等。
下面只举一个Map的例子
app.Use(async (context, next) =>
{
Console.WriteLine("中间件 A:开始");
await next();// 下一个中间件
Console.WriteLine("中间件 A:结束");
});app.Map(new PathString("/maptest"),
a => a.Use(async (context, next) =>
{
Console.WriteLine("中间件 B:开始");
await next(); // 下一个中间件
Console.WriteLine("中间件 B:结束");
}));
app.Run(async context =>
{
Console.WriteLine("中间件 C");
await context.Response.WriteAsync("Hello, world");
});
访问 https://localhost:5001/maptest
中间件 A:开始
中间件 B:开始
中间件 B:结束
中间件 A:结束
可以看到新分支管道执行完毕后不会回到原来的管道,而剩下两个是会继续回去执行。
创建自定义中间件需要一个特定的构造函数和一个名为Invoke的方法。对于构造函数应包含一个RequestDelegate类型的参数,该参数表示在管道中的下一个中间件;而对于Invoke方法,应包含一个HttpContext类型的参数,并返回Task类型。
比如创建自定义中间件让应用程序只接受GET和HEAD方法:
public class HttpMethodCheckMiddleware
{
// 在管道中的下一个中间件
private readonly RequestDelegate _next; // 构造函数中可以得到下一个中间件,并且还可以注入需要的服务,比如 IHostEnvironment
public HttpMethodCheckMiddleware(RequestDelegate requestDelegate, IHostEnvironment environment)
{
this._next = requestDelegate;
}
// 对 HTTP 请求方法进行判断,如果符合条件则继续执行下一个中间件
// 否则返回 400 Bad Request 错误,并在响应中添加自定义消息头用于说明错误原因
public Task Invoke(HttpContext context)
{
var requestMethod = context.Request.Method.ToUpper();
if (requestMethod == HttpMethods.Get || requestMethod == HttpMethods.Head)
{
return _next(context);
}
else
{
context.Response.StatusCode = 400;
context.Response.Headers.Add("X-AllowHTTPWeb", new[] {"GET,HEAD"});
context.Response.WriteAsync("只支持 GET、HEAD 方法");
return Task.CompletedTask;
}
}
}
在Startup类的Configure方法中添加中间件:
app.UseMiddleware<HttpMethodCheckMiddleware>();
或者创建扩展方法
public static class CustomMiddlewareExtensions
{
public static IApplicationBuilder UseHttpMethodCheckMiddleware(this IApplicationBuilder builder)
{
return builder.UseMiddleware<HttpMethodCheckMiddleware>();
}
}
调用扩展方法添加中间件
app.UseHttpMethodCheckMiddleware();
依赖注入(Dependency injection,DI)是一种实现对象及其合作者或依赖项之间松散耦合的技术。
ASP.NET Core框架内部集成了自身的依赖注入容器,在ASP.NET Core中,所有被放入依赖注入容器的类型或组件成为服务。分为两类,第一种是框架服务,是ASP.NET Core框架的组成部分;另一种是应用服务,所有由用户放到容器中的服务都属于这一类。
在程序中使用服务需要向容器添加服务,然后通过构造函数以注入的方式注入所需要的类。若要添加服务,则需要使用Startup类的ConfigureService方法,该方法有一个IServiceCollection类型的参数,它位于Microsoft.Extensions.DependencyInjection命名空间,如下:
public void ConfigureServices(IServiceCollection services)
{
services.Add(new ServiceDescriptor(typeof(IBook), typeof(Book), ServiceLifetime.Scoped));
}
使用了IServiceCollection的Add方法添加了一个ServiceDescriptor,ServiceDescriptor类用来描述一个服务和对应的实现,以及其生命周期;如上构造函数,前两个参数分别是接口及其实现的类型,第3个参数是其生命周期。
在ASP.NET Core中内置的依赖注入容器中,服务的生命周期有如下3种:
当服务添加至容器中后,就可以在程序中使用了,例如在Controller中或Startup类的Configure方法中。使用的方式有以下几种:构造函数注入、方法注入和通过HttpContext手工获取。
别怀疑,我也看不懂,看完就俩字“你在说什么钩8?”,这里引用一个别的例子,很形象:
软件设计原则中有一个依赖倒置原则(DIP),就是为了解耦;高层模块不应该依赖于底层模块。二者都应该依赖于抽象;抽象不应该依赖于细节,细节应该依赖于抽象;而依赖注入是实现这种原则的方式之一;
举个现实中例子:小明去行政领一节5号电池,然后行政给了小明一节黑象牌5号电池来分析 ;
小明只需要向行政领一节5号电池即可,小明不需要关心什么牌子的电池,电池从哪来的,电池的价格等等。他们俩共同需要关心的是一节5号电池即可;即使后期行政给了小明北孚电池,小明仍可以正常使用;他们只需要满足一个规则(5号电池)即可;小明(高层模块)不应该依赖黑象牌电池(低层模块),两者应该都依赖5号电池(抽象)。如果小明直接获取到(new)黑象牌电池,如果后期业务变更提供的是北孚电池,那么我们就需要更改小明的代码;再如果公司有几百个小明,代码量可想而知;为了解决直接获取(new)黑象牌电池,简单说为了解耦,我们让每位员工通过行政领取(构造函数,属性,方法等),这种即使更改其他品牌,而小明压根不需要关心;
最常见的就是构造注入:
public class ProductService
{
private readonly IProductRepository _productRepository;
public ProductService(IProductRepository productRepository)
{
_productRepository = productRepository;
}
public void Delete(int id)
{
_productRepository.Delete(id);
}
}
ProductService将IProductRepository作为依赖项注入其构造函数中,然后在Delete方法中使用它。
还有两种分别是属性注入和方法注入。属性注入是通过设置类的属性来获取所需要的依赖;方法注入是通过在方法的参数中传入所需要的依赖。
(能看懂构造注入就行...
MVC是模型(Model)、视图(View)、控制器(Controller),是一种常见的Web应用程序的架构模式,
Controller的角色比较重要,介于Model与View之间,起到了入口点的作用。当应用程序收到请求时,ASP.NET Core MVC会将请求路由到相应的Controller,Controller操作Model完成对数据的更改。不仅如此,Controller还会将获取到的数据传给对应的View。ASP.NET Core MVC是构建在ASP.NET Core之上的,若使用需要添加中间件。
Public void ConfigureServices(IServiceCollection services){
services.AddMvc();
}Public void Configure(IApplicationBuilder app, IHostingEnvironment env){
app.UseMvc();
}
路由负责从请求的URL中获取信息,并根据这些信息来定位或映射到对应的Controller与Action
ASP.NET Core 中提供了创建路由和路由处理器的接口,要创建路由,首先要先添加与路由相关的服务,然后配置路由中间件。
public void ConfigureServices(IServiceCollection services)
{
services.AddRouting();
}// This method gets called by the runtime. Use this method to configure the HTTP requ
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
var trackPackageRouteHandler = new RouteHandler(context =>
{
var routeValues = context.GetRouteData().Values;
return context.Response.WriteAsync($"路由值:{routeValues.ToString()}");
});
var routeBuilder = new RouteBuilder(app,trackPackageRouteHandler);
routeBuilder.MapRoute("Track Package Route","package/{operation}/{id:int}");
routeBuilder.MapGet("hello/{name}", context =>
{
var name = context.GetRouteValue("name");
return context.Response.WriteAsync($"Hi {name}");
});
var routes = routeBuilder.Build();
app.UseRouter(routes);
}
在上述代码的Configure方法中,首先创建一个RouteHandler,即路由处理器,它会从请求的URL中获取路由信息,并将其输出;接着,创建一个RouteBuilder;并使用它的MapRoute方法来添加路由信息,这些信息包括路由名称以及要匹配的URL模板,在上面的实例中,URL模板的值为package/{operation}/{id:int}。除了调用MapRoute外,后面还可以使用MapGet方法添加仅匹配GET方法的请求,最后调用IApplicationBuilder的UseRouter扩展方法来添加路由中间件。
对于ASP.NET Core MVC ,定义路由的方法有以下两种。
基于约定的路由:基于约定的路由会根据一些约定来创建路由,它要在应用程序的Startup类中来定义,事实上,上面的实例就是基于约定的路由。
特性路由:使用c#特性对Controller和Action指定其路由信息。
.NET MVC里面封装了上述逻辑
要使用基本约定的路由,首先定义一个或若干个路由约定,同时,只要保证所有定义的路由约定能够尽可能满足不同形式的映射即可。
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseMvc(routes =>
{
routes.MapRoute(
template:$"{{controller}}/{action}");
});
}
在大括号{}中的部分是路由参数,每一个参数都有一个名称,它们充当了占位符的作用,参数与参数之间以“/”分隔。
比如下面的URL:
http://localhost:5001/home/index
http://localhost:5001/account/register
他们会分别映射到HomeController的Index方法以及AccountController的register方法。
举例子:
routes.MapRoute(
template: "{controller}/{action}/{id?}",
defaults: new { controller = "Home", action = "Index"}
)
也可以写成:
routes.MapRoute(
"default": "{Controller=Home}/{action=Index}/{id?}"
)
最后一个参数id(问号代表可选),会向action映射的方法同名参数传值,如上例子的HomeController可能就是:
public class HomeController : Controller{
public IActionResult Index(){
return Ok("Hello");
}
public IActionResult Welcome(){
return Ok("Hello,Your id:" + id);
}
}
当请求URL为/Home/Welcome/1时,URL中的1会传递给Welcome方法的id参数,最后的id还可以通过别的函数限制,比如length()、range()和正则等。
如下格式,只需要在Controller类或者Action方法上添加Route特性即可
public class HomeController: Controller{
[Route("")]
[Route("Home/Index")]
[Route("AnotherOne")]
public IActionResult Index()
{
return Ok("Hello");
}
}
下面三个URL都能映射到这个Action上
http://locahost:5001
http://localhost:5001/Home/Index
http://localhost:5001/AnotherOne](http://localhost:5001/AnotherOne)
如果要为Action方法传递固定参数的话,如下:
[Route("[action]/{name?}")]
public IActionResult Welcome(string name){
...
}
指定HTTP方法的话:
[HttpGet("{id:int}")]
public IActionResult Welcome(int id){
...
}
在ASP.NET Core MVC中,一个Controller包括一个或多个Action,而Action则是Controller中一些public类型的函数,它们可以接受参数、执行相关逻辑,最终返回一个结果,该结果作为HTTP响应返回给发起HTTP请求的客户端。对于MVC视图应用而言,Action返回的是View;而对于Web API应用程序来讲,则返回响应的资源或者HTTP状态码。
根据约定,Controller 通常应放在应用程序根目录下的Controller目录中,并且它继承自using Microsoft.AspNetCore.Mvc; 命名空间下的Controller类,而这个Controller类由继承自自己的ControllerBase抽象类。此外,在类的命名上应以Controller结尾。
using Microsoft.AspNetCore.Mvc;public class HomeController : Controller{
}
每个Action都应返回IActionResult类型或ActionResult类型的值作为HTTP请求的结果。在ASP.NET Core MVC中,Action的返回结果有几种比较常见的类型,包括状态码、包含对象的状态码、重定向和内容。
返回具体的状态码:StatusCode:return StatusCode(403);
更直观的方法是,使用StatusCode静态类,该类定义了所有可能的状态码常量:
return StatusCode(Microsoft.AspNetCore.Http.StatusCodes.Status100Continue);
包含对象的状态码,这一类结果继承自ObjectResult,包括OkObjectResult、CreatedResult和NotFoundObjectResult等:
var result = new OkObjectResult(new { message ="操作成功",currentDate = DateTime.Now});
return result;
重定向结果包括RedirectResult、LoaclRedirectResult、RedirectToActionResult和RedirectToResult等
//重定向到指定的URL
return Redirect("http://www.microsoft.com/");
//重新定向到当前应用程序中美的另一个URL
return LocalRedirect("/account/login");
//重定向到指定的Action
return RedirectToAction("login");
//重定向到指定的路由
return RedirectToRoute("default",new { action="logion",controller = "account"});
内容结果包括ViewResult、ParialViewResult、JsonResult和ContentResult等,其中ViewResult和PariaViewResult在MVC试图应用中非常常见,用于返回响应的页面;JsonResult用于返回JSON字符串,ContentResult用于返回一个字符串。
return JsonResult(new { message="This is a JSON result.",data=DateTime.Now});
return Content("返回字符串");
除了返回IActionResult外,当在Action要返回数据时,还可以使用ActionResult类,它既可以表示一个ActionResult对象,也可以表示一个具体类型。
[HttpGet("{id}")]
public ActionResult<Employee> Get(long id)
{
if (id<=0)
{
return BadRequest();
} var employee = new Employee(id);
if (employee == null)
{
return NotFound();
}
return employee;
}
ActionResult的优点在于更为灵活地为Action设置返回值,同时,当使用OpenAPI(即Swagger)为API生成文档时,Action不需要使用[Produces]特性显示地指明其返回类型,因为其中的泛型参数T已经为OpenAPI指明了要返回的数据类型。
在ASP.NET Core MVC中,当一个HTTP请求通过路由定位到Controller中的某一个Action上时,HTTP请求中的一部分信息会作为Action的参数。在URL中的id或name会传递给Action中的同名参数。将HTTP请求中数据映射到Action中参数的过程称为模型绑定。
[Route("api/[controller]")]
public class BlogsControlls : Controller
{
[HttpGet("[action]/{keyword}")]
public IActionResult Search(string keyword, int top)
{
return NotFound();
}
}
在上面的例子中:当请求的URL为https://localhost:5001/api/blogs/search/web?top=10时,其中的web和10会分别传递给Search方法的两个参数keyword和top。MVC在进行模型绑定时,会通过参数名在多个可能的数据源中进行匹配。第一个参数keyword实在路由中指定的,它的值会直接从URL中响应的部分解析得到;而第二个参数top并未在路由中定义,因此ASP.NET Core MVC会尝试从查询字符串中获取。
除了从路由以及查询字符串中获取数据以外,ASP.NET Core MVC还会尝试从表单(Form)中获取数据来帮顶到Action中的参数。因此,它主要使用以下3中数据源来为Action的参数提供数据,并且按照顺序来从一下每一种方式中获取:
像特性路由一样,ASP.NET Core MVC也提供了用于模型绑定的特性,使用如下特性能够为Action的参数显示指定不同的绑定源。
[FromHeader]特性:从HTTP请求的Header中获取参数的值
[FromQuery]特性:从查询字符串中获取参数的值
[FromServices]特性:从依赖注入容器中获取参数的值
[FromRoute]特性:从路由中获取参数的值
[FromForm]特性:从表单中获取该参数的值
[FromBody]特性:从HTTP请求的消息正文获取参数的值。
另外还有两个特性用于指明参数是否必须使用绑定
BinRequiredAttribute:如果没有值绑定到此参数,或绑定不成功,这个特性将添加一个ModelState错误。
BinNeverAttribute:在进行模型绑定时,忽略此参数。
比如:
[HttpGet("[action]/{keyword}")]
public IActionResult Post([FromBody] string keyword, [FromHeader]int id)
{
...
}
要正确访问这个Action的话,请求如下:
POST /api/post1 HTTP/1.1
Host: localhost:5001
id: testid
Conten-Type: application/jsonkeyword=testkeyword
模型验证是指数据被使用之前的验证过程,它发生在模型绑定之后。在ASP.NET Core MVC中,要实现对数据的验证,最方便的方式时使用数据注解(Data annotation),它使用特性为数据添加额外的信息。数据注解通常用于验证,只要为类的属性添加需要的数据注解即可,这些特性位于System.ComponentModel.DataAnnotations
命名空间下。
public class BlogDto
{
[Required]
public int Id { get; set; }
[Required, MinLength(10, ErrorMessage = "验证失败,自定义的错误提示信息")]
public string Title { get; set; }
[MaxLength(1000)]
public string Content { get; set; }
[Url]
public string Url { get; set; }
[Range(1,5)]
public int Level { get; set; }
}
补充,上一小节的模型绑定不仅可以处理基本数据类型,也可以是上面这种,类似于C语言结构体一样,很多个属性。
在Controller内的Action中,要检查某一个对象是否满足指定的条件,只要调用ModelState.IsValid属性,其中ModelState是ControllerBase类的属性
过滤器和中间件很相似,在ASP.NET Core MVC中它们能够在某些功能前后执行,由此而形成一个管道。如果,在Action方法开始执行前与执行后运行,因此它能够极大地减少代码重复,如果一些代码需要每个Action之前执行,那么只要使用一个Action过滤器即可,而无需添加重复的代码。
ASP.NET Core MVC提供了以下5种类型的过滤器。
Authorization过滤器:最先执行,用于判断用户是否授权,如果未授权,则直接结束当前请求,这种类型的过滤器实现了IAsyncAuthorizationFilter或IAuthorizationFilter接口。
Resource过滤器:在Authorization过滤器后执行,并在执行其他过滤器(除Authorization过滤器外)之前和之后执行,由于它在Action之前执行,因而可以用来对请求判断,根据条件来决定是否继续执行Action,这种类型过滤器实现了IAsyncResourceFilter或IResourceFilter接口
Action过滤器:在Action执行的前后执行,与Resource过滤器不一样,它在模型绑定后执行,这种类型的过滤器实现了IAsyncActionFilter或IActionFilter接口
Exception过滤器:用于捕获异常,这种类型的过滤器实现了IAsyncExceptionFilter或IExceptionFilter接口
Result过滤器:在IActionResult执行的前后执行,使用它能够控制Action的执行结果,比如格式化结果等。需要注意的时,它只有在Action方法成功执行后才会运行,这种类型的过滤器实现了IAsyncdResultFilter或IRsultFilter接口。
工作顺序:
(本地图片已丢失😅)
当要创建过滤器的时候,应该实现IXXXFilter或IAsyncXXXFilter,这两个接口的区别是前者同步、后者异步。ASP.NET Core MVC会首先检查异步实现,如果没有实现异步方式,则继续检查同步实现,因此在创建过滤器的时,不需要同步和异步接口都实现。
以IAsyncActionFilter和IActionFilter为例,这两个接口的定义分别如下:
public interface IAsyncActionFilter : IFilterMetadata
{
Task OnActionExecutionAsync(ActionExecutedContext context,ActionExecutionDelegate next);
}public interface IActionFilter : IFilterMetadata
{
void OnActionExecuted(ActionExecutedContext context);
void OnActionExecuting(ActionExecutingContext context);
}
在IActionFilter接口中包括两个方法,分别表示Action执行前与执行后要执行的方法;在IAsyncActionFilter接口中仅有一个 OnActionExecutionAsync方法,该方法的第二个参数ActionExecutionDelegate表示要执行的Action,它是一个委托类型,因此在这个方法的内部可以直接调用next(),并在next()前后执行相应的代码。
下面的代码展示了一个自定义过滤器同时实现了异步与同步的Action过滤器接口:
public class CustomActionFilter : IActionFilter, IAsyncActionFilter
{
public void OnActionExecuting(ActionExecutingContext context)
{
//Action执行前
} public void OnActionExecuted(ActionExecutedContext context)
{
//Action执行后
}
public Task OnActionExecutionAsync(ActionExecutedContext context, ActionExecutionDelegate next)
{
//Action执行前
next();
//Action执行后
return Task.CompletedTask;
}
}
看完上面的我相信再把Teamserver的入口文件放在这里,就好理解了:
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;using CommandLine;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using TeamServer.Interfaces;
using TeamServer.Models;
namespace TeamServer
{
public class Program
{
private static IHandlerService _handlerService;
private static IServerService _serverService;
public static async Task Main(string[] args)
{
await Parser.Default.ParseArguments<Options>(args)
.MapResult(RunOptions, HandleParseErrors);
}
private static async Task RunOptions(Options opts)
{
var host = CreateHostBuilder(Array.Empty<string>()).Build();
// Set the server password for user auth
var userService = host.Services.GetRequiredService<IUserService>();
userService.SetPassword(opts.SharedPassword);
// Always load the default handlers
_handlerService = host.Services.GetRequiredService<IHandlerService>();
_handlerService.LoadDefaultHandlers();
// If custom Handler
if (!string.IsNullOrEmpty(opts.HandlerPath))
await LoadHandler(opts.HandlerPath);
_serverService = host.Services.GetRequiredService<IServerService>();
C2Profile profile;
// If custom C2 profile
if (!string.IsNullOrEmpty(opts.ProfilePath))
profile = await LoadC2Profile(opts.ProfilePath);
else // otherwise load the default
profile = new C2Profile();
_serverService.SetC2Profile(profile);
await host.RunAsync();
}
private static async Task LoadHandler(string path)
{
var bytes = await File.ReadAllBytesAsync(path);
_handlerService.LoadHandler(bytes);
}
private static async Task<C2Profile> LoadC2Profile(string path)
{
var text = await File.ReadAllTextAsync(path);
var deserializer = new YamlDotNet.Serialization.DeserializerBuilder().Build();
return deserializer.Deserialize<C2Profile>(text);
}
private static async Task HandleParseErrors(IEnumerable<Error> errs)
{
foreach (var err in errs)
await Console.Error.WriteLineAsync(err?.ToString());
}
private static IHostBuilder CreateHostBuilder(string[] args)
{
var builder = Host.CreateDefaultBuilder(args);
builder.ConfigureWebHostDefaults(Configure);
return builder;
}
private static void Configure(IWebHostBuilder webBuilder)
=> webBuilder.UseStartup<Startup>();
}
}
《第三章 ASP.NET Core核心特性》
https://www.debugger.wiki/article/html/1585477416849943
https://www.jianshu.com/p/cc95657a2a2a