ASP.NET Core与RESTful API 开发实战(二)

ASP.NET Core与RESTful API 开发实战(二)

简介

这是一篇《ASP.NET Core与RESTful API 开发实战》的个人读书笔记,如有需要推荐购买正版书籍,详细阅读学习。

文件结构

launchSettings.json:应用程序运行配置文件,包含了程序运行的相关配置,如URL和端口信息等。

wwwroot:文件夹,用于储存静态文件,如图片、CSS和JavaScript等文件。

依赖项:当前应用程序所依赖的NuGet包和SDK,其中Microsoft.NETCore.App含了.NET Core中的所有API。

Controllers:文件夹,用于储存在应用程序运行时要用到的一些配置项。

Program.cs:程序入口类,ASP.NET Core应用程序从这个类中的Main函数运行,这与控制台程序完全一样。

Startup.cs应用程序启动时的配置类,用于配置ASP.NET Core应用程序中的服务、中间件、MVC和异常处理等。

ASP.NET Core核心特性

ASP.NET Core提供了一系列重要特性,如启动、中间件=依赖注入、MVC、配置、日志以及错误处理等,为应用程序的开发、启动、运行以及错误记录等提供了全面的支持与灵活的配置。

应用程序的启动时通过构建WebHost对象实现的,在这一过程中,ASP.NET Core允许灵活配置WebHost,它提供了Kestrel这一轻量级、跨平台Web服务器。

通过Startup类,开发人员能够充分地使用ASP.NET Core所提供的依赖注入和中间件等特性,为应用程序的功能提供了极大的灵活性与多样性。依赖注入能够帮助开发人员创建低耦合的应用,中间件能够灵活控制对Web请求的处理。

MVC模式是Web应用程序中常见的架构模式,MVC也是ASP.NET Core非常重要的组成部分。ASP.NET Core MVC还提供了模型绑定、模型验证和过滤器等功能。

配置、日志以及错误处理都是应用程序中不可缺少的功能,ASP.NET Core提供强大且灵活的配置系统与日志系统,支持不同形式的配置源,并且支持自定义配置源,可以便捷地访问配置项;ASP.NET Core还提供了丰富的组件以及灵活、简单的使用方式,可以在应用程序中轻松实现日志功能。

启动与宿主

当ASP.NET Core应用程序启动时,它首先会配置并运行其宿主(Host),宿主主要用用来启动、初始化应用程序,并管理其生命周期。

Program类时ASP.NET Core应用程序的入口,它包括一个名为Main的静态方法,程序运行时,将从这个方法开始执行。这与控制台应用程序完全相同。因此ASP.NET Core应用程序本质上就是控制台应用程序。

