ASP.NET Web API 2 中的媒体格式化程序

本教程介绍如何在 ASP.NET Web API 中支持其他媒体格式。

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 如何序列化和反序列化 HTTP 消息正文。 Web API 内置了对 XML、JSON、BSON 和表单 url 编码数据的支持,你可以通过编写 媒体格式化程序来支持其他媒体类型。

若要创建媒体格式化程序,请派生自以下类之一:

BufferedMediaTypeFormatter 派生更简单,因为没有异步代码,但也意味着调用线程可以在 I/O 期间阻塞。

示例:创建 CSV 媒体格式化程序

以下示例演示一个媒体类型格式化程序,该格式化程序可以将 Product 对象序列化为逗号分隔值 (CSV) 格式。 此示例使用创建 支持 CRUD 操作的 Web API 教程中定义的产品类型。 下面是 Product 对象的定义:

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; }
    }
}

若要实现 CSV 格式化程序,请定义派生自 BufferedMediaTypeFormatter 的类:

using System;
using System.Collections.Generic;
using System.IO;
using System.Net.Http;
using System.Net.Http.Formatting;
using System.Net.Http.Headers;
using ProductStore.Models;

namespace ProductStore.Formatters
{
    public class ProductCsvFormatter : BufferedMediaTypeFormatter
    {
    }
}

在构造函数中,添加格式化程序支持的媒体类型。 在此示例中,格式化程序支持单个媒体类型“text/csv”:

public ProductCsvFormatter()
{
    // Add the supported media type.
    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<Product>);
        return enumerableType.IsAssignableFrom(type);
    }
}

在此示例中,格式化程序可以序列化单个 Product 对象以及对象的集合 Product

同样,重写 CanReadType 方法以指示格式化程序可以反序列化的类型。 在此示例中,格式化程序不支持反序列化,因此该方法只返回 false

public override bool CanReadType(Type type)
{
    return false;
}

最后,重写 WriteToStream 方法。 此方法通过将类型写入流来序列化类型。 如果格式化程序支持反序列化,则还要重写 ReadFromStream 方法。

public override void WriteToStream(Type type, object value, Stream writeStream, HttpContent content)
{
    using (var writer = new StreamWriter(writeStream))
    {
        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);
        }
    }
}

// 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)
    {
        // Delimit the entire field with quotes and replace embedded quotes with "".
        return String.Format("\"{0}\"", field.Replace("\"", "\"\""));
    }
    else return field;
}

将媒体格式化程序添加到 Web API 管道

若要将媒体类型格式化程序添加到 Web API 管道,请使用 HttpConfiguration 对象上的 Formatters 属性。

public static void ConfigureApis(HttpConfiguration config)
{
    config.Formatters.Add(new ProductCsvFormatter()); 
}

字符编码

(可选)媒体格式化程序可以支持多个字符编码,例如 UTF-8 或 ISO 8859-1。

在构造函数中,将一个或多个 System.Text.Encoding 类型添加到 SupportedEncodings 集合。 将默认编码放在第一位。

public ProductCsvFormatter()
{
    SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/csv"));

    // New code:
    SupportedEncodings.Add(new UTF8Encoding(encoderShouldEmitUTF8Identifier: false));
    SupportedEncodings.Add(Encoding.GetEncoding("iso-8859-1"));
}

WriteToStreamReadFromStream 方法中,调用 MediaTypeFormatter.SelectCharacterEncoding 以选择首选字符编码。 此方法将请求标头与支持的编码列表匹配。 从流中读取或写入时,请使用返回的 编码

public override void WriteToStream(Type type, object value, Stream writeStream, HttpContent content)
{
    Encoding effectiveEncoding = SelectCharacterEncoding(content.Headers);

    using (var writer = new StreamWriter(writeStream, effectiveEncoding))
    {
        // Write the object (code not shown)
    }
}