Внедрение сервисов в представления

Steve Smith

ASP.NET Core поддерживает внедрение зависимостей в представления. Это очень полезно для сервисов, предназначенных для представлений, таких как для локализации. В должны стараться поддерживать разделение ответственности между контроллерами и представлениями. Большинство данных, которые отображает представление, должны быть переданы им из контроллеров.

Файлы примера

Простой пример

Вы можете внедрить сервис в представление, используя директиву @inject. Вы можете рассматривать @inject так, как будто добавляете в представление свойство и заполняете свойство с помощью DI.

Синтаксис @inject:
@inject <type> <name>

Вот пример работы @inject:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
@using System.Threading.Tasks
@using ViewInjectSample.Model
@using ViewInjectSample.Model.Services
@model IEnumerable<ToDoItem>
@inject StatisticsService StatsService
<!DOCTYPE html>
<html>
<head>
    <title>To Do Items</title>
</head>
<body>
    <div>
        <h1>To Do Items</h1>
        <ul>
            <li>Total Items: @StatsService.GetCount()</li>
            <li>Completed: @StatsService.GetCompletedCount()</li>
            <li>Avg. Priority: @StatsService.GetAveragePriority()</li>
        </ul>
        <table>
            <tr>
                <th>Name</th>
                <th>Priority</th>
                <th>Is Done?</th>
            </tr>
            @foreach (var item in Model)
            {
                <tr>
                    <td>@item.Name</td>
                    <td>@item.Priority</td>
                    <td>@item.IsDone</td>
                </tr>
            }
        </table>
    </div>
</body>
</html>

Это представление отображает экземпляры ToDoItem наряду с общей статистикой. Резюмирующий экземпляр заполняется из внедренного StatisticsService. Этот сервис регистрируется с помощью внедрения зависимостей в ConfigureServices в Startup.cs:

1
2
3
4
5
6
7
8
// For more information on how to configure your application, visit http://go.microsoft.com/fwlink/?LinkID=398940
public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc();

    services.AddTransient<IToDoItemRepository, ToDoItemRepository>();
    services.AddTransient<StatisticsService>();
    services.AddTransient<ProfileOptionsService>();

StatisticsService выполняет некоторые вычисления по экземплярам ToDoItem, к которым он получает доступ через репозиторий:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
using System.Linq;
using ViewInjectSample.Interfaces;

namespace ViewInjectSample.Model.Services
{
    public class StatisticsService
    {
        private readonly IToDoItemRepository _toDoItemRepository;

        public StatisticsService(IToDoItemRepository toDoItemRepository)
        {
            _toDoItemRepository = toDoItemRepository;
        }

        public int GetCount()
        {
            return _toDoItemRepository.List().Count();
        }

        public int GetCompletedCount()
        {
            return _toDoItemRepository.List().Count(x => x.IsDone);
        }

        public double GetAveragePriority()
        {
            if (_toDoItemRepository.List().Count() == 0)
            {
                return 0.0;
            }

            return _toDoItemRepository.List().Average(x => x.Priority);
        }
    }
}

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

В примере отображаются данные из модели, связанной с представлением, и сервис внедрен в представление:

../../_images/screenshot.png

Заполнение данными

Внедрение представления может быть полезным для добавления опций в UI элементы, например, выпадающие списки. Представьте себе форму пользовательского профиля, которая включает в себя опции для указания пола, положения и других вещей. Отображение такой формы с помощью стандартного MVC подхода потребует контроллер, чтобы запрашивать сервисы доступа к данным для каждого из этих наборов опций, а затем заполнить модель или ViewBag.

При альтернативном подходе сервисы внедряются напрямую в представление, чтобы получить опции. Это уменьшает количество кода, требуемого контроллеру, перемещая эту логику создания элементов в само представление. Метод действия для отображения формы должен только передать форму экземпляру профиля:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
using Microsoft.AspNetCore.Mvc;
using ViewInjectSample.Model;

namespace ViewInjectSample.Controllers
{
    public class ProfileController : Controller
    {
        [Route("Profile")]
        public IActionResult Index()
        {
            // TODO: look up profile based on logged-in user
            var profile = new Profile()
            {
                Name = "Steve",
                FavColor = "Blue",
                Gender = "Male",
                State = new State("Ohio","OH")
            };
            return View(profile);
        }
    }
}

HTML форма, которая используется для обновления этих данных, включает в себя выпадающие списки для этих трех свойств:

../../_images/updateprofile.png

Эти списки заполняются сервисом, который был внедрен в представление:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
@using System.Threading.Tasks
@using ViewInjectSample.Model.Services
@model ViewInjectSample.Model.Profile
@inject ProfileOptionsService Options
<!DOCTYPE html>
<html>
<head>
    <title>Update Profile</title>
</head>
<body>
<div>
    <h1>Update Profile</h1>
    Name: @Html.TextBoxFor(m => m.Name)
    <br/>
    Gender: @Html.DropDownList("Gender",
           Options.ListGenders().Select(g => 
                new SelectListItem() { Text = g, Value = g }))
    <br/>

    State: @Html.DropDownListFor(m => m.State.Code,
           Options.ListStates().Select(s => 
                new SelectListItem() { Text = s.Name, Value = s.Code}))
    <br />

    Fav. Color: @Html.DropDownList("FavColor",
           Options.ListColors().Select(c => 
                new SelectListItem() { Text = c, Value = c }))
    </div>
</body>
</html>

ProfileOptionsService - это сервис на уровне UI, который предоставляет только данные, необходимые для этой формы:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
using System.Collections.Generic;

namespace ViewInjectSample.Model.Services
{
    public class ProfileOptionsService
    {
        public List<string> ListGenders()
        {
            // keeping this simple
            return new List<string>() {"Female", "Male"};
        }

        public List<State> ListStates()
        {
            // a few states from USA
            return new List<State>()
            {
                new State("Alabama", "AL"),
                new State("Alaska", "AK"),
                new State("Ohio", "OH")
            };
        }

        public List<string> ListColors()
        {
            return new List<string>() { "Blue","Green","Red","Yellow" };
        }
    }
}

Совет

Не забудьте зарегистрировать необходимые типы в методе ConfigureServices в Startup.cs.

Переопределение сервисов

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

../../_images/razor-fields.png

Как вы видите, поля по умолчанию включают в себя Html, Component и Url (а также StatsService, который мы внедрили). Если, например, вы хотите заменить HTML Helpers своими собственными, вы можете сделать это с помощью @inject:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
@using System.Threading.Tasks
@using ViewInjectSample.Helpers
@inject MyHtmlHelper Html
<!DOCTYPE html>
<html>
<head>
    <title>My Helper</title>
</head>
<body>
    <div>
        Test: @Html.Value
    </div>
</body>
</html>

Если вы хотите расширить существующие сервисы, просто используйте эту технологию при наследовании от существующей реализации заменой на свою собственную.

Поделись хорошей новостью с друзьями!
Следи за новостями!