Build and deploy .NET 8 Blazor WASM apps with serverless APIs using Azure Static Web Apps

This post has been republished via RSS; it originally appeared at: New blog articles in Microsoft Community Hub.

 

With the release of .NET 8, Blazor has evolved from a client-first web UI framework to a full-stack web UI framework. This announcement introduced new rendering modes and interactivity, allowing more flexibility to choose the right rendering mode for your Blazor web apps. In this article, we’ll take a closer look at standalone Blazor WebAssembly (WASM) projects, their considerations relative to other Blazor rendering modes, and how we can build full-stack .NET 8 serverless apps on Azure Static Web Apps with Blazor WASM and Functions APIs.

 

.NET 8 Blazor rendering modes & hosting considerations

 

In .NET 8, Blazor has introduced a unified full-stack model, the Blazor Web App. This model enables you to opt for any of the various supported rendering modes (client rendering, server-side rendering, and static rendering) and switch between them on a per-component or per-page basis within a single application. (This has superseded the previous Blazor Server model.) 

 

The Blazor Web App complements the existing standalone Blazor WebAssembly (WASM) model. This model supports frontend client-side capabilities, offloading UI updates from the server to the client.

 

These two models are two different tools and serve different purposes: the Blazor Web App allows you to build full-stack projects with various rendering modes, making it useful for a broad range of use cases from line-of-business applications to content-heavy sites such as e-commerce. In comparison, standalone Blazor WASM apps will need to be complemented by APIs to achieve full-stack functionality, and their client-side rendering model makes them well suited for app-like scenarios such as line-of-business applications.

 

An important differentiator between these two models is that Blazor Web Apps need to be hosted on servers, as they benefit from server-side capabilities such as server-side rendering and SignalR-enabled interactivity. This makes services such as App Service great options for hosting Blazor Web Apps. On the other hand, standalone Blazor WASM apps can be hosted on CDNs or static site hosts, such as Azure Static Web Apps, for reliable and scalable hosting.

 

Building a .NET 8 standalone Blazor WASM application

 

Before starting, install .NET 8. To create our Blazor app, we’ll use Visual Studio and create a template project. Searching for “Blazor”, we’ll find the Blazor WebAssembly Standalone App template. 

Screenshot of "Create new project" form in Visual StudioScreenshot of "Create new project" form in Visual Studio

For this project template, we will ensure that we place the project as a child directory of the solution. This is because our solution will eventually contain our Blazor WASM project, along with an API Azure Functions project, both of which will be in the same solution to facilitate local development and deployment to Azure Static Web Apps. Our project name will be 'Client', and our solution name will be 'BlazorOnSWA'. We can also specify that we're using .NET 8 in the next page, and leave the rest as default.

Visual Studio, create project form with detailsVisual Studio, create project form with details

After this, we'll get a scaffolded sample Blazor WASM application. We can then run the application to see the sample application by clicking the debug button.

Screenshot of Visual Studio showing the button to start the projectScreenshot of Visual Studio showing the button to start the project

This should automatically open the application in our browser. Here, we see the sample Blazor WASM application that features a counter and some weather data.

Screenshot of the default template Blazor appScreenshot of the default template Blazor app

Now that we have a scaffolded template, we can deploy the project to our static site host, Azure Static Web Apps.

 

Deploying our .NET 8 Blazor WASM application to Azure Static Web Apps

 

Azure Static Web Apps comes with integrated CI/CD with GitHub or Azure DevOps, making this the easiest way to manage deployments to our Static Web Apps resource. This is the approach we’ll take to deploy our .NET 8 standalone Blazor WASM app. We’ll first create a new repository for our Blazor WASM app, and then push our local project to the new GitHub repository we created. This can be done easily from Visual Studio’s Git configuration.

Screenshot of "Create Git repository" option in Visual StudioScreenshot of "Create Git repository" option in Visual Studio

With our newly created GitHub repository, we can then create an Azure Static Web Apps resource to host our project. In the create blade, we’ll provide the details for our GitHub repository in order to connect to it, which will automatically set up our GitHub Actions integration. When providing our build details, we can select the 'Blazor' preset. If you've entered custom project names when creating your Blazor app, you should match these values here. Since we have not created an API yet, we can leave this blank as we'll fill this out later.

Screenshot of Azure Portal with Azure Static Web Apps creationScreenshot of Azure Portal with Azure Static Web Apps creation

