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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM