Archive

Archive for avril 2017

Limiter l’exécution d’une action uniquement aux requêtes AJAX

En ASP.Net MVC nous manipulons entre autres des vues. Parmi ces vues certaines représentent des pages complètes et d’autres ne sont que des parties de page. Ces parties ou zones appartenant à une vue sont appelées des vues partielles et elles sont renvoyées elles aussi par des actions du contrôleur. Ces vues partielles ne devant être utilisées qu’à l’intérieur d’une vue alors le framework ASP.Net MVC nous permet de protéger l’appel à ces actions partielles en les décorant avec l’attribut ChildActionOnly. Cet attribut permet d’être sûr que l’action :

  • ne pourra pas être utilisée comme une vue entière et que les développeurs de l’application l’exécuteront toujours en passant par les méthodes HtmHelper.Action ou HtmlHelper.RenderAction.
  •  a une URL qui ne sera pas accessible via la barre d’adresse si un utilisateur, je ne sais par quel moyen, serait au courant de l’existence de cette URL.

Cependant comme tout site dynamique nous aurons des requêtes AJAX qui pourront faire elles aussi des demandes de contenu HTML sans qu’on ait besoin de charger la page de façon complète. Ce contenu aussi représente une partie puis qu’à la réception de la réponse du serveur Web nous devons incorporer ce bout de HTML quelque part dans la page. La requête AJAX envoyée au serveur invoquera certainement une action du contrôleur. Cette action comme celles marquées avec l’attribut ChildActionOnly doit possèder des restrictions soient :

  • obligation de passer par une requête faite en AJAX.
  • inaccessible via la barre d’adresse du navigateur.

Cependant le framework ASP.Net MVC n’offre aucun attribut nous permettant d’appliquer ces restrictions sur une action mais il nous fournit les outils pour en créer. Pour cela nous devons coder un filtre qui sera exécuté juste avant l’exécution de l’action concernée. Si la requête entrante respecte les conditions d’une requête faite en AJAX alors on laisse l’action continuer son chemin. Dans le cas où les conditions ne seraient pas respectées nous envoyons une page 404 (comme quoi l’URL est inexistante).

Le code du nouveau filtre que je nommerai AjaxOnlyAttribute, classe dérivée de la classe ActionFilterAttribute, est le suivant :

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false)]
public class AjaxOnlyAttribute : ActionFilterAttribute
{
	public override void OnActionExecuting(ActionExecutingContext filterContext)
	{
		if (filterContext.HttpContext.Request.IsAjaxRequest())
		{
			base.OnActionExecuting(filterContext);
		}
		else
		{
			filterContext.HttpContext.Response.StatusCode = 404;
			filterContext.Result = new HttpNotFoundResult();
		}
	}
}

La nouvelle classe ne fait que surcharger la méthode qui nous intéresse. C’est à dire la méthode OnActionExecuting qui est appelée juste avant le début de l’exécution de l’action demandée par la requête entrante. L’attribut peut être posé sur le contrôleur pour atteindre l’ensemble des actions ou unitaire sur chaque action où la requête AJAX est obligatoire.

Pour éviter des soucis de pertes de quelques petites minutes qui peuvent arriver dans le cas où l’un des développeurs ayant rejoint l’équipe en cours de route et qui ne comprendrait pas pourquoi sa requête renvoie du 404 alors je pense que ce serait un avantage d’avoir un mode DEBUG activé par défaut pour les codeurs. Le code de notre classe ressemblerait finalement à celui-ci :

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false)]
public class AjaxOnlyAttribute : ActionFilterAttribute
{
	public override void OnActionExecuting(ActionExecutingContext filterContext)
	{
		if (filterContext.HttpContext.Request.IsAjaxRequest())
		{
			base.OnActionExecuting(filterContext);
		}
		else
		{
			#if DEBUG
				filterContext.Result = new ViewResult { ViewName = "AjaxOnly" };
			#else
				filterContext.HttpContext.Response.StatusCode = 404;
				filterContext.Result = new HttpNotFoundResult();
			#endif
		}
	}
}

