{"id":635,"date":"2025-04-05T11:43:39","date_gmt":"2025-04-05T03:43:39","guid":{"rendered":"https:\/\/www.hyy.net\/?p=635"},"modified":"2025-04-05T11:43:39","modified_gmt":"2025-04-05T03:43:39","slug":"asp-net-core-in-action-32-building-custom-mvc-and-razor-pages-components","status":"publish","type":"post","link":"https:\/\/diji.net\/?p=635","title":{"rendered":"ASP.NET Core in Action 32 Building custom MVC and Razor Pages components"},"content":{"rendered":"<p>32 Building custom MVC and Razor Pages components\u200c<\/p>\n<h2>This chapter covers<\/h2>\n<p>\u2022  Creating custom Razor Tag Helpers<br \/>\n\u2022  Using view components to create complex Razor views<br \/>\n\u2022  Creating a custom DataAnnotations validation attribute<br \/>\n\u2022  Replacing the DataAnnotations validation framework with an alternative<\/p>\n<p>In the previous chapter you learned how to customize and extend some of the core systems in ASP.NET Core: con\ufb01guration, dependency injection (DI), and your middleware pipeline. These components form the basis of all ASP.NET Core apps. In this chapter we\u2019re focusing on Razor Pages and Model-View-Controller (MVC)\/API controllers. You\u2019ll learn how to build custom components that work with Razor views. You\u2019ll also learn how to build components that work with the validation framework used by both Razor Pages and API controllers.<\/p>\n<p>We\u2019ll start by looking at Tag Helpers. In section 32.1 I show how to create two Tag Helpers: one that generates HTML to describe the current machine and one that lets you write if statements in Razor templates without having to use C#.<\/p>\n<p>These will give you the details you need to create your own custom Tag Helpers in your own apps if the need arises.<\/p>\n<p>In section 32.2 you\u2019ll learn about a new Razor concept: view components. View components are a bit like partial views, but they can contain business logic and database access. For example, on an e-commerce site you might have a shopping cart, a dynamically populated menu, and a login widget all on one page. Each of those sections is independent of the main page content and has its own logic and data-access needs. In an ASP.NET Core app using Razor Pages, you\u2019d implement each of those as a view component.<\/p>\n<p>In section 32.3 I\u2019ll show you how to create a custom validation attribute. As you saw in chapter 6, validation is a key responsibility of Razor Page handlers and action methods, and the DataAnnotations attributes provide a clean, declarative way of doing so. We previously looked only at the built-in attributes, but you\u2019ll often \ufb01nd you need to add attributes tailored to your app\u2019s domain. In section 32.3 you\u2019ll see how to create a simple validation attribute and how to extend it to use services registered with the DI container.<\/p>\n<p>Throughout this book I\u2019ve mentioned that you can easily swap out core parts of the ASP.NET Core framework if you wish. In section 32.4 you\u2019ll do that by replacing the built-in attribute-based validation framework with a popular alternative, FluentValidation. This open-source library allows you to separate your binding models from the validation rules, which makes building certain validation logic easier.Many people prefer this approach of separating concerns to the declarative approach of DataAnnotations.<\/p>\n<p>When you\u2019re building pages with Razor Pages, one of the best productivity features is Tag Helpers, and in the next section you\u2019ll see how you can create your own.<\/p>\n<h2>32.1 Creating a custom Razor Tag Helper\u200c<\/h2>\n<p>In this section you\u2019ll learn how to create your own Tag Helpers, which allow you to customize your HTML output. You\u2019ll learn how to create Tag Helpers that add new elements to your HTML markup, as well as Tag Helpers that can remove or customize existing markup. You\u2019ll also see that your custom Tag Helpers integrate with the tooling of your integrated development environment (IDE) to provide rich IntelliSense in the same way as the built-in Tag Helpers.<\/p>\n<p>In my opinion, Tag Helpers are one of the best additions to the venerable Razor template language in ASP.NET Core.They allow you to write Razor templates that are easier to read, as they require less switching between C# and HTML, and they augment your HTML tags rather than replace them (as opposed to the HTML Helpers used extensively in the legacy version of ASP.NET).<\/p>\n<p>ASP.NET Core comes with a wide variety of Tag Helpers (see chapter 18), which cover many of your day-to-day requirements, especially when it comes to building forms.For example, you can use the Input Tag Helper by adding an<\/p>\n<p>asp-for attribute to an <code>&lt;input&gt;<\/code> tag and passing a\u200c reference to a property on your PageModel, in this case Input.Email:<br \/>\n`<\/p>\n<p><input asp-for=\"Input.Email\" \/><\/p>\n<p>`<\/p>\n<p>The Tag Helper is activated by the presence of the attribute and gets a chance to augment the <input> tag when rendering to HTML. The Input Tag Helper uses the name of the property to set the <input> tag\u2019s name and id properties, the value of the model to set the value property, and the presence of attributes such as [Required] or [EmailAddress] to add attributes for validations:\u200c\u200c\u200c<\/p>\n<pre><code>&lt;input type=&quot;email&quot; id=&quot;Input_Email&quot; name=&quot;Input.Email&quot; value=&quot;test@example.com&quot; data-val=&quot;true&quot;\ndata-val-email=&quot;The Email Address field is not a valid e-mail address.&quot;\n\ndata-val-required=&quot;The Email Address field is required.&quot;\n\/&gt;<\/code><\/pre>\n<p>Tag Helpers help reduce the duplication in your code, or they can simplify common patterns. In this section I show how you can create your own custom Tag Helpers.<\/p>\n<p>In section 32.1.1 you\u2019ll create a system information Tag Helper, which prints details about the name and operating system of the server your app is running on. In section 32.1.2 you\u2019ll create a Tag Helper that you can use to conditionally show or hide an element based on a C# Boolean property. In section 32.1.3 you\u2019ll create a Tag Helper that reads the Razor content written inside the Tag Helper and transforms it.<\/p>\n<h3>32.1.1 Printing environment information with a custom Tag Helper\u200c<\/h3>\n<p>A common problem you may run into when you start running your web applications in production, especially if you\u2019re using a server-farm setup, is working out which machine rendered the page you\u2019re currently looking at. Similarly, when deploying frequently, it can be useful to know which version of the application is running. When I\u2019m developing and testing, I sometimes like to add a little \u201cinfo dump\u201d at the bottom of my layouts so I can easily work out which server generated the current page, which environment it\u2019s running in, and so on.<\/p>\n<p>In this section I\u2019m going to show you how to build a custom Tag Helper to output system information to your layout. You\u2019ll be able to toggle the information it displays, but by default it displays the machine name and operating system on which the app is running, as shown in \ufb01gure 32.1.<\/p>\n<p><img decoding=\"async\" src=\"\/images\/aspnetcoreinaction\/3201.jpg\" alt=\"alt text\" \/><\/p>\n<p>Figure 32.1 The SystemInfoTagHelper displays the machine name and operating system on which the application is running. It can be useful for identifying which instance of your app handled the request when running in a web-farm scenario.<\/p>\n<p>You can call this Tag Helper from Razor by creating a <code>&lt;system-info&gt;<\/code> element in your template:<\/p>\n<pre><code>&lt;footer&gt;\n&lt;system-info&gt;&lt;\/system-info&gt;\n&lt;\/footer&gt;<\/code><\/pre>\n<p><b>TIP<\/b>  You might not want to expose this sort of information in production, so you could also wrap it in an <environment> Tag Helper, as you saw in chapter 18.<\/p>\n<p>The easiest way to create a custom Tag Helper is to derive from the TagHelper base class and override the Process() or ProcessAsync() function that describes how the class should render itself. The following listing shows your complete custom Tag Helper, SystemInfoTagHelper, which renders the system information to a <\/p>\n<div>. You could easily extend this class if you wanted to display additional \ufb01elds or add options.\u200c\u200c\u200c<\/p>\n<p>Listing 32.1 SystemInfoTagHelper to render system information to a view<\/p>\n<pre><code>public class SystemInfoTagHelper : TagHelper \u2776\n{\nprivate readonly HtmlEncoder _htmlEncoder; \u2777\npublic SystemInfoTagHelper(HtmlEncoder htmlEncoder) \u2777\n{\n_htmlEncoder = htmlEncoder;\n}\n[HtmlAttributeName(&quot;add-machine&quot;)] \u2778\npublic bool IncludeMachine { get; set; } = true;\n[HtmlAttributeName(&quot;add-os&quot;)] \u2778\npublic bool IncludeOS { get; set; } = true;\npublic override void Process( \u2779\nTagHelperContext context, TagHelperOutput output) \u2779\n{\noutput.TagName = &quot;div&quot;; \u277a\noutput.TagMode = TagMode.StartTagAndEndTag; \u277b\nvar sb = new StringBuilder();\nif (IncludeMachine) \u277c\n{ \u277c\nsb.Append(&quot; &lt;strong&gt;Machine&lt;\/strong&gt; &quot;); \u277c\nsb.Append(_htmlEncoder.Encode(Environment.MachineName)); \u277c\n} \u277c\nif (IncludeOS) \u277d\n{ \u277d\nsb.Append(&quot; &lt;strong&gt;OS&lt;\/strong&gt; &quot;); \u277d\nsb.Append( \u277d\n_htmlEncoder.Encode(RuntimeInformation.OSDescription)); \u277d\n} \u277d\noutput.Content.SetHtmlContent(sb.ToString()); \u277e\n}\n}<\/code><\/pre>\n<p>\u2776 Derives from the TagHelper base class<br \/>\n\u2777 An HtmlEncoder is necessary when writing HTML content to the page.<br \/>\n\u2778 Decorating properties with HtmlAttributeName allows you to set their values from Razor markup.<br \/>\n\u2779 The main function called when an element is rendered.<br \/>\n\u277a Replaces the <code>&lt;system-info&gt;<\/code> element with a <code>&lt;div&gt;<\/code> element<br \/>\n\u277b Renders both the <code>&lt;div&gt;<\/code> <code>&lt;\/div&gt;<\/code> start and end tag<br \/>\n\u277c If required, adds a <code>&lt;strong&gt; <\/code> element and the HTML-encoded machine name<br \/>\n\u277d If required, adds a <code>&lt;strong&gt;<\/code> element and the HTML-encoded OS name<br \/>\n\u277e Sets the inner content of the <\/p>\n<div> tag with the HTML-encoded value stored in the string builder<\/p>\n<p>There\u2019s a lot of new code in this example, so we\u2019ll work through it line by line. First, the class name of the Tag Helper de\ufb01nes the name of the element you must create in your Razor template, with the su\ufb03x removed and converted to kebab-case. As this Tag Helper is called SystemInfoTagHelper, you must create a <code>&lt;system- info&gt;<\/code> element.\u200c<\/p>\n<p><b>TIP<\/b>  If you want to customize the name of the element, for example to <code>&lt;env-info&gt;<\/code>, but you want to keep the same class name, you can apply [HtmlTargetElement] with the desired name, such as [HtmlTargetElement(&quot;Env-Info&quot;)]. HTML tags are not case-sensitive, so you could use &quot;Env-Info&quot; or &quot;env-info&quot;.<\/p>\n<p>Inject an HtmlEncoder into your Tag Helper so you can HTML-encode any data you write to the page. As you saw in chapter 29, you should always HTML-encode data you write to the page to avoid cross-site scripting (XSS) vulnerabilities and to ensure that the data is displayed correctly.<\/p>\n<p>You\u2019ve de\ufb01ned two properties on your Tag Helper, IncludeMachine and IncludeOS, which you\u2019ll use to control which data is written to the page. These are decorated with a corresponding [HtmlAttributeName], which enables setting the properties from the Razor template. In Visual Studio you\u2019ll even get IntelliSense and type-checking for these values, as shown in \ufb01gure 32.2.\u200c<\/p>\n<p><img decoding=\"async\" src=\"\/images\/aspnetcoreinaction\/3202.jpg\" alt=\"alt text\" \/><\/p>\n<p>Figure 32.2 In Visual Studio, Tag Helpers are shown in a purple font, and you get IntelliSense for properties decorated with [HtmlAttributeName].<\/p>\n<p>Finally, we come to the Process() method. The Razor engine calls this method to execute the Tag Helper when it identi\ufb01es the target element in a view template. The Process() method de\ufb01nes the type of tag to render (<code>&lt;div&gt;<\/code>), whether it should render a start and end tag (or a self-closing tag\u2014it depends on the type of tag you\u2019re rendering), and the HTML content of the <code>&lt;div&gt;<\/code>. You set the HTML content to be rendered inside the tag by calling Content.SetHtmlContent() on the provided instance of TagHelperOutput.<\/p>\n<p><b>WARNING<\/b> Always HTML-encode your output before writing to your tag with SetHtmlContent(). Alternatively, pass unencoded input to SetContent(), and the output will be automatically HTML-encoded for you.<\/p>\n<p>Before you can use your new Tag Helper in a Razor template, you need to register it. You can do this in the _ViewImports.cshtml \ufb01le, using the  @addTagHelper directive and specifying the fully quali\ufb01ed name of the Tag Helper and the assembly, as in this example:<\/p>\n<pre><code>@addTagHelper CustomTagHelpers.SystemInfoTagHelper, CustomTagHelpers<\/code><\/pre>\n<p>Alternatively, you can add all the Tag Helpers from a given assembly by using the wildcard syntax, *, and specifying the assembly name:<\/p>\n<pre><code>@addTagHelper *, CustomTagHelpers<\/code><\/pre>\n<p>With your custom Tag Helper created and registered, you\u2019re now free to use it in any of your Razor views, partial views, or layouts.<\/p>\n<p><b>TIP<\/b>  If you\u2019re not seeing IntelliSense for your Tag Helper in Visual Studio, and the Tag Helper isn\u2019t rendered in the bold font used by Visual Studio, you probably haven\u2019t registered your Tag Helpers correctly in _ViewImports .cshtml using @addTagHelper.<\/p>\n<p>The SystemInfoTagHelper is an example of a Tag Helper that generates content, but you can also use Tag Helpers to control how existing elements are rendered. In the next section you\u2019ll create a simple Tag Helper that can control whether an element is rendered based on an HTML attribute.<\/p>\n<h3>32.1.2 Creating a custom Tag Helper to conditionally hide elements\u200c<\/h3>\n<p>If you want to control whether an element is displayed in a Razor template based on some C# variable, you\u2019d typically wrap the element in a C# if statement:\u200c<\/p>\n<pre><code>@{\nvar showContent = true;\n}\n@if(showContent)\n{\n&lt;p&gt;The content to show&lt;\/p&gt;\n}<\/code><\/pre>\n<p>Falling back to C# constructs like this can be useful, as it allows you to generate any markup you like. Unfortunately, it can be mentally disruptive having to switch back and forth between C# and HTML, and it makes it harder to use HTML editors that don\u2019t understand Razor syntax.<\/p>\n<p>In this section you\u2019ll create a simple Tag Helper to avoid the cognitive dissonance problem. You can apply this Tag Helper to existing elements to achieve the same result as shown previously but without having to fall back to C#:<\/p>\n<pre><code>@{\nvar showContent = true;\n}\n&lt;p if=&quot;showContent&quot; &gt;\nThe content to show\n&lt;\/p&gt;\n<\/code><\/pre>\n<p>When rendered at runtime, this Razor template would return the HTML<\/p>\n<pre><code>&lt;p&gt;\nThe content to show\n&lt;\/p&gt;<\/code><\/pre>\n<p>Instead of creating a new element, as you did for SystemInfoTagHelper (<code>&lt;system-info&gt;<\/code>), you\u2019ll create a Tag Helper that you apply as an attribute to existing HTML elements. This Tag Helper does one thing: controls the visibility of the element it\u2019s attached to. If the value passed in the if attribute is true, the element and its content is rendered as normal. If the value passed is false, the Tag Helper removes the element and its content from the template. The following listing shows how you could achieve this.<\/p>\n<p>Listing 32.2 Creating an IfTagHelper to conditionally render elements<\/p>\n<pre><code>[HtmlTargetElement(Attributes = &quot;if&quot;)] \u2776\npublic class IfTagHelper : TagHelper\n{\n[HtmlAttributeName(&quot;if&quot;)] \u2777\npublic bool RenderContent { get; set; } = true;\npublic override void Process( \u2778\nTagHelperContext context, TagHelperOutput output) \u2778\n{\nif(RenderContent == false) \u2779\n{\noutput.TagName = null; \u277a\noutput.SuppressOutput(); \u277b\n}\n}\npublic override int Order =&gt; int.MinValue; \u277c\n}<\/code><\/pre>\n<p>\u2776 Setting the Attributes property ensures that the Tag Helper is triggered by an if attribute.<br \/>\n\u2777 Binds the value of the if attribute to the RenderContent property<br \/>\n\u2778 The Razor engine calls Process() to execute the Tag Helper.<br \/>\n\u2779 If the RenderContent property evaluates to false, removes the element<br \/>\n\u277a Sets the element the Tag Helper resides on to null, removing it from the page<br \/>\n\u277b Doesn\u2019t render or evaluate the inner content of the element<br \/>\n\u277c Ensures that this Tag Helper runs before any others attached to the element<\/p>\n<p>Instead of a standalone <code>&lt;if&gt;<\/code> element, the Razor engine executes the IfTagHelper whenever it \ufb01nds an element with an if attribute. This can be applied to any HTML element: <code>&lt;p&gt;<\/code>, <code>&lt;div&gt;<\/code>, <code>&lt;input&gt;<\/code>, whatever you need. You should de\ufb01ne a Boolean property specifying whether you should render the content, which is bound to the value in the if attribute.\u200c\u200c\u200c\u200c<\/p>\n<p>The Process() function is much simpler here. If RenderContent is false, it sets the TagHelperOutput.TagName to null, which removes the element from the page. It also calls SuppressOutput(), which prevents any content inside the attributed element from being rendered. If RenderContent is true, you skip these steps, and the content is rendered as normal.<\/p>\n<p>One other point of note is the overridden Order property. This controls the order in which Tag Helpers run when multiple Tag Helpers are applied to an element. By setting Order to int.MinValue, you ensure that IfTagHelper always runs \ufb01rst, removing the element if required, before other Tag Helpers execute. There\u2019s generally no point running other Tag Helpers if the element is going to be removed from the page anyway.<\/p>\n<p><b>NOTE<\/b>  Remember to register your custom Tag Helpers in _ViewImports .cshtml with the @addTagHelper directive.<\/p>\n<p>With a simple HTML attribute, you can now conditionally render elements in Razor templates without having to fall back to C#. This Tag Helper can show and hide content without needing to know what the content is. In the next section we\u2019ll create a Tag Helper that does need to know the content.<\/p>\n<h3>32.1.3 Creating a Tag Helper to convert Markdown to HTML\u200c<\/h3>\n<p>The two Tag Helpers shown so far are agnostic to the content written inside the Tag Helper, but it can also be useful to create Tag Helpers that inspect, retrieve, and modify this<\/p>\n<p>content. In this section you\u2019ll see an example of one such Tag Helper that converts Markdown content written inside it to HTML.<\/p>\n<p><b>DEFINITION<\/b> Markdown is a commonly used text-based markup language that is easy to read but can also be converted to HTML. It is the common format used by README \ufb01les on GitHub, and I use it to write blog posts, for example. For an introduction to Markdown, see the GitHub guide at <a href=\"http:\/\/mng.bz\/o1rp\">http:\/\/mng.bz\/o1rp<\/a>.<\/p>\n<p>We\u2019ll use the popular Markdig library (<a href=\"https:\/\/github.com\/xoofx\/markdig\">https:\/\/github.com\/xoofx\/markdig<\/a>) to create the Markdown Tag Helper. This library converts a string containing Markdown to an HTML string. You can install Markdig using Visual Studio by running dotnet add package Markdig or by adding a <code>&lt;PackageReference&gt;<\/code> to your .csproj \ufb01le:<\/p>\n<pre><code>&lt;PackageReference Include=&quot;Markdig&quot; Version=&quot;0.30.4&quot; \/&gt;<\/code><\/pre>\n<p>The Markdown Tag Helper that we\u2019ll create shortly can be used by adding <markdown> elements to your Razor Page, as shown in the following listing.<\/p>\n<p>Listing 32.3 Using a Markdown Tag Helper in a Razor Page<\/p>\n<pre><code>@page\n@model IndexModel\n@{\nvar showContent = true;\n}\n&lt;markdown&gt; \u2776\n## This is a markdown title \u2777\nThis is a markdown list: \u2778\n* Item 1 \u2778\n* Item 2 \u2778\n&lt;div if=&quot;showContent&quot;&gt; \u2779\nContent is shown when showContent is true \u2779\n&lt;\/div&gt; \u2779\n&lt;\/markdown&gt;\n<\/code><\/pre>\n<p>\u2776 Adds the Markdown Tag Helper using the <code>&lt;markdown&gt;<\/code> element<br \/>\n\u2777 Creates titles in Markdown using # to denote h1, ## to denote h2, and so on<br \/>\n\u2778 Markdown converts simple lists to HTML <code>&lt;ul&gt;<\/code> elements.<br \/>\n\u2779 Razor content can be nested inside other Tag Helpers.<\/p>\n<p>The Markdown Tag Helper renders content with these steps:<\/p>\n<ol>\n<li>\n<p>Render any Razor content inside the Tag Helper. This includes executing any nested Tag Helpers and C# code inside the Tag Helper. Listing 32.3 uses the IfTagHelper, for example.<\/p>\n<\/li>\n<li>\n<p>Convert the resulting string to HTML using the Markdig library.<\/p>\n<\/li>\n<li>\n<p>Replace the content with the rendered HTML and remove the Tag Helper <code>&lt;markdown&gt;<\/code> element.<\/p>\n<\/li>\n<\/ol>\n<p>The following listing shows a simple approach to implementing a Markdown Tag Helper using Markdig. Markdig supports many additional extensions and features that you could enable, but the overall pattern of the Tag Helper would be the same.<\/p>\n<p>Listing 32.4 Implementing a Markdown Tag Helper using Markdig<\/p>\n<pre><code>public class MarkdownTagHelper: TagHelper \u2776\n{\npublic override async Task ProcessAsync(\nTagHelperContext context, TagHelperOutput output)\n{\nTagHelperContent markdownRazorContent = await \u2777\noutput.GetChildContentAsync(); \u2777\nstring markdown = \u2778\nmarkdownRazorContent.GetContent(); \u2778\nstring html = Markdig.Markdown.ToHtml(markdown); \u2779\noutput.Content.SetHtmlContent(html); \u277a\noutput.TagName = null; \u277b\n}\n}\n<\/code><\/pre>\n<p>\u2776 The Markdown Tag Helper will use the <code>&lt;markdown&gt;<\/code> element.<br \/>\n\u2777 Retrieves the contents of the <code>&lt;markdown&gt;<\/code> element<br \/>\n\u2778 Renders the Razor contents to a string<br \/>\n\u2779 Converts the Markdown string to HTML using Markdig<br \/>\n\u277a Writes the HTML content to the output<br \/>\n\u277b Removes the <code>&lt;markdown&gt;<\/code> element from the content<\/p>\n<p>When rendered to HTML, the Markdown content in listing 32.3 becomes<\/p>\n<pre><code>&lt;h2&gt;This is a markdown title&lt;\/h2&gt;\n&lt;p&gt;This is a markdown list:&lt;\/p&gt;\n&lt;ul&gt;\n&lt;li&gt;Item 1&lt;\/li&gt;\n&lt;li&gt;Item 2&lt;\/li&gt;\n&lt;\/ul&gt;\n&lt;div&gt;\nContent is shown when showContent is true\n&lt;\/div&gt;<\/code><\/pre>\n<p><b>NOTE<\/b>  In listing 32.4 we implemented ProcessAsync() instead of Process() because we called the async method GetChildContentAsync(). You must call async methods only from other async methods; otherwise, you can get problems such as thread starvation. For more details, see Microsoft\u2019s \u201cASP.NET Core Best Practices\u201d at <a href=\"http:\/\/mng.bz\/KM7X\">http:\/\/mng.bz\/KM7X<\/a>.\u200c<\/p>\n<p>The Tag Helpers in this section represent a small sample of possible avenues you could explore, but they cover the two broad categories: Tag Helpers for rendering new content and Tag Helpers for controlling the rendering of other elements.<\/p>\n<p><b>TIP<\/b>  For further details and examples, see Microsoft\u2019s \u201cAuthor Tag Helpers in ASP.NET Core\u201d documentation at <a href=\"http:\/\/mng.bz\/Idb0\">http:\/\/mng.bz\/Idb0<\/a>.<\/p>\n<p>Tag Helpers can be useful for providing small pieces of isolated, reusable functionality like this, but they\u2019re not designed to provide larger, application-speci\ufb01c sections of an app or to make calls to business-logic services. Instead, you should use view components, as you\u2019ll see in the next section.\u200c<\/p>\n<h2>32.2 View components: Adding logic to partial views\u200c<\/h2>\n<p>In this section you\u2019ll learn about view components, which operate independently of the main Razor Page and can be used to encapsulate complex business logic. You can use view components to keep your main Razor Page focused on a single task\u2014rendering the main content\u2014instead of also being responsible for other sections of the page.<\/p>\n<p>If you think about a typical website, you\u2019ll notice that it may have multiple independent dynamic sections in addition to the main content. Consider Stack Over\ufb02ow, shown in \ufb01gure 32.3. As well as the main body of the page, which shows questions and answers, there\u2019s a section showing the current logged-in user, a panel for blog posts and related items, and a section for job suggestions.<\/p>\n<p><img decoding=\"async\" src=\"\/images\/aspnetcoreinaction\/3203.jpg\" alt=\"alt text\" \/><\/p>\n<p>Figure 32.3 The Stack Over\ufb02ow website has multiple sections that are independent of the main content but contain business logic and complex rendering logic.<\/p>\n<p>Each of these sections could be rendered as a view component in ASP.NET Core.<\/p>\n<p>Each of these sections is e\ufb00ectively independent of the main content. Each section contains business logic (deciding which posts or ads to show), database access (loading the details of the posts), and rendering logic for how to display the data.<\/p>\n<p>In chapter 7 you saw that you can use layouts and partial views to split the rendering of a view template into similar sections, but partial views aren\u2019t a good \ufb01t for this example. Partial views let you encapsulate view rendering logic but not business logic that\u2019s independent of the main page content. Instead, view components provide this functionality, encapsulating both the business logic and rendering logic for displaying a small section of the page. You can use DI to provide access to a database context, and you can test view components independently of the view they generate, much like MVC and API controllers. Think of them as being a bit like mini MVC controllers or mini Razor Pages, but you invoke them directly from a Razor view instead of in response to an HTTP request.<\/p>\n<p><b>TIP<\/b>  View components are comparable to child actions from the legacy .NET Framework version of ASP.NET, in that they provide similar functionality. Child actions don\u2019t exist in ASP.NET Core.<\/p>\n<blockquote>\n<p>View components vs. Razor Components and Blazor<\/p>\n<p>In this book I focus on server-side rendered applications using Razor Pages and API applications using minimal APIs and web API controllers. .NET 7 also has a di\ufb00erent approach to building ASP.NET Core applications: Blazor. I don\u2019t cover Blazor in this book, so I recommend reading Blazor in Action, by Chris Sainty (Manning, 2021).\u200c<\/p>\n<p>Blazor has two programming models, client-side and server-side, but both approaches use Blazor components (confusingly, o\ufb03cially called Razor components). Blazor components have a lot of parallels with view components, but they live in a fundamentally di\ufb00erent world. Blazor components can interact easily, but you can\u2019t use them with Tag Helpers or view components, and it\u2019s hard to combine them with Razor Page form posts.<\/p>\n<p>Nevertheless, if you need an island of rich client-side interactivity in a single Razor Page, you can embed a Blazor component in a Razor Page, as shown in the \u201cRender components from a page or view\u201d section of the \u201cPrerender and integrate ASP.NET Core Razor components\u201d documentation at <a href=\"http:\/\/mng.bz\/PPen\">http:\/\/mng.bz\/PPen<\/a>. You could also use Blazor components as a way to replace Asynchronous JavaScript and XML (AJAX) calls in your Razor Pages, as I show in my blog entry \u201cReplacing AJAX calls in Razor Pages with Razor Components and Blazor\u201d at <a href=\"http:\/\/mng.bz\/9MJj\">http:\/\/mng.bz\/9MJj<\/a>.<\/p>\n<p>If you don\u2019t need the client-side interactivity of Blazor, view components are still the best option for isolated sections in Razor Pages. They interoperate cleanly with your Razor Pages; have no additional operational overhead; and use familiar concepts like layouts, partial views, and Tag Helpers. For more details on why you should continue to use view components, see my \u201cDon\u2019t replace your View Components with Razor Components\u201d blog entry at <a href=\"http:\/\/mng.bz\/1rKq\">http:\/\/mng.bz\/1rKq<\/a>.<\/p>\n<\/blockquote>\n<p>In this section you\u2019ll see how to create a custom view component for the recipe app you built in previous chapters, as shown in \ufb01gure 32.4. If the current user is logged in, the view component displays a panel with a list of links to the user\u2019s recently created recipes. For unauthenticated users, the view component displays links to the login and register actions.<\/p>\n<p><img decoding=\"async\" src=\"\/images\/aspnetcoreinaction\/3204.jpg\" alt=\"alt text\" \/><\/p>\n<p>Figure 32.4 The view component displays di\ufb00erent content based on the currently logged-in user. It includes both business logic (determining which recipes to load from the database) and rendering logic (specifying how to display the data).<\/p>\n<p>This component is a great candidate for a view component, as it contains database access and business logic (choosing which recipes to display) as well as rendering logic (deciding how the panel should be displayed).<\/p>\n<p><b>TIP<\/b>  Use partial views when you want to encapsulate the rendering of a speci\ufb01c view model or part of a view model. Consider using a view component when you have rendering logic that requires business logic or database access or when the section is logically distinct from the main page content.<\/p>\n<p>You invoke view components directly from Razor views and layouts using a Tag Helper-style syntax with a vc: pre\ufb01x:<\/p>\n<pre><code>&lt;vc:my-recipes number-of-recipes=&quot;3&quot;&gt;\n&lt;\/vc:my-recipes&gt;<\/code><\/pre>\n<p>Custom view components typically derive from the ViewComponent base class and implement an InvokeAsync() method, as shown in listing 32.5. Deriving from this base class allows access to useful helper methods in much the same way that deriving from the ControllerBase class does for API controllers. Unlike with API controllers, the parameters passed to InvokeAsync don\u2019t come from model binding. Instead, you pass the parameters to the view component using properties on the Tag Helper element in your Razor view.\u200c\u200c<\/p>\n<p>Listing 32.5 A custom view component to display the current user\u2019s recipes<\/p>\n<pre><code>public class MyRecipesViewComponent : ViewComponent \u2776\n{\nprivate readonly RecipeService _recipeService; \u2777\nprivate readonly UserManager&lt;ApplicationUser&gt; _userManager; \u2777\npublic MyRecipesViewComponent(RecipeService recipeService, \u2777\nUserManager&lt;ApplicationUser&gt; userManager) \u2777\n{ \u2777\n_recipeService = recipeService; \u2777\n_userManager = userManager; \u2777\n} \u2777\npublic async Task&lt;IViewComponentResult&gt; InvokeAsync( \u2778\nint numberOfRecipes) \u2779\n{\nif(!User.Identity.IsAuthenticated)\n{\nreturn View(&quot;Unauthenticated&quot;); \u277a\n}\nvar userId = _userManager.GetUserId(HttpContext.User); \u277b\nvar recipes = await _recipeService.GetRecipesForUser( \u277b\nuserId, numberOfRecipes);\nreturn View(recipes); \u277c\n}\n}<\/code><\/pre>\n<p>\u2776 Deriving from the ViewComponent base class provides useful methods like<br \/>\nView().<br \/>\n\u2777 You can use DI in a view component.<br \/>\n\u2778 InvokeAsync renders the view component. It should return a<br \/>\n<code>Task&lt;IViewComponentResult&gt;<\/code>.<br \/>\n\u2779 You can pass parameters to the component from the view.<br \/>\n\u277a Calling View() will render a partial view with the provided name.<br \/>\n\u277b You can use async external services, allowing you to encapsulate logic in your<br \/>\nbusiness domain.<br \/>\n\u277c You can pass a view model to the partial view. Default.cshtml is used by<br \/>\ndefault.<\/p>\n<p>This custom view component handles all the logic you need to render a list of recipes when the user is logged in or a di\ufb00erent view if the user isn\u2019t authenticated. The name of the view component is derived from the class name, like Tag Helpers. Alternatively, you can apply the [ViewComponent] attribute to the class and set a di\ufb00erent name entirely.<\/p>\n<p>The InvokeAsync method must return a <code>Task&lt;IViewComponentResult&gt;<\/code>. This is similar to the way you can return IActionResult from an action method or a page handler, but it\u2019s more restrictive; view components must render some sort of content, so you can\u2019t return status codes or redirects. You\u2019ll typically use the View() helper method to render a partial view template (as in the previous listing), though you can also return a string directly using the Content() helper method, which will HTML-encode the content and render it to the page directly.\u200c\u200c<\/p>\n<p>You can pass any number of parameters to the InvokeAsync method. The name of the parameters (in this case, numberOfRecipes) is converted to kebab-case and exposed as a property in the view component\u2019s Tag Helper (<code>&lt;number-of-recipes&gt;<\/code>). You can provide these parameters when you invoke the view component from a view, and you\u2019ll get IntelliSense support, as shown in \ufb01gure 32.5.<\/p>\n<p><img decoding=\"async\" src=\"\/images\/aspnetcoreinaction\/3205.jpg\" alt=\"alt text\" \/><\/p>\n<p>Figure 32.5 Visual Studio provides IntelliSense support for the method parameters of a view component\u2019s InvokeAsync method. The parameter name, in this case numberOfRecipes, is converted to kebab-case for use as an attribute in the Tag Helper.<\/p>\n<p>View components have access to the current request and HttpContext. In listing 32.5 you can see that we\u2019re checking whether the current request was from an authenticated user. You can also see that we\u2019ve used some conditional logic. If the user isn\u2019t authenticated, we render the \u201cUnauthenticated\u201d Razor template; if they\u2019re authenticated, we render the default Razor template and pass in the view models loaded from the database.<\/p>\n<p><b>NOTE<\/b>  If you don\u2019t specify a speci\ufb01c Razor view template to use in the View() function, view components use the template name Default.cshtml.<\/p>\n<p>The partial views for view components work similarly to other Razor partial views that you learned about in chapter 7, but they\u2019re stored separately from them. You must create partial views for view components at one of these locations:<\/p>\n<p>\u2022  Views\/Shared\/Components\/ComponentName\/Templ ateName<\/p>\n<p>\u2022  Pages\/Shared\/Components\/ComponentName\/Templ ateName<\/p>\n<p>Both locations work, so for Razor Pages apps I typically use the Pages\/ folder. For the view component in listing 32.5, for example, you\u2019d create your view templates at<\/p>\n<p>\u2022  Pages\/Shared\/Components\/MyRecipes\/Def ault.cshtml<br \/>\n\u2022  Pages\/Shared\/Components\/MyRecipes\/Una uthenticated.cshtml<\/p>\n<p>This was a quick introduction to view components, but it should get you a long way. View components are a simple way to embed pockets of isolated, complex logic in your Razor layouts. Having said that, be mindful of these caveats:<\/p>\n<p>\u2022  View component classes must be public, non- nested, and nonabstract classes.<\/p>\n<p>\u2022  Although they\u2019re similar to MVC controllers, you can\u2019t use \ufb01lters with view components.<\/p>\n<p>\u2022  You can use layouts in your view components\u2019 views to extract rendering logic common to a speci\ufb01c view component. This layout may contain @sections, as you saw in chapter 7, but these sections are independent of the main Razor view\u2019s layout.<\/p>\n<p>\u2022  View components are isolated from the Razor Page they\u2019re rendered in, so you can\u2019t, for example, de\ufb01ne a @section in a Razor Page layout and then add that content from a view component; the contexts are completely separate.<\/p>\n<p>\u2022  When using the <code>&lt;vc:my-recipes&gt;<\/code> Tag Helper syntax to invoke your view component, you must import it as a custom Tag Helper, as you saw in section 32.1.<\/p>\n<p>\u2022  Instead of using the Tag Helper syntax, you may invoke the view component from a view directly by using IViewComponentHelper Component, though I don\u2019t recommend using this syntax, as in this example:<\/p>\n<pre><code>@await Component.InvokeAsync(&quot;MyRecipes&quot;, new { numberOfRecipes = 3 })<\/code><\/pre>\n<p>We\u2019ve covered Tag Helpers and view components, which are both features of the Razor engine in ASP.NET Core. In the next section you\u2019ll learn about a di\ufb00erent but related topic: how to create a custom DataAnnotations attribute. If you\u2019ve used older versions of ASP.NET, this will be familiar, but ASP.NET Core has a couple of tricks up its sleeve to help you out.\u200c<\/p>\n<h2>32.3 Building a custom validation attribute\u200c<\/h2>\n<p>In this section you\u2019ll learn how to create a custom DataAnnotations validation attribute that speci\ufb01es speci\ufb01c values a string property may take. You\u2019ll then learn how you can expand the functionality to be more generic by delegating to a separate service that is con\ufb01gured in your DI controller. This will allow you to create custom domain-speci\ufb01c validations for your apps.\u200c<\/p>\n<p>We looked at model binding in chapter 7, where you saw how to use the built-in DataAnnotations attributes in your binding models to validate user input. These provide several built-in validations, such as<\/p>\n<p>\u2022  [Required]\u2014The property isn\u2019t optional and must be provided.<\/p>\n<p>\u2022  [StringLength(min, max)]\u2014The length of the string value must be between min and max characters.<\/p>\n<p>\u2022  [EmailAddress]\u2014The value must have a valid email address format.<\/p>\n<p>But what if these attributes don\u2019t meet your requirements? Consider the following listing, which shows a binding model from a currency converter application. The model contains three properties: the currency to convert from, the currency to convert to, and the quantity.<\/p>\n<p>Listing 32.6 Currency converter initial binding model<\/p>\n<pre><code>public class CurrencyConverterModel\n{\n[Required] \u2776\n[StringLength(3, MinimumLength = 3)] \u2777\npublic string CurrencyFrom { get; set; }\n[Required] \u2776\n[StringLength(3, MinimumLength = 3)] \u2777\npublic string CurrencyTo { get; set; }\n[Required] \u2776\n[Range(1, 1000)] \u2778\npublic decimal Quantity { get; set; }\n}<\/code><\/pre>\n<p>\u2776 All the properties are required.<br \/>\n\u2777 The strings must be exactly three characters.<br \/>\n\u2778 The quantity can be between 1 and 1000.<\/p>\n<p>There\u2019s some basic validation on this model, but during testing you identify a problem: users can enter any three- letter string for the CurrencyFrom and CurrencyTo properties. Users should be able to choose only a valid currency code, like &quot;USD&quot; or &quot;GBP&quot;, but someone attacking your application could easily send &quot;XXX&quot; or &quot;\u00a3$%&quot;.\u200c<\/p>\n<p>Assuming that you support a limited set of currencies\u2014say, GBP, USD, EUR, and CAD\u2014you could handle the validation in a few ways. One way would be to validate the CurrencyFrom and CurrencyTo values within the Razor Page handler method, after model binding and attribute validation has already occurred.<\/p>\n<p>Another way would be to use a [RegularExpresssion] attribute to look for the allowed strings. The approach I\u2019m going to take here is to create a custom ValidationAttribute. The goal is to have a custom validation attribute you can apply to the CurrencyFrom and CurrencyTo attributes, to restrict the range of valid values. This will look something like the following example.<\/p>\n<p>Listing 32.7 Applying custom validation attributes to the binding model<\/p>\n<pre><code>public class CurrencyConverterModel\n{\n[Required]\n[StringLength(3, MinimumLength = 3)]\n[CurrencyCode(&quot;GBP&quot;, &quot;USD&quot;, &quot;CAD&quot;, &quot;EUR&quot;)] \u2776\npublic string CurrencyFrom { get; set; }\n[Required]\n[StringLength(3, MinimumLength = 3)]\n[CurrencyCode(&quot;GBP&quot;, &quot;USD&quot;, &quot;CAD&quot;, &quot;EUR&quot;)] \u2776\npublic string CurrencyTo { get; set; }\n[Required]\n[Range(1, 1000)]\npublic decimal Quantity { get; set; }\n}<\/code><\/pre>\n<p>\u2776 CurrencyCodeAttribute validates that the property has one of the provided<br \/>\nvalues.<\/p>\n<p>Creating a custom validation attribute is simple; you can start with the ValidationAttribute base class, and you have to override only a single method. The next listing shows how you could implement CurrencyCodeAttribute to ensure that the currency codes provided match the expected values.<\/p>\n<p>Listing 32.8 Custom validation attribute for currency codes<\/p>\n<pre><code>public class CurrencyCodeAttribute : ValidationAttribute \u2776\n{\nprivate readonly string[] _allowedCodes; \u2777\npublic CurrencyCodeAttribute(params string[] allowedCodes) \u2777\n{ \u2777\n_allowedCodes = allowedCodes; \u2777\n} \u2777\nprotected override ValidationResult IsValid( \u2778\nobject value, ValidationContext context) \u2778\n{\nif(value is not string code \u2779\n|| !_allowedCodes.Contains(code)) \u277a\n{ \u277a\nreturn new ValidationResult(&quot;Not a valid currency code&quot;); \u277a\n}\nreturn ValidationResult.Success; \u277b\n}\n}<\/code><\/pre>\n<p>\u2776 Derives from ValidationAttribute to ensure that your attribute is used during validation<br \/>\n\u2777 The attribute takes in an array of allowed currency codes.<br \/>\n\u2778 The IsValid method is passed the value to validate and a context object.<br \/>\n\u2779 Tries to cast the value to a string and store it in the code variable<br \/>\n\u277a If the value provided isn\u2019t a string, is null, or isn\u2019t an allowed code, returns an  error . . .<br \/>\n\u277b . . .otherwise, returns a success result.<\/p>\n<p>As you know from chapter 16, Validation occurs in the \ufb01lter pipeline after model binding, before the action or Razor Page handler executes. The validation framework calls IsValid() for each instance of ValidationAttribute on the model property being validated. The framework passes in value (the value of the property being validated) and the ValidationContext to each attribute in turn. The context object contains details that you can use to validate the property.<\/p>\n<p>Of particular note is the ObjectInstance property. You can use this to access the top-level model being validated when you validate a subproperty. For example, if the CurrencyFrom property of the CurrencyConvertModel is being validated, you can access the top-level object from the ValidationAttribute as follows:<\/p>\n<pre><code>var model = validationContext.ObjectInstance as CurrencyConverterModel;<\/code><\/pre>\n<p>This can be useful if the validity of a property depends on the value of another property of the model. For example, you might want a validation rule that says that GBP is a valid value for CurrencyTo except when CurrencyFrom is also GBP. ObjectInstance makes these sorts of comparison validations easy.<\/p>\n<p><b>NOTE<\/b>  Although using ObjectInstance makes it easy to make model-level comparisons like these, it reduces the portability of your validation attribute. In this case, you would be able to use the attribute only in the application that de\ufb01nes CurrencyConverterModel.<\/p>\n<p>Within the IsValid() method, you can cast the value provided to the required data type (in this case, string) and check against the list of allowed codes. If the code isn\u2019t allowed, the attribute returns a ValidationResult with an error message indicating that there was a problem. If the code is allowed, ValidationResult.Success is returned, and the validation succeeds.<\/p>\n<p>Putting your attribute to the test in \ufb01gure 32.6 shows that when CurrencyTo is an invalid value (\u00a3$%), the validation for the property fails, and an error is added to the ModelState. You could do some tidying-up of this attribute to set a custom message, allow nulls, or display the name of the property that\u2019s invalid, but all the important features are there.<\/p>\n<p><img decoding=\"async\" src=\"\/images\/aspnetcoreinaction\/3206.jpg\" alt=\"alt text\" \/><\/p>\n<p>Figure 32.6 The Watch window of Visual Studio showing the result of validation using the custom ValidationAttribute. The user has provided an invalid currencyTo value, \u00a3$%. Consequently, ModelState isn\u2019t valid and contains a single error with the message &quot;Not a valid currency code&quot;.<\/p>\n<p>The main feature missing from this custom attribute is client- side validation. You\u2019ve seen that the attribute works well on the server side, but if the user entered an invalid value, they wouldn\u2019t be informed until after the invalid value had been sent to the server. That\u2019s safe, and it\u2019s as much as you need to do for security and data-consistency purposes, but client- side validation can improve the user experience by providing immediate feedback.<\/p>\n<p>You can implement client-side validation in several ways, but it\u2019s heavily dependent on the JavaScript libraries you use to provide the functionality. Currently, ASP.NET Core Razor templates rely on jQuery for client-side validation. See the \u201cCustom client-side validation\u201d section of Microsoft\u2019s \u201cModel validation in ASP.NET Core MVC and Razor Pages\u201d documentation for an example of creating a jQuery Validation adapter for your attributes: <a href=\"http:\/\/mng.bz\/Wd6g\">http:\/\/mng.bz\/Wd6g<\/a>.<\/p>\n<p><b>TIP<\/b>  Instead of using the o\ufb03cial jQuery-based validation libraries, you could use the open source aspnet-client- validation library (<a href=\"https:\/\/github.com\/haacked\/aspnet-client\">https:\/\/github.com\/haacked\/aspnet-client<\/a>- validation) as I describe on my blog at <a href=\"http:\/\/mng.bz\/AoXe\">http:\/\/mng.bz\/AoXe<\/a>.<\/p>\n<p>Another improvement to your custom validation attribute would be to load the list of currencies from a DI service, such as an ICurrencyProvider. Unfortunately, you can\u2019t use constructor DI in your CurrencyCodeAttribute, as you can pass only constant values to the constructor of an Attribute in .NET. In chapter 22 we worked around this limitation for \ufb01lters by using [TypeFilter] or [ServiceFilter], but there\u2019s no such solution for ValidationAttribute.<\/p>\n<p>Instead, for validation attributes you must use the service locator pattern. As I discussed in chapter 9, this antipattern is best avoided where possible, but unfortunately it\u2019s necessary in this case. Instead of declaring an explicit dependency via a constructor, you must ask the DI container directly for an instance of the required service.<\/p>\n<p>Listing 32.9 shows how you could rewrite listing 32.8 to load the allowed currencies from an instance of ICurrencyProvider instead of hardcoding the allowed values in the attribute\u2019s constructor. The attribute calls the GetService<T>() method on ValidationContext to resolve an instance of ICurrencyProvider from the DI container. Note that ICurrencyProvider is a hypothetical service that would need to be registered in your application\u2019s ConfigureServices() method in Startup.cs.\u200c<\/p>\n<p>Listing 32.9 Using the service-locator pattern to access services<\/p>\n<pre><code>public class CurrencyCodeAttribute : ValidationAttribute\n{\nprotected override ValidationResult IsValid(\nobject value, ValidationContext context)\n{\nvar provider = context \u2776\n.GetRequiredService&lt;ICurrencyProvider&gt;(); \u2776\nvar allowedCodes = provider.GetCurrencies(); \u2777\nif(value is not string code \u2778\n|| !_allowedCodes.Contains(code)) \u2778\n{ \u2778\nreturn new ValidationResult(&quot;Not a valid currency code&quot;); \u2778\n} \u2778\nreturn ValidationResult.Success; \u2778\n}\n}<\/code><\/pre>\n<p>\u2776 Retrieves an instance of ICurrencyProvider directly from the DI container<br \/>\n\u2777 Fetches the currency codes using the provider<br \/>\n\u2778 Validates the property as before<\/p>\n<p><b>TIP<\/b>  The generic <code>GetRequiredService&lt;T&gt;<\/code> method is an extension method available in the Microsoft.Extensions.DependencyInjection namespace.\u200c<\/p>\n<p>The default DataAnnotations validation system can be convenient due to its declarative nature, but this has tradeo\ufb00s, as shown by the dependency injection problem above. Luckily, you can replace the validation system your application uses, as shown in the following section.<\/p>\n<h2>32.4 Replacing the validation framework with FluentValidation\u200c<\/h2>\n<p>In this section you\u2019ll learn how to replace the DataAnnotations-based validation framework that\u2019s used by default in Razor Pages and MVC Controllers. You\u2019ll see the arguments for why you might want to do this and learn how to use a third-party alternative: FluentValidation. This open- source project allows you to de\ufb01ne the validation requirements of your models separately from the models themselves. This separation can make some types of validation easier and ensures that each class in your application has a single responsibility.\u200c<\/p>\n<p>Validation is an important part of the model-binding process in ASP.NET Core. In chapter 7 you learned that minimal APIs don\u2019t have any validation built in, so you\u2019re free to choose whichever framework you like. I demonstrated using DataAnnotations, but you could easily choose a di\ufb00erent validation framework.<\/p>\n<p>In Razor Pages and MVC, however, the DataAnnotations validation framework is built into ASP.NET Core. You can apply DataAnnotations attributes to properties of your binding models to de\ufb01ne your requirements, and ASP.NET Core automatically validates them. In section 32.3 we even created a custom validation attribute.<\/p>\n<p>But ASP.NET Core is \ufb02exible. You can replace whole chunks of the Razor Pages and MVC frameworks if you like. The validation system is one such area that many people choose to replace.<\/p>\n<p>FluentValidation (<a href=\"https:\/\/\ufb02uentvalidation.net\">https:\/\/\ufb02uentvalidation.net<\/a>) is a popular alternative validation framework for ASP.NET Core. It is a mature library, with roots going back well before ASP.NET Core was conceived of. With FluentValidation you write your validation code separately from your binding model code.This gives several advantages:<\/p>\n<p>\u2022  You\u2019re not restricted to the limitations of Attributes, such as the dependency injection problem we had to work around in listing 32.9.<\/p>\n<p>\u2022  It\u2019s much easier to create validation rules that apply to multiple properties, such as to ensure that an EndDate property contains a later value than a StartDate property. Achieving this with DataAnnotations attributes is possible but di\ufb03cult.\u200c<\/p>\n<p>\u2022  It\u2019s generally easier to test FluentValidation validators than DataAnnotations attributes.<\/p>\n<p>\u2022  The validation is strongly typed compared with DataAnnotations attributes where it\u2019s possible to apply attributes in ways that don\u2019t make sense, such as applying an [EmailAddress] attribute to an int property.<\/p>\n<p>\u2022  Separating your validation logic from the model itself arguably better conforms to the single- responsibility principle (SRP).<\/p>\n<p>That \ufb01nal point is sometimes given as a reason not to use FluentValidation: FluentValidation separates a binding model from its validation rules. Some people are happy to accept the limitations of DataAnnotations to keep the model and validation rules together.<\/p>\n<p>Before I show how to add FluentValidation to your application, let\u2019s see what FluentValidation validators look like.<\/p>\n<h3>32.4.1 Comparing FluentValidation with DataAnnotations attributes\u200c<\/h3>\n<p>To better understand the di\ufb00erence between the DataAnnotations approach and FluentValidation, we\u2019ll convert the binding models from section 32.3 to use FluentValidation. The following listing shows what the binding model from listing 32.7 would look like when used with FluentValidation. It is structurally identical but has no validation attributes.<\/p>\n<p>Listing 32.10 Currency converter initial binding model for use with FluentValidation<\/p>\n<pre><code>public class CurrencyConverterModel\n{\npublic string CurrencyFrom { get; set; }\npublic string CurrencyTo { get; set; }\npublic decimal Quantity { get; set; }\n}<\/code><\/pre>\n<p>In FluentValidation you de\ufb01ne your validation rules in a separate class, with a class per model to be validated. Typically, these rules derive from the <code>AbstractValidator&lt;&gt;<\/code> base class, which provides a set of extension methods for de\ufb01ning your validation rules.\u200c<\/p>\n<p>The following listing shows a validator for the CurrencyConverterModel, which matches the validations added using attributes in listing 32.7. You create a set of validation rules for a property by calling RuleFor() and chaining method calls such as NotEmpty() from it. This style of method chaining is called a \ufb02uent interface, hence the name.<\/p>\n<p>Listing 32.11 A FluentValidation validator for the currency converter binding model<\/p>\n<pre><code>public class CurrencyConverterModelValidator \u2776\n: AbstractValidator&lt;CurrencyConverterModel&gt; \u2776\n{\nprivate readonly string[] _allowedValues \u2777\n= new []{ &quot;GBP&quot;, &quot;USD&quot;, &quot;CAD&quot;, &quot;EUR&quot; }; \u2777\npublic CurrencyConverterModelValidator() \u2778\n{\nRuleFor(x =&gt; x.CurrencyFrom) \u2779\n.NotEmpty() \u277a\n.Length(3) \u277a\n.Must(value =&gt; _allowedValues.Contains(value)) \u277b\n.WithMessage(&quot;Not a valid currency code&quot;); \u277b\nRuleFor(x =&gt; x.CurrencyTo)\n.NotEmpty()\n.Length(3)\n.Must(value =&gt; _allowedValues.Contains(value))\n.WithMessage(&quot;Not a valid currency code&quot;);\nRuleFor(x =&gt; x.Quantity)\n.NotNull()\n.InclusiveBetween(1, 1000); \u277c\n}\n}\n<\/code><\/pre>\n<p>\u2776 The validator inherits from AbstractValidator.<br \/>\n\u2777 Defines the static list of currency codes that are supported<br \/>\n\u2778 You define validation rules in the validator\u2019s constructor.<br \/>\n\u2779 RuleFor is used to add a new validation rule. The lambda syntax allows for strong typing.<br \/>\n\u277a There are equivalent rules for common DataAnnotations validation attributes.<br \/>\n\u277b You can easily add custom validation rules without having to create separate<br \/>\nclasses.<br \/>\n\u277c Thanks to strong typing, the rules available depend on the property being<br \/>\nvalidated.<\/p>\n<p>Your \ufb01rst impression of this code might be that it\u2019s quite verbose compared with listing 32.7, but remember that listing 32.7 used a custom validation attribute, [CurrencyCode]. The validation in listing 32.11 doesn\u2019t require anything else. The logic implemented by the [CurrencyCode] attribute is right there in the validator, making it easy to reason about. The Must() method can be used to perform arbitrarily complex validations without having the additional layers of indirection required by custom DataAnnotations attributes.\u200c<\/p>\n<p>On top of that, you\u2019ll notice that you can de\ufb01ne only validation rules that make sense for the property being validated. Previously, there was nothing to stop us from applying the [CurrencyCode] attribute to the Quantity property; that\u2019s not possible with FluentValidation.<\/p>\n<p>Of course, just because you can write the custom [CurrencyCode] logic in-line doesn\u2019t necessarily mean you have to. If a rule is used in multiple parts of your application, it may make sense to extract it into a helper class. The following listing shows how you could extract the currency code logic into an extension method that can be used in multiple validators.<\/p>\n<p>Listing 32.12 An extension method for currency validation<\/p>\n<pre><code>public static class ValidationExtensions\n{\n    public static IRuleBuilderOptions&lt;T, string&gt; \u2776\nMustBeCurrencyCode&lt;T&gt;( \u2776\nthis IRuleBuilder&lt;T, string&gt; ruleBuilder) \u2776\n{\nreturn ruleBuilder \u2777\n.Must(value =&gt; _allowedValues.Contains(value)) \u2777\n.WithMessage(&quot;Not a valid currency code&quot;); \u2777\n}\nprivate static readonly string[] _allowedValues = \u2778\nnew []{ &quot;GBP&quot;, &quot;USD&quot;, &quot;CAD&quot;, &quot;EUR&quot; }; \u2778\n}<\/code><\/pre>\n<p>\u2776 Creates an extension method that can be chained from RuleFor() for string<br \/>\nproperties<br \/>\n\u2777 Applies the same validation logic as before<br \/>\n\u2778 The currency code values to allow<\/p>\n<p>You can then update your CurrencyConverterModelValidator to use the new extension method, removing the duplication in your validator and ensuring consistency across country-code \ufb01elds:<\/p>\n<pre><code>RuleFor(x =&gt; x.CurrencyTo)\n.NotEmpty()\n.Length(3)\n.MustBeCurrencyCode();<\/code><\/pre>\n<p>Another advantage of the FluentValidation approach of using standalone validation classes is that they are created using DI, so you can inject services into them. As an example, consider the [CurrencyCode] validation attribute from listing 32.9, which used a service, ICurrencyProvider, from the DI container. This requires using service location to obtain an instance of ICurrencyProvider using an injected context object.\u200c<\/p>\n<p>With the FluentValidation library, you can inject the ICurrencyProvider directly into your validator, as shown in the following listing. This requires fewer gymnastics to get the desired functionality and makes your validator\u2019s dependencies explicit.<\/p>\n<p>Listing 32.13 Currency converter validator using dependency injection<\/p>\n<pre><code>public class CurrencyConverterModelValidator\n: AbstractValidator&lt;CurrencyConverterModel&gt;\n{\npublic CurrencyConverterModelValidator(ICurrencyProvider provider) \u2776\n{\nRuleFor(x =&gt; x.CurrencyFrom)\n.NotEmpty()\n.Length(3)\n.Must(value =&gt; provider \u2777\n.GetCurrencies() \u2777\n.Contains(value)) \u2777\n.WithMessage(&quot;Not a valid currency code&quot;);\nRuleFor(x =&gt; x.CurrencyTo)\n.NotEmpty()\n.Length(3)\n.MustBeCurrencyCode(provider.GetCurrencies()); \u2778\nRuleFor(x =&gt; x.Quantity)\n.NotNull()\n.InclusiveBetween(1, 1000);\n}\n}<\/code><\/pre>\n<p>\u2776 Injects the service using standard constructor dependency injection<br \/>\n\u2777 Uses the injected service in a Must() rule<br \/>\n\u2778 Uses the injected service with an extension method<\/p>\n<p>The \ufb01nal feature I\u2019ll show demonstrates how much easier it is to write validators that span multiple properties with FluentValidation. For example, imagine we want to validate that the value of CurrencyTo is di\ufb00erent from CurrencyFrom. Using FluentValidation, you can implement this with an overload of Must(), which provides both the model and the property being validated, as shown in the following listing.<\/p>\n<p>Listing 32.14 Using Must() to validate that two properties are di\ufb00erent<\/p>\n<pre><code>RuleFor(x =&gt; x.CurrencyTo) \u2776\n.NotEmpty()\n.Length(3)\n.MustBeCurrencyCode()\n.Must((InputModel model, string currencyTo) \u2777\n=&gt; currencyTo != model.CurrencyFrom) \u2778\n.WithMessage(&quot;Cannot convert currency to itself&quot;); \u2779\n<\/code><\/pre>\n<p>\u2776 The error message will be associated with the CurrencyTo property.<br \/>\n\u2777 The Must function passes the top-level model being validated and the current property.<br \/>\n\u2778 Performs the validation. The currencies must be different.<br \/>\n\u2779 Uses the provided message as the error message<\/p>\n<p>Creating a validator like this is certainly possible with DataAnnotations attributes, but it requires far more ceremony than the FluentValidation equivalent and is generally harder to test. FluentValidation has many more features for making it easier to write and test your validators, too:<\/p>\n<p>\u2022  Complex property validations\u2014Validators can be applied to complex types as well as to the primitive types like string and int shown here in this section.<\/p>\n<p>\u2022  Custom property validators\u2014In addition to simple extension methods, you can create your own property validators for complex validation scenarios.<\/p>\n<p>\u2022  Collection rules\u2014When types contain collections, such as <code>List&lt;T&gt;<\/code>, you can apply validation to each item in the list, as well as to the overall collection.<\/p>\n<p>\u2022  RuleSets\u2014You can create multiple collections of rules that can be applied to an object in di\ufb00erent circumstances. These can be especially useful if you\u2019re using FluentValidation in additional areas of your application.<\/p>\n<p>\u2022  Client-side validation\u2014FluentValidation is a server- side framework, but it emits the same attributes as DataAnnotations attributes to enable client- side validation using jQuery.<\/p>\n<p>There are many more features, so be sure to browse the documentation at <a href=\"https:\/\/docs.\ufb02uentvalidation.net\">https:\/\/docs.\ufb02uentvalidation.net<\/a> for details. In the next section you\u2019ll see how to add FluentValidation to your ASP.NET Core application.\u200c<\/p>\n<h3>32.4.2 Adding FluentValidation to your application\u200c<\/h3>\n<p>Replacing the whole validation system of ASP.NET Core sounds like a big step, but the FluentValidation library makes it easy to add to your application. Simply follow these steps:<\/p>\n<ol>\n<li>\n<p>Install the FluentValidation.AspNetCore NuGet package using Visual Studio\u2019s NuGet package manager via the command-line interface (CLI) by running dotnet add package FluentValidation.AspNetCore or by adding a <code>&lt;PackageReference&gt;<\/code> to your .csproj \ufb01le:<\/p>\n<pre><code>&lt;PackageReference Include=&quot;FluentValidation.AspNetCore&quot; Version=&quot;11.2.2&quot; \/&gt;<\/code><\/pre>\n<\/li>\n<li>\n<p>Con\ufb01gure the FluentValidation library for MVC and Razor Pages in Program.cs by calling builder.Services.AddFluentValidationA utoValidation(). You can further con\ufb01gure the library as shown in listing 32.15.<\/p>\n<\/li>\n<li>\n<p>Register your validators (such as the CurrencyConverterModelValidator from listing 32.13) with the DI container. These can be registered manually, using any scope you choose:<\/p>\n<pre><code>WebApplicationBuilder builder = WebApplication.CreateBuilder(args); builder.Services.AddRazorPages(); builder.Services.AddFluentValidationAutoValidation(); builder.services.AddScoped&lt;IValidator&lt;CurrencyConverterModelValidator&gt;, CurrencyConverterModelValidator&gt;();<\/code><\/pre>\n<\/li>\n<\/ol>\n<p>Alternatively, you can allow FluentValidation to automatically register all your validators using the options shown in listing 32.15.<\/p>\n<p>For such a mature library, FluentValidation has relatively few con\ufb01guration options to decipher. The following listing shows some of the options available; in particular, it shows how to automatically register all the custom validators in your application and disable DataAnnotations validation.<\/p>\n<p>Listing 32.15 Con\ufb01guring FluentValidation in an ASP.NET Core application<\/p>\n<pre><code>var builder = WebApplication.CreateBuilder(args);\nbuilder.Services.AddRazorPages();\nbuilder.Services.AddValidatorsFromAssemblyContaining&lt;Program&gt;(); \u2776\nbuilder.Services.AddFluentValidationAutoValidation( \u2777\nx =&gt; x.DisableDataAnnotationsValidation = true) \u2777\n.AddFluentValidationClientsideAdapters(); \u2778\nValidatorOptions.Global.LanguageManager.Enabled = false; \u2779<\/code><\/pre>\n<p>\u2776 Instead of manually registering validators, FluentValidation can autoregister them for you.<br \/>\n\u2777 Setting to true disables DataAnnotations validation completely for model binding.<br \/>\n\u2778 Enables integration with client-side validation via data-* attributes<br \/>\n\u2779 FluentValidation has full localization support, but you can disable it if you don\u2019t need it.<\/p>\n<p>It\u2019s important to understand that if you don\u2019t set DisableDataAnnotationsValidation to true, ASP.NET Core will run validation with both DataAnnotations and FluentValidation. That may be useful if you\u2019re in the process of migrating from one system to the other, but otherwise, I recommend disabling it. Having your validation split between both places seems like the worst of both worlds!<\/p>\n<p>One \ufb01nal thing to consider is where to put your validators in your solution. There are no technical requirements for this; if you\u2019ve registered your validator with the DI container, it will be used correctly, so the choice is up to you. I prefer to place validators close to the models they\u2019re validating.<\/p>\n<p>For Razor Pages binding-model validators, I create the validator as a nested class of the PageModel, in the same place as I create the InputModel, as described in chapter 16. That gives a class hierarchy in the Razor Page similar to the following:<\/p>\n<pre><code>public class IndexPage : PageModel\n{\npublic class InputModel { }\npublic class InputModelValidator: AbstractValidator&lt;InputModel&gt; { }\n}<\/code><\/pre>\n<p>That\u2019s my preference. Of course, you\u2019re free to adopt another approach if you prefer.<\/p>\n<p>That brings us to the end of this chapter on custom Razor Pages components. When you combine it with the components in the previous chapter, you\u2019ve got a great base for extending your ASP.NET Core applications to meet your needs. It\u2019s a testament to ASP.NET Core\u2019s design that you can swap out whole sections like the Validation framework entirely. If you don\u2019t like how some part of the framework works, see whether someone has written an alternative!\u200c<\/p>\n<h2>Summary<\/h2>\n<p>With Tag Helpers, you can bind your data model to HTML elements, making it easier to generate dynamic HTML. Tag Helpers can customize the elements they\u2019re attached to, add attributes, and customize how they\u2019re rendered to HTML. This can greatly reduce the amount of markup you need to write.<\/p>\n<p>The name of a Tag Helper class dictates the name of the element in the Razor templates, so the SystemInfoTagHelper corresponds to the<\/p>\n<p><code>&lt;system-info&gt;<\/code> element. You can choose a di\ufb00erent element name by adding the [HtmlTargetElement] attribute to your Tag Helper.<\/p>\n<p>You can set properties on your Tag Helper object from Razor syntax by decorating the property with an [HtmlAttributeName(&quot;name&quot;)] attribute and providing a name. You can set these properties from Razor using HTML attributes, as in <code>&lt;system- info name=&quot;value&quot;&gt;<\/code>.<\/p>\n<p>The TagHelperOutput parameter passed to the Process or ProcessAsync methods control the HTML that\u2019s rendered to the page. You can set the element type with the TagName property and set the inner content using Content.SetContent() or Content.SetHtmlContent().<\/p>\n<p>You can prevent inner Tag Helper content from being processed by calling SupressOutput(), and you can remove the element by setting TagName=null. This is useful if you want to conditionally render elements to the response.<\/p>\n<p>You can retrieve the contents of a Tag Helper by calling GetChildContentAsync() on the TagHelperOutput parameter. You can then render this content to a string by calling GetContent(). This will render any Razor expressions and Tag Helpers to HTML, allowing you to manipulate the contents.<\/p>\n<p>View components are like partial views, but they allow you to use complex business and rendering logic. You can use them for sections of a page, such as the shopping cart, a dynamic navigation menu, or suggested articles.<\/p>\n<p>Create a view component by deriving from the ViewComponent base class and implementing InvokeAsync(). You can pass parameters to this function from the Razor view template using HTML attributes, in a similar way to Tag Helpers.<\/p>\n<p>View components can use DI, access the HttpContext, and render partial views. The partial views should be stored in the <code>Pages\/Shared\/Components\/&lt;Name&gt;\/<\/code> folder, where Name is the name of the view component. If not speci\ufb01ed, view components will look for a default view named Default.cshtml.<\/p>\n<p>You can create a custom DataAnnotations attribute by deriving from ValidationAttribute and overriding the IsValid method. You can use this to decorate your binding model properties and perform arbitrary validation.<\/p>\n<p>You can\u2019t use constructor DI with custom validation attributes. If the validation attribute needs access to services from the DI container, you must use the Service Locator pattern to load them from the validation context, using the <code>GetService&lt;T&gt;<\/code> method.<\/p>\n<p>FluentValidation is an alternative validation system that can replace the default DataAnnotations validation system. It is not based on attributes, which makes it easier to write custom validations for your validation rules and makes those rules easier to test.<\/p>\n<p>To create a validator for a model, create a class derived from AbstractValidator&lt;&gt; and call RuleFor&lt;&gt;() in the constructor to add validation rules. You can chain multiple requirements on <code>RuleFor&lt;&gt;() <\/code>in the same way that you could add multiple DataAnnotations attributes to a model.<\/p>\n<p>If you need to create a custom validation rule, you can use the Must() method to specify a predicate. If you wish to reuse the validation rule across multiple models, encapsulate the rule as an extension method to reduce duplication.<\/p>\n<p>To add FluentValidation to your application, install the FluentValidation .AspNetCore NuGet package, call AddFluentValidationAutoValidation() in Program.cs, and register your validators with the DI container. This will add FluentValidation validations in addition to the built-in DataAnnotations system.<\/p>\n<p>To remove the DataAnnotations validation system and use FluentValidation only, set the DisableDataAnnotationsValidation option to true in your call to AddFluentValidationAutoValidation().<\/p>\n<p>Favor this approach where possible to avoid running validation methods from two di\ufb00erent systems.<\/p>\n<p>You can allow FluentValidation to automatically discover and register all the validators in your application by calling <code>AddValidatorsFromAssemblyContaining&lt;T&gt;()<\/code>, where T is a type in the assembly to scan. This means you don\u2019t have to register each validator in your application with the DI container individually.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>32 Building custom MVC and Razor Pages components\u200c This chapter covers \u2022 Creating custom Razor Tag Helpers \u2022 Using view components to create complex Razor views \u2022 Creating a custom DataAnnotations validation attribute \u2022 Replacing the DataAnnotations validation framework with an alternative In the previous chapter you learned how to customize and extend some of [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[3],"tags":[],"class_list":["post-635","post","type-post","status-publish","format-standard","hentry","category-csharp"],"_links":{"self":[{"href":"https:\/\/diji.net\/index.php?rest_route=\/wp\/v2\/posts\/635","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/diji.net\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/diji.net\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/diji.net\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/diji.net\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=635"}],"version-history":[{"count":0,"href":"https:\/\/diji.net\/index.php?rest_route=\/wp\/v2\/posts\/635\/revisions"}],"wp:attachment":[{"href":"https:\/\/diji.net\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=635"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/diji.net\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=635"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/diji.net\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=635"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}