Utiliser Unity 2 avec Asp.net MVC 2

Le principe MVC pour Asp.Net est un vraie révolution dans le monde Microsoft par rapport aux WebForms, j’ai vraiment été surpris du virage à 180° qui a été initié par les architectes MS, notamment sur l’abandon total du ViewState et du systeme evenementiel  (pas de regret pour moi ;)),  elle l’est encore plus lorsque l’on peut conjuguer ce systeme avec un inverseur de controle type Unity.

J’ai déjà expliqué sur ce blog qu’est que Unity application block et fait une introduction sur la façon de l’utiliser.

Je vais expliquer ici comment se servir de Unity pour faire de l’injection dans les controllers MVC.

Dans un premier temps il faut mettre en place un container Unity et le configurer, le meilleur endroit pour faire ça est de placer dans l’evenement Application Start dans Global.asax par exemple.

 

	public class MvcApplication : System.Web.HttpApplication
{
public static IUnityContainer IoC { get; private set; }

protected void Application_Start()
{
IoC = new UnityContainer();
}


        }



Il existe différentes techniques pour “stocker” l’instance du container , dans cet exemple pour simplifier, je l’ai placé dans une variable statique de la classe MvcApplication, il sera possible de la retrouver facilement via une appel MvcApplication.IoC.



Asp.Net utilise une Factory pour charger les controlleurs qui “matchent” les routes configurées dans un site MVC, il s’agit de System.Web.Mvc.DefaultControllerFactory, il faut dans un premier temps creer un nouveau Type qui hérite de la fabrique de controlleurs par defaut.



	internal class UnityControllerFactory : DefaultControllerFactory
{
public UnityControllerFactory(IUnityContainer container)
{
this.Container = container;
}

protected IUnityContainer Container { get; private set; }

protected override IController GetControllerInstance(System.Web.Routing.RequestContext requestContext, Type controllerType)
{
if (controllerType == null)
{
throw new HttpException(404, String.Format("The controller for path '{0}' could not be found or it does not implement IController.",
requestContext.HttpContext.Request.Path));
}


var controller = Container.Resolve(controllerType) as Controller;
controller.ActionInvoker = new UnityActionInvoker(Container);

return controller;
}
}


Il faut passer le container IoC dans le constructeur de la fabrique, et overrider la methode GetControllerType pour que IoC s’occupe de charger les bons controllers en fonction des routes via :


 var controller = Container.Resolve(controllerType) as Controller; 


la ligne juste en dessous nous permet aussi, si une action d’un controller est marquée par un attribut de type ActionFilterAttribute de pouvoir y injecter aussi des services référencés dans le container, voici le code de l’action Invoker


	public class UnityActionInvoker : ControllerActionInvoker
{
public UnityActionInvoker(IUnityContainer container)
{
this.Container = container;
}

protected IUnityContainer Container { get; private set; }

protected override ActionExecutedContext InvokeActionMethodWithFilters(ControllerContext controllerContext, IList<IActionFilter> filters, ActionDescriptor actionDescriptor, IDictionary<string, object> parameters)
{
foreach (var filter in filters)
{
Container.BuildUp(filter.GetType(), filter);
}

return base.InvokeActionMethodWithFilters(controllerContext, filters, actionDescriptor, parameters);
}
}


De retour dans notre Application_Start, il suffit alors d’indiquer notre nouvelle fabrique de controller au systeme de la manière suivante :


 


		protected void Application_Start() 



              { 


                   IoC = new UnityContainer(); 


	           var cfactory = new UnityControllerFactory(IoC);
ControllerBuilder.Current.SetControllerFactory(cfactory);


              }



 


Unity prendra alors le control sur l’application web et permettra l’injection, l’interception, ect...


Exemple d’injection d’un service de log pour toutes les actions d’un controller marqué par un Attribut.


voici l’interface du logger :


	public interface ILogger
{
void Write(string message);
}


 



Dans un deuxieme temps nous allons creer un Attribut de log pour marquer les actions du controller :



	public class LoggerActionFilterAttribute : ActionFilterAttribute
{
[Dependency]
public Services.ILogger Logger { get; set; }

public override void OnResultExecuted(ResultExecutedContext filterContext)
{
base.OnResultExecuted(filterContext);
var viewResult = filterContext.Result as ViewResult;
Logger.Write(viewResult.ViewName);
}
}


 



Il faut remarquer l’attribut [Dependency] qui marque la propriété Logger de type ILogger, comme évoqué plus haut nous avons également dans la fabrique de controller intercepté la configuration des ActionInvoker via la ligne suivante



controller.ActionInvoker = new UnityActionInvoker(Container);



Grace au mécanisme d’injection de Unity, il affecte la propriété Logger sans qui soit nécéssaire de chercher ou d’instancier son type .



Dans le controller Home par exemple , il faut alors marquer une action pour pouvoir la logger



		[ActionFilters.LoggerActionFilter]
public ActionResult Index()
{
ViewData["Message"] = "Welcome to ASP.NET MVC!";

return View();
}


Bien sur il est aussi possible d’injecter l’instance du logger dans le controller toujours en utilisant Unity , une technique serait par exemple d’ajouter un paramètre dans le constructeur.



	[HandleError]
public class HomeController : Controller
{
public HomeController(Services.ILogger logger)
{
this.Logger = logger;
}

protected Services.ILogger Logger { get; private set; }

[ActionFilters.LoggerActionFilter]
public ActionResult Index()
{
ViewData["Message"] = "Welcome to ASP.NET MVC!";

return View();
}

public ActionResult About()
{
return View();
}
}


Et la il n’y a pas besoin de marquer la propriété via un attribut [Dependency] car Unity sait résoudre le paramètre du constructeur s’il a été défini au préalable de la manière suivante :


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

RegisterRoutes(RouteTable.Routes);

var container = new UnityContainer();

container.RegisterType<Services.ILogger, Services.ConcreteLogger>(new ContainerControlledLifetimeManager());

var cfactory = new UnityControllerFactory(container);
ControllerBuilder.Current.SetControllerFactory(cfactory);
}


Voila, avec le couple Asp.Net MVC + Unity il devient vraiment possible de créer de très grandes applications web très agiles, dans un prochain billet, j’expliquerai comment mettre en place un dispositif de plugins toujours basé sur Unity pour asp.net MVC

Aucun commentaire: