Passar valor de tempo de execução para o construtor usando Simple Injector e WebFormsMVP

Estou tentando combinar o SimpleInjector com o WebFormsMvp .

Para facilitar DI WebFormsMvp fornece a interface IPresenterFactory .
Ele contém o método Create , que fornece o tipo de apresentador a ser resolvido e a instância da visualização .
Eu preciso injetar a instância de exibição no construtor do apresentador .
O apresentador também tem outras dependencies que precisam ser criadas pelo contêiner .

Isso é o que eu tenho até agora, mas não é o ideal.
Qual é a solução correta para o problema?

Construtor do apresentador:

 public FooPresenter(IFooView view, IClientFactory clientFactory) : base(view) 

Fábrica:

 public class SimpleInjectorPresenterFactory : IPresenterFactory { private readonly Container _container; private IView _currentView; public SimpleInjectorPresenterFactory() { _container = new Container(); Func isIView = type => typeof(IView).IsAssignableFrom(type); _container.ResolveUnregisteredType += (s, e) => { if (isIView(e.UnregisteredServiceType)) e.Register(() => _currentView); }; } public IPresenter Create(Type presenterType, Type viewType, IView viewInstance) { lock (_currentView) { _currentView = viewInstance; return _container.GetInstance(presenterType) as IPresenter; } } } 

   

WebFormsMvp obriga você a ter a visão no construtor do apresentador, mas isso desencadeia uma referência circular. Se você olhar nas implementações de fábrica para o contêiner diferente, verá que, para cada contêiner, eles fazem um truque diferente para solucionar essa peculiaridade do design. Por exemplo, com a unidade, eles criam um contêiner filho e registram essa exibição no contêiner filho e resolvem o apresentador usando esse contêiner filho. Bastante bizarro e pesado desempenho.

Em vez de assumir a visão no construtor do apresentador, os designers do WebFormsMvp deveriam ter tornado a View uma propriedade gravável na interface do IPresenter . Isso tornaria vergonhosamente fácil definir a exibição no apresentador. Algo assim:

 public IPresenter Create(Type presenterType, IView view) { var presenter = (IPresenter)_container.GetInstance(presenterType); presenter.View = view; return presenter; } 

Infelizmente eles não fizeram isso, e é impossível estender o design para permitir isso (sem fazer coisas realmente desagradáveis ​​usando reflection).

O Simple Injector não suporta o fornecimento de argumentos de construtor ao método GetInstance() . Por um bom motivo, uma vez que isso normalmente levaria ao anti-padrão Service Locator, e você sempre pode contornar isso alterando o design. No seu caso, você não fez esse design peculiar, então não pode mudá-lo.

O que você fez com o ResolveUnregisteredType é bastante inteligente. Eu não teria pensado sobre isso sozinho. E já que eu sou o principal desenvolvedor do Simple Injector, estou em posição de dizer que o que você fez foi realmente inteligente 🙂

Apenas dois pontos de feedback sobre o seu SimpleInjectorPresenterFactory .

Em primeiro lugar, você deve fornecer o argumento Container como construtor, pois é muito provável que você precise adicionar outros registros ao contêiner e não deseja registrar o Container dentro do SimpleInjectorPresenterFactory .

Em segundo lugar, você pode melhorar o código usando um System.Threading.ThreadLocal . Isso permite que você se livre do bloqueio global. O bloqueio impede que qualquer apresentador seja feito concorrentemente, e isso pode tornar o site mais lento.

Então aqui está uma versão refatorada:

 public class SimpleInjectorPresenterFactory : IPresenterFactory { private readonly Container _container; private ThreadLocal _currentView = new ThreadLocal(); public SimpleInjectorPresenterFactory(Container container) { _container = container; _container.ResolveUnregisteredType += (s, e) => { if (typeof(IView).IsAssignableFrom(e.UnregisteredServiceType)) { e.Register(() => _currentView.Value); } }; } public IPresenter Create(Type presenterType, Type viewType, IView viewInstance) { _currentView.Value = viewInstance; try { return _container.GetInstance(presenterType) as IPresenter; } finally { // Clear the thread-local value to ensure // views can be disposed after the request ends. _currentView.Value = null; } } } 

Se você observar a implementação do UnityPresenterFactory , verá um monte de cache acontecendo lá. Eu não tenho ideia de por que eles fazem isso, mas do ponto de vista da performance, você não precisa de algo assim para o Injetor Simples. Talvez eu esteja perdendo alguma coisa, mas não vejo por que deveria haver um cache.

Mas, pior ainda, há um bug de simultaneidade no UnityPresenterFactory . Dê uma olhada neste método:

 private Type FindPresenterDescribedViewTypeCached(Type presenter, IView view) { IntPtr handle = presenter.TypeHandle.Value; if (!this.cache.ContainsKey(handle)) { lock (this.syncLock) { if (!this.cache.ContainsKey(handle)) { Type viewType = CreateType(presenter, view); this.cache[handle] = viewType; return viewType; } } } return this.cache[handle]; } 

À primeira vista, este código parece bem, já que um bloqueio duplo verificado é implementado. Infelizmente, o cache (dictionary) é lido de fora do bloqueio, enquanto é atualizado dentro do bloqueio. Isso não é thread-safe. Em vez disso, o desenvolvedor deve ter envolvido a coisa toda em um bloqueio, usar um ConcurrentDictionary (somente no .net 4) ou considerar o cache imutável, o que significa que você cria uma cópia do dictionary original, adiciona o novo valor e substitui o referência ao dictionary antigo com o novo. No entanto, neste caso, eu provavelmente teria bloqueado a coisa toda.

Este foi um pouco fora do tópico, mas só queria dizer 🙂