Blazor WebAssembly 3.2.0 Preview 5 release now available

Daniel Roth

A new preview update of Blazor WebAssembly is now available! Here’s what’s new in this release:

  • Read configuration during startup
  • Configure HTTP fetch request options
  • Honor existing web.config when publishing
  • Attach tokens to outgoing requests
  • Support for time zones

Get started

To get started with Blazor WebAssembly 3.2.0 Preview 5 install the latest .NET Core 3.1 SDK.

NOTE: Version 3.1.201 or later of the .NET Core SDK is required to use this Blazor WebAssembly release! Make sure you have the correct .NET Core SDK version by running dotnet --version from a command prompt.

Once you have the appropriate .NET Core SDK installed, run the following command to install the updated Blazor WebAssembly template:

dotnet new -i Microsoft.AspNetCore.Components.WebAssembly.Templates::3.2.0-preview5.20216.8

If you’re on Windows using Visual Studio, we recommend installing the latest preview of Visual Studio 2019 16.6. For this preview, you should still install the template from the command-line as described above to ensure that the Blazor WebAssembly template shows up correctly in Visual Studio and on the command-line.

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.2.0 Preview 4 to 3.2.0 Preview 5:

  • Update all Microsoft.AspNetCore.Components.WebAssembly.* package references to version 3.2.0-preview5.20216.8.
  • Update any Microsoft.AspNetCore.Components.WebAssembly.Runtime package references to version 3.2.0-preview5.20216.1.
  • Remove any calls to set WebAssemblyHttpMessageHandlerOptions.DefaultCredentials and instead call SetBrowserRequestCredentials on individual requests (see “Configure HTTP fetch request options” section below).
  • Remove the redirect parameter from calls to TryGetToken on AccessTokenResult.

You’re all set!

Read configuration during startup

Configuration data is now available during app startup in Program.Main using the Configuration property on WebAssemblyHostBuilder. This property can now be used both to add configuration sources and to access the current configuration data.

You can see this feature in action in the project templates when you enable authentication with Azure AD, Azure AD B2C, or an OpenID Connect provider of your choice. The authentication settings are stored in appsettings.json and then read from configuration when the app starts up:

Program.cs

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

        builder.Services.AddTransient(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });

        builder.Services.AddOidcAuthentication(options =>
        {
            // Configure your authentication provider options here.
            // For more information, see https://aka.ms/blazor-standalone-auth
            builder.Configuration.Bind("Local", options.ProviderOptions);
        });

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

appsettings.json

{
  "Local": {
    "Authority": "https:login.microsoftonline.com/",
    "ClientId": "33333333-3333-3333-33333333333333333"
  }
}

Configure HTTP fetch request options

HTTP requests issued from a Blazor WebAssembly app using HttpClient are handled using the browser fetch API. In this release, we’ve added a set of extension methods for HttpRequestMessage that configure various fetch related options. These extension methods live in the Microsoft.AspNetCore.Components.WebAssembly.Http namespace:

HttpRequestMessage extension method Fetch request property
SetBrowserRequestCredentials credentials
SetBrowserRequestCache cache
SetBrowserRequestMode mode
SetBrowserRequestIntegrity integrity

You can set additional options using the more generic SetBrowserRequestOption extension method.

The HTTP response is typically buffered in a Blazor WebAssembly app to enable support for sync reads on the response content. To enable support for response streaming, use the SetBrowserResponseStreamingEnabled extension method on the request.

Honor existing web.config when publishing

When publishing a standalone Blazor WebAssembly app, a web.config is automatically generated for the app that handles configuring IIS appropriately. You can now specify your own web.config in the project, which will get used instead of the generated one.

Attach tokens to outgoing requests

Configuring authentication now adds a BaseAddressAuthorizationMessageHandler as a service that can be used with HttpClient to attach access tokens to outgoing requests. The BaseAddressAuthorizationMessageHandler is preconfigured to make requests to the the app base address. Tokens are acquired using the existing IAccessTokenProvider service. If a token cannot be acquired, an AccessTokenNotAvailableException is thrown. This exception has a Redirect method that can be used to navigate the user to the identity provider to acquire a new token.

