九、可复用的组件

本章介绍 ASP.NET Core 的可重用组件。所谓可重用,我的意思是,它们可能会在不同的项目中使用,或者在同一个项目中的不同位置使用不同的参数,从而产生可能不同的结果。在本章中,我们将介绍视图组件标记帮助程序(ASP.NET Core 新增)、标记帮助程序组件(ASP.NET Core 2 新增)以及我们的老朋友部分视图。

在本章中,我们将介绍以下主题:

  • 视图组件
  • 标记助手
  • 标记辅助对象组件
  • 局部视图
  • Razor 类库
  • 添加外部内容

技术要求

为了实现本章中介绍的示例,您需要.NET Core 3 SDK 和文本编辑器。当然,VisualStudio2019(任何版本)满足所有要求,但您也可以使用 VisualStudio 代码。

源代码可在从 GitHub 检索 https://github.com/PacktPublishing/Modern-Web-Development-with-ASP.NET-Core-3-Second-Edition

本章中介绍的所有技术都有助于构建代码并最小化全局代码库的大小。

深入到视图组件中

视图组件是 ASP.NET Core 的新组件,它们在 ASP.NET 预核心中不存在。您可以将它们视为局部视图(仍然存在)和返回子操作(不再可用)的RenderAction方法的替代品。不再与控制器绑定;它们是可重用的,因为它们可以从外部程序集(即,不是 web 应用的程序集)加载,并且比局部视图更适合呈现复杂的 HTML。在以下部分中,我们将了解什么是视图组件、它们如何工作以及在何处使用它们,并将它们与局部视图进行比较。

发现视图组件

可以通过以下方式之一查找视图组件:

  • 通过继承ViewComponent
  • 通过添加一个[ViewComponent]属性
  • 通过向类添加ViewComponent后缀

您很可能会从ViewComponent类继承组件,因为该类提供了两个有用的方法。如果 web 应用引用外部程序集或将其注册为应用部件,则可以从外部程序集加载视图组件:

services
    .AddMvc()
    .ConfigureApplicationPartManager(options =>
    {
        options.ApplicationParts.Add(new AssemblyPart(Assembly.Load
        ("ClassLibrary"));
    })

对于 POCO 视图组件,您将无法轻松访问请求的环境上下文。如果您必须在前面的选项中进行选择,请选择从ViewComponent继承,否则,您将需要投入额外的工作来获取所需的所有引用(如HttpContext等)。我们将在后面的依赖注入一节中对此进行更详细的描述。

视图组件只需声明一个方法,InvokeAsync

public class MyViewComponent : ViewComponent
{
    public async Task<IViewComponentResult> InvokeAsync()
    {
        return this.Content("This is a view component");
    }
}

您还可以使用参数,我们将看到。

[ViewComponent]属性可用于更改视图组件的名称,但您应注意,这样做意味着您在加载视图组件时需要使用此名称:

[ViewComponent(Name = "MyView")]
public class SomeViewComponent : ViewComponent { ... }

不要给它一个带有连字符(“-”)的名称,因为它会影响使用!我们将在下一节中看到这一点。

使用视图组件

视图组件是从视图调用的,有两种不同的语法:

  • 代码语法允许您传递复杂类型的参数,但代码必须在语法上有效:
@await Component.InvokeAsync("MyViewComponent", new { Parameter
 = 4, OtherParameter = true })

InvokeAsync方法采用视图组件名称(默认情况下,类的名称减去ViewComponent后缀)和一个可选参数,其中包含要传递给视图组件的InvokeAsync方法的参数;此方法可以接受任意数量的参数并返回一个IViewComponentResult实例。

  • 标记使用标记助手语法(稍后将对此进行详细介绍);注意vc名称空间:
<vc:my-view-component parameter="4" otherParameter="true"/>

同样,这是不带ViewComponent后缀但使用连字符大小写的类的名称。这里您还需要使用[ViewComponent]属性中指定的Name,如果有的话。不要在命名中使用连字符。

Pascal-cased class and method parameters for tag helpers are translated into lower-kebab case, which you can read about at http://stackoverflow.com/questions/11273282/whats-the-name-for-dash-separated-case/12273101#12273101.

如果您的参数很复杂,无法用属性轻松表示,则应选择代码语法。此外,名称空间是可配置的。

另一个选项是以ViewComponentResult的形式从控制器操作返回视图组件:

public IActionResult MyAction()
{
    return this.ViewComponent("MyViewComponent");
}

这与返回部分视图非常相似,只有视图中的组件,所有内容都需要由代码生成。也就是说,如果要返回自定义 HTML,可能需要通过连接字符串来构建。

查看组件结果

View components 返回一个IViewComponentResult实例,该实例在 ASP.NET Core 中有三个实现,每个实现通过ViewComponent类的方法返回:

  • ContentContentViewComponentResult:返回字符串内容。
  • ViewViewViewComponentResult):返回局部视图。
  • HtmlViewComponentResult:与ContentViewComponentResult类似,但返回编码的 HTML。没有创建此类实例的方法,但您可以自己实例化一个。

The rules for discovering partial view files are identical to the ones described earlier in Chapter 5, Views.

IViewComponentResult接口在异步(ExecuteAsync和同步(Execute版本中只指定了一个方法。它以ViewComponentContext的一个实例作为其唯一参数,该参数具有以下属性:

  • ArgumentsIDictionary<string, object>:传递给InvokeAsync方法的对象的命名属性
  • HtmlEncoderHtmlEncoder:HTML 输出编码器
  • ViewComponentDescriptorViewComponentDescriptor):描述当前视图组件
  • ViewContextViewContext:所有视图上下文,包括当前视图对象、HTTP 上下文、路由数据、模型、表单上下文和动作描述符
  • ViewDataViewDataDictionary:来自控制器的视图数据
  • WriterTextWriter):直接写入输出流

因为您可以访问所有这些上下文,所以您可以做您想做的事情,例如访问头、cookie 和请求参数,但是您不会将视图组件结果用于重定向,只用于呈现 HTML。

依赖注入

您可以通过调用ConfigureServicesAddMvc方法之上的AddViewComponentsAsServices扩展方法,将视图组件注册为服务:

services
    .AddMvc()
    .AddViewComponentsAsServices();

视图组件支持构造函数注入,因此您可以在构造函数中声明任何已注册的类型:

public class MyViewComponent : ViewComponent
{
    private readonly ILoggerFactory loggerFactory;

    public MyViewComponent(ILoggerFactory loggerFactory)
    {
        this._loggerFactory = loggerFactory;
    }
}

一个共同的需求是掌握当前的HttpContext;如果在 POCO 控制器中需要它,则需要注入一个IHttpContextAccessor实例:

public class MyViewComponent : ViewComponent
{
    public MyViewComponent(IHttpContextAccessor httpAccessor)
    {
        this.HttpContext = httpAccessor.HttpContext;
    }

    public HttpContext HttpContext { get; }
}

在本例中,我们注入了IHttpContextAccessor接口,从中我们可以提取请求的当前HttpContext实例。不要忘记,要使其工作,以下行必须出现在ConfigureServices中:

services.AddHttpContextAccessor();

视图组件与局部视图

如您所见,视图组件和局部视图有一些相似之处;它们都是生成标记的可重用机制。两者之间的区别在于局部视图继承了包含视图的大部分上下文,例如模型和视图数据集合,因此这两个视图和局部视图必须兼容。例如,它们必须具有兼容的模型。视图组件的情况并非如此,您可以使用自己喜欢的任何数据调用它们。

接下来,我们将讨论一个非常类似的主题,该主题使用了与 HTML 标记帮助程序更接近的不同语法。

探索标记助手

标记帮助程序也是 ASP.NET Core 的新功能。标记助手是一种向常规 HTML/XML 标记添加服务器端处理的机制;您可以将其视为类似于 ASP.NET Web 窗体的服务器端控件,尽管存在一些差异。标记帮助器在 Razor 视图上注册,当视图上的任何标记与标记帮助器匹配时,它将被触发。它们是 HTML 助手的另一种选择(可以说更简单),因为它们可以在没有代码块的情况下生成更清晰的标记。

标记助手的功能通过ITagHelper接口指定,其中TagHelper抽象基类提供了一个基本实现。其生命周期包括两种方法:

  • Init:在初始化标记助手时,在任何可能的子对象之前调用
  • ProcessAsync:标签助手的实际处理

视图侧的标记辅助对象只不过是常规标记,因此,它可以包含其他标记,这些标记本身也可能是标记辅助对象。让我们看一个例子:

<time></time>

正如您所看到的,它只不过是一个普通的 XML 标记而不是 HTML,因为在任何版本的 HTML 上都没有这样的标记。

为了添加自定义服务器端行为,我们定义了一个标记帮助器类,如下所示:

