ASP.NET Core 中的视图组件

作者:Rick Anderson

视图组件

视图组件与分部视图类似,但它们的功能更加强大。 视图组件不使用模型绑定,它们依赖于调用视图组件时传递的数据。 本文是使用控制器和视图编写的,但视图组件适用于 Razor Pages

视图组件:

  • 呈现一个区块而不是整个响应。
  • 包括控制器和视图间发现的相同关注点分离和可测试性优势。
  • 可以有参数和业务逻辑。
  • 通常从布局页调用。

视图组件可用于具有可重用呈现逻辑(对部分视图来说过于复杂)的任何位置,例如:

  • 动态导航菜单
  • 标记云,查询数据库的位置
  • 登录面板
  • 购物车
  • 最近发布的文章
  • 博客上的边栏内容
  • 一个登录面板,呈现在每页上并显示注销或登录链接,具体取决于用户的登录状态

视图组件包含两个部分:

  • 类,通常派生自 ViewComponent
  • 它返回的结果,通常是视图。

与控制器一样,视图组件可以是 POCO,但大多数开发人员都利用派生自 ViewComponent 的可用方法和属性。

在考虑视图组件是否符合应用的规范时,请考虑改用 Razor 组件。 Razor 组件还将标记与 C# 代码组合来生成可重用的 UI 单元。 Razor 组件专用于让开发人员在提供客户端 UI 逻辑和组合时保持高效。 有关详细信息,请参阅 ASP.NET Core Razor 组件。 有关如何将组件合并 Razor 到 MVC 或 Razor Pages 应用中的信息,请参阅 将 ASP.NET 核心 Razor 组件与 MVC 或 Razor Pages 集成。

创建视图组件

本部分包含创建视图组件的高级别要求。 本文后续部分将详细检查每个步骤并创建视图组件。

视图组件类

可通过以下任一方法创建视图组件类:

与控制器一样,视图组件必须是公共、非嵌套和非抽象的类。 视图组件名称是删除了 ViewComponent 后缀的类名。 也可以使用 Name 属性显式指定它。

视图组件类:

要阻止将具有 ViewComponent 后缀(不区分大小写)的类视为视图组件,请使用 [NonViewComponent] 属性修饰该类:

using Microsoft.AspNetCore.Mvc;

[NonViewComponent]
public class ReviewComponent
{
    public string Status(string name) => JobStatus.GetCurrentStatus(name);
}

视图组件方法

视图组件在以下方法中定义其逻辑:

  • 返回 Task<IViewComponentResult>InvokeAsync 方法。
  • 返回 IViewComponentResultInvoke 同步方法。

参数直接来自视图组件的调用,而不是来自模型绑定。 视图组件从不直接处理请求。 通常,视图组件通过调用 View 方法来初始化模型并将其传递到视图。 总之,视图组件方法:

  • 定义返回 Task<IViewComponentResult>InvokeAsync 方法,或是返回 IViewComponentResult 的同步 Invoke 方法。
  • 通常,通过调用 ViewComponent.View 方法来初始化模型并将其传递到视图。
  • 参数来自调用方法,而不是 HTTP。 没有模型绑定。
  • 不可直接作为 HTTP 终结点进行访问。 它们通常在视图中调用。 视图组件从不处理请求。
  • 在签名上重载,而不是当前 HTTP 请求的任何详细信息。

视图搜索路径

运行时在以下路径中搜索视图:

  • /Views/{Controller Name}/Components/{View Component Name}/{View Name}
  • /Views/Shared/Components/{View Component Name}/{View Name}
  • /Pages/Shared/Components/{View Component Name}/{View Name}
  • /Areas/{Area Name}/Views/Shared/Components/{View Component Name}/{View Name}

搜索路径适用于使用控制器 + 视图和 Razor Pages 的项目。

视图组件的默认视图名称为 Default,这意味着视图文件通常命名为 Default.cshtml。 可以在创建视图组件结果或调用 View 方法时指定不同的视图名称。

建议将视图文件命名为 Default.cshtml 并使用 Views/Shared/Components/{View Component Name}/{View Name} 路径。 此示例中使用的 PriorityList 视图组件对视图组件视图使用 Views/Shared/Components/PriorityList/Default.cshtml

自定义视图搜索路径

修改 Razor 的 ViewLocationFormats 集合,以自定义视图搜索路径。 例如,将新项添加到集合,以搜索路径 /Components/{View Component Name}/{View Name} 中的视图:

using Microsoft.EntityFrameworkCore;
using ViewComponentSample.Models;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllersWithViews()
    .AddRazorOptions(options =>
    {
        options.ViewLocationFormats.Add("/{0}.cshtml");
    });

builder.Services.AddDbContext<ToDoContext>(options =>
        options.UseInMemoryDatabase("db"));

var app = builder.Build();

// Remaining code removed for brevity.

在前面的代码中,占位符 {0} 表示路径 Components/{View Component Name}/{View Name}

调用视图组件

要使用视图组件,请在视图中调用以下内容:

@await Component.InvokeAsync("Name of view component",
                             {Anonymous Type Containing Parameters})

参数将传递给 InvokeAsync 方法。 本文中开发的 PriorityList 视图组件调用自 Views/ToDo/Index.cshtml 视图文件。 在以下代码中,使用两个参数调用 InvokeAsync 方法:

</table>

<div>
    Maximum Priority: @ViewData["maxPriority"] <br />
    Is Complete:  @ViewData["isDone"]
    @await Component.InvokeAsync("PriorityList",
                     new { 
                         maxPriority =  ViewData["maxPriority"],
                         isDone = ViewData["isDone"]  }
                     )
</div>

作为标记帮助程序调用视图组件

视图组件可以作为标记帮助程序调用:

<div>
       Maxium Priority: @ViewData["maxPriority"] <br />
       Is Complete:  @ViewData["isDone"]
    @{
        int maxPriority = Convert.ToInt32(ViewData["maxPriority"]);
        bool isDone = Convert.ToBoolean(ViewData["isDone"]);
    }
    <vc:priority-list max-priority=maxPriority is-done=isDone>
    </vc:priority-list>
</div>

标记帮助程序采用 Pascal 大小写格式的类和方法参数将转换为各自相应的短横线格式。 要调用视图组件的标记帮助程序使用 <vc></vc> 元素。 按如下方式指定视图组件:

<vc:[view-component-name]
  parameter1="parameter1 value"
  parameter2="parameter2 value">
</vc:[view-component-name]>

若要将视图组件用作标记帮助程序,请使用 @addTagHelper 指令注册包含视图组件的程序集。 如果视图组件位于名为 MyWebApp 的程序集中,请将以下指令添加到 _ViewImports.cshtml 文件:

@addTagHelper *, MyWebApp

可将视图组件作为标记帮助程序注册到任何引用视图组件的文件。 要详细了解如何注册标记帮助程序,请参阅管理标记帮助程序作用域

本教程中使用的 InvokeAsync 方法:

@await Component.InvokeAsync("PriorityList",
                 new { 
                     maxPriority =  ViewData["maxPriority"],
                     isDone = ViewData["isDone"]  }
                 )

在前面的标记中,PriorityList 视图组件变为 priority-list。 视图组件的参数作为短横线格式的属性进行传递。

从控制器直接调用视图组件

视图组件通常从视图调用,但可以直接从控制器方法调用它们。 尽管视图组件不定义控制器等终结点,但可以实现返回 ViewComponentResult 内容的控制器操作。

在以下示例中,直接从控制器调用视图组件:

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

创建基本视图组件

下载、生成和测试起始代码。 它是一个带有 ToDo 控制器的基本项目,该控制器显示 ToDo 项列表。

ToDo 列表

更新控制器以传入优先级和完成状态

更新 Index 方法以使用优先级和完成状态参数:

using Microsoft.AspNetCore.Mvc;
using ViewComponentSample.Models;

namespace ViewComponentSample.Controllers;
public class ToDoController : Controller
{
    private readonly ToDoContext _ToDoContext;

    public ToDoController(ToDoContext context)
    {
        _ToDoContext = context;
        _ToDoContext.Database.EnsureCreated();
    }

    public IActionResult Index(int maxPriority = 2, bool isDone = false)
    {
        var model = _ToDoContext!.ToDo!.ToList();
        ViewData["maxPriority"] = maxPriority;
        ViewData["isDone"] = isDone;
        return View(model);
    }

添加 ViewComponent 类

将 ViewComponent 类添加到 ViewComponents/PriorityListViewComponent.cs

using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
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] 属性可以更改用于引用视图组件的名称。 例如,类可以使用以下 [ViewComponent] 属性命名为 XYZ

    [ViewComponent(Name = "PriorityList")]
       public class XYZ : ViewComponent
    
  • 上述代码中的 [ViewComponent] 属性告知视图组件选择器使用:

    • 查找与组件关联的视图时的名称 PriorityList
    • 从视图中引用类组件时的字符串“PriorityList”。
  • 组件使用依赖关系注入以使数据上下文可用。

  • InvokeAsync 公开可以从视图调用的方法,且可以采用任意数量的参数。

  • InvokeAsync 方法返回满足 isDonemaxPriority 参数的 ToDo 项集。

创建视图组件 Razor 视图

  • 创建 Views/Shared/Components 文件夹。 此文件夹 必须 命名为 Components

  • 创建 Views/Shared/Components/PriorityList 文件夹。 此文件夹名称必须与视图组件类的名称相匹配,或者与类的名称减去后缀相匹配。 如果使用了 ViewComponent 属性,则类名称需要匹配指定的属性。

  • 创建 Views/Shared/Components/PriorityList/Default.cshtmlRazor 视图:

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

    Razor 视图获取并显示 TodoItem 列表。 如果视图组件 InvokeAsync 方法不传递视图名称,则按照约定使用“默认”作为视图名称。 要替代特定控制器的默认样式,请将视图添加到控制器特定的视图文件夹(例如 Views/ToDo/Components/PriorityList/Default.cshtml)。

    如果视图组件特定于控制器,则可以将其添加到特定于控制器的文件夹。 例如,Views/ToDo/Components/PriorityList/Default.cshtml 是特定于控制器的。

  • 将包含优先级列表组件调用的 div 添加到 Views/ToDo/index.cshtml 文件底部:

    </table>
    
    <div>
        Maximum Priority: @ViewData["maxPriority"] <br />
        Is Complete:  @ViewData["isDone"]
        @await Component.InvokeAsync("PriorityList",
                         new { 
                             maxPriority =  ViewData["maxPriority"],
                             isDone = ViewData["isDone"]  }
                         )
    </div>
    

标记 @await Component.InvokeAsync 显示调用视图组件的语法。 第一个参数是要调用的组件的名称。 后续参数将传递给该组件。 InvokeAsync 可以采用任意数量的参数。

测试应用。 下图显示 ToDo 列表和优先级项:

Todo 列表和优先级项

可以直接从控制器调用视图组件:

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

IndexVC 操作的优先级项

指定视图组件名称

在某些情况下,复杂的视图组件可能需要指定非默认视图。 以下代码显示如何从 InvokeAsync 方法指定“PVC”视图。 更新 PriorityListViewComponent 类中的 InvokeAsync 方法。

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>

运行应用并验证 PVC 视图。

优先级视图组件

如果不呈现 PVC 视图,请验证是否调用优先级为 4 或更高的视图组件。

检查视图路径

  • 将优先级参数更改为 3 或更低,从而不返回优先级视图。

  • 暂时将 Views/ToDo/Components/PriorityList/Default.cshtml 重命名为 1Default.cshtml

  • 测试应用,出现以下错误:

    An unhandled exception occurred while processing the request.
    InvalidOperationException: The view 'Components/PriorityList/Default' wasn't found. The following locations were searched:
    /Views/ToDo/Components/PriorityList/Default.cshtml
    /Views/Shared/Components/PriorityList/Default.cshtml
    
  • Views/ToDo/Components/PriorityList/1Default.cshtml 复制到 Views/Shared/Components/PriorityList/Default.cshtml

  • 将一些标记添加到共享 ToDo 视图组件视图,以指示视图来自“Shared”文件夹。

  • 测试“共享”组件视图

有共享组件视图的 ToDo 输出

避免使用硬编码字符串

为确保编译时安全性,请将硬编码的视图组件名称替换为类名。 将 PriorityListViewComponent.cs 文件更新为不使用“ViewComponent”后缀:

using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
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();
    }
}

视图文件:

</table>

<div>
    Testing nameof(PriorityList) <br />

    Maxium Priority: @ViewData["maxPriority"] <br />
    Is Complete:  @ViewData["isDone"]
    @await Component.InvokeAsync(nameof(PriorityList),
                     new { 
                         maxPriority =  ViewData["maxPriority"],
                         isDone = ViewData["isDone"]  }
                     )
</div>

采用 CLR 类型的 Component.InvokeAsync 方法重载使用 typeof 运算符:

</table>

<div>
    Testing typeof(PriorityList) <br />

    Maxium Priority: @ViewData["maxPriority"] <br />
    Is Complete:  @ViewData["isDone"]
    @await Component.InvokeAsync(typeof(PriorityList),
                     new { 
                         maxPriority =  ViewData["maxPriority"],
                         isDone = ViewData["isDone"]  }
                     )
</div>

执行同步工作

如果不需要异步工作,框架将处理调用同步 Invoke 方法。 以下方法将创建同步 Invoke 视图组件:

using Microsoft.AspNetCore.Mvc;
using ViewComponentSample.Models;

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

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

        public IViewComponentResult Invoke(int maxPriority, bool isDone)
        {
 
            var x = db!.ToDo!.Where(x => x.IsDone == isDone &&
                                  x.Priority <= maxPriority).ToList();
            return View(x);
        }
    }
}

视图组件的 Razor 文件:

<div>
    Testing nameof(PriorityList) <br />

    Maxium Priority: @ViewData["maxPriority"] <br />
    Is Complete:  @ViewData["isDone"]
    @await Component.InvokeAsync(nameof(PriorityListSync),
                     new { 
                         maxPriority =  ViewData["maxPriority"],
                         isDone = ViewData["isDone"]  }
                     )
</div>

使用以下方法之一在 Razor 文件(例如 Views/Home/Index.cshtml)中调用视图组件:

若要使用 IViewComponentHelper 方法,请调用 Component.InvokeAsync

@await Component.InvokeAsync(nameof(PriorityList),
                             new { maxPriority = 4, isDone = true })

若要使用标记帮助程序,请使用 @addTagHelper 指令注册包含视图组件的程序集(视图组件位于名为 MyWebApp 的程序集中):

@addTagHelper *, MyWebApp

在 Razor 标记文件中使用视图组件标记帮助程序:

<vc:priority-list max-priority="999" is-done="false">
</vc:priority-list>

PriorityList.Invoke 的方法签名是同步的,但 Razor 在标记文件中使用 Component.InvokeAsync 找到并调用该方法。

其他资源

视图组件

视图组件与分部视图类似,但它们的功能更加强大。 视图组件不使用模型绑定,它们依赖于调用视图组件时传递的数据。 本文是使用控制器和视图编写的,但视图组件适用于 Razor Pages

视图组件:

  • 呈现一个区块而不是整个响应。
  • 包括控制器和视图间发现的相同关注点分离和可测试性优势。
  • 可以有参数和业务逻辑。
  • 通常从布局页调用。

视图组件可用于具有可重用呈现逻辑(对部分视图来说过于复杂)的任何位置,例如:

  • 动态导航菜单
  • 标记云,查询数据库的位置
  • 登录面板
  • 购物车
  • 最近发布的文章
  • 博客上的边栏内容
  • 一个登录面板,呈现在每页上并显示注销或登录链接,具体取决于用户的登录状态

视图组件包含两个部分:

  • 类,通常派生自 ViewComponent
  • 它返回的结果,通常是视图。

与控制器一样,视图组件可以是 POCO,但大多数开发人员都利用派生自 ViewComponent 的可用方法和属性。

在考虑视图组件是否符合应用的规范时,请考虑改用 Razor 组件。 Razor 组件还将标记与 C# 代码组合来生成可重用的 UI 单元。 Razor 组件专用于让开发人员在提供客户端 UI 逻辑和组合时保持高效。 有关详细信息,请参阅 ASP.NET Core Razor 组件。 有关如何将组件合并 Razor 到 MVC 或 Razor Pages 应用中的信息,请参阅 将 ASP.NET 核心 Razor 组件与 MVC 或 Razor Pages 集成。

创建视图组件

本部分包含创建视图组件的高级别要求。 本文后续部分将详细检查每个步骤并创建视图组件。

视图组件类

可通过以下任一方法创建视图组件类:

与控制器一样,视图组件必须是公共、非嵌套和非抽象的类。 视图组件名称是删除了 ViewComponent 后缀的类名。 也可以使用 Name 属性显式指定它。

视图组件类:

要阻止将具有 ViewComponent 后缀(不区分大小写)的类视为视图组件,请使用 [NonViewComponent] 属性修饰该类:

using Microsoft.AspNetCore.Mvc;

[NonViewComponent]
public class ReviewComponent
{
    public string Status(string name) => JobStatus.GetCurrentStatus(name);
}

视图组件方法

视图组件在以下方法中定义其逻辑:

  • 返回 Task<IViewComponentResult>InvokeAsync 方法。
  • 返回 IViewComponentResultInvoke 同步方法。

参数直接来自视图组件的调用,而不是来自模型绑定。 视图组件从不直接处理请求。 通常,视图组件通过调用 View 方法来初始化模型并将其传递到视图。 总之,视图组件方法:

  • 定义返回 Task<IViewComponentResult>InvokeAsync 方法,或是返回 IViewComponentResult 的同步 Invoke 方法。
  • 通常,通过调用 ViewComponent.View 方法来初始化模型并将其传递到视图。
  • 参数来自调用方法,而不是 HTTP。 没有模型绑定。
  • 不可直接作为 HTTP 终结点进行访问。 它们通常在视图中调用。 视图组件从不处理请求。
  • 在签名上重载,而不是当前 HTTP 请求的任何详细信息。

视图搜索路径

运行时在以下路径中搜索视图:

  • /Views/{Controller Name}/Components/{View Component Name}/{View Name}
  • /Views/Shared/Components/{View Component Name}/{View Name}
  • /Pages/Shared/Components/{View Component Name}/{View Name}
  • /Areas/{Area Name}/Views/Shared/Components/{View Component Name}/{View Name}

搜索路径适用于使用控制器 + 视图和 Razor Pages 的项目。

视图组件的默认视图名称为 Default,这意味着视图文件通常命名为 Default.cshtml。 可以在创建视图组件结果或调用 View 方法时指定不同的视图名称。

建议将视图文件命名为 Default.cshtml 并使用 Views/Shared/Components/{View Component Name}/{View Name} 路径。 此示例中使用的 PriorityList 视图组件对视图组件视图使用 Views/Shared/Components/PriorityList/Default.cshtml

自定义视图搜索路径

修改 Razor 的 ViewLocationFormats 集合,以自定义视图搜索路径。 例如,将新项添加到集合,以搜索路径 /Components/{View Component Name}/{View Name} 中的视图:

using Microsoft.EntityFrameworkCore;
using ViewComponentSample.Models;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllersWithViews()
    .AddRazorOptions(options =>
    {
        options.ViewLocationFormats.Add("/{0}.cshtml");
    });

builder.Services.AddDbContext<ToDoContext>(options =>
        options.UseInMemoryDatabase("db"));

var app = builder.Build();

// Remaining code removed for brevity.

在前面的代码中,占位符 {0} 表示路径 Components/{View Component Name}/{View Name}

调用视图组件

要使用视图组件,请在视图中调用以下内容:

@await Component.InvokeAsync("Name of view component",
                             {Anonymous Type Containing Parameters})

参数将传递给 InvokeAsync 方法。 本文中开发的 PriorityList 视图组件调用自 Views/ToDo/Index.cshtml 视图文件。 在以下代码中,使用两个参数调用 InvokeAsync 方法:

</table>

<div>
    Maximum Priority: @ViewData["maxPriority"] <br />
    Is Complete:  @ViewData["isDone"]
    @await Component.InvokeAsync("PriorityList",
                     new { 
                         maxPriority =  ViewData["maxPriority"],
                         isDone = ViewData["isDone"]  }
                     )
</div>

作为标记帮助程序调用视图组件

视图组件可以作为标记帮助程序调用:

<div>
       Maxium Priority: @ViewData["maxPriority"] <br />
       Is Complete:  @ViewData["isDone"]
    @{
        int maxPriority = Convert.ToInt32(ViewData["maxPriority"]);
        bool isDone = Convert.ToBoolean(ViewData["isDone"]);
    }
    <vc:priority-list max-priority=maxPriority is-done=isDone>
    </vc:priority-list>
</div>

标记帮助程序采用 Pascal 大小写格式的类和方法参数将转换为各自相应的短横线格式。 要调用视图组件的标记帮助程序使用 <vc></vc> 元素。 按如下方式指定视图组件:

<vc:[view-component-name]
  parameter1="parameter1 value"
  parameter2="parameter2 value">
</vc:[view-component-name]>

若要将视图组件用作标记帮助程序,请使用 @addTagHelper 指令注册包含视图组件的程序集。 如果视图组件位于名为 MyWebApp 的程序集中,请将以下指令添加到 _ViewImports.cshtml 文件:

