Home - About me - Browse by categories

[ASP.NET MVC] Bonnes pratiques : création d’un Bootstrapper ou comment isoler l’initialisation d’une application

Dans ce post, je souhaite revenir sur une bonne pratique que Rui et moi-même avons évoqué lors de notre session sur Rui le mois dernier : l’isolation de l’initialisation d’une application ASP.NET MVC.

En effet, le template par défaut de création de projet ASP.NET MVC de Visual Studio concentre tout le code dans un seul et même projet ce qui peut poser des problèmes de maintenabilité et/ou d’organisation, notamment sur un projet conséquent.

L’idée est de créer un Bootstrapper, c’est à dire une entité qui soit capable d’initialiser l’application, charger les différentes dépendances via un conteneur IoC (Unity, dans cet exemple), enregistrer les différentes routes, areas (etc…) qui composent l’application.

Voilà l’architecture globale de ma solution d’exemple (sources disponibles en bas de page!) :

image

  • Sample.Bootstrapper est une bibliothèque de classe qui référence : System.Web, System.Web.Mvc et System.Web.Routing et Microsoft.Practices.Unity, afin d’être en capacité d’initialiser l’application
  • Sample.Services est une bibliothèque de classes qui défini un service tout simple, représenté par une interface et une implémentation concrète.
  • Sample.Web est l’application ASP.NET MVC

Vous l’avez surement compris, l’essentiel du travail va se concentrer dans le projet Sample.Bootstrapper.

En premier lieu, le UnityBootstrapper : il s’agit d’une classe qui expose un conteneur Unity au reste de l’application, c’est celui-ci qui est utilisé pour la résolution des dépendances au runtime :

public class UnityBootstrapper
{
private UnityBootstrapper()
{

}

private static void InitializeContainer(IUnityContainer container)
{
container.RegisterType<ISampleService, LoremIpsumSampleService>();
}

private static readonly object _syncRoot = new object();
private static IUnityContainer _container;

public static IUnityContainer Container
{
get
{
if (_container == null)
{
lock (_syncRoot)
{
if (_container == null)
{
_container = new UnityContainer();
InitializeContainer(_container);
}
}
}

return _container;
}
}
}

On expose ici le conteneur Unity racine de l’application sous la forme d’un Singleton. Les registers sont fait dans le code ou dans le fichier Web.config, selon le besoin.

Afin de pouvoir utiliser l’injection de dépendance dans l’application, il faut configurer le moteur MVC pour utiliser ce conteneur Unity lors de la résolution des contrôleur. Pour cela, on passe par une implémentation de l’interface IControllerActivator, ici la classe UnityControllerActivator :

public class UnityControllerActivator : IControllerActivator
{
private readonly IUnityContainer _container;
public UnityControllerActivator(IUnityContainer unityContainer)
{
_container = unityContainer;
}

public IController Create(System.Web.Routing.RequestContext requestContext, Type controllerType)
{
return _container.Resolve(controllerType) as IController;
}
}

Il ne reste plus qu’à définir la bonne controller factory afin que le UnityControllerActivator soit utilisé.

Avant cela, j’attire votre attention sur la dernière classe qui se trouve dans le projet Sample.Bootstrapper : MvcApplication. Il s’agit en réalité de la classe d’application (qui dérive de System.Web.HttpApplication) et qui se trouve par défaut liée au fichier Global.asax, lors de la création du projet. Le fait de déporter cette classe dans un projet externe permet de bien isoler le chargement de l’application et d’y définir les différentes routes, areas ou comportement du Framework, par exemple, demander au ControllerBuilder d’aller utiliser notre propre IControllerActivator :

public class MvcApplication : HttpApplication
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new HandleErrorAttribute());
}

public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

routes.MapRoute(
"Default",
"{controller}/{action}",
new { controller = "Sample", action = "Index" }
);

}

protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();

RegisterGlobalFilters(GlobalFilters.Filters);
RegisterRoutes(RouteTable.Routes);

var controllerFactory = new DefaultControllerFactory(
new UnityControllerActivator(UnityBootstrapper.Container));
ControllerBuilder.Current.SetControllerFactory(controllerFactory);
}
}

Il ne reste plus qu’à lier cette classe au fichier Global.asax afin qu’elle soit prise en compte au démarrage de l’application. Pour cela, il suffit d’ouvrir le fichier à l’aide de l’éditeur XML de Visual Studio, et de modifier la définition de la classe d’application :

<%@ Application Codebehind="Global.asax.cs" Inherits="Sample.Bootstrapper.MvcApplication" Language="C#" %>

Et voilà, le tour est joué! Les sources sont disponibles ici : http://juliencorioland.blob.core.windows.net/publicfiles/Sample-Bootstrapper.zip

A bientôt image

Julien

read more

[Techdays 2012] Webcasts en ligne, slides et codes source !

