blog post image
Andrew Lock avatar

Andrew Lock

~7 min read

An introduction to ViewComponents - a login status view component

View components are one of the potentially less well known features of ASP.NET Core Razor views. Unlike tag-helpers which have the pretty much direct equivalent of Html Helpers in the previous ASP.NET, view components are a bit different.

In spirit, they fit somewhere between a partial view and a full controller - approximately like a ChildAction. However whereas actions and controllers have full model binding semantics and the filter pipeline etc, view components are invoked directly with explicit data. They are more powerful than a partial view however, as they can contain business logic, and separate the UI generation from the underlying behaviour.

View components seem to fit best in situations where you would want to use a partial, but that the rendering logic is complicated and may need to be tested.

In this post, I'll use the example of a Login widget that displays your email address when you are logged in:

Logged in result

and a register / login link when you are logged out:

Logged out result

This is a trivial example - the behaviour above is achieved without the use of view components in the templates. This post is just meant to introduce you to the concept of view components, so you can see when to use them in your own applications.

Creating a view component

View components can be defined in a multitude of ways. You can give your component a name ending in ViewComponent, you can decorate it with the [ViewComponent] attribute, or you can derive from the ViewComponent base class. The latter of these is probably the most obvious, and provides a number of helper properties you can use, but the choice is yours.

To implement a view component you must expose a public method called InvokeAsync which is called when the component is invoked:

public Task<IViewComponentResult> InvokeAsync();

As is typical for ASP.NET Core, this method is found at runtime using reflection, so if you forget to add it, you won't get compile time errors, but you will get an exception at runtime:

Missing Invoke Async method on view component

Other than this restriction, you are pretty much free to design your view components as you like. They support dependency injection, so you are able to inject dependencies into the constructor and use them in your InvokeAsync method. For example, you could inject a DbContext and query the database for the data to display in your component.

The LoginStatusViewComponent

Now you have a basic understanding of view components, we can take a look at the LoginStatusViewComponent. I created this component in a project created using the default MVC web template in VisualStudio with authentication.

This simple view component only has a small bit of logic, but it demonstrates the features of view components nicely.

public class LoginStatusViewComponent : ViewComponent
{
    private readonly SignInManager<ApplicationUser> _signInManager;
    private readonly UserManager<ApplicationUser> _userManager;

    public LoginStatusViewComponent(SignInManager<ApplicationUser> signInManager, UserManager<ApplicationUser> userManager)
    {
        _signInManager = signInManager;
        _userManager = userManager;
    }

    public async Task<IViewComponentResult> InvokeAsync()
    {
        if (_signInManager.IsSignedIn(HttpContext.User))
        {
            var user = await _userManager.GetUserAsync(HttpContext.User);
            return View("LoggedIn", user);
        }
        else
        {
            return View();
        }
    }
}

You can see I have chosen to derive from the base ViewComponent class, as that provides me access to a number of helper methods.

We are injecting two services into the constructor of our component. These will be fulfilled automatically by the dependency injection container when our component is invoked.

Our InvokeAsync method is pretty self explanatory. We are checking if the current user is signed in using the SignInManager<>, and if they are we fetch the associated ApplicationUser from the UserManager<>. Finally we call the helper View method, passing in a template to render and the model user. If the user is not signed in, we call the helper View without a template argument.

The calls at the end of the InvokeAsync method are reminiscent of action methods. They are doing a very similar thing, in that they are creating a result which will execute a view template, passing in the provided model.

In our example, we are rendering a different template depending on whether the user is logged in or not. That means we could test this ViewComponent in isolation, testing that the correct template is displayed depending on our business requirements, without having to inspect the HTML output, which would be our only choice if this logic was embedded in a partial view instead.

Rendering View templates

When you use return View() in your view component, you are returning a ViewViewComponentResult (yes, that name is correct!) which is analogous to the ViewResult you typically return from MVC action methods.

This object contains an optional template name and view model, which is used to invoke a Razor view template. The location of the view to execute is given by convention, very similar to MVC actions. In the case of our LoginStatusViewComponent, the Razor engine will search for views in two folders:

  1. Views\Components\LoginStatus; and
  2. Views\Components\Shared

If you don't specify the name of the template to find, then the engine will assume the file is called default.cshtml. In the example I provided, when the user is signed in we explicitly provide a template name, so the engine will look for the template at

  1. Views\Components\LoginStatus\LoggedIn.cshtml; and
  2. Views\Components\Shared\LoggedIn.cshtml

The view templates themselves are just normal razor, so they can contain all the usual features, tag helpers, strongly typed models etc. The LoggedIn.cshtml file for our LoginviewComponent is shown below:

