Blazor WebAssembly 3.2.0 Preview 1 release now available

Daniel Roth

Today we released a new preview update for Blazor WebAssembly with a bunch of great new features and improvements.

Here’s what’s new in this release:

  • Version updated to 3.2
  • Simplified startup
  • Download size improvements
  • Support for .NET SignalR client

Get started

To get started with Blazor WebAssembly 3.2.0 Preview 1 install the .NET Core 3.1 SDK and then run the following command:

dotnet new -i Microsoft.AspNetCore.Blazor.Templates::3.2.0-preview1.20073.1

That’s it! You can find additional docs and samples on https://blazor.net.

Upgrade an existing project

To upgrade an existing Blazor WebAssembly app from 3.1.0 Preview 4 to 3.2.0 Preview 1:

  • Update all Microsoft.AspNetCore.Blazor.* package references to 3.2.0-preview1.20073.1.
  • In Program.cs in the Blazor WebAssembly client project replace BlazorWebAssemblyHost.CreateDefaultBuilder() with WebAssemblyHostBuilder.CreateDefault().
  • Replace IWebAssemblyHost with WebAssemblyHost.
  • Replace IWebAssemblyHostBuilder with WebAssemblyHostBuilder.
  • Move the root component registrations in the Blazor WebAssembly client project from Startup.Configure to Program.cs by calling builder.RootComponents.Add<TComponent>(string selector).
  • Move the configured services in the Blazor WebAssembly client project from Startup.ConfigureServices to Program.cs by adding services to the builder.Services collection.
  • Remove Startup.cs from the Blazor WebAssembly client project.
  • If you’re hosting Blazor WebAssembly with ASP.NET Core, in your Server project replace the call to app.UseClientSideBlazorFiles<Client.Startup>(...) with app.UseClientSideBlazorFiles<Client.Program>(...).

Version updated to 3.2

In this release we updated the versions of the Blazor WebAssembly packages to 3.2 to distinguish them from the recent .NET Core 3.1 Long Term Support (LTS) release. There is no corresponding .NET Core 3.2 release – the new 3.2 version applies only to Blazor WebAssembly. Blazor WebAssembly is currently based on .NET Core 3.1, but it doesn’t inherit the .NET Core 3.1 LTS status. Instead, the initial release of Blazor WebAssembly scheduled for May of this year will be a Current release, which “are supported for three months after a subsequent Current or LTS release” as described in the .NET Core support policy. The next planned release for Blazor WebAssembly after the 3.2 release in May will be with .NET 5. This means that once .NET 5 ships you’ll need to update your Blazor WebAssembly apps to .NET 5 to stay in support.

Simplified startup

We’ve simplified the startup and hosting APIs for Blazor WebAssembly in this release. Originally the startup and hosting APIs for Blazor WebAssembly were designed to mirror the patterns used by ASP.NET Core, but not all of the concepts were relevant. The updated APIs also enable some new scenarios.

Here’s what the new startup code in Program.cs looks like:

public class Program
{
    public static async Task Main(string[] args)
    {
        var builder = WebAssemblyHostBuilder.CreateDefault(args);
        builder.RootComponents.Add<App>("app");

        await builder.Build().RunAsync();
    }
}

Blazor WebAssembly apps now support async Main methods for the app entry point.

To a create a default host builder, call WebAssemblyHostBuilder.CreateDefault(). Root components and services are configured using the builder; a separate Startup class is no longer needed.

The following example adds a WeatherService so it’s available through dependency injection (DI):

public class Program
{
    public static async Task Main(string[] args)
    {
        var builder = WebAssemblyHostBuilder.CreateDefault(args);
        builder.Services.AddSingleton<WeatherService>();
        builder.RootComponents.Add<App>("app");

        await builder.Build().RunAsync();
    }
}

Once the host is built, you can access services from the root DI scope before any components have been rendered. This can be useful if you need to run some initialization logic before anything is rendered:

