Namespace Registration
Namespace registration maps XML namespaces to .NET assembly namespaces, enabling the parser to discover and instantiate custom components when it encounters unknown XML elements.
How It Works
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Template XML β
β <custom:StatCard ... /> β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β XML Namespace β
β xmlns:custom='http://mycompany.com/components' β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Namespace Registration (appsettings.json) β
β Source: http://mycompany.com/components β
β Namespace: MyCompany.Components β
β Assembly: MyAssembly β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Type Resolution β
β Load assembly β Reflect types β Match "StatCard" β
β Found: MyCompany.Components.StatCard β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Component Instantiation β
β Activator.CreateInstance(typeof(StatCard)) β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Configuration
JSON Configuration
appsettings.json:
{
"Scryber": {
"Parsing": {
"Namespaces": [
{
"Source": "http://mycompany.com/schemas/components",
"Namespace": "MyCompany.Components",
"Assembly": "MyCompany.Components"
},
{
"Source": "http://mycompany.com/schemas/charts",
"Namespace": "MyCompany.Charts",
"Assembly": "MyCompany.Charts"
}
]
}
}
}
Configuration Properties
| Property | Description | Example |
|---|---|---|
Source |
XML namespace URI (must match the xmlns:prefix declaration) |
http://mycompany.com/components |
Namespace |
The .NET namespace of the component classes | MyCompany.Components |
Assembly |
The name of the assembly that contains them | MyCompany.Components |
The configuration is passed to the engine at startup, e.g. with services.AddPaperwork(builder.Configuration) - see Configuration Structure.
Template Declaration
Once registered, use the namespace in templates:
<?xml version='1.0' encoding='utf-8' ?>
<html xmlns='http://www.w3.org/1999/xhtml'
xmlns:custom='http://mycompany.com/schemas/components'
xmlns:charts='http://mycompany.com/schemas/charts'>
<body>
<main>
<!-- Resolves to MyCompany.Components.StatCard -->
<custom:StatCard value='100' label='Sales' />
<!-- Resolves to MyCompany.Charts.BarChart -->
<charts:BarChart data='{@:ChartData}' />
</main>
</body>
</html>
Type Resolution Process
1. Assembly Loading
When the parser encounters an unknown element (e.g., <custom:StatCard>), it:
- Extracts the namespace prefix (
custom) - Looks up the XML namespace URI (
http://mycompany.com/schemas/components) - Finds the registered Namespace and Assembly (
MyCompany.Components,MyCompany.Components) - Loads or locates the assembly (
MyCompany.Components.dll)
2. Type Resolution
The element name (StatCard) is looked up as a type in the registered namespace and assembly (MyCompany.Components.StatCard), or as the name given in its [PDFParsableComponent("Name")] attribute. The lookup is made by ParserDefintionFactory in the engine - see Configuration Structure for the code.
3. Type Caching
Resolved types are cached, so the reflection only happens the first time an element is used.
Complete Example
1. Component Assembly
MyCompany.Components/StatCard.cs:
using Scryber;
using Scryber.Components;
namespace MyCompany.Components
{
[PDFParsableComponent("StatCard")]
public class StatCard : Panel
{
[PDFAttribute("value")]
public string Value { get; set; }
[PDFAttribute("label")]
public string Label { get; set; }
public StatCard() : base(ObjectTypes.Panel)
{
}
protected override void OnInitialized(InitContext context)
{
base.OnInitialized(context);
BuildContent();
}
private void BuildContent()
{
var valueLabel = new Label { Text = Value };
valueLabel.Style.Font.FontSize = 24;
this.Contents.Add(valueLabel);
var labelText = new Label { Text = Label };
labelText.Style.Font.FontSize = 12;
this.Contents.Add(labelText);
}
}
}
2. Configuration
appsettings.json:
{
"Scryber": {
"Parsing": {
"Namespaces": [
{
"Source": "http://mycompany.com/schemas/components",
"Namespace": "MyCompany.Components",
"Assembly": "MyCompany.Components"
}
]
}
}
}
3. Template
Dashboard.pdfx:
<?xml version='1.0' encoding='utf-8' ?>
<html xmlns='http://www.w3.org/1999/xhtml'
xmlns:custom='http://mycompany.com/schemas/components'>
<body>
<main>
<custom:StatCard value='$125,432' label='Total Sales' />
<custom:StatCard value='1,847' label='Active Users' />
</main>
</body>
</html>
4. Application
Program.cs:
using Microsoft.Extensions.Configuration;
using Paperwork;
// Pass the configuration, with the namespace registration, to the engine once at startup
var config = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.Build();
Scryber.ServiceProvider.Init(config);
// Generate the document with custom components
using var factory = PaperworkFactory.Create().Build();
await factory.NewDocument()
.WithLayoutFile("Dashboard.html")
.SaveAsync("Dashboard.pdf");
Advanced Configuration
Multiple Namespaces
Register multiple component libraries:
{
"Scryber": {
"Parsing": {
"Namespaces": [
{
"Source": "http://mycompany.com/components",
"Namespace": "MyCompany.Components",
"Assembly": "MyCompany.Components"
},
{
"Source": "http://mycompany.com/charts",
"Namespace": "MyCompany.Charting",
"Assembly": "MyCompany.Charting"
},
{
"Source": "http://thirdparty.com/widgets",
"Namespace": "ThirdParty.Widgets",
"Assembly": "ThirdParty.Widgets"
}
]
}
}
}
Nested Namespaces
Components can exist in nested namespaces:
Code structure:
MyCompany.Components
βββ StatCard.cs
βββ Cards
β βββ ProductCard.cs
βββ Charts
βββ BarChart.cs
Configuration:
{
"Scryber": {
"Parsing": {
"Namespaces": [
{
"Source": "http://mycompany.com/components",
"Namespace": "MyCompany.Components",
"Assembly": "MyCompany.Components"
},
{
"Source": "http://mycompany.com/components/cards",
"Namespace": "MyCompany.Components.Cards",
"Assembly": "MyCompany.Components"
},
{
"Source": "http://mycompany.com/components/charts",
"Namespace": "MyCompany.Components.Charts",
"Assembly": "MyCompany.Components"
}
]
}
}
}
Template:
<html xmlns='http://www.w3.org/1999/xhtml'
xmlns:custom='http://mycompany.com/components'
xmlns:cards='http://mycompany.com/components/cards'
xmlns:charts='http://mycompany.com/components/charts'>
<custom:StatCard ... /> <!-- MyCompany.Components.StatCard -->
<cards:ProductCard ... /> <!-- MyCompany.Components.Cards.ProductCard -->
<charts:BarChart ... /> <!-- MyCompany.Components.Charts.BarChart -->
</html>
Implementation Details
Namespaces are registered from the Scryber:Parsing:Namespaces configuration the first time a document is parsed, and the engineβs own namespaces (HTML, SVG and the Scryber component namespaces) are always included. Component types are found by reflection on the registered assembly, and cached. See Configuration Structure for the engine code.
Best Practices
Namespace URI Design
- Use your company domain:
http://mycompany.com/schemas/... - Include version for breaking changes:
http://mycompany.com/schemas/v2/components - Group related components:
http://mycompany.com/components/charts - Document namespace URIs
Assembly Organization
- Group related components in same assembly
- Use consistent naming:
Company.Product.Components - Version assemblies appropriately
- Document component libraries
Component Discovery
- Mark all components with
[PDFParsableComponent] - Use descriptive element names
- Avoid name collisions across namespaces
- Document component APIs
Performance
- Namespace resolution is cached - registration order doesnβt impact performance
- Assembly loading happens once per namespace
- Type reflection is cached - repeated use is fast
Troubleshooting
βNo assembly registered for XML namespaceβ
- Verify namespace URI matches exactly (including http/https, case)
- Check the configuration is passed to the engine (
AddPaperwork(configuration)orScryber.ServiceProvider.Init(configuration)) - Ensure JSON syntax is valid
- Verify namespace is in
Parsing:Namespaces[]array
βCould not load type βMyCompany.Components.StatCardββ
- Check the assembly name matches
Assembly - Verify assembly is referenced by application
- Ensure the class namespace matches
Namespace - Check component class is public
- Verify [PDFParsableComponent] attribute is present
Type not found in assembly
- Check class is public
- Verify namespace matches configuration
- Ensure class name matches element name (or [PDFParsableComponent(βNameβ)])
- Check assembly is compiled and up-to-date
Namespace prefix undeclared
- Add
xmlns:prefix='...'to Document root element - Verify URI matches registered namespace exactly
- Check for typos in namespace URI
Related Documentation
- Custom Components - Creating parseable components
- Processing Instructions - Document-level configuration
- Integration Example - Complete namespace registration example
- Best Practices - Namespace design patterns