包装IHttpActionResult - 通用解决方案

时间:2016-12-19 20:43:52

标签: asp.net-mvc generics design-patterns asp.net-web-api asp.net-web-api2

我想包装IHttpActionResult,因为我需要一些额外的数据供客户端应用程序使用。

我的第一个方法是创建并返回简单的DTO,如果成功则包装结果对象:

回复DTO:

 public class Response<T>
{
    public string ErrorMessage { get; set; }
    public bool Success { get; set; }
    public string CodeStatus { get; set; }
    public T Result { get; set; }

    public Response(bool isSuccess, [Optional] T result, [Optional] string codeStatus, [Optional] string errorMessage)
    {
        Success = isSuccess;
        Result = result;
        CodeStatus = codeStatus;
        ErrorMessage = errorMessage;
    }
}

控制器:

    public IHttpActionResult Get(int id)
    {
        return BadRequest(new Response<MyObjectClass>(false, null,"Invalid Id",400));
        ...
        return Ok(new Response<MyObjectClass>(true, result);

    }

我发现处理包装非常无效。我觉得它不是很优雅。我试图找出一些通用的解决方案,最后得到以下结论:

示例控制器操作:

    public IHttpActionResult GetById(int id)
    {
        var result = _someService.Get(id);

        if (result == null)
            return NotFound().WithError("Invalid Id");

        return Ok().WithSuccess(result);
    }

这仍然会返回Response DTO。

我已经包装了IHttpActionResult来处理创建Response DTO:

public class HttpActionResult : IHttpActionResult
{
    private readonly string _errorMessage;
    private readonly IHttpActionResult _innerResult;
    private readonly object _result;
    private readonly bool _isSuccess;

    public HttpActionResult(IHttpActionResult inner, bool isSuccess, object result,string errorMessage)
    {
        _errorMessage = errorMessage;
        _innerResult = inner;
        _result = result;
        _isSuccess = isSuccess;            
    }


    public async Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
    {
        HttpResponseMessage response = await _innerResult.ExecuteAsync(cancellationToken);
        response.Content = new ObjectContent(typeof(Response), new Response(_isSuccess, _result, ((int)response.StatusCode).ToString(), _errorMessage), new JsonMediaTypeFormatter());
        return response;
    }
}

最后,我为IHttpActionResult添加了扩展方法,以便在控制器中使用:

public static class IHttpActionResultExtensions
    {
        public static IHttpActionResult WithSuccess(this IHttpActionResult inner, object result = null, string message = null)
        {
            return new HttpActionResult(inner, true, result, message);
        }

        public static IHttpActionResult WithError(this IHttpActionResult inner,  string message = null)
        {
            return new HttpActionResult(inner, false,null, message);
        }
    }

在API控制器中处理包装http消息有哪些替代方法? 您在我的解决方案中看到了哪些弱点?

2 个答案:

答案 0 :(得分:2)

顺便说一句,我看到你的方法存在一些弱点:

  1. WebAPI旨在用于创建RESTful Web服务。您为什么要提供另一层状态和其他详细信息? HTTP足以满足这些要求。例如,您可以使用标准状态代码和子代码,如下所示:500.1500.2

  2. 使用HTTP状态代码更容易表达成功或失败。 2XX成功操作的范围,以及您可以使用的不成功操作,例如400(错误请求)。 401用于未经授权的访问... 500表示服务器发生故障...

  3. WebAPI已经提供ModelState让框架构建响应对象。使用它并尝试不重新发明轮子。

  4. 再次,保持简单。响应实体继续响应。成功或失败由状态代码表示。有关错误请求的详细信息将添加到ModelState字典中。应将错误消息设置为响应ReasonPhrase

  5. IHttpActionResult实现旨在将您的域结果转换为HTTP响应。也就是说,除非您尝试按方式返回响应对象,否则您将处于正确的轨道中。我建议您应该使用IHttpActionResult将自己的响应对象的每个细节设置为标准HTTP语义,并使用ModelState开箱即用的方法通知错误。

答案 1 :(得分:1)

避免使用IHttpActionResult并将HttpResponseException与业务实体一起用作结果类型。在您的解决方案中,您无法编写静态类型的测试用例。

例如,

protected void ThrowHttpError(HttpStatusCode statusCode, string message) 
{
     throw new HttpResponseException(
         new HttpResponseMessage(statusCode) {
            ReasonPhrase = message,
            // HTTP 2.0 ignores ReasonPhrase
            // so we send ReasonPhrase again in the Content
            Content = new StringContent(message)
     });
}


// some generic option...
protected void ThrowHttpError<T>(HttpStatusCode statusCode, T content) 
    where T:class
{
     throw new HttpResponseException(
         new HttpResponseMessage(statusCode) {
            ReasonPhrase = "Error",
            Content = JsonConvert.Serialize(content)
     });
}

你的方法,

public async Task<Product> Get(long id){

    var product = await context.Products
       .FirstOrDefaultAsync( x=> x.ProductID == id);

    if(product==null){
        ThrowHttpError(HttpStatusCode.NotFound, 
           $"Product not found for {id}");
    }

    if(product.RequiresValidation){

        // generic version....

        ThrowHttpError(HttpStatusCode.Conflict,
           new Product{
                ProductID = product.ProductID,
                ValidationRequestCode = product.ValidationRequestCode
        });
    }

    return product;
}

更多信息,您可以自定义方法ThrowHttpError以满足您的需求。最好的部分是,它仍然是可测试的。