The authentication enabled Blazor WebAssembly templates now use IHttpClientFactory to set up an HttpClient with the BaseAddressAuthorizationMessageHandler:

builder.Services.AddHttpClient("BlazorWithIdentityApp1.ServerAPI", client => client.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress))
    .AddHttpMessageHandler<BaseAddressAuthorizationMessageHandler>();

// Supply HttpClient instances that include access tokens when making requests to the server project
builder.Services.AddTransient(sp => sp.GetRequiredService<IHttpClientFactory>().CreateClient("BlazorWithIdentityApp1.ServerAPI"));

You can use the configured HttpClient to make authorized requests using a simple try-catch pattern. For example, here’s the updated code in the FetchData component for requesting the weather forecast data:

protected override async Task OnInitializedAsync()
{
    try
    {
        forecasts = await Http.GetFromJsonAsync<WeatherForecast[]>("WeatherForecast");
    }
    catch (AccessTokenNotAvailableException exception)
    {
        exception.Redirect();
    }
}

Alternatively, you can simplify things even further by defining a strongly-typed client that handles all of the HTTP and token acquisition concerns within a single class:

WeatherClient.cs

public class WeatherClient
{
    private readonly HttpClient httpClient;

    public WeatherClient(HttpClient httpClient)
    {
        this.httpClient = httpClient;
    }

    public async Task<IEnumerable<WeatherForecast>> GetWeatherForeacasts()
    {
        IEnumerable<WeatherForecast> forecasts = new WeatherForecast[0];
        try
        {
            forecasts = await httpClient.GetFromJsonAsync<WeatherForecast[]>("WeatherForecast");
        }
        catch (AccessTokenNotAvailableException exception)
        {
            exception.Redirect();
        }
        return forecasts;
    }
}

Program.cs

builder.Services.AddHttpClient<WeatherClient>(client => client.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress))
    .AddHttpMessageHandler<BaseAddressAuthorizationMessageHandler>();

FetchData.razor

protected override async Task OnInitializedAsync()
{
    forecasts = await WeatherClient.GetWeatherForeacasts();
}

To make authorized requests to resources not hosted at the app base, use the AuthorizationMessageHandler. The AuthorizationMessageHandler can be configured with the authorized URLs, scopes, and return URL using the ConfigureHandler method.

For example, you can configure an HttpClient to use the AuthorizationMessageHandler like this:

builder.Services.AddSingleton(sp =>
{
    return new HttpClient(sp.GetRequiredService<AuthorizationMessageHandler>()
        .ConfigureHandler(
          new [] { "https://www.example.com/base" },
          scopes: new[] {"example.read", "example.write"}))
        {
           BaseAddress = new Uri("https://www.example.com/base")
        };
});

Alternatively, you can define a custom message handler that inherits from AuthorizationMessageHandler and preconfigures the handler as required.

Support for time zones

Blazor now infers the user’s time zone and uses it in date and time calculations. In addition, APIs on System.TimeZoneInfo that previously returned incomplete results now report correct results.

Help improve the Blazor docs!

Thank you everyone who has taken the time to give feedback on how we can best improve the Blazor docs!

If you haven’t already, please join in with helping us improve the docs by doing the following:

  • As you read the Blazor docs, let us know where we should focus our efforts by telling us if you find a topic helpful or not using the helpfulness widget at the top of each doc page:

    Doc helpfulness

  • Use the Feedback section at the bottom of each doc page to let us know when a particular topic is unclear, inaccurate, or incomplete.

    Doc feedback

  • Comment on our Improve the Blazor docs GitHub issue with your suggestions for new content and ways to improve the existing content.

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!

