Niestandardowe elementy formatujące w internetowym interfejsie API platformy ASP.NET Core
ASP.NET Core MVC obsługuje wymianę danych w internetowych interfejsach API przy użyciu formatów wejściowych i wyjściowych. Formatery danych wejściowych są używane przez powiązanie modelu. Formatery danych wyjściowych służą do formatowania odpowiedzi.
Platforma udostępnia wbudowane formatery wejściowe i wyjściowe dla formatu JSON i XML. Udostępnia wbudowany formater danych wyjściowych dla zwykłego tekstu, ale nie udostępnia formatera wejściowego zwykłego tekstu.
W tym artykule pokazano, jak dodać obsługę dodatkowych formatów, tworząc niestandardowe formatery. Aby zapoznać się z przykładem niestandardowego formatowania danych wejściowych w postaci zwykłego tekstu, zobacz TextPlainInputFormatter w witrynie GitHub.
Wyświetl lub pobierz przykładowy kod (jak pobrać)
Kiedy używać niestandardowego programu formatującego
Użyj niestandardowego formatera, aby dodać obsługę typu zawartości, który nie jest obsługiwany przez wbudowane formatery.
Omówienie tworzenia niestandardowego modułu formatującego
Aby utworzyć niestandardowy formater:
- W przypadku serializacji danych wysyłanych do klienta utwórz klasę formatera danych wyjściowych.
- W przypadku deserializacji danych odebranych od klienta utwórz klasę formatatora danych wejściowych.
- Dodaj wystąpienia klas formaterów do InputFormatters kolekcji i OutputFormatters w pliku MvcOptions.
Tworzenie niestandardowego programu formatującego
Aby utworzyć program formatujący:
- Utwórz klasę z odpowiedniej klasy bazowej. Przykładowa aplikacja pochodzi z i TextOutputFormatter TextInputFormatter.
- Określ obsługiwane typy i kodowanie multimediów w konstruktorze.
- Zastąpij CanReadType metody i CanWriteType .
- Zastąpij ReadRequestBodyAsync metody i WriteResponseBodyAsync .
Poniższy kod przedstawia klasę VcardOutputFormatter
z przykładu:
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);
}
}
Pochodne od odpowiedniej klasy bazowej
W przypadku typów multimediów tekstowych (na przykład vCard) pochodzą z TextInputFormatter klasy podstawowej lub TextOutputFormatter :
public class VcardOutputFormatter : TextOutputFormatter
W przypadku typów binarnych pochodzą z klasy bazowej InputFormatter lub OutputFormatter .
Określanie obsługiwanych typów i kodowań multimediów
W konstruktorze określ obsługiwane typy i kodowania multimediów, dodając do SupportedMediaTypes kolekcji i SupportedEncodings :
public VcardOutputFormatter()
{
SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("text/vcard"));
SupportedEncodings.Add(Encoding.UTF8);
SupportedEncodings.Add(Encoding.Unicode);
}
Klasa formatatora nie może używać iniekcji konstruktora dla jego zależności. Na przykład ILogger<VcardOutputFormatter>
nie można dodać go jako parametru do konstruktora. Aby uzyskać dostęp do usług, użyj obiektu kontekstu, który jest przekazywany do metod. Przykładowy kod w tym artykule i przykład pokazujący, jak to zrobić.
Zastąp wartości CanReadType i CanWriteType
Określ typ do deserializacji do lub serializacji z, przesłaniając CanReadType metody lub CanWriteType . Na przykład aby utworzyć tekst vCard na podstawie Contact
typu i na odwrót:
protected override bool CanWriteType(Type? type)
=> typeof(Contact).IsAssignableFrom(type)
|| typeof(IEnumerable<Contact>).IsAssignableFrom(type);
Metoda CanWriteResult
W niektórych scenariuszach CanWriteResult należy zastąpić zamiast CanWriteType. Użyj CanWriteResult
, jeśli spełnione są następujące warunki:
- Metoda akcji zwraca klasę modelu.
- Istnieją klasy pochodne, które mogą być zwracane w czasie wykonywania.
- Klasa pochodna zwracana przez akcję musi być znana w czasie wykonywania.
Załóżmy na przykład, że metoda akcji:
- Podpis zwraca
Person
typ. - Może zwrócić typ
Student
lubInstructor
pochodzący z klasyPerson
.
Aby program formatujący obsługiwał tylko Student
obiekty, sprawdź typ Object obiektu kontekstu dostarczonego do CanWriteResult
metody . Gdy metoda akcji zwraca wartość IActionResult:
- Nie jest konieczne użycie polecenia
CanWriteResult
. - Metoda
CanWriteType
odbiera typ środowiska uruchomieniowego.
Zastąpij metodę ReadRequestBodyAsync i WriteResponseBodyAsync
Deserializacja lub serializacja jest wykonywana w programie ReadRequestBodyAsync lub WriteResponseBodyAsync. W poniższym przykładzie pokazano, jak pobrać usługi z kontenera wstrzykiwania zależności. Nie można uzyskać usług z parametrów konstruktora:
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);
}
Konfigurowanie wzorca MVC do używania niestandardowego formatowania
Aby użyć niestandardowego formatatora, dodaj wystąpienie klasy formatatora MvcOptions.InputFormatters do kolekcji lub MvcOptions.OutputFormatters :
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers(options =>
{
options.InputFormatters.Insert(0, new VcardInputFormatter());
options.OutputFormatters.Insert(0, new VcardOutputFormatter());
});
Formatery są oceniane w kolejności, w której pierwszy ma pierwszeństwo.
Kompletna VcardInputFormatter
klasa
Poniższy kod przedstawia klasę VcardInputFormatter
z przykładu:
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;
}
}
Testowanie aplikacji
Uruchom przykładową aplikację dla tego artykułu, która implementuje podstawowe formatery danych wejściowych i wyjściowych karty wirtualnej. Aplikacja odczytuje i zapisuje karty wirtualne podobne do następującego formatu:
BEGIN:VCARD
VERSION:2.1
N:Davolio;Nancy
FN:Nancy Davolio
END:VCARD
Aby wyświetlić dane wyjściowe karty vCard, uruchom aplikację i wyślij żądanie Get z nagłówkiem text/vcard
Accept do .https://localhost:<port>/api/contacts
Aby dodać kartę vCard do kolekcji kontaktów w pamięci:
Post
Wyślij żądanie do/api/contacts
przy użyciu narzędzia, takiego jak http-repl.Content-Type
Ustaw nagłówek natext/vcard
.- Ustaw
vCard
tekst w treści, sformatowany tak jak w poprzednim przykładzie.
Dodatkowe zasoby
ASP.NET Core MVC obsługuje wymianę danych w internetowych interfejsach API przy użyciu formatów wejściowych i wyjściowych. Formatery danych wejściowych są używane przez powiązanie modelu. Formatery danych wyjściowych służą do formatowania odpowiedzi.
Platforma udostępnia wbudowane formatery wejściowe i wyjściowe dla formatu JSON i XML. Udostępnia wbudowany formater danych wyjściowych dla zwykłego tekstu, ale nie udostępnia formatera wejściowego zwykłego tekstu.
W tym artykule pokazano, jak dodać obsługę dodatkowych formatów, tworząc niestandardowe formatery. Aby zapoznać się z przykładem niestandardowego formatowania danych wejściowych w postaci zwykłego tekstu, zobacz TextPlainInputFormatter w witrynie GitHub.
Wyświetl lub pobierz przykładowy kod (jak pobrać)
Kiedy używać niestandardowego programu formatującego
Użyj niestandardowego formatera, aby dodać obsługę typu zawartości, który nie jest obsługiwany przez wbudowane formatery.
Omówienie tworzenia niestandardowego modułu formatującego
Aby utworzyć niestandardowy formater:
- W przypadku serializacji danych wysyłanych do klienta utwórz klasę formatera danych wyjściowych.
- W przypadku deserializacji danych odebranych od klienta utwórz klasę formatatora danych wejściowych.
- Dodaj wystąpienia klas formaterów do InputFormatters kolekcji i OutputFormatters w pliku MvcOptions.
Tworzenie niestandardowego programu formatującego
Aby utworzyć program formatujący:
- Utwórz klasę z odpowiedniej klasy bazowej. Przykładowa aplikacja pochodzi z i TextOutputFormatter TextInputFormatter.
- Określ obsługiwane typy i kodowanie multimediów w konstruktorze.
- Zastąpij CanReadType metody i CanWriteType .
- Zastąpij ReadRequestBodyAsync metody i WriteResponseBodyAsync .
Poniższy kod przedstawia klasę VcardOutputFormatter
z przykładu:
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);
}
}
Pochodne od odpowiedniej klasy bazowej
W przypadku typów multimediów tekstowych (na przykład vCard) pochodzą z TextInputFormatter klasy podstawowej lub TextOutputFormatter :
public class VcardOutputFormatter : TextOutputFormatter
W przypadku typów binarnych pochodzą z klasy bazowej InputFormatter lub OutputFormatter .
Określanie obsługiwanych typów i kodowań multimediów
W konstruktorze określ obsługiwane typy i kodowania multimediów, dodając do SupportedMediaTypes kolekcji i SupportedEncodings :
public VcardOutputFormatter()
{
SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("text/vcard"));
SupportedEncodings.Add(Encoding.UTF8);
SupportedEncodings.Add(Encoding.Unicode);
}
Klasa formatatora nie może używać iniekcji konstruktora dla jego zależności. Na przykład ILogger<VcardOutputFormatter>
nie można dodać go jako parametru do konstruktora. Aby uzyskać dostęp do usług, użyj obiektu kontekstu, który jest przekazywany do metod. Przykładowy kod w tym artykule i przykład pokazujący, jak to zrobić.
Zastąp wartości CanReadType i CanWriteType
Określ typ do deserializacji do lub serializacji z, przesłaniając CanReadType metody lub CanWriteType . Na przykład aby utworzyć tekst vCard na podstawie Contact
typu i na odwrót:
protected override bool CanWriteType(Type type)
{
return typeof(Contact).IsAssignableFrom(type) ||
typeof(IEnumerable<Contact>).IsAssignableFrom(type);
}
Metoda CanWriteResult
W niektórych scenariuszach CanWriteResult należy zastąpić zamiast CanWriteType. Użyj CanWriteResult
, jeśli spełnione są następujące warunki:
- Metoda akcji zwraca klasę modelu.
- Istnieją klasy pochodne, które mogą być zwracane w czasie wykonywania.
- Klasa pochodna zwracana przez akcję musi być znana w czasie wykonywania.
Załóżmy na przykład, że metoda akcji:
- Podpis zwraca
Person
typ. - Może zwrócić typ
Student
lubInstructor
pochodzący z klasyPerson
.
Aby program formatujący obsługiwał tylko Student
obiekty, sprawdź typ Object obiektu kontekstu dostarczonego do CanWriteResult
metody . Gdy metoda akcji zwraca wartość IActionResult:
- Nie jest konieczne użycie polecenia
CanWriteResult
. - Metoda
CanWriteType
odbiera typ środowiska uruchomieniowego.
Zastąpij metodę ReadRequestBodyAsync i WriteResponseBodyAsync
Deserializacja lub serializacja jest wykonywana w programie ReadRequestBodyAsync lub WriteResponseBodyAsync. W poniższym przykładzie pokazano, jak pobrać usługi z kontenera wstrzykiwania zależności. Nie można uzyskać usług z parametrów konstruktora:
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);
}
Konfigurowanie wzorca MVC do używania niestandardowego formatowania
Aby użyć niestandardowego formatatora, dodaj wystąpienie klasy formatatora MvcOptions.InputFormatters do kolekcji lub MvcOptions.OutputFormatters :
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers(options =>
{
options.InputFormatters.Insert(0, new VcardInputFormatter());
options.OutputFormatters.Insert(0, new VcardOutputFormatter());
});
}
Formatery są oceniane w kolejności ich wstawiania. Pierwszy z nich ma pierwszeństwo.
Kompletna VcardInputFormatter
klasa
Poniższy kod przedstawia klasę VcardInputFormatter
z przykładu:
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;
}
}
Testowanie aplikacji
Uruchom przykładową aplikację dla tego artykułu, która implementuje podstawowe formatery danych wejściowych i wyjściowych karty wirtualnej. Aplikacja odczytuje i zapisuje karty wirtualne podobne do następującego formatu:
BEGIN:VCARD
VERSION:2.1
N:Davolio;Nancy
FN:Nancy Davolio
END:VCARD
Aby wyświetlić dane wyjściowe karty vCard, uruchom aplikację i wyślij żądanie Get z nagłówkiem text/vcard
Accept do .https://localhost:5001/api/contacts
Aby dodać kartę vCard do kolekcji kontaktów w pamięci:
Post
Wyślij żądanie do/api/contacts
za pomocą narzędzia, takiego jak curl.Content-Type
Ustaw nagłówek natext/vcard
.- Ustaw
vCard
tekst w treści, sformatowany tak jak w poprzednim przykładzie.