在 ASP.NET Core 中将依赖项注入到视图

ASP.NET Core 支持将依赖关系注入到视图。 这对于视图特定服务很有用,例如仅为填充视图元素所需的本地化或数据。 视图显示的大部分数据应该从控制器传入。

查看或下载示例代码如何下载

配置注入

设置文件中的值(如 appsettings.jsonappsettings.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 中填充。 在 Program.csConfigureServices 中为依赖项注入注册此服务:

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

示例存储库使用内存中集合。 内存中实现不应用于远程访问的大型数据集。

该示例显示绑定到视图的模型数据以及注入到视图中的服务:

To Do view listing total items, completed items, average priority, and a list of tasks with their priority levels and boolean values indicating completion.

填充查找数据

视图注入可用于填充 UI 元素(如下拉列表)中的选项。 请考虑这样的用户个人资料窗体,其中包含用于指定性别、状态和其他首选项的选项。 使用标准方法呈现此类窗体可能需要控制器或 Razor Page 才能:

  • 请求每个选项集的数据访问服务。
  • 使用要绑定的每个选项集填充模型或 ViewBag

另一种方法是将服务直接注入视图以获取选项。 这最大限度地减少了控制器或 razor Page 所需的代码量,将此视图元素构造逻辑移入视图本身。 显示个人资料编辑窗体的控制器操作或 Razor Page 只需要传递个人资料实例的窗体:

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 窗体包括三个属性的下拉列表:

Update Profile view with a form allowing the entry of name, gender, state, and favorite Color.

这些列表由已注入视图的服务填充:

@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 级别的服务,旨在准确提供此窗体所需的数据:

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 contextual menu on a typed @ symbol listing Html, Component, StatsService, and Url fields

默认字段包括 HtmlComponentUrl。 若要将默认 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>

另请参阅

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 中填充。 在 Startup.csConfigureServices 中为依赖项注入注册此服务:

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

示例存储库使用内存中集合。 建议不将上示实现(对内存中的所有数据进行操作)用于远程访问的大型数据集。

该示例显示绑定到视图的模型数据以及注入到视图中的服务:

To Do view listing total items, completed items, average priority, and a list of tasks with their priority levels and boolean values indicating completion.

填充查找数据

视图注入可用于填充 UI 元素(如下拉列表)中的选项。 请考虑这样的用户个人资料窗体,其中包含用于指定性别、状态和其他首选项的选项。 使用标准 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 窗体包括三个属性的下拉列表:

Update Profile view with a form allowing the entry of name, gender, state, and favorite Color.

这些列表由已注入视图的服务填充:

@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 级别的服务,旨在准确提供此窗体所需的数据:

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 contextual menu on a typed @ symbol listing Html, Component, StatsService, and Url fields

如你所见,包括默认字段 HtmlComponentUrl(以及我们注入的 StatsService)。 如果想用自己的 HTML 帮助程序替换默认的 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>

如果想扩展现有的服务,用自己的方法继承或包装现有的实现时,只需使用此方法。

另请参阅