Simple Custom Media Formatter in ASP.Net Web API

Media formatters in ASP.Net Web API is useful to define our own custom content-type with which we can present data in a specific format. By default Web API support XML, JSON and form-urlencoded data formatters. But with our own media formatter we can have full control on data serialization and deserialization processes. Writing custom media formatters helps us in exposing our logic/data to specific clients with their custom specific formats in place.

In this short tutorial, we are going to see how we can create a simple media formatter to handle both serialization and deserialization process for our Product Model. I extended Product Controller with other specific actions. To create a media formatter we need to inherit BufferedMediaTypeFormater abstract helper class in System.Net.Http.Formatting namespace. We can also use MediaTypeFormatter abstract class for performing asynchronous operations. But for this tutorial, we are getting settled for synchronous operations using BufferedMediaTypeFormater. In fact BufferedMediaTypeFormater is a derived version from MediaTypeFormatter class.

We use CanReadType and ReadFromStream methods to deserialize data. And we use CanWritetype and WriteToStream methods to serialize data.

IMPORTANT – Most of the basic plumbing for this project is taken from my previous tutorial – http://www.intstrings.com/ramivemula/articles/testing-asp-net-web-apiget-post-put-delete-using-fiddler/. Please check with that tutorial prior.

Now lets add one more action to our Products Controller –

[HttpPost]
public HttpResponseMessage PostProducts(string multiple,List<Product> p)
{
    if (p == null)
        return new HttpResponseMessage(HttpStatusCode.BadRequest);

    _products.AddRange(p);
    return new HttpResponseMessage(HttpStatusCode.Created);
}

 

Now lets create a Media Formatter to server/consume a content-type “application/custom-product-type”. Create class with name “ProductFormatter.cs” in Utils folder and add following code to it –

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http.Formatting;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Web;
using System.IO;
using BasicWebAPI1.Models;

namespace BasicWebAPI1.Utils
{
    public class ProductFormatter : BufferedMediaTypeFormatter
    {
        public ProductFormatter()
        {
            SupportedMediaTypes.Add(
                                new MediaTypeHeaderValue("application/custom-product-type"));
        }

        public override bool CanReadType(Type type)
        {
            // for single product object
            if (type == typeof(Product))
                return true;
            else
            {
                // for multiple product objects
                Type _type = typeof(IEnumerable<Product>);
                return _type.IsAssignableFrom(type);
            }
        }

        public override bool CanWriteType(Type type)
        {
            //for single product object
            if (type == typeof(Product))
                return true;
            else
            {
                // for multiple product objects
                Type _type = typeof(IEnumerable<Product>);
                return _type.IsAssignableFrom(type);
            }
        }

        public override void WriteToStream(Type type, 
                                           object value, 
                                           Stream writeStream, 
                                           HttpContent content)
        {
            using (StreamWriter writer = new StreamWriter(writeStream))
            {
                /* In this code, we are going to serialize product object to 
                 * "application/custom-product-type" format string
                 */
                var products = value as IEnumerable<Product>;
                if (products != null)
                {
                    foreach (var product in products)
                    {
                        writer.Write(String.Format("[{0},\"{1}\",\"{2}\"]", 
                                                    product.Id, 
                                                    product.Name, 
                                                    product.Description));
                    }
                }
                else
                {
                    var pro = value as Product;
                    if (pro == null)
                    {
                        throw new InvalidOperationException("Cannot serialize type");
                    }
                    writer.Write(String.Format("[{0},\"{1}\",\"{2}\"]", 
                                                pro.Id, 
                                                pro.Name, 
                                                pro.Description));
                }
            }
        }

        public override object ReadFromStream(Type type, 
                                              Stream readStream, 
                                              HttpContent content, 
                                              IFormatterLogger formatterLogger)
        {
            using (StreamReader reader = new StreamReader(readStream))
            {
                /* Following code is not in good shape. In this code we make the basic plumbing to parse 
                 * the input "application/custom-product-type" format string and deserialize it to Product 
                 * objects.
                 */
                String productString = reader.ReadToEnd().ParseProductsString();
                String[] productArray = productString.Split(new string[] { "}{" }, 
                                                            StringSplitOptions.RemoveEmptyEntries);

                List<Product> products = new List<Product>();
                foreach (string s in productArray)
                {
                    String[] productInterim = s.Split(new char[] { ',' });
                    string _name = productInterim[1].Replace("\"", String.Empty);
                    string _description = productInterim[2].Replace("\"", String.Empty);

                    products.Add(new Product()
                    {
                        Id = Int32.Parse(productInterim[0]),
                        Name = _name,
                        Description = _description
                    });
                }
                return products;
            }
        }
    }

    public static class ExtensionMethods
    {
        public static string ParseProductsString(this string original)
        {
            return original.Replace("][", "}{")
                           .Replace("[", string.Empty)
                           .Replace("]", string.Empty);
        }

        public static string ReplaceExtraQuotes(this string original)
        {
            return original.Replace("\"", String.Empty);
        }
    }
}

Register our custom formatter to GlobalConfiguration (Global.asax file/Application_Start() event) as follows –

GlobalConfiguration.Configuration.Formatters.Add(new ProductFormatter());

That’s it, now we can test our API for custom data formats. Lets test it out with fiddler –

A simple GET request to all products would be –

mediaformatter1

Response would be –

mediaformatter2

 

Now lets a POST request to Web API –

mediaformatter3

Interim result in visual studio code –

mediaformatter4

Final Response to Fiddler –

mediaformatter5

You may also like...

2 Pingbacks/Trackbacks