Etant donné que les développeurs sont amenés à compiler l’application la plupart du temps en mode DEBUG du coup la vue AjaxOnly contenant le texte qui va bien leur expliquera ce qu’il faut faire. Il faut noter que cette version de la classe fonctionne uniquement si une vue AjaxOnly.[cs|vb]html a été ajoutée dans le répertoire Views\Shared de l’application. Aussi on n’est pas obligé de renvoyer une vue partielle. On peut tout simplement remplacer le code suivant :

filterContext.Result = new ViewResult { ViewName = "AjaxOnly" };

par juste du contenu textuel :

filterContext.Result = new ContentResult
{
     Content = $"Cette action '{filterContext.HttpContext.Request.RawUrl}' a été conçue pour être utilisée uniquement via des requêtes AJAX"
};

La version utilisant la vue AjaxOnly a l’avantage de prendre en compte automatiquement le rendu _Layout par défaut défini au niveau de l’application.

Catégories :ASP.Net MVC Étiquettes : , , ,

Entity Framework et C# 6 : Utilisation des propriétés automatiques en lecture seule au niveau de vos entités

Avec C# 6 nous avons vu l’apparition de pas mal d’améliorations au niveau de la syntaxe. Ces nouveautés syntaxiques sont souvent mises dans la catégorie sucre syntaxique et comme exemple nous avons la propriété automatique en lecture seule permettant d’avoir juste un accesseur Get et qu’il est possible d’assigner via le constructeur.

Supposons que nous ayons la classe ci-dessous utilisée comme entité Entity Framework :

public class Product
{
	[Key]
	public Guid Reference { get; private set; }

	public string Name { get; set; }

	public string Description { get; set; }

	public Product()
	{
		this.Reference = Guid.NewGuid();
	}
}

Avec un contexte très simple :

public class MyDbContext : DbContext
{
	public DbSet<Product> Products { get; set; }

	public MyDbContext() : base("name=MyDbContext")
	{
	}
}

Avec C# 6 et les propriétés automatiques en lecture seule nous pouvons changer la définition de la propriété Reference en supprimant le modificateur Set. D’ailleurs si vous utilisez Resharper, cet outil vous incitera à changer la syntaxe comme le montre la capture d’écran ci-dessous :

ReadonlyAutoProperty

Au final nous aurons le code suivant :

public class Product
{
	[Key]
	public Guid Reference { get; }

	public string Name { get; set; }

	public string Description { get; set; }

	public Product()
	{
		this.Reference = Guid.NewGuid();
	}
}

Sauf qu’avec cette dernière version de l’entité Product en essayant de mettre les fichiers de migrations de la base de données à jour via la commande suivante : add-migration ProductUpdated nous rencontrons l’erreur suivante dans le Package Manager Console :

EntityType ‘Product’ has no key defined. Define the key for this EntityType.
Products: EntityType: EntitySet ‘Products’ is based on type ‘Product’ that has no keys defined.

Bien que notre propriété est toujours décorée de l’attribut Key, l’entité Product est quand même considérée comme n’ayant pas défini une clé primaire d’après Entity Framework. Il faut se rappeler que la plomberie d’Entity Framework veut que toutes les propriétés définies au niveau de l’entité qui sont mappées à des colonnes d’une table de la base données soient définies avec un accesseur Get et un modificateur Set. Le modificateur doit être présent dans tous les cas même si sa visibilité est restreinte en utilisant le mot-clé private.

Ce n’est parce qu’on a une propriété automatique écrite avec la syntaxe { get; private set; } puis remplacée avec l’utilisation d’une propriété automatique en lecture seule { get; } que l’objectif initial reste inchangé et qu’un modificateur Set sera créé automatiquement une fois le code compilé. On peut le croire vu que l’assignation de la propriété existe toujours au niveau du constructeur.

Pour preuve la propriété Reference  définie en tant que propriété automatique avec { get; private set; } est générée comme suit en IL :

PrivateSet

La version IL de la propriété lorsqu’on utilise la propriété automatique en lecture seule avec la syntaxe C# 6 { get; } et avec toujours la présence de l’assignation de la propriété dans le constructeur :

Readonly auto-prop

Dans la dernière image on note l’absence d’un modificateur Set.

