Miroslav Holec
Premium

Migrace na .NET 9 a splátka technických dluhů

Miroslav Holec   6. ledna 2025  update 9. ledna 2025

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

Konečně jsem se dostal k aktualizaci mých 7 menších aplikací. Až na jednu výjimku vše proběhlo hladce a téměř celá smečka webů běží na .NET 9 a EF Core 9. Podle výsledků v produkci mi bude zbývat migrace největšího projektu textomet.cz, kde bych se rád vyhnul překvapením.

Níže je tabulka projektů a jejich původní verze:

Website Stack .NET EF
adnpasociace.cz Blazor SSR .NET 9 --
check.miroslavholec.cz Blazor Server .NET 8 EF 6
moto.miroslavholec.cz Razor Pages .NET 6 --
miroslavholec.cz Blazor SSR .NET 8 EF 7
restapi.cz Razor Pages .NET 6 EF 6
restdemo.miroslavholec.cz MVC API .NET 8 EF 8
zakázkové listy [intra] Razor Pages .NET 8 EF 7
textomet.cz Blazor Server .NET 8 EF 7

U naprosté většiny projektů proběhlo všechno hladce. Dva záseky jsem měl na restapi.cz a restdemo.miroslavholec.cz. Zároveň teď bude otázka, jaké chyby se eventuelně projeví v produkci.

PasswordHasher, SuccessRehashNeeded

Na webu restapi.cz mi přestalo fungovat přihlášení. V podmínce jsem vyžadoval výsledek ověření hesla striktně na success, ale protože jsem migroval z .NET 6 / EF 6, zřejmě se změnilo hashování uvnitř PasswordHasher a vracel se mi výsledek SuccessRehashNeeded. Tedy přihlášení v takovém případě je OK a pouze přehashuji heslo.

Problém s [Consumes("application/json")]

Na REST API napsaném v MVC jsem si udělal pro všechny controllery ApiControllerBase. Doteď všechno fungovalo v pořádku, ale po migraci z .NET 8 na .NET 9 (+ přechod na WebApplicationBuilder) přestal endpoint reagovat. Po cca hodině hledání jsem zjistil příčinu:

CleanShot 2025-01-06 at 16.34.49@2x

Atribut Consumes používám na tomto webu kvůli generování dokumentace. Bohužel muselo dojít ve frameworku k změně, protože všechny endpointy v takto anotovaném controlleru nově vyžadují poslat media type, jinak endpoint vrací 404 Not Found. Neviděl jsem to ani v breaking changes, takže to odhaduji na bug. Dále jsem po tom nepátral.

Zapojení Scalar pro REST API

Microsoft už do .NET 9 neaktualizuje Swashbuckle Swagger a místo toho doporučil na .NET Conf přejít na Scalar. Zapojení je celkem jednoduché. Ponechal jsem původní nastavení generování OAS pomocí Swagger toolingu, takže došlo opravdu jen k výměně UI:

builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

if (app.Environment.IsDevelopment())
{
    app.UseSwagger(options =>
    {
        options.RouteTemplate = "/openapi/{documentName}.json";
    });
    app.MapScalarApiReference();
}

Výsledek si můžete prohlédnout u mé demo aplikace.

CleanShot 2025-01-06 at 16.41.11@2x

Migrace EF Core

Protože používám na všech projektech EF Core a režim migrací, vždy jsem si aktualizoval EF Core na novou verzi a vygeneroval testovací migraci. Cílem bylo ověřit, zda nedojde k nějaké chybné interpretaci kódu. Nemám rád nullable reference types, takže jsem se ani nesnažil o změny v modelu. V csproj jsem tedy vždy nastavil:

<PropertyGroup>
  <TargetFramework>net9.0</TargetFramework>
  <Nullable>disable</Nullable>
</PropertyGroup>

U mého webu následně aplikace selhala na nové breaking change v .NET 9. Při startu aplikace dojde k vyhození výjimky, pokud jsou na modelu nalezeny změny oproti poslední migraci. To by bylo fajn, kdyby v tom nebylo hned asi 5 výjimek, které v enginu vyvolají chybnou představu o tom, že se model změnil. Takže bez změny kódu jsem to vyřešil vypnutím této funkce pomocí RelationalEventId.PendingModelChangesWarning:

builder.Services.AddDbContextFactory<UnitOfWork>(options =>
{
    options.ConfigureWarnings(x =>
    {
        x.Ignore(RelationalEventId.PendingModelChangesWarning);
    });
});

Při migraci na EF Core 7 jsem ještě narazil na tradiční chybu s connection stringem. Od EF Core 7 je totiž u SQL clienta výchozí hodnota Encrypt=False změněna na Encrypt=True, což je na localhostu otravné. Stačí tedy aktualizovat connection string o toto nastavení a vše opět funguje jak má.

Static assets

U většina webových stránek jsem přešel na nové statické assety v .NET 9. U většiny aplikací stačí nahradit app.UseStaticFiles() za app.MapStaticAssets. U Blazor aplikací jsem pak upravil i cesty ke statickým souborům:

<link rel="stylesheet" href="@Assets["css/site.css"]" />
<link rel="stylesheet" href="@Assets["Holec.Web.styles.css"]" />

U některých aplikací jsem ponechal variantu app.UseStaticFiles(), protože tam používám specifické funkce přístupu do složek, které nový middleware nepodporuje. Dle dokumentace to ničemu nevadí.

U mého webu došlo k chybě, která se projevila jen v produkci. Přestože se všechny soubory správně zkomprimovaly a přenesly, prohlížeč je nedokázal interpretovat. Podezření padlo na brotli kompresi. Po delším troubleshootingu jsem zjistil, že chybu způsobuje jeden můj middleware, kde nastavuji encoding (viz. jiný workaround kvůli stream renderingu z 5/2024). Vyřešil jsem to tedy podmínkou v middleware a pro statické assety tuto hlavičku nevracím.

Závěr, sběr dat, performance

Protože jsem aktualizoval všechny aplikace na Azure Service Planu, budu sledovat nejen funkčnost aplikací, ale i výkonnost celého řešení. Brzy se můžete těšit na další článek zaměřený na výsledky měření výkonnosti v produkčním režimu. Když se neobjeví žádné chyby, budu ještě migrovat textomet.cz, kde už je zajímavější traffic. U menších aplikací se jako vždy není čeho bát a migrace je docela hladká.

Miroslav Holec | Pomáhám vývojářským týmům správně používat technologii .NET a vytvářet špičkové aplikace a REST služby.
ADNP
ASOCIACE