@addTagHelper *, MyWebApp

可将视图组件作为标记帮助程序注册到任何引用视图组件的文件。 要详细了解如何注册标记帮助程序,请参阅管理标记帮助程序作用域

本教程中使用的 InvokeAsync 方法:

@await Component.InvokeAsync("PriorityList",
                 new { 
                     maxPriority =  ViewData["maxPriority"],
                     isDone = ViewData["isDone"]  }
                 )

在前面的标记中,PriorityList 视图组件变为 priority-list。 视图组件的参数作为短横线格式的属性进行传递。

从控制器直接调用视图组件

视图组件通常从视图调用,但可以直接从控制器方法调用它们。 尽管视图组件不定义控制器等终结点,但可以实现返回 ViewComponentResult 内容的控制器操作。

在以下示例中,直接从控制器调用视图组件:

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

创建基本视图组件

下载、生成和测试起始代码。 它是一个带有 ToDo 控制器的基本项目,该控制器显示 ToDo 项列表。

ToDo 列表

更新控制器以传入优先级和完成状态

更新 Index 方法以使用优先级和完成状态参数:

using Microsoft.AspNetCore.Mvc;
using ViewComponentSample.Models;

namespace ViewComponentSample.Controllers;
public class ToDoController : Controller
{
    private readonly ToDoContext _ToDoContext;

    public ToDoController(ToDoContext context)
    {
        _ToDoContext = context;
        _ToDoContext.Database.EnsureCreated();
    }

    public IActionResult Index(int maxPriority = 2, bool isDone = false)
    {
        var model = _ToDoContext!.ToDo!.ToList();
        ViewData["maxPriority"] = maxPriority;
        ViewData["isDone"] = isDone;
        return View(model);
    }

添加 ViewComponent 类

将 ViewComponent 类添加到 ViewComponents/PriorityListViewComponent.cs

using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
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] 属性可以更改用于引用视图组件的名称。 例如,类可以使用以下 [ViewComponent] 属性命名为 XYZ

    [ViewComponent(Name = "PriorityList")]
       public class XYZ : ViewComponent
    
  • 上述代码中的 [ViewComponent] 属性告知视图组件选择器使用:

    • 查找与组件关联的视图时的名称 PriorityList
    • 从视图中引用类组件时的字符串“PriorityList”。
  • 组件使用依赖关系注入以使数据上下文可用。

  • InvokeAsync 公开可以从视图调用的方法,且可以采用任意数量的参数。

  • InvokeAsync 方法返回满足 isDonemaxPriority 参数的 ToDo 项集。

创建视图组件 Razor 视图

  • 创建 Views/Shared/Components 文件夹。 此文件夹 必须 命名为 Components

  • 创建 Views/Shared/Components/PriorityList 文件夹。 此文件夹名称必须与视图组件类的名称相匹配,或者与类的名称减去后缀相匹配。 如果使用了 ViewComponent 属性,则类名称需要匹配指定的属性。

  • 创建 Views/Shared/Components/PriorityList/Default.cshtmlRazor 视图:

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

    Razor 视图获取并显示 TodoItem 列表。 如果视图组件 InvokeAsync 方法不传递视图名称,则按照约定使用“默认”作为视图名称。 要替代特定控制器的默认样式,请将视图添加到控制器特定的视图文件夹(例如 Views/ToDo/Components/PriorityList/Default.cshtml)。

    如果视图组件特定于控制器,则可以将其添加到特定于控制器的文件夹。 例如,Views/ToDo/Components/PriorityList/Default.cshtml 是特定于控制器的。

  • 将包含优先级列表组件调用的 div 添加到 Views/ToDo/index.cshtml 文件底部:

    </table>
    
    <div>
        Maximum Priority: @ViewData["maxPriority"] <br />
        Is Complete:  @ViewData["isDone"]
        @await Component.InvokeAsync("PriorityList",
                         new { 
                             maxPriority =  ViewData["maxPriority"],
                             isDone = ViewData["isDone"]  }
                         )
    </div>
    

标记 @await Component.InvokeAsync 显示调用视图组件的语法。 第一个参数是要调用的组件的名称。 后续参数将传递给该组件。 InvokeAsync 可以采用任意数量的参数。

测试应用。 下图显示 ToDo 列表和优先级项:

Todo 列表和优先级项

可以直接从控制器调用视图组件:

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

IndexVC 操作的优先级项

指定视图组件名称

在某些情况下,复杂的视图组件可能需要指定非默认视图。 以下代码显示如何从 InvokeAsync 方法指定“PVC”视图。 更新 PriorityListViewComponent 类中的 InvokeAsync 方法。

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>

运行应用并验证 PVC 视图。

优先级视图组件

如果不呈现 PVC 视图,请验证是否调用优先级为 4 或更高的视图组件。

检查视图路径

  • 将优先级参数更改为 3 或更低,从而不返回优先级视图。

  • 暂时将 Views/ToDo/Components/PriorityList/Default.cshtml 重命名为 1Default.cshtml

  • 测试应用,出现以下错误:

    An unhandled exception occurred while processing the request.
    InvalidOperationException: The view 'Components/PriorityList/Default' wasn't found. The following locations were searched:
    /Views/ToDo/Components/PriorityList/Default.cshtml
    /Views/Shared/Components/PriorityList/Default.cshtml
    
  • Views/ToDo/Components/PriorityList/1Default.cshtml 复制到 Views/Shared/Components/PriorityList/Default.cshtml

  • 将一些标记添加到共享 ToDo 视图组件视图,以指示视图来自“Shared”文件夹。

  • 测试“共享”组件视图

