Nota
O acesso a esta página requer autorização. Pode tentar iniciar sessão ou alterar os diretórios.
O acesso a esta página requer autorização. Pode tentar alterar os diretórios.
ASP.NET Core MVC suporta a troca de dados em APIs da Web usando os formatadores de entrada e saída. Os formatadores de entrada são usados pelos Vinculação de Modelo. Os formatters de saída são usados para formatar respostas.
A estrutura fornece formatadores integrados de entrada e saída para JSON e XML. Ele fornece um formatador de saída interno para texto sem formatação, mas não fornece um formatador de entrada para texto sem formatação.
Este artigo mostra como adicionar suporte para formatos adicionais criando formatters personalizados. Para obter um exemplo de um formatador de entrada de texto simples personalizado, consulte TextPlainInputFormatter no GitHub.
Visualizar ou descarregar amostra de código (como descarregar)
Quando usar um formatador personalizado
Utilize um formatador personalizado para adicionar suporte a um tipo de conteúdo que não é manipulado pelos formatadores internos.
Visão geral de como criar um formatador personalizado
Para criar um formatador personalizado:
- Para serializar dados enviados ao cliente, crie uma classe de formatador de saída.
- Para desserializar dados recebidos do cliente, crie uma classe de formatador de entrada.
- Adicione instâncias de classes de formatador às coleções InputFormatters e OutputFormatters em MvcOptions.
Criar um formatador personalizado
Para criar um formatador:
- Derive a classe a partir da classe base apropriada. O aplicativo de exemplo deriva de TextOutputFormatter e TextInputFormatter.
- Especifique os tipos de mídia suportados e codificações no construtor.
- Substitua os métodos CanReadType e CanWriteType.
- Substitua os métodos ReadRequestBodyAsync e WriteResponseBodyAsync.
O código a seguir mostra a VcardOutputFormatter classe do exemplo:
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);
}
}
Derivar da classe base apropriada
Para tipos de mídia de texto (por exemplo, vCard), derive da classe base TextInputFormatter ou TextOutputFormatter:
public class VcardOutputFormatter : TextOutputFormatter
Para tipos binários, derive da classe base InputFormatter ou OutputFormatter.
Especificar tipos de mídia e codificações suportados
No construtor, especifique os tipos de mídia suportados e codificações, adicionando às coleções SupportedMediaTypes e SupportedEncodings:
public VcardOutputFormatter()
{
SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("text/vcard"));
SupportedEncodings.Add(Encoding.UTF8);
SupportedEncodings.Add(Encoding.Unicode);
}
Uma classe de formatador não pode usar a injeção de construtor para suas dependências. Por exemplo, ILogger<VcardOutputFormatter> não pode ser adicionado como um parâmetro para o construtor. Para acessar serviços, use o objeto de contexto que é passado para os métodos. Um exemplo de código neste artigo e o exemplo mostram como fazer isso.
Substituir CanReadType e CanWriteType
Especifique o tipo para desserializar ou serializar substituindo os CanReadType métodos ou CanWriteType . Por exemplo, para criar texto vCard a partir de um tipo Contact e vice-versa:
protected override bool CanWriteType(Type? type)
=> typeof(Contact).IsAssignableFrom(type)
|| typeof(IEnumerable<Contact>).IsAssignableFrom(type);
O método CanWriteResult
Em alguns cenários, CanWriteResult deve ser substituído em vez de CanWriteType. Use CanWriteResult se as seguintes condições forem verdadeiras:
- O método action retorna uma classe de modelo.
- Há classes derivadas que podem ser retornadas em tempo de execução.
- A classe derivada retornada pela ação deve ser conhecida em tempo de execução.
Por exemplo, suponha o método de ação:
- A assinatura retorna um tipo
Person. - Pode retornar um
StudentouInstructortipo que deriva dePerson.
Para que o formatador manipule apenas objetos Student, verifique o tipo de Object no objeto de contexto fornecido ao método CanWriteResult. Quando o método action retorna IActionResult:
- Não é necessário usar
CanWriteResult. - O
CanWriteTypemétodo recebe o tipo de tempo de execução.
Sobrescrever ReadRequestBodyAsync e WriteResponseBodyAsync
A desserialização ou serialização é realizada em ReadRequestBodyAsync ou WriteResponseBodyAsync. O exemplo a seguir mostra como obter serviços do contêiner de injeção de dependência. Os serviços não podem ser obtidos a partir dos parâmetros do construtor:
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);
}
Configurar o MVC para usar um formatador personalizado
Para usar um formatador personalizado, adicione uma instância da classe formatador à coleção MvcOptions.InputFormatters ou MvcOptions.OutputFormatters.
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers(options =>
{
options.InputFormatters.Insert(0, new VcardInputFormatter());
options.OutputFormatters.Insert(0, new VcardOutputFormatter());
});
Os formatadores são avaliados pela ordem em que são inseridos, sendo que o primeiro tem precedência.
A classe completa VcardInputFormatter
O código a seguir mostra a VcardInputFormatter classe do exemplo:
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;
}
}
Testar a aplicação
Execute a aplicação de exemplo deste artigo, que implementa formatadores de entrada e saída básicos de vCard. O aplicativo lê e grava vCards semelhante ao seguinte formato:
BEGIN:VCARD
VERSION:2.1
N:Davolio;Nancy
FN:Nancy Davolio
END:VCARD
Para ver a saída do vCard, execute o aplicativo e envie uma solicitação Get com o cabeçalho text/vcard Accept para https://localhost:<port>/api/contacts.
Para adicionar um vCard à coleção de contatos na memória:
- Envie uma
Postsolicitação para/api/contactscom uma ferramenta como http-repl. - Defina o
Content-Typecabeçalho comotext/vcard. - Defina
vCardo texto no corpo, formatado como o exemplo anterior.
Recursos adicionais
ASP.NET Core MVC suporta a troca de dados em APIs da Web usando os formatadores de entrada e saída. Os formatadores de entrada são usados pelos Vinculação de Modelo. Os formatters de saída são usados para formatar respostas.
A estrutura fornece formatadores integrados de entrada e saída para JSON e XML. Ele fornece um formatador de saída interno para texto sem formatação, mas não fornece um formatador de entrada para texto sem formatação.
Este artigo mostra como adicionar suporte para formatos adicionais criando formatters personalizados. Para obter um exemplo de um formatador de entrada de texto simples personalizado, consulte TextPlainInputFormatter no GitHub.
Visualizar ou descarregar amostra de código (como descarregar)
Quando usar um formatador personalizado
Utilize um formatador personalizado para adicionar suporte a um tipo de conteúdo que não é manipulado pelos formatadores internos.
Visão geral de como criar um formatador personalizado
Para criar um formatador personalizado:
- Para serializar dados enviados ao cliente, crie uma classe de formatador de saída.
- Para desserializar dados recebidos do cliente, crie uma classe de formatador de entrada.
- Adicione instâncias de classes de formatador às coleções InputFormatters e OutputFormatters em MvcOptions.
Criar um formatador personalizado
Para criar um formatador:
- Derive a classe a partir da classe base apropriada. O aplicativo de exemplo deriva de TextOutputFormatter e TextInputFormatter.
- Especifique os tipos de mídia suportados e codificações no construtor.
- Substitua os métodos CanReadType e CanWriteType.
- Substitua os métodos ReadRequestBodyAsync e WriteResponseBodyAsync.
O código a seguir mostra a VcardOutputFormatter classe do exemplo:
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);
}
}
Derivar da classe base apropriada
Para tipos de mídia de texto (por exemplo, vCard), derive da classe base TextInputFormatter ou TextOutputFormatter:
public class VcardOutputFormatter : TextOutputFormatter
Para tipos binários, derive da classe base InputFormatter ou OutputFormatter.
Especificar tipos de mídia e codificações suportados
No construtor, especifique os tipos de mídia suportados e codificações, adicionando às coleções SupportedMediaTypes e SupportedEncodings:
public VcardOutputFormatter()
{
SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("text/vcard"));
SupportedEncodings.Add(Encoding.UTF8);
SupportedEncodings.Add(Encoding.Unicode);
}
Uma classe de formatador não pode usar a injeção de construtor para suas dependências. Por exemplo, ILogger<VcardOutputFormatter> não pode ser adicionado como um parâmetro para o construtor. Para acessar serviços, use o objeto de contexto que é passado para os métodos. Um exemplo de código neste artigo e o exemplo mostram como fazer isso.
Substituir CanReadType e CanWriteType
Especifique o tipo para desserializar ou serializar substituindo os CanReadType métodos ou CanWriteType . Por exemplo, para criar texto vCard a partir de um tipo Contact e vice-versa:
protected override bool CanWriteType(Type type)
{
return typeof(Contact).IsAssignableFrom(type) ||
typeof(IEnumerable<Contact>).IsAssignableFrom(type);
}
O método CanWriteResult
Em alguns cenários, CanWriteResult deve ser substituído em vez de CanWriteType. Use CanWriteResult se as seguintes condições forem verdadeiras:
- O método action retorna uma classe de modelo.
- Há classes derivadas que podem ser retornadas em tempo de execução.
- A classe derivada retornada pela ação deve ser conhecida em tempo de execução.
Por exemplo, suponha o método de ação:
- A assinatura retorna um tipo
Person. - Pode retornar um
StudentouInstructortipo que deriva dePerson.
Para que o formatador manipule apenas objetos Student, verifique o tipo de Object no objeto de contexto fornecido ao método CanWriteResult. Quando o método action retorna IActionResult:
- Não é necessário usar
CanWriteResult. - O
CanWriteTypemétodo recebe o tipo de tempo de execução.
Sobrescrever ReadRequestBodyAsync e WriteResponseBodyAsync
A desserialização ou serialização é realizada em ReadRequestBodyAsync ou WriteResponseBodyAsync. O exemplo a seguir mostra como obter serviços do contêiner de injeção de dependência. Os serviços não podem ser obtidos a partir dos parâmetros do construtor:
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);
}
Configurar o MVC para usar um formatador personalizado
Para usar um formatador personalizado, adicione uma instância da classe formatador à coleção MvcOptions.InputFormatters ou MvcOptions.OutputFormatters.
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers(options =>
{
options.InputFormatters.Insert(0, new VcardInputFormatter());
options.OutputFormatters.Insert(0, new VcardOutputFormatter());
});
}
Formatadores são avaliados pela ordem em que são inseridos. A primeira tem precedência.
A classe completa VcardInputFormatter
O código a seguir mostra a VcardInputFormatter classe do exemplo:
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;
}
}
Testar a aplicação
Execute a aplicação de exemplo deste artigo, que implementa formatadores de entrada e saída básicos de vCard. O aplicativo lê e grava vCards semelhante ao seguinte formato:
BEGIN:VCARD
VERSION:2.1
N:Davolio;Nancy
FN:Nancy Davolio
END:VCARD
Para ver a saída do vCard, execute o aplicativo e envie uma solicitação Get com o cabeçalho text/vcard Accept para https://localhost:5001/api/contacts.
Para adicionar um vCard à coleção de contatos na memória:
- Envie uma
Postsolicitação para/api/contactscom uma ferramenta como curl. - Defina o
Content-Typecabeçalho comotext/vcard. - Defina
vCardo texto no corpo, formatado como o exemplo anterior.