ποΈ ConnectSoft Blazor Templates β High-Level Design¶
The ConnectSoft Blazor Templates provide a standardized, enterprise-ready foundation for building SaaS portals. They unify component reuse, microfrontend modularity, and portal orchestration into one cohesive blueprint, ensuring faster delivery and consistent user experiences across all products.
π§± What We Deliver¶
-
Component Library Template
- Reusable, token-driven Blazor components (buttons, forms, modals).
- UI-kit agnostic, accessible, and packaged for reuse.
-
Microfrontend Library Template
- Independent feature modules, deployable as Web Components.
- Secure, manifest-driven loading with built-in observability.
-
Application Shell Template
- Portal-grade Blazor Web App with navigation, blades, and theming.
- Tenant/edition policies and secure API access via BFF proxy.
π Core Guarantees¶
- Security & Compliance: Strong defaults (CSP, SRI, signed packages, SBOM).
- Observability: End-to-end telemetry and monitoring from client to backend.
- Testability: Automated QA anchors (bUnit, Playwright, coverage gates).
- Developer Experience: Docs, quickstarts, onboarding guides, and galleries.
- Distribution: Automated pipelines for NuGet, CDN, and documentation.
π Benefits¶
- π Speed: Faster onboarding and delivery, fewer reinventions.
- π¨ Consistency: Unified UX patterns across portals.
- π‘οΈ Trust: Compliance-ready outputs for enterprise needs.
- π Visibility: Observability and diagnostics as defaults.
- π Global Reach: Multilingual and RTL support built in.
flowchart LR
subgraph DesignSystem["π¨ Component Library (RCL)"]
Tokens["Design Tokens\n+ Tailwind Preset"]
Components["Reusable Components\n(Button, Modal, Form, ...)"]
Adapters["UI Kit Adapters\n(Flowbite/DaisyUI/Custom)"]
Tokens --> Components
Components --> Adapters
end
subgraph MFEs["π§± Microfrontends (Blazor WASM β Web Components)"]
MFE1["Billing MFE\n<cs-mfe-billing>"]
MFE2["Catalog MFE\n<cs-mfe-catalog>"]
MFE3["Pipelines MFE\n<cs-mfe-pipelines>"]
end
subgraph Shell["ποΈ Application Shell (Blazor Web App)"]
Nav["Left Rail & Top Bar"]
Blades["Blade/Portal Layout"]
Host["MFE Host & Registry\n(SRI, Capabilities, Policy)"]
Theme["Theme/Density/Culture State"]
Nav --> Blades --> Host
Theme --> Host
end
subgraph Runtime["βοΈ Runtime Services"]
BFF["BFF / YARP Proxy\nAuth, RBAC, Tenancy"]
Flags["Feature Flags & Editions\n(ECS/AppConfig)"]
Realtime["SignalR Notifications & Activity"]
OTel["Observability\n(OTel Traces/Logs/Metrics)"]
Sec["Security & Compliance\n(CSP, SRI, SBOM, Signing)"]
end
DesignSystem --> MFEs
MFEs --> Shell
Shell --> Runtime
MFEs --> Runtime
How to read it:
- Design System (tokens β components β adapters) ensures UI consistency without UI-kit lock-in.
- Microfrontends ship as Web Components and can be versioned and deployed independently.
- The Application Shell composes MFEs at runtime, manages navigation and blades, and governs theme/density/culture.
- Runtime Services (BFF, flags/editions, SignalR, Observability, Security) provide the enterprise spine: secure APIs, real-time UX, telemetry, and compliance.
π Vision & Problem Context¶
π― Purpose¶
The ConnectSoft Blazor Templates provide a standardized, SaaS-ready foundation for frontend development inside the Factory. They unify component reuse, microfrontend modularity, and portal orchestration into a single blueprint family, ensuring every project shares the same UX principles, observability anchors, and integration points.
π‘ Think of it as the βfrontend skeleton keyβ β one set of templates that unlock consistent experiences across products, tenants, and editions.
π Problem Context¶
Current frontend efforts in SaaS and portal environments face recurring issues:
- β οΈ Fragmentation β teams create ad-hoc component libraries, each with different patterns.
- β οΈ UI Kit Lock-in β components are often hard-wired to Flowbite, Telerik, or custom CSS.
- β οΈ Duplication β buttons, forms, and layouts are rebuilt for every project.
- β οΈ Runtime Rigidity β MFEs are difficult to load dynamically into a shared shell.
- β οΈ Inconsistent UX β portals lack unified navigation, theming, and accessibility support.
- β οΈ Observability Gaps β frontend telemetry is rarely consistent across solutions.
The result: higher cost, slower delivery, and broken coherence across tenant experiences.
π― Goals¶
The templates solve these issues by introducing a unified factory-standard approach:
- Reusable Component Library Tailwind-driven Razor Class Library, agnostic of UI kits, packaged as NuGet.
- Pluggable UI Kit Adapters Flowbite, DaisyUI, or other kits are chosen at the shell/application layer, not inside components.
- Microfrontend Independence Blazor WASM packaged as Web Components, deployed via CDN with manifest metadata.
- Portal-Ready Application Shell Blazor Web App orchestrating authentication, theming, layout, feature flags, and runtime composition.
- SaaS Essentials by Default Tenant/edition awareness, OpenTelemetry observability, SignalR live updates, secure BFF/YARP API access.
π The promise: faster delivery, fewer mismatches, and a consistent SaaS UX no matter which team builds the feature.
π§± Alignment with the Factory¶
The templates sit in the Frontend Cluster of the ConnectSoft AI Software Factory:
flowchart TD
CompLib["π¨ Component Library (RCL)"]
MFE["π§± Microfrontend Library (WASM CE)"]
Shell["ποΈ Application Shell (Blazor Web App)"]
CompLib --> MFE --> Shell
Shell --> ECS["βοΈ External Configuration System"]
Shell --> Services["π¦ Backend Microservices"]
Shell --> Agents["π€ Factory Agents"]
- Component Library β standard design tokens and UI blocks.
- Microfrontends β domain-scoped features deployable independently.
- Application Shell β the βportal spineβ hosting MFEs and enforcing tenant rules.
β Guarantees¶
- Every frontend artifact is traceable to this vision block.
- All downstream templates are idempotent: regeneration wonβt diverge unless vision changes.
- UX, observability, and SaaS alignment are non-negotiable defaults.
π Frontend Architectural Principles¶
π― Purpose¶
Establish the guiding standards and design principles that govern all ConnectSoft Blazor templates. These principles ensure that components, microfrontends, and application shells are built with consistency, scalability, and SaaS readiness across every project in the Factory.
π‘ Principles are the guardrails β they prevent drift, enforce quality, and make every template dependable out of the box.
π Core Principles¶
-
Clean Separation of Concerns UI rendering, state management, and service integration layers must remain isolated.
-
Cloud-Native by Default Templates are optimized for SaaS portals, multi-tenancy, edition management, and distributed deployment.
-
Configuration via Options All configurable elements use
IOptions<T>with validation to guarantee safe, predictable behavior. -
Observability First Logging, tracing, and metrics are not optional β they are embedded in every template by default.
-
Accessibility Always Components must be WCAG-compliant, with ARIA roles, keyboard navigation, and focus handling.
-
Localization-Ready Every template includes
IStringLocalizersupport for multilingual portals. -
Security by Design No component or shell feature can be exposed without CSP, sanitization, and hardened defaults.
π Layered Architecture¶
The templates enforce a layered structure for maintainability and clarity:
graph TD
UI[π¨ UI Layer - Razor Components, Scoped CSS, Tokens]
State[π§© State Layer - Fluxor/Redux-like Store]
Integration[π Integration Layer - BFF, YARP, ECS, SignalR]
Backend[π¦ Backend - Microservices, APIs, ECS]
UI --> State --> Integration --> Backend
- UI Layer β Tailwind tokens + scoped styles, reusable Razor components.
- State Layer β predictable, unidirectional data flow for reliable UI updates.
- Integration Layer β HttpClient (BFF), ECS feature flags, SignalR subscriptions.
- Backend β microservices and ECS policies consumed through standardized APIs.
π« Anti-Patterns¶
To avoid fragmentation, the following are explicitly forbidden:
- β Hardcoded UI kit dependencies inside core templates.
- β Direct backend calls bypassing the BFF/YARP layer.
- β Inline CSS that bypasses tokens or theme system.
- β Embedding domain/business logic inside UI components.
- β Missing observability anchors (logs, metrics, traces).
β Guarantees¶
- Every template enforces these principles automatically at generation.
- All downstream implementations inherit localization, accessibility, observability, and security by default.
- Anti-patterns are detectable and preventable through linting and QA pipelines.
- Regeneration remains idempotent β principles are reapplied consistently across updates.
π Template Identity & Catalog Registration¶
π― Purpose¶
Define the unique identity, naming conventions, and catalog metadata for all ConnectSoft Blazor templates. This section ensures every template is traceable, discoverable, and consistently generated inside the Factory.
π‘ Identity is not cosmetic β itβs the anchor that drives catalog resolution, namespace mapping, and long-term traceability.
π Identity Model¶
Each template is uniquely defined by:
- Template ID β canonical identifier in the catalog (
connectsoft.blazor.component-lib). - Trace ID β globally unique ID used across the Factory for versioning and memory embedding.
- Namespace Root β
.NETnamespace prefix derived from solution/project scope. - Tags β key labels for grouping (e.g.,
frontend,blazor,saas,component). - Versioning β semantic version alignment with Factory releases.
π§© Catalog Registration¶
Templates are registered in the ConnectSoft Template Catalog:
-
Catalog Entry Metadata
- Template ID
- Short/long description
- Default project structure
- Supported options/parameters
-
Discovery & Installation
- Published to NuGet + internal feed
- Installable via
dotnet new - Searchable by ID, tags, or domain context
-
Consistency Rules
- IDs must be globally unique
- Namespaces must match project and domain context
- Version increments must align with blueprint evolution
π Example Identity Block¶
{
"templateId": "connectsoft.blazor.appshell",
"traceId": "tpl-20250905-blazor-appshell-v1",
"namespaceRoot": "ConnectSoft.Blazor.AppShell",
"tags": ["frontend", "blazor", "portal", "saas"],
"version": "1.0.0"
}
π Example Catalog Manifest¶
templates:
- id: connectsoft.blazor.component-lib
description: "Reusable Razor Class Library with Tailwind tokens"
tags: [frontend, blazor, rcl]
namespace: ConnectSoft.Blazor.ComponentLib
version: 1.0.0
- id: connectsoft.blazor.microfrontend
description: "Blazor WASM packaged as Custom Element"
tags: [frontend, blazor, mfe]
namespace: ConnectSoft.Blazor.Microfrontend
version: 1.0.0
- id: connectsoft.blazor.appshell
description: "Blazor Web App shell for SaaS portals"
tags: [frontend, blazor, portal, saas]
namespace: ConnectSoft.Blazor.AppShell
version: 1.0.0
β Guarantees¶
- Every template has a single source of truth identity block.
- IDs, namespaces, and trace IDs are immutable once generated.
- Catalog registration ensures discoverability and reuse across all Factory projects.
- Versioning is semantic and traceable, allowing deterministic regeneration.
π¦ Repository & Packaging Topology¶
π― Purpose¶
Define a multi-repository topology where each template (Component Library, Microfrontend Library, Application Shell) lives in its own dedicated repo, with a lightweight Catalog repo to coordinate discovery, cross-cutting standards, and releases.
π‘ Separate repos = clean ownership, clearer permissions, faster CI, and independent release trains β without losing a unified catalog.
ποΈ Repositories¶
1) connectsoft.blazor.component-library
Reusable RCL with Tailwind tokens; bUnit tests; NuGet packaging.
2) connectsoft.blazor.microfrontend
Blazor WASM β Custom Element packaging; CDN manifest; integration tests.
3) connectsoft.blazor.appshell
Blazor Web App shell; BFF/YARP; SignalR; feature flags; Playwright/E2E.
4) connectsoft.blazor.catalog (coordination repo)
Template Catalog (IDs, metadata), shared CI templates, analyzers, ADR index, release notes, documentation site.
π Each functional repo remains autonomous; the Catalog repo provides the βsingle pane of glass.β
π Directory Conventions (per repo)¶
root/
βββ src/
β βββ <ProductName>/ # Primary library/app
βββ samples/
β βββ <ProductName>.Samples/ # Minimal runnable showcase
βββ tests/
β βββ <ProductName>.UnitTests/ # bUnit/xUnit for libs
β βββ <ProductName>.E2E/ # Playwright for shell; MFE host tests
βββ build/
β βββ versioning.props # SemVer, source link
β βββ pipelines/ # Reusable YAML fragments
βββ docs/
βββ adr/ # Architecture Decisions
βββ site/ # Local docs (synced to Catalog)
π§± Build / Pack / Publish Strategy¶
Build
- .NET SDK pinned via
global.json. - Roslyn analyzers + style rules from Catalog repo (consumed as a submodule or NuGet).
- Deterministic builds, source link enabled.
Pack
dotnet packproduces signed.nupkg(Component + Shell) and tool/template packs.- Microfrontend builds emit CDN bundle +
microfrontend.json(SRI + integrity).
Publish
- NuGet: internal Azure Artifacts; optional NuGet.org for stabilized releases.
- CDN: MFE bundles + manifests pushed to CDN buckets with immutable hashes.
- Docs: repo-level docs synced as a subtree into Catalog for the central site.
π‘ MFEβs dual-output is critical: a NuGet developer package and a CDN runtime bundle.
π Release Channels & Versioning¶
- Channels:
alpha(feature dev),beta(API stable, UX evolving),stable(API & UX locked). - Tags:
vX.Y.Z[-channel]on each repo. - Semantic Versioning:
- Major: breaking template scaffolds/parameters.
- Minor: new features, non-breaking options/components.
- Patch: fixes, docs, perf, a11y improvements.
Cross-Repo Coordination (Catalog-driven):
- Catalog repo holds a Release Matrix mapping compatible versions:
π§° Shared Governance (from Catalog repo)¶
- Reusable YAML pipelines imported into each repo:
build.yml(restore, build, test, pack)quality.yml(lint, analyzers, coverage gates)security.yml(SBOM, vuln scan)release.yml(tag, publish, notify)
- Analyzer package:
ConnectSoft.CodeQuality(ruleset, editorconfig, banned APIs). - Template catalog manifest: IDs, descriptions, tags, default parameters, sample links.
π‘ Keep shared assets versioned in the Catalog as packages; repos consume pinned versions to avoid accidental coupling.
π§ͺ CI Stages (per repo)¶
- Validate: restore, build, unit tests (bUnit/xUnit).
- Integration: Playwright (Shell), MFE host smoke tests, API contract checks (if any).
- Security: SBOM, dependency scan, CSP/SRI validation (for MFE).
- Package: produce
.nupkg,site.zip,bundle.zip. - Publish: push to feed/CDN; create GitHub/Azure DevOps release; update Catalog via PR.
π¦ Example Outputs¶
Component Library
packages/ConnectSoft.Blazor.ComponentLibrary.1.0.0.nupkg
symbols/ConnectSoft.Blazor.ComponentLibrary.1.0.0.snupkg
docs/site.zip
Microfrontend Library
packages/ConnectSoft.Blazor.Microfrontend.1.0.0.nupkg
cdn/billing-mfe.1.0.0/{app.js, app.wasm, assets/*}
cdn/microfrontend.json # name, version, element, integrity, capabilities
Application Shell
π Access & Branching Policy¶
- Branching:
main(protected),release/*,feature/*. - Protection: required reviews, status checks (quality, security, coverage).
- Permissions: per-repo code owners (UI, MFE, Shell), Catalog owners for shared assets.
- Tokens/Secrets: per-repo Key Vault/Variable Groups; no shared secrets across repos.
π Catalog Sync Flow¶
- Template repo releases (
v1.0.0). - Pipeline opens PR in Catalog to update:
- catalog manifest (IDs, versions, tags)
- compatibility matrix
- docs navigation + sample links
- Catalog CI builds the central docs site and publishes the updated index.
π The Catalog PR is the single source of truth for consumers β if itβs not in Catalog, itβs not official.
β Guarantees¶
- Each template is isolated and independently releasable.
- Cross-cutting quality and security are enforced via shared, versioned tooling.
- The Catalog provides a unified discovery layer without reintroducing monorepo coupling.
- All artifacts (NuGet, CDN bundles, docs) are reproducible and traceable to a tag.
π¨ Design Tokens & Tailwind Preset¶
π― Purpose¶
Define a shared, UI-kit-agnostic design system delivered as CSS variables + Tailwind preset, consumed by all three templates (Component Library, Microfrontend, Application Shell). This ensures consistent theming (light/dark/density), accessibility, and brand cohesion across the Factory.
π‘ Tokens are the single source of visual truthβcomponents render tokens, adapters style behaviors.
𧬠Token Taxonomy¶
Foundations (raw scales)
color:--cs-color-*(brand, neutral, accent, success, warning, danger)space:--cs-space-*(0, 0.5, 1, 1.5, 2, 3, 4, 6, 8, 12)size:--cs-size-*(2, 4, 6, 8, 10, 12, 14, β¦ 64)radius:--cs-radius-*(none, sm, md, lg, xl, 2xl)elevation:--cs-shadow-*(0..5)opacity:--cs-opacity-*z:--cs-z-*(dropdown, modal, toast, overlay)
Typography
font.family:--cs-font-sans,--cs-font-monofont.size:--cs-text-*(xs..9xl)font.weight:--cs-weight-*(regular..black)line.height:--cs-leading-*letter.spacing:--cs-tracking-*
Semantic aliases (theme-aware)
surface.*: background layers (--cs-surface-1/2/3)content.*: text levels (--cs-content-hi/lo/inverse)border.*:--cs-border-muted/strong/focusaction.*:--cs-action-fg/bg/hover/active/disabledstatus.*:--cs-status-success/warning/danger/info
Behavioral
focus: ring width, color, offsetmotion: durations/easings for enter/exitdensity: compact / cozy / comfortable paddings & heights
π Components should bind only to semantic aliases (e.g.,
--cs-action-bg)βnever raw brand colors.
π Source Layout (shared package)¶
connectsoft.theme.tokens/
βββ css/
β βββ tokens.css # CSS variables (light theme baseline)
β βββ tokens.dark.css # Dark-mode overrides
β βββ tokens.density.css # Compact/cozy/comfortable presets
β βββ tokens.a11y.css # High-contrast & focus utilities
βββ tailwind/
β βββ preset.cjs # Tailwind preset mapping β CSS vars
β βββ plugins.cjs # Utilities (focus-ring, elevation)
βββ docs/
βββ tokens-map.md
π§© Tailwind Preset Mapping¶
tailwind/preset.cjs (excerpt)
module.exports = {
darkMode: ["class", '[data-theme="dark"]'],
theme: {
extend: {
colors: {
surface: {
1: "rgb(var(--cs-surface-1) / <alpha-value>)",
2: "rgb(var(--cs-surface-2) / <alpha-value>)",
3: "rgb(var(--cs-surface-3) / <alpha-value>)",
},
content: {
DEFAULT: "rgb(var(--cs-content-hi) / <alpha-value>)",
muted: "rgb(var(--cs-content-lo) / <alpha-value>)",
inverse: "rgb(var(--cs-content-inverse) / <alpha-value>)",
},
action: {
bg: "rgb(var(--cs-action-bg) / <alpha-value>)",
fg: "rgb(var(--cs-action-fg) / <alpha-value>)",
hover: "rgb(var(--cs-action-hover) / <alpha-value>)",
},
status: {
success: "rgb(var(--cs-status-success) / <alpha-value>)",
warning: "rgb(var(--cs-status-warning) / <alpha-value>)",
danger: "rgb(var(--cs-status-danger) / <alpha-value>)",
info: "rgb(var(--cs-status-info) / <alpha-value>)",
},
},
spacing: {
'xs': 'var(--cs-space-1)',
'sm': 'var(--cs-space-2)',
'md': 'var(--cs-space-3)',
'lg': 'var(--cs-space-4)',
'xl': 'var(--cs-space-6)'
},
borderRadius: {
none: 'var(--cs-radius-none)',
sm: 'var(--cs-radius-sm)',
md: 'var(--cs-radius-md)',
lg: 'var(--cs-radius-lg)',
xl: 'var(--cs-radius-xl)',
'2xl': 'var(--cs-radius-2xl)',
},
boxShadow: {
cs0: 'var(--cs-shadow-0)',
cs1: 'var(--cs-shadow-1)',
cs2: 'var(--cs-shadow-2)',
cs3: 'var(--cs-shadow-3)',
cs4: 'var(--cs-shadow-4)',
cs5: 'var(--cs-shadow-5)',
}
}
},
plugins: [
require('./plugins.cjs') // focus ring, elevation, a11y helpers
]
};
π§Ύ CSS Variables (light baseline & dark overrides)¶
css/tokens.css (excerpt)
:root {
/* brand/neutral (as RGB triplets for alpha control) */
--cs-color-brand-600: 20 115 230;
--cs-color-neutral-900: 17 24 39;
/* semantic surfaces & content */
--cs-surface-1: 255 255 255;
--cs-surface-2: 249 250 251;
--cs-content-hi: 17 24 39;
--cs-content-lo: 75 85 99;
/* actions */
--cs-action-bg: var(--cs-color-brand-600);
--cs-action-fg: 255 255 255;
--cs-action-hover: 16 98 196;
/* status */
--cs-status-success: 16 185 129;
--cs-status-warning: 245 158 11;
--cs-status-danger: 239 68 68;
--cs-status-info: 59 130 246;
/* spacing/radius */
--cs-space-1: .25rem; --cs-space-2: .5rem; --cs-space-3: .75rem; --cs-space-4: 1rem; --cs-space-6: 1.5rem;
--cs-radius-sm: .25rem; --cs-radius-md: .375rem; --cs-radius-lg: .5rem; --cs-radius-xl: .75rem; --cs-radius-2xl: 1rem;
/* focus */
--cs-focus-ring: 59 130 246; /* blue-500 */
--cs-focus-width: 2px;
}
css/tokens.dark.css (excerpt)
:root[data-theme="dark"], .dark {
--cs-surface-1: 17 24 39;
--cs-surface-2: 31 41 55;
--cs-content-hi: 243 244 246;
--cs-content-lo: 209 213 219;
--cs-action-fg: 17 24 39; /* ensure contrast for brand bg */
}
β οΈ High-contrast variants live in
tokens.a11y.css. Apply via[data-a11y="high-contrast"].
π§° Consumption Patterns¶
Component Library (RCL)
- Imports preset in
tailwind.config.cjs:presets: [require('@connectsoft/theme/tailwind/preset.cjs')] - Razor components use semantic classes:
bg-surface-1 text-content p-sm rounded-md shadow-cs1 - No hard-coded colors; behavior visuals via adapters.
Microfrontend (WASM CE)
- Shadow-DOM aware styles: expose token scope on host; provide
:hostmappings. - Respect shell-provided theme via
data-themeand density viadata-density. - Avoid global overrides; mount styles inside CE boundary.
Application Shell
- Owns theme selection (light/dark/a11y) and density; sets data attributes on
<html>or root layout. - Persists preferences per-tenant; broadcasts theme changes to MFEs.
π§ͺ Validation & Quality Gates¶
- Contrast checks (WCAG AA/AAA) automated against semantic pairs (
surface.*Γcontent.*,action.bgΓaction.fg). - Token drift detector: CI step verifying no component uses raw hex/RGB values.
- Preset integrity: snapshot test for Tailwind theme map β CSS vars.
- Bundle bloat guard: ensure token CSS β€ target size (e.g., 8KB gz).
π‘ If a component needs a color not in tokensβthatβs a token bug, not a component exception.
π Theming & Density Contract¶
- Theme switching via
data-theme="light|dark"and optionaldata-a11y="high-contrast". - Density via
data-density="compact|cozy|comfortable"adjusts paddings/heights using--cs-space-*. - MFEs must listen and re-render on attribute changes (MutationObserver or event).
π¦ Distribution¶
- Publish as NuGet for .NET consumers (embed CSS files + static web assets).
- Publish as NPM (optional) for cross-ecosystem use (future-proofing).
- Version tokens independently (
connectsoft.theme.tokens@X.Y.Z) with documented breaking-change policy.
β Guarantees¶
- One token source of truth drives all visuals across RCL, MFEs, and Shell.
- UI kits (Flowbite, DaisyUI, etc.) are plugged via adapters, never altering tokens.
- Accessibility and theming are first-class, not afterthoughts.
- Regeneration is idempotentβsame inputs produce the same preset maps.
π§± Component Library Template¶
π― Purpose¶
Ship a Blazor Class Library (RCL) template that provides a tokens-first component set with scoped CSS, accessibility baked-in, and a MSTest harness (unit + rendering tests). This library becomes the foundation design system for microfrontends and shellsβUI-kit-agnostic and ready for NuGet distribution.
π‘ Components render tokens; behavior is pluggable via adapters. No kit lock-in.
π¦ Template Output¶
- Template ID:
connectsoft.blazor.component-lib - Project Name:
ConnectSoft.Blazor.ComponentLibrary - Artifacts:
.nupkgwith static web assets (tokens CSS), XML docs, SourceLink, symbols - Scaffold: sample components (Button, TextField, Modal), docs snippets, tests
π Project Layout (generated)¶
src/
ConnectSoft.Blazor.ComponentLibrary/
Components/
Button.razor
Button.razor.css
TextField.razor
TextField.razor.css
Modal.razor
Modal.razor.css
Accessibility/
FocusRing.razor.css
Aria.cs
Adapters/
IUiKitAdapter.cs
TokensAdapter.cs // default no-kit adapter
Options/
UiOptions.cs
UiOptionsValidator.cs
Localization/
Resources.resx
Resources.ru.resx
Resources.he.resx
wwwroot/
css/tokens.css // imported from tokens package at pack time
ConnectSoft.Blazor.ComponentLibrary.csproj
tests/
ConnectSoft.Blazor.ComponentLibrary.Tests/
ButtonTests.cs
TextFieldTests.cs
ModalTests.cs
TestHost.cs // bUnit + MSTest integration
template/
.template.config/template.json // dotnet new metadata & parameters
π§± Baseline Components (v1)¶
- Button: sizes (sm, md, lg), variants (primary, secondary, subtle, danger), loading, disabled, icon-before/after, full-width
- TextField: label, helper/error text, prefix/suffix slots, validation states, required marker
- Modal: header/body/footer slots, trap focus, ESC/overlay close, size presets
π All variants/style states are driven by semantic tokens (e.g.,
--cs-action-bg).
π¨ Styling & Accessibility¶
- Scoped CSS per component (
.razor.css), zero global bleed - Tokens-first utility classes (
bg-surface-1 text-content rounded-md) via Tailwind preset - A11y: ARIA roles/labels, keyboard navigation, visible focus ring, motion-respecting transitions
- Density & Theme: respect
data-densityanddata-themeattributes propagated from host
Example: Button.razor (excerpt)
<button
class="cs-btn inline-flex items-center justify-center rounded-md px-sm py-xs
bg-[color:rgb(var(--cs-action-bg))] text-[color:rgb(var(--cs-action-fg))]
hover:bg-[color:rgb(var(--cs-action-hover))] focus:outline-none
focus:ring-2 focus:ring-offset-2 focus:ring-[color:rgb(var(--cs-focus-ring))]"
disabled="@Disabled"
aria-disabled="@Disabled"
@onclick="OnClick">
@if (IconBefore is not null) { <span class="mr-xs">@IconBefore</span> }
<span>@ChildContent</span>
@if (IconAfter is not null) { <span class="ml-xs">@IconAfter</span> }
</button>
@code {
[Parameter] public bool Disabled { get; set; }
[Parameter] public RenderFragment? IconBefore { get; set; }
[Parameter] public RenderFragment? IconAfter { get; set; }
[Parameter] public RenderFragment ChildContent { get; set; } = default!;
[Parameter] public EventCallback<MouseEventArgs> OnClick { get; set; }
}
π§° Options & Localization¶
- Options:
UiOptions(density default, focus ring mode, animation enablement) with DataAnnotations validator - Localization:
IStringLocalizerwired; RESX for built-in strings (e.g., βCloseβ, βRequiredβ)
public sealed class UiOptions
{
[Required, RegularExpression("compact|cozy|comfortable")]
public string DefaultDensity { get; init; } = "cozy";
public bool ReducedMotion { get; init; } = false;
}
π Adapter Surface (no kit lock-in)¶
public interface IUiKitAdapter
{
RenderFragment Toast(string message, UiSeverity severity);
RenderFragment ModalFrame(RenderFragment content);
RenderFragment ValidationMessage(string text);
}
- Default:
TokensAdapter(pure tokens) - Extensible: optional
FlowbiteAdapteror others in sample repo (not a core dependency)
π¬ Testing (MSTest + bUnit)¶
- Framework: MSTest V2 with bUnit for component rendering
- Coverage: rendering states, events, accessibility (role/label), focus trap for Modal
- Snapshots: optional structural snapshots (pragma kept small to avoid fragility)
[TestClass]
public class ButtonTests : BunitTestContext
{
[TestMethod]
public void Renders_Primary_Button_With_Text()
{
var cut = RenderComponent<Button>(p => p.AddChildContent("Save"));
cut.MarkupMatches(@"<button ...>Save</button>");
}
}
π§ͺ Quality Gates¶
- No raw colors in components (CI token drift rule)
- Axe/Pa11y checks for sample gallery (contrast, roles)
- Analyzer pack: disallow synchronous JS interop, enforce nullable, logging categories
- API surface: public components documented with XML; breaking changes tracked with API diff
βοΈ Packaging & Static Web Assets¶
- Static Web Assets:
wwwroot/css/tokens.cssembedded; consumers get tokens automatically - SourceLink & Symbols: enabled for NuGet packages
- Minimal Dependencies: keep transitive graph tight; adapters shipped separately
π§ͺ Sample Gallery (included)¶
- Renders all states/variants with copyable code
- Theme & density switchers (light/dark/high-contrast, compact/cozy/comfortable)
- A11y panel: tab order, focus outlines, screen-reader labels
π‘ Gallery is your living spec. If a state is missing in the gallery, it doesnβt exist in the design system.
β Guarantees¶
- Tokens-first, UI-kit-agnostic components with scoped CSS
- Accessible by default (roles, keyboard, focus)
- MSTest + bUnit harness ensures render correctness and regression safety
- Packaged as NuGet with static assets, ready for immediate reuse
π UI Kit Adapter Contracts¶
π― Purpose¶
Define a stable abstraction that decouples visual behavior (dialogs, toasts, validation, menus, overlays) from visual tokens (colors, spacing). Components render tokens-first; adapters implement behaviors using a concrete UI kit (Flowbite, DaisyUI, etc.) or a pure-tokens default.
π‘ Adapters let product teams pick or swap a UI kit at the application/shell layer β without touching component code.
π§ Contract Goals¶
- Stable interfaces: minimal, composable, versioned.
- No hard dependency on any UI kit in the core library.
- Pluggable via DI at app/shell level (
IUiKitAdapter). - SSR/WASM-safe: avoid assumptions that break in prerender/WASM.
- A11y-first: adapters must preserve ARIA, focus management, and keyboard flows.
π§© Core Interfaces¶
Base marker & capabilities
public interface IUiKitAdapter { }
[Flags]
public enum UiKitCapabilities
{
None = 0,
Dialogs = 1 << 0,
Toasters = 1 << 1,
Dropdowns = 1 << 2,
Tooltips = 1 << 3,
Validation = 1 << 4,
Tabs = 1 << 5
}
Dialog & Overlay
public interface IUiDialogAdapter : IUiKitAdapter
{
// Creates a modal frame that traps focus and respects ESC/overlay close
RenderFragment Frame(RenderFragment content, DialogOptions? options = null);
// Optional programmatic API (shell may call)
Task ShowAsync(RenderFragment content, DialogOptions? options = null, CancellationToken ct = default);
Task CloseAsync(CancellationToken ct = default);
}
public sealed record DialogOptions(
string? Title = null,
bool CloseOnOverlay = true,
string Size = "md", // sm|md|lg|xl
bool InitialFocus = true);
Toasts / Notifications
public interface IUiToastAdapter : IUiKitAdapter
{
RenderFragment Inline(string message, UiSeverity severity = UiSeverity.Info, ToastOptions? options = null);
Task ShowAsync(string message, UiSeverity severity = UiSeverity.Info, ToastOptions? options = null, CancellationToken ct = default);
}
public enum UiSeverity { Info, Success, Warning, Danger }
public sealed record ToastOptions(
TimeSpan? Duration = null,
bool Closable = true,
string? IconClass = null);
Dropdowns, Menus, Tooltips (optional)
public interface IUiMenuAdapter : IUiKitAdapter
{
RenderFragment Trigger(RenderFragment button, string id);
RenderFragment Content(RenderFragment items, string id, MenuOptions? options = null);
}
public sealed record MenuOptions(bool CloseOnSelect = true);
Validation Rendering
public interface IUiValidationAdapter : IUiKitAdapter
{
RenderFragment Message(string text, UiSeverity severity = UiSeverity.Danger);
string InputValidClass { get; }
string InputInvalidClass { get; }
}
π Keep interfaces small and orthogonal. New behaviors β new interfaces, not new methods on old ones.
π§± Default Adapter (Tokens-Only)¶
TokensAdapter ships in the component library to provide zero-dependency behaviors using tokens + minimal JS (if any).
public sealed class TokensDialogAdapter : IUiDialogAdapter
{
public RenderFragment Frame(RenderFragment content, DialogOptions? options = null) => builder =>
{
// Backdrop + container using tokenized classes
builder.OpenElement(0, "div");
builder.AddAttribute(1, "class", "fixed inset-0 bg-[color:rgb(var(--cs-content-lo))]/40");
builder.OpenElement(2, "div");
builder.AddAttribute(3, "class", "fixed inset-0 flex items-center justify-center p-lg");
builder.OpenElement(4, "div");
builder.AddAttribute(5, "class", "bg-surface-1 rounded-lg shadow-cs4 w-full max-w-xl");
if (!string.IsNullOrWhiteSpace(options?.Title))
{
builder.OpenElement(6, "div");
builder.AddAttribute(7, "class", "px-lg pt-lg text-content font-medium");
builder.AddContent(8, options!.Title);
builder.CloseElement();
}
builder.OpenElement(9, "div");
builder.AddAttribute(10, "class", "p-lg");
builder.AddContent(11, content);
builder.CloseElement(); // body
builder.CloseElement(); // panel
builder.CloseElement(); // center
builder.CloseElement(); // backdrop
};
public Task ShowAsync(RenderFragment content, DialogOptions? options = null, CancellationToken ct = default)
=> Task.CompletedTask;
public Task CloseAsync(CancellationToken ct = default) => Task.CompletedTask;
}
- Focus trap: implemented via a small JS module (tokens-only) or Blazor focus APIs.
- Animations: respect
prefers-reduced-motionandUiOptions.ReducedMotion.
π Flowbite Adapter (example package)¶
A separate, optional package: ConnectSoft.Blazor.UiKit.FlowbiteAdapter (not referenced by core).
public sealed class FlowbiteDialogAdapter : IUiDialogAdapter
{
public RenderFragment Frame(RenderFragment content, DialogOptions? options = null) => @<FlowbiteModal Size="@(options?.Size ?? "md")">
@content
</FlowbiteModal>;
public Task ShowAsync(RenderFragment content, DialogOptions? options = null, CancellationToken ct = default)
=> FlowbiteInterop.OpenAsync(/*...*/);
public Task CloseAsync(CancellationToken ct = default)
=> FlowbiteInterop.CloseAsync(/*...*/);
}
- Maps dialog/menu/tooltip to Flowbite components.
- Respects semantic tokens for colors via CSS var bridge where possible.
- Ships no tokens, only behavior mappings.
πΌ DaisyUI Adapter (example package)¶
ConnectSoft.Blazor.UiKit.DaisyAdapter:
- Uses DaisyUI classes for frames, alerts, menus.
- Adds a tiny bridge stylesheet mapping semantic tokens β Daisy classes when needed.
- Ensures keyboard & ARIA parity with the default adapter.
π§° Registration & Selection (DI)¶
App/Shell registration
services.AddSingleton<IUiDialogAdapter, TokensDialogAdapter>();
services.AddSingleton<IUiToastAdapter, TokensToastAdapter>();
services.AddSingleton<IUiValidationAdapter, TokensValidationAdapter>();
// Swap to Flowbite
// services.Replace(ServiceDescriptor.Singleton<IUiDialogAdapter, FlowbiteDialogAdapter>());
// services.Replace(ServiceDescriptor.Singleton<IUiToastAdapter, FlowbiteToastAdapter>());
// services.Replace(ServiceDescriptor.Singleton<IUiValidationAdapter, FlowbiteValidationAdapter>());
Component consumption
@inject IUiDialogAdapter Dialog
@code {
void Open() => Dialog.ShowAsync(builder => @<div>Settings</div>);
}
π‘ The shell decides the adapter set. MFEs inherit through DI when hosted in the shell.
π§ͺ Testing & Compliance¶
- Adapter conformance tests in an Adapter Test Kit project:
- Focus trap, ESC/overlay close, role/label expectations.
- Toast lifetimes & stacking.
- Validation classes applied on success/error.
- A11y checks (axe) against the same spec for tokens/Flowbite/Daisy adapters.
- Snapshot deltas allowed per adapter (visuals differ), behavior must match.
π Security & Performance¶
- No untrusted HTML injection.
- CSP-safe: adapters must work without
unsafe-inline(use classes/attributes). - No long-running JS timers; toasts use
requestAnimationFrameor CSS animation events. - Keep adapter bundles small; avoid bringing entire kit if only 1β2 behaviors used.
π Versioning & Evolution¶
- Interfaces follow semantic versioning.
- New behaviors => new interfaces or optional methods with defaults.
- Breaking changes batched into major updates and accompanied by adapter migration notes.
β Guarantees¶
- Components stay UI-kit-agnostic; behavior is pluggable.
- Shells can swap adapters without recompiling component packages.
- Accessibility & keyboard/focus behaviors remain consistent across adapters.
- No core dependency on Flowbite/Daisy β adapters are optional extensions.
π§© Advanced Component Library Features¶
π― Purpose¶
Elevate the Component Library beyond visuals by standardizing options binding, localization (i18n), accessibility verification, and state conventions. This ensures every component behaves predictably in enterprise SaaS portals (multi-tenant, multilingual, accessible-by-default).
π‘ Visuals attract. Predictability retains. These features turn a pretty kit into a production system.
βοΈ Options Binding (strongly-typed, validated)¶
Goals
- Centralize component-wide defaults (density, motion, focus, validation mode).
- Guarantee safe configuration with validation and live updates.
Contracts
public sealed class UiOptions
{
[Required, RegularExpression("compact|cozy|comfortable")]
public string DefaultDensity { get; init; } = "cozy";
public bool ReducedMotion { get; init; } = false;
[Required, RegularExpression("inline|tooltip|none")]
public string ValidationPresentation { get; init; } = "inline";
}
public static class ServiceCollectionExtensions
{
public static IServiceCollection AddConnectSoftUi(this IServiceCollection services, IConfiguration cfg)
{
services.AddOptions<UiOptions>()
.Bind(cfg.GetSection("Ui"))
.ValidateDataAnnotations()
.ValidateOnStart();
return services;
}
}
Patterns
- Use
IOptionsSnapshot<UiOptions>in server contexts (changes per-request). - Use
IOptionsMonitor<UiOptions>in WASM for runtime updates (theme/density toggles). - Expose component-level overrides via parameters; merge with
UiOptionson render.
π Options govern behavior defaults; parameters allow local overrides without forking patterns.
π Localization & Globalization (i18n/L10n)¶
Goals
- First-class multilingual support (EN, RU, HE) with RTL awareness.
- Locale-aware formatting (dates, numbers) and pluralization.
Contracts
// In library startup (sample)
services.AddLocalization(options => options.ResourcesPath = "Localization");
// Component usage
@inject IStringLocalizer<Resources> T
<span aria-label="@T["Close"]">@T["Close"]</span>
Guidelines
- Ship neutral
Resources.resx+ culture-specific.resx(e.g.,Resources.ru.resx,Resources.he.resx). - Support RTL via
[dir="rtl"]detection and tokens that flip paddings/margins logically (start/end). - Use
CultureInfo.CurrentUICulturefor number/date formats. - Provide Plural helper:
public static string Plural(this IStringLocalizer T, string key, int count)
=> T[$"{key}_{(count == 1 ? "One" : "Other")}", count];
Testing
- Snapshot critical components in at least two languages and LTR/RTL.
- Validate visual overflow for long translations (German-like length).
π‘ If you canβt switch to HE (RTL) in the gallery and keep layout intact, itβs not done.
βΏ Accessibility Checks (a11y)¶
Goals
- Enforce WCAG AA at component level: contrast, keyboard, roles, focus order.
Baseline
-
Every interactive component must:
-
have a visible focus indicator (token-driven),
- support keyboard navigation (Tab/Shift+Tab, Arrow for lists/menus),
- include proper roles/ARIA (
role="dialog",aria-modal="true",aria-expanded,aria-controls), - support labels & descriptions (
aria-label,aria-labelledby,aria-describedby).
Automation
- Run axe against the Sample Gallery routes in CI.
- Pa11y smoke checks for high-level flows (open/close Modal, error states).
- Contrast tests for semantic pairs (
contentvssurface,action.fgvsaction.bg).
Contract Snippet
<div role="dialog" aria-modal="true" aria-labelledby="@TitleId" aria-describedby="@BodyId">
<h2 id="@TitleId">@T["Settings"]</h2>
<div id="@BodyId">@ChildContent</div>
</div>
π Accessibility is not a lint ruleβit's a behavior contract. Tests must prove it.
π State Conventions (controlled/uncontrolled, events, async)¶
Goals
- Predictable component behavior under different ownership models.
- Consistent event naming, cancellation, and async handling.
Patterns
-
Controlled vs Uncontrolled
-
If a componentβs visibility/value can be externally driven, expose:
bool? IsOpen+EventCallback<bool> IsOpenChangedTValue? Value+EventCallback<TValue> ValueChanged- If
IsOpen/Valueis not provided, manage internal state (uncontrolled).
-
Event Model
-
Use
EventCallbackfor UI events (OnClick,OnChange). - Provide cancellable events via
BeforeXxxAsync(CancellableArgs args). - Provide completion events via
AfterXxxAsync(EventArgs args).
public sealed class CancellableArgs : EventArgs
{
public bool Cancel { get; set; }
public string? Reason { get; set; }
}
-
Async & Loading
-
Public
Taskmethods must be await-safe; prevent double-activation with an IsBusy token. - Show skeletons or progress based on
UiOptions.ReducedMotion. -
Ensure idempotency: repeated clicks donβt break state.
-
Forms & Validation
-
Integrate with
EditForm+ValidationMessage<T>patterns. - Expose
ValidationClassProvidermapping for valid/invalid states (viaIUiValidationAdapter).
Example (controlled modal)
@code {
[Parameter] public bool? IsOpen { get; set; }
[Parameter] public EventCallback<bool> IsOpenChanged { get; set; }
private bool internalOpen;
private bool Open => IsOpen ?? internalOpen;
private async Task SetOpenAsync(bool value)
{
internalOpen = value;
if (IsOpen.HasValue) await IsOpenChanged.InvokeAsync(value);
StateHasChanged();
}
}
π‘ Every interactive component should document both controlled and uncontrolled usage.
π§ͺ Testing & Quality Gates¶
- Unit: MSTest + bUnit for state transitions, two-way binding, cancellable events.
- A11y: axe/Pa11y CI gates; fail build on violations (allow explicit, reviewed suppressions).
- i18n: culture-switch tests for pluralization and RTL mirrors.
- Options: configure with invalid values to assert validation failures at startup.
- API Surface: XML docs completeness; public API diff checked per PR.
π¦ Documentation (Sample Gallery as living spec)¶
- Controls panel per component: props, events, slots/child content.
- Usage tabs: controlled/uncontrolled examples, form integration, i18n/RTL toggles.
- A11y tab: keyboard map and roles/ARIA used.
- Design tab: semantic token references (what colors/spacing it binds to).
π Security & Performance¶
- Disallow raw
MarkupStringunless explicitly documented and sanitized. - No layout thrash: prefer CSS transitions; watch for forced reflows.
- No unbounded timers or global event leaks; dispose subscriptions.
β Guarantees¶
- Behavior is configurable, localizable, and accessible by default.
- State patterns are consistent across components (controlled/uncontrolled).
- CI enforces a11y + i18n + options validation to keep quality non-negotiable.
- Components remain UI-kit-agnostic and token-driven while offering rich behavior via adapters.
βοΈ Microfrontend Library Template¶
π― Purpose¶
Provide a Blazor WASM template that compiles into a Web Component (Custom Element), with a runtime bootstrap thatβs shell-aware and a manifest stub for CDN distribution and policy enforcement. This enables domain teams to ship independent features that the portal shell can discover, load, and compose at runtime.
π‘ MFEs are product slices: build, version, and deploy them without touching the shell repo.
π§± Template Output¶
- Template ID:
connectsoft.blazor.microfrontend - Project Name:
ConnectSoft.Blazor.Microfrontend -
Artifacts:
-
NuGet package (developer dependencies + helpers)
- CDN bundle (WASM, JS boot, assets)
microfrontend.jsonmanifest stub (capabilities, integrity, metadata)
π Project Layout (generated)¶
src/
ConnectSoft.Blazor.Microfrontend/
App.razor
Components/
HostFrame.razor
Bootstrap/
MfeBootstrap.cs
CustomElementRegistration.cs
ShellBridge.cs
ThemeAndCultureSync.cs
Interop/
dom.ts # CE registration & focus helpers
loader.ts # manifest-aware loader
Manifest/
microfrontend.json # emitted at build with placeholders
wwwroot/
entry.js # CE boot β Blazor.start()
styles.css
ConnectSoft.Blazor.Microfrontend.csproj
tests/
ConnectSoft.Blazor.Microfrontend.Tests/
MountAsCustomElementTests.cs
ThemeCulturePropagationTests.cs
ManifestValidationTests.cs
template/
.template.config/template.json
π Runtime Bootstrap¶
Goals
- Register the Blazor app as a Custom Element (e.g.,
<cs-mfe-billing>). - Support multiple instances on a page.
- Be aware of shell context (tenant, edition, theme, culture).
- Fail-safe when offline or capabilities are missing.
Key pieces
CustomElementRegistration.csβ callsRootComponents.RegisterCustomElement<App>("cs-mfe-β¦").entry.jsβ CE lifecycle hooks (connectedCallback,disconnectedCallback) +Blazor.start().ThemeAndCultureSync.csβ listens for shell attribute changes (data-theme,lang) and applies to the root.ShellBridge.csβ optional events/commands channel initialized from element attributes.
// CustomElementRegistration.cs
public static class CustomElementRegistration
{
public static void Register(WebAssemblyHostBuilder builder, string tag)
{
builder.RootComponents.RegisterCustomElement<App>(tag);
}
}
π·οΈ Custom Element Contract¶
Element tag
- Default:
cs-mfe-{kebab-name}(e.g.,cs-mfe-billing) - Configurable via template parameter
--element-tag.
Attributes
data-tenant/data-editionβ bootstrap contextdata-theme="light|dark"/data-densityβ visual statelang="en|ru|he"β culture/UI languagedata-endpointβ optional BFF endpoint root
π All context attributes are optional; if not provided, MFE uses its defaults (UiOptions, neutral theme).
π Manifest Stub (microfrontend.json)¶
Purpose: Tell the shell what this MFE is, what it needs, and how to load it.
{
"name": "cs.billing",
"displayName": "Billing",
"version": "1.0.0",
"element": "cs-mfe-billing",
"entry": {
"script": "https://cdn.example.com/cs.billing/1.0.0/entry.js",
"wasm": "https://cdn.example.com/cs.billing/1.0.0/app.wasm",
"assets": [
"https://cdn.example.com/cs.billing/1.0.0/styles.css"
],
"integrity": {
"entry.js": "sha384-β¦",
"app.wasm": "sha384-β¦",
"styles.css": "sha384-β¦"
}
},
"capabilities": ["notify", "openBlade"],
"permissions": {
"endpoints": ["bff:/billing/*"]
},
"minShellVersion": "1.0.0",
"i18n": ["en", "ru", "he"],
"theming": ["light", "dark"],
"density": ["compact", "cozy", "comfortable"]
}
- Capabilities β what the MFE can request from shell (
notify,openBlade, β¦). - Permissions β allowed BFF routes (shell will proxy/deny).
- Integrity β SRI hashes for secure loading.
- minShellVersion β composition gate in the shell registry.
π Shell Awareness & Bridge¶
-
ShellBridgeexposes lightweight APIs (events/commands):- Events:
ThemeChanged,CultureChanged,TenantChanged,EditionChanged - Commands:
Notify(string message, severity),OpenBlade(string id, object? args)
- Events:
-
Communication mediums (in order of preference):
- Direct host API (shell sets a JS object on CE),
- DOM events (CustomEvent on CE),
- window.postMessage fallback (namespaced channel).
π‘ Bridge is optional β CE still runs standalone for local demos and unit tests.
π i18n & Theme/Density Sync¶
- Honor
langattribute on CE; setCultureInfo.CurrentUICulture. - Watch for
data-themeanddata-densitychanges (MutationObserver). - Provide helper events so the shell can broadcast changes at once.
π§ͺ Testing¶
- Mount tests: Custom Element attaches/detaches without leaks; multiple instances supported.
- Manifest validation: schema validation + SRI presence.
- Context propagation: theme/culture applied; UI re-renders on change.
- Offline mode: graceful error if assets unavailable (renders fallback message).
[TestMethod]
public void Registers_Custom_Element_Tag()
{
var tag = "cs-mfe-sample";
var builder = WebAssemblyHostBuilder.CreateDefault();
CustomElementRegistration.Register(builder, tag);
// Assert: RootComponents contains tag mapping (pseudo-assertion).
}
ποΈ Build & Emit¶
- Build produces:
entry.js+app.wasm+assets/*(hashed filenames recommended)microfrontend.jsonpopulated from MSBuild variables (name, version, element, integrity)
- Integrity hashes computed in CI; manifest updated post-build.
MSBuild knobs (sample)
<PropertyGroup>
<MfeName>cs.billing</MfeName>
<MfeElement>cs-mfe-billing</MfeElement>
<MfeCdnRoot>$(CDN_ROOT)/cs.billing/$(Version)/</MfeCdnRoot>
</PropertyGroup>
π Security & Compliance¶
- CSP: support strict mode β no
unsafe-inline; use class-based styling & event handlers. - SRI: entry + wasm + CSS must include
integrity. - Sandbox: no untrusted HTML injection; sanitize any dynamic content.
- Endpoint allowlist: only call BFF routes approved in
permissions.endpoints. - No localStorage secrets; use shell-managed auth via cookies (BFF).
π¦ Distribution¶
- Publish NuGet (developer helper APIs, DI & bridge contracts).
- Publish CDN bundle with manifest for shell runtime loading.
- Optional NPM wrapper for non-.NET hosts (future).
β Guarantees¶
- The MFE template compiles to a reusable Custom Element with shell-awareness.
- Loading is manifest-driven with SRI and capability gating.
- Theme, culture, and density sync seamlessly from the shell.
- CI emits NuGet + CDN artifacts and a validated manifest ready for portal composition.
π Microfrontend Runtime Contracts¶
π― Purpose¶
Define the formal contracts that allow the shell to discover, validate, authorize, load, and communicate with Microfrontends (MFEs) at runtime. This includes the manifest schema, the event/command bus protocol, and a capability allowlist that enforces least privilege.
π‘ If itβs not in the contract, it doesnβt exist at runtime. Contracts β docs β theyβre the rules the shell enforces.
π Manifest Schema (microfrontend.json)¶
The manifest is the source of truth for MFE identity, assets, capabilities, and compatibility.
{
"$schema": "https://catalog.connectsoft.dev/schemas/mfe/1-0-0.json",
"name": "cs.billing", // unique, kebab/camel allowed
"displayName": "Billing",
"version": "1.2.3",
"element": "cs-mfe-billing", // Custom Element tag to render
"entry": {
"script": "https://cdn.example/cs.billing/1.2.3/entry.js",
"wasm": "https://cdn.example/cs.billing/1.2.3/app.wasm",
"assets": [
"https://cdn.example/cs.billing/1.2.3/styles.css"
],
"integrity": {
"entry.js": "sha384-β¦",
"app.wasm": "sha384-β¦",
"styles.css": "sha384-β¦"
}
},
"i18n": ["en","ru","he"], // supported UI languages
"theming": ["light","dark"], // visual modes MFE supports
"density": ["compact","cozy","comfortable"],
"capabilities": ["notify","openBlade"], // requested shell features
"permissions": {
"endpoints": ["bff:/billing/*"] // BFF allowlist (shell enforces)
},
"minShellVersion": "1.0.0",
"telemetry": {
"activitySource": "CS.MFE.Billing",
"metricsPrefix": "cs_mfe_billing_"
},
"health": {
"statusEndpoint": "https://cdn.example/cs.billing/1.2.3/health.json"
}
}
Rules
versionuses SemVer; shell compares againstminShellVersion.- All
entry.*assets must include SRI (integrity) and be served with CORS headers compatible with CSP. permissions.endpointsis deny-by-default: shell proxies only allowlisted routes.- Manifest must be immutable post-release; new versions publish a new manifest URL.
π Manifests are machine-validated; human-readable fields (like
displayName) must not affect behavior.
π§© Schema Fragments (JSON Schema)¶
Identity & Compatibility
{
"type": "object",
"required": ["name","version","element","entry","capabilities","minShellVersion"],
"properties": {
"name": { "type":"string", "pattern":"^[a-z0-9_.-]+$" },
"version":{ "type":"string", "pattern":"^\\d+\\.\\d+\\.\\d+(-[A-Za-z0-9.-]+)?$" },
"element":{ "type":"string", "pattern":"^[a-z][a-z0-9-]*$" },
"minShellVersion": { "type":"string" }
}
}
Entry Assets & Integrity
{
"entry": {
"type": "object",
"required": ["script", "wasm", "assets", "integrity"],
"properties": {
"script": { "type":"string", "format":"uri" },
"wasm": { "type":"string", "format":"uri" },
"assets": { "type":"array", "items": { "type":"string", "format":"uri" } },
"integrity": {
"type":"object",
"additionalProperties": { "type":"string", "pattern":"^sha(256|384|512)-" }
}
}
}
}
Capabilities & Permissions
{
"capabilities": {
"type":"array",
"items": { "enum": ["notify","openBlade","navigate","activityLog","clipboard"] },
"uniqueItems": true
},
"permissions": {
"type":"object",
"properties": {
"endpoints": {
"type":"array",
"items": { "type":"string", "pattern":"^(bff:/.+)$" },
"uniqueItems": true
}
},
"additionalProperties": false
}
}
π Event / Command Bus¶
Bidirectional Shell β MFE communication uses typed events and commands. Transport is abstract; implementations may use direct host API, DOM CustomEvents, or postMessage. The protocol (names, payloads) is stable.
Event Names (Shell β MFE)
Shell.ThemeChangedβ{ theme: "light|dark", density: "compact|cozy|comfortable" }Shell.CultureChangedβ{ culture: "en|ru|he" }Shell.TenantChangedβ{ tenantId: "β¦" , edition: "Pro|Enterprise" }Shell.FeatureFlagsUpdatedβ{ flags: Record<string, boolean> }
Command Names (MFE β Shell)
Mfe.Notifyβ{ message: string, severity: "info|success|warning|danger" }(requiresnotify)Mfe.OpenBladeβ{ bladeId: string, args?: object }(requiresopenBlade)Mfe.Navigateβ{ path: string }(requiresnavigate)Mfe.LogActivityβ{ category: string, details?: object }(requiresactivityLog)
π‘ Capabilities gate commands: if
openBladeisnβt in the manifest, the shell rejects the command.
DOM Event Example
// within custom element instance
this.dispatchEvent(new CustomEvent("Mfe.Notify", {
bubbles: true,
detail: { message: "Saved", severity: "success" }
}));
Host API Example
// shell injects a host object onto the CE instance
ce.hostApi.openBlade({ bladeId: "billing.invoice", args: { id: 123 } });
π‘οΈ Capability Allowlist (Least Privilege)¶
Principles
- Capabilities are explicit opt-ins declared in the manifest.
- Shell may deny at runtime via policy (tenant/edition/role).
- New capabilities are versioned; old shells ignore unknown capabilities.
Standard Set (v1)
notifyβ raise a shell notification.openBladeβ open a specific blade in the shell.navigateβ request a route change within the shell.activityLogβ append to the shellβs activity stream.clipboardβ request text copy via a shell-mediated API (CSP-safe).
Policy Evaluation
- Manifest β capability request
- Shell policy (tenant/edition/role) β allow/deny
- Command invocation β checked against effective capabilities (request β© policy)
π Deny-by-default. Capability elevation requires manifest change and policy approval.
π§° Shell Registry & Loading Flow¶
- Discovery
- Shell fetches manifest (signed/immutable URL) from its allowlist registry.
- Validation
- JSON Schema validate; SRI checks; compatibility (
minShellVersion).
- JSON Schema validate; SRI checks; compatibility (
- Policy
- Capabilities intersect with tenant/edition/role policies.
- Load
- Inject
entry.script; verify SRI; instantiateelementtag.
- Inject
- Wire
- Provide host API (or subscribe to CE events); send initial
Shell.*events.
- Provide host API (or subscribe to CE events); send initial
Failure Modes (and UI)
- Invalid schema β Block + log (activity + telemetry).
- SRI mismatch β Block + security alert.
- Incompatible version β Offer downgrade or skip with explanation.
- Denied capability β Load without that command; return structured error if invoked.
π§ͺ Validation & QA Anchors¶
- Schema validation (CI + shell runtime).
- SRI verification for each asset; fail build if missing.
- Policy simulation: run matrix tests (capabilities Γ tenant/edition/role).
- Backward-compat suite: ensure older shells ignore unknown fields gracefully.
- Contract tests: emit each
Mfe.*command and assert shell responses (allow/deny).
π Security Considerations¶
- CSP-compatible loading (no inline JS/CSS).
- No eval or dynamic script URLs; only registry-approved CDN origins.
- BFF-only network: MFEs never call third-party APIs directly; the shell proxies.
- Data sanitization: notifications & logs are HTML-escaped by the shell.
- Rate-limits on chatty commands (e.g.,
Notify,ActivityLog).
π Versioning & Evolution¶
- Schema version is part of
$schemaURL (e.g.,1-0-0). - Additive-first: new optional fields/capabilities are backward compatible.
- Breaking changes β new major schema; shell may support multiple concurrently.
- Capability set evolves with RFCs; each new item carries security guidance and policy knobs.
β Guarantees¶
- MFEs are self-describing via a strict manifest.
- Shell enforces capability least privilege and policy gates.
- Communication uses a stable, typed bus with clear allow/deny outcomes.
- Loading is secure (CSP/SRI), validated, and backward compatible across versions.
π Advanced Microfrontend Features¶
π― Purpose¶
Elevate the Microfrontend Library with secure API access (BFF/YARP), runtime feature flags, and WASM-safe observability. This turns MFEs into production-grade units that integrate cleanly with enterprise shells and backends.
π‘ The MFE never talks to raw APIs β it talks to the BFF. Observability is on by default, not bolted on.
π BFF/YARP API Client Boilerplate¶
Goals
- Ensure zero-token handling in the browser (cookies via BFF).
- Provide a typed
HttpClientpreconfigured for relative/bff/*routes. - Propagate trace context and tenant/edition headers.
Boilerplate
// Startup.cs (WASM)
builder.Services.AddScoped(sp =>
{
var http = new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) };
http.DefaultRequestHeaders.Add("X-CS-Tenant", ShellContext.TenantId ?? "default");
http.DefaultRequestHeaders.Add("X-CS-Edition", ShellContext.Edition ?? "Pro");
return http;
});
// Typed client
public sealed class BillingApi
{
private readonly HttpClient _http;
public BillingApi(HttpClient http) => _http = http;
public async Task<InvoiceDto[]> ListAsync(CancellationToken ct = default)
=> await _http.GetFromJsonAsync<InvoiceDto[]>("/bff/billing/invoices", ct)
?? Array.Empty<InvoiceDto>();
public async Task PayAsync(Guid id, CancellationToken ct = default)
=> (await _http.PostAsync($"/bff/billing/pay/{id}", content: null, ct)).EnsureSuccessStatusCode();
}
Guidelines
- All network calls go through
/bff/*; server enforces auth, RBAC, and downstream service access. - No token in JS; auth cookies managed server-side.
- Timeouts, retries, and circuit-breakers are handled on the server, not the browser.
π If a call needs third-party access, the BFF integrates it. MFEs remain sandboxed.
ποΈ Feature Flag Hooks¶
Goals
- Runtime flag evaluation with sticky treatments (per tenant/user/edition).
- Consistent interface across MFEs and shell.
Contract & Provider
public interface IFeatureFlags
{
bool IsEnabled(string key);
event EventHandler<FlagsChangedEventArgs>? Changed;
}
public sealed record FlagsChangedEventArgs(IReadOnlyDictionary<string, bool> Flags);
public static class FeatureFlagKeys
{
public const string Billing_BetaBlade = "billing.betaBlade";
public const string Billing_NewGrid = "billing.newGrid";
}
Bootstrap & Updates
- Initial values can come from the manifest defaults or a shell-provided snapshot.
- Shell broadcasts updates via
Shell.FeatureFlagsUpdatedβ provider updates β raisesChanged.
Usage (component)
@inject IFeatureFlags Flags
@if (Flags.IsEnabled(FeatureFlagKeys.Billing_BetaBlade))
{
<BetaBlade />
}
else
{
<StableBlade />
}
Sticky Behavior
- Provider stores sticky decisions in memory (session) or via BFF cookie keyed by tenant/user.
- Respect edition overrides (Enterprise vs Pro) before local toggles.
π WASM Observability¶
Goals
- Provide Activity/trace correlation from MFE β BFF β downstream services.
- Emit structured logs and user action events.
- Keep it WASM-safe (avoid APIs not available in browser).
Setup
public static class Telemetry
{
public static readonly ActivitySource Source = new("CS.MFE.Sample");
public static IDisposable StartUiActivity(string name, params (string, object)[] tags)
{
var activity = Source.StartActivity(name, ActivityKind.Internal);
if (activity != null)
foreach (var (k, v) in tags) activity.SetTag(k, v);
return activity ?? Disposable.Nop;
}
}
Usage
using var _ = Telemetry.StartUiActivity("Invoice.Pay", ("invoice.id", id));
await _api.PayAsync(id, ct);
_logger.LogInformation("Invoice payment requested {InvoiceId}", id);
Trace Context
- BFF adds/propagates W3C
traceparent/tracestateacross services. - Browser β BFF correlation achieved via request headers (added by fetch/HttpClient automatically when same-origin).
Metrics (lightweight)
- Use counters for user actions (
cs_mfe_clicks_total,cs_mfe_api_errors_total). - Prefer in-memory accumulators; exporting is done server-side (BFF aggregates).
Error Reporting
- Wrap UI actions; send minimal error events to BFF (PII scrubbed).
- Avoid sending stack traces to analytics; log IDs & correlation only.
π‘ The browser emits enough to correlate; the server does the heavy lifting.
π Security & Privacy¶
- No secrets or tokens stored client-side.
- Enforce CSP; avoid inline handlers; ship scripts with SRI.
- PII minimization: logs contain IDs and categories, never raw user content.
- Feature flags must not leak entitlements via DOM attributes or global vars.
π§ͺ Testing & QA Anchors¶
- API: contract tests against a mock BFF route set (
/bff/billing/*). - Flags: matrix tests (flag on/off Γ edition) for rendering paths.
- Telemetry: assert that user actions emit activities and that correlation headers are present.
- Resilience: simulate BFF failures/timeouts; ensure UI fallbacks and retries are sane.
π¦ Distribution & Config¶
- Typed API clients live in the MFE package; endpoints configurable via element attribute (
data-endpoint) or shell DI. - Feature flag provider is injected; default is in-memory with shell sync, advanced providers can wrap ECS/AppConfig.
- Telemetry namespaces (
ActivitySource, metric prefixes) are declared in manifest to aid server aggregation.
β Guarantees¶
- MFEs access data only through BFF, with correlation and policy enforcement.
- Feature flags are runtime-tunable, edition-aware, and sticky per context.
- Observability is WASM-safe, enabling end-to-end traces without browser-only hacks.
- Security and privacy constraints are non-negotiable defaults.
ποΈ Application Shell Template¶
π― Purpose¶
Provide a Blazor Web App shell that acts as the portal spine: it owns layout (left rail + top bar + blade/portal canvas), theme/density state, and runtime composition of MFEs loaded as Custom Elements. The shell is the single point of UX truth: navigation, context (tenant/edition), and cross-cutting concerns (theming, localization, notifications).
π‘ MFEs bring features; the shell brings order. Itβs the conductor of the orchestra.
π§± Template Output¶
- Template ID:
connectsoft.blazor.appshell - Project Name:
ConnectSoft.Blazor.AppShell - Artifacts: NuGet package (scaffold + helpers), runnable project template, sample portal with a mock MFE registry
π Project Layout (generated)¶
src/
ConnectSoft.Blazor.AppShell/
App.razor
Program.cs
Pages/
_Host.cshtml # if using Blazor Web App w/ SSR
Home.razor
Layout/
PortalLayout.razor # left rail + top bar + blade host
LeftNav.razor
TopBar.razor
BladeHost.razor
Components/
NotificationCenter.razor
ThemeSwitch.razor
DensitySwitch.razor
MfeHost.razor # CE wrapper & lifecycle
State/
ShellState.cs # theme, density, tenant, edition
ShellReducers.cs # (optional) if using Fluxor
ShellEvents.cs # typed events for bus
Registry/
ManifestRegistry.cs # allowlist & discovery
MfeDescriptor.cs # element, permissions, version
Services/
ShellBridgeHost.cs # event/command bus (host side)
ThemeService.cs
LocalizationService.cs
NotificationService.cs
wwwroot/
css/site.css
js/mfe-host.js # CE loader, SRI verify, events
ConnectSoft.Blazor.AppShell.csproj
template/
.template.config/template.json
π§ Layout System (left rail + top bar + blade canvas)¶
Left Rail
- Groups: Favorites, Workspace, Admin (configurable)
- Items: icon, title, route, optional blade target (
bladeId) - Collapsible, keyboard accessible (Arrow navigation, Home/End)
Top Bar
- Tenant selector, Search/Command palette entry point
- Theme & density toggles, User menu
- Notification bell with unread indicator
Blade/Portal Canvas
- Multi-pane blades with sizes:
narrow|normal|wide|full - Stack & focus management; ESC closes topmost
- URL-bound:
/blades/{bladeId}?args=β¦or state-bound with deep link support
@* PortalLayout.razor *@
<div class="h-screen grid grid-cols-[auto_1fr] grid-rows-[auto_1fr]">
<LeftNav class="col-start-1 row-span-2" />
<TopBar class="col-start-2 row-start-1" />
<BladeHost class="col-start-2 row-start-2" />
</div>
π If the shell canβt open and close a blade via keyboard only, itβs not a blade.
π¨ Theme & Density State¶
- State lives in
ShellStatewith two-way binding to UI and persistent storage (cookie/localStorage per tenant). - Attributes applied to root for tokens consumption:
data-theme="light|dark"data-density="compact|cozy|comfortable"- Optional
data-a11y="high-contrast"
public sealed class ShellState
{
public string Theme { get; private set; } = "light";
public string Density { get; private set; } = "cozy";
public string? TenantId { get; private set; }
public string Edition { get; private set; } = "Pro";
public event Action? Changed;
public void SetTheme(string theme) { Theme = theme; Changed?.Invoke(); }
public void SetDensity(string density) { Density = density; Changed?.Invoke(); }
public void SetTenant(string? id, string edition) { TenantId = id; Edition = edition; Changed?.Invoke(); }
}
Propagation
ThemeServiceupdates DOM attributes & persists preference.ShellBridgeHostbroadcastsShell.ThemeChanged/Shell.CultureChangedto MFEs.
π§© MFE Hosting & Composition¶
Registry & Allowlist
ManifestRegistrystores approved manifests (origin allowlist, capability policy).- Supports local dev (file manifests) and CDN (signed URLs).
MfeHost.razor
- Receives
MfeDescriptor(from route, command, or nav) - Loads manifest β verifies SRI β injects script β creates CE element
- Passes context as attributes:
data-tenant,data-edition,data-theme,lang,data-density, optionaldata-endpoint - Wires host API on CE instance or subscribes to DOM events
@code {
[Parameter] public MfeDescriptor Mfe { get; set; } = default!;
protected override async Task OnAfterRenderAsync(bool firstRender)
=> await Js.InvokeVoidAsync("csMfeHost.mount", elementRef, Mfe);
}
Host JS (excerpt)
export async function mount(hostEl, mfe) {
await loadWithSri(mfe.entry.script, mfe.entry.integrity["entry.js"]);
const ce = document.createElement(mfe.element);
ce.setAttribute("data-theme", getTheme());
ce.setAttribute("data-density", getDensity());
ce.setAttribute("lang", getCulture());
hostEl.appendChild(ce);
wireHostApi(ce); // openBlade, notify, navigateβ¦
}
π§ Navigation Model¶
- Router: standard Blazor routing (
@page) for pages and blade routes. - Blade Navigation:
- Programmatic:
ShellBridgeHost.OpenBlade("billing.invoice", new { id = 42 }) - Declarative from nav items (left rail)
- Programmatic:
- Deep Links:
- URL encodes blade id & args; shell reconstructs blade stack on refresh.
π Notifications & Activity¶
NotificationServicestores transient toasts & persistent activity items.- MFEs invoke
Mfe.Notify/Mfe.LogActivityβ capability & policy checked β show & persist.
π Localization¶
LocalizationServicemanagesCultureInfo.CurrentUICultureand sendsShell.CultureChangedevents.- Language selector in TopBar; persisted per tenant.
βΏ Accessibility¶
- Every interactive item in left rail & top bar is reachable via Tab and arrow keys.
- Blades expose
role="dialog"/aria-modal="true"when modal; non-modal panes still keep focus order predictable. - Visible focus ring driven by tokens; high-contrast mode via
data-a11y.
π§ͺ Testing & QA Anchors¶
- Playwright: nav keyboard traversal, blade open/close, focus trap, deep-link reload.
- a11y: axe/Pa11y on key routes; fail on AA contrast/role issues.
- MFE smoke: host mounts a sample MFE from local manifest; verifies event roundtrip.
- Regression: snapshots for layout structure (left rail, top bar, blade host) with minimal fragility.
βοΈ Extension Points¶
- Command Palette entry (Ctrl/Cmd+K) β later cycle can plug providers (routes, MFEs).
- Search provider interface for resource lookup.
- Edition/Feature Flag provider (ECS/AppConfig) to filter nav items & blades dynamically.
π Security & Compliance¶
- CSP strict defaults; inline scripting disabled; nonces/hashes for shell assets.
- SRI enforced for MFE scripts; only allowlisted CDN origins.
- No direct external APIs from shell; all calls via BFF (configured next cycles).
- XSS hygiene: notifications/activity text escaped; manifest fields sanitized.
π¦ Distribution¶
- Ships as:
- Template:
dotnet new cs-blazor-appshellrunnable portal - NuGet helper: shared services (ThemeService, ShellBridgeHost, ManifestRegistry), ready to reuse across shells
- Template:
- Includes a SamplePortal wiring a sample MFE registry (local manifests) for immediate run.
π‘ If the sample portal canβt compose a demo MFE in 60 seconds from
dotnet new, the shell template is not done.
β Guarantees¶
- A portal-grade Blazor Web App shell with left rail, top bar, and blade canvas.
- Theme/density/localization state owned by the shell and propagated to MFEs.
- Secure, policy-enforced MFE hosting with SRI/CSP and a validated registry.
- Keyboard-accessible, token-driven layout β predictable and extensible from day one.
π Authentication & BFF Integration¶
π― Purpose¶
Establish OIDC cookie authentication for the shell, and a backend-for-frontend (BFF) that proxies all browser API calls through YARP (or the ConnectSoft Gateway). The browser never handles tokens; the server maintains a secure session cookie, validates tokens, and enforces tenant/edition/RBAC before calling downstream services.
π‘ Rule #1: MFEs and the shell never touch access tokens. The BFF does the heavy lifting.
π§ Architecture¶
flowchart LR
Browser[Browser - Shell + MFEs] -- cookie --> BFF[BFF - AppShell Server]
BFF -- mTLS/JWT --> Gateway[ConnectSoft Gateway / YARP Routes]
Gateway --> SvcA[Billing API]
Gateway --> SvcB[Catalog API]
IdP[(OIDC Provider)]
Browser <--> IdP
BFF <--> IdP
- OIDC: Authorization Code + PKCE β cookie session in shell (server).
- BFF: Same-origin
/bff/*routes; adds correlation, tenant, edition, user claims; strips cookies for downstream hops; caches tokens server-side. - Gateway/YARP: routes
/bff/billing/*βhttps://billing.api/...,/bff/catalog/*βhttps://catalog.api/....
βοΈ Shell OIDC Configuration¶
Key choices
- Cookie auth (HttpOnly, Secure, SameSite=Lax).
- Sliding expiration with short access token lifetime, long refresh (server-only).
- Stateful session (server cache) or distributed (Redis) for scale-out.
Program.cs (excerpt)
var builder = WebApplication.CreateBuilder(args);
builder.Services
.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddCookie(options =>
{
options.Cookie.Name = "__CS.Auth";
options.Cookie.HttpOnly = true;
options.Cookie.SecurePolicy = CookieSecurePolicy.Always;
options.Cookie.SameSite = SameSiteMode.Lax;
options.SlidingExpiration = true;
})
.AddOpenIdConnect(options =>
{
options.Authority = builder.Configuration["Auth:Authority"];
options.ClientId = builder.Configuration["Auth:ClientId"];
options.ClientSecret = builder.Configuration["Auth:ClientSecret"]; // if confidential
options.ResponseType = "code";
options.UsePkce = true;
options.SaveTokens = false; // tokens stay server-side only
options.GetClaimsFromUserInfoEndpoint = true;
options.Scope.Clear();
options.Scope.Add("openid");
options.Scope.Add("profile");
options.Scope.Add("email");
options.Scope.Add("cs.api"); // example API scope
options.TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = "name",
RoleClaimType = "role",
ValidateIssuer = true,
ValidateAudience = true
};
});
builder.Services.AddAuthorization(options =>
{
options.AddPolicy("TenantScoped", policy => policy.RequireClaim("tenant_id"));
options.AddPolicy("EditionPro", policy => policy.RequireAssertion(ctx =>
ctx.User.HasClaim("edition", "Pro") || ctx.User.HasClaim("edition", "Enterprise")));
});
Login/Logout endpoints
app.MapGet("/login", () => Results.Challenge(new()
{
RedirectUri = "/"
}, new[] { OpenIdConnectDefaults.AuthenticationScheme }));
app.MapPost("/logout", async (HttpContext ctx) =>
{
await ctx.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
await ctx.SignOutAsync(OpenIdConnectDefaults.AuthenticationScheme, new()
{
RedirectUri = "/signed-out"
});
});
π Silent renew is handled via OIDC refresh on the server; the browser only sees a cookie.
π BFF & YARP Proxy¶
Goals
- Single same-origin entrypoint
/bff/*. - Inject tenant/edition/trace headers; enforce RBAC/ABAC.
- Remove browser cookies for outbound calls; use server-held tokens or mTLS.
YARP configuration (appsettings.json)
{
"ReverseProxy": {
"Routes": {
"billing": {
"ClusterId": "billing",
"Match": { "Path": "/bff/billing/{**catch-all}" },
"Transforms": [
{ "PathRemovePrefix": "/bff" },
{ "RequestHeaderOriginalHost": "true" }
]
},
"catalog": {
"ClusterId": "catalog",
"Match": { "Path": "/bff/catalog/{**catch-all}" },
"Transforms": [
{ "PathRemovePrefix": "/bff" }
]
}
},
"Clusters": {
"billing": { "Destinations": { "d1": { "Address": "https://billing.api/" } } },
"catalog": { "Destinations": { "d1": { "Address": "https://catalog.api/" } } }
}
}
}
Runtime enrichment
app.Use(async (ctx, next) =>
{
if (ctx.Request.Path.StartsWithSegments("/bff"))
{
var user = ctx.User;
if (!user.Identity?.IsAuthenticated ?? true)
throw new UnauthorizedAccessException();
// Attach tenant/edition/trace headers
ctx.Request.Headers["X-CS-Tenant"] = user.FindFirst("tenant_id")?.Value ?? "default";
ctx.Request.Headers["X-CS-Edition"] = user.FindFirst("edition")?.Value ?? "Pro";
ctx.Request.Headers["x-correlation-id"] = Activity.Current?.TraceId.ToString() ?? Guid.NewGuid().ToString();
}
await next();
});
Token handling
- Downstream calls use server-side access tokens acquired via OIDC on behalf of user (or client credentials for service calls).
- Tokens stored in server cache (protected), not emitted to browser.
- For ConnectSoft Gateway, use mTLS or JWT exchange at the edge; map user claims to internal roles/scopes.
π‘ No
Authorizationheaders from the browser. The BFF crafts them per request.
π§© Tenant, Edition & RBAC¶
- Claims issued by IdP:
tenant_id,edition,role/permissions. - Policies:
- Router guards for shell pages/blades (e.g., Admin only).
- BFF guards for route families (e.g.,
/bff/billing/**requiresrole: billing.read).
- Edition gating:
- Shell hides nav items/blades if userβs
edition< required. - BFF rejects requests that attempt unauthorized editions or tenants.
- Shell hides nav items/blades if userβs
π‘οΈ Security Posture¶
- Cookies:
HttpOnly,Secure,SameSite=Lax, short lifetime, sliding expiration. - CSRF: anti-forgery tokens on unsafe methods (POST/PUT/PATCH/DELETE) for
/bff/**. - CSP: strict; MFEs loaded with SRI; only allowlisted CDNs; forbid
unsafe-inline. - PII Minimization: logs carry IDs/codes, not raw user content.
- Rate limiting: apply per-user limits on chatty BFF routes.
- Header Hygiene: strip inbound
Authorization/ sensitive headers before proxying.
π MFE Consumption of BFF¶
MFEs use relative routes, the shell injects base:
// In MFE bootstrap
builder.Services.AddScoped(sp =>
{
var http = new HttpClient { BaseAddress = new Uri("/", UriKind.Relative) };
http.DefaultRequestHeaders.Add("X-CS-Tenant", ShellContext.TenantId ?? "default");
http.DefaultRequestHeaders.Add("X-CS-Edition", ShellContext.Edition ?? "Pro");
return http;
});
All calls like GET /bff/billing/invoices remain same-origin β cookie session applies automatically.
π Session & Token Refresh¶
- Server refreshes tokens using refresh token or client credentials β rotates access tokens before expiry.
- If refresh fails (revoked/expired), the next
/bff/*call returns 401; shell redirects to/login. - Blacklist users/tenants mid-session via server-side session eviction (cache removal).
π§ͺ Testing & QA Anchors¶
- OIDC stub in CI for deterministic auth flows.
- Cookie semantics: assert HttpOnly/Secure/SameSite flags.
- CSRF tests: POST without token β 400; with valid token β 200.
- Proxy policy: unauthorized roles/editions/tenants β 403.
- Header tests: no
Authorizationfrom browser;X-CS-*injected server-side. - Trace correlation:
/bff/*addstraceparent; downstream logs correlate to shell request.
π¦ Configuration & Secrets¶
appsettings.json (per environment) holds Authority, ClientId, Scopes; secrets via KeyVault/KeyChain.
YARP routes and ConnectSoft Gateway endpoints configured by environment.
Enable distributed cache for sessions in multi-instance deployments.
β Guarantees¶
- The shell authenticates via OIDC and maintains a server cookie session.
- All browser-originated API calls go through a BFF/YARP layer (or ConnectSoft Gateway).
- Tokens are never exposed to the browser; headers are enriched server-side.
- Tenant/edition/RBAC are enforced centrally; CSRF/CSP/SRI hardening is built in.
π‘ Real-Time Events & SignalR¶
π― Purpose¶
Establish a reliable, secure, and scalable real-time layer for the portal:
- Notifications (toasts, alerts)
- Activity feed (auditable stream)
- Bidirectional Shell β MFE communication (command/event bus with live updates)
π‘ If the experience needs a refresh button, weβre not done. Real-time is a first-class UX channel.
π§ Architecture¶
flowchart LR
subgraph Browser
Shell[App Shell] <--> MFE1[Custom Element A]
Shell <--> MFE2[Custom Element B]
end
Shell <--> HubSignalR[(SignalR Hub)]
HubSignalR --> BFF[BFF Policies]
BFF --> SvcA[Domain Services]
BFF --> Bus[Event Bus/Outbox]
- SignalR Hub: single multiplexed connection per user/session.
- Shell owns the SignalR client and fans-in/out events to MFEs via the bus.
- BFF authorizes hub connections, enriches context (tenant, edition, roles), and optionally bridges backend events (via outbox/bus).
π Contracts (Shell Bus)¶
Inbound (Server β Shell)
NotificationPushedβ{ id, ts, severity, title, message, tags? }ActivityAppendedβ{ id, ts, category, title, details }FlagsUpdatedβ{ flags: Record<string, boolean> }ResourceChangedβ{ kind, id, changeType, data? }
Outbound (Shell β MFEs) via typed events
Shell.ThemeChanged,Shell.CultureChanged,Shell.TenantChanged,Shell.FeatureFlagsUpdated(already defined)Shell.Notification(selective forward)Shell.Activity(selective forward)
Inbound (MFEs β Shell)
Mfe.Notifyβ request to raise notificationMfe.LogActivityβ request to append activity record
π MFEs never connect to SignalR directly. The Shell is the broker.
π°οΈ SignalR Hub Design¶
Endpoints
/hubs/shell(authenticated, cookie session)- Groups by tenant, user, and capabilities:
tenant:{tenantId}user:{userId}cap:{notify},cap:{activityLog}(derived from effective policy)
Hub methods (server β client)
public interface IShellClient
{
Task NotificationPushed(NotificationDto dto);
Task ActivityAppended(ActivityDto dto);
Task FlagsUpdated(Dictionary<string, bool> flags);
Task ResourceChanged(ResourceChangedDto dto);
}
Hub invocations (client β server) β minimal; primarily admin/ops or ACKs when needed.
π Notifications¶
Schema
public sealed record NotificationDto(
string Id,
DateTimeOffset Timestamp,
string Severity, // info|success|warning|danger
string Title,
string Message,
string[]? Tags = null,
string? CorrelationId = null);
Flow
- Backend emits domain event β BFF translates to NotificationDto.
- Hub broadcasts to
user:{id}and optionallytenant:{tenantId}. - Shell receives and:
- Stores in NotificationCenter (persist for session)
- Optionally toasts via Adapter (
IUiToastAdapter) - Fan-out to MFEs that requested
notifycapability (policy-filtered)
π‘ Notifications are short-lived UX artifacts; Activity is the durable audit trail.
π Activity Feed (Durable)¶
Schema
public sealed record ActivityDto(
string Id,
DateTimeOffset Timestamp,
string Category, // e.g., "billing.invoice"
string Title,
object? Details = null,
string? Actor = null,
string? CorrelationId = null);
Flow
- Domain services write to outbox β ingested by BFF β ActivityAppended on hub.
- Shell persists a rolling window in memory (for UI) and loads history via BFF on demand.
- MFEs may request an append via
Mfe.LogActivity(capability & policy enforced).
π Shell Event Fan-Out¶
Inbound from Hub
- Normalize payload β update Shell state/services.
- Selective forwarding to MFEs:
- Forward only if the MFE declared interest (capability + subscription).
- Forward via host API on the CE instance or DOM CustomEvent.
// Shell side: forward a notification to interested MFEs
for (const ce of hostedMFEs.filter(x => x.capabilities.includes("notify"))) {
ce.dispatchEvent(new CustomEvent("Shell.Notification", { detail: dto, bubbles: true }));
}
π Security & Policy¶
- Auth: OIDC cookie; hub uses the same session as BFF.
- Authorization: hub joins groups based on claims (tenant/edition/roles).
- Capability gate: Shell forwards only events the MFE can handle (from manifest) and the policy allows.
- PII Minimization: redact user content in notifications; activity details limited to IDs/codes.
- Rate limiting: server throttles chatty channels; client debounces UI updates.
- Replay protection:
Id+Timestampused to ignore duplicates client-side.
π§± Reliability & Resilience¶
- Reconnect strategy: exponential backoff; show passive banner (βReconnectingβ¦β) in the shell.
- Offline fallback: queue user actions; on reconnect, flush idempotently.
- Backpressure: drop non-critical toasts when buffer exceeds threshold; never drop activity records (they reload from BFF).
- Idempotency: use
CorrelationIdfor de-duplication across hub + HTTP.
π Observability¶
- Emit
ActivitySourcespans:SignalR.Connect,SignalR.Receive.Notification,SignalR.Receive.Activity. - Metrics:
cs_shell_signalr_connections{tenant}cs_shell_notifications_total{severity}cs_shell_activity_total{category}
- Logs (structured): user, tenant, capability, result (forwarded/blocked), latency.
π§ͺ Testing & QA Anchors¶
- Hub auth: unauthenticated β 401; wrong tenant/edition β no group membership.
- Event round-trip: backend β hub β shell β MFE; verify policy and capability gates.
- Reconnect: simulate network loss; ensure queued notifications do not flood on resume.
- A11y: toasts are non-blocking and dismissible; activity list is fully keyboard navigable.
- Performance: stress test with N events/sec; verify UI remains responsive.
βοΈ Implementation Sketch¶
Server
builder.Services.AddSignalR().AddJsonProtocol();
app.MapHub<ShellHub>("/hubs/shell")
.RequireAuthorization(); // cookie auth
public class ShellHub : Hub<IShellClient> { /* group joins in OnConnectedAsync */ }
Client (Shell)
var conn = new HubConnectionBuilder()
.WithUrl("/hubs/shell", o => { o.AccessTokenProvider = null; }) // cookie auth
.WithAutomaticReconnect()
.Build();
conn.On<NotificationDto>("NotificationPushed", dto => _notifications.Add(dto));
conn.On<ActivityDto>("ActivityAppended", dto => _activity.Add(dto));
await conn.StartAsync();
β Guarantees¶
- Single, secured real-time channel per user, brokered by the shell.
- Notifications and activity arrive live, are policy-filtered, and auditable.
- MFEs communicate via a typed, capability-aware event bus β no direct hub access.
- The system is observable, resilient, and accessible by default.
ποΈ Feature Flags & Edition Awareness¶
π― Purpose¶
Provide a unified feature control plane for the portal:
- Tenant β Edition β Feature mapping,
- Runtime flags (on/off, % rollouts, kill switches),
- Synchronized evaluation across Shell and MFEs via a single provider abstraction.
π‘ Flags arenβt toggles; theyβre policy. Edition awareness ensures the UI never exposes what the tenant isnβt entitled to.
π§ Model & Hierarchy¶
Resolution order (highest precedence first):
- Tenant Overrides (support, hotfix, VIP tenants)
- Edition Policy (Free/Pro/Enterprise)
- Global Defaults (system-wide baseline)
flowchart TD
Global[Global Defaults] --> Edition[Edition Policy]
Edition --> Tenant[Tenant Overrides]
Tenant --> Result[Effective Flags]
- Flags can be boolean, percentage rollouts, or targeted (by role/group).
- Edition policy defines entitlements; flags can only narrow entitlements, not expand them beyond edition.
π§© Contracts¶
Provider Abstraction (shared by Shell & MFEs):
public interface IFeatureFlags
{
bool IsEnabled(string key);
double GetRollout(string key); // 0..1, 1.0 == 100%
event EventHandler<FlagsChangedEventArgs>? Changed;
IReadOnlyDictionary<string, bool> Snapshot(); // for diagnostics
}
public sealed record FlagsChangedEventArgs(IReadOnlyDictionary<string, bool> Diff);
Key Registry (typed constants):
public static class FeatureKeys
{
public static class Billing
{
public const string BetaBlade = "billing.betaBlade";
public const string NewGrid = "billing.newGrid";
}
public static class Portal
{
public const string CommandPalette = "portal.commandPalette";
}
}
Edition Map (declarative):
{
"editions": {
"Free": { "portal.commandPalette": false, "billing.betaBlade": false, "billing.newGrid": false },
"Pro": { "portal.commandPalette": true, "billing.betaBlade": false, "billing.newGrid": true },
"Enterprise": { "portal.commandPalette": true, "billing.betaBlade": true, "billing.newGrid": true }
}
}
βοΈ ECS/AppConfig Integration¶
Shell-side Provider (server):
- Loads global defaults and edition entitlements from ECS/AppConfig at startup.
- Applies tenant overrides (targeted rules, % rollouts) fetched per-tenant.
- Exposes a typed snapshot to the UI and pushes updates via SignalR (
Shell.FeatureFlagsUpdated).
MFE-side Provider (client/WASM):
- Receives initial flags via:
- Manifest defaults (optional), then
- Shell bootstrap snapshot at mount time.
- Subscribes to
Shell.FeatureFlagsUpdatedevent via the host bridge and updates internal cache.
public sealed class MfeFeatureFlags : IFeatureFlags
{
private readonly Dictionary<string,bool> _flags = new();
public bool IsEnabled(string key) => _flags.TryGetValue(key, out var v) && v;
public double GetRollout(string key) => IsEnabled(key) ? 1.0 : 0.0;
public event EventHandler<FlagsChangedEventArgs>? Changed;
public void ApplySnapshot(IReadOnlyDictionary<string,bool> snapshot)
{
var diff = snapshot.Where(kv => !_flags.ContainsKey(kv.Key) || _flags[kv.Key] != kv.Value)
.ToDictionary(kv => kv.Key, kv => kv.Value);
foreach (var (k,v) in diff) _flags[k] = v;
if (diff.Count > 0) Changed?.Invoke(this, new(diff));
}
}
π MFEs do not query ECS directly; the Shell is the source of truth for runtime policy.
π Edition Entitlements¶
Policy Rules
- Entitlement-first: Determine whether a feature is eligible based on edition.
- Flag-second: If eligible, flag can enable/disable or gradually roll out.
- Shell applies UI filtering (hide nav, blades, commands) when not entitled.
- BFF enforces the same policy on API routes (server authority).
UI Guard Example (Shell):
API Guard Example (BFF):
app.MapGet("/bff/billing/invoices", [Authorize] async (HttpContext ctx, IFeatureFlags flags) =>
{
if (!flags.IsEnabled(FeatureKeys.Billing.NewGrid))
return Results.Forbid();
// proceed
});
ποΈ Percentage Rollouts & Targeting¶
- Rollouts computed server-side with stable hashing:
hash(userId + key) < percentage. - Targeting rules: roles (
billing.admin), groups, tenants, regions. - MFEs see only the effective boolean; rollout math is opaque to the client.
bool EvaluatePercentage(string userId, string key, double pct)
{
var h = Murmur3.Hash(userId + key); // stable 0..1
return h < pct;
}
π Runtime Updates¶
- Shell listens for ECS/AppConfig changes β recomputes effective flags per context β
pushes
Shell.FeatureFlagsUpdatedwith diff only. - MFEs react to the
Changedevent and re-render affected regions.
// Host forwards updates into a custom element
ce.dispatchEvent(new CustomEvent("Shell.FeatureFlagsUpdated", { detail: flagsDiff }));
π§ͺ Testing & QA Anchors¶
- Matrix tests: (Edition Γ Tenant Override Γ Role) β Expected flags.
- UI hiding: Nav and blades should not render when not entitled; direct URL β 403 by BFF.
- Sticky behavior: Rollouts remain stable across sessions for the same user.
- Hot toggles: Flip a flag in ECS; verify SignalR update β Shell UI & MFE re-render.
- Localization: Flag-driven banners/messages translated (verify RESX keys present).
π Observability¶
- Audit: record who changed what flag, when, and scope (global/edition/tenant).
- Metrics:
cs_flags_updates_total,cs_flags_active{key,edition}. - Tracing: annotate spans with
feature.flagsfor critical user actions.
π§± Developer Experience¶
- Feature Directory in docs: each key has purpose, owner, rollout plan, deprecation policy.
- Playground in Sample Portal: switch edition (Free/Pro/Enterprise), simulate tenant override, see UI react.
- Fail-closed guidance: always code secure defaults (feature off unless explicitly enabled).
π‘ If a feature must be always-on for an edition, encode it in entitlements, not just a flag.
β Guarantees¶
- Single source of truth for flags via Shell; MFEs consume effective state only.
- Edition entitlements gate UI and APIs consistently (client + server).
- Percentage rollouts and targeting are stable and auditable.
- Real-time updates propagate safely, with minimal payloads and deterministic rendering.
π Observability & Diagnostics¶
π― Purpose¶
Deliver end-to-end visibility across Shell, MFEs, BFF, and backend services using OpenTelemetry for traces, logs, and metrics. Provide WASM-safe shims for the browser, standardized naming, and out-of-the-box Grafana/Azure Monitor dashboards so issues are fast to detect and easy to diagnose.
π‘ If you canβt see it, you canβt scale it. Observability is a product requirement, not an ops checkbox.
π§ Scope & Pillars¶
- Tracing: W3C trace-context from MFE β Shell/BFF β Services.
- Logging: structured, PII-minimized logs with correlation IDs.
- Metrics: RED/USE style signals for UX (client) and services (server).
- Diagnostics: dev overlay, sampling toggles, incident bundles.
π§© Naming Conventions¶
ActivitySource / Traces
- Shell:
CS.Shell(Shell.Route.Navigate,Shell.Blade.Open,SignalR.Receive.Notification) - MFE:
CS.MFE.<Domain>(Mfe.UI.Action,Mfe.Api.Call) - BFF:
CS.Bff(Bff.Proxy.Forward,Bff.Auth.Refresh) - Service:
CS.Svc.<Name>(downstream standard)
Meters / Metrics
- Prefix:
cs_shell_*,cs_mfe_*,cs_bff_*,cs_svc_* - Examples:
cs_shell_blade_open_total{blade}cs_mfe_api_errors_total{route}cs_bff_proxy_latency_ms{cluster,route}cs_shell_signalr_connections{tenant}
Logs
- Logger categories map to components (e.g.,
ConnectSoft.Shell.Navigation,ConnectSoft.Mfe.Billing.Api). - Required fields:
correlationId,tenantId,edition,userId(hashed/anonymized),capability.
π Consistency wins: names are standardized, reviewed, and linted.
π§± Server Instrumentation (Shell & BFF)¶
Traces & Metrics
builder.Services.AddOpenTelemetry()
.WithTracing(t => t
.AddAspNetCoreInstrumentation()
.AddHttpClientInstrumentation()
.AddGrpcClientInstrumentation()
.AddSource("CS.Shell", "CS.Bff")
.SetResourceBuilder(ResourceBuilder.CreateDefault().AddService("cs-shell"))
.AddOtlpExporter())
.WithMetrics(m => m
.AddAspNetCoreInstrumentation()
.AddHttpClientInstrumentation()
.AddMeter("cs_shell_meter", "cs_bff_meter")
.AddRuntimeInstrumentation()
.AddProcessInstrumentation()
.AddOtlpExporter());
Logs (structured)
builder.Logging.ClearProviders();
builder.Logging.AddJsonConsole(options => options.IncludeScopes = true);
Correlation Middleware
app.Use(async (ctx, next) =>
{
var traceId = Activity.Current?.TraceId.ToString() ?? Guid.NewGuid().ToString("N");
ctx.Items["CorrelationId"] = traceId;
ctx.Response.Headers["x-correlation-id"] = traceId;
using var activity = Observability.StartServerActivity("Http.Incoming", ("route", ctx.Request.Path));
await next();
});
π Client/WASM Instrumentation (MFE)¶
WASM-Safe Tracing
public static class MfeTelemetry
{
public static readonly ActivitySource Source = new("CS.MFE.Billing");
public static IDisposable Start(string name, params (string, object)[] tags)
{
var a = Source.StartActivity(name, ActivityKind.Internal);
if (a is not null) foreach (var (k,v) in tags) a.SetTag(k, v);
return a ?? new Noop();
}
private sealed class Noop : IDisposable { public void Dispose() { } }
}
Usage
using var _ = MfeTelemetry.Start("Mfe.Api.Call", ("route","/bff/billing/invoices"));
var data = await _api.ListAsync(ct);
_logger.LogInformation("Loaded invoices {@Count}", data.Length);
Client Metrics (in-memory)
- Counters:
cs_mfe_clicks_total{control},cs_mfe_api_errors_total{route} - Histograms (optional in browser):
cs_mfe_api_latency_ms{route} - Export strategy: push to BFF via lightweight
/bff/telemetrybatch (configurable, disabled by default).
β οΈ No heavy exporters in the browser. Keep memory footprint small; export via server when needed.
π Diagnostics Overlay (Dev Only)¶
- Toggle with
?diag=1or palette command (βDiagnostics: Toggleβ). - Panels:
- Trace: current correlation ID, recent spans.
- Flags: effective feature flags.
- Theme: tokens resolved, contrast warnings.
- Network: recent
/bff/*calls with status & latency.
- No PII; hidden in production builds.
π Grafana / Azure Monitor Templates¶
Dashboards (bundled JSON/ARM):
- Portal Health: shell route latency, SignalR connections, error rate, top blades.
- BFF Proxy: upstream latency per cluster/route, 4xx/5xx rates, retry/circuit stats.
- MFE UX: API error totals by route, action frequencies, slowest UI actions.
- Flags & Editions: active flag counts by edition, recent flips, rollout coverage.
Alerts
- High
cs_bff_proxy_latency_ms(p95 > SLO) - Spike in
cs_shell_signalr_disconnects_total - Increase in
cs_mfe_api_errors_totalfor a route - Error log rate per tenant crossing threshold
π Privacy & Security¶
- PII minimization: hash
userId, never log raw content. - Secrets: exporters use managed identity or key vault; no tokens client-side.
- Sampling: head-based on server; tail-based allowed for rare errors; disable client sampling to keep code simple.
- Redaction: explicit allowlist for log properties; drop unknown dynamic objects.
π Incident Bundle¶
- On error spikes, server compiles an incident bundle:
- time window spans, top logs, request samples (headers scrubbed), metric deltas.
- Linked in the notification to on-call; downloadable from the ops portal.
π§ͺ Testing & QA Anchors¶
- Verify
traceparentflows from browser β BFF β service (span linkage). - Ensure logs always include
correlationId,tenantId,edition. - Synthetic load: open/close blades, fire notifications, API bursts β dashboards update.
- Toggle signal loss: SignalR reconnects, spans continue, no leaks.
- Browser perf: telemetry disabled/enabled has < X ms overhead per action (budgeted).
π¦ Configuration¶
- AppSettings: exporters (OTLP endpoint), sampling rates, PII policy.
- Env Switches:
OBS_ENABLE_CLIENT_EXPORT=false(default),OBS_SAMPLING_RATE=0.1(10%). - Per-Tenant Overrides: raise/lower sampling during incidents.
β Guarantees¶
- Unified trace/log/metric story across Shell, MFEs, BFF, and services.
- Browser instrumentation is WASM-safe, lightweight, and privacy-aware.
- Teams get ready-to-use dashboards and alerts β not a blank observability canvas.
- Correlation is first-class: every action is traceable end to end.
π‘οΈ Security & Compliance¶
π― Purpose¶
Embed security-first defaults and compliance-ready artifacts into all ConnectSoft Blazor templates. Every component library, microfrontend, and application shell must ship with defensive policies, signed packages, and verifiable manifests β making them safe for enterprise SaaS and regulatory audits.
π‘ Security is not optional glue; it is generated into every repo, enforced in every build, and validated at runtime.
π Threat & Compliance Context¶
Common pitfalls in frontend SaaS systems:
- β οΈ Inline scripts/styles weaken Content Security Policy (CSP).
- β οΈ CDN assets without Subresource Integrity (SRI) risk tampering.
- β οΈ Overly broad CORS exposes APIs to hostile origins.
- β οΈ Unsigned packages erode supply chain trust.
- β οΈ Missing SBOMs make audits and CVE scans impossible.
Outcome: portals that fail enterprise reviews and invite security breaches.
π Controls¶
1. Content Security Policy (CSP)¶
- Default
csp.config.jsonper app shell, generated with:default-src 'self'script-src 'self' https://cdn.connectsoft.net 'sha256-β¦'style-src 'self' 'sha256-β¦'object-src 'none'frame-ancestors 'none'
- Inline scripts forbidden; adapters use
nonceor pre-hashed bundles.
2. Subresource Integrity (SRI)¶
- CI computes hashes for every script/style/wasm asset.
- SRI hashes injected into:
microfrontend.jsonmanifest.- Shell loader scripts.
- Build fails if assets lack integrity metadata.
3. Cross-Origin Resource Sharing (CORS)¶
- Registry-driven allowlist: only shell origins per tenant/edition.
- No
*wildcards. - Declarative
cors.rules.jsonsynced with ECS/AppConfig. - Enforced by BFF/YARP, not browser defaults.
4. Secure Asset Loading¶
- All MFEs must load over HTTPS only.
- Shell loader rejects manifests with
http://URLs. - Assets cached with immutable versioned paths (
/v1.2.3/app.wasm). - No dynamic
evalorFunction()allowed in adapters.
5. NuGet Signing¶
- All
.nupkgproduced with:- Strong-name signing.
- Repository signatures via Azure DevOps key vault certs.
- Pipelines verify signatures before publishing.
- Unsigned packages rejected at catalog ingestion.
6. SBOM (Software Bill of Materials)¶
- Every build emits CycloneDX + SPDX SBOM:
- Dependency graph
- Licenses
- Vulnerability scan results
- Artifacts stored in
artifacts/sbom/and published with releases. - Integrates with Dependabot + GitHub Advanced Security or Azure Security Center.
π§± Alignment with the Factory¶
flowchart TD
Repo["π¦ Repo (Template Code)"]
CI["βοΈ CI/CD Pipeline"]
Policies["π‘οΈ Security Policies"]
Artifacts["π Compliance Artifacts"]
Repo --> CI
CI --> Policies
CI --> Artifacts
Policies --> Shell["ποΈ App Shell Runtime Enforcement"]
Artifacts --> Audit["π Audit / Security Review"]
π¦ Outputs¶
| Artifact | Purpose |
|---|---|
csp.config.json |
Baseline CSP headers for shell apps |
sri-manifest.json |
Integrity hashes for CDN assets |
cors.rules.json |
Tenant/edition-aware allowlist |
Signed .nupkg |
Verified supply chain packages |
sbom.json + sbom.xml |
Full dependency & license manifest for audits |
π§ͺ Testing & QA Anchors¶
- CSP smoke tests: Playwright verifies blocked inline
<script>. - SRI validation: remove hash β loader rejects.
- CORS enforcement: origin mismatch β 403.
- NuGet check: unsigned package β CI fails.
- SBOM validation: schema + CVE scan run on every release.
β Guarantees¶
- No inline code; CSP strictly enforced.
- All assets integrity-checked before execution.
- CORS is explicit; no wildcard origins.
- NuGet artifacts signed and verifiable.
- Every release emits SBOMs for compliance & security audits.
π Compliance is continuous. Every repo, build, and runtime inherits these guarantees automatically.
π Localization & Multi-Language Strategy¶
π― Purpose¶
Enable multi-language, multi-directional (LTR/RTL) support across Component Library, MFEs, and Shell with consistent localization contracts. This ensures that every ConnectSoft SaaS portal can reach global audiences with accessible, culturally aware UX.
π‘ Localization is not translation β itβs cultural adaptation, built in as a first-class capability.
π Problem Context¶
Current risks without a strategy:
- β οΈ Hard-coded strings β non-translatable UX.
- β οΈ No RTL awareness β broken layouts for Hebrew/Arabic.
- β οΈ Locale drift β inconsistent date/number formats across MFEs.
- β οΈ Missing resource synchronization β MFEs and Shell use different terms for the same concept.
Consequence: fractured, inaccessible experiences for non-English tenants.
π― Goals¶
- Resource-driven strings across all templates (RESX +
IStringLocalizer). - RTL-aware tokens for spacing, alignment, and flow.
- Culture propagation: Shell sets
lang+dirattributes, MFEs auto-sync. - Pluralization & formatting with
System.Globalizationand ICU patterns. - Tenant-specific defaults loaded from ECS/AppConfig.
π§± Contracts¶
Shared Resource Pattern¶
Resources.resx
<data name="WelcomeMessage" xml:space="preserve">
<value>Welcome to the Portal</value>
</data>
<data name="WelcomeMessage" xml:lang="ru">
<value>ΠΠΎΠ±ΡΠΎ ΠΏΠΎΠΆΠ°Π»ΠΎΠ²Π°ΡΡ Π² ΠΏΠΎΡΡΠ°Π»</value>
</data>
<data name="WelcomeMessage" xml:lang="he">
<value>ΧΧ¨ΧΧΧΧ ΧΧΧΧΧ ΧΧ€ΧΧ¨ΧΧ</value>
</data>
Culture Propagation¶
-
Shell applies:
<html lang="he" dir="rtl">- Attributes forwarded to MFEs via Custom Element host.
-
MFEs listen for
langanddirattribute changes β rerender content accordingly.
// in Custom Element host
const observer = new MutationObserver(muts => {
for (const m of muts) {
if (m.attributeName === "lang" || m.attributeName === "dir") {
this.updateCulture(document.documentElement.lang, document.documentElement.dir);
}
}
});
observer.observe(document.documentElement, { attributes: true });
π RTL-Aware Tokens¶
- Use logical properties:
margin-inline-startinstead ofmargin-left. - Tokens define spacing by flow (
--cs-space-inline,--cs-space-block). - Tailwind preset maps tokens β logical CSS.
π Formatting & Pluralization¶
- Dates/numbers: use
CultureInfo.CurrentCulture. - Plural forms handled via helpers:
public static string Plural(this IStringLocalizer T, string key, int count) =>
T[$"{key}_{(count == 1 ? "One" : "Other")}", count];
- Example keys:
UserCount_One,UserCount_Other.
π¦ ECS/AppConfig Integration¶
- Tenant default language set in ECS:
"tenant:1234": { "defaultCulture": "he-IL" }
- Shell applies defaults on login/session init.
- Users can override preference in Settings (stored per profile).
π§ͺ Testing & QA Anchors¶
- Snapshot tests: key pages in EN, RU, HE.
- Visual regression: check for overflow with long translations.
- RTL regression: ensure nav/blades invert properly.
- Fallbacks: missing keys β fall back to EN with telemetry warning.
- A11y: screen readers correctly announce localized strings.
π Observability¶
- Telemetry attaches
cultureandui.dirto user actions. - Metrics:
cs_shell_users_by_culturecs_mfe_localization_missing_keys_total
β Guarantees¶
- All templates ship with multilingual, RTL-ready scaffolds.
- Shell enforces culture & direction propagation; MFEs auto-sync.
- Formatting and pluralization are consistent across domains.
- Tenants can set defaults in ECS, and users can override.
- No component is accepted into the library without localization hooks.
π§ͺ Testability & QA Anchors¶
π― Purpose¶
Establish a factory-wide test strategy and enforceable QA gates for the Component Library, MFEs, and Shell. The goal is to make regressions hard to introduce and easy to detect, with practical coverage and resilience scenarios baked into CI.
π‘ Quality isnβt a phase β itβs a property of the templates we generate.
π§ Test Strategy Overview¶
| Layer | Primary Focus | Tools |
|---|---|---|
| Component Library (RCL) | Rendering, state transitions, a11y, i18n | MSTest + bUnit, axe |
| Microfrontends (WASM CE) | CE mount/unmount, manifest contract, shell-bus | MSTest + bUnit, contract tests, DOM events |
| Application Shell | Navigation, blades, MFE composition | Playwright (E2E), a11y checks |
| Cross-Cutting | Faults, timeouts, network drops | Resilience tests, chaos hooks |
| Pipelines | Coverage, flake control, mutation sanity | Coverage gates, retry policy, mutation sample |
π We test behavior contracts first, visuals second. Snapshots are minimal and purposeful.
π§± Unit & Rendering Tests (RCL & MFEs)¶
bUnit + MSTest patterns
[TestClass]
public class ModalTests : BunitTestContext
{
[TestMethod]
public void Opens_and_traps_focus()
{
var cut = RenderComponent<Modal>(p => p.Add(mt => mt.IsOpen, true));
cut.Find("[role='dialog']").Should().NotBeNull();
// assert focus trap
}
[TestMethod]
public void Honors_localization_and_rtl()
{
Services.AddLocalization();
SetCulture("he-IL"); // helper
var cut = RenderComponent<Button>(p => p.AddChildContent("Χ©ΧΧΧ¨"));
cut.Markup.Contains("dir=\"rtl\"");
}
}
Guidelines
- Prefer semantic assertions (roles, labels, classes from tokens) over brittle HTML snapshots.
- Cover controlled/uncontrolled modes, cancellable events, async flows.
- bUnit tests for MFEs focus on bootstrap and CE lifecycle (mount/detach + re-render on theme/culture).
π§ͺ Contract Tests (MFEs β Shell)¶
Why: Ensure manifests and bus events remain compatible over time.
Contracts
- Manifest schema validation (JSON Schema).
- Capability gates enforced (deny unauthorized commands).
- Event/command names and payload shapes are stable.
[TestMethod]
public async Task Bus_Rejects_OpenBlade_When_Capability_Missing()
{
var shell = new FakeShellHost(capabilities: new[] { "notify" });
var mfe = new TestCustomElement(shell);
var result = await mfe.TryOpenBladeAsync("billing.invoice");
result.Should().BeFalse();
}
π End-to-End (Shell)¶
Playwright suite
- Keyboard navigation: left rail, top bar, blade open/close.
- MFE composition: manifest load (SRI ok), CE instantiation, event roundtrip.
- Deep links: refresh rebuilds blade stack.
- A11y sweep: run axe on key routes; fail on AA violations.
Resilience scenarios
- Drop SignalR β banner shows, reconnect succeeds, no duplicated events.
- BFF returns 5xx β toast + retry/backoff UI, user unharmed.
- CDN asset missing SRI β shell blocks load, logs security event.
π§― Failure & Chaos Tests¶
- Network faults: inject latency, timeouts, and aborts for
/bff/*. - Partial outages: one cluster (e.g., billing) degraded; shell remains responsive.
- Client reloads: stress open/close blades, theme switches, culture flips.
- Memory leaks: mount/unmount MFEs in a loop; assert GC/handle stability via browser heap snapshots (CI optional).
β οΈ Chaos hooks are guarded by env flag; never run in prod pipelines.
π Coverage & Gates¶
Targets (minimums)
- RCL unit/render: 85% lines, 80% branches for public components.
- MFE unit/contract: 80% lines.
- Shell E2E critical flows: scenario coverage defined (not line-based):
- nav keyboard traversal,
- first MFE load,
- blade deep-link restore.
CI Gates
- Coverage thresholds enforced; failures block release stages.
- API Surface Check: public API diff must be reviewed on changes.
- A11y Gate: axe violations fail unless explicitly waived (tracked issue).
- Security Gate: manifests missing SRI or CSP misconfig β fail.
π§ͺ Flakiness & Retry Policy¶
- Single retry for networked E2E tests (idempotent only).
- Quarantine tag moves flaky tests off the blocking path; auto-ticket created.
- Max 7 days in quarantine before escalation.
# Example Playwright CI step
- script: npx playwright test --reporter=line --config=e2e.config.ts || npx playwright test --last-failed
π§° Test Data & Fixtures¶
- BFF stubs for billing/catalog with deterministic payloads.
- SignalR test hub in-process for contract tests.
- Tokenless auth in CI (OIDC stub) for login flows.
- Localization fixtures: EN/RU/HE strings; RTL toggles.
π§· Visual Regression (Optional, Targeted)¶
- Use per-component image snapshots (Button variants, Modal states) behind an opt-in flag.
- Only for high-value visuals with stable output (tokens minimize churn).
- Store baselines in artifact storage; diff threshold small but non-zero.
π Mutations & Static Analysis¶
- Mutation sample: run on one representative component suite to detect assertion weakness.
- Roslyn analyzers: ban inline styles, enforce tokens, forbid browser token usage, JS interop sync calls.
- Lint: Tailwind class validator, i18n missing keys detector.
π§© Pipeline Structure¶
- Unit/Render (fast) β bUnit, MSTest.
- Contracts β manifest/bus, SRI/CSP validators.
- E2E β Playwright headless (Desktop Chromium + WebKit matrix).
- A11y β axe run on major routes.
- Coverage merge β thresholds.
- Security β SBOM, vulnerability scan, CSP/SRI check.
- Publish (if all green) β NuGet/CDN artifacts.
π Reporting & Triage¶
- PR comments with coverage deltas, axe results, and E2E GIFs (optional).
- Flake dashboard: failure heatmap by test and day.
- Defect taxonomy: classify regressions (a11y, i18n, performance, security, behavior).
β Guarantees¶
- Each template is testable by design with ready-to-run suites.
- CI enforces coverage, a11y, security, and contract gates.
- Failures are actionable with deterministic fixtures and clear triage.
- Resilience is proven, not assumed β common failure modes are exercised regularly.
π Documentation & Developer Experience¶
π― Purpose¶
Empower developers to adopt, extend, and maintain the ConnectSoft Blazor Templates with self-explanatory documentation, onboarding guides, and interactive showcases. Every template (Component Library, Microfrontend, Application Shell) must teach itself to new engineers, ensuring consistent factory adoption and rapid productivity.
π‘ DX is a feature. Documentation is not a side artifact, but a generated and curated part of the template itself.
π Problem Context¶
Without structured documentation:
- β οΈ Developers struggle to understand template conventions.
- β οΈ Onboarding slows due to tribal knowledge.
- β οΈ UI components lack discoverability or examples.
- β οΈ ADRs (Architecture Decision Records) are scattered or missing.
- β οΈ No centralized gallery for interactive component exploration.
Consequence: slower delivery, inconsistent implementation, and higher support burden.
π― Goals¶
-
MkDocs Documentation Each repo ships with a
docs/folder + MkDocs pipeline for publishing internal sites. -
ADRs (Architecture Decision Records) Use Log4Brains or similar to capture architectural trade-offs in
docs/adr/. -
Storybook-like Gallery Component libraries expose a browsable UI sandbox for visual exploration and testing.
-
Quickstarts & Tutorials Ready-to-run samples (
/samples) to demonstrate setup, extension, and integration. -
Onboarding Guides Human + agent-friendly walkthroughs for each template, including architecture diagrams and commands.
π Documentation Layers¶
| Layer | Artifact | Purpose |
|---|---|---|
| Core Docs | README.md, docs/index.md |
Overview, getting started |
| Blueprint Docs | blueprint-trace.md, feature-map.md |
Traceability to factory design |
| ADRs | /docs/adr/* |
Decisions (UI kit adapter, API contracts, etc.) |
| Gallery | /storybook or /gallery |
Interactive component showcase |
| Samples | /samples/basic-app, /samples/mfe-shell |
Quickstart entry points |
| API Docs | XML docs β DocFX/MkDocs integration | Reference for contracts, adapters |
π§± Developer Experience Anchors¶
-
Onboarding Flow
docs/onboarding.mdcovers setup, build, run, and extension.- Diagrams (
mermaid,plantuml) for repo structure. - Expected time-to-hello-world: <30 minutes.
-
Component Gallery (Storybook-like)
- Uses bUnit + Playwright snapshots for Blazor.
- Optional MudBlazor/Flowbite live preview in sandbox mode.
-
CLI Quickstart
dotnet new csblazor-component -n MyButtonLib- Output includes docs + gallery starter.
-
DX Automation
- PR bot ensures new components include:
- XML docs
- Gallery story
- ADR (if architectural change)
- Failing builds if missing.
- PR bot ensures new components include:
π¦ Repository Outputs¶
Each repo includes:
/docs/
index.md
onboarding.md
architecture.md
adr/
0001-ui-kit-adapter.md
0002-feature-flags.md
gallery/
Button.razor.story.md
quickstarts/
shell-sample.md
mfe-sample.md
README.md
π§ͺ QA for Docs¶
- CI pipeline lints markdown and ADRs.
- Playwright validates gallery loads and renders.
- ADR completeness check: no open issue without ADR reference.
- Coverage check: every public component has at least one story + doc.
β Guarantees¶
- Every template repo ships with documentation baked in.
- ADRs are first-class citizens; decisions are traceable over time.
- Interactive gallery ensures discoverability of components and layouts.
- Quickstarts + onboarding reduce ramp-up friction.
- DX is enforced by automation, not optional human effort.
π Release & Distribution Pipelines¶
π― Purpose¶
Define factory-standard release pipelines for the Component Library, MFEs, and Application Shell. Pipelines must build, sign, validate, and distribute artifacts automatically, producing NuGet packages, CDN bundles, and compliance outputs ready for enterprise consumption.
π‘ A template isnβt real until it ships. Pipelines make delivery repeatable, secure, and traceable.
π Problem Context¶
Without controlled release flows:
- β οΈ Packages may publish unsigned or untested.
- β οΈ CDN assets risk tampering or drift.
- β οΈ SBOMs and compliance artifacts are missing.
- β οΈ Multi-tenant SaaS teams must reinvent CI/CD logic for each repo.
Consequence: fragile, inconsistent releases that fail enterprise checks.
π― Goals¶
- Unified Pipeline Templates (Azure DevOps YAML, GitHub Actions fallback).
- End-to-end automation: build β test β sign β validate β publish.
- Multi-channel distribution: NuGet feeds, CDN, internal artifact store.
- Compliance built-in: SBOM, SRI, NuGet signing, coverage reports.
- Version governance: SemVer enforced, changelog generated.
π Pipeline Stages¶
-
Build
- Compile RCL, MFEs, and Shell.
- Generate tokens, manifests, and docs.
-
Test
- Run unit, rendering, contract, and E2E tests.
- Enforce coverage thresholds.
-
Security & Compliance
- CSP/SRI validation.
- SBOM generation + vulnerability scan.
- NuGet signing validation.
-
Package & Sign
.nupkgwith strong-name + repo signatures.- CDN bundles hashed and SRI-manifested.
-
Publish
- Push NuGet β internal feed + NuGet.org (stable only).
- Upload CDN bundles β Azure Storage + global CDN.
- Publish docs β Azure Static Web Apps / GitHub Pages.
-
Tag & Release
- Git tag + annotated changelog.
- Release notes built from ADRs + PR metadata.
π¦ Distribution Channels¶
NuGet
- Component Library β
ConnectSoft.Blazor.ComponentLibrary - Microfrontend Helpers β
ConnectSoft.Blazor.MFE - Shell Base β
ConnectSoft.Blazor.AppShell
CDN
- MFEs: versioned bundles
/cs.billing/1.2.3/entry.js - SRI manifest published alongside
Docs
- Hosted via Azure Static Web Apps, behind auth for internal repos
Artifacts
- Stored in
artifacts/per build:/packages/*.nupkg/cdn/*(entry.js, wasm, css, manifest.json)/docs/*(site.zip)/compliance/sbom.json
π Governance & Versioning¶
- SemVer enforced by pipeline:
- Breaking change β major bump.
- Features β minor bump.
- Fixes β patch bump.
- Changelog generated from commits + ADR refs.
- Release Approval gates:
- Pre-release β internal feed only.
- Stable β NuGet.org + CDN.
π§ͺ QA Anchors¶
- Pipeline fails on:
- Coverage < threshold.
- Missing SRI for assets.
- Unsigned NuGet packages.
- SBOM schema invalid.
- Dry-run mode for developers: local pipeline simulation.
- Canary release path: deploy to
nexttag for validation in staging portal.
π Observability of Pipelines¶
- Emit pipeline metrics:
cs_ci_build_duration_secondscs_ci_test_failures_totalcs_release_artifacts_published_total
- Dashboards: release frequency, lead time, failure rate, MTTR (align with DORA metrics).
β Guarantees¶
- Every template has a ready-to-clone release pipeline.
- Releases are signed, versioned, and integrity-verified.
- Distribution spans NuGet, CDN, Docs, and Compliance artifacts.
- Governance rules (SemVer, ADRs, coverage, SBOM) are automated in CI/CD.
- Devs spend time on features, not on reinventing pipelines.
π Summary & Conclusion¶
The ConnectSoft Blazor Templates provide a factory-standard foundation for building SaaS-ready portals with:
- Reusable Component Libraries (RCL) β token-driven, UI-kit agnostic, accessible by default.
- Microfrontend Libraries β WASM packaged as Custom Elements, manifest-driven, secure, observable.
- Application Shell β Portal-grade Blazor Web App with navigation, blades, theming, edition awareness, and tenant policies.
Across the document, we defined architecture, contracts, security, observability, testability, and developer experience. Each template is not just scaffolding β it is compliance-hardened, observable, localizable, and ready for enterprise distribution.
π‘ From vision to release, the templates guarantee consistency, traceability, and velocity across all frontend efforts in the ConnectSoft AI Factory.
π References¶
π Internal Factory Blueprints¶
- Frontend Developer Agent
- UI Component Library Generator Agent
- Frontend Blueprint & Libraries (supporting files in this repo)
ποΈ Architecture & Microfrontends¶
- BoardGameStore Blazor Microfrontends Demo (GitHub)
- Microfrontends with Blazor Demos (Florian Rappl)
- Kalle Marjokorpi Blog β Microfrontend + BFF