C
C#4mo ago
Caleb Weeks

Using Blazor WebAssembly for client interactions in Blazor Server

Hello all! I'm building an admin dashboard sort of application that heavily interacts with the server, so I'm using InteractiveServer rendering by default. For small client side interactions such as a collapsible sidebar, it seems like there are a few options: - Use JavaScript (with or without a library such as Jquery or Alpine) - Use HTML + CSS "hacks" such as the checkbox method that ships with the default Blazor template - Use Blazor WebAssembly I love the idea of doing as much as possible directly in Blazor, but I'm running into an issue with getting a collapsible sidebar to work. I want the functionality of the collapsing sidebar to live in Blazor WebAssembly so that it isn't talking to the server just to toggle a single piece of state. But the contents of the sidebar should be a Blazor Server component so I can directly access the database (through a service), and don't need to expose an API. I tried creating a Blazor component with RenderFragments, but I'm getting that issue where they can't be serialized. So, my question is, in an environment that is primarily tightly coupled to the server, what's the best way of sprinkling in some client side interactivity? Is it possible to just use WebAssembly for this, or should I just be using JavaScript? Thanks!
6 Replies
Caleb Weeks
Caleb WeeksOP4mo ago
Thanks! I'm familiar with the render modes. I'm looking for architectural advice on the best way of adding small client side interactions. I'd like to use Blazor WebAssembly, but it seems like nesting Server components in WebAssembly components isn't possible.
friedice
friedice4mo ago
In my current setup for a project I have, I have blazor server as the host and WASM act as the client. I expose apis for db access, but if you dont want to do that you can inject your services directly into the host
friedice
friedice4mo ago
If that approach is not for you, an alternative I can think of is integrating razor components? Might be tricky to set up though https://learn.microsoft.com/en-us/aspnet/core/blazor/components/integration?view=aspnetcore-8.0
Integrate ASP.NET Core Razor components into ASP.NET Core apps
Learn about Razor component integration scenarios ASP.NET Core apps, Razor Pages and MVC.
friedice
friedice4mo ago
Here's an example program.cs of my server host, maybe it can give you inspiration
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddDbContext<AppName.Persistence.MasterDbContext>(
options => options
.UseSqlServer(
builder.Configuration.GetConnectionString("MasterDatabase")
),
ServiceLifetime.Scoped
);

builder.Services.AddRazorComponents()
.AddInteractiveServerComponents()
.AddInteractiveWebAssemblyComponents();

builder.Services.AddRateLimiter(rateLimiterOptions =>
{
rateLimiterOptions.RejectionStatusCode = StatusCodes.Status429TooManyRequests;

rateLimiterOptions.AddPolicy("fixed", httpContext =>
RateLimitPartition.GetFixedWindowLimiter(
partitionKey: httpContext.Connection.RemoteIpAddress?.ToString(),
factory: partiotion => new FixedWindowRateLimiterOptions
{
PermitLimit = 10,
Window = TimeSpan.FromSeconds(60)
}));
});

builder.Services.Configure<AzureStorageSettings>(builder.Configuration.GetSection("AzureStorageSettings"));
builder.Services.AddMemoryCache();
builder.Services.AddSingleton<Lib_Middleware.Services.CryptoService>();
builder.Services.AddHttpClient();
builder.Services.AddHttpContextAccessor();
//add backend repositories/services
builder.Services.AddScoped<IEmailSender, AppName.Infrastructure.Services.EmailEqueuer>();

//Add front end repositires/servicies
builder.Services.AddScoped<AppStateService>();
builder.Services.AddScoped<ToastService>();
builder.Services.AddScoped<ApiExceptionHandler>();
builder.Services.AddScoped<ExceptionFilter>();

//Add MediatR
builder.Services.AddMediatR(cfg => cfg.RegisterServicesFromAssemblies(AppName.Application.AssemblyReference.Assembly));

//Add serilog
builder.Host.UseSerilog((context, loggerConfig) =>
loggerConfig.ReadFrom.Configuration(context.Configuration));

builder.Services
.AddControllers()
.AddApplicationPart(AppName.Presentation.AssemblyReference.Assembly)
.AddNewtonsoftJson(options =>
options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore);

builder.Services.AddCors(options =>
{
options.AddPolicy(name: "AllowOrigin",
builder => {
builder
.AllowAnyOrigin()
.AllowAnyHeader().WithExposedHeaders("*")
.AllowAnyMethod();
});
});

