Компоненты представления

Rick Anderson

Просмотрите или скачайте пример с GitHub <https://github.com/aspnet/Docs/tree/master/aspnet/mvc/views/view-components/sample>`__.

Компоненты представления

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

  • Работает с частью, а не со всем ответом
  • Использует преимущества разделения ответственности и тестируемости, какие существуют между контроллером и представлением
  • Может содержать параметры и бизнес логику

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

  • меню с динамической навигацией
  • облако тэгов (с запросами к БД)
  • панель логина
  • корзина покупателя
  • недавно опубликованные статьи
  • контент сбоку в обычном блоге
  • панель логина, которая отображается на каждой странице и показывает ссылки для логаута или логина в зависимости от текущего статуса пользователя

Компонент представления состоит из двух частей: класса (обычно наследуется от ViewComponent) и результата, который он возвращает (обычно представления). Как и контроллер, компонент представления может быть POCO, но большинство разработчиков предпочитают воспользоваться преимуществами методов и свойств, доступных при наследовании от ViewComponent.

Создание компонента представления

В этом разделе рассказывается, как создать компонент представления. Далее мы рассмотрим каждый шаг и создадим компонент представления.

Класс компонента представления

Класс компонента представления может быть создан каким-либо из этих способов:

  • наследование от ViewComponent
  • когда классу добавляется атрибут [ViewComponent] или когда класс наследуется от класса с атрибутом [ViewComponent]
  • добавить в конце имени класса ViewComponent

Как и контроллеры, компоненты представления должны быть открытыми, не вложенными, не абстрактными классами. Имя компонента представления - это имя класса с удаленным суффиксом “ViewComponent”. Его также можно указать напрямую, используя свойство `ViewComponentAttribute.Name`__.

Класс компонента представления:

  • полностью поддерживает конструктор DI
  • не принимает участия в жизненном цикле контроллера, то есть, вы не можете использовать фильтры

Методы компонента представления

