比try-catch更好的方法是将失败消息传递回Web API调用者?

时间:2017-03-28 18:02:57

标签: c# entity-framework web-services exception asp.net-web-api

我有许多Web API调用委托给调用我的ORM(实体框架)的数据层类中的方法,如下所示:

public OperationResult DeleteThing(Guid id)
{
    var result = new OperationResult() { Success = true };
    using (var context = this.GetContext())
    {
        try
        {
            context.Things.Where(x => x.Id == id).Delete();
            context.SaveChanges();
        }
        catch (Exception ex)
        {
            Logger.Instance.LogException(ex);
            result.AddError("There was a database error deleting the thing. Check log for details.");
        }

    return result;
}

(您可能会将返回值视为与Notification pattern类似。)

所以我有许多相同的try-catch块,它对我来说闻起来很糟糕。我想摆脱它们并使用全局异常处理程序来记录错误,但除了记录之外,我还需要将消息传递给消费者,特定于每种不同的服务方法,以便消费者可以通过适当的服务调用结果传递消息。网络服务消费者,例如我们的网站最终可以显示此处生成的消息,以便用户了解错误的性质。

任何人可以建议更好的方式吗?我的直觉是通过特定异常类型的捕获来替换,但这似乎是为了实现零利益并且对我的代码可维护性造成损害的大量工作。

2 个答案:

答案 0 :(得分:3)

与Stuart的答案类似,您还可以使用从ExceptionFilterAttribute继承的Filter属性来根据您需要的任何输入修改响应。

这是一个完整的工作示例:

  • 异常类型的自定义消息
  • 修改操作结果
  • 通过所有例外类型的通用消息

<强> ValuesController.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http;
using System.Web.Http.Filters;
using Demo.Models;

namespace Demo.Controllers
{
    public class ValuesController : ApiController
    {

        // DELETE api/values/5

        [OperationError("The operation failed to delete the entity")]
        public OperationResult Delete(int id)
        {
            throw new ArgumentException("ID is bad", nameof(id));
        }

        // DELETE api/values/5?specific=[true|false]

        [OperationError("The operation tried to divide by zero", typeof(DivideByZeroException))]
        [OperationError("The operation failed for no specific reason")]
        public OperationResult DeleteSpecific(int id, bool specific)
        {
            if (specific)
            {
                throw new DivideByZeroException("DBZ");
            }
            else
            {
                throw new ArgumentException("ID is bad", nameof(id));
            }
        }
    }

    public class OperationErrorAttribute : ExceptionFilterAttribute
    {
        public Type ExceptionType { get; }
        public string ErrorMessage { get; }

        public OperationErrorAttribute(string errorMessage)
        {
            ErrorMessage = errorMessage;
        }

        public OperationErrorAttribute(string errorMessage, Type exceptionType)
        {
            ErrorMessage = errorMessage;
            ExceptionType = exceptionType;
        }

        public override void OnException(HttpActionExecutedContext actionExecutedContext)
        {
            // Exit early for non OperationResult action results
            if (actionExecutedContext.ActionContext.ActionDescriptor.ReturnType != typeof(OperationResult))
            {
                base.OnException(actionExecutedContext);
                return;
            }

            OperationResult result = new OperationResult() {Success = false};

            // Add error for specific exception types
            Type exceptionType = actionExecutedContext.Exception.GetType();

            if (ExceptionType != null)
            {
                if (exceptionType == ExceptionType)
                {
                    result.AddError(ErrorMessage);
                }
                else
                {
                    // Fall through
                    base.OnException(actionExecutedContext);
                    return;
                }
            }
            else if (ErrorMessage != null)
            {
                result.AddError(ErrorMessage);
            }

            // TODO: Log exception, generate correlation ID, etc.

            // Set new result
            actionExecutedContext.Response =
                actionExecutedContext.Request.CreateResponse(HttpStatusCode.InternalServerError, result);

            base.OnException(actionExecutedContext);
        }
    }
}

特殊例外:

Specific exception

一般例外:

Generic exception

答案 1 :(得分:1)

您可以将堆栈中的逻辑移动到自定义ExceptionHandler中。这是一个简单的例子,但基本思路是处理特定的异常并控制状态代码,并且(未在下图中)标准化调用者的错误消息。

public class ApiExceptionHandler: ExceptionHandler
{
    public override void Handle(ExceptionHandlerContext context)
    {
        if (context == null) throw new ArgumentNullException("context");

        LogManager.GetLoggerForCurrentClass().Error(context.Exception, "Captured in ExceptionHandler");

        if (context.Exception.GetType() == typeof(NotFoundException))
        {
            context.Result = new NotFoundResult(context.Request);
        }
        else if (context.Exception.GetType() == typeof(ArgumentException))
        {
            // no-op - probably a routing error, which will return a bad request with info
        }
        else if (context.Exception.GetType() == typeof(ArgumentNullException))
        {
            context.Result = new BadRequestResult(context.Request);
        }
        else
        {
            context.Result = new InternalServerErrorResult(context.Request);
        }
    }
}

将其连接到WebApiConfig:

config.Services.Replace(typeof(IExceptionHandler), new ApiExceptionHandler());