Vous n’êtes certainement pas passé à côté de cette information : les webcasts de l’édition 2012 des Microsoft Techdays sont en ligne ! Il est possible de les visionner à cette adresse : http://www.microsoft.com/france/mstechdays/programmes/parcours.aspx.

J’en profite pour vous donner les pointeurs vers les webcasts, slides et codes source des sessions que j’ai eu le plaisir d’animer cette année :

Architecture, bonnes pratiques et recettes pour la réussite de vos projets ASP.NET MVC

La dure lutte du développeur : 10 trucs pratiques pour une application mobile bien léchée

Enseigner les technologies Microsoft, un exemple avec Windows Phone

Bon visionnage !

Julien

read more

Azure SDK on Windows 8 Consumer preview : there was an error attaching the debugger…

When I was trying to debug a Windows Azure application in Visual Studio 2010 on Windows 8 Consumer Preview, I was getting the following error :

image

If you encounter this error, open the Control Panel, go to the Programs section and click the Turn Windows features on or off link. In the Windows features, check that the Internet Information Service Hostable Web Core feature is installed on your machine. If not, install it :

image

It solves my problem and now I can debug Windows Azure applications without any error ! It seems that the Web PI 4.0 preview does not detect this dependency while installing the Windows Azure SDK…

Hope this helps image !

Julien

read more

[Azure] Communication entre instances avec les endpoints internes

Il y a quelques temps j’ai posté deux billets décrivant des techniques pour réaliser de l’invalidation de cache sur plusieurs instances d’un rôle Windows Azure :

Dans ce billet, je souhaiterai vous présenter une autre manière de communiquer entres différentes instances d’un même rôle, en utilisant les Endpoints internes.

Les endpoints internes sont des points d’accès sur lesquels chaque instance est en capacité, par exemple, d’hoster un service WCF. Il existe plusieurs avantages dans l’utilisation de ceux-ci :

  • Ils sont internes, donc inaccessibles depuis l’extérieur
  • Il est possible de communiquer via un binding en net.tcp
  • Leur utilisation n’entraîne pas de surcoût sur votre facture Windows Azure

Création d’un endpoint interne

Pour ajouter un endpoint interne à un rôle Windows Azure, rendez-vous dans les propriétés de celui-ci dans votre projet Visual Studio, puis accédez à l’onglet Endpoints :

image

Il suffit de cliquer sur le bouton Add Endpoint, de le nommer, de mettre son type à Internal, de choisir le protocole et  enfin de définir le port privé sur lequel le endpoint sera accessible :

image

Toutes les instances du rôles exposeront alors ce endpoint automatiquement !

###

Héberger un service WCF sur un endpoint interne

Il est possible de faire en sorte qu’un service WCF soit hébergé au démarrage de chaque instance. Celui-ci pourra être appelé par n’importe quelle autre instance du rôle. Par exemple, dans le cas d’un scénario d’invalidation de cache, une instance de rôle doit être capable de notifier toutes les autres de réinitialiser leur cache local. Pour cela, il suffit de créer un canal de communication via WCF entre les instances.

Dans le cas présent, le contrat de service WCF est très simple :

[ServiceContract]
public interface ICacheInvalidationService
{
[OperationContract]
void InvalideCache();
}

Son implémentation aussi :

public class CacheInvalidationService : ICacheInvalidationService
{
public void InvalideCache()
{
//invalidation du cache local
}
}

Le démarrage du service WCF se fait dans la méthode OnStart du WebRole. En premier lieu, il faut récupérer l’adresse du endpoint interne qui a été créé :

if (RoleEnvironment.CurrentRoleInstance.InstanceEndpoints
.Any(ip => ip.Key == "CacheInvalidationEndpoint"))
{
IPEndPoint endpoint = RoleEnvironment.CurrentRoleInstance.InstanceEndpoints
.First(ip => ip.Key == "CacheInvalidationEndpoint").Value.IPEndpoint;

}

Ensuite, l’hébergement du service WCF se fait très simplement à l’aide d’un ServiceHost :

public class WebRole : RoleEntryPoint
{
private ServiceHost _serviceHost;

public override bool OnStart()
{
if (RoleEnvironment.CurrentRoleInstance.InstanceEndpoints
.Any(ip => ip.Key == "CacheInvalidationEndpoint"))
{
IPEndPoint endpoint = RoleEnvironment.CurrentRoleInstance.InstanceEndpoints
.First(ip => ip.Key == "CacheInvalidationEndpoint").Value.IPEndpoint;

Uri baseAddress = new Uri(string.Format("net.tcp://{0}", endpoint));
_serviceHost = new ServiceHost(typeof(CacheInvalidationService), baseAddress);

NetTcpBinding binding = new NetTcpBinding(SecurityMode.None);
_serviceHost.AddServiceEndpoint(typeof(ICacheInvalidationService), binding, "CacheInvalidation");

try
{
_serviceHost.Open();
}
catch
{
Trace.WriteLine("Impossible de démarrer le service d'invalidation de cache");
}
}

return base.OnStart();
}
}