public class TimeTagHelper : TagHelper
{
    public override Task ProcessAsync(TagHelperContext context, 
    TagHelperOutput output)
    {
        var time = DateTime.Now.ToString();

        output.Content.Append(time);

        return base.ProcessAsync(context, output);
    }
}

标记帮助器是递归的,这意味着在其他标记帮助器中声明的标记帮助器都将被处理。

我们将很快了解 ASP.NET Core 需要做些什么才能认识到这一点,但现在,让我们看看ProcessAsync方法的参数。

TagHelperContext包含上下文,如标记帮助器中所示。它包括以下属性:

  • AllAttributesReadOnlyTagHelperAttributeList):视图中为此标记帮助器声明的所有属性
  • ItemsIDictionary<string, object>:一个自由形式的项目集合,用于在当前请求中将上下文传递给其他标记帮助程序
  • UniqueIdstring:当前标记帮助器的唯一标识符

至于TagHelperOutput,它不仅允许将内容返回到视图,还允许返回标记内声明的任何内容。它公开了以下属性:

  • IsContentModifiedbool):只读标志,表示内容是否已修改
  • Orderint:标签助手的处理顺序
  • PostElementTagHelperContent):以下标签元素
  • PostContentTagHelperContent:当前标签后的内容
  • ContentTagHelperContent:当前标签的内容
  • PreContentTagHelperContent:当前标签前的内容
  • PreElementTagHelperContent:前一个标签元素
  • TagModeTagMode):标签模式(SelfClosingStartTagAndEndTagStartTagOnly),用于定义标签在标记中应如何进行验证(允许内部内容为SelfClosing,仅无自身内容的标签为StartTagOnly
  • TagNamestring:视图中标签的名称
  • AttributesTagHelperAttributeList:标签的原始属性列表,可以修改

例如,假设您有以下标签:

<time format="yyyy-MM-dd">Current date is: {0}</time>

在这里,您需要访问属性(format<time>标记的内容。让我们看看如何实现这一点:

public class TimeTagHelper : TagHelper
{
    public string Format { get; set; }

    public override async Task ProcessAsync(TagHelperContext
     context, TagHelperOutput output)
    {
        var content = await output.GetChildContentAsync();
        var stringContent = content.GetContent();
        var time = DateTime.Now.ToString(this.Format);

        output.TagName = "span";
        output.Content.Append(string.Format(CultureInfo.Invariant
        Culture, stringContent, time));

        return base.ProcessAsync(context, output);
    }
}

在这里,我们可以看到我们正在做一些事情:

  • 获取Format属性的值
  • 获取所有标记的内容
  • 将目标标签的名称设置为span
  • 使用内容和格式输出带有格式化时间戳的字符串

这基本上就是获取内容和属性的方法。您还可以向输出添加属性(通过向output.Attributes添加值)、更改输出标记名称(output.TagName)或阻止生成任何内容(通过使用output.SuppressOutput方法)。

在输出内容时,我们可以返回按照视图的HtmlEncoder实例编码的普通字符串,也可以返回已经编码的内容,在这种情况下,我们将调用AppendHtml,而不是Append

output.Content.Append("<p>hello, world!</p>"); 

除了附加,我们还可以替换所有的内容;为此,我们称之为\tSetHtmlContent,甚至清除所有内容(ClearSuppressOutput

[OutputElementHint]属性可用于提供关于哪个标记将输出的提示。这非常有用,以便 Visual Studio 知道如何提供有关其他一些元素的属性的提示,例如img

[OutputElementHint("img")]

这样,当您在标记中添加自定义标记时,VisualStudio 将建议所有img元素的属性,例如SRC

我们可以使用context.Items集合将数据从一个标记帮助器传递到另一个标记帮助器。记住Order属性定义了将首先处理的标记。

现在让我们看看标记帮助程序公开的属性。

了解标记辅助对象的属性

可以通过视图设置标记帮助器类中的任何公共属性。默认情况下,使用小写或大小写相同的名称,但我们可以通过应用[HtmlAttributeName]属性为属性指定不同的名称,以便在视图中使用:

[HtmlAttributeName("time-format")]
public string Format { get; set; }

在这种情况下,属性现在必须声明为time-format

另一方面,如果我们不希望通过视图的标记设置属性的值,我们可以对其应用[HtmlAttributeNotBound]属性。

可以在标记中指定基本类型的属性,以及可以从字符串(例如GuidTimeSpanDateTime)和任何枚举转换的其他两个属性。

我们可以使用 Razor 表达式将代码生成的值传递给标记属性:

<time format="GetTimeFormat()">Time is {0}</format>

最后,值得一提的是,如果我们使用一个ModelExpression类型的属性,我们可以为视图的模型获得 Visual Studio 的 IntelliSense:

public ModelExpression FormatFrom { get; set; }

这就是它的样子:

为了实际检索属性的值,我们需要分析ModelExpressionNameMetadata属性。

限制标记辅助对象的适用性

可以通过以下几种方式限制标记辅助对象的适用性:

  • 它可以针对特定的元素,既可以是已知的 HTML 标记,也可以是自定义的 XML 标记。
  • 其目标标记必须包含在另一个标记中。
  • 其目标标记必须具有某些属性,可能具有特定格式。
  • 其标记必须具有特定的结构。

可以根据几个[HtmlTargetElement]属性指定许多限制:

//matches any a elements
[HtmlTargetElement("a")]
//matches any a elements contained inside a div tag
[HtmlTargetElement("a", ParentTag = "div")]
//matches any a elements that target a JavaScript file ending in .js
[HtmlTargetElement("a", Attributes = "[href$='.js']")]
//matches any a elements that target a link starting with ~
[HtmlTargetElement("a", Attributes = "[href^='~']")]
//matches any a elements with a value for the name attribute
[HtmlTargetElement("a", Attributes = "name")]
//matches any a elements with a specific id
[HtmlTargetElement("a", Attributes = "id='link'")]
//matches any a elements that do not have any inner contents (for example, <a/>)
[HtmlTargetElement("a", TagStructure = TagStructure.WithoutEndTag)]

因此,我们具有以下特性:

  • ParentTagstring:父标签的名称
  • Attributesstring:以逗号分隔的属性和可选值列表
  • TagStructureTagStructure:标签的格式,默认为Unspecified

TagStructure指定标签是自动关闭(WithoutEndTag)还是可能有内容(NormalOrSelfClosing)。

如果找到与其适用性规则不匹配的标记帮助器,则会在运行时引发异常。可以同时指定多个规则,不同的标记帮助器可以匹配相同的规则。

如果您以*为目标,它将应用于任何元素。

发现标记帮助程序

标签助手需要实现ITagHelper接口。需要显式地添加标记帮助程序,这样做的好地方是_ViewImports.cshtml文件。此处放置的任何内容都将应用于所有视图。让我们看看每一个是如何工作的:

  • addTagHelper指令添加了一个特定的组件和标记辅助程序:
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

The syntax is @addTagHelper <types>, <assembly>, where * stands for all types.

  • 如果我们想阻止使用特定的标记帮助器,我们将应用一个或多个removeTagHelper指令:
@removeTagHelper Microsoft.AspNetCore.Mvc.TagHelpers.AnchorTagHelper, Microsoft.AspNetCore.Mvc.TagHelpers
  • 如果我们想明确使用标记帮助器,我们可以使用tagHelperPrefix指令强制它们使用前缀:
@tagHelperPrefix asp:
  • 最后,我们还可以选择禁用针对特定标记的任何可能的标记帮助程序。我们只是在它前面加上!字符:
<!a href="...">link</a>

依赖注入

标记帮助程序由依赖项注入机制实例化,这意味着它们可以在构造函数中接受已注册的服务。

InitProcessProcessAsync方法都不提供对执行上下文的访问,但类似于我们可以注入ViewContext的 POCO 控制器和视图组件:

[ViewContext]
public ViewContext ViewContext { get; set; }

从这里,我们可以访问HttpContextActionDescriptor,以及路由数据、视图数据等。

研究包含的标记助手

Microsoft.AspNetCore.Mvc.TagHelpers组件中包括以下标签辅助件:

  • AnchorTagHelper<a>):呈现锚
  • CacheTagHelper<cache>):定义一个内存缓存区
  • ComponentTagHelper<component>):呈现 Blazor 组件
  • DistributedCacheTagHelper<distributed-cache>:呈现分布式缓存区域
  • EnvironmentTagHelper<environment>:根据当前环境决定是否渲染
  • FormActionTagHelper<form>:呈现一个表单,该表单呈现给控制器动作
  • FormTagHelper<form>):呈现表单
  • ImageTagHelper<img>):渲染图像
  • InputTagHelper<input>):呈现一个输入元素
  • LabelTagHelper<label>):呈现标签
  • LinkTagHelper<link>):呈现内部链接
  • OptionTagHelper<option>):呈现选择选项
  • PartialTagHelper<partial>):渲染剃刀局部视图
  • RenderAtEndOfFormTagHelper<form>):在表单末尾呈现内容
  • ScriptTagHelper<script>):呈现脚本
  • SelectTagHelper<select>):呈现select元素
  • TextAreaTagHelper<textarea>):呈现文本区域
  • ValidationMessageTagHelper<span>):呈现验证消息占位符
  • ValidationSummaryTagHelper<div>):呈现验证摘要占位符

其中一些将以~开头的 URL 转换为服务器特定地址,或添加控制器和操作属性,这些属性反过来转换为控制器操作 URL:

<a href="~">Root</a>
<a asp-controller="Account" asp-action="Logout">Logout</a>
<form asp-controller="Account" asp-action="Login">
</form>

例如,您可以将应用部署在/或虚拟路径下,如/admin。如果您事先不知道这一点,您不能将链接硬编码为指向/,而是可以使用~,ASP.NET Core 框架将确保它设置为正确的路径。

然而,其他一些标签功能非常强大,提供了非常有趣的特性。

<a>标记帮助器为锚提供了一些属性,允许您针对特定控制器、页面或命名路由的特定操作:

<a
  asp-action="ActionName"
  asp-controller="ControllerName"
  asp-page="RazorPageName"
  asp-route="RouteName"
  asp-area="AreaName">...</a>

请注意,我们如何始终指定区域名称,而不管我们是以控制器的操作、Razor 页面还是以名称为目标的特定路由。

如果添加asp-action而不是asp-controller属性,则默认为当前控制器。

其性质如下:

  • asp-actionstring:控制器动作的名称。
  • asp-areastring:区域名称。
  • asp-controllerstring:控制器名称,应与asp-action配合使用。如果未提供,则默认为当前控制器。
  • asp-pagestring:剃须刀页面的名称。
  • asp-routestring):端点定义中指定的路由名称。

