Category Archives: .Net Core

My object won’t deserialise using System.Text.Json

I imagine this is a very common scenario, so I thought it worth documenting.

Let’s take the following controller method in a bog standard Asp.Net Core API:

        public async Task<DataContainer> Get()
        {
            var data = Enumerable.Range(1, 5).Select(index => new MyData
            {
                Value1 = index,
                Value2 = "some other value"
            })
            .ToList();

            return new DataContainer()
            {
                Data = data
            };
        }

Here’s a method that consumes it:

            var client = _clientFactory.CreateClient();
            var result = await client.GetAsync($"https://localhost:22222/endpoint");

            using var responseStream = await result.Content.ReadAsStreamAsync();
            var data = await JsonSerializer.DeserializeAsync<DataContainer>(responseStream);

So, you’ve expect this to work, right? It makes no difference what DataContainer looks like, because you’re serialising and deserialising using the same technology. In fact, it just returns a correctly structured object, but every value will be null.

I’ll tell you what does work, is this:

            var client = _clientFactory.CreateClient();
            var result = await client.GetAsync($"https://localhost:22222/endpoint");

            var responseString = await result.Content.ReadAsStringAsync();
            var data = Newtonsoft.Json.JsonConvert.DeserializeObject<DataContainer>(responseString);             

If you look at the JSON, it looks kosher enough (again, the exact structure is irrelevant). So, what’s the issue?

Interestingly, when the JSON is serialised, it is serialised into camelCase; so if you have a variable such as MyData, it will get serialised as myData. Newtonsoft deals with this, because it’s a common use case in .Net; however, System.Text.Json.Deserialize* assumes that the casing will match the object!

This is odd considering it changes it on the way out!!

Okay, so what’s the fix?

You just tell Deserialize* to use camelCase:

            var client = _clientFactory.CreateClient();
            var result = await client.GetAsync($"https://localhost:22222/endpoint");

            using var responseStream = await result.Content.ReadAsStreamAsync();

            var options = new JsonSerializerOptions()
            {
                PropertyNamingPolicy = JsonNamingPolicy.CamelCase
            };
            var data = await JsonSerializer.DeserializeAsync<DataContainer>(responseStream, options);

Annoying, eh?

References

https://docs.microsoft.com/en-us/dotnet/standard/serialization/system-text-json-migrate-from-newtonsoft-how-to#table-of-differences-between-newtonsoftjson-and-systemtextjson

https://docs.microsoft.com/en-us/dotnet/standard/serialization/system-text-json-how-to#use-camel-case-for-all-json-property-names

Using HttpClientFactory

In .Net Framework (prior to .Net Core), HttpClient was something of a pain. You essentially kept it around in a static variable. If you didn’t, and used too many of them, you could end up issues such as socket exhaustion.

I think most people got around this by implementing their own version of a HttpClientFactory… but now you don’t need to.

In this post, I’m covering the simplest possible use case inside an Asp.Net Core application. The articles linked at the bottom of this post go into much more detail.

Usage

The first step to using the HttpClientFactory is to add it to the IoC:

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllersWithViews();
            services.AddHttpClient();
        }

Then, simply inject it into your controller; for example:

        public HomeController(ILogger<HomeController> logger,
            IHttpClientFactory clientFactory)
        {
            _logger = logger;
            _clientFactory = clientFactory;
        }

Finally, you request a client like this:

var client = _clientFactory.CreateClient();

Then you can use it as a normal HttpClient:

var result = await client.GetAsync($"https://localhost:22222/endpoint");

References

https://docs.microsoft.com/en-us/aspnet/core/fundamentals/http-requests?view=aspnetcore-3.1

https://www.stevejgordon.co.uk/introduction-to-httpclientfactory-aspnetcore

Manually Adding DbContext for an Integration Test

In EF Core, there is an extension method that allows you to add a DBContext, called AddDBContext. This is a really useful method, however, in some cases, you may find that it doesn’t work for you. Specifically, if you’re trying to inject a DBContext to use for unit testing, it doesn’t allow you to access the DBContext that you register.

Take the following code:

services.AddDbContext<MyDbContext>(options =>
                options.UseSqlServer());
         

I’ve previously written about using UseInMemoryDatabase. However, this article covered unit tests only – that is, you are able to instantiate a version of the DBContext in the unit test, and use that.

