Pull to refresh

Применение паттерна MVP в классическом ASP.NET

Reading time 3 min
Views 5.7K
Приходилось ли Вам сталкиваться с долгоиграющими проектами на ASP.NET?
Может быть Вы сейчас над таким проектом как раз и трудитесь?
Если да, то вы скорее всего сталкивались со сложно переплетенным набором событий, логики и валидаций внутри классов страниц.
Эта статья рассказывает о том как можно упростить жизнь на таких проектах используя все тот же шаблон Model-View-Presenter.

Предположу что Вы знакомы с шаблоном MVC и не буду в этой статье на нем подробно останавливаться.

Я попробую сопоставить MVC с теми возможностями которые предоставляет классический ASP.NET:

1. Routing — обработка запросов
Дерево папок сайта в большинстве случаев может служить тем же целям.

2. View и PartialView — представление данных
Класс Page и соответственно UserControl замечательно подходят для этих целей.

3. Model — данные
Классы моделей скорее всего присутствуют в Вашей системе в каком либо виде.

4. Presenter — клей между данными и представлением
Вот этого типа объектов ASP.NET к сожалению не предусматривает.

Реализацией презентера мы сейчас и будем заниматься.
Назначение презентера — подготовить данные для показа пользователю и обработать введенные им данные.
Назначение представления — показать данные пользователю и получить данные от пользователя.

Существуют разные мнения на счет того, насколько умным может быть представление и презентер.
Так как единого мнения нет, то я предпочитаю исходить из эффективности реализации которая зависит от окружения. В данном случае от имеющейся библиотеки ASP.NET которая обладает широким набором достаточно умных элементов управления которые хотелось бы использовать в полной мере, раз уж такая возможность присутствует.

Наконец приступаем к реализации.

Создаем два класса PageView и UserControlView:
public abstract class PageView<T>: Page where T : class
{
    private T _presenter;

    public T Presenter
    {
        get { return _presenter ?? (_presenter = CreatePresenter()); }
    }

    protected abstract T CreatePresenter();
}

public class UserControlView<T> : UserControl
{
    public T Presenter { get; set; }
}

Класс PageView абстрактный и предполагает самостоятельное создание презентера в потомке.
Класс UserControlView обладает единственным свойством — Presenter которое инициализируется родителем на котором пользовательский элемент располагается.

Предполагается что презентер страницы содержит презентеры своих пользовательских элементов.

Пример реализации списка и его элемента:

List.aspx
<%@ Page Title="" Language="C#" MasterPageFile="~/Site.Master" AutoEventWireup="true" CodeBehind="List.aspx.cs" Inherits="WebApplication1.Client.List" EnableViewState="false" %>
<%@ Register TagPrefix="ctl" TagName="Item" Src="Details.ascx" %>
<asp:Content ContentPlaceHolderID="MainContent" runat="server">
    <asp:Repeater ID="ctlList" runat="server" >
        <ItemTemplate>
            <ctl:Item runat="server" Presenter="<%# Container.DataItem %>" />
        </ItemTemplate>
    </asp:Repeater>
</asp:Content>

List.aspx.cs
public partial class List : PageView<ListPresenter>
{
    protected override ListPresenter CreatePresenter()
    {
        return new ListPresenter();
    }

    protected override void OnLoad(System.EventArgs e)
    {
        base.OnLoad(e);
        ctlList.DataSource = Presenter.Items;
        ctlList.DataBind();
    }
}

Details.aspx
<%@ Control Language="C#" AutoEventWireup="true" CodeBehind="Details.ascx.cs" Inherits="WebApplication1.Client.Details" %>
<asp:TextBox runat="server" Text="<%# Presenter.Model.FirstName %>" OnTextChanged="OnFirstNameChanged" />
<asp:TextBox runat="server" Text="<%# Presenter.Model.LastName %>" OnTextChanged="OnLastNameChanged" />
<asp:Button runat="server" Text="Save" OnClick="OnSave" />
<br />

Detalis.aspx.cs
public partial class Details : UserControlView<DetailsPresenter>
{
    protected void OnFirstNameChanged(object sender, System.EventArgs e)
    {
        Presenter.Model.FirstName = ((TextBox) sender).Text;
    }

    protected void OnLastNameChanged(object sender, System.EventArgs e)
    {
        Presenter.Model.LastName = ((TextBox)sender).Text;
    }

    protected void OnSave(object sender, System.EventArgs e)
    {
        Presenter.Save();
    }
}


Кстати, прошу заметить, ViewState на страницах выключен.

Полностью пример можно скачать здесь.

Достоинства такого подхода:
1. Код страницы становится значительно проще
2. Можно писать модульные тесты на классы презентеров
3. Можно использовать презентеры для реализации различных интерфейсов (web, win, mobile)
4. Можно использовать совместно с лапшекодом и модернизировать приложение постепенно

Надеюсь мой опыт будет Вам полезен.
Tags:
Hubs:
+6
Comments 24
Comments Comments 24

Articles