您可以使用"~/"而不是"/"启动超链接,这意味着可以根据应用的基本路径映射具有的本地路径-例如,如果应用部署到"/app",则"~/file"的 URL 将变为"/app/file"

标签

此标记帮助器将其中声明的内容缓存在内存缓存中(在依赖项注入框架中注册的任何IMemoryCache实例)。我们唯一的选择是保持缓存的持续时间,以及它是相对的、绝对的还是滑动的。让我们来看一个最基本的例子:

<cache expires-after="TimeSpan.FromMinutes(5)">
@DateTime.Now
</cache>

这将在内存缓存中的<cache>标记中保留字符串一段时间。我们还具有以下属性:

  • enabledbool:是否启用(默认)
  • expires-afterTimeSpan:相对到期时间的值
  • expires-on``DateTime:绝对有效期
  • expires-slidingTimeSpan):滑动过期,与相对过期几乎相同,只是每次命中缓存时都会重新启动
  • priorityCacheItemPriority:缓存的优先级,默认为Normal
  • vary-bystring):用于改变缓存的任意(可能是动态)字符串值
  • vary-by-cookiestring):以逗号分隔的 cookie 名称列表,可根据其改变缓存
  • vary-by-headerstring):以逗号分隔的头名称列表,用于根据不同的头名称改变缓存
  • vary-by-querystring):一个以逗号分隔的query字符串参数名称列表,可根据其改变缓存
  • vary-by-routestring):一个以逗号分隔的路由数据参数列表,用于改变缓存
  • vary-by-userbool:是否根据登录用户名改变缓存(默认为false

必须提供expires-afterexpires-onexpires-sliding,但默认值为 20 分钟。对于vary-by,通常设置一个模型值,如订单或产品 ID,如下所示:

<cache vary-by="@ProductId">
...
</cache>

标签

此标记助手仅介绍给.NET Core 3.0,与 Blazor 相关,我们将在第 17 章介绍 Blazor中详细介绍。本质上,它呈现 Blazor 组件(一个.razor文件)。它接受以下参数:

  • typestring.razor文件的名称。
  • render-modeRenderMode):在第 17 章介绍 Blazor中讨论的一种可能的渲染模式。
  • param-XXXstring:传递给 Blazor 组件的可选参数;XXX应与组件上的属性名称匹配。

标签如下:

<component type="typeof(SomeComponent)" render-mode="ServerPrerendered" param-Text="Hello, World"/>

标签

<distributed-cache>标记与<cache>标记帮助器相同,只是它使用分布式缓存(IDistributedCache。它将另一个属性添加到由<cache>-namestring)提供的属性中,这是分布式缓存项的唯一名称。每个条目都应有自己的标签:

<distributed-cache name="redis" />

标签

<environment>标记也非常方便,它提供了根据正在运行的环境添加内容的功能(例如,DevelopmentStagingProduction

<environment names="Development,Staging">
    <script src="development/file.js"></script>
</environment>
<environment names="Production">
    <script src="production/file.js"></script>
</environment>

从 ASP.NET Core 2 开始,除了names之外,我们还有两个新属性—includeexcludeincludenames完全相同,而exclude的功能与您期望的完全相同,它显示了所有环境的内容,但命令后面列出的环境除外(逗号分隔)。

这些属性的属性如下所示:

  • include:提供用于渲染的环境列表
  • exclude:提供要从渲染中排除的环境列表

exclude始终优先。

标签

可以使用表单标记帮助器代替IHtmlHelper.BeginForm()。两者都提供相同的功能,包括发布到特定的控制器操作和添加防伪令牌作为隐藏字段(有关更多信息,请参阅第 11 章安全)。让我们看一下以下示例:

<form asp-controller="Home" asp-antiforgery="false" asp-action="Process">

默认情况下,防伪造功能处于启用状态。其特性如下:

  • asp-controller:如果不提供控制器名称,则默认为当前名称(如果使用 MVC)。
  • asp-action:控制器的动作方式
  • asp 区域:目标控制器所在区域的名称
  • asp-page:处理表单的刮胡刀页面
  • asp 页面处理程序:Razor 页面中处理表单的页面处理程序方法的名称
  • asp-route:指定路线
  • asp-antiforgery:决定是否检测默认打开的请求伪造

请注意,asp-controllerasp-areaasp-action可以一起使用,但将它们与asp-routeasp-page结合使用没有任何意义,因为它们是指定目的地的不同方式。

<script>标记帮助器允许为源属性指定测试、默认和回退值。测试是一个 JavaScript 表达式;让我们看一个例子:

<!-- if the current browser does not have the window.Promise property load a polyfill -->
<script asp-fallback-test="window.Promise" src="file.js" asp-fallback-src="polyfill.js"></script>

它还可用于一次将所有文件加载到文件夹中(某些可能的例外情况除外):

<script asp-src-include="~/app/**/*.js" asp-src-exclude="~/app/services/**/*.js"></script>

最后,它还可以通过向本地脚本添加版本号来阻止缓存;此版本反映文件的时间戳:

<script src="~/file.js" asp-append-version="true"></script>

您可能已经注意到了src属性中的前导~符号;它将自动替换为应用的根文件夹。例如,如果您的应用部署在/中,它将使用/,但如果部署在/virtualPath中,则~将被/virtualPath替换。这是第 2 章配置中描述的基本路径。

标签

<link>标记帮助器可以在本地 URL 后面加上一个版本的后缀,使其对缓存友好:

<link rel="stylesheet" href="~/css/site.min.css" asp-append-version="true"/>

<script>标记帮助器类似,它有条件地包含内容:

<link rel="stylesheet" href="file.css" asp-fallback-href="otherfile.css"
    asp-fallback-test-class="hidden" asp-fallback-test-property
    ="visibility" 
    asp-fallback-test-value="hidden" />

标签

<select>标记帮助器知道如何从可枚举类型的模型属性或SelectListItem对象集合检索项:

@functions
{
    IEnumerable<SelectListItem> GetItems()
    {
        yield return new SelectListItem { Text = "Red", Value = "#FF0000" };
        yield return new SelectListItem { Text = "Green",
        Value = "#00FF00" };
        yield return new SelectListItem { Text = "Blue",
        Value = "#0000FF", 
         Selected = true };
    }
}
<select asp-items="GetItems()"/>

有两个重要特性:

  • asp-for:从中检索当前所选项目(或项目列表)的属性或方法
  • asp-items:从中检索要填充列表的项目的属性或方法

标签

此标记已引入 ASP.NET 2.1;它呈现了一个局部视图,这与RenderPartialAsync)和PartialAsync方法的功能非常相似。

<partial name="_PartialFile" for="ModelProperty" model="Model" view-data="ViewData"></partial>