public static IHostBuilder CreateHostBuilder(string[] args) =>
    Host.CreateDefaultBuilder(args)
        .ConfigureWebHostDefaults(webBuilder =>
        {
            webBuilder.UseStartup<Startup>();

public static void Main(string[] args)
{
    CreateHostBuilder(args).Build().Run();
}

在Main方法中可以看到,整个程序首先是由CreateHostBuilder方法创建一个IHostBuilder对象,并调用它的

CreateDefaultBuilder方法得到IHost对象,然后调用该对象的Run方法运行起来的。在CreateHostBuilder方法内部,调用IHost类的静态方法CreateDefaultBuilder,会返回IHostBuilder类型的对象,该对象具有一些默认设定的值,之后又调用了UseStarup方法进一步来配置应用程序的启动。

由CreateHostBuilder方法创建的IHostBuilder对象时包含的主要默认选项如下。

  • 配置Kestrel服务器为默认的Web服务器来负责Web请求与响应。
  • 使用当前目录作为应用程序的内容目录(ContentRoot),该目录决定了ASP.NET Core查找内容文(如MVC视图等)的位置。
  • 从以ASPNETCORE_开头的环境变量(如ASPNETCORE_ENVIRONMENT)中以及命令行参数中加载配置项
  • 从appsettings.json、appsettings.{Environment}.json、用户机密(仅开发环境)、环境变量和命令行参数中加载配置项
  • 配置日志功能,默认添加控制台输出与调试输出
  • 如果应用程序被托管在IIS中,启动IIS继承,它会配置应用程序的主机地址和端口,并允许捕获启动错误等。

Kestrel

ASP.NET Core依靠Kestrel与IIS完全解耦,彻底跨平台。

除了让Kestrel服务器直接处理HTTP请求与响应外,还可以使用主流的Web服务器(IIS和Apache等)放在Kestrel之前作为反向代理服务器,从而使传来的HTTP请求经过并由反向代理服务器再传给Kestrel服务器。

在实际生产环境部署应用程序时,这是常见且推荐的方式。因为借助于反向代理服务器增加了应用程序的安全性,也提供了负载均衡、过滤请求和URL重定向等功能

Startup类

IWebHostBuilder接口有多个扩展方法,其中有一个很重要的就是UseStartup方法,它主要想应用程序提供用于配置启动的类,而制定的这个类应具有以下两个方法:

  • ConfigureServices:用于向ASP.NET Core的依赖注入容器添加服务
  • Configure:用于添加中间件,配置请求管道

这两个方法都会在运行时被调用,且在应用程序的整个生命周期内,只执行一次。其中ConfigureServices方法时可选的,而Configure方法则是必选的。在程序启动时,它会执行ConfigureServices方法(如果有),将指定的服务放入应用程序的依赖注入容器中,然后再执行Configure方法,向请求管道中添加中间件

ConfigureServices方法有一个IServiceCollection类型的参数,使用它能够将应用程序级别的服务注册到ASP.NET Core默认的依赖注入容器中。

Configure方法默认包含一个IApplicationBuilder类型的参数,通过它可以添加一个或多个中间件,所有添加的中间件将会对传入的HTTP请求进行处理,并将处理后的结果返回为发起请求的客户端。

// 该方法通过运行时调用。使用此方法配置HTTP请求管道。
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
                app.UseSwagger();
                app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "WebApplication1 v1"));
            }

            app.UseHttpsRedirection();

            app.UseRouting();

            app.UseAuthorization();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
            });
        }

// 该方法通过运行时调用。使用此方法将服务添加到容器中。
public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllers();
            services.AddSwaggerGen(c =>
            {
                c.SwaggerDoc("v1", new OpenApiInfo { Title = "WebApplication1", Version = "v1" });
            });
        }

中间件介绍

ASP.NET Core引入了中间件(Middleware)的概念。所谓中间件,就是处理HTTP亲求和响应的组件,它本质是一段用来处理请求于响应的代码。多个中间件之间的链式关系使之形成了管道(Pipeline)或请求管道。管道意味着请求将从一段进入,并按照顺序由每一个中间件处理,最后从另一端来。每一个传入的HTTP亲求,都会进入管道,其中每一个中间件可以对传入的请求进行一些操作并传入下一个中间件或直接返回;而对于响应也会遍历进来时所经过的中间件,顺序与进来时的正好相反。

请求处理模式显示请求到达、通过三个中间件进行处理以及响应离开应用。 每个中间件运行其逻辑,并在 next() 语句处将请求传递到下一个中间件。 在第三个中间件处理请求之后,请求按相反顺序返回通过前两个中间件,以进行离开应用前并在其 next() 语句后的其他处理,作为对客户端的响应。

ASP.NET Core中内置了多个中间件,它们主要包含MVC、认证、错误、静态文件、HTTPS重定向和跨域资源共享等,ASP.NET Core也允许向管道添加自定义中间件。

添加中间件

Startup类的Configure方法,该方法就是添加中间件的地方。在Configure方法中,通过调用IApplicationBuilder接口中以Use开头的扩展方法,即可添加系统内置的中间件:

