开发者

Dependency Injection for Presenter

I have a Presenter that takes a Service and a View Contract as parameters in its constructor:

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

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

I resolve my service with Autofac:

private ContainerProvider BuildDependencies() {
    var builder = new ContainerBuilder();
    builder.Register<FooService>().As&l开发者_如何学Ct;IFooService>().FactoryScoped();  

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

In my ASPX page (View implementation):

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

The issue is FooPresenter's constructor expects the specific Page, not for the container to create a new one.

Can I supply a specific instance of the view, the current page, to the container for just this resolution? Does that make sense to do, or should I do this another way?


The way to solve passing what I like to call data parameters when resolving dependencies in Autofac is by using generated factories.

(Update: this question discusses the same problem and my article shows how you can avoid large amounts of factory delegates).

The solution to your problem will look something like this:

First, declare a factory delegate thath only accepts the data parameters:

public delegate IFooPresenter FooPresenterFactory(IFooView view);

Your presenter goes unchanged:

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

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

Next the Autofac container setup:

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

Now in your page you can in two lines of code resolve the presenter by first getting the factory and then calling the factory to do the resolution for you:

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

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


I actually solved this exact problem and built a framework around it. I used Autofac parameters to pass existing views to the presenter resolution call.

First, I defined a custom resolution interface derived from Autofac's:

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

which allowed me to register a presenter which resolves the view:

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

using an extension method which wraps Autofac's IContext in an implementation of IMvpContext:

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

I defined a parameter type representing the view parameter:

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

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

It uses its own assembly-qualified type name as the parameter name. This has a very low likelihood of conflicting with legitimate parameters.

MvpContext passes all standard resolution calls to the base context. For the view, it resolves the parameter with the well-known name:

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
}

The call to resolve the presenter provides the view parameter:

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

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

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