2013-02-27 4 views
6

Ich versuche, SimpleInjector mit WebFormsMvp zu kombinieren.Übergeben Sie den Laufzeitwert an Konstruktor mit Simple Injector abd WebFormsMVP

Zur Vereinfachung von DI WebFormsMvp bietet die IPresenterFactory Schnittstelle.
Sie enthält die Create-Methode, die den Moderatortyp zum Auflösen von und die Sichtinstanz bereitstellt.
Ich muss inject die Ansicht Instanz in den Konstruktor der Moderator.
Der Präsentator hat auch andere Abhängigkeiten die durch den Behälter zu schaffen benötigen.

Das ist, was ich bis jetzt bekommen habe, aber es ist nicht ideal.
Was ist die richtige Lösung für das Problem?

Presenter Konstruktor:

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

Factory:

public class SimpleInjectorPresenterFactory : IPresenterFactory 
{ 
    private readonly Container _container; 
    private IView _currentView; 

    public SimpleInjectorPresenterFactory() 
    { 
     _container = new Container(); 

     Func<Type, bool> 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; 
     } 
    } 
} 

Antwort

5

WebFormsMvp zwingt Sie die Ansicht im Konstruktor des Moderators zu übernehmen, aber dies löst eine zirkuläre Referenz. Wenn Sie in den Factory-Implementierungen nach den verschiedenen Containern suchen, sehen Sie, dass sie für jeden Container einen anderen Trick anwenden, um diese Eigenart im Design zu umgehen. Mit der Einheit erstellen sie beispielsweise einen untergeordneten Container und registrieren diese Ansicht im untergeordneten Container und lösen den Moderator mithilfe dieses untergeordneten Containers auf. Ziemlich bizarr und Leistung schwer.

Anstatt die Ansicht im Konstruktor des Präsentators zu verwenden, sollten die Designer von WebFormsMvp die Ansicht als schreibbare Eigenschaft für die Schnittstelle IPresenter festgelegt haben. Dies würde es peinlich leicht machen, den Blick auf den Moderator zu richten. Etwas wie folgt aus:

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

Leider haben sie dies nicht tun, und es ist unmöglich, das Design zu erweitern, dies zu ermöglichen (ohne dabei wirklich böse Dinge mit Reflexion).

Simple Injector unterstützt die Übergabe von Konstruktorargumenten an die Methode GetInstance() nicht. Aus gutem Grund, da dies normalerweise zu einem Anti-Pattern des Service Locators führen würde, und Sie können dies jederzeit umgehen, indem Sie das Design ändern. In deinem Fall hast du dieses schrullige Design nicht gemacht, also kannst du es nicht ändern.

Was Sie mit der gemacht haben, ist ziemlich clever. Daran hätte ich selbst nicht gedacht. Und da ich der führende Entwickler hinter Simple Injector bin, bin ich in der Lage zu sagen, dass das, was du getan hast, wirklich clever war :-)

Nur zwei Punkte Feedback über deine SimpleInjectorPresenterFactory.

Zunächst einmal sollten Sie die Container als Konstruktor Argument liefern, da es sehr wahrscheinlich sein, dass Sie andere Registrierungen in den Behälter hinzufügen müssen, und Sie wollen nicht die Container innerhalb des SimpleInjectorPresenterFactory registrieren.

Zweitens können Sie den Code mit einem System.Threading.ThreadLocal<IView> verbessern. Dadurch können Sie die globale Sperre loswerden. Die Sperre verhindert, dass Moderatoren gleichzeitig geschaltet werden. Dies könnte Ihre Website verlangsamen.

Also hier ist eine Überarbeitete Version:

public class SimpleInjectorPresenterFactory : IPresenterFactory { 
    private readonly Container _container; 
    private ThreadLocal<IView> _currentView = new ThreadLocal<IView>(); 

    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; 
     } 
    } 
} 

Wenn Sie bei der Umsetzung des UnityPresenterFactory schauen, sehen Sie eine Menge Caching dort los ist. Ich habe keine Ahnung, warum sie das machen, aber aus Performanceperspektive brauchen Sie überhaupt nichts für Simple Injector. Vielleicht vermisse ich etwas, aber ich verstehe nicht, warum es einen Cache geben sollte.

Aber noch schlimmer, es gibt einen Nebenläufigkeitsfehler in der UnityPresenterFactory. Werfen Sie einen Blick auf diese Methode:

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]; 
} 

Auf den ersten Blick sieht dieser Code in Ordnung, da eine doppelt überprüfte Sperre implementiert ist. Leider wird der Cache (Wörterbuch) von außerhalb der Sperre gelesen, während er innerhalb der Sperre aktualisiert wird. Dies ist nicht Thread-sicher. Stattdessen sollte der Entwickler entweder das ganze Ding in ein Schloss gewickelt haben, ein ConcurrentDictionary (nur .net 4) verwenden oder das cache unveränderlich betrachten, was bedeutet, dass Sie eine Kopie des ursprünglichen Wörterbuchs erstellen, den neuen Wert hinzufügen und das ersetzen Verweis auf das alte Wörterbuch mit dem neuen. In diesem Fall hätte ich wahrscheinlich die ganze Sache abgeschlossen.

Dies war ein wenig off Thema, aber wollte nur sagen :-)

+0

Bedankt, Steven! Das globale Schloss war es, was mich gestört hat. Bevor ich auf das skurrile Design eingehe, wie du es schon erwähnt hast, wie geht ThreadLocal mit der Freigabe der Objekte um? Es wird nicht immer einen Bezug zu jeder erstellten Ansicht halten, richtig? :-) – David

+0

Es behält einen Verweis auf die Ansicht, bis eine andere Anforderung es überschreibt. Dies bedeutet, dass die Seite länger als benötigt am Leben bleiben kann, aber dieses Problem gab es auch in Ihrem Beispiel. Könnte es eine WeakReference machen, aber nicht sicher, dass es sich lohnt. – Steven

+0

Um zurück zum WebFormsMvp-Design zu kommen: Ich bekomme irgendwie die Cortinjektion, weil die Ansicht nicht optional ist, während prop Injektion ist für optionale Abhängigkeiten. Die Tatsache, dass die Ansicht erstellt wird, bevor Sie DI einbinden können, ist ein Webformular-Designproblem, mit dem sie arbeiten mussten. Nichtsdestotrotz sollte die schiere Komplexität der Verbindungs-ID auf diese Weise den Ausschlag geben. Die Unity-Fabrik ist schrecklich auf vielen Ebenen, die Burg ist viel sauberer :) Während ich Sie hier, irgendwelche Release-Funktionen in SimpleInjector bekam? – David

Verwandte Themen