public void Configure(IApplicationBuilder app)
{
    app.UseExceptionHandler("/Home/Error");
    app.UseStaticFiles();
    app.UseAuthentication();
    app.UseMvc();
}

上述代码中每一个Use开头的方法都会逐一并顺序地向管道添加响应的中间件。这里需要特别注意,中间件的添加顺序将决定HTTP请求以及HTTP响应遍历它们的顺序。因此对于上面的代码,传入的请求首先会经过异常处理中间件,再到静态文件中间件,接着是认证中间件,最后是MVC中间件。每一个中间件都可以终止请求管道,例如,如果认证失败,则不再继续向后执行。

这些以Use开头的方法都是扩展方法,它们封装了一些细节。而在每一个扩展方法的内部实现中,每个中间件都是通过调用IApplicationBuilder接口的Use和Run方法添加到请求管道中的。

除了Run和Use方法外,IApplicationBuilder接口还提供了Map、MapWhen及UseWhen方法,它们都可以指定条件,并在条件满足时创建新的分支管道,同时在新的分支上添加并运行中间件。

方法名 作用
Run 添加一个中间件接受一个RequestDelegate类型参数,处理传入的HTTP请求
Use 处理完请求后还会将请求传入下一个中间件
Map 根据是否匹配指定的请求路径来决定是否在一个新的分支上继续执行后续中间件,并在新分支上执行完后,不再回到原来的管道上
MapWhen 则可以满足更复杂的条件,它接收Fun<HttpContext,bool>类型的参数,并以该参数作为判断条件,因此它会对HttpContext对象进行更细致的判断(如是否包含指定的请求消息头等),然后决定是否进入新的分支继续执行指定的中间件。
UseWhen和MapWhen 尽管接受的参数完全一致,但它不想Map和MapWhen一样,由它创建的分支在执行完后会继续回到原来的管道上。

自定义中间件

创建自定义中间件非常简单,需要至少一个特定的构造函数和一个名为Invoke的方法。

  • 对于构造函数,应包括一个RequestDelegate类型的参数,该半数表示在管道中的下一个中间件;
  • 对于Invoke方法,应包括一个HttpContext类型的参数,并返回Task类型。

创建自定义中间件可以很灵活地控制HTTP请求的处理流程,比如要让应用程序仅接受GET和HEAD方法,就可以创建如下的中间件:

public class HttpMethodChexkMiddleware
{
    private readonly RequestDelegate _next;

    public HttpMethodChexkMiddleware(RequestDelegate requestDelegate, IHostEnvironment environment)
    {
        this._next = requestDelegate;
    }

    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-AllowHttpVerb", new[] { "GET,HEAD" });
            Context.Response.WriteAsync("只支持GET、HEAD方法");
            return Task.CompletedTask;
        }
    }
}

在中间件的构造函数中,可以得到下一个中间件,并且还可以注入需要的服务,如上例中的IhostingEnvironment参数。在Invoke方法中,对HTTP请求方法进行判断,如果复合条件,则继续执行下一个中间件;否则返回400错误,并在响应中添加了自定义消息头用于说明错误原因。

接下来,在Configure方法中添加中间件:

app.UseMiddleware();

添加时应注意其顺序,比如,上面中间件应位于MVC中间件之前。为了更方便地使用自定义中间件,还可以为它创建一个扩展方法。

public static class CustomMiddlewareExtensions
{
    public static IApplicationBuilder UseHttpMethodChexkMiddleware(this IApplicationBuilder builder)
    {
        return builder.UseMiddleware<HttpMethodChexkMiddleware>();
    }
}

使用时,只要调用该扩展方法即可:

app.UseHttpMethodChexkMiddleware();

依赖注入

在ASP.NET Core中,所有被放入依赖注入容器类型或组件成为服务。容器中的服务有两种类型:

  • 第一种是框架服务,它们是ASP.NET Core框架的组成部分,如IApplicationBuilder、IHostingEnvironment和ILoggerFactory等;
  • 另一种是应用服务,所有由用户放到容器中的服务都属于这一类。

