Share assets across web and native clients using a Razor class library (RCL)
Note
This isn't the latest version of this article. For the current release, see the .NET 9 version of this article.
Warning
This version of ASP.NET Core is no longer supported. For more information, see the .NET and .NET Core Support Policy. For the current release, see the .NET 9 version of this article.
Important
This information relates to a pre-release product that may be substantially modified before it's commercially released. Microsoft makes no warranties, express or implied, with respect to the information provided here.
For the current release, see the .NET 9 version of this article.
Use a Razor class library (RCL) to share Razor components, C# code, and static assets across web and native client projects.
This article builds on the general concepts found in the following articles:
- Consume ASP.NET Core Razor components from a Razor class library (RCL)
- Reusable Razor UI in class libraries with ASP.NET Core
The examples in this article share assets between a server-side Blazor app and a .NET MAUI Blazor Hybrid app in the same solution:
- Although a server-side Blazor app is used, the guidance applies equally to Blazor WebAssembly apps sharing assets with a Blazor Hybrid app.
- Projects are in the same solution, but an RCL can supply shared assets to projects outside of a solution.
- The RCL is added as a project to the solution, but any RCL can be published as a NuGet package. A NuGet package can supply shared assets to web and native client projects.
- The order that the projects are created isn't important. However, projects that rely on an RCL for assets must create a project reference to the RCL after the RCL is created.
For guidance on creating an RCL, see Consume ASP.NET Core Razor components from a Razor class library (RCL). Optionally, access the additional guidance on RCLs that apply broadly to ASP.NET Core apps in Reusable Razor UI in class libraries with ASP.NET Core.
Target frameworks for ClickOnce deployments
To publish a WPF or Windows Forms project with a Razor class library (RCL) in .NET 6 with ClickOnce, the RCL must target net6.0-windows
in addition to net6.0
.
Example:
<TargetFrameworks>net6.0;net6.0-windows</TargetFrameworks>
For more information, see the following articles:
Share web UI Razor components, code, and static assets
Components from an RCL can be simultaneously shared by web and native client apps built using Blazor. The guidance in Consume ASP.NET Core Razor components from a Razor class library (RCL) explains how to share Razor components using a Razor class library (RCL). The same guidance applies to reusing Razor components from an RCL in a Blazor Hybrid app.
Component namespaces are derived from the RCL's package ID or assembly name and the component's folder path within the RCL. For more information, see ASP.NET Core Razor components. @using
directives can be placed in _Imports.razor
files for components and code, as the following example demonstrates for an RCL named SharedLibrary
with a Shared
folder of shared Razor components and a Data
folder of shared data classes:
@using SharedLibrary
@using SharedLibrary.Shared
@using SharedLibrary.Data
Place shared static assets in the RCL's wwwroot
folder and update static asset paths in the app to use the following path format:
_content/{PACKAGE ID/ASSEMBLY NAME}/{PATH}/{FILE NAME}
Placeholders:
{PACKAGE ID/ASSEMBLY NAME}
: The package ID or assembly name of the RCL.{PATH}
: Optional path within the RCL'swwwroot
folder.{FILE NAME}
: The file name of the static asset.
The preceding path format is also used in the app for static assets supplied by a NuGet package added to the RCL.
For an RCL named SharedLibrary
and using the minified Bootstrap stylesheet as an example:
_content/SharedLibrary/css/bootstrap/bootstrap.min.css
For additional information on how to share static assets across projects, see the following articles:
- Consume ASP.NET Core Razor components from a Razor class library (RCL)
- Reusable Razor UI in class libraries with ASP.NET Core
The root index.html
file is usually specific to the app and should remain in the Blazor Hybrid app or the Blazor WebAssembly app. The index.html
file typically isn't shared.
The root Razor Component (App.razor
or Main.razor
) can be shared, but often might need to be specific to the hosting app. For example, App.razor
is different in the server-side Blazor and Blazor WebAssembly project templates when authentication is enabled. You can add the AdditionalAssemblies
parameter to specify the location of any shared routable components, and you can specify a shared default layout component for the router by type name.
Provide code and services independent of hosting model
When code must differ across hosting models or target platforms, abstract the code as interfaces and inject the service implementations in each project.
The following weather data example abstracts different weather forecast service implementations:
- Using an HTTP request for Blazor Hybrid and Blazor WebAssembly.
- Requesting data directly for a server-side Blazor app.
The example uses the following specifications and conventions:
- The RCL is named
SharedLibrary
and contains the following folders and namespaces:Data
: Contains theWeatherForecast
class, which serves as a model for weather data.Interfaces
: Contains the service interface for the service implementations, namedIWeatherForecastService
.
- The
FetchData
component is maintained in thePages
folder of the RCL, which is routable by any of the apps consuming the RCL. - Each Blazor app maintains a service implementation that implements the
IWeatherForecastService
interface.
Data/WeatherForecast.cs
in the RCL:
namespace SharedLibrary.Data;
public class WeatherForecast
{
public DateTime Date { get; set; }
public int TemperatureC { get; set; }
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
public string? Summary { get; set; }
}
Interfaces/IWeatherForecastService.cs
in the RCL:
using SharedLibrary.Data;
namespace SharedLibrary.Interfaces;
public interface IWeatherForecastService
{
Task<WeatherForecast[]?> GetForecastAsync(DateTime startDate);
}
The _Imports.razor
file in the RCL includes the following added namespaces:
@using SharedLibrary.Data
@using SharedLibrary.Interfaces
Services/WeatherForecastService.cs
in the Blazor Hybrid and Blazor WebAssembly apps:
using System.Net.Http.Json;
using SharedLibrary.Data;
using SharedLibrary.Interfaces;
namespace {APP NAMESPACE}.Services;
public class WeatherForecastService : IWeatherForecastService
{
private readonly HttpClient http;
public WeatherForecastService(HttpClient http)
{
this.http = http;
}
public async Task<WeatherForecast[]?> GetForecastAsync(DateTime startDate) =>
await http.GetFromJsonAsync<WeatherForecast[]?>("WeatherForecast");
}
In the preceding example, the {APP NAMESPACE}
placeholder is the app's namespace.
Services/WeatherForecastService.cs
in the server-side Blazor app:
using SharedLibrary.Data;
using SharedLibrary.Interfaces;
namespace {APP NAMESPACE}.Services;
public class WeatherForecastService : IWeatherForecastService
{
private static readonly string[] Summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot"
};
public async Task<WeatherForecast[]?> GetForecastAsync(DateTime startDate) =>
await Task.FromResult(Enumerable.Range(1, 5)
.Select(index => new WeatherForecast
{
Date = startDate.AddDays(index),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = Summaries[Random.Shared.Next(Summaries.Length)]
}).ToArray());
}
In the preceding example, the {APP NAMESPACE}
placeholder is the app's namespace.
The Blazor Hybrid, Blazor WebAssembly, and server-side Blazor apps register their weather forecast service implementations (Services.WeatherForecastService
) for IWeatherForecastService
.
The Blazor WebAssembly project also registers an HttpClient. The HttpClient registered in an app created from the Blazor WebAssembly project template is sufficient for this purpose. For more information, see Call a web API from an ASP.NET Core Blazor app.
Pages/FetchData.razor
in the RCL:
@page "/fetchdata"
@inject IWeatherForecastService ForecastService
<PageTitle>Weather forecast</PageTitle>
<h1>Weather forecast</h1>
@if (forecasts == null)
{
<p><em>Loading...</em></p>
}
else
{
<table class="table">
<thead>
<tr>
<th>Date</th>
<th>Temp. (C)</th>
<th>Temp. (F)</th>
<th>Summary</th>
</tr>
</thead>
<tbody>
@foreach (var forecast in forecasts)
{
<tr>
<td>@forecast.Date.ToShortDateString()</td>
<td>@forecast.TemperatureC</td>
<td>@forecast.TemperatureF</td>
<td>@forecast.Summary</td>
</tr>
}
</tbody>
</table>
}
@code {
private WeatherForecast[]? forecasts;
protected override async Task OnInitializedAsync()
{
forecasts = await ForecastService.GetForecastAsync(DateTime.Now);
}
}
Additional resources
ASP.NET Core