在 .NET Web Api 的控制器中捕获依赖注入错误

Posted

技术标签:

【中文标题】在 .NET Web Api 的控制器中捕获依赖注入错误【英文标题】:Catching dependency injection errors in controllers in .NET Web Api 【发布时间】:2020-03-02 04:49:36 【问题描述】:

我有一个简单的 C# Web Api 项目,它提供了一些安静的端点。

控制器致命的服务器错误处理/记录通常可以通过以下方式很好地描述:

    在 Global.asax.cs 中实现/覆盖 Application_Error 方法
protected override void Application_Error(object sender, EventArgs e)
   
       var ex = Server.GetLastError();
       _logger.Error("Unexpected error while initializing application", ex);
   
    或者通过添加异常处理过滤器:

config.Filters.Add(new ExceptionHandlingAttribute());

GlobalConfiguration.Configuration.Filters.Add(new ExceptionHandlingAttribute());

public class ExceptionHandlingAttribute : ExceptionFilterAttribute
    
        private static readonly ILog _logger = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);

        public override void OnException(HttpActionExecutedContext actionExecutedContext)
        
            _logger.Error("Unexpected error in API.", actionExecutedContext.Exception);

            throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.InternalServerError)
            
                Content = new StringContent("An error occurred, please try again or contact the administrator."),
                ReasonPhrase = "Critical Exception"
            );
        
    

但是,当控制器的实例化过程中由于此代码的构造函数中的依赖注入失败而发生错误时:

    public class DataController : ApiController
    
        private readonly IDataService _dataService;

        public DataController(IDataService dataService)
        
            _dataService = dataService;
        

        [AllowAnonymous]
        [HttpGet]
        public IHttpActionResult GetSomeStuff()
        
            return Ok(new AjaxResponse("somestuff"));
        

以上方法都没有捕捉到错误。我怎样才能发现这些错误?

【问题讨论】:

【参考方案1】:

这在this blog post 中有很好的描述。摘录以下问题:

创建一个类:

public class GlobalExceptionHandler : ExceptionHandler

    public async override Task HandleAsync(ExceptionHandlerContext context, CancellationToken cancellationToken)
    
        // Access Exception
        // var exception = context.Exception;

        const string genericErrorMessage = "An unexpected error occured";
        var response = context.Request.CreateResponse(HttpStatusCode.InternalServerError, 
            new
             
                Message = genericErrorMessage
            );

        response.Headers.Add("X-Error", genericErrorMessage);
        context.Result = new ResponseMessageResult(response);
    

然后从您的应用程序启动或 owin 设置如下注册您的异常处理程序:

public static class SetupFiltersExtensions

    public static IAppBuilder SetupFilters(this IAppBuilder builder, HttpConfiguration config)
    
        config.Services.Replace(typeof (IExceptionHandler), new GlobalExceptionHandler());

        return builder;
    

正如他的帖子中所述,他没有使用上述方法登录,而是更喜欢通过GlobalErrorLogger 登录:

public class GlobalErrorLogger : ExceptionLogger

    public override void Log(ExceptionLoggerContext context)
    
        var exception = context.Exception;
        // Write your custom logging code here
    

这样注册:

public static class SetupFiltersExtensions

    public static IAppBuilder SetupFilters(this IAppBuilder builder, HttpConfiguration config)
    
        config.Services.Replace(typeof (IExceptionHandler), new GlobalExceptionHandler());
        config.Services.Add(typeof(IExceptionLogger), new GlobalErrorLogger());

        return builder;
    

【讨论】:

这实际上并不能解决每个控制器实例化期间发生的错误。它只处理控制器方法中发生的错误。 GlobalExceptionFilter 不会像您的问题一样在实例化期间捕获。 GlobalExceptionHandler 应该如帖子中所述。我已经在one 这样的项目中成功使用了这种技术【参考方案2】:

我自己终于找到了答案,如下。

必须在 GlobalConfiguration.Configuration.Services 级别实现和覆盖 IHttpControllerActivator 接口(这很重要,因为 config.Services 只处理已经实例化的控制器)。

这里有一些sn-ps:

Startup.cs

    // the following will make sure that any errors that happen within the constructor
    // of any controller due to dependency injection error will also get logged
    GlobalConfiguration.Configuration.Services.Replace(typeof(IHttpControllerActivator),
              new ExceptionHandlingControllerActivator(                        
                 GlobalConfiguration.Configuration.Services.GetHttpControllerActivator())
                    );


ExceptionHandlingControllerActivator.cs

    /// <summary>
    /// This class handles instantiation of every api controller. It handles and logs 
    /// any exception that occurs during instatiation of each controller, e.g. errors
    /// that can happen due to dependency injection.
    /// </summary>
    public class ExceptionHandlingControllerActivator : IHttpControllerActivator
    
        private IHttpControllerActivator _concreteActivator;
        private static readonly ILog _logger = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);

        public ExceptionHandlingControllerActivator(IHttpControllerActivator concreteActivator)
        
            _concreteActivator = concreteActivator;
        

        public IHttpController Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType)
        
            try
            
                return _concreteActivator.Create(request, controllerDescriptor, controllerType);
            
            catch (Exception ex)
            
                _logger.Error("Internal server error occured while creating API controller " + controllerDescriptor.ControllerName, ex);

                throw new HttpResponseException(request.CreateErrorResponse(HttpStatusCode.InternalServerError, "Unexpected error while creating controller " + controllerDescriptor.ControllerName));
            
        
    

【讨论】:

以上是关于在 .NET Web Api 的控制器中捕获依赖注入错误的主要内容,如果未能解决你的问题,请参考以下文章

在 Asp.net Web API 中捕获 404 错误

在 ASP.NET Web Api 中捕获所有未处理的异常

.NET Web API - 添加日志记录

Asp.Net Web API 2第十一课——在Web API中使用Dependency Resolver

来自静态类的 ASP.NET Core Web API 日志记录

用于依赖注入的 MVC Web API 和 Unity