首页 > 解决方案 > 减少/删除 ASP.NET Core 中的重复业务逻辑检查

问题描述

有没有办法减少/删除业务层中不断重复的用户访问检查(或其他一些检查)?

让我们考虑以下示例:具有一个实体的简单 CRUD 应用程序BlogPost

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

这种代码对于UpdateDelete方法以及将来可能添加的其他方法都是相同的,并且对于所有实体都是相同的。

有什么方法可以减少或完全消除这种重复?

我考虑了这一点并提出了以下解决方案,但它们并不能完全满足我。

第一个解决方案

使用过滤器。我们可以创建一些自定义过滤器[EnsureEntityExists][EnsureUserCanManageEntity]但是这样我们在 API 层中传播了一些业务逻辑,它不够灵活,因为我们需要为每个实体创建这样的过滤器。也许可以使用反射制作某种通用过滤器。

这种方法还有另一个问题,假设我们已经制作了这样的过滤器来检查我们的规则。我们从数据库中获取实体,进行检查,抛出异常和所有这些东西并让控制器方法执行。但是在服务层我们需要再次获取实体,所以我们要进行两次到 db 的往返。考虑到可以应用缓存的事实,也许我在考虑这个问题并且可以进行 2 次往返。

第二种解决方案

由于我使用的是 CQRS(或至少某种形式),因此我有MediatR库,我可以利用管道行为,甚至可以通过变异将获取的实体进一步传递到管道中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)。这看起来很蹩脚,让我想起了ViewBagASP.NET MVC。

第四种解决方案

它比解决方案更具增强性,但我们可以使用GuardClause或扩展方法来减少if语句的嵌套。

再说一次,也许我在想这个问题,或者这根本不是问题,或者那是一个设计问题。任何帮助,想法表示赞赏。

标签: c#asp.net-corecqrsmediatr

解决方案


如果您担心许多数据库调用,您可以尝试使用 LazyCache https://github.com/alastairtree/LazyCache之类的方式缓存每个请求返回的对象

我不建议跨请求缓存...

对于代码组织,我建议将授权逻辑提取到一个单独的方法中,并在每个请求中调用该方法。好处是如果逻辑发生变化,那么只需要在一个地方更新它。

例如这样的:

bool canEdit(userId){
    var user = getUserByUserId(userId);
    if(user.IsAdmin) return true;

    //depending on where this method lives might have access to blogpost here
    if(_blogPost.AuthorId == userId) return true;

    return false;
}

推荐阅读