C # ASP.NET Внедрение зависимостей с осложнениями контейнера IoC - PullRequest
1 голос
/ 30 ноября 2011

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

Я пытаюсь создатьфреймворк для унаследованных приложений для использования DI в веб-формах ASP.NET.Вероятно, я буду использовать Castle Windsor в качестве основы.

Эти устаревшие приложения в некоторых местах будут частично использовать шаблон MVP.

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

class Presenter1
{
    public Presenter1(IView1 view, 
        IRepository<User> userRepository)
    {
    }
}

Теперь страница ASP.NET будет выглядеть примерно так:

public partial class MyPage1 : System.Web.UI.Page, IView1
{
    private Presenter1 _presenter;
}

Перед использованием DI я создаю экземпляр Presenter следующим образом в OnInit страницы:

protected override void OnInit(EventArgs e)
{
    base.OnInit(e);
    _presenter = new Presenter1(this, new UserRepository(new SqlDataContext()));
}

Итак, теперь я хочу использовать DI.

Сначала я должен создать фабрику обработчиков, чтобы переопределить конструкцию моей страницы.Я нашел ЭТО действительно хороший ответ, чтобы помочь: Как использовать Dependency Injection с веб-формами ASP.NET

Теперь я могу легко настроить свои контейнеры в корне моей композиции, как предложил Марк Симан.Global.asax (хотя это означает создание статического контейнера, который должен быть потокобезопасным и запечатанным, чтобы не было возможности добавлять дополнительные регистрации)

Теперь я могу пойти и объявить внедрение конструктора на странице

public MyPage1() : base()
{
}

public MyPage1(Presenter1 presenter) : this()
{
    this._presenter =  presenter;
}

Теперь мы столкнулись с первой проблемой, у меня круговая зависимость.Presenter1 зависит от IView1, но страница зависит от докладчика.

Теперь я знаю, что некоторые скажут, что дизайн, вероятно, неверен, когда у вас есть циклические зависимости.Во-первых, я не думаю, что дизайн Presenter является неправильным, поскольку он переносит зависимость в конструкторе к представлению, и я могу сказать это, рассматривая множество реализаций MVP.

Некоторые могут предложить изменить страницу на дизайнгде Presenter1 становится свойством, а затем использует свойство инъекции

public partial class MyPage1 : System.Web.UI.Page, IView1
{
    [Dependency]
    public Presenter1 Presenter
    {
        get; set;
    }
}

Некоторые могут даже предложить полностью удалить зависимость от Presenter, а затем просто подключиться через кучу событий, но это не тот дизайн, который я хотел, и, честно говоря,Не понимаю, почему мне нужно внести это изменение, чтобы приспособить его.

В любом случае, независимо от предложения, существует другая проблема:

Когда фабрика обработчиков получает запрос страницы, доступен только тип (НЕИНТЕРФЕЙС ПРОСМОТРА):

Type pageType = page.GetType().BaseType;

теперь используя этот тип, вы можете разрешить страницу через IoC и его зависимости:

container.Resolve(pageType)

После этого будет известно, что существует свойство с именем Presenter1 ибыть в состоянии ввести это.Но Presenter1 нужен IView1, но мы никогда не разрешали IView1 через контейнер, поэтому контейнер не будет знать, чтобы предоставить конкретный экземпляр фабрике обработчиков, только что созданной, как она была создана вне контейнера.

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

private  void InjectDependencies(object page)
{
    Type pageType = page.GetType().BaseType;
    // hack
    foreach (var intf in pageType.GetInterfaces())
    {
        if (typeof(IView).IsAssignableFrom(intf))
        {
            _container.Bind(intf, () => page); 
        }
    }

    // injectDependencies to page...    
} 

Это создает другую проблему, большинство контейнеров, таких как Castle Windsor, не позволят вам перерегистрировать этот интерфейс в том экземпляре, в котором он находится.указывая на сейчас.Кроме того, поскольку контейнер зарегистрирован в Global.asax, это не является потокобезопасным, поскольку контейнер должен читаться только в этой точке.

Другое решение состоит в создании функции для перестройки контейнера накаждый веб-запрос, а затем проверьте, содержит ли контейнер компонент IView, если не установлен экземпляр.Но это кажется расточительным и идет вразрез с предлагаемым использованием.

Другое решение - создать специальную фабрику под названием IPresenterFactory и поместить зависимость в конструктор страницы:

public MyPage1(IPresenter1Factory factory) : this()
{
    this._presenter = factory.Create(this);
}

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

class Presenter1Factory : IPresenter1Factory 
{
    public Presenter1Factory(Container container)
    {
        this._container = container;
    }
    public Presenter1 Create(IView1 view)
    {
        return new Presenter1(view, _container.Resolve<IUserRepository>,...)
    }
}

Этот дизайн также кажется громоздким и сложным, есть ли у кого-нибудь идеи для более элегантного решения?

1 Ответ

1 голос
/ 01 декабря 2011

Возможно, я неправильно понимаю ваши проблемы, но мне кажется, что решение довольно простое: рекламируйте IView для свойства на Presenter1:

class Presenter1
{
    public Presenter1(IRepository<User> userRepository)
    {
    }

    public IView1 View { get; set; }            
}

Таким образом, вы можете установить докладчика на вид следующим образом:

public Presenter1 Presenter { get; set; }

public MyPage1() 
{
    ObjectFactory.BuildUp(this);
    this.Presenter.View = this;
}

Или без внедрения свойства вы можете сделать это следующим образом:

private Presenter1 _presenter;

public MyPage1() 
{
    this._presenter = ObjectFactory.Resolve<Presenter1>();
    this._presenter.View = this;
}

Внедрение в конструктор классов Page и пользовательских элементов управления никогда не будет работать. Вы можете заставить его работать с полным доверием (, как показано в этой статье ), но при частичном доверии это не удастся. Поэтому вам придется вызвать контейнер для этого.

Все контейнеры DI являются поточно-ориентированными, если вы не добавляете регистрации самостоятельно после фазы инициализации, а с некоторыми контейнерами, даже поточно-ориентированными ( некоторые контейнеры даже запрещают регистрировать типы после инициализация). Это никогда не потребуется (за исключением разрешения незарегистрированного типа, которое поддерживается большинством контейнеров). Однако с помощью Castle вам необходимо заранее зарегистрировать все конкретные типы, а это значит, что вам нужно знать о вашем Presenter1, прежде чем разрешить его. Зарегистрируйте это, измените это поведение или перейдите в контейнер, который позволяет разрешать конкретные типы по умолчанию.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...