Шаблон MVP с использованием веб-форм и создание объектов DI

Я использую общий шаблон репозитория для сохранения своих данных. В PageLoad я создаю новый объект Repository (из IRepository), а в PageUnload я избавляюсь от него.

Должен ли MasterPage/Page отвечать за создание экземпляров объектов для передачи презентатору или презентатор должен отвечать за это? Меня больше интересует тестирование ведущего, чем страницы (представление), поскольку легче имитировать интерфейсы, переданные ведущему.

Пример страницы

public partial class _Default : System.Web.UI.Page
{
    private IRepository _repo;
    protected void Page_Load(object sender, EventArgs e)
    {
        if (_repo == null)
            _repo = new Repository();
        ConnectPresenter();
    }

    private void ConnectPresenter()
    {
        _DefaultPresenter presenter = new _DefaultPresenter(_repo);
    }

    private void Page_Unload(object sender, EventArgs e)
    {
        if (_repo != null)
            _repo.Dispose();
    }
}

Поможет ли в этом случае DI Framework, например StructureMap или Ninject? Будет ли он отвечать за утилизацию таких объектов?


person Steve Wright    schedule 15.05.2009    source источник


Ответы (2)


Ни класс Page, ни презентеры не должны иметь дело непосредственно с управлением построением или жизненным циклом любой из его зависимостей — все это должен обрабатывать ваш контейнер. Поскольку внедрение конструктора не работает с WebForms, вам нужно будет предоставить все необходимые зависимости как свойства класса. Например, вы можете изменить свой класс на:

public partial class _Default : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
    }

    public _DefaultPresenter Presenter { get; set; }
}

Страница не должна нуждаться в каких-либо ссылках на репозиторий, так как она будет внедрена в презентатор.

Остальная часть этого ответа относится к StructureMap — детали могут отличаться для других контейнеров.

Чтобы включить внедрение сеттера, вам нужно сообщить StructureMap, какие свойства следует заполнить. Один из способов — применить атрибут [SetterProperty] к самому свойству. Тем не менее, это может показаться немного навязчивым, чтобы иметь детали StructureMap в ваших классах. Другой способ — настроить StructureMap так, чтобы он знал, какие типы свойств следует вводить. Например:

protected void Application_Start(object sender, EventArgs e)
{
    ObjectFactory.Initialize(x =>
    {
        x.Scan(scan =>
        {
            scan.TheCallingAssembly();
            scan.WithDefaultConventions();
        });
        x.ForRequestedType<IRepository>().TheDefaultIsConcreteType<Repository>().CacheBy(InstanceScope.Hybrid);
        x.SetAllProperties(set => set.WithAnyTypeFromNamespaceContainingType<IRepository>());
    });
}

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

Вам по-прежнему необходимо выполнять инъекцию сеттера для каждого запроса. В StructureMap вы используете метод BuildUp() для внедрения зависимостей в существующий экземпляр. Вы можете сделать это в событиях Init или Load каждой страницы или базового класса страницы, но опять же, это кажется навязчивым. Чтобы полностью исключить контейнер из классов страниц, вы можете использовать событие PreRequestHandlerExecute приложения (в global.asax или IHttpModule):

protected void Application_PreRequestHandlerExecute(object sender, EventArgs e)
{
    var application = (HttpApplication)sender;
    var page = application.Context.CurrentHandler as Page;
    if (page == null) return;
    ObjectFactory.BuildUp(page);
}

Наконец, если вы хотите явно удалить свой IRepository, вы можете обработать это в событии EndRequest:

protected void Application_EndRequest(object sender, EventArgs e)
{
    var disposable = ObjectFactory.GetInstance<IRepository>() as IDisposable;
    if (disposable != null) disposable.Dispose();
}

Обратите внимание, что это работает правильно, потому что при инициализации мы указали StructureMap кэшировать IRepository с помощью Hybrid, что означает «дайте мне один и тот же экземпляр для каждого HTTP-запроса (или потока, если он не выполняется на веб-сайте)». Когда вы извлекаете IRepository в EndRequest, вы получите тот же самый, который использовался во всем запросе, и вы можете его удалить.

person Joshua Flanagan    schedule 07.11.2009

Да, вам стоит изучить одно из пошаговых руководств. использование DI с ASP.NET.

Да, удаление объектов поведения для каждого запроса в соответствующий момент обычно управляется интеграцией контейнера с ASP.NET.

Типичная компоновка заключается в том, что создание объекта происходит от страницы и Application/Modules внутрь. Обычно вы отмечаете свойства [Inject] в своем классе Page, но это зависит от того, как вы организовали свою триаду. Presenter обычно может использовать Constructo Injection для объявления того, что ему нужно, независимо от того, является ли это тестом или сопутствующим текстом ASP.NET. Затем во время выполнения зависимости будут удовлетворяться DI. Во время тестирования вы все еще можете использовать DI, хотя в других случаях может быть более естественным просто создать кучу подделок вместе с SUT и передать их Presenter.

Что касается схем Triad для тестирования, я нашел эту статью MSDN Mag об использовании Ninject с xUnit. net от Джастина Этереджа очень полезен, несмотря на то, что он нацелен на ASP.NET MVC.

person Ruben Bartelink    schedule 05.11.2009
comment
Извините, что не посмотрели код или теги в вашем вопросе! Переработали его сейчас - надеюсь, что это улучшит ситуацию! Удалим это, если вы удалите свое... - person Ruben Bartelink; 06.11.2009