有共享组件视图的 ToDo 输出

避免使用硬编码字符串

为确保编译时安全性,请将硬编码的视图组件名称替换为类名。 将 PriorityListViewComponent.cs 文件更新为不使用“ViewComponent”后缀:

using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
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();
    }
}

视图文件:

</table>

<div>
    Testing nameof(PriorityList) <br />

    Maxium Priority: @ViewData["maxPriority"] <br />
    Is Complete:  @ViewData["isDone"]
    @await Component.InvokeAsync(nameof(PriorityList),
                     new { 
                         maxPriority =  ViewData["maxPriority"],
                         isDone = ViewData["isDone"]  }
                     )
</div>

采用 CLR 类型的 Component.InvokeAsync 方法重载使用 typeof 运算符:

</table>

<div>
    Testing typeof(PriorityList) <br />

    Maxium Priority: @ViewData["maxPriority"] <br />
    Is Complete:  @ViewData["isDone"]
    @await Component.InvokeAsync(typeof(PriorityList),
                     new { 
                         maxPriority =  ViewData["maxPriority"],
                         isDone = ViewData["isDone"]  }
                     )
</div>

执行同步工作

如果不需要异步工作,框架将处理调用同步 Invoke 方法。 以下方法将创建同步 Invoke 视图组件:

using Microsoft.AspNetCore.Mvc;
using ViewComponentSample.Models;

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

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

        public IViewComponentResult Invoke(int maxPriority, bool isDone)
        {
 
            var x = db!.ToDo!.Where(x => x.IsDone == isDone &&
                                  x.Priority <= maxPriority).ToList();
            return View(x);
        }
    }
}

视图组件的 Razor 文件:

<div>
    Testing nameof(PriorityList) <br />

    Maxium Priority: @ViewData["maxPriority"] <br />
    Is Complete:  @ViewData["isDone"]
    @await Component.InvokeAsync(nameof(PriorityListSync),
                     new { 
                         maxPriority =  ViewData["maxPriority"],
                         isDone = ViewData["isDone"]  }
                     )
</div>

使用以下方法之一在 Razor 文件(例如 Views/Home/Index.cshtml)中调用视图组件:

若要使用 IViewComponentHelper 方法,请调用 Component.InvokeAsync

@await Component.InvokeAsync(nameof(PriorityList),
                             new { maxPriority = 4, isDone = true })

若要使用标记帮助程序,请使用 @addTagHelper 指令注册包含视图组件的程序集(视图组件位于名为 MyWebApp 的程序集中):

@addTagHelper *, MyWebApp

在 Razor 标记文件中使用视图组件标记帮助程序:

<vc:priority-list max-priority="999" is-done="false">
</vc:priority-list>

PriorityList.Invoke 的方法签名是同步的,但 Razor 在标记文件中使用 Component.InvokeAsync 找到并调用该方法。

其他资源

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

视图组件

视图组件与分部视图类似,但它们的功能更加强大。 视图组件不使用模型绑定,并且仅依赖调用时提供的数据。 本文是使用控制器和视图编写的,但视图组件也适用于 Razor Pages。

视图组件:

  • 呈现一个区块而不是整个响应。
  • 包括控制器和视图间发现的相同关注点分离和可测试性优势。
  • 可以有参数和业务逻辑。
  • 通常从布局页调用。

视图组件可用于具有可重用呈现逻辑(对分部视图来说过于复杂)的任何位置,例如:

  • 动态导航菜单
  • 标记云(查询数据库的位置)
  • 登录面板
  • 购物车
  • 最近发布的文章
  • 典型博客上的边栏内容
  • 一个登录面板,呈现在每页上并显示注销或登录链接,具体取决于用户的登录状态

视图组件由两部分组成:类(通常派生自 ViewComponent)及其返回的结果(通常为视图)。 与控制器一样,视图组件可以是 POCO,但大多数开发人员都利用派生自 ViewComponent 的可用方法和属性。

在考虑视图组件是否符合应用的规范时,请考虑改用 Razor 组件。 Razor 组件还将标记与 C# 代码组合来生成可重用的 UI 单元。 Razor 组件专用于让开发人员在提供客户端 UI 逻辑和组合时保持高效。 有关详细信息,请参阅 ASP.NET Core Razor 组件。 有关如何将组件合并 Razor 到 MVC 或 Razor Pages 应用中的信息,请参阅 将 ASP.NET 核心 Razor 组件与 MVC 或 Razor Pages 集成。

创建视图组件

本部分包含创建视图组件的高级别要求。 本文后续部分将详细检查每个步骤并创建视图组件。

视图组件类

可通过以下任一方法创建视图组件类:

  • 从 ViewComponent 派生
  • 使用 [ViewComponent] 属性修饰类,或者从具有 [ViewComponent] 属性的类派生
  • 创建名称以 ViewComponent 后缀结尾的类

与控制器一样,视图组件必须是公共、非嵌套和非抽象的类。 视图组件名称是删除了“ViewComponent”后缀的类名。 也可以使用 ViewComponentAttribute.Name 属性显式指定它。

视图组件类:

要阻止将具有 ViewComponent 后缀(不区分大小写)的类视为视图组件,请使用 [NonViewComponent] 属性修饰该类:

