Včera jsem se rozhodl v praxi vyzkoušet, jak snadné nebo složité je zmigrovat kód z .NETu 4.5 na .NET Core – vzal jsem Redwood a vytvořil jsem větev dotnetcore, která obsahuje (zatím ještě nezkompilovatelnou) verzi.
Redwood jsem psal s ohledem na to, že tu migraci budu v budoucnu provádět a tím pádem jsem se snažil vyvarovat použití věcí, o kterých jsem věděl, že v novém .NET Core nebudou (zejména cokoliv ze System.Web atd.).
I tak jsem ale narazil na pár věcí, které bude potřeba upravit či řešit jinak – stále mi tam visí 19 kompilačních chyb a to jsem řešil zatím jen projekt Framework, neřešil jsem jeho použití v ASP.NET aplikaci (i když to už nebude tak složité).
Každopádně pokud chcete migraci provádět, dejte si pozor na následující (tento seznam rozhodně není úplný – je v něm jen to, na co jsem při migraci Redwoodu narazil):
Runtime T4 šablony
T4 šablony spouštěné za běhu (více v článku Používáme T4 šablony). Tyto šablony fungují tak, že z nich Visual Studio vygeneruje třídu, která obsahuje vše, co nadeklarujete v šabloně uvnitř bloků ohraničených <#+ #>, a funkci TransformText, která celou transformaci spustí.
Bohužel to, co VS standardně generuje, má závislosti na namespace System.CodeDom, který v .NET Core není a nebude (nahradil ho Roslyn).
Navíc Visual Studio ani v projektu pro nový framework T4 šablony nepodporuje a nevygeneruje z nich patřičný výstup. Vzhledem k tomu, že jsem šablony potřeboval, a vzhledem k tomu, že pro automatizaci procesů při vývoji se dá využít Grunt, napsal jsem do něj (v javascriptu – grr!!) vlastní task grunt-runtime-t4-template-task, který poštvete na T4 šablonu a ona z ní vygeneruje patřičnou C# třídu. Tato třída nemá závislosti na CodeDomu, jediné omezení je, že nepodporuje metody jako PushIndent, PopIndent a pár dalších věcí – nicméně ten základ, že vytvoříte instanci šablony, naplníte ji daty a zavoláte TransformText, ten pochopitelně funguje.
Vyzkoušel jsem si při tom Visual Studio Code a na jednodušší věci to není úplně špatné, dokonce se mi povedlo donutit ho, aby mi ten grunt task spustil a uměl v něm debugovat, což mě velmi potěšilo.
RESX soubory
Další podobnou libůstkou, kterou ještě vyřešenou nemám, je kompilace RESX souborů. Člověk byl zvyklý, že v projektu vytvořil soubor MyResources.resx, což na pozadí vygenerovalo třídu MyResources, která měla jednotlivé položky zpřístupněné jako readonly properties, takže stačilo napsat MyResources.MyErrorMessage a vrátilo to danou položku v aktuálním jazyce.
Nic takového v .NET Core opět není, takže jsem napsal ještě grunt task grunt-resx-compile-task, který tohle také řeší – vygeneruje třídu, která je zkompilovatelná v .NET Core.
NuGet téměř balíčky pro každý namespace
.NET Core dramaticky změnil rozdělení jednotlivých tříd do assemblies. Vše je velmi modulární, takže třeba System.IO, System.Reflection, System.Collections apod. jsou separátní knihovny, které se distribuují pomocí NuGetu.
Má to svůj smysl, protože framework není jeden monolitický balík, který se updatuje jednou za 2 roky, nicméně aby to bylo použitelné, bylo by krásné, kdyby člověk ve chvíli, kdy ve Visual Studiu napíše název nějaké třídy, aby mu to zobrazilo nejen namespace, ale i ze kterého NuGet balíčku pochází a hlavně aby to snadno umožnilo dotáhnout balíčky, které chybí (o což se VS do jisté míry snaží, ale zatím to moc nefunguje).
V souboru package.json pak bohužel IntelliSense nenabízí všechny balíčky, které by měla, takže člověk píše a hádá naslepo, jak by se daný balíček mohl jmenovat.
Velmi užitečná služba je http://packagesearch.azurewebsites.net/, kde zadáte název třídy nebo metody a ono vám to najde balíčky, které tuto metodu obsahují. Je také dobré se podívat na složku https://github.com/dotnet/corefx/tree/master/src, kde je velmi dobře vidět, na jaké balíčky byl .NET rozdělen – v zásadě platí, že co složka, to NuGet balíček.
AppDomain a Assembly
Vzhledem k tomu, že Redwood je samá reflection, narazil jsem na mnoho kompilačních chyb. Autoři .NET Core totiž udělali jednu drobnou změnu, a to zeštíhlení třídy System.Type. Spousta vlastností a metod, které tam byly, jsou nyní dostupné ne přímo ze System.Type, ale musíte na ní zavolat extension metodu GetTypeInfo, jejíž výsledek teprve obsahuje to, co potřebujete (např. vlastnosti Assembly, IsValueType atd.). Takže jsem to musel asi na 150 místech měnit. To byl ovšem ten menší problém.
Zbylo mi tam 15 kompilačních chyb, se kterými se budu muset vypořádat, jelikož .NET Core změnil některé základní principy. Třída Assembly má pouze metodu Load, která umí načíst assembly dle názvu. Není tam žádná funkce, která by uměla načíst assembly ze streamu nebo z pole bajtů – pokud ano, tak se jmenuje jinak a nepovedlo se mi ji zatím najít. Oblíbené a často používané funkce Assembly.GetExecutingAssembly chybí.
Podobně je na tom třída AppDomain – pokud jste ve starém .NETu chtěli zjistit seznam assemblies, které jsou ve vaší appdoméně načtené, zavolali jste AppDomain.CurrentDomain.GetAssemblies. Tato funkce zde není, takže budu muset najít jiný způsob, jak se s tím vypořádat.
OWIN
Nedávno Microsoft začal humbuk kolem OWINu, a tak jsem Redwood naimplementoval na Owinu, nicméně teď jsem jej musel upravit tak, aby nepoužíval OWIN, ale nový ASP.NET runtime. Prakticky je rozdíl jen v pojmenování tříd (IApplicationBuilder místo IAppBuidler, RequestDelegate místo IOwinMiddleware apod.) a vše je v jiných namespacech, principy naštěstí zůstaly stejné, takže se jednalo jen o relativně snadné úpravy.
Každopádně udržovat dvě verze frameworku pro nový a starý .NET možná nebude tak jednoduché – budu muset zjistit, jestli je to reálně možné. Projekt ve VS to sice podporuje, nicméně vzhledem k tomu, že framework používá věci z nového ASP.NET, nebude to zřejmě tak snadné udělat, abych měl jeden kód pro obě platformy. Uvidíme.