eQuantic.UIeQuantic.UI
Docs
Playground
GitHub
START HERE
Getting Started
WRITE-ONCE
Write-Once Components
Declarative Surface
Photon Engine
Design System
Capabilities
Storage
Forms
Code Editor
Markdown
Mermaid
Email Rendering
ARCHITECTURE
Architecture Overview
Package Architecture
Components
Styling
Localization
Analytics & GTM
COMPILATION
Compiler
Compile-Time Evaluation
Supported C# Features
External Type Resolution
Build Flow
Diagnostics
RUNTIME
Runtime (TypeScript)
Performance
SERVER
Server Integration
Assets
BunPackage
Security
ECOSYSTEM
Image
Charts
Icons
Lottie
DEVELOPMENT
Visual Editor
Debug
Roadmap
PT-BR
Analytics-pt-BR
Architecture-pt-BR
Assets-pt-BR
BuildFlow-pt-BR
BunPackage-pt-BR
Capabilities-pt-BR
Charts-pt-BR
CodeEditor-pt-BR
Compiler-pt-BR
CompileTimeEvaluation-pt-BR
Components-pt-BR
Debug-pt-BR
DeclarativeSurface-pt-BR
DesignSystem-pt-BR
Diagnostics-pt-BR
EmailRealizer-pt-BR
ExternalTypeResolution-pt-BR
Forms-pt-BR
GettingStarted-pt-BR
Home-pt-BR
Icons-pt-BR
Image-pt-BR
Localization-pt-BR
Lottie-pt-BR
Markdown-pt-BR
Mermaid-pt-BR
PackageArchitecture-pt-BR
Performance-pt-BR
Photon-pt-BR
Roadmap-pt-BR
Runtime-pt-BR
Security-pt-BR
ServerIntegration-pt-BR
Storage-pt-BR
Styling-pt-BR
SupportedFeatures-pt-BR
VisualEditor-pt-BR
WriteOnceComponents-pt-BR
ACTIONS & INPUTS
Button
IconButton
TextInput
Select
Checkbox
Switch
RadioGroup
SegmentedControl
Slider
Stepper
SearchField
SURFACES & DISPLAY
Card
Badge
Chip
Avatar
Banner
ProgressBar
EmptyState
Divider
NAVIGATION
Tabs
AppBar
BottomNavigation
Breadcrumb
Pagination
PageIndicator
Menu
Drawer
OVERLAYS
Dialog
BottomSheet
Toast
Popover
Tooltip
LISTS & DATA
List
ListView
Table
Accordion
CodeBlock
TOUCH INTERACTION
PullToRefresh
SwipeableRow
DocsServer
Asset Management System
Edit this page
3 min read
🌐 This page in: English · Português
eQuantic.UI provides a declarative asset dependency system that allows components to declare their required scripts and stylesheets. Assets are automatically collected during SSR, deduplicated, and injected into the page's <head>.
Overview
The system solves a common problem: components that depend on external libraries (Prism.js, Chart.js, etc.) need their scripts/CSS loaded, but shouldn't embed <script> tags inline in their rendered HTML. Instead, they declare dependencies, and the framework handles injection.
1
Component Tree Walk → Collect IRequireAssets → Collect IComponentAssetProvider<T> → Deduplicate → Inject into <head>
Core Types
All types are in eQuantic.UI.Core.Assets.
IAsset
Base interface for all asset types.
1
2
3
4
5
6
public interface IAsset
{
string Key { get; } // Unique key for deduplication
string? Id { get; } // Optional HTML id for client-side manipulation
string Render(); // Renders as HTML tag
}
Asset Implementations
Type
Output
Key Format
ScriptAsset
<script src="..." defer></script>
script:{Src}
InlineScriptAsset
<script>...</script>
inline-script:{hash}
StylesheetAsset
<link rel="stylesheet" href="...">
stylesheet:{Href}
InlineStyleAsset
<style>...</style>
inline-style:{hash}
All types support an optional Id parameter for client-side DOM manipulation:
1
2
new StylesheetAsset("https://cdn.example.com/theme.css", Id: "theme-css")
// Renders: <link rel="stylesheet" href="https://cdn.example.com/theme.css" id="theme-css">
AssetBuilder
Fluent builder passed to components for declaring dependencies:
1
2
3
4
5
6
7
public class AssetBuilder
{
AssetBuilder AddScript(string src, bool defer = true, string? id = null);
AssetBuilder AddInlineScript(string content, string? id = null);
AssetBuilder AddStylesheet(string href, string? id = null);
AssetBuilder AddInlineStyle(string content, string? id = null);
}
AssetCollection
Collects and deduplicates assets. Rendering order is optimized: CSS first, then JS (proper render-blocking order).
1
StylesheetAsset → InlineStyleAsset → ScriptAsset → InlineScriptAsset
Deduplication uses TryAdd with the asset's Key, so the first registration wins.
Two Patterns
Pattern 1: IRequireAssets (Self-Declaring)
For components that own their dependencies. The component itself declares what it needs.
1
2
3
4
5
6
7
8
9
10
public class CodeBlock : StatelessComponent, IRequireAssets
{
public void ConfigureAssets(AssetBuilder assets)
{
assets.AddStylesheet(
"https://cdn.jsdelivr.net/npm/prismjs@1.29.0/themes/prism-tomorrow.min.css",
id: "prism-theme");
assets.AddScript("https://cdn.jsdelivr.net/npm/prismjs@1.29.0/prism.min.js");
}
}
When to use:
Framework components (CodeBlock, charts, etc.)
Components where dependencies are intrinsic
The developer using the component doesn't need to know about the underlying libraries
Pattern 2: IComponentAssetProvider\<T\> (External Provider)
For associating assets with components externally, typically third-party components that don't implement IRequireAssets.
1
2
3
4
5
6
7
public class ChartJsAssetProvider : IComponentAssetProvider<ChartCanvas>
{
public void ConfigureAssets(AssetBuilder assets)
{
assets.AddScript("https://cdn.jsdelivr.net/npm/chart.js@4.4.1/dist/chart.umd.min.js");
}
}
Registration (choose one):
1
2
3
4
5
6
7
8
// Option 1: Auto-scan (discovered automatically in scanned assemblies)
options.ScanAssembly(typeof(Program).Assembly);
// Option 2: Explicit registration via UIOptions
options.WithAssetProvider<ChartJsAssetProvider>();
// Option 3: Manual DI registration
services.AddSingleton<IComponentAssetProvider<ChartCanvas>, ChartJsAssetProvider>();
When to use:
Third-party components that you can't modify
App-level overrides for framework component assets
Components from external packages
Priority
When both patterns exist for the same component type:
1.
IRequireAssets executes first (component default)
2.
IComponentAssetProvider<T> executes second (external provider)
Deduplication is by Key with first-wins semantics. If you need the provider to override a component's default asset, use a different Key (e.g., different URL).
How It Works (SSR Pipeline)
During Server-Side Rendering, ServerRenderingService walks the component tree:
1
2
3
4
5
6
7
8
9
RenderPageAsync()
├── Create AssetCollection
├── CollectAssets(rootComponent, assets, services, visited)
│ ├── Check IRequireAssets → ConfigureAssets()
│ ├── Check DI for IComponentAssetProvider<T> → ConfigureAssets()
│ ├── Recurse into component.Children
│ └── For StatelessComponent → Build() → recurse result
├── Render HTML
└── Return ServerRenderResult with Assets
Assets are then merged into HtmlShellOptions.HeadTags before serving the page.
Key behaviors:
Per-type deduplication: Each component type is processed once (via HashSet<Type>)
Per-asset deduplication: Each asset key is registered once (via Dictionary.TryAdd)
Zero overhead: Pages without asset-requiring components get no extra tags
Graceful fallback: If Build() fails (e.g., missing DI), the component is skipped
Auto-Registration
IComponentAssetProvider<T> implementations are auto-discovered during AddUI():
1
2
3
4
builder.Services.AddUI(options =>
{
options.ScanAssembly(typeof(Program).Assembly); // Auto-finds providers here
});
The scan looks for all non-abstract classes implementing IComponentAssetProvider<T> in the scanned assemblies and registers them as singletons via TryAddSingleton.
Explicit registrations via WithAssetProvider<T>() take priority over auto-scanned ones (registered first).
Real-World Example: CodeBlock
The CodeBlock component demonstrates the full pattern:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class CodeBlock : StatelessComponent, IRequireAssets
{
public void ConfigureAssets(AssetBuilder assets)
{
// Stylesheet with id for dynamic theme switching
assets.AddStylesheet(
"https://cdn.jsdelivr.net/npm/prismjs@1.29.0/themes/prism-tomorrow.min.css",
id: "prism-theme");
// Core Prism.js scripts
assets.AddScript("https://cdn.jsdelivr.net/npm/prismjs@1.29.0/prism.min.js");
assets.AddScript("https://cdn.jsdelivr.net/npm/prismjs@1.29.0/plugins/autoloader/prism-autoloader.min.js");
// Utility functions + dark/light theme auto-switching
assets.AddInlineScript(
"function copyToClipboard(id){...}" +
"function toggleCodeBlock(id){...}" +
"(function(){" +
"function updateTheme(){...}" + // Switches between prism.css and prism-tomorrow.css
"updateTheme();" +
"new MutationObserver(function(){updateTheme()})" +
".observe(document.documentElement,{attributes:true,attributeFilter:['class']});" +
"})();"
);
}
}
The developer just uses new CodeBlock(code, "csharp"), and Prism.js scripts, CSS, theme switching, and utility functions are all handled automatically.
Since 0.2.0-preview.1
The app icon, drawn in C# (IAppIcon)
The launcher icon is a component like anything else, in the same vocabulary, so it cannot drift from the brand the app already draws with:
1
2
3
4
5
6
public sealed class AppIcon : IAppIcon
{
public VisualNode Build(ComponentContext context) =>
Box(new BoxStyle { Background = Brand, CornerRadius = new CornerRadii(0) },
Text("eQ", TypeRole.Display, Ink).Centered());
}
Two fences, both from the medium rather than the framework:
Fill the whole square, opaque. A transparent icon is composited against whatever the launcher happens to be showing, which is never what you designed against.
No state, no interaction. It is built once; a press handler on an icon is a handler nobody can reach. Keep it to shapes, gradients and at most a letter or two, because at the size a home screen actually shows, anything finer is a smudge.
The build rasterizes it into every size each platform wants, and links the web ones into the head without the app saying so.