Migrace na .NET 9 a splátka technických dluhů
Č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:
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.
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á.