As a reminder of the linked article, if you were to try to write a test that included that DBContext, you might want to use an in memory database; you might, therefore, build up a DBContextOptions like this:

var options = new DbContextOptionsBuilder<MyDbContext>()
                .UseInMemoryDatabase(Guid.NewGuid().ToString())
                .EnableSensitiveDataLogging()
                .Options;
var context = new MyDbContext(options);

But in a scenario where you’re writing an integration test, you may need to register this with the IoC. Unfortunately, in this case, AddDbContext can stand in your way. The alternative is that you can simply register the DbContext yourself:

var options = new DbContextOptionsBuilder<MyDbContext>()
                .UseInMemoryDatabase(Guid.NewGuid().ToString())
                .EnableSensitiveDataLogging()
                .Options;
var context = new MyDbContext(options);
AddMyData(context);
services.AddScoped<MyDbContext>(_ => context);

AddMyData just adds some data into your database; for example:

private void AddTestUsers(MyDbContext context)
{            
    MyData data = new MyData()
    {
        value1 = "test",
        value2 = "1"                
    };
    context.MyData.Add(subject);
    context.SaveChanges();
}

This allows you to register your own, in memory, DbContext in your IoC.

Upgrade a .Net Framework WPF Application to .Net Core 3.x

One of the main things that was introduced as part of .Net Core 3 was the ability to upgrade your WinForms or WPF application to use Core. I have an example of such an application here. This was upgraded using the side-by-side method. If you are upgrading, you essentially have 2 options: Big Bang and Side-by-Side.

Big Bang Upgrade

This is the process by which you edit your csproj file on the framework app, and convert that file to use .Net Core 3. This means potentially less work, and is suited for a situation where you know that you will never need the original application again. For a personal project this may be fine, but realistically, it too big a risk for most companies, who would want the security of a gradual rollout, and the ability to fix bugs and make interim releases in the meantime.

Side-by-Side Upgrade

There are three ways to do this, but essentially, what you’re doing here is creating a second project that is running .Net Core. The benefit here is that the two applications can continue to run, and you can gradually discontinue the Framework app as and when you see fit. The work involved can be greater; but it varies depending on your methodology and requirements.

1. Copy and Paste

This is the simplest method: create a brand new .Net Core WPF application, and copy the entire contents of your directory across. You’ll need to convert the packages (covered later), but other than that, you should be good to go. Obviously, that depends hugely on the complexity of your project.

The downside here is that if you fix a bug, or make a change to one of these projects, you either need to do it twice, or have them get out of sync.

2. Two Projects One Directory

This seems to be Microsoft’s preferred approach: the idea being that you create a new .Net Core project inside the same directory as the .Net Framework app. This means that all the files just appear in your framework app. You’ll need to convert the packages, and exclude the csproj, and other .Net Framework specific files from the .Net Core project. This, and the following approach both give you the ability to change the code in both files simultaneously.

3. Two Projects Linked Files

This is my personal preference. You create your .Net Core project it a different directory and just link the files and directories. You get all the benefits of having the projects in the same directory, but without the hassle of trying to manage files being there that you don’t want.

The downside to this approach is that you need to include the files yourself.

Two Projects Linked Files Upgrade

The following steps, whilst for this particular approach, are not specific to it, unless stated.

1. Start by installing the UWP Workload in Visual Studio, assuming you haven’t already.

2. In your WPF Framework app, convert your packages.config, as that doesn’t exist in .Net Core:

3. Create a new project. Whilst this is specific to this approach, you will need a new project for any of the side-by-side methods.

For this method, the project needs to be in a different directory; my suggestion is that you put it inside the solution directory, under its own folder; for example, in the example above, you might create: WpfCoreApp1:

The directory structure might look like this:

4. Copy the package references from your packages.config directly into the new csproj file (following step 1, this should be a simple copy and paste).

5. Gut the new project by removing MainWindow.xaml and App.xaml (from here on in, all of the steps are specific to this method):

6. Edit the new csproj file. Depending on your directory structure, the following may differ, but broadly speaking you need the following code in your new csproj file:

<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop">
  <PropertyGroup>
    <OutputType>WinExe</OutputType>
    <TargetFramework>netcoreapp3.1</TargetFramework>
    <UseWPF>true</UseWPF>
  </PropertyGroup>
  <ItemGroup>
    <ApplicationDefinition Include="..\WpfApp1\App.xaml" Link="App.xaml">
      <Generator>MSBuild:Compile</Generator>
    </ApplicationDefinition>
    <Compile Include="..\WpfApp1\App.Xaml.cs" Link="App.Xaml.cs" />
    <Page Include="..\WpfApp1\MainWindow.xaml" Link="MainWindow.xaml">
      <Generator>MSBuild:Compile</Generator>
    </Page>
    <Compile Include="..\WpfApp1\MainWindow.Xaml.cs" Link="MainWindow.Xaml.cs" />
  </ItemGroup>
</Project>

If, for example, you were to have a directory that you wished to bring across, you could use something similar to the following:

  <ItemGroup>
    <Compile Include="..\WpfApp1\Helpers\**">
      <Link>Helpers\%(Filename)%(Extension)</Link>
    </Compile>
</ItemGroup>

That’s it – you should now be able to set your new .Net Core project as start-up and run it. The code is the same code as that running the Framework app, and should you change either, it will affect the other.

As an addendum, here is a little chart that I think should help you decide which approach to take for an upgrade:

MSix Packaging Project

Deployment is hard. Arguably, deployment is the reason the web apps have won over desktop apps. I’ve seen applications written as web apps that would have been easier, faster and more reliable written as desktop applications; however, the deployment problem is such a big factor, that these days, you have to have such a compelling reason to create a desktop application that they are almost unheard of.

With .Net Core 3, came a new attempt from Microsoft to make this whole deployment process easier. MSix allows you to take a Winforms or WPF application, and package it up in a very similar way to that used for UWP. You can then deploy these to the Windows Store; or, you can self host them, and deploy them from your own web-site.

Using an MSix does solve a number of deployment problems; for example, it pretty much picks up where ClickOnce left off. It guarantees that you’re not installing anything horrendous on your machine, because the packaging process sandboxes the application.

However, it is, by no means, perfect. This post covers how you can create a side-loaded deployment project. As a disclaimer, I used this post from James Montemagno as a base for this; however, there were a couple of parts where I got stuck with his post, so hopefully this fills in some gaps.

The post assumes that you have a WPF or WinForms application that you wish to deploy, and that you’re using Visual Studio.

1. Set runtime identifier:

<PropertyGroup>
    <!--Regular Settings-->
    <Platforms>AnyCPU;x64;x86</Platforms>
    <RuntimeIdentifiers>win-x64;win-x86</RuntimeIdentifiers>
  </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
    <PlatformTarget>x64</PlatformTarget>
  </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x86'">
    <PlatformTarget>x86</PlatformTarget>
  </PropertyGroup>

2. Create new Windows Application Packaging Project in your solution:

3. Name project:

4. Pick a minimum and target version:

5. Add reference to the project to deploy. That is, reference the project that you wish to be deployed by your packaging application:

6. Create App Packages (if you were publishing to the store, and you’d already created the app in the store, this is where you would diverge from these instructions):

7. Select Sideloading (if you were registering a store app for the first time, this is where you would diverge from these instructions):

8. Create a test certificate:

9. Set-up version and target platforms:

10. Create a distribution web site (https://docs.microsoft.com/en-us/windows/msix/app-installer/web-install-azure):

Contrary to what this screenshot may lead you to believe, don’t bother doing anything to it just yet – just create it so that you have the URL.

11. Deploy the app packages (you’ll need the URL from above):

12. Once you’ve published the app, install the test certificate to Local Machine -> Trusted Root Certificates. Obviously, were you to do this in real life, you’d need a kosher certificate.

13. FTP the entire contents of the AppPackages directory to the site. To find the FTP details, select the deployment centre:

From here, select FTP:

Once you select FTP, a Dashboard button will appear at the bottom of the screen, select that, and the FTP credentials should appear:

14. Create the default.html, as shown above; clearly, you’ll need your own link, but the format should be the same as mine.

That’s it – you should have a web-site with a link that allows you to install a desktop application locally (albeit you’ll need the certificate installed on the local machine).

References

https://montemagno.com/distributing-a-net-core-3-wpf-and-winforms-app-with-msix/

https://docs.microsoft.com/en-gb/windows/msix/app-installer/web-install-azure

https://www.advancedinstaller.com/install-test-certificate-from-msix.html

Policy Server with Asp.Net Core

It took me a while to collate my notes for this; hopefully this will be the first in a vaguely related series on using Policy Server.

Policy Server is a product from a company called Solliance, allowing you to control authorisation within your application (as opposed to authentication – the same company produces a sister product, called Identity Server). The idea is that, the user is authenticated to determine they are who they say they are, but then authorised to use certain parts of the application, or to do certain things.

This post discusses setting up the open source version of Policy Server. There is a commercial version of this.

The ReadMe on the above GitHub page is quite extensive, however, when I was trying this out, I had some areas that I thought weren’t too clear. This post will explain that to me in the future, should I become unclear again (a common occurrence when you get to a certain age!)

It’s worth bearing in mind that authorisation and authentication need to be done on the server; whilst you may choose to also do that on the client, the code on the client is, well, on the client! If you have some Javascript code sat in the web browser that’s stopping me from seeing the defence plans, then that’s just not secure enough.

Installing and Configuring Policy Server

The first step here is to install the NuGet package:

Install-Package PolicyServer.Local

Next, add Policy Server in startup.cs:

    public void ConfigureServices(IServiceCollection services)
    {
            services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);

            // In production, the React files will be served from this directory
            services.AddSpaStaticFiles(configuration =>
            {
                configuration.RootPath = "ClientApp/build";
            });

            services.AddPolicyServerClient(Configuration.GetSection("Policy"));
    }

Amazingly, at this stage, you’re basically done. The next stage is to add the rules into the appsettings.json. There is an example on the page above, but here’s a much simpler one:

  "Policy": {
    "roles": [
      {
        "name": "salesmanager",
        "subjects": [ "1" ]
      },
      {
        "name": "driver",
        "subjects": [ "2" ]
      }
    ],
    "permissions": [
      {
        "name": "ViewRoutes",
        "roles": [ "driver" ]
      },
      {
        "name": "CreateSalesOrder",
        "roles": [ "salesmanager" ]
      },
      {
        "name": "Timesheet",
        "roles": [ "salesmanager", "driver" ]
      }
    ]
  }

This is, in fact, the part that I didn’t feel was clear enough in the GitHub readme. What, exactly, are “subjects”, and how do I associate them to anything useful? I’ll come back to this shortly, but first, let’s see the rules that we have set up here:

The Sales Manager can create a sales order, and complete their timesheet.

The Driver can view routes and complete their timesheet.

The Sales Manager can not view routes; nor can the driver create a sales order.

Add Policy Server to a Controller

The next stage is to inject Policy Server into our controller. For the home controller, all you need to do is inject IPolicyServerRuntimeClient:

        private readonly IPolicyServerRuntimeClient _policyServerRuntimeClient;

        public HomeController(IPolicyServerRuntimeClient policyServerRuntimeClient)
        {
            _policyServerRuntimeClient = policyServerRuntimeClient;
        }

This can now be used anywhere in the controller. However, for testing purposes, let’s just circle back round to the subject question. The subject is the identity of the user. You may have noticed that I’ve started this post with PolicyServer, so I don’t have any authentication in place here. However, for testing purposes, we can force our user to a specific identity.

Let’s override the Index method of the HomeController:

        public async Task<IActionResult> Index()
        {
            var identity = new ClaimsIdentity(new Claim[]
            {
                new Claim("sub", "2"),
                new Claim("name", "TestUser")
            }, "test");

            User.AddIdentity(identity);

            var homeViewModel = await SecureViewModel();
            return View(homeViewModel);
        }

We’ll come back to the ViewModel business shortly, but let’s focus on the new ClaimsIdentity; notice that the “sub” (or subject) refers to the Subject ID of the driver above. So what we’ve done is created a driver!

The SecureViewModel method returns a ViewModel (which, as you can see, is passed to the view); let’s see what that might look like:

        public async Task<HomeViewModel> SecureViewModel()
        {
            var homeViewModel = new HomeViewModel();            
            homeViewModel.ViewRoutes = await _policyServerRuntimeClient.HasPermissionAsync(User, "ViewRoutes");
            homeViewModel.CreateSalesOrder = await _policyServerRuntimeClient.HasPermissionAsync(User, "CreateSalesOrder");
            homeViewModel.Timesheet = await _policyServerRuntimeClient.HasPermissionAsync(User, "Timesheet");

            return homeViewModel;
        }