@model ApplicationUser
<form asp-area="" asp-controller="Account" asp-action="LogOff" method="post" id="logoutForm" class="navbar-right">
    <ul class="nav navbar-nav navbar-right">
        <li>
            <a asp-area="" asp-controller="Manage" asp-action="Index" title="Manage">Hello @Model.Email!</a>
        </li>
        <li>
            <button type="submit" class="btn btn-link navbar-btn navbar-link">Log off</button>
        </li>
    </ul>
</form>

There is nothing special here - we are using the form and action link tag helpers to create links and we are writing values from our strongly typed model to the response. All bread and butter for razor templates!

When the user is not logged in, I didn't specify a template name, so the default name of default.cshtml is used:

View folders for a view component

This view is even simpler as we didn't pass a model to the view, it just contains a couple of links:

<ul class="nav navbar-nav navbar-right">
    <li><a asp-area="" asp-controller="Account" asp-action="Register">Register</a></li>
    <li><a asp-area="" asp-controller="Account" asp-action="Login">Log in</a></li>
</ul>

Invoking a view component

With your component configured, all that remains is to invoke it from your view. View components can be invoked from a different view by calling, in this case, @await Component.InvokeAsync("LoginStatus"), where "LoginStatus" is the name of the view component. We can call it in the header of our _Layout.cshtml:

<div class="navbar-collapse collapse">
    <ul class="nav navbar-nav">
        <li><a asp-area="" asp-controller="Home" asp-action="Index">Home</a></li>
        <li><a asp-area="" asp-controller="Home" asp-action="About">About</a></li>
        <li><a asp-area="" asp-controller="Home" asp-action="Contact">Contact</a></li>
    </ul>
    @await Component.InvokeAsync("LoginStatus")
</div>

Invoking directly from a controller

It is also possible to return a view component directly from a controller; this is the closest you can get to directly exposing a view component at an endpoint:

public IActionResult IndexVC()
{
    return ViewComponent("LoginStatus");
}

Calling View Components like TagHelpers in ASP.NET Core 1.1.0

View components work well, but one of the things that seemed like a bit of a step back was the need to explicitly use the @ symbol to render them. One of the nice things brought to Razor with ASP.NET Core was tag-helpers. These do pretty much the same job as the HTML helpers from the previous ASP.NET MVC Razor views, but in a more editor-friendly way.

For example, consider the following block, which would render a label, text box and validation summary for a property on your model called Email

<div class="form-group">
    @Html.LabelFor(x=>x.Email, new { @class= "col-md-2 control-label"})
    <div class="col-md-10">
        @Html.TextBoxFor(x=>x.Email, new { @class= "form-control"})
        @Html.ValidationMessageFor(x=>x.Email, null, new { @class= "text-danger" })
    </div>
</div>

Compare that to the new tag helpers, which allow you to declare your model bindings as asp- attributes:

<div class="form-group">
    <label asp-for="Email" class="col-md-2 control-label"></label>
    <div class="col-md-10">
        <input asp-for="Email" class="form-control" />
        <span asp-validation-for="Email" class="text-danger"></span>
    </div>
</div>

Syntax highlighting is easier for basic editors and you don't need to use ugly @ symbols to escape the class properties - everything is just that little bit nicer. In ASP.NET Core 1.1.0, you can get similar benefits for calling your tag helpers, by using a vc: prefix.

To repeat my LoginStatus example in ASP.NET Core 1.1.0, you first need to register your view components as tag helpers in _ViewImports.cshtml (where WebApplication1 is the namespace of your view components) :

@addTagHelper *, WebApplication1

and you can then invoke your view component using the tag helper syntax:

<div class="navbar-collapse collapse">
    <ul class="nav navbar-nav">
        <li><a asp-area="" asp-controller="Home" asp-action="Index">Home</a></li>
        <li><a asp-area="" asp-controller="Home" asp-action="About">About</a></li>
        <li><a asp-area="" asp-controller="Home" asp-action="Contact">Contact</a></li>
    </ul>
    <vc:login-status></vc:login-status>
</div>

Note the name of the tag helper here, vc:login-status. The vc helper, indicates that you are invoking a view component, and the name of the helper is our view component's name (LoginStatus) converted to lower-kebab case (thanks to the ASP.NET monsters for figuring out the correct name)!

With these two pieces in place, your tag-helper is functionally equivalent to the previous invocation, but is a bit nicer to read:)

Summary

This post provided an introduction to building your first view component, including how to invoke it. You can find sample code on GitHub. In the next post, I'll show how you can pass parameters to your component when you invoke it.

Andrew Lock | .Net Escapades
Want an email when
there's new posts?