假设我们有一个多租户博客应用程序。该应用程序的每个用户可能拥有多个由该服务托管的博客。
我们的 API 允许读取和写入博客文章。在某些情况下,指定 BlogId 是可选的,例如,获取用 ASP.NET 标记的所有帖子:
/api/posts?tags=aspnet
如果我们想查看某个网站上所有带有 ASP.NET 标签的帖子specific博客,我们可以要求:
/api/posts?blogId=10&tags=aspnet
一些API方法require有效的 BlogId,例如创建新博客文章时:
POST: /api/posts
{
"blogid" : "10",
"title" : "This is a blog post."
}
BlogId 需要在服务器上进行验证,以确保它属于当前(经过身份验证的)用户。我也想要infer如果请求中未指定,则为用户的默认 blogId(为简单起见,您可以假设默认是用户的第一个博客)。
我们有一个IAccountContext
包含当前用户信息的对象。如果需要的话可以注射。
{
bool ValidateBlogId(int blogId);
string GetDefaultBlog();
}
在 ASP.NET Web API 中,推荐的方法是:
- 如果在消息正文或 uri 中指定了 BlogId,请验证它以确保它属于当前用户。如果没有则抛出 400 错误。
- 如果请求中未指定 BlogId,则从以下位置检索默认 BlogId
IAccountContext
并使其可供控制器操作使用。我不希望控制器知道这个逻辑,这就是为什么我不想打电话IAccountContext
直接来自我的行动。
[Update]
经过 Twitter 上的讨论并考虑到 @Aliostad 的建议,我决定将博客视为一种资源,并将其作为我的 Uri 模板的一部分(因此始终是必需的),即
GET api/blog/1/posts -- get all posts for blog 1
PUT api/blog/1/posts/5 -- update post 5 in blog 1
我用于加载单个项目的查询逻辑已更新为按帖子 id 和博客 id 加载(以避免租户加载/更新其他人的帖子)。
剩下要做的唯一一件事就是验证 BlogId。遗憾的是我们不能在 Uri 参数上使用验证属性,否则 @alexanderb 的建议会起作用。相反,我选择使用 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
}
}
验证 blogId 现在只是装饰我的控制器/操作的一个例子[ValidateBlog]
.
几乎每个人的答案都对解决方案有所帮助,但我已将答案标记为 @alexanderb,因为它没有耦合控制器内的验证逻辑。