In the build details section, we’ll specify the app location of our Blazor WASM application. This will be the name of the folder containing our Blazor project in our repository (this is the folder that contains your `<project name>.csproj`,  not the folder containing the `<project name>.sln`), indicating that our frontend Blazor app is located in that folder. We can then leave the output location as the default one (since this is the path relative to the app location), and the API location as empty. We now have our Blazor WASM application deployed to Azure Static Web Apps!

Screenshot of running Blazor app deployed to Static Web AppsScreenshot of running Blazor app deployed to Static Web Apps

Our last step will be to configure our Blazor WASM application to include a navigation fallback such that various routes and pages (which are handled by our client-side Blazor WASM application) are routed to the index.html to be handled properly. Add a new file to your Blazor WASM project, with the filename `staticwebapp.config.json`.

Screenshot of adding the staticwebapp.config.json file to our projectScreenshot of adding the staticwebapp.config.json file to our project

Here, provide the navigation fallback configuration as such:

 

 

{ "navigationFallback": { "rewrite": "/index.html" } }

 

 

Screenshot of staticwebapp.config.jsonScreenshot of staticwebapp.config.json

We can now commit this new configuration and push to our remote repository for redeployment.

 

Adding serverless .NET 8 APIs with Azure Static Web Apps’ managed functions

 

As mentioned above, standalone Blazor WASM applications are client-only, meaning that in order to provide full-stack functionality, they need to be paired with APIs that can have access to backend resources such as databases or other services. This can be conveniently added with Azure Static Web Apps’ managed functions, which allow for serverless APIs all within a single project and Static Web Apps resource.

 

Before we do this, make sure to pull the changes to your project from your repository. This can be done from Visual Studio by doing Git > Pull. Now, to add an Azure Function, we can create a new project and add it to our existing Blazor app solution. From our current Blazor solution, we can create a new Function project with File > New > Project.

 

Then, we’ll search for “Azure Functions” to create our Functions project. In the create configuration, we’ll name this project "Api", and make this Function to be added to our current solution. We’ll also select the functions worker to be .NET 8, and the authorization level to be anonymous.

Configuration of new Azure Function projectConfiguration of new Azure Function project

Configure new Azure Function project and select .NET 8 as the framework and anonymous as authorization levelConfigure new Azure Function project and select .NET 8 as the framework and anonymous as authorization level

Configure your Azure Functions and Blazor project for better local development

 

Now that we’ve created an API as part of our solution, we can set our solution up to start up both our Blazor and our API projects at once.

Configure the solution to startup both projectsConfigure the solution to startup both projects

Set up both projects to start upon startupSet up both projects to start upon startup

 

Next, we’ll configure the Azure Function to accept requests from our Blazor application when running locally by configuring CORS. This is only needed for local development since Azure Static Web Apps takes care of this when deployed. In the API’s `local.settings.json`, we’ll set the following:

 

 

... "Host": { "CORS": "*", "CORSCredentials": false } ...

 

 

Screenshot of local.settings.json CORS configurationScreenshot of local.settings.json CORS configuration

 

We’ll then configure our Blazor application to make API requests to the Function for local development. When deployed, requests made in the Blazor app to the `/api` endpoint of our Static Web Apps will be automatically proxied to our managed Azure Function. To reproduce this behavior for local development, we can set an app setting for routing to the local Azure Function host.

 

Start by identifying the port on which the Azure Function runs when developing locally. You can either do so by running the project and finding the Azure Function port, or by finding it directly from the Functions API’s configuration in the API (Azure Functions project) > Properties > launchSettings.json. From the `commandLineArgs`, we can find that the port is working on `localhost:7107`. This is where we want to send API requests from our Blazor app (note that this is for local development only, as routing to the managed Azure Function is handled by Static Web Apps in your hosted environment).

Locate the launchSettings.json file of your Functions project to identify the port on which your Functions run when developing locallyLocate the launchSettings.json file of your Functions project to identify the port on which your Functions run when developing locally

Then, we’ll set an app setting for our Blazor WASM application for local development. In our client’s `wwwroot`, we can add the file `appsettings.Development.json`. This will set app settings for the Blazor project only when running locally, pointing to the local Functions project when running.

 

 

{ "API_Prefix": "http://localhost:<YOUR_PORT_HERE>" }

 

 

Set the API prefix app setting for your Blazor project for local development with the port identified for the Function projectSet the API prefix app setting for your Blazor project for local development with the port identified for the Function project

