Comment utiliser Unity avec Asp.Net WebApi MVC4 RTM

La version RTM d’asp.net MVC4 est maintenant disponible à l’adresse suivante : http://www.microsoft.com/en-us/download/details.aspx?id=30683

Il y a eu quelques changements entre la version Beta puis RC pour utiliser Unity avec WebApi, voici une solution, dans un premier temps il faudra utiliser la nouvelle version de unity disponible via NuGet :

image

Il faut ensuite instancier le container dans Global.asax de la manière suivante :

	public class WebApiApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();

WebApiConfig.Register(GlobalConfiguration.Configuration);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);

var container = new Microsoft.Practices.Unity.UnityContainer();
container.RegisterType<Services.ValueService>();

System.Web.Http.GlobalConfiguration.Configuration.DependencyResolver = new UnityServiceDependencyResolver(container);
}
}

Le dependency resolver de webapi est configurable, c’est ce que l’on peut voir à la dernière ligne , voici la classe utilisée :

	public class UnityServiceDependencyResolver : System.Web.Http.Dependencies.IDependencyResolver
{
Microsoft.Practices.Unity.IUnityContainer m_Container;
System.Web.Http.Dependencies.IDependencyResolver m_DefaultResolver;

public UnityServiceDependencyResolver(Microsoft.Practices.Unity.IUnityContainer container)
{
m_Container = container;
m_DefaultResolver = System.Web.Http.GlobalConfiguration.Configuration.DependencyResolver;
}

public System.Web.Http.Dependencies.IDependencyScope BeginScope()
{
// This example does not support child scopes, so we simply return 'this'.
return this;
}

public object GetService(Type serviceType)
{
if (IsBuiltIn(serviceType))
{
return m_DefaultResolver.GetService(serviceType);
}
return m_Container.Resolve(serviceType);
}

public IEnumerable<object> GetServices(Type serviceType)
{
if (IsBuiltIn(serviceType))
{
return m_DefaultResolver.GetServices(serviceType);
}

return m_Container.ResolveAll(serviceType);
}

private bool IsBuiltIn(Type serviceType)
{
if (serviceType == typeof(System.Web.Http.Tracing.ITraceManager)
|| serviceType == typeof(System.Web.Http.Tracing.ITraceWriter)
|| serviceType == typeof(System.Web.Http.Dispatcher.IHttpControllerSelector)
|| serviceType == typeof(System.Web.Http.Dispatcher.IAssembliesResolver)
|| serviceType == typeof(System.Net.Http.Formatting.IContentNegotiator)
|| serviceType == typeof(System.Web.Http.Dispatcher.IHttpControllerTypeResolver)
|| serviceType == typeof(System.Web.Http.Dispatcher.IHttpControllerActivator)
|| serviceType == typeof(System.Web.Http.Controllers.IHttpActionSelector)
|| serviceType == typeof(System.Web.Http.Controllers.IHttpActionInvoker)
|| serviceType == typeof(System.Web.Http.Controllers.IActionValueBinder)
|| serviceType == typeof(System.Web.Http.Validation.IBodyModelValidator)
|| serviceType == typeof(System.Web.Http.Metadata.ModelMetadataProvider)
|| serviceType == typeof(System.Web.Http.Hosting.IHostBufferPolicySelector)
|| serviceType.FullName.Equals("System.Web.Http.Validation.IModelValidatorCache")
)
{
return true;
}
return false;
}

public void Dispose()
{
m_Container.Dispose();
}
}

L’idée c’est d’utiliser le resolver standard pour les types prédéfinis, a noter certains type comme IModelValidatorCache sont internal, pour tous les autres cas on utilise le resolver Unity passé en paramètre du constructeur, pour utiliser ses propres services et les resoudre par injection de dépendance.


