如何以标准方式返回数据?

时间:2013-04-19 23:54:13

标签: c# .net json asp.net-web-api

我试图弄清楚如何以标准方式返回我的数据。我的意思是,当我返回json或xml时,最好有一种格式(成功和错误)。

说我有以下json结果。

{
  "person": {
    "id": 12345,
    "firstName": "John",
    "lastName": "Doe",
    "phones": {
      "home": "800-123-4567",
      "work": "888-555-0000",
      "cell": "877-123-1234"
    },
    "email": [
      "jd@example.com",
      "jd@example.org"
    ],
    "dateOfBirth": "1980-01-02T00:00:00.000Z",
    "registered": true,
    "emergencyContacts": [
      {
        "name": "",
        "phone": "",
        "email": "",
        "relationship": "spouse|parent|child|other"
      }
    ]
  }
}

这一切都很好,但现在如果出现验证错误会发生什么

我可以使用内置方法CreateErrorResponse

{
  "Message": "The request is invalid.",
  "ModelState": {
    "item": [
      "Required property 'Name' not found in JSON. Path '', line 1, position 14."
    ],
    "item.Name": [
      "The Name field is required."
    ],
    "item.Price": [
      "The field Price must be between 0 and 999."
    ]
  }
}

*是的我知道数据没有意义且不同但数据只与结构无关。

现在如果我有错误会发生什么,在这种情况下它有一个自定义错误代码。

我可以返回这样的东西(使用HttpError)

{
  "Message": "My custom error message",
  "CustomErrorCode": 37
}

现在你可以看到我有3种不同格式的json回来了。现在客户端我必须这样做

  1. 检查HttpStatusCode
    • 如果是200则在这种情况下使用Person的格式解析json。
    • 如果400则可能是验证错误或服务器错误。
    • 如果找到客户错误,则使用该格式,否则使用modlestate。
  2. 我一直在使用foursquare,看起来他们总是将相同的格式返回给用户,但我不知道如何在我这样做的时候得到同样的东西。

      {
              "meta": {
                "code": 200,
                 ...errorType and errorDetail...
              },
              "notifications": {
                 ...notifications...
              },
              "response": {
                 ...results...
              }
            }
    

    我想做类似

    的事情

    将是一个好的请求。

    {
        "meta": {
            "code": 200,
             "ModelState": {}
        },
        "response": {
            "person": {
                "id": 12345,
                "firstName": "John",
                "lastName": "Doe",
                "phones": {
                    "home": "800-123-4567",
                    "work": "888-555-0000",
                    "cell": "877-123-1234"
                },
                "email": [
                    "jd@example.com",
                    "jd@example.org"
                ],
                "dateOfBirth": "1980-01-02T00:00:00.000Z",
                "registered": true,
                "emergencyContacts": [
                    {
                        "name": "",
                        "phone": "",
                        "email": "",
                        "relationship": "spouse|parent|child|other"
                    }
                ]
            }
        }
    }
    

    服务器错误看起来像这样

    {
        "meta": {
            "code": 500,
            "message": "this is a server error",
            "ModelState": {}
        },
        "response": {}
    }
    

    验证看起来像这样

    {
        "meta": {
            "code": 400,
            "message": "validation errors",
            "Message": "The request is invalid.",
            "ModelState": {
                "item": [
                    "Required property 'Name' not found in JSON. Path '', line 1, position 14."
                ],
                "item.Name": [
                    "The Name field is required."
                ],
                "item.Price": [
                    "The field Price must be between 0 and 999."
                ]
            }
        },
        "response": {}
    }
    

    但是就像我说不确定如何做这样的事情而不是100%肯定这是最好的方式。至少它应该是一种格式呢?

    修改 @Erik Philips

    当我只做asp.net mvc项目时,我会做这样的事情。

    public readonly IValidation validation;
    
    public PersonService(IValidation validation)
    {
        this.validation = validation;
    }
    
    public Person GetPerson(int id)
    {
    
        try
        {
           return FindPerson(id);
        }
        catch(Exception ex)
        {
            //log real error with elmah
            validation.addError("internal", "Something went wrong");
        }
    }
    
    
    public class PersonController
    {
         public readonly IPersonService personService;
         public PersonController(IPersonService personService)
         {
           this.personService = personService;
         }
    
        public ActionResult GetPerson(int id)
        {
            personService.GetPerson(id);
    
            if(personService.Validation.IsValid)
            {
              // do something
            }
            else
            { 
              // do something else
            }
    
            return View();
        }
    }
    

    我喜欢你如何设置它,但我想保持这种方式。我不认为我可以使用界面,但我在想这样的事情

    public PersonService()
    {
    
    }
    
    public ResponseResult<Person> GetPerson(int id)
    {
        var result = ResponseResult<Person>();
        try
        {
           return FindPerson(id);
        }
        catch(Exception ex)
        {
           result.Errorcode = 200;
           result.Msg = "Failed";
        }
    }
    
    
    public class PersonController
    {
         public readonly IPersonService personService;
         public PersonController(IPersonService personService)
         {
           this.personService = personService;
         }
    
        public HttpResponseMessage GetPerson(int id)
        {
           var result = personService.GetPerson(id);
           if(result.isValid)
           {
              Request.CreateResponse<ResponseResult<Person>>(HttpStatusCode.OK, result);
           }
    
             Request.CreateResponse<ResponseResult<Person>>(HttpStatusCode.BadRequest, result);
        }
    }
    

