Внедрение зависимостей в представления в ASP.NET Core

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

Просмотреть или скачать образец кода (как скачивать)

Внедрение конфигурации

Значения в файлах параметров, таких как appsettings.json и appsettings.Development.json, могут быть внедрены в представление. Рассмотрим пример appsettings.Development.jsonкода:

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "MyRoot": {
    "MyParent": {
      "MyChildName": "Joe"
    }
  }
}

Следующая разметка отображает значение конфигурации в представлении Razor Pages:

@page
@model PrivacyModel
@using Microsoft.Extensions.Configuration
@inject IConfiguration Configuration
@{
    ViewData["Title"] = "Privacy RP";
}
<h1>@ViewData["Title"]</h1>

<p>PR Privacy</p>

<h2>
   MyRoot:MyParent:MyChildName: @Configuration["MyRoot:MyParent:MyChildName"]
</h2>

Следующая разметка отображает значение конфигурации в представлении MVC:

@using Microsoft.Extensions.Configuration
@inject IConfiguration Configuration
@{
    ViewData["Title"] = "Privacy MVC";
}
<h1>@ViewData["Title"]</h1>

<p>MVC Use this page to detail your site's privacy policy.</p>

<h2>
   MyRoot:MyParent:MyChildName: @Configuration["MyRoot:MyParent:MyChildName"]
</h2>

Дополнительные сведения см. в статье Конфигурация в ASP.NET Core.

Внедрение службы

Службу можно внедрить в представление с помощью директивы @inject.

@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. Эта служба регистрируется для внедрения зависимостей в ConfigureServicesProgram.cs:

using ViewInjectSample.Helpers;
using ViewInjectSample.Infrastructure;
using ViewInjectSample.Interfaces;
using ViewInjectSample.Model.Services;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllersWithViews();
builder.Services.AddRazorPages();

builder.Services.AddTransient<IToDoItemRepository, ToDoItemRepository>();
builder.Services.AddTransient<StatisticsService>();
builder.Services.AddTransient<ProfileOptionsService>();
builder.Services.AddTransient<MyHtmlHelper>();

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Home/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.MapRazorPages();

app.MapDefaultControllerRoute();


app.Run();

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

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);
        }
    }
}

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

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

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

Заполнение данных подстановки

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

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

Альтернативный вариант получения параметров заключается во внедрении служб непосредственно в представление. Это позволяет свести к минимуму объем кода, необходимый контроллеру или странице razor, переместив эту логику построения элемента представления в само представление. Действие контроллера или Razor страница для отображения формы редактирования профиля должна передавать только экземпляр профиля:

using Microsoft.AspNetCore.Mvc;
using ViewInjectSample.Model;

namespace ViewInjectSample.Controllers;

public class ProfileController : Controller
{
    public IActionResult Index()
    {
        // A real app would up profile based on the user.
        var profile = new Profile()
        {
            Name = "Rick",
            FavColor = "Blue",
            Gender = "Male",
            State = new State("Ohio","OH")
        };
        return View(profile);
    }
}

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

Представление редактирования профиля с формой для ввода имени, пола, штата и любимого цвета.

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

@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 — это работающая на уровне пользовательского интерфейса служба, которая предоставляет именно те данные, которые нужны в этой форме:

namespace ViewInjectSample.Model.Services;

public class ProfileOptionsService
{
    public List<string> ListGenders()
    {
        // Basic sample
        return new List<string>() {"Female", "Male"};
    }

    public List<State> ListStates()
    {
        // Add a few states
        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" };
    }
}

Обратите внимание, что незарегистрированный тип вызывает исключение во время выполнения, так как поставщик услуг выполняет внутренний запрос через GetRequiredService.

Переопределение служб

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

Контекстное меню IntelliSense к введенному символу @ с полями Html, Component, StatsService и Url

К полям по умолчанию относятся Html, Componentи Url. Чтобы заменить вспомогательные функции HTML по умолчанию пользовательской версией, используйте @injectследующую команду:

@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>

См. также:

  • Блог Саймона Тиммса (Simon Timms): Getting Lookup Data Into Your View (Внедрение данных подстановки в ваше представление)

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

Просмотреть или скачать образец кода (как скачивать)

Внедрение конфигурации

appsettings.json значения можно вставить непосредственно в представление.

appsettings.json Пример файла:

{
   "root": {
      "parent": {
         "child": "myvalue"
      }
   }
}

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

Вот пример использования @inject:

@using Microsoft.Extensions.Configuration
@inject IConfiguration Configuration
@{
   string myValue = Configuration["root:parent:child"];
   ...
}

Внедрение службы

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

@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. Эта служба регистрируется для внедрения зависимостей в ConfigureServicesStartup.cs:

public class Startup
{
    // This method gets called by the runtime. Use this method to add services to the container.
    // For more information on how to configure your application, visit http://go.microsoft.com/fwlink/?LinkID=398940
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddMvc();

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

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);
        }
    }
}

Пример репозитория использует коллекцию в памяти. Представленная выше реализация (работающая со всеми данными в памяти) не рекомендуется для больших наборов данных с удаленным доступом.

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

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

Заполнение данных подстановки

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

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

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-форма, используемая для изменения этих свойств, содержит для трех свойств раскрывающиеся списки:

Представление редактирования профиля с формой для ввода имени, пола, штата и любимого цвета.

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

@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 — это работающая на уровне пользовательского интерфейса служба, которая предоставляет именно те данные, которые нужны в этой форме:

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" };
        }
    }
}

Важно!

Не забудьте зарегистрировать типы, которые вы будете запрашивать через внедрение зависимостей в методе Startup.ConfigureServices. Незарегистрированный тип вызывает исключение во время выполнения, так как поставщик услуг внутренний запрос осуществляется через GetRequiredService.

Переопределение служб

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

Контекстное меню IntelliSense к введенному символу @ с полями Html, Component, StatsService и Url

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

@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>

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

См. также:

  • Блог Саймона Тиммса (Simon Timms): Getting Lookup Data Into Your View (Внедрение данных подстановки в ваше представление)