Delen via


Aangepaste formatters in ASP.NET Core Web-API

ASP.NET Core MVC ondersteunt gegevensuitwisseling in web-API's met behulp van invoer- en uitvoerindelingen. Invoerindelingen worden gebruikt door Modelbinding. Uitvoerindelingen worden gebruikt om antwoorden op te maken.

Het framework biedt ingebouwde invoer- en uitvoerindelingen voor JSON en XML. Het biedt een ingebouwde uitvoerindeling voor tekst zonder opmaak, maar biedt geen invoeropmaak voor tekst zonder opmaak.

In dit artikel wordt beschreven hoe u ondersteuning voor aanvullende indelingen toevoegt door aangepaste formatters te maken. Zie TextPlainInputFormatter op GitHub voor een voorbeeld van een aangepaste indeling voor tekstinvoer zonder opmaak.

Voorbeeldcode bekijken of downloaden (hoe download je)

Wanneer u een aangepaste formatter gebruikt

Gebruik een aangepaste formatter om ondersteuning toe te voegen voor een inhoudstype dat niet wordt verwerkt door de ingebouwde formatters.

Overzicht van het maken van een aangepaste formatter

Een aangepaste formatter maken:

  • Voor het serialiseren van gegevens die naar de client worden verzonden, maakt u een outputformatterklasse.
  • Voor het deserialiseren van gegevens die van de client zijn ontvangen, maakt u een invoerindelingsklasse.
  • Voeg exemplaren van formatterklassen toe aan de InputFormatters en OutputFormatters verzamelingen in MvcOptions.

Een aangepaste formatter maken

Een formatter maken:

De volgende code toont de VcardOutputFormatter klasse uit het voorbeeld:

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

Afgeleid van de juiste basisklasse

Voor tekstmediatypen (bijvoorbeeld vCard), afgeleid van de TextInputFormatter of TextOutputFormatter basisklasse:

public class VcardOutputFormatter : TextOutputFormatter

Voor binaire typen, afgeleid van de InputFormatter of OutputFormatter basisklasse.

Ondersteunde mediatypen en coderingen opgeven

Geef in de constructor ondersteunde mediatypen en coderingen op door deze toe te voegen aan de SupportedMediaTypes en SupportedEncodings verzamelingen:

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

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

Een formatterklasse kan geen constructorinjectie gebruiken voor de afhankelijkheden. Kan bijvoorbeeld ILogger<VcardOutputFormatter> niet worden toegevoegd als parameter aan de constructor. Voor toegang tot services gebruikt u het contextobject dat wordt doorgegeven aan de methoden. Een codevoorbeeld in dit artikel en het voorbeeld laten zien hoe u dit doet.

De methoden CanReadType en CanWriteType overschrijven

Geef het type op waaruit moet worden gedeserialiseerd of geserialiseerd door de CanReadType of CanWriteType methoden te overschrijven. Als u bijvoorbeeld vCard-tekst wilt maken op basis van een Contact type en omgekeerd:

protected override bool CanWriteType(Type? type)
    => typeof(Contact).IsAssignableFrom(type)
        || typeof(IEnumerable<Contact>).IsAssignableFrom(type);

De methode CanWriteResult

In sommige scenario's moet CanWriteResult worden overschreven in plaats van CanWriteType. Gebruik CanWriteResult deze optie als aan de volgende voorwaarden wordt voldaan:

  • De actiemethode retourneert een modelklasse.
  • Er zijn afgeleide klassen die tijdens runtime kunnen worden geretourneerd.
  • De afgeleide klasse die door de actie wordt geretourneerd, moet tijdens runtime bekend zijn.

Neem bijvoorbeeld de actiemethode:

  • Handtekening retourneert een Person type.
  • Kan een Student of Instructor type retourneren dat is afgeleid van Person.

Als u wilt dat alleen Student-objecten worden verwerkt, controleert u in het contextobject Object het type dat aan de CanWriteResult methode is verstrekt. Wanneer de actiemethode retourneert IActionResult:

  • Het is niet nodig om te gebruiken CanWriteResult.
  • De CanWriteType methode ontvangt het runtimetype.

ReadRequestBodyAsync en WriteResponseBodyAsync overschrijven

Deserialisatie of serialisatie wordt uitgevoerd in ReadRequestBodyAsync of WriteResponseBodyAsync. In het volgende voorbeeld ziet u hoe u services kunt ophalen uit de container voor afhankelijkheidsinjectie. Services kunnen niet worden verkregen uit constructorparameters:

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

MVC configureren voor het gebruik van een aangepaste formatter

Als u een aangepaste formatter wilt gebruiken, voegt u een exemplaar van de formatterklasse toe aan de MvcOptions.InputFormatters of MvcOptions.OutputFormatters verzameling:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers(options =>
{
    options.InputFormatters.Insert(0, new VcardInputFormatter());
    options.OutputFormatters.Insert(0, new VcardOutputFormatter());
});

Formatters worden geëvalueerd in de volgorde waarin ze worden ingevoegd, waarbij de eerste prioriteit heeft.

De volledige VcardInputFormatter klasse

De volgende code toont de VcardInputFormatter klasse uit het voorbeeld:

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

De app testen

Voer de voorbeeld-app uit voor dit artikel, waarmee eenvoudige vCard-invoer- en uitvoerindelingen worden geïmplementeerd. De app leest en schrijft vCards die vergelijkbaar zijn met de volgende indeling:

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

Als u de vCard-uitvoer wilt zien, voert u de app uit en verzendt u een Get-aanvraag met de header text/vcard Accepteren naar https://localhost:<port>/api/contacts.

