假设我们有一个多租户博客应用程序.该应用程序的每个用户可以具有由该服务托管的多个博客.
我们的API允许阅读和撰写博客文章.在某些情况下,指定BlogId是可选的,例如,获取用ASP.NET标记的所有帖子:
/api/posts?tags=aspnet
Run Code Online (Sandbox Code Playgroud)
如果我们想查看特定博客上标记为ASP.NET的所有帖子,我们可以请求:
/api/posts?blogId=10&tags=aspnet
Run Code Online (Sandbox Code Playgroud)
某些API方法需要有效的BlogId,例如在创建新博客帖子时:
POST: /api/posts
{
"blogid" : "10",
"title" : "This is a blog post."
}
Run Code Online (Sandbox Code Playgroud)
需要在服务器上验证BlogId以确保它属于当前(经过身份验证的)用户.如果未在请求中指定,我还想推断用户的默认blogId(为简单起见,您可以假设默认是用户的第一个博客).
我们有一个IAccountContext包含有关当前用户的信息的对象.如有必要,可以注入.
{
bool ValidateBlogId(int blogId);
string GetDefaultBlog();
}
Run Code Online (Sandbox Code Playgroud)
在ASP.NET Web API中,推荐的方法是:
IAccountContext并使其可用于控制器操作.我不希望控制器知道这个逻辑,这就是为什么我不想IAccountContext直接从我的动作中调用.在关于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
Run Code Online (Sandbox Code Playgroud)
我的用于加载单个项目的查询逻辑已更新为按帖子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
}
}
Run Code Online (Sandbox Code Playgroud)
验证blogId现在只是一个装饰我的控制器/动作的情况[ValidateBlog].
事实上,每个人的答案都有助于解决方案,但我已将@ alexanderb标记为答案,因为它没有将验证逻辑耦合到我的控制器中.
Ali*_*tad 11
我担心这可能不是你正在寻找的答案类型,但它可能会给讨论添加一点点谦虚.
因为你需要推断出你所看到的所有麻烦,你会看到所有麻烦blogId吗?我认为这就是问题所在.REST是关于无状态的,而你似乎在服务器上持有一个单独的状态(上下文),它与HTTP的无状态特性发生冲突.
BlogId何时是操作的一个组成部分,需要明确地成为资源标识符的一部分 - 因此我将其简单地放在URL中.如果不这样做,这里的问题是URL/URI并不真正唯一地标识资源 - 与名称暗示不同.如果John去那个资源,那么就会看到与Amy不同的资源.
这将简化设计,这也是有说服力的.当设计正确时,一切都很好.我努力实现简单.
这是方法,我将如何实现(考虑到,我不是ASP.NET Web API专家).
所以,所有的拳头 - 验证.你需要一个简单的模型,如下所示:
public class BlogPost
{
[Required]
[ValidateBlogId]
public string BlogId { get; set; }
[Required]
public string Title { get; set; }
}
Run Code Online (Sandbox Code Playgroud)
对于此模型,最好实现自定义验证规则.如果blogId可用,它将根据规则进行验证.实施可能是,
public class ValidateBlogId : ValidationAttribute
{
[Inject]
public IAccountContext Context { get; set; }
public override bool IsValid(object value)
{
var blogId = value as string;
if (!string.IsNullOrEmpty(blogId))
{
return Context.ValidateBlogId(blogId);
}
return true;
}
}
Run Code Online (Sandbox Code Playgroud)
(这里和之后,我假设使用Ninject,但你可以在没有它的情况下继续).
接下来,您不希望公开blogId初始化的详细信息.该工作的最佳候选人是动作过滤器.
public class InitializeBlogIdAttribute : ActionFilterAttribute
{
[Inject]
public IAccountContext Context { get; set; }
public override void OnActionExecuting(HttpActionContext actionContext)
{
var blogPost = actionContext.ActionArguments["blogPost"] as BlogPost;
if (blogPost != null)
{
blogPost.BlogId = blogPost.BlogId ?? Context.DefaultBlogId();
}
}
}
Run Code Online (Sandbox Code Playgroud)
因此,如果blogPost模型绑定并且没有Id,则将应用默认值.
所以,最后是API控制器
public class PostsController : ApiController
{
[InitializeBlogId]
public HttpResponseMessage Post([FromBody]BlogPost blogPost)
{
if (ModelState.IsValid)
{
// do the job
return new HttpResponseMessage(HttpStatusCode.Ok);
}
return new HttpResponseMessage(HttpStatusCode.BadRequest);
}
}
Run Code Online (Sandbox Code Playgroud)
而已.我刚刚在我的VS中尝试过,似乎很有效.
我认为它应该满足你的要求.
| 归档时间: |
|
| 查看次数: |
12966 次 |
| 最近记录: |