Логика компонента представления определена в его методе InvokeAsync, который возвращает IViewComponentResult. Параметры передаются напрямую при вызове компонента представления, а не при связывании моделей. Компонент представления никогда напрямую не обрабатывает запрос. Обычно компонент представления инициализирует модель и передает ее представлению, вызывая метод View. Методы компонента представления:

  • определяют метод InvokeAsync`, который возвращает IViewComponentResult
  • обычно инициализируют модель и передают ее представлению, вызывая ViewComponent View
  • параметры передаются при вызове метода, не HTTP, здесь нет связывания моделей
  • не доступны напрямую в качестве конечной точки HTTP, они вызываются из кода (обычно в представлении). Компонент представления никогда не обрабатывает запрос

Путь поиска представления

Среда разработки ищет представление по следующим путям:

  • Views/<controller_name>/Components/<view_component_name>/<view_name>
  • Views/Shared/Components/<view_component_name>/<view_name>

По умолчанию имя представления для компонента представления - это Default, то есть, файл представления обычно называется Default.cshtml. Вы можете указать другое имя представления при создании результата компонента представления или при вызове метода View.

Мы рекомендуем называть файл представления Default.cshtml и использовать путь Views/Shared/Components/<view_component_name>/<view_name>. Компонент представления ``PriorityList``из данного примера использует Views/Shared/Components/PriorityList/Default.cshtml.

Вызов компонента представления

Чтобы использовать компонент представления, вызовите из представления @Component.InvokeAsync("имя компонента представления", <анонимный тип с параметрами>). Параметры передаются методу InvokeAsync. Компонент представления PriorityList, используемый в этой статье, вызывается из файла Views/Todo/Index.cshtml. Далее метод InvokeAsync вызывается с двумя параметрами:

@await Component.InvokeAsync("PriorityList", new { maxPriority = 2, isDone = false })

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

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

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

  public IActionResult IndexVC()
  {
      return ViewComponent("PriorityList", new { maxPriority = 3, isDone = false });
  }

Создание простого компонента представления

Скачайте, соберите и протестируйте код. Это простой проект с контроллером Todo, который отображает список элементов Todo.

../../_images/2dos.png

Добавление класса ViewComponent

Создайте папку ViewComponents и добавьте класс PriorityListViewComponent.

using Microsoft.AspNet.Mvc;
using Microsoft.Data.Entity;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using ViewComponentSample.Models;

namespace ViewComponentSample.ViewComponents
{
    public class PriorityListViewComponent : ViewComponent
    {
        private readonly ToDoContext db;

        public PriorityListViewComponent(ToDoContext context)
        {
            db = context;
        }

        public async Task<IViewComponentResult> InvokeAsync(
        int maxPriority, bool isDone)
        {
            var items = await GetItemsAsync(maxPriority, isDone);
            return View(items);
        }
        private Task<List<TodoItem>> GetItemsAsync(int maxPriority, bool isDone)
        {
            return db.ToDo.Where(x => x.IsDone == isDone &&
                                 x.Priority <= maxPriority).ToListAsync();
        }       
    }
}

В коде обратите внимание на:

  • классы компонента представления могут содержаться в любой папке проекта

  • поскольку имя класса PriorityListViewComponent заканчивается на ViewComponent, при рантайме будет использоваться строка “PriorityList”. Позже мы это обсудим более детально.

  • атрибут [ViewComponent] может изменить имя, которое используется для ссылки на компонент представления. Например, мы могли бы назвать класс XYZ и применить атрибут ViewComponent:

    [ViewComponent(Name = "PriorityList")]
    public class XYZ : ViewComponent
    
  • Атрибут [ViewComponent] говорит селектору компонента представлений использовать имя PriorityList, когда он ищет представления, связанные с компонентом, и использовать строку “PriorityList” при ссылке на компонент класса из представления.

  • Компонент использует внедрение зависимостей для получения данных.

  • InvokeAsync представляет метод, который можно вызвать из представления, и он может принимать произвольное число аргументов.

  • Метод InvokeAsync возвращает набор “сырых” элементов ToDo, и их приоритет меньше или равен maxPriority.

Создание нового компонента представления Razor

  1. Создайте папку Views/Shared/Components. Эта папка должна быть названа Components.
  2. Создайте папку Views/Shared/Components/PriorityList. Имя этой папки должно соответствовать имени класса компонента представления или имени класса минус суффикс (если мы следовали соглашению и использовали суффикс ViewComponent в имени класса). Если вы использовали атрибут ViewComponent, имя класса должно соответствовать обозначению атрибута.
  3. Создайте Razor представление Views/Shared/Components/PriorityList/Default.cshtml.
@model IEnumerable<ViewComponentSample.Models.TodoItem>

<h3>Priority Items</h3>
<ul>
    @foreach (var todo in Model)
    {
        <li>@todo.Name</li>
    }
</ul>

Razor представление принимает список``TodoItem`` и отображает его. Если метод InvokeAsync не передал имя представления (как в нашем примере), именем представления будет Default. Далее я покажу вам, как передать имя представления. Чтобы переопределить стилизацию для контроллера, нужно добавить в контроллер конкретную папку (например, Views/Todo/Components/PriorityList/Default.cshtml).

Если компонент представления предназначался для конкретного контроллера, вы можете добавить его в папку контроллера (Views/Todo/Components/PriorityList/Default.cshtml)

Добавьте div, содержащий вызов приоритетного списка компонентов, в начале файла Views/Todo/index.cshtml:

    }
</table>
<div >
    @await Component.InvokeAsync("PriorityList", new { maxPriority = 2, isDone = false })
</div>

Разметка @Component.InvokeAsync показывает синтаксис для вызова компонентов представления. Первый аргумент - это имя компонента, который мы хотим вызвать. Последующие параметры передаются компоненту. InvokeAsync может принимать произвольное число аргументов.

На изображении снизу показан список приоритетных элементов:

../../_images/pi.png

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

  public IActionResult IndexVC()
  {
      return ViewComponent("PriorityList", new { maxPriority = 3, isDone = false });
  }

Определение имени представления

При определенных условиях для сложного компонента представления нужно указать представление не по умолчанию. В следующем коде показано, как указать представление “PVC” из метода InvokeAsync. Обновите метод InvokeAsync в классе PriorityListViewComponent.

public async Task<IViewComponentResult> InvokeAsync(
    int maxPriority, bool isDone)
{
    string MyView = "Default";
    // If asking for all completed tasks, render with the "PVC" view.
    if (maxPriority > 3 && isDone == true)
    {
        MyView = "PVC";
    }
    var items = await GetItemsAsync(maxPriority, isDone);
    return View(MyView, items);
}

Скопируйте файл Views/Shared/Components/PriorityList/Default.cshtml в представление Views/Shared/Components/PriorityList/PVC.cshtml. Добавьте заголовок, чтобы указать используемое PVC представление.

@model IEnumerable<ViewComponentSample.Models.TodoItem>

<h2> PVC Named Priority Component View</h2>
<h4>@ViewBag.PriorityMessage</h4>
<ul>
    @foreach (var todo in Model)
    {
        <li>@todo.Name</li>
    }
</ul>

Обновите Views/TodoList/Index.cshtml

</table>

<div>
    @await Component.InvokeAsync("PriorityList", new { maxPriority = 4, isDone = true })
</div>

Запустите приложение и проверьте PVC представление.

../../_images/pvc.png

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

Изучение пути представления

  1. Измените параметр приоритета на 3 или ниже, чтобы представление не возвращалось.

  2. Временно переименуйте Views/Todo/Components/PriorityList/Default.cshtml в Temp.cshtml.

  3. Протестируйте приложение, и вы увидите следующую ошибку:

    An unhandled exception occurred while processing the request.
    
    InvalidOperationException: The view 'Components/PriorityList/Default'
       was not found. The following locations were searched:
       /Views/ToDo/Components/PriorityList/Default.cshtml
       /Views/Shared/Components/PriorityList/Default.cshtml.
    Microsoft.AspNet.Mvc.ViewEngines.ViewEngineResult.EnsureSuccessful()
    

Скопируйте Views/Shared/Components/PriorityList/Default.cshtml в *Views/Todo/Components/PriorityList/Default.cshtml. #. Добавьте в представление Todo разметку. #. Протестируйте non-shared.

../../_images/shared.png

Как избежать магических строк

Вы можете заменить жестко закодированное имя компонента представления именем класса. Создайте компонент представления без суффикса “ViewComponent”:

using Microsoft.AspNet.Mvc;
using Microsoft.Data.Entity;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using ViewComponentSample.Models;

namespace ViewComponentSample.ViewComponents
{
    public class PriorityList : ViewComponent
    {
        private readonly ToDoContext db;

        public PriorityList(ToDoContext context)
        {
            db = context;
        }

        public async Task<IViewComponentResult> InvokeAsync(
        int maxPriority, bool isDone)
        {
            var items = await GetItemsAsync(maxPriority, isDone);
            return View(items);
        }
        private Task<List<TodoItem>> GetItemsAsync(int maxPriority, bool isDone)
        {
            return db.ToDo.Where(x => x.IsDone == isDone &&
                                 x.Priority <= maxPriority).ToListAsync();
        }
    }
}

Добавьте выражение using в файл Razor представления и используйте оператор nameof:

@using ViewComponentSample.Models
@using ViewComponentSample.ViewComponents
@model IEnumerable<TodoItem>

<h2>ToDo nameof</h2>
<!-- Markup removed for brevity.  -->
    }
</table>

<div>

    @await Component.InvokeAsync(nameof(PriorityList), new { maxPriority = 4, isDone = true })
</div>
Поделись хорошей новостью с друзьями!
Следи за новостями!