Лучшие практики ASP.NET MVC (часть 2)

суббота, 2 июля 2011, Александр Краковецкий

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

Рекомендации по работе с роутингом (Routing)

Роутинг используется в ASP.NET MVC для ассоциации адресов (URL) и контроллеров, а не к конкретным файлам. Это полезно для читабельности адресов, так как разработчик может их задать вручную (это полезно для поддержки продукта и индексации поисковыми системами).

Роуты по умолчанию добавляются в таблицу роутов RouteTable внутри метода Application_Start в файле Global.asax. Карта роутов позволяет ассоциировать адреса к конкретным контроллерам и action методам.

Сортируйте роуты от специфических к общим когда используете стандартный роутинг

Таблица роутов имеет порядок, поэтому необходимо создавать роуты от специфических к более общим.

Рассмотрим следующий пример. Пусть у нас есть каталог продуктов, для которых мы хотим создать адреса в таком виде:

  • http://sellmyproducts/
  • http://sellmyproducts/Page#
  • http://sellmyproducts/category
  • http://sellmyproducts/category/Page#

Также мы имеем метод List (в классе ProductsController):

public ViewResult List(string category, int page)

Следующее объявление таблицы роутов корректно перенаправлял пользователя согласно приведенной выше схеме адресов:

routes.MapRoute(
    null,
    "",
    new { controller = "Products", action = "List", category = (string)null, page = 1 }
    );

routes.MapRoute(
    null,
    "Page{page}",
    new { controller = "Products", action = "List", category = (string)null },
    new { page = @"\d+" }
    );

routes.MapRoute(
    null,
    "{category}",
    new { controller = "Products", action = "List", page = 1}
    );

routes.MapRoute(
    null,
    "{category}/Page{page}",
    new { controller = "Products", action = "List"},
    new { page = @"\d+" }
    );

Используйте именованные роуты во избежание двусмысленности

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

В следующем примере создаются именованные роуты:

routes.MapRoute(
    "Default",
    "",
    new { controller = "Products", action = "List", category = (string)null, page = 1 }
    );

routes.MapRoute(
    "PageRoute",
    "Page{page}",
    new { controller = "Products", action = "List", category = (string)null },
    new { page = @"\d+" }
    );

Используя объявленные роуты, вы можете создать ссылку, которая явно использует имя роута:

@Html.RouteLink("Next", "PageRoute", new RouteValueDictionary( new { page = 1 } ));

Рекомендации по расширяемости

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

  • движок роутов (MvcRouteHandler)
  • фабрику контроллеров (IControllerFactory)
  • движок представления (IViewEngine)

Например, вы, возможно, захотите написать свою фабрику контроллеров, которая использует IoC контейнеры.

Замена основных компонентом пользовательским выходит за рамки этого документа. Однако, вы можете расширить фреймворк путем добавления пользовательской логики в виде фильтров. Некоторые стандартные фильтры, входящие в состав фреймворка: OutputCache, HandleError, и Authorize.

Используйте фильтры

MVC содержит механизм добавления фильтров с помощью специальных атрибутов. Фильтры могут быть применены к конкретному action методу для изменения его поведения. Также фильтры могут быть применены к классу контроллера, в этом случае это повлияет на все action методы контроллера. Фильтры с базовыми сценариями могут быть объявлены для базовых классов, например, контроллера, а потом все остальные классы контроллеров могут быть унаследованы от него.

Фильтр операции (action filter) — это атрибут, который, будучи связанным с классом или методом контроллера, позволяет декларативно подключать к запрошенной операции некое поведение. Написав фильтр операции, вы можете подключить исполняющий конвейер метода операции (action method) и адаптировать его под свои потребности. Благодаря этому вы также можете выделить из класса контроллера любую логику, которая непосредственно к нему не относится. Тем самым вы сделаете данное поведение повторно используемым и, что важнее, необязательным. Фильтры операций идеально подходят для реализации комплексов дополнительной обработки, влияющих на работу контроллеров.

Детальнее в статье Дино Эспозито "Фильтры операций в ASP.NET MVC"

