Migrer un projet WPF vers .NET Core 3

Migrer un projet WPF vers .NET Core 3

La sortie finale de .NET Core 3 approche et on peut dès à présent se pencher sur la possibilité de migrer ses projets existant en .NET Framework vers .NET Core 3. Dans cet article, je vais parler des différentes étapes à réaliser afin de mener à bien la migration.

Je vais me baser sur le projet de Mike Rousos pour illustrer mes propos. Le projet se trouve sur github et va me permettre d’aller un peu plus loin que le simple Hello Word. 

Étapes de migration 

Etape 1 : Préparation 

Pour commencer, il faut vérifier le pourcentage de compatibilité de notre projet avec .Net Core 3. Pour ça, il faut télécharger et exécuter l’outils  .NET Portability Analyzer. 

Générez votre application. 

Lancer le logiciel. Dans la fenêtre nouvellement ouverte, cliquez sur Browse et choisissez l’emplacement des fichiers générés. 

Cliquez ensuite sur le bouton Analyse.  

Cette action va générer un fichier Excel donnant le niveau de compatibilité de votre application avec .NET Core 3. Il faut noter que cet utilitaire envoi le même rapport à Microsoft leur permettant de faire des statistiques sur les APIs dont les personnes ont le plus besoin. 

L’outils indique ensuite l’emplacement du rapport généré et un bouton Open Report qui permet de le lancer directement. 

  • Si vous rencontrez un problème avec l’interface, il est possible de lancer l’utilitaire en ligne de commande.  
    • ApiPort.exe analyze -f <cheminverslesbinaires> -r html -r excel -t « .NET Core«  
      • L’argument –f définie le chemin vers les binaires à analyser.  
      • L’argument –r définie le format du rapport. Ici il sera généré en HTML et en Excel. 
      • L’argument -t définie quelle plateforme .NET sera analyser. Ici .Net Core. Si rien n’est spécifié, c’est la dernière version disponible qui sera utilisé. 

Le fichier Excel comporte 2 feuilles. La première liste les dépendances avec le pourcentage de compatibilité. 

 

100 indique une compatibilité complète. Il n’y aura donc aucun problème pour ces dépendances. En revanche, un nombre inférieur à 100 indique que la DLL n’est pas complètement compatible.  

Le deuxième onglet affiche le détail sur les assemblies non compatible avec .Net Core 3.

Les assemblies les plus problématiques sont celles dont vous avez écrit le code. Les autres sont relatives à des paquets NuGet. Pour ces dernières, il faut aller faire un tour sur le site nuget.org ou le site communautaire fuget.org. 

Par exemple, Castle.Windsor est à 97,14. En allant sur fuget.org, on constate que la version 5 est compatible avec .netstandard1.6.

 

La version est donc compatible avec .NET Core. Il n’y aura donc pas de problème.  

Pour le code non compatible, il faudra envisager une alternative ou une réécriture.  

 

Maintenant que l’on a fait le tour de la compatibilité de notre projet, nous allons (si ce n’est pas déjà fait) passer au nouveau format de référence des package NuGet. Historiquement, les ajouts de paquets NuGet dans notre solution étaient listés dans un fichier packages.config. Ils sont maintenant enregistrés dans le fichier csproj dans un élément <PackageReference>.  

L’intérêt de migrer vers ce nouveau style est double.  

D’abord il est nécessaire pour le nouveau fichier de projet .Net Core. Le but de cet article étant d’effectuer une migration vers .Net Core3, cela s’avère être un prérequis obligatoire. Notez malgré tout qu’il est tout à fait possible d’opter pour le nouveau style de référence des packages NuGet pour les projets restant sous Net Framework et n’effectuant pas de migration. 

L’autre point intéressant, c’est qu’à la différence du fichier packages.config, il ne référence que les paquets de niveau supérieur dont le projet dépend directement. Tous les autres paquets seront déterminés à la restauration et enregistré dans le fichier obj\project.assets.json. 

La migration est en fait relativement simple. Avec visual studio, il suffit de faire un clic droit sur le fichier packages.config puis de choisir Faire migrer packages.config vers PackageReference 

Une nouvelle fenêtre apparait présentant les dépendances de niveau supérieur ainsi que les dépendances transitives. Des cases à cocher permettent de passer les dépendances transitives en niveau supérieur s’il y a besoin. Ici il n’est pas nécessaire de le faire, on peut donc laisser les cases décochées.  

Il ne reste plus qu’à cliquer sur OK pour valider la migration.

Note : Le nouveau style de référencement ne stocke plus les paquets NuGet  localement dans un dossier packages. Ils sont maintenant stockés de manière global dans le dossier C:\Users\<vous>\.nuget\packages

Maintenant que la migration est faite, nous allons pouvoir passer à la mise à jour des paquets afin d’utiliser les versions ciblant .Net Core ou .Net standard.

Après la mise à jour, il faut vérifier que la montée de version n’a pas induis de problème en procédant à une vérification du bon fonctionnement de l’application. Si tel est le cas, il faut alors procéder à des ajustements afin de corriger lesdits problèmes.

Nous avons maintenant un projet qui compile, qui fonctionne et dont les paquets  NuGet sont prêt pour .Net Core3. Nous allons pouvoir mettre à jour le csproj vers le nouveau style .NET Core.

Etape 2 : Migration du fichier de projet

Pour faciliter la migration, il existe un utilitaire ( CsprojToVs2017) permettant d’automatiser un peu le processus. Néanmoins, si vous n’avez pas encore migrer le fichier package.config, il va le faire pour vous mais en plaçant toutes les Dépendances transitives en dépendances de niveau supérieur. Si vous avez suivi ce guide jusqu’ici, la migration a déjà été faites et de va donc pas poser de problème. Malgré tout, il sera nécessaire de vérifier le nouveau fichier et une intervention humaine n’est pas à exclure.

Pour la suite de ce guide, je vais opter pour une migration manuelle. C’est instructif et permet de garder la main.

Il existe plusieurs façons de gérer le nouveau fichier .csproj. Je vais me concentrer sur celle que j’utilise, c’est à dire avoir l’ancien et le nouveau fichier dans le même répertoire. Cette méthode a néanmoins un petit inconvénient, il ne sera pas possible d’ouvrir les 2 projets en même temps, les dossiers obj et bin entrant en conflit.

Faites une copie de votre fichier .csproj. Remplacer ensuite le contenu de votre fichier par ceci :

 

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>WinExe</OutputType>
    <TargetFramework>net472</TargetFramework>
    <UseWPF>true</UseWPF>
    <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
  </PropertyGroup>
</Project>

Regardons d’un peu plus près ce code.

  • TargetFramework informe sur la version du framework utilisé. Ici il s’agit du .net Framework 4.7.2.
  • UseWPF indique qu’il s’agit d’une application wpf.
  • GenerateAssemblyInfo est ici à false. Ce paramètre est important parce qu’avec la nouvelle version du .csproj, le fichier AssemblyInfo.cs est généré automatiquement à partir des propriétés du csproj. Néanmoins, vu qu’il s’agit d’une migration, votre projet contient déjà un fichier AssemblyInfo.cs. Il ne faut donc pas en générer un nouveau.

Il faut maintenant copier dans PropertyGroup les balises <RootNamespace>, <AssemblyName> et <ApplicationIcon>.

Nous avons préalablement migré les paquets NuGet dans le fichier csproj. Il faut donc copier également tous les le nœud <ItemGroup> avec les PackageReference.

Il faut également copier les resources comme les images par exemple. Le nouveau format de csproj supporte les patterns globaux. On peut donc ajouter par exemple tous les fichiers png en une seule ligne : <Resource Include= »**\*.png »/>.

Les Nœuds <None> sont inclus mais pas copiés dans le fichier de sortie. Aussi, s’il faut qu’il le soit, il faut alors le préciser avec <CopyToOutputDirectory> :

<None Update="BeanTrader.pfx">, 
  <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>

Par défaut, les fichiers xaml sont assimilé comme des <Page>. Néanmoins, il se peut que vous souhaitiez les ajouter comme contenu. Il faut alors le préciser dans le fichier en les retirant des pages et en les ajoutant en content.

<Page Remove="**\Default.Accent.xaml" />
<Content Include="Resources\Themes\Default.Accent.xaml">, 
    <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>

A ce stade, il est possible de d’ouvrir la solution avec le nouveau .csproj.

 

Etape 3 : .Net Core 3 et Corrections

Le projet cible pour le moment toujours le .Net Framework 4.7.2. Il est temps de le passer à .Net Core 3. Pour cela, ouvrez le fichier csproj et remplacez net472 par netcoreapp3.0 dans la balise <TargetFramework>.

<PropertyGroup>
   <TargetFramework>netcoreapp3.0</TargetFramework>
 </PropertyGroup>

A ce moment, il est fort probable que la génération de la solution échoue en affichant de multiples erreurs.

Une bonne partie des erreurs est dû à l’absence de System.ServiceModel. La correction est simple, il suffit d’ajouter les bons paquets NuGet. Une très bonne solution consiste à ajouter le paquet Microsoft.Windows.Compatibility. En effet ce paquet contient de nombreux paquets compatibles .Net Core,  dont un certain nombre d’API relative à WCF, le registre etc.

L’ajout de ce paquet devrait régler une bonne partie des problèmes de génération.

Une autre source d’erreurs concerne d’anciens fichiers inutilisés. En effet, avant la migration, certains fichiers étaient exclus du projet. Ceci n’est plus le cas maintenant et ces derniers peuvent être source d’erreurs. Il faut donc au choix, exclure à nouveau ce fichier ou bien s’il n’est vraiment pas nécessaire, le supprimer.

Il est possible qu’après ces étapes il reste encore quelques erreurs à régler. Cela arrive lorsque que l’on utilise un paquet NuGet qui n’est pas entièrement compatible avec .Net Core. Il faut alors procéder à quelques ajustements.

Dans le projet en exemple, Castle.Windsor fait justement partie de cela.

  • MicroKernel.Registration.Classes.FromThisAssembly n’est pas compatible avec .Net Core. Cependant il existe une API similaire qui elle est compatible : Classes.FromAssemblyContaining. Il suffit juste de remplacer Classes.FromThisAssembly() par Classes.FromAssemblyContaining(t) ou t est le type de l’appel.
  • Dans le fichier Bootstrapper.cs, Windsor.Installer.FromAssembly.This doit être remplacer par FromAssembly.Containing(typeof(Bootstrapper)).

Il s’agit ici bien sûr d’un exemple bien ciblé mais l’approche reste la même pour les autres problèmes de ce genre.

Etape 4 : Test runtime

Vous avez réglé tous les problèmes et votre projet compile maintenant sans erreur. C’est un bon début mais cela ne suffit pas. Il faut également s’assurer que le programme fonctionne bien. La dernière étape consiste donc à lancer le programme et faire tous les tests nécessaires afin de s’assurer du bon fonctionnement.

Laisser un commentaire