Miroslav Holec
Premium

Minimal REST APIs v .NET 6

Miroslav Holec   21. července 2021

Článek se vztahuje k verzi produktu .NET 6

Tento článek je již zastaralý. Článek nemusí popisovat aktuální stav technologie, ideální řešení a můj současný pohled na dané téma.

Nové verze .NET Core poslední roky nepřinesly pro vývojáře REST API žádné revoluční změny. Většina novinek spíše vylepšovala a doplňovala stávající funkcionality frameworku. Před dvěma lety jsem na některých konferencích ukazoval alternativní možnost, jak vytvářet RESTová API bez MVC frameworku s využitím Endpoint Routingu. Od verze .NET 6 už to nebude alternativní přístup, ale zcela nový způsob návrhu REST API na platformě .NET.

Motivace

Před dvěma lety jsem ukazoval přístup návrhu REST API, který jsem pracovně označil MicroAPI (demo projekt najdete na GitHubu). Mou motivací bylo zahodit MVC framework, který s sebou nese mnoho zbytečného balastu nepoužitelného pro návrh REST API. Samotné MVC totiž registruje do DI na pět desítek zbytečných tříd. Základní podstatou mého přístupu bylo použití Endpoint Routingu a tehdy nových metod MapGet, MapPost, MapDelete a dalších metod kopírujících HTTP metody. Nepříjemnou daní za tento přístup byla nutnost celou řadu věcí dopisovat ručně. Příkladem budiž:

  • model binding a validace
  • resolve route parametrů a parsování
  • horší podpora DI - nutnost injection do Configure() metody
  • nutnost ručního zápisu Response nad HttpContextem

Ve své době ještě přicházelo v úvahu použít Nancy framework. Ten se ale zavázal pouze podpoře .NET Core 3.1 a následně definitivně zemřel.

Vývojáři v Microsoftu nově rozšířili Endpoint Routing a přidali některé ingredience z jazyka C# 9.0, díky kterým dosáhli kýženého výsledku. Motivací je oficiálně zjednodušení tvorby REST API bez zbytečných tříd (Startup, Controllers, atd.). Ve skutečnosti bylo snahou Microsoftu vytvořit konkurenční přístup vůči oblíbeným frameworkům, jako například Express.js:

import 'dotenv/config';
...
import express from 'express';

const app = express();

...

app.get('/', (req, res) => {
  return res.send('Received a GET HTTP method');
});

app.post('/', (req, res) => {
  return res.send('Received a POST HTTP method');
});

app.put('/', (req, res) => {
  return res.send('Received a PUT HTTP method');
});

app.delete('/', (req, res) => {
  return res.send('Received a DELETE HTTP method');
});

app.listen(process.env.PORT, () =>
  console.log(`Example app listening on port ${process.env.PORT}!`),
);

Tento jediný soubor se nedá se smečkou C# tříd tvořících objektově orientovaný přístup srovnat. Vývojáře na platformě .NET to příliš netankuje, protože mají OOP přístup a unifikované šablony rádi. Pro vývojáře z ostatních platforem však .NET pro návrh REST API nebyl příliš sexy. To se teď zcela změní.

Jak to inženýři v Microsoftu vymysleli

Vývojáři v zásadě vyřešili jednotlivé problémy, které jsem popsal v předchozích odstavcích. V úvodu je třeba říct, že celý projekt Minimal APIs s sebou nese mnoho práce. Z následujícího popisu to snad bude patrnější.

Response - přetížení Map{XXX} metod

V první řadě vývojáři rozšířili Endpoint Routing tak, aby nebylo nutné ručně sahat na response. Všechny metody Map{XX}, které jsou nad IEndpointBuilderem tak mají nové přetížení, které umožňuje vrátit Delegate místo RequestDelegate. V souvislosti s tím si lze vytvářet vlastní resulty implementací IResult interfacu a ty následně dle potřeby vracet. Stejné metody je možné použít i v nové třídě WebApplication, jelikož i tato třída realizuje rozhraní IEndpointRouteBuilder a IApplicationBuilder. WebApplication je v podstatě hybridní třída, která nahrazuje metodu Configure() ze Startup.cs a nevyžaduje explicitní deklaraci endpoint routingu skrze UseRouting a UseEndpoints.