Ci-dessous le controller sample “ValueController” refactoré pour utiliser un service prédéclaré :

	public class ValuesController : ApiController
{
public ValuesController(Services.ValueService valueService)
{
this.ValueService = valueService;
}

protected Services.ValueService ValueService { get; private set; }

// GET api/values
public IEnumerable<string> Get()
{
return ValueService.GetList();
}

// GET api/values/5
public string Get(int id)
{
return ValueService.GetList().ElementAt(id);
}

// POST api/values
public void Post([FromBody]string value)
{
ValueService.Add(value);
}

// PUT api/values/5
public void Put(int id, [FromBody]string value)
{
}

// DELETE api/values/5
public void Delete(int id)
{
}
}

Le resultat est le suivant :


image

Comment compiler les vues d’un projet asp.net mvc

Il est possible de compiler toutes les vues d’un projet MVC, c’est plus simple pour retrouver toutes les erreurs de syntaxe possibles avant de les decouvrir en production Clignement d'œil

Pour cela il faut editer le fichier .csproj du projet asp.net mvc (a partir de 3) et retrouver l’élément suivant :

<MvcBuildViews>false</MvcBuildViews>
Remplacer la valeur par true
Ensuite tout en bas du projet il faut retrouver l’element suivant :
  <Target Name="MvcBuildViews" AfterTargets="AfterBuild" Condition="'$(MvcBuildViews)'=='true'">     <AspNetCompiler VirtualPath="temp" PhysicalPath="$(WebProjectOutputDir)" />   </Target>
Remplacer par :

<Target Name="MvcBuildViews" AfterTargets="AfterBuild" Condition="'$(MvcBuildViews)'=='true'">
  <AspNetCompiler VirtualPath="/" PhysicalPath="$(WebProjectOutputDir)" TargetPath="$(WebProjectOutputDir)\..\temp" Force="true" Debug="true" LogStandardErrorAsError="false" />
</Target>


Ceci pour eviter l’erreur dans le ficher web.config “MachineToApplication” eventuel


et dernière chose ajouter l’element suivant ou completer sur le postbuild du projet :


<PropertyGroup>
  <PostBuildEvent>rd "$(ProjectDir)obj" /S /Q</PostBuildEvent>
</PropertyGroup>


Ce qui aura pour effet de supprimer tous les fichiers du repertoire obj qui pourrait compliquer la compilation des vues.


 

 

Comment mettre en place des tests unitaires avec asp.net mvc4

image

Ce post fait suite à une critique constructive dont j’ai fait l’objet via le projet opensource ERPStore concernant du code de test que j’avais écrit, la source se trouve ici :

http://hadihariri.com/2012/04/07/test-setups-and-design-smells/

Effectivement je reconnais que la version affichée sur cet exemple (V2) n’était pas bonne, pas sur le plan de l’héritage que je revendique et qui respecte le pattern DRY,  mais parce que l’injection de dépendance était écrite “en dur” dans une méthode d’initialisation qui pouvait ne pas correspondre à la réalité du site qu’elle était censée tester.

Quand on utilise de l’injection de dépendance avec asp.net mvc il est difficile de reproduire au plus proche le comportement du serveur, mais il est possible de reproduire le comportement d’une requête, pour cela j’utilise le framework Moq http://code.google.com/p/moq/ et NUnit http://www.nunit.org/  et “toujours” une classe de base abstraite TestBase

Cette classe possède une méthode initialize, il faudra appeler cette méthode dans toutes les classes de test qui hériteront de TestBase via l’attribut [SetUp] de NUnit.
 
  public virtual void Initialize()
{
var httpContext = GetHttpContext();

if (m_Application == null)
{
m_Application = new ERPStoreApplication();
m_Application.Initialize(httpContext);

}

var resolver = System.Web.Mvc.DependencyResolver.Current as IOC.UnityDependencyResolver;
m_Container = resolver.Container;

this.Logger = m_Container.Resolve<ERPStore.Logging.ILogger>();
}

 

Elle récupere ou crée un contexte Http via la Methode GetHttpContext()
  public HttpContextBase GetHttpContext()
{
if (m_MockHttpContext == null)
{
m_MockHttpContext = CreateMockHttpContext();
}
return m_MockHttpContext.Object;
}

