【发布时间】:2021-06-15 19:48:30
【问题描述】:
有没有办法减少/删除业务层中不断重复的用户访问检查(或其他一些检查)?
让我们考虑以下示例:具有一个实体 BlogPost 的简单 CRUD 应用程序:
public class BlogPost
{
public int Id { get; set; }
public string Title { get; set; }
public string Body { get; set; }
public int AuthorId { get; set; }
}
在修改或删除实体之前的 PUT/DELETE 请求中,我需要检查发出请求的用户是否是 BlogPost 的作者,以便允许他删除/编辑它。
所以无论是在UpdateBlogPost 和DeleteBlogPost 还是想象中的BlogPostService,我都必须这样写:
var blogPostInDb = _blogPostRepository.GetBlogPost();
if(blogPostInDb == null)
{
// throw exception or do whatever is needed
}
if(blogPostInDb.AuthorId != _currentUser.Id)
{
// throw exception etc...
}
这种代码对于 Update 和 Delete 方法以及将来可能添加的其他方法是相同的,并且对于所有实体都是相同的。
有没有办法减少或完全消除这种重复?
我考虑了这一点并想出了以下解决方案,但它们并不能完全满足我。
第一个解决方案
使用过滤器。我们可以创建一些自定义过滤器,例如[EnsureEntityExists] 和[EnsureUserCanManageEntity],但是这样我们在 API 层中传播了一些业务逻辑,它不够灵活,因为我们需要为每个实体创建这样的过滤器。也许可以使用反射来制作某种通用过滤器。
另外这种方法还有另一个问题,假设我们已经制作了这样的过滤器来检查我们的规则。我们从数据库中获取实体,进行检查,抛出异常和所有这些东西并让控制器方法执行。但是在服务层我们需要再次获取实体,所以我们要进行两次到 db 的往返。考虑到可以应用缓存这一事实,也许我在考虑这个问题并且可以进行 2 次往返。
第二种解决方案
由于我使用的是 CQRS(或至少是某种 CQRS)我有 MediatR 库,我可以使用 Pipeline Behaviors 甚至通过变异 TRequest 将获取的实体进一步传递到管道中(我没有不想做)。此解决方案需要一些通用接口,以便所有请求能够检索实体的 id。往返问题也适用于此。
public interface IBlogPostAccess
{
public int Id { get; set; }
}
public class ChangeBlogPostCommand: IRequest, IBlogPostAccess
{
// ...
}
public class DeleteBlogPostCommand: IRequest, IBlogPostAccess
{
// ...
}
public class BlogPostAccessBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse> where TRequest : IBlogPostAccess
{
// all nessesary stuff injected via DI
public BlogPostAccessBehavior()
{
}
public async Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate<TResponse> next)
{
var blogPostInDb = _blogPostRepository.GetBlogPost(request.Id);
if(blogPostInDb == null)
{
// throw exception or do whatever is needed
}
if(blogPostInDb.AuthorId != _currentUser.Id)
{
// throw exception etc...
}
return await next();
}
}
第三种解决方案
创建类似请求上下文服务的东西。以一种非常简化的方式,它将是一个字典,它将在我们可以存储数据的请求中持久保存(在这种情况下,我们已经在过滤器/管道中获取了我们的 BlogPost)。这看起来很蹩脚,让我想起了 ASP.NET MVC 中的 ViewBag。
第四种解决方案
它比解决方案更多的是增强,但我们可以使用GuardClause 或扩展方法来减少if 语句的嵌套。
再一次,也许我想多了这个问题,或者根本不是问题,或者那是设计问题。任何帮助,想法表示赞赏。
【问题讨论】:
-
我建议您改为实现自定义授权。您可以查找基于策略的授权,或者更具体地说,查找基于资源的授权。
-
将通用代码委托给单独的可重用验证方法怎么样?
-
@RodrigoRodrigues 基于策略的授权是关于索赔 afaik。它是通过 API 端的
AuthorizationHandler实现的,但我认为我们可以在处理程序方法中调用服务/命令。谢谢,我去看看! -
@WiktorZychla 使用默认的“基于服务”的应用程序可能看起来不错。另外,也许我们将验证提取到命令中也是一种选择。感谢您的想法
-
@AnonAnon 基于策略的授权并不总是关于声明,而是关于 AuthorizationRequirement 和 AuthorizationHandler。实际上,基于声明的身份验证和基于角色的身份验证也使用 AuthorizationRequirement 和它们自己的 AuthorizationHandler,但它们专门用于检查 User.Claims 和 User.IsInRole。
标签: c# asp.net-core cqrs mediatr