Miroslav Holec
Premium

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

Miroslav Holec   6. 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"]" />

Zde funguje vše dle očekávání. 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í.

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