除了name部分视图(这是唯一必需的属性)之外,我们还可以向其传递一个视图数据对象(view-data和一个模型(一个for属性或model)。for属性可用于传递相对于包含视图的模型的表达式;例如,模型的属性。如果愿意,可以通过为model属性指定一个值,将全新模型传递给局部视图。请注意,formodel是相互排斥的;您只能使用其中一个。如果不使用其中任何一个,则将当前模型传递给局部视图。

其性质如下:

  • forExpression):相对于当前模型传递到局部视图的可选表达式。不能与model一起使用。
  • modelobject:传递到局部视图的可选模型。如果已设置,则它必须与其声明的模型类型匹配。不能与for一起使用。
  • name``string:必填项。这是要渲染的局部视图的名称。

验证消息和摘要

ValidationMessageTagHelperValidationSummaryTagHelper标记帮助程序仅为整个模型的<span>标记和<div>标记中的任何模型属性添加验证消息。例如,假设您想要获取Email模型属性的当前验证消息。您将执行以下操作:

<span asp-validation-for="Email"/>

对于整个模型,请执行以下操作:

<div asp-validation-summary/>

下一个主题将标记帮助器的概念介绍到更高的层次。我们将看到标记帮助器组件是如何工作的。

标记辅助对象组件

将标签助手组件引入 ASP.NET Core 2.0。它们是使用 DI 在输出中插入标记的一种方法。例如,想象一下,在视图中的特定位置插入 JavaScript 或 CSS 文件。

