--- title: Share assets across web and native clients using a Razor class library (RCL) author: guardrex description: Learn how to share Razor components, C# code, and static assets across web and native clients using a Razor class library (RCL). monikerRange: '>= aspnetcore-6.0' ms.author: riande ms.custom: mvc ms.date: 11/12/2024 uid: blazor/hybrid/class-libraries --- # Share assets across web and native clients using a Razor class library (RCL) [!INCLUDE[](~/includes/not-latest-version.md)] 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: * * 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](/visualstudio/ide/solutions-and-projects-in-visual-studio#solutions), 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 . Optionally, access the additional guidance on RCLs that apply broadly to ASP.NET Core apps in . :::moniker range="= aspnetcore-6.0" ## Target frameworks for ClickOnce deployments To publish a WPF or Windows Forms project with a Razor class library (RCL) in .NET 6 with [ClickOnce](/visualstudio/deployment/clickonce-security-and-deployment), the RCL must target `net6.0-windows` in addition to `net6.0`. Example: ```xml net6.0;net6.0-windows ``` For more information, see the following articles: * [Target frameworks in SDK-style projects](/dotnet/standard/frameworks) * [ClickOnce security and deployment](/visualstudio/deployment/clickonce-security-and-deployment) * [Publish ClickOnce applications](/visualstudio/deployment/publishing-clickonce-applications) :::moniker-end ## Sample app For an example of the scenarios described in this article, see the [eShop Reference Application (AdventureWorks) (`dotnet/eShop` GitHub repository)](https://github.com/dotnet/eShop). The .NET MAUI Blazor Hybrid app is in the `src/HybridApp` folder. For a version of the sample app tailored for Azure hosting, see the [`Azure-Samples/eShopOnAzure` GitHub repository](https://github.com/Azure-Samples/eShopOnAzure). The sample app showcases the following technologies: * [.NET](https://dotnet.microsoft.com/download/dotnet) * [ASP.NET Core](https://dotnet.microsoft.com/apps/aspnet) * [Blazor](https://dotnet.microsoft.com/apps/aspnet/web-apps/blazor) * [.NET MAUI](https://dotnet.microsoft.com/apps/maui) * [.NET Aspire](/dotnet/aspire/get-started/aspire-overview) * [Docker](https://docs.docker.com/get-started/docker-overview/) ## 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 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 . [`@using`](xref:mvc/views/razor#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: ```razor @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: > :::no-loc text="_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's `wwwroot` 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: > :::no-loc text="_content/SharedLibrary/css/bootstrap/bootstrap.min.css"::: For additional information on how to share static assets across projects, see the following articles: * * 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](xref:blazor/fundamentals/routing#route-to-components-from-multiple-assemblies) to specify the location of any shared routable components, and you can specify a [shared default layout component for the router](xref:blazor/fundamentals/routing#route-templates) 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 the `WeatherForecast` class, which serves as a model for weather data. * `Interfaces`: Contains the service interface for the service implementations, named `IWeatherForecastService`. * The `FetchData` component is maintained in the `Pages` 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: ```csharp 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: ```csharp using SharedLibrary.Data; namespace SharedLibrary.Interfaces; public interface IWeatherForecastService { Task GetForecastAsync(DateTime startDate); } ``` The `_Imports.razor` file in the RCL includes the following added namespaces: ```razor @using SharedLibrary.Data @using SharedLibrary.Interfaces ``` `Services/WeatherForecastService.cs` in the Blazor Hybrid and Blazor WebAssembly apps: ```csharp 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 GetForecastAsync(DateTime startDate) => await http.GetFromJsonAsync("WeatherForecast"); } ``` In the preceding example, the `{APP NAMESPACE}` placeholder is the app's namespace. `Services/WeatherForecastService.cs` in the server-side Blazor app: ```csharp 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 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 . The registered in an app created from the Blazor WebAssembly project template is sufficient for this purpose. For more information, see . `Pages/FetchData.razor` in the RCL: ```razor @page "/fetchdata" @inject IWeatherForecastService ForecastService Weather forecast

Weather forecast

@if (forecasts == null) {

Loading...

} else { @foreach (var forecast in forecasts) { }
Date Temp. (C) Temp. (F) Summary
@forecast.Date.ToShortDateString() @forecast.TemperatureC @forecast.TemperatureF @forecast.Summary
} @code { private WeatherForecast[]? forecasts; protected override async Task OnInitializedAsync() { forecasts = await ForecastService.GetForecastAsync(DateTime.Now); } } ``` ## Additional resources * * * [CSS isolation support with Razor class libraries](xref:blazor/components/css-isolation#razor-class-library-rcl-support)