Subpath scenario for multiple hosted WASM apps (#29142)

pull/29165/head
Luke Latham 2023-05-04 10:52:44 -04:00 committed by GitHub
parent f586a0c2e8
commit 64f9fdb051
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 217 additions and 57 deletions

View File

@ -5,8 +5,9 @@ description: Learn how to configure a hosted Blazor WebAssembly app to host mult
monikerRange: '>= aspnetcore-3.1'
ms.author: riande
ms.custom: mvc
ms.date: 11/08/2022
ms.date: 05/04/2023
uid: blazor/host-and-deploy/multiple-hosted-webassembly
zone_pivot_groups: blazor-multiple-hosted-wasm-apps
---
# Multiple hosted ASP.NET Core Blazor WebAssembly apps
@ -16,17 +17,39 @@ This article explains how to configure a hosted Blazor WebAssembly app to host m
## Configuration
Hosted Blazor [solutions](xref:blazor/tooling#visual-studio-solution-file-sln) can serve 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`).
In the following example:
:::zone pivot="port-domain"
With the current hosting selection, this article covers port/domain hosting (for example, `:5001`/`:5002` or `firstapp.com`/`secondapp.com`).
In the following examples:
* The project name of the hosted Blazor WebAssembly app is `MultipleBlazorApps` in a folder named `MultipleBlazorApps`.
* The three projects in the solution before a second client app is added are `MultipleBlazorApps.Client` in the `Client` folder, `MultipleBlazorApps.Server` in the `Server` folder, and `MultipleBlazorApps.Shared` in the `Shared` folder.
* The initial (first) client app is the default client project of the solution created from the Blazor WebAssembly project template. The first client app is accessible in a browser at port 5001 or with a host of `firstapp.com`.
* A second client app is added to the solution, `MultipleBlazorApps.SecondClient` in a folder named `SecondClient`. The second client app is accessible in a browser at port 5002 or with a host of `secondapp.com`.
* Optionally, the server project (`MultipleBlazorApps.Server`) can serve pages or views as a formal Razor Pages or MVC app.
* The initial (first) client app is the default client project of the solution created from the Blazor WebAssembly project template.
* A second client app is added to the solution, `MultipleBlazorApps.SecondClient` in a folder named `SecondClient`.
* Optionally, the server project (`MultipleBlazorApps.Server`) can serve pages or views as a Razor Pages or MVC app.
* The first client app is accessible in a browser at port 5001 or with a host of `firstapp.com`. The second client app is accessible in a browser at port 5002 or with a host of `secondapp.com`.
The example shown in this section requires additional configuration for:
:::zone-end
:::zone pivot="route-subpath"
With the current selection, this article covers route subpath hosting (for example, `/FirstApp` and `/SecondApp`).
In the following examples:
* The project name of the hosted Blazor WebAssembly app is `MultipleBlazorApps` in a folder named `MultipleBlazorApps`.
* The three projects in the solution before a second client app is added are `MultipleBlazorApps.Client` in the `Client` folder, `MultipleBlazorApps.Server` in the `Server` folder, and `MultipleBlazorApps.Shared` in the `Shared` folder.
* The initial (first) client app is the default client project of the solution created from the Blazor WebAssembly project template.
* A second client app is added to the solution, `MultipleBlazorApps.SecondClient` in a folder named `SecondClient`.
* Optionally, the server project (`MultipleBlazorApps.Server`) can serve pages or views as a formal Razor Pages or MVC app.
* Both client apps use the default port defined by the `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.
:::zone-end
The examples shown in this article require additional configuration for:
* Accessing the apps directly at the example host domains, `firstapp.com` and `secondapp.com`.
* Certificates for the client apps to enable TLS/HTTPS security.
@ -34,7 +57,7 @@ The example shown in this section requires additional configuration for:
* Integration of Razor components into pages or views.
* Prerendering Razor components.
The preceding configurations are beyond the scope of this demonstration. For more information, see the following resources:
The preceding configurations are beyond the scope of this article. For more information, see the following resources:
* [Host and deploy articles](xref:host-and-deploy/index)
* <xref:security/enforcing-ssl>
@ -44,31 +67,48 @@ Use an existing hosted Blazor WebAssembly [solution](xref:blazor/tooling#visual-
Use a folder for the solution named `MultipleBlazorApps` and name the project `MultipleBlazorApps`.
Initial projects in the solution and their folders:
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.
* `MultipleBlazorApps.Client` is a Blazor WebAssembly client app in the `Client` folder.
* `MultipleBlazorApps.Server` is an ASP.NET Core server app that serves Blazor WebAssembly apps) in the `Server` folder. Optionally, the server app can also serve pages or views, as a traditional Razor Pages or MVC app.
* `MultipleBlazorApps.Shared` is a shared resources project for the client and server projects in the `Shared` folder.
Make the following changes to the `MultipleBlazorApps.SecondClient` project:
In the client app's project file (`MultipleBlazorApps.Client.csproj`), add a [`<StaticWebAssetBasePath>` property](xref:blazor/fundamentals/static-files#static-web-asset-base-path) to a `<PropertyGroup>` with a value of `FirstApp` to set the base path for the project's static assets:
* Copy the `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 **:::no-loc text="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.
* Delete the `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.
:::zone pivot="port-domain"
> [!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.
:::zone-end
:::zone pivot="route-subpath"
> [!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.
:::zone-end
In the first client app's project file (`MultipleBlazorApps.Client.csproj`), add a [`<StaticWebAssetBasePath>` property](xref:blazor/fundamentals/static-files#static-web-asset-base-path) to a `<PropertyGroup>` with a value of `FirstApp` to set the base path for the project's static assets:
```xml
<StaticWebAssetBasePath>FirstApp</StaticWebAssetBasePath>
```
> [!NOTE]
> The demonstration in this section uses web asset path names of `FirstApp` and `SecondApp`, but these specific names are merely for demonstration purposes. Any base path segments that distinguish the client apps are acceptable, such as `App1`/`App2`, `Client1`/`Client2`, `1`/`2`, or any similar naming scheme. These base path segments are used internally to route requests and serve responses and are ***not*** seen in a browser's address bar.
Add a second client app to the solution. 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:
* Name the project `MultipleBlazorApps.SecondClient` and place the app into a folder named `SecondClient`.
* The solution folder created from the project template contains the following solution file and folders after the `SecondClient` folder is added:
* `Client` (folder)
* `SecondClient` (folder)
* `Server` (folder)
* `Shared` (folder)
* `MultipleBlazorApps.sln` (file)
In the `MultipleBlazorApps.SecondClient` app's project file (`MultipleBlazorApps.SecondClient.csproj`):
* Add a `<StaticWebAssetBasePath>` property to a `<PropertyGroup>` with a value of `SecondApp`:
@ -91,7 +131,9 @@ In the server app's project file (`Server/MultipleBlazorApps.Server.csproj`), cr
<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 two ports.
:::zone pivot="port-domain"
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.
@ -104,9 +146,9 @@ In the server app's `Properties/launchSettings.json` file, configure the `applic
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:
* The Razor Pages or MVC app responds to requests at port 5000.
* Responses to requests for the first client are at port 5001.
* Responses to requests for the second client are at port 5002.
* Optionally, the Razor Pages or MVC app (`MultipleBlazorApps.Server` project) responds to requests at port 5000.
* Responses to requests for the first client (`MultipleBlazorApps.Client` project) are at port 5001.
* Responses to requests for the second client (`MultipleBlazorApps.SecondClient` project) are at port 5002.
```json
"applicationUrl": "https://localhost:5000;https://localhost:5001;https://localhost:5002",
@ -121,14 +163,20 @@ If you don't plan for the server app to serve pages or views and only serve the
"applicationUrl": "https://localhost:5001;https://localhost:5002",
```
:::zone-end
In the server app's `Program.cs` file, remove the following code, which appears after the call to <xref:Microsoft.AspNetCore.Builder.HttpsPolicyBuilderExtensions.UseHttpsRedirection%2A>:
* If you plan to serve pages or views from the server app, delete the following line of code:
* If you plan to serve pages or views from the server app, delete the following lines of code:
```diff
- app.UseBlazorFrameworkFiles();
```
```diff
- app.MapFallbackToFile("index.html");
```
* If you plan for the server app to only serve the Blazor WebAssembly client apps, delete the following code:
```diff
@ -142,15 +190,14 @@ In the server app's `Program.cs` file, remove the following code, which appears
- app.MapFallbackToFile("index.html");
```
* Add middleware that maps requests to the client apps. The following example configures the middleware to run when:
:::zone pivot="port-domain"
* The request port is either 5001 for the first client app or 5002 for the second client app.
* The request host is either `firstapp.com` for the first client app or `secondapp.com` for the second client app.
* 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.
Place the following code where the code was removed earlier in the `Program.cs` file:
Where you removed the `app.UseBlazorFrameworkFiles();` line from `Program.cs`, place the following code:
```csharp
app.MapWhen(ctx => ctx.Request.Host.Port == 5001 ||
@ -198,6 +245,62 @@ In the server app's `Program.cs` file, remove the following code, which appears
});
```
:::zone-end
:::zone pivot="route-subpath"
* 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:
```csharp
app.MapWhen(ctx => ctx.Request.Path.StartsWithSegments("/FirstApp"), 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"), 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:
```html
<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:
```html
<base href="/SecondApp/" />
```
:::zone-end
For more information on <xref:Microsoft.AspNetCore.Builder.StaticFileExtensions.UseStaticFiles%2A>, see <xref:blazor/fundamentals/static-files#static-file-middleware>.
For more information on `UseBlazorFrameworkFiles` and `MapFallbackToFile`, see the following resources:
@ -207,6 +310,15 @@ For more information on `UseBlazorFrameworkFiles` and `MapFallbackToFile`, see t
[!INCLUDE[](~/includes/aspnetcore-repo-ref-source-links.md)]
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:
```csharp
[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`:
@ -215,13 +327,27 @@ If you plan to serve pages from the server app, add an `Index` Razor page to the
@page
@model MultipleBlazorApps.Server.Pages.IndexModel
@{
ViewData["Title"] = "Home page";
ViewData["Title"] = "Home";
}
<div>
<h1>Welcome</h1>
<p>Hello from Razor Pages!</p>
</div>
<!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`:
@ -264,7 +390,7 @@ namespace MultipleBlazorApps.Server.Pages
:::moniker-end
> [!NOTE]
> 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 <xref:razor-pages/index>.
> 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 <xref:razor-pages/index>.
If you plan to serve MVC views from the server app, add an `Index` view and a `Home` controller:
@ -272,13 +398,27 @@ If you plan to serve MVC views from the server app, add an `Index` view and a `H
```cshtml
@{
ViewData["Title"] = "Home Page";
ViewData["Title"] = "Home";
}
<div>
<h1>Welcome</h1>
<p>Hello from MVC!</p>
</div>
<!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`:
@ -321,31 +461,43 @@ namespace MultipleBlazorApps.Server.Controllers
:::moniker-end
> [!NOTE]
> 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 <xref:tutorials/first-mvc-app/start-mvc>.
> 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 <xref:tutorials/first-mvc-app/start-mvc>.
The middleware added to the server app's request processing pipeline earlier modifies incoming requests to `/WeatherForecast` to either `/FirstApp/WeatherForecast` or `/SecondApp/WeatherForecast` depending on the port (5001/5002) or domain (`firstapp.com`/`secondapp.com`). Therefore, the controller routes that return weather data from the server app to the client apps require a modification.
For more information on using the Razor components from either of the client apps in pages or views of the server app, see <xref:blazor/components/prerendering-and-integration?pivots=webassembly>.
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 apps' base paths added by the middleware (`FirstApp`/`SecondApp`):
```csharp
[Route("FirstApp/[controller]")]
[Route("SecondApp/[controller]")]
```
## Run the app
Run the `MultipleBlazorApps.Server` project:
:::zone pivot="port-domain"
* Access the initial client app at `https://localhost:5001`.
* Access the added client app at `https://localhost:5002`.
* If the server app is configured to serve pages or views, access the `Index` page or view at `https://localhost:5000`.
For more information on using the Razor components from either of the client apps in pages or views of the server app, see <xref:blazor/components/prerendering-and-integration?pivots=webassembly>.
:::zone-end
:::zone pivot="route-subpath"
* Access the initial client app at `https://localhost:{DEFAULT PORT}/FirstApp`.
* Access the added client app at `https://localhost:{DEFAULT PORT}/SecondApp`.
* If the server app is configured to serve pages or views, access the `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.
:::zone-end
> [!IMPORTANT]
> When running the app with the `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 set as the startup project (highlighted in Solution Explorer).
## Static assets
When an asset is in a client app's `wwwroot` folder, provide the static asset request path in components:
```html
<img alt="..." src="/{PATH AND FILE NAME}" />
<img alt="..." src="{PATH AND FILE NAME}" />
```
The `{PATH AND FILE NAME}` placeholder is the path and file name under `wwwroot`.
@ -353,7 +505,7 @@ 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`:
```html
<img alt="Jeep Wrangler YJ" src="/vehicle/jeep-yj.png" />
<img alt="Jeep Wrangler YJ" src="vehicle/jeep-yj.png" />
```
## Razor class library (RCL) support

View File

@ -58,3 +58,11 @@ groups:
title: Graph SDK v5
- id: named-client-graph-api
title: Named HttpClient with Graph API
- id: blazor-multiple-hosted-wasm-apps
title: Hosting approach
prompt: Choose a hosting approach for the client apps
pivots:
- id: port-domain
title: Port/domain hosting
- id: route-subpath
title: Route subpath hosting