Внедрение зависимостей для докладчика - PullRequest
2 голосов
/ 18 декабря 2009

У меня есть Presenter, который принимает в своем параметре Service и View Contract в качестве параметров:

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

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

Я разрешаю свой сервис с помощью Autofac:

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

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

На моей странице ASPX (Просмотреть реализацию):

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

Проблема заключается в том, что конструктор FooPresenter ожидает, что конкретная страница будет создана контейнером, а не новой.

Могу ли я предоставить конкретный экземпляр представления, текущую страницу, контейнеру только для этого разрешения? Имеет ли это смысл делать, или я должен сделать это по-другому?

Ответы [ 2 ]

2 голосов
/ 18 декабря 2009

Способ решения проблемы передачи параметров, которые я люблю называть data , при разрешении зависимостей в Autofac - использование сгенерированных фабрик .

(обновление: в этом вопросе обсуждается та же проблема, а в в моей статье показано, как можно избежать большого количества делегатов фабрики).

Решение вашей проблемы будет выглядеть примерно так:

Сначала объявите делегата фабрики, что h only принимает параметры данных:

public delegate IFooPresenter FooPresenterFactory(IFooView view);

Ваш докладчик остается без изменений:

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

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

Далее настройка контейнера Autofac:

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

Теперь на своей странице вы можете в двух строках кода разрешить докладчика, сначала получив фабрику, а затем позвонив на фабрику, чтобы сделать решение за вас:

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

    public Foo() {
        var factory = IoCResolve<FooPresenterFactory>();
        this.presenter = factory(this);
    }
}
0 голосов
/ 18 декабря 2009

Я на самом деле решил именно эту проблему и построил структуру вокруг нее. Я использовал параметры Autofac для передачи существующих представлений в вызов разрешения докладчика.

Сначала я определил пользовательский интерфейс разрешения, полученный из Autofac:

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

, что позволило мне зарегистрировать докладчика, который разрешает представление:

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

с использованием метода расширения, который оборачивает IContext Autofac в реализацию IMvpContext:

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

Я определил тип параметра, представляющий параметр представления:

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

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

В качестве имени параметра используется собственное имя типа, уточненное сборкой. Это имеет очень низкую вероятность конфликта с законными параметрами.

MvpContext передает все вызовы стандартного разрешения в базовый контекст. Для представления он разрешает параметр с известным именем:

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
}

Вызов для разрешения докладчика предоставляет параметр просмотра:

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

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