c# - How to create context object accessible application-wide to store current user details in data access?
问题描述
I have an n-tier web application with a Web UserInterface, BusinessLogicLayer and DataAccessLayer. I am looking for the best possible solution to pass my current logged in user's details all the way to the data-access without add it to all my method signatures. The user details are needed for auditing purposes. I believe you can create a context that is available application wide, is there anyone with an example of doing this? I am looking for the best design pattern that will separate my concerns.
解决方案
这里有两种方法:
首先
,如果其他层确实需要了解用户,这种方法更有意义。例如,他们可能会检查权限或根据用户是谁做出一些决定。
您可以创建一个抽象或接口来描述您希望这些类能够访问的内容,如下所示:
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
. 在运行时,它检索当前用户,然后做自己的事情来确定用户拥有什么权限。
如果你有很多类和方法,那么维护单独的包装类可能会变得乏味。另一种选择是使用拦截器,这可能意味着使用不同的依赖注入容器,如Windsor或Autofac。这些对于日志记录特别有用。
以 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
. 拦截器可以检查内部方法的属性,该属性指定需要哪些权限,并将其与当前用户的权限进行比较。
您通常可以编写一个拦截器并将其与许多其他类一起使用,因为它不需要了解有关内部目标类的任何信息。但是,如果您需要,您还可以编写更多用途更窄的拦截器,仅用于某些类。
好的是它为您提供了很大的灵活性,但不会触及您的业务逻辑或数据类的实际接口或实现。他们可以专注于他们的单一职责,而其他类被配置为记录对他们的请求或检查用户权限。
推荐阅读
- javascript - 使用javascript更改子元素的innerHTML
- jenkins - Jenkins 插件开发 - 如何检查插件是否在 Jenkins Master 上执行
- grails - grails 3.x中grails 2.x的Config.groovy对应的神器是什么?
- regex - 多个正则表达式匹配不是 Haskell 中的 [String]
- python - 对特定列 python 取每一行的正弦值
- python - 如何通过登录 Seaborn 等量缩放 x 和 y 轴?
- c# - 正则表达式模式不在 c# 中工作,但在在线正则表达式测试器中工作
- .htaccess - .htaccess 将 example.com 重定向到 example.com/admin_panel
- html - CSS 边框半径仅围绕模态 div 窗口的一个角
- angularjs - AngularUI Calendar 获取事件对象后获取范围