We’ll then consume this app setting from our Blazor’s startup program for local development, by updating our `Program.cs` file to change the HttpClient base address.

 

 

builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.Configuration["API_Prefix"] ?? builder.HostEnvironment.BaseAddress) });

 

 

Edit your Blazor app's Program.cs to use the API Prefix when present for to configure local developmentEdit your Blazor app's Program.cs to use the API Prefix when present for to configure local development

Call your Functions API from your Blazor WASM app

 

Now that we've completed the configuration steps for local development, we’ll edit our Blazor app’s Weather page to fetch the data from our Function app. We’ll start by editing our Azure Function to return sample information for our weather data. Rename API > Function1.cs to WeatherForecastFunction.cs. We’ll also update the code of this file to the following, such that our Function returns sample weather forecasts:

 

 

using System.Net; using Microsoft.Azure.Functions.Worker; using Microsoft.Azure.Functions.Worker.Http; using Microsoft.Extensions.Logging; namespace API { public class WeatherForecastFunction { private readonly ILogger _logger; public WeatherForecastFunction(ILoggerFactory loggerFactory) { _logger = loggerFactory.CreateLogger<WeatherForecastFunction>(); } [Function("WeatherForecast")] public HttpResponseData Run([HttpTrigger(AuthorizationLevel.Anonymous, "get")] HttpRequestData req) { var randomNumber = new Random(); var temp = 0; var result = Enumerable.Range(1, 5).Select(index => new WeatherForecast { Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)), TemperatureC = temp = randomNumber.Next(-20, 55), Summary = GetSummary(temp) }).ToArray(); var response = req.CreateResponse(HttpStatusCode.OK); response.WriteAsJsonAsync(result); return response; } private string GetSummary(int temp) { var summary = "Mild"; if (temp >= 32) { summary = "Hot"; } else if (temp <= 16 && temp > 0) { summary = "Cold"; } else if (temp <= 0) { summary = "Freezing"; } return summary; } public class WeatherForecast { public DateOnly Date { get; set; } public int TemperatureC { get; set; } public string Summary { get; set; } public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); } } }

 

 

Now that we have an API endpoint `/api/WeatherForecast` on our Azure Function that returns sample weather forecast data, we’ll call this API from our Blazor application. Specifically, we'll update the BlazorWASMOnSWA > Pages > Weather.razor page, replacing the `OnInitializedAsync` method with the following:

 

 

protected override async Task OnInitializedAsync() { try { forecasts = await Http.GetFromJsonAsync<WeatherForecast[]>("/api/WeatherForecast") ?? new WeatherForecast[] { }; } catch (Exception ex) { Console.WriteLine(ex.ToString()); } }

 

 

Update method OnInitializedAsync to call the newly created Functions API endpointUpdate method OnInitializedAsync to call the newly created Functions API endpoint

Running our application, we can see that the weather page makes a call to the Weather Forecast API.

Screenshot of Blazor app in browser showing API call to functionScreenshot of Blazor app in browser showing API call to function

We can now update our GitHub Actions workflow to include this new API project to be deployed as our managed functions for the Static Web Apps resource. We can do so by configuring api_location: "Api".

Edit the api_location of your GitHub Actions workflowEdit the api_location of your GitHub Actions workflow

Finally, we can commit our changes and push them to our repository. Doing this will trigger a new GitHub Actions build and deploy workflow, and we will be able to see the new changes in our Static Web Apps resource.

Screenshot of Blazor application deployed to Static Web Apps making an API request to the managed functionsScreenshot of Blazor application deployed to Static Web Apps making an API request to the managed functions

We've successfully deployed our .NET 8 Blazor WASM application to Static Web Apps. We've also complemented our .NET 8 Blazor application with serverless Functions APIs, deployed to our same Azure Static Web Apps resource and configured our solution for local development between our Blazor application and Functions project. This entire project is available as a template repository, pre-configured with local development and ready to be deployed: staticwebdev/blazor-starter: A starter template in C# APIs and Blazor for Azure Static Web Apps (github.com).

 

Try out .NET 8 with Azure Static Web Apps

 

In this article, we discussed the considerations when choosing Blazor WASM, and how Blazor WASM can be hosted on Azure Static Web Apps and how these projects can be easily complemented with managed Functions APIs deployed to the same Static Web Apps resource. 

 

Leave a Reply

Your email address will not be published. Required fields are marked *

*

This site uses Akismet to reduce spam. Learn how your comment data is processed.