除了将自己的中间件添加到ASP.NET MVC Core应用程序管道之外,您还可以使用自定义MVC过滤器属性来控制响应,并有选择地将它们应用于整个控制器或控制器操作。
ASP.NET Core中常用的MVC过滤器之一是 ExceptionFilterAttribute,用于处理Wep API应用程序中的错误响应。它很容易实现,开发人员和我在ASP.NET Core中使用MVC过滤器属性所面临的问题是访问Startup.cs类中注入的组件。这些通常是配置,环境或日志记录。
通常依赖注入对象的一个非常有用的用法,例如上面提到的IEnvironment,IConfiguration和ILogger
示例MVC过滤器属性
在一个自定义ExceptionFilterAttrubute类的简单示例中,我将向您演示如何在自定义属性中使用依赖注入对象。让我们从代码开始吧。
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。
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派生类。
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); } }
让我们用这个属性来装饰我们的控制器来处理它可能发生的错误。
// 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(new ConfigurationBuilder() .SetBasePath(Directory.GetCurrentDirectory()) .AddJsonFile($"appsettings.{this.HostingEnvironment.EnvironmentName.ToLower()}.json") .Build()); services.AddLogging(); services.AddMvc(); services.AddScoped (); } public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) { loggerFactory.AddConsole(); app.UseMvc(); } }
您可以看到我们将ApiExceptionFilter添加为我们的DI的作用域服务。这是因为我们将在控制器上以不同的方式引用它,以便通过带有参数的新构造函数的依赖注入来初始化它。
我们已经在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 ILoggerlogger; private IHostingEnvironment environment; private IConfiguration configuration; public ApiExceptionFilter(IHostingEnvironment environment, IConfiguration configuration, ILogger 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