Building Reusable UI Components in ASP.NET Core

In recent years, the modern web platform has propelled itself forward at the speed of light. Open source web development communities have popped up on GitHub like canvas tents at peak-season campsites; and state-of-the-art technologies have emerged like fireflies in the night sky.

Where was our friend ASP.NET, you ask? Startled by the clamor, it settled in unfashionably late at the desolate campground next door. Accompanied by a bag of Jet-Puffed marshmallows, roasting skewers, and a crackling campfire, it was comfortable and contemplated its next move through a pair of binoculars.

Fast-forward to the launch of ASP.NET Core, a caffeine-fueled reincarnation of ASP.NET proper. Microsoft had undoubtedly learned some invaluable lessons from competing technologies and was listening intently to its customers. ASP.NET Core MVC resuscitated a stale and stagnated collection of tools used by web developers for creating server-rendered, reusable UI components on the Microsoft technology stack. We can all agree that this was a long-overdue product refresh – if not, let's agree to disagree.

This article aims to provide clarity to the befuddled and skeptical. If you had previously deemed ASP.NET incapable of meeting your UI component needs, ASP.NET welcomes you back with open arms. This article also assumes a basic understanding of Partial Views, Child Actions, and HTML Helpers.

The State of ASP.NET Core MVC

Version 1.0 of ASP.NET Core introduced Tag Helpers and View Components to the MVC application model. On the surface, Tag Helpers seemed to render HTML Helpers obsolete; and, View Components were peculiar constructs that felt like Partial Views with Child Actions.

Fast-forward to ASP.NET Core 1.1, where we witnessed the melding of the two concepts together by enabling View Component invocation as a Tag Helper. Indisputably, if both concepts are being used together, there must be something worth learning in this space.

The leap from ASP.NET proper to ASP.NET Core demands careful thought in planning an MVC application's architecture. Consider this a positive characteristic of the framework. There are more choices partly because best practices have evolved and partly because Microsoft has sought out the needs of previously-neglected Dark Matter Developers. The smorgasbord of options is intentional — this reinvigorated MVC application model caters to a much broader audience.

Tag Helpers

Tag Helpers aspire to be the server-side rendition of what the popular SPA frameworks offer in terms of UI components for the client. Think back to creating custom AngularJS directives, or the simpler-to-configure Angular components. Directives could take multiple forms. Here are a few examples:

<my-dir></my-dir>
<span my-dir="exp"></span>

That first line of markup appears to be our own custom HTML tag. Likewise, the next line of markup appears to be a custom HTML attribute affixed to a span tag. This modest markup is a facade backed by JavaScript, which handles any necessary processing — for example, requesting an external HTML template file from the server and hydrating the template's placeholders with the results of a current weather API request.

Tag Helpers offer the same feature set as described above. Aside from the syntactical disparities, the distinguishing factors are the use of C# instead of JavaScript and the shift from client-side to server-side processing. The point is Microsoft isn't reinventing the wheel with Tag Helpers. These concepts were already popularized and proven by other communities.

Tag Helpers are consumed by Razor views (.cshtml files) and are relatively simple to create. Before deciding to create one, however, remember that Microsoft ships around 18 of them in the Microsoft.AspNetCore.Mvc.TagHelpers NuGet package. Also, be conscious of the thriving community forming around Tag Helper authoring. You may save both time and money by shopping around and reaping the benefits of a third party's hard work. Progress is one company with an expanding presence in the Tag Helper marketplace.

Telerik UI for ASP.NET Core

Progress has developed the Telerik UI for ASP.NET Core suite, which disguises the most popular Kendo UI widgets as Tag Helpers. At the time of writing, there are a dozen Tag Helpers in existence. See the complete list here.

With some basic markup inside a Razor view, a date picker widget can be added to the page:

<kendo-datepicker name="startDate"
        start="CalendarView.Year"
        depth="CalendarView.Year"
        format="MMMM yyyy"
        value='DateTime.Parse("November 2011")'
        style="width: 100%;" />

The ASP.NET Core application is made aware of the Telerik Tag Helpers via the AddKendo extension method call in the Startup.cs file:

public void ConfigureServices(IServiceCollection services)
{
    // Add framework services.
    services
        .AddMvc()
        .AddJsonOptions(options => options.SerializerSettings.ContractResolver = new DefaultContractResolver());

    // Add Kendo UI services to the services container
    services.AddKendo();
}

The view becomes aware of the kendo-datepicker element via the final @addTagHelper directive in the _ViewImports.cshtml file:

@using TelerikTagHelpers
@using Kendo.Mvc.UI
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@addTagHelper *, Kendo.Mvc

The Telerik Tag Helpers handle a plethora of accessibility concerns for developers, including the addition of Accessible Rich Internet Applications (ARIA) attributes. The harsh reality is that accessibility is, at best, an afterthought in enterprise development. ARIA is rarely used because many decision makers categorize it as an additional expense with little-to-no return on investment. Consequently, the application's reach to assistive devices, such as screen readers, is inhibited by the pages' lack of machine-readable semantics. This feature alone makes the Tag Helpers worth adopting.

Aside from the attractive accessibility benefits, take into consideration the other time savings. When your development team needs a date picker widget, there's no longer a debate over how to optimally structure or style the widget for various form factors. These decisions have been made intelligently for you:

This is far from an exhaustive inventory of the benefits, but hopefully now you're recognizing the potential when using third-party Tag Helpers.

Building Tag Helpers

For those situations where a third-party Tag Helper either doesn't exist or doesn't quite fulfill your needs, it's time to roll up your sleeves and get your hands dirty. Building Tag Helpers can be a simple and rewarding endeavor. But what if you're stuck in a rut and aren't sure where to begin?

The official ASP.NET Core documentation offers moral support and dives into great detail with Authoring Tag Helpers in ASP.NET Core, a walkthrough with samples. Instead of rehashing what's already clearly documented there, read through it from top to bottom. Employ this knowledge to better understand how Microsoft has built their own Tag Helpers. You can view the source code in the ASP.NET Core MVC GitHub repository. This is a fantastic starting point if you're building a Tag Helper similar to one offered by Microsoft.

As an additional resource, I previously blogged about some Tag Helper fringe cases in which the terrain gets rough: Building Complex UI Components with ASP.NET Core MVC Tag Helpers. These rough edges are likely to disappear with future product iterations. Associated with the blog post is a sample application demonstrating how to represent a deck of playing cards with Tag Helpers.

Consider the following elegant markup inside of a Razor view:

<hand player="John">
    <card suit="@CardSuit.Heart" rank="@CardRank.Ace"></card>
    <card suit="@CardSuit.Club" rank="@CardRank.Eight"></card>
    <card suit="@CardSuit.Diamond" rank="@CardRank.Jack"></card>
</hand>

<hand player="Jane">
    <card suit="@CardSuit.Spade" rank="@CardRank.Four"></card>
    <card suit="@CardSuit.Heart" rank="@CardRank.Queen"></card>
    <card suit="@CardSuit.Heart" rank="@CardRank.Seven"></card>
</hand>

For the sake of brevity, the supporting C# classes are omitted; however, recognize that those classes are an important implementation detail. Tag Helpers are not automatically discovered by the application. The magic is explained by the last two @addTagHelper directives in the _ViewImports.cshtml file, which expose the card and hand elements to the view:

@using TagHelpersDemo
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@addTagHelper TagHelpersDemo.TagHelpers.CardTagHelper, TagHelpersDemo
@addTagHelper TagHelpersDemo.TagHelpers.HandTagHelper, TagHelpersDemo

The nested Tag Helper markup yields two hands of cards:

View Components

View Components are reserved for more complex scenarios than Tag Helpers. These are overarching, data-driven widgets typically included in the master layout file. A great example is a location-specific current weather widget which connects to a third-party weather API to retrieve its data. This View Component might also include specialized business logic for manipulating the API response before data-binding to the view. The Weather Channel uses this style of widget, except it's written in Angular.

Think of View Components as a disciplined progression of Partial Views — they reach into the dark corners which Partial Views never addressed. Additionally, View Components are a wholesale replacement for Child Actions in ASP.NET Core MVC.

Since you're reading this article, my assumption is you're capable of creating a C# class and a Razor view. These are the essential building blocks of a View Component. See Microsoft's detailed instructions at Creating a view component.

