Monthly Archives: July 2020

Logging in Blazor

It’s not unusual for us to need to output log message to file, console etc. Obviously there’s going to be a different in capabilities for logging from WASM (essentially on the client browser) and on a server.

Blazor supplies the logging framework

Blazor supplies loggin via the ILogger, ILogger, ILoggerFactory and ILoggerProvider interfaces. In most cases you’ll use the ILogger and ILogger.

Logging capabilities are supplied via the WebAssemblyHostBuilder on WebAssembly and Host on the server (see Program.cs in both project types).

We can therefore use these interfaces/implementation via injection like this

@inject ILogger Logger;
// OR
@inject ILogger<MyPage> Logger;

Alternatively we might use the ILogFactory like this

@inject ILoggerFactory LoggerFactory

// with the following
var logger = LoggerFactory.CreateLogger<MyPage>();

Logging in WebAssembly

With WebAssembly on the client, we’d obviously expect to be logging to the Browser devtools. Whilst we can actually just use

Console.WriteLine("Message 1");
Debug.WriteLine("Message 2");

the Logging framework offers some extra functionality, such as categorisation of logs etc. To use the ILogger or ILogger, we simply use

Logger.LogWarning("Log a warning");

(we have the ability to log debug, warning, info etc.)

Logging to Server

For Blazor Server, the output from the logging will be visible in the console window if running as a standalone application or the output window of Visual Studio.

However if you want your Blazor server application to output to the browser, then you need to use the IJSRuntime, which we can inject like this

@inject IJSRuntime JsRuntime

and then use the runtime like this

JsRuntime.InvokeAsync<string>("console.log", "Browser Message");

as you can see, the runtime allows us access to the JavaScript runtime hosting our pages and we can call the console.log JavaScript method, passing the parameters for the method call. In our case it’s just a string “Browser Message”.

Adding custom logger

To add a custom logger we can use something like this, where the DebugProvider is our implementation of an ILoggingProvider

builder.Logging.AddProvider(new DebugProvider());

Configuration

Configuration is placed in the appsettings.json file (remember if you’re using appsettings.json on WebAssembly client then this should be placed in the wwwroot folder otherwise it’s not set to the browser). For server the appsettings.json can be in the project folder.

Here’s the template generated appsettings section for logging

"Logging": {
  "LogLevel": {
    "Default": "Trace",
    "Microsoft": "Warning",
    "Microsoft.Hosting.Lifetime": "Information"
  }
},

Creating Blazor fragments

At some point you may need to dynamically create elements of mark-up in a Blazor application. For example, you might create a reusable class library for returning components, icons or the likes depending upon the user.

Ofcourse we can do this from the @code block within a page or we might prefer to simply return mark-up from a function.

Let’s look at a simple example method which, depending upon the id sent to it returns a different icon, here’s an abridge snippet of the code

public static RenderFragment GetIconForId(int id)
{
   var iconType = typeof(ListIcon);
   switch (id)
   {
      case 0:
         iconType = typeof(HomeIcon);
         break;
      case 1:
         iconType = typeof(NotificationImportantIcon);
         break;
      // etc.
   }
   
   return builder =>
   {
      builder.OpenComponent(0, iconType);
      builder.CloseComponent();
   };
}

As you can see we return a RenderFragment which is actually just a delegate which takes a RenderTreeBuilder argument. From this builder we create/append a child component, the first value is a sequence (a position of the instructions in the source code) followed by the type of the child component to add.

If the case above, the HomeIcon is an Icon from Skclusive.Material, which is ultimately an Microsoft.AspNetCore.Components.IComponent.

So anything that takes a RenderFragment will now display the icon that’s returned via the method in our example.

We can also use AddAttribute to our component, see RenderTreeBuilder for more information.

Basically it’s not dissimilar (as you’ve expect) to create XML documents using the XmlDocument classes etc.

Blazor routing and Navigation

Routing

If you take a look at the generated pages for a Blazor application, you’ll see the following

@page "/counter"

this denotes a route to our page, i.e. http://localhost:44319/counter.

Note: When you compile your application a file is generated (see Counter.g.cs in compiled folder) and this page declaration is converted to a RouteAttribute.

You can have multiple routes pointing to the same page, in this example all these routes will navigate to the same Counter.razor page

@page "/counter"
@page "/mycounter"
@page "/something"

Probably more useful is the ability to add parameters to our routes, for example

@page "/counter"
@page "/counter/{InitialValue}"

By default InitialValue is of type string, but we can change this by using a constraint, for example

@page "/counter"
@page "/counter/{InitialValue:int}"

Now we simply create a property in our code with the ParameterAttribute applied to it, like this