54 comments

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

  • Lochy 0

    builder.Services.AddTransient(sp => sp.GetRequiredService() .CreateClient(“{APP ASSEMBLY}.ServerAPI”));

    With it, all api requests ask for auth, without it, they can’t auth at all, with azure
    I thought you had to put tag helpers on the controller and pages to force auth, but that’s not what’s happening. That line in program.cs seems to have attached auth to all http instances or something, which isn’t really what I want.
    I.e. submitting data shouldn’t require auth, reviewing the submitted data later, does.
    Let me know if I’m missing something.
    This is also looks like the same issue as the above comment by: quang

    Been looking into named clients of httpclient and [AllowAnonymous], but niether have worked so far

    • Daniel RothMicrosoft employee 0

      Hi Lochy. The authorizing HTTP message handler takes care of acquiring tokens and attaching them to requests. It doesn’t interact with the authorization system in Blazor. In your component logic you need to make sure you use a correctly configured HttpClient given the level of authorization for the current user. Tomake requests with different access tokens or no access tokens at all you’ll need to configure multiple HttpClients. The best way to do this is to use IHttpClientFactory: https://docs.microsoft.com/aspnet/core/fundamentals/http-requests.

  • Gianluca Gentile 0

    Hi Daniel, how are you?
    Blazor WebAssembly is amazing, i love it but I have a little problem.
    With the preview 5, the method GetFromJsonAsync not seralize the nested objects.
    For example:

    public class WeatherForecast
    {
        public DateTime Date { get; set; }
        public int TemperatureC { get; set; }
        public string Summary { get; set; }
        public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
        public List<string> TestList = new List<string>();
    }
    

    The object WeatherForecast in WASM have always TestList empty.
    In the WeatherForecastController:

    TestList = new List() { “test1”, “test2”, “test3” }

    Thanks! 🙂

    • Daniel RothMicrosoft employee 0

      Hi Gianluca. I’m happy to hear you are enjoying Blazor! It looks like you’ve implemented the TestList member as a public field. The System.Net.Http.Json extension methods are based on System.Text.Json, which doesn’t support serializing or deserializing public fields like Json.NET does. You can read more about this difference in behavior here: https://docs.microsoft.com/dotnet/standard/serialization/system-text-json-migrate-from-newtonsoft-how-to#public-and-non-public-fields.

      • Mark Radcliffe 0

        If Daniel’s message wasn’t clear, you’d just need to change your class to something like this so that TestList is a property and not a field:

        public class WeatherForecast
        {
            public DateTime Date { get; set; }
            public int TemperatureC { get; set; }
            public string Summary { get; set; }
            public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
            public List TestList { get; set; } = new List();
        }
        • Morten Meisler 0

          Thanks for clarifying Mark! My heart rate just went from high to low again 😀

  • Wade Balzer 0

    Hi Daniel,

    I’m having an issue publishing a PWA…

    Conflicting assets with the same path ‘/service-worker.js’ for content root paths ‘C:\Users\wade\Source\Repos\Apps\BlazorApp5\BlazorApp5\Client\obj\Release\netstandard2.1\blazor\serviceworkers\wwwroot\service-worker.js’ and ‘C:\Users\wade\Source\Repos\Apps\BlazorApp5\BlazorApp5\Client\wwwroot\service-worker.js’. BlazorApp5.Server 0

    It seems to be a lingering problem from Issue # 20789 which is showing closed.

    Because I can’t seem to publish in VS v16.6.0 Preview 4 , I’m trying to publish manually via the command line…

    dotnet publish -p:PublishProfile="Properties\PublishProfiles\FolderProfile.pubxml"
    

    … but it doesn’t seem to be working. Am I missing a command line switch?

    • Daniel RothMicrosoft employee 0

      Hi Wade. We’ll need to take a look at your project to see what’s going on. Could you please file a new issue for this on GitHub with your steps to reproduce the issue?

  • Wil Wilder Apaza Bustamante 0

    this comment has been deleted.

    • Daniel RothMicrosoft employee 0

      Hi Mark. By “server side .net core project” I assume you mean ASP.NET Core hosted and not a Blazor Server project, correct? Does the app run correctly when you run it from the command-line using dotnet run?

      • Wil Wilder Apaza Bustamante 0

        this comment has been deleted.

        • Daniel RothMicrosoft employee 0

          Thanks Mark. I think you should go ahead and open a GitHub issue for this so that we can investigate further: https://github.com/dotnet/aspnetcore/issues. I suspect it may be related to your dev environment, so please provide as much detail as you can about how your dev machine is setup and at what point you see this exception.

          • Wil Wilder Apaza Bustamante 0

            this comment has been deleted.

  • Rogier van der Hee 0

    Great work! The debugger is starting to work, that is an amazing achievement.

    Now if only I could both debug and edit my components in real time or with reload….

  • Manoj Kumar 0

    Hi Daniel,
    When can we expect the official release of blazor web assembly?
    Is AOT is enabled by default in that version?

    • Daniel RothMicrosoft employee 0

      Hi Manoj. We’ll be announcing the Blazor WebAssembly 3.2 Release Candidate in just a little bit. The final Blazor WebAssembly 3.2 release is expected to land in May. AoT compilation to WebAssembly is not part of the 3.2 release, but is planned for .NET 5 later this year.

  • Ian Cowley 0

    Loving Blazor and very thankful for all the work you guys are putting in.

    I am stuck on something though.
    In a WebAssembly client…
    HttpClient calling an API that is setting

    context.HttpContext.Response.Headers.Add(HeaderNames.ETag, String.Format("\"{0}\"", etag));
    context.HttpContext.Response.Headers.Add(HeaderNames.CacheControl, new CacheControlHeaderValue
    {
        MustRevalidate = true,
        NoTransform = _etagOptions.Value.NoTransform,
        MaxAge = (_etagOptions.Value.MaxAge != 0) ? new TimeSpan(0, 0, _etagOptions.Value.MaxAge) : (TimeSpan?)null,
        Private = _etagOptions.Value.CacheLocation == CacheLocation.Private
    }.ToString());

    if the endpoint is a GET the subsequent requests headers contain the
    {[If-Modified-Since, {Thu, 30 Apr 2020 18:13:20 GMT}]}
    {[If-None-Match, {“3363758323”}]}

    But when it’s a PUT it doesn’t…

    doesn’t send the headers
    forecasts = await (await Http.PutAsJsonAsync(“WeatherForecast”, 6)).Content.ReadFromJsonAsync<WeatherForecast[]>();

    does send the headers
    forecasts = await Http.GetFromJsonAsync<WeatherForecast[]>(“WeatherForecast”);

    Am I missing a trick, or is there an issue here?

  • Todd Albers 0

    I have submitted an error report regarding this on developercommunity.visualstudio.com.

    Default Example Blazor WebAssembly solution fails to load the FetchData page with error message “An unhandled error has occurred. Reload”

    In short, you should change the “Start-Up Project” setting on the default example solution from “Currently Selected” to [YourSolutionName].Server. The FetchData page fails if anything but [YourSolutionName].Server is the “Start-Up Project” for the solution.

    • Daniel RothMicrosoft employee 0

      Hi Todd. Thanks for reporting this issue! You are correct that you need to run the Server project, not the Client project. We will investigate what we can do to prevent users from accidentally running the Client project when the app is ASP.NET Core hosted.

  • saurabh srivastava 0

    Hi Daniel,

    First of all, great work with Blazor webassembly release.

    I see mentioned in this post that TimeZone support has been fixed.
    I’ve been trying out Blazor webassembly with dotnet version 3.1.300 and the TimeZoneInfo.Local still returns UTC all the time irrespective of the system time configuration.
    Am I missing something, any configuration that I need to add for Blazor SPA to have correct TimeZoneInfo ?

    P.S.
    I did notice the dotnet.timezones.dat file getting downloaded by the browser on loading the Blazor SPA along with other dotnet assemblies.

    • Daniel RothMicrosoft employee 0

      Hi Saurabh. I just tried out @TimeZoneInfo.Local in a Blazor WebAssembly app and it correctly showed my local timezone. It could be this is an issue with your specific timezone. Please open a GitHub issue with as much detail about how to reproduce the issue as possible and we’ll take a look: https://github.com/dotnet/aspnetcore/issues.

Feedback usabilla icon