如何使用依赖注入处理 Asp.net MVC 应用程序中的会话信息
How to handle session information in an Asp.net MVC app with dependency injection
我有一个多租户应用程序,我想在应用程序中跟踪两件事:用户和租户。
我有这三种情况:
- 匿名用户:在这种情况下 user 为空,并且 tenant 由查询字符串跟踪。
- 已验证用户:用户来自cookie,租户保存在会话中。
- 工作:我的应用程序中有一些工作要做,在这种情况下 user 为 null 并且 tenant 已设置手动。
在我所有的服务中,我都使用用户和租户。
起初我想在我的 DI 容器中为每个 http 会话使用一个对象,但这不适用于 Jobs。
关于如何处理这些信息的任何问题,我目前在 Session 变量中使用它,但我在这个实现中有很多问题,我需要有很多特殊情况来处理工作和未经身份验证用户。
您至少应该做的是抽象化对用户上下文的访问。例如:
public interface IUserContext {
IPrincipal User { get; }
Tenant Tenant { get; }
}
您可能希望有一个单独的 ITenantContext
抽象,但我们现在坚持使用一个。
您的系统可能有两个应用程序:windows 运行作业的服务和一个处理用户交互的 Web 应用程序。这两个应用程序都有自己的入口点、自己的 Composition Root 和自己独特的 DI 配置。
对于 Web 应用程序,我想象的实现如下所示:
public AspNetUserContext : IUserContext
{
private readonly ITenantRepository tenantRepository;
public AspNetUserContext(ITenantRepository tenantRepository) {
this.tenantRepository = tenantRepository;
}
public IPrincipal User {
get { return HttpContext.Current.User; }
}
public Tenant Tenant {
get {
if (this.User.Identity.IsAuthenticated) {
return this.tenantRepository.GetByName(
HttpContext.Current.Session["tenant"]);
} else {
return this.tenantRepository.GetByName(
HttpContext.Current.Request.QueryString["tenant"]);
}
}
}
}
对于作业服务,乍一看可能完全不同,但是对于该服务,处理一个作业可以被视为一个请求。所以这意味着当请求开始时,您需要设置上下文(ASP.NET 在后台为我们做的事情)。 IUserContext
可能如下所示:
public JobServiceUserContext : IUserContext
{
[ThreadStatic]
private static IPrinciple user;
[ThreadStatic]
private static Tenant tenant;
public IPrincipal User {
get { return this.user; }
set { this.user = user; }
}
public IPrincipal User {
get { return this.tenant; }
set { this.tenant = tenant; }
}
}
现在作业的执行可以用一些设置适当上下文的逻辑来包装,例如:
public class JobRunner {
private readonly JobServiceUserContext context;
private readonly IJobDactory jobFactory;
public JobServiceUserContext(JobServiceUserContext context,
IJobDactory jobFactory) {
this.context = context;
this.jobFactory = jobFactory;
}
public void RunJob(JobDetails job) {
try {
this.context.User = job.User;
this.context.Tenant = job.Tenant;
IJob job = this.jobFactory.Create(job.Type);
job.Execute(job.Data);
Activator.CreateInsta
} finally {
// Reset
this.context.Tenant = null;
this.context.User = null;
}
}
}
更新
如果它们都 运行 在同一个应用程序中,并且作业 运行 在后台线程上,您可以为 IUserContext
引入一个透明切换到的代理实现正确的实施。例如:
public SelectingUserContextProxy : IUserContext {
private readonly Func<bool> selector;
private readonly IUserContext trueContext;
private readonly IUserContext falseContext;
public SelectingUserContextProxy(Func<bool> selector,
IUserContext trueContext, IUserContext falseContext) {
this.selector = selector;
this.trueContext = trueContext;
this.falseContext = falseContext;
}
public IPrincipal User { get { return this.Context.User; } }
public Tenant Tenant { get { return this.Context.Tenant; } }
private IUserContext Context {
get { return selector() ? trueContext : falseContext; }
}
}
您可以通过以下方式注册:
var jobContext = new JobServiceUserContext();
container.RegisterSingle<IUserContext>(
new SelectingUserContextProxy(
() => HttpContext.Current != null,
trueContext: new AspNetUserContext(),
falseContext: jobContext));
我有一个多租户应用程序,我想在应用程序中跟踪两件事:用户和租户。
我有这三种情况:
- 匿名用户:在这种情况下 user 为空,并且 tenant 由查询字符串跟踪。
- 已验证用户:用户来自cookie,租户保存在会话中。
- 工作:我的应用程序中有一些工作要做,在这种情况下 user 为 null 并且 tenant 已设置手动。
在我所有的服务中,我都使用用户和租户。
起初我想在我的 DI 容器中为每个 http 会话使用一个对象,但这不适用于 Jobs。
关于如何处理这些信息的任何问题,我目前在 Session 变量中使用它,但我在这个实现中有很多问题,我需要有很多特殊情况来处理工作和未经身份验证用户。
您至少应该做的是抽象化对用户上下文的访问。例如:
public interface IUserContext {
IPrincipal User { get; }
Tenant Tenant { get; }
}
您可能希望有一个单独的 ITenantContext
抽象,但我们现在坚持使用一个。
您的系统可能有两个应用程序:windows 运行作业的服务和一个处理用户交互的 Web 应用程序。这两个应用程序都有自己的入口点、自己的 Composition Root 和自己独特的 DI 配置。
对于 Web 应用程序,我想象的实现如下所示:
public AspNetUserContext : IUserContext
{
private readonly ITenantRepository tenantRepository;
public AspNetUserContext(ITenantRepository tenantRepository) {
this.tenantRepository = tenantRepository;
}
public IPrincipal User {
get { return HttpContext.Current.User; }
}
public Tenant Tenant {
get {
if (this.User.Identity.IsAuthenticated) {
return this.tenantRepository.GetByName(
HttpContext.Current.Session["tenant"]);
} else {
return this.tenantRepository.GetByName(
HttpContext.Current.Request.QueryString["tenant"]);
}
}
}
}
对于作业服务,乍一看可能完全不同,但是对于该服务,处理一个作业可以被视为一个请求。所以这意味着当请求开始时,您需要设置上下文(ASP.NET 在后台为我们做的事情)。 IUserContext
可能如下所示:
public JobServiceUserContext : IUserContext
{
[ThreadStatic]
private static IPrinciple user;
[ThreadStatic]
private static Tenant tenant;
public IPrincipal User {
get { return this.user; }
set { this.user = user; }
}
public IPrincipal User {
get { return this.tenant; }
set { this.tenant = tenant; }
}
}
现在作业的执行可以用一些设置适当上下文的逻辑来包装,例如:
public class JobRunner {
private readonly JobServiceUserContext context;
private readonly IJobDactory jobFactory;
public JobServiceUserContext(JobServiceUserContext context,
IJobDactory jobFactory) {
this.context = context;
this.jobFactory = jobFactory;
}
public void RunJob(JobDetails job) {
try {
this.context.User = job.User;
this.context.Tenant = job.Tenant;
IJob job = this.jobFactory.Create(job.Type);
job.Execute(job.Data);
Activator.CreateInsta
} finally {
// Reset
this.context.Tenant = null;
this.context.User = null;
}
}
}
更新
如果它们都 运行 在同一个应用程序中,并且作业 运行 在后台线程上,您可以为 IUserContext
引入一个透明切换到的代理实现正确的实施。例如:
public SelectingUserContextProxy : IUserContext {
private readonly Func<bool> selector;
private readonly IUserContext trueContext;
private readonly IUserContext falseContext;
public SelectingUserContextProxy(Func<bool> selector,
IUserContext trueContext, IUserContext falseContext) {
this.selector = selector;
this.trueContext = trueContext;
this.falseContext = falseContext;
}
public IPrincipal User { get { return this.Context.User; } }
public Tenant Tenant { get { return this.Context.Tenant; } }
private IUserContext Context {
get { return selector() ? trueContext : falseContext; }
}
}
您可以通过以下方式注册:
var jobContext = new JobServiceUserContext();
container.RegisterSingle<IUserContext>(
new SelectingUserContextProxy(
() => HttpContext.Current != null,
trueContext: new AspNetUserContext(),
falseContext: jobContext));