Поделиться через


Внедрение зависимостей в представления в 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 добавляет в представление свойство и заполняет это свойство с помощью внедрения зависимостей (DI).

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

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>

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

См. также