Skip to content

Commit 436271d

Browse files
Add Blazor data binding guide and expand state management docs
- Add BlazorDataBinding.md: comprehensive guide for AI agents on creating Blazor pages data bound to CSLA editable business objects - Document ViewModel<T> usage and GetPropertyInfo() for property metastate - Clarify distinction between Csla.Blazor.IPropertyInfo (metastate) and Csla.Core.IPropertyInfo (registration metadata) - Include reusable input component patterns (TextInput, DateInput, etc.) - Cover EditForm with CslaValidator and CslaValidationMessages - Document authorization patterns at page, element, and property levels - Expand BlazorConfiguration.md state management section: - Add CslaStateController documentation (previously missing) - Add ASCII diagram showing state flow between server and client - Document StateManager.InitializeAsync() and SaveState() usage - Clarify when to call SaveState() (explicitly after LocalContext changes) - Add troubleshooting for state controller issues - Update Scenario 1 with complete controller examples Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent fd1bfbf commit 436271d

File tree

2 files changed

+1321
-23
lines changed

2 files changed

+1321
-23
lines changed

csla-examples/BlazorConfiguration.md

Lines changed: 297 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,9 @@ Complete configuration for a modern Blazor app supporting all render modes with
234234
```csharp
235235
var builder = WebApplication.CreateBuilder(args);
236236

237+
// Add controller support for DataPortalController and CslaStateController
238+
builder.Services.AddControllers();
239+
237240
// Configure authentication on the server
238241
builder.Services.AddAuthentication()
239242
.AddCookie();
@@ -267,6 +270,47 @@ app.MapControllers();
267270
app.Run();
268271
```
269272

273+
**Server Project - DataPortalController.cs:**
274+
275+
```csharp
276+
using Csla;
277+
using Microsoft.AspNetCore.Mvc;
278+
279+
namespace MyApp.Server.Controllers
280+
{
281+
[Route("api/[controller]")]
282+
[ApiController]
283+
public class DataPortalController : Csla.Server.Hosts.HttpPortalController
284+
{
285+
public DataPortalController(ApplicationContext applicationContext)
286+
: base(applicationContext)
287+
{
288+
}
289+
}
290+
}
291+
```
292+
293+
**Server Project - CslaStateController.cs:**
294+
295+
```csharp
296+
using Csla;
297+
using Csla.State;
298+
using Microsoft.AspNetCore.Mvc;
299+
300+
namespace MyApp.Server.Controllers
301+
{
302+
[ApiController]
303+
[Route("[controller]")]
304+
public class CslaStateController : Csla.AspNetCore.Blazor.State.StateController
305+
{
306+
public CslaStateController(ApplicationContext applicationContext, ISessionManager sessionManager)
307+
: base(applicationContext, sessionManager)
308+
{
309+
}
310+
}
311+
}
312+
```
313+
270314
**Client Project (Program.cs):**
271315

272316
```csharp
@@ -283,6 +327,11 @@ builder.Services.AddCsla(options => options
283327
await builder.Build().RunAsync();
284328
```
285329

330+
**Required Components:**
331+
- `DataPortalController` - Handles data portal requests from WebAssembly clients
332+
- `CslaStateController` - Handles state synchronization between server and WebAssembly clients
333+
- `AddControllers()` and `MapControllers()` - Required for both controllers to work
334+
286335
**Security Notes:**
287336
- User authenticates on the server using standard ASP.NET Core authentication
288337
- User identity flows from server to client via CSLA state management (`SyncContextWithServer`)
@@ -364,31 +413,229 @@ await builder.Build().RunAsync();
364413

365414
## State Management
366415

