除了將本身的中間件添加到ASP.NET MVC Core應用程序管道以外,您還可使用自定義MVC過濾器屬性來控制響應,並有選擇地將它們應用於整個控制器或控制器操做。git
ASP.NET Core中經常使用的MVC過濾器之一是 ExceptionFilterAttribute,用於處理Wep API應用程序中的錯誤響應。它很容易實現,開發人員和我在ASP.NET Core中使用MVC過濾器屬性所面臨的問題是訪問Startup.cs類中注入的組件。這些一般是配置,環境或日誌記錄。github
一般依賴注入對象的一個很是有用的用法,例如上面提到的IEnvironment,IConfiguration和ILogger <T>,是您正在實現的MVC過濾器屬性的不一樣行爲。根據這些值,您的屬性行爲可能會有所不一樣。例如,您不但願將錯誤詳細信息和堆棧跟蹤公開給Production Web API服務錯誤響應,尤爲是在該服務端點是公共的狀況下。您但願僅針對隔離的開發和登臺環境執行此操做。json
在一個自定義ExceptionFilterAttrubute類的簡單示例中,我將向您演示如何在自定義屬性中使用依賴注入對象。讓咱們從代碼開始吧。api
public class ExceptionMessage { private object errorMessage; public string Message { get; private set; } public string Description { get; private set; } public IDictionary<string,string> ValidationErrors { get; private set;} public ExceptionMessage(ExceptionContext context) { if (context.ModelState != null && context.ModelState.Any(m => m.Value.Errors.Any())) { this.Message = "Model validation failed."; this.ValidationErrors = context.ModelState.Keys .SelectMany(key => context.ModelState[key].Errors.ToDictionary(k => key, v => v.ErrorMessage)) .ToDictionary(k => k.Key, v => v.Value); } else { this.Message = context.Exception.Message; this.Description = context.Exception.StackTrace; } } }
因爲本文重點不是錯誤消息結構,不過在Microsoft REST API準則 Github存儲庫中提供了一些錯誤消息準則,這些可能會給你帶來幫助。服務器
如今您的錯誤響應理想狀況下是一條JSON消息,可是讓咱們將序列化留給應用程序的管道並返回一個ObjectResponse派生實例。爲此我建立了ErrorObjectResult。mvc
using Microsoft.AspNetCore.Mvc; using System.Net; namespace CzarCms.Models { public class ErrorObjectResult : ObjectResult { public ErrorObjectResult(object value, HttpStatusCode statusCode = HttpStatusCode.InternalServerError) : base(value) { StatusCode = (int)statusCode; } } }
除了獲取狀態代碼的構造函數(默認爲500內部服務器錯誤)和ObjectResponse基礎構造函數的對象以外,此類中沒有什麼特別之處。咱們在本文中關注的核心組件是咱們的自定義ExceptionFilterAttribute派生類。app
public class ApiExceptionFilter : ExceptionFilterAttribute { public override void OnException(ExceptionContext context) { var errorMessage = new ExceptionMessage(context); if (context.ModelState.ErrorCount==0) context.Result = new ErrorObjectResult(errorMessage); else context.Result = new ErrorObjectResult(errorMessage,HttpStatusCode.BadRequest); base.OnException(context); } }
讓咱們用這個屬性來裝飾咱們的控制器來處理它可能發生的錯誤。ide
// GET api/values [HttpGet] [ApiExceptionFilter] public IEnumerable<string> Get() { return new string[] { "value1", "value2" }; }
咱們首先須要爲咱們要在Startup類的MVC過濾器屬性中訪問的這三個接口設置依賴注入。函數
public class Startup { public IConfiguration Configuration { get; private set; } public IHostingEnvironment HostingEnvironment { get; private set; } public Startup(IConfiguration configuration, IHostingEnvironment env) { Configuration = configuration; HostingEnvironment = env; ILogger Logger = new LoggerFactory() .AddConsole() .AddDebug() .CreateLogger(typeof(Program)); } public void ConfigureServices(IServiceCollection services) { services.AddSingleton<IConfiguration>(new ConfigurationBuilder() .SetBasePath(Directory.GetCurrentDirectory()) .AddJsonFile($"appsettings.{this.HostingEnvironment.EnvironmentName.ToLower()}.json") .Build()); services.AddLogging(); services.AddMvc(); services.AddScoped<ApiExceptionFilter>(); } public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) { loggerFactory.AddConsole(); app.UseMvc(); } }
您能夠看到咱們將ApiExceptionFilter添加爲咱們的DI的做用域服務。這是由於咱們將在控制器上以不一樣的方式引用它,以便經過帶有參數的新構造函數的依賴注入來初始化它。ui
咱們已經在Startup類中的ConfigureServices方法中注入了咱們的IEnvironment和ILogger,但咱們沒法在屬性中訪問它。若是咱們使用IEnvironment和ILogger參數添加屬性的構造函數,咱們將獲得編譯錯誤,由於您沒法使用[ApiExceptionFilter]裝飾Controller / Action,由於它如今須要經過IEnvironment和ILogger接口實現。爲此,咱們使用帶有屬性ServiceFilter的控制器來裝飾 它,它將咱們的 ApiExceptionFilter類的類型做爲構造函數參數。
[HttpGet] [ServiceFilter(typeof(ApiExceptionFilter))] public IEnumerable<string> Get() { return new string[] { "value1", "value2" }; }
最後,咱們必須更新MVC過濾器屬性的構造函數以接受IEnvironment,IConfiguration和ILogger參數。
public class ApiExceptionFilter : ExceptionFilterAttribute { private ILogger<ApiExceptionFilter> logger; private IHostingEnvironment environment; private IConfiguration configuration; public ApiExceptionFilter(IHostingEnvironment environment, IConfiguration configuration, ILogger<ApiExceptionFilter> logger) { this.environment = environment; this.configuration = configuration; this.logger = logger; } public override void OnException(ExceptionContext context) { var errorMessage = new ExceptionMessage(context); if (context.ModelState.ErrorCount==0) context.Result = new ErrorObjectResult(errorMessage); else context.Result = new ErrorObjectResult(errorMessage,HttpStatusCode.BadRequest); base.OnException(context); } }
咱們在自定義過濾器屬性類中注入了IEnvironment和ILogger <T>。從Microsoft.AspNetCore.Mvc.Filters命名空間中的任何操做過濾器屬性派生的任何類均可以使用相同的方法。