Bien entendu nous n’avons pas lors des tests un serveur IIS avec une requete http en bonne et due forme, il va donc falloir en creer une de toute pièce , pour ce faire Microsoft a fourni les éléments nécessaires via l’assembly System.Web.Abstraction, il s’agit de wrappers (classes abstraites) utilisées par le serveur lors du traitement d’une requête, à savoir HttpContext (son wrapper HttpContextBase), Request et son wrapper RequestBase, Response, ect…, tout l’art est de pouvoir “bouchonner” ces wrappers pour récupérer un context http utilisable par les controllers. C’est la qu’intervient le framework Moq il va nous aider à creer des instances concrètes des differents wrappers.

  private Mock<HttpContextBase> CreateMockHttpContext()
{
var context = new Mock<HttpContextBase>();
var cookies = new HttpCookieCollection();

// Response
var response = new Mock<HttpResponseBase>();
var cachePolicy = new Mock<HttpCachePolicyBase>();
response.SetupProperty(r => r.StatusCode, 200);
response.Setup(r => r.Cache).Returns(cachePolicy.Object);
response.Setup(r => r.ApplyAppPathModifier(It.IsAny<string>())).Returns<string>(r => r);
response.Setup(r => r.Cookies).Returns(cookies);
context.Setup(ctx => ctx.Response).Returns(response.Object);

// Request
var request = new Mock<HttpRequestBase>();
var visitorId = Guid.NewGuid().ToString();
var principal = new ERPStore.Models.UserPrincipal(visitorId);
request.Setup(r => r.AnonymousID).Returns(principal.VisitorId);
request.Setup(r => r.Cookies).Returns(cookies);
request.Setup(r => r.Url).Returns(new Uri("http://www.test.com"));
request.Setup(r => r.Headers).Returns(new System.Collections.Specialized.NameValueCollection());
request.Setup(r => r.RequestContext).Returns(new System.Web.Routing.RequestContext(context.Object, new System.Web.Routing.RouteData()));
request.SetupGet(x => x.PhysicalApplicationPath).Returns("/");
request.Setup(r => r.UserHostAddress).Returns("127.0.0.1");
request.Object.Cookies.Add(new HttpCookie("erpstorevid")
{
Value = principal.VisitorId,
});
context.Setup(ctx => ctx.Request).Returns(request.Object);

// Sessions
var session = new Mock<HttpSessionStateBase>();
context.Setup(ctx => ctx.Session).Returns(session.Object);

// Server
var server = new Mock<HttpServerUtilityBase>();
server.Setup(s => s.MapPath(It.IsAny<string>())).Returns<string>(r => {
if (r.Equals("/bin", StringComparison.InvariantCultureIgnoreCase))
{
r = string.Empty;
}
var path = typeof(ERPStore.ERPStoreApplication).Assembly.Location;
var fileName = System.IO.Path.GetFileName(path);
path = path.Replace(fileName, string.Empty);
r = r.Trim('/').Trim('\\');
path = System.IO.Path.Combine(path, r);
return path;
});
context.Setup(ctx => ctx.Server).Returns(server.Object);

// Principal
context.Setup(ctx => ctx.User).Returns(principal);

// Items
context.Setup(ctx => ctx.Items).Returns(new Dictionary<string, object>());

return context;
}

Le principe est de faire un retour du type demandé pour chaque propriété et méthode, a l’issue un Context Http est retourné pret à l’emploi.


La critique portait au niveau de l’initialisation, ce qui a changé maintenant l’appel de classe ERPStoreApplication est fait au moment du initialize (simulation d’une requete http) qui est en quelque sorte le bootstapper d’ERPStore c’est lui qui a la responsabilité de configurer le container d’inversion de dépendance (Unity), que ce soit pour les tests ou en production il est appelé exactement de la même manière maintenant. Je pouvais aller plus loin encore en créant une instance de Global.asax et en appelant la méthode BeginRequest()


