External Type Resolution in eQuantic.UI CompilerThe 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.
public string Name { get; set; }
public string Email { get; set; }
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.
Option 1: Using MSBuildWorkspace (Recommended)
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:
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(
var compiler = new ComponentCompiler();
compiler.SetProjectCompilation(compilation);
Option 3: Clearing Project Compilation
Revert to minimal compilation mode (isolated files):
compiler.ClearProjectCompilation();
┌─────────────────────────────────────────┐
│ MSBuild Project Compilation │
│ - All .cs files in project │
│ - All referenced assemblies │
│ - Full type information │
└────────────┬────────────────────────────┘
↓ SetProjectCompilation()
┌────────────────────────────────────────┐
│ SemanticModelProvider │
│ - Stores project compilation │
│ - Returns SemanticModel for each file │
└────────────┬───────────────────────────┘
┌────────────────────────────────────────┐
│ - Uses SemanticModel for type info │
│ - Converts _currentUser.Name → name │
└────────────────────────────────────────┘
•
SetProjectCompilation(Compilation) - Set full project compilation
•
GetSemanticModel(SyntaxTree) - Returns semantic model with full type info
•
GetProjectCompilationAsync(string) - Load from .csproj
•
CreateCompilationFromSources(...) - Build from source files
•
GetProjectSourceFiles(string) - Find all .cs files
•
SetProjectCompilation(Compilation) - Enable external type resolution
•
ClearProjectCompilation() - Revert to isolated mode
Example 1: Component with External Model
Models/Product.cs:
public int Id { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
public bool InStock { get; set; }
Pages/ProductCard.cs:
public class ProductCard : StatelessComponent
public Product Item { get; set; }
protected override HtmlNode Build()
Text($"${Item.Price:F2}"),
Text(Item.InStock ? "In Stock" : "Out of Stock")
Generated JavaScript (with project compilation):
class ProductCard extends StatelessComponent {
Text(`$${this.item.price.toFixed(2)}`),
Text(this.item.inStock ? 'In Stock' : 'Out of Stock')
Example 2: Multiple External Types
Models/Address.cs:
public string Street { get; set; }
public string City { get; set; }
public string ZipCode { get; set; }
Models/Customer.cs:
public string Name { get; set; }
public string Email { get; set; }
public Address ShippingAddress { get; set; }
Pages/CheckoutPage.cs:
public class CheckoutPage : StatefulComponent
private Customer _customer;
protected override HtmlNode Render()
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.
Tests verify external type resolution works correctly:
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();
•
Without SetProjectCompilation(): minimal compilation, so each file is resolved in isolation
•
With SetProjectCompilation(): full project type resolution
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
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.
•
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.
var compilation = await ProjectCompilationHelper.GetProjectCompilationAsync(projectPath);
compiler.SetProjectCompilation(compilation);