为了能够在程序中使用服务,首先需要向容器添加服务,然后通过构造函数以注入的方式注入所需要的类中,若要添加服务,则需要使用Startup类的ConfigureServices方法,该方法由一个IServiceCollection类型的参数。

public void ConfigureServices(IServiceCollection services)
{
        services.Add(new  			ServiceDescriptor(typeof(IBook),typeof(Book),ServiceLifetime.Scoped));
}

在上例中,使用了IServiceCollection的Add方法添加了一个ServiceDescriptor对象,事实上,IServiceCollection就是一个ServiceDescriptor集合,它继承自Icollection类。ServiceDescriptor类描述一个服务和它的实现,以及其生命周期,正如上例中的构造函数所表明的,前两个参数分别是接口及其实现的类型,而第三个参数则是指明它的生命周期。

在ASP.NET Core内置的依赖注入容器中,服务的生命周期有如下3种类型:

  • Singleton:容器或创建并共享服务的单例,且一直会存在于应用程序的整个生命周期内。
  • Transient:每次服务被请求时,总会创建新实例。
  • Scoped:在每一次请求时会创建服务的新实例,并在这个请求内一直共享这个实例。当每次在容器中添加服务时,都需要指明其生命周期类型。当服务的生命周期结束时,它就会被销毁。
  • ServiceDescriptor除了上面的构造函数以外,还有以下两种形式:

public ServiceDescriptor(Type serviceType, object instance)

public ServiceDescriptor(
Type serviceType,
Func<IServiceProvider, object> factory,
ServiceLifetime lifetime)

  • 第一种形式可以直接指定一个实例化的对象,使用这种方式,服务的生命周期将是Singleton
  • 第二种形式则是以工厂的方式来创建实例,以满足更复杂的创建要求。

除了直接调用Add方法外,IServiceCollection还提供了分别对应以上3中类型生命周期的扩展方法:AddSingleton()、AddTransient()、AddScoped()

对于一些常用的服务,如MVC、EF Core的DbContext等,都提供了对应的扩展方法:AddMvc、AddDbContext和AddOptions等。

MVC

MVC 模式

在MVC的3部分中,Controller的作用非常重要,它介于Model与View之间,起到了入口点的作用。当应用程序受到HTTP请求时,ASP.NET Core MVC会将请求路由到响应的Controller,Controller将操作Model并完成对数据的修改。

不仅如此,Controller还会将获取到的数据传给对应的View,并最终展示给用户。对于ASP.NET Core MVC视图应用,View会使用Razor和Taghelper等组件向用户据最终呈现一个HTML页面,

而对于Web API应用程序,则会返回一个资源,通常时JSON格式。

ASP.NET Core MVC是构建在ASP.NET Core 之上的MVC框架。若要在应用程序中使用MVC,则需要添加MVC中间件:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    app.UseMvc();
}

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

在ASP.NET Core MVC框架中,除了Controller、Model和Action外,它还包括路由,模型绑定、模型验证和过滤器等功能。

路由

基本约定的路由

对于Web应用程序,路由是一个非常重要且基础的功能,它的主要功能是根据预先配置的路由信息对客户端传来的请求进行路由映射,映射完成后,再将请求传给对应的路由处理器处理。具体说,在ASP.NET Core MVC中,路由负责从请求的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指定其路由信息。

要使用基本约定的路由,首先定义一个或若干个路由约定,同时,只要保证所有定义的路由约定能够尽可能满足不同形式的映射即可。

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    app.UseMvc(routes =>
    {
        routes.MapRoute(
            template:$"{{controller}}/{action}");
    });
}

在上述代码中,使用字符串设置了一个路由约定。所谓的路由约定,本质上是一个URL模板信息其中,在大括号{}中的部分是路由参数,每一个参数都有一个名称,它们充当了占位符的作用,参数与参数之间以“/”分隔。对于路由参数名,controller与action是ASP.NET Core MVC特定的,它们分别用于匹配到Controller与Action。注意任何一个MVC应用程序应该至少定义一个路由。

