views:

165

answers:

2

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

A: 

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));
    }
}
Bryan Watts
This is unnecessarily complex. Do this using the built-in generated factory stuff and you can drop the whole MvpContext parameter thing.
Peter Lillevold
(See the comments on @Peter's answer for further discussion of this approach.)
Bryan Watts
+1  A: 

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);
    }
}
Peter Lillevold
Excellent solution. Thanks for pointing it out. I hadn't really grasped generated factories until now.
Bryan Watts
I am working out how to do this at an abstract level. My current framework allows views to be decorated with `[Presenter(typeof(FooPresenter))]` (so the boilerplate resolution code is eliminated). With generated factories, I won't know which specific factory type to resolve without more metadata. I will probably use `builder.RegisterGeneratedFactory<Func<TView, TPresenter>>();` behind an extension method `RegisterPresenter<T>` which discovers the view type via reflection. Anyways, thanks for the inspiration!
Bryan Watts
You could also try out my generic factory sample (link to the article in my sample). You would only have to register one generic factory that can build any presenter dynamically. It should even be possible to couple that with your Presenter attribute minimizing both boilerplate resolution code AND boilerplate container setup :)
Peter Lillevold
+1 This looks clean and straightforward, I will check it out later and see how it goes. Thanks for the reply.
blu
I read through your article. I wanted to verify this delegate signature: `public delegate T ServiceFactory() where T:class;`. `T` needs to be declared somewhere, which means it should be `ServiceFactory<T>()`. This would change `CustomerController`'s constructor to use `ServiceFactory<CustomerService>` as the parameter type. Does that sound right?
Bryan Watts
@Bryan - oooh thanks for spotting my error! You are totally right, this was lost in my article probably because Wordpress strips out < > in the html editor. Clearly I had not converted all to < and >.The article is now fixed.
Peter Lillevold
So I have tried to implement this in the abstract. Because delegate parameters are mapped to constructor parameters by name, I would have to enforce that all presenters use "view"; I am not comfortable with that tacit requirement. Funny enough, I read through the implementation of this feature, and it very similar to what I did, just broader in scope. I use `builder.RegisterPresenter<T>()` and generate a delegate which fills out the constructor, using conventions (`view`) or annotation (`[View] legacyView`) to pass the view via `.View<T>()`.
Bryan Watts
It looks like `Func` delegates are matched using `TypedParameter` instead of `NamedParameter`, which I assume means it matched positionally by type. I'm going to try that out.
Bryan Watts
Interesting. But yes, Func delegates couldn't been done otherwise since parameters are anonymous at declaration. But I agree, default by position would be better methinks.
Peter Lillevold
@Bryan - will you be distributing your work somewhere?
Peter Lillevold
Actually, I tried using `Func`, but it still put tacit requirements on the presenter writer (you must know to put your arguments in a certain order). Using delegate factories, I cannot figure out how to make the delegate work for all presenters without each presenter having restrictions on its constructor signature. By using the `.View<T>()` method on `IMvpContext` (as stated in my answer), I let the registration of the presenter specify what goes where. This was a fun excursion, but I can't continue because delegate factories are meant to instanstiate *specific* types, not just any type.
Bryan Watts
I see your point, there will always be this disconnect since the delegate declaration cannot be used to dictate the constructor signature. (it would be a great thing to solve this :) But... you will still have some tacit requirement using conventions, right? The developer must know to name the parameter "view", or annotate it using the [View] attribute.
Peter Lillevold
You are correct, the presenter author will need to either name a parameter `view` (in the first position) or use `[View]` to indicate it. The ability to override the convention with the attribute is what sets this approach apart from the delegate signature issue. Also, there is a level of indirection when selecting a presenter's constructor, so the `view`/`[View]` approach is just one possible mapping implementation. If I were to solve the constructor signature issue, I would allow `RegisterGeneratedFactory` to take a second function which maps the delegate parameters to the constructor.
Bryan Watts
Oh, and that is only for the case where the presenter type is reflected when calling `.RegisterPresenter<T>()`. If you call .`RegisterPresenter(c => ...)`, then you choose where `.View<T>()` is called. No inherent mapping is necessary. This supports legacy presenters which can't specify the view via convention or attribute.
Bryan Watts