Comment résoudre la System.Resources.MissingManifestResourceException pouvant se produire après un Migration .net Framework ?

Dans le cadre d'une migration d'un projet .net Framework, je me suis retrouvé avec des projets ne respectant que très peu les conventions liées aux namespaces. Plusieurs dossiers pourtant bien présents n'apparaissent pas dans les namespaces. Pour compléter le tableau, Visual Studio doit gérer des fichiers C# pour des Ressources, et formulaires WinForm présents dans ces dossiers.

Après migration de ces projets du vieux format csproj / vbproj vers le format actuel (SDK project style), les tests unitaires font remonter une erreur bien étrange :

System.Resources.MissingManifestResourceException : Could not find any resources appropriate for the specified culture or the neutral culture. Make sure "xxxForm.resources" was correctly embedded or linked into assembly "xxx" at compile time, or that all the satellite assemblies required are loadable and fully signed.

Pour saisir l’origine du problème, je vérifie les propriétés de Ressources. Tout est à sa place. Je fais un petit tour dans dotPeek pour comparer les deux DLL avant, et après migration. Je me rends alors compte que les ressources WinFrom ne sont pas nommées de la même manière avant et après migration du projet. Les projets de tests unitaires étant encore à l’ancien format, il s’attend à ce que les ressources respectent un certain nommage.

Un petit tour dans Microsoft Learn (après une bonne heure de recherche infructueuse ici et là), je trouve le Satin Graal : How MSBuild generates manifest file names - .NET | Microsoft Learn

Cette page de documentation fait état d'un paramètre qui rétablit le comportement des namespace pour les applications .net Framework :


<EmbeddedResourceUseDependentUponConvention>true</EmbeddedResourceUseDependentUponConvention>

Après ajout de celui-ci dans le fichier vsproj / vbproj, les ressources sont correctement nommée et l'erreur ne se produit plus.

Moralité

Encore une preuve que les bonnes pratiques ne sont pas juste là pour nous embeter. Si les conventions de nommage des namespaces étaint respectées, l'exception ne se serait jamais produite.

Jérémy Jeanson

Comment identifier, et résoudre les problèmes d'une migration vers PackageReference ratée ?

En temps normal, la migration des références vers PackageReference se passe en douceur. Il suffit de faire un click droit sur le dossier References, et de profiter du menu contextuel qui s'ouvre pour lancer la migration. Quelques secondes plus tard, le fichier package.config disparait.

Mais il arrive que les choses ne se passent pas bien. Ce peut être au moment de la migration, ou après la récupération du code d'un collègue.

Une migration peut être refusée, pour des raisons d'incompatibilité. Le motif est noté dans la fenêtre de sortie de Visual Studio. Il suffit de résoudre le problème indiqué.

Dans d'autres cas, la migration est allée au bout. Mais ce n'est pas pour autant que tout va bien. Cela se matérialise par des références apparaissant en double. Certaines ont l'icône nuget, d'autres non.

Projet avec des références en double

Cela est dû au fait que lors de la migration, l'assistant n'a pas supprimé les liens vers le dossier ..\packages. Pour corriger ce projet, il suffit donc d'éditer le fichier csproj, ou vbproj, et de supprimer les nœuds XML de type Reference dont le HnitPath pointe vers le dossier ..\packages.


<Reference Include="AutoFixture, Version=4.17.0.0, Culture=neutral, PublicKeyToken=b24654c590009d4f, processorArchitecture=MSIL">
  <HintPath>..\packages\AutoFixture.4.17.0\lib\net452\AutoFixture.dll</HintPath>
</Reference>

Voilà, vous savez maintenant corriger un projet dont la migration vers PackageReference n'a pas réussi du premier coup ;)

Jérémy Jeanson

Gérer le warning "Cannot convert null literal to non-nullable reference type" avec Moq

Quand on utilise Moq avec de vieux projets qui migrent de .net Framework vers .net, il y a un petit Warning qui devient vite irritant :

Warning CS8625 Cannot convert null literal to non-nullable reference type.

Celui-ci se produit quand une méthode doit retourner null.

Voici un exemple d'interface avec une opération asynchrone (histoire de reproduire un cas proche de la réalité).


public interface IMyRepository {
  ValueTask<MaClass?> MethodeReturnMaClassNullableAsync(...);
}

Pour utiliser Moq, il faut ajouter un Cast en plus du ValueTask.FromResult.


var repository = new Mock<IMyRepository>();
repository
  .Setup(c => c.MethodeReturnMaClassNullableAsync(...))
  .Returns(ValueTask.FromResult((MaClass?)null));

Toute implémentation sans le Cast et le ValueTask.FromResult feront apparaitre un Waring. Cette écriture n'est pas nouvelle, mais il semble qu'elle perturbe encore nombre de développeurs.

Jérémy Jeanson

Peut-on effectuer la mise à jour d'un agent de build en pleine journée ?

Voici une petite question qui peut perturber les administrateurs d’agent hébergés on-premise. La réponse est très simple : Oui, on peut demander la mise à jour des agents de build / déploiement en pleine journée.

La demande de mise à jour provoque la planification de celle-ci. Malheureusement, cette planification n’apparait pas dans la liste des jobs en attente.

Si un agent est occupé par une build ou un déploiement, il finit son travail. La mise à jour s'effectue ensuite.

Jérémy Jeanson

Le GC de .net 8 est parti pour être fun. Mais est-ce suffisant ?

Dynamically Adapting To Application Sizes (DATAS), voici un nom auquel il va falloir s’adapter. IL s’agit d’une nouvelle fonctionnalité du GC introduite avec .net 8.

Vous trouverez ici un très bel article décrivant le fonctionnement de DATAS, et quelques indicateurs sur le gain de performances obtenu (merci à Laurent de me l’avoir fait connaitre).

Dans les faits, il faudra attendre la RTM de .net 8 pour se faire une idée de l’implémentation définitive. Sur le principe, cette fonctionnalité va permettre une gestion dynamique du nombre de heaps (augmentation, et diminution). L’augmentation devrait permettre d’accélérer les allocations. La diminution devrait permettre de réduire la consommation globale de l’application.

Depuis que j’ai entendu parler de DATAS, je suis emballé. J’ai cependant un petit regret. La configuration est minimale. Un simple switch on / off. J’aurais aimé disposer d’un contrôle sur les nombres min / max de heap, ou une option pour anticiper les besoins en mémoire.

Ne vous méprenez pas, DATAS est une très bonne chose. Il me semble cependant que cette fonctionnalité devait initialement aller un peu plus loin. Un peu de contrôle n’aurait pas été désagréable ;)

Jérémy Jeanson