Dans la première partie, nous avons abordé les différents manières de sécuriser un formulaire en ASP.NET MVC. C’est finit pour la théorie, passons à la pratique.
Nous allons créer une application qui contiendra une page de contact composé d’un formulaire et d’un Captcha :
- Le captcha sera configurable avec 3 modes de fonctionnement (mot, calcul ou un nombre) et sa taille.
- Une classe CaptchaService : qui contient les méthodes pour gérer notre Captcha.
- Un filtre d’action qui hérite de la classe ActionResult pour générer l’image.
- Un filtre d’action qui hérite de la classe ActionFilterAttribute pour la validation du Captcha.
- Un EditorTemplate pour générer le HTML du Captcha.
- Un formulaire de contact avec une classe “Captcha » et une classe “contact« .
Le Code
1- Structure du projet
Pour mener à bien notre projet, nous allons créer un formulaire de contact. Pour cela nous partons d’un projet Visual Studio ASP.NET MVC et nous ajoutons les classes et les dossiers correspondant à la capture ci-dessous :
La structure du projet étant classique nous ajoutons juste un dossier Captcha qui contiendra toutes nos classes pour le module captcha.
Dans un premier temps, exécuter la commande suivante dans Package Manager Console :
PM> Install-Package AntiXSS
Ou bien en cliquant sur “ Manage Nuget Package ” dans le menu contextuel de “ Références ” puis rechercher AntiXss
2- La configuration
Ensuite, nous allons ajouter dans la classe CaptchaSetting, le code suivant :
public enum TypeCaptcha
{
String,
Number,
Calcul
}
public class CaptchaSetting
{
public TypeCaptcha Type { get; set; }
public int Size { get; set; }
public CaptchaSetting()
{
Type = TypeCaptcha.String;
Size = 7;
}
}
On définit une énumération pour gérer les différents types de Captcha. Un constructeur par défaut a été ajouté pour mettre des valeurs par défaut à nos propriétés.
Maintenant nous allons écrire les méthodes dans le fichier CaptchaService.
3- Le service
Ouvrir le fichier CaptchaService. cs est mettre le code ci-dessous :
public static class CaptchaService
{
private static string _caractereArray = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
public static string RandomCaptcha(CaptchaSetting captchaSetting, HttpContextBase httpContextBase)
{
string captchaVal = string.Empty;
switch (captchaSetting.Type)
{
case TypeCaptcha.String:
captchaVal = Captcha(size:captchaSetting.Size, min:10, max:41);
httpContextBase.Session["captchaCode"] = captchaVal;
break;
case TypeCaptcha.Number:
captchaVal = Captcha(size: captchaSetting.Size, min: 0, max: 9);
httpContextBase.Session["captchaCode"] = captchaVal;
break;
case TypeCaptcha.Calcul:
string total = "";
captchaVal = CaptchaCalcul(captchaSetting.Size, out total);
httpContextBase.Session["captchaCode"] = total;
break;
}
return captchaVal;
}
public static bool IsValidCaptcha(string captchaCode, string captchaForm)
{
return captchaCode == captchaForm;
}
#region Private Method
private static string Captcha(int size, int min = 0, int max = 41)
{
var rand = new Random();
StringBuilder sb = new StringBuilder();
for (int i = 0; i < size; i++)
sb.Append(_caractereArray[rand.Next(min, max)]);
return sb.ToString();
}
private static string CaptchaCalcul(int size, out string total)
{
var rand = new Random();
int number1 = rand.Next(0, 50);
int number2 = rand.Next(0, 50);
total = Convert.ToString(number1 + number2);
return string.Format("{0} + {1} = ?", number1, number2);
}
#endregion
}
Notre service est composé de 4 méthodes permettant la gestion du Captcha.
- Random : retourne la valeur du Captcha et l’ajoute à une session
- IsValidCaptcha : retourne true ou false et nous sert à vérifier si le code entré par l’utilisateur correspond au code généré par le serveur.
- Captcha : retourne un string contenant soit une chaine de caractère ou un nombre.
- CaptchaCalcul : retourne un string avec l’opération et le paramètre “total”, et retourne la valeur de l’opération.
4- Préparation du formulaire de contact
Pour commencer, nous allons ajouter une propriété à la classe CaptchaModel :
public class CaptchaModel
{
[UIHint("CaptchaModel")]
public string Captcha { get; set; }}
L’attribut UIHint sert à définir un Template spécifique pour une propriété. En l’occurrence dans le code ci-dessus, on va créer un template d’édition nommée CaptchaModel pour la propriété Captcha. Pour cela, il faut créer dans le dossier “Shared”, le dossier “EditorTemplates” et créer un fichier nommé “CaptchaModel” qui est le nom du modèle de Captcha.
Ouvrir le fichier “CaptchaModel. cshtml” et ajouter le code ci-dessous :
@model MvcApplication4.Models.CaptchaModel
<img src="@Url.Action("GetImage")" alt="infoCaptcha" />
<div>
<input type="text" id="captchaForm" name="captchaForm" />
</div>
On hérite de la classe CaptchaModel pour avoir accès aux propriétés de celle-ci.
Maintenant nous allons créer des filtres, qui vont nous permettre de centraliser le code et de pourvoir les utiliser dans nos contrôleurs.
5-Les Filtres d’action
Pour favoriser la maintenance, nous allons utiliser les filtres d’action qui permettent de faire de l’AOP. L’AOP (Aspect Oriented Programming) permet de séparer les aspects techniques des aspects métiers.
Nous allons d’abord créer un CapthaResult qui hérite de la classe ActionResult et va nous permettre de retourner une image.
public class CaptchaResult : ActionResult
{
private CaptchaSetting _captchaSetting { get; set; }
public CaptchaResult(CaptchaSetting captchaSetting)
{
_captchaSetting = captchaSetting;
}
public override void ExecuteResult(ControllerContext context)
{
Bitmap bmp = new Bitmap(115, 30);
Font objFont = new Font("Arial", 20, FontStyle.Bold, GraphicsUnit.Pixel);
Graphics grap = Graphics.FromImage(bmp);
string reCaptcha = CaptchaService.RandomCaptcha(_captchaSetting, context.HttpContext);
grap.Clear(Color.White);
grap.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias;
grap.DrawString(reCaptcha, objFont, new SolidBrush(Color.FromArgb(102, 102, 102)), 0, 0);
grap.Flush();
HttpResponseBase response = context.HttpContext.Response;
response.ContentType = "image/Jpeg";
bmp.Save(response.OutputStream, System.Drawing.Imaging.ImageFormat.Jpeg);
bmp.Dispose();
}
}
Dans cette classe, on ajoute un constructeur avec comme paramètre notre CaptchaSetting afin de générer notre image grâce à la surcharge de la méthode ExecuteResult (cette méthode provient de la classe de la classe ActionResult dont on hérite).
Maintenant nous allons ajouter la validation dans la classe ValidationCaptcha.
public class ValidationCaptcha : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
string captchaCode = filterContext.HttpContext.Session["captchaCode"].ToString();
string captchaForm = filterContext.HttpContext.Request.Unvalidated.Form["captchaForm"].ToString();
filterContext.ActionParameters["isValidCaptcha"] = CaptchaService.IsValidCaptcha(captchaCode, captchaForm);
base.OnActionExecuting(filterContext);
}
}
Ce filtre permet de vérifier la validité du Captcha en décorant une action d’un contrôleur avec cet attribut.
On surcharge la méthode OnActionExecuting de la classe héritée ActionFilterAttribute. Dans cette méthode on compare la valeur contenue dans la session correspondant au Captcha et la valeur du formulaire grâce à l’object FilterContext. Le résultat de cette comparaison sera ajouté à notre paramètre de l’action isValidCaptcha.
Note : quand on ajoute l’attribut à une méthode de type ActionResult, il ne faut pas oublier d’ajouter le paramètre isCaptcha pour récupérer le résultat.
Maintenant nous allons nous intéresser au contrôleur.
6- Contrôleur
On va ajouter un contrôleur de base dans lequel on va créer une méthode virtuelle (virtual). Cette méthode pourra ainsi être surchargée dans les classes dérivant de ce contrôleur afin de définir une nouvelle configuration du captcha.
public class BaseController : Controller
{
public virtual CaptchaResult GetImage()
{
return new CaptchaResult(new CaptchaSetting());
}
}
Ensuite nous ajoutons le code ci-dessous à notre HomeController
public class HomeController : BaseController
{
public ActionResult Index()
{
return View();
}
public ActionResult Contact()
{
return View();
}
[HttpPost]
[ValidateAntiForgeryToken]
[ValidationCaptcha]
[ActionName("Contact")]
public ActionResult Create(ContactViewModel model, bool isValidCaptcha)
{
if (!isValidCaptcha)
{
TempData["info"] = "Le Captcha est non valide.";
return View("Contact", model);
}
TempData["info"] = "Le Captcha est valide.";
return RedirectToAction("Contact");
}
public override CaptchaResult GetImage()
{
var setting = new CaptchaSetting { Type = TypeCaptcha.Calcul };
return new CaptchaResult(setting);
}
}
Ce contrôleur possède 2 méthodes :
- Index : retourne une vue avec la création d’une instance de la classe ContactViewModel qui contient nos informations pour le formulaire.
- Create : qui est appelé quand on valide le formulaire. Les données du formulaire ne sont pas sauvegardées, on va juste tester la validité du Captcha : on retourne simplement un TempData et on retourne dans la page index contenant notre formulaire de contact.
Maintenant que toute l’implémentation côté serveur est terminée, nous allons créer notre page Index. chtml pour l’action Create du HomeController
7- Le formulaire
Le formulaire avec les propriétés contenues dans notre classe ContactViewModel.
On ajoute le code ci-dessous :
@model CaptchaMVC.ViewModels.ContactViewModel
@{
ViewBag.Title = "Formulaire de contact";
}
<h2>Contact</h2>
@if (TempData["info"] != null)
{
<div>
<p>@TempData["info"].ToString()</p>
</div>
}
@using (Html.BeginForm("Contact","Home", FormMethod.Post, new { id = "idForm"})) {
@Html.AntiForgeryToken()
@Html.ValidationSummary(true)
<fieldset>
<legend>Contact</legend>
<div class="editor-label">
@Html.LabelFor(model => model.Nom)
</div>
<div class="editor-field">
@Html.EditorFor(model => model.Nom)
@Html.ValidationMessageFor(model => model.Nom)
</div>
<div class="editor-label">
@Html.LabelFor(model => model.Prenom)
</div>
<div class="editor-field">
@Html.EditorFor(model => model.Prenom)
@Html.ValidationMessageFor(model => model.Prenom)
</div>
<div class="editor-label">
@Html.LabelFor(model => model.Email)
</div>
<div class="editor-field">
@Html.EditorFor(model => model.Email)
@Html.ValidationMessageFor(model => model.Email)
</div>
<div class="editor-label">
@Html.LabelFor(model => model.Commentaire)
</div>
<div class="editor-field">
@Html.EditorFor(model => model.Commentaire)
@Html.ValidationMessageFor(model => model.Commentaire)
</div>
<div class="editor-field">
@Html.EditorFor(model => model.Captcha)
</div>
<p>
<input type="submit" id="btnSubmit" value="Envoyer" />
</p>
</fieldset>
}
@section Scripts {
@Scripts.Render("~/bundles/jqueryval")
<script>
$("#idForm").submit(function()
{
if ($('#idForm').valid()) {
$("#btnSubmit").attr("disabled", "disabled");
return true;
}
});
</script>
}
8- Le résultat
Maintenant que tous les fichiers sont remplis, il nous reste plus qu’à compiler et lancer le site web pour obtenir ce résultat :
1er affichage | Valide | Non valide |
![]() | ![]() | ![]() |
Pour avoir un autre type de Captcha, on surcharge la méthode GetImage du BaseController dans le HomeController.
Exemple de surcharge :
public override CaptchaResult GetImage()
{
var setting = new CaptchaSetting { Type = TypeCaptcha.Calcul };
return new CaptchaResult(setting);
}
Conclusion
Au travers de ces articles, nous avons pu voir différentes fonctionnalités offertes par ASP.NET MVC pour sécuriser un formulaire.