public class Program
{
    public static async Task Main(string[] args)
    {
        var builder = WebAssemblyHostBuilder.CreateDefault(args);
        builder.Services.AddSingleton<WeatherService>();
        builder.RootComponents.Add<App>("app");

        var host = builder.Build();

        var weatherService = host.Services.GetRequiredService<WeatherService>();
        await weatherService.InitializeWeatherAsync();

        await host.RunAsync();
    }
}

The host also now provides a central configuration instance for the app. The configuration isn’t populated with any data by default, but you can populate it as required in your app.

public class Program
{
    public static async Task Main(string[] args)
    {
        var builder = WebAssemblyHostBuilder.CreateDefault(args);
        builder.Services.AddSingleton<WeatherService>();
        builder.RootComponents.Add<App>("app");

        var host = builder.Build();

        var weatherService = host.Services.GetRequiredService<WeatherService>();
        await weatherService.InitializeWeatherAsync(host.Configuration["WeatherServiceUrl"]);

        await host.RunAsync();
    }
}

Download size improvements

Blazor WebAssembly apps run the .NET IL linker on every build to trim unused code from the app. In previous releases only the core framework libraries were trimmed. Starting with this release the Blazor framework assemblies are trimmed as well resulting in a modest size reduction of about 100 KB transferred. As before, if you ever need to turn off linking, add the <BlazorLinkOnBuild>false</BlazorLinkOnBuild> property to your project file.

Support for the .NET SignalR client

You can now use SignalR from your Blazor WebAssembly apps using the .NET SignalR client.

To give SignalR a try from your Blazor WebAssembly app:

  1. Create an ASP.NET Core hosted Blazor WebAssembly app.

    dotnet new blazorwasm -ho -o BlazorSignalRApp
    
  2. Add the ASP.NET Core SignalR Client package to the Client project.

    cd BlazorSignalRApp
    dotnet add Client package Microsoft.AspNetCore.SignalR.Client
    
  3. In the Server project, add the following Hub/ChatHub.cs class.

    using System.Threading.Tasks;
    using Microsoft.AspNetCore.SignalR;
    
    namespace BlazorSignalRApp.Server.Hubs
    {
        public class ChatHub : Hub
        {
            public async Task SendMessage(string user, string message)
            {
                await Clients.All.SendAsync("ReceiveMessage", user, message);
            }
        }
    }
    
  4. In the Server project, add the SignalR services in the Startup.ConfigureServices method.

    services.AddSignalR();
    
  5. Also add an endpoint for the ChatHub in Startup.Configure.

    .UseEndpoints(endpoints =>
    {
        endpoints.MapDefaultControllerRoute();
        endpoints.MapHub<ChatHub>("/chatHub");
        endpoints.MapFallbackToClientSideBlazor<Client.Program>("index.html");
    });
    
  6. Update Pages/Index.razor in the Client project with the following markup.

    @using Microsoft.AspNetCore.SignalR.Client
    @page "/"
    @inject NavigationManager NavigationManager
    
    <div>
        <label for="userInput">User:</label>
        <input id="userInput" @bind="userInput" />
    </div>
    <div class="form-group">
        <label for="messageInput">Message:</label>
        <input id="messageInput" @bind="messageInput" />
    </div>
    <button @onclick="Send" disabled="@(!IsConnected)">Send Message</button>
    
    <hr />
    
    <ul id="messagesList">
        @foreach (var message in messages)
        {
            <li>@message</li>
        }
    </ul>
    
    @code {
        HubConnection hubConnection;
        List<string> messages = new List<string>();
        string userInput;
        string messageInput;
    
        protected override async Task OnInitializedAsync()
        {
            hubConnection = new HubConnectionBuilder()
                .WithUrl(NavigationManager.ToAbsoluteUri("/chatHub"))
                .Build();
    
            hubConnection.On<string, string>("ReceiveMessage", (user, message) =>
            {
                var encodedMsg = user + " says " + message;
                messages.Add(encodedMsg);
                StateHasChanged();
            });
    
            await hubConnection.StartAsync();
        }
    
        Task Send() => hubConnection.SendAsync("SendMessage", userInput, messageInput);
    
        public bool IsConnected => hubConnection.State == HubConnectionState.Connected;
    }
    
  7. Build and run the Server project

    cd Server
    dotnet run
    
  8. Open the app in two separate browser tabs to chat in real time over SignalR.

