如何在应用程序范围内创建可访问的上下文对象,以便在数据访问中存储当前用户

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了如何在应用程序范围内创建可访问的上下文对象,以便在数据访问中存储当前用户相关的知识,希望对你有一定的参考价值。

我有一个带有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。拦截器可以检查内部方法的属性,该属性指定需要哪些权限并将其与当前用户的权限进行比较。

您通常可以编写一个拦截器并将其与许多其他类一起使用,因为它不需要了解内部目标类的任何信息。但是如果你需要,你也可以编写更多用于某些类的狭义目的拦截器。

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

以上是关于如何在应用程序范围内创建可访问的上下文对象,以便在数据访问中存储当前用户的主要内容,如果未能解决你的问题,请参考以下文章

如何使大量现有函数在一个类的范围内可用?

Sling/OSGi 请求范围对象问题。如何访问过滤器和服务类中对象的相同实例(在相同的请求上下文中)?

JSP的范围(作用域)

打字稿:如何在全局范围内进行一些导入?

Jsp中的pageContext对象

数据“未在此范围内声明的成员”