Auto recompile Razor Class Library during development with ASP.NET Core 3

If your .NET Core web app has Razor Class Libraries (RCL) as dependencies and you make a change to a Razor Page inside your RCL, you certainly hope to see your change in the browser with no more effort than a refresh (either by you or BrowserLink). This involves an auto recompile of the projects changed, but out of box ASP.NET Core 3 web application does not provide you that. This post shows you how to enable it for both a RCL with Razor Pages and one with the new Razor Components. It involves some typical setup code which works for Razor Pages but unfortunately not currently working for Razor Components, so a workaround is presented for the latter.

I opened up a GitHub issue on this and got help from people there, this post presents what I learned from them. I also created a GitHub repo with all the code involved in this post you can take a look. The webapp used here is the WebAppRazor project and it's referencing two RCLs, the RazorClassLibRazorPage and RazorClassLibRazorComponent projects. The first project contains a Razor Page Page1 and the second project contains a Razor Component Component1, both are being used on the Index.cshtml of the hosting WebAppRazor. And my goal is that when I save a change to either Page1 or Component1, I want to see that change reflected in the browser running my WebAppRazor.

Razor RuntimeCompilation and FileProviderRazorProjectFileSystem

First you need to install the Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation package to your webapp, after which your .csproj will have

<ItemGroup>
   <PackageReference Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="3.0.0" Condition="'$(Configuration)' == 'Debug'" />
</ItemGroup>

Secondly, you need to add this code to your Startup.cs

public void ConfigureServices(IServiceCollection services) 
{ 
    ....           
    var builder = services.AddRazorPages(); 
#if DEBUG 
    if (env.IsDevelopment()) 
    { 
        // list all my class libs I want to monitor 
        string[] dirs = { "RazorClassLibRazorPage", "RazorClassLibRazorComponent" }; 
        builder.AddRazorRuntimeCompilation(options => 
        { 
            // add each to options file providers 
            foreach (var dir in dirs) 
            { 
                var libraryPath = Path.GetFullPath(Path.Combine(env.ContentRootPath, "..", dir)); 
                options.FileProviders.Add(new PhysicalFileProvider(libraryPath)); 
            } 
        }); 
 } 
#endif 
} 

The code above basically adds the content path to your RCLs as file providers to razor runtime compilation options, so that dotnet can compile our razor stuff during runtime. Since I have two RCLs, I'm adding them both to the file provider list. For Razor Pages, this is all you have to do. You can run WebAppRazor with Ctrl + F5 and then make a change on Page1.cshtml, after that refresh the page at https://localhost:5001/myfeature/page1 and you will see your change.

Unfortunately, this does not work for Razor Component. And someone pointed FileProviderRazorProjectFileSystem.cs to me as the potential suspect and I agree. If you take a look at this file provider's implementation on Line 15, you will see that it's only dealing with Razor Page file extension .cshtml but not the Razor Component file extension .razor, at least not yet. Hope this could be addressed in the next release.

Workaround for Razor Component

To get the auto recompile working for Razor Component we have to use dotnet watch. And to enable that with Visual Studio we have to do two things.

First go to launchSettings.json file in my WebAppRazor project, and add the following to the end of the profiles node.

"profiles": { 
    ...
   // dotnet watch run must run Ctrl + F5 not debug 
   "WebAppRazor Watch": { 
     "commandName": "Executable", 
     "executablePath": "dotnet", 
     "launchBrowser": true, 
     "launchUrl": "https://localhost:5001/", 
     "workingDirectory": "$(ProjectDir)", 
     "commandLineArgs": "watch run", 
     "applicationUrl": "https://localhost:5001;http://localhost:5000", 
     "environmentVariables": { 
       "ASPNETCORE_ENVIRONMENT": "Development" 
     } 
} 

This new profile will add a new launch configuration to Visual Studio.

Add Watch profile to launchSettings
Add Watch profile to launchSettings

Secondly, you have to add the following to the WebAppRazor app's .csproj file. This let dotnet watch over any .razor files in my RazorClassLibRazorComponent project.

<ItemGroup>
   <Watch Include="..\RazorClassLibRazorComponent\**\*.razor" />
</ItemGroup>

Now set Visual Studio to the new WebAppRazor Watch profile on the dropdown shown in the above picture and press Ctrl + F5 to launch the webapp. NOTE: you cannot debug the app, you must run it. Make a change to Component1 and refresh browser on the index page (that's where component1 is used), you show see auto recompile happening and after a couple of seconds your change should be there.

BrowserLink

BrowserLink is a common feature devs enable with their webapp during development so that we don't have to constantly go to our browser and hit refresh. But with the setup I'm showing you above it is not working, in fact based on my experience it crashes my Visual Studio.

To enable BrowserLink involves two steps too. First, add nuget package Microsoft.VisualStudio.Web.BrowserLink to the WebAppRazor. Then go to Startup.cs and in the Configure method add this code snippet app.UseBrowserLink(); inside the if env.IsDevelopment() section. This is good addition if you are developing with Razor Pages with IIS Express, but it crashes Visual Studio running dotnet watch shown above.