Formattatori personalizzati nell'API Web ASP.NET CoreCustom formatters in ASP.NET Core Web API

Tom DykstraBy Tom Dykstra

ASP.NET Core MVC supporta lo scambio di dati in API Web mediante formattatori di input e di output.ASP.NET Core MVC supports data exchange in Web APIs using input and output formatters. I formattatori di input vengono utilizzati dall' associazione di modelli.Input formatters are used by Model Binding. I formattatori di output vengono utilizzati per formattare le risposte.Output formatters are used to format responses.

Il Framework fornisce formattatori di input e output predefiniti per JSON e XML.The framework provides built-in input and output formatters for JSON and XML. Fornisce un formattatore di output incorporato per il testo normale, ma non fornisce un formattatore di input per testo normale.It provides a built-in output formatter for plain text, but doesn't provide an input formatter for plain text.

In questo articolo viene illustrato come aggiungere supporto per altri formati creando formattatori personalizzati.This article shows how to add support for additional formats by creating custom formatters. Per un esempio di formattatore di input personalizzato per testo normale, vedere TextPlainInputFormatter su GitHub.For an example of a custom input formatter for plain text, see TextPlainInputFormatter on GitHub.

Visualizzare o scaricare il codice di esempio (procedura per il download)View or download sample code (how to download)

Quando usare i formattatori personalizzatiWhen to use custom formatters

Utilizzare un formattatore personalizzato quando si desidera che il processo di negoziazione del contenuto supporti un tipo di contenuto non supportato dai formattatori predefiniti.Use a custom formatter when you want the content negotiation process to support a content type that isn't supported by the built-in formatters.

Ad esempio, se alcuni dei client per l'API Web gestiscono il formato Protobuf, si potrebbe voler usare Protobuf con tali client per una maggiore efficienza.For example, if some of the clients for your web API can handle the Protobuf format, you might want to use Protobuf with those clients because it's more efficient. Oppure potrebbe essere necessario che l'API Web invii nomi di contatto e indirizzi nel formato vCard, un formato usato comunemente per lo scambio di dati di contatto.Or you might want your web API to send contact names and addresses in vCard format, a commonly used format for exchanging contact data. L'app di esempio disponibile in questo articolo implementa un semplice formattatore vCard.The sample app provided with this article implements a simple vCard formatter.

Panoramica sull'uso di un formattatore personalizzatoOverview of how to use a custom formatter

Di seguito sono elencati i passaggi per creare e usare un formattatore personalizzato:Here are the steps to create and use a custom formatter:

  • Creare una classe formattatore di output se si vogliono serializzare i dati da inviare al client.Create an output formatter class if you want to serialize data to send to the client.
  • Creare una classe formattatore di input se si vogliono deserializzare i dati ricevuti dal client.Create an input formatter class if you want to deserialize data received from the client.
  • Aggiungere le istanze dei formattatori alle raccolte InputFormatters e OutputFormatters in MvcOptions.Add instances of your formatters to the InputFormatters and OutputFormatters collections in MvcOptions.

Nelle sezioni seguenti sono disponibili indicazioni ed esempi di codici per ognuno dei passaggi specificati.The following sections provide guidance and code examples for each of these steps.

Come creare una classe formattatore personalizzatoHow to create a custom formatter class

Per creare un formattatore:To create a formatter:

  • Derivare la classe dalla classe di base appropriata.Derive the class from the appropriate base class.
  • Specificare i formati di testo multimediali validi e le codifiche nel costruttore.Specify valid media types and encodings in the constructor.
  • Eseguire l'override dei metodi CanReadType/CanWriteTypeOverride CanReadType/CanWriteType methods
  • Eseguire l'override dei metodi ReadRequestBodyAsync/WriteResponseBodyAsyncOverride ReadRequestBodyAsync/WriteResponseBodyAsync methods

Derivare dalla classe di base appropriataDerive from the appropriate base class

Per i formati multimediali di testo (ad esempio, vCard), derivare dalla classe TextInputFormatter o TextOutputFormatter.For text media types (for example, vCard), derive from the TextInputFormatter or TextOutputFormatter base class.