367-
### In-Memory Context Manager
416+
For modern Blazor apps with multiple render modes (InteractiveAuto), CSLA provides a state management subsystem that synchronizes application context between the server and WebAssembly client. This is essential for maintaining user identity, `LocalContext`, and `ClientContext` across render mode transitions.
417+
418+
### How State Management Works
419+
420+
```
421+
┌─────────────────────────────────────────────────────────────────────────────┐
422+
│ Server (ASP.NET Core) │
423+
│ ┌─────────────────────┐ ┌─────────────────────┐ │
424+
│ │ ISessionManager │◄───│ CslaStateController │◄──── HTTP GET/PUT ────┐│
425+
│ │ (stores sessions) │ │ (API endpoint) │ ││
426+
│ └─────────────────────┘ └─────────────────────┘ ││
427+
│ │ ││
428+
│ ▼ ││
429+
│ ┌─────────────────────┐ ││
430+
│ │ ApplicationContext │ ◄── Server-side Blazor components use this ││
431+
│ │ - Principal │ ││
432+
│ │ - LocalContext │ ││
433+
│ │ - ClientContext │ ││
434+
│ └─────────────────────┘ ││
435+
└─────────────────────────────────────────────────────────────────────────────┘│
436+
437+
┌─────────────────────────────────────────────────────────────────────────────┐│
438+
│ Client (WebAssembly) ││
439+
│ ┌─────────────────────┐ ┌─────────────────────┐ ││
440+
│ │ StateManager │───►│ ISessionManager │────────────────────────┘│
441+
│ │ - InitializeAsync() │ │ (HTTP to server) │ │
442+
│ │ - SaveState() │ └─────────────────────┘ │
443+
│ └─────────────────────┘ │
444+
│ │ │
445+
│ ▼ │
446+
│ ┌─────────────────────┐ │
447+
│ │ ApplicationContext │ ◄── WebAssembly components use this │
448+
│ │ - Principal │ (synchronized from server) │
449+
│ │ - LocalContext │ │
450+
│ │ - ClientContext │ │
451+
│ └─────────────────────┘ │
452+
└─────────────────────────────────────────────────────────────────────────────┘
453+
```
454+
455+
**Flow:**
456+
1. User authenticates on the server (SSR login page)
457+
2. Server stores session state including the user's `Principal`
458+
3. When a WebAssembly component loads, `StateManager.InitializeAsync()` retrieves the session from the server via `CslaStateController`
459+
4. The WebAssembly client now has the same `ApplicationContext` (Principal, LocalContext, ClientContext) as the server
460+
5. If the client modifies `LocalContext`, call `StateManager.SaveState()` to push changes back to the server
461+
462+
### Required Components for Multi-Mode Apps
463+
464+
For InteractiveAuto or any configuration where components can run in both server and WebAssembly modes, you need:
465+
466+
| Component | Location | Purpose |
467+
|-----------|----------|---------|
468+
| `CslaStateController` | Server project | API endpoint for state synchronization |
469+
| `ISessionManager` | Server (DI) | Stores session state on the server |
470+
| `StateManager` | Client pages | Retrieves/saves state from WebAssembly |
471+
| `UseInMemoryApplicationContextManager = false` | Server config | Enables persistent state management |
472+
| `SyncContextWithServer = true` | Client config | Enables client-side state sync |
368473

369-
Use when state only needs to persist within a single execution context:
474+
### Server-Side State Controller
475+
476+
Add a state controller to the server project. This controller handles HTTP requests from the WebAssembly client to retrieve and update session state:
370477

371478
```csharp
372-
.AddServerSideBlazor(blazor => blazor
373-
.UseInMemoryApplicationContextManager = true)
479+
using Csla;
480+
using Csla.State;
481+
using Microsoft.AspNetCore.Mvc;
482+
483+
namespace MyApp.Server.Controllers
484+
{
485+
[ApiController]
486+
[Route("[controller]")]
487+
public class CslaStateController : Csla.AspNetCore.Blazor.State.StateController
488+
{
489+
public CslaStateController(ApplicationContext applicationContext, ISessionManager sessionManager)
490+
: base(applicationContext, sessionManager)
491+
{
492+
}
493+
}
494+
}
374495
```
375496