特性路由

[Route("")]
[Route("Home/Index")]
[Route("AnotherOne")]
public IActionResult Index()
{
    return View();
}

上例中,使用[Route]特性为HomeController的Index方法添加了3个路由,因此能使如下URL都能映射到这个Action上。

除了在Action上使用[Route]特性,也可以在Controller 上添加,当以个Controller中包括多个Action时,这会非常方便,因为在为Action配置路由特性时,就不需再指明其Controller了:

为Action设置路由时除了使用[Route]特性外,更常见的是使用HTTP特性。

最后需要说明的是,基本约定的路由和特性路由方式可以同时存在,但是如果已经为一个Action指定了特性路由,那么基本约定的路由在该Action上就不会起作用了。

Controller与Action

在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{}
  • 如果一个类并不满足上述约定,那么只要为它添加[Controller]特性,即可作为Controller处理。
  • 反之,加上[NotController]特性,就会忽略该Controller

返回结果

每个Action都应返回IActionResult类型或ActionResult类型的值作为HTTP请求的结果。在ASP.NET Core MVC中,Action的返回结果有几种比较常见的类型,包括状态码、包含对象的状态码、重定向和内容。

状态码结果

状态码结果是最简单的一种,它们仅返回一个HTTP状态码给客户端。

状态码结果对象 对应的状态码 描述 ControllerBase中的方法
OkResult 200 操作成功 Ok()
BadRequestResult 400 错误的操作 BadRequest()
NoContentResult 204 操作成功但未返回任何信息 NoContent()
NotFoundResult 404 请求的资源找不到 NotFound()
UnauthorizedResult 401 未授权 Unauthorized()
UnsupportedMediaTypenResult 415 无法处理请求附带的媒体格式

返回具体的状态码: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对象,也可以表示一个具体类型(由参数T指定)。

ActionResult的优点在于更为灵活地为Action设置返回值,同时,当使用OpenAPI(即Swagger)为API生成文档时,Action不需要使用[Produces]特性显示地指明其返回类型,因为其中的泛型参数T已经为OpenAPI指明了要返回的数据类型。

[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;
}

模型绑定

在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的参数提供数据,并且按照顺序来从一下每一种方式中获取:

  • Form值:HTTP POST 请求时表单中的数据
  • 路由值:通过路由系统解析得到
  • 查询字符串:从URL中的查询字符串中获取

像特性路由一样,ASP.NET Core MVC也提供了用于模型绑定的特性,使用如下特性能够为Action的参数显示指定不同的绑定源。

  • [FromHeader]特性:从HTTP请求的Header中获取参数的值
  • [FromQuery]特性:从查询字符串中获取参数的值
  • [FromServices]特性:从依赖注入容器中获取参数的值
  • [FromRoute]特性:从路由中获取参数的值
  • [FromForm]特性:从表单中获取该参数的值
  • [FromBody]特性:从HTTP请求的消息正文获取参数的值。

另外还有两个特性用于指明参数是否必须使用绑定

  • BinRequiredAttribute:如果没有值绑定到此参数,或绑定不成功,这个特性将添加一个ModelState错误。
  • BinNeverAttribute:在进行模型绑定时,忽略此参数。
[HttpGet("[action]/{keyword}")]
public IActionResult Search([FromBody] string keyword, [FromHeader]int top)
{
    return NotFound();
}

模型验证

模型验证是指数据被使用之前的验证过程,它发生在模型绑定之后。在ASP.NET Core MVC中,要实现对数据的验证,最方便的方式时使用数据注解(Data annotation),它使用特性为数据添加额外的信息。数据注解通常用于验证,只要为类的属性添加需要的数据注解即可。

基本验证

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

在Controller内的Action中,要检查某一个对象是否满足指定的条件,只要调用ModelState.IsValid属性,其中ModelState是ControllerBase类的属性

