Почти в каждом веб сайте есть общая часть дизайна, которую можно поместить в master page страницу. Все складывается хорошо, если этой частью является статический контент(шапка сайта с рисунком, меню и т.д.), но иногда надо показывать и динамические данные, например, список категорий или свежие новости сайта.
В asp.net webforms все очень просто: открываем файл с codebehind-ом и начинаем писать функционал для получения данных из бд. В asp.net mvc так сделать нельзя - там нет файла с c# кодом :).
Есть несколько способов реализации данной задачи:


1). Первый и самый плохой - написать helper метод, который будет вызываться прямо из Site.master.


<%@ Master Language="C#" Inherits="System.Web.Mvc.ViewMasterPage" %>

<html>
<body>
  <div class="page">
    <div id="header">
      <div id="title">
        <h1>My MVC Application</h1>
      </div>
      <div id="categories">
        <%: MyHelper.RenderCategories() %>
      </div>
      <div id="menucontainer">
      
        <ul id="menu">       
          <li><%: Html.ActionLink("Home", "Index", "Home")%></li>
          <li><%: Html.ActionLink("About", "About", "Home")%></li>
        </ul>
      </div>
    </div>
    <div id="main">
      <asp:ContentPlaceHolder ID="MainContent" runat="server" />
      <div id="footer">
      </div>
    </div>
  </div>
</body>
</html>


* This source code was highlighted with Source Code Highlighter.


<%: MyHelper.RenderCategories() %> - этот кусочек кода должен получить данные из бд и вернуть строку с разметкой. Этот вариант не подходит, поскольку он нарушает всю концепцию mvc - файлы представлений(view) не должны содержать в себе бизнес логики.


2). Второй способ - в каждом Action методе делать выборку и вставлять в коллекцию ViewData.


Для этого требуется изменить код контролера таким образом:
  public class HomeController : Controller
  {
    public const string CATEGORIES_KEY = "categories";
    public CategoriesRepository _categoryRepository;

    public HomeController()
    {
      _categoryRepository = new CategoriesRepository();
    }

    public ActionResult Index()
    {
      ViewData[CATEGORIES_KEY] = _categoryRepository.GetMainCategories();
      return View();
    }

    public ActionResult About()
    {
      ViewData[CATEGORIES_KEY] = _categoryRepository.GetMainCategories();
      return View();
    }
  }


* This source code was highlighted with Source Code Highlighter.


Также надо сделать функционал, который будет показывать разметку в Site.master:

<%@ Master Language="C#" Inherits="System.Web.Mvc.ViewMasterPage" %>
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
  <title><asp:ContentPlaceHolder ID="TitleContent" runat="server" /></title>
  <link href="../../Content/Site.css" rel="stylesheet" type="text/css" />
</head>

<body>
  <div class="page">
    <div id="header">
      <div id="title">
        <h1>My MVC Application</h1>
      </div>
      <div id="categories">
        <% var categories = ViewData["categories"] as IEnumerable<Category>; %>
        <ul>
        <% foreach (var category in categories)
          { %>
            <li><%= category.Name %></li>
        <% } %>
        </ul>
      </div>
      <div id="menucontainer">
      
        <ul id="menu">       
          <li><%: Html.ActionLink("Home", "Index", "Home")%></li>
          <li><%: Html.ActionLink("About", "About", "Home")%></li>
        </ul>
      </div>
    </div>
    <div id="main">
      <asp:ContentPlaceHolder ID="MainContent" runat="server" />
      <div id="footer">
      </div>
    </div>
  </div>
</body>
</html>

* This source code was highlighted with Source Code Highlighter.


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


3). Создать базовый класс BaseController для нашего HomeController.


Основная идея этого подхода - это создание базового класса для HomeController и реализации в нем логики выборки данных из базы данных.

Site.master остается без изменений.

  public class BaseController : Controller
  {
    public BaseController()
    {
      CategoriesRepository repository = new CategoriesRepository();
      ViewData["categories"] = repository.GetMainCategories();
    }
  }

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

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

* This source code was highlighted with Source Code Highlighter.

Теперь код HomeController выглядит красивее и соблюдает принцип DRY.


4). Создать фильтр, который будет заполнять коллекцию viewdata нужными данными


В таком случае HomeController будет выглядеть вот так:
  [CategoriesFilter]
  public class HomeController : Controller
  {
    public ActionResult Index()
    {
      return View();
    }

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


* This source code was highlighted with Source Code Highlighter.

А код атрибута:
  public class CategoriesFilterAttribute : ActionFilterAttribute
  {
    CategoriesRepository _repository;

    public CategoriesFilterAttribute()
    {
      _repository = new CategoriesRepository();
    }

    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
      filterContext.Controller.ViewData["categories"] = _repository.GetMainCategories();
    }
  }


* This source code was highlighted with Source Code Highlighter.




5). Еще один, наверное, самый распространенный метод - использование метода RenderAction


Вкратце: метод RenderAction - вызывает метод контролера.

В HomeController добавится еще один метод:

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

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

    [ChildActionOnly]
    public ActionResult GetCategories()
    {
      CategoriesRepository repository = new CategoriesRepository();
      var categories = repository.GetMainCategories();
      return View(categories);
    }
  }

* This source code was highlighted with Source Code Highlighter.

Вьюшка, которая будет показывать разметку для категорий выглядит след. образом:
<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<IEnumerable<Category>>" %>
<ul>
<% foreach (var category in Model)
  { %>
    <li><%= category.Name %></li>
<% } %>
</ul>

* This source code was highlighted with Source Code Highlighter.


Вывод:
Мне больше всего нравится метод с фильтром по нескольким причинам:
1). Метод с базовым классом не гибкий: запрос в базу данных будет происходить при вызове любого метода контролера, даже если он не будет использовать эти данные. В случае с фильтром, можно просто пометить те методы, которые будут использовать данные из бд.
2). Последний способ(RenderAction) - один из наиболее популярных. При его вызове создается новый экземпляр контролера и вызывается нужный action метод - это занимает некоторое время(тем более если вы используете иньекции в контролеры). Фильтр же позволяет избежать создание экземпляра класса контролера.

В четверг 14 октября состоялась очередная встреча профессиональной группы .net разработчиков в Харькове - uneta. Я рассказывал про MongoDB :), а точнее про наши первые впечатления об использовании сего продукта


Mongo
View more presentations from fudz1k.

что это?

10-40pm - блог, в котором можно найти новинки, связанные с миром IT, а также миллионы байтов полезной информации для повседневного применения.

.........

Теги