376-
**Use for:**
377-
- Legacy Blazor Server apps
378-
- Single render mode scenarios
497+
The `StateController` base class provides:
498+
- `GET` endpoint - Returns serialized session data (including Principal if `FlowUserIdentityToWebAssembly = true`, which is the default)
499+
- `PUT` endpoint - Receives updated session data from the client
379500

380-
### Persistent Context Manager
501+
### Server-Side Configuration
381502

382-
Use when state needs to persist across multiple execution contexts:
503+
Configure the server to support state management:
383504

384505
```csharp
385-
.AddServerSideBlazor(blazor => blazor
386-
.UseInMemoryApplicationContextManager = false)
506+
var builder = WebApplication.CreateBuilder(args);
507+
508+
// Add controller support for CslaStateController and DataPortalController
509+
builder.Services.AddControllers();
510+
511+
builder.Services.AddRazorComponents()
512+
.AddInteractiveServerComponents()
513+
.AddInteractiveWebAssemblyComponents();
514+
515+
builder.Services.AddCsla(options => options
516+
.AddAspNetCore()
517+
.AddServerSideBlazor(blazor => blazor
518+
// CRITICAL: Set to false for multi-mode apps
519+
.UseInMemoryApplicationContextManager = false)
520+
.DataPortal(dp => dp
521+
.AddServerSideDataPortal()));
522+
523+
var app = builder.Build();
524+
525+
app.UseHttpsRedirection();
526+
app.UseStaticFiles();
527+
app.UseAntiforgery();
528+
app.UseAuthentication();
529+
app.UseAuthorization();
530+
531+
app.MapRazorComponents<App>()
532+
.AddInteractiveServerRenderMode()
533+
.AddInteractiveWebAssemblyRenderMode()
534+
.AddAdditionalAssemblies(typeof(Client._Imports).Assembly);
535+
536+
// CRITICAL: Map controllers for CslaStateController and DataPortalController
537+
app.MapControllers();
538+
539+
app.Run();
540+
```
541+
542+
**Key Points:**
543+
- `AddControllers()` - Required for the state and data portal controllers
544+
- `UseInMemoryApplicationContextManager = false` - Enables the persistent state management subsystem
545+
- `MapControllers()` - Exposes the controller endpoints
546+
547+
### Client-Side Configuration
548+
549+
Configure the client to synchronize state with the server:
550+
551+
```csharp
552+
var builder = WebAssemblyHostBuilder.CreateDefault(args);
553+
554+
builder.Services.AddCsla(options => options
555+
.AddBlazorWebAssembly(blazor => blazor
556+
// CRITICAL: Enable state synchronization
557+
.SyncContextWithServer = true)
558+
.DataPortal(dp => dp
559+
.AddClientSideDataPortal(csp => csp
560+
.UseHttpProxy(proxy => proxy
561+
.DataPortalUrl = "/api/DataPortal"))));
562+
563+
await builder.Build().RunAsync();
564+
```
565+
566+
**Key Points:**
567+
- `SyncContextWithServer = true` - Enables the client to retrieve state from the server via `CslaStateController`
568+
569+
### In-Memory vs Persistent Context Manager
570+
571+
| Setting | Value | Use Case |
572+
|---------|-------|----------|
573+
| `UseInMemoryApplicationContextManager` | `true` | Legacy Blazor Server apps, single render mode only |
574+
| `UseInMemoryApplicationContextManager` | `false` | Modern Blazor apps with InteractiveAuto or multiple render modes |
575+
576+
When `UseInMemoryApplicationContextManager = false`:
577+
- Session state is stored in a server-side `ISessionManager`
578+
- State can be retrieved by WebAssembly clients via `CslaStateController`
579+
- State persists across render mode transitions
580+
581+
When `UseInMemoryApplicationContextManager = true`:
582+
- State is stored in-memory per request/circuit
583+
- No state synchronization with WebAssembly clients
584+
- Simpler but only works for server-only scenarios
585+
586+
### Using StateManager in Blazor Pages
587+
588+
In your Blazor pages (especially those that may run in WebAssembly), inject and use `StateManager`:
589+
590+
```csharp
591+
@inject Csla.Blazor.State.StateManager StateManager
592+
593+
@code {
594+
protected override async Task OnInitializedAsync()
595+
{
596+
// CRITICAL: Always call InitializeAsync() first in every interactive page
597+
// This retrieves the current session state from the server (when running in WebAssembly)
598+
// On the server, this is a no-op but should still be called for consistency
599+
await StateManager.InitializeAsync();
600+
601+
// Now ApplicationContext.Principal, LocalContext, and ClientContext are available
602+
// ... rest of initialization
603+
}
604+
}
605+
```
606+
607+
### Saving State Changes
608+
609+
When your code modifies `ApplicationContext.LocalContext` or `ApplicationContext.ClientContext` on a WebAssembly client, you should explicitly call `SaveState()` to push those changes back to the server:
610+
611+
```csharp
612+
@inject Csla.Blazor.State.StateManager StateManager
613+
@inject Csla.ApplicationContext ApplicationContext
614+
615+
@code {
616+
private async Task UpdateUserPreference(string key, string value)
617+
{
618+
// Modify LocalContext
619+
ApplicationContext.LocalContext[key] = value;
620+
621+
// Explicitly save state to the server
622+
// This ensures the server has the updated state immediately
623+
// On the server, this is a no-op but is safe to call
624+
await StateManager.SaveState();
625+
}
626+
}
387627
```
388628

