Ces derniers jours j’ai été confronté à une problématique très intéressante; j’aimerais la partager avec vous aujourd’hui et vous soumettre la solution que j’ai imaginé pour y répondre.
Mon client souhaitait restreindre l’accès à son site/application intranet aux seuls membres d’un groupe Windows prédéfini.
Le hic est que le site fonctionne avec des rôles définis en base de données, laissant ainsi libre champs aux admins de l’appli d’attribuer et révoquer des droits aux autres utilisateurs. Évidement, il était hors de question de devoir passer par l’IT pour changer les droits des utilisateurs à l’avenir.
Si l’on transpose la demande en termes techniques, on a besoin du WindowsTokenRoleProvider pour autoriser l’accès au site et pour tout le reste il faut se reposer sur un SqlRoleProvider.
Comme vous le savez, ce genre de configuration n’est pas prise en charge par le modèle de provider tel que nous le connaissons jusqu’à présent.
En cherchant sur la toile si ce genre de situation à déjà été traitée (ne réinventons pas la roue carrée), vous tomberez très certainement sur un article de Scott Guthrie qui traite d’un problème très similaire. Dans l’article l’homme à la chemise rouge nous montre comment, sans code, utiliser les rôles issus de la base de données et n’autoriser l’accès au site qu’aux utilisateurs identifiés. Et c’est bien sur ce dernier point que nos problématiques diffèrent car dans mon cas, il ne suffit pas d’être identifié sur le domaine, mais il faut aussi faire partie d’un groupe d’utilisateurs.
J’ai donc remonté mes manches, et quitte à écrire du code j’ai tenté de trouver une solution générique permettant de combiner des RoleProvider.
Commençons par définir un RoleProviderDecorator qui comme son nom l’indique suit le pattern Decorator.
public class RoleProviderDecorator : RoleProvider
where TSurrogateRoleProvider : RoleProvider, new()
{
protected TSurrogateRoleProvider _surrogate;
public override void Initialize(string name, NameValueCollection config)
{
if (config == null)
throw new ArgumentNullException("config");
base.Initialize(name, config);
_surrogate = new TSurrogateRoleProvider();
InitializeInnerProvider(_surrogate, name, config);
}
protected virtual void InitializeInnerProvider(RoleProvider innerProvider, string name, NameValueCollection config)
{
innerProvider.Initialize(name, config);
}
public override bool IsUserInRole(string username, string roleName)
{
return _surrogate.IsUserInRole(username, roleName);
}
public override string[] GetRolesForUser(string username)
{
return _surrogate.GetRolesForUser(username).ToArray();
}
public override void CreateRole(string roleName)
{
_surrogate.CreateRole(roleName);
}
public override bool DeleteRole(string roleName, bool throwOnPopulatedRole)
{
return _surrogate.DeleteRole(roleName, throwOnPopulatedRole);
}
public override bool RoleExists(string roleName)
{
return _surrogate.RoleExists(roleName);
}
public override void AddUsersToRoles(string[] usernames, string[] roleNames)
{
_surrogate.AddUsersToRoles(usernames, roleNames);
}
public override void RemoveUsersFromRoles(string[] usernames, string[] roleNames)
{
_surrogate.RemoveUsersFromRoles(usernames, roleNames);
}
public override string[] GetUsersInRole(string roleName)
{
return _surrogate.GetUsersInRole(roleName).ToArray();
}
public override string[] GetAllRoles()
{
return _surrogate.GetAllRoles().ToArray();
}
public override string[] FindUsersInRole(string roleName, string usernameToMatch)
{
return
_surrogate.FindUsersInRole(roleName, usernameToMatch).ToArray();
}
public override string ApplicationName
{
get { return _surrogate.ApplicationName; }
set { _surrogate.ApplicationName = value; }
}
}
L’idée du décorateur, vous l’aurez compris, est de déléguer l’exécution des méthodes au vrai RoleProvider passé sous forme de Generic.
Maintenant définissons un RoleProvider qui se charge d’utiliser de manière sous-jacente deux RoleProvider :
public abstract class UnionRoleProvider : RoleProviderDecorator
where TPrimaryRoleProvider : RoleProvider, new()
where TSecondaryRoleProvider : RoleProvider, new()
{
private TSecondaryRoleProvider _secondSurrogate;
public override void Initialize(string name, NameValueCollection config)
{
if (config == null)
throw new ArgumentNullException("config");
base.Initialize(name, config);
_secondSurrogate = new TSecondaryRoleProvider();
InitializeInnerProvider(_secondSurrogate, name, config);
}
public override bool IsUserInRole(string username, string roleName)
{
return _surrogate.IsUserInRole(username, roleName) || _secondSurrogate.IsUserInRole(username, roleName);
}
public override string[] GetRolesForUser(string username)
{
return _surrogate.GetRolesForUser(username).Union(_secondSurrogate.GetRolesForUser(username)).ToArray();
}
public override void CreateRole(string roleName)
{
_surrogate.CreateRole(roleName);
_secondSurrogate.CreateRole(roleName);
}
public override bool DeleteRole(string roleName, bool throwOnPopulatedRole)
{
return _surrogate.DeleteRole(roleName, throwOnPopulatedRole) && _surrogate.DeleteRole(roleName, throwOnPopulatedRole);
}
public override bool RoleExists(string roleName)
{
return _surrogate.RoleExists(roleName) || _secondSurrogate.RoleExists(roleName);
}
public override void AddUsersToRoles(string[] usernames, string[] roleNames)
{
_surrogate.AddUsersToRoles(usernames, roleNames);
_secondSurrogate.AddUsersToRoles(usernames, roleNames);
}
public override void RemoveUsersFromRoles(string[] usernames, string[] roleNames)
{
_surrogate.RemoveUsersFromRoles(usernames, roleNames);
_secondSurrogate.RemoveUsersFromRoles(usernames, roleNames);
}
public override string[] GetUsersInRole(string roleName)
{
return _surrogate.GetUsersInRole(roleName).Union(_secondSurrogate.GetUsersInRole(roleName)).ToArray();
}
public override string[] GetAllRoles()
{
return _surrogate.GetAllRoles().Union(_secondSurrogate.GetAllRoles()).ToArray();
}
public override string[] FindUsersInRole(string roleName, string usernameToMatch)
{
return
_surrogate.FindUsersInRole(roleName, usernameToMatch)
.Union(_secondSurrogate.FindUsersInRole(roleName, usernameToMatch))
.ToArray();
}
}
Dans mon cas, seul un des deux RoleProvider doit pouvoir écrire dans son médium de stockage.
L’autre doit être en lecture seule. Pour ce faire, j’introduis un ReadOnlyRoleProvider :
public class ReadOnlyRoleProvider : RoleProviderDecorator
where TSurrogateRoleProvider : RoleProvider, new()
{
public override void CreateRole(string roleName){}
public override bool DeleteRole(string roleName, bool throwOnPopulatedRole)
{
return true;
}
public override void AddUsersToRoles(string[] usernames, string[] roleNames){}
public override void RemoveUsersFromRoles(string[] usernames, string[] roleNames){}
Ça y est on a fait 90% du boulot.
Dans le fichier web.config on veut écrire ceci :
Étant donné que l’on ne peut pas utiliser de classe générique dans la config, on va simplement en spécialiser une :
public class MyRoleProvider : UnionRoleProvider>
{
protected override void InitializeInnerProvider(RoleProvider innerProvider, string name, NameValueCollection config)
{
NameValueCollection cfg = config;
if (innerProvider is ReadOnlyRoleProvider)
{
cfg = new NameValueCollection(config);
cfg.Remove("connectionStringName");
}
base.InitializeInnerProvider(innerProvider, name, cfg);
}
}
Ouf, 99% du boulot. Vous pouvez maintenant exécuter et ça va fonctionner… jusqu’à ce que vous utilisiez une des méthodes suivantes :
public class SilentWindowsTokenRoleProvider : ReadOnlyRoleProvider
where TSurrogateRoleProvider : RoleProvider, new()
{
public override string[] GetUsersInRole(string roleName)
{
return new string[]{};
}
public override string[] GetAllRoles()
{
return new string[]{};
}
public override string[] FindUsersInRole(string roleName, string usernameToMatch)
{
return new string[]{};
}
}
Voila 100% de la solution; beaucoup de code aujourd’hui, j’espère que ça ne vous a pas trop démotivé.
Si vous avez une solution autre, ou contestez mon approche n’hésitez pas à laisser un commentaire.
Cela fait des mois que je repousse la publication d’un post sur l’AOP, car je ne voulais pas vous resservir le sempiternel exemple de mise en place d’une gestion de log applicatif simplifiée.
Si vous voulez vous rafraichir les idées sur le sujet je vous conseille de jeter un œil sur l’article d’Ayende Rahien sur le sujet
Avec le framework .NET, il existe au moins 6 façons différentes d’ajouter un brin d’AOP dans vos programmes;
Pour mémoire il s’agit de :
Remoting Proxies
Dériver votre classe de ContextBoundObject
Passer par un dynamique proxy ( ex : Castle Dynamic Proxy)
Utiliser l’API de profiling de .NET
Injection d’IL après compilation
Injection d’IL au runtime
Dans cet article on s’intéressera particulièrement a l’injection post compilation, tout simplement car c’est la plus performante (le code lié à l’aspect est directement inscrit dans l’assembly finale et rien ne le distingue du reste du code) et aussi car c’est la façon la plus sexy a mon gout de faire de l’AOP (c’est une raison comme une autre, non?).
Bon revenons à nos moutons.
Sur presque tous les projets sur lesquels je suis intervenu ces dernières années, lorsqu’on modélise les entités qui devront être persistées en base, on leur adjoint au moins quatre propriétés :
Created By (string)
Created (datetime)
Last Updated By (string)
Last Updated (datetime)
J’ai pour habitude de nommer cette construction, une entité “Auditable”, ce qui se traduit en code par l’interface suivante :
Vous l’aurez compris, l’idée ici, est de stocker la date et l’utilisateur ayant créé ou modifié l’entité en question et ceci a chaque accès base.
Je vous laisse imaginer le travail rébarbatif que cela peut vite devenir si l’on doit tout gérer à la main et si notre modèle est composé de dizaines voire de centaines d’entités.
Je vous propose donc une idée afin de se faciliter la vie grâce à l’AOP.
Il est à noter que bien que l’exemple ci-dessous s’appuie sur Entity Framework, le mécanisme est très certainement transposable (avec adaptation) aux autres ORM.
On commence par utiliser Entity Framework Code First, et on ajoute notre framework AOP préféré
En attendant qu’un de mes patch soit accepté et intégré à Afterthought, il vous faudra remplacer la dll d’Afterthought par la mienne disponible ici.
Nuget a rajouté quelques dll et références dans votre projet et modifié également le post build event de votre projet;
Désormais à chaque compilation, Afterthought scannera les assemblies à la recherche de taches d’injection d’IL à effectuer.
Voyons comment demander à Afterthought d’injecter l’interface IAuditable sur nos entités.
public class AuditableAmender : Amendment
{
public AuditableAmender()
{
Properties.Add("CreatedBy");
Properties.Add("Created");
Properties.Add("UpdatedBy");
Properties.Add("Updated");
Implement();
}
}
Le code me semble assez clair sans avoir a revenir longuement dessus; On demande simplement à Afterthought d’injecter les propriétés nécessaires à l’implémentation de l’interface IAuditable.
Bien créons un attribut de marquage, que nous placerons sur nos entités :
[AttributeUsage(AttributeTargets.Class)]
public class AuditableAttribute : Attribute { }
il nous reste encore deux tâches à réaliser;
Préciser les assemblies à introspecter
Permettre à Afterthought de découvrir les classes qui doivent être modifiées et surtout comment.
Cela se fait dans une même classe :
[AttributeUsage(AttributeTargets.Assembly)]
public class AmendAttribute : Attribute, IAmendmentAttribute
{
IEnumerable IAmendmentAttribute.GetAmendments(Type target)
{
if (target.GetCustomAttributes(typeof(AuditableAttribute), true).Length >0)
{
ConstructorInfo constructorInfo =typeof(AuditableAmender<>).MakeGenericType(target).GetConstructor(Type.EmptyTypes);
if (constructorInfo != null)
yieldreturn (ITypeAmendment)constructorInfo.Invoke(new object[0]);
}
}
}
En clair, pour chaque classe qui implémente IAuditable, on va faire appel à la classe AuditableAmender (créée précédemment) pour modifier la classe.
En appliquant cet attribut sur l’assembly qui contient vos entités, Afterthought effectuera son travail d’injection.
Désormais si l’on applique l’attribut Auditable sur une de nos entités comme suit :
[Auditable]
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
}
public class Context : DbContext
{
public virtual DbSet Products { get; set; }
public Context()
{
Database.SetInitializer(new DropCreateDatabaseIfModelChanges());
}
}
Ce qui produit bien en base la table suivante :
et dans l’assembly finale (vu avec Reflector)
Évidemment, en généralisant ce principe il est possible de facilement faire évoluer votre modèle sans avoir à travailler parfois de manière répétitive.
Vous retrouverez cette fonctionnalité (il vous suffit de marquer votre entité avec l’attribut) dans la librairie EntityFramework.Patterns que je maintiens et disponible via nuget.
Que pensez-vous de cette technique?
Utilisez-vous un autre framework (Postsharp) pour réaliser ce genre de tâche?
SignalR is awesome ! Oui il va falloir vous habituer à entendre dire que cette librairie est un petit bijou.
Mais qu’est-ce qu’elle a de si bien cette librairie SignalR ?
SignalR fournit une couche d’abstraction au-dessus des WebSocket et des long polling connections pour les développeurs .NET et fonctionne sous IIS sans avoir à installer un autre service à côté.
Jusqu’alors il existait bien des solutions commerciales de ces technos dans l’écosystème .NET mais aucune n’avait vraiment convaincu, souvent par manque d’intégration forte entre le client et le serveur; rendant la communication entre les deux fastidieuse.
Avec SignalR c’est tout le contraire, le code serveur et le code client ne semble faire qu’un. Les lignes de code glissent du serveur vers le navigateur, sans aucune lourdeur. C’est SIMPLE.
Comme d’hab, rien de mieux qu’un petit bout de code pour démontrer ça.
Bon, si vous avez lu le titre vous avez une petite idée de ce que l’on va coder…
Coté composants techniques, évidement SignalR pour la communication client/serveur et Highchart coté client pour dessiner le chart.
Après avoir créé un nouveau projet WebForm ou MVC ajoutez le package SignalR via nuget.
Intéressons-nous au code javascript. (A noter que le code lié à la configuration du chart à été omis a des fins de clarté.)
$(function () {
var stockExchangeServer =$.connection.stockExchange;
stockExchangeServer.drawShareValue = function (id, content) {
if (id != this.Id) {
return;
}
// set up the updating of the chart.var series = chart.series[0];
var x = new Date(parseInt(content[0].Date.substr(6))).getTime(),
y = content[0].Price;
series.addPoint([x, y], true, true);
};
$.connection.hub.start(function () {
stockExchangeServer.connect();
.done(function (success) {
if (success ===false) {
console.log(":(");
}
console.log("connected");
});
});
});
Pour initier la communication avec le serveur, il suffit comme vous pouvez le voir d’utiliser la méthode $.connection.hub.start();
On se contente alors de faire appel à la méthode connect; nous implémenterons cette dernière côté serveur…. oui vous avez bien lu, côté serveur.
Remarquez également la définition de la méthode drawShareValue() qui se charge de dessiner un nouveau point dans le chart, nous ferons appel à elle dans le code serveur.
Côté serveur, nous allons définir un hub, la classe de base qui abstrait la communication client/serveur dans l’api SignalR.
public class StockExchange : Hub
{
private Timer _timer;
private double _fowlerPrice = 10.2;
private double VaryPrice()
{
Random rnd = new Random();
_fowlerPrice = _fowlerPrice + (rnd.Next(-1, 2) * rnd.NextDouble());
return _fowlerPrice;
}
public bool Connect()
{
// Set unique id for client.
Caller.Id = Context.ClientId;
_timer = new Timer { Interval = 1000 };
_timer.Elapsed += (sender, e) => Send(
new List(new[]
{
new SharePrice
{
Date = DateTime.Now,
Price = VaryPrice(),
Share = new Share {Name = "Fowler-Corp"}
}
}));
_timer.Start();
return true;
}
public void Send(IEnumerable sp)
{
Clients.drawShareValue(Context.ClientId, sp);
}
}
Avez-vous remarqué la méthode Connect()? On y a fait appel à partir du code JS.
Et l’appel à drawShareValue(), fait bien référence à la méthode que l’on à définit dans le javascript. Enorme, non?
Tout cela est possible parce que SignalR utilise dans ses fondements le typage dynamic à laquelle vient s’ajouter une résolution de Propriété/Méthode “simple” par convention, évitant ainsi d’avoir à écrire du code verbeux.
Au final, voici ce que l’on peut obtenir…
Bon ok, l’image ne bouge pas, il faut imaginer qu’un point vient s’ajouter chaque seconde dans le graph :)
Pour ceux qui voudrait jeter un coup d’œil à la solution entière, vous pouvez la récupérer sur Github
Avez-vous utilisé d’autres technos/framework pour faciliter les communications “temps réel” entre le serveur et le browser?
Je vous propose un très rapide post pour faciliter la prise en main des Patterns Repository et Unit Of Work disponibles dans EntityFramework.Patterns
Pour simplifier le sujet, le but du pattern Repository est de donner au développeur une passerelle de requêtage unifiée, via des opérations (le plus souvent) atomiques.
Unit Of Work, est la quant à lui pour valider transactionnellement un ensemble de modifications faites sur les données.
Partons d’un modèle simplissime comme celui ci-dessous :
public class Context : DbContext
{
public virtual DbSet Products { get; set; }
public virtual DbSet Categories { get; set; }
public Context()
{
Database.SetInitializer(new DropCreateDatabaseAlways());
}
}
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public int? ProductCategoryId { get; set; }
public virtual ProductCategory ProductCategory { get; set; }
}
public class ProductCategory
{
public int Id { get; set; }
public string Name { get; set; }
}
Commençons par créer le Repository
using(Context ctx = new Context())
{
DbContextAdapter adapter = new DbContextAdapter(ctx);
IRepository productRepo = new Repository(adp);
...
}
Interrogeons la base pour ramener l’ensemble des produits :
IEnumerable lst = productRepo.GetAll();
Ramenons le premier élément dont le nom commence par “Bike”
L’utilisation de la méthode Single a pour effet de ramener un seul élément de la base de données; mais lève une exception si plusieurs éléments répondent aux critères demandés.
productRepo.Single(p => p.Name.StartsWith("Roc"))
Find(), permet de filtrer en fonction de plusieurs critères :
IEnumerable lst = productRepo.Find(
p => p.Id >[] includeProperties
définissant les jointures à effectuer lors du requêtage.
Ainsi :
productRepo.First(p => p.ProductCategoryId != null , p => p.ProductCategory);
charge le produit et sa catégorie associée en une seule requete SQL; Cette façon de faire nous protège du fameux problème du Select N+1 lié au lazy loading utilisé par défaut avec Entity Framework.
Après l’interrogation de la base passons a la persistance de nos données. Pour cela nous allons faire appel au pattern UnitOfWork.
L’insertion d’un nouvel élément prend cette forme :
using(...)
{
...
IUnitOfWork unitOfWork = new UnitOfWork(adp);
Product p = new Product{Name = "Skateboard"};
productRepo.Insert(p);
unitOfWork.Commit();
}
Vous l’aurez compris la mise à jour et la suppression sont aussi simple que ca :
Dans les prochains jours je vous montrerais comment injecter Repository et Unit of work dans une couche Service par exemple avec un conteneur DI comme NInject.
Bon ça y est je me suis décidé à trouver un toit pour EntityFramework.Patterns, une librairie qui s’adossant à Entity Framework 4.1, propose l’implémentation de patterns couramment nécessaire lorsqu’on utilise un ORM.
Je n’ai toujours pas cédé aux appels des sirènes de Github; EntityFramework.Patterns est donc hébergé sur Codeplex.
Vous trouverez également la librairie sur nuget… d’ailleurs elle y était présente bien avant la création du repository sur codeplex.
Pour l’installer via nuget, rien de plus simple :
install-package EntityFramework.Patterns
A l’heure actuelle, vous trouverez deux patterns d’infrastructure :
The Repository Pattern is all about encapsulating calls to your DB as methods to do a thing. These calls are (typically) atomic.
Tout est dit! L’avantage est simple, couplé avec un/des décorateurs il sera facile d’ajouter des comportements transverses (cache, securité, log etc…)
UnitOfWork is - well it’s a way of transactionally flushing changes to a persistence store (aka Database)
Ce qui permet de découpler facilement la gestion d’état des entités et le requêtage.
Des patterns d’infrastructure pour l’instant, qui seront rapidement suivit par les patterns suivants :
Repository Decorator
Audit log
Audit trail
Archived entity
Internationalized entity
Dans un tout prochain post je présenterais ces deux patterns Repostitory et UnitOfWork.
Un rapide post pour vous faire partager ma joie de ce jour.
J’ai enfin reçu la commande que j’avais passé sur Roboshop il y a bientôt un mois (ils déconnent un peu chez Roboshop sur les délai…)
Bref, entre autre dans le colis :
Dans ce post il lève le voile sur l’intégration de nuget dans Visual Studio. On y apprend que malheureusement par manque de temps l’intégration n’est que minimaliste et que seul les packages présent sur la machine (%ProgramFiles%\Microsoft ASP.NET\ASP.NET MVC 3\Packages) ne peuvent être installés.
Après avoir fait un peu joujou avec, voici quelques points complémentaires :
Pour télécharger un package nuget (extension nupkg), vous pouvez utiliser nuget package explorer
Il n’y a pas de résolution de dépendance entre package. Vous devez donc les ordonner dans la section WizardData :
Les template de quickstart (plusieurs projets) fonctionnent dans cette configuration et peuvent tirer parti de l’installation de packages via nuget.
Il m’est arrivé a plusieurs reprise de noter que la commande
devenv /installvstemplates
ne suffisait a rafraichir le cache de template de Visual Studio. Un reboot de la machine remet tout dans l’ordre.
Dans mon précédent post j’effleurais le sujet des gains de productivité que pouvais procurer les templates et autres Quickstart dans vos développement de tous les jours.
Prenons le cas d’un Quickstart, qui je le rappelle n’est autre qu’une solution templatisée. Imaginons que l’on souhaite utiliser 3 ou 4 librairies externes .NET bien sentie et pourquoi pas une ou deux librairies javascript s’il s’agit d’une solution Web.
En m’appuyant sur Nuget lors de la création des templates de projet composant le Quickstart, les dépendances pourrons être facilement être mise à jour par les développeurs à posteriori toujours grâce à Nuget. Rien de magique dans tout ça, en fait chaque projet est doté de son propre fichier ‘packages.config’ (repository nuget pour le projet) relatant la version des librairies référencées.
Voici ce que j’aimerais mettre en place en plus :
Mise à jour automatique de nuget avant toute autre opération.
Téléchargement et installation automatisée des dépendances référencées pour chaque projet.
Mise à jour automatique des dépendances lors de la première installation.
Infos complémentaires :
Microsoft depuis le MVC3 Tool Update du 12 Avril 2011 propose un nouveau template de projet MVC3 basé lui aussi sur nuget. La technique utilisée est quelque peu différente de celle que je vous présente ici, mais ne permet pas la mise à jour automatique des dépendances à l’installation.
Je reviendrais surement très rapidement sur cette façon de faire dans un prochain billet.
Pour réaliser ces différents points il nous faudra coder un Wizard custom.
La première tâche consistant à déployer nuget.exe est simplissime. Il nous suffit de l’embarquer dans les ressources de notre Wizard, puis au runtime extraire l’exécutable et le copier par exemple dans le dossier « packages » au sein de la solution. Pour info, le dossier « package » est utilisé par nuget pour y stocker les dépendances qu’il a téléchargé.
Notez que le dossier package doit absolument se trouvé dans le même répertoire que votre fichier sln, si vous voulez pouvoir profiter de la mise à jour des packages. Il s’agit d’une restriction imposé par nuget lui-même
Nuget déployé, il est possible de le mettre à jour automatiquement en exécutant la ligne de commande suivante :
nuget update
Dans notre Wizard on pourra utiliser le code suivant :
Process nugetProc = new Process
{
StartInfo = new ProcessStartInfo(_nugetFilePath)
{
Arguments = "update",
RedirectStandardError = true,
RedirectStandardOutput = true,
UseShellExecute = false,
CreateNoWindow = true
},
};
nugetProc.Start();
StreamReader output = nugetProc.StandardOutput;
StreamReader error = nugetProc.StandardError;
nugetProc.WaitForExit();
Concernant l’installation des dépendances de chaque projet se fera simplement en utilisant une fois de plus une ligne de commande ; que l’on créera en C# de la manière suivante :
Process nugetProc = new Process
{
StartInfo = new ProcessStartInfo(_nugetFilePath)
{
Arguments = string.Format("install {0}", _packageFilePath),
RedirectStandardError = true,
RedirectStandardOutput = true,
UseShellExecute = false,
CreateNoWindow = true,
WorkingDirectory = new FileInfo(_nugetFilePath).DirectoryName
},
};
nugetProc.Start();
L’idée ici est en fait d’exécuter 1 fois pour chacun de vos projets la commande suivante :
nuget install %path/to%/packages.config
Reste encore la tache de mettre à jour toutes les dépendances. Malheureusement, pour l’instant nuget.exe ne propose pas encore de commande permettant la mise à jour des dépendances. Cependant un récent post, Phil Haack annonce la disponibilité de cette option pour la version de 1.4 de nuget.
En attendant cette fonctionnalité, nous pouvons nous en sortir en installant par default le package NuGetPackageUpdater. Ce dernier vous offre la possibilité d’exécuter la commande ‘Update-Package’ qui se chargera d’effectuer la mise à jour de toutes les dépendances de la solution.
J’espère qu’en suivant ces instructions vous pourrez construire des Templates qui se mettrons à jour tout seul.
Pour ceux qui souhaiteraient jeter un coup d’œil plus approfondi au code que je viens de présenter, il est disponible sur Codeplex avec l’ensemble des briques du Quikstart Obsidian sur lequel je travaille actuellement.
Dernièrement j’ai fait mumuse avec les template de fichiers et projets que l’on peut créer dans Visual Studio.
Pour ceux qui ne connaissent pas, les template de fichier vous permettent de définir l’ossature d’un type de fichier que vous utilisez souvent. Une fois créé vous retrouverez votre template dans le menu de Visual Studio « Add new Item ».
Par extension les projects template vous permettent de définir la structure d’un type projet, afin de prendre en compte les conventions de votre équipe par exemple. Vous y définissez l’ensemble des fichiers présent dès la création du projet.
Ce qui est très intéressant, c’est que cela permet d’avoir des projets prêts à l’emploi dans votre environnement, avec par exemple NLoget votre conteneur DIpréféré. Si vous êtes expérimenté cela vous évitera quelques copier-coller, si par contre vous ne connaissez pas bien une des briques technique cela vous évitera un tas de problématique.
Quoi qu’il en soit voici quelques astuces sur ces templates :
Bien que le SDK de Visual Studio vous propose des projets de type Item Template et Project Template, le plus simple reste d’utiliser le menu File -> Export Template de Viual Studio pour générer votre précieux template.
Pour créer un quickstart, qui n’est autre qu’un template de solution (plusieurs projets), je vous conseille de :
Exporter vos différents projets template et extraire les archives zip correspondante dans un même dossier.
Ajouter dans ce dossier de travail un fichier .vstemplate reprenant cette structure
Ma solution
Ma description
CSharp
1000
false
Enabled
true
monicone_100x100.ico
Web\MyTemplate.vstemplate
Domain\MyTemplate.vstemplate
DAL\MyTemplate.vstemplate
IServices\MyTemplate.vstemplate
Services\MyTemplate.vstemplate
Vous pouvez y mettre autant de référence a des projects que vous le souhaitez en ajoutant des balises ProjectTemplateLink.
Créez une archive zip du dossier de travail et placez la dans le dossier %VSInstallDir%\Common7\IDE\ProjectTemplates. Après redémarrage de Visual Studio le nouveau template sera dispo.
Dans le fichier vstemplate de votre Quickstart, la première occurrence à un projet sera par convention le startup project.
Pour étendre avec du code custom vos project template il vous faudra :
Il est possible de faire appel à une assembly custom dans un template de type quickstart, mais attention, la plupart des appels que vous ferez sur les objects EnvDTE et plus particulièrement EnvDTE.Project fréquemment utilisés dans la méthode IWizard. ProjectFinishedGenerating vous renverront des valeurs nulles.
La façon la plus simple de créer un VSIX de déploiement est d’utiliser le projet de type VSIX Project (Visual C# -> Extensibility -> VSIX Project). Le SDK doit être installé.
Dans le designer de VSIX, le champ ID comporte un GUID… n’y touchez surtout pas !
Il est possible de définir dans quelle catégorie de template se trouvera l’élément que vous déployez via votre VSIX en renseignant le champ Add to subfolder.
Sans nul doute Nuget a grandement amélioré le process de déploiement/configuration/utilisation de bibliothèques tierces dans l’écosystème .NET.
Malheureusement, certain package ne sont pas parfait, et nécessite que l’on trifouille encore un peu dans la config pour que tout soit fonctionnel.
J’en ai personnellement fait l’expérience lorsque j’ai tenté d’utiliser les MVC Extensions de Telerik via Nuget, une excellente librairie de composants graphiques soit dit en passant.
Vous l’aurez compris, l’idée de ce post est de présenter les manipulations à faire pour pouvoir finaliser l’installation de la librairie de Telerik.
On commence par demander l’installation du package :
Install-Package TelerikMvcExtensions
Pas d’inquiétude, si cela prend un peu de temps, c’est normal; il y a un paquet de fichier a rapatrier puis à ajouter dans la solution.
Une fois que c’est fait, si vous ouvrez une vue, vous vous rendrez compte que malheureusement, l’intellisense ne vous propose rien de nouveau. Pire, s’il vous prenait l’envie de copier coller un exemple de code issue du site de Telerik, vous auriez un joli plantage.
Pour corriger tout ça, on va trifouiller dans le web.config. Ci-dessous les élèments à ajouter :
Ouf! ca y est on a rajouté tout ce qu’il nous manquait.
En ouvrant de nouveau une vue, l’intellisense devrait se mettre a vous proposer de nouvelles choses dans le namespace Telerik. Parfois, l’intellisense reste muet et je n’ai pas trouvé d’autres alternatives que de redemarrer mon Visual Studio 2010.
Edit – 18/04
Evidemment, il nous faut également modifier la master page afin d’y ajouter les styles et les scripts de Telerik.