Например, вы хотите добавить функциональность протоколирования для каждого запроса для отладки проблемы с HTTP заголовками. Следующий код объявляет класс, который является наследником класса ActionFilterAttribute:

public class LogHeadersFilterAttribute : ActionFilterAttribute
{
   public override void OnActionExecuting(ActionExecutingContext filterContext)
   {
     foreach (string header in 
filterContext.HttpContext.Request.Headers.AllKeys)
      {
            Debug.WriteLine("Header " + header);
            Debug.WriteLine("Value " +     
              filterContext.HttpContext.Request.Headers.Get(header)); 
      }
            base.OnActionExecuting(filterContext);
   }
}

Для того, чтобы применить этот фильтр для конкретного action метода, необходимо просто разместить атрибут LogHeadersFilter над нужным методом (контроллером).

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

В общем случае HandleError используется таким образом:

[HandleError]
public class HomeController : Controller
{
    public ActionResult Index()
    {
        throw new NullReferenceException();
    }

    public ActionResult About()
    {
        return View();
    }
}

При таком сценарии будет искаться файл представления под названием Error.

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

[HandleError(ExceptionType = typeof(NullReferenceException),
     View = "NullError")]
[HandleError(ExceptionType = typeof(SecurityException),
     View = "SecurityError")]
public class HomeController : Controller
{
    public ActionResult Index()
    {
        throw new NullReferenceException();
    }

    public ActionResult About()
    {
        return View();
    }
}

Фильтр Aurorize используется для разделения доступа к различным страницам. В общем случае код имеет такой вид:

public class HomeController : Controller
{
    public ActionResult Index()
    {
        return View();
    }

    [Authorize]
    public ActionResult About()
    {
        return View();
    }
}

В этом случае лишь авторизированнеы пользователи смогут посмотреть страницу About.

Если вы хотите разрешить простотр страницы только пользователям из конкретной группы, то код будет выглядеть таким образом:

public class HomeController : Controller
{
    public ActionResult Index()
    {
        return View();
    }

    [Authorize(Roles="Admin, SalesReps")]
    public ActionResult About()
    {
        return View();
    }
}

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

public class HomeController : Controller
{
    public ActionResult Index()
    {
        return View();
    }

    [Authorize(Users = "John, Bob")]
    public ActionResult About()
    {
        return View();
    }
}

Фильтр OutputCache позволяет использовать кэширование.

Можно указать время жизни кэш. В примере ниже используется 15-секундный кэш для Index:

public class HomeController : Controller
{
    [OutputCache(Duration = 15)]
    public ActionResult Index()
    {
        ViewData["Message"] = DateTime.Now;
        return View();
    }

    public ActionResult About()
    {
        return View();
    }
}

Также можно использовать кэширование в зависимости от переданного значения:

public class HomeController : Controller
{
    [OutputCache(Duration = 15, VaryByParam = "id")]
    public ActionResult Index(string id)
    {
        ViewData["Message"] = DateTime.Now;
        return View();
    }

    public ActionResult About()
    {
        return View();
    }
}

Рекомендации по тестированию

Одним из главных преимуществ MVC шаблона является возможность тестирования логики в условиях разделения концептов. В ASP.NET MVC вы легко можете выделить и протестировать отдельно бизнес логику в модели. Например, вы можете протестировать логику добавления ставки в аукционе, не затрагивая контроллер или представление.

Пишите модульные тесты

ASP.NET MVC предоставляет множество инструментов для разработчиков для создания тестируемых приложений. Также просто использовать внешние инструменты для модульного тестирования, моки или IoC контейнеры. Создание модульных тестов выходит за рамки этого документа. Но ASP.NET MVC предоставляет гибкую архитектуру для простого процесса тестирования за счет подключаемых движков разметки, фабрики контроллеров, различных типов action results и обверток вокруг контекстных типов ASP.NET. Для дополнительной информации о модульном тестировании ASP.NET MVC приложений ознакомьтесь с документом Unit Testing in MVC Applications.

Компании из статьи


Microsoft Украина


Сайт:
http://www.microsoft.com/ukr/ua/

Microsoft Украина Украинское подразделение компании Microsoft.

Ищите нас в интернетах!

Комментарии

Свежие вакансии