@@ -234,6 +234,9 @@ Complete configuration for a modern Blazor app supporting all render modes with
234234``` csharp
235235var builder = WebApplication .CreateBuilder (args );
236236
237+ // Add controller support for DataPortalController and CslaStateController
238+ builder .Services .AddControllers ();
239+
237240// Configure authentication on the server
238241builder .Services .AddAuthentication ()
239242 .AddCookie ();
@@ -267,6 +270,47 @@ app.MapControllers();
267270app .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
283327await 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
4496961 . ** Authenticate on the server** - For multi-mode Blazor apps, use server-side SSR authentication
4506972 . ** Choose the right context manager** - Use in-memory for server-only, persistent for multi-mode
4516983 . ** 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
464713If 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
480744If 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