Home - About me - Browse by categories

[ASP.NET MVC] Authentification et utilisateurs anonymes

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 image

read more

MishraReader Beta 1 dans les bacs !

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 image

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 image

read more

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

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…

image

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 :

image

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 :

image

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 :

image

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 :

image   image

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

A bientôt image

read more

[ASP.NET MVC] Validation de formulaire avec jQuery

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 :

<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

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

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 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 class="editor-label">
<label for="Description">Description</label>

<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>

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 image

read more

[ASP.NET MVC] Repository Entity Framework dans un contexte stateless

Les mappeurs objets relationnels comme Entity Framework apportent de nombreux avantages lors du développement d’application utilisant des bases de données comme système de stockage. Au delà d’effectuer un mapping entre le monde objet et le monde relationnel, permettre la génération de requête SQL à partir du langage .NET LINQ, Entity Framework fournit également un système de détection de changement sur les entités afin de générer uniquement le nécessaire lorsque l’on demande un commit sur la base de données.

L’idée est assez simple au final : toute entité chargée depuis un contexte Entity Framework possède un état :

  • Unchanged : l’entité n’est pas modifiée, aucune requête SQL ne sera générée pour celle-ci lors du commit
  • Added : l’entité a été ajoutée au contexte, un ordre INSERT sera généré lors du commit
  • Modified : l’entité a été modifiée depuis qu’elle a été chargée via le contexte, un ordre UPDATE sera généré lors du commit
  • Deleted : l’entité a été supprimée du contexte, un ordre DELETE sera généré lors du commit

Il existe un dernier état que peut prendre une entité : Detached, c’est à dire qu’elle n’a pas été récupérée via le contexte Entity Framework.

L’état d’une entité est maintenue au sein du contexte par l’ObjectStateManager. Dès lors que le contexte Entity Framework est disposé, toutes les informations relatives au suivi des changements sont perdues.

ASP.NET MVC de part sa nature stateless force une très courte durée de vie pour un contexte Entity Framework : j’envoie une requête HTTP au serveur, le contexte Entity Framework est créé, la récupération de données est effectuée, le contexte est libéré et la connection à la base de données fermée, impliquant que le suivi des changements soit impossible dans ce cas : comment replacer mon objet dans le contexte lors que je post un formulaire HTML ?

Pour l’exemple, nous allons travailler avec l’entity data model suivant :

image

Créeons à présent la classe repository qui va permettre d’effectuer les opérations de CRUD sur un item de type Blog :

public class BlogRepository : IDisposable
{
private readonly BlogsContainer _blogsContainer = new BlogsContainer();

public void AddBlog(Blog blog)
{
_blogsContainer.Blogs.AddObject(blog);
_blogsContainer.SaveChanges();
}

public Blog GetBlog(int blogId)
{
return _blogsContainer.Blogs.SingleOrDefault(b => b.Id == blogId);
}

public IEnumerable<Blog> GetBlogs()
{
return _blogsContainer.Blogs.ToList();
}

public void DeleteBlog(Blog blog)
{

}

public void UpdateBlog(Blog blog)
{

}

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

Comme il est possible de le constater dans l’extrait de code ci-dessus, les opérations Add/Get sont réalisées de manière tout à fait classique sur l’object context Entity Framework. Cela va en revanche différer pour les méthodes Delete et Update, volontairement laissées vides pour le moment.

En effet, dans un contexte stateless, il n’est pas possible de bénéficier directement du mécanisme de change tracking proposé par Entity Framework : il faut auparavant attacher l’objet au contexte s’il ne l’est pas déjà.

Pour cela, l’object context expose une propriété ObjectStateManager qui va permettre de récupérer une ObjectStateEntry associée a une entité : si celle-ci existe, l’élément est connu du contexte, sinon il est inconnu :

ObjectStateEntry stateEntry;
if (_blogsContainer.ObjectStateManager.TryGetObjectStateEntry(blog, out stateEntry))
{
//l'objet Blog est connu du change tracker
}
else
{
//l'objet blog est inconnu du change tracker
}

Du coup, dans le cas d’un delete, on vérifie si l’objet est connu du change tracker : si oui, pas de souci, sinon on attache l’objet au contexte avant de demander sa suppression :

public void DeleteBlog(Blog blog)
{
ObjectStateEntry stateEntry;
if (!_blogsContainer.ObjectStateManager.TryGetObjectStateEntry(blog, out stateEntry))
{
_blogsContainer.Blogs.Attach(blog);
}

_blogsContainer.Blogs.DeleteObject(blog);
_blogsContainer.SaveChanges();
}

De la même manière, pour la mise à jour il faut vérifier si l’objet est connu du change tracker. Si oui, on va pouvoir utiliser la méthode ApplyCurrentValues de l’object set Blogs pour appliquer les changements et forcer le passage de l’entité dans l’état Modified. Sinon, il faut forcer le changement d’état via l’object state manager, après avoir attacher l’entité blog :

public void UpdateBlog(Blog blog)
{
ObjectStateEntry stateEntry;
if (!_blogsContainer.ObjectStateManager.TryGetObjectStateEntry(blog, out stateEntry))
{
_blogsContainer.Blogs.Attach(blog);
_blogsContainer.ObjectStateManager.ChangeObjectState(blog, System.Data.EntityState.Modified);
}
else
{
_blogsContainer.Blogs.ApplyCurrentValues(blog);
}

_blogsContainer.SaveChanges();
}

Et voilà un repository ultra simple, fonctionnel et adapté au développement d’applications stateless avec ASP.NET MVC.

A bientôt image

read more