public class VcardOutputFormatter : TextOutputFormatter

Per un esempio di formattatore di input, vedere l'app di esempio.For an input formatter example, see the sample app.

Per i tipi binari, derivare dalla classe di base InputFormatter o OutputFormatter.For binary types, derive from the InputFormatter or OutputFormatter base class.

Specificare i formati multimediali validi e le codificheSpecify valid media types and encodings

Nel costruttore specificare i formati multimediali validi e le codifiche aggiungendo alle raccolte SupportedMediaTypes e SupportedEncodings.In the constructor, specify valid media types and encodings by adding to the SupportedMediaTypes and SupportedEncodings collections.

public VcardOutputFormatter()
{
    SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("text/vcard"));

    SupportedEncodings.Add(Encoding.UTF8);
    SupportedEncodings.Add(Encoding.Unicode);
}

Per un esempio di formattatore di input, vedere l'app di esempio.For an input formatter example, see the sample app.

Nota

In una classe formattatore non è possibile inserire le dipendenze del costruttore.You can't do constructor dependency injection in a formatter class. Ad esempio, non è possibile ottenere un logger aggiungendo un parametro di logger al costruttore.For example, you can't get a logger by adding a logger parameter to the constructor. Per accedere ai servizi, usare l'oggetto di contesto che viene passato ai metodi.To access services, you have to use the context object that gets passed in to your methods. Nell'esempio di codice riportato di seguito viene illustrato come eseguire tale operazione.A code example below shows how to do this.

Eseguire l'override di CanReadType/CanWriteTypeOverride CanReadType/CanWriteType

Eseguire l'override dei metodi CanReadType o CanWriteType per specificare il tipo in cui eseguire la deserializzazione o il tipo da cui eseguire la serializzazione.Specify the type you can deserialize into or serialize from by overriding the CanReadType or CanWriteType methods. Ad esempio, potrebbe essere possibile creare solo un testo vCard da un tipo Contact e viceversa.For example, you might only be able to create vCard text from a Contact type and vice versa.

protected override bool CanWriteType(Type type)
{
    if (typeof(Contact).IsAssignableFrom(type) 
        || typeof(IEnumerable<Contact>).IsAssignableFrom(type))
    {
        return base.CanWriteType(type);
    }
    return false;
}

Per un esempio di formattatore di input, vedere l'app di esempio.For an input formatter example, see the sample app.

Metodo CanWriteResultThe CanWriteResult method

In alcuni scenari è necessario eseguire l'override di CanWriteResult anziché di CanWriteType.In some scenarios you have to override CanWriteResult instead of CanWriteType. Usare CanWriteResult se vengono soddisfatte le condizioni seguenti:Use CanWriteResult if the following conditions are true:

  • Il metodo di azione restituisce una classe modello.Your action method returns a model class.
  • Sono disponibili classi derivate che possono essere restituite in fase di esecuzione.There are derived classes which might be returned at runtime.
  • In fase di esecuzione è necessario conoscere quale classe derivata è stata restituita dall'azione.You need to know at runtime which derived class was returned by the action.

Ad esempio, si supponga che la firma del metodo di azione restituisca un tipo Person, ma può restituire un tipo Student o Instructor che deriva da Person.For example, suppose your action method signature returns a Person type, but it may return a Student or Instructor type that derives from Person. Se il formattatore deve gestire solo oggetti Student, controllare il tipo di oggetto nell'oggetto di contesto specificato per il metodo CanWriteResult.If you want your formatter to handle only Student objects, check the type of Object in the context object provided to the CanWriteResult method. Si noti che non è necessario usare CanWriteResult quando il metodo di azione restituisce IActionResult; in tal caso, il metodo CanWriteType riceve il tipo di runtime.Note that it's not necessary to use CanWriteResult when the action method returns IActionResult; in that case, the CanWriteType method receives the runtime type.

Eseguire l'override di ReadRequestBodyAsync/WriteResponseBodyAsyncOverride ReadRequestBodyAsync/WriteResponseBodyAsync

