In addition to adding to the own middleware application ASP.NET MVC Core pipe outside, you can also use custom attributes to control MVC filter response, and selectively apply them to the entire operation of the controller or controllers.
One commonly used MVC ASP.NET Core filter is ExceptionFilterAttribute, for processing error response Wep API application. It is very easy to implement, developers, and I use the MVC problems faced by the filter properties in ASP.NET Core is injected access Startup.cs class components. These are usually configured, the environment or logging.
Dependency injection is usually a very useful object usage, such as the above-mentioned IEnvironment , IConfiguration and ILogger <T>, is different behavior MVC filter properties you are achievable. Based on these values, the behavior of your property may be different. For example, you do not want to stack trace and error details to the public Production Web API service error response, especially in the service endpoint is a common situation. You want to do this only for the isolation of development and staging environment.
Filter properties exemplary MVC #
In a custom ExceptionFilterAttrubute simple example of a class, I'll show you how to use dependency injection objects in a custom attribute. Let us begin from the code.
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; } } }
As this document is not an error message structure, but in Microsoft REST API guidelines Github repository provides some guidelines in the error message, which may give you help.
Now your error response is a JSON Ideally news, but let's serialized left to the application of the pipeline and returns a ObjectResponse derived instance. For this reason I created 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; } } }
In addition to obtaining the status code constructor (the default is 500 Internal Server Error), and ObjectResponse than the object infrastructure function, there is nothing special in this class. The core component of our focus in this article is our custom ExceptionFilterAttribute derived class.
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); } }
Let us use this property to decorate our controller to handle the error it may occur.
// GET api/values [HttpGet] [ApiExceptionFilter] public IEnumerable<string> Get() { return new string[] { "value1", "value2" }; }
Set dependency injection #
We first need to visit us in the MVC filter properties Startup class in the three interface settings dependency injection.
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(); } }
You can see that we will add to our DI ApiExceptionFilter scope of services. This is because we will refer to it in different ways on the controller, so as to initialize it by the constructor dependency injection with the new parameters.
We have in the Startup type of ConfigureServices method of injecting our IEnvironment and ILogger , but we can not access it in the properties. If we use IEnvironment ILogger parameters and add constructor property, we will get a compilation error, because you can not use [ApiExceptionFilter] decorative Controller / Action, because it now needs to interface through IEnvironment and ILogger. To do this, we use with attributes ServiceFilter controller to decorate it, we will ApiExceptionFilter class type as a constructor argument.
[HttpGet] [ServiceFilter(typeof(ApiExceptionFilter))] public IEnumerable<string> Get() { return new string[] { "value1", "value2" }; }
Finally, we must update the constructor MVC filter attributes to accept IEnvironment, IConfiguration and ILogger parameters.
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); } }
We injected IEnvironment and ILogger <T> in the custom attribute class in the filter. From Microsoft.AspNetCore.Mvc.Filters namespace any operation of the filter properties may be any class derived using the same method.