logo

log de Julien Corioland (MVP)

Quand les technologies .NET deviennent passion...

archives : August 2011


[ASP.NET MVC] Authentification et utilisateurs anonymes

by Julien Corioland the 8/22/2011

Lorsque l’on développe une application web nécessitant de l’authentification, plusieurs stratégies sont possibles, par exemple :

  • On autorise l’accès anonyme partout, sauf sur les pages nécessitant que l’utilisateur soit authentifié
  • On refuse l’accès anonyme partout sauf sur les pages ne nécessitant pas que l’utilisateur soit authentifié

Depuis ASP.NET MVC 2 il est possible de réaliser la première option très facilement. En effet, il suffit de placer un filtre d’authorisation sur les méthodes des contrôleurs (entendre action) pour lesquelles il faut que l’utilisateur soit authentifié. Il s’agit de l’attribut AuthorizeAttribute :

[Authorize]
public ActionResult Admin()
{
    //code
    return View();
}

Sur certains projets, seules quelques pages ne nécessitent pas que l’utilisateur soit activée, il devient donc contraigant d’avoir à placer l’attribut Authorize sur TOUTES les méthodes de contrôleur. ASP.NET MVC 3 introduit un nouveau concept : les filtres globaux. Il s’agit en fait de la possibilité d’appliquer des filtres sur toutes les actions de tous les contrôleurs, par exemple.

Pour cela, il suffit de venir ajouter le filtre en question dans la liste des filtres globaux. Ceci se fait dans le fichier Global.asax. Depuis la version 3 d’ASP.NET MVC, une nouvelle méthode est générée : RegisterGlobalFilters :

public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
    filters.Add(new HandleErrorAttribute());
    filters.Add(new AuthorizeAttribute());
}

Cette méthode est ensuite appelée dans le Application_Start du même fichier :

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

    RegisterGlobalFilters(GlobalFilters.Filters);
    RegisterRoutes(RouteTable.Routes);
}

Si vous testez ce code, vous verrez que plus rien ne s’affiche, même pas la page d’authentification puisque vous demandez que l’utilisateur soit authentifié partout.

Pour rendre le process un peu plus permissif, il suffit de créer un nouvel attribut AnonymousAttribute qui servira à marquer les actions ne nécessitant pas que l’utilisateur soit authentifié :

[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
public class AnonymousAttribute : Attribute
{
}

A présent, il est possible de dériver le filtre AuthorizeAttribute et de surcharger la méthode OnAuthorization de celui-ci. Dans cette surcharge, il est possible de vérifier sur le contexte du filtre si l’action possède ou non l’attribut Anonymous décrit ci-dessus :

public class AuthenticationRequiredAttribute : AuthorizeAttribute
{
    public override void OnAuthorization(AuthorizationContext filterContext)
    {
        bool anonymousAllowed = filterContext.ActionDescriptor.IsDefined(typeof (AnonymousAttribute), false);

        if (!anonymousAllowed)
            base.OnAuthorization(filterContext);
    }
}
Du coup, il suffit d’enregister ce filtre plutôt que le AuthorizeAttribute dans le Global.asax
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
    filters.Add(new HandleErrorAttribute());
    filters.Add(new AuthenticationRequiredAttribute());
}

Puis de placer l’attribut Anonymous partout ou vous souhaitez conserver une accès anonyme (par exemple les actions LogOn) :

[Anonymous]
public ActionResult LogOn()
{
    return View();
}

[HttpPost]
[Anonymous]
public ActionResult LogOn(LogOnModel model, string returnUrl)
{
    //code
}

Du coup, le comportement désiré est bien obtenu : toutes les actions nécessitent que l’utilisateur soit authentifié, sauf celles où pour lesquelles l’accès anonyme est autorisé explicitement.

En espérant que cela serve à certains d’entre vous !

A bientôt Winking smile


read full post -

MishraReader Beta 1 dans les bacs !

by Julien Corioland the 8/12/2011

MishraReader est un client GoogleReader écrit en WPF 4.0, respectant le design Metro et disponible sur CodePlex (sources et exécutable) : http://mishrareader.codeplex.com

image

A l’initiative de David Catuhe ce projet regroupe une dizaine de développeurs français. J’y ai d’ailleurs moi-même écrit quelques modestes lignes de codes Smile

De nombreuses fonctionnalités ont été inclues dans cette première béta, pour une liste complète de celles-ci je vous invite à lire le billet de David sur son blog.

Pour tester l’application, rendez-vous sur CodePlex !

N’hésitez pas à faire des retours d’utilisation / propositions de fonctionnalités.

Bonne lecture et à bientôt Winking smile


read full post -

[Visual Studio] Développement de règles d’archivage personnalisées pour TFS

by Julien Corioland the 8/3/2011

Visual Studio permet d’exécuter du code avant chaque opération d’archivage afin de valider qu’un certain nombre de conditions soit réunies pour autoriser (ou non) le développeur à archiver son code : c’est ce qu’on appel des règles d’archivage (ou checkin policies).