389-
**Use for:**
390-
- Modern Blazor apps with multiple render modes
391-
- Apps that switch between server and WebAssembly rendering
629+
**When to call SaveState():**
630+
- After modifying `ApplicationContext.LocalContext`
631+
- After modifying `ApplicationContext.ClientContext`
632+
- Any time you need to ensure the server has the latest client state
633+
634+
**Notes on SaveState():**
635+
- On the server, `SaveState()` is a no-op (does nothing) - it's safe to call unconditionally
636+
- On WebAssembly, it sends the current session state to the server via `CslaStateController`
637+
- Call it explicitly after state changes rather than relying on automatic synchronization
638+
- The default timeout is 10 seconds; use `SaveState(TimeSpan timeout)` for custom timeouts
392639

393640
## Security Configuration
394641

@@ -449,13 +696,15 @@ builder.Services.AddCsla(options => options
449696
1. **Authenticate on the server** - For multi-mode Blazor apps, use server-side SSR authentication
450697
2. **Choose the right context manager** - Use in-memory for server-only, persistent for multi-mode
451698
3. **Sync context for multi-mode apps** - Set `SyncContextWithServer = true` to flow state from server to client
452-
4. **Avoid client-to-server identity flow** - Do not set `FlowSecurityPrincipalFromClient = true` in production
453-
5. **Configure timeouts appropriately** - WebAssembly apps may need longer timeouts than server apps
454-
6. **Use HTTPS in production** - Always use HTTPS for data portal URLs in production
455-
7. **Configure CORS properly** - Ensure CORS is configured correctly for cross-origin scenarios
456-
8. **Test all render modes** - If using Auto mode, test with both server and WebAssembly rendering
457-
9. **Handle connection failures** - Implement proper error handling for network issues
458-
10. **Use compression** - Consider `HttpCompressionProxy` for large data transfers
699+
4. **Add both controllers** - For multi-mode apps, add both `DataPortalController` and `CslaStateController` to the server
700+
5. **Always call StateManager.InitializeAsync()** - Call this first in every interactive page's `OnInitializedAsync()`
701+
6. **Explicitly save state changes** - Call `StateManager.SaveState()` after modifying `LocalContext` or `ClientContext`
702+
7. **Avoid client-to-server identity flow** - Do not set `FlowSecurityPrincipalFromClient = true` in production
703+
8. **Configure timeouts appropriately** - WebAssembly apps may need longer timeouts than server apps
704+
9. **Use HTTPS in production** - Always use HTTPS for data portal URLs in production
705+
10. **Configure CORS properly** - Ensure CORS is configured correctly for cross-origin scenarios
706+
11. **Test all render modes** - If using Auto mode, test with both server and WebAssembly rendering
707+
12. **Handle connection failures** - Implement proper error handling for network issues
459708

460709
## Troubleshooting
461710

@@ -464,6 +713,10 @@ builder.Services.AddCsla(options => options
464713
If state doesn't persist across render mode changes:
465714
- Ensure `UseInMemoryApplicationContextManager = false` on the server
466715
- Verify `SyncContextWithServer = true` on the client
716+
- Check that `CslaStateController` exists in the server project
717+
- Verify `app.MapControllers()` is called in the server's `Program.cs`
718+
- Confirm `StateManager.InitializeAsync()` is called in page `OnInitializedAsync()`
719+
- If modifying `LocalContext`, ensure `StateManager.SaveState()` is called after changes
467720

468721
### Authentication Not Working
469722

@@ -472,22 +725,43 @@ If authentication isn't working in a multi-mode Blazor app:
472725
- Ensure `UseAuthentication()` and `UseAuthorization()` middleware are added to the server pipeline
473726
- Check that `SyncContextWithServer = true` is set on the client
474727
- Verify `UseInMemoryApplicationContextManager = false` is set on the server
475-
- Ensure the data portal controller is using the correct ApplicationContext
728+
- Confirm `CslaStateController` is present and `app.MapControllers()` is called
729+
- Ensure `StateManager.InitializeAsync()` is called before accessing `ApplicationContext.Principal`
476730
- Check that you are **not** using `FlowSecurityPrincipalFromClient = true` (this is not recommended)
477731

732+
### State Controller Not Working
733+
734+
If the `CslaStateController` isn't functioning:
735+
- Verify the controller inherits from `Csla.AspNetCore.Blazor.State.StateController`
736+
- Check that the route is correct (default is `[Route("[controller]")]` which maps to `/CslaState`)
737+
- Ensure `AddControllers()` is called in `builder.Services`
738+
- Ensure `MapControllers()` is called on the app
739+
- Check browser Network tab for HTTP errors when calling the state endpoint
740+
- Verify `ISessionManager` is properly registered (it should be automatic with `AddServerSideBlazor`)
741+
478742
### Data Portal Calls Failing
479743

480744
If data portal calls fail:
481745
- Verify the data portal URL is correct
482-
- Check that the server has the data portal controller
746+
- Check that the server has the `DataPortalController`
747+
- Ensure `app.MapControllers()` is called
483748
- Ensure CORS is configured if client and server are on different origins
484749
- Check browser console and server logs for errors
485750

751+
### LocalContext Changes Not Synchronized
752+
753+
If changes to `ApplicationContext.LocalContext` on WebAssembly aren't reflected on the server:
754+
- Call `StateManager.SaveState()` explicitly after modifying `LocalContext`
755+
- Do not rely on automatic synchronization - explicitly save state after changes
756+
- Verify `CslaStateController` is configured and reachable
757+
486758
## Notes
487759

488760
- Modern Blazor apps require careful configuration to support all render modes
489761
- The `UseInMemoryApplicationContextManager` setting is critical for state management
762+
- **Required for multi-mode apps**: Both `CslaStateController` (server) and `StateManager` usage (pages) are required
490763
- **Security Recommendation**: Authenticate users on the server and let identity flow from server to client via state management
491764
- **Avoid** `FlowSecurityPrincipalFromClient = true` in production - this creates security vulnerabilities
492765
- For pure server-side or pure WebAssembly apps, identity flow is not a concern as there is no client/server boundary
493766
- Always test with the actual render modes you'll use in production
767+
- Call `StateManager.SaveState()` explicitly after modifying `LocalContext` or `ClientContext` on WebAssembly clients

0 commit comments

Comments
 (0)