Maintenant pour tester un controller il suffit d’écrire sa propre classe qui doit heriter de TestBase, par exemple le login du site :


 

[TestFixture]
public class AccountControllerTests : TestBase
{
    [SetUp]
    public override void Initialize()
    {
        base.Initialize();
    }


    [TearDown]
    public void TearDown()
    {
        base.TeardownHttpContext();
    }



    [Test]
    public void Login()
    {
        var m_Controller = CreateController<ERPStore.Controllers.AccountController>();
        var result = m_Controller.Login() as System.Web.Mvc.ViewResult;
        Assert.AreEqual(result.ViewName, string.Empty);
    }


    [Test]
    public void Login_Form()
    {
        var accountController = CreateController<ERPStore.Controllers.AccountController>();


        var accountService = System.Web.Mvc.DependencyResolver.Current.GetService<ERPStore.Services.IAccountService>();
        var user = new ERPStore.Models.User();
        user.Login = "userName1";
        user.Id = 12345;
        accountService.SaveUser(user);


        accountService.SetPassword(user, "password1");


        var loginResult = accountController.Login("userName1", "password1", false, null) as System.Web.Mvc.RedirectToRouteResult;


        Assert.AreEqual(loginResult.RouteName, Routing.ERPStoreRoutes.ACCOUNT);
    }


}


La classe de base fournit le controller a tester grâce à la methode CreateController<…>()

  protected T CreateController<T>()
where T : System.Web.Mvc.Controller
{
var httpContext = GetHttpContext();
var result = CreateController<T>(httpContext);
return result;
}

protected T CreateController<T>(System.Web.HttpContextBase httpContext)
where T : System.Web.Mvc.Controller
{
var controller = m_Container.Resolve<T>();
controller.SetupControllerContext(httpContext);

return controller;
}
Grace a cette classe de base il est possible d’obtenir une couverture de 100% sur les controllers, il est meme possible de tester également les routes.
  [Test]
public void Cart_Href()
{
var ctrl = CreateController<ERPStore.Cart.Controllers.CartController>();

var url = ctrl.Url.CartHref();

Assert.AreEqual(url, "/panier");
}
avec sa methode d’extension
  public static string CartHref(this UrlHelper helper)
{
return helper.RouteERPStoreUrl(Cart.Routes.CART);
}
et son enregistrement
   routes.MapERPStoreRoute(
Routes.CART
, "panier"
, new { controller = "Cart", action = "Index", id = string.Empty }
, namespaces
);
 
Bons tests à tous

Asp.net MVC4 Web Api et Unity

 

Je suis en train de migrer un certain nombre de service pour utiliser Web Api, j’utilise au quotidien Unity comme container pour tous mes services. Voici comment utiliser un Inverseur de controle comme Unity avec Asp.Net MVC4 :

image

Dans on premier temps il faut sélectionner un projet de type Web Api

image

Puis ajouter Unity en utilisant NuGet, la dernière version est la 2.1

l’ApiControlleur  fourni par défaut avec le nouveau projet est le suivant :

   1:      public class ValuesController : ApiController
   2:      {
   3:          // GET /api/values
   4:          public IEnumerable<string> Get()
   5:          {
   6:              return new string[] { "value1", "value2" };
   7:          }
   8:   
   9:          // GET /api/values/5
  10:          public string Get(int id)
  11:          {
  12:              return "value";
  13:          }
  14:   
  15:          // POST /api/values
  16:          public void Post(string value)
  17:          {
  18:          }
  19:   
  20:          // PUT /api/values/5
  21:          public void Put(int id, string value)
  22:          {
  23:          }
  24:   
  25:          // DELETE /api/values/5
  26:          public void Delete(int id)
  27:          {
  28:          }
  29:      }

