1. 程式人生 > 其它 >APS.NET Core與RESTful API 開發實戰(二)

APS.NET Core與RESTful API 開發實戰(二)

目錄

APS.NET Core與RESTful API 開發實戰(二)

簡介

這是一篇《APS.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親求,都會進入管道,其中每一箇中間件可以對傳入的請求進行一些操作並傳入下一個中介軟體或直接返回;而對於響應也會遍歷進來時所經過的中介軟體,順序與進來時的正好相反。

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的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);
    });
}
登峰造極的成就源於自律