Now consider a sample application in which the following View Component invocation exists:

@await Component.InvokeAsync(nameof(CurrentWeather), new { city = "Chicago", stateAbbrev = "IL" })

The preceding code initiates a request to a current weather API and displays the results for the provided city/state combination:

The power in this example lies in the fact that application developers needing this type of widget need not understand the underlying implementation details. Instead, the developers can focus on solving the business problem by simply passing the desired city and state via this terse C# snippet.

View Components as Tag Helpers

Recall the current weather View Component in the previous section. The Razor syntax responsible for rendering it may have disgusted some. Here is the equivalent View Component invocation using the Tag Helper invocation syntax:

<vc:current-weather city="Chicago" state-abbrev="IL" />

The significance of the vc prefix is that it's reserved for View Component invocation as a Tag Helper. Moreover, the ViewComponentsDemo assembly in which the View Component exists must be registered as a Tag Helper. That's accomplished via the last line in the following _ViewImports.cshtml file:

@using ViewComponentsDemo
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@addTagHelper *, ViewComponentsDemo

Recommended Use Cases

Using the right tool for the job is important, which means understanding the differences between the UI options is paramount. Furthermore, just because ASP.NET Core MVC introduced new tools doesn't mean they're valuable or applicable to every application. The seasoned HTML Helpers and Partial Views, born in ASP.NET proper, are here to stay as well. There's no need to panic and pivot.

Tag Helpers vs. HTML Helpers

Prefer Tag Helpers when Razor syntax is unappealing, but HTML is appealing. This may not completely eliminate the Razor syntax. For example, passing an enum value to a Tag Helper property necessitates it. This concept was demonstrated by the <card /> Tag Helper's rank and suit properties.

Some developers, including myself, have long complained about weaving in-and-out of HTML and C# code inside Razor views. The context switching can decrease productivity, which is why Tag Helpers promote the use of a unified syntax all throughout the view.

There's another camp of developers who love the power of Razor. If peppering your views with the following statements doesn't bother you, stick with it:

@Html.Label("FirstName", "First Name:", new { @class = "caption" })

IntelliSense is yet another consideration. In the preceding HTML Helper code snippet, the IDE wouldn't have been able to assist when working with the third parameter, the anonymous object. Strongly-typed properties represent attributes within a Tag Helper element. For this reason, IntelliSense is a strong point of Tag Helpers.

Customers porting "legacy" MVC applications from ASP.NET proper to ASP.NET Core should consider whether the time investment to migrate from HTML Helpers to Tag Helpers is justified. Rewriting custom HTML Helpers could be a painful endeavor. Although, if the HTML Helpers exist without unit tests and if high test coverage is a goal, you may have a sound case for migrating. Tag Helpers were designed with testability in mind, thanks in part to the separation of concerns.

View Components vs. Partial Views

View Components are ideal when the markup abstraction power of Partial Views seems appropriate but there's a dynamic aspect of the widget requiring complex rendering logic. API data retrieval and the application of specialized business rules on that response data are responsibilities better left to a View Component.

Partial Views would require the use of C# in the view itself. When code scalability and testability are things you're willing to sacrifice, this approach could be acceptable. Through the enforcement of separation of concerns, View Components don't sacrifice these things. A class inheriting from ViewComponent houses the C# code associated with the view. Again, thanks to separation of concerns, writing meaningful unit tests is a much easier feat.

Prefer Partial Views when porting your existing ASP.NET proper application to ASP.NET Core. I'll let you be the judge, but it's unlikely to be worth the investment in time to refactor those Partial Views into View Components. An exception to that statement may be the situation where testability is a goal. Remember that Child Actions are absent in ASP.NET Core, so a View Component makes sense as a Partial View / Child Action duo replacement.

Conclusion

In this article, we briefly touched on ASP.NET proper's hardened tools for building reusable UI components. From there, two new ASP.NET Core tools for this purpose were presented: Tag Helpers and View Components. Finally, recommendations were made for when each of these tools is best to use.

As with virtually anything in the real world, there are always exceptions to the rules. As such, use this guidance with a grain of salt and deviate where it makes sense. Much of this guidance has been gathered from my own personal experiences.

Comments