---
title: "Advanced Blazor: Shared Assemblies and Debugging from Edge"
author: "Jeremy Likness"
date: 2019-11-13T10:56:33-08:00
years: "2019"
lastmod: 2019-11-13T10:56:33-08:00

draft: false
comments: true
toc: true

subtitle: "Sometimes it takes a Razor to cut Blazor bugs."

description: "Advanced Blazor techniques covered such as sharing Razor class libraries between a Blazor WebAssembly project and Blazor server-side. Shows how to reference embedded resources like JavaScript libraries, debug on server-side then deploy as client-side and debug in the browser."

tags:
 - Blazor 
 - WebAssembly 
 - Web Development
 - Debugging
 - Edge

image: "/blog/advanced-blazor-shared-assemblies-debugging/images/blazordebug.png" 
images:
 - "/blog/advanced-blazor-shared-assemblies-debugging/images/blazordebug.png" 
 - "/blog/advanced-blazor-shared-assemblies-debugging/images/blazorserverside.png" 
 - "/blog/advanced-blazor-shared-assemblies-debugging/images/blazorclientside.png" 
 - "/blog/advanced-blazor-shared-assemblies-debugging/images/debuginstructions.png" 
 - "/blog/advanced-blazor-shared-assemblies-debugging/images/edgedebug.png" 
 - "/blog/advanced-blazor-shared-assemblies-debugging/images/clientdebug.png" 
---

