Pemformat kustom di ASP.NET Core Web API
ASP.NET Core MVC mendukung pertukaran data di API Web menggunakan pemformat input dan output. Pemformat input digunakan oleh Pengikatan Model. Formatter output digunakan untuk memformat respons.
Kerangka kerja ini menyediakan pemformat input dan output bawaan untuk JSON dan XML. Ini menyediakan formatter output bawaan untuk teks biasa, tetapi tidak menyediakan formatter input untuk teks biasa.
Artikel ini memperlihatkan cara menambahkan dukungan untuk format tambahan dengan membuat pemformat kustom. Untuk contoh pemformat input teks biasa kustom, lihat TextPlainInputFormatter di GitHub.
Melihat atau mengunduh kode sampel (cara mengunduh)
Kapan menggunakan pemformat kustom
Gunakan formatter kustom untuk menambahkan dukungan untuk tipe konten yang tidak ditangani oleh pemformat bawaan.
Gambaran umum cara membuat pemformat kustom
Untuk membuat formatter kustom:
- Untuk menserialisasikan data yang dikirim ke klien, buat kelas pemformat output.
- Untuk deserialisasi data yang diterima dari klien, buat kelas pemformat input.
- Tambahkan instans kelas formatter ke InputFormatters koleksi dan OutputFormatters di MvcOptions.
Membuat pemformat kustom
Untuk membuat pemformat:
- Dapatkan kelas dari kelas dasar yang sesuai. Aplikasi sampel berasal dari TextOutputFormatter dan TextInputFormatter.
- Tentukan jenis media dan pengodean yang didukung di konstruktor.
- Ganti metode CanReadType dan CanWriteType.
- Ganti metode ReadRequestBodyAsync dan WriteResponseBodyAsync.
Kode berikut menunjukkan VcardOutputFormatter
kelas dari sampel:
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);
}
}
Berasal dari kelas dasar yang sesuai
Untuk jenis media teks (misalnya, vCard), berasal dari TextInputFormatter kelas dasar atau TextOutputFormatter :
public class VcardOutputFormatter : TextOutputFormatter
Untuk jenis biner, berasal dari InputFormatter kelas atau OutputFormatter dasar.
Tentukan jenis media dan pengodean yang didukung
Di konstruktor, tentukan jenis media dan pengodean yang didukung dengan menambahkan ke SupportedMediaTypes koleksi dan SupportedEncodings :
public VcardOutputFormatter()
{
SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("text/vcard"));
SupportedEncodings.Add(Encoding.UTF8);
SupportedEncodings.Add(Encoding.Unicode);
}
Kelas formatter tidak dapat menggunakan injeksi konstruktor untuk dependensinya. Misalnya, ILogger<VcardOutputFormatter>
tidak dapat ditambahkan sebagai parameter ke konstruktor. Untuk mengakses layanan, gunakan objek konteks yang diteruskan ke metode . Contoh kode dalam artikel ini dan sampel menunjukkan cara melakukan ini.
Mengganti CanReadType dan CanWriteType
Tentukan jenis yang akan dideserialisasi ke dalam atau serialisasi dengan mengambil CanReadType alih metode atau CanWriteType . Misalnya, untuk membuat teks vCard dari Contact
jenis dan sebaliknya:
protected override bool CanWriteType(Type? type)
=> typeof(Contact).IsAssignableFrom(type)
|| typeof(IEnumerable<Contact>).IsAssignableFrom(type);
Metode CanWriteResult
Dalam beberapa skenario, CanWriteResult harus ditimpa daripada CanWriteType. Gunakan CanWriteResult
jika kondisi berikut ini benar:
- Metode tindakan mengembalikan kelas model.
- Ada kelas turunan yang mungkin dikembalikan pada runtime.
- Kelas turunan yang dikembalikan oleh tindakan harus diketahui saat runtime.
Misalnya, misalkan metode tindakan:
- Tanda tangan mengembalikan
Person
jenis. - Dapat mengembalikan
Student
atauInstructor
jenis yang berasal dariPerson
.
Agar pemformat hanya Student
menangani objek, periksa jenis Object dalam objek konteks yang disediakan untuk CanWriteResult
metode . Ketika metode tindakan mengembalikan IActionResult:
- Tidak perlu menggunakan
CanWriteResult
. - Metode
CanWriteType
menerima jenis runtime.
Mengambil alih ReadRequestBodyAsync dan WriteResponseBodyAsync
Deserialisasi atau serialisasi dilakukan di ReadRequestBodyAsync atau WriteResponseBodyAsync. Contoh berikut menunjukkan cara mendapatkan layanan dari kontainer injeksi dependensi. Layanan tidak dapat diperoleh dari parameter konstruktor:
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);
}
Mengonfigurasi MVC untuk menggunakan pemformat kustom
Untuk menggunakan formatter kustom, tambahkan instans kelas formatter ke MvcOptions.InputFormatters koleksi atau MvcOptions.OutputFormatters :
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers(options =>
{
options.InputFormatters.Insert(0, new VcardInputFormatter());
options.OutputFormatters.Insert(0, new VcardOutputFormatter());
});
Formatter dievaluasi dalam urutan yang disisipkan, di mana yang pertama diutamakan.
Kelas lengkap VcardInputFormatter
Kode berikut menunjukkan VcardInputFormatter
kelas dari sampel:
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;
}
}
Menguji aplikasi
Jalankan aplikasi sampel untuk artikel ini, yang mengimplementasikan pemformat input dan output vCard dasar. Aplikasi membaca dan menulis vCard yang mirip dengan format berikut:
BEGIN:VCARD
VERSION:2.1
N:Davolio;Nancy
FN:Nancy Davolio
END:VCARD
Untuk melihat output vCard, jalankan aplikasi dan kirim permintaan Dapatkan dengan header text/vcard
Terima ke https://localhost:<port>/api/contacts
.
Untuk menambahkan vCard ke kumpulan kontak dalam memori:
Post
Kirim permintaan ke/api/contacts
dengan alat seperti http-repl.- Atur header
Content-Type
ketext/vcard
. - Atur
vCard
teks dalam isi, diformat seperti contoh sebelumnya.
Sumber Daya Tambahan:
ASP.NET Core MVC mendukung pertukaran data di API Web menggunakan pemformat input dan output. Pemformat input digunakan oleh Pengikatan Model. Formatter output digunakan untuk memformat respons.
Kerangka kerja ini menyediakan pemformat input dan output bawaan untuk JSON dan XML. Ini menyediakan formatter output bawaan untuk teks biasa, tetapi tidak menyediakan formatter input untuk teks biasa.
Artikel ini memperlihatkan cara menambahkan dukungan untuk format tambahan dengan membuat pemformat kustom. Untuk contoh pemformat input teks biasa kustom, lihat TextPlainInputFormatter di GitHub.
Melihat atau mengunduh kode sampel (cara mengunduh)
Kapan menggunakan pemformat kustom
Gunakan formatter kustom untuk menambahkan dukungan untuk tipe konten yang tidak ditangani oleh pemformat bawaan.
Gambaran umum cara membuat pemformat kustom
Untuk membuat formatter kustom:
- Untuk menserialisasikan data yang dikirim ke klien, buat kelas pemformat output.
- Untuk deserialisasi data yang diterima dari klien, buat kelas pemformat input.
- Tambahkan instans kelas formatter ke InputFormatters koleksi dan OutputFormatters di MvcOptions.
Membuat pemformat kustom
Untuk membuat pemformat:
- Dapatkan kelas dari kelas dasar yang sesuai. Aplikasi sampel berasal dari TextOutputFormatter dan TextInputFormatter.
- Tentukan jenis media dan pengodean yang didukung di konstruktor.
- Ganti metode CanReadType dan CanWriteType.
- Ganti metode ReadRequestBodyAsync dan WriteResponseBodyAsync.
Kode berikut menunjukkan VcardOutputFormatter
kelas dari sampel:
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);
}
}
Berasal dari kelas dasar yang sesuai
Untuk jenis media teks (misalnya, vCard), berasal dari TextInputFormatter kelas dasar atau TextOutputFormatter :
public class VcardOutputFormatter : TextOutputFormatter
Untuk jenis biner, berasal dari InputFormatter kelas atau OutputFormatter dasar.
Tentukan jenis media dan pengodean yang didukung
Di konstruktor, tentukan jenis media dan pengodean yang didukung dengan menambahkan ke SupportedMediaTypes koleksi dan SupportedEncodings :
public VcardOutputFormatter()
{
SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("text/vcard"));
SupportedEncodings.Add(Encoding.UTF8);
SupportedEncodings.Add(Encoding.Unicode);
}
Kelas formatter tidak dapat menggunakan injeksi konstruktor untuk dependensinya. Misalnya, ILogger<VcardOutputFormatter>
tidak dapat ditambahkan sebagai parameter ke konstruktor. Untuk mengakses layanan, gunakan objek konteks yang diteruskan ke metode . Contoh kode dalam artikel ini dan sampel menunjukkan cara melakukan ini.
Mengganti CanReadType dan CanWriteType
Tentukan jenis yang akan dideserialisasi ke dalam atau serialisasi dengan mengambil CanReadType alih metode atau CanWriteType . Misalnya, untuk membuat teks vCard dari Contact
jenis dan sebaliknya:
protected override bool CanWriteType(Type type)
{
return typeof(Contact).IsAssignableFrom(type) ||
typeof(IEnumerable<Contact>).IsAssignableFrom(type);
}
Metode CanWriteResult
Dalam beberapa skenario, CanWriteResult harus ditimpa daripada CanWriteType. Gunakan CanWriteResult
jika kondisi berikut ini benar:
- Metode tindakan mengembalikan kelas model.
- Ada kelas turunan yang mungkin dikembalikan pada runtime.
- Kelas turunan yang dikembalikan oleh tindakan harus diketahui saat runtime.
Misalnya, misalkan metode tindakan:
- Tanda tangan mengembalikan
Person
jenis. - Dapat mengembalikan
Student
atauInstructor
jenis yang berasal dariPerson
.
Agar pemformat hanya Student
menangani objek, periksa jenis Object dalam objek konteks yang disediakan untuk CanWriteResult
metode . Ketika metode tindakan mengembalikan IActionResult:
- Tidak perlu menggunakan
CanWriteResult
. - Metode
CanWriteType
menerima jenis runtime.
Mengambil alih ReadRequestBodyAsync dan WriteResponseBodyAsync
Deserialisasi atau serialisasi dilakukan di ReadRequestBodyAsync atau WriteResponseBodyAsync. Contoh berikut menunjukkan cara mendapatkan layanan dari kontainer injeksi dependensi. Layanan tidak dapat diperoleh dari parameter konstruktor:
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);
}
Mengonfigurasi MVC untuk menggunakan pemformat kustom
Untuk menggunakan formatter kustom, tambahkan instans kelas formatter ke MvcOptions.InputFormatters koleksi atau MvcOptions.OutputFormatters :
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers(options =>
{
options.InputFormatters.Insert(0, new VcardInputFormatter());
options.OutputFormatters.Insert(0, new VcardOutputFormatter());
});
}
Pemformat dievaluasi dalam urutan Anda menyisipkannya. Yang pertama lebih diutamakan.
Kelas lengkap VcardInputFormatter
Kode berikut menunjukkan VcardInputFormatter
kelas dari sampel:
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;
}
}
Menguji aplikasi
Jalankan aplikasi sampel untuk artikel ini, yang mengimplementasikan pemformat input dan output vCard dasar. Aplikasi membaca dan menulis vCard yang mirip dengan format berikut:
BEGIN:VCARD
VERSION:2.1
N:Davolio;Nancy
FN:Nancy Davolio
END:VCARD
Untuk melihat output vCard, jalankan aplikasi dan kirim permintaan Dapatkan dengan header text/vcard
Terima ke https://localhost:5001/api/contacts
.
Untuk menambahkan vCard ke kumpulan kontak dalam memori:
Post
Kirim permintaan ke/api/contacts
dengan alat seperti curl.- Atur header
Content-Type
ketext/vcard
. - Atur
vCard
teks dalam isi, diformat seperti contoh sebelumnya.
Sumber Daya Tambahan:
ASP.NET Core