You can play about with how this works by altering the “sub” claim.

How does this fit into Identity Server (or an identity server such as Azure B2C)?

The identity result of the authentication, should map to the “sub” claim. In some cases, it’s “nameidentitifier”. Once you’ve captured that, you’ll need to store that reference against the user record.

That’s all very well for testing, but when I use this for real, I want to plug my data in from a database

The first thing you’ll need to do is to ‘flatten’ the data. this StackOverflow question was about the best resource I could find for this, and it gets you about 90% of the way there. I hope to come back to that, and linking this in with Asp.Net Core Policies in the next post.

References

https://auth0.com/blog/role-based-access-control-rbac-and-react-apps/

https://github.com/IdentityServer/IdentityServer4/issues/2968

https://stackoverflow.com/questions/55450393/persist-entitys-in-database-instead-of-appsettings-json

https://stackoverflow.com/questions/55450393/persist-entitys-in-database-instead-of-appsettings-json

Simple binding in Blazor

A while back, I asked on Dev about moving my blog from WordPress to … well, not WordPress anymore. My main critera was to Markdown instead of whatever the WordPress format is called. The main issue being that you need to replace the code tags.

I resolved to create a tool to do it for me. I’m doing so in Blazor.

The first task was to create a very simple data binding scenario, so I could handle all the view logic in the View Model. I’ve previously written about using view models. This post covers the scenario where you want to bind text boxes and a button.

Let’s see the View Model first:

    public class MainViewModel
    {
        public string WpText { get; set; }
        public string MdText { get; set; }

        public void ConvertText()
        {
            MdText = $"Converted {WpText}";
        }
    }

Okay, so we have two strings, and a method that populates the second string with the first. As per the linked post, the plumbing for the view model is very simple; first, it’s registered in ConfigureServices:

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddTransient<MainViewModel, MainViewModel>();
        }

Then it’s injected into the Razor view:

@page "/"
@inject ViewModels.MainViewModel MainViewModel

In fact, the next part is much simpler than I thought it would be. To bind a view model property to the view, you just use the syntax bind:

<div class="container">
    <div class="row">
        <div class="form-group col-md-6">
            <label for="WpText">Wordpress Text</label>
            <input type="text" @[email protected] class="form-control" id="WpText" name="WpText"/>
        </div>

        <div class="form-group col-md-6">
            <label for="MdText">Markdown</label>
            <input type="text" @[email protected] class="form-control" id="MdText" name="MdText"/>
        </div>
    </div>
    <div class="row">
        <div class="form-group col-md-12">
            <input type="button" value="Convert" @[email protected](() => MainViewModel.ConvertText()) class="form-control" id="Submit" />
        </div>
    </div>
</div>

Just to point out one thing that tripped me up before I leave this: the event handlers that relate to Blazor must be prefixed with an at symbol (@).

References

https://www.nativoplus.studio/blog/blazor-data-binding-2/

Using Blazor Components

Imagine that you’re writing a Blazor application – maybe it’s similar to this one. Now, imagine that you have a large chunk of HTML in your main view. You might think: I wish I was using React, then I could separate this into its own component.

You can also do this in Blazor. Here’s how.

Components in Blazor

Let’s start with moving your code. The first step is to cut your HTML and paste it into a new Razor Component:

The format of your new component, from scratch, will be:

&lt;h3&gt;Component Name&lt;/h3&gt;

@code {

}

Your existing code should go beneath, or instead of:

&lt;h3&gt;Component Name&lt;h3&gt;

Parameters

The @code section allows you to put all kinds of crazy C# code in a code behind type model – so you probably don’t want to use that, except for passing parameters; for example:

@code {
    [Parameter]
    private string MyParameter { get; set; }
}

This allows you to pass a string into your component; for example (in your main view):

&lt;MyComponent MyParameter=&quot;test&quot; /&gt;

Complex Parameters

So far so good. But what if you need a complex type? You could, for example, pass a View Model into your component:

[Parameter]
private MyViewModel MyViewModel { get; set; }

You can pass this into the component as though it were a primitive type:

&lt;MyComponent MyViewModel=&quot;@MyViewModel&quot; /&gt;

This means that you can lift and shift the code with no changes.

Using External Namespaces