1 个答案:

答案 0 :(得分:2)

这是一个很大的问题,因为它是发送包含多个部分的数据的设计,但我相信这是一个相当简单,小巧而优雅的解决方案。

这不完全是我使用的,但它是一个很好的例子:

首先让我们构建一个代表所有响应需要的模型,或者在没有结果数据时可以使用的模型:

public class ResponseResult
{
    public ResponseResult()
    {
    }

    public ResponseResult(ModelStateDictionary modelState)
    {
        this.ModelState = new ModelStateResult (modelState);
    }

    // Is this request valid, in the context of the actual request
    public bool IsValid { get; set; }
    // Serialized Model state if needed
    public ModelStateResult ModelState { get; set; }
}

接下来,可能会返回一大堆不同类型的结果,这里的Generics来自Handy:

public class ResponseResult<T> : ResponseResult
{
    public ResponseResult() : base()
    {
    }

    public ResponseResult(ModelStateDictionary modelState)
        : base(modelState)
    {
    }

    public ResponseResult(T Data, ModelStateDictionary modelState)
        : base (modelState)
    {
        this.Data = Data;
    }

    public T Data { get; set; }
}

所以现在如果您需要返回Person,您可以返回:

var result = ResponseResult<Person>();

result.Data = person;

//serialize result and send to client.

我的API可以被Javascript使用,因此我更改了Http Status代码,并提供了有关如何使用jQuery重定向和使用数据的示例。

request = $.ajax({
  type: "POST",
  url: url,
  data: data,
  success: function(data, textStatus, jqXHR)
  {
    processResponseResult(data);
  }
  complete: function(e, xhr, settings)
  {
    if(e.status === 401)
    {
      // login to 
    }
    // else if (e.status == ) 
    else
    {
      // unknown status code
    }
)};

您可能希望将结果扩展为将来甚至可能不使用http(WCF)的客户端:

public class ResponseResult
{
   ....
   ....
   public int ErrorCode { get; set; }
   public string ErrorMessage { get; set; }
}

或者更进一步:

public class ResponseErrorBase
{
   public int ErrorCode { get; set; }
   public string ErrorMessage { get; set; }
}

public class ResponseResult
{
   ....
   ....
   public ResponseErrorBase Error { get; set; }
}

因此您可以在将来添加更多错误类型/信息。

按评论更新

评论1:如果你有一群人,那么你有......

List<Person> persons = new List<Person>();
var result = new ResponseResult<List<Person>>();
result.Data = persons;

评论2:有两个班级..

如果您的API有一个FileExists(fileName)来电话,那么您不必返回一个对象,只需调用成功。

var result = new ResponseResult();
result.IsValid = FileExists(fileName);

如果您的API想要返回新Person的ID,则可以返回新ID。

var result = new ResponseResult<Guid?>();
result.IsValid = CreatePerson(personInfo);
if (result.IsValid)
{
  result.Data = personInfo.ID;
}

或者您可以返回新的成功Person对象,如果不成功则返回null。

var result = new ResponseResult<Person>();
result.IsValid = CreatePerson(personInfo);
if (result.IsValid)
{
  result.Data = Person;
}

按评论更新

我建议的是我之前写的内容,并在ResponseResult中包含ResponseErrorBase

public class ResponseResult
{
  public ResponseResult()
  {
  }

  public ResponseResult(ModelStateDictionary modelState)
  {
    this.ModelState = new ModelStateResult (modelState);
  }

  public bool IsValid { get; set; }
  public ModelStateResult ModelState { get; set; }
  public ResponseErrorBase Error { get; set; }
}

然后将您的错误从基础派生到特定的东西:

// this isn't abstract because you may want to just return
// non-specific error messages
public class ResponseErrorBase
{
  public int Code { get; set; }
  public string Message { get; set; }
}

public class InternalResponseError : ResponseErrorBase
{
  // A Property that is specific to this error but
  // not for all Errors
  public int InternalErrorLogID { get; set; }
}

然后返回它(返回值的示例,您需要更多逻辑):

var result = new ResponseResult<Person>();

try
{
  result.Data = db.FindPerson(id);
}
catch (SqlException ex)
{
  var error = ResponseErrorBase();
  error.Code = 415;
  error.Message = "Sql Exception";
}
catch (Exception ex)
{
  var error = InternalResponseError();
  error.InternalErrorLogID  = Log.WriteException(ex);
  error.Code = 500;
  error.Message = "Internal Error";
}

// MVC might look like:
return this.Json(result);