Miroslav Holec
Premium

Podpora content negotiation a XML formátu v REST API

Miroslav Holec   20. listopadu 2019

Článek se vztahuje k verzi produktu ASP.NET Core 3.0

Tento článek byl napsán v roce 2019. Vývojářské technologie se neustále inovují a článek již nemusí popisovat aktuální stav technologie, ideální řešení a můj současný pohled na dané téma.

Použitelnosti REST API pomáhá podpora různých formátů pro serializaci. Výchozí volbou pro většinu vývojářských týmů je logicky JSON. Z hlediska integrace REST API v historicky starších systémech se však vyplatí přidat také podporu pro XML. To je v ASP.NET Core naštěstí velmi jednoduché.

Výchozí nastavení ASP.NET Core

Rozhodnete-li se postavit si jednoduché REST API v ASP.NET Core MVC, automaticky máte v projektu zajištěnou podporu content negotiation a formátu JSON. V zásadě máte v projektu zapojené:

  1. Input Formatters - slouží pro deserializaci payloadu
  2. Output Formatters - slouží pro serializaci objektů do zvoleného formátu

Automatická volba formatteru probíhá na základě HTTP hlaviček. V případě input formatters se zohledňuje request hlavička Content-Type a v případě požadovaného návratového typu hlavička Accept. Pokud HTTP hlavička chybí, použijí se výchozí formattery: SystemTextJsonInputSerializer a SystemTextJsonOutputFormatter.

Status Codes 406 a 415

Výchozí nastavení není správné, protože vytváří prostor pro vznik chyb. Konzument by měl deklarovat, v jakém formátu posílá payload a v jakém formátu chce data vrátit. Pokud nejsme schopni data v REST API přijmout, měli bychom vracet 415 Unsupported Media Type. Jestliže nejsme schopni data emitovat v požadovaném formátu, měli bychom vracet 406 Not Acceptable. Nastavení v ASP.NET Core je jednoduché:

public void ConfigureServices(IServiceCollection services)
{
    var mvc = services.AddControllers(options =>
    {
        options.ReturnHttpNotAcceptable = true;
    });
}

JSON Serializer

Od verze ASP.NET Core 3.0 se používá jako výchozí serializer System.Text.Json. Jedná se o nový, výkonnostně optimalizovaný serializer. Ve starších verzích frameworku byla zapojena podpora Newtonsoft.Json. Znamená to, že po migraci na ASP.NET Core 3.0 lze očekávat, že se serializace a deserializace může (a v praxi bude) chovat trochu jinak. Proti tomu můžete bojovat více způsoby:

  1. Konfigurací nového JsonSerializeru
  2. Náhradou JsonSerializeru za tradiční Newtonsoft.Json

Konfigurace System.Text.Json

První pokusy můžete věnovat snaze o konfiguraci nového serializeru. Slouží k tomu třída JsonSerializerOptions a můžete ji nastavit buď globálně, nebo lokálně na místě, kde serializaci používáte.

var options = new JsonSerializerOptions
{
    WriteIndented = true,
    IgnoreNullValues = true,
    PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
    DictionaryKeyPolicy = JsonNamingPolicy.CamelCase
};

context.Response.Headers.Add(HeaderNames.ContentType, "application/json");
await JsonSerializer.SerializeAsync(context.Response.Body, payload, options);

Jestliže Vám konfigurace nebude stačit a budete stále bojovat s chováním nového serializeru, dost možná vám dojde trpělivost a nakonec skončíte u druhé možnosti.

Přechod zpět na Newtonsoft.Json

Druhá možnost je vrátit se k Newtonsoft.Json serializeru. Pro tento účel můžeme použít integrační NuGet balíček

<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="3.0.0" />

a následně zapojit serializer pomocí provolání

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllers().AddNewtonsoftJson(options =>
    {
        // settings
    });
}

Zapojení podpory XML

Konečně se také dostáváme k tomu, jak lze snadno zapojit podporu XML. Chcete-li přidat hromadně input + output formatter, stačí přidat řádky:

public void ConfigureServices(IServiceCollection services)
{
   var mvc = services.AddControllers();
   mvc.AddXmlSerializerFormatters();
}

Alternativně mohu zapojit například jen output formatter. To se hodí pro scénáře, kdy vaše API zpracovává výhradně JSON, ale některé endpointy musí umět vracet ven i XML.

var mvc = services.AddControllers(options =>
{
    options.OutputFormatters.Add(new XmlSerializerOutputFormatter());
});