Disable *all* exception handling in ASP.NET Web API 2 (to make room for my own)?

前端 未结 5 2014
隐瞒了意图╮
隐瞒了意图╮ 2020-12-02 12:21

I want to wire up exception handling in a middleware component, something like this:

public override async Task Invoke(IOwinContext context)
{
    try
    {
         


        
相关标签:
5条回答
  • 2020-12-02 13:00

    OWIN is not supposed to handle exceptions like that because web api has its own error handling built in. OWIN is designed to be separate from the application. If you set a breakpoint on the HandleAsync method of your exception handler, you should be able to inspect the context variable and see the details of the exception.

    If you are trying to do this just for debugging purposes, setting your breakpoint there should allow you to see the exception. If you need to log the exceptions, the exception handler is the best place to do that, in my opinion.

    Hope that helps.

    0 讨论(0)
  • 2020-12-02 13:02

    Here's something that might help:

    https://stackoverflow.com/a/21382651/1419853

    http://www.asp.net/web-api/overview/releases/whats-new-in-aspnet-web-api-21#global-error

    Essentially, there is some built in support to catch, handle, and change errors.

    It looks something like this:

    public class ExceptionLogger : System.Web.Http.ExceptionHandling.ExceptionLogger
    {
        Logger _logger;
    
        public ExceptionLogger(Logger logger)
        {
            _logger = logger;
        }
    
        public override void Log(ExceptionLoggerContext context)
        {
            _logger.Error(context.ExceptionContext.Exception.ToString());
        }
    }
    
    0 讨论(0)
  • 2020-12-02 13:04

    Update: I blogged about this. When researching the blog post, I found some potential for improvement; I've updated the relevant parts of this answer. For more detail on why I think this is better than all other suggestions here, or the default behavior, read the entire post :)


    I have now gone with the following approach, which seems to work OK, even if not 100 % compliant with what I was looking for:

    • Create a class PassthroughExceptionHandler:

      public class PassthroughExceptionHandler : IExceptionHandler
      {
          public Task HandleAsync(ExceptionHandlerContext context, CancellationToken cancellationToken)
          {
              // don't just throw the exception; that will ruin the stack trace
              var info = ExceptionDispatchInfo.Capture(context.Exception);
              info.Throw();
              return Task.CompletedTask;
          }
      }
      
    • Let that class replace the IExceptionHandler service of Web API:

      config.Services.Replace(typeof(IExceptionHandler), new PassthroughExceptionHandler());
      
    • Create a middleware class which does what I want:

      public class ExceptionHandlerMiddleware
      {
          public override async Task Invoke(IOwinContext context)
          {
              try
              {
                  await Next?.Invoke(context);
              }
              catch (Exception ex)
              {
                  // handle and/or log
              }
          }
      }
      
    • Register that middleware first in the stack:

      app.Use<ExceptionHandlerMiddleware>()
         .UseStageMarker(PipelineStage.Authenticate)
         // other middlewares omitted for brevity
         .UseStageMarker(PipelineStage.PreHandlerExecute)
         .UseWebApi(config);
      

    I will still award the bounty to anyone who comes up with (bounty expired...) I'm still looking for a better solution, which, for example, breaks when an unhandled exception is thrown. (This approach makes VS break when I rethrow the exception in the handler, but the original call stack is lost; I have to set a breakpoint at the faulting line and debug again to be able to intercept the state when an exception is thrown.)

    0 讨论(0)
  • 2020-12-02 13:07

    Not sure if this will work for you, but I have a similar requirement to send all errors back as JSON even for not found errors. I created a base controller and overrode the ExecuteAsync allowing me to create my own responses.

    public class ControllerBase : ApiController
    {
        protected string ClassName = "ControllerBase::";
    
        public override System.Threading.Tasks.Task<HttpResponseMessage> ExecuteAsync(System.Web.Http.Controllers.HttpControllerContext controllerContext, System.Threading.CancellationToken cancellationToken)
        {
            try
            {
                System.Threading.Tasks.Task<HttpResponseMessage> TaskList = base.ExecuteAsync(controllerContext, cancellationToken);
    
                if (TaskList.Exception != null && TaskList.Exception.GetBaseException() != null)
                {
                    JSONErrorResponse AsyncError = new JSONErrorResponse();
                    AsyncError.ExceptionMessage = TaskList.Exception.GetBaseException().Message;
                    AsyncError.ErrorMessage = string.Format("Unknown error {0} ExecuteAsync {1}", ClassName ,controllerContext.Request.RequestUri.AbsolutePath);
                    AsyncError.HttpErrorCode = HttpStatusCode.BadRequest;
    
                    HttpResponseMessage ErrorResponse = controllerContext.Request.CreateResponse(AsyncError.HttpErrorCode, AsyncError);
    
                    return System.Threading.Tasks.Task.Run<HttpResponseMessage>(() => ErrorResponse);
                }
                return TaskList;
            }
            catch (Exception Error)
            {
                JSONErrorResponse BadParameters = new JSONErrorResponse();
                BadParameters.ExceptionMessage = Error.Message;
                BadParameters.ErrorMessage = string.Format("Method [{0}], or URL [{1}] not found, verify your request", controllerContext.Request.Method.Method, controllerContext.Request.RequestUri.AbsolutePath);
                BadParameters.HttpErrorCode = HttpStatusCode.NotFound;
                HttpResponseMessage ErrorResponse = controllerContext.Request.CreateResponse(BadParameters.HttpErrorCode, BadParameters);
    
                return System.Threading.Tasks.Task.Run<HttpResponseMessage>(() => ErrorResponse);
            }
        }
    }
    
    public class JSONErrorResponse
    {
        //Possible message from exception
        public string ExceptionMessage { get; set; }
        //Possible custom error message
        public string ErrorMessage { get; set; }
        //Http error code
        public HttpStatusCode HttpErrorCode { get; set; }
    }
    
    0 讨论(0)
  • 2020-12-02 13:08

    You can also try to create your own controller activator, have your custom exception handler and try to use ExceptionFilterAttribute.

    1. Create your controller activator

      public class ExceptionHandlingControllerActivator : IHttpControllerActivator
      {
          private readonly IHttpControllerActivator _concreteActivator;
      
          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)
              {
                  // do stuff with the exception
                  throw new HttpResponseException(request.CreateResponse(HttpStatusCode.InternalServerError, new ResponseModel(ex)));
              }
          }
      }
      
    2. Create ExceptionFilterAttribute

      public class ExceptionHandlingFilter : ExceptionFilterAttribute
      {
          public override void OnException(HttpActionExecutedContext context)
          {
              // do stuff with the exception
      
              var request = context.Request;
              ResponseModel respMod = null;
      
              // Example: if debug constant is not defined, mask exception, otherwise create normal object with message, inner exception and stacktrace
      
              #if !DEBUG
              respMod = new ResponseModel(context.Exception, context.Exception.Message, true);
              #else
              respMod = new ResponseModel(context.Exception);
              #endif
      
              context.Response = request.CreateResponse(HttpStatusCode.InternalServerError, respMod);
          }
      }
      
    3. ResponseModel is a class i do serialize to JSON using Formatters and return by all controller responses, so client is able to identify error data as well as successful response in addition to the HTTP status code.

      config.Formatters.Clear(); // do not need any other
      config.Formatters.Add(new JsonMediaTypeFormatter());
      
    4. Wire up

      // ... [cut] ...            
      config.Filters.Add(new ExceptionHandlingFilter());
      // ... [cut] ...
      config.Services.Replace(typeof(IHttpControllerActivator),
          new ExceptionHandlingControllerActivator(config.Services.GetHttpControllerActivator())
      );
      // ... [cut] ...
      app.UseWebApi(config);
      
    0 讨论(0)
提交回复
热议问题