top of page
Writer's pictureThe Tech Platform

Building Modular Blazor Apps: RCLs, Static SSR, and Interactive Strategies

Blazor, a web framework by Microsoft, empowers developers to build interactive web applications using C# and .NET. With Blazor, you can create reusable UI components, manage state, and handle user interactions—all while targeting client-side (WebAssembly) and server-side (ASP.NET Core) scenarios.

Building Modular Blazor Apps: RCLs, Static SSR, and Interactive Strategies

In this article, we’ll explore three essential aspects of Blazor components:

  1. Razor Class Libraries (RCLs): Discover how RCLs simplify component sharing, promote code reuse, and enhance modularity within your Blazor projects.

  2. Static Server-Side Rendering (SSR): Learn about the benefits of pre-rendering components on the server and how to optimize initial load times.

  3. Interactive Strategies: Understand how to handle links, forms, and other interactive elements across different rendering modes.


What are Razor Class Libraries (RCLs)?

Razor Class Libraries (RCLs) are a powerful feature introduced in ASP.NET Core. They simplify the management and distribution of reusable UI components, including Razor pages, views, and controllers. Think of RCLs as a way to package and share UI-related code across multiple projects within a solution or even among different solutions.


Why RCLs Matter for Component Authors:

  1. Packaging UI Components: RCLs allow you to bundle Razor pages, views, layouts, partials, and other UI-related assets into a class library. These components can then be referenced and consumed by other projects.

  2. Modularity and Separation of Concerns: By isolating UI components in separate libraries, you achieve better modularity and separation of concerns. This makes your codebase cleaner and more maintainable.

  3. Code Reuse: RCLs promote code reuse. You can create a set of common UI components (e.g., navigation menus, widgets, authentication pages) and share them across different parts of your application or even across applications.


Here are the steps to create RCL:

STEP 1: Define Components and Services:

In the RCL project, create folders to organize your components. Common folders include:

  • Pages: For Razor pages.

  • Views: For Razor views.

  • Components: For Blazor components.

  • Services: For any services or utilities.


Add your Razor pages, views, and components within these folders. For example: Create a Razor page named MyPage.cshtml in the Pages folder. Design your Blazor components (e.g., MyButton.razor, MyChart.razor) in the Components folder.


STEP 2: Resource Files (Optional):

If your components require CSS, JavaScript, or other static files, add them to the RCL project. Create a wwwroot folder and organize your resources there.


STEP 3: Build and Package:

Build your RCL project to ensure there are no compilation errors. Once successfully built, the RCL generates a .dll file containing your components.


STEP 4: Referencing the RCL:

In your main ASP.NET Core application (e.g., a Blazor app), add a reference to your RCL project.


In the main app’s .csproj file, include the following line:

<ItemGroup> <ProjectReference Include="path-to-MyComponentLibrary.csproj" /> </ItemGroup>

Now you can use the components from your RCL in your main app.


STEP 5: Consuming Components:

In your main app, import the namespaces corresponding to your RCL components. Use the components in your Razor pages or Blazor components.


Understanding Static Server-Side Rendering

Static SSR is a rendering mode where components execute on the server when handling an incoming HTTP request. When a user requests a page (e.g., a Blazor page), the server processes the incoming HTTP request. The server-side component executes on the server, following the component’s logic and data flow. During execution, the component generates HTML output based on its rendering instructions.


After executing the component, the server produces an HTML representation of the component’s rendered content. This HTML is then included in the HTTP response sent back to the client (usually the user’s browser). The client receives the pre-rendered HTML and displays it immediately.


Unlike client-side rendering, where components run in the browser, static SSR doesn’t maintain ongoing connections or client-side state. The server discards the component and its state after rendering, which reduces memory usage and server resources. Subsequent interactions (e.g., user clicks) trigger new HTTP requests, repeating the process.


Benefits of static SSR:

  1. Static SSR reduces the load on the client, as the server performs the heavy lifting.

  2. It is valuable in scenarios with high traffic or limited server capacity.

  3. Unlike client-side Blazor, which establishes a WebSocket connection for real-time updates, static SSR doesn’t require an ongoing connection.


Limitations of Static SSR:

Here are some of the limitations of Static - Side Rendering in Blazor:


1. Event handler restrictions

In static SSR, event handlers that rely on client-side .NET code (such as @onclick) won’t work during the initial rendering. These handlers require JavaScript interop, which isn’t available until the client-side Blazor runtime initializes.

<!-- MyComponent.razor -->