[HttpGet("[action]/{keyword}")]
public IActionResult Search([FromBody] string keyword, [FromHeader] int top)
{
    if (ModelState.IsValid)
    {
        return Ok();
    }
    else
    {
        return BadRequest(ModelState);
    }
}

过滤器

过滤器和中间件很相似,在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;
    }
}

Startup添加过滤器

为了能够使用这个过滤器,首先应在ASP.NET Core MVC 的Filter集合中添加它,在Startup中注册过滤器会影响到所有Action,这种做法是全局的

public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc(options=>
        options.Filters.Add<ActionParameterValidationFilter>());
}

特性添加过滤器

[ActionParameterValidationFilter]
[HttpGet]
public IEnumerable<WeatherForecast> Get()
{
	···
}

解决过滤器特性中引用其他依赖项的问题

如果过滤器以全局有效的方式添加(即在Startup类的ConfigureServices方法中,通过调用AddMvc方法添加),则它会在ConfigureServices方法中被添加到容器中。而如果以特性的方式使用包含依赖项的过滤器时,则会出错,这是因为在自定义特性的构造函数中所定义的接口类型的参数并不是有效的特性参数。此时就需要使用[ServiceFilter]特性或[TypeFilter]特性,这两个特性都能够解决过滤器特性中引用其他依赖项的问题。在使用时,应设置他们的Type属性为自定义过滤器的类型。

[ServiceFilter(typeof(ActionParameterValidationFilterAttribute))]
[HttpGet]
public IEnumerable<WeatherForecast> Get()
{
}

[ServiceFilter]特性与[TypeFilter]特性的区别是前者会从容器中获取过滤器实例,而后者不从容器中获取过滤器实例。因此如果使用[SerivceFilter]特性,还应在Startup类的ConfigureServices方法中将该过滤器添加到容器中。

public void ConfigureServices(IServiceCollection services)
{
    services.AddScoped<IDataSerializer,DataService>();
    services.AddScoped<ActionParameterValidationFilterAttribute>();
}

配置

在应用程序中访问配置是很普遍的,ASP.NET Core提供了相当强大且灵活的配置机制,它采用常见的键值对来表示配置项,并且支持多种形式的配置源,包括文件(JSON、XML和INI格式)、命令行参数、环境变量、.NET内存对象和Azure Key Vault等。其中JSON与XML数据在通常情况下是层级结构形式,ASP.NET Core配置系统也完全支持这种层级结构的数据,也支持创建自定义配置源。

要访问配置,需要使用ConfigurationBuilder类,它实现了IConfigurationBuilder接口,该接口包括两个重要的方法。

public interface IConfigurationBuilder
{
    IConfigurationBuilder Add(IConfigurationSource source);
    IConfigurationRoot Build();
}
  • Add方法能够添加不同形式的配置源
  • Build方法会把所有添加的配置源中的配置信息构建(或生成)为程序可访问的配置项

自定义配置源

使用自定义配置源可以读取特定格式配置文件中的内容,也可以灵活地从配置文件中读取所需要的内容,最终向配置系统提供配置项。要创建自定义配置源,需要用到两个接口,即

public interface IConfigurationSource
{
    IConfigurationProvider Build(IConfigurationBuilder builder);
}

public interface IConfigurationBuilder
{
    IDictionary<string, object> Properties { get; }
    IList<IConfigurationSource> Sources { get; }
    IConfigurationBuilder Add(IConfigurationSource source);
    IConfigurationRoot Build();
}
  • IConfigurationSource方法仅包含一个成员-Build方法,它返回IConfigurationProvider类型的对象
  • IConfigurationBuilder接口包括多个成员。其中Load方法负责从IConfigurationSource中加载所有的配置信息,并最终以键值对的形式添加到配置系统中,从而使应用程序可以访问。

ASP.NET Core的配置(1):读取配置信息
ASP.NET Core的配置(2):配置模型详解

日志

