XForge.AutoMapper.Testing
0.14.0
See the version list below for details.
dotnet add package XForge.AutoMapper.Testing --version 0.14.0
NuGet\Install-Package XForge.AutoMapper.Testing -Version 0.14.0
<PackageReference Include="XForge.AutoMapper.Testing" Version="0.14.0" />
<PackageVersion Include="XForge.AutoMapper.Testing" Version="0.14.0" />
<PackageReference Include="XForge.AutoMapper.Testing" />
paket add XForge.AutoMapper.Testing --version 0.14.0
#r "nuget: XForge.AutoMapper.Testing, 0.14.0"
#:package XForge.AutoMapper.Testing@0.14.0
#addin nuget:?package=XForge.AutoMapper.Testing&version=0.14.0
#tool nuget:?package=XForge.AutoMapper.Testing&version=0.14.0
XForge.AutoMapper
Source-generator-first .NET object mapping library — zero reflection, NativeAOT safe, Blazor WASM friendly.
Modern, fluent, lightweight object mapping library for .NET with source generators, compile-time diagnostics, LINQ/EF Core projection, value converters, hooks, conditional mapping, and NativeAOT compatibility. Ergonomic alternative to AutoMapper, Mapster, and Mapperly.
Why XForge.AutoMapper?
| Feature | AutoMapper | Mapster | Mapperly | XForge.AutoMapper |
|---|---|---|---|---|
| Source Generation | No | Partial | Yes | Yes (primary) |
| Zero Reflection (critical path) | No | Partial | Yes | Yes |
| NativeAOT Friendly | No | Partial | Yes | Yes |
| Trimming Safe | No | Partial | Yes | Yes |
| Blazor WASM | Slow | OK | Good | First-class |
| Compile-time Diagnostics | No | No | Yes | Yes (XAM prefix) |
| LINQ Projection | Yes | Yes | Limited | Yes |
| DI Integration | Yes | Manual | Manual | Yes (optional) |
| AutoMapper-style API | Yes | Yes | No | Yes ([Map<S,D>]) |
| Reverse Mapping | Yes | Yes | No | Yes ([MapReverse]) |
| Value Converters | Yes | Yes | Limited | Yes |
| Conditional Mapping | No | Yes | No | Yes ([MapWhen]) |
| Before/After Hooks | Yes | Yes | No | Yes |
| Init-only Properties | Yes | Yes | Yes | Yes |
| Record Support | Yes | Yes | Yes | Yes |
| Enum Mapping | Yes | Yes | Yes | Yes |
| Flattening | Yes | Yes | Yes | Yes |
| Inheritance | Yes | Yes | Yes | Yes |
| Update Existing | Yes | Yes | No | Yes |
| Cold Start | Slow | Fast | Fast | Near-zero |
Quick Start
1. Install
dotnet add package XForge.AutoMapper
dotnet add package XForge.AutoMapper.SourceGeneration
2. Define your types
public class Customer
{
public int Id { get; set; }
public string Name { get; set; } = "";
public string Email { get; set; } = "";
public Address Address { get; set; } = new();
}
public class CustomerDto
{
public int Id { get; set; }
public string Name { get; set; } = "";
public string Email { get; set; } = "";
public string AddressStreet { get; set; } = ""; // Auto-flattened
}
3. Create a mapper
Option A: Explicit partial methods (full control)
using XForge.AutoMapper;
[Mapper]
public partial class CustomerMapper
{
public partial CustomerDto ToDto(Customer source);
}
Option B: AutoMapper-style (class-level declarations)
using XForge.AutoMapper;
[Mapper]
[Map(typeof(Customer), typeof(CustomerDto))]
[Map(typeof(Order), typeof(OrderDto))]
public partial class AppMapper { }
4. Use it
// Explicit partial methods:
var mapper = new CustomerMapper();
var dto = mapper.ToDto(customer);
// AutoMapper-style (non-generic IMapper):
var appMapper = new AppMapper();
CustomerDto dto2 = appMapper.Map<CustomerDto>(customer);
// Via strongly-typed interface:
IMapper<Customer, CustomerDto> iMapper = appMapper;
CustomerDto dto3 = iMapper.Map(customer);
// Update existing instance:
var existingDto = new CustomerDto();
mapper.ToDto(customer, existingDto);
Features
Property Mapping
Properties with the same name and compatible types are mapped automatically:
[Mapper]
public partial class OrderMapper
{
public partial OrderDto ToDto(Order source);
}
AutoMapper-style API ([Map<S,D>])
Declare mappings at the class level — no partial methods needed:
[Mapper]
[Map(typeof(Customer), typeof(CustomerDto))]
[Map(typeof(Order), typeof(OrderDto))]
public partial class AppMapper { }
// Usage:
var mapper = new AppMapper();
var dto = mapper.Map<CustomerDto>(customer);
The non-generic IMapper interface uses source-generated pattern matching dispatch — zero reflection, AOT-safe.
Custom Property Mappings on [Map]
Use class-level [MapProperty] when property names differ:
[Mapper]
[Map(typeof(Customer), typeof(CustomerDto))]
[MapProperty(typeof(Customer), typeof(CustomerDto), "FullName", "Name")]
[MapProperty(typeof(Customer), typeof(CustomerDto), "YearsOld", "Age")]
public partial class CustomerMapper { }
Auto-generated Reverse Mapping
Set GenerateReverse = true to automatically generate a reverse mapping:
[Mapper]
[Map(typeof(Customer), typeof(CustomerDto), GenerateReverse = true)]
public partial class CustomerMapper { }
// Both directions are available:
var mapper = new CustomerMapper();
CustomerDto dto = mapper.MapCustomerToCustomerDto(customer);
Customer entity = mapper.MapCustomerDtoToCustomer(dto);
Auto-generated methods support all property-level attributes: [IgnoreMap], [ValueConverter], [MapWhen], [NullSubstitute], and flattening.
Custom Mapping ([MapProperty])
Map properties with different names:
[Mapper]
public partial class CustomerMapper
{
[MapProperty(nameof(Customer.FullName), nameof(CustomerDto.Name))]
[MapProperty(nameof(Customer.Address.City), nameof(CustomerDto.City))]
public partial CustomerDto ToDto(Customer source);
}
Ignore Properties ([IgnoreMap])
Skip specific destination properties:
public class CustomerDto
{
public string Name { get; set; } = "";
[IgnoreMap]
public string InternalCode { get; set; } = "";
}
Value Converters ([ValueConverter])
Transform values during mapping:
public class ToUpperConverter : IValueConverter<string, string>
{
public string Convert(string value) => value.ToUpperInvariant();
}
public class CustomerDto
{
[ValueConverter(typeof(ToUpperConverter))]
public string Name { get; set; } = "";
}
Conditional Mapping ([MapWhen])
Map a property only when a source property is non-null:
public class CustomerDto
{
[MapWhen("Email")]
public string Email { get; set; } = "";
}
Null Substitution ([NullSubstitute])
Provide fallback values for null source properties:
public class CustomerDto
{
[NullSubstitute("Unknown")]
public string Name { get; set; } = "";
}
Reverse Mapping ([MapReverse])
Declare bidirectional mappings:
[Mapper]
public partial class CustomerMapper
{
public partial CustomerDto ToDto(Customer source);
[MapReverse(nameof(ToDto))]
public partial Customer ToEntity(CustomerDto source);
}
Before/After Hooks
Add side effects before or after mapping:
[Mapper]
public partial class CustomerMapper
{
public partial CustomerDto ToDto(Customer source);
[BeforeMap]
private void OnBeforeMap(Customer source) { /* audit, logging */ }
[AfterMap]
private void OnAfterMap(CustomerDto dest) { /* post-processing */ }
}
Constructor Mapping ([MapConstructor])
Use a specific constructor for destination creation:
public class OrderDto
{
[MapConstructor]
public OrderDto(string orderId, decimal total) { ... }
public string OrderId { get; }
public decimal Total { get; }
}
Record Support
Records with positional parameters are automatically mapped via constructor:
public record ProductDto(int Id, string Name, decimal Price);
[Mapper]
public partial class ProductMapper
{
public partial ProductDto ToDto(Product source);
}
Init-only Properties
Init-only properties are set via object initializer syntax:
public class CustomerDto
{
public string Name { get; init; } = "";
public int Age { get; set; }
}
Collection Mapping
Collection properties are automatically iterated and mapped:
public class OrderDto
{
public List<OrderItemDto> Items { get; set; } = new();
}
Enum Mapping
Different enum types are mapped via cast:
public enum SourceStatus { Active, Inactive }
public enum DestStatus { Active, Inactive }
// Generated: destination.Status = (DestStatus)source.Status;
Flattening
Nested properties are auto-flattened using PascalCase prefix matching:
public class Customer
{
public Address Address { get; set; } = new();
}
public class CustomerDto
{
public string AddressStreet { get; set; } = ""; // → source.Address.Street
public string AddressCity { get; set; } = ""; // → source.Address.City
}
Inheritance
Properties from base classes are included automatically:
public class BaseEntity
{
public int Id { get; set; }
public DateTime CreatedAt { get; set; }
}
public class Customer : BaseEntity
{
public string Name { get; set; } = "";
}
// Id, CreatedAt, and Name are all mapped
LINQ Projection (ProjectTo)
Generate expression trees for IQueryable projection:
using XForge.AutoMapper.Queryable;
[Mapper]
public partial class CustomerMapper
{
public partial CustomerDto ToDto(Customer source);
}
// Usage with EF Core:
var dtos = dbContext.Customers
.Where(c => c.IsActive)
.Select(CustomerMapper.ToDtoExpression)
.ToListAsync();
Dependency Injection
Register mappers in IServiceCollection:
dotnet add package XForge.AutoMapper.Extensions.DependencyInjection
using XForge.AutoMapper.Extensions.DependencyInjection;
services.AddXForgeAutoMapper(typeof(CustomerMapper).Assembly);
// Resolve via DI:
var mapper = serviceProvider.GetRequiredService<IMapper<Customer, CustomerDto>>();
// Non-generic IMapper (for AutoMapper-style usage):
var iMapper = serviceProvider.GetRequiredService<IMapper>();
var dto = iMapper.Map<CustomerDto>(customer);
Testing Helpers
dotnet add package XForge.AutoMapper.Testing
using XForge.AutoMapper.Testing;
var mapper = new CustomerMapper();
var source = new Customer { Name = "Test" };
// Assert all matching properties are correctly mapped:
MapperAssert.AssertMapping(mapper, source);
// Assert with ignored properties:
MapperAssert.AssertMapping(mapper, source, "ComputedField");
Update Existing Destination
The generator emits an overload that updates an existing instance:
var existing = new CustomerDto { Age = 0 };
mapper.ToDto(customer, existing); // Updates existing instance, skips init-only props
Diagnostics
XForge.AutoMapper emits compile-time diagnostics with the XAM prefix:
| ID | Severity | Description |
|---|---|---|
| XAM001 | Warning | Target property has no matching source property |
| XAM002 | Info | Source property is not mapped to any target property |
| XAM003 | Warning | Cyclic mapping detected (A→B and B→A in same mapper) |
| XAM004 | Error | Invalid value converter type |
| XAM005 | Error | Ambiguous mapping hook (MapMethodName required) |
| XAM006 | Error | Invalid [MapWhen] source property |
| XAM007 | Error | Invalid [MapReverse] method reference |
| XAM008 | Error | [Map] attribute conflicts with existing partial method |
| XAM010 | Error | [Mapper] class must be partial |
| XAM011 | Error | Mapper method must be partial |
| XAM012 | Warning | Duplicate [MapProperty] target |
Architecture
XForge.AutoMapper.Abstractions → Contracts & attributes (netstandard2.0)
XForge.AutoMapper → Minimal runtime (net8.0/9.0/10.0)
XForge.AutoMapper.SourceGeneration → Roslyn incremental source generator
XForge.AutoMapper.Analyzers → Roslyn analyzers & code fixes
XForge.AutoMapper.Queryable → IQueryable projection support
XForge.AutoMapper.Extensions.DependencyInjection → IServiceCollection integration
XForge.AutoMapper.Testing → Testing helpers
XForge.AutoMapper.Benchmarks → BenchmarkDotNet performance suite
Benchmarks
Run benchmarks locally:
cd benchmarks/XForge.AutoMapper.Benchmarks
dotnet run -c Release -- --filter "*"
Available benchmarks:
SimpleClassBenchmark— Simple flat class mappingNestedObjectBenchmark— Nested object mappingCollectionBenchmark— Collection (50 items) mappingRecordBenchmark— Record constructor mappingEnumBenchmark— Enum type mappingFlatteningBenchmark— Nested-to-flat property mappingInheritanceBenchmark— Base class property mappingUpdateDestinationBenchmark— Update existing instanceColdStartBenchmark— First-run overhead comparison
Requirements
- .NET 8.0, 9.0, or 10.0
- C# 12+
License
MIT
Contributing
See CONTRIBUTING.md for development setup and guidelines.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- XForge.AutoMapper (>= 0.14.0)
- XForge.AutoMapper.Abstractions (>= 0.14.0)
-
net8.0
- XForge.AutoMapper (>= 0.14.0)
- XForge.AutoMapper.Abstractions (>= 0.14.0)
-
net9.0
- XForge.AutoMapper (>= 0.14.0)
- XForge.AutoMapper.Abstractions (>= 0.14.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.