Architecture Guarantee at Compile Time with Roslyn Analyzers
EnsureArch is a library that uses Roslyn Analyzers to ensure your .NET application follows defined architecture rules, providing immediate feedback during compilation instead of relying on unit tests.
Unlike libraries such as NetArchTest that validate architecture through unit tests, EnsureArch:
- โ Immediate Feedback: Architecture errors are detected during compilation
- โ IDE Integration: Warnings and errors appear directly in Visual Studio/VS Code
- โ CI/CD Friendly: Build fails automatically if there are violations
- โ Zero Overhead: Doesn't add code to the application runtime
flowchart TD
Config[architecture.json] --> Analyzer[Roslyn Analyzer]
Code[C# Code] --> Analyzer
Analyzer --> Validation{Validate Dependencies}
Validation -->|โ
Valid| Success[Compilation OK]
Validation -->|โ Invalid| Error[ARCH001: Architecture Violation]
Error --> BuildFail[Build Fails]
dotnet add package EnsureArch.AnalyzerAdd the file to your project root:
{
"baseNamespace": "EnsureArch.Playground",
"rules": [
{
"on": "Api",
"allow": ["IoC", "ServiceDefaults", "Shareable"]
},
{
"on": "Domain",
"allow": ["Shareable"]
},
{
"on": "Data",
"allow": ["Domain", "Shareable"]
},
{
"on": "IoC",
"allow": ["Api", "Domain", "Data", "ServiceDefaults", "Shareable"]
},
{
"on": "ServiceDefaults",
"allow": ["Shareable"]
},
{
"on": "Shareable",
"allow": []
}
]
}<Project Sdk="Microsoft.NET.Sdk">
<ItemGroup>
<PackageReference Include="EnsureArch.Analyzer" Version="1.0.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<AdditionalFiles Include="architecture.json" />
</ItemGroup>
</Project>graph TB
subgraph "Application Layers"
Api[๐ Api Layer<br/>Controllers, Endpoints]
Domain[๐ข Domain Layer<br/>Entities, Services]
Data[๐พ Data Layer<br/>Repositories, DbContext]
IoC[๐ IoC Layer<br/>Dependency Injection]
ServiceDefaults[โ๏ธ Service Defaults<br/>Common Configurations]
Shareable[๐ฆ Shareable<br/>DTOs, Interfaces]
end
Api --> IoC
Api --> ServiceDefaults
Api --> Shareable
Domain --> Shareable
Data --> Domain
Data --> Shareable
IoC --> Api
IoC --> Domain
IoC --> Data
IoC --> ServiceDefaults
IoC --> Shareable
ServiceDefaults --> Shareable
// โ ARCH001: Namespace dependency violation
// The namespace 'EnsureArch.Playground.Domain' cannot depend on 'EnsureArch.Playground.Api' according to architecture.json
namespace EnsureArch.Playground.Domain;
using EnsureArch.Playground.Api; // โ Violation!
public class UserService
{
// Domain cannot depend on Api
}ARCH001: The namespace 'EnsureArch.Playground.Domain' cannot depend on 'EnsureArch.Playground.Api' according to architecture.json
// โ
Allowed dependency
namespace EnsureArch.Playground.Domain;
using EnsureArch.Playground.Shareable; // โ
Allowed!
public class UserService
{
public UserDto GetUser(int id) // Using DTO from Shareable
{
// Implementation
}
}public static ArchitectureModel? LoadArchitecture(AnalyzerOptions options)
{
var file = options.AdditionalFiles.FirstOrDefault(f => f.Path.EndsWith("architecture.json"));
if (file == null) return null;
using var fileStream = new FileStream(file.Path, FileMode.Open);
var deserializer = System.Text.Json.JsonSerializer.Deserialize<ArchitectureModel>(fileStream);
return deserializer;
}context.RegisterSyntaxNodeAction(ctx =>
{
if (ctx.Node is UsingDirectiveSyntax { Name: { } @namespace } usingDirective)
{
var sourceNamespace = GetSourceNamespace(ctx.Node);
var targetNamespace = @namespace.ToString();
if (!IsAllowedDependency(sourceNamespace, targetNamespace))
{
var diagnostic = Diagnostic.Create(Rule, usingDirective.GetLocation(),
sourceNamespace, targetNamespace);
ctx.ReportDiagnostic(diagnostic);
}
}
}, SyntaxKind.UsingDirective);public record ArchitectureModel(string BaseNamespace, List<DependencyRule> Rules);
public record DependencyRule(string On, List<string> Allow);For solutions with multiple projects, each project can have its own architecture.json:
Solution/
โโโ Project.Api/
โ โโโ architecture.json
โ โโโ Project.Api.csproj
โโโ Project.Domain/
โ โโโ architecture.json
โ โโโ Project.Domain.csproj
โโโ Project.Data/
โโโ architecture.json
โโโ Project.Data.csproj
Or use a global file at the solution root:
<Project Sdk="Microsoft.NET.Sdk">
<ItemGroup>
<AdditionalFiles Include="../architecture.json" />
</ItemGroup>
</Project>name: Architecture Validation
on: [push, pull_request]
jobs:
validate-architecture:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: '8.0.x'
- name: Build and Validate Architecture
run: dotnet build --configuration Release
# Build will fail if there are ARCH001 violations- task: DotNetCoreCLI@2
displayName: 'Build and Validate Architecture'
inputs:
command: 'build'
configuration: 'Release'
# Pipeline will fail automatically with violations- โ Errors appear in Error List
- โ Red squiggles in code
- โ Tooltips with error messages
- โ Solution Explorer integration
- โ Problems appear in Problems Panel
- โ C# Extension integration
- โ IntelliSense warnings
| Aspect | EnsureArch | NetArchTest |
|---|---|---|
| Feedback | โก Compile time | ๐งช Test time |
| IDE Integration | โ Native | โ Only in tests |
| CI/CD | โ Build fails automatically | |
| Performance | โ Zero runtime overhead | |
| Configuration | ๐ JSON file | ๐ป C# code |
| Flexibility | โ Custom logic |
- Attribute Support: Attribute-based validation
- Custom Rules: Plugin extensibility
- Architecture Metrics: Complexity reports
- Visual Studio Extension: GUI for configuration
- VB.NET Support: Expand beyond C#
- Integration Tests: External dependency validation
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
Keep your architecture clean and consistent with EnsureArch! ๐๏ธโจ