Il existe déjà des règles “out of the box” qu’il est possible d’activer en faisant un clic droit sur le projet d’équipe pour lequel vous souhaitez activer une règle dans le Team Explorer, en sélectionnant l’entrée de menu Team Project Settings puis Source Control…

CheckinPolicies

Il est également possible de développer ses propres règles d’archivage. Pour cela, il vous faut installer le SDK Visual Studio (disponible via l’extension manager), si ce n’est pas déjà fait :

vs2010sdk

Créez une projet de type Class Library et ajoutez-y les références suivantes :

  • Microsoft.TeamFoundation.VersionControl.Client
  • Microsoft.TeamFoundation.WorkItemTracking.Client

Une checkin policy est en fait une classe qui dérive de PolicyBase et qui soit sérialisable. Pour l’exemple, nous allons créer une règle vérifiant qu’un work item ait bien été associé au changeset (déjà existante) mais en plus que l’état du work item n’est pas “Closed” :

[Serializable]
public class SampleCheckinPolicy : PolicyBase
{
    public override string Description
    {
        get { return "Cette règle d'archivage vérifie qu'aucun work item associé au changeset n'est en état Closed."; }
    }

    public override bool Edit(IPolicyEditArgs policyEditArgs)
    {
        return true;
    }

    public override PolicyFailure[] Evaluate()
    {
        throw new NotImplementedException();
    }

    public override string Type
    {
        get { return "SampleCheckinPolicy"; }
    }

    public override string TypeDescription
    {
        get { return "Cette règle d'archivage vérifie qu'aucun work item associé au changeset n'est en état Closed."; }
    }
}

PolicyBase est une classe abstraite, voilà les méthodes / propriété à redéfinir :

  • Description / Type Description : information à propos de la règle (pour affichage dans les interfaces de VS)
  • Type : nom de la règle (pour affichage également)
  • Edit : permet d’afficher une fenêtre dans le cas où l’on souhaite que la règle soit configurable
  • Evaluate : vérifie les conditions de la règles et retourne une liste d’erreur, si la règle n’est pas validée

La classe PolicyBase expose une propriété PendingChecking sur laquelle il est possible de récupérer les work items associées, les notes d’archivage… Il est donc possible de vérifier que des work items sont associés et qu’aucun n’est en état Closed :

public override PolicyFailure[] Evaluate()
{
    var failures = new List<PolicyFailure>();

    if (PendingCheckin.WorkItems.CheckedWorkItems.Any())
    {
        foreach (var workItemCheckinInfo in PendingCheckin.WorkItems.CheckedWorkItems)
        {
            if(workItemCheckinInfo.WorkItem.State == "Closed")
            {
                string message = String.Format("Le workitem #{0} est en état Closed. Archivage impossible",
                                               workItemCheckinInfo.WorkItem.Id);

                failures.Add(new PolicyFailure(message,this));
            } 
        }
    }
    else
    {
        failures.Add(new PolicyFailure("Aucun work item n'est associé au changeset !", this));
    }

    return failures.ToArray();
}

Tant que la méthode Evaluate retourne des erreurs, Visual Studio empêchera l’opération d’archivage (sauf si l’utilisateur contourne explicitement la règle).

La règle est prête, il ne reste plus qu’à la packager !

Pour cela ajoutez un projet d’extensibilité VSIX à la solution :

vsixproject

Ajoutez une référence vers la librairie contenant la règle à ce projet et vérifiez bien que la valeur “Copy Local” de cette référence est à vrai.

Il faut maintenant ajouter une fichier pkgdef au projet VSIX dans lequel sera renseigné l’enregistrement de la checkin policy dans le registre Windows. Ce fichier doit porter le nom du projet VSIX (SampleCheckinPolicyPackage.pkgdef, dans mon cas). Placez la propriété Include in VSIX à vrai dans les propriétés du fichier.

[$RootKey$\TeamFoundation\SourceControl\Checkin Policies] 
"SampleCheckinPolicy"="$PackageFolder$\SampleCheckinPolicy.dll"

Attention : Le nom de la clé doit correspondre à celui de la dll !

Il ne reste plus qu’à lancer le projet VSIX en debug dans l’instance expérimentale de Visual Studio ou d’installer le VSIX produit lors de la génération du projet.

Rendez-vous alors dans les paramètres du source contrôles pour le projet d’équipe désiré : vous devriez pouvoir ajouter la règle :

CheckinPolicies2

Tentez un archivage sans associer de Work Item ou en associant un Work Item en état “Closed” sur ce projet. Vous devriez voir apparaître les messages d’erreur de la règle dans la fenêtre Pending Changes :

AucunWI   WIStateInvalid

Téléchargez le code d’exemple : SampleCheckinPolicy.zip

A bientôt Winking smile


read full post -

[ASP.NET MVC] Validation de formulaire avec jQuery

by Julien Corioland the 8/1/2011

ASP.NET MVC 3 supporte de nouvelle fonctionnalités en terme de validation de formulaire côté client, à l’aide de la bibliothèque de jQuery et de son plugin dédié à la validation.