builder.Services
.AddAuthentication(options => {
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options => {
options.Authority = builder.Configuration.GetSection("IdentityServerUrl").Value;
options.TokenValidationParameters.ValidateAudience = false;
});


builder.Services.AddAuthorization(options => {

//auth
});


var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseWebAssemblyDebugging();
}
else
{
app.UseExceptionHandler("/Error", createScopeForErrors: true);
app.UseHsts();
}

app.UseHttpsRedirection();

app.UseRateLimiter();

app.UseStaticFiles();
app.UseAntiforgery();

//app.UseRouting();
app.MapControllers();

app.MapRazorComponents<AppName.Client.App>()
.AddInteractiveServerRenderMode()
.AddInteractiveWebAssemblyRenderMode();

app.Run();
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddDbContext<AppName.Persistence.MasterDbContext>(
options => options
.UseSqlServer(
builder.Configuration.GetConnectionString("MasterDatabase")
),
ServiceLifetime.Scoped
);

builder.Services.AddRazorComponents()
.AddInteractiveServerComponents()
.AddInteractiveWebAssemblyComponents();

builder.Services.AddRateLimiter(rateLimiterOptions =>
{
rateLimiterOptions.RejectionStatusCode = StatusCodes.Status429TooManyRequests;

rateLimiterOptions.AddPolicy("fixed", httpContext =>
RateLimitPartition.GetFixedWindowLimiter(
partitionKey: httpContext.Connection.RemoteIpAddress?.ToString(),
factory: partiotion => new FixedWindowRateLimiterOptions
{
PermitLimit = 10,
Window = TimeSpan.FromSeconds(60)
}));
});

builder.Services.Configure<AzureStorageSettings>(builder.Configuration.GetSection("AzureStorageSettings"));
builder.Services.AddMemoryCache();
builder.Services.AddSingleton<Lib_Middleware.Services.CryptoService>();
builder.Services.AddHttpClient();
builder.Services.AddHttpContextAccessor();
//add backend repositories/services
builder.Services.AddScoped<IEmailSender, AppName.Infrastructure.Services.EmailEqueuer>();

//Add front end repositires/servicies
builder.Services.AddScoped<AppStateService>();
builder.Services.AddScoped<ToastService>();
builder.Services.AddScoped<ApiExceptionHandler>();
builder.Services.AddScoped<ExceptionFilter>();

//Add MediatR
builder.Services.AddMediatR(cfg => cfg.RegisterServicesFromAssemblies(AppName.Application.AssemblyReference.Assembly));

//Add serilog
builder.Host.UseSerilog((context, loggerConfig) =>
loggerConfig.ReadFrom.Configuration(context.Configuration));

builder.Services
.AddControllers()
.AddApplicationPart(AppName.Presentation.AssemblyReference.Assembly)
.AddNewtonsoftJson(options =>
options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore);

builder.Services.AddCors(options =>
{
options.AddPolicy(name: "AllowOrigin",
builder => {
builder
.AllowAnyOrigin()
.AllowAnyHeader().WithExposedHeaders("*")
.AllowAnyMethod();
});
});

builder.Services
.AddAuthentication(options => {
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options => {
options.Authority = builder.Configuration.GetSection("IdentityServerUrl").Value;
options.TokenValidationParameters.ValidateAudience = false;
});


builder.Services.AddAuthorization(options => {

//auth
});


var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseWebAssemblyDebugging();
}
else
{
app.UseExceptionHandler("/Error", createScopeForErrors: true);
app.UseHsts();
}

app.UseHttpsRedirection();

app.UseRateLimiter();

app.UseStaticFiles();
app.UseAntiforgery();

//app.UseRouting();
app.MapControllers();

app.MapRazorComponents<AppName.Client.App>()
.AddInteractiveServerRenderMode()
.AddInteractiveWebAssemblyRenderMode();

app.Run();
Caleb Weeks
Caleb WeeksOP4mo ago
Thanks! I'll look into that more. This repo is what gets generated when using Radzen Blazor Studio. I like how it's set up with the layout in the client side, but I just need the nav menu to be generated on the server. https://github.com/sethcalebweeks/RadzenBlazorAuto
GitHub
GitHub - sethcalebweeks/RadzenBlazorAuto
Contribute to sethcalebweeks/RadzenBlazorAuto development by creating an account on GitHub.
Want results from more Discord servers?
Add your server