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!