Internet Media Types
A media type, also called a MIME type, identifies the format of a piece of data. In HTTP, media types describe the format of the message body. A media type consists of two strings, a type and a subtype. For example:
- text/html
- image/png
- application/json
When an HTTP message contains an entity-body, the Content-Type header specifies the format of the message body. This tells the receiver how to parse the contents of the message body.
For example, if an HTTP response contains a PNG image, the response might have the following headers.
HTTP/1.1 200 OK Content-Length: 95267 Content-Type: image/png
When the client sends a request message, it can include an Accept header. The Accept header tells the server which media type(s) the client wants from the server. For example:
Accept: text/html,application/xhtml+xml,application/xml
This header tells the server that the client wants either HTML, XHTML, or XML.
In Web API, the media type determines how Web API serializes and deserializes the HTTP message body. There is built-in support for XML, JSON, and form-urlencoded data, and you can support additional media types by writing a media formatter.
To create a media formatter, derive from one of these classes:
- MediaTypeFormatter. This class uses asynchronous read and write methods.
- BufferedMediaTypeFormatter. This class derives from MediaTypeFormatter but wraps the asynchronous read/write methods inside sychronous methods.
Deriving from BufferedMediaTypeFormatter is simpler, because there is no asynchronous code, but it also means the calling thread can block during I/O.
Creating a Media Formatter
The following example shows a media type formatter that can serialize a Product object to a comma-separated values (CSV) format. This example uses the Product type defined in the tutorial Creating a Web API that Supports CRUD Operations. Here is the definition of the Product object:
namespace ProductStore.Models { public class Product { public int Id { get; set; } public string Name { get; set; } public string Category { get; set; } public decimal Price { get; set; } } }
To implement a CSV formatter, define a class that derives from BufferedMediaTypeFormater:
namespace ProductStore.Formatters { using System; using System.Collections.Generic; using System.IO; using System.Net.Http.Formatting; using System.Net.Http.Headers; using ProductStore.Models; public class ProductCsvFormatter : BufferedMediaTypeFormatter { } }
In the constructor, add the media types that the formatter supports. In this example, the formatter supports a single media type, "text/csv":
public ProductCsvFormatter() { // Add the supported media type. SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/csv")); }
Override the CanWriteType method to indicate which types the formatter can serialize:
public override bool CanWriteType(System.Type type) { if (type == typeof(Product)) { return true; } else { Type enumerableType = typeof(IEnumerable<Product>); return enumerableType.IsAssignableFrom(type); } }
In this example, the formatter can serialize single
Product
objects as well as collections of Product
objects.
Similarly, override the CanReadType method to indicate which types the formatter can deserialize. In this example, the formatter does not support deserialization, so the method simply returns false.
protected override bool CanReadType(Type type) { return false; }
Finally, override the WriteToStream method. This method serializes a type by writing it to a stream. If your formatter supports deserialization, also override the ReadFromStream method.
public override void WriteToStream( Type type, object value, Stream stream, HttpContentHeaders contentHeaders) { using (var writer = new StreamWriter(stream)) { var products = value as IEnumerable<Product>; if (products != null) { foreach (var product in products) { WriteItem(product, writer); } } else { var singleProduct = value as Product; if (singleProduct == null) { throw new InvalidOperationException("Cannot serialize type"); } WriteItem(singleProduct, writer); } } stream.Close(); } // Helper methods for serializing Products to CSV format. private void WriteItem(Product product, StreamWriter writer) { writer.WriteLine("{0},{1},{2},{3}", Escape(product.Id), Escape(product.Name), Escape(product.Category), Escape(product.Price)); } static char[] _specialChars = new char[] { ',', '\n', '\r', '"' }; private string Escape(object o) { if (o == null) { return ""; } string field = o.ToString(); if (field.IndexOfAny(_specialChars) != -1) { return String.Format("\"{0}\"", field.Replace("\"", "\"\"")); } else return field; }
Adding the Media Formatter
To add a media type formatter to the Web API pipeline, use the Formatters property on the HttpConfigurationobject.
public static void ConfigureApis(HttpConfiguration config) { config.Formatters.Add(new ProductCsvFormatter()); }
For ASP.NET hosting, add this function to the Global.asax file and call it from the Application_Start method.
protected void Application_Start() { ConfigureApis(GlobalConfiguration.Configuration); // ... }
Now if a client specifies "text/csv" in the Accept header, the server will return the data in CSV format.
The following example uses HttpClient to get the CSV data and write it to a file:
HttpClient client = new HttpClient(); // Add the Accept header client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("text/csv")); // Get the result and write it to a file. // (Port 9000 is just an example port number.) string result = client.GetStringAsync("http://localhost:9000/api/product/").Result; System.IO.File.WriteAllText("products.csv", result);
This article was written by Mike Wasson and was originally appeared on asp.net, copyright 2013