<button @onclick="HandleClick">Click me!</button>

@code {
    private void HandleClick()
    {
        // This logic won't execute during initial rendering.
        // It requires client-side Blazor runtime.
    }
}

2. Immediate discarding of State

After rendering, the server discards the component and its renderer state. Unlike client-side Blazor, where components persist in memory, static SSR doesn’t maintain ongoing connections. Subsequent interactions (e.g., user clicks) trigger new requests, re-rendering the component.


3. Special exception for @onsubmit

The @onsubmit event handler for forms is an exception. Even in static SSR, form submissions are handled on the server, allowing validation and processing before the response is sent back to the client.

<!-- MyForm.razor -->

<form @onsubmit="HandleSubmit">
    <input type="text" />
    <button type="submit">Submit</button>
</form>

@code {
    private void HandleSubmit()
    {
        // Handle form submission logic (runs on the server).
    }
}

Strategies for Handling Static SSR


Read-Only Components

Read-only components produce static content—content that doesn’t require client-side interactivity. These components are ideal for static SSR because they don’t rely on JavaScript or user interactions. Examples include displaying user profiles, static charts, or informational content.

<!-- ReadOnlyComponent.razor -->

<h3>User Profile</h3>
<p>Name: John Doe</p>
<p>Email: john@example.com</p>
<!-- Other static content here -->

Hybrid Approach

The hybrid approach detects the render mode (static or interactive) using the @rendermode directive. Based on the context, you adjust the behavior of your components:


Static SSR Mode:

  • Provide read-only functionality.

  • Avoid event handlers that require client-side .NET code.

  • Optimize for initial load time.


Interactive SSR Mode:

  • Allow full interactivity.

  • Use event handlers like @onclick.

  • Leverage client-side Blazor features.


<!-- HybridComponent.razor -->

@if (@rendermode == "static")
{
    <p>This content is static.</p>
}
else
{
    <button @onclick="HandleClick">Click me!</button>
}

@code {
    [Parameter] public string RenderMode { get; set; }

    private void HandleClick()
    {
        // Handle button click logic (runs on the client).
    }
}

Client-Side Rendering (CSR) Fallback

When static SSR limitations are too restrictive (e.g., for complex interactivity), consider providing a fallback mechanism for client-side rendering. You can load additional JavaScript and switch to client-side Blazor when needed.

<!-- CSRComponent.razor -->

@if (@rendermode == "static")
{
    <p>This content is static.</p>
}
else
{
    <p>Loading interactive content...</p>
    <!-- Load client-side Blazor scripts here -->
}

@code {
    [Parameter] public string RenderMode { get; set; }
}

Streaming Rendering and Incremental UI Updates:

Streaming rendering allows the server to send parts of the rendered content to the client as they become available. It improves perceived performance by showing content sooner, especially for large pages or complex components. In Blazor, streaming rendering is particularly useful during static SSR.


Apply the [StreamRendering] attribute to a component to enable streaming rendering. It instructs Blazor to send the initial part of the component’s rendering to the client as soon as it’s available. Subsequent parts are sent incrementally, allowing the client to start rendering before the entire component is processed on the server.

<!-- StreamingComponent.razor -->

@page "/streaming"

<h3>Streaming Rendering Example</h3>
<p>This content is streamed to the client.</p>

@code {
    [StreamRendering]
    private async Task OnInitializedAsync()
    {
        // Simulate async data loading (e.g., fetching from an API).
        await Task.Delay(1000);
    }
}

Consistency in Data-Loading Patterns

Let’s compare Static Server-Side Rendering (SSR) with Interactive Rendering (client-side Blazor)

Aspect

Static SSR (Server-Side Rendering)

Interactive Rendering (Client-Side Blazor)

Rendering Process

Components fully render on the server before sending HTML to the client.

Components execute both on the server and in the browser, allowing dynamic behavior.

Initial Load Time

Optimized for initial load time.

May have slightly longer initial load due to client-side processing.

Interactivity

No client-side interactivity during initial load.

Full interactivity with event handlers (e.g., @onclick).

Data Loading Consistency

Fetch data during server-side rendering (e.g., in OnInitializedAsync).

Use the same data-fetching logic for both modes. Avoid unnecessary re-fetching.


Handling Links and Forms Across Render Modes:

The importance of handling links and forms consistently across different render modes (static SSR and interactive rendering) in Blazor.

  1. User Experience Consistency:

    • Users expect consistent behavior when interacting with links and forms, regardless of whether the page initially loads via static SSR or client-side rendering.

    • Inconsistent behavior can confuse users and negatively impact their experience.

  2. Seamless Navigation:

    • Links play a crucial role in navigation. Whether it’s a simple link (<a> tag) or an enhanced navigation component (NavLink), users rely on them to move between pages.

    • Ensuring equivalent behavior for links across render modes ensures seamless navigation transitions.

  3. Form Handling and Validation:

    • Forms are essential for user input (e.g., login forms, search forms, data submission).

    • Consistent form behavior includes validation, input handling, and submission logic.

    • Forms should work reliably whether rendered statically or interactively.


Handling Links

Use the @href Attribute: When creating links, use the @href attribute to specify the target URL. Whether it’s a static SSR link or a client-side navigation link, ensure they lead to the same destination.


Example of a simple link in Blazor:

<!-- MyLink.razor -->
<a href="/about">About Us</a>

For client-side routing, consider using Blazor’s built-in NavLink component. NavLink provides additional features like active link styling and smooth transitions. It’s useful when building dynamic navigation menus or handling active routes.


Example of using NavLink:

<!-- MyNavigation.razor -->
<NavLink href="/products" Match="NavLinkMatch.All">
    Products
</NavLink>

Handling Forms

When building Blazor applications with static SSR, it’s essential to ensure consistent behavior for forms. Forms allow users to input data, submit requests, and interact with your application. Here’s how to handle forms effectively:


Define Forms as Usual:

  • Create your forms using standard HTML elements (e.g., <form>) or Blazor’s built-in <EditForm> component.

  • Include input fields (textboxes, checkboxes, etc.), labels, and any other form elements you need.


Validation and Input Handling:

  • Implement form validation logic to ensure data integrity.

  • Use Blazor’s validation features (e.g., data annotations, custom validation logic) to validate user input.

  • Bind form fields to model properties using @bind-Value or other appropriate binding techniques.


Handle Form Submissions on the Server:

  • In static SSR, form submissions are processed on the server.

  • Define an event handler for form submission (usually OnSubmitAsync).

  • Inside the event handler, handle the form data, perform any necessary server-side processing, and respond accordingly.


Example of a simple form in static SSR:

<!-- MyForm.razor -->
<EditForm Model="@myModel" OnSubmit="HandleSubmit">
	<label for="name">Name:</label>
    <InputText @bind-Value="myModel.Name" />

    <button type="submit">Submit</button>
</EditForm>

@code {
    private MyModel myModel = new MyModel();

    private async Task HandleSubmit()
    {
        // Handle form submission logic (runs on the server).
	   // For example, save data to a database or perform other server-side actions.
    }
}

Avoiding APIs Specific to Static SSR

While static SSR provides benefits, it’s essential to use the right tools and avoid relying on context that might not be available during server-side rendering.


Avoid HttpContext in Static SSR

  1. HttpContext Limitations:

    During static SSR, the HttpContext object is available only on the server. Components executing on the server can access it, but client-side components won’t have access.


  2. Avoid Direct Reliance: Discourage direct reliance on HttpContext within your components.

    Instead, use alternatives that work consistently across static SSR and interactive rendering.


Suggested Alternatives


1. NavigationManager for URL-Related Tasks:

Use NavigationManager to handle navigation and URL-related tasks. It provides methods like NavigateTo for programmatic navigation.


Example of using NavigationManager:

@inject NavigationManager Navigation 

<button @onclick="NavigateToAbout">Go to About</button> 
@code 
{ 
	private void NavigateToAbout() 
	{ 
		Navigation.NavigateTo("/about"); 
	} 
}

2. AuthenticationStateProvider for Authentication State:

For managing the authentication state, use AuthenticationStateProvider. It provides information about the user’s authentication status.


Example of using AuthenticationStateProvider:

@inject AuthenticationStateProvider AuthenticationState 

<p>User is authenticated: @AuthenticationState.User.Identity.IsAuthenticated</p>

You ensure consistent behavior across rendering modes by avoiding direct reliance on HttpContext and using recommended alternatives.


Conclusion

In this article on building modular Blazor apps, we’ve delved into essential concepts that empower developers to create efficient and user-friendly web applications. As you continue your Blazor journey, keep these principles in mind. Whether you’re building enterprise applications, personal projects, or contributing to open-source libraries, Blazor empowers you to create delightful web experiences using familiar tools and languages.


Happy coding! 🚀

Comments


bottom of page