Nous ajoutons un constructeur dans lequel nous allons passer le service comme ceci :


   1:      public class ValuesController : ApiController
   2:      {
   3:          public ValuesController(Services.IValueService valueService)
   4:          {
   5:              ValueService = valueService;
   6:          }
   7:   
   8:          protected Services.IValueService ValueService { get; private set; }
   9:   
  10:          // GET /api/values
  11:          public IEnumerable<string> Get()
  12:          {
  13:              return ValueService.GetValues();
  14:          }
  15:   
  16:          // GET /api/values/5
  17:          public string Get(int id)
  18:          {
  19:              return ValueService.GetValueById(id);
  20:          }
  21:   
  22:          // POST /api/values
  23:          public void Post(string value)
  24:          {
  25:              ValueService.Save(value);
  26:          }
  27:   
  28:          // PUT /api/values/5
  29:          public void Put(int id, string value)
  30:          {
  31:              var v = new KeyValuePair<int, string>(id, value);
  32:              ValueService.Save(v);
  33:          }
  34:   
  35:          // DELETE /api/values/5
  36:          public void Delete(int id)
  37:          {
  38:              ValueService.Delete(id);
  39:          }
  40:      }

Voici le détail du service (simpliste) :


   1:      public class ValueService : IValueService
   2:      {
   3:          private Dictionary<int, string> m_Values;
   4:   
   5:          public ValueService()
   6:          {
   7:              m_Values = new Dictionary<int, string>()
   8:              {
   9:                  { 1 , "value1" }, 
  10:                  { 2 , "value2" },
  11:              };
  12:          }
  13:   
  14:          public IEnumerable<string> GetValues()
  15:          {
  16:              return m_Values.Select(i => i.Value);
  17:          }
  18:   
  19:          public string GetValueById(int Id)
  20:          {
  21:              var value = m_Values.SingleOrDefault(i => i.Key == Id);
  22:               if (value.Key != null)
  23:              {
  24:                  return value.Value;
  25:              }
  26:              return null;
  27:          }
  28:   
  29:          public void Save(string value)
  30:          {
  31:              m_Values.Add(m_Values.Count + 1, value);
  32:          }
  33:   
  34:          public void Save(KeyValuePair<int, string> v)
  35:          {
  36:              m_Values.Add(v.Key, v.Value);
  37:          }
  38:   
  39:          public void Delete(int id)
  40:          {
  41:              m_Values.Remove(id);
  42:          }
  43:      }

Maintenant si nous appelons le service directement voici le résultat :


image


<ExceptionType>System.InvalidOperationException</ExceptionType>

<Message>

An error occurred when trying to create a controller of type 'UnityMvcApplication.Controllers.ValuesController'. Make sure that the controller has a parameterless public constructor.

</Message>

<StackTrace>

Nous allons donc faire de telle sorte que Unity vienne injecter le service attendu dans le constructeur, pour cela il faut configurer unity dans la methode app_start de l’application comme ceci :

   1:          protected void Application_Start()
   2:          {
   3:              AreaRegistration.RegisterAllAreas();
   4:   
   5:              RegisterGlobalFilters(GlobalFilters.Filters);
   6:              RegisterRoutes(RouteTable.Routes);
   7:   
   8:              var container = new Microsoft.Practices.Unity.UnityContainer();
   9:   
  10:              // Mapping des services pour Web Api
  11:              container.RegisterInstance<System.Web.Http.HttpConfiguration>(System.Web.Http.GlobalConfiguration.Configuration);
  12:              container.RegisterType<System.Web.Http.Dispatcher.IHttpControllerFactory, System.Web.Http.Dispatcher.DefaultHttpControllerFactory>();
  13:              container.RegisterType<System.Web.Http.Dispatcher.IHttpControllerActivator, System.Web.Http.Dispatcher.DefaultHttpControllerActivator>();
  14:              container.RegisterType<System.Web.Http.Common.ILogger, Services.ServiceLogger>();
  15:              container.RegisterType<System.Web.Http.Controllers.IHttpActionSelector, System.Web.Http.Controllers.ApiControllerActionSelector>();
  16:              container.RegisterType<System.Web.Http.Controllers.IHttpActionInvoker, System.Web.Http.Controllers.ApiControllerActionInvoker>();
  17:              container.RegisterType<System.Web.Http.Controllers.IActionValueBinder, System.Web.Http.ModelBinding.DefaultActionValueBinder>();
  18:              container.RegisterType<System.Web.Http.Metadata.ModelMetadataProvider, System.Web.Http.Metadata.Providers.CachedDataAnnotationsModelMetadataProvider>();
  19:              container.RegisterType<System.Net.Http.Formatting.IFormatterSelector, System.Net.Http.Formatting.FormatterSelector>();
  20:              System.Web.Http.GlobalConfiguration.Configuration.ServiceResolver.SetResolver(
  21:                  (Type serviceType) =>
  22:                  {
  23:                      return container.Resolve(serviceType);
  24:                  },
  25:                  (Type serviceType) =>
  26:                  {
  27:                      return container.ResolveAll(serviceType);
  28:                  });
  29:   
  30:              // Mapping du service des valeurs pour la demo
  31:              container.RegisterType<Services.IValueService, Services.ValueService>();
  32:   
  33:              BundleTable.Bundles.RegisterTemplateBundles();
  34:          }

