Formattatori personalizzati nell'API Web ASP.NET Core
ASP.NET Core MVC supporta lo scambio di dati nelle API Web usando formattatori di input e output. I formattatori di input vengono usati dall'associazione di modelli. I formattatori di output vengono usati per formattare le risposte.
Il framework fornisce formattatori di input e output predefiniti per JSON e XML. Fornisce un formattatore di output predefinito per il testo normale, ma non fornisce un formattatore di input per il testo normale.
In questo articolo viene illustrato come aggiungere supporto per altri formati creando formattatori personalizzati. Per un esempio di formattatore di input di testo normale personalizzato, vedere TextPlainInputFormatter su GitHub.
Visualizzare o scaricare il codice di esempio (procedura per il download)
Quando usare un formattatore personalizzato
Usare un formattatore personalizzato per aggiungere il supporto per un tipo di contenuto non gestito dai formattatori predefiniti.
Panoramica di come creare un formattatore personalizzato
Per creare un formattatore personalizzato:
- Per serializzare i dati inviati al client, creare una classe formattatore di output.
- Per deserializzare i dati ricevuti dal client, creare una classe formattatore di input.
- Aggiungere istanze di classi formattatore alle InputFormatters raccolte e OutputFormatters in MvcOptions.
Creare un formattatore personalizzato
Per creare un formattatore:
- Derivare la classe dalla classe di base appropriata. L'app di esempio deriva da TextOutputFormatter e TextInputFormatter.
- Specificare i tipi di supporti e le codifiche supportati nel costruttore.
- Eseguire l'override dei metodi CanReadType e CanWriteType.
- Eseguire l'override dei metodi ReadRequestBodyAsync e WriteResponseBodyAsync.
Il codice seguente illustra la VcardOutputFormatter
classe dell'esempio:
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);
}
}
Derivare dalla classe di base appropriata
Per i tipi di supporti di testo (ad esempio, vCard), derivano dalla TextInputFormatter classe base o TextOutputFormatter :
public class VcardOutputFormatter : TextOutputFormatter
Per i tipi binari, derivare dalla InputFormatter classe o OutputFormatter di base.
Specificare tipi di supporti e codifiche supportate
Nel costruttore specificare i tipi di supporti e le codifiche supportati aggiungendo alle SupportedMediaTypes raccolte e SupportedEncodings :
public VcardOutputFormatter()
{
SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("text/vcard"));
SupportedEncodings.Add(Encoding.UTF8);
SupportedEncodings.Add(Encoding.Unicode);
}
Una classe formattatore non può usare l'inserimento del costruttore per le relative dipendenze. Ad esempio, ILogger<VcardOutputFormatter>
non può essere aggiunto come parametro al costruttore. Per accedere ai servizi, utilizzare l'oggetto contesto passato ai metodi . Un esempio di codice in questo articolo e l'esempio illustrano come eseguire questa operazione.
Eseguire l'override di CanReadType e CanWriteType
Specificare il tipo in cui deserializzare o serializzare da eseguendo l'override dei CanReadType metodi o CanWriteType . Ad esempio, per creare testo vCard da un Contact
tipo e viceversa:
protected override bool CanWriteType(Type? type)
=> typeof(Contact).IsAssignableFrom(type)
|| typeof(IEnumerable<Contact>).IsAssignableFrom(type);
Metodo CanWriteResult
In alcuni scenari, CanWriteResult è necessario eseguire l'override anziché CanWriteType. Usare CanWriteResult
se vengono soddisfatte le condizioni seguenti:
- Il metodo di azione restituisce una classe del modello.
- Esistono classi derivate che potrebbero essere restituite in fase di esecuzione.
- La classe derivata restituita dall'azione deve essere nota in fase di esecuzione.
Si supponga, ad esempio, che il metodo di azione:
- La firma restituisce un
Person
tipo. - Può restituire un
Student
tipo oInstructor
che deriva daPerson
.
Affinché il formattatore gestisca solo Student
gli oggetti, controllare il tipo di Object nell'oggetto di contesto fornito al CanWriteResult
metodo . Quando il metodo di azione restituisce IActionResult:
- Non è necessario usare
CanWriteResult
. - Il
CanWriteType
metodo riceve il tipo di runtime.
Eseguire l'override di ReadRequestBodyAsync e WriteResponseBodyAsync
La deserializzazione o la serializzazione viene eseguita in ReadRequestBodyAsync o WriteResponseBodyAsync. Nell'esempio seguente viene illustrato come ottenere servizi dal contenitore di inserimento delle dipendenze. I servizi non possono essere ottenuti dai parametri del costruttore:
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);
}
Configurare MVC per l'uso di un formattatore personalizzato
Per usare un formattatore personalizzato, aggiungere un'istanza della classe formattatore alla MvcOptions.InputFormatters raccolta o MvcOptions.OutputFormatters :
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers(options =>
{
options.InputFormatters.Insert(0, new VcardInputFormatter());
options.OutputFormatters.Insert(0, new VcardOutputFormatter());
});
I formattatori vengono valutati nell'ordine in cui vengono inseriti, dove il primo ha la precedenza.
Classe completa VcardInputFormatter
Il codice seguente illustra la VcardInputFormatter
classe dell'esempio:
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;
}
}
Testare l'app
Eseguire l'app di esempio per questo articolo, che implementa formattatori di input e output vCard di base. L'app legge e scrive vCard in modo simile al formato seguente:
BEGIN:VCARD
VERSION:2.1
N:Davolio;Nancy
FN:Nancy Davolio
END:VCARD
Per visualizzare l'output vCard, eseguire l'app e inviare una richiesta Get con l'intestazione text/vcard
Accept a https://localhost:<port>/api/contacts
.
Per aggiungere una vCard alla raccolta in memoria dei contatti:
- Inviare una
Post
richiesta a/api/contacts
con uno strumento come http-repl. - Impostare l'intestazione
Content-Type
sutext/vcard
. - Impostare
vCard
il testo nel corpo, formattato come nell'esempio precedente.
Risorse aggiuntive
ASP.NET Core MVC supporta lo scambio di dati nelle API Web usando formattatori di input e output. I formattatori di input vengono usati dall'associazione di modelli. I formattatori di output vengono usati per formattare le risposte.
Il framework fornisce formattatori di input e output predefiniti per JSON e XML. Fornisce un formattatore di output predefinito per il testo normale, ma non fornisce un formattatore di input per il testo normale.
In questo articolo viene illustrato come aggiungere supporto per altri formati creando formattatori personalizzati. Per un esempio di formattatore di input di testo normale personalizzato, vedere TextPlainInputFormatter su GitHub.
Visualizzare o scaricare il codice di esempio (procedura per il download)
Quando usare un formattatore personalizzato
Usare un formattatore personalizzato per aggiungere il supporto per un tipo di contenuto non gestito dai formattatori predefiniti.
Panoramica di come creare un formattatore personalizzato
Per creare un formattatore personalizzato:
- Per serializzare i dati inviati al client, creare una classe formattatore di output.
- Per deserializzare i dati ricevuti dal client, creare una classe formattatore di input.
- Aggiungere istanze di classi formattatore alle InputFormatters raccolte e OutputFormatters in MvcOptions.
Creare un formattatore personalizzato
Per creare un formattatore:
- Derivare la classe dalla classe di base appropriata. L'app di esempio deriva da TextOutputFormatter e TextInputFormatter.
- Specificare i tipi di supporti e le codifiche supportati nel costruttore.
- Eseguire l'override dei metodi CanReadType e CanWriteType.
- Eseguire l'override dei metodi ReadRequestBodyAsync e WriteResponseBodyAsync.
Il codice seguente illustra la VcardOutputFormatter
classe dell'esempio:
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);
}
}
Derivare dalla classe di base appropriata
Per i tipi di supporti di testo (ad esempio, vCard), derivano dalla TextInputFormatter classe base o TextOutputFormatter :
public class VcardOutputFormatter : TextOutputFormatter
Per i tipi binari, derivare dalla InputFormatter classe o OutputFormatter di base.
Specificare tipi di supporti e codifiche supportate
Nel costruttore specificare i tipi di supporti e le codifiche supportati aggiungendo alle SupportedMediaTypes raccolte e SupportedEncodings :
public VcardOutputFormatter()
{
SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("text/vcard"));
SupportedEncodings.Add(Encoding.UTF8);
SupportedEncodings.Add(Encoding.Unicode);
}
Una classe formattatore non può usare l'inserimento del costruttore per le relative dipendenze. Ad esempio, ILogger<VcardOutputFormatter>
non può essere aggiunto come parametro al costruttore. Per accedere ai servizi, utilizzare l'oggetto contesto passato ai metodi . Un esempio di codice in questo articolo e l'esempio illustrano come eseguire questa operazione.
Eseguire l'override di CanReadType e CanWriteType
Specificare il tipo in cui deserializzare o serializzare da eseguendo l'override dei CanReadType metodi o CanWriteType . Ad esempio, per creare testo vCard da un Contact
tipo e viceversa:
protected override bool CanWriteType(Type type)
{
return typeof(Contact).IsAssignableFrom(type) ||
typeof(IEnumerable<Contact>).IsAssignableFrom(type);
}
Metodo CanWriteResult
In alcuni scenari, CanWriteResult è necessario eseguire l'override anziché CanWriteType. Usare CanWriteResult
se vengono soddisfatte le condizioni seguenti:
- Il metodo di azione restituisce una classe del modello.
- Esistono classi derivate che potrebbero essere restituite in fase di esecuzione.
- La classe derivata restituita dall'azione deve essere nota in fase di esecuzione.
Si supponga, ad esempio, che il metodo di azione:
- La firma restituisce un
Person
tipo. - Può restituire un
Student
tipo oInstructor
che deriva daPerson
.
Affinché il formattatore gestisca solo Student
gli oggetti, controllare il tipo di Object nell'oggetto di contesto fornito al CanWriteResult
metodo . Quando il metodo di azione restituisce IActionResult:
- Non è necessario usare
CanWriteResult
. - Il
CanWriteType
metodo riceve il tipo di runtime.
Eseguire l'override di ReadRequestBodyAsync e WriteResponseBodyAsync
La deserializzazione o la serializzazione viene eseguita in ReadRequestBodyAsync o WriteResponseBodyAsync. Nell'esempio seguente viene illustrato come ottenere servizi dal contenitore di inserimento delle dipendenze. I servizi non possono essere ottenuti dai parametri del costruttore:
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);
}
Configurare MVC per l'uso di un formattatore personalizzato
Per usare un formattatore personalizzato, aggiungere un'istanza della classe formattatore alla MvcOptions.InputFormatters raccolta o MvcOptions.OutputFormatters :
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers(options =>
{
options.InputFormatters.Insert(0, new VcardInputFormatter());
options.OutputFormatters.Insert(0, new VcardOutputFormatter());
});
}
I formattatori vengono valutati nell'ordine d'inserimento. Il primo ha la precedenza.
Classe completa VcardInputFormatter
Il codice seguente illustra la VcardInputFormatter
classe dell'esempio:
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;
}
}
Testare l'app
Eseguire l'app di esempio per questo articolo, che implementa formattatori di input e output vCard di base. L'app legge e scrive vCard in modo simile al formato seguente:
BEGIN:VCARD
VERSION:2.1
N:Davolio;Nancy
FN:Nancy Davolio
END:VCARD
Per visualizzare l'output vCard, eseguire l'app e inviare una richiesta Get con l'intestazione text/vcard
Accept a https://localhost:5001/api/contacts
.
Per aggiungere una vCard alla raccolta in memoria dei contatti:
- Inviare una
Post
richiesta a/api/contacts
con uno strumento come curl. - Impostare l'intestazione
Content-Type
sutext/vcard
. - Impostare
vCard
il testo nel corpo, formattato come nell'esempio precedente.