Repository navigation
Sample Projects
Working code examples demonstrating each generator in realistic scenarios. All samples are located in the sample/ directory of the repository.
sample/
βββ Atc.SourceGenerators.DependencyRegistration/ (Console app - DI registration)
β βββ Atc.SourceGenerators.DependencyRegistration.Domain/
βββ Atc.SourceGenerators.OptionsBinding/ (Console app - Configuration binding)
β βββ Atc.SourceGenerators.OptionsBinding.Domain/
βββ Atc.SourceGenerators.Mapping/ (ASP.NET Core API - Object mapping)
β βββ Atc.SourceGenerators.Mapping.Domain/
β βββ Atc.SourceGenerators.Mapping.DataAccess/
β βββ Atc.SourceGenerators.Mapping.Contract/
βββ Atc.SourceGenerators.MappingOnlyConfiguration/ (Console app - Config-based mapping)
β βββ Atc.SourceGenerators.MappingOnlyConfiguration.Domain/
β βββ Atc.SourceGenerators.MappingOnlyConfiguration.ExternalCrm/
βββ Atc.SourceGenerators.MappingCombinedConfiguration/ (Console app - Mixed mapping)
β βββ Atc.SourceGenerators.MappingCombinedConfiguration.Domain/
β βββ Atc.SourceGenerators.MappingCombinedConfiguration.Contract/
β βββ Atc.SourceGenerators.MappingCombinedConfiguration.ExternalNotifications/
β βββ Atc.SourceGenerators.MappingCombinedConfiguration.ExternalAnalytics/
βββ Atc.SourceGenerators.EnumMapping/ (Console app - Enum mapping)
βββ Atc.SourceGenerators.AnnotationConstants/ (Console app - Annotation constants)
βββ PetStore.Api/ (Full app - All generators together)
βββ PetStore.Domain/
βββ PetStore.DataAccess/
βββ PetStore.Api.Contract/
π Multi-project console app showing automatic DI registration across layers with auto-detection of interfaces.
cd sample/Atc.SourceGenerators.DependencyRegistration
dotnet rungraph TB
subgraph "Atc.SourceGenerators.DependencyRegistration (Console App)"
Program[Program.cs]
SC[ServiceCollection]
end
subgraph "Atc.SourceGenerators.DependencyRegistration.Domain"
US[UserService]
CS[CacheService]
ES[EmailService]
IUS[IUserService]
ICS[ICacheService]
IES[IEmailService]
INS[INotificationService]
US -->|implements| IUS
CS -->|implements| ICS
ES -->|implements| IES
ES -->|implements| INS
end
subgraph "Generated Code"
EXT1["AddDependencyRegistrationsFromAtcSourceGeneratorsDependencyRegistration()"]
EXT2["AddDependencyRegistrationsFromAtcSourceGeneratorsDependencyRegistrationDomain()"]
end
Program --> SC
SC --> EXT1
SC --> EXT2
EXT1 -->|registers| Program
EXT2 -->|registers| US
EXT2 -->|registers| CS
EXT2 -->|registers| ES
style US fill:#0969da
style CS fill:#0969da
style ES fill:#0969da
style EXT1 fill:#2ea44f
style EXT2 fill:#2ea44f
sequenceDiagram
participant App as Program.cs
participant Gen as Source Generator
participant DI as ServiceCollection
Note over Gen: Build Time
Gen->>Gen: Scan for [Registration] attributes
Gen->>Gen: Detect implemented interfaces
Gen->>Gen: Generate AddDependencyRegistrationsFromXXX()
Note over App,DI: Runtime
App->>DI: AddDependencyRegistrationsFromDomain()
DI->>DI: services.AddScoped<IUserService, UserService>()
DI->>DI: services.AddSingleton<ICacheService, CacheService>()
DI->>DI: services.AddScoped<IEmailService, EmailService>()
DI->>DI: services.AddScoped<INotificationService, EmailService>()
DI-->>App: Configured ServiceProvider
π Domain Layer Services:
using Atc.DependencyInjection;
// Auto-detected as IUserService
[Registration(Lifetime.Scoped)]
public class UserService : IUserService
{
public void CreateUser(string name) => Console.WriteLine($"Creating user: {name}");
}
// Auto-detected as ICacheService
[Registration] // Defaults to Singleton
public class CacheService : ICacheService
{
public void Set(string key, object value) => Console.WriteLine($"Caching {key}");
}
// Multi-interface example - registers against BOTH interfaces
[Registration(Lifetime.Scoped)]
public class EmailService : IEmailService, INotificationService
{
public void SendEmail(string to, string subject) => Console.WriteLine($"Email to {to}: {subject}");
public void Notify(string message) => Console.WriteLine($"Notification: {message}");
}π Application Setup:
var services = new ServiceCollection();
// Option 1: Manual registration - one line per project
services.AddDependencyRegistrationsFromAtcSourceGeneratorsDependencyRegistration();
services.AddDependencyRegistrationsFromAtcSourceGeneratorsDependencyRegistrationDomain();
// Option 2: Transitive registration (recommended) - single call
services.AddDependencyRegistrationsFromAtcSourceGeneratorsDependencyRegistration(includeReferencedAssemblies: true);π All Available Overloads:
// Overload 1: Default (no transitive registration)
services.AddDependencyRegistrationsFromYourProject();
// Overload 2: Auto-detect ALL referenced assemblies recursively
services.AddDependencyRegistrationsFromYourProject(includeReferencedAssemblies: true);
// Overload 3: Register specific referenced assembly (short or full name)
services.AddDependencyRegistrationsFromYourProject("Domain");
// Overload 4: Register multiple specific assemblies
services.AddDependencyRegistrationsFromYourProject("Domain", "DataAccess", "Infrastructure");- β Zero Configuration: Services are automatically registered against all implemented interfaces
- π‘οΈ Type Safety: Compile-time errors if interfaces don't match or lifetimes conflict
- π¦ Multi-Project: Each project gets its own
AddDependencyRegistrationsFromXXX()method - π Multi-Interface: One service can be registered against multiple interfaces automatically
- π§Ή Smart Filtering: System interfaces (IDisposable, etc.) are automatically excluded
π Full documentation: Working with Dependency Registration
π§ Console app demonstrating type-safe configuration binding with validation and multiple options classes.
cd sample/Atc.SourceGenerators.OptionsBinding
dotnet rungraph TB
subgraph "Configuration Sources"
JSON[appsettings.json]
ENV[Environment Variables]
end
subgraph "Options Classes"
DO[DatabaseOptions]
CO[CacheOptions]
AO[ApiOptions]
LO[LoggingOptions]
end
subgraph "Generated Extension Methods"
EXT1["AddOptionsFromAtcSourceGeneratorsOptionsBinding()"]
EXT2["AddOptionsFromAtcSourceGeneratorsOptionsBindingDomain()"]
end
JSON --> EXT1
ENV --> EXT1
JSON --> EXT2
ENV --> EXT2
EXT1 --> DO
EXT2 --> CO
EXT2 --> AO
EXT2 --> LO
style EXT1 fill:#2ea44f
style EXT2 fill:#2ea44f
style DO fill:#0969da
style CO fill:#0969da
style AO fill:#0969da
style LO fill:#0969da
π Domain Layer Options:
using Atc.SourceGenerators.Annotations;
using System.ComponentModel.DataAnnotations;
// Explicit section name with fail-fast validation
[OptionsBinding("Database", ValidateDataAnnotations = true, ValidateOnStart = true, ErrorOnMissingKeys = true)]
public partial class DatabaseOptions
{
[Required]
[MinLength(10)]
public string ConnectionString { get; set; } = string.Empty;
[Range(1, 10)]
public int MaxRetries { get; set; } = 3;
}
// Using const SectionName (2nd priority)
[OptionsBinding(ValidateDataAnnotations = true)]
public partial class CacheOptions
{
public const string SectionName = "ApplicationCache";
[Range(100, 10000)]
public int MaxSize { get; set; } = 1000;
}
// Nested section path
[OptionsBinding("App:Api")]
public partial class ApiOptions
{
[Required] [Url]
public string BaseUrl { get; set; } = string.Empty;
}
// Configuration change callbacks with Monitor lifetime
[OptionsBinding("Logging", Lifetime = OptionsLifetime.Monitor, OnChange = nameof(OnLoggingChanged))]
public partial class LoggingOptions
{
public string Level { get; set; } = "Information";
internal static void OnLoggingChanged(LoggingOptions options, string? name)
{
Console.WriteLine($"[OnChange] Level: {options.Level}");
}
}π Application Setup:
var services = new ServiceCollection();
// Transitive registration (recommended)
services.AddOptionsFromAtcSourceGeneratorsOptionsBinding(configuration, includeReferencedAssemblies: true);- π Section name resolution priority: Explicit > SectionName const > NameTitle const > Name const > Auto-inferred
- π‘οΈ Validation: DataAnnotations with
ValidateOnStart - π Configuration change callbacks: Auto-generated
IHostedServiceforOnChangenotifications - π·οΈ Named options: Multiple configurations of same type (Email: Primary/Secondary/Fallback)
- π Child sections: Simplified syntax for named options (
ChildSections = new[] { "Email", "SMS", "Push" }) - πͺ Nested subsection binding: Complex properties auto-bound to subsections
π Full documentation: Working with Options Binding
π ASP.NET Core Minimal API showing 3-layer mapping (Entity β Domain β DTO) with automatic enum conversion and nested objects.
cd sample/Atc.SourceGenerators.Mapping
dotnet rungraph TB
subgraph "API Layer"
API[Minimal API Endpoints]
end
subgraph "Contract Layer"
DTO[DTOs: UserDto, AddressDto, UserStatusDto]
end
subgraph "Domain Layer"
DM[Domain Models: User, Address]
DE[Domain Enums: UserStatus]
end
subgraph "Data Access Layer"
ENT[Entities: UserEntity, AddressEntity]
EE[Entity Enums: UserStatusEntity]
end
subgraph "Generated Bidirectional Mappings"
M1["User β UserEntity"]
M2["User β UserDto"]
M3["Address β AddressEntity"]
M4["Address β AddressDto"]
end
API --> DTO
DTO --> DM
DM --> M1
DM --> M2
M1 --> ENT
M2 --> DTO
style M1 fill:#2ea44f
style M2 fill:#2ea44f
style M3 fill:#2ea44f
style M4 fill:#2ea44f
π Domain Layer:
using Atc.SourceGenerators.Annotations;
// Domain model with BIDIRECTIONAL mapping to Entity and forward mapping to DTO
[MapTo(typeof(UserDto))]
[MapTo(typeof(UserEntity), Bidirectional = true)]
public partial class User
{
public Guid Id { get; set; }
public string FirstName { get; set; } = string.Empty;
public string LastName { get; set; } = string.Empty;
public string Email { get; set; } = string.Empty;
public UserStatus Status { get; set; }
public Address? Address { get; set; }
}
[MapTo(typeof(AddressDto))]
[MapTo(typeof(AddressEntity), Bidirectional = true)]
public partial class Address
{
public string Street { get; set; } = string.Empty;
public string City { get; set; } = string.Empty;
public string Country { get; set; } = string.Empty;
}π Usage - Multi-layer mapping chain:
var dto = entity
.MapToUser() // Entity β Domain
.MapToUserDto(); // Domain β DTO- π Multi-layer mapping chains: Entity β Domain β DTO
- π Bidirectional mapping: Single attribute generates both directions
- π¨ Automatic enum conversion: Enum types mapped automatically
- πͺ Nested object mapping: Address automatically chained
- π‘οΈ Null safety: Built-in null checks for nullable types
- ποΈ Base class property inheritance: Audit fields from base classes included automatically
π Full documentation: Working with Object Mapping
π§ Console app demonstrating pure configuration-based mapping for third-party types. Use this approach when you cannot place [MapTo] attributes on source types (e.g., third-party SDK types, NuGet package models, or legacy code you don't control).
dotnet run --project sample/Atc.SourceGenerators.MappingOnlyConfigurationsample/
Atc.SourceGenerators.MappingOnlyConfiguration/ # Console app (entry point)
Program.cs
Atc.SourceGenerators.MappingOnlyConfiguration.Domain/ # Domain models + mapping config
Customer.cs
Address.cs
CustomerCategory.cs
CrmMappings.cs # Configuration-based mappings
Atc.SourceGenerators.MappingOnlyConfiguration.ExternalCrm/ # Simulated 3rd-party SDK
Contact.cs
ContactAddress.cs
ContactType.cs
The [MappingConfiguration] class defines how external types map to domain types. Each method signature declares the mapping direction, and attributes control property renaming and exclusion:
using Atc.SourceGenerators.Annotations;
using Atc.SourceGenerators.MappingOnlyConfiguration.ExternalCrm;
namespace Atc.SourceGenerators.MappingOnlyConfiguration.Domain;
[MappingConfiguration]
public static partial class CrmMappings
{
// Maps CRM Contact to domain Customer with property renaming
[MapConfigProperty("FullName", "Name")]
[MapConfigProperty("EmailAddress", "Email")]
[MapConfigProperty("PhoneNumber", "Phone")]
[MapConfigProperty("PrimaryAddress", "Address")]
[MapConfigProperty("CreatedDate", "CreatedAt")]
[MapConfigProperty("ModifiedDate", "UpdatedAt")]
[MapConfigProperty("Type", "Category")]
[MapConfigIgnore("ContactId")]
[MapConfigIgnore("InternalTrackingCode")]
public static partial Customer MapToCustomer(this Contact source);
// Maps CRM ContactAddress to domain Address with ZipCode -> PostalCode rename
[MapConfigProperty("ZipCode", "PostalCode")]
public static partial Address MapToAddress(this ContactAddress source);
}π― Key points:
- π·οΈ
[MapConfigProperty("SourceProp", "TargetProp")]renames properties during mapping - π«
[MapConfigIgnore("SourceProp")]excludes properties from mapping (e.g., internal tracking fields) - β
Same-name properties (like
Street,City) are mapped automatically without any attribute - πͺ Nested mapping is automatic --
PrimaryAddresscallsMapToAddress()for theContactAddresstype - π¨ Enum values are auto-detected using the special case engine (
Nonemaps toUnknown, etc.)
- π« No attributes on source types: All mapping configuration lives in your domain project
- π·οΈ Property renaming:
[MapConfigProperty]handles different naming conventions between external and domain types - π Property exclusion:
[MapConfigIgnore]removes internal/irrelevant fields from external types - β Automatic same-name mapping: Properties with matching names require no configuration
- π Nested mapping chains:
ContactAddresstoAddressis automatically chained when mappingContacttoCustomer - π¨ Enum auto-detection:
ContactType.Nonemaps toCustomerCategory.Unknownautomatically
π Full documentation: Configuration-Based Mapping
π Console app demonstrating mixed attribute-based and configuration-based mapping working together. This sample shows how [MapTo] attributes for types you own can coexist with [MappingConfiguration] for external types, including multi-hop mapping chains that span both styles.
dotnet run --project sample/Atc.SourceGenerators.MappingCombinedConfigurationsample/
Atc.SourceGenerators.MappingCombinedConfiguration/ # Console app (entry point)
Program.cs
Atc.SourceGenerators.MappingCombinedConfiguration.Domain/ # Domain models (mixed mapping)
Notification.cs # Attribute-based [MapTo] for owned types
Urgency.cs # Attribute-based [MapTo] for enum
ActivityEvent.cs # Plain domain model (target of config mapping)
ActivitySeverity.cs # Domain enum
BrowsingSession.cs # Plain domain model (target of config mapping)
ExternalMappings.cs # Configuration-based mappings for external types
Atc.SourceGenerators.MappingCombinedConfiguration.Contract/ # API contracts
NotificationDto.cs
UrgencyLevel.cs
Atc.SourceGenerators.MappingCombinedConfiguration.ExternalNotifications/ # Simulated SDK
PushNotification.cs
NotificationPriority.cs
Atc.SourceGenerators.MappingCombinedConfiguration.ExternalAnalytics/ # Simulated SDK
AnalyticsEvent.cs
UserSession.cs
EventSeverity.cs
βοΈ Attribute-based for types you own (forward mapping to DTOs):
[MapTo(typeof(NotificationDto))]
public partial class Notification
{
public int Id { get; set; }
public string Recipient { get; set; } = string.Empty;
public string Title { get; set; } = string.Empty;
public string Message { get; set; } = string.Empty;
public Urgency Urgency { get; set; }
public DateTime SentAt { get; set; }
public bool Delivered { get; set; }
}
[MapTo(typeof(UrgencyLevel))]
public enum Urgency { Unknown, Low, Medium, High, Urgent }π§ Configuration-based for external types (inbound mapping from SDKs):
[MappingConfiguration]
public static partial class ExternalMappings
{
// PushNotification -> Notification (property renaming)
[MapConfigProperty("NotificationId", "Id")]
[MapConfigProperty("RecipientToken", "Recipient")]
[MapConfigProperty("Body", "Message")]
[MapConfigProperty("IsDelivered", "Delivered")]
[MapConfigProperty("Priority", "Urgency")]
public static partial Notification MapToNotification(this PushNotification source);
// AnalyticsEvent -> ActivityEvent (property renaming)
[MapConfigProperty("EventId", "Id")]
[MapConfigProperty("EventName", "Name")]
public static partial ActivityEvent MapToActivityEvent(this AnalyticsEvent source);
// UserSession -> BrowsingSession (record support)
[MapConfigProperty("SessionId", "Id")]
public static partial BrowsingSession MapToBrowsingSession(this UserSession source);
}The combined approach enables multi-hop chains that span both mapping styles:
PushNotification (External SDK)
β .MapToNotification() β configuration-based mapping
Notification (Domain)
β .MapToNotificationDto() β attribute-based mapping
NotificationDto (Contract)
// Chain: External SDK -> Domain -> DTO
var domainNotification = pushNotification.MapToNotification(); // config-based
var dto = domainNotification.MapToNotificationDto(); // attribute-based- π€ Both approaches coexist: Attribute-based
[MapTo]for owned types and[MappingConfiguration]for external types work together seamlessly - π Multi-hop chains: External SDK types flow through domain models to DTOs, crossing mapping style boundaries
- π¨ Enum auto-detection in both modes:
NotificationPrioritytoUrgency(config-based) andUrgencytoUrgencyLevel(attribute-based) both use the special case engine - π Record type support:
UserSessionrecord maps toBrowsingSessionclass including constructor parameter matching - π¦ Multiple external SDKs: Configuration-based mappings can target types from different external assemblies in a single
[MappingConfiguration]class
π Full documentation: Configuration-Based Mapping
π¨ Console app demonstrating intelligent enum-to-enum mapping with special case handling, bidirectional mappings, and case-insensitive matching.
cd sample/Atc.SourceGenerators.EnumMapping
dotnet runusing Atc.SourceGenerators.Annotations;
using Atc.Mapping;
// Special case: None β Unknown (auto-detected)
[MapTo(typeof(PetStatusDto), Bidirectional = true)]
public enum PetStatusEntity
{
None, // Maps to PetStatusDto.Unknown
Pending,
Available,
Adopted,
}
public enum PetStatusDto
{
Unknown, // Maps from PetStatusEntity.None
Available,
Pending,
Adopted,
}
// Usage
var entity = PetStatusEntity.None;
var dto = entity.MapToPetStatusDto(); // PetStatusDto.Unknown
var back = dto.MapToPetStatusEntity(); // PetStatusEntity.None (bidirectional!)- πͺ Special case detection: None β Unknown β Default auto-mapped
- π Bidirectional mapping: Both forward and reverse from one attribute
- π€ Case-insensitive matching: ACTIVE matches Active
β οΈ Compile-time warnings: ATCENUM002 for unmapped values- π‘οΈ Runtime safety: ArgumentOutOfRangeException for unmapped values
π Full documentation: Working with Enum Mapping
π·οΈ Console app demonstrating compile-time access to DataAnnotation metadata without reflection.
cd sample/Atc.SourceGenerators.AnnotationConstants
dotnet runπ Model with DataAnnotations:
using System.ComponentModel.DataAnnotations;
public class Product
{
[Display(Name = "Product Name", Description = "The display name of the product")]
[Required(ErrorMessage = "Product name is required")]
[StringLength(100, MinimumLength = 3)]
public string Name { get; set; } = string.Empty;
[Display(Name = "Price")]
[Required]
[Range(typeof(decimal), "0.01", "999999.99")]
public decimal Price { get; set; }
[Display(Name = "SKU")]
[StringLength(20)]
[RegularExpression(@"^[A-Z]{3}-\d{4}$")]
public string? Sku { get; set; }
}β‘ Access generated constants:
// Zero reflection - compile-time constants!
string label = AnnotationConstants.Product.Name.DisplayName; // "Product Name"
int maxLen = AnnotationConstants.Product.Name.MaximumLength; // 100
bool required = AnnotationConstants.Product.Name.IsRequired; // true
string minPrice = AnnotationConstants.Product.Price.Minimum; // "0.01"
string pattern = AnnotationConstants.Product.Sku.Pattern; // "^[A-Z]{3}-\d{4}$"- π₯οΈ Blazor/MAUI Forms: Generate form labels and validation rules without reflection
- π API Documentation: Extract constraint metadata for OpenAPI/Swagger
- π Client-Side Validation: Send validation rules to JavaScript/TypeScript
- β‘ Native AOT Applications: Access metadata without breaking trimming
π Full documentation: Working with Annotation Constants
π Full-featured ASP.NET Core application using all generators together with OpenAPI/Scalar documentation. This is the flagship example demonstrating production-ready patterns.
cd sample/PetStore.Api
dotnet run
# Open https://localhost:42616/scalar/v1 for API documentationπ See PetStore API Example for the complete walkthrough with architecture diagrams, code, and request flow sequences.
To inspect the source code that generators produce for any sample project, use the EmitCompilerGeneratedFiles MSBuild property:
dotnet build sample/PetStore.Domain -p:EmitCompilerGeneratedFiles=trueGenerated files appear under:
sample/PetStore.Domain/obj/Debug/net10.0/generated/
Atc.SourceGenerators/
Atc.SourceGenerators.Generators.DependencyRegistrationGenerator/
Atc.SourceGenerators.Generators.OptionsBindingGenerator/
Atc.SourceGenerators.Generators.ObjectMappingGenerator/
Atc.SourceGenerators.Generators.EnumMappingGenerator/
Each file contains the complete generated extension methods and registration code. This is useful for debugging mapping logic or understanding what the generator produces.
Tip: In Visual Studio, expand the project node in Solution Explorer under Dependencies > Analyzers > Atc.SourceGenerators to browse generated files directly.
π Home
- π Getting Started
- π Migration Guide
- π Working with Dependency Registration
- βοΈ Working with Options Binding
- πΊοΈ Working with Object Mapping
- π Working with Enum Mapping
- π Working with Annotation Constants
- π¦ Sample Projects
- πΎ PetStore API Example
- π Attribute API Reference
- π©Ί Diagnostics Reference
β οΈ Common Patterns and Anti-Patterns- π Native AOT Compatibility
- π§ Troubleshooting
- π Dependency Registration Feature Roadmap
- βοΈ Options Binding Feature Roadmap
- πΊοΈ Object Mapping Feature Roadmap
- π Annotation Constants Feature Roadmap
- π¦ GitHub Repository
- π₯ NuGet Package
- π Report Issues