[Parameter]
public int? InitialValue { get; set; }

We use a nullable int in case the value is missing. We can also have routes handling different type contraints (for example a string or on int) to mean two different things

@page "/counter"
@page "/counter/{InitialValue:int}"
@page "/counter/{SomeString}"

In our OnInitialized() method (part of the component life cycle) we might then set the currentCount to InitialValue.

The routes are declared in our pages but the routing component itself can be found in App.razor. In this case the Router will locate the routes from the supplied AppAssembly

<Router AppAssembly="@typeof(Program).Assembly">

We can also includes routes within AdditionalAssemblies, for example maybe you’ve got some reusable components with routing supplied, hence we write

<Router AppAssembly="@typeof(Program).Assembly" 
   AdditionalAssemblies="new[] { typeof(MyComponents).Assembly }>

Navigation

If you want to navigate to other pages, we need to use the NavigationManager. This needs to be injected into our page, using

@inject NavigationManager NavManager

Now to navigate, for example when a button is click, we use the following

NavManager.NavigateTo("counter/2");

In the instance we’ll navigate to the relative URL/page with “counter/someData”.

This works fine when navigating to other pages but if we want to navigate within a page and we’re using the component’s OnInitialized method to initialize data?

Because OnInitialized is called when the page is initial loaded and because we’re already in the page, this method will not normally be called again if we navigate within the page, so we simply need to supply an extra parameter to our NavigateTo code to force the page to reload, i.e.

NavManager.NavigateTo("counter/2", true);

Note: An alternative solution ofcourse is to override the other life cycle method OnParameterSet and instead move initialization code to that method.

Visual Code debugging in Chrome

I’m using Visual Code for my React development and I’m using Chrome as my browser – I want to debug my React code in Chrome using Visual Code, so let’ see how it’s done.

First off, check out Introducing Chrome Debugging for VS Code, secondly VS Code – Debugger for Chrome.

Or, if you prefer, here’s the steps

  • Ctrl+Shift+X within Visual Code to load the Extensions list
  • Search for Debugger for Chrome and install it (if not already installed)

You’re going to need a launch.json file in the .vscode folder, here’s a minimal example

{
    // Use IntelliSense to learn about possible attributes.
    // Hover to view descriptions of existing attributes.
    // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
    "version": "0.2.0",
    "configurations": [
        {
            "type": "chrome",
            "request": "launch",
            "name": "Launch Chrome",
            "url": "http://localhost:3000",
            "webRoot": "${workspaceFolder}"
        },
        {
            "type": "chrome",
            "request": "attach",
            "name": "Attach to Chrome",
            "port": 9222,
            "webRoot": "${workspaceFolder}"
        }
   ]
}

In the above we have two configurations, both for Chrome, one launches Chrome with the supplied URL, the second attaches to an existing instance of Chrome.

If you now choose the option Run | Start Debugging, Chrome will launch with your URL loaded. You can place breakpoints in your code (before or after you start debugging) and you’re application will break at these breakpoints as you’d expect and you can inspect variables etc. So basically a full debugging experience.

Default commands within debug mode are as follows

  • F5 Continue
  • F10 Step Over
  • F11 Step Into
  • Shift+F11 Step Out
  • Ctrl+Shift+F5 Restart
  • Shift+F5 Stop

Adding imports to your Blazor app.

The default template’s supplied for Blazor Server and WebAssembly applications, gives you lots upfront. However, ofcourse you’re likely to want to import further functionality via the @using directive.

For example, let’s say we want to add the following to the Counter.razor IncrementCount method (from the default template generated files)

Debug.WriteLine("Increment");

We can simply add the following to the top of the Counter.razor file

@using System.Diagnostics

Alternatively and especially useful for using code across multiple pages/components, you can put the above code into the _Imports.razor file. Think of this as a global set of using reference, but only for @code sections, for standard C# classes we ofcourse use the using directive as usual.

Note: I’ve found my current version of Visual Studio doesn’t seem to immediately recognise the updated _Imports.razor file, a build fixes that.

Getting started with Blazor on Server and WebAssembly

As of Visual Studio 16.6.3 and .NET core 3.1.301 there’s now support for create Blazor applications for Server and WebAssembly.

Creating your project

From Visual Studio

  • Create a new project and look for the Blazor App template
  • You’ll be presented with the options to create a Blazor Server App or Blazor WebAssembly App

From the dotnet CLI

  • To create a server app run
    dotnet new blazorserver -o your-app-name
    
  • To create a WebAssembly app run
    dotnet new blazorwasm -o your-app-name
    

You can also run the generated code by changing directory to the your-app-name and then execute the following

dotnet run

Whichever route you took to generate your Blazor app. When you run the application, you can access the application via https://localhost:5001/.

Blazor Server

I’m not going to cover Blazor on the server specifically, as this is very much like Blazor WebAssembly crossed with ASP.NET core development. Basically what you write for WebAssembly is very much the same as you’d write for the Server.

There are benefits and downsides to Blazor Server and Blazor WebAssembly, I’m not going to list them all (because I’m sure I’ll forget one or two) but basically running Blazor on the server gives faster start-ups, allows us to be more secure with our source code (in that the code is not available to be downloaded but runs instead on the server). However the Server implementation suffers from the lack of an offline mode (i.e. PWA is supported via Visual Studio for Blazor WebAssembly) and there’s bound to be more network traffic with a server implementation.

Blazor WebAssembly

WebAssembly or WASM is a binary instruction set for browsers (well those that support it). Think of it as a virtual machine for running byte code. The important parts of WASM are that any it can run code generated from C# (other languages are adding support) instead of just using JavaScript for the Web. The second thing of interest is that the virtual machines are sandboxed.

It might sound like we’ve sort of been here before, and we have to a degree, but this time we have an open standard instead of plugins, Java applets, Silverlight or ActiveX.

With Blazor WebAssembly the compiled code is executed on the client, within the browser as a SPA. With Blazor Server, much like ASP.NET, the code comes from the server, however clever use of SignalR allows diffs to/from the DOM to be sent back and forth in an efficient manner.

Looking into the Blazor templates

If we look at both the WASM and Server code we’ll see our pages written in Razor syntax, here’s the Counter.razor file

@page "/counter"

<h1>Counter</h1>

<p>Current count: @currentCount</p>

<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>

@code {
    private int currentCount = 0;

    private void IncrementCount()
    {
        currentCount++;
    }
}

Razor syntax is denoted by the @ prefixed commands, for example @page is basically a routing command to this counter page. @code is a C# code block. Also we can use variables from our code by prefixing with the @ (such as @currentCount).

When you see a @code block, think of it as a class, hence in this example we have a field currentCount and a member method named IncrementCount.

Binding

One way binding is very simple (and shown in the above snippet)

<p>Current count: @currentCount</p>

In this case the variable is updated via the @code and one way binding ensures that the text in the HTML is updated automatically for us.

Ofcourse we’re going to need two-way binding and this is pretty simple in Blazor. Let’s add an input to the Counter.razor code (just place above the button HTML).

<input @bind="currentCount" />

This input will by default, bind the variable currentCount to it’s value property, we can also write explicit code for this in this way

<input @bind-value="currentCount" />

You use the syntax @bind-{Property} replacing {Property} with whatever the property or attribute name is that you wish to bind to.

What happens now is that, when the user changes the input value and the control loses focus, the currentCount is automatically set to the new value. We may want to instead have the value update as the input changes, in this instead we use @bind-value:event, for example

<input @bind-value="currentCount" @bind-value:event="oninput"/>

Note: if you put a char or string into the input, the binding doesn’t update and hence you get no exceptions or anything.

Blazor Components

Each page that we have is basically a Blazor component, but we can create a component that’s not a page – in other words it’s just a component and will be used to render HTML fragments.

From Visual Studio, right mouse click on the Shared folder and then select Add | Razor Component. Mine’s named Count.razor and the aim is to move the code into it’s own component

<p>Current count: @currentCount</p>

I called my component Count.razor. Let’s change the Counter.razor page to use this new component, so replace the code and above with

<Count @bind-Counter="currentCount"></Count>

Here’s our Count.razor component code

<p>Current count: @Counter</p>

@code {
    [Parameter]
    public int Counter { get; set; }

    [Parameter]
    public EventCallback<int> CounterChanged { get; set; }
}

Whilst we’re not using the CounterChanged event, it’s required for the binding code. The ParameterAttribute is basically exposing properties to the parent components or pages, i.e. for use outside of the component itself.

Now I’m not 100% on this code (i.e. CounterChanged not being used) as it worked fine, but I was finding Visual Studio displays error tildes under the currentCount usage in the Counter page, although everything worked. I’m not sure if this is just an issue where Visual Studio it not yet upto date with all the syntax or a mistake on my part – although it does work.

However, if we just change the page’s code to

<Count @bind-Counter="currentCount" 
   @bind-Counter:event="CounterChanged"></Count>

then no error tildes were displayed in Visual Studio – this needs further investigation as it seems a bit superfluous adding this event when it’s not used – but hey, I’m new to Blazor!

That’s it for Getting Started.

References

Razor syntax reference for ASP.NET Core
WebAssembly
Introduction to ASP.NET Core Blazor