ASP.NET Core Web API のカスタム フォーマッタ
ASP.NET Core MVC は、入力と出力のフォーマッタを使用した Web API でのデータ交換をサポートしています。 入力フォーマッタは、モデル バインドによって使用されます。 出力フォーマッタは、応答の書式設定に使用されます。
フレームワークには、JSON および XML 用の組み込みの入力および出力フォーマッタが用意されています。 プレーン テキスト用の組み込みの出力フォーマッタが用意されていますが、プレーン テキスト用の入力フォーマッタは用意されていません。
この記事では、カスタム フォーマッタを作成して、追加形式のサポートを追加する方法を示します。 カスタムのプレーン テキスト入力フォーマッタの例については、GitHub の TextPlainInputFormatter を参照してください。
サンプル コードを表示またはダウンロードします (ダウンロード方法)。
カスタム フォーマッタを使用するタイミング
カスタム フォーマッタを使用して、組み込みのフォーマッタによって処理されていないコンテンツ タイプのサポートを追加します。
カスタム フォーマッタを作成する方法の概要
カスタム フォーマッタ クラスを作成するには:
- クライアントに送信されるデータをシリアル化するには、出力フォーマッタ クラスを作成します。
- クライアントから受信したデータを逆シリアル化するには、入力フォーマッタ クラスを作成します。
- フォーマッタ クラスのインスタンスを MvcOptions 内の InputFormatters および OutputFormatters コレクションに追加します。
カスタム フォーマッタを作成する
フォーマッタを作成する場合は、次のようにします。
- クラスを適切な基底クラスから派生させます。 サンプル アプリは、TextOutputFormatter と TextInputFormatter から派生します。
- サポートされるメディアの種類とエンコードをコンストラクターで指定します。
- CanReadType および CanWriteType メソッドをオーバーライドします。
- ReadRequestBodyAsync および WriteResponseBodyAsync メソッドをオーバーライドします。
次のコードは、サンプルの VcardOutputFormatter
クラスを示しています。
public class VcardOutputFormatter : TextOutputFormatter
{
public VcardOutputFormatter()
{
SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("text/vcard"));
SupportedEncodings.Add(Encoding.UTF8);
SupportedEncodings.Add(Encoding.Unicode);
}
protected override bool CanWriteType(Type? type)
=> typeof(Contact).IsAssignableFrom(type)
|| typeof(IEnumerable<Contact>).IsAssignableFrom(type);
public override async Task WriteResponseBodyAsync(
OutputFormatterWriteContext context, Encoding selectedEncoding)
{
var httpContext = context.HttpContext;
var serviceProvider = httpContext.RequestServices;
var logger = serviceProvider.GetRequiredService<ILogger<VcardOutputFormatter>>();
var buffer = new StringBuilder();
if (context.Object is IEnumerable<Contact> contacts)
{
foreach (var contact in contacts)
{
FormatVcard(buffer, contact, logger);
}
}
else
{
FormatVcard(buffer, (Contact)context.Object!, logger);
}
await httpContext.Response.WriteAsync(buffer.ToString(), selectedEncoding);
}
private static void FormatVcard(
StringBuilder buffer, Contact contact, ILogger logger)
{
buffer.AppendLine("BEGIN:VCARD");
buffer.AppendLine("VERSION:2.1");
buffer.AppendLine($"N:{contact.LastName};{contact.FirstName}");
buffer.AppendLine($"FN:{contact.FirstName} {contact.LastName}");
buffer.AppendLine($"UID:{contact.Id}");
buffer.AppendLine("END:VCARD");
logger.LogInformation("Writing {FirstName} {LastName}",
contact.FirstName, contact.LastName);
}
}
適切な基底クラスからの派生
メディアの種類がテキスト (vCard など) の場合は、TextInputFormatter または TextOutputFormatter 基底クラスから派生します。
public class VcardOutputFormatter : TextOutputFormatter
バイナリ型の場合は、InputFormatter または OutputFormatter 基底クラスから派生します。
サポートされるメディアの種類とエンコードの指定
コンストラクターで、SupportedMediaTypes および SupportedEncodings コレクションに追加することにより、サポートされるメディアの種類とエンコードを指定します。
public VcardOutputFormatter()
{
SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("text/vcard"));
SupportedEncodings.Add(Encoding.UTF8);
SupportedEncodings.Add(Encoding.Unicode);
}
フォーマッタ クラスは、その依存関係にコンストラクターの挿入を使用できません。 たとえば、ILogger<VcardOutputFormatter>
をパラメーターとしてコンストラクターに追加することはできません。 サービスにアクセスするには、メソッドに渡されるコンテキスト オブジェクトを使用します。 この記事のコード例とサンプルでは、これを行う方法を示します。
CanReadType と CanWriteType のオーバーライド
CanReadType または CanWriteType メソッドをオーバーライドして、逆シリアル化またはシリアル化する型を指定します。 たとえば、Contact
型から vCard テキストを作成したり、その逆を実行したりします。
protected override bool CanWriteType(Type? type)
=> typeof(Contact).IsAssignableFrom(type)
|| typeof(IEnumerable<Contact>).IsAssignableFrom(type);
CanWriteResult メソッド
一部のシナリオでは、CanWriteType ではなく CanWriteResult をオーバーライドする必要があります。 次のすべての条件が満たされている場合は、CanWriteResult
を使用します。
- アクション メソッドではモデル クラスが返されます。
- 実行時に返される可能性がある派生クラスがあります。
- アクションによって返される派生クラスは、実行時に既知である必要があります。
たとえば、アクション メソッドを想定します。
- シグネチャでは
Person
型が返されます。 Person
から派生したStudent
またはInstructor
を返すことができます。
フォーマッタで Student
オブジェクトのみを処理する場合は、CanWriteResult
メソッドに提供されたコンテキスト オブジェクト内で Object の種類を確認します。 アクション メソッドによって IActionResult が返される場合:
CanWriteResult
を使用する必要はありません。CanWriteType
メソッドはランタイム型を受け取ります。
ReadRequestBodyAsync と WriteResponseBodyAsync のオーバーライド
逆シリアル化またはシリアル化は、ReadRequestBodyAsync または WriteResponseBodyAsync で実行されます。 次の例は、依存関係挿入コンテナーからサービスを取得する方法を示しています。 サービスをコンストラクター パラメーターから取得することはできません。
public override async Task WriteResponseBodyAsync(
OutputFormatterWriteContext context, Encoding selectedEncoding)
{
var httpContext = context.HttpContext;
var serviceProvider = httpContext.RequestServices;
var logger = serviceProvider.GetRequiredService<ILogger<VcardOutputFormatter>>();
var buffer = new StringBuilder();
if (context.Object is IEnumerable<Contact> contacts)
{
foreach (var contact in contacts)
{
FormatVcard(buffer, contact, logger);
}
}
else
{
FormatVcard(buffer, (Contact)context.Object!, logger);
}
await httpContext.Response.WriteAsync(buffer.ToString(), selectedEncoding);
}
private static void FormatVcard(
StringBuilder buffer, Contact contact, ILogger logger)
{
buffer.AppendLine("BEGIN:VCARD");
buffer.AppendLine("VERSION:2.1");
buffer.AppendLine($"N:{contact.LastName};{contact.FirstName}");
buffer.AppendLine($"FN:{contact.FirstName} {contact.LastName}");
buffer.AppendLine($"UID:{contact.Id}");
buffer.AppendLine("END:VCARD");
logger.LogInformation("Writing {FirstName} {LastName}",
contact.FirstName, contact.LastName);
}
カスタム フォーマッタを使用するように MVC を構成する
カスタム フォーマッタを使用するには、MvcOptions.InputFormatters または MvcOptions.OutputFormatters コレクションにフォーマッタ クラスのインスタンスを追加します。
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers(options =>
{
options.InputFormatters.Insert(0, new VcardInputFormatter());
options.OutputFormatters.Insert(0, new VcardOutputFormatter());
});
フォーマッタは挿入された順序で評価され、最初のフォーマッタが優先されます。
完全な VcardInputFormatter
クラス
次のコードは、サンプルの VcardInputFormatter
クラスを示しています。
public class VcardInputFormatter : TextInputFormatter
{
public VcardInputFormatter()
{
SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("text/vcard"));
SupportedEncodings.Add(Encoding.UTF8);
SupportedEncodings.Add(Encoding.Unicode);
}
protected override bool CanReadType(Type type)
=> type == typeof(Contact);
public override async Task<InputFormatterResult> ReadRequestBodyAsync(
InputFormatterContext context, Encoding effectiveEncoding)
{
var httpContext = context.HttpContext;
var serviceProvider = httpContext.RequestServices;
var logger = serviceProvider.GetRequiredService<ILogger<VcardInputFormatter>>();
using var reader = new StreamReader(httpContext.Request.Body, effectiveEncoding);
string? nameLine = null;
try
{
await ReadLineAsync("BEGIN:VCARD", reader, context, logger);
await ReadLineAsync("VERSION:", reader, context, logger);
nameLine = await ReadLineAsync("N:", reader, context, logger);
var split = nameLine.Split(";".ToCharArray());
var contact = new Contact(FirstName: split[1], LastName: split[0].Substring(2));
await ReadLineAsync("FN:", reader, context, logger);
await ReadLineAsync("END:VCARD", reader, context, logger);
logger.LogInformation("nameLine = {nameLine}", nameLine);
return await InputFormatterResult.SuccessAsync(contact);
}
catch
{
logger.LogError("Read failed: nameLine = {nameLine}", nameLine);
return await InputFormatterResult.FailureAsync();
}
}
private static async Task<string> ReadLineAsync(
string expectedText, StreamReader reader, InputFormatterContext context,
ILogger logger)
{
var line = await reader.ReadLineAsync();
if (line is null || !line.StartsWith(expectedText))
{
var errorMessage = $"Looked for '{expectedText}' and got '{line}'";
context.ModelState.TryAddModelError(context.ModelName, errorMessage);
logger.LogError(errorMessage);
throw new Exception(errorMessage);
}
return line;
}
}
アプリをテストする
この記事用のサンプル アプリを実行します。これにより、基本的な vCard の入力および出力フォーマッタが実装されます。 アプリにより、次の形式に似た vCard の読み取りと書き込みが行われます。
BEGIN:VCARD
VERSION:2.1
N:Davolio;Nancy
FN:Nancy Davolio
END:VCARD
vCard 出力を表示するには、アプリを実行し、Accept ヘッダー text/vcard
を含む Get 要求を https://localhost:<port>/api/contacts
に送信します。
連絡先のメモリ内コレクションに vCard を追加するには:
- http-repl のようなツールを使用して
/api/contacts
にPost
要求を送信します。 Content-Type
ヘッダーをtext/vcard
に設定します。- 前の例のように書式設定された
vCard
テキストを本文に設定します。
その他のリソース
ASP.NET Core MVC は、入力と出力のフォーマッタを使用した Web API でのデータ交換をサポートしています。 入力フォーマッタは、モデル バインドによって使用されます。 出力フォーマッタは、応答の書式設定に使用されます。
フレームワークには、JSON および XML 用の組み込みの入力および出力フォーマッタが用意されています。 プレーン テキスト用の組み込みの出力フォーマッタが用意されていますが、プレーン テキスト用の入力フォーマッタは用意されていません。
この記事では、カスタム フォーマッタを作成して、追加形式のサポートを追加する方法を示します。 カスタムのプレーン テキスト入力フォーマッタの例については、GitHub の TextPlainInputFormatter を参照してください。
サンプル コードを表示またはダウンロードします (ダウンロード方法)。
カスタム フォーマッタを使用するタイミング
カスタム フォーマッタを使用して、組み込みのフォーマッタによって処理されていないコンテンツ タイプのサポートを追加します。
カスタム フォーマッタを作成する方法の概要
カスタム フォーマッタ クラスを作成するには:
- クライアントに送信されるデータをシリアル化するには、出力フォーマッタ クラスを作成します。
- クライアントから受信したデータを逆シリアル化するには、入力フォーマッタ クラスを作成します。
- フォーマッタ クラスのインスタンスを MvcOptions 内の InputFormatters および OutputFormatters コレクションに追加します。
カスタム フォーマッタを作成する
フォーマッタを作成する場合は、次のようにします。
- クラスを適切な基底クラスから派生させます。 サンプル アプリは、TextOutputFormatter と TextInputFormatter から派生します。
- サポートされるメディアの種類とエンコードをコンストラクターで指定します。
- CanReadType および CanWriteType メソッドをオーバーライドします。
- ReadRequestBodyAsync および WriteResponseBodyAsync メソッドをオーバーライドします。
次のコードは、サンプルの VcardOutputFormatter
クラスを示しています。
public class VcardOutputFormatter : TextOutputFormatter
{
public VcardOutputFormatter()
{
SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("text/vcard"));
SupportedEncodings.Add(Encoding.UTF8);
SupportedEncodings.Add(Encoding.Unicode);
}
protected override bool CanWriteType(Type type)
{
return typeof(Contact).IsAssignableFrom(type) ||
typeof(IEnumerable<Contact>).IsAssignableFrom(type);
}
public override async Task WriteResponseBodyAsync(
OutputFormatterWriteContext context, Encoding selectedEncoding)
{
var httpContext = context.HttpContext;
var serviceProvider = httpContext.RequestServices;
var logger = serviceProvider.GetRequiredService<ILogger<VcardOutputFormatter>>();
var buffer = new StringBuilder();
if (context.Object is IEnumerable<Contact> contacts)
{
foreach (var contact in contacts)
{
FormatVcard(buffer, contact, logger);
}
}
else
{
FormatVcard(buffer, (Contact)context.Object, logger);
}
await httpContext.Response.WriteAsync(buffer.ToString(), selectedEncoding);
}
private static void FormatVcard(
StringBuilder buffer, Contact contact, ILogger logger)
{
buffer.AppendLine("BEGIN:VCARD");
buffer.AppendLine("VERSION:2.1");
buffer.AppendLine($"N:{contact.LastName};{contact.FirstName}");
buffer.AppendLine($"FN:{contact.FirstName} {contact.LastName}");
buffer.AppendLine($"UID:{contact.Id}");
buffer.AppendLine("END:VCARD");
logger.LogInformation("Writing {FirstName} {LastName}",
contact.FirstName, contact.LastName);
}
}
適切な基底クラスからの派生
メディアの種類がテキスト (vCard など) の場合は、TextInputFormatter または TextOutputFormatter 基底クラスから派生します。
public class VcardOutputFormatter : TextOutputFormatter
バイナリ型の場合は、InputFormatter または OutputFormatter 基底クラスから派生します。
サポートされるメディアの種類とエンコードの指定
コンストラクターで、SupportedMediaTypes および SupportedEncodings コレクションに追加することにより、サポートされるメディアの種類とエンコードを指定します。
public VcardOutputFormatter()
{
SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("text/vcard"));
SupportedEncodings.Add(Encoding.UTF8);
SupportedEncodings.Add(Encoding.Unicode);
}
フォーマッタ クラスは、その依存関係にコンストラクターの挿入を使用できません。 たとえば、ILogger<VcardOutputFormatter>
をパラメーターとしてコンストラクターに追加することはできません。 サービスにアクセスするには、メソッドに渡されるコンテキスト オブジェクトを使用します。 この記事のコード例とサンプルでは、これを行う方法を示します。
CanReadType と CanWriteType のオーバーライド
CanReadType または CanWriteType メソッドをオーバーライドして、逆シリアル化またはシリアル化する型を指定します。 たとえば、Contact
型から vCard テキストを作成したり、その逆を実行したりします。
protected override bool CanWriteType(Type type)
{
return typeof(Contact).IsAssignableFrom(type) ||
typeof(IEnumerable<Contact>).IsAssignableFrom(type);
}
CanWriteResult メソッド
一部のシナリオでは、CanWriteType ではなく CanWriteResult をオーバーライドする必要があります。 次のすべての条件が満たされている場合は、CanWriteResult
を使用します。
- アクション メソッドではモデル クラスが返されます。
- 実行時に返される可能性がある派生クラスがあります。
- アクションによって返される派生クラスは、実行時に既知である必要があります。
たとえば、アクション メソッドを想定します。
- シグネチャでは
Person
型が返されます。 Person
から派生したStudent
またはInstructor
を返すことができます。
フォーマッタで Student
オブジェクトのみを処理する場合は、CanWriteResult
メソッドに提供されたコンテキスト オブジェクト内で Object の種類を確認します。 アクション メソッドによって IActionResult が返される場合:
CanWriteResult
を使用する必要はありません。CanWriteType
メソッドはランタイム型を受け取ります。
ReadRequestBodyAsync と WriteResponseBodyAsync のオーバーライド
逆シリアル化またはシリアル化は、ReadRequestBodyAsync または WriteResponseBodyAsync で実行されます。 次の例は、依存関係挿入コンテナーからサービスを取得する方法を示しています。 サービスをコンストラクター パラメーターから取得することはできません。
public override async Task WriteResponseBodyAsync(
OutputFormatterWriteContext context, Encoding selectedEncoding)
{
var httpContext = context.HttpContext;
var serviceProvider = httpContext.RequestServices;
var logger = serviceProvider.GetRequiredService<ILogger<VcardOutputFormatter>>();
var buffer = new StringBuilder();
if (context.Object is IEnumerable<Contact> contacts)
{
foreach (var contact in contacts)
{
FormatVcard(buffer, contact, logger);
}
}
else
{
FormatVcard(buffer, (Contact)context.Object, logger);
}
await httpContext.Response.WriteAsync(buffer.ToString(), selectedEncoding);
}
private static void FormatVcard(
StringBuilder buffer, Contact contact, ILogger logger)
{
buffer.AppendLine("BEGIN:VCARD");
buffer.AppendLine("VERSION:2.1");
buffer.AppendLine($"N:{contact.LastName};{contact.FirstName}");
buffer.AppendLine($"FN:{contact.FirstName} {contact.LastName}");
buffer.AppendLine($"UID:{contact.Id}");
buffer.AppendLine("END:VCARD");
logger.LogInformation("Writing {FirstName} {LastName}",
contact.FirstName, contact.LastName);
}
カスタム フォーマッタを使用するように MVC を構成する
カスタム フォーマッタを使用するには、MvcOptions.InputFormatters または MvcOptions.OutputFormatters コレクションにフォーマッタ クラスのインスタンスを追加します。
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers(options =>
{
options.InputFormatters.Insert(0, new VcardInputFormatter());
options.OutputFormatters.Insert(0, new VcardOutputFormatter());
});
}
フォーマッタは、挿入した順序で評価されます。 最初のものが優先されます。
完全な VcardInputFormatter
クラス
次のコードは、サンプルの VcardInputFormatter
クラスを示しています。
public class VcardInputFormatter : TextInputFormatter
{
public VcardInputFormatter()
{
SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("text/vcard"));
SupportedEncodings.Add(Encoding.UTF8);
SupportedEncodings.Add(Encoding.Unicode);
}
protected override bool CanReadType(Type type)
{
return type == typeof(Contact);
}
public override async Task<InputFormatterResult> ReadRequestBodyAsync(
InputFormatterContext context, Encoding effectiveEncoding)
{
var httpContext = context.HttpContext;
var serviceProvider = httpContext.RequestServices;
var logger = serviceProvider.GetRequiredService<ILogger<VcardInputFormatter>>();
using var reader = new StreamReader(httpContext.Request.Body, effectiveEncoding);
string nameLine = null;
try
{
await ReadLineAsync("BEGIN:VCARD", reader, context, logger);
await ReadLineAsync("VERSION:", reader, context, logger);
nameLine = await ReadLineAsync("N:", reader, context, logger);
var split = nameLine.Split(";".ToCharArray());
var contact = new Contact
{
LastName = split[0].Substring(2),
FirstName = split[1]
};
await ReadLineAsync("FN:", reader, context, logger);
await ReadLineAsync("END:VCARD", reader, context, logger);
logger.LogInformation("nameLine = {nameLine}", nameLine);
return await InputFormatterResult.SuccessAsync(contact);
}
catch
{
logger.LogError("Read failed: nameLine = {nameLine}", nameLine);
return await InputFormatterResult.FailureAsync();
}
}
private static async Task<string> ReadLineAsync(
string expectedText, StreamReader reader, InputFormatterContext context,
ILogger logger)
{
var line = await reader.ReadLineAsync();
if (!line.StartsWith(expectedText))
{
var errorMessage = $"Looked for '{expectedText}' and got '{line}'";
context.ModelState.TryAddModelError(context.ModelName, errorMessage);
logger.LogError(errorMessage);
throw new Exception(errorMessage);
}
return line;
}
}
アプリをテストする
この記事用のサンプル アプリを実行します。これにより、基本的な vCard の入力および出力フォーマッタが実装されます。 アプリにより、次の形式に似た vCard の読み取りと書き込みが行われます。
BEGIN:VCARD
VERSION:2.1
N:Davolio;Nancy
FN:Nancy Davolio
END:VCARD
vCard 出力を表示するには、アプリを実行し、Accept ヘッダー text/vcard
を含む Get 要求を https://localhost:5001/api/contacts
に送信します。
連絡先のメモリ内コレクションに vCard を追加するには:
- curl のようなツールを使用して
/api/contacts
にPost
要求を送信します。 Content-Type
ヘッダーをtext/vcard
に設定します。- 前の例のように書式設定された
vCard
テキストを本文に設定します。
その他のリソース
ASP.NET Core