Il est également possible de surcharger la méthode OnStop du rôle pour arrêter le service WCF proprement :

public override void OnStop()
{
if (_serviceHost != null)
{
try
{
_serviceHost.Close();
}
catch
{
_serviceHost.Abort();
}
}
base.OnStop();
}

A présent, toute instance de ce rôle expose un service WCF permettant l’invalidation de son cache local !

Contacter toutes les instances

La dernière étape consiste à écrire le code qui permet d’appeler le service d’invalidation de cache sur toutes les instances du rôle.

Pour cela, il est nécessaire de récupérer tous les IPEndPoint qui portent le nom CacheInvalidationEndpoint :

var ipEndpoints =
from instance in RoleEnvironment.CurrentRoleInstance.Role.Instances
from endpoint in instance.InstanceEndpoints
where endpoint.Key == "CacheInvalidationEndpoint"
select endpoint.Value.IPEndpoint;

Ensuite, il suffit de parcourir les endpoints pour créer un canal de communication vers chacun d’eux, à l’aide d’une ChannelFactory WCF :

foreach (var ipEndPoint in ipEndpoints)
{
try
{
NetTcpBinding binding = new NetTcpBinding(SecurityMode.None);

ChannelFactory<ICacheInvalidationService> channelFactory =
new ChannelFactory<ICacheInvalidationService>(binding);

string uri = string.Format("net.tcp://{0}/CacheInvalidation", ipEndPoint);
EndpointAddress address = new EndpointAddress(uri);

ICacheInvalidationService service = channelFactory.CreateChannel(address);
if (service != null)
{
service.InvalideCache();
}
}
catch
{
//log exception
}
}

Et voilà, toutes les instances ont maintenant été notifiée de vider leur cache local !

Vraiment simple à mettre en place, et super utile !

Pour tester, récupérez le projet d’exemple et mettez un point d’arrêt dans la méthode InvalidateCache du service WCF. Lancez le rôle Azure (configuré pour exécuter deux instances) et appelez ensuite l’URL /Home/InvalidateCache sur l’application MVC. Vous verrez que Visual Studio s’arrête bien deux fois dans le service, une fois pour chaque instance du rôle image

Le code source est disponible ici.

A bientôt !

read more

2012 c'est parti : Meilleurs voeux, livre sur ASP.NET MVC 4, Techdays...

Avant tout, je profite de ce post pour vous souhaiter une excellente année 2012 ! Qu’elle soit aussi riche que 2011 professionnellement et qu’elle vous apporte tout ce que vous souhaitez : santé, bonheur et réussite !

Pour ma part, 2012 s’annonce déjà très riche en travail image

Pour commencer, un livre sur ASP.NET MVC 4 actuellement en cours d’écriture et sur lequel j’aurai amplement l’occasion de revenir d’ici à sa sortie !

Trois sessions aux Techdays 2012 (merci aux personnes concernées pour leur confiance !) :

  • *Architecture, bonnes pratiques et recettes pour la réussite de vos projets avec ASP.NET MVC* : je coanimerai cette session avec [Rui Carvalho](http://www.rui.fr/). Nous tenterons de vous fournir un ensemble de bonnes pratiques et des `trucs` que nous utilisons régulièrement chez nos clients dans la mise en place de projets ASP.NET MVC ! Plus d’info sur la session sur le [Rui Carvalho](http://www.rui.fr/)
  • *La dure lutte du développeur : 10 trucs pratiques pour une application mobile bien peaufinée :* il manque souvent peu de choses pour qu’une application soit parfaite ! Malheuresement ce petit effort est parfois négligé avant la publication d’une application. Cyril Cathala, David Poulin et moi-même vous donnerons des conseils pratiques pour peaufiner toutes vos applications Windows Phone ! Plus d’info sur la session sur le [site des Techdays 2012…](http://www.microsoft.com/france/mstechdays/programmes/parcours.aspx?SessionID=8ff36860-fd09-4981-bfe9-253aaaba4736)
  • *Enseigner les technologies Microsoft, un exemple avec Windows Phone :* je co-animerai cette session avec Julie Knibbe. L’idée est de vous présenter, par l’exemple de Windows Phone, les ressources qui sont à votre disposition pour enseigner les technologies Microsoft ! Plus d’info sur la session sur le [site des Techdays 2012…](http://www.microsoft.com/france/mstechdays/programmes/parcours.aspx?SessionID=b2ef1ab8-f6fd-4072-b494-e67813dba7e3)
Rendez-vous aux Techdays donc !

image

Et évidemment, en 2012 toujours, mon travail au quotidien comme consultant chez Infinite Square ! image Il me permettra d’animer ce blog qui sera certainement rythmé par Windows Phone, ASP.NET MVC 4 et Windows Azure !

Bon 2012 !

Julien

read more