繁体   English   中英

如何在整个应用程序范围内创建可访问的上下文对象,以在数据访问中存储当前用户详细信息?

[英]How to create context object accessible application-wide to store current user details in data access?

我有一个带有Web UserInterface,BusinessLogicLayer和DataAccessLayer的n层Web应用程序。 我正在寻找最佳解决方案,以将当前登录用户的详细信息一直传递到数据访问,而不将其添加到我的所有方法签名中。 需要用户详细信息才能进行审核。 我相信您可以创建一个在整个应用程序范围内都可用的上下文,有人可以这样做吗? 我正在寻找将我的关注点分开的最佳设计模式。

这是两种方法:

第一
如果其他层实际上需要了解用户,则此方法更有意义。 例如,他们可能检查权限或根据用户身份做出某些决定。

您可以创建一个抽象或接口来描述您希望这些类能够访问的内容,如下所示:

public interface IUserContext
{
    SomeClassContainingUserData GetCurrentUser();
}

我将根据使用者类的需求来定义该用户数据类,而不是仅仅使用一些现有的Customer类,以防止其与您的Web应用程序紧密耦合。

现在,您可以将该类注入其他类:

public class MyBusinessLogicClass
{
    private readonly IUserContext _userContext;

    public MyBusinessLogicClass(IUserContext userContext)
    {
        _userContext = userContext;
    }

    public void SomeOtherMethod(Whatever whatever)
    {
        var user = _userContext.GetCurrentUser();
        // do whatever you need to with that user
    }
}

这样可以使其他类保持可测试性,因为很容易注入返回所需内容的接口模拟,因此可以确保您的类对于不同类型的用户而言行为正确。

如果您的用户数据来自HttpContext则您的运行时实现可能大致如下所示:

public class HttpUserContext
{
    private readonly IHttpContextAccessor _contextAccessor;

    public HttpUserContext(IHttpContextAccessor contextAccessor)
    {
        _contextAccessor = contextAccessor;
    }

    public SomeClassContainingUserData GetCurrentUser()
    {
        var httpContext = _contextAccessor.HttpContext;
        // get the user data from the context and return it.
    }
}

那是一个粗略的轮廓。 一个考虑因素是范围界定。 如果所有对象都按请求确定作用域,则IUserContext实现可以一次生成用户数据并将其存储在成员变量中,而不是一遍又一遍地访问它。

缺点是必须将其注入所有地方,但是如果这些类需要该信息,那是不可避免的。

第二
如果那些内部类实际上根本不需要用户信息怎么办? 如果您只想记录哪些用户发出了由这些类处理的请求,该怎么办? 如果要一个单独的对象检查权限怎么办?

在那种情况下,选项可以是拦截器或包装器。 最简单的形式可能是这样的:

public class SomeBusinessClassSecurityInterceptor : ISomeBusinessClass
{
    private readonly ISomeBusinessClass _inner;
    private readonly IUserContext _userContext;

    public SomeBusinessClassSecurityInterceptor(
        ISomeBusinessClass inner, IUserContext userContext)
    {
        _inner = inner;
        _userContext = userContext;
    }

    public void UpdateSomeData(Foo data)
    {
        if(!CanUpdate()) 
            throw new YouCantUpdateThisException();
        _inner.UpdateSomeData(data);
    }

    private bool CanUpdate()
    {
        var user = _userContext.GetCurrentUser();
        // make some decision based on the user
    }   
}

如果更多地涉及到为用户检索权限,则可能需要IUserPermissions并注入它而不是IUserContext 然后注入IUserContext到的实行IUserPermissions 在运行时,它会检索当前用户,然后自行决定该用户拥有哪些权限。

如果您有很多类和方法,那么维护单独的包装器类可能会变得乏味。 另一种选择是使用拦截器,这可能意味着使用诸如WindsorAutofac之类的其他依赖项注入容器。 这些对于日志记录尤其有用。

以Autofac为例,这意味着编写这样的类:

public class LoggingInterceptor : IInterceptor
{
    private readonly IUserContext _userContext;
    private readonly ILogger _logger;

    public CallLogger(IUserContext userContext, ILogger logger)
    {
        _userContext = userContext;
        _logger = logger;
    }

    public void Intercept(IInvocation invocation)
    {
        var user = _userContext.GetCurrentUser();
        _logger.Log( 
         // some stuff about the invocation, like method name and maybe parameters)
         // and who the user was.
         // Or if you were checking permissions you could throw an exception here.

        invocation.Proceed();
    }
}

然后,您将告诉容器,对给定类的“真实”实现的所有调用都将通过此拦截器(在其文档中有很好的描述)。

如果您正在检查权限,则可以注入IUserPermissions 拦截器可以检查内部方法的属性,该属性指定所需的权限,并将其与当前用户的权限进行比较。

通常,您可以编写一个拦截器,并将其与其他许多类一起使用,因为它无需了解内部目标类的任何知识。 但是,如果需要,您还可以编写仅用于某些类的用途更窄的拦截器。

很好的是,它为您提供了很大的灵活性,但没有涉及业务逻辑或数据类的实际接口或实现。 他们可以专注于自己的单一职责,而其他类则配置为记录对其发出的请求或检查用户权限。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM