6.1.1 Internet的媒体类型
媒体类型,也叫做MIME类型,标识了数据的格式。在HTTP中,媒体类型描述了消息体的格式。一个媒体类型由两个字符串组成:类型和子类型。例如:
- text/html
- image/png
- application/json
当一条HTTP消息含有报文体时,Content-Type(内容类型)报头指定消息体的格式。这是告诉接收器如何解析消息体的内容。
例如,如果一个HTTP响应含有一个PNG图片,该响应可能会有以下报头。
HTTP/1.1 200 OK Content-Length: 95267 Content-Type: image/png
当客户端发送一条请求消息时,它可能包括一个Accept报头。Accept报头是告诉服务器,客户端希望从服务器得到哪种媒体类型。例如:
Accept: text/html,application/xhtml+xml,application/xml
该报头告诉服务器,客户端希望得到的是HTML、XHTML,或XML。
在Web API中,媒体类型决定了Web API如何对HTTP消息体进行序列化和解序列化。对于XML、JSON,以及URL编码的表单数据,已有了内建的支持。而且,通过编写媒体格式化器(Media Formatter),可以支持额外的媒体类型。
为了创建媒体格式化器,需从以下类进行派生:
- MediaTypeFormatter。这个类使用了异步读写方法
- BufferedMediaTypeFormatter。这个类派生于MediaTypeFormatter,但将异步读写方法封装在同步方法之中。
从BufferedMediaTypeFormatter派生要更简单些,因为没有异步代码,但它也意味着在I/O期间可能会阻塞线程。
6.1.2 创建媒体格式化器
以下示例演示了一个媒体类型格式化器,它可以将Product对象序列化成一个逗号分隔的值(CSV)格式。该示例使用了“创建支持CRUD操作的Web API”教程中定义的Product类型。以下是Product对象的定义:
public class Product { public int Id { get; set; } public string Name { get; set; } public string Category { get; set; } public decimal Price { get; set; } }
为了实现CSV格式化器,要定义一个派生于BufferedMediaTypeFormater的类:
public class ProductCsvFormatter : BufferedMediaTypeFormatter { }
在其构造器中,要添加一个该格式化器所支持的媒体类型。在这个例子中,该格式化器只支持单一的媒体类型:“text/csv”:
public ProductCsvFormatter() { // 添加所支持的媒体类型 SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/csv")); }
重写这个CanWriteType方法,以指示该格式化器可以序列化哪种类型:
public override bool CanWriteType(System.Type type) { if (type == typeof(Product)) { return true; } else { Type enumerableType = typeof(IEnumerable); return enumerableType.IsAssignableFrom(type); } }
在这个例子中,格式化器可以序列化单个Product对象,以及Product对象集合。
相应地,重写CanReadType方法,以指示该格式化器可以解序列化哪种类型。在此例中,格式化器不支持解序列化,因此该方法简单地返回false。
protected override bool CanReadType(Type type) { return false; }
最后,重写WriteToStream方法。通过将一种类型写成一个流,该方法对该类型进行序列化。如果你的格式化器要支持解序列化,也可以重写ReadFromStream方法。
public override void WriteToStream( Type type, object value, Stream stream, HttpContentHeaders contentHeaders) { using (var writer = new StreamWriter(stream)) { var products = value as IEnumerable;
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(); } // 将Product序列化成CSV格式的辅助器方法 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; }
6.1.4 添加媒体格式化器
为了将媒体类型格式化器添加到Web API管线,要使用HttpConfiguration对象上的Formatters属性。
public static void ConfigureApis(HttpConfiguration config) { config.Formatters.Add(new ProductCsvFormatter()); }
对于ASP.NET托管,要将这个函数添加到Global.asax文件,并通过Application_Start方法调用它。
protected void Application_Start() { ConfigureApis(GlobalConfiguration.Configuration);
// ... }
现在,如果客户端在Accept报头指定“text/csv”,则服务器将返回CSV格式的数据。
以下示例使用HttpClient来获取CSV数据,并将其写入一个文件:
HttpClient client = new HttpClient();
// 添加Accept报头 client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("text/csv"));
// 获取结果并将其写入文件 // (端口号9000只是一个示例端口号) string result = client.GetStringAsync("http://localhost:9000/api/product/").Result;