If you missed the earlier posts on Twinify: Twinify is a MIT-licensed object mapper for .NET 8, 9, and 10. You call mapper.Map<UserDto>(user) and it copies matching members. Profiles still exist when names are not enough.
1.0.2 was about speed. 1.1.0 is about less boilerplate, safer DTOs, and Native AOT.
dotnet add package Twinify
dotnet add package Twinify.DependencyInjection
NuGet: Twinify
The idea
You still inject IMapper and you still call Map. Existing profile-only apps keep the same behaviour. Everything below is additive.
1. Mapping without a profile
Matching names used to still need CreateMap. Not anymore.
builder.Services.AddTwinify();
public class UserService(IMapper mapper)
{
public UserDto ToDto(User user) => mapper.Map<UserDto>(user);
}
No CreateMap. Twinify copies same-name members, flattens Address.City → AddressCity, and walks nested objects and collections.
No DI? Same thing:
var mapper = new Mapper();
var dto = mapper.Map<UserDto>(user);
2. Keep your profiles. Auto-map the rest.
Registering a profile is still classic Twinify: only declared top-level pairs map.
Want both? One flag:
builder.Services.AddTwinify(cfg => cfg.AddProfile<UserProfile>())
.AllowMappingWithoutProfiles();
CreateMap always wins for that pair. Nested types inside an explicit map still auto-map — you do not need CreateMap<Address, AddressDto>() just because User has an Address.
3. Attributes instead of another ForMember
Hide secrets
public class UserDto
{
public int Id { get; set; }
public string Name { get; set; } = "";
[IgnoreMap]
public string? Password { get; set; }
}
Rename or flatten without a profile
public class UserDto
{
[MapFrom("Address.City")]
public string Town { get; set; } = "";
}
Pick a constructor when the destination has more than one: [MapConstructor].
Need to turn attributes off? UseMappingAttributes(false).
4. Native AOT: a source generator in the same package
JIT apps still compile maps at runtime. Native AOT cannot do that.
The Twinify package now ships Twinify.SourceGenerator. Mark the pair; Twinify emits a static, reflection-free map at compile time.
[Twinify(typeof(User))]
public class UserDto
{
public int Id { get; set; }
public string Name { get; set; } = "";
}
Or at assembly scope:
[assembly: TwinifyMap(typeof(User), typeof(UserDto))]
Uncustomized CreateMap in a profile constructor can be generated too. Nested convention-compatible members of a generated pair are included.
Honest limitation: fluent ForMember / ConvertUsing are not generated. Prefer [MapFrom] / [IgnoreMap] on AOT, or keep those pairs on JIT. A pair with no generated map throws MappingNotFoundException on Native AOT.
That is the 1.0.2 “what’s next” item, shipped.
5. Records, in-place map, fail at boot
Records bind constructor parameters by name. Extra settable properties still copy after construction.
public record Person(int Id, string Name);
public record PersonDto(int Id, string Name);
var dto = mapper.Map<PersonDto>(new Person(1, "Ada"));
Update an existing instance:
mapper.Map(user, existingDto);
Catch bad maps at startup instead of on the first request:
builder.Services.AddTwinify(cfg => cfg.AddProfile<UserProfile>())
.ValidateMappings();
Still stuck? mapper.Explain<User, UserDto>() shows where each destination member gets its value.
Upgrade path
| You have | You do |
|---|---|
| Profile-only 1.0.x app | Update the package. Behaviour stays the same. |
Tired of CreateMap for identical DTOs |
AllowMappingWithoutProfiles() or drop profiles for those pairs. |
| Native AOT / trimming | Add [Twinify] or [TwinifyMap] and prefer attributes over fluent customizations. |
IMapper and Map<TDestination>(source) did not change.
Try it
dotnet add package Twinify --version 1.1.0
dotnet add package Twinify.DependencyInjection --version 1.1.0
- NuGet: https://www.nuget.org/packages/Twinify
- Release notes: Twinify 1.1.0
If you map DTOs in ASP.NET Core, workers, or AOT apps, I would love a try, a star, or a comment about the pair that still feels too noisy.
Twinify — object mapping without the ceremony.
Top comments (0)