eQuantic.UIeQuantic.UI
Docs
Playground
GitHub
DocsCompilation
External Type Resolution in eQuantic.UI Compiler
Edit this page
2 min read
🌐 This page in: English · Português
Overview
The eQuantic.UI compiler resolves types defined in external files within the same project. Components reference models, DTOs, and other classes without requiring them to be in the same file: the compiler works against the full project Roslyn Compilation, which includes all source files and references.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// Models/User.cs
public class User
{
public string Name { get; set; }
public string Email { get; set; }
}
// Pages/UserProfile.cs
[Page("/profile")]
public class UserProfile : StatefulComponent
{
private User _currentUser; // ✅ Type 'User' fully resolved
protected override HtmlNode Render()
{
return Text(_currentUser.Name); // ✅ Converts to: this._currentUser.name
}
}
A full semantic model matters because member-access conversion depends on knowing the receiver's type: with it, _currentUser.Name lowers to this._currentUser.name; without it, the compiler cannot distinguish a property from a local or pick the right method mapping.
API Usage
Option 1: Using MSBuildWorkspace (Recommended)
1
2
3
4
5
6
7
8
9
10
11
12
13
using eQuantic.UI.Compiler;
using eQuantic.UI.Compiler.Services;
// Get the full project compilation
var compilation = await ProjectCompilationHelper
.GetProjectCompilationAsync("path/to/MyApp.csproj");
// Create compiler and set project compilation
var compiler = new ComponentCompiler();
compiler.SetProjectCompilation(compilation);
// Now compile components - external types will be resolved
var results = compiler.CompileFile("Pages/UserProfile.cs");
Option 2: Manual Compilation from Sources
Useful in MSBuild tasks where MSBuildWorkspace might not be available:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
using eQuantic.UI.Compiler.Services;
// Get all .cs files in the project
var sourceFiles = ProjectCompilationHelper
.GetProjectSourceFiles("path/to/MyApp");
// Get assembly references
var assemblyPaths = new[]
{
"path/to/eQuantic.UI.Core.dll",
"path/to/other-dependencies.dll"
};
// Create compilation from sources
var compilation = ProjectCompilationHelper.CreateCompilationFromSources(
sourceFiles,
assemblyPaths,
"MyApp");
var compiler = new ComponentCompiler();
compiler.SetProjectCompilation(compilation);
Option 3: Clearing Project Compilation
Revert to minimal compilation mode (isolated files):
1
compiler.ClearProjectCompilation();
How It Works
Architecture
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
┌─────────────────────────────────────────┐
│ MSBuild Project Compilation │
│ - All .cs files in project │
│ - All referenced assemblies │
│ - Full type information │
└────────────┬────────────────────────────┘
↓ SetProjectCompilation()
┌────────────────────────────────────────┐
│ SemanticModelProvider │
│ - Stores project compilation │
│ - Returns SemanticModel for each file │
└────────────┬───────────────────────────┘
↓ GetSemanticModel(tree)
┌────────────────────────────────────────┐
│ CSharpToJsConverter │
│ - Uses SemanticModel for type info │
│ - Converts _currentUser.Name → name │
└────────────────────────────────────────┘
Key Classes
1.
SemanticModelProvider (SemanticModelProvider.cs)
SetProjectCompilation(Compilation) - Set full project compilation
GetSemanticModel(SyntaxTree) - Returns semantic model with full type info
2.
ProjectCompilationHelper (ProjectCompilationHelper.cs)
GetProjectCompilationAsync(string) - Load from .csproj
CreateCompilationFromSources(...) - Build from source files
GetProjectSourceFiles(string) - Find all .cs files
3.
ComponentCompiler (ComponentCompiler.cs)
SetProjectCompilation(Compilation) - Enable external type resolution
ClearProjectCompilation() - Revert to isolated mode
Examples
Example 1: Component with External Model
Models/Product.cs:
1
2
3
4
5
6
7
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
public bool InStock { get; set; }
}
Pages/ProductCard.cs:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
[Component]
public class ProductCard : StatelessComponent
{
public Product Item { get; set; }
protected override HtmlNode Build()
{
return Container(
Heading(Item.Name),
Text($"${Item.Price:F2}"),
Text(Item.InStock ? "In Stock" : "Out of Stock")
);
}
}
Generated JavaScript (with project compilation):
1
2
3
4
5
6
7
8
9
class ProductCard extends StatelessComponent {
build() {
return Container([
Heading(this.item.name),
Text(`$${this.item.price.toFixed(2)}`),
Text(this.item.inStock ? 'In Stock' : 'Out of Stock')
]);
}
}
Example 2: Multiple External Types
Models/Address.cs:
1
2
3
4
5
6
public class Address
{
public string Street { get; set; }
public string City { get; set; }
public string ZipCode { get; set; }
}
Models/Customer.cs:
1
2
3
4
5
6
public class Customer
{
public string Name { get; set; }
public string Email { get; set; }
public Address ShippingAddress { get; set; }
}
Pages/CheckoutPage.cs:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
[Page("/checkout")]
public class CheckoutPage : StatefulComponent
{
private Customer _customer;
protected override HtmlNode Render()
{
return Container(
Text($"Customer: {_customer.Name}"),
Text($"Email: {_customer.Email}"),
Text($"Shipping: {_customer.ShippingAddress.City}, {_customer.ShippingAddress.ZipCode}")
);
}
}
All property accesses are correctly resolved and converted to JavaScript.
Testing
Tests verify external type resolution works correctly:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
[Fact]
public void SemanticModel_WithProjectCompilation_CanResolveExternalTypes()
{
// Create compilation with User and Component
var userTree = CSharpSyntaxTree.ParseText("public class User { ... }");
var componentTree = CSharpSyntaxTree.ParseText("public class UserProfile { ... }");
var compilation = CSharpCompilation.Create("Test", new[] { userTree, componentTree }, ...);
var provider = new SemanticModelProvider();
provider.SetProjectCompilation(compilation);
var semanticModel = provider.GetSemanticModel(componentTree);
// Assert: User type is resolved
var userType = semanticModel.Compilation.GetTypeByMetadataName("User");
userType.Should().NotBeNull();
}
See ExternalTypeResolutionTests.cs for complete test suite.
Modes
Without SetProjectCompilation(): minimal compilation, so each file is resolved in isolation
With SetProjectCompilation(): full project type resolution
Performance
Minimal overhead:
Project compilation is created once at build time
Shared across all component files
No per-file compilation penalty
Memory efficient:
Single compilation instance
Reuses existing Roslyn infrastructure
Limitations
1.
Requires compilation before component compilation: The project must be compiled (or at least parsed) before running the component compiler.
2.
MSBuildWorkspace availability: GetProjectCompilationAsync requires MSBuild APIs, which may not be available in all contexts. Use CreateCompilationFromSources as fallback.
3.
Generated code: Auto-generated files in obj/ are excluded to avoid conflicts.
Summary
Components reference classes from other files; full type information is available during compilation, so JavaScript generation for external type members is correct.
The SDK build feeds the compiler the project's real assembly references (--refs), so receiver types resolve even across package boundaries.
1
2
var compilation = await ProjectCompilationHelper.GetProjectCompilationAsync(projectPath);
compiler.SetProjectCompilation(compilation);