Vlastní formátovací moduly ve webových rozhraních API ASP.NET Core

ASP.NET Core MVC podporuje výměnu dat ve webových rozhraních API pomocí vstupních a výstupních formátovacích nástrojů. Vstupní formátovací moduly používají vazby modelu. Výstupní formátovací moduly se používají k formátování odpovědí.

Architektura poskytuje integrované vstupní a výstupní formátovací moduly pro JSON a XML. Poskytuje integrovaný výstupní formátovací modul pro prostý text, ale neposkytuje vstupní formátovací modul pro prostý text.

Tento článek ukazuje, jak přidat podporu dalších formátů vytvořením vlastních formátovacích souborů. Příklad vlastního formátu formátu zadávání prostého textu najdete v tématu TextPlainInputFormatter na GitHubu.

Zobrazení nebo stažení ukázkového kódu (postup stažení)

Kdy použít vlastní formátovací modul

Pomocí vlastního formátovače můžete přidat podporu pro typ obsahu, který není zpracován integrovanými formátovacími moduly.

Přehled vytvoření vlastního formátovače

Vytvoření vlastního formátovače:

  • Pro serializaci dat odesílaných klientovi vytvořte výstupní třídu formátovače.
  • Pro deserializaci dat přijatých z klienta vytvořte vstupní formátovací třídu.
  • Přidejte instance tříd formátovače do InputFormatters kolekcí OutputFormatters v .MvcOptions

Vytvoření vlastního formátovače

Vytvoření formátovače:

Následující kód ukazuje VcardOutputFormatter třídu z ukázky:

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

Odvození z příslušné základní třídy

Pro typy textových médií (například vCard) odvozujte ze TextInputFormatter základní třídy nebo TextOutputFormatter základní třídy:

public class VcardOutputFormatter : TextOutputFormatter

Binární typy jsou odvozeny od InputFormatter základní třídy.OutputFormatter

Určení podporovaných typů a kódování médií

V konstruktoru zadejte podporované typy médií a kódování přidáním do SupportedMediaTypes kolekcí:SupportedEncodings

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

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

Formátovací třída nemůže pro své závislosti použít injektáž konstruktoru. Například ILogger<VcardOutputFormatter> nelze do konstruktoru přidat jako parametr. Pro přístup ke službám použijte kontextový objekt, který se předá metodám. Příklad kódu v tomto článku a ukázka ukazuje, jak to udělat.

Přepsání canReadType a CanWriteType

Zadejte typ, který se má deserializovat do nebo serializovat z přepsáním CanReadType nebo CanWriteType metodami. Pokud chcete například vytvořit text vizitky Contact vCard z typu a naopak:

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

Metoda CanWriteResult

V některých scénářích CanWriteResult je nutné přepsat místo CanWriteType. Použijte CanWriteResult , pokud jsou splněny následující podmínky:

  • Metoda akce vrátí třídu modelu.
  • Existují odvozené třídy, které mohou být vráceny za běhu.
  • Odvozená třída vrácená akcí musí být známa za běhu.

Předpokládejme například, že metoda akce:

  • Podpis vrátí Person typ.
  • Může vrátit Student nebo Instructor typ, který je odvozen od Person.

Chcete-li formátovací modul zpracovat pouze Student objekty, zkontrolujte typ Object v kontextovém objektu poskytnuté metodě CanWriteResult . Když metoda akce vrátí IActionResult:

  • Není nutné používat CanWriteResult.
  • Metoda CanWriteType obdrží typ modulu runtime.

Přepsání readRequestBodyAsync a WriteResponseBodyAsync

Deserializace nebo serializace se provádí v ReadRequestBodyAsync nebo WriteResponseBodyAsync. Následující příklad ukazuje, jak získat služby z kontejneru injektáže závislostí. Služby nelze získat z parametrů konstruktoru:

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

Konfigurace MVC pro použití vlastního formátovače

Pokud chcete použít vlastní formátovací modul, přidejte instanci třídy formatter do MvcOptions.InputFormatters kolekce nebo MvcOptions.OutputFormatters kolekce:

var builder = WebApplication.CreateBuilder(args);

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

Formátovací moduly se vyhodnocují v pořadí, ve kterém má první přednost.

Úplná VcardInputFormatter třída

Následující kód ukazuje VcardInputFormatter třídu z ukázky:

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

Otestování aplikace

Spusťte ukázkovou aplikaci pro tento článek, která implementuje základní vstupní a výstupní formátovací moduly vCard. Aplikace čte a zapisuje vizitky vCard podobně jako v následujícím formátu:

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

Pokud chcete zobrazit výstup vizitky vCard, spusťte aplikaci a odešlete požadavek Get s hlavičkou text/vcard Accept na https://localhost:<port>/api/contactsadresu .

Přidání vizitky vCard do kolekce kontaktů v paměti:

  • Post Odešlete požadavek /api/contacts pomocí nástroje, jako je http-repl.
  • Content-Type Nastavte záhlaví na text/vcardhodnotu .
  • Nastavte vCard text v textu naformátovaný jako předchozí příklad.