En conclusion, bien qu’on soit bien tenté par l’utilisation de la propriété automatique en lecture seule (surtout quand des outils comme ReSharper nous les proposent) on doit toujours se rappeler qu’Entity Framework a bien besoin de la présence d’un modificateur quelle que soit la visibilité de ce dernier pour les propriétés mappées aux tables de la base de données et ne manquera pas de vous le rappeler. Pour éviter à d’autres développeurs, qui seraient amenés à faire évoluer le modèle Entity Framework, de modifier toutes les propriétés { get; private set; } et les remplacer par { get; }, il faudra veiller à bien configurer les outils d’aide à la programmation.

Dans le cas de ReSharper, certaines propositions peuvent être désactivées au niveau du fichier comme dans l’image ci-dessous :

DisableReSharper

On peut aussi configurer la version de C# sur laquelle ReSharper doit se baser pour les différentes propositions en faisant F4 sur le projet concerné :

DisableReSharperProjectProperties

J’espère que ce billet vous a été utile 😉

ASP.Net MVC : Eviter de polluer le modèle de la vue avec des messages d’erreurs

22 avril 2017 1 commentaire

J’ai récemment eu à répondre à une question posée sur le site StackOverflow. Je pose le contexte. Nous avons un modèle suivant:

public class ForgotPasswordMV
{
    [Display(Name = "Enter your email"), Required]
    public string Email { get; set; }
}

Ce modèle est utilisé dans l’action d’un contrôleur comme suit :

[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Search(ForgotPasswordMV viewModel)
{
	if (Temp.Check(viewModel.Email))
		return RedirectToAction("VerifyToken", new { query = viewModel.Email });
	else
	{
		ViewBag.ErrorMessage = "Email not found or matched";
		return View();
	}
}

La question était de savoir si le fait d’utiliser la propriété dynamique ViewBag du contrôleur pour exposer le message d’erreur était une bonne pratique et que les recherches effectuées par le questionneur lui ont fait savoir qu’il fallait exposer une propriété au niveau du modèle.

Evidemment il est fortement recommandé de ne pas utiliser la propriété ViewBag étant donné qu’on ne bénéficie pas du typage fort. Si on veut communiquer avec la vue il faut toujours passer par un modèle typé. La solution proposée est donc légitime mais n’est pas une bonne pratique dans le cas de la gestion des erreurs du modèle dans le framework ASP.Net MVC.

La solution pour exposer les messages d’erreurs (comme dans l’exemple précédent qui n’utilise pas les attributs d’annotations de données) vient par l’utilisation de la méthode AddModelError de la classe ModelStateDictionary.  Nous n’avons pas besoin d’instancier cette classe étant donné qu’une propriété ModelState contenant une instance de cette classe existe déjà au niveau du contrôleur.

Du coup la bonne solution est de faire comme suit:

[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Search(ForgotPasswordMV viewModel)
{
    // ...
    else
    {
        this.ModelState.AddModelError("Email", "Email not found or matched");
        return View(viewModel);
    }
}

Il faut noter que cette méthode reçoit en premier paramètre le nom de la propriété du modèle à laquelle le message d’erreur est associé. Ainsi pour que pour ce message d’erreur soit affiché dans la vue à côté du champ Email il faut ajouter juste à côté de ce dernier la ligne suivante :

@Html.ValidationMessageFor(m => m.Email)

Cependant il est possible d’avoir un message d’erreur général à l’ensemble du modèle c’est à dire que ce message d’erreur n’est rattaché à aucune propriété du modèle. Pour cela il faudra utiliser une chaîne vide comme premier paramètre :

ModelState.AddModelError(String.Empty, "Email not found or matched");

Dans la vue Razor, il faudra utiliser la ligne suivante :

@Html.ValidationSummary(true, "The following error has occured:")

Le premier paramètre booléen indique qu’on ne veut pas afficher les messages d’erreur déjà rattachés à des propriétés du modèles.

Dans ce billet nous avons vu qu’on n’a pas besoin de polluer notre modèle et d’exposer des propriétés spécifiques aux messages d’erreur. Il suffit juste d’utiliser ce que nous offre le framework ASP.Net MVC pour nous faciliter la tâche.

J’espère que ce billet vous a été utile.

%d blogueurs aiment cette page :