Sharing is caring, and what’s more affectionate than sharing Razor Pages functionality with the .NET community? Well, a lot of other things, but sharing Razor Pages can help you deliver functionality to the other developers that would otherwise be difficult to integrate into existing applications.

Some examples of practical Razor Page implementations might include a route debugger, an administrative panel, a terminal emulator, and a documentation renderer. All of these could be something you take on and ship to users to help accelerate their development and help them focus more on their problem domain.

To accomplish this goal, you may use the Razor Class Library, which allows you to bundle Razor Pages, Razor Components, and other static assets. That said, there are bound to be conflicts with routes and users’ aesthetic senses of what routes should look like.

In this post, we’ll see how you can override Razor Pages routes delivered from a Razor Class Library.

What Is A Razor Class Library?

Razor Class Libraries (RCL) allow developers to share Razor components, C# code, and static assets to projects referencing the library. The approach is helpful for situations where you have standard functionality worth sharing in other projects but not necessarily repeating. Projects that can use an RCL include ASP.NET Core MVC, Blazor Server app, and Blazor WASM. You can distribute RCLs in multiple ways, but the most effective approach is through a NuGet package.

You can create an RCL project using the .NET template razorclasslib or the “New Project” facilities of your favorite .NET IDE.

Creating A Razor Class Library For Razor Pages

To create an RCL, begin a new project within your solution using the .NET SDK-provided template. You’ll need two additional elements in your .csproj file to support Razor Pages.

First, let’s add the AddRazorSupportForMvc element. In the csrpoj file, you should add the following element to your project file’s first PropertyGroup tag. This addition allows the RCL to support Razor Pages and the structure required to embed the files in the project.

<AddRazorSupportForMvc>true</AddRazorSupportForMvc>

The following essential element is a FrameworkReference in the same project file. The reference imports the ASP.NET Core primitives required by Razor pages.

<FrameworkReference Include="Microsoft.AspNetCore.App" />

Without this additional reference, you won’t be able to use classes like PageModel.

Adding A Razor Page

In the RCL project, we will use Areas to isolate our Razor Pages from a consuming project’s pages. The approach keeps conflicts at a minimum. We’ll add a custom “Privacy” Razor Page in an “Extra” area. The directory structure will look like.

|- wwwroot
|- Areas
   |- Extra
      |- Pages    
         |- Privacy.cshtml 
         |- Privacy.cshtml.cs 

The contents of the Razor Page are not very important in this case. I reworked the namespaces and copied the default Privacy page from an existing Razor Pages project.

Here’s the important part! Don’t skip this.

When defining your Razor Pages, do not set the route in your pages. Instead, each page should only have the @page attribute.

// correct
@page
// incorrect
@page "/privacy"

This decision will be necessary for letting users override their applications’ routes.

In a referencing project, you should now be able to navigate to the page using /Extra/Privacy, but what if you don’t do that? Well, let’s look at the crux of this post, overriding the route.

Overriding RCL Razor Pages Routes

ASP.NET Core registers Razor Pages by their “page name”. The page name is typically a version of the physical path combined with an area, if applicable.

In our case, the route to our RCL Razor Page has a page name of /Privacy and an area name of Extra. We can use this information by adding a new convention to our Razor Pages options. The code is straightforward; you can add the following code in your Program.cs or Startup class.

builder.Services.Configure<RazorPagesOptions>(opt =>
{
    // Override an imported Razor Class Library
    // Razor Pages Route
    opt.Conventions.AddAreaPageRoute(
        areaName: "Extra",
        pageName: "/Privacy",
        route: "/Privacy");
});

Instead of navigating to /Extra/Privacy, you can get to the RCL Razor page using /Privacy. Hooray!

It’s imperative that the author of the RCL did not set the @page route, or else you will run into issues.

Now, you can generate routes using the link generation found in ASP.NET Core using the original page name and area name from the RCL library.

<a asp-area="Extra" asp-page="/Privacy">Privacy</a>

And the resulting link will be the override route set in your RazorPagesOptions instance. Great!

An additional step you could take in your RCL implementation is providing an extension method on IServiceCollection that sets the route in a more user-friendly manner.

using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.DependencyInjection;

namespace RazorPagesExtra.Extra;

public static class RoutesExtensions
{
    public static IServiceCollection SetPrivacyUrl(this IServiceCollection services, string route)
    {
        services.Configure<RazorPagesOptions>(opt =>
        {
            // Override an imported Razor Class Library
            // Razor Pages Route
            opt.Conventions.AddAreaPageRoute(
                areaName: "Extra",
                pageName: "/Privacy",
                route: route);
        });
        
        return services;
    }
}

With a call in the referencing app, looking like the following:

builder.Services.SetPrivacyUrl("/Privacy");

And this could be expanded to a configuration object instead. So you have unlimited options for how you’d like to approach this from here on out.

Note: If you’re noticing your RCL page has no layout, you can always add a new Areas folder in your referencing project with a _ViewStart.cshtml file.

@{
   /*
    *  |- wwwroot
    *  |- Areas
    *     |- _ViewStart.cshtml 
    */
   Layout = "_Layout";
}

This additional file will set the Layout for all RCL libraries and let you use your project’s layout file.

Conclusion

Delivering value to other developers using RCLs is an excellent feature of ASP.NET Core, but sometimes users want control over their routes. Using Razor Pages conventions and excluding a default route, you can give your users the ability to override any route you provide via an RCL.

I hope you found this post helpful, and as always, thank you for reading and sharing my work.

If you’d like to see a complete sample of this repository, you can see it on my GitHub repository.