Vlastní formátovací funkce ve ASP.NET Core Api

Od Od Odysla Polakina a Toma Dykstra.

ASP.NET Core MVC podporuje výměnu dat ve webových rozhraních API pomocí vstupních a výstupních formátovatrů. Formátovací metody vstupu jsou používány vazbou modelu. K formátování odpovědí se používají formátovací soubory výstupu.

Tato rozhraní poskytuje integrované formátovací funkce vstupu a výstupu pro JSON a XML. Poskytuje integrovaný formátovací modul výstupu pro prostý text, ale neposkytuje vstupní formátovací modul pro prostý text.

Tento článek popisuje, jak přidat podporu pro další formáty vytvořením vlastních formátovatrů. Příklad formátování vlastního vstupu prostého textu najdete v tématu TextPlainInputFormatter na GitHub.

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

Kdy použít vlastní formátovací nástroje

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

Přehled použití vlastního formátovacího modulu

Vytvoření vlastního formátovacího modulu:

  • Pro serializaci dat odeslaných do klienta vytvořte výstupní třídu formátovacího modulu.
  • Pro deserializaci dat přijatých z klienta vytvořte vstupní třídu formátování.
  • Přidejte instance tříd formátování do kolekcí a InputFormatters OutputFormatters v MvcOptions .

Jak vytvořit vlastní třídu formátovacího modulu

Vytvoření formátovacího modulu:

Následující kód ukazuje třídu VcardOutputFormatter 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) jsou odvozeny ze TextInputFormatter základní TextOutputFormatter třídy nebo .

public class VcardOutputFormatter : TextOutputFormatter

U binárních typů je odvozen od InputFormatter základní OutputFormatter třídy nebo .

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

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

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

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

Třída formátovacího modulu nemůže pro své závislosti použít injektáž konstruktoru. Například nelze ILogger<VcardOutputFormatter> přidat jako parametr do konstruktoru. 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 ukazují, jak to provést.

Override CanReadType a CanWriteType

Zadejte typ, který chcete deserializovat do nebo serializovat z CanReadType přepsáním nebo CanWriteType metody. Například vytvoření textu vCard z typu a Contact 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 . Tuto CanWriteResult hodnotu použijte, pokud jsou splněny následující podmínky:

  • Metoda action 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ámá za běhu.

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

  • Funkce Signature vrací Person typ.
  • Může vrátit Student typ Instructor nebo odvozený z Person .

Formátovací modul pro zpracování pouze objektů, zkontrolujte typ v objektu Student Object kontextu poskytnuté CanWriteResult metody. Když metoda akce vrátí IActionResult :

  • Není nutné používat CanWriteResult .
  • Metoda CanWriteType přijímá 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);
}

Postup konfigurace MVC pro použití vlastního formátovacího modulu

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

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

Formátovací funkce se vyhodnocují v pořadí, ve které je vkládáte. První z nich má přednost.

Úplná VcardInputFormatter třída

Následující kód ukazuje třídu VcardInputFormatter 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í formátovací metody vstupu a výstupu vCard. Aplikace čte a zapisuje vCard podobné následujícímu:

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

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

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

  • Pomocí Post nástroje, /api/contacts jako je Postman, odešlete požadavek na .
  • Nastavte Content-Type hlavičku na text/vcard .
  • Nastavte vCard text v těle naformátovaný jako v předchozím příkladu.

Další materiály