通过HttpContext中的参数统一构造函数依赖注入

时间:2014-02-08 10:52:08

标签: c# unity-container

我们正在使用域来自定义应用程序的行为方式。我将在示例中说明它:

// default behavior
public class CoreService : IService {
  public virtual string Hello { get { return "Hello"; } }
  public virtual string FavouriteDrink { get { return "Water"; } }
}

// german.site.com
public class GermanService : CoreService {
  public override string Hello { get { return "Gutten tag"; } }
  public override string FavouriteDrink { get { return "Beer"; } }
}

// usa.site.com
public class UsaService : CoreService {
  public override string FavouriteDrink { get { return "Cofee"; } }
}

服务如下引导:

var container = new UnityContainer();
container.RegisterType<IService, CoreService>();
container.RegisterType<IService, GermanService>("german.site.com");
container.RegisterType<IService, UsaService>("usa.site.com");

我使用Unity来引导mvc控制器。 IE:

public class HomeController : Controller {
  private IService m_Service;

  // contructor dependency injection magic - this resolves into "CoreService"
  public HomeController([Dependency]IService service) {
    if (service == null) {
      throw new ArgumentNullException("service");
    }
    m_Service = service;
  } 
}

有没有办法如何改变统一分辨率,以便将域考虑在内?现在我最终得到了

public class HomeController : Controller {
  private IService m_Service;

  // contructor dependency injection magic - a lot less magical
  public HomeController() {
    m_Service = DomainServiceLocator.Retrieve<IService>();
  } 
}

支持课程:

public static class DomainServiceLocator {
  private static UnityContainerAdapter adapter; 

  public static T Retrieve<T>() {
    string domain = HttpContext.Current.Request.Url.Host;
    if (adapter.IsServiceRegistered(typeof(T), domain)) {
      return adapter.Resolve<T>(domain);
    }

    return adapter.Resolve<T>();
  }
}

public class QueryableContainerExtension : UnityContainerExtension {
  private List<RegisterInstanceEventArgs> registeredInstances = new List<RegisterInstanceEventArgs>();
  private List<RegisterEventArgs> registeredTypes = new List<RegisterEventArgs>();

  protected override void Initialize() {
    this.Context.Registering += (sender, e) => { this.registeredTypes.Add(e); };
    this.Context.RegisteringInstance += (sender, e) => { this.registeredInstances.Add(e); };
  }


  public bool IsServiceRegistered(Type service, string name) {
    return registeredTypes.FirstOrDefault(e => e.TypeFrom == service && e.Name == name) != null
           || registeredInstances.FirstOrDefault(e => e.RegisteredType == service && e.Name == name) != null;
  }
}

public class UnityContainerAdapter {
  private readonly QueryableContainerExtension queryableContainerExtension;
  private readonly IUnityContainer unityContainer;

  public UnityContainerAdapter()
    : this(new UnityContainer()) {
  }

  public UnityContainerAdapter(IUnityContainer unityContainer) {
    this.unityContainer = unityContainer;

    // adding extensions to unity container
    this.queryableContainerExtension = new QueryableContainerExtension();
    unityContainer.AddExtension(this.queryableContainerExtension);
  }

  public T Resolve<T>(string name) {
    return unityContainer.Resolve<T>(name);
  }

  public T Resolve<T>() {
    return unityContainer.Resolve<T>();
  }

  public bool IsServiceRegistered(Type service, string name) {
    return this.queryableContainerExtension.IsServiceRegistered(service, name);
  }
}

2 个答案:

答案 0 :(得分:1)

在运行时解析某些内容时,我喜欢在这些场景中使用注入工厂。基本上,您通过域名解析您的类型:

所以在你的作文根你可以像这样注册:

container.RegisterType<Func<string, IService>>
                (
                    new InjectionFactory(c => new Func<string, IService>(name => c.Resolve<IService>(name)))
                );

然后在你的HomeController中你可以注入委托

public class HomeController
{
    private readonly Func<string,IService> _serviceFactory;

    public HomeController(Func<string, IService> serviceFactory)
    {
        if(serviceFactory==null)
            throw new ArgumentNullException("serviceFactory");

        this._serviceFactory= serviceFactory;
    }

    public void DoSomethingWithTheService()
    {
        var domain = this.HttpContext.Uri.Host;                             
        var service = this._serviceFactory(domain);
        var greeting = service.Hello;
    }
}

```

然后,这仍然是单元可测试的,并且你没有泄露DI包含&#34;组合root&#34;之外的实现。

另外.. CoreService应该是抽象的,以避免直接实例化它吗?

答案 1 :(得分:0)

以下是我最终得到的解决方案 - 它基于@Spencer的想法。我已经创建了一个工厂,工厂的默认实现引用了DI容器本身(在我的情况下为IUnityContainer),因此它可以在被要求时根据域执行解析。它也更加现代友好&#34;因为在当前的ASP.NET(ASP.NET CORE)中,没有提供当前HttpContext的魔术单例,并且DI被硬编码到框架中。

public interface IFactory<T>
{
    T Retrieve(string domain);
}   

internal sealed class Factory<T> : IFactory<T>
{
    private readonly IUnityContainer _container;

    public Factory(IUnityContainer container)
    {
        _container = container;
    }

    public T Resolve(string domain)
    {
        // this is actually more complex - we have chain inheritance here
        // for simplicity assume service is either registered for given 
        // domain or it throws an error
        return _container.Resolve<T>(domain);
    }
}

// bootstrapper
var container = new UnityContainer();
container.RegisterType<IService, CoreService>();
container.RegisterType<IService, GermanService>("german.site.com");
container.RegisterType<IService, UsaService>("usa.site.com");
container.RegisterInstance<IFactory<IService>>(new Factory<IService>(container));    

家庭控制器看起来像

public class HomeController : Controller {
  private IFactory<IService> m_Factory;

  public HomeController(IFactory<IService> factory) {
    m_Factory = factory;
  } 

  private void FooBar() {
    var service = m_Factory.Retrieve(this.HttpContext.Uri.Host);
    var hello = service.Hello;
  }   
}

还值得一提的是 - 因为我懒惰 - 我构建了一个装饰属性系统,如

[Domain("german.site.com")]
public class GermanService : IService { ... }

[DomainRoot]
public class CoreService : IService { ... }

[Domain("usa.site.com")]
public class UsaService : CoreService { ... }

因此,在给定程序集中的所有类型上自动完成引导。但是那部分有点冗长 - 如果有人有兴趣我可以在github上发布。