Search code examples
asp.net-mvcxmljsonrestloose-coupling

Best way to structure the code for an ASP.NET MVC REST API that is decoupled from the data formats?


I am creating a REST API in ASP.NET MVC. I want the format of the request and response to be JSON or XML, however I also want to make it easy to add another data format and easy to create just XML first and add JSON later.

Basically I want to specify all of the inner workings of my API GET/POST/PUT/DELETE requests without having to think about what format the data came in as or what it will leave as and I could easily specify the format later or change it per client. So one guy could use JSON, one guy could use XML, one guy could use XHTML. Then later I could add another format too without having to rewrite a ton of code.

I do NOT want to have to add a bunch of if/then statements to the end of all my Actions and have that determine the data format, I'm guessing there is some way I can do this using interfaces or inheritance or the like, just not sure the best approach.


Solution

  • Serialization

    The ASP.NET pipeline is designed for this. Your controller actions don't return the result to the client, but rather a result object (ActionResult) which is then processed in further steps in the ASP.NET pipeline. You can override the ActionResult class. Note that FileResult, JsonResult, ContentResult and FileContentResult are built-in as of MVC3.

    In your case, it's probably best to return something like a RestResult object. That object is now responsible to format the data according to the user request (or whatever additional rules you may have):

    public class RestResult<T> : ActionResult
    {
        public override void ExecuteResult(ControllerContext context)
        {
            string resultString = string.Empty;
            string resultContentType = string.Empty;
    
            var acceptTypes = context.RequestContext.HttpContext.Request.AcceptTypes;
    
            if (acceptTypes == null)
            {
                resultString = SerializeToJsonFormatted();
                resultContentType = "application/json";
            }
            else if (acceptTypes.Contains("application/xml") || acceptTypes.Contains("text/xml"))
            {
                resultString = SerializeToXml();
                resultContentType = "text/xml";
            }
    
           context.RequestContext.HttpContext.Response.Write(resultString);
            context.RequestContext.HttpContext.Response.ContentType = resultContentType;
       }
    }
    

    Deserialization

    This is a bit more tricky. We're using a Deserialize<T> method on the base controller class. Please note that this code is not production ready, because reading the entire response can overflow your server:

    protected T Deserialize<T>()
    {
        Request.InputStream.Seek(0, SeekOrigin.Begin);
        StreamReader sr = new StreamReader(Request.InputStream);
        var rawData = sr.ReadToEnd(); // DON'T DO THIS IN PROD!
    
        string contentType = Request.ContentType;
    
        // Content-Type can have the format: application/json; charset=utf-8
        // Hence, we need to do some substringing:
        int index = contentType.IndexOf(';');
        if(index > 0)
            contentType = contentType.Substring(0, index);
        contentType = contentType.Trim();
    
        // Now you can call your custom deserializers.
        if (contentType == "application/json")
        {
            T result = ServiceStack.Text.JsonSerializer.DeserializeFromString<T>(rawData);                
            return result;
        }
        else if (contentType == "text/xml" || contentType == "application/xml")
        {
            throw new HttpException(501, "XML is not yet implemented!");
        }
    }