Další materiály

ASP.NET Core MVC podporuje výměnu dat ve webových rozhraních API pomocí vstupních a výstupních formátovacích nástrojů. Vstupní formátovací moduly používají vazby modelu. Výstupní formátovací moduly se používají k formátování odpovědí.

Architektura poskytuje integrované vstupní a výstupní formátovací moduly pro JSON a XML. Poskytuje integrovaný výstupní formátovací modul pro prostý text, ale neposkytuje vstupní formátovací modul pro prostý text.

Tento článek ukazuje, jak přidat podporu dalších formátů vytvořením vlastních formátovacích souborů. Příklad vlastního formátu formátu zadávání prostého textu najdete v tématu TextPlainInputFormatter na GitHubu.

Zobrazení nebo stažení ukázkového kódu (postup stažení)

Kdy použít vlastní formátovací modul

Pomocí vlastního formátovače můžete přidat podporu pro typ obsahu, který není zpracován integrovanými formátovacími moduly.

Přehled vytvoření vlastního formátovače

Vytvoření vlastního formátovače:

  • Pro serializaci dat odesílaných klientovi vytvořte výstupní třídu formátovače.
  • Pro deserializaci dat přijatých z klienta vytvořte vstupní formátovací třídu.
  • Přidejte instance tříd formátovače do InputFormatters kolekcí OutputFormatters v .MvcOptions

Vytvoření vlastního formátovače

Vytvoření formátovače:

Následující kód ukazuje VcardOutputFormatter třídu z ukázky:

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

Odvození z příslušné základní třídy

Pro typy textových médií (například vCard) odvozujte ze TextInputFormatter základní třídy nebo TextOutputFormatter základní třídy:

public class VcardOutputFormatter : TextOutputFormatter

Binární typy jsou odvozeny od InputFormatter základní třídy.OutputFormatter

Určení podporovaných typů a kódování médií

V konstruktoru zadejte podporované typy médií a kódování přidáním do SupportedMediaTypes kolekcí:SupportedEncodings

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

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

Formátovací třída nemůže pro své závislosti použít injektáž konstruktoru. Například ILogger<VcardOutputFormatter> nelze do konstruktoru přidat jako parametr. Pro přístup ke službám použijte kontextový objekt, který se předá metodám. Příklad kódu v tomto článku a ukázka ukazuje, jak to udělat.

Přepsání canReadType a CanWriteType

Zadejte typ, který se má deserializovat do nebo serializovat z přepsáním CanReadType nebo CanWriteType metodami. Pokud chcete například vytvořit text vizitky Contact vCard z typu a naopak:

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

Metoda CanWriteResult

V některých scénářích CanWriteResult je nutné přepsat místo CanWriteType. Použijte CanWriteResult , pokud jsou splněny následující podmínky:

  • Metoda akce vrátí třídu modelu.
  • Existují odvozené třídy, které mohou být vráceny za běhu.
  • Odvozená třída vrácená akcí musí být známa za běhu.

Předpokládejme například, že metoda akce:

  • Podpis vrátí Person typ.
  • Může vrátit Student nebo Instructor typ, který je odvozen od Person.

Chcete-li formátovací modul zpracovat pouze Student objekty, zkontrolujte typ Object v kontextovém objektu poskytnuté metodě CanWriteResult . Když metoda akce vrátí IActionResult:

  • Není nutné používat CanWriteResult.
  • Metoda CanWriteType obdrží typ modulu runtime.

Přepsání readRequestBodyAsync a WriteResponseBodyAsync

Deserializace nebo serializace se provádí v ReadRequestBodyAsync nebo WriteResponseBodyAsync. Následující příklad ukazuje, jak získat služby z kontejneru injektáže závislostí. Služby nelze získat z parametrů konstruktoru:

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

Konfigurace MVC pro použití vlastního formátovače

Pokud chcete použít vlastní formátovací modul, přidejte instanci třídy formatter do MvcOptions.InputFormatters kolekce nebo MvcOptions.OutputFormatters kolekce:

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

Formátovací moduly se vyhodnocují v pořadí, v jakém je vložíte. První má přednost.

Úplná VcardInputFormatter třída

Následující kód ukazuje VcardInputFormatter třídu z ukázky:

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

Otestování aplikace

Spusťte ukázkovou aplikaci pro tento článek, která implementuje základní vstupní a výstupní formátovací moduly vCard. Aplikace čte a zapisuje vizitky vCard podobně jako v následujícím formátu:

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

Pokud chcete zobrazit výstup vizitky vCard, spusťte aplikaci a odešlete požadavek Get s hlavičkou text/vcard Accept na https://localhost:5001/api/contactsadresu .

Přidání vizitky vCard do kolekce kontaktů v paměti:

  • Post Odešlete žádost /api/contacts pomocí nástroje, jako je Postman.
  • Content-Type Nastavte záhlaví na text/vcardhodnotu .
  • Nastavte vCard text v textu naformátovaný jako předchozí příklad.

Další materiály