Implement OpenAPI Support for Blazor WebAssembly

OpenAPI is a language-agnostic specification for documenting RESTful APIs. It allows both humans and computers to understand the capabilities of a service without accessing code, docs, or using other methods. Hosted Blazor WebAssembly apps include an ASP.NET Core backend for file and API access, and so implementing support for OpenAPI provides numerous benefits.

This post demonstrates the following:

  • Automatically generate an OpenAPI specification based on the API
  • Use tools such as Swagger UI or ReDoc UI to better understand API capabilities
  • Simplifying API access through the generation of typed clients

Before continuing, ensure you have the latest .NET 6 SDK installed.

Create a new app

Create a new Blazor app named AwesomeBlazor in a command shell:

dotnet new blazorwasm --hosted --output AwesomeBlazor

This creates a Blazor app that runs on WebAssembly and is hosted by ASP.NET Core. The solution contains three projects:

  1. Client the frontend Blazor WebAssembly app (accesses the web API)
  2. Server the backend ASP.NET Core app (hosts the client and contains the web API)
  3. Shared the logic and types shared between the client and server

Build and run the app by executing the following commands:

cd .\AwesomeBlazor\Server\
dotnet run

After building, you should see output similar to the following:

info: Microsoft.Hosting.Lifetime[14]
      Now listening on: https://localhost:7251

Your port number will likely be different. Open a browser and navigate to https://localhost:<port> and ensure the app is behaving correctly.

Generate an OpenAPI specification

In this section, NSwag will be used to generate an OpenAPI specification. The specification is a document that describes the capabilities of the API. Popular OpenAPI implementations for .NET include Swashbuckle and NSwag. I prefer to use NSwag since it supports OpenAPI specification generation and client generation in a single toolchain.

Install the NSwag NuGet package using the following command:

cd .\AwesomeBlazor\Server
dotnet add package NSwag.AspNetCore

Within the Server project, update Program.cs to register the required services and serve the generated specification:

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddControllersWithViews();
builder.Services.AddRazorPages();

+ // Register required services
+ builder.Services.AddOpenApiDocument(config =>
+    config.Title = "AwesomeBlazor API");

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseWebAssemblyDebugging();
}
else
{
    app.UseExceptionHandler("/Error");
    // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
    app.UseHsts();
}

app.UseHttpsRedirection();

app.UseBlazorFrameworkFiles();
app.UseStaticFiles();

+ // Generate and serve the OpenAPI specification
+ app.UseOpenApi();

app.UseRouting();


app.MapRazorPages();
app.MapControllers();
app.MapFallbackToFile("index.html");

app.Run();

Run the app and navigate to http://localhost:<port>/swagger/v1/swagger.json to view the generated specification.

Add support for Swagger UI and Redoc UI

In this section, NSwag will be used to serve both Swagger UI and Redoc UI. These tools allow users and developers to understand the capabilities of the API, and when using Swagger UI, to interact with the API. For both tools, the UI is dynamically built based on the OpenAPI specification generated in the previous section.

Within the Server project, update Program.cs to configure the required middleware:

app.UseOpenApi();
+ app.UseSwaggerUi3();
+ app.UseReDoc(configure => configure.Path = "/redoc");

app.UseRouting();

Launch the app and navigate to http://localhost:<port>/swagger to view Swagger UI, then navigate to http://localhost:<port>/redoc to view ReDoc UI.

This is the simplest approach to adding OpenAPI support with NSwag. The specification is generated and served at runtime along with Swagger UI and Redoc. If this is all you need, you can stop right here. In the following sections, I’ll show you how to generate an OpenAPI specification and typed C# clients at build time.

Generate typed clients

In this section, you will generate typed C# clients to improve API access from the Blazor WebAssembly frontend to the ASP.NET Core backend. This is best achieved using NSwagStudio, a simple-to-use Windows GUI to create and manage NSwag configuration files.

Download, install, and launch NSwagStudio. Ensure you are working with a new configuration by clicking File | New.

Add the required configuration to support the generation of the OpenAPI specification. Define the new configuration as follows:

RuntimeNet60
GeneratorASP.NET Core via API Explorer
Project file pathBrowse and select the file AwesomeBlazor.Server.csproj
NoBuildChecked. NSwagStudio will not build the app before generating the specification.
Output file pathThis is the file path to the new API specification file. Recommend adding it to  AwesomeBlazor\Server\wwwroot\swagger\v1\swagger.json

Ensure the new configuration successfully generates a specification by clicking Generate Outputs.

Next, add the necessary configuration to generate typed C# clients. Under Outputs check CSharp Client. Select the CSharp Client tab followed by the Settings tab and then update as follows:

NamespaceAwesomeBlazor.Client
Additional Namespace UsagesAwesomeBlazor.Shared
Use the base URL for the requestUnchecked
Generate interfaces for Client classesChecked
Generate DTO TypesUnchecked
Output file pathAwesomeBlazor\Client\AwesomeBlazorClients.g.cs

Ensure the updated configuration successfully generates the typed clients by clicking Generate Outputs.