Known issues

Below is the list of known issues with this release that will get addressed in a future update.

  • Running a new ASP.NET Core hosted Blazor WebAssembly app from the command-line results in the warning: CSC : warning CS8034: Unable to load Analyzer assembly C:\Users\user\.nuget\packages\microsoft.aspnetcore.components.analyzers\3.1.0\analyzers\dotnet\cs\Microsoft.AspNetCore.Components.Analyzers.dll : Assembly with same name is already loaded.

    • Workaround: This warning can be ignored or suppressed using the <DisableImplicitComponentsAnalyzers>true</DisableImplicitComponentsAnalyzers> MSBuild property.

Feedback

We hope you enjoy the new features in this preview release of Blazor WebAssembly! Please let us know what you think by filing issues on GitHub.

Thanks for trying out Blazor!

108 comments

Discussion is closed. Login to edit/delete existing comments.

  • Lucas Elzinga 0

    I have the .NET Core 3.1 SDK installed (along with a few older ones) yet when I run the command dotnet new -i Microsoft.AspNetCore.Blazor.Templates::3.2.0-preview1.20073.1 it doesn’t install and spits out the dotnet help message. When I attempt to make a new Blazor Web Assembly through VS above the “Back” and ‘Create” button it states the Source is CLI V3.1.101.

    • Daniel RothMicrosoft employee 0

      Hi Lucas,

      Probably best if you open a GitHub issue at https://github.com/dotnet/aspnetcore/issues and we’ll take a look.

      Some things to verify:
      dotnet --version should show 3.1.100 or later
      – After installing the Blazor WebAssembly template pack, the blazorwasm template should be listed when you run dotnet new -h.
      – Your VS version should be VS 2019 16.4 or later.

      • Sandro Rizzetto 0

        Same here…
        Is there a method to know the version of the installed WASM template ? (to be sure it is the new 3.2 one)

        • Daniel RothMicrosoft employee 0

          If you run dotnet new -u you can see the list of template packs that have been installed. Look for Microsoft.AspNetCore.Blazor.Templates in the list and check the version.

  • Mitchell Currey 0

    Thanks for the detailed post!

    I just have one question..
    I am attempting to apply auth to the SignalR requests, but when setting up my connection hub in the Client by using the AccessTokenProvider (as documented here), the access token I am adding is not being sent in the query params as would be expected.

    Is this a bug, or is there something I am missing that is required in Blazor by following this documentation?

    Thanks again!

    • Andrew Stanton-NurseMicrosoft employee 0

      That’s a bug! Please file an issue at https://github.com/dotnet/aspnetcore .

      The .NET client doesn’t do anything special for WebAssembly (yet) but the browser WebSocket APIs don’t support headers so the client tries to use a header and it’s silently ignored. We’ll have to do some special casing here and will look at getting that done in time for an upcoming preview!

  • Tyler Brinkley 0

    To note, the last two blog entries did not show up on the RSS feed.

    Update – It appears this has been corrected.

  • Daniel Matthews 0

    Is it safe to say that this preview is more aligned with how the release will look when pushed in the May release? Are there going to be any other major breaking changes coming down the pipeline?

    • Daniel RothMicrosoft employee 0

      We’re still listening to feedback and making changes accordingly, so yes, it’s still possible that there will be breaking changes while Blazor WebAssembly is in preview. Note that these changes are limited to Blazor WebAssembly specific functionality, like the WebAssembly host. The component model shipped already with .NET Core 3.1 and is shared with Blazor Server, so it has a very high compatibility guarantee at this point.

  • Mihai Dumitru 0

    How we can now add authentication and authorization? How is the middleware configuration being done now?

      • Lakshmi Narasimham Addepalli 0

        Do we have a ballpark date when the authentication and authorization support for Blazor WASM will be available.

        • Daniel RothMicrosoft employee 0

          Support for authentication is expected to be available with our next Blazor WebAssembly update in early March.

  • Enrique Ledesma 0

    Hi,
    This release Is incluyes un the new Visual Studio 2019 16.4.4 versión?

    • Daniel RothMicrosoft employee 0

      No, you need to separately install the Blazor WebAssembly template to use this release. Blazor Server, however, ships with .NET Core 3.1, which is included with VS 2019 16.4 and later.

  • ysmoradi 0

    How can I use Autofac.Extensions.DependencyInjection in blazor 3.2? My approach was working fine with older releases, but now there is no doc to show me what to do. Thanks in advance.

  • Guillaume ZAHRA 0

    Sorry if this is duplicate, but i didn’t find any answer to this question here, and even by reading aspnet issues on GitHub, nor the documentation:

    Does the new Microsoft.AspNetCore.SignalR.Client package support connections to a regular WebSocket server (not managed by a SignalR server hub) ?
    I knew there was some limitations some times ago about using Websockets API on Blazor WASM.

    Does Microsoft.AspNetCore.SignalR.Client issue this requirement ? Otherwise, what is the alternative ? Does this kind of scenario is still impossible from C# in Blazor WASM ?

    I would like to refactor any C# to Javascript code on my project for Websocket usage.

    Regards,
    Guillaume

    • Daniel RothMicrosoft employee 0

      Hi Guillaume,

      The .NET SignalR client is specific to SignalR scenarios. If you just want to connect over a WebSocket you should now be able to do that using the normal ClientWebSocket API in .NET: https://docs.microsoft.com/dotnet/api/system.net.websockets.clientwebsocket. Let us know if you run into any issues with that.

      I hope this helps!

      Daniel Roth

      • Guillaume ZAHRA 0

        Thanks for your answer Daniel ! This is perfect !

        I will make a test on my project. If i encounter any unexpected issue about this, i will open an issue on GitHub.

        Guillaume ZAHRA

  • Steven Cramer 0

    Thanks again Dan,

    A few other upgrade things. Mostly occurring in test projects if you are using WebApplicationFactory.

    Replace IWebAssemblyHost with WebAssemblyHost.
    BlazorWebAssemblyHost becomes WebAssemblyHostBuilder
    Replace IWebAssemblyHostBuilder with WebAssemblyHostBuilder.

    Example of some of the changes I made in my TestFixture.

        private readonly IWebAssemblyHostBuilder WebAssemblyHostBuilder;
    
        public TestFixture(WebApplicationFactory<Server.Startup> aWebApplicationFactory)
        {
          WebApplicationFactory = aWebApplicationFactory;
          WebAssemblyHostBuilder = BlazorWebAssemblyHost.CreateDefaultBuilder()
              .ConfigureServices(ConfigureServices);
        }
    

    updated to

        private readonly WebAssemblyHostBuilder WebAssemblyHostBuilder;
    
        public TestFixture(WebApplicationFactory<Server.Startup> aWebApplicationFactory)
        {
          WebApplicationFactory = aWebApplicationFactory;
          WebAssemblyHostBuilder = WebAssemblyHostBuilder.CreateDefault();
          ConfigureServices(WebAssemblyHostBuilder.Services);
        }
    
    • Daniel RothMicrosoft employee 0

      Thanks Steve! I’ve made some updates to the post based on your feedback.

  • Hasse Jansson 0

    Hi Daniel and all other!

    Nice work with blazor, awesome!

    I added this chathub to my app and deployed to azure, but the chat page is crashing. It works locally..

    blazor.webassembly.js:1 WASM: Unhandled exception rendering component:
    l.printErr @ blazor.webassembly.js:1
    put_char @ dotnet.js:1
    write @ dotnet.js:1
    write @ dotnet.js:1
    ___syscall4 @ dotnet.js:1
    (anonymous) @ dotnet.wasm:1
    (anonymous) @ dotnet.wasm:1
    (anonymous) @ dotnet.wasm:1
    (anonymous) @ dotnet.wasm:1
    (anonymous) @ dotnet.wasm:1
    Module._mono_wasm_invoke_method @ dotnet.js:1
    call_method @ dotnet.js:1
    (anonymous) @ dotnet.js:1
    callEntryPoint @ blazor.webassembly.js:1
    (anonymous) @ blazor.webassembly.js:1
    (anonymous) @ blazor.webassembly.js:1
    (anonymous) @ blazor.webassembly.js:1
    i @ blazor.webassembly.js:1
    Promise.then (async)
    l @ blazor.webassembly.js:1
    i @ blazor.webassembly.js:1
    Promise.then (async)
    l @ blazor.webassembly.js:1
    (anonymous) @ blazor.webassembly.js:1
    r @ blazor.webassembly.js:1
    f @ blazor.webassembly.js:1
    (anonymous) @ blazor.webassembly.js:1
    n @ blazor.webassembly.js:1
    (anonymous) @ blazor.webassembly.js:1
    (anonymous) @ blazor.webassembly.js:1
    Show 8 more frames from Library code
    blazor.webassembly.js:1 WASM: System.InvalidOperationException: A suitable constructor for type ‘Microsoft.AspNetCore.Http.Connections.Client.HttpConnectionFactory’ could not be located. Ensure the type is concrete and services are registered for all parameters of a public constructor.
    l.printErr @ blazor.webassembly.js:1
    put_char @ dotnet.js:1
    write @ dotnet.js:1
    write @ dotnet.js:1
    ___syscall4 @ dotnet.js:1
    (anonymous) @ dotnet.wasm:1
    (anonymous) @ dotnet.wasm:1
    Module._mono_wasm_invoke_method @ dotnet.js:1
    call_method @ dotnet.js:1
    (anonymous) @ dotnet.js:1
    callEntryPoint @ blazor.webassembly.js:1

    i @ blazor.webassembly.js:1
    Promise.then (async)
    l @ blazor.webassembly.js:1
    i @ blazor.webassembly.js:1
    Promise.then (async)
    l @ blazor.webassembly.js:1
    (anonymous) @ blazor.webassembly.js:1
    r @ blazor.webassembly.js:1
    f @ blazor.webassembly.js:1
    (anonymous) @ blazor.webassembly.js:1
    n @ blazor.webassembly.js:1
    (anonymous) @ blazor.webassembly.js:1
    (anonymous) @ blazor.webassembly.js:1
    Show 8 more frames from Library code
    blazor.webassembly.js:1 WASM: at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.CreateConstructorCallSite (Microsoft.Extensions.DependencyInjection.ServiceLookup.ResultCache lifetime, System.Type serviceType, System.Type implementationType, Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteChain callSiteChain) in :0
    l.printErr @ blazor.webassembly.js:1
    put_char @ dotnet.js:1
    write @ dotnet.js:1
    write @ dotnet.js:1
    ___syscall4 @ dotnet.js:1
    (anonymous) @ dotnet.wasm:1
    Module._mono_wasm_invoke_method @ dotnet.js:1
    call_method @ dotnet.js:1
    (anonymous) @ dotnet.js:1
    callEntryPoint @ blazor.webassembly.js:1
    (anonymous) @ blazor.webassembly.js:1
    (anonymous) @ blazor.webassembly.js:1
    (anonymous) @ blazor.webassembly.js:1
    i @ blazor.webassembly.js:1
    Promise.then (async)
    l @ blazor.webassembly.js:1
    i @ blazor.webassembly.js:1
    Promise.then (async)
    l @ blazor.webassembly.js:1
    (anonymous) @ blazor.webassembly.js:1
    r @ blazor.webassembly.js:1
    f @ blazor.webassembly.js:1
    (anonymous) @ blazor.webassembly.js:1
    n @ blazor.webassembly.js:1
    (anonymous) @ blazor.webassembly.js:1
    (anonymous) @ blazor.webassembly.js:1
    Show 8 more frames from Library code
    blazor.webassembly.js:1 WASM: at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.TryCreateExact (Microsoft.Extensions.DependencyInjection.ServiceDescriptor descriptor, System.Type serviceType, Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteChain callSiteChain, System.Int32 slot) in :0
    l.printErr @ blazor.webassembly.js:1
    put_char @ dotnet.js:1
    write @ dotnet.js:1
    write @ dotnet.js:1
    ___syscall4 @ dotnet.js:1
    (anonymous) @ dotnet.wasm:1
    Module._mono_wasm_invoke_method @ dotnet.js:1
    call_method @ dotnet.js:1
    (anonymous) @ dotnet.js:1
    callEntryPoint @ blazor.webassembly.js:1
    (anonymous) @ blazor.webassembly.js:1
    (anonymous) @ blazor.webassembly.js:1
    (anonymous) @ blazor.webassembly.js:1
    i @ blazor.webassembly.js:1
    Promise.then (async)
    l @ blazor.webassembly.js:1
    i @ blazor.webassembly.js:1
    Promise.then (async)
    l @ blazor.webassembly.js:1
    (anonymous) @ blazor.webassembly.js:1
    r @ blazor.webassembly.js:1
    f @ blazor.webassembly.js:1
    (anonymous) @ blazor.webassembly.js:1
    n @ blazor.webassembly.js:1
    (anonymous) @ blazor.webassembly.js:1
    (anonymous) @ blazor.webassembly.js:1
    Show 8 more frames from Library code
    blazor.webassembly.js:1 WASM: at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.TryCreateExact (System.Type serviceType, Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteChain callSiteChain) in :0
    l.printErr @ blazor.webassembly.js:1
    put_char @ dotnet.js:1
    write @ dotnet.js:1
    write @ dotnet.js:1
    ___syscall4 @ dotnet.js:1
    (anonymous) @ dotnet.wasm:1
    Module._mono_wasm_invoke_method @ dotnet.js:1
    call_method @ dotnet.js:1
    (anonymous) @ dotnet.js:1
    callEntryPoint @ blazor.webassembly.js:1
    (anonymous) @ blazor.webassembly.js:1
    (anonymous) @ blazor.webassembly.js:1
    (anonymous) @ blazor.webassembly.js:1
    i @ blazor.webassembly.js:1
    Promise.then (async)
    l @ blazor.webassembly.js:1
    i @ blazor.webassembly.js:1
    Promise.then (async)
    l @ blazor.webassembly.js:1
    (anonymous) @ blazor.webassembly.js:1
    r @ blazor.webassembly.js:1
    f @ blazor.webassembly.js:1
    (anonymous) @ blazor.webassembly.js:1
    n @ blazor.webassembly.js:1
    (anonymous) @ blazor.webassembly.js:1
    (anonymous) @ blazor.webassembly.js:1
    Show 8 more frames from Library code
    blazor.webassembly.js:1 WASM: at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.CreateCallSite (System.Type serviceType, Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteChain callSiteChain) in :0

    • Daniel RothMicrosoft employee 0

      It looks like you’re somehow ending up with an older version of the Blazor WebAssembly runtime when you publish the app. Maybe you have some files cached locally that are out of date? If you continue to have this issue please open an issue on GitHub at https://github.com/aspnet/aspnetcore/issues and we’ll take a look.

      • Hasse Jansson 0

        Ok, thx for quick answer, how can I refresh the cache? If I right click refresh icon in the new edge browser and choose ’empty cache and hard refresh’, but that doesnt seem to work for this issue, if I open browser in private mode locally, then it works. But private mode doesn’t work on the published app running in azure.

        Do I need to use azure signalr service for this to work? I added that dependency when I deployed from visual studio, but I did not do that at first, I haven’t seen that in any demo examples?

        • Worrapong Chuiploy 0

          Try to delete bin and obj folder under Client then rebuild.

        • Daniel RothMicrosoft employee 0

          OK, please open a GitHub issue then and we’ll try to help you figure out what’s going on.

Feedback usabilla icon