2014-04-18 65 views
0

我最近重构了我的MVC应用程序以使用Unity依赖注入来解决依赖关系,这非常棒。它更易于分解等等。Unity - 使用请求中的信息来解决依赖关系

我现在正在做的是为多个租户添加功能来使用它。我使用的方法(以便其余代码不必知道租户多少)正在创建诸如租户过滤版本的存储库接口(这只是另一个存储库的代理...)所以它会调用其中一个底层方法,然后检查记录是否有合适的租户并相应地执行)。这让我基本上可以模拟为每个租户设置一个完全独立的存储,即使数据在数据不隔离的情况下也是如此,所以客户代码的相对较少需要更改。

所有这一切的问题是它如何适应DI方式的做事。我打算做的是,在请求开始时,检测主机名,然后使用它来确定租户(每个租户都将拥有数据库中的主机名列表)。虽然我正在使用每个请求的生命期来处理Unity正在构建和解决的大多数对象,但我并没有真正了解Unity如何“知道”要使用的租户,因为它需要关于请求的数据(我想控制器会有,但我不认为在我的容器配置方法中可用)和访问数据库以了解哪个主机(并且几乎不需要让我的容器配置进行数据库调用)。我可以通过只传递一个主机名来解决#2,并让租户的类去确定哪个租户被引用,但这对#1没有帮助。

现在我正在使用“财产注入”(也被称为“一个公共财产”在较低的高度falutin'圈子),但我不明白我将如何避免让我的控制器成为一个实际提供租户数据,所以现在我不是真的只有一个组合根控制一切。

有没有一种方法可以在组合根中做到这一点,或者我应该让自己屈服于让控制器完成这项工作?

回答

1

出于某种原因,您似乎忘了注塑工厂。通过向工厂注册接口/类型,您可以在解析时执行任意复杂的代码,包括咨询请求,租户数据库等等。

container.RegisterType<IRepository>( 
    new InjectionFactory( 
     c => { 
      // whatever, consult the database 
      // whatever, consult the url 
      return ...; 
     }); 

工厂组成是透明的,所以,每当你需要一个目标,甚至不知道工厂代码已被执行,而不是从简单的映射一个类型的实例。

+0

但是我怎样才能找到该方法的网址?这一点不在范围内,是吗? – Casey

+0

我想我可以用这种方式真正简洁地描述我的问题:做我想做的事情我需要访问组合根内的请求数据,但据我所知我没有访问当我配置容器时的任何当前请求上下文。 – Casey

+0

'HttpContext.Current.Request.Url' –

2

某处需要进行数据库调用。如果系统需要,最简单的地方可能在global.ascx中。

private static ConcurrentDictionary<string, string> _tenantCache = new ConcurrentDictionary<string, string>(); 

protected virtual void Application_BeginRequest(object sender, EventArgs e) 
{ 
    HttpApplication app = (HttpApplication)source; 
    var tenantId = _tenantCache.GetOrAdd(app.Context.Request.Url.Host, host => 
      { 
       // Make database call in this class 
       var tenant = new TenantResolver(); 
       return tenant.GetTenantId(host); 
      }) 
    app.Context.Items["TenantID"] = tenantId ; 
} 

您将要缓存结果,因为Application_BeginRequest被称为很多。然后,您可以将Unity配置为具有子容器。将所有常见/默认映射放在父容器中,然后为每个租户创建一个子容器,并为每个租户在其自己的子容器中注册正确的实现。

然后实现IDependencyResolver以返回正确的子容器。

public class TenantDependencyResolver : IDependencyResolver 
{ 
    private static IUnityContainer _parentContainer; 
    private static IDictionary<string, IUnityContainer> _childContainers = new Dictionary<string, IUnityContainer>(); 

    public TenantDependencyResolver() 
    { 
     var fakeTenentID = "localhost"; 
     var fakeTenentContainer = _parentContainer.CreateChildContainer(); 
     // register any specific fakeTenent Interfaces to classes here 

     //Add the child container to the dictionary for use later 
     _childContainers[fakeTenentID] = fakeTenentContainer; 
    } 

    private IUnityContainer GetContainer() 
    { 
     var tenantID = HttpContext.Current.Items["TenantID"].ToString(); 
     if (_childContainers.ContainsKey(tenantID) 
     { 
      return _childContainers[tenantID]; 
     } 
     return _parentContainer; 
    } 

    public object GetService(Type serviceType) 
    { 
     var container = GetContainer(); 
     return container.Resolve(serviceType); 
    } 

    public IEnumerable<object> GetServices(Type serviceType) 
    { 
     var container = GetContainer(); 
     return container.ResolveAll(serviceType); 
    } 
} 

然后将ASP.NET MVC DependecyResolver设置为TenantDependencyResolver。我没有运行这个代码,但它应该让你知道你需要做什么。如果您的实现已设置,那么您可以在TenantDependecyResolver的静态构造函数中完成此操作。

+0

非常有趣。我不确定我会这么想。 – Casey

+0

@CharlesNRice:这是不是每个租户有额外的儿童容器矫枉过正?如果为每个租户注册不同的实现,这将是有意义的。另一方面,他似乎需要更简单的东西。 –

+0

@CharlesNRice:另外,放在'BeginRequest'中的代码需要一个重构器,你可以把它放在一个单独的类中,并包装在一个访问内部的'HttpContext.Current.Items'的属性中,以便它只被执行在需要的时候,而不是在每次请求时,无条件地。 –