Don't replace your View Components with Razor Components
Tags: aspnetcore, blazor, netcore30, razor
Published: 2020-04-05 14:25:59

---
title: Don't replace your View Components with Razor Components
published: true
date: 2020-03-17 10:00:00 UTC
tags: ASP.NET Core,Blazor,.NET Core 3.0,Razor
canonical_url: https://andrewlock.net/dont-replace-your-view-components-with-razor-components/
---

 ![Don't replace your View Components with Razor Components](https://andrewlock.net/content/images/2020/vc_vs_rc_banner.png)

In this post I take a brief look at Razor Components, and whether you should consider using them instead of View Components. I'm sure you can guess my conclusion from the title, but I admit that's pretty click-baity, and the conclusions are a bit more subtle.

I start by taking a brief look at View Components and what they can be used for. I then compare them to Razor Components highlighting the similarities and differences, and describing their relation to Blazor. Finally, I walk through converting a Razor partial view into a Razor Component rendered using the `static` mode, and decide whether or not it's worth doing!

> **tl;dr;** Razor Components are cool, but I can't see a reason to use the `static` rendering mode. It doesn't give you the client interactivity of Blazor and there's a bit of an impedance mis-match with Razor Pages, so it doesn't seem worth the hassle over using Razor partials or View Components in a Razor Pages app.

## What are View Components?

I wrote [an introduction to View Components](https://andrewlock.net/an-introduction-to-viewcomponents-a-login-status-view-component/) a few years ago, and nothing much has changed with them since then. The following section is taken from that post:

[View Components](https://docs.microsoft.com/en-us/aspnet/core/mvc/views/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 from "classic" 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 MVC filter pipeline etc, View Components are invoked directly with explicit parameters. 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 ordinarily want to use a Razor partial, but where the rendering logic is complicated and may need to be tested.

In [my introductory post](https://andrewlock.net/an-introduction-to-viewcomponents-a-login-status-view-component/) I used the example of the classic "login status" component used in a typical ASP.NET Core app, where you want to render something different depending on whether the current user is logged in.

![A typical web app showing a login widget](https://andrewlock.net/content/images/2017/01/Logged-in.png)

While this is definitely at the low-end complexity-wise for View Components, it gives the general idea - a small section of UI that has some somewhat-complex rending requirements. They can also be useful where it is rendering data unrelated to the main body of the page. For example, take a typical eStore:

![A product page from Amazon.co.uk](https://andrewlock.net/content/images/2020/vc_vs_rc_00.png)

With a traditional MVC/Razor Page app, the ViewModel used to render the product page would typically contain all the data required to render the bulk of the page body. But what about the "frequently bought together" section, or the wish list? If every View Model has to include that data too, your View Models and controllers would become cluttered. Theses are good candidates for View Components.

## Razor Components

View Components seem to have their place then. But if you look at [the official documentation for View Components](https://docs.microsoft.com/en-us/aspnet/core/mvc/views/view-components), you'll find this interesting paragraph:

> When considering if View Components meet an app's specifications, consider using Razor Components instead. Razor Components also combine markup with C# code to produce reusable UI units. Razor Components are designed for developer productivity when providing client-side UI logic and composition.

When I read that initially, I read it as View Components essentially being deprecated in favour of Razor Components. After re-reading it, and playing with Razor Components, I realised that's not really the case. _Consider_ Razor Components, yes, but as you'll see, they're not a direct replacement.

### Razor Components - do you mean Blazor?

This is where things start to get a bit fuzzy. I'm not going to dive very far into [Blazor](https://dotnet.microsoft.com/apps/aspnet/web-apps/blazor) in this post, as it's a _big_ topic, and one that has a lot of people excited, but [the brief summary](https://docs.microsoft.com/en-us/aspnet/core/blazor/?view=aspnetcore-3.1) for those unacquainted is:

> Blazor is a framework for building interactive client-side web UI with .NET
> 
> - Create rich interactive UIs using C# instead of JavaScript.
> - Share server-side and client-side app logic written in .NET.
> - Render the UI as HTML and CSS for wide browser support, including mobile browsers.

The important point in here is that Blazor is for building _client-side_ interactivity. Traditional MVC and Razor Pages is all about rendering HTML _server-side_ — any client-side interactivity has to be added with JavaScript. With Blazor, you can now use C# ([mostly](https://docs.microsoft.com/en-gb/aspnet/core/blazor/javascript-interop)) to add that interactivity.

> I won't get into [Blazor Server](https://docs.microsoft.com/en-gb/aspnet/core/blazor/?view=aspnetcore-3.1#blazor-server) vs [Blazor WebAssembly](https://docs.microsoft.com/en-gb/aspnet/core/blazor/?view=aspnetcore-3.1#blazor-webassembly) here, as it's not important for the purposes of this post.

The key thing is that Blazor apps are built using _components_ - Razor Components. These are very closely related to Razor views, but with some important syntactic and stylistic differences. The most superficial changes are they use a _.razor_ file extension instead of _.cshtml_, and `@code{}` blocks in place of `@functions{}`, but the changes go a lot deeper than that.

The interesting point for this post is that you can actually use Razor Components in your MVC/Razor Pages app _without using Blazor_!

### Razor Components without the Blazor

First off, it's absolutely possible to [use Blazor for adding client-side interactivity in your traditional MVC/Razor Pages app](https://docs.microsoft.com/en-us/aspnet/core/blazor/hosting-models?view=aspnetcore-3.1#integrate-razor-components-into-razor-pages-and-mvc-apps). You can [add Razor Components to your Razor Pages](https://docs.microsoft.com/en-us/aspnet/core/blazor/components?view=aspnetcore-3.1#integrate-components-into-razor-pages-and-mvc-apps), and they can form a little island of client-side interactivity within your otherwise server-side rendered app.

![Blazor being used inside a Razor Pages app](https://andrewlock.net/content/images/2020/vc_vs_rc_01.png)

The thing to be aware though is that you're now running a Blazor app.

If you're using Blazor Server, that means you now need to [worry about all the state being stored in memory on the server](https://docs.microsoft.com/en-us/aspnet/core/blazor/state-management) (in case of restarts), you need to be aware that you're [using SignalR behind the scenes](https://docs.microsoft.com/en-us/aspnet/core/blazor/hosting-models?view=aspnetcore-3.1#blazor-server), that you need to [make changes to your project](https://docs.microsoft.com/en-us/aspnet/core/blazor/hosting-models?view=aspnetcore-3.1#integrate-razor-components-into-razor-pages-and-mvc-apps) to support the new functionality, and that your app won't work in a disconnected scenario.

If you're using Blazor WebAssembly, you need to be aware that it's currently in preview only, that you have a large payload to download (the .NET interpreter running in Web Assembly), and that you can't support legacy clients (e.g. Internet Explorer).

What if you don't want all those extra trade-offs? Maybe you just want traditional server-side rendered HTML, but want to use the Razor component model?

You're in luck, that's possible too. Razor Components have [three render modes](https://docs.microsoft.com/en-us/aspnet/core/blazor/components?view=aspnetcore-3.1#integrate-components-into-razor-pages-and-mvc-apps):

- `ServerPrerendered`: The component is rendered to HTML in the response, and then connects back to the server when run in the browser to provide the interactivity.
- `Server`: The component isn't rendered in the HTML, instead a marker is rendered that connects back to your app when run in the browser.
- `Static`: The component is rendered to static HTML in the response, and that's it, done. 

So by rendering components using the `Static` mode, you don't need the extra Blazor overhead, but _you can still use Razor Components_. So, what does that actually look like in practice?

## Converting the login partial view to Razor Components

As an experiment, I decided to convert the [_\_LoginPartial.Identity.chtml_ file included in the default Razor Pages template](https://github.com/dotnet/aspnetcore/blob/6255c1ed960f5277d2e96ac2d0968c2c7e844ce2/src/ProjectTemplates/Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Pages/Shared/_LoginPartial.Identity.cshtml) to a Razor Component.

The partial looks like this:

```html
@using Microsoft.AspNetCore.Identity
@inject SignInManager<IdentityUser> SignInManager
@inject UserManager<IdentityUser> UserManager

<ul class="navbar-nav">
@if (SignInManager.IsSignedIn(User))
{
    <li class="nav-item">
        <a class="nav-link text-dark" asp-area="Identity" asp-page="/Account/Manage/Index" title="Manage">Hello @User.Identity.Name!</a>
    </li>
    <li class="nav-item">
        <form class="form-inline" asp-area="Identity" asp-page="/Account/Logout" asp-route-returnUrl="@Url.Page("/", new { area = "" })" method="post" >
            <button type="submit" class="nav-link btn btn-link text-dark">Logout</button>
        </form>
    </li>
}
else
{
    <li class="nav-item">
        <a class="nav-link text-dark" asp-area="Identity" asp-page="/Account/Register">Register</a>
    </li>
    <li class="nav-item">
        <a class="nav-link text-dark" asp-area="Identity" asp-page="/Account/Login">Login</a>
    </li>
}
</ul>
```

This partial only has a small amount of logic in it, but it uses some injected services, has links to other pages, and has a form (for logout). Lots of bits to play with!

### Creating the stub component

I'm actually going to cheat a bit to start with. Instead of injecting the services used in the partial to determine the current user, I'm going to use an existing pre-built Razor component, the `AuthorizeView` component. This component is designed exactly for this purpose - it lets you display different content depending on whether you're logged in or not.

I'll start by creating a component called _LoginDisplay.razor_ in the _Pages/Shared_ folder of the project, and add the following content:

```html
<CascadingAuthenticationState>
    <AuthorizeView>
        <Authorized>
            <h1>Hello, @context.User.Identity.Name!</h1>
        </Authorized>
        <NotAuthorized>
            <h1>Not Logged in</h1>
        </NotAuthorized>
    </AuthorizeView>
</CascadingAuthenticationState>
```

There's a few more things we need to add before we can test our component.

Update your `Startup.ConfigureServices` method by adding the following method:

```csharp
public void ConfigureServices(IServiceCollection services)
{
    // ... existing services

    services.AddServerSideBlazor();
}
```

Now, just to be clear, we're adding Blazor because we need some of the services it uses, but we're not _actually_ going to run it as a Blazor app. There'll be no connection from the component to the server, or any client-side interaction.

Next, we'll add an _\_Imports.razor_ file in _Pages/Shared_ and add the following:

```csharp
@using System.Net.Http
@using Microsoft.AspNetCore.Authorization
@using Microsoft.AspNetCore.Components.Authorization
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.Web
@using Microsoft.JSInterop
@using RazorComponentsIntro // The project's namespace
```

That will put all the namespaces we need in scope.

Finally, we update _\_Layout.cshtml_ and replace the reference to the login partial:

```html
<partial name="_LoginPartial" />
```

with a reference to our new Razor component. Note that this is where we specify the render mode of the component to be `Static`:

```html
<component type="typeof(LoginDisplay)" render-mode="Static" />
```

If we run the app now, we'll see our component rendered! It's ugly currently, but it works 🙂

![Razor page rendering Razor component](https://andrewlock.net/content/images/2020/vc_vs_rc_02.png)

Now we have something that works, we can see about fleshing it out!

### Some teething problems

My first attempt at restoring the functionality involved copying the Razor from the _\_LoginPartial.Identity.chtml_ file to the appropriate section of the new component. After all, Razor Components use Razor right?

The end result _looked_ fine initially:

![Razor page rendering Razor component](https://andrewlock.net/content/images/2020/vc_vs_rc_03.png)

but on closer inspection there were some problems. The "Register" and "Log In" links were missing their typical "hover" behaviour (mouse cursor change) and clicking them did nothing!

Inspecting the HTML rendered in the browser for the Register button revealed the problem:

```html
<a class="nav-link text-dark" asp-area="Identity" asp-page="/Account/Register">Register</a>
```

The problem is that the Razor markup I added hadn't been transformed to generate the `href` attribute, as you'd expect in Razor views or Razor Pages. Specifically, the Tag Helpers `asp-area` and `asp-page` are being ignored, and just rendered as normal attributes. That explains the behaviour in the browser, but why is it happening?

[Well it turns out that you can't use tag helpers in Razor Components](https://docs.microsoft.com/en-us/aspnet/core/blazor/components?view=aspnetcore-3.1#tag-helpers-arent-used-in-components):

> Tag Helpers aren't supported in Razor Components (.razor files). To provide Tag Helper-like functionality in Blazor, create a component with the same functionality as the Tag Helper and use the component instead.

That's a bit of a pain, but as we're just generating `href` attributes with the tag helpers, we can just hard code them in for now. We _could_ also pass the URLs to use in as parameters to the component, and I guess that's the sort of thing you'd likely do in a real app, but it was a lot easier to just hard code everything.

> Unfortunately, while I don't go into it here, passing parameters from Razor Pages to a Razor component is a bit of a pain as you have to use the `param-*` syntax. I initially started to do this in _\_Layout.cshtml_, using the `Url` helper to generate the values, but that got old **very** quickly…

After restoring all the `href` attributes (and the `action` attribute of the `<form>` tag) I was left with something that looked like this:

```html
<CascadingAuthenticationState>
    <ul class="navbar-nav">
        <AuthorizeView>
            <Authorized>
                <li class="nav-item">
                    <a class="nav-link text-dark" href="Identity/Account/Manage">Hello, @context.User.Identity.Name!</a>
                </li>
                <li class="nav-item">
                    <form class="form-inline" method="post" action="Identity/Account/LogOut?returnUrl=/">
                        <button type="submit" class="nav-link btn btn-link text-dark">Log out</button>
                    </form>
                </li>
            </Authorized>
            <NotAuthorized>
                <li class="nav-item">
                    <a class="nav-link text-dark" href="Identity/Account/Register">Register</a>
                </li>
                <li class="nav-item">
                    <a class="nav-link text-dark" href="Identity/Account/Login">Log in</a>
                </li>
            </NotAuthorized>
        </AuthorizeView>
    </ul>
</CascadingAuthenticationState>
```

What's more, I could register and login, everything worked! As a bonus, the Razor component code is arguably more readable than the Razor view version that relies on large C# `if-else` blocks.

![Razor page rendering Razor component](https://andrewlock.net/content/images/2020/vc_vs_rc_04.png)

_Almost_ everything worked… As long as you don't try and log out… 🙁

![Error trying to log out](https://andrewlock.net/content/images/2020/vc_vs_rc_05.png)

### Blazor and CSRF AntiForgeryTokens

A look into the logs for the application revealed that the problem is due to a missing AntiForgeryToken in the form request. [Razor Pages automatically adds a hidden field containing an antiforgery token to forms by default](https://docs.microsoft.com/en-us/aspnet/core/security/anti-request-forgery?view=aspnetcore-3.1#aspnet-core-antiforgery-configuration). When a form is POSTed, the presence of the token is validated by the Razor Page handler / MVC action. If the field has an invalid value, or is missing, the request is rejected with a `400 Bad Request` response.

That's what we're seeing here. As Razor Components don't use Tag Helpers, the antiforgery token isn't added, and the [Logout Razor Page](https://github.com/dotnet/aspnetcore/blob/6255c1ed960f5277d2e96ac2d0968c2c7e844ce2/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/Logout.cshtml.cs#L47) rejects the request!

So what's the solution? I found references to the problem in a [(now rather old) Gist](https://gist.github.com/SteveSandersonMS/60ca3a5f70a7f42fba14981add7e7f79#6-fix-more-rough-edges) from Steve Sanderson, that describes exactly this problem. I can't find any other references to the problem (or solution) anywhere else.

There _is_ a possible solution though, and it's the [one used by the Blazor project templates](https://github.com/dotnet/aspnetcore/blob/6255c1ed960f5277d2e96ac2d0968c2c7e844ce2/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorServerWeb-CSharp/Areas/Identity/Pages/Account/LogOut.cshtml#L3) that ship with `dotnet new`/Visual Studio when you add authentication. They turn off the antiforgery verification for the logout endpoint. That's not an especially big problem - the worst an attacker could do with an exploit is force you to logout, so it's not a big problem security-wise.

To achieve this we have to override the _LogOut.cshtml_ Razor Page by placing a similarly named file in the magic path _Areas/Identity/Pages/Account/LogOut.cshtml_, with the contents:

```csharp
@page
@using Microsoft.AspNetCore.Identity
@inject SignInManager<IdentityUser> SignInManager

@attribute [IgnoreAntiforgeryToken]

@functions {
    public async Task<IActionResult> OnPost()
    {
        if (SignInManager.IsSignedIn(User))
        {
            await SignInManager.SignOutAsync();
        }

        return Redirect("~/");
    }
}
```

This removes the antiforgery token check, and means we can log out again! And with that, our mission is complete.

## Final thoughts

In [my previous post on View Components](https://andrewlock.net/an-introduction-to-viewcomponents-a-login-status-view-component/), I replaced the existing Razor login partial with an equivalent view component. The process was pretty simple and easy, and generally seemed like a good way to extract moderately complex logic out of the view.

The same can't be said for Razor Components. If all you're trying to do is encapsulate some complex logic inside a Razor Pages app, then Razor Components are _not_ the way to go. The fact they can't use Tag Helplers and issues such as the antiforgery example means I can't see any reason you would choose to use the `Static` render mode in this way. In other words, Razor Components _without_ Blazor don't make a lot of sense to me.

Don't get me wrong, Razor Components (or Blazor) absolutely can have their place within Razor Pages by providing _isolated pockets_ of _client interactivity_. There's two important phrases there:

- _Isolated pockets_: Passing in lots of parameters from Razor Pages into a component is a bit of a pain, and you've already seen the issue going the other way, where Blazor can't easily add the antiforgery token to forms.
- _Client interactivity_: The example in this post had _nothing_ to do with client interactivity; we were just rendering static HTML. If we were rendering something that needed a rich client experience then that's an entirely different scenario.

And to be fair, this is exactly the use case for Razor Components. The thing that fooled me was suggesting you should evaluate Razor Components if you're using View Components. The key word there is _evaluate_; Razor Components and View Components satisfy two different use cases. Make sure you use the right one!

## Summary

In this post I gave a brief introduction to View Components. I then described razor components, their relation to Blazor, and their various render-modes. For the second half of the post I worked through converting the Login Razor partial view to an equivalent Razor Component. I ran into a few issues in the conversion, such as the lack of Tag Helpers and no antiforgery support.

Those issues and the lack of obvious advantages were enough to convince me that Razor Components aren't generally worth using in a Razor Pages app using their `Static` render mode. If you have isolated pockets requiring client-interactivity I can absolutely see the case for using Razor Components in combination with Blazor. But for simply encapsulating view rendering logic in a server-side rendered app, stick to View Components and partials!