Skip to content

Instantly share code, notes, and snippets.

@MackinnonBuck
Created November 21, 2023 23:11
Show Gist options
  • Save MackinnonBuck/441681764d879d9ac4906aaec6410eb9 to your computer and use it in GitHub Desktop.
Save MackinnonBuck/441681764d879d9ac4906aaec6410eb9 to your computer and use it in GitHub Desktop.
Example: Define interactivity on pages shared between Blazor Web and Blazor Hybrid apps
@* In project 'RazorClassLibrary' *@
@page "/counter"
@attribute [InteractivePage] @* <-- Marks the page as interactive *@
<PageTitle>Counter</PageTitle>
<h1>Counter</h1>
<p role="status">Current count: @currentCount</p>
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
@code {
private int currentCount = 0;
private void IncrementCount()
{
currentCount++;
}
}
// In project 'RazorClassLibrary'
namespace RazorClassLibrary;
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
public sealed class InteractivePageAttribute : Attribute;
// In project 'BlazorWebApp'
using RazorClassLibrary;
using Microsoft.AspNetCore.Components.Rendering;
using System.Collections.Concurrent;
using System.Reflection;
namespace Microsoft.AspNetCore.Components;
public static class RouteDataExtensions
{
private static readonly ConcurrentDictionary<Type, bool> s_componentInteractivityCache = new();
public static RouteData WithAllowedInteractivity(this RouteData routeData, IComponentRenderMode interactiveRenderMode)
{
var isPageInteractive = s_componentInteractivityCache.GetOrAdd(routeData.PageType, IsPageInteractive);
if (!isPageInteractive)
{
// In the non-interactive case, we don't modify the route data.
return routeData;
}
// In the interactive case, we wrap the route data with a new one that
// renders the original page interactively.
return new(typeof(InteractivePage), new Dictionary<string, object?>
{
[nameof(InteractivePage.InteractiveRenderMode)] = interactiveRenderMode,
[nameof(InteractivePage.RouteData)] = routeData,
});
}
private static bool IsPageInteractive(Type pageType)
=> pageType.GetCustomAttribute<InteractivePageAttribute>() is not null;
private sealed class InteractivePage : IComponent
{
private RenderHandle _renderHandle;
[Parameter]
public IComponentRenderMode InteractiveRenderMode { get; set; } = default!;
[Parameter]
public RouteData RouteData { get; set; } = default!;
public void Attach(RenderHandle renderHandle)
{
_renderHandle = renderHandle;
}
public Task SetParametersAsync(ParameterView parameters)
{
parameters.SetParameterProperties(this);
_renderHandle.Render(Render);
return Task.CompletedTask;
}
private void Render(RenderTreeBuilder builder)
{
builder.OpenComponent(0, RouteData.PageType);
foreach (var kvp in RouteData.RouteValues)
{
builder.AddComponentParameter(1, kvp.Key, kvp.Value);
}
builder.AddComponentRenderMode(InteractiveRenderMode);
builder.CloseComponent();
}
}
}
@* In project 'BlazorWebApp' *@
<Router AppAssembly="@typeof(Program).Assembly" AdditionalAssemblies="new[] { typeof(Client._Imports).Assembly }">
<Found Context="routeData">
@{
routeData = routeData.WithAllowedInteractivity(InteractiveAuto);
}
<RouteView RouteData="@routeData" DefaultLayout="@typeof(Layout.MainLayout)" />
<FocusOnNavigate RouteData="@routeData" Selector="h1" />
</Found>
</Router>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment