Benutzerdefinierte Formatierer in Web-APIs in ASP.NET CoreCustom formatters in ASP.NET Core Web API

Von Kirk Larkin und Tom Dykstra.By Kirk Larkin and Tom Dykstra.

ASP.NET Core MVC unterstützt den Datenaustausch in Web-APIs mithilfe von Eingabe- und Ausgabeformatierern.ASP.NET Core MVC supports data exchange in Web APIs using input and output formatters. Eingabeformatierer werden von der Modellbindung verwendet.Input formatters are used by Model Binding. Ausgabe Formatierer werden verwendet, um Antworten zu formatieren.Output formatters are used to format responses.

Das Framework stellt integrierte Eingabe- und Ausgabeformatierer für JSON und XML bereit.The framework provides built-in input and output formatters for JSON and XML. Es stellt einen integrierten Ausgabeformatierer für Nur-Text bereit, jedoch keinen Eingabeformatierer.It provides a built-in output formatter for plain text, but doesn't provide an input formatter for plain text.

In diesem Artikel wird dargestellt, wie Sie die Unterstützung von zusätzlichen Formaten hinzufügen, indem Sie benutzerdefinierte Formatierer erstellen.This article shows how to add support for additional formats by creating custom formatters. Ein Beispiel für einen benutzerdefinierten Formatierer für die nur-Text-Eingabe finden Sie unter textplaininputformatter auf GitHub.For an example of a custom plain text input formatter, see TextPlainInputFormatter on GitHub.

Anzeigen oder Herunterladen von Beispielcode (Vorgehensweise zum Herunterladen)View or download sample code (how to download)

Empfohlene Verwendung von benutzerdefinierten FormatierernWhen to use custom formatters

Verwenden Sie einen benutzerdefinierten Formatierer, um Unterstützung für einen Inhaltstyp hinzuzufügen, der nicht von den integrierten Formatierern verarbeitet wird.Use a custom formatter to add support for a content type that isn't handled by the built-in formatters.

Übersicht über die Verwendung des FormatierersOverview of how to use a custom formatter

So erstellen Sie einen benutzerdefinierten Formatierer:To create a custom formatter:

  • Zum Serialisieren von Daten, die an den Client gesendet werden, erstellen Sie eine Ausgabe formatterklasse.For serializing data sent to the client, create an output formatter class.
  • Zum Deserialisieren von Daten, die vom Client empfangen werden, erstellen Sie eine eingabeformatterklasse.For deserializing data received from the client, create an input formatter class.
  • Fügen Sie den-und-Auflistungen in Instanzen von formatiererklassen hinzu InputFormatters OutputFormatters MvcOptions .Add instances of formatter classes to the InputFormatters and OutputFormatters collections in MvcOptions.

Erstellen einer benutzerdefinierten FormatiererklasseHow to create a custom formatter class

Erstellen eines Formatierers:To create a formatter:

Der folgende Code zeigt die- VcardOutputFormatter Klasse aus dem Beispiel:The following code shows the VcardOutputFormatter class from the sample:

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());
    }

    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);
    }
}

Ableiten von der entsprechenden BasisklasseDerive from the appropriate base class

Leiten Sie für Text Medientypen (z. b. vCard) von der- TextInputFormatter oder- TextOutputFormatter Basisklasse ab.For text media types (for example, vCard), derive from the TextInputFormatter or TextOutputFormatter base class.

public class VcardOutputFormatter : TextOutputFormatter

Leiten Sie für binäre Typen von der- InputFormatter oder- OutputFormatter Basisklasse ab.For binary types, derive from the InputFormatter or OutputFormatter base class.

Angeben von gültigen Medientypen und CodierungenSpecify valid media types and encodings

Geben Sie im Konstruktor gültige Medientypen und Codierungen an, indem Sie SupportedMediaTypes- und SupportedEncodings-Sammlungen hinzufügen.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);
}

Eine Formatiererklasse kann keine Konstruktorinjektion für ihre Abhängigkeiten verwenden.A formatter class can not use constructor injection for its dependencies. ILogger<VcardOutputFormatter>Kann z. b. nicht als Parameter zum-Konstruktor hinzugefügt werden.For example, ILogger<VcardOutputFormatter> cannot be added as a parameter to the constructor. Verwenden Sie zum Zugreifen auf Dienste das Kontext Objekt, das an die-Methoden übermittelt wird.To access services, use the context object that gets passed in to the methods. Ein Codebeispiel in diesem Artikel und das Beispiel zeigen, wie Sie dies tun.A code example in this article and the sample show how to do this.

Überschreiben von "canlestype" und "CanWrite Type"Override CanReadType and CanWriteType