You might note the typed clients depend on Newtonsoft.Json. While NSwag includes supports System.Text.Json, the support is experimental/incomplete, so I recommend sticking with Newtonsoft.Json. Install the required package using the following command:

cd .\AwesomeBlazor.Client
dotnet add package Newtonsoft.Json

Generate the specification and typed clients by clicking Generate Files. You can view the OpenAPI specification within Server/wwwroot/swagger/v1/swagger.json and the typed clients within Client/AwesomeBlazorClients.g.cs.

Save the NSwag configuration file. Click File | Save, name the file config.nswag, and save it to the Server project directory, e.g. C:\Code\AwesomeBlazor\Server\config.nswag.

Automation with MSBuild

In the previous section, you used NSwagStudio to build an NSwag configuration file and manually generate an Open API specification and typed clients. Using that approach, you would need to manually generate new files every time the API changed. In this section I’ll show you how to automate this process with MSBuild, so you can instead just focus on building new features or fixing bugs.

Reconfigure the Server project to run NSwag after a successful build to generate an updated specification and clients. First, install NSwag.MSBuild:

cd .\AwesomeBlazor.Server
dotnet add package NSwag.MSBuild

Next update AwesomeBlazor.Server.csproj to add a new post-build event by inserting the following code:

  <PropertyGroup>
	<RunPostBuildEvent>OnBuildSuccess</RunPostBuildEvent>
  </PropertyGroup>

  <Target Name="NSwag" AfterTargets="PostBuildEvent" Condition=" '$(Configuration)' == 'Debug' ">
	<Exec WorkingDirectory="$(ProjectDir)" EnvironmentVariables="ASPNETCORE_ENVIRONMENT=Development" Command="$(NSwagExe_Net60) run /variables:Configuration=$(Configuration)" />
  </Target>

Save all changes and build the solution. Along with the normal build output, you will see the following NSwag run output:

NSwag command line tool for .NET Core Net60, toolchain v13.16.1.0 (NJsonSchema v10.7.2.0 (Newtonsoft.Json v13.0.0.0))
Visit http://NSwag.org for more information.
NSwag bin directory: C:\Users\<user>\.nuget\packages\nswag.msbuild\13.16.1\tools\Net60

Executing file 'C:\<path>\AwesomeBlazor\Server\config.nswag' with variables 'Configuration=Debug'...
Launcher directory: C:\Users\<user>\.nuget\packages\nswag.msbuild\13.16.1\tools\Net60
Done.

The OpenAPI specification and typed clients will now be automatically generated when the project builds. So, if you change the API and build the solution, the specification and typed clients will immediately reflect that change.

Upgrade the Fetch Data component

In this section, you will simply the Fetch Data component by leveraging the new typed clients.

First, register the IWeatherForecastClient service. Within the Client project, update Program.cs as follows:

using Microsoft.AspNetCore.Components.Web;
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
using AwesomeBlazor.Client;

var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("#app");
builder.RootComponents.Add<HeadOutlet>("head::after");

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

+ builder.Services.AddScoped<IWeatherForecastClient, WeatherForecastClient>();

await builder.Build().RunAsync();

Update the Fetch Data component to use the new typed client. Within the Client project open Pages/FetchData.razor, and make the following changes:

@page "/fetchdata"
@using AwesomeBlazor.Shared
- @inject HttpClient Http
+ @inject IWeatherForecastClient Client

Towards the bottom of the file, update the @code directive as follows:

@code {
-    private WeatherForecast[]? forecasts;
+    private IEnumerable<WeatherForecast>? forecasts;

    protected override async Task OnInitializedAsync()
    {
-        forecasts = await Http.GetFromJsonAsync<WeatherForecast[]>("WeatherForecast");
+        forecasts = await Client.GetAsync();
    }
}

This is a relatively small change that resulted in a number of significant improvements:

  • Hard-coded routes the previously hard-code routes are now generated within the client, and will automatically be updated should routing change
  • Required knowledge of the API the previous implementation required knowledge of API operations, inputs, and outputs. This information is now captured in the typed clients, so it is easy for the developer to understand and interact with the API.
  • Breaking changes are hard to detect a bug in the previous implementation meant a failure at run-time, with the new implementation the client will fail to build.

Launch the app and navigate to http://localhost:<port>/fetchdataand ensure the app is behaving normally.

Complications on build

Occasionally you will run into an issue the solution won’t build because of breaking changes in typed clients. In this scenario, you might try to regenerate the typed clients by building the Server project, however, this can fail since the Server project depends on the Client project, and the Client project fails to build due to breaking changes in the typed clients. What a mess! Fortunately, this issue is easily resolved by running NSwag from the command line:

cd .\AwesomeBlazor\Server
nswag run /runtime:Net60

Once the new typed clients are generated, you will be able to build the solution normally.

Next steps

In this post, I have demonstrated how to effectively implement support for OpenAPI for a hosted Blazor WebAssembly app. If you would like to learn more, take a look at some of the following resources:

Thanks for reading, please feel free to post any questions or comments below.