标记助手组件必须实现ITagHelperComponent并在 DI 框架中注册(方法ConfigureServices

services.AddSingleton<ITagHelperComponent, HelloWorldTagHelperComponent>();

ITagHelperComponent接口只指定了一种方法ProcessAsync。每个注册的标记帮助器组件都有其ProcessAsync方法,用于当前视图(包括布局)中找到的每个标记,使其有机会注入自定义标记帮助器:

public class HelloWorldTagHelperComponent : TagHelperComponent
{
    public override Task ProcessAsync(TagHelperContext context, 
    TagHelperOutput output)
    {
        if (context.TagName.ToLowerInvariant() == "head")
        {
            output.Content.AppendHtml("<script>window.alert('Hello, 
            World!')</script>");
        }

        return Task.CompletedTask;
    }
}

如果此标记为head,则此示例在当前标记内容的末尾插入自定义 JavaScript。

TagHelperComponent类实现ITagHelperComponent并提供虚拟方法,我们可以随意重写这些方法。我们有与ITagHelper接口相同的两种方法—InitProcessAsync,并且它们的使用方式相同。

ProcessAsync取两个参数,与ITagHelper接口ProcessAsync方法取的参数相同。

Tag helper components, as they are instantiated by the DI framework, fully support constructor injection.

现在让我们讨论一下局部视图,它是 Razor MVC 的构建块之一。

局部视图

我们已经在第 8 章API 控制器中介绍了部分视图。尽管它们是重用内容的一种非常有趣的机制,但它们在历史上存在一个问题,即它们不能跨程序集重用。总有办法解决这个问题;想法是将视图的.cshtml文件标记为嵌入式资源:

然后,我们只需要使用一个文件提供程序,它知道如何从程序集嵌入的资源中检索文件内容。为本例添加Microsoft.Extensions.FileProviders.EmbeddedNuGet 包。

ConfigureServices中注册 MVC 服务时,我们需要注册另一个文件提供程序EmbeddedFileProvider,将其传递给包含嵌入式资源的程序集:

services
    .AddMvc()
    .AddRazorOptions(options =>
    {
        var assembly = typeof(MyViewComponent).GetTypeInfo().Assembly;
        var embeddedFileProvider = new EmbeddedFileProvider(assembly, 
        "ReusableComponents");
        options.FileProviders.Add(embeddedFileProvider);
    });

在本例中,MyViewComponent类位于嵌入视图的同一程序集上,其默认名称空间为ReusableComponents。尝试加载文件时,ASP.NET Core 将遍历所有已注册的文件提供程序,直到其中一个返回非空结果。

幸运的是,我们现在有了Razor 类库,我们将很快介绍这些类库。

局部视图与视图组件

这两种机制是相似的,但如果要在视图组件中呈现较大的 HTML 块,则可以选择使用部分视图,因为需要操作字符串并通过代码返回它们。另一方面,局部视图是外部文件,这可能具有优势。

接下来,我们将讨论一些完全不同的代码库,您可以在项目中重用它们。

理解 Razor 类库

Razor 类库已引入 ASP.NET Core 2.2。这意味着所有基于代码或文件的组件都可以添加到程序集中,然后由应用引用。例如,如果这个类库包含多个.cshtml文件,我们可以在控制器中引用它们,或者在应用中为它们提供覆盖,只要遵循相同的路径。例如,想想 Identity 提供的身份验证和注册视图;如果你不喜欢其中任何一个,你可以提供一个替代方案,同时保留其他方案。

可以使用 Visual Studio 创建 Razor 类库:

它实质上生成一个.csproj文件,该文件使用Microsoft.NET.Sdk.RazorSDK(Razor 类库),而不是Microsoft.NET.Sdk.Web(用于 web 应用)或Microsoft.NET.Sdk(用于.NET Core 程序集):

<Project Sdk="Microsoft.NET.Sdk.Razor">
    <PropertyGroup>
        <TargetFramework>netstandard3.0</TargetFramework>
    </PropertyGroup> 
    <ItemGroup>
        <PackageReference Include="Microsoft.AspNetCore.Mvc" />
    </ItemGroup>
</Project>

当引用 Razor 类库程序集时,ASP.NET Core 知道该做什么,并且您无需任何其他工作即可引用其组件。

引用静态内容

如果您在 Razor 类库项目中创建一个wwwroot文件夹,您可以在引用该文件夹的项目中访问其中存储的任何静态内容(如.js.css和图像)。只需使用以下格式创建链接:

<script src="_content/<ClassLib>/<File>"></script>

这里,<ClassLib>是引用的 Razor 类库的名称,<File>是静态文件的名称。需要记住的一点是,您需要支持加载静态文件,例如,在Configure中,添加以下内容:

app.UseStaticFiles();

引用外部组件

可以通过引用包含视图零部件和标记辅助对象的部件来添加视图零部件和标记辅助对象,这是在创建或处理项目时完成的。但是,我们也可以在运行时添加引用。这些被称为应用部件。

为了为 Razor 类库注册应用部件,我们将执行以下操作:

services
    .AddMvc()
    .ConfigureApplicationManager(options =>
    {
        var path = "<path-to-razor-class-library-dll>";
        var asm = Assembly.LoadFrom(path);
        options.ApplicationParts.Add(new CompiledRazorAssemblyPart(asm));
    });

CompiledRazorAssemblyPart应用于 Razor 类库,其中还包括静态(基于文件的)资源。我们也可以对类型执行此操作,在这种情况下,要使用的类是AssemblyPart

在这里,我们看到了如何从外部组件引用零件,外部组件可以包括任何可重用组件。这是本章的最后一个主题。在下一章中,我们将介绍过滤器。

总结

在本章中,我们看到我们总是为视图组件、标记帮助器和标记帮助器组件使用提供的基类,因为它们使我们的生活更加轻松。

在可能的情况下,最好使用标记帮助程序而不是 HTML 帮助程序,并编写我们自己的标记帮助程序,因为它们比代码更容易阅读。标记帮助器组件对于在特定位置自动插入代码非常有用。<cache><distributed-cache><environment>标记助手非常有趣,它们将为您提供很多帮助。

然后,我们看到,当您有一个更易于用 HTML 编码的模板时,局部视图比视图组件更可取。视图组件都是关于代码的,通过字符串连接实现 HTML 比较困难。另一方面,视图组件使您可以更轻松地传递参数。

Razor 类库是在项目之间分发静态资产的一种新方法。一定要使用它们!

我们还了解到,标记帮助器组件是一种非常好的方法,可以从集中的位置向任何地方注入 HTML 元素。将它们用于常见的 CSS 和 JavaScript。

在本章中,我们研究了跨项目重用组件的技术。代码重用几乎总是一个好主意,您可以使用带有参数的视图组件来帮助实现这一点。在下一章中,我们将介绍过滤器,它是一个拦截并可能修改请求和响应的过程。

问题

您现在应该能够回答以下问题:

  1. 如何从其他部件加载局部视图?
  2. 渲染局部视图的两种方式是什么?
  3. 标记帮助器和标记帮助器组件之间有什么区别?
  4. 如何根据环境限制视图上显示的内容?
  5. Razor 类库和类库之间有什么区别?
  6. 什么是嵌入式资源?
  7. 执行视图组件的两种语法是什么?