lors du même appel Web Api le résultat est le suivant, le service à bien été injecté dans le constructeur  :


image


Explications, le service Web Api pour fonctionner avec Unity à besoin qu’un certain nombre de services du framework soient déclarés :

			container.RegisterType<System.Web.Http.Dispatcher.IHttpControllerFactory, System.Web.Http.Dispatcher.DefaultHttpControllerFactory>();
container.RegisterType<System.Web.Http.Dispatcher.IHttpControllerActivator, System.Web.Http.Dispatcher.DefaultHttpControllerActivator>();
container.RegisterType<System.Web.Http.Common.ILogger, Services.ServiceLogger>();
container.RegisterType<System.Web.Http.Controllers.IHttpActionSelector, System.Web.Http.Controllers.ApiControllerActionSelector>();
container.RegisterType<System.Web.Http.Controllers.IHttpActionInvoker, System.Web.Http.Controllers.ApiControllerActionInvoker>();
container.RegisterType<System.Web.Http.Controllers.IActionValueBinder, System.Web.Http.ModelBinding.DefaultActionValueBinder>();
container.RegisterType<System.Web.Http.Metadata.ModelMetadataProvider, System.Web.Http.Metadata.Providers.CachedDataAnnotationsModelMetadataProvider>();
container.RegisterType<System.Net.Http.Formatting.IFormatterSelector, System.Net.Http.Formatting.FormatterSelector>();

 

J’ai trouvé pour chacun une implémentation concrète sauf pour ILogger, si quelqu’un sait ou se trouve une implémentation dans le framework, pour l’instant j’ai du créer une classe qui implémente l’interface.

une fois tous les service déclarés “mappé” avec Unity il suffit d’indiquer un resolver pour Web Api, ce mecanisme avait déjà été initié avec MVC3 et son DependencyResolver.

			System.Web.Http.GlobalConfiguration.Configuration.ServiceResolver.SetResolver(
(Type serviceType) =>
{
return container.Resolve(serviceType);
},
(Type serviceType) =>
{
return container.ResolveAll(serviceType);
});

la c’est très bien fait, il n’y a que 2 methodes à declarer comme pour le DependencyResolver , Resolve et ResolveAll, a la fin on oublie pas de déclarer également le service des valeurs :

			container.RegisterType<Services.IValueService, Services.ValueService>();
Ci dessous le code source de cet exemple :
 

Asp.Net MVC4 (beta) Web Api et Autocompletion via JQuery

 

Je n’ai pas bloggé depuis très longtemps faute de temps, je profite donc de la fin de mes congés pour écrire un petit billet concernant l’utilisation de Web Api dans asp.net MVC4.

Nous allons voir comment mettre en place toutes ces technologie pour activer une auto-completion asynchrone dans une Textbox avec JQuery. Notamment la mise en place d’une couche de service REST.

Dans un premier temps nous allons commencer un projet asp.net mvc4

