Implémenter une sécurité à base de rôles avec Windows Authentication & SQL Server

Publié par Fabrice Michellonet sous le(s) label(s) le 14 octobre 2011

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<TSurrogateRoleProvider> : 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<TPrimaryRoleProvider, TSecondaryRoleProvider> : RoleProviderDecorator<TPrimaryRoleProvider>
        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<TSurrogateRoleProvider> : RoleProviderDecorator<TSurrogateRoleProvider>
        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 :

<roleManager enabled="true" defaultProvider="MyRoleProvider">
      <providers>
        <clear/>
        <add name="MyRoleProvider " type="Providers.MyRoleProvider " connectionStringName="cnx" applicationName="MyApp" />
      </providers>
    </roleManager>

É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<SqlRoleProvider, SilentWindowsTokenRoleProvider<WindowsTokenRoleProvider>>
    {

        protected override void InitializeInnerProvider(RoleProvider innerProvider, string name, NameValueCollection config)
        {
            NameValueCollection cfg = config;
            if (innerProvider is ReadOnlyRoleProvider<WindowsTokenRoleProvider>)
            {
                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 :
  • GetUsersInRole
  • GetAllRoles
  • FindUsersInRole

Tout simplement parce que le WindowsTokenRoleProvider ne supporte pas ces opérations.
Ok, voici le tout dernier % à réaliser :

public class SilentWindowsTokenRoleProvider<TSurrogateRoleProvider> : ReadOnlyRoleProvider<TSurrogateRoleProvider>
        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.

Entity Framework et AOP

Publié par Fabrice Michellonet sous le(s) label(s) , , , le 7 octobre 2011

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 :
public interface IAuditable
    {
        string CreatedBy { get; set; }
        DateTime Created { get; set; }
        string UpdatedBy { get; set; }
        DateTime? Updated { get; set; }
    }

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é
Install-Package EntityFramework
Install-Package Afterthought

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<T> : Amendment<T, T>
    {

        public AuditableAmender()
        {
            Properties.Add<string>("CreatedBy");
            Properties.Add<DateTime>("Created");
            Properties.Add<string>("UpdatedBy");
            Properties.Add<DateTime?>("Updated");

            Implement<IAuditable>();
        }
    }

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<ITypeAmendment> IAmendmentAttribute.GetAmendments(Type target)
        {
            if (target.GetCustomAttributes(typeof(AuditableAttribute), true).Length > 0)
            {
                ConstructorInfo constructorInfo = typeof(AuditableAmender<>).MakeGenericType(target).GetConstructor(Type.EmptyTypes);
                if (constructorInfo != null)
                    yield return (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<Product> Products { get; set; }

        public Context()
        {
            Database.SetInitializer(new DropCreateDatabaseIfModelChanges<Context>());
        }
    }

Ce qui produit bien en base la table suivante :

table

et dans l'assembly finale (vu avec Reflector)

assembly

É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?

Live charting avec SignalR

Publié par Fabrice Michellonet sous le(s) label(s) , , , le 26 septembre 2011

SignalR is awesome ! Oui il va falloir vous habituer à entendre dire que cette librairie est un petit bijou.


tweet signalr

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<SharePrice>(new[]
                                         {
                                             new SharePrice
                                                 {
                                                     Date = DateTime.Now,
                                                     Price = VaryPrice(),
                                                     Share = new Share {Name = "Fowler-Corp"}
                                                 }
                                         }));
            _timer.Start();

            return true;
        }

        public void Send(IEnumerable<SharePrice> 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...

chart

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?

EntityFramework.Patterns : Repository & Unit Of Work

Publié par Fabrice Michellonet sous le(s) label(s) , le 13 juillet 2011

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.

Pour la version longue vous pourrez trouver votre bonheur sur le site de Martin Fowler ici pour le Repository et ici pour Unit Of Work

Partons d'un modèle simplissime comme celui ci-dessous :


public class Context : DbContext
{
    public virtual DbSet<Product> Products { get; set; }
    public virtual DbSet<ProductCategory> Categories { get; set; }

    public Context()
    {
        Database.SetInitializer(new DropCreateDatabaseAlways<Context>());
    }
}

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<Product> productRepo = new Repository<Product>(adp);
    ...
}

Interrogeons la base pour ramener l'ensemble des produits :

IEnumerable<Product> lst = productRepo.GetAll();

Ramenons le premier élément dont le nom commence par "Bike"

Product prod = productRepo.First(p => p.Name.StartsWith("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<Product> lst = productRepo.Find(
                p => p.Id < 100 && p.Name.Contains("o") && p.Name.Length < 20);

Toutes ces méthodes acceptent optionnellement un ensemble de liens de navigation inter entités

params Expression<Func<T, object>>[] 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 :

Product p = productRepo.First(c => c.Name == "Bike");
p.Name = "New bike";
productRepo.Update(p);

Product delete = productRepo.First(p => p.Name.StartsWith("To be")); 
productRepo.Delete(delete);

unitOfWork.Commit();

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.

Release d'EntityFramework.Patterns

Publié par Fabrice Michellonet sous le(s) label(s) le 7 juillet 2011

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 :

  • Repository
  • Unit Of work
Rob Conery les définissaient ainsi récemment :
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<T> et UnitOfWork.

FEZ Domino

Publié par Fabrice Michellonet sous le(s) label(s) , le 16:51

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 :

  1. Micro-contrôleur Fez Domino
    • Processeur 72Mhz 32-bit ARM7 LPC2388
    • ~148KB free Flash
    • ~62KB free RAM

    fez domino

  2. Un servo controlleur SSC-32
    • Peut gérer jusqu'à 32 servo moteurs.

    SSC-32

C'est vraiment petit ces trucs la, pour vous donner une idée, les voici à côté d'une pile 9V.

Domino & SSC-32

Mon premier défit va être de tenter le portage d'un conteneur DI sur cette plateforme. Je sens que ça va être chaud!

Ensuite, je crois que je vais tenter la construction d'un Hexapode... d’où l'achat du SSC-32.

Custom ASP.NET MVC Project Template

Publié par Fabrice Michellonet sous le(s) label(s) , , le 21 juin 2011

Récemment, Phil Haack nous présentait dans un très bon post comment ajouter un template MVC3 personnalisé.

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 :
    <WizardData>
        <packages>
            <package id="jQuery" version="1.5.1" />
            <package id="jQuery.Validation" version="1.8.0" />
            <package id="jQuery.UI.Combined" version="1.8.11" />
        </packages>
    </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.

Happy Nuget!

Quickstart & Nuget

Publié par Fabrice Michellonet sous le(s) label(s) , , le 17 mai 2011

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é.

DirectoryInfo packageDirInfo = _solutionDirInfo.CreateSubdirectory("packages");
string _nugetFilePath = Path.Combine(packageDirInfo.FullName, "Nuget.exe");
File.WriteAllBytes(_nugetFilePath, Resources.NuGet);

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.

Project Template, Quickstart et VSIX

Publié par Fabrice Michellonet sous le(s) label(s) le 13 mai 2011

Dernièrement j’ai fait mumuse avec les template de fichiers et projets que l’on peut créer dans Visual Studio.

coccinelle

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 NLog et votre conteneur DI pré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 :


  1. 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.

  2. Pour créer un quickstart, qui n’est autre qu’un template de solution (plusieurs projets), je vous conseille de :

    1. Exporter vos différents projets template et extraire les archives zip correspondante dans un même dossier.

    2. Ajouter dans ce dossier de travail un fichier .vstemplate reprenant cette structure

    3. <VSTemplate Version="2.0.0" xmlns="http://schemas.microsoft.com/developer/vstemplate/2005" Type="ProjectGroup">
        <TemplateData>
          <Name>Ma solution</Name>
          <Description>Ma description</Description>
          <ProjectType>CSharp</ProjectType>
          <ProjectSubType></ProjectSubType>
          <SortOrder>1000</SortOrder>
          <CreateNewFolder>false</CreateNewFolder>
          <LocationField>Enabled</LocationField>
          <EnableLocationBrowseButton>true</EnableLocationBrowseButton>
          <Icon>monicone_100x100.ico</Icon>
        </TemplateData>
        <TemplateContent>
          <ProjectCollection>
            <ProjectTemplateLink ProjectName="Web">
              Web\MyTemplate.vstemplate
            </ProjectTemplateLink>
            <ProjectTemplateLink ProjectName="Domain">
              Domain\MyTemplate.vstemplate
            </ProjectTemplateLink>
            <ProjectTemplateLink ProjectName="DAL">
              DAL\MyTemplate.vstemplate
            </ProjectTemplateLink>
            <ProjectTemplateLink ProjectName="IServices">
              IServices\MyTemplate.vstemplate
            </ProjectTemplateLink>
            <ProjectTemplateLink ProjectName="Services">
              Services\MyTemplate.vstemplate
            </ProjectTemplateLink>
          </ProjectCollection>
        </TemplateContent>
      </VSTemplate>
      

      Vous pouvez y mettre autant de référence a des projects que vous le souhaitez en ajoutant des balises ProjectTemplateLink.

    4. 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.

  3. Dans le fichier vstemplate de votre Quickstart, la première occurrence à un projet sera par convention le startup project.

  4. Pour étendre avec du code custom vos project template il vous faudra :

    1. Créer une assembly et implémenter l’interface Microsoft.VisualStudio.TemplateWizard.IWizard

    2. Ajouter la balise WizardExtension dans le fichier .vstemplate de votre project template

    3. <VSTemplate Version="3.0.0" xmlns="http://schemas.microsoft.com/developer/vstemplate/2005" Type="Project">
        …
        <WizardExtension>
          <Assembly>MyAssembly, Version=1.0.0.0, Culture=neutral, PublicKeyToken=952e5fc020f3b126</Assembly>
          <FullClassName>MyAssembly.Wizard</FullClassName>
        </WizardExtension>
      </VSTemplate>
      
  5. 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.

  6. 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é.

  7. Dans le designer de VSIX, le champ ID comporte un GUID… n’y touchez surtout pas !

  8. 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.

  9. subfolder

Quickstart Telerik Extensions for ASP.NET MVC via Nuget

Publié par Fabrice Michellonet sous le(s) label(s) , le 31 mars 2011

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.

nuget  telerik mvc extensions

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 :


<configuration>
  <configSections>
    <sectionGroup name="telerik">
      <section name="webAssets" type="Telerik.Web.Mvc.Configuration.WebAssetConfigurationSection, Telerik.Web.Mvc" requirePermission="false" />
    </sectionGroup>

    <sectionGroup name="system.web.webPages.razor" type="System.Web.WebPages.Razor.Configuration.RazorWebSectionGroup, System.Web.WebPages.Razor, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35">
      <section name="host" type="System.Web.WebPages.Razor.Configuration.HostSection, System.Web.WebPages.Razor, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" requirePermission="false" />
      <section name="pages" type="System.Web.WebPages.Razor.Configuration.RazorPagesSection, System.Web.WebPages.Razor, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" requirePermission="false" />
    </sectionGroup>
  </configSections>

  <system.web>
    <pages>
      <namespaces>
        <add namespace="Telerik.Web.Mvc.UI" />
      </namespaces>
    </pages>

    <httpHandlers>
      <add verb="GET,HEAD" path="asset.axd" validate="false" type="Telerik.Web.Mvc.WebAssetHttpHandler, Telerik.Web.Mvc" />
    </httpHandlers>
  </system.web>

  <system.webServer>

    <handlers>
      <remove name="asset" />
      <add name="asset" preCondition="integratedMode" verb="GET,HEAD" path="asset.axd" type="Telerik.Web.Mvc.WebAssetHttpHandler, Telerik.Web.Mvc" />
    </handlers>

  </system.webServer>

  <telerik>
    <webAssets useTelerikContentDeliveryNetwork="false" />
  </telerik>

  <system.web.webPages.razor>
    <host factoryType="System.Web.Mvc.MvcWebRazorHostFactory, System.Web.Mvc, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
    <pages pageBaseType="System.Web.Mvc.WebViewPage">
      <namespaces>
        <add namespace="System.Web.Mvc" />
        <add namespace="System.Web.Mvc.Ajax" />
        <add namespace="System.Web.Mvc.Html" />
        <add namespace="System.Web.Routing" />
        <add namespace="Telerik.Web.Mvc.UI" />
      </namespaces>
    </pages>
  </system.web.webPages.razor>

</configuration>

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.

<head>
    ...
    @(Html.Telerik().StyleSheetRegistrar().DefaultGroup(group => group.Add("telerik.common.css").Add("telerik.windows7.css").Add("telerik.rtl.css").Combined(true).Compress(true)))
</head>

</body>
@(Html.Telerik().ScriptRegistrar().Globalization(true).DefaultGroup(group => group.Combined(true).Compress(true)))
</html>

Ok, ok m'sieur Michellonet c'est bien beau tout ça, mais pourquoi j'irais utiliser la librairie de Telerik... en deux mots!

Tout simplement parce que la grille est géniale, elle a toutes les fonctionnalités que l'on peut attendre d'une grille Web 3.0 :p

Mercurial : Régler les problèmes de certificats SSL.

Publié par Fabrice Michellonet sous le(s) label(s) le 22 mars 2011

Ce n'est pas dans mon habitude de poster des conseils sur des outils et leur configuration, mais cette fois je vais faire une petite entorse au règlement; tout simplement car j'ai pas mal galéré pour résoudre ce soucis, et si ce post permet d'aider une seule personne alors cela en aura valu le coup.

Bref, depuis la version 1.7 le niveau de sécurité de Mercurial à été revu à la hausse, et donc évidement des petits tracas pour nous utilisateurs de Tortoise HG & Visual HG sous Windows. En résumé, les connexions HTTPS utilisant des certificats auto signés (Self-signed certificates) ne sont plus acceptés par le controleur de code source.

La documentation stipule qu'il est possible de rajouter dans le fichier cacert.pem de Tortoise le certificat que vous souhaitez autoriser; Le problème est qu'il va vous falloir utiliser openssl, non présent en standard sur un Windows. Je vous avouerais que je n'ai même pas tenté de le télécharger et de l'utiliser; Alors peut-être est-ce facilement faisable sous Windows... personnellement j'avais peur de me lancer dans une galère de plus.

Par contre, laissez-moi vous montrer une façon de faire beaucoup plus simple, pour nous Windowsien de base et tout aussi sécurisée.

L'idée est d'ajouter l'empreinte numérique du serveur hébergeant le repository dans la configuration de Tortoise; la doc étant un peu légère sur ce point, voici les étapes pour y arriver.

Bon, tout d'abord ouvrez votre navigateur préféré et rendez-vous sur la home page de votre repository. En cliquant sur le petit cadenas en bas de votre fenêtre vous devriez avoir une fenêtre comme celle-ci qui s'ouvre :

Certificat

Le bouton "afficher certifat" ouvre une nouvelle fenêtre dans laquelle vous copierez l'empreinte numérique SHA1. Cette empreinte identifie de manière unique le serveur hebergeant votre repository Mercurial.

Ensuite, ouvrez Tortoise hg Workbench, puis dans la fenêtre de configuration, éditez le fichier de configuration mercurial.ini.

Config

Il ne reste plus qu'à ajouter une section hostfingerprints reprenant l'adresse de votre server ainsi que son empreinte.

[hostfingerprints]
mercurial.devolis.com = ..:..:..:..:02:76:B5:29:65:47:A1:43:8E:0F:F5:13:03:AC:9D:0A

Voilà enregistrez le fichier et tout devrait désormais fonctionner comme avant.

Razor plugin pour SyntaxHighlighter

Publié par Fabrice Michellonet sous le(s) label(s) , le 28 février 2011

Comme beaucoup d'entre vous j'utilise l'excellent SyntaxHighlighter d'Alex Gorbatchev, pour mettre en forme les snippets de code sur mon blog.

Bien qu'il dispose de nombreux plugin de prise en charge de language, parfois très ésotérique, je n'ai encore rien vu pour le language préféré de tous ceux qui font du MVC 3 ou du WebMatrix, j'ai nommé razor.

razor

Du coup, je me suis lancé et voici une toute première version téléchargeable ici

et voici un exemple de mise en forme

@model CustomMVCScaffolder.Models.Employee

@* This partial view defines form fields that will appear when creating and editing entities *@
@Html.LabelFor(model => model.NationalIdNumber)
@Html.EditorFor(model => model.NationalIdNumber) @Html.ValidationMessageFor(model => model.NationalIdNumber)
@foreach (var ourItem in ourList) { if (ourItem != ourList.First()) { outItem.Name } } @Html.ActionLink("Edit", "Edit", new { id=item.UserName }) | @Html.ActionLink("Details", "Details", new { id=item.UserName }) | @Html.ActionLink("Delete", "Delete", new { id=item.UserName }) @Html.Hidden("UserName", Model.UserName)

Voici comment l'utiliser :

  <pre style="brush:razor">
    @Html.Hidden("UserName", Model.UserName)
  </pre>

J'espère que cette petite contribution à la communauté viendra enjoliver les snippets de code que l'on voit ça et là.

Ho... j'oubliais, si vous avez des propositions pour améliorer la mise en forme, je suis preneur.

Transformations web.config d'une Web application.

Publié par Fabrice Michellonet sous le(s) label(s) , le 22 février 2011

On a tous connu ce cas de figure, ou l'on doit déployer une application web sur une plateforme autre que celle sur laquelle on développe (serveur de dev, recette, prod etc...);
A minima on se retrouve à modifier les chaines de connexions vers la/les bases de données, au mieux on a prévu une copie tweakée du web.config par plateforme (avec le gros inconvénient d'avoir maintenant plusieurs fichiers à maintenir en parallèle), on fait ça dans l'urgence et hop ça plante :p.
Surement un petit bout de configuration non que l'on a oublié de reporté dans ce fichier de conf spécifique à l'environnement visé. Ça sent le vécu, tout ça !

Réjouissez-vous ce temps est révolu!

Dans Visual Studio 2010, il possible de mettre en œuvre des transformations sur les fichiers de configuration. L'idée est d'avoir un fichier web.config servant de base, puis pour chaque Configuration de Solution (Debug, Release, etc..) un fichier de transformation contient les modifications apportés vis à vis du fichier de base.

Si l'on ajoute deux Configurations de solutions, par exemple Recette et Prod, puis que l'on clique sur Add Config Transform deux nouveaux fichiers apparaissent Web.Recette.config & Web.Prod.config

L'idée de ce billet n'est pas d'expliquer en détail la syntaxe de transformation bien expliquée sur MSDN; mais afin de poursuivre l'exemple voici mon web.config initial


    

et la transformation pour l'environnement de recette :




Bon maintenant la mauvaise nouvelle (haaa ? j'vous avais pas dit qu'il y avait une mauvaise nouvelle?), c'est que la transformation du fichier de config ne se fait pas lors du build.
Malheureusement, elle n’a lieu que lorsqu'on utilise webDeploy.
La bonne nouvelle? C’est qu'en trafiquant un peu le csproj on peut lancer la tache MSBuild en charge de la transformation.

Allez tout en bas du fichier et modifiez-le comme suit :


    


A partir de maintenant après chaque build le fichier web.config sera transformé et copié dans le répertoire obj/Nom_De_La_Config/web.config

Voilà, c'est tout pour ce soir, j’espère que cette astuce vous facilitera la vie lors de vos futurs déploiements.

MVC3 - Utiliser facilement le Datepicker de JQuery.

Publié par Fabrice Michellonet sous le(s) label(s) le 18 février 2011

Un des points apportant le plus de productivité dans le framework ASP.NET MVC est surement l'avènement des Display & Editor Templates.

Je m'explique si vous suivez les best practices et que vos vues ressemblent plus ou moins à cela :

@model CustomMVCScaffolder.Models.Employee

@* This partial view defines form fields that will appear when creating and editing entities *@
@Html.LabelFor(model => model.NationalIdNumber)
@Html.EditorFor(model => model.NationalIdNumber) @Html.ValidationMessageFor(model => model.NationalIdNumber)
@Html.LabelFor(model => model.Login)
@Html.EditorFor(model => model.Login) @Html.ValidationMessageFor(model => model.Login)
@Html.LabelFor(model => model.JobTitle)
@Html.EditorFor(model => model.JobTitle) @Html.ValidationMessageFor(model => model.JobTitle)
@Html.LabelFor(model => model.BirthDate)
@Html.EditorFor(model => model.BirthDate) @Html.ValidationMessageFor(model => model.BirthDate)
@Html.LabelFor(model => model.Maried)
@Html.EditorFor(model => model.Maried) @Html.ValidationMessageFor(model => model.Maried)
@Html.LabelFor(model => model.Gender)
@Html.EditorFor(model => model.Gender) @Html.ValidationMessageFor(model => model.Gender)
@Html.LabelFor(model => model.HireDate)
@Html.EditorFor(model => model.HireDate) @Html.ValidationMessageFor(model => model.HireDate)

alors au runtime, le framework choisira le composant graphique HTML (Input, select etc...) le plus adapté à afficher les propriétés du model et ca sur toutes vos vues.

La bonne nouvelle, c'est que l'on peut créer nous même des templates pour les nouveaux types et encore mieux modifier les templates liés aux types de base (int, string, DateTime etc..)

Stop le blabla, place au Datepicker.

Ok, on commence par créer dossier un EditorTemplates sous Views/Shared et on y place une nouvelle vue que l'on nommera DateTime.cshtml.

adding an editor

@using MVCTest
@model DateTime?

@Html.Datepicker("", Model.HasValue ? Model.Value.ToShortDateString() : string.Empty, 0)

Remarquez la simplicité, on ne fait qu'appeler une méthode d'extension (que nous allons étudier de suite) et on formatte très légèrement la date (ToShortDateString).

Rajoutons une classe dans notre projet comportant le code suivant :

using System.Collections.Generic;
using System.Web.Mvc;

namespace MVCTest
{
    public static class JQueryHtmlHelpers
    {
        public static MvcHtmlString Datepicker(this HtmlHelper htmlHelper, string name, object value)
        {
            return System.Web.Mvc.Html.InputExtensions.TextBox(htmlHelper, name, value, new Dictionary { { "class", "datepicker" } });
        }
    }
}

Dans cette méthode d'extension, on se contente de demander le rendu d'une TextBox (input html) a laquelle on applique la class css datepicker.
Vous l'aurez compris il ne nous reste plus qu'a écrire un peu de JQuery dans la master page pour que toute les texbox du site ayant la class datepicker se transforment par magie en DatePicker.

$(document).ready(function () {
    $(".datepicker").datepicker();
});

Voila ce que ça donne :

Avant Apres

Je vous laisse imaginer tous les widgets que l'on peut intégrer facilement!

Analysis Services Report Pack

Publié par Fabrice Michellonet sous le(s) label(s) , le 20 janvier 2011

Avant-hier, mon client m'a demandé s'il était possible d'avoir une "cartographie" de sa base OLAP que nous sommes en train de construire.

Par cartographie, il entendait a minima avoir un listing de toutes les mesures, dimensions et niveaux existants. Ma première réaction fut de vérifier si BIDS Helper n'avait pas ce genre de feature cachée dans un coin. Circulez rien à voir de très intéressant de ce côté-là!

Je me suis ensuite rué sur Google pour voir s'il n'y avait pas un SQL Server Report Pack comme celui qui était apparu pour SQL Server 2005, mais axé Analysis Services. Rien non plus en direct de chez Redmond.
Par contre en creusant un peu je suis tombé sur l'excellent blog de la société Capstone et leur superbe Analysis Services 2008 Metadata Report Pack.

Voici un aperçu du rendu des rapports Reporting Services mis à disposition :

Database Cube Dimension

Et pour faire plaisir à mon ami François, il y a même des infos sur le Data Mining :

Dimension

Pour installer le pack il vous faudra créer un serveur lié dans SQL Server; Voici le script SQL a mettre en adéquation avec la configuration de votre machine :

EXEC master.dbo.sp_addlinkedserver @server = N'SSAS_METADATA', @srvproduct=N'SSAS', @provider=N'MSOLAP', @datasrc=N'localhost', @provstr=N'Provider=MSOLAP.4', @catalog=N'Adventure Works DW 2008R2'
EXEC master.dbo.sp_addlinkedsrvlogin @rmtsrvname=N'SSAS_METADATA',@useself=N'False',@locallogin=NULL,@rmtuser=NULL,@rmtpassword=NULL

Puis déployer les rapports sur Reporting Services.

Je me permets de reposter l'ensemble de la solution de Dan Meyers ici; version dans laquelle j'ai corrigé les connexions en dur par une connexion vers le serveur lié.

En dehors de répondre au besoin ponctuel de mon client, je me dis que ces rapports pourraient très bien trouver leur place dans une documentation technique à réaliser en fin de projet.

Injection HTML à base d'HTTPModule

Publié par Fabrice Michellonet sous le(s) label(s) , le 18 janvier 2011

Dernièrement j'ai échangé avec mon Boss, Jérôme, sur les possibilités d'enrichir un site web non dévellopé en .NET mais qui serait hosté dans IIS.
On pourrait se mettre à apprendre le Python... mais voila on est un peu fainéant :)

Une des solutions que je lui ai proposé étais de tirer parti du mécanisme d'extensibilité du pipeline de IIS7 afin d'intervenir directement sur le code HTML renvoyé au navigateur.

Green Injection

Je me propose donc de présenter dans les grandes lignes comment mettre en place ce genre de solution.
Pour illustrer mes propos simplement nous allons nous rajouter un div en fin de page, le div contenant l'heure du système.

On commence par créer notre HTTPModule et on répond à l'évenement BeginRequest :

using System;
using System.IO;
using System.Web;

namespace InjectorModule
{
    public class Injector : IHttpModule
    {
        private HttpApplication _application;

        public void Init(HttpApplication context)
        {
            context.BeginRequest += OnBeginRequest;
            _application = context;
        }

        private void OnBeginRequest(object sender, EventArgs e)
        {
            Stream filter = FilterFactory.GetFilter(_application);
            if (filter == null)
                return;

            _application.Response.Filter = filter;
        }

        public void Dispose() {}
    }
}

On délègue à une factory le role de créer ou non un filter en fonction de règles qui ne sont pas connues par le Module.
Le module reste ainsi une brique uniquement technique technique.

Passons à la factory

using System.IO;
using System.Web;

namespace InjectorModule
{
    internal static class FilterFactory
    {
        public static Stream GetFilter(HttpApplication application)
        {
            if (application == null || application.Response.ContentType == null)
                return null;

            if (application.Response.ContentType.ToUpperInvariant().Contains("HTML"))
                return new AppendDateTimeFilter(application.Response.Filter, application.Request.ContentEncoding);

            return null;
        }
    }
}

Ici pas de règles métiers compliquées, on ajoute un Filter si le content type est de type HTML.

Et finalement voici l'implémentation du Filter qui tranforme la réponse envoyée aux navigateurs :

using System;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;

namespace InjectorModule
{
    internal class AppendDateTimeFilter : Stream
    {

        private readonly Stream _inputStream;
        private readonly Encoding _encoding;
        private readonly StringBuilder _responseHtml;

        public AppendDateTimeFilter(Stream input, Encoding contentEncoding)
        {
            _inputStream = input;
            _encoding = contentEncoding;
            _responseHtml = new StringBuilder();
        }

        #region Filter overrides

        public override bool CanRead
        {
            get { return true; }
        }

        public override bool CanSeek
        {
            get { return true; }
        }

        public override bool CanWrite
        {
            get { return true; }
        }

        public override void Close()
        {
            _inputStream.Close();
        }

        public override void Flush()
        {
            _inputStream.Flush();
        }

        public override long Length
        {
            get { return 0; }
        }

        public override long Position { get; set; }

        public override long Seek(long offset, SeekOrigin origin)
        {
            return _inputStream.Seek(offset, origin);
        }

        public override void SetLength(long length)
        {
            _inputStream.SetLength(length);
        }

        public override int Read(byte[] buffer, int offset, int count)
        {
            return _inputStream.Read(buffer, offset, count);
        }
        #endregion

        public override void Write(byte[] buffer, int offset, int count)
        {
            try
            {
                string bufferContent = _encoding.GetString(buffer);

                // Wait for the closing tag
                Regex eof = new Regex("", RegexOptions.IgnoreCase);

                _responseHtml.Append(bufferContent);
                if (!eof.IsMatch(bufferContent))
                    return;

                // Transform the response and write it back out
                string finalHtml = _responseHtml
                                        .Replace("", string.Format(@"
{0}
", DateTime.Now)) .ToString(); // Send. byte[] data = _encoding.GetBytes(finalHtml); _inputStream.Write(data, 0, data.Length); } catch (Exception) { } } } }

Pour tester rien de plus simple, on compile l'assembly, on la place dans le dossier bin du site web que l'on veux trafiquer.
Dans notre cas, nous avons aussi du créer le dossier bin car non existant dans l'appli Web Python.
Le fait de placer l'assembly dans un dossier bin, est une contrainte technique imposée par IIS; je n'ai pas trouvé d'alternatives et a priorit il n'y a pas de configuration qui pourrait influer sur cette contrainte.
Finalement dans le gestionnaire IIS, rendez vous dans la section Modules de votre application Web.
En ouvrant la fenetre d'ajout de modules, votre assembly sera désormais présente; Sélectionnez la, et pointez votre navigateur sur ce dernier.

Tadam!!! l'heure s'affiche tout en bas :)

J'esperes que cette solutions vous ouvre de nouvelles possibilités.

Un dernier mot pour vous conseiller l'excellente librairie htmlagilitypack qui vous donnera les moyens de parser facilement un document HTML, même malformé.