As with standard C#, you can access anything within the current namespace. Should you need any classes that are not in your current namespace, you can declare them at the top of the file, like this:

@using MVVMShirt

&lt;h3&gt;My Component&lt;/h3&gt;

Summary

Blazor is still in its infancy, but hopefully, adding actual code to these @code sections will become as frowned upon as code-behind.

UWP using Unity and EF Core and Sqlite

If you intent to use IoC with a UWP application, there are a lot of options. Most of them come with MVVM packages, like MVVM Cross. These are excellent packages – I’ve used MVVM Cross and MVVM Light myself and can highly recommend them.

However, if you didn’t want all that baggage, how would you implement a very simple IoC system in UWP?

In this example, I’m using Unity, however, I believe this will work for any IoC container. I’m also using the IoC container to resolve a View Model – but you don’t need to use View Models (although IMHO, it makes your life so much easier.)

Secondly, I’ll be showing how to use Ef Core with your UWP app. This sounds very trivial, but there’s a bit of fiddling about to get it to work.

Entity Framework Core Set-up

In my project, I’ve separated the data access layer, but you don’t need to do that. Start by creating a data context:

public class MyDbContext : DbContext
{
    public DbSet<Data> MyData { get; set; }
 
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder.UseSqlite("Data Source=mydata.db");
    }
}

You’ll need the following packages:

Install-Package Microsoft.EntityFrameworkCore
Install-Package Microsoft.EntityFrameworkCore.Design
Install-Package Microsoft.EntityFrameworkCore.Tools
Install-Package Microsoft.EntityFrameworkCore.Sqlite

You’ll also need to create a console application – why? Because you can’t use any of the EF tools with UWP! If you set your UWP app as the start-up and create your migration, you’ll get this error:

Startup project ‘SendMessage.UWP’ is a Universal Windows Platform app. This version of the Entity Framework Core Package Manager Console Tools doesn’t support this type of project.

Set the console app as startup and add the migration:

Add-Migration "InitialDbCreate"

Don’t worry about updating the DB, we’ll get the app to do that (it just can’t use the tools, but it can perform a migration.)

UWP

From a new, blank, UWP app; in app.xaml.cs:

sealed partial class App : Application
{
    public static IUnityContainer Container { get; set; } = new UnityContainer();
 
    /// <summary>
    /// Initializes the singleton application object.  This is the first line of authored code
    /// executed, and as such is the logical equivalent of main() or WinMain().
    /// </summary>
    public App()
    {
        this.InitializeComponent();
        this.Suspending += OnSuspending;
 
        using (var db = new MyDbContext())
        {
            db.Database.Migrate();
        }
        
        Container.RegisterType<MainViewModel>();
    }

We’re creating a static UnityContainer in App.Xaml.cs. Register the type (in this case a MainViewModel, but it could as easily be an interface).

The next step is resolving the interface. Unfortunately, because of the way that the UWP navigate works, Unity won’t perform constructor injection for us. A little trick around this is to create a parameterless constructor and have that call the injected constructor. It’s not quite constructor injection, but semantically it’s the same thing. Here’s the code from my MainPage.xaml.cs:

public sealed partial class MainPage : Page
{
    public MainPage() : this(App.Container.Resolve<MainViewModel>()) { }
 
    public MainPage(MainViewModel mainViewModel)
    {
        this.InitializeComponent();
 
        this.DataContext = mainViewModel;
    }
}

That’s pretty much it; you can run this, and it’ll migrate the data, and resolve the dependency.

References

https://docs.microsoft.com/en-us/ef/core/get-started/uwp/getting-started

Adding Logging to Client Side Blazor

Whilst there are some pre-built libraries for this, I seemed to be getting Mono linking errors. What finally worked for me was to install the pre-release versions of:

Install-Package Microsoft.Extensions.Logging -Version 3.0.0-preview6.19304.6
Install-Package Microsoft.Extensions.Logging.Console -Version 3.0.0-preview6.19304.6

Now, in your View Model, accept the Logger:

public MyViewModel(ILogger<MyViewModel> logger)

Then you can log as normal:

_logger.LogInformation("Hello, here's a log message");

You should now see the debug message in the F12 console.

You might be wondering why you don’t need to explicitly inject the logging capability; the reason is that:

BlazorWebAssemblyHost.CreateDefaultBuilder()            

Does that for you.