Megosztás a következőn keresztül:


Egyéni formázók a ASP.NET Core Web API-ban

ASP.NET Core MVC támogatja a webes API-kban az adatcserét bemeneti és kimeneti formázók használatával. A bemeneti formázókat a modellkötés használja. A kimeneti formázók a válaszok formázására szolgálnak.

A keretrendszer beépített bemeneti és kimeneti formátumokat biztosít a JSON-hoz és az XML-hez. Beépített kimeneti formátumot biztosít az egyszerű szöveghez, de nem biztosít bemeneti formázót az egyszerű szöveghez.

Ez a cikk bemutatja, hogyan hozhat létre saját formázókat a további formátumok támogatásának érdekében. Példa egy egyszerű szöveges beviteli formátumra: TextPlainInInputFormatter a GitHubon.

Mintakód megtekintése vagy letöltése (hogyan töltsd le)

Mikor érdemes egyéni formázót használni?

Egyéni formázó használatával olyan tartalomtípust támogathat, amelyet nem a beépített formázók kezelnek.

Egyéni formázó létrehozásának áttekintése

Egyéni formázó létrehozásához:

  • Az ügyfélnek küldött adatok szerializálásához hozzon létre egy kimeneti formázó osztályt.
  • Az ügyféltől kapott adatok deszerializálásához hozzon létre egy bemeneti formázó osztályt.
  • Formázóosztályok példányait adja hozzá a InputFormatters és OutputFormatters gyűjteményekhez a MvcOptions-ben.

Egyéni formázó létrehozása

Formázó létrehozása:

Az alábbi kód a VcardOutputFormattermintából származó osztályt mutatja be:

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

Származtasson a megfelelő alaposztályból

Szöveges médiatípusokhoz (például vCard) az TextInputFormatter vagy TextOutputFormatter alaposztályból kell származtatni:

public class VcardOutputFormatter : TextOutputFormatter

Bináris típusok esetén az InputFormatterOutputFormatter alaposztályból származtatható.

Támogatott médiatípusok és kódolások megadása

A konstruktorban adja meg a támogatott médiatípusokat és kódolásokat úgy, hogy hozzáadja azokat a SupportedMediaTypes és SupportedEncodings gyűjteményekhez:

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

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

A formázóosztályok nem használhatnak konstruktorinjektálást a függőségeihez. Például ILogger<VcardOutputFormatter> nem adható hozzá paraméterként a konstruktorhoz. A szolgáltatások eléréséhez használja a metódusok számára átadott környezeti objektumot. A jelen cikkben és a mintában szereplő kód példája bemutatja, hogyan teheti ezt meg.

CanReadType és CanWriteType felülbírálása

Adja meg azt a típust, amelybe deszerializálni vagy amelyből szerializálni szeretne a CanReadType vagy CanWriteType metódusok felülírásával. Például szöveg létrehozása vCard típusból Contact és fordítva:

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

A CanWriteResult metódus

Bizonyos esetekben a CanWriteResult felülbírálása szükséges ahelyett, hogy a CanWriteType-et felül kellene bírálni. Használja CanWriteResult a következő feltételek teljesülése esetén:

  • A műveletmetódus egy modellosztályt ad vissza.
  • Vannak származtatott osztályok, amelyeket futásidőben lehet visszaadni.
  • A művelet által visszaadott származtatott osztálynak futásidőben ismertnek kell lennie.

Tegyük fel például, hogy a művelet metódusa:

  • Az aláírás visszaad egy Person típust.
  • Visszaadhat egy Student vagy Instructor típust, amely Person-ből származik.

Ha a formázó csak Student objektumokat szeretne kezelni, ellenőrizze a metódushoz Object megadott környezeti objektum típusátCanWriteResult. Amikor a műveletfüggvény a következőt adja vissza: IActionResult

  • Nem szükséges használni CanWriteResult.
  • A CanWriteType metódus megkapja a futtatókörnyezet típusát.

A ReadRequestBodyAsync és a WriteResponseBodyAsync felülbírálása

A deszerializálás vagy a szerializálás a ReadRequestBodyAsync vagy WriteResponseBodyAsync helyen történik. Az alábbi példa bemutatja, hogyan kérhet le szolgáltatásokat a függőséginjektálási tárolóból. A szolgáltatások nem kérhetők le konstruktorparaméterekből:

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

Az MVC konfigurálása egyéni formázó használatára

Egyéni formázó használatához adja hozzá a formázóosztály egy példányát a MvcOptions.InputFormatters vagy MvcOptions.OutputFormatters gyűjteményhez.

var builder = WebApplication.CreateBuilder(args);

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

A formázókat a beillesztett sorrendben értékelik ki, amelyben az első élvez elsőbbséget.

A teljes VcardInputFormatter osztály

Az alábbi kód a VcardInputFormattermintából származó osztályt mutatja be:

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

Az alkalmazás tesztelése

Futtassa a cikkhez tartozó mintaalkalmazást, amely alapszintű vCard bemeneti és kimeneti formátumokat implementál. Az alkalmazás a következő formátumhoz hasonló vCard-fájlokat olvas és ír:

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

A vCard kimenetének megtekintéséhez futtassa az alkalmazást, és küldjön egy GET-kérést Accept fejléccel text/vcardhttps://localhost:<port>/api/contacts.

Egy vCard hozzáadása a névjegyek gyűjteményének memóriájába:

  • Küldjön egy Post kérést a /api/contacts számára egy olyan eszközzel, mint a http-repl.
  • Állítsa a Content-Type fejlécet text/vcardértékre.
  • Állítsa be vCard a szövegtörzsben az előző példához hasonlóan formázott szöveget.