[NonViewComponent]
public class ReviewComponent
{
    // ...

视图组件方法

视图组件以返回 Task<IViewComponentResult>InvokeAsync 方法,或是以返回 IViewComponentResult 的同步 Invoke 方法定义其逻辑。 参数直接来自视图组件的调用,而不是来自模型绑定。 视图组件从不直接处理请求。 通常,视图组件通过调用 View 方法来初始化模型并将其传递到视图。 总之,视图组件方法:

  • 定义返回 Task<IViewComponentResult>InvokeAsync 方法,或是返回 IViewComponentResult 的同步 Invoke 方法。
  • 一般通过调用 ViewComponent View 方法来初始化模型并将其传递到视图。
  • 参数来自调用方法,而不是 HTTP。 没有模型绑定。
  • 不可直接作为 HTTP 终结点进行访问。 通过代码调用它们(通常在视图中)。 视图组件从不处理请求。
  • 在签名上重载,而不是当前 HTTP 请求的任何详细信息。

视图搜索路径

运行时在以下路径中搜索视图:

  • /Views/{Controller Name}/Components/{View Component Name}/{View Name}
  • /Views/Shared/Components/{View Component Name}/{View Name}
  • /Pages/Shared/Components/{View Component Name}/{View Name}
  • /Areas/{Area Name}/Views/Shared/Components/{View Component Name}/{View Name}

搜索路径适用于使用控制器 + 视图和 Razor Pages 的项目。

视图组件的默认视图名称为“默认”,这意味着视图文件通常命名为“Default.cshtml”Default.cshtml。 可以在创建视图组件结果或调用 View 方法时指定不同的视图名称。

建议将视图文件命名为 Default.cshtml 并使用 Views/Shared/Components/{View Component Name}/{View Name} 路径。Default.cshtml 此示例中使用的 PriorityList 视图组件对视图组件视图使用 Views/Shared/Components/PriorityList/Default.cshtml

自定义视图搜索路径

修改 Razor 的 ViewLocationFormats 集合,以自定义视图搜索路径。 例如,将新项添加到集合,以搜索路径“/Components/{视图组件名称}/{视图名称}”中的视图:

services.AddMvc()
    .AddRazorOptions(options =>
    {
        options.ViewLocationFormats.Add("/{0}.cshtml");
    })
    .SetCompatibilityVersion(CompatibilityVersion.Version_2_2);

在前面的代码中,占位符“{0}”表示路径“Components/{视图组件名称}/{视图名称}”。

调用视图组件

要使用视图组件,请在视图中调用以下内容:

@await Component.InvokeAsync("Name of view component", {Anonymous Type Containing Parameters})

参数将传递给 InvokeAsync 方法。 本文中开发的 PriorityList 视图组件调用自 Views/ToDo/Index.cshtml 视图文件。 在下例中,使用两个参数调用 InvokeAsync 方法:

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

调用视图组件作为标记帮助程序

对于 ASP.NET Core 1.1 及更高版本,可以调用视图组件作为标记帮助程序

<vc:priority-list max-priority="2" is-done="false">
</vc:priority-list>

标记帮助程序采用 Pascal 大小写格式的类和方法参数将转换为各自相应的短横线格式。 要调用视图组件的标记帮助程序使用 <vc></vc> 元素。 按如下方式指定视图组件:

<vc:[view-component-name]
  parameter1="parameter1 value"
  parameter2="parameter2 value">
</vc:[view-component-name]>

若要将视图组件用作标记帮助程序,请使用 @addTagHelper 指令注册包含视图组件的程序集。 如果视图组件位于名为 MyWebApp 的程序集中,请将以下指令添加到 _ViewImports.cshtml 文件:

@addTagHelper *, MyWebApp

可将视图组件作为标记帮助程序注册到任何引用视图组件的文件。 要详细了解如何注册标记帮助程序,请参阅管理标记帮助程序作用域

本教程中使用的 InvokeAsync 方法:

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

在标记帮助程序标记中:

<vc:priority-list max-priority="2" is-done="false">
</vc:priority-list>

在以上示例中,PriorityList 视图组件变为 priority-list。 视图组件的参数作为短横线格式的属性进行传递。

从控制器直接调用视图组件

视图组件通常从视图调用,但你可以直接从控制器方法调用它们。 尽管视图组件不定义控制器等终结点,但你可以轻松实现返回 ViewComponentResult 内容的控制器操作。

在此示例中,视图组件直接从控制器调用:

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

演练:创建简单的视图组件

下载、生成和测试起始代码。 它是一个带有 ToDo 控制器的简单项目,该控制器显示 ToDo 项的列表。

ToDo 列表

添加 ViewComponent 类

创建一个 ViewComponents 文件夹并添加以下 PriorityListViewComponent

using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
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] 属性可以更改用于引用视图组件的名称。 例如,类可以使用 ViewComponent 属性命名为 XYZ

    [ViewComponent(Name = "PriorityList")]
       public class XYZ : ViewComponent
    
  • 上述代码中的 [ViewComponent] 属性告知视图组件选择器使用:

    • 查找与组件关联的视图时的名称 PriorityList
    • 从视图中引用类组件时的字符串“PriorityList”。
  • 组件使用依赖关系注入以使数据上下文可用。

  • InvokeAsync 公开可以从视图调用的方法,且可以采用任意数量的参数。

  • InvokeAsync 方法返回满足 isDonemaxPriority 参数的 ToDo 项集。

