En 2024, les développeurs .net commencent à être habitués aux acronymes STS/LTS associés aux différentes versions de .net. Mais tous ne saisissent pas forcément les implications de ceux-ci. Le plus souvent la réponse apportée peu se résume dans le tableau suivant :
Définition | Acronyme | Identification | Durée du support |
---|---|---|---|
Long Term Support | LTS | Version paire de .net | 3 ans |
Standard Term Support | STS | Version impaire de .net | 1 an et demi |
Alors, oui certaines personnes peuvent confondre le premier S de STS avec Short, mais la vraie méprise n'est pas là.
Il s'agit de la situation dans laquelle se trouve toute version de .net quand elle arrive dans ses 6 derniers mois de support. Durant cette période, vous ne recevrez que des correctifs de sécurités. Cela signifie que si un bug venait à être découvert durant cette période, il ne serait pas corrigé. Il vous serait juste recommandé de passer à une version supérieure.
Tout cela est expliqué sur la page ".NET and .NET Core Support Policy".
Maintenant, vous voyez peut-être où je souhaite en venir. La version 7 de .net arrive en fin de vie le 14 mai 2024, cela signifie que :
La fin de support de .net 7 est un jalon très important. Il est fortement recommandé de passer à .net 8 avant cette date. Voilà pourquoi les développeurs .net doivent migrer leurs applications dans les 6 mois qui suivent la publication d'une nouvelle version LTS. Considérer que vous avez davantage de temps pour le faire est une erreur. Que vous utilisiez une version LTS ou STS.
Voici une situation peu ragoutante que j'ai rencontrée il y a déjà quelque temps. IIS ne permettait pas la suppression d'un pool.
Heureusement, tout IIS est pilotable via PowerShell. Il suffit de connaitre les bonnes commandes.
L'application bloquant la suppression du pool n'était pas visible via la console. Dans un tel cas, il faut donc commencer par identifier les sites, et les applications que IIS ne semble pas vouloir afficher. Pour cela, il y a deux commandes à connaitre :
Get-Website
Get-WebApplication
Pour l'exemple, imaginons que je trouve une application app1
qui se trouvait dans le site web site1
. Pour pouvoir supprimer cette application, il faut donc utiliser la commande :
Remove-WebApplication -Name app1 -Site site1
Pour finir, le pool nommé pool1
dans mon exemple peut être supprimé via cette commande :
Remove-WebAppPool –Name pool1
L'une des choses que je préfère avec Blazor, consiste dans la possibilité de tester intégralement un composant via bUnit. Un bon exemple valant toujours mieux que de long discourt, voici un cas simple permettant de résoudre deux problématiques :
input
.input
.Pour commencer, on peut initialiser un contexte de test, et effectuer un rendu d'un composant :
// Création du context de test
using var context = new TestContext();
// Création du composent et rendu
var obj = context.RenderComponent<MonComposent>();
Pour modifier une input
dont l'Id serait inputId
, on peut utiliser le code :
obj.Find("#inputId").Change("Ma valeur");
Pour consulter le contenu d'un input
dont l'ID serait inputId
, on peut utiliser le code :
var actual = obj.Find("#inputId").GetAttribute("value");
Pour finir, voici un exemple complet :
[Theory]
[InlineData("Foo")]
public void Exemple(String value)
{
// Création du context de test
using var context = new TestContext();
// Création du composent et rendu
var obj = context.RenderComponent<MonComposent>();
// Modification
obj.Find("#inputId").Change(value);
// … ajouter des interactions avec le composant ici
// Vérifier la valeur de l'input
var actual= obj.Find("#inputId").GetAttribute("value");
actual.Should().BeEquivalentTo(value);
}
Voici du code simple et efficace. Tout ce que j'aime!
Dans un précédent article, je présentais la démarche à suivre pour exécuter une action custom avec des privilèges élevés. Cette approche passe par l'exécution de l'action dans un contexte deferred
. Celui-ci a un impact sur la manière d'accéder aux propriétés via l'action custom.
Habituellement, WiX toolset permet d'utiliser la syntaxe qui suit pour accéder à des paramètres lors de l'installation. Dans le cadre d'une action custom deferred
, le code suivant n'est pas utilisable.
[CustomAction]
public static ActionResult DoSomething(Session session)
{
var destination = session["INSTALLFOLDER"];
// …
}
Il doit être remplacé par :
[CustomAction]
public static ActionResult DoSomething(Session session)
{
var destination = session.CustomActionData["INSTALLFOLDER"];
// …
}
CustomActionData
est alimenté via la propriété Value
de l'action custom. Le contenu accepte une syntaxe du style nom1=valuer1;nom2=valeur2;...
.
Malheureusement, cette propriété ne peut pas être affectée directement. Il faut ajout une seconde action custom pour cela. Dans l'exemple suivant, j'ai créé une action custom SetDoSomethingValue
qui affecter les propriétés que j'utilise dans l'action custom DoSomething.
<!-- Actions personnalisées -->
<Binary Id="CustomActions"
SourceFile="CustomActions.CA.dll"/>
<CustomAction Id="DoSomething"
BinaryRef="CustomActions"
DllEntry="DoSomething"
Execute="deferred"
Return="check"
Impersonate="no"/>
<CustomAction Id="SetDoSomethingValue"
Property="DoSomething"
Value="INSTALLFOLDER=[INSTALLFOLDER]"/>
Ben évidemment, l'action custom SetDoSomethingValue
doit être ajoutée à la section InstallExecuteSequence
, et s'exécuter avant l'action DoSomething
.
<InstallExecuteSequence>
<Custom Action="DoSomething" Before="InstallFinalize" Condition="NOT REMOVE"/>
<Custom Action="SetDoSomethingValue" Before="DoSomething" Condition="NOT REMOVE"/>
</InstallExecuteSequence>
Par défaut, lors du déploiement, une action custom s'exécute avec les privilèges de l'utilisateur courant. Celle-ci ne dispose d'aucun privilège administrateur. Le fait de fixer un scope
perMachine
comme ceci ne suffit pas :
<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
<Package
Name="..."
Scope="perMachine">
Une solution peut consister dans le fait de d'utiliser un terminal avec une élévation de privilège pour lancer msiexec (msiexec /i mon-fichier.msi).
Heureusement WiX Toolset, permet de demander à Windows Installer d'exécuter une action custom avec des privilèges élever facilement. Pour cela, il faut utiliser l'option deferred
pour la propriété Execute
, et no
pour la propriété Impersonate
. La raison de ce changement est expluqée ici : Immediate Custom Actions Always Impersonate - Visual Studio Setup (microsoft.com)
Exemple :
<!-- Actions personnalisées -->
<Binary
Id="CustomActions"
SourceFile="CustomActions.CA.dll"/>
<CustomAction Id="DoSomething"
BinaryRef="CustomActions"
DllEntry="DoSomething"
Return="check"
Execute="deferred"
Impersonate="no"/>
Bien évidemment, l'action custom DoSomething
doit être présente dans la séquence InstallExecuteSequence
. Son exécution doit être planifiée entre InstallExecute
, et InstallFinalize
.
<InstallExecuteSequence>
<Custom Action="DoSomething" Before="InstallFinalize" />
</InstallExecuteSequence>
Après avoir appliqué ces modifications, l'action custom s'exécutera toujours avec des privilèges élevés.
Automatisé le déploiement de MSI, n’a jamais été une tâche triviale. Aujourd’hui, je vous propose d’effectuer celle-ci avec Azure DevOps.
Pour l’exemple, j’utilise un pipeline de Release, et j’y ai ajouté une tâche powerShell :
Dans la propriété Inline
, il suffit de coller le contenu du script qui suit, et le tour est joué :
$msi = Get-ChildItem $(System.ArtifactsDirectory)\*.msi -Recurse
Write-Host "Msi trouvé : $msi"
$log = "D:\Applications\MyApp\installation.log"
if (Test-Path $log) {
Remove-Item $log -verbose
}
$arguments = @( "/i", $msi, "INSTALLFOLDER=""D:\Applications\MyApp""", "/quiet","/lv",$log)
Write-Host $arguments
Start-Process msiexec.exe -ArgumentList $arguments -Wait
Ce script va piloter msiexec en mode silencieux (c’est-à-dire sans interface), et attendre la fin du déploiement. Pour diagnostiquer les éventuels problèmes qui pourraient survenir lors du déploiement, un fichier de log est créé. Ce fichier est conservé jusqu’à la prochaine exécution.
Bien évidemment, il faudra adapter la variable $arguments
en fonction de vos besoins, et les différents chemins.
Ce script peut aussi être utilisé avec un pipeline de type multistage, et un job de déploiement.
Avec Azure Pipeline, changer le nom d'un job à la voler, ou du moins afficher un libellé propre n'est pas toujours de tout repos. Certaines syntaxes peuvent fonctionner avec la version Cloud d'Azure DevOps, et ne pas être prises en charge on-premise. D'autres ne sont pas possibles sur certaines propriétés.
Afin d'arriver à mes fins avec Azure DevOps, j'ai mis en place une stratégie simple, et efficace : passer par des variables.
Quelle que soit la plateforme :
Voici un petit exemple avec la propriété displayName
d'un job (un vrai cauchemar à conditionner). Le texte affiché pour présenter le job change en fonction d'un paramètre choisi par l'utilisateur au moment de lancer la build. Pour effectuer cette opération, je passe par une variable jobDisplayName
dont la définition change en fonction du paramètre jobType
.
parameters:
- name: jobType
displayName: Type de build
type: string
default: evolution
values:
- correctif
- evolution
variables:
${{ if eq(parameters.jobType,'correctif') }}:
jobDisplayName: "Compilation d'un correctif"
${{ else }}:
jobDisplayName: "Compilation d'une nouvelle version"
jobs:
- job: Build
displayName: ${{ variables.jobDisplayName }}
Simple, efficace, et déclinable à volonté.
Attention : le
else
utilisé ici ne fonctionnera pas sur une instance Azure DevOps Server qui n'est pas à jour. Il faudra alors utiliser un secondif
.
Diverses tâches d’Azure Pipeline disposent d’options de Clean
permettant de supprimer les fichiers inutiles (artefacts, binaires des précédentes builds, dossiers bin/obj et...). Ces options sont disponibles sur les tâches de compilations, les tâches de copies de fichiers, et dossiers.
Des développeurs un peu trop zélés auraient tendance à activer ces options à défaut. Malheureusement, celles-ci peuvent augmenter inutilement la durée d’exécution de la build. Il convient donc de prendre un peu de temps pour réfléchir à l’utilité de telles options.
Pour savoir si l’on peut avoir besoin d’utiliser des options de Clean
, il convient de répondre à quelques questions simples :
L’usage des options de clean est conditionné par la combinaison de vos réponses à ces trois questions.
Voulant faire simple, j’ai choisi de construire un tableau indiquant les situations pour lesquelles, un Clean
est utile.
Source control | BinariesDirectory | |||
---|---|---|---|---|
Agent de build | Git | TFVC | Oui | Non |
Microsoft hosted | Non | Non | Non | Non |
Self hosted / Azure VMs Scale Set |
Non | Oui | Oui | Non |
Pour résumer simplement les raisons qui ont conduit à apporter ces réponses :
git clean -ffdx
(par défaut). Celle-ci effectue un clean du repository local. Le répertoire contant le code source est donc propre, quoi qu’il arrive (par défaut).Aucun mécanisme de Clean
n’est nécessaire pour un usage 100% Cloud d’Azure DevOps, sauf si on utilise des VM Azure. Il en est de même pour un usage hybride avec Azure DevOps Server, et des agents Microsoft hosted.
Un usage 100% on-premise demandera donc un peu plus de réflexion.
Par le passé, j'ai eu à expliquer à des développeurs qu'il ne fallait pas utiliser un Guid
pour piloter winget
. Car l'Id
d’une application peut parfois prendre des formes étranges.
Sachant qu'un bon exemple vaut mieux que de longues explications, voici une petite démo de winget
retournant une liste contenant un id hors norme :
PS C:\Users\jeremy> winget list "app"
Nom ID Version
-----------------------------------------------------------
App {A0B1C63D-F1E2-4306-4A58-A8140C917516} 1.0.0.9
App2 a8cf55b6c07a823e 1.0.0.1
Dans le cas présent, la première application a été déployée via un msi, la seconde ClickOnce.
Voici donc la raison pour laquelle il ne faut pas penser que winget
n'utilise que des Guid
;)
Dans un précédent article, j'avais présenté la nouvelle manière de rendre accessibles les éléments internes d'un projet pour les tests unitaire. Aujourd'hui, je vous propose d'effectuer la même opération, avec un projet signé. L'approche est très similaire, il suffit d'ajouter l'attribut Key
à InternalsVisibleTo
. Seul petit problème, la documentation manque légèrement de détails sur la démarche à suivre (comment trouver la clé demandée, et comment la mettre en forme?).
Voici les étapes à suivre :
À ce niveau, la documentation est très bien faite. Si l'on suit la méthode présentée ici, Visual Studio ajoute des nœuds similaires à ceux-ci au projet de tests:
<SignAssembly>True</SignAssembly>
<AssemblyOriginatorKeyFile>..\strong-name-key.snk</AssemblyOriginatorKeyFile>
Note : Une bonne habitude pour se simplifier la vie consiste à réutiliser le même fichier snk pour signer l'ensemble des projets. Un seul fichier signifie qu'il n'y a qu'une seule clé à utiliser pour la totalité des projets. Ainsi, il est plus facile de s'y retrouver, et l'opération qui suit n'a besoin d'être effectuée une seule foi.
Après compilation du projet de tests, on peut demander à Visual Studio d'afficher la totalité des fichiers. Il suffit alors de dérouler l'arborescence jusqu'aux dossiers de destination des binaires du projet de tests. Ensuite, un simple click droit sur le répertoire permet d'afficher le menu contextuel suivant. Il reste alors à choisir Open in Terminal
.
Via le terminal intégré à Visual Studio, il ne reste plus qu'à utiliser la commande sn en lui fournissant le nom de l'assembly produit par le projet de tests unitaires.
PS C:\xxx\bin\x64\Debug\net8.0> sn -Tp .\MonProjet.Tests.dll
Microsoft (R) .NET Framework Strong Name Utility Version 4.0.30319.0
Copyright (c) Microsoft Corporation. All rights reserved.
Public key (hash algorithm: sha1):
xxx1
xxx2
xxx3
xxx4
xxx5
Public key token is yyy
La clé qui nous intéresse se trouve derrière Public key (hash algorithm: sha1)
.
La clé récupérée via la précédente étape peut alors être utilisée pour renseigner l'attribut Key de InternalVisibleTo qui se trouve dans le projet à tester. La clé doit être retranscrite sans saut de ligne.
<ItemGroup>
<InternalsVisibleTo Include="$(AssemblyName).Tests" Key="xxx1xxx2xxx3xxx4xxx5" />
</ItemGroup>
Simple, et efficace. Par contre, il ne s'agit pas du genre d'opération que l'on a envie de faire chaque matin. D'où mon précédent conseil d'utiliser le même fichier snk pour signer l'ensemble des projets d'une même solution.