chobo2
chobo2

Reputation: 85765

How to return data in a standard way?

I am trying to figure out how to return my data in a standard way. What I mean by this is when I return json or xml it would be nice to have one format for everything(success and errors).

Say I have the following json result.

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

This is all fine but now what happens if there is a validation error

I could use the built in method 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."
    ]
  }
}

*Yes I know the data does not make sense and is different but data is irrelevant only the structure is.

Now what happens if I have an error and in this case it is has a custom error code.

I could return something like this(using HttpError)

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

Now you can see I have 3 different formats of json coming back. Now on the client I would have to do this

  1. Check HttpStatusCode
    • If 200 then in this case parse json using format of Person.
    • If 400 then could be a validation error or server error.
    • If Customer error is found then use that format otherwise use modlestate.

I been working with foursquare and it seems like they always return the same format back to the user but I have no clue how to get the same sort of thing when I do it.

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

I would like to do something similar to it like

would be an ok request.

{
    "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": [
                "[email protected]",
                "[email protected]"
            ],
            "dateOfBirth": "1980-01-02T00:00:00.000Z",
            "registered": true,
            "emergencyContacts": [
                {
                    "name": "",
                    "phone": "",
                    "email": "",
                    "relationship": "spouse|parent|child|other"
                }
            ]
        }
    }
}

server error would look like this

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

validation would look like this

{
    "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": {}
}

but like I said not sure how to do something like this and not 100% certain this is the best way still. At least it should be one format then?

Edit @Erik Philips

When I was doing just asp.net mvc projects I would do something like this.

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();
    }
}

I like how you set it up but I would like to keep it sort of that way. I don't think I can use a interface but I was thinking of something like this

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);
    }
}

Upvotes: 2

Views: 238

Answers (1)

Erik Philips
Erik Philips

Reputation: 54618

This is sort of a large question as it's the design to send data which has multiple parts, but I believe this is a fairly easy, small and elegant solution.

This isn't exactly what I use, but it a good example:

First lets build out a model that represents what all responses need, or can be used when no result data is required:

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; }
}

Next, there is probably a large set of different types of results to return, and here Generics come in 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; }
}

So now if you need to return a Person you can return:

var result = ResponseResult<Person>();

result.Data = person;

//serialize result and send to client.

My APIs can be consumed by Javascript so I change the Http Status code, and give examples on how to use jQuery to redirect and consume the data.

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
    }
)};

You may want to extend the result to be consumed by a client that may not even be using http (WCF) in the future:

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

or Take it a step further:

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

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

so you could add more error types/information in the future.

Update Per Comments

Comment 1: If you have a collection of people then you have..

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

Comment 2: There are 2 classes..

If your API had a call a FileExists(fileName) then you don't really have to return an object, just that the call succeeded.

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

If your API wanted to return the ID of a new Person you could return the new ID.

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

Or you could return back new a successful Person object, or null if not successful.

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

Update Per Comments

What I would recommend is what I wrote earlier and include the ResponseErrorBase in the ResponseResult:

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; }
}

Then derive your error from the base to something specific:

// 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; }
}

Then return it (example for returning the value, you'll want more logic):

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);

Upvotes: 2

Related Questions