Geben Sie den zu deserialisierenden oder zu serialisierenden Typ durch Überschreiben der- CanReadType Methode oder der- CanWriteType Methode anSpecify the type to deserialize into or serialize from by overriding the CanReadType or CanWriteType methods. Beispielsweise das Erstellen von vCard-Text von einem Contact Typ und umgekehrt.For example, creating vCard text from a Contact type and vice versa.

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

Die CanWriteResult-MethodeThe CanWriteResult method

In einigen Szenarien CanWriteResult muss anstelle von überschrieben werden CanWriteType .In some scenarios, CanWriteResult must be overridden rather than CanWriteType. Verwenden Sie CanWriteResult, wenn alle der folgenden Bedingungen zutreffen:Use CanWriteResult if the following conditions are true:

  • Die Aktionsmethode gibt eine Modell Klasse zurück.The action method returns a model class.
  • Es gibt abgeleitete Klassen, die möglicherweise zur Laufzeit zurückgegeben werden.There are derived classes which might be returned at runtime.
  • Die von der Aktion zurückgegebene abgeleitete Klasse muss zur Laufzeit bekannt sein.The derived class returned by the action must be known at runtime.

Angenommen, die Aktionsmethode lautet wie folgt:For example, suppose the action method:

  • Signature gibt einen Person Typ zurück.Signature returns a Person type.
  • Kann einen- Student oder-Typ zurückgeben Instructor , der von abgeleitet wird Person .Can return a Student or Instructor type that derives from Person.

Damit der Formatierer nur- Student Objekte behandelt, überprüfen Sie den Typ von Object im Kontext Objekt, das für die-Methode bereitgestellt wird CanWriteResult .For the formatter to handle only Student objects, check the type of Object in the context object provided to the CanWriteResult method. Wenn die Aktionsmethode Folgendes zurückgibt IActionResult :When the action method returns IActionResult:

  • Es ist nicht erforderlich, zu verwenden CanWriteResult .It's not necessary to use CanWriteResult.
  • Die- CanWriteType Methode empfängt den Lauf Zeittyp.The CanWriteType method receives the runtime type.

Überschreiben von "Read requestbodyasync" und "schreiteresponsebodyasync"Override ReadRequestBodyAsync and WriteResponseBodyAsync

Die Deserialisierung oder Serialisierung wird in ReadRequestBodyAsync oder ausgeführt WriteResponseBodyAsync .Deserialization or serialization is performed in ReadRequestBodyAsync or WriteResponseBodyAsync. Im folgenden Beispiel wird gezeigt, wie Sie Dienste aus dem Container für die Abhängigkeitsinjektion erhalten.The following example shows how to get services from the dependency injection container. Dienste können nicht aus Konstruktorparametern abgerufen werden.Services can't be obtained from constructor parameters.

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());
}

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);
}

Konfigurieren von MVC zum Verwenden eines benutzerdefinierten FormatierersHow to configure MVC to use a custom formatter

Wenn Sie einen benutzerdefinierten Formatierer verwenden, fügen Sie der InputFormatters- oder OutputFormatters-Sammlung eine Instanz der Formatiererklasse hinzu.To use a custom formatter, add an instance of the formatter class to the InputFormatters or OutputFormatters collection.

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

Formatierer werden in der Reihenfolge überprüft, in der Sie sie einfügen.Formatters are evaluated in the order you insert them. Der erste hat Vorrang.The first one takes precedence.

Die komplette VcardInputFormatter KlasseThe complete VcardInputFormatter class

Der folgende Code zeigt die- VcardInputFormatter Klasse aus dem Beispiel:The following code shows the VcardInputFormatter class from the sample:

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;
    }
}

Testen der AppTest the app

Führen Sie die Beispiel-App für diesen Artikel aus, der grundlegende vCard-Eingabe-und-Ausgabe Formatierer implementiert.Run the sample app for this article, which implements basic vCard input and output formatters. Die APP liest und schreibt vCards ähnlich der folgenden:The app reads and writes vCards similar to the following:

BEGIN:VCARD
VERSION:2.1
N:Davolio;Nancy
FN:Nancy Davolio
END:VCARD

Um die vcardausgabe anzuzeigen, führen Sie die APP aus, und senden Sie eine GET-Anforderung mit dem Accept-Header text/vcard an https://localhost:5001/api/contacts .To see vCard output, run the app and send a Get request with Accept header text/vcard to https://localhost:5001/api/contacts.

So fügen Sie der Auflistung von Kontakten im Arbeitsspeicher eine vCard hinzu:To add a vCard to the in-memory collection of contacts:

  • Senden Sie eine Post Anforderung an /api/contacts mit einem Tool wie postman.Send a Post request to /api/contacts with a tool like Postman.
  • Legen Sie den Header Content-Type auf text/vcard fest.Set the Content-Type header to text/vcard.
  • Legen Sie vCard Text im Textkörper fest, wie im vorherigen Beispiel dargestellt.Set vCard text in the body, formatted like the preceding example.

Zusätzliche RessourcenAdditional resources