В этом посте хочу рассказать об использовании ASP.NET MVC в уже работающем WebForms приложении. Прежде чем начать делать что-то подобное, надо ответить на вопрос «Зачем?».
Кончено, зачастую нами движет желание попробовать что-то новое в работе — испробовать какую-то новую блестящую технологию, которая в сто раз лучше старой. Это субъективный фактор, за который цепляются все остальные. Загоревшись подобной идеей, мы начинаем собирать факты «за» и стараемся не обращать внимание на «против». Хочу лишний раз предостеречь от необдуманных шагов и тем более от сценария «всё старое выкидываем и переписываем заново».
Объективно веб-формы имеют своё право на жизнь и обладают рядом преимуществ, например с ними очень быстро можно сделать веб-форму, но, скажем, реализация AJAX через UpdatePanel выглядит как надругательство над всем разумом. Поэтому первое, что было решено: постепенно менять все места, где используется AJAX на MVC и разрабатывать новый функционал тоже на MVC. Отлаженные веб-формы было решено не трогать, но подвергнуть рефакторингу.
Разработчики, использующие ASP.NET MVC стараются использовать лучшие практики программирования, и основная прелесть этой технологии как раз в том, что она подталкивает разработчика делать это изначально: разделять логику частей приложения, инвертировать управление, писать юнит-тесты. Всё это, конечно, можно делать и в веб-формах, однако, в тех приложениях, что попадались мне на глаза ничего этого сделано не было, поэтому пришлось немало потрудиться, чтобы внедрить в них элементы MVC Framework: использовать роутинг, изолировать работу с сессией, контекстом, сервисами через их интерфейсы , а разрешать зависимости при помощи IoC контейнера ( в нашем случае использовался Unity).
Уже довольно много написано о том, как скрестить MVC и WebForms. Напишу о своём опыте: что же всё-таки было сделано, а главное — как.
Теперь можно прописывать роутинг в Global.asax. Я вынес это в отдельный класс-хелпер, занимающийся роутингом.
Это позволит использовать механизм роутинга только в одну сторону: определение хендлера по URL. Для того чтобы генерить URL во View, можно написать следующий хелпер:
Мой совет использовать такие хелперы везде, даже в MVC представлениях, вместо генерирования адреса по имени контроллера и акшена, правда с введением типизированной генерации путей, проблема хардкоженных имён несколько сглаживается.
К сожалению, внедрить зависимости через конструктор не получится, но можно создать кастомный httpHandler и инъектировать зависимости в свойства после создания страницы:
Кончено, зачастую нами движет желание попробовать что-то новое в работе — испробовать какую-то новую блестящую технологию, которая в сто раз лучше старой. Это субъективный фактор, за который цепляются все остальные. Загоревшись подобной идеей, мы начинаем собирать факты «за» и стараемся не обращать внимание на «против». Хочу лишний раз предостеречь от необдуманных шагов и тем более от сценария «всё старое выкидываем и переписываем заново».
Объективно веб-формы имеют своё право на жизнь и обладают рядом преимуществ, например с ними очень быстро можно сделать веб-форму, но, скажем, реализация AJAX через UpdatePanel выглядит как надругательство над всем разумом. Поэтому первое, что было решено: постепенно менять все места, где используется AJAX на MVC и разрабатывать новый функционал тоже на MVC. Отлаженные веб-формы было решено не трогать, но подвергнуть рефакторингу.
Разработчики, использующие ASP.NET MVC стараются использовать лучшие практики программирования, и основная прелесть этой технологии как раз в том, что она подталкивает разработчика делать это изначально: разделять логику частей приложения, инвертировать управление, писать юнит-тесты. Всё это, конечно, можно делать и в веб-формах, однако, в тех приложениях, что попадались мне на глаза ничего этого сделано не было, поэтому пришлось немало потрудиться, чтобы внедрить в них элементы MVC Framework: использовать роутинг, изолировать работу с сессией, контекстом, сервисами через их интерфейсы , а разрешать зависимости при помощи IoC контейнера ( в нашем случае использовался Unity).
Уже довольно много написано о том, как скрестить MVC и WebForms. Напишу о своём опыте: что же всё-таки было сделано, а главное — как.
1. Роутинг
Все страницы приложения связаны ссылками. Первое, что необходимо сделать — избавиться от всех захардкоженных ссылок на веб-формах и создать единый механизм роутинга. Для того, чтобы заставить механизм роутинга MVC вызывать страницы aspx, необходимо создать кастомный IRouteHandler.public class AspxPageRouteHandler<T>: IRouteHandler where T : IHttpHandler
{
private readonly string _virtualPath;
public AspxPageRouteHandler(string virtualPath)
{
_virtualPath = virtualPath;
}
public IHttpHandler GetHttpHandler(RequestContext requestContext)
{
var page = BuildManager.CreateInstanceFromVirtualPath(_virtualPath, typeof(Page)) as Page;
if (page != null)
{
// Да, это не очень круто, но придётся сделать синглтон из контейнера
DependencyResolver.Current.BuildUp(page.GetType(), page);
return page;
}
return null;
}
}
public const string ORDER_PAGE_ROUTE_NAME = "ORDER_PAGE_ROUTE_NAME";
public static void RegisterRoutes(RouteCollection routes)
{
// .. Прописываем сначала MVC роутинг
routes.Add(ORDER_PAGE_ROUTE_NAME, CreateRouteToAspx("orders/newOrder"));
// .. other routes
// Default route
routes.MapRoute(
"Default",
"{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional });
}
private static Route CreateRouteToAspx(string urlPath) where T : IHttpHandler
{
var noControllerDefaults = new RouteValueDictionary { { "controller", null }, { "action", null } };
return new Route(urlPath, noControllerDefaults, new AspxPageRouteHandler(string.Format("~/{0}.aspx", urlPath )));
}
Это позволит использовать механизм роутинга только в одну сторону: определение хендлера по URL. Для того чтобы генерить URL во View, можно написать следующий хелпер:
public static string GetOrderFormLink(this UrlHelper helper)
{
return helper.RouteUrl(ORDER_PAGE_ROUTE_NAME, new RouteValueDictionary { { "action", "Index" } });
}
Мой совет использовать такие хелперы везде, даже в MVC представлениях, вместо генерирования адреса по имени контроллера и акшена, правда с введением типизированной генерации путей, проблема хардкоженных имён несколько сглаживается.
2. Dependency Injection
Следующий шаг — прикручивание IoC контейнера для инъектирования зависимостей страницы от внешних сервисов. Необходимо учесть, что решение использовать контейнер должно быть обдумано и обосновано. Стоит это делать если планируется тестировать страницы и контроллеры, а также если реализаций сервисов будет несколько.К сожалению, внедрить зависимости через конструктор не получится, но можно создать кастомный httpHandler и инъектировать зависимости в свойства после создания страницы:
public class PageHandlerFactory : PageHandlerFactory
{
public override IHttpHandler GetHandler(HttpContext context, string requestType, string virtualPath, string path)
{
var page = base.GetHandler(context, requestType, virtualPath, path) as Page;
if (page == null)
return null;
DependencyResolver.Current.BuildUp(page.GetType(), page);
return page;
}
}
2.1 IocPage
Будет полезно создать базовый Page-класс, поддерживающий IoC.public abstract class BaseWebPage : Page
{
[Dependency]
public ISomeService CommonService { get; set; }
[Dependency]
public IHttpContext PageContext { get; set; }
// ..
}
2.2 IocControl
Вместе с ним, базовый Control-класс, поддерживающий IoC.public class IocControl : UserControl
{
protected override void FrameworkInitialize()
{
base.FrameworkInitialize();
DependencyResolver.Current.BuildUp(GetType(), this);
}
}
No comments:
Post a Comment