创建视图组件 Razor 视图

  • 创建 Views/Shared/Components 文件夹。 此文件夹必须命名为 Components

  • 创建 Views/Shared/Components/PriorityList 文件夹。 此文件夹名称必须与视图组件类的名称或类名去掉后缀(如果遵照约定并在类名中使用了“ViewComponent”后缀)的名称相匹配。 如果使用了 ViewComponent 属性,则类名称需要匹配指定的属性。

  • 创建 Views/Shared/Components/PriorityList/Default.cshtmlRazor 视图:

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

    Razor 视图获取并显示 TodoItem 列表。 如果视图组件 InvokeAsync 方法不传递视图名称(如示例中所示),则按照约定使用“默认”作为视图名称。 在本教程后面部分,我将演示如何传递视图名称。 要替代特定控制器的默认样式,请将视图添加到控制器特定的视图文件夹(例如 Views/ToDo/Components/PriorityList/Default.cshtml)。

    如果视图组件是控制器特定的,则可将其添加到控制器特定的文件夹 (Views/ToDo/Components/PriorityList/Default.cshtmlViews/ToDo/Components/PriorityList/Default.cshtml)。

  • 将包含优先级列表组件调用的 div 添加到 Views/ToDo/index.cshtml 文件底部:

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

标记 @await Component.InvokeAsync 显示调用视图组件的语法。 第一个参数是要调用的组件的名称。 后续参数将传递给该组件。 InvokeAsync 可以采用任意数量的参数。

测试应用。 下图显示 ToDo 列表和优先级项:

Todo 列表和优先级项

也可直接从控制器调用视图组件:

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

IndexVC 操作的优先级项

指定视图名称

在某些情况下,复杂的视图组件可能需要指定非默认视图。 以下代码显示如何从 InvokeAsync 方法指定“PVC”视图。 更新 PriorityListViewComponent 类中的 InvokeAsync 方法。

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/ToDo/Index.cshtml

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

运行应用并验证 PVC 视图。

优先级视图组件

如果不呈现 PVC 视图,请验证是否调用优先级为 4 或更高的视图组件。

检查视图路径

  • 将优先级参数更改为 3 或更低,从而不返回优先级视图。

  • 暂时将 Views/ToDo/Components/PriorityList/Default.cshtml 重命名为 1Default.cshtml

  • 测试应用,你将收到以下错误:

    An unhandled exception occurred while processing the request.
    InvalidOperationException: The view 'Components/PriorityList/Default' wasn't found. The following locations were searched:
    /Views/ToDo/Components/PriorityList/Default.cshtml
    /Views/Shared/Components/PriorityList/Default.cshtml
    EnsureSuccessful
    
  • Views/ToDo/Components/PriorityList/1Default.cshtml 复制到 Views/Shared/Components/PriorityList/Default.cshtml

  • 将一些标记添加到共享 ToDo 视图组件视图,以指示视图来自“Shared”文件夹。

  • 测试“共享”组件视图

有共享组件视图的 ToDo 输出

避免使用硬编码字符串

若要确保编译时的安全性,可以用类名替换硬编码的视图组件名称。 创建没有“ViewComponent”后缀的视图组件:

using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
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.  -->

    <div>

        @*
            Note: 
            To use the below line, you need to #define no_suffix in ViewComponents/PriorityList.cs or it won't compile.
            By doing so it will cause a problem to index as there will be multiple viewcomponents 
            with the same name after the compiler removes the suffix "ViewComponent"
        *@

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

可以使用采用 CLR 类型的 Component.InvokeAsync 方法的重载。 请记住,在本例中,使用 typeof 运算符:

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

<h2>ToDo typeof</h2>
<!-- Markup removed for brevity.  -->

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

执行同步工作

如果不需要执行异步工作,框架将处理调用同步 Invoke 方法。 以下方法将创建同步 Invoke 视图组件:

public class PriorityList : ViewComponent
{
    public IViewComponentResult Invoke(int maxPriority, bool isDone)
    {
        var items = new List<string> { $"maxPriority: {maxPriority}", $"isDone: {isDone}" };
        return View(items);
    }
}

视图组件的 Razor 文件列出了传递给 Invoke 方法 (Views/Home/Components/PriorityList/Default.cshtml) 的字符串:

@model List<string>

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

使用以下方法之一在 Razor 文件(例如 Views/Home/Index.cshtml)中调用视图组件:

若要使用 IViewComponentHelper 方法,请调用 Component.InvokeAsync

@await Component.InvokeAsync(nameof(PriorityList), new { maxPriority = 4, isDone = true })

若要使用标记帮助程序,请使用 @addTagHelper 指令注册包含视图组件的程序集(视图组件位于名为 MyWebApp 的程序集中):

@addTagHelper *, MyWebApp

在 Razor 标记文件中使用视图组件标记帮助程序:

<vc:priority-list max-priority="999" is-done="false">
</vc:priority-list>

PriorityList.Invoke 的方法签名是同步的,但 Razor 在标记文件中使用 Component.InvokeAsync 找到并调用该方法。

所有视图组件参数都是必需的

视图组件中的每个参数都是必需的属性。 请参阅此 GitHub 问题。 如果省略任何参数:

  • InvokeAsync 方法签名不匹配,因此该方法将不会执行。
  • ViewComponent 不会呈现任何标记。
  • 不会引发任何错误。

其他资源