Een vCard toevoegen aan de verzameling contactpersonen in het geheugen:

  • Verzend een Post aanvraag naar /api/contacts met een hulpprogramma zoals http-repl.
  • Stel de Content-Type-header in op text/vcard.
  • Stel vCard tekst in de hoofdtekst in, opgemaakt zoals in het voorgaande voorbeeld.

Additional resources

ASP.NET Core MVC ondersteunt gegevensuitwisseling in web-API's met behulp van invoer- en uitvoerindelingen. Invoerindelingen worden gebruikt door Modelbinding. Uitvoerindelingen worden gebruikt om antwoorden op te maken.

Het framework biedt ingebouwde invoer- en uitvoerindelingen voor JSON en XML. Het biedt een ingebouwde uitvoerindeling voor tekst zonder opmaak, maar biedt geen invoeropmaak voor tekst zonder opmaak.

In dit artikel wordt beschreven hoe u ondersteuning voor aanvullende indelingen toevoegt door aangepaste formatters te maken. Zie TextPlainInputFormatter op GitHub voor een voorbeeld van een aangepaste indeling voor tekstinvoer zonder opmaak.

Voorbeeldcode bekijken of downloaden (hoe download je)

Wanneer u een aangepaste formatter gebruikt

Gebruik een aangepaste formatter om ondersteuning toe te voegen voor een inhoudstype dat niet wordt verwerkt door de ingebouwde formatters.

Overzicht van het maken van een aangepaste formatter

Een aangepaste formatter maken:

  • Voor het serialiseren van gegevens die naar de client worden verzonden, maakt u een outputformatterklasse.
  • Voor het deserialiseren van gegevens die van de client zijn ontvangen, maakt u een invoerindelingsklasse.
  • Voeg exemplaren van formatterklassen toe aan de InputFormatters en OutputFormatters verzamelingen in MvcOptions.

Een aangepaste formatter maken

Een formatter maken:

De volgende code toont de VcardOutputFormatter klasse uit het voorbeeld:

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

Afgeleid van de juiste basisklasse

Voor tekstmediatypen (bijvoorbeeld vCard), afgeleid van de TextInputFormatter of TextOutputFormatter basisklasse:

public class VcardOutputFormatter : TextOutputFormatter

Voor binaire typen, afgeleid van de InputFormatter of OutputFormatter basisklasse.

Ondersteunde mediatypen en coderingen opgeven

Geef in de constructor ondersteunde mediatypen en coderingen op door deze toe te voegen aan de SupportedMediaTypes en SupportedEncodings verzamelingen:

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

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

Een formatterklasse kan geen constructorinjectie gebruiken voor de afhankelijkheden. Kan bijvoorbeeld ILogger<VcardOutputFormatter> niet worden toegevoegd als parameter aan de constructor. Voor toegang tot services gebruikt u het contextobject dat wordt doorgegeven aan de methoden. Een codevoorbeeld in dit artikel en het voorbeeld laten zien hoe u dit doet.

De methoden CanReadType en CanWriteType overschrijven

Geef het type op waaruit moet worden gedeserialiseerd of geserialiseerd door de CanReadType of CanWriteType methoden te overschrijven. Als u bijvoorbeeld vCard-tekst wilt maken op basis van een Contact type en omgekeerd:

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

De methode CanWriteResult

In sommige scenario's moet CanWriteResult worden overschreven in plaats van CanWriteType. Gebruik CanWriteResult deze optie als aan de volgende voorwaarden wordt voldaan:

  • De actiemethode retourneert een modelklasse.
  • Er zijn afgeleide klassen die tijdens runtime kunnen worden geretourneerd.
  • De afgeleide klasse die door de actie wordt geretourneerd, moet tijdens runtime bekend zijn.

Neem bijvoorbeeld de actiemethode:

  • Handtekening retourneert een Person type.
  • Kan een Student of Instructor type retourneren dat is afgeleid van Person.

Als u wilt dat alleen Student-objecten worden verwerkt, controleert u in het contextobject Object het type dat aan de CanWriteResult methode is verstrekt. Wanneer de actiemethode retourneert IActionResult:

  • Het is niet nodig om te gebruiken CanWriteResult.
  • De CanWriteType methode ontvangt het runtimetype.

ReadRequestBodyAsync en WriteResponseBodyAsync overschrijven

Deserialisatie of serialisatie wordt uitgevoerd in ReadRequestBodyAsync of WriteResponseBodyAsync. In het volgende voorbeeld ziet u hoe u services kunt ophalen uit de container voor afhankelijkheidsinjectie. Services kunnen niet worden verkregen uit constructorparameters:

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

MVC configureren voor het gebruik van een aangepaste formatter

Als u een aangepaste formatter wilt gebruiken, voegt u een exemplaar van de formatterklasse toe aan de MvcOptions.InputFormatters of MvcOptions.OutputFormatters verzameling:

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

Formatters worden geëvalueerd in de volgorde waarin u ze invoegt. De eerste heeft voorrang.

De volledige VcardInputFormatter klasse

De volgende code toont de VcardInputFormatter klasse uit het voorbeeld:

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

De app testen

Voer de voorbeeld-app uit voor dit artikel, waarmee eenvoudige vCard-invoer- en uitvoerindelingen worden geïmplementeerd. De app leest en schrijft vCards die vergelijkbaar zijn met de volgende indeling:

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

Als u de vCard-uitvoer wilt zien, voert u de app uit en verzendt u een Get-aanvraag met de header text/vcard Accepteren naar https://localhost:5001/api/contacts.

Een vCard toevoegen aan de verzameling contactpersonen in het geheugen:

  • Verzend een Post aanvraag naar /api/contacts met een hulpprogramma zoals curl.
  • Stel de Content-Type-header in op text/vcard.
  • Stel vCard tekst in de hoofdtekst in, opgemaakt zoals in het voorgaande voorbeeld.

Additional resources