Le effettive operazioni di deserializzazione o serializzazione vengono eseguite in ReadRequestBodyAsync o WriteResponseBodyAsync.You do the actual work of deserializing or serializing in ReadRequestBodyAsync or WriteResponseBodyAsync. Le righe evidenziate nell'esempio seguente indicano come ottenere i servizi dal contenitore dell'inserimento delle dipendenze. Non è possibile ottenerli dai parametri del costruttore.The highlighted lines in the following example show how to get services from the dependency injection container (you can't get them from constructor parameters).

public override async Task WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding)
{
    IServiceProvider serviceProvider = context.HttpContext.RequestServices;
    var logger = serviceProvider.GetService(typeof(ILogger<VcardOutputFormatter>)) as ILogger;

    var response = context.HttpContext.Response;

    var buffer = new StringBuilder();
    if (context.Object is IEnumerable<Contact>)
    {
        foreach (Contact contact in context.Object as IEnumerable<Contact>)
        {
            FormatVcard(buffer, contact, logger);
        }
    }
    else
    {
        var contact = context.Object as Contact;
        FormatVcard(buffer, contact, logger);
    }
    await response.WriteAsync(buffer.ToString());
}

private static void FormatVcard(StringBuilder buffer, Contact contact, ILogger logger)
{
    buffer.AppendLine("BEGIN:VCARD");
    buffer.AppendLine("VERSION:2.1");
    buffer.AppendFormat($"N:{contact.LastName};{contact.FirstName}\r\n");
    buffer.AppendFormat($"FN:{contact.FirstName} {contact.LastName}\r\n");
    buffer.AppendFormat($"UID:{contact.ID}\r\n");
    buffer.AppendLine("END:VCARD");
    logger.LogInformation("Writing {FirstName} {LastName}", contact.FirstName, contact.LastName);
}

Per un esempio di formattatore di input, vedere l'app di esempio.For an input formatter example, see the sample app.

Come configurare MVC per usare un formattatore personalizzatoHow to configure MVC to use a custom formatter

Per usare un formattatore personalizzato, aggiungere un'istanza della classe formattatore alla raccolta InputFormatters o OutputFormatters.To use a custom formatter, add an instance of the formatter class to the InputFormatters or OutputFormatters collection.

services.AddMvc(options =>
{
    options.InputFormatters.Insert(0, new VcardInputFormatter());
    options.OutputFormatters.Insert(0, new VcardOutputFormatter());
})
.SetCompatibilityVersion(CompatibilityVersion.Version_2_2);

I formattatori vengono valutati nell'ordine d'inserimento.Formatters are evaluated in the order you insert them. Il primo ha la precedenza.The first one takes precedence.

Passaggi successiviNext steps

  • App di esempio per questo documento, che implementa semplici formattatori di input e output vCard.Sample app for this doc, which implements simple vCard input and output formatters. L'app legge e scrive vCard simili all'esempio seguente:The apps reads and writes vCards that look like the following example:
BEGIN:VCARD
VERSION:2.1
N:Davolio;Nancy
FN:Nancy Davolio
UID:20293482-9240-4d68-b475-325df4a83728
END:VCARD

Per visualizzare l'output vCard, eseguire l'applicazione, inviare una richiesta Get con intestazione Accept "text/vcard" a http://localhost:63313/api/contacts/ (se l'applicazione è eseguita da Visual Studio) o a http://localhost:5000/api/contacts/ (se l'applicazione è eseguita dalla riga di comando).To see vCard output, run the application and send a Get request with Accept header "text/vcard" to http://localhost:63313/api/contacts/ (when running from Visual Studio) or http://localhost:5000/api/contacts/ (when running from the command line).

Per aggiungere un file vCard alla raccolta di contatti in memoria, inviare una richiesta Post allo stesso URL, con intestazione Content-Type "text/vcard" e con testo vCard nel corpo, formattato come illustrato nell'esempio precedente.To add a vCard to the in-memory collection of contacts, send a Post request to the same URL, with Content-Type header "text/vcard" and with vCard text in the body, formatted like the example above.