Migration d’applications WPF vers .NET Core

Cet article décrit les étapes nécessaires pour migrer une application Windows Presentation Foundation (WPF) de .NET Framework vers .NET Core 3.0. Si vous n’avez pas d’application WPF à la main sur le port, mais souhaitez essayer le processus, vous pouvez utiliser l’exemple d’application Bean Trader disponible sur GitHub. L’application d’origine (ciblant .NET Framework 4.7.2) est disponible dans le dossier NetFx\BeanTraderClient. Tout d’abord, nous allons expliquer les étapes nécessaires pour porter des applications en général, puis nous allons parcourir les modifications spécifiques qui s’appliquent à l’exemple Bean Trader .

Important

La documentation du Guide du bureau pour .NET 6 et .NET 5 (y compris .NET Core 3.1) est en cours de construction.

Essayer l’Assistant mise à niveau

L’Assistant de mise à niveau de .NET est un outil en ligne de commande qui peut être exécuté sur différents types d’applications .NET Framework. Il est conçu pour faciliter la mise à niveau des applications .NET Framework vers .NET 5. Après l’exécution de l’outil, dans la plupart des cas, l’application nécessite des efforts supplémentaires pour effectuer la migration. L’outil comprend l’installation d’analyseurs qui peuvent vous aider à effectuer la migration.

Pour plus d’informations, consultez Mettre à niveau une application Windows Forms vers .NET 5 avec l’Assistant Mise à niveau .NET.

Prérequis

Pour migrer vers .NET Core, vous devez d’abord :

  1. Comprendre et mettre à jour les dépendances NuGet :

    1. Mettez à niveau NuGet dépendances pour utiliser le <PackageReference> format.
    2. Passez en revue les dépendances de niveau supérieur NuGet pour la compatibilité .NET Core ou .NET Standard.
    3. Mettez à niveau NuGet packages vers des versions plus récentes.
    4. Utilisez .NET Portability Analyzer pour comprendre les dépendances .NET.
  2. Migrez le fichier projet vers le nouveau format de style SDK :

    1. Choisissez s’il faut cibler .NET Core et .NET Framework, ou uniquement .NET Core.
    2. Copiez les propriétés et les éléments de fichier projet pertinents dans le nouveau fichier projet.
  3. Résoudre les problèmes de build :

    1. Ajoutez une référence à Microsoft.Windows. Package de compatibilité.
    2. Recherchez et corrigez les différences au niveau de l’API.
    3. Supprimez app.config sections autres que appSettings ou connectionStrings.
    4. Régénérer le code généré, si nécessaire.
  4. Test d’exécution :

    1. Vérifiez que l’application portée fonctionne comme prévu.
    2. Attention aux NotSupportedException exceptions.

À propos de l’exemple

