In this article, we will learn how to publish a Blazor WebAssembly application on IIS.

To download the source code for this article, you can visit our GitHub repository.

Let’s dive in.

Setting up the App

Blazor has three hosting models: server, hybrid, and client side. We won’t discuss hybrid in this article, since it is the approach to develop native mobile applications. The main difference between server and client-side hosting is where the components execute. To have a more in-depth comparison, check out our article on Blazor Server vs Blazor WebAssembly.

Support Code Maze on Patreon to get rid of ads and get the best discounts on our products!
Become a patron at Patreon!

Creating From the Template

In this article we will be using the client-side hosting model, so let’s start by creating a Blazor WebAssembly Standalone App from the template:

Selecting the Blazor WebAssembly Standalone App template

We can use the template with its default settings, so just press Next, and then Create.

Adding Environment-Specific Configuration

Now, to be able to utilize environment-specific settings, let’s create two files called appsettings.Development.json and appsettings.Production.json under the wwwroot folder and add an Enviroment property to them with the respective environment name as the value:

{
  "Environment": "Development"
}

Blazor WebAssembly will load these configurations for us automatically if we follow the naming structure appsettings.<env>.json.

Let’s modify the home page to show us our Environment property’s value:

@page "/"
@inject IConfiguration Configuration

<PageTitle>Home</PageTitle>

<h1>Hello, @Configuration["Environment"]!</h1>

Welcome to your new app.

Furthermore, let’s run the application from Visual Studio to verify it’s working:

The modified home page

Blazor WebAssembly successfully reads the environment setting from the JSON file. However, it’s important to note that we should never add sensitive information to the client-side appsettings.json file, because this file will be served to everybody accessing our site.

Adding a Publish Profile

Since Blazor WebAssembly is a fully client-side framework, it will need a web server to host it.

When running from Visual Studio, Kestrel serves as the server, and it sets up all the hosting automatically. However, when building a WebAssembly project, the compiler bundles all our C# code, translates it to WebAssembly, and produces a single folder. This folder can be hosted on any static file hosting platform or web server, similar to the operation of JavaScript SPA frameworks.

Let’s publish our application to this folder. First, let’s add a Folder publish profile, by right-clicking the project, selecting Publish..., then Folder, then Next, and Finish. Finally, the profile should look like this:

The created publish profile

Note the Configuration setting, which is set to Release. This will not affect which appsettings.json will be loaded, it will only tell the compiler to build the project in Release mode, thus making as many optimizations as possible. The hosting environment can be controlled using the ASPNETCORE_ENVIRONMENT environment variable.

Let’s publish the application by clicking on the Publish button. Once the application is published, let’s go on and configure IIS.

Configuring IIS

IIS is a general-purpose webserver for Windows-based systems, developed by Microsoft. It’s easy to get started with because it is part of every Windows version from Home to Server editions.

Let’s enable it in the Windows features menu:

Enabling the IIS Windows feature

We only need Web Management Tools and World Wide Web Services, FTP Server is unnecessary for hosting Blazor applications.

After Windows finished installing the necessary components let’s install the URL Rewrite module for IIS. Unfortunately, this module is not part of the core IIS installation, however, it is necessary for Blazor WebAssembly’s client-side routing to work.

Now we’re ready to create our first site in IIS Manager.

Creating the Site

In IIS Manager we have a default website, but let’s remove it:

Removing the default site

Then create a new site, by right-clicking on Sites and selecting Add Website:

Creating the site

In the Physical path input, let’s select the folder where we published our app.
It should be <project folder>\bin\Release\net8.0\browser-wasm\publish.

Once we create the site, let’s configure IIS to access the published folder.

Setting up Access Rights

IIS runs our website in an application pool, which has an identity. All application pool identities are part of the IIS_IUSRS group, so let’s allow full read and execute access for that group on the publish folder:

Permission of the IIS_IUSRS group