日志包括两种类型,即系统日志和用户记录日志。系统日志是系统在运行时向外输出的记录信息;而用户记录日志是由开发人员在程序中适当的位置调用与日志功能相关的API输出的日志。一般情况下,记录日志时也可以指定其重要级别,如调试、信息、警告和错误等。

ASP.NET Core框架内部集成了日志功能,它主要由以下一些重要的接口组成。

  • Ilogger:包括实际执行记录日志操作的方法。
  • IloggerProvider:用于创建Ilogger对象。
  • IloggerFactory:通过ILoggerProvider对象创建ILogger对象。
public interface ILogger
{
  void Log<TState>(
    LogLevel logLevel,
    EventId eventId,
    TState state,
    Exception exception,
    Func<TState, Exception, string> formatter);

  bool IsEnabled(LogLevel logLevel);

  IDisposable BeginScope<TState>(TState state);
}

Log方法的第一个参数指明了这条信息的级别,日志级别及其重要程度。ASP.NET Core日志系统定义了6个级别,具体如下:

  • Trace:级别最低,通常仅用于开发阶段调试问题。这些信息可能包含敏感的应用程序数据,因此不应该用于生产环境,默认情况下应金庸,即不输出。
  • Debug:用于记录调试信息,这种类型的日志有助于开发人员调试应用程序。
  • Informmation:用于记录应用程序的执行流程信息,这些信息具有一定的意义,比较常用。
  • Warning:用于记录应用程序出现的轻微错误或其他不会导致程序停止的警告信息。
  • Error:用于记录错误信息,这一类错误将影响程序正常执行。
  • Critical:严重级别最高,用于记录引起应用程序崩溃、灾难性故障等信息,如数据丢失、磁盘空间不够等。

除了指定日志级别以外,还需要指定EventId、一个返回值类型为字符串的委托,委托的意义在于根据指定的状态以及异常返回要输出的日志信息。

错误处理

ASP.NET Core提供了完善的错误处理机制,它使用一些中间件在响应请求时处理遇到的错误。

异常处理

在ASP.NET Core中,有以下两个用来处理异常的中间件

  • DeveloperExceptionPageMiddleware:仅用于开发环境的异常处理中间件。
  • ExceptionHandlerMiddeware:适用于非开发环境的异常处理中间件。
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
    else
    {
        app.UseHsts();
    }

    app.Run(content => throw new Exception("这里发生异常了"));

在伤处代码中,首先对当前环境进行了判断,如果是开发环境则通过IApplicationBuilder提供的UseDeveloperExceptionPage方法添加这个中间件。

ExceptionHandlerMiddeware中间件同样用来处理异常,不过它可以在任何环境中。它和DeveloperExceptionPageMiddleware一样,用来捕获应用程序中所有未处理的异常,因此可以视其为应用程序全局级别的try-catch。

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    app.UseExceptionHandler(errorApp =>
    {
        errorApp.Run(async context =>
        {
            context.Response.ContentType = "text/plain;charset = utf-8";
            await context.Response.WriteAsync("对不起,请求遇到错误");
        });
    });

    app.Run(context=>throw  new  Exception("这里发生异常了"));
}

错误码处理

默认情况下,ASP.NET Core对于这些状态码没有提供具体细节,使用StatusCodePagesMiddleware则能够自定义关于这些错误状态码的细节。

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    app.UseStatusCodePages();
}

如果要自定义显示结果,则可以调用UseStatusCodePages的另一个重载形式。

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    app.UseStatusCodePages(async context =>
    {
        var statusCode = context.HttpContext.Response.StatusCode;
        context.HttpContext.Response.ContentType = "text/plain;charset=utf-8";

        var errorMessage = $"对不起,请求遇到错误,状态码{statusCode}";
        await context.HttpContext.Response.WriteAsync(errorMessage);
    });
}
登峰造极的成就源于自律
原文地址:https://www.cnblogs.com/fishpond816/p/15086649.html