Extension metody nad Response

Od .NET 5 máme k dispozici nové extension metody nad HttpContextem a HttpResponse. Tato rozšíření byla přidána primárně kvůli technologii Blazor, aby byla usnadněna komunikace s REST API z UI komponent. Extension metody se nicméně úročí i v novém přístupu Minimal APIs. Snadno můžeme například provolat:

httpContext.Response.WriteAsJsonAsync(objekt);

Vzhledem k tomu, že dnes je většina REST API postavená na výměně dat ve formátu JSON, je tento přístup akceptovatelný. V případě Content Negotiation bychom museli dopsat kód, který by zajistil podporu i pro další formáty (XML, CSV, ProtoBuf atd.).

Zpřístupnění WebApplication

Aby bylo možné popsat celé REST API jediným souborem, použil Microsoft funkcionalitu Top Level Statements, kterou přidal do jazyka C# 9.0. Zde se náramně hodí. Není nutné vytvářet inicializační třídu Program.cs s metodou Main. Místo toho lze rovnou napsat programový kód. Lze si to představit, jako byste veškerý inicializační kód aplikace napsali do metody Main() v Program.cs, avšak samotná konstrukce třídy ani metody Main není uvedena.

Aby byla konfigurace pohodlná, potřeboval Microsoft zpřístupnit možnost konfigurovat DI kontejner a samotnou aplikaci. V tradičních aplikacích tak činíme ve Startup.cs a metodách Configure a ConfigureServices. Nově nám MS zpřístupnil třídu WebApplicationBuilder, do které registrujeme potřebné služby jak jsme zvyklí v ConfigureService. Zavoláním metody Build() pak získáme již zmíněnou hybridní třídu WebApplication, která umožní konfigurovat middlewares a endpointy.

// vytvoříme builder
var builder = WebApplication.CreateBuilder(args);

// registrujeme služby do DI
builder.Services.AddScoped<MyContext>();

// zbuildíme app
var app = builder.Build();

// registrujeme middlewares
app.UseMiddleware<MyMiddleware>();

// registrujeme routy pro REST API
app.MapGet("/", () => new { Title = "OK"});

// spustíme proces
app.Run();

To už je výsledek srovnatelný s tím, co umožňuje například Express.js.

Závěrem

Microsoft během posledních Preview přidal i podporu pro Swagger. Nechybí možnost zapojit vlastní atributy a průběžně přibývají i další funkcionality. Intenzivní vývoj probíhá na několika frontách:

  • default builder (WebApplication.CreateBuilder)
  • podpora services v DI (WebApplicationBuilder)
  • začišťování routingu, atributy aj. (WebApplication)

V posledních commitech přibyla přirozená dependency injection bez nutnosti používat atributy, řeší se podpora validace, vracení chybových struktur dle RFC 7807, pojmenování endpointů pro swagger, ignorace endpointů a další funkce.

S eliminací tradičního MVC přicházíme o filtry. Naštěstí je můžeme nahradit buď pomocí univerzálních middlewares nebo pomocí pipeline behaviours v případě použití balíčku MediatR. Právě mnou dlouhodobě doporučovaný balíček MediatR najde v Minimal APIs své uplatnění. Kromě náhrady filtrů přidává i globální handlování výjimek, práci s notifikacemi a výrazně zpřehledňuje a zefektivňuje použití DI.

Kalendář

Další preview .NET 6 se dočkáme 10. srpna 2021 a 14. září 2021 by měla spatřit světlo světa první release candidate. Finální verze .NET 6 bude uvolněna zřejmě 9. listopadu 2021 v rámci online konference .NET Conf 2021.