image

image

Ce que l’on peut voir pour un nouveau projet MVC grâce au template “Internet Application” c’est que toutes les assemblies nécessaires sont chargées via NuGet, je trouve ça très agile.

image

Pour la mise en place de notre page d’autocompletion, il faut ajouter une methode dans le controller HomeController de la manière suivante :

   1:          public ActionResult Autocomplete()

   2:          {

   3:              ViewBag.Message = "Your quintessential contact page.";

   4:   

   5:              return View();

   6:          }

 

Puis il faut ajouter la vue dans le repertoire /views/home/autocomplete.cshtml avec le contenu suivant :

 

@{
    ViewBag.Title = "Autocomplete";
}
 
<h2>Autocomplete</h2>
 
<input type="text" name="autocomplete" />




le rendu sera le suivant en appelant la page /home/autocomplete :


image


 

Maintenant nous allons mettre en place un service dont le role sera de gerer une liste de pays avec leurs caractèristiques intrinseques. Dans ce service la methode GetStartWith(…) permettra de recuperer la liste des n pays dont le nom commence par un terme, voici son implementation :
 

   1:      public class CountryService
   2:      {
   3:          public IEnumerable<string> GetStartWith(string term, int count)
   4:          {
   5:              return Country.GetValues().Where(i => i.LocalizedName.StartsWith(term, StringComparison.InvariantCultureIgnoreCase)).Select(i => i.LocalizedName).Take(count);
   6:          }
   7:      }


 

(Pour le détail de la liste des pays ce n’est pas l’objet de ce billet il suffira de regarder le code source.)


Pour appeler ce service nous allons maintenant utiliser une nouveauté d’asp.net mvc4 qui est Web Api, pour commencer il faut ajouter un controller de type WebApi :


   1:      public class CountryApiController : ApiController
   2:      {
   3:          public CountryApiController()
   4:          {
   5:              this.CountryService = new Services.CountryService();
   6:          }
   7:   
   8:          protected Services.CountryService CountryService { get; private set; }
   9:   
  10:          public IEnumerable<string> CountryNameStartWith(string term)
  11:          {
  12:              return CountryService.GetStartWith(term, 15);
  13:          }
  14:      }



Ce controller de type ApiController est a mettre dans le repertoire des controllers aux cotés de homecontroller.


Maintenant il nous reste à appeler celui-ci via JQuery :


<input type="text" name="autocomplete" id="country"/>
 
<script type="text/javascript">
    $("#country").autocomplete({
        source: function (request, response) {
            $.ajax({
                type: "POST",
                contentType: "application/json; charset=utf-8",
                url: "/api/country/CountryNameStartWith",
                data: '"' + request.term + '"',
                success: function (data) {
                    response(data);
                }
            });
        }
    });
 
</script>



Pour que le bon controlleur CountryApiController soit appelé via JQuery j’ai déclaré au préalable dans App_Start la route qui permet de le retrouver :


   1:              routes.MapHttpRoute(
   2:                  "CountryApi"
   3:                  , "api/country/{action}"
   4:                  , new { controller = "CountryApi" }
   5:              );



et voici le résultat :


image


A noter , il était tout a fait possible de réaliser la même chose avec MVC3, ce qui change avec MVC4 et Web Api c’est que le formatage de sortie est auto-adaptatif , si l’appel est fait a partir d’un client JQuery le résultat sera renvoyé au format JSon :


image


Directement dans l’url et le résultat sera retourné en XML


image


Conclusion :


On peut voir qu’un nouveau modèle apparait avec MVC4, le Modèle et le Controller sont gérés sur le serveur IIS alors que la vue est gérée par le client , la communication entre la vue et le controlleur étant gérée via WCF avec une surcouche Web Api, la vue est gérée via JQuery et une surcouche Knockout pour la partie MVC “Client”. Je trouve cette architecture très puissante et je n’ai aucun doute sur le fait qu’elle va devenir un vrai standard.


Ci-dessous , le code source :