There is tremendous interest in [Blazor WebAssembly](https://github.com/JeremyLikness/blazor-wasm). Developers are finally able to build browser-based Single Page Applications (SPA) using the language of their choice, such as C#. As of this writing, Blazor WebAssembly is in preview status. It is scheduled to release in May of 2020 as the team works to add features and improve the developer experience. One of the biggest challenges with Blazor WebAssembly projects is the lack of integrated debugging.

![Blazor debug](/blog/advanced-blazor-shared-assemblies-debugging/images/blazordebug.png)

There _is_ a debug experience that is slowly evolving. In the meantime, however, there are a few simple steps and practices you can follow to make life easier.

## Use a Razor

I follow a "lowest common denominator" development philosophy. I pull out the things that can be shared most broadly into separate components and try to keep logic decoupled from framework implementations. For example, instead of embedding logic directly inside of Razor components, I prefer to use the [Blazor MVVM pattern](https://blog.jeremylikness.com/blog/2019-01-04_mvvm-support-in-blazor/). This allows me to use a class library that can be shared between Blazor, ASP.NET MVC, WPF and even Xamarin (mobile) projects. The next "layer" is the view, and with [Razor Class Libraries](https://docs.microsoft.com/en-us/aspnet/core/razor-pages/ui-class?view=aspnetcore-3.0&utm_source=jeliknes&utm_medium=blog&utm_campaign=link&WT.mc_id=link-blog-jeliknes), the user interface (UI) can be shared as well!

Instead of building Razor components directly in your Blazor project, consider putting them in a Razor class library. Razor class libraries are not limited to native components; you can also embed JavaScript, images, and other resources that are shared as well. The code in this blog post is all contained in a sample app:

{{<github "JeremyLikness/AdvancedBlazor">}}

To start, I created a Razor class library and named it `BlazorApp.Shared`. The project template generates some default files that I overwrote with a few custom components. By default, a JavaScript file is embedded that returns text from a prompt.

{{<highlight JavaScript>}}
window.exampleJsFunctions = {
  showPrompt: function (message) {
    return prompt(message, 'Type anything here');
  }
};
{{</highlight>}}

A class named `ExampleJsInterop` provides a wrapper around the call to JavaScript.

{{<highlight CSharp>}}
public class ExampleJsInterop
{
    public static ValueTask<string> Prompt(IJSRuntime jsRuntime, string message)
    {
        // Implemented in exampleJsInterop.js
        return jsRuntime.InvokeAsync<string>(
            "exampleJsFunctions.showPrompt",
            message);
    }
}
{{</highlight>}}

To make it easy to use JavaScript interoperability anywhere in the project, I added a using statement to `_Imports.razor`:

{{<highlight CSharp>}}
@using Microsoft.JSInterop
{{</highlight>}}

Next, I created a component named `Prompter.razor` that shows the prompt on a button click and updates some text. The template looks like this:

{{<highlight HTML>}}
<div class="my-component">
    This Blazor component is defined in the <strong>BlazorApp.Shared</strong> package.
</div>
<button @onclick="GetText">Enter Text</button>
<p>Prompt text: @Text</p>
{{</highlight>}}

The code-behind uses a JavaScript prompt to retrieve text and update the properties on the class.

{{<highlight CSharp>}}
public string Text { get; set; } = "Click the button to change this.";
public async void GetText()
{
    Text = await ExampleJsInterop.Prompt(JsRuntime, "Enter your text:");
    StateHasChanged();
}
{{</highlight>}}

> I use the code generated by the template for this example. A better implementation than using a static method would be to provide an interface with the method signature and register the implementation as a singleton in the `Startup` class, then inject it when needed. This will make unit tests much easier to write.

The purpose of this simple component is to illustrate how I can write custom JavaScript and ship it in a shared library. I created another component called `ShowHost` that displays a date and the host (OS) platform as reported by `System.Environment.OSVersion`. You'll see why later in this blog post. The template:

{{<highlight HTML>}}
<h3>Your environment is: @OS (last refreshed at: @Refresh)</h3>
<button @onclick="DoRefresh">Refresh Environment</button>
{{</highlight>}}

The code-behind:

{{<highlight CSharp>}}
public string OS { get; set;} = "Not Set";
public string Refresh { get; set; } = "never";

protected override void OnInitialized()
{
    DoRefresh();
}

public void DoRefresh()
{
    OS = Environment.OSVersion.VersionString;
    Refresh = DateTime.Now.ToString();
}
{{</highlight>}}

Finally, I added a `Wrapper` control that wraps the other components, so I only need to drop a single component in my Blazor project. If I expand the example in the future I can embed additional components in `Wrapper` and not change my hosting projects. The `Wrapper` component has two instances of `Prompter` to show it works fine with multiple instances.

{{<highlight HTML>}}
<h3>Wrapper</h3>
<h4>Environment:</h4>
<ShowHost></ShowHost>
<h4>First prompt:</h4>
<Prompter></Prompter>
<h4>Second prompt:</h4>
<Prompter></Prompter>
{{</highlight>}}

## Serve your Blazor

The Razor class library doesn't do much on its own. It needs a host project. To add a little twist, instead of hosting a Blazor WebAssembly project, the first project I created is a server-side project.

> <i class="fa fa-info-circle"></i> It turns out that Blazor has a few different hosting models. Although WebAssembly is still in preview, the [Blazor Server](https://docs.microsoft.com/en-us/aspnet/core/blazor/hosting-models?view=aspnetcore-5.0&utm_source=jeliknes&utm_medium=blog&utm_campaign=link&WT.mc_id=link-blog-jeliknes&viewFallbackFrom=aspnetcore-3.0#blazor-server) hosting model is ready for production. You can learn more about it in the [official documentation](https://docs.microsoft.com/en-us/aspnet/core/blazor/hosting-models?view=aspnetcore-5.0&utm_source=jeliknes&utm_medium=blog&utm_campaign=link&WT.mc_id=link-blog-jeliknes&viewFallbackFrom=aspnetcore-3.0#blazor-server). I'm a WebAssembly fan myself, but there are a few reasons it makes sense to use a server-side project. First, it can reference the same Razor class library that Blazor WebAssembly uses. Second, it provides a fully integrated debug experience. Just set your breakpoint(s), hit F5 and you're on your way!

In the project named `BlazorApp.Server` I added a reference to `BlazorApp.Shared`. Next, I needed to ensure the JavaScript file is properly loaded in my app. Fortunately, Razor class libraries expose a very simple convention to access embedded resources. Relative to the web page, you can access the resource using the convention: `_content/{assemblyname}/{filename}`. To include the script is as simple as adding the following `script` tag to `_Host.cshtml`:

{{<highlight HTML>}}
<script src="_content/BlazorApp.Shared/exampleJsInterop.js"></script>
{{</highlight>}}

This is a manual step and must be repeated for any additional files. The Blazor team has good reasons for not automatically including the resources, such as the possibility of resource and naming conflicts. I have some ideas around making the process more "auto-discoverable" that I'll explore in later posts. If you have a compelling reason to have this functionality "out-of-the-box", let me know in the comments below and I'll share your feedback with the team.

Next, I opened `Index.razor` and dropped in the `Wrapper` component:

{{<highlight HTML>}}
@using BlazorApp.Shared
@page "/"

<h1>Hello, world!</h1>

Welcome to your new app.

<Wrapper></Wrapper>
{{</highlight>}}

To debug "the easy way" I put a breakpoint on the `StateHasChanged()` line in the `Prompter` component and hit F5. My app renders with my OS version (notice it's my "server" or laptop version), date, and buttons.

![Server-side Blazor app](/blog/advanced-blazor-shared-assemblies-debugging/images/blazorserverside.png)

Clicking one of the "Enter Text" buttons results in the JavaScript prompt displaying, showing me that both embedding shared resources and JavaScript interoperability are working. After I enter text and click "OK", the breakpoint is hit and I can inspect variables, modify properties, view the call stack and perform just about any other debug task I might want. Now the code looks good, so it's time to create the Blazor WebAssembly project!

## Debug the Client (Edge Edition)

After creating a Blazor WebAssembly project named `BlazorApp.Client`, I added a reference to the same Razor class library (`BlazorApp.Shared`) I used in the server-side app. I used the exact same convention to reference the JavaScript, only this time I placed the `script` tag in `index.html` under `wwwroot` instead of `_Host.cshtml`. The `Index.razor` modifications are identical to the server-side version. I set the startup project to the client and pressed CTRL+F5 to launch it. The result looks almost identical ... with one exception. Do you see it right away?

![Client-side Blazor](/blog/advanced-blazor-shared-assemblies-debugging/images/blazorclientside.png)

In case you missed it, the "platform" is reported as `Unix 1.0.0.0`. This is because the app is now running entirely in the browser, on Mono via WebAssembly. The rest of the functionality works _mostly_ the same.

What if you need to debug the application now? If you open the debug console, you'll see a message like this:

`Debugging hotkey: Shift+Alt+D (when application has focus)`

Chances are pressing the magic key combination results in an error with instructions on how to fix it.

![Debug instructions](/blog/advanced-blazor-shared-assemblies-debugging/images/debuginstructions.png)

If you're on Chrome, you're good to go. Just note that you'll need to close all existing Chrome windows and, if you have the Chrome accelerator in your task bar, exit from that, too.

But what if you use Edge? No problem! Close all your open Edge instances then run the following instead (I simply changed the path to point to my Edge install, yours may be different):

`"%programfiles(x86)%\Microsoft\Edge Dev\Application\msedge.exe" --remote-debugging-port=9222 http://localhost:57955`

> <i class="fa fa-lightbulb"></i> Not sure what the path to your Edge install is? No problem. Just right-click on the icon you use to launch it and go into properties. You should either see a property with the path, or a shortcut tab with a "Target" property that contains the full path to the executable.

If your app is running on a different port, change the URL accordingly. This should bring up the app again. Hit the SHIFT+ALT+D combination and a new tab will open with some additional information. This is what mine looks like:

![Edge debug view](/blog/advanced-blazor-shared-assemblies-debugging/images/edgedebug.png)

You can expand the virtual filesystem under `file://` to navigate your source code and place a breakpoint on the same `StateHasChanged()` line we used in the server-side example. Flip back to the main tab, click the prompt, enter some text and hit "OK." You should see a "paused in debugger" message. Head over to the debug tab and you can now see the breakpoint has been triggered. It is possible to expand the right panel under "Scope" to inspect the value of variables and properties.

![Client debug experience](/blog/advanced-blazor-shared-assemblies-debugging/images/clientdebug.png)

That's the end of the magic trick!

In this blog post we explored:

- Using a Razor class library to improve the portability of our code
- Implementing a Blazor Server project for advanced debugging
- Debugging Blazor WebAssembly from the Edge browser

You can pull down the project from <i class="fab fa-github"></i> GitHub:

{{<github "JeremyLikness/AdvancedBlazor">}}

As always, I'd love to hear your thoughts and feedback. Start or join a discussion below!

Regards,

![Jeremy Likness](/images/jeremylikness.gif)
