ASP.NET Web API 2 のメディア フォーマッタ

このチュートリアルでは、ASP.NET Web API で追加のメディア形式をサポートする方法について説明します。

インターネット メディアの種類

メディアの種類 (MIME の種類とも呼ばれます) は、一部のデータの形式を識別します。 HTTP では、メディアの種類はメッセージ本文の形式を記述します。 メディアの種類は、タイプとサブタイプの 2 つの文字列で構成されます。 次に例を示します。

  • 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 エンコード フォーム データのサポートが組み込まれており、メディア フォーマッタを記述することで追加のメディアの種類をサポートできます。

メディア フォーマッタを作成するには、次のクラスのいずれかから派生させます。

  • MediaTypeFormatter。 このクラスは、非同期の読み取りおよび書き込みメソッドを使用します。
  • BufferedMediaTypeFormatter。 このクラスは MediaTypeFormatter から派生しますが、同期読み取りおよび書き込みメソッドを使用します。

BufferedMediaTypeFormatter からの派生は、非同期コードがないためシンプルですが、呼び出し元のスレッドが I/O 中にブロックできることも意味します。

例: CSV メディア フォーマッタの作成

次の例は、Product オブジェクトをコンマ区切り値 (CSV) 形式にシリアル化できるメディア タイプ フォーマッタを示しています。 この例では、チュートリアル「CRUD 操作をサポートする Web API の作成」で定義されている Product タイプを使用します。 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 などの複数の文字エンコードをサポートできます。

コンストラクターで、SupportedEncodings コレクションに 1 つ以上の System.Text.Encoding タイプを追加します。 最初に既定のエンコードを配置します。

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

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

WriteToStream メソッドと ReadFromStream メソッドで、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)
    }
}