ASP.NET Core中使用自定义MVC过滤器属性的依赖注入

C#

浏览数:300

2019-7-2

  除了将自己的中间件添加到ASP.NET MVC Core应用程序管道之外,您还可以使用自定义MVC过滤器属性来控制响应,并有选择地将它们应用于整个控制器或控制器操作。

  ASP.NET Core中常用的MVC过滤器之一是  ExceptionFilterAttribute,用于处理Wep API应用程序中的错误响应。它很容易实现,开发人员和我在ASP.NET Core中使用MVC过滤器属性所面临的问题是访问Startup.cs类中注入的组件。这些通常是配置,环境或日志记录。

  通常依赖注入对象的一个​​非常有用的用法,例如上面提到的IEnvironment,IConfiguration和ILogger <T>,是您正在实现的MVC过滤器属性的不同行为。根据这些值,您的属性行为可能会有所不同。例如,您不希望将错误详细信息和堆栈跟踪公开给Production Web API服务错误响应,尤其是在该服务端点是公共的情况下。您希望仅针对隔离的开发和登台环境执行此操作。

示例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<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的作用域服务。这是因为我们将在控制器上以不同的方式引用它,以便通过带有参数的新构造函数的依赖注入来初始化它。

我们已经在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命名空间中的任何操作过滤器属性派生的任何类都可以使用相同的方法。

作者:张子浩