Then the last step to allow access for IIS is to set the anonymous authentication credentials to the application pool user. Let’s select our site in IIS, select the Authentication component, then edit the Anonymous Authentication and select the Application pool identity:

Setting the app pool identity for anonymous authentication

And our site is fully functional. Let’s navigate to http://127.0.0.1 and check if everything is working fine, and what our environment is:

Home page hosted from IIS

Enabling Compression While Publishing a Blazor WebAssembly Application on IIS

In the published folder we can notice that the compiler created .br and .gz files for us. These are compressed files, hence their size is reduced, and due to the smaller size the application loads faster, especially on slower networks. However, by default, IIS will serve the non-compressed variant of the application. We can configure this behavior in the web.config file. It contains settings for MIME types, compression, and URL rewrite rules for IIS. Let’s add compression-related settings to the web.config file in the root of the publish folder.

First, let’s add the mimeMaps of compressed files under configuration/system.webServer/staticContent:

<mimeMap fileExtension=".js.gz" mimeType="application/javascript" />
<mimeMap fileExtension=".dat.gz" mimeType="application/octet-stream" />
<mimeMap fileExtension=".dll.gz" mimeType="application/octet-stream" />
<mimeMap fileExtension=".json.gz" mimeType="application/json" />
<mimeMap fileExtension=".wasm.gz" mimeType="application/wasm" />
<mimeMap fileExtension=".blat.gz" mimeType="application/octet-stream" />
<mimeMap fileExtension=".html.gz" mimeType="text/html" />
<mimeMap fileExtension=".css.gz" mimeType="text/css" />
<mimeMap fileExtension=".ico.gz" mimeType="image/x-icon" />
<mimeMap fileExtension=".svg.gz" mimeType="image/svg+xml" />
<mimeMap fileExtension=".js.br" mimeType="application/javascript" />
<mimeMap fileExtension=".dat.br" mimeType="application/octet-stream" />
<mimeMap fileExtension=".dll.br" mimeType="application/octet-stream" />
<mimeMap fileExtension=".json.br" mimeType="application/json" />
<mimeMap fileExtension=".wasm.br" mimeType="application/wasm" />
<mimeMap fileExtension=".blat.br" mimeType="application/octet-stream" />
<mimeMap fileExtension=".html.br" mimeType="text/html" />
<mimeMap fileExtension=".css.br" mimeType="text/css" />
<mimeMap fileExtension=".ico.br" mimeType="image/x-icon" />
<mimeMap fileExtension=".svg.br" mimeType="image/svg+xml" />

The mimeMap tag helps IIS recognize the compressed file’s MIME type and can appropriately serve them to the browser.

Next, let’s replace configuration/system.webServer/httpCompression‘s contents:

<dynamicTypes>
  <remove mimeType="text/*" />
  <remove mimeType="application/javascript" />
  <remove mimeType="image/svg+xml" />
</dynamicTypes>
<staticTypes>
  <remove mimeType="text/*" />
  <remove mimeType="application/javascript" />
  <remove mimeType="image/svg+xml" />
</staticTypes>

When using the default web.config, IIS would attempt to compress the already compressed file once more. To prevent this, we remove types that do not require further compression and avoid adding types that are already compressed during the building of the Blazor WebAssembly project.

Now, under the configuration/system.webServer/rewrite, let’s add these rules:

<outboundRules rewriteBeforeCache="true">
  <rule name="Add Vary Accept-Encoding" preCondition="PreCompressedFile" enabled="true">
    <match serverVariable="RESPONSE_Vary" pattern=".*" />
    <action type="Rewrite" value="Accept-Encoding" />
  </rule>
  <rule name="Add Encoding Brotli" preCondition="PreCompressedBrotli" enabled="true" stopProcessing="true">
    <match serverVariable="RESPONSE_Content_Encoding" pattern=".*" />
    <action type="Rewrite" value="br" />
  </rule>
  <rule name="Add Encoding Gzip" preCondition="PreCompressedGzip" enabled="true" stopProcessing="true">
    <match serverVariable="RESPONSE_Content_Encoding" pattern=".*" />
    <action type="Rewrite" value="gzip" />
  </rule>
  <preConditions>
    <preCondition name="PreCompressedFile">
      <add input="{HTTP_URL}" pattern="\.(gz|br)$" />
    </preCondition>
      <preCondition name="PreCompressedBrotli">
      <add input="{HTTP_URL}" pattern="\.br$" />
    </preCondition>
    <preCondition name="PreCompressedGzip">
      <add input="{HTTP_URL}" pattern="\.gz$" />
    </preCondition>
  </preConditions>
</outboundRules>

These rules outline the headers to include in responses. For each compressed file, we include the Vary: Accept-Encoding header to notify caches that the content varies by encoding. If the file undergoes Brotli compression, we assign br to the Content-Encoding header and for gzip compression, we specify gzip as the header value. The preconditions guarantee that these rules exclusively pertain to compressed files.

Lastly, let’s add the following rules under configuration/system.webServer/rewrite/rules:

<rule name="Rewrite brotli file" stopProcessing="true">
  <match url="(.*)"/>
  <conditions>
    <add input="{HTTP_ACCEPT_ENCODING}" pattern="br" />
    <add input="{REQUEST_FILENAME}" pattern="\.(js|dat|dll|json|wasm|blat|htm|html|css|ico|svg)$" />
    <add input="{REQUEST_FILENAME}.br" matchType="IsFile" />
  </conditions>
  <action type="Rewrite" url="{R:1}.br" />
</rule>
<rule name="Rewrite gzip file" stopProcessing="true">
  <match url="(.*)"/>
  <conditions>
    <add input="{HTTP_ACCEPT_ENCODING}" pattern="gzip" />
    <add input="{REQUEST_FILENAME}" pattern="\.(js|dat|dll|json|wasm|blat|htm|html|css|ico|svg)$" />
    <add input="{REQUEST_FILENAME}.gz" matchType="IsFile" />
  </conditions>
  <action type="Rewrite" url="{R:1}.gz" />
</rule>

These rules rewrite URLs of plain files to their compressed versions, based on which compression the client supports.

Further, let’s enable static and dynamic response compression Windows features:

Enabling compression

After rebooting the computer, let’s open http://127.0.0.1, and check the Content-Encoding header value for the wasm files:

Dev tools showing br content encoding

However, it’s important to note that browsers cache these files heavily, so it’s a good idea to clear all site data or use an incognito tab before checking the results.

Enabling compression can result in significant first-load time improvements. Now let’s finish our IIS deployment by enabling HTTPS.

Setting Up HTTPS 

HTTPS encrypts communication between the server and the client so that no listener can decode the communication. This is crucial in the case of web applications, so let’s turn it on.

In IIS Manager, select our site, then in the right side menu click on Bindings..., and then add. Let’s fill out the form for our new HTTPS binding with the development certificate:

Adding https binding

The server uses the SSL certificate to authenticate itself and encrypt traffic. In a real-world scenario, we should purchase an SSL certificate from a root certificate authority trusted by all computers worldwide. Our own machine issues the development certificate when we initially start a development server, and only our computer trusts it. If someone else were accessing our site, they would see a not secure warning:

https warning

On our development machine, this is not a problem, however, if we were to host a real application, we should get a certificate from a trusted root certificate authority instead of creating our own.

Conclusion

We successfully set up a Blazor WebAssembly application hosted on IIS. Enabling compression made our first load faster, and we secured the communication between the client and server by using HTTPS. Lastly, we added appsettings.json and discussed how environment variables can be accessed from Blazor WebAssembly, and how to set different values for them in different environments.

Liked it? Take a second to support Code Maze on Patreon and get the ad free reading experience!
Become a patron at Patreon!