Cet article fait référence à l’exemple d’application Bean Trader , car il utilise diverses dépendances similaires à celles que les applications WPF réelles peuvent avoir. L’application n’est pas volumineuse, mais elle est destinée à être une étape de « Hello World » en termes de complexité. L’application illustre certains problèmes que les utilisateurs peuvent rencontrer lors du portage d’applications réelles. L’application communique avec un service WCF, de sorte qu’elle s’exécute correctement, vous devez également exécuter le projet BeanTraderServer (disponible dans le même référentiel GitHub) et vérifier que la configuration de BeanTraderClient pointe vers le point de terminaison correct. (Par défaut, l’exemple suppose que le serveur est en cours d’exécution sur le même ordinateur à l’emplacement http://localhost:8090, ce qui sera vrai si vous lancez BeanTraderServer localement.)

N’oubliez pas que cet exemple d’application est destiné à illustrer les défis et les solutions de portage .NET Core. Il n’est pas destiné à démontrer les meilleures pratiques WPF. En fait, il inclut délibérément certains anti-modèles pour vous assurer que vous rencontrez au moins quelques défis intéressants lors du portage.

Préparation

Le principal défi de la migration d’une application .NET Framework vers .NET Core est que ses dépendances peuvent fonctionner différemment ou pas du tout. La migration est beaucoup plus facile qu’elle n’est utilisée ; de nombreux packages NuGet ciblent désormais .NET Standard. À compter de .NET Core 2.0, les zones de surface .NET Framework et .NET Core sont devenues similaires. Même si, certaines différences (à la fois dans la prise en charge des packages NuGet et dans les API .NET disponibles) restent. La première étape de migration consiste à passer en revue les dépendances de l’application et à vérifier que les références sont dans un format facilement migré vers .NET Core.

Mettre à niveau vers des références NuGet <PackageReference>

Les projets .NET Framework plus anciens répertorient généralement leurs dépendances NuGet dans un fichier packages.config. Le nouveau format de fichier projet de style SDK fait référence NuGet packages en tant qu’éléments dans le fichier csproj lui-même plutôt que <PackageReference> dans un fichier de configuration distinct.

Lors de la migration, il existe deux avantages pour utiliser <PackageReference>des références de style :

  • Il s’agit du style NuGet référence requis pour le nouveau fichier projet .NET Core. Si vous utilisez <PackageReference>déjà, ces éléments de fichier projet peuvent être copiés et collés directement dans le nouveau projet.
  • Contrairement à un fichier packages.config, <PackageReference> les éléments font uniquement référence aux dépendances de niveau supérieur que votre projet dépend directement. Tous les autres packages de NuGet transitifs seront déterminés au moment de la restauration et enregistrés dans le fichier obj\project.assets.json généré automatiquement. Cela permet de déterminer plus facilement les dépendances dont votre projet dispose, ce qui est utile pour déterminer si les dépendances nécessaires fonctionnent sur .NET Core ou non.

La première étape de la migration d’une application .NET Framework vers .NET Core consiste à la mettre à jour pour utiliser <PackageReference> NuGet références. Visual Studio rend cela simple. Cliquez avec le bouton droit sur le fichier packages.config du projet dans le Explorateur de solutions de Visual Studio, puis sélectionnez Migrer packages.config vers PackageReference.

Upgrading to PackageReference

Une boîte de dialogue s’affiche montrant les dépendances de niveau supérieur calculées NuGet et demandant quels autres packages NuGet doivent être promus au niveau supérieur. Aucun de ces autres packages n’a besoin d’être de niveau supérieur pour l’exemple Bean Trader. Vous pouvez donc décocher toutes ces cases. Ensuite, cliquez sur Ok et le fichier packages.config est supprimé et <PackageReference> les éléments sont ajoutés au fichier projet.

<PackageReference>Les références de style ne stockent pas NuGet packages localement dans un dossier de packages. Au lieu de cela, ils sont stockés globalement en tant qu’optimisation. Une fois la migration terminée, modifiez le fichier csproj et supprimez tous <Analyzer> les éléments faisant référence aux analyseurs qui proviennent précédemment du fichier .. Répertoire \packages . Ne vous inquiétez pas; étant donné que vous disposez toujours des références de package NuGet, les analyseurs seront inclus dans le projet. Vous devez simplement nettoyer les anciens éléments de style <Analyzer> packages.config.

Passer en revue les packages NuGet

Maintenant que vous pouvez voir les packages de niveau supérieur NuGet dont dépend le projet, vous pouvez vérifier si ces packages sont disponibles sur .NET Core. Vous pouvez déterminer si un package prend en charge .NET Core en examinant ses dépendances sur nuget.org. Le site fuget.org créé par la communauté affiche ces informations en haut de la page d’informations du package.

Lorsque vous ciblez .NET Core 3.0, tous les packages ciblant .NET Core ou .NET Standard doivent fonctionner (étant donné que .NET Core implémente la surface de surface .NET Standard). Dans certains cas, la version spécifique d’un package utilisé ne cible pas .NET Core ou .NET Standard, mais les versions plus récentes seront. Dans ce cas, vous devez envisager la mise à niveau vers la dernière version du package.

Vous pouvez également utiliser des packages ciblant .NET Framework, mais cela introduit un certain risque. .NET Core pour .NET Framework dépendances sont autorisées, car .NET Core et .NET Framework zones de surface sont assez similaires que ces dépendances fonctionnent souvent. Toutefois, si le package tente d’utiliser une API .NET qui n’est pas présente dans .NET Core, vous rencontrerez une exception d’exécution. En raison de cela, vous devez uniquement référencer .NET Framework packages lorsqu’aucune autre option n’est disponible et comprendre que cela impose une charge de test.

S’il existe des packages référencés qui ne ciblent pas .NET Core ou .NET Standard, vous devez réfléchir à d’autres alternatives :

  • Existe-t-il d’autres packages similaires qui peuvent être utilisés à la place ? Parfois, NuGet auteurs publient des « distincts ». Versions principales de leurs bibliothèques ciblant spécifiquement .NET Core. Enterprise packages de bibliothèque sont un exemple de publication communautaire ». Alternatives NetCore. Dans d’autres cas, les kits SDK plus récents pour un service particulier (parfois avec différents noms de package) sont disponibles pour .NET Standard. Si aucune alternative n’est disponible, vous pouvez continuer à utiliser les packages ciblés .NET Framework, en gardant à l’esprit que vous devez les tester soigneusement une fois en cours d’exécution sur .NET Core.

L’exemple Bean Trader comporte les dépendances de niveau supérieur NuGet suivantes :

  • Castle.Windsor, version 4.1.1

    Ce package cible .NET Standard 1.6. Il fonctionne donc sur .NET Core.

  • Microsoft.CodeAnalysis.FxCopAnalyzers, version 2.6.3
    Il s’agit d’un méta-package. Il n’est donc pas immédiatement évident que les plateformes qu’il prend en charge, mais la documentation indique que sa version la plus récente (2.9.2) fonctionnera à la fois pour les .NET Framework et .NET Core.

  • Nito.AsyncEx, version 4.0.1

    Ce package ne cible pas .NET Core, mais la version 5.0 la plus récente fait. Cela est courant lors de la migration, car de nombreux packages NuGet ont ajouté la prise en charge .NET Standard récemment, mais les versions antérieures du projet ciblent uniquement les .NET Framework. Si la différence de version n’est qu’une différence de version mineure, il est souvent facile de mettre à niveau vers la version la plus récente. Étant donné qu’il s’agit d’un changement de version majeur, vous devez être prudent dans la mise à niveau, car il peut y avoir des changements cassants dans le package. Il y a un chemin vers l’avant, mais c’est bon.

  • MahApps.Metro, version 1.6.5

    Ce package ne cible pas non plus .NET Core, mais a une préversion plus récente (2.0-alpha) qui le fait. Encore une fois, vous devez rechercher les changements cassants, mais le package plus récent est encourageant.

Les dépendances NuGet de l’exemple Bean Trader ciblent toutes les versions .NET Standard/.NET Core ou ont des versions plus récentes. Il est donc peu probable qu’il y ait des problèmes de blocage ici.

Mettre à niveau les packages NuGet

Si possible, il serait préférable de mettre à niveau les versions de tous les packages qui ciblent uniquement .NET Core ou .NET Standard avec des versions plus récentes à ce stade (avec le projet ciblant toujours .NET Framework) pour découvrir et résoudre les changements cassants tôt.

Si vous préférez ne pas apporter de modifications matérielles à la version existante .NET Framework de l’application, cela peut attendre que vous ayez un nouveau fichier projet ciblant .NET Core. Toutefois, la mise à niveau des packages NuGet vers des versions compatibles .NET Core à l’avance facilite le processus de migration une fois que vous créez le nouveau fichier projet et réduit le nombre de différences entre les versions .NET Framework et .NET Core de l’application.

Avec l’exemple Bean Trader, toutes les mises à niveau nécessaires peuvent être effectuées facilement (à l’aide du gestionnaire de package Visual Studio de NuGet) à une exception : la mise à niveau de MahApps.Metro 1.6.5 à 2.0 révèle les changements cassants liés aux API de gestion des thèmes et des accents.

Dans l’idéal, l’application est mise à jour pour utiliser la version la plus récente du package (car cela est plus susceptible de fonctionner sur .NET Core). Toutefois, dans certains cas, cela peut ne pas être possible. Dans ces cas, ne mettez pas à niveau MahApps.Metro, car les modifications nécessaires ne sont pas triviales et ce didacticiel se concentre sur la migration vers .NET Core 3, pas vers MahApps.Metro 2. En outre, il s’agit d’une dépendance à faible risque .NET Framework car l’application Bean Trader n’exerce qu’une petite partie de MahApps.Metro. Bien sûr, il nécessite des tests pour vous assurer que tout fonctionne une fois la migration terminée. S’il s’agissait d’un scénario réel, il serait bon de déposer un problème pour suivre le travail pour passer à MahApps.Metro version 2.0 , car la migration ne laisse pas derrière une dette technique.

Une fois que les packages NuGet sont mis à jour vers les versions récentes, le <PackageReference> groupe d’éléments du fichier projet de l’exemple Bean Trader doit ressembler à ceci.

<ItemGroup>
  <PackageReference Include="Castle.Windsor">
    <Version>4.1.1</Version>
  </PackageReference>
  <PackageReference Include="MahApps.Metro">
    <Version>1.6.5</Version>
  </PackageReference>
  <PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers">
    <Version>2.9.2</Version>
  </PackageReference>
  <PackageReference Include="Nito.AsyncEx">
    <Version>5.0.0</Version>
  </PackageReference>
</ItemGroup>

analyse de la portabilité .NET Framework

Une fois que vous avez compris l’état des dépendances NuGet de votre projet, la prochaine chose à prendre en compte est .NET Framework dépendances d’API. L’outil .NET Portability Analyzer est utile pour comprendre lequel des API .NET que votre projet utilise sont disponibles sur d’autres plateformes .NET.

L’outil est fourni en tant que plug-in Visual Studio, un outil en ligne de commande ou encapsulé dans une interface graphique graphique simple, ce qui simplifie ses options. Vous pouvez en savoir plus sur l’utilisation de .NET Portability Analyzer (port API) à l’aide de l’interface graphique graphique dans les applications de bureau de portage vers le billet de blog .NET Core . Si vous préférez utiliser la ligne de commande, les étapes nécessaires sont les suivantes :

  1. Téléchargez l’analyseur de portabilité .NET si vous ne l’avez pas déjà.

  2. Assurez-vous que l’application .NET Framework à porter correctement (il s’agit d’une bonne idée avant la migration).

  3. Exécutez le port d’API avec une ligne de commande comme celle-ci.

    ApiPort.exe analyze -f <PathToBeanTraderBinaries> -r html -r excel -t ".NET Core"
    

    L’argument -f spécifie le chemin contenant les fichiers binaires à analyser. L’argument -r spécifie le format de fichier de sortie souhaité. L’argument -t spécifie la plateforme .NET à laquelle analyser l’utilisation de l’API. Dans ce cas, vous souhaitez .NET Core.

Lorsque vous ouvrez le rapport HTML, la première section répertorie tous les fichiers binaires analysés et le pourcentage des API .NET qu’ils utilisent sont disponibles sur la plateforme ciblée. Le pourcentage n’est pas significatif par lui-même. Ce qui est plus utile est de voir les API spécifiques manquantes. Pour ce faire, sélectionnez un nom d’assembly ou faites défiler jusqu’aux rapports des assemblys individuels.

Concentrez-vous sur les assemblys dont vous possédez le code source. Dans le rapport Bean Trader ApiPort, par exemple, il existe de nombreux fichiers binaires répertoriés, mais la plupart d’entre eux appartiennent à NuGet packages. Castle.Windsor montre qu’elle dépend de certaines API System.Web manquantes dans .NET Core. Ce n’est pas une préoccupation, car vous avez précédemment vérifié que vous avez vérifié qu’il Castle.Windsor prend en charge .NET Core. Il est courant que les packages NuGet aient des fichiers binaires différents à utiliser avec différentes plateformes .NET, de sorte que la version .NET Framework d’utilisation Castle.Windsor des API System.Web ou non n’est pas pertinente tant que le package cible également .NET Standard ou .NET Core (ce qu’il fait).

Avec l’exemple Bean Trader, le seul binaire que vous devez prendre en compte est BeanTraderClient et le rapport montre que seules deux API .NET sont manquantes : System.ServiceModel.ClientBase<T>.Close et System.ServiceModel.ClientBase<T>.Open.

BeanTraderClient portability report

Il est peu probable qu’il s’agit de problèmes de blocage, car les API clientes WCF sont (principalement) prises en charge sur .NET Core. Il doit donc y avoir des alternatives disponibles pour ces API centrales. En fait, en System.ServiceModelexaminant la surface de surface .NET Core (à l’aide https://apisof.net), vous voyez qu’il existe des alternatives asynchrones dans .NET Core à la place.

En fonction de ce rapport et de l’analyse des dépendances NuGet précédentes, il semble qu’il ne devrait y avoir aucun problème majeur lors de la migration de l’exemple Bean Trader vers .NET Core. Vous êtes prêt pour l’étape suivante dans laquelle vous allez réellement démarrer la migration.

Migration du fichier projet

Si votre application n’utilise pas le nouveau format de fichier projet de style SDK, vous aurez besoin d’un nouveau fichier projet pour cibler .NET Core. Vous pouvez remplacer le fichier csproj existant ou, si vous préférez conserver le projet existant intouché dans son état actuel, vous pouvez ajouter un nouveau fichier csproj ciblant .NET Core. Vous pouvez créer des versions de l’application pour .NET Framework et .NET Core avec un fichier projet de style SDK unique avec un ciblage multiple (spécification de plusieurs <TargetFrameworks> cibles).

Pour créer le fichier projet, vous pouvez créer un projet WPF dans Visual Studio ou utiliser la dotnet new wpf commande dans un répertoire temporaire pour générer le fichier projet, puis copier/renommer celui-ci à l’emplacement approprié. Il existe également un outil créé par la communauté, CsprojToVs2017, qui peut automatiser une partie de la migration des fichiers projet. L’outil est utile mais a toujours besoin d’un humain pour passer en revue les résultats pour vérifier que tous les détails de la migration sont corrects. Une zone particulière que l’outil ne gère pas de manière optimale consiste à migrer NuGet packages à partir de fichiers packages.config. Si l’outil s’exécute sur un fichier projet qui utilise toujours un fichier packages.config pour référencer NuGet packages, il migrera automatiquement vers <PackageReference> des éléments, mais ajoutera <PackageReference> des éléments pour tous les packages au lieu de simples packages de niveau supérieur. Si vous avez déjà migré vers<PackageReference> des éléments avec Visual Studio (comme vous l’avez fait dans cet exemple), l’outil peut vous aider avec le reste de la conversion. Comme Scott Hanselman recommande dans son billet de blog sur la migration de fichiers csproj, le portage par main est éducatif et donnera de meilleurs résultats si vous n’avez que quelques projets à porter. Toutefois, si vous portez des dizaines ou des centaines de fichiers projet, un outil comme CsprojToVs2017 peut être une aide.

Pour créer un fichier projet pour l’exemple Bean Trader, exécutez dans un répertoire temporaire et déplacez dotnet new wpf le fichier .csproj généré dans le dossier BeanTraderClient et renommez-le BeanTraderClient.Core.csproj.

Étant donné que le nouveau format de fichier projet inclut automatiquement des fichiers C#, des fichiers resx et des fichiers XAML qu’il trouve dans ou sous son répertoire, le fichier projet est déjà presque terminé ! Pour terminer la migration, ouvrez les anciens et nouveaux fichiers projet côte à côte et examinez l’ancien pour voir si des informations qu’il contient doivent être migrées. Dans l’exemple Bean Trader, les éléments suivants doivent être copiés dans le nouveau projet :

  • Les <RootNamespace>propriétés et <ApplicationIcon> les propriétés <AssemblyName>doivent toutes être copiées.

  • Vous devez également ajouter une <GenerateAssemblyInfo>false</GenerateAssemblyInfo> propriété au nouveau fichier projet, car l’exemple Bean Trader inclut des attributs au niveau de l’assembly (comme [AssemblyTitle]) dans un fichier AssemblyInfo.cs. Par défaut, les nouveaux projets de style SDK génèreront automatiquement ces attributs en fonction des propriétés du fichier csproj. Comme vous ne souhaitez pas que cela se produise dans ce cas (les attributs générés automatiquement entrent en conflit avec ceux de AssemblyInfo.cs), vous désactivez les attributs générés automatiquement avec <GenerateAssemblyInfo>.

  • Bien que les fichiers resx soient automatiquement inclus en tant que ressources incorporées, les autres <Resource> éléments comme les images ne le sont pas. Copiez donc les éléments pour incorporer des <Resource> fichiers d’image et d’icône. Vous pouvez simplifier les références png à une seule ligne à l’aide de la prise en charge du nouveau format de fichier projet pour les modèles de globbing : <Resource Include="**\*.png" />.

  • De même, <None> les éléments sont inclus automatiquement, mais ils ne sont pas copiés dans le répertoire de sortie, par défaut. Étant donné que le projet Bean Trader inclut un <None>élément copié dans le répertoire de sortie (à l’aide PreserveNewest de comportements), vous devez mettre à jour l’élément renseigné <None> automatiquement pour ce fichier, comme ceci.

    <None Update="BeanTrader.pfx">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    
  • L’exemple Bean Trader inclut un fichier XAML (Default.Accent.xaml) comme Content (plutôt qu’un Page) car les thèmes et les accents définis dans ce fichier sont chargés à partir du code XAML du fichier au moment de l’exécution, plutôt que d’être incorporés dans l’application elle-même. Le nouveau système de projet inclut automatiquement ce fichier en tant que <Page>fichier, mais il s’agit d’un fichier XAML. Par conséquent, vous devez supprimer le fichier XAML en tant que page (<Page Remove="**\Default.Accent.xaml" />) et l’ajouter en tant que contenu.

    <Content Include="Resources\Themes\Default.Accent.xaml">
        <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </Content>
    
  • Enfin, ajoutez NuGet références en copiant les <ItemGroup> éléments avec tous les <PackageReference> éléments. Si vous n’aviez pas précédemment mis à niveau les packages NuGet vers des versions compatibles .NET Core, vous pouvez le faire maintenant que les références de package se trouvent dans un projet spécifique à .NET Core.

À ce stade, il doit être possible d’ajouter le nouveau projet à la solution BeanTrader et de l’ouvrir dans Visual Studio. Le projet doit être correct dans Explorateur de solutions et dotnet restore BeanTraderClient.Core.csproj doit restaurer correctement les packages (avec deux avertissements attendus liés à la version MahApps.Metro que vous utilisez pour cibler .NET Framework).

Bien qu’il soit possible de conserver les deux fichiers projet côte à côte (et peut même être souhaitable si vous souhaitez continuer à créer l’ancien projet exactement comme il l’était), il complique le processus de migration (les deux projets essaieront d’utiliser les mêmes dossiers bin et obj) et n’est généralement pas nécessaire. Si vous souhaitez générer pour les cibles .NET Core et .NET Framework, vous pouvez remplacer la <TargetFramework>netcoreapp3.0</TargetFramework> propriété dans le nouveau fichier <TargetFrameworks>netcoreapp3.0;net472</TargetFrameworks> projet par celle-ci. Pour l’exemple Bean Trader, supprimez l’ancien fichier projet (BeanTraderClient.csproj) car il n’est plus nécessaire. Si vous préférez conserver les deux fichiers projet, veillez à les générer à différentes sorties et chemins de sortie intermédiaires.

Résoudre les problèmes de build

La troisième étape du processus de portage consiste à obtenir le projet à générer. Certaines applications seront déjà générées une fois que le fichier projet est converti en projet de style SDK. Si c’est le cas pour votre application, félicitations! Vous pouvez passer à l’étape 4. D’autres applications ont besoin de mises à jour pour les créer pour .NET Core. Si vous essayez d’exécuter dotnet build sur l’exemple de projet Bean Trader maintenant, par exemple (ou générez-le dans Visual Studio), il y aura de nombreuses erreurs, mais vous les obtiendrez rapidement.

Références System.ServiceModel et Microsoft. Windows. Compatibilité

Une source courante d’erreurs est des références manquantes pour les API disponibles pour .NET Core, mais pas automatiquement incluses dans le métapackage de l’application .NET Core. Pour ce faire, vous devez référencer le Microsoft.Windows.Compatibility package. Le package de compatibilité comprend un large ensemble d’API courantes dans Windows applications de bureau, telles que le client WCF, les services d’annuaire, le registre, la configuration, les API d’ACL, etc.

Avec l’exemple Bean Trader, la majorité des erreurs de build sont dues à des types manquants System.ServiceModel . Elles peuvent être traitées en référençant les packages de NuGet WCF nécessaires. Les API clientes WCF sont parmi celles présentes dans le Microsoft.Windows.Compatibility package. Toutefois, le référencement du package de compatibilité est une solution encore meilleure (car elle résout également les problèmes liés aux API ainsi que les solutions aux problèmes WCF que le package de compatibilité rend disponibles). Le Microsoft.Windows.Compatibility package aide dans la plupart des scénarios de portage WPF et WinForms .NET Core 3.0. Après avoir ajouté la référence NuGet à Microsoft.Windows.Compatibility, une seule erreur de build reste !

Nettoyage des fichiers inutilisés

Un type de problème de migration qui se rapporte souvent aux fichiers C# et XAML qui n’ont pas été précédemment inclus dans la build sont récupérés par les nouveaux projets de style SDK qui incluent automatiquement toutes les sources.

L’erreur de génération suivante que vous voyez dans l’exemple Bean Trader fait référence à une implémentation d’interface incorrecte dans OldUnusedViewModel.cs. Le nom du fichier est un indicateur, mais à l’inspection, vous constaterez que ce fichier source est incorrect. Il n’a pas causé de problèmes précédemment, car il n’a pas été inclus dans le projet de .NET Framework d’origine. Les fichiers sources présents sur le disque, mais qui ne sont pas inclus dans l’ancien csproj , sont inclus automatiquement.

Pour les problèmes ponctuels tels que celui-ci, il est facile de comparer au csproj précédent pour confirmer que le fichier n’est pas nécessaire, puis <Compile Remove="" /> qu’il ou, si le fichier source n’est plus nécessaire, supprimez-le. Dans ce cas, il est sûr de simplement supprimer OldUnusedViewModel.cs.

Si vous avez de nombreux fichiers sources qui doivent être exclus de cette façon, vous pouvez désactiver l’inclusion automatique des fichiers C# en définissant la <EnableDefaultCompileItems> propriété sur false dans le fichier projet. Ensuite, vous pouvez copier <Compile Include> des éléments de l’ancien fichier projet vers le nouveau afin de générer uniquement des sources que vous avez prévues d’inclure. De même, <EnableDefaultPageItems> vous pouvez désactiver l’inclusion automatique des pages XAML et <EnableDefaultItems> contrôler les deux avec une seule propriété.

Un bref écart sur les compilateurs multi-passe

Après avoir supprimé le fichier incriminé de l’exemple Bean Trader, vous pouvez recréer et obtenir quatre erreurs. Tu n’en as pas eu un auparavant ? Pourquoi le nombre d’erreurs s’est-il élevé ? Le compilateur C# est un compilateur à plusieurs passes. Cela signifie qu’il passe deux fois par chaque fichier source. Tout d’abord, le compilateur examine simplement les métadonnées et les déclarations dans chaque fichier source et identifie tous les problèmes au niveau de la déclaration. Il s’agit des erreurs que vous avez corrigées. Ensuite, il passe à nouveau par le code pour générer la source C# en IL; il s’agit du deuxième ensemble d’erreurs que vous voyez maintenant.

Notes

Le compilateur C# effectue plus que deux passes, mais le résultat final est que les erreurs du compilateur pour les modifications de code volumineuses comme cela ont tendance à se produire en deux vagues.

Correctifs de dépendances tiers (Castle.Windsor)

Une autre classe de problème qui apparaît dans certains scénarios de migration est les différences d’API entre les versions .NET Framework et .NET Core des dépendances. Même si un package NuGet cible à la fois .NET Framework et .NET Standard ou .NET Core, il peut y avoir différentes bibliothèques à utiliser avec des cibles .NET différentes. Cela permet aux packages de prendre en charge de nombreuses plateformes .NET différentes, qui peuvent nécessiter différentes implémentations. Cela signifie également qu’il peut y avoir de petites différences d’API dans les bibliothèques lors du ciblage de différentes plateformes .NET.

L’ensemble suivant d’erreurs que vous verrez dans l’exemple Bean Trader est lié aux Castle.Windsor API. Le projet .NET Core Bean Trader utilise la même version que Castle.Windsor le projet ciblé .NET Framework (4.1.1), mais les implémentations de ces deux plateformes sont légèrement différentes.

Dans ce cas, vous voyez les problèmes suivants qui doivent être résolus :

  1. Castle.MicroKernel.Registration.Classes.FromThisAssembly n’est pas disponible sur .NET Core. Toutefois, il existe l’API Classes.FromAssemblyContaining similaire disponible. Nous pouvons donc remplacer les deux utilisations par Classes.FromThisAssembly() les appels vers Classes.FromAssemblyContaining(t), où t est le type qui effectue l’appel.
  2. De même, dans Bootstrapper.cs, Castle.Windsor.Installer.FromAssembly. Il n’est pas disponible sur .NET Core. Au lieu de cela, cet appel peut être remplacé par FromAssembly.Containing(typeof(Bootstrapper)).

Mise à jour de l’utilisation du client WCF

Après avoir résolu les Castle.Windsor différences, la dernière erreur de build restante dans le projet .NET Core Bean Trader est que BeanTraderServiceClient (qui dérive de DuplexClientBase) n’a pas de Open méthode. Cela n’est pas surprenant, car il s’agit d’une API mise en surbrillance par l’analyseur de portabilité .NET au début de ce processus de migration. BeanTraderServiceClient Cependant, en examinant notre attention sur une question plus importante. Ce client WCF a été généré automatiquement par l’outil Svcutil.exe .

Les clients WCF générés par Svcutil sont destinés à être utilisés sur .NET Framework.

Les solutions qui utilisent des clients WCF générés par svcutil devront régénérer les clients compatibles .NET Standard à utiliser avec .NET Core. L’une des principales raisons pour lesquelles les anciens clients ne fonctionnent pas est qu’ils dépendent de la configuration de l’application pour définir des liaisons et des points de terminaison WCF. Étant donné que les API WCF .NET Standard peuvent fonctionner sur plusieurs plateformes (où les API System.Configuration ne sont pas disponibles), les clients WCF pour les scénarios .NET Core et .NET Standard doivent définir des liaisons et des points de terminaison par programmation plutôt que dans la configuration.

En fait, toute utilisation du client WCF qui dépend de la <system.serviceModel> section app.config (créée avec Svcutil ou manuellement) doit être modifiée pour fonctionner sur .NET Core.

Il existe deux façons de générer automatiquement des clients WCF compatibles avec .NET Standard :

  • L’outil dotnet-svcutil est un outil .NET qui génère des clients WCF d’une manière similaire à celle utilisée précédemment par Svcutil.
  • Visual Studio pouvez générer des clients WCF à l’aide de l’option WCF Web Service Reference de sa fonctionnalité Services connectés.

L’une ou l’autre approche fonctionne bien. Vous pouvez également écrire le code client WCF vous-même. Pour cet exemple, j’ai choisi d’utiliser la fonctionnalité Visual Studio Service connecté. Pour ce faire, cliquez avec le bouton droit sur le projet BeanClient.Core dans l’Explorateur de solutions de Visual Studio, puis sélectionnez AddConnected> Service. Ensuite, choisissez le fournisseur WCF Web Service Reference. Cela affiche une boîte de dialogue dans laquelle vous pouvez spécifier l’adresse du service web Bean Trader principal (localhost:8080 si vous exécutez le serveur localement) et l’espace de noms que les types générés doivent utiliser (BeanShell.Service, par exemple).

WCF Web Service Reference Connected Service Dialog

Après avoir sélectionné le bouton Terminer , un nouveau nœud Services connectés est ajouté au projet et un fichier Reference.cs est ajouté sous ce nœud contenant le nouveau client WCF .NET Standard pour accéder au service Bean Trader. Si vous examinez les méthodes ou GetBindingForEndpoint les GetEndpointAddress méthodes de ce fichier, vous verrez que les liaisons et les points de terminaison sont désormais générés par programme (au lieu de via la configuration de l’application). La fonctionnalité « Ajouter des services connectés » peut également ajouter des références à certains packages System.ServiceModel dans le fichier projet, ce qui n’est pas nécessaire, car tous les packages WCF nécessaires sont inclus via Microsoft. Windows. Compatibilité. Vérifiez csproj pour voir si des éléments System.ServiceModel <PackageReference> supplémentaires ont été ajoutés et, le cas échéant, supprimez-les.

Notre projet a de nouvelles classes clientES WCF maintenant (dans Reference.cs), mais elle a également les anciennes (dans BeanShell.cs). Il existe deux options à ce stade :

  • Si vous souhaitez pouvoir générer le projet de .NET Framework d’origine (en même temps que le nouveau projet ciblé par .NET Core), vous pouvez utiliser un <Compile Remove="BeanTrader.cs" /> élément dans le fichier csproj du projet .NET Core afin que les versions .NET Framework et .NET Core de l’application utilisent différents clients WCF. Cela présente l’avantage de laisser le projet .NET Framework existant inchangé, mais a l’inconvénient que le code utilisant les clients WCF générés peut avoir besoin d’être légèrement différent dans le cas .NET Core que dans le projet .NET Framework, vous devrez probablement utiliser #if directives pour compiler de manière conditionnelle une certaine utilisation du client WCF (création de clients, par exemple) pour fonctionner d’une façon unique lorsqu’elle est générée pour .NET Core et une autre façon lorsqu’elle est générée pour .NET Framework.

  • Si, d’un autre côté, une activité de code dans le projet .NET Framework existant est acceptable, vous pouvez supprimer Bean Basic.cs ensemble. Étant donné que le nouveau client WCF est conçu pour .NET Standard, il fonctionne à la fois dans les scénarios .NET Core et .NET Framework. Si vous créez des .NET Framework en plus de .NET Core (en multi-ciblage ou en ayant deux fichiers csproj), vous pouvez utiliser ce nouveau fichier Reference.cs pour les deux cibles. Cette approche présente l’avantage que le code n’a pas besoin de bifurquer pour prendre en charge deux clients WCF différents ; le même code sera utilisé partout. L’inconvénient est qu’il implique de modifier le projet (probablement stable) .NET Framework.

Dans le cas de l’exemple Bean Trader, vous pouvez apporter de petites modifications au projet d’origine s’il facilite la migration. Procédez comme suit pour rapprocher l’utilisation du client WCF :

  1. Ajoutez le nouveau fichier Reference.cs au projet .NET Framework BeanClient.csproj à l’aide du menu contextuel « Ajouter un élément existant » dans l’Explorateur de solutions. Veillez à ajouter « as link » afin que le même fichier soit utilisé par les deux projets (par opposition à la copie du fichier C#). Si vous créez pour .NET Core et .NET Framework avec un seul csproj (à l’aide de multi-ciblage), cette étape n’est pas nécessaire.

  2. Supprimez BeanActive.cs.

  3. Le nouveau client WCF est similaire à l’ancien, mais un certain nombre d’espaces de noms dans le code généré sont différents. Pour cette raison, il est nécessaire de mettre à jour le projet afin que les types de client WCF soient utilisés à partir de Bean Recovery.Service (ou le nom de l’espace de noms que vous avez choisi) au lieu de Bean Basic.Model ou sans espace de noms. La génération de BeanClient.Core.csproj permet d’identifier l’endroit où ces modifications doivent être apportées. Les correctifs seront nécessaires à la fois en C# et dans les fichiers sources XAML.

  4. Enfin, vous découvrirez qu’il existe une erreur dans BeanClientServiceFactory.cs , car les constructeurs disponibles pour le BeanTraderServiceClient type ont changé. Il était possible de fournir un InstanceContext argument (créé à l’aide Castle.Windsor d’un CallbackHandler conteneur IoC). Les nouveaux constructeurs créent des s CallbackHandler. Toutefois, il existe des constructeurs dans BeanTraderServiceClientle type de base de 's qui correspondent à ce que vous voulez. Étant donné que le code client WCF généré automatiquement existe tous dans des classes partielles, vous pouvez facilement l’étendre. Pour ce faire, créez un fichier appelé Bean DefenderServiceClient.cs , puis créez une classe partielle portant ce même nom (à l’aide de l’espace de noms Bean Defender.Service). Ensuite, ajoutez un constructeur au type partiel comme indiqué ici.

    public BeanTraderServiceClient(System.ServiceModel.InstanceContext callbackInstance) :
        base(callbackInstance, EndpointConfiguration.NetTcpBinding_BeanTraderService)
            { }
    

Avec ces modifications apportées, l’exemple Bean Trader utilise désormais un nouveau client WCF compatible .NET Standard et vous pouvez apporter le correctif final de la modification de l’appel dans TradingService.cs à utiliser await OpenAsync à la Open place.

Avec les problèmes WCF résolus, la version .NET Core de l’exemple Bean Trader génère désormais correctement !

Tests d’exécution

Il est facile d’oublier que le travail de migration n’est pas effectué dès que le projet s’appuie correctement sur .NET Core. Il est également important de laisser le temps de tester l’application portée. Une fois les éléments générés correctement, assurez-vous que l’application s’exécute et fonctionne comme prévu, en particulier si vous utilisez des packages ciblant .NET Framework.

Essayons de lancer l’application Bean Trader portée et voyons ce qui se passe. L’application n’est pas loin avant d’échouer avec l’exception suivante.

System.Configuration.ConfigurationErrorsException: 'Configuration system failed to initialize'

Inner Exception
ConfigurationErrorsException: Unrecognized configuration section system.serviceModel.

C’est logique, bien sûr. N’oubliez pas que WCF n’utilise plus la configuration d’application, de sorte que l’ancienne section system.serviceModel du fichier app.config doit être supprimée. Le client WCF mis à jour inclut toutes les mêmes informations dans son code. Par conséquent, la section de configuration n’est plus nécessaire. Si vous souhaitez que le point de terminaison WCF soit configurable dans app.config, vous pouvez l’ajouter en tant que paramètre d’application et mettre à jour le code client WCF pour récupérer le point de terminaison de service WCF à partir de la configuration.

Après avoir supprimé la section system.serviceModel de app.config, l’application démarre mais échoue avec une autre exception lorsqu’un utilisateur se connecte.

System.PlatformNotSupportedException: 'Operation is not supported on this platform.'

L’API non prise en charge est Func<T>.BeginInvoke. Comme expliqué dans dotnet/corefx#5940, .NET Core ne prend pas en charge les méthodes et EndInvoke les méthodes sur les BeginInvoke types délégués en raison des dépendances de communication à distance sous-jacentes. Ce problème et son correctif sont expliqués plus en détail dans le billet de blog Delegate.BeginInvoke Calls for .NET Core , mais le gist est que BeginInvoke et EndInvoke les appels doivent être remplacés Task.Run par (ou des alternatives asynchrones, si possible). Application de la solution générale ici, l’appel BeginInvoke peut être remplacé par un Invoke appel lancé par Task.Run.

Task.Run(() =>
{
    return userInfoRetriever.Invoke();
}).ContinueWith(result =>
{
    // BeginInvoke's callback is replaced with ContinueWith
    var task = result.ConfigureAwait(false);
    CurrentTrader = task.GetAwaiter().GetResult();
}, TaskScheduler.Default);

Après avoir supprimé l’utilisation BeginInvoke , l’application Bean Trader s’exécute correctement sur .NET Core !

Bean Trader running on .NET Core

Toutes les applications sont différentes, de sorte que les étapes spécifiques nécessaires pour migrer vos propres applications vers .NET Core varient. Mais espérons que l’exemple Bean Trader illustre le flux de travail général et les types de problèmes qui peuvent être attendus. Et, malgré la longueur de cet article, les modifications réelles nécessaires dans l’exemple Bean Trader pour qu’elle fonctionne sur .NET Core étaient relativement limitées. De nombreuses applications migrent vers .NET Core de la même façon ; avec des modifications de code limitées ou même aucune modification requise.