Creating Custom Formatter in ASP.NET Web API for specific formatted response

In this article I will show you how to create a custom formatter in ASP.Net Web API to send formatted response.

Before diving into deep, let me give you a quick introduction to formatters

Formatters in ASP .NET Web API plays an important role, when the request comes to the server, depending on the media-type, determines which formatter has to be used to parse the request and assemble the data into appropriate format as response. In ASP.Net Web API when the request is made by the user, Web API framework checks if it is Simple Type or a Complex Type. All types like string, integers, etc. are simple types and by default ASP.Net Web API read those values from URI and uses Model binding. On the other hand for complex types, ASP.Net WEB API framework read it from request body by default (the behavior can be overridden) an depending on the content-type load specific formatter from the list of formatters available in HttpConfiguration list.

When you are creating a custom formatter in ASP.Net Web API, you have two options one is when the request is arrived on server and you want to return the data in specific format or when the request arrives and you want to read/parse the data in specific format.

In this example, we will see how you can return the response in PSV i.e. pipe separated value format.

Create a class that should be derived from BufferedMediaTypeFormatter or MediaTypeFormatter.

The difference is

  • MediaTypeFormatter – This class uses asynchronous read and write methods.
  • BufferedMediaTypeFormatter – This class derives from MediaTypeFormatter but wraps the asynchronous read/write methods inside synchronous methods. Deriving from BufferedMediaTypeFormatter is simpler, because there is no asynchronous code, but it also means the calling thread can block during I/O

In the example, we will use BufferedMediaTypeFormatter

Following are the methods available in BufferedMediaTypeFormatter class

// Summary:


// Represents a helper class to allow a synchronous formatter on top of the


// asynchronous formatter infrastructure.


public
abstract
class
BufferedMediaTypeFormatter : MediaTypeFormatter

{


// Summary:


// Initializes a new instance of the System.Net.Http.Formatting.BufferedMediaTypeFormatter


// class.


protected BufferedMediaTypeFormatter();

 


// Summary:


// Gets or sets the suggested size of buffer to use with streams in bytes.


//


// Returns:


// The suggested size of buffer to use with streams in bytes.


public
int BufferSize { get; set; }

 


// Summary:


// Reads synchronously from the buffered stream.


//


// Parameters:


// type:


// The type of the object to deserialize.


//


// readStream:


// The stream from which to read


//


// content:


// The System.Net.Http.HttpContent, if available. Can be null.


//


// formatterLogger:


// The System.Net.Http.Formatting.IFormatterLogger to log events to.


//


// Returns:


// An object of the given type.


public
virtual
object ReadFromStream(Type type, Stream readStream, HttpContent content, IFormatterLogger formatterLogger);


//


// Summary:


// Reads asynchronously from the buffered stream.


//


// Parameters:


// type:


// The type of the object to deserialize.


//


// readStream:


// The stream from which to read.


//


// content:


// The System.Net.Http.HttpContent, if available. Can be null.


//


// formatterLogger:


// The System.Net.Http.Formatting.IFormatterLogger to log events to.


//


// Returns:


// A task object representing the asynchronous operation.


public
override
sealed
Task<object> ReadFromStreamAsync(Type type, Stream readStream, HttpContent content, IFormatterLogger formatterLogger);


//


// Summary:


// Writes synchronously to the buffered stream.


//


// Parameters:


// type:


// The type of the object to serialize.


//


// value:


// The object value to write. Can be null.


//


// writeStream:


// The stream to which to write.


//


// content:


// The System.Net.Http.HttpContent, if available. Can be null.


public
virtual
void WriteToStream(Type type, object value, Stream writeStream, HttpContent content);


//


// Summary:


// Writes asynchronously to the buffered stream.


//


// Parameters:


// type:


// The type of the object to serialize.


//


// value:


// The object value to write. It may be null.


//


// writeStream:


// The stream to which to write.


//


// content:


// The System.Net.Http.HttpContent, if available. Can be null.


//


// transportContext:


// The transport context.


//


// Returns:


// A task object representing the asynchronous operation.


public
override
sealed
Task WriteToStreamAsync(Type type, object value, Stream writeStream, HttpContent content, TransportContext transportContext);

}

 

When we derive our custom formatter class from BufferedMediaTypeFormatter class we have to override following methods.

 

public
override
bool CanWriteType(Type type)

 

public
override
void WriteToStream(Type type, object value, System.IO.Stream stream, System.Net.Http.HttpContent content)

 

We can put some logic inside CanWriteType method that checks the type and return true or false. If the resultant value is true the framework calls WriteToStream method otherwise the WriteToStream method won’t be called.

Here is the complete code of custom formatter.


public
class
PsvFormatter : BufferedMediaTypeFormatter

{


public PsvFormatter()

{

SupportedMediaTypes.Add(new System.Net.Http.Headers.MediaTypeHeaderValue(“text/pipe”));

 

}


public
override
bool CanWriteType(Type type)

{


if (type == typeof(Session))

{


return
true;

}


return
false;

}

 


public
override
void WriteToStream(Type type, object value, System.IO.Stream stream, System.Net.Http.HttpContent content)

{


using (var writer = new
StreamWriter(stream))

{

 


var singleSession = value as
Session;


if (singleSession == null)

{


throw
new
InvalidOperationException(“Cannot serialize type”);

}

WriteItem(singleSession, writer);

}

stream.Close();

 

}

 


private
void WriteItem(Session session, StreamWriter writer)

{

writer.WriteLine(“{0}|{1}|{2}|{3}”, session.Id,

session.SessionName, session.SessionDateTime, session.Place);

}

 

}

 

In the above code snippet, if you see the constructor I have added the Media type header value in the SupportedMediaTypes list which will be used to pass in the request header information. While invoking WEB API method you can pass text/pipe as ACCEPT header attribute.

In the CanWriteType method I am checking if the type of the object which I am returning is of type Session. In my code example I have a Session whose structure is as follows.

public
class
Session

{


public
int Id { set; get; }


public
string SessionName { set; get; }


public
DateTime SessionDateTime { set; get; }


public
string Place { set; get; }

}

 

In the WriteToStream method I am using the stream writer and writing the object as pipe separated string.

Now in the DataController which is the Web API controller, I have a Get method which fetches the session object from database. After fetching the session object from database, web API framework calls CanWriteType and WriteToStream method and parse the object into Pipe separted string and send that string as the response.

public
class
DataController : ApiController

{


public
Session Get()

{


return repository.All<Session>().Where(i => i.Id == 3).First();

}

}

Using fiddler, I made a request to my data controller and passed text/pipe as ACCEPT attribute this tells the framework that the return response should be text/pipe. Web API framework checks the formatter and uses my custom formatter to serialize the response in pipe separated string.


And the response is here


You can see how easy it is to implement custom formatter in ASP.Net WEB API and in the next series of article I will blog a post about handling custom content type requests in ASP.Net Web API

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s