La validation et la Manipulation de paramètres d'entrée multi-locataire de l'API web
Supposons que nous avons un multi-locataire en application de blog. Chaque utilisateur de l'application peut avoir un certain nombre de blogs hébergés par le service.
Notre API permet la lecture et l'écriture de messages de blog. Dans certains cas, la spécification d'un BlogId est facultatif, par exemple, l'obtention de tous les posts tagged avec ASP.NET:
/api/posts?tags=aspnet
Si nous voulions afficher tous les articles taggés avec ASP.NET sur un spécifiques blog, nous pourrions nous demander:
/api/posts?blogId=10&tags=aspnet
Certaines méthodes de l'API de besoin valide BlogId, comme lors de la création d'un nouveau blog:
POST: /api/posts
{
"blogid" : "10",
"title" : "This is a blog post."
}
La BlogId doit être validé sur le serveur pour s'assurer qu'elle appartient au courant (authentifié) de l'utilisateur. Je tiens également à déduire par défaut de l'utilisateur blogId si elle n'est pas spécifié dans la demande (pour plus de simplicité vous pouvez supposer que la valeur par défaut est l'utilisateur du premier blog).
Nous avons une IAccountContext
objet qui contient des informations sur l'utilisateur actuel. Cela peut être injecté si nécessaire.
{
bool ValidateBlogId(int blogId);
string GetDefaultBlog();
}
Dans ASP.NET Web API quelle serait l'approche recommandée pour:
- Si le BlogId est indiqué dans le corps du message ou d'uri, de les valider pour s'assurer qu'il appartient à l'utilisateur actuel. Jeter une erreur 400 si ce n'.
- Si le BlogId n'est pas spécifié dans la demande, de récupérer la valeur par défaut BlogId de
IAccountContext
et de mettre à la disposition du contrôleur de l'action. Je ne veux pas que le contrôleur soit au courant de cette logique, c'est pourquoi je ne veux pas l'appelerIAccountContext
directement à partir de mon action.
[Mise à jour]
À la suite de discussions sur Twitter, et en tenant compte @Aliostad les conseils, j'ai décidé de traiter le Blog en tant que ressource et de faire partie de mon modèle Uri (il est donc toujours nécessaire) c'est à dire
GET api/blog/1/posts -- get all posts for blog 1
PUT api/blog/1/posts/5 -- update post 5 in blog 1
Ma logique de requête pour le chargement des éléments uniques a été mis à jour à la charge par la Poste id et le blog id (pour éviter les locataires de chargement/mise à jour des autres peuples postes).
La seule chose à faire était de valider la BlogId. C'est une honte que nous ne pouvons pas utiliser les attributs de validation sur les paramètres Uri sinon @alexanderb recommandation aurait travaillé. Au lieu de cela j'ai opté pour l'utilisation d'un ActionFilter:
public class ValidateBlogAttribute : ActionFilterAttribute
{
public IBlogValidator Validator { get; set; }
public ValidateBlogAttribute()
{
//set up a fake validator for now
Validator = new FakeBlogValidator();
}
public override void OnActionExecuting(HttpActionContext actionContext)
{
var blogId = actionContext.ActionArguments["blogId"] as int?;
if (blogId.HasValue && !Validator.IsValidBlog(blogId.Value))
{
var message = new HttpResponseMessage(HttpStatusCode.BadRequest);
message.ReasonPhrase = "Blog {0} does not belong to you.".FormatWith(blogId);
throw new HttpResponseException(message);
}
base.OnActionExecuting(actionContext);
}
}
public class FakeBlogValidator : IBlogValidator
{
public bool IsValidBlog(int blogId)
{
return blogId != 999; //so we have something to test
}
}
De la validation de la blogId est tout simplement un cas de décorer mon contrôleur/action avec [ValidateBlog]
.
Pratiquement tous les réponses ont aidé dans la solution, mais j'ai marqué comme @alexanderb est comme la réponse puisqu'il n'a pas couple la logique de validation à l'intérieur de mon contrôleur.
- J'ai trouvé quelques incohérences dans cette question ou j'ai mal compris. Vous avez dit
Each user of the application may have a number of blogs hosted by the service
et en même tempsIf the BlogId is not specified in the request, retrieve the default BlogId from IAccountContext and make it available to the controller action
. Si un utilisateur a une chance d'avoir plusieurs blogs à l'intérieur de ce service, comment voulez-u de récupérer un? - J'ai mis à jour ma question pour mieux expliquer. Si aucun blogId est spécifié, nous supposons que l'utilisateur par défaut du blog Id (dans la plupart des cas l'utilisateur aura seulement un blog).
Vous devez vous connecter pour publier un commentaire.
Ici est le chemin, la façon dont j'aurais mis en œuvre (en prenant en compte, je ne suis pas ASP.NET l'API Web expert).
Donc, le poing de l'ensemble de validation. Vous avez besoin d'avoir un modèle simple, comme ceci:
Pour ce modèle, il est préférable de mettre en œuvre règle de validation personnalisée. Dans le cas où si
blogId
est disponible, il serait de valider à l'encontre de la règle. La mise en œuvre pourrait être,(ici et après, je suis en supposant à utiliser Ninject, mais vous pouvez aller de l'avant sans lui).
Prochaine, vous ne voulez pas exposer les détails de
blogId
d'initialisation. Meilleur candidat pour l'emploi est une action de filtre.Donc, si l'article sur le blog modèle est appliqué et il n'a pas d'Id, la valeur par défaut sera appliquée.
Donc, finalement, l'API contrôleur
Que c'est. J'ai rapidement essayé dans mon VS, semble fonctionner.
Je pense qu'il devrait répondre à vos exigences.
Je crains que tout cela n'est probablement pas le type de réponse que vous cherchez, mais bon, ça pourrait ajouter un peu humble à la discussion.
Vous voir tous les ennuis que vous allez à travers et de sauter tous les cercles que vous avez besoin d'en déduire la
blogId
? Je pense que c'est le problème. Le REPOS est tout au sujet de apatrides, alors que vous semblez à la tenue d'un état séparé (contexte) sur le serveur qui entre en conflit avec d'apatride, de la nature de HTTP.BlogId quand est une partie intégrante de l'opération, doit être explicitement partie de l'identificateur de ressource - donc je voudrais le mettre tout simplement dans l'URL. Si vous ne le faites pas, le problème ici est la URL/URI n'a pas vraiment unique identifier une ressource - contrairement à l'indique son nom. Si Jean va à cette ressource voit une autre ressource à partir de quand Amy n'.
Cela permettra de simplifier la conception, qui est également révélateur. Quand le design est bon, tout fonctionne très bien. Je m'efforce d'atteindre la simplicité.
Probablement que vous pourriez utiliser HttpParameterBinding aussi pour votre scénario. Vous pouvez regarder les posts de Mike et Hongmei pour plus de détails.
Exemple ci-dessous:
}
[Mis à JOUR]
Mon collègue Youssef a proposé un très facile d'approche à l'aide de ActionFilter. Voici un exemple d'utilisation de cette approche: les
De ne pas l'ensemble de l'action du contrôleur exige, normalement je devait mettre en œuvre une Action de Filtre pour ce but et de faire de la validation de là, mais à vos exigences ont d'autres préoccupations, ce qui rend cette option, pas une option.
Aussi, j'aurais besoin pour le client d'envoyer le
BlogId
dans le cadre de l'Uri, parce que dans cet endroit, vous voulez éviter supplémentaire de la désérialisation de l'organisme (comme vous ne voulez pas gérer cela à l'intérieur de l'action du contrôleur).Vous avez certaines conditions ici et il a de l'importance:
vous voulez retourner à 400 Bad Request.
Compte tenu de ces exigences, la meilleure option est de gérer cela par le contrôleur de base. Peut-être pas une bonne option pour vous, mais les poignées de toutes vos exigences:
Vous pouvez également envisager de mettre en œuvre la Ivalidatableobjet pour votre RequestModel mais qui pourraient rendre votre modèle un peu couplé avec un autre des parties de votre application