L’image ci-dessous représente un formulaire d’inscription et comme tout bon formulaire une vérification des données est nécessaire avant de prendre en compte l’inscription de l’utilisateur. Avec ASP.Net MVC nous pouvons mettre en place des validations asynchrones comme par exemple vérifier côté serveur si le login saisi par l’utilisateur est déjà utilisé ou non par quelqu’un d’autre. Cette vérification doit se faire sans attendre que l’utilisateur ait fini la saisie complète du formulaire et ait à cliquer sur le bouton Register.

registration

Pour cela dans notre modèle nous ajoutons l’attribut RemoteAttribute sur la propriété représentant le champ concerné dans le formulaire. Le code ci-dessous représente le modèle utilisé :

public class RegisterViewModel    
{    
   [Required, Remote("ValidateUserName", "Accounts"), Display(Name = "Login")]    
   public string UserName { get; set; }     
   [Required, MinLength(6), DataType(DataType.Password)]    
   public string Password { get; set; }     
   [Required, EmailAddress,]    
   public string Email { get; set; }    
}

Ci-dessous l’action qui nous permet de vérifier l’unicité du login d’un nouvel utilisateur :

public JsonResult ValidateUserName(string userName)     
{     
   if (UserManager.FindByNameAsync(userName) != null)     
   return Json(string.Format("The login '{0}' is already taken.", userName), JsonRequestBehavior.AllowGet);     
   return Json(true, JsonRequestBehavior.AllowGet);     
}

L’action ValidateUserName n’est utile que si elle est appelée via mon formulaire d’inscription et cela de façon asynchrone grâce à une requête AJAX. En l’état actuel rien n’empêche à quelqu’un de taper l’url directement dans la barre d’adresse du navigateur et d’avoir comme résultat l’image ci-dessous :

ValidateUserName 

Il nous faut un moyen nous permettant d’interdire ces types d’appel qui ne seraient pas faits avec de l’AJAX donc du code JavaScript écrit pour notre application côté client.

La méthode d’extension HttpRequestBase. IsAjaxRequest () :

Cette méthode d’extension nous permet de savoir si la requête reçue du client est faite en Ajax ou pas. C’est la solution la plus facile vu qu’il s’agit de simplement vérifier au début du code de notre action que la requête que nous recevons est faite en AJAX et dans le cas contraire on renvoie tout simplement un résultat null comme dans le code ci-dessous :

public JsonResult ValidateUserName(string userName)     
{     
   if (!this.Request.IsAjaxRequest()) return null;     
   if (UserManager.FindByNameAsync(userName) != null)     
   return Json(string.Format("The login '{0}' is already taken.", userName), JsonRequestBehavior.AllowGet);     
   return Json(true, JsonRequestBehavior.AllowGet);     
}

Le problème avec cette solution est que qu’il n’est pas rare de voir une application Web avec plein d’actions que nous utilisons uniquement en faisant des requêtes AJAX. La ligne permettant de vérifier s’il s’agit d’une requête AJAX doit être répétée au début de toutes les actions concernées alors qu’elle n’est aucunement liée à la logique que doit effectuer chacune de nos actions.

Utilisation de l’attribut ActionMethodSelectorAttribute :

La solution que je préfère de loin est d’agir au niveau de la sélection de notre action c’est à dire quand ASP.Net MVC cherche à savoir lesquelles parmi les actions répondent parfaitement aux critères de la requête et surtout laquelle de ces actions éligibles est valide. Pour cela nous allons créer un attribut personnalisé dérivant de la classe ActionMethodSelectorAttribute. Il faut noter que les attributs HttpGetHttpPostHttpPut etc… dérivent aussi de cet même attribut.

La classe ActionMethodSelectorAttribute est abstraite alors ses classes dérivées doivent obligatoirement redéfinir la méthode IsValidForRequest qui doit renvoyer true si l’action concernée est valide pour la requête courante.

[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)]    
public abstract class ActionMethodSelectorAttribute : Attribute    
{    
   protected ActionMethodSelectorAttribute();    
   public abstract bool IsValidForRequest(ControllerContext controllerContext, MethodInfo methodInfo);    
}

Mon nouvel attribut que j’appelle AjaxRequestAttribute est très simple et son code est le suivant :

public class AjaxRequestAttribute : ActionMethodSelectorAttribute     
{     
   public override bool IsValidForRequest(ControllerContext controllerContext, System.Reflection.MethodInfo methodInfo)     
   {     
   return controllerContext.HttpContext.Request.IsAjaxRequest();     
   }     
}

Son utilisation aussi est très simple il suffit de décorer les actions concernées de l’attribut AjaxRequest comme suit :

[AjaxRequest]    
public JsonResult ValidateUserName(string userName)    
{    
   if (UserManager.FindByNameAsync(userName) != null)    
   return Json(string.Format("The login '{0}' is already taken.", userName), JsonRequestBehavior.AllowGet);     
   return Json(true, JsonRequestBehavior.AllowGet);    
}

Quand l’utilisateur essaie de faire appel à notre action par un autre moyen que par le biais d’une requête AJAX alors le serveur lui renverra une erreur avec le code 404 comme le montre l’image ci-dessous quand j’interroge directement l’action en utilisant la barre d’adresse du navigateur :

message d'erreur

L’attribut ChildActionOnly :

Il faut noter que l’attribut ChildActionOnly est disponible et a pour but d’interdire l’appel d’une action (créée la plupart du temps pour renvoyer une vue partielle) en dehors d’une vue et cela en nous obligeant d’utiliser soit la méthode HtmlHelper.RenderAction ou HtmlHelper.Action à l’intérieur de notre vue ASP.Net MVC. ChildActionOnly ne nous permet pas de résoudre le problème soulevé par cet article et pour finir il faut savoir que cet attribut ne dérive pas de ActionMethodSelectorAttribute et qu’il s’agit plutôt d’un filtre (dérive de FilterAttribute) et implémente  l’interface IAuthorizationFilter.

Ne ratez plus aucunes actualités avec la newsletter mensuelle de SoftFluent