2009-12-18 5 views
2

У меня есть ведущий, который принимает Услугу и вид договора в качестве параметров в его конструктор:Dependency Injection для Presenter

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

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

(Обновление: this question обсуждает ту же проблему, и my article показывает, как можно избежать большого количества делегатов завода).

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

Во-первых, объявить заводскую делегат Тат только принимает параметры данных:

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

Отличное решение. Спасибо, что указали это. До сих пор я до сих пор не разбирал сгенерированные фабрики. –

+0

Я работаю над тем, как это сделать на абстрактном уровне. Моя текущая структура позволяет просматривать виды с помощью '[Presenter (typeof (FooPresenter))]' (поэтому код разрешения шаблона исключается). С сгенерированными фабриками я не буду знать, какой конкретный тип фабрики разрешить без дополнительных метаданных. Я, вероятно, буду использовать 'builder.RegisterGeneratedFactory >();' за расширением метода 'RegisterPresenter ', который обнаруживает тип вида через отражение. В любом случае, спасибо за вдохновение! –

+0

Вы также можете попробовать мой образец заводской фабрики (ссылка на статью в моем примере). Вам нужно будет зарегистрировать только одну фабрику, которая может динамически создавать любого презентатора. Должно быть даже возможно связать это с вашим атрибутом Presenter, минимизирующим как код разрешения шаблона, так и конфигурацию контейнера. –

0

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

Во-первых, я определил интерфейс пользовательского разрешения, полученного из Autofac'S:

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

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

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

используя метод расширения, который оборачивает Autofac-х IContext в реализации 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)); 
    } 
} 
+0

Это излишне сложным. Сделайте это, используя встроенные сгенерированные фабричные материалы, и вы можете удалить все параметры параметра MvpContext. –

+0

(См. Комментарии к ответу Питера для дальнейшего обсуждения этого подхода.) –