Web Api 封装自定义的异常和统一的返回结果

10/11/2018 .NETAPI后端

Web Api 封装自定义的异常和统一的返回结果对于API返回结果的封装,需要考虑下面的情况 处理错误异常 处理参数异常 自定义API异常 自定义的统一的返回结果 封装的类ValidationError ApiError ApiException ApiResponse Respo

# Web Api 封装自定义的异常和统一的返回结果

对于API返回结果的封装,需要考虑下面的情况

  • 处理错误异常
  • 处理参数异常
  • 自定义API异常
  • 自定义的统一的返回结果

# 封装的类

  • ValidationError
  • ApiError
  • ApiException
  • ApiResponse
  • ResponseMessageEnum

# ValidationError

对参数验证的封装

public class ValidationError
{
    public string Field { get; set; }
    public string Message { get; set; }

    public ValidationError(string field,string message)
    {
        Field = field != string.Empty ? field : null;
        Message = message;
    }
}
1
2
3
4
5
6
7
8
9
10
11

# ApiError

public class ApiError
{
    public bool IsError { get; set; }
    public string ExceptionMessage { get; set; }
    public string Details { get; set; }
    public string ReferenceErrorCode{ get; set; }
    public string ReferenceDocumentLink { get; set; }
    public IEnumerable<ValidationError> ValidationErrors { get; set; }

    public ApiError(string message)
    {
        this.ExceptionMessage = message;
        this.IsError = true;
    }

    public ApiError(ModelStateDictionary modelState)
    {
        this.IsError = true;
        if(modelState != null && modelState.Any(m => m.Value.Errors.Count > 0))
        {
            this.ExceptionMessage = "the validation errors";
            this.ValidationErrors = modelState.Keys.
                SelectMany(k => modelState[k].Errors.Select(x => new ValidationError(k, x.ErrorMessage))).ToList();
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

# ApiException

public class ApiException : Exception
{
    public int StatusCode { get; set; }
    public IEnumerable<ValidationError> Errors { get; set; }
    public string ReferenceErrorCode { get; set; }
    public string ReferenceDocumentLink { get; set; }
    public ApiException(string message,
        int statusCode = 500,
        IEnumerable<ValidationError> errors = null,
        string errorCode = "",
        string refLink = "")
    {
        this.StatusCode = statusCode;
        this.Errors = errors;
        this.ReferenceErrorCode = errorCode;
        this.ReferenceDocumentLink = refLink;
    }

    public ApiException(Exception ex,int statusCode = 500): base(ex.Message)
    {
        StatusCode = statusCode;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# ApiResponse

public class ApiResponse
{
    public string Version { get; set; }
    public int StatusCode { get; set; }
    public string Message { get; set; }
    public ApiError ResponseException { get; set; }
    public object Result { get; set; }

    public ApiResponse(int statusCode,string message = "",object result = null,ApiError apiError = null,string apiVersion = "1.0.0")
    {
        this.StatusCode = statusCode;
        this.Message = message;
        this.Result = result;
        this.ResponseException = apiError;
        this.Version = apiVersion;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# ResponseMessageEnum

public enum ResponseMessageEnum
{
    [Description("Request successful.")]
    Success,
    [Description("Request responsed with exception.")]
    Exception,
    [Description("Request denied.")]
    Unauthorized,
    [Description("Request with validation error.")]
    ValidationError,
    [Description("Unable to process the request.")]
    Failure
}
1
2
3
4
5
6
7
8
9
10
11
12
13

# Exception Filter

发成异常时,可以通过实现 ExceptionFilterAttribute进行统一处理

public class ApiExceptionFilter : ExceptionFilterAttribute
{
    public override void OnException(HttpActionExecutedContext context)
    {
        ApiError apiError = null;
        ApiResponse apiResponse = null;
        int code = 0;

        if(context.Exception is ApiException)
        {
            var ex = context.Exception as ApiException;
            apiError = new ApiError(ex.Message);
            apiError.ValidationErrors = ex.Errors;
            apiError.ReferenceErrorCode = ex.ReferenceErrorCode;
            apiError.ReferenceDocumentLink = ex.ReferenceDocumentLink;
            code = ex.StatusCode;
        }else if(context.Exception is UnauthorizedAccessException)
        {
            apiError = new ApiError("Unauthorized Access");
            code = (int)HttpStatusCode.Unauthorized;
        }else
        {
#if !DEBUG
            var msg = "an unhandled error";
            string stack = null;
#else
            var msg = context.Exception.GetBaseException().Message;
            string stack = context.Exception.StackTrace;
#endif
            apiError = new ApiError(msg);
            apiError.Details = stack;
            code = (int)HttpStatusCode.InternalServerError;
        }

        apiResponse = new ApiResponse(code, ResponseMessageEnum.Exception.ToString(), null, apiError);
        HttpStatusCode c = (HttpStatusCode)code;
        context.Response = context.Request.CreateResponse(c, apiResponse);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39

# Delegating Handler

public class WrapperHandler : DelegatingHandler
{
    protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        if (IsSwagger(request))
        {
            return await base.SendAsync(request, cancellationToken);
        }else
        {
            var response = await base.SendAsync(request, cancellationToken);
            return BuildApiResponse(request, response);
        }

    }

    private static HttpResponseMessage BuildApiResponse(HttpRequestMessage request,HttpResponseMessage response)
    {
        dynamic content = null;
        object data = null;
        string errorMessage = null;
        ApiError apiError = null;

        var code = (int)response.StatusCode;
        if(response.TryGetContentValue(out content) && !response.IsSuccessStatusCode)
        {
            HttpError error = content as HttpError;
            if(error != null)
            {
                content = null;
                if(response.StatusCode == System.Net.HttpStatusCode.NotFound)
                {
                    apiError = new ApiError("the uri not fount");
                }else if(response.StatusCode == System.Net.HttpStatusCode.NoContent)
                {
                    apiError = new ApiError("the uri not contain the content");
                }else
                {
                    errorMessage = error.Message;
#if DEBUG
                    errorMessage = string.Concat(errorMessage, error.ExceptionMessage, error.StackTrace);
#endif
                }
                data = new ApiResponse((int)code, ResponseMessageEnum.Failure.ToString(), null, apiError);
            }else
            {
                data = content;
            }
        }else
        {
            if(response.TryGetContentValue(out content))
            {
                Type type;
                type = content?.GetType();
                if (type.Name.Equals("ApiResponse"))
                {
                    response.StatusCode = Enum.Parse(typeof(HttpStatusCode), content.StatusCode.ToString());
                    data = content;
                }else if (type.Name.Equals("SwaggerDocument"))
                {
                    data = content;
                }else
                {
                    data = new ApiResponse(code, ResponseMessageEnum.Success.ToString(), content);
                }
            }else
            {
                if (response.IsSuccessStatusCode)
                {
                    data = new ApiResponse((int)response.StatusCode, ResponseMessageEnum.Success.ToString());
                }
            }
        }

        var newResponse = request.CreateResponse(response.StatusCode, data);
        foreach(var header in response.Headers)
        {
            newResponse.Headers.Add(header.Key, header.Value);
        }
        return newResponse;
    }

    private bool IsSwagger(HttpRequestMessage request)
    {
        return request.RequestUri.PathAndQuery.StartsWith("/swagger");
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86

# 实现

WebApiConfig中注册

config.Filters.Add(new ApiExceptionFilter());
config.MessageHandlers.Add(new WrapperHandler());
1
2

更多精彩内容 (opens new window)