Guide for implementing LionFire workspace documents with Blazor MVVM patterns, including ObservableDataView component usage, workspace-scoped service injection, and reactive persistence. Use this skill when creating Blazor pages for workspace documents, fixing workspace service scoping issues, or implementing list/detail views with ObservableReader/Writer.
npx claudepluginhub joshuarweaver/cascade-code-devops-misc-2 --plugin lionfire-coreThis skill uses the workspace's default tool permissions.
Use this skill when:
Conducts multi-round deep research on GitHub repos via API and web searches, generating markdown reports with executive summaries, timelines, metrics, and Mermaid diagrams.
Dynamically discovers and combines enabled skills into cohesive, unexpected delightful experiences like interactive HTML or themed artifacts. Activates on 'surprise me', inspiration, or boredom cues.
Generates images from structured JSON prompts via Python script execution. Supports reference images and aspect ratios for characters, scenes, products, visuals.
Use this skill when:
ObservableDataView component for list viewsIObservableReader/Writer) in BlazorKeywords: workspace, ObservableDataView, IObservableReader, IObservableWriter, workspace services, CascadingParameter, Blazor MVVM, workspace scoping, workspace documents
The Problem: IObservableReader/Writer services are workspace-scoped, not in the root DI container.
The Solution: Use CascadingParameter to get WorkspaceServices and resolve services manually.
[CascadingParameter(Name = "WorkspaceServices")]
public IServiceProvider? WorkspaceServices { get; set; }
var reader = WorkspaceServices.GetService<IObservableReader<string, BotEntity>>();
See: references/service-scoping.md for complete explanation.
When: Displaying a list/grid of workspace documents with CRUD operations.
Code (~20-30 lines):
<ObservableDataView TKey="string"
TValue="BotEntity"
TValueVM="BotVM"
DataServiceProvider="@WorkspaceServices">
<Columns>
<PropertyColumn Property="x => x.Value.Name" />
</Columns>
</ObservableDataView>
@code {
[CascadingParameter(Name = "WorkspaceServices")]
public IServiceProvider? WorkspaceServices { get; set; }
}
See: examples/list-view-example.razor for complete example.
When: Displaying/editing a single workspace document with custom layout.
Code (~60-80 lines):
@code {
[CascadingParameter(Name = "WorkspaceServices")]
public IServiceProvider? WorkspaceServices { get; set; }
private ObservableReaderWriterItemVM<string, BotEntity, BotVM>? VM { get; set; }
protected override async Task OnParametersSetAsync()
{
var reader = WorkspaceServices.GetService<IObservableReader<string, BotEntity>>();
var writer = WorkspaceServices.GetService<IObservableWriter<string, BotEntity>>();
VM = new ObservableReaderWriterItemVM<string, BotEntity, BotVM>(reader, writer);
VM.Id = BotId;
}
}
See: examples/detail-view-example.razor for complete example.
When encountering:
InvalidOperationException: Unable to resolve service for type
'IObservableReader`2[System.String,MyEntity]'
Steps:
Identify the issue: Component trying to inject workspace-scoped services from root container
Check current pattern:
// ❌ WRONG: Tries to inject from root
@inherits ReactiveInjectableComponentBase<ObservableReaderWriterItemVM<string, BotEntity, BotVM>>
// ✅ RIGHT: Get workspace services via cascading parameter
[CascadingParameter(Name = "WorkspaceServices")]
public IServiceProvider? WorkspaceServices { get; set; }
protected override async Task OnParametersSetAsync()
{
var reader = WorkspaceServices.GetService<IObservableReader<string, BotEntity>>();
var writer = WorkspaceServices.GetService<IObservableWriter<string, BotEntity>>();
VM = new ObservableReaderWriterItemVM<string, BotEntity, BotVM>(reader, writer);
VM.Id = ItemId;
}
// In Program.cs, ensure:
services
.AddWorkspaceChildType<BotEntity>() // ← Must have this!
.AddWorkspaceDocumentService<string, BotEntity>();
For detailed explanation: Load references/service-scoping.md.
Goal: Display a reactive grid of workspace documents with CRUD operations.
Steps:
[Alias("Bot")]
public partial class BotEntity : ReactiveObject
{
[Reactive] private string? _name;
}
public class BotVM : KeyValueVM<string, BotEntity>
{
public BotVM(string key, BotEntity value) : base(key, value) { }
}
services
.AddWorkspaceChildType<BotEntity>()
.AddWorkspaceDocumentService<string, BotEntity>()
.AddTransient<BotVM>();
Create list page using ObservableDataView:
examples/list-view-example.razorDataServiceProvider="@WorkspaceServices"Customize columns as needed (PropertyColumn, TemplateColumn)
Add navigation to detail page:
<MudButton Href="@($"/bots/{context.Item.Key}")">Edit</MudButton>
For complete pattern details: Load references/blazor-mvvm-patterns.md.
Goal: Display/edit a single workspace document.
Steps:
@page "/bots/{BotId}"
@code {
[Parameter]
public string? BotId { get; set; }
}
@inject ILogger<Bot> Logger
@inject IServiceProvider ServiceProvider
@code {
[CascadingParameter(Name = "WorkspaceServices")]
public IServiceProvider? WorkspaceServices { get; set; }
}
Resolve reader/writer and create VM:
examples/detail-view-example.razorObservableReaderWriterItemVMBind UI to VM.Value:
<MudTextField @bind-Value="VM.Value.Name" Label="Name" />
private async Task Save() => await VM.Write();
For complete pattern details: Load references/blazor-mvvm-patterns.md.
Goal: Add a new document type (e.g., Portfolio) to existing workspace application.
Steps:
[Alias("Portfolio")]
public partial class PortfolioEntity : ReactiveObject
{
[Reactive] private string? _name;
// ... other properties
}
public class PortfolioVM : KeyValueVM<string, PortfolioEntity>
{
public PortfolioVM(string key, PortfolioEntity value) : base(key, value) { }
}
services
.AddWorkspaceChildType<PortfolioEntity>()
.AddWorkspaceDocumentService<string, PortfolioEntity>()
.AddTransient<PortfolioVM>();
Create pages (follow Workflow 2 and 3 above)
Result: Portfolios appear in workspace subdirectory Portfolios/
Purpose: Pre-built MudDataGrid component with reactive workspace data integration.
Key Parameters:
TKey, TValue, TValueVM - Type parametersDataServiceProvider - CRITICAL: Pass @WorkspaceServices hereAllowedEditModes - EditMode.None, .Cell, .Form, .AllReadOnly - Enable/disable editingColumns - Custom column definitionsAutomatic Features:
Purpose: ViewModel wrapper for single workspace documents.
Constructor:
public ObservableReaderWriterItemVM(
IObservableReader<TKey, TValue> reader,
IObservableWriter<TKey, TValue> writer)
Key Properties:
Id - The document key (triggers load when set)Value - The wrapped entity (as TValueVM)Writer - Access to persistenceKey Methods:
Write() - Save changes to file// ❌ WRONG
@inject IObservableReader<string, BotEntity> Reader
// ✅ RIGHT
[CascadingParameter(Name = "WorkspaceServices")]
public IServiceProvider? WorkspaceServices { get; set; }
var reader = WorkspaceServices.GetService<IObservableReader<string, BotEntity>>();
<!-- ❌ WRONG -->
<ObservableDataView TKey="string" TValue="BotEntity" TValueVM="BotVM" />
<!-- ✅ RIGHT -->
<ObservableDataView TKey="string" TValue="BotEntity" TValueVM="BotVM"
DataServiceProvider="@WorkspaceServices" />
// ❌ WRONG - Missing AddWorkspaceChildType
services.AddWorkspaceDocumentService<string, BotEntity>();
// ✅ RIGHT
services
.AddWorkspaceChildType<BotEntity>() // ← MUST have this
.AddWorkspaceDocumentService<string, BotEntity>();
Detailed documentation (load as needed):
references/service-scoping.md - Deep dive on workspace service scoping and DI
references/blazor-mvvm-patterns.md - Complete Blazor MVVM pattern guide
references/workspaces-architecture.md - Workspace architecture overview
Need to display workspace documents?
├─ Multiple items (list/grid)?
│ └─ Use ObservableDataView ✅
│ → See examples/list-view-example.razor
└─ Single item (detail/edit)?
└─ Use Manual VM Pattern ✅
→ See examples/detail-view-example.razor
Getting "Unable to resolve service" error?
└─ Using @inject instead of CascadingParameter?
└─ Fix: Use CascadingParameter for WorkspaceServices
→ See references/service-scoping.md
This skill teaches two complementary patterns:
Both require: CascadingParameter to access workspace-scoped services.
Most Common Issue: Trying to inject workspace services from root DI container.
Solution: Always use CascadingParameter(Name = "WorkspaceServices").