További erőforrások

ASP.NET Core MVC támogatja a webes API-kban az adatcserét bemeneti és kimeneti formázók használatával. A bemeneti formázókat a modellkötés használja. A kimeneti formázók a válaszok formázására szolgálnak.

A keretrendszer beépített bemeneti és kimeneti formátumokat biztosít a JSON-hoz és az XML-hez. Beépített kimeneti formátumot biztosít az egyszerű szöveghez, de nem biztosít bemeneti formázót az egyszerű szöveghez.

Ez a cikk bemutatja, hogyan hozhat létre saját formázókat a további formátumok támogatásának érdekében. Példa egy egyszerű szöveges beviteli formátumra: TextPlainInInputFormatter a GitHubon.

Mintakód megtekintése vagy letöltése (hogyan töltsd le)

Mikor érdemes egyéni formázót használni?

Egyéni formázó használatával olyan tartalomtípust támogathat, amelyet nem a beépített formázók kezelnek.

Egyéni formázó létrehozásának áttekintése

Egyéni formázó létrehozásához:

  • Az ügyfélnek küldött adatok szerializálásához hozzon létre egy kimeneti formázó osztályt.
  • Az ügyféltől kapott adatok deszerializálásához hozzon létre egy bemeneti formázó osztályt.
  • Formázóosztályok példányait adja hozzá a InputFormatters és OutputFormatters gyűjteményekhez a MvcOptions-ben.

Egyéni formázó létrehozása

Formázó létrehozása:

Az alábbi kód a VcardOutputFormattermintából származó osztályt mutatja be:

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

Származtasson a megfelelő alaposztályból

Szöveges médiatípusokhoz (például vCard) az TextInputFormatter vagy TextOutputFormatter alaposztályból kell származtatni:

public class VcardOutputFormatter : TextOutputFormatter

Bináris típusok esetén az InputFormatterOutputFormatter alaposztályból származtatható.

Támogatott médiatípusok és kódolások megadása

A konstruktorban adja meg a támogatott médiatípusokat és kódolásokat úgy, hogy hozzáadja azokat a SupportedMediaTypes és SupportedEncodings gyűjteményekhez:

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

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

A formázóosztályok nem használhatnak konstruktorinjektálást a függőségeihez. Például ILogger<VcardOutputFormatter> nem adható hozzá paraméterként a konstruktorhoz. A szolgáltatások eléréséhez használja a metódusok számára átadott környezeti objektumot. A jelen cikkben és a mintában szereplő kód példája bemutatja, hogyan teheti ezt meg.

CanReadType és CanWriteType felülbírálása

Adja meg azt a típust, amelybe deszerializálni vagy amelyből szerializálni szeretne a CanReadType vagy CanWriteType metódusok felülírásával. Például szöveg létrehozása vCard típusból Contact és fordítva:

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

A CanWriteResult metódus

Bizonyos esetekben a CanWriteResult felülbírálása szükséges ahelyett, hogy a CanWriteType-et felül kellene bírálni. Használja CanWriteResult a következő feltételek teljesülése esetén:

  • A műveletmetódus egy modellosztályt ad vissza.
  • Vannak származtatott osztályok, amelyeket futásidőben lehet visszaadni.
  • A művelet által visszaadott származtatott osztálynak futásidőben ismertnek kell lennie.

Tegyük fel például, hogy a művelet metódusa:

  • Az aláírás visszaad egy Person típust.
  • Visszaadhat egy Student vagy Instructor típust, amely Person-ből származik.

Ha a formázó csak Student objektumokat szeretne kezelni, ellenőrizze a metódushoz Object megadott környezeti objektum típusátCanWriteResult. Amikor a műveletfüggvény a következőt adja vissza: IActionResult

  • Nem szükséges használni CanWriteResult.
  • A CanWriteType metódus megkapja a futtatókörnyezet típusát.

A ReadRequestBodyAsync és a WriteResponseBodyAsync felülbírálása

A deszerializálás vagy a szerializálás a ReadRequestBodyAsync vagy WriteResponseBodyAsync helyen történik. Az alábbi példa bemutatja, hogyan kérhet le szolgáltatásokat a függőséginjektálási tárolóból. A szolgáltatások nem kérhetők le konstruktorparaméterekből:

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

Az MVC konfigurálása egyéni formázó használatára

Egyéni formázó használatához adja hozzá a formázóosztály egy példányát a MvcOptions.InputFormatters vagy MvcOptions.OutputFormatters gyűjteményhez.

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

A formázók a beszúrt sorrendben lesznek kiértékelve. Az első elsőbbséget élvez.

A teljes VcardInputFormatter osztály

Az alábbi kód a VcardInputFormattermintából származó osztályt mutatja be:

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

Az alkalmazás tesztelése

Futtassa a cikkhez tartozó mintaalkalmazást, amely alapszintű vCard bemeneti és kimeneti formátumokat implementál. Az alkalmazás a következő formátumhoz hasonló vCard-fájlokat olvas és ír:

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

A vCard kimenetének megtekintéséhez futtassa az alkalmazást, és küldjön egy GET-kérést Accept fejléccel text/vcardhttps://localhost:5001/api/contacts.

Egy vCard hozzáadása a névjegyek gyűjteményének memóriájába:

  • Küldjön egy Post kérést a /api/contacts részére egy olyan eszközzel, mint a curl.
  • Állítsa a Content-Type fejlécet text/vcardértékre.
  • Állítsa be vCard a szövegtörzsben az előző példához hasonlóan formázott szöveget.

További erőforrások