Events
Power BI DataViz World Championships
Feb 14, 4 PM - Mar 31, 4 PM
With 4 chances to enter, you could win a conference package and make it to the LIVE Grand Finale in Las Vegas
Learn moreThis browser is no longer supported.
Upgrade to Microsoft Edge to take advantage of the latest features, security updates, and technical support.
Note
This isn't the latest version of this article. For the latest version of this article, see the .NET 7 version.
This article explains how to configure a hosted Blazor WebAssembly app to host multiple Blazor WebAssembly apps.
Select the version of this article that matches your hosting requirements, either port/domain hosting (for example, :5001
/:5002
or firstapp.com
/secondapp.com
) or route subpath hosting (for example, /FirstApp
and /SecondApp
).
With the current hosting selection, this article covers port/domain hosting (for example, :5001
/:5002
or firstapp.com
/secondapp.com
).
In the following examples:
MultipleBlazorApps
in a folder named MultipleBlazorApps
.MultipleBlazorApps.Client
in the Client
folder, MultipleBlazorApps.Server
in the Server
folder, and MultipleBlazorApps.Shared
in the Shared
folder.MultipleBlazorApps.SecondClient
in a folder named SecondClient
.MultipleBlazorApps.Server
) can serve pages or views as a Razor Pages or MVC app.firstapp.com
. The second client app is accessible in a browser at port 5002 or with a host of secondapp.com
.With the current selection, this article covers route subpath hosting (for example, /FirstApp
and /SecondApp
).
In the following examples:
MultipleBlazorApps
in a folder named MultipleBlazorApps
.MultipleBlazorApps.Client
in the Client
folder, MultipleBlazorApps.Server
in the Server
folder, and MultipleBlazorApps.Shared
in the Shared
folder.MultipleBlazorApps.SecondClient
in a folder named SecondClient
.MultipleBlazorApps.Server
) can serve pages or views as a formal Razor Pages or MVC app.MultipleBlazorApps.Server
project's Properties/launchSettings.json
file in its applicationUrl
value. The first client app is accessible in a browser at the /FirstApp
subpath. The second client app is accessible in a browser at the /SecondApp
subpath.The examples shown in this article require additional configuration for:
firstapp.com
and secondapp.com
.The preceding configurations are beyond the scope of this article. For more information, see the following resources:
Use an existing hosted Blazor WebAssembly solution or create a new hosted Blazor WebAssembly solution from the Blazor WebAssembly project template by passing the -ho|--hosted
option if using the .NET CLI or selecting the ASP.NET Core Hosted checkbox in Visual Studio when the project is created in the IDE.
Use a folder for the solution named MultipleBlazorApps
and name the project MultipleBlazorApps
.
Create a new folder in the solution named SecondClient
. In the new folder, add a second Blazor WebAssembly client app named MultipleBlazorApps.SecondClient
. Add the project as a standalone Blazor WebAssembly app. To create a standalone Blazor WebAssembly app, don't pass the -ho|--hosted
option if using the .NET CLI or don't use the ASP.NET Core Hosted checkbox if using Visual Studio.
Make the following changes to the MultipleBlazorApps.SecondClient
project:
FetchData
component (Pages/FetchData.razor
) from the Client/Pages
folder to the SecondClient/Pages
folder. This step is required because a standalone Blazor WebAssembly app doesn't call a Server project's controller for weather data, it uses a static data file. By copying the FetchData
component to the added project, the second client app also makes a web API call to the server API for weather data.SecondClient/wwwroot/sample-data
folder, as the weather.json
file in the folder isn't used.The following table describes the solution's folders and project names after the SecondClient
folder and MultipleBlazorApps.SecondClient
project are added.
Physical folder | Project name | Description |
---|---|---|
Client |
MultipleBlazorApps.Client |
Blazor WebAssembly client app |
SecondClient |
MultipleBlazorApps.SecondClient |
Blazor WebAssembly client app |
Server |
MultipleBlazorApps.Server |
ASP.NET Core server app |
Shared |
MultipleBlazorApps.Shared |
Shared resources project |
The MultipleBlazorApps.Server
project serves the two Blazor WebAssembly client apps and provides weather data to the client apps' FetchData
components via an MVC controller. Optionally, the MultipleBlazorApps.Server
project can also serve pages or views, as a traditional Razor Pages or MVC app. Steps to enable serving pages or views are covered later in this article.
Note
The demonstration in this article uses static web asset path names of FirstApp
for the MultipleBlazorApps.Client
project and SecondApp
for the MultipleBlazorApps.SecondClient
project. The names "FirstApp
" and "SecondApp
" are merely for demonstration purposes. Other names are acceptable to distinguish the client apps, such as App1
/App2
, Client1
/Client2
, 1
/2
, or any similar naming scheme.
When routing requests to the client apps by a port or a domain, "FirstApp
" and "SecondApp
" are used internally to route requests and serve responses for static assets and aren't seen in the browser's address bar.
Note
The demonstration in this article uses static web asset path names of FirstApp
for the MultipleBlazorApps.Client
project and SecondApp
for the MultipleBlazorApps.SecondClient
project. The names "FirstApp
" and "SecondApp
" are merely for demonstration purposes. Other names are acceptable to distinguish the client apps, such as App1
/App2
, Client1
/Client2
, 1
/2
, or any similar naming scheme.
"FirstApp
" and "SecondApp
" also appear in the browser's address bar because requests are routed to the two client apps using these names. Other valid URL route segments are supported, and the route segments don't strictly need to match the names used to route static web assets internally. Using "FirstApp
" and "SecondApp
" for both the internal static asset routing and app request routing is merely for convenance in this article's examples.
In the first client app's project file (MultipleBlazorApps.Client.csproj
), add a <StaticWebAssetBasePath>
property to a <PropertyGroup>
with a value of FirstApp
to set the base path for the project's static assets:
<StaticWebAssetBasePath>FirstApp</StaticWebAssetBasePath>
In the MultipleBlazorApps.SecondClient
app's project file (MultipleBlazorApps.SecondClient.csproj
):
Add a <StaticWebAssetBasePath>
property to a <PropertyGroup>
with a value of SecondApp
:
<StaticWebAssetBasePath>SecondApp</StaticWebAssetBasePath>
Add a project reference for the MultipleBlazorApps.Shared
project to an <ItemGroup>
:
<ItemGroup>
<ProjectReference Include="..\Shared\MultipleBlazorApps.Shared.csproj" />
</ItemGroup>
In the server app's project file (Server/MultipleBlazorApps.Server.csproj
), create a project reference for the added MultipleBlazorApps.SecondClient
client app in an <ItemGroup>
:
<ProjectReference Include="..\SecondClient\MultipleBlazorApps.SecondClient.csproj" />
In the server app's Properties/launchSettings.json
file, configure the applicationUrl
of the Kestrel profile (MultipleBlazorApps.Server
) to access the client apps at ports 5001 and 5002. If you configure your local environment to use the example domains, URLs for applicationUrl
can use firstapp.com
and secondapp.com
and not use the ports.
Note
The use of ports in this demonstration allows access to the client projects in a local browser without the need to configure a local hosting environment so that web browsers can access the client apps via the host configurations, firstapp.com
and secondapp.com
. In production scenarios, a typical configuration is to use subdomains to distinguish the client apps.
For example:
www.contoso.com
for site visitors and admin.contoso.com
for administrators.If you plan to serve pages or views from the server app, use the following applicationUrl
setting in the Properties/launchSettings.json
file, which permits the following access:
MultipleBlazorApps.Server
project) responds to requests at port 5000.MultipleBlazorApps.Client
project) are at port 5001.MultipleBlazorApps.SecondClient
project) are at port 5002."applicationUrl": "https://localhost:5000;https://localhost:5001;https://localhost:5002",
If you don't plan for the server app to serve pages or views and only serve the Blazor WebAssembly client apps, use the following setting, which permits the following access:
"applicationUrl": "https://localhost:5001;https://localhost:5002",
In the server app's Program.cs
file, remove the following code, which appears after the call to UseHttpsRedirection:
If you plan to serve pages or views from the server app, delete the following lines of code:
- app.UseBlazorFrameworkFiles();
- app.MapFallbackToFile("index.html");
If you plan for the server app to only serve the Blazor WebAssembly client apps, delete the following code:
- app.UseBlazorFrameworkFiles();
...
- app.UseRouting();
- app.MapRazorPages();
- app.MapControllers();
- app.MapFallbackToFile("index.html");
Leave Static File Middleware in place:
app.UseStaticFiles();
Add middleware that maps requests to the client apps. The following example configures the middleware to run when the request port is either 5001 for the first client app or 5002 for the second client app, or the request host is either firstapp.com
for the first client app or secondapp.com
for the second client app.
Note
Use of the hosts (firstapp.com
/secondapp.com
) on a local system with a local browser requires additional configuration that's beyond the scope of this article. For local testing of this scenario, we recommend using ports. Typical production apps are configured to use subdomains, such as www.contoso.com
for site visitors and admin.contoso.com
for administrators. With the proper DNS and server configuration, which is beyond the scope of this article and depends on the technologies used, the app responds to requests at whatever hosts are named in the following code.
Where you removed the app.UseBlazorFrameworkFiles();
line from Program.cs
, place the following code:
app.MapWhen(ctx => ctx.Request.Host.Port == 5001 ||
ctx.Request.Host.Equals("firstapp.com"), first =>
{
first.Use((ctx, nxt) =>
{
ctx.Request.Path = "/FirstApp" + ctx.Request.Path;
return nxt();
});
first.UseBlazorFrameworkFiles("/FirstApp");
first.UseStaticFiles();
first.UseStaticFiles("/FirstApp");
first.UseRouting();
first.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
endpoints.MapFallbackToFile("/FirstApp/{*path:nonfile}",
"FirstApp/index.html");
});
});
app.MapWhen(ctx => ctx.Request.Host.Port == 5002 ||
ctx.Request.Host.Equals("secondapp.com"), second =>
{
second.Use((ctx, nxt) =>
{
ctx.Request.Path = "/SecondApp" + ctx.Request.Path;
return nxt();
});
second.UseBlazorFrameworkFiles("/SecondApp");
second.UseStaticFiles();
second.UseStaticFiles("/SecondApp");
second.UseRouting();
second.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
endpoints.MapFallbackToFile("/SecondApp/{*path:nonfile}",
"SecondApp/index.html");
});
});
Warning
API that relies on the Host header, such as HttpRequest.Host and RequireHost, are subject to potential spoofing by clients.
To prevent host and port spoofing, use one of the following approaches:
Add middleware that maps requests to the client apps. The following example configures the middleware to run when the request subpath is /FirstApp
for the first client app or /SecondApp
for the second client app.
Where you removed the app.UseBlazorFrameworkFiles();
line from Program.cs
, place the following code:
app.MapWhen(ctx => ctx.Request.Path.StartsWithSegments("/FirstApp",
StringComparison.OrdinalIgnoreCase), first =>
{
first.UseBlazorFrameworkFiles("/FirstApp");
first.UseStaticFiles();
first.UseStaticFiles("/FirstApp");
first.UseRouting();
first.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
endpoints.MapFallbackToFile("/FirstApp/{*path:nonfile}",
"FirstApp/index.html");
});
});
app.MapWhen(ctx => ctx.Request.Path.StartsWithSegments("/SecondApp",
StringComparison.OrdinalIgnoreCase), second =>
{
second.UseBlazorFrameworkFiles("/SecondApp");
second.UseStaticFiles();
second.UseStaticFiles("/SecondApp");
second.UseRouting();
second.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
endpoints.MapFallbackToFile("/SecondApp/{*path:nonfile}",
"SecondApp/index.html");
});
});
Set the base path in each client app:
In the first client app's index.html
file (Client/wwwroot/index.html
), update the <base>
tag value to reflect the subpath. The trailing slash is required:
<base href="/FirstApp/" />
In the second client app's index.html
file (SecondClient/wwwroot/index.html
), update the <base>
tag value to reflect the subpath. The trailing slash is required:
<base href="/SecondApp/" />
For more information on UseStaticFiles, see ASP.NET Core Blazor static files.
For more information on UseBlazorFrameworkFiles
and MapFallbackToFile
, see the following resources:
Note
Documentation links to .NET reference source usually load the repository's default branch, which represents the current development for the next release of .NET. To select a tag for a specific release, use the Switch branches or tags dropdown list. For more information, see How to select a version tag of ASP.NET Core source code (dotnet/AspNetCore.Docs #26205).
Requests from the client apps to /WeatherForecast
in the server API are either to /FirstApp/WeatherForecast
or /SecondApp/WeatherForecast
depending on which client app makes the request. Therefore, the controller routes that return weather data from the server API require a modification to include the path segments.
In the server app's weather forecast controller (Controllers/WeatherForecastController.cs
), replace the existing route ([Route("[controller]")]
) to WeatherForecastController
with the following routes, which take into account the client request paths:
[Route("FirstApp/[controller]")]
[Route("SecondApp/[controller]")]
If you plan to serve pages from the server app, add an Index
Razor page to the Pages
folder of the server app:
Pages/Index.cshtml
:
@page
@model MultipleBlazorApps.Server.Pages.IndexModel
@{
ViewData["Title"] = "Home";
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Home</title>
</head>
<body>
<div class="main">
<div class="content px-4">
<div>
<h1>Welcome</h1>
<p>Hello from Razor Pages!</p>
</div>
</div>
</div>
</body>
</html>
Pages/Index.cshtml.cs
:
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace MultipleBlazorApps.Server.Pages;
public class IndexModel : PageModel
{
public void OnGet()
{
}
}
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace MultipleBlazorApps.Server.Pages
{
public class IndexModel : PageModel
{
public void OnGet()
{
}
}
}
Note
The preceding Index
page is a minimal example purely for demonstration purposes. If the app requires additional Razor Pages assets, such as a layout, styles, scripts, and imports, obtain them from an app created from the Razor Pages project template. For more information, see Introduction to Razor Pages in ASP.NET Core.
If you plan to serve MVC views from the server app, add an Index
view and a Home
controller:
Views/Home/Index.cshtml
:
@{
ViewData["Title"] = "Home";
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Home</title>
</head>
<body>
<div class="main">
<div class="content px-4">
<div>
<h1>Welcome</h1>
<p>Hello from MVC!</p>
</div>
</div>
</div>
</body>
</html>
Controllers/HomeController.cs
:
using Microsoft.AspNetCore.Mvc;
namespace MultipleBlazorApps.Server.Controllers;
public class HomeController : Controller
{
public IActionResult Index() => View();
}
Note
The preceding Index
view is a minimal example purely for demonstration purposes. If the app requires additional MVC assets, such as a layout, styles, scripts, and imports, obtain them from an app created from the MVC project template. For more information, see Get started with ASP.NET Core MVC.
For more information on using the Razor components from either of the client apps in pages or views of the server app, see Integrate ASP.NET Core Razor components with MVC or Razor Pages in hosted Blazor WebAssembly solutions.
Run the MultipleBlazorApps.Server
project:
https://localhost:5001
.https://localhost:5002
.Index
page or view at https://localhost:5000
.https://localhost:{DEFAULT PORT}/FirstApp
.https://localhost:{DEFAULT PORT}/SecondApp
.Index
page or view at https://localhost:{DEFAULT PORT}
.In the preceding example URLs, the {DEFAULT PORT}
placeholder is the default port defined by the MultipleBlazorApps.Server
project's Properties/launchSettings.json
file in its applicationUrl
value.
Important
When running the app with the dotnet watch
(or dotnet run
) command (.NET CLI), confirm that the command shell is open in the Server
folder of the solution.
When using Visual Studio's start button to run the app, confirm that the MultipleBlazorApps.Server
project is set as the startup project (highlighted in Solution Explorer).
When an asset is in a client app's wwwroot
folder, provide the static asset request path in components:
<img alt="..." src="{PATH AND FILE NAME}" />
The {PATH AND FILE NAME}
placeholder is the path and file name under wwwroot
.
For example, the source for a Jeep image (jeep-yj.png
) in the vehicle
folder of wwwroot
:
<img alt="Jeep Wrangler YJ" src="vehicle/jeep-yj.png" />
Add the Razor class library (RCL) to the solution as a new project:
ComponentLibrary
, which is also the RCL's assembly name. Don't select the Support pages and views checkbox.For each hosted Blazor WebAssembly client app, create a project reference for the RCL project by right-clicking each client project in Solution Explorer and selecting Add > Project Reference.
Use components from the RCL in the client apps with either of the following approaches:
Place an @using
directive at the top of the component for the RCL's namespace and add Razor syntax for the component. The following example is for an RCL with the assembly name ComponentLibrary
:
@using ComponentLibrary
...
<Component1 />
Provide the RCL's namespace along with the Razor syntax for the component. This approach doesn't require an @using
directive at the top of the component file. The following example is for an RCL with the assembly name ComponentLibrary
:
<ComponentLibrary.Component1 />
Note
An @using
directive can also be placed into each client app's _Import.razor
file, which makes the RCL's namespace globally available to components in that project.
When any other static asset is in the wwwroot
folder of an RCL, reference the static asset in a client app per the guidance in Reusable Razor UI in class libraries with ASP.NET Core:
<img alt="..." src="_content/{PACKAGE ID}/{PATH AND FILE NAME}" />
The {PACKAGE ID}
placeholder is the RCL's package ID. The package ID defaults to the project's assembly name if <PackageId>
isn't specified in the project file. The {PATH AND FILE NAME}
placeholder is path and file name under wwwroot
.
The following example shows the markup for a Jeep image (jeep-yj.png
) in the vehicle
folder of the RCL's wwwroot
folder. The following example is for an RCL with the assembly name ComponentLibrary
:
<img alt="Jeep Wrangler YJ" src="_content/ComponentLibrary/vehicle/jeep-yj.png" />
ASP.NET Core feedback
ASP.NET Core is an open source project. Select a link to provide feedback:
Events
Power BI DataViz World Championships
Feb 14, 4 PM - Mar 31, 4 PM
With 4 chances to enter, you could win a conference package and make it to the LIVE Grand Finale in Las Vegas
Learn moreTraining
Module
Publish a Blazor WebAssembly app and .NET API with Azure Static Web Apps - Training
Publish a Blazor WebAssembly app and .NET API with Azure Static Web Apps
Documentation
Learn how to configure Blazor startup.
ASP.NET Core Blazor static files
Learn how to configure and manage static files for Blazor apps.
ASP.NET Core Blazor configuration
Learn about Blazor app configuration, including app settings, authentication, and logging configuration.