2 Stimmen

Dependency Injection für Presenter

Ich habe einen Presenter, der einen Service und einen View Contract als Parameter in seinem Konstruktor nimmt:

public FooPresenter : IFooPresenter {
    private IFooView view;
    private readonly IFooService service;

    public FooPresenter(IFooView view, IFooService service) {
        this.view = view;
        this.service = service;
    }
}

Ich wickle meinen Service mit Autofac ab:

private ContainerProvider BuildDependencies() {
    var builder = new ContainerBuilder();
    builder.Register<FooService>().As<IFooService>().FactoryScoped();  

    return new ContainerProvider(builder.Build());  
}

In meiner ASPX-Seite (View-Implementierung):

public partial class Foo : Page, IFooView {
    private FooPresenter presenter;

    public Foo() {
        // this is straightforward but not really ideal
        // (IoCResolve is a holder for how I hit the container in global.asax)
        this.presenter = new FooPresenter(this, IoCResolve<IFooService>());

        // I would rather have an interface IFooPresenter so I can do
        this.presenter = IoCResolve<IFooPresenter>();
        // this allows me to add more services as needed without having to 
        // come back and manually update this constructor call here
    }
}

Das Problem ist, dass der Konstruktor von FooPresenter die spezifische Seite erwartet und nicht, dass der Container eine neue Seite erstellt.

Kann ich dem Container eine bestimmte Instanz der Ansicht, die aktuelle Seite, nur für diese Auflösung zur Verfügung stellen? Ist das sinnvoll, oder sollte ich das anders machen?

2voto

Peter Lillevold Punkte 32995

Der Weg zur Lösung des Passierens, den ich gerne als Daten Parameter bei der Auflösung von Abhängigkeiten in Autofac ist durch die Verwendung von erzeugte Fabriken .

(Aktualisierung: diese Frage erörtert das gleiche Problem und mein Artikel zeigt, wie Sie große Mengen von Fabrikdelegierten vermeiden können).

Die Lösung für Ihr Problem sieht in etwa so aus:

Deklarieren Sie zunächst einen Fabrikdelegierten, der nur nimmt die Datenparameter entgegen:

public delegate IFooPresenter FooPresenterFactory(IFooView view);

Ihr Moderator bleibt unverändert:

public FooPresenter : IFooPresenter {
    private IFooView view;
    private readonly IFooService service;

    public FooPresenter(IFooView view, IFooService service) {
        this.view = view;
        this.service = service;
    }
}

Als nächstes wird der Autofac-Container eingerichtet:

var builder = new ContainerBuilder();
builder.Register<FooService>().As<IFooService>().FactoryScoped();  
builder.Register<FooPresenter>().As<IFooPresenter>().FactoryScoped();  
builder.RegisterGeneratedFactory<FooPresenterFactory>();

Jetzt können Sie auf Ihrer Seite in zwei Codezeilen den Präsentator auflösen, indem Sie zuerst die Fabrik abrufen und dann die Fabrik aufrufen, um die Auflösung für Sie durchzuführen:

public partial class Foo : Page, IFooView {
    private FooPresenter presenter;

    public Foo() {
        var factory = IoCResolve<FooPresenterFactory>();
        this.presenter = factory(this);
    }
}

0voto

Bryan Watts Punkte 43539

Ich habe genau dieses Problem gelöst und einen Rahmen dafür geschaffen. Ich benutzte Autofac-Parameter um vorhandene Ansichten an den Auflösungsaufruf des Moderators zu übergeben.

Zunächst habe ich eine benutzerdefinierte Auflösungsschnittstelle definiert, die von der von Autofac abgeleitet ist:

public interface IMvpContext : IContext
{
    T View<T>();
}

die es mir ermöglichte, einen Präsentator zu registrieren, der die Ansicht auflöst:

builder.RegisterPresenter(c => new FooPresenter(
    c.View<IFooView>(),
    c.Resolve<IFooService>()));

mit Hilfe einer Erweiterungsmethode, die Autofacs IContext in einer Implementierung von IMvpContext :

public static IConcreteRegistrar RegisterPresenter<T>(
    this ContainerBuilder builder,
    Func<IMvpContext, T> creator)
{
    return builder
        .Register((context, parameters) => creator(new MvpContext(context, parameters)))
        .FactoryScoped();
}

Ich habe einen Parametertyp definiert, der den Ansichtsparameter darstellt:

public class MvpViewParameter : NamedParameter
{
    public static readonly string ParameterName = typeof(MvpViewParameter).AssemblyQualifiedName;

    public MvpViewParameter(object view) : base(ParameterName, view)
    {}
}

Es verwendet seinen eigenen assemblerqualifizierten Typnamen als Parameternamen. Dadurch ist die Wahrscheinlichkeit eines Konflikts mit legitimen Parametern sehr gering.

MvpContext übergibt alle Aufrufe zur Standardauflösung an den Basiskontext. Für die Ansicht löst er den Parameter mit dem bekannten Namen auf:

public sealed class MvpContext : IMvpContext
{
    private IContext _context;
    private IEnumerable<Parameter> _resolutionParameters;

    public MvpContext(IContext context, IEnumerable<Parameter> resolutionParameters)
    {
        _context = context;
        _resolutionParameters = resolutionParameters;
    }

    #region IContext

    // Pass through all calls to _context

    #endregion

    #region IMvpContext

    public T View<T>()
    {
        return _resolutionParameters.Named<T>(MvpViewParameter.ParameterName);
    }
    #endregion
}

Der Aufruf zum Auflösen des Präsentators liefert den Parameter view:

public partial class Foo : Page, IFooView
{
    private readonly FooPresenter presenter;

    public Foo()
    {
        this.presenter = IoCResolve<IFooPresenter>(new MvpViewParameter(this));
    }
}

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X