Dans ce post, nous verrons comment valider un formulaire côté client. Il faut garder à l’esprit que la validation côté client n’est là que pour rendre le formulaire plus ergonomique (il s’agit plutôt d’aide à la saisie) : il est impératif de toujours valider un modèle côté serveur !

Dans le fichier de configuration de votre application ASP.NET MVC 3, vous devriez voir les deux clés suivantes dans la section <appSettings /> :

<add key="ClientValidationEnabled" value="true" />
<add key="UnobtrusiveJavaScriptEnabled" value="true" />

La première, ClientValidationEnabled, permet de préciser que vous souhaitez que la validation des formulaires côté client (en Javascript donc) soit activée.

La seconde, UnobtrusiveJavaScriptEnabled, permet de préciser que vous souhaitez que du javascript “non intrusif” soit utilisé pour la validation. Le monteur ASP.NET MVC 3 rajoutera alors des attributs supplémentaires lors de la génération des champs d’un formulaire par l’intermédiaire d’un HtmlHelper (TextBox, TextBoxFor, CheckBox, CheckBoxFor…).

Ces attributs pourront ensuite être interprétés par un framework de validation JQuery : jQuery Validate (et jQuery Validate Unobtrusive).

Lorsque vous ajoutez une nouvelle vue à votre projet via l’interface Visual Studio prévue à cet effet, il est possible de cocher une case “Reference script libraries” afin que les scripts nécessaires à la mise en place de la validation soit ajoutés à la page :

image_thumb1

Cela aura pour effet d’ajouter les lignes suivantes à la vue :

<script src="@Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>

Il s’agit des scripts nécessaires pour faire fonctionner l’API jQuery Validate. L’ajout de ces deux références de scripts ainsi que les deux clés du fichier Web.config suffise à permettre la validation côté client :

image_thumb3

NB : pour que cela fonctionne, le modèle doit porter des attributs de validation ! (cf. exemple en bas de page)

Le markup HTML généré lors de l’appel du formulaire est le suivant :

<div class="editor-label">
    <label for="Title">Title</label>
</div>
<div class="editor-field">
    <input class="text-box single-line" data-val="true" data-val-required="Le champ titre est obligatoire" id="Title" name="Title" type="text" value="" />
    <span class="field-validation-valid" data-valmsg-for="Title" data-valmsg-replace="true"></span>
</div>

<div class="editor-label">
    <label for="Description">Description</label>
</div>
<div class="editor-field">
    <input class="text-box single-line" data-val="true" data-val-required="Le champ description est obligatoire" id="Description" name="Description" type="text" value="" />
    <span class="field-validation-valid" data-valmsg-for="Description" data-valmsg-replace="true"></span>
</div>

Comme il est possible de le constater, un certain nombre d’attribut / class ont été rajoutés sur les input afin de permettre la valdidation “non intrusive” :

  • data-val : indique que la validation doit avoir lieu sur cet élément
  • data-val-required : indique que le champ est requis et fournit le message d’erreur à afficher
  • data-val-message-for : indique que la balise span affiche le message d’erreur du champ description

Du coup, si JavaScript est activé sur la machine de l’utilisateur, les champs doivent être saisis convenablement avant le post du formulaire.

Il faut cependant valider le modèle côté serveur, dans le contrôleur :

if (ModelState.IsValid)
{
    using (var repo = new BlogRepository())
    {
        repo.AddBlog(blog);
        return RedirectToAction("Index", "Home");
    }
}

Si vous désactivez la validation non intrusive (via la clé de configuration UnobtrusiveJavaScriptEnabled vue précédemment) vous obtiendrez alors le markup suivant à la suite du formulaire :

<script type="text/javascript">
//<![CDATA[
if (!window.mvcClientValidationMetadata) { window.mvcClientValidationMetadata = []; }
window.mvcClientValidationMetadata.push({"Fields":[{"FieldName":"Title","ReplaceValidationMessageContents":true,"ValidationMessageId":"Title_validationMessage","ValidationRules":[{"ErrorMessage":"Le champ titre est obligatoire","ValidationParameters":{},"ValidationType":"required"}]},{"FieldName":"Description","ReplaceValidationMessageContents":true,"ValidationMessageId":"Description_validationMessage","ValidationRules":[{"ErrorMessage":"Le champ description est obligatoire","ValidationParameters":{},"ValidationType":"required"}]},{"FieldName":"IsPublished","ReplaceValidationMessageContents":true,"ValidationMessageId":"IsPublished_validationMessage","ValidationRules":[{"ErrorMessage":"The IsPublished field is required.","ValidationParameters":{},"ValidationType":"required"}]},{"FieldName":"PublishDate","ReplaceValidationMessageContents":true,"ValidationMessageId":"PublishDate_validationMessage","ValidationRules":[{"ErrorMessage":"The PublishDate field is required.","ValidationParameters":{},"ValidationType":"required"}]}],"FormId":"form0","ReplaceValidationSummary":false,"ValidationSummaryId":"validationSummary"});
//]]>
</script>

Projet web d’exemple : BlogSample.Mvc.zip

A bientôt Winking smile


read full post -

  • prev
  • next