Skip to content
Najaf Shaikh edited this page Oct 11, 2025 · 3 revisions

TurboMapper - Complete Guide

Table of Contents

  1. Introduction
  2. Getting Started
  3. Basic Usage
  4. Advanced Features
  5. Mapping Modules
  6. Dependency Injection
  7. Performance Considerations
  8. API Reference
  9. Best Practices
  10. Troubleshooting

Introduction

TurboMapper is a lightweight, high-performance object mapper for .NET that provides both shallow and deep mapping capabilities. It serves as a free alternative to AutoMapper with a simple, intuitive API.

Key Features

  • Automatic Property Mapping: Maps properties by name automatically
  • Custom Property Mapping: Define explicit mappings between different property names
  • Nested Object Support: Handles deep object hierarchies seamlessly
  • Type Conversion: Automatic conversion between compatible types
  • Mapping Modules: Organize mappings in reusable modules
  • Dependency Injection: First-class support for Microsoft.Extensions.DependencyInjection
  • Thread-Safe: Safe for concurrent operations
  • Multi-Framework Support: Compatible with .NET Framework 4.6.2, .NET Standard 2.0/2.1, and .NET 9.0

Installation

dotnet add package TurboMapper

Getting Started

Quick Start Example

using TurboMapper;
using Microsoft.Extensions.DependencyInjection;

// Setup
var services = new ServiceCollection();
services.AddTurboMapper();
var serviceProvider = services.BuildServiceProvider();
var mapper = serviceProvider.GetService<IMapper>();

// Define models
public class Source
{
    public string Name { get; set; }
    public int Age { get; set; }
}

public class Target
{
    public string Name { get; set; }
    public int Age { get; set; }
}

// Map objects
var source = new Source { Name = "John Doe", Age = 30 };
var target = mapper.Map<Source, Target>(source);

Basic Usage

Default Name-Based Mapping

TurboMapper automatically maps properties with matching names:

public class UserDTO
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public int Age { get; set; }
}

public class UserEntity
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public int Age { get; set; }
}

var dto = new UserDTO 
{ 
    FirstName = "Jane", 
    LastName = "Smith", 
    Age = 28 
};

var entity = mapper.Map<UserDTO, UserEntity>(dto);
// entity.FirstName = "Jane"
// entity.LastName = "Smith"
// entity.Age = 28

Nested Object Mapping

TurboMapper automatically handles nested objects:

public class Address
{
    public string Street { get; set; }
    public string City { get; set; }
    public string ZipCode { get; set; }
}

public class Person
{
    public string Name { get; set; }
    public Address Address { get; set; }
}

var source = new Person
{
    Name = "John",
    Address = new Address
    {
        Street = "123 Main St",
        City = "Boston",
        ZipCode = "02101"
    }
};

var target = mapper.Map<Person, Person>(source);
// All nested properties are mapped automatically

Type Conversion

TurboMapper handles common type conversions:

public class Source
{
    public int Age { get; set; }
    public string Status { get; set; }
}

public class Target
{
    public string Age { get; set; }  // int to string
    public StatusEnum Status { get; set; }  // string to enum
}

public enum StatusEnum { Active, Inactive }

var source = new Source { Age = 25, Status = "Active" };
var target = mapper.Map<Source, Target>(source);
// target.Age = "25"
// target.Status = StatusEnum.Active

Null Handling

TurboMapper gracefully handles null values:

// Null source returns null
Source source = null;
var target = mapper.Map<Source, Target>(source); // returns null

// Null nested objects are preserved
var person = new Person { Name = "John", Address = null };
var mapped = mapper.Map<Person, Person>(person);
// mapped.Address is null

Advanced Features

Explicit Property Mapping

Configure custom mappings without modules:

using TurboMapper.Impl;

var mapper = new Mapper();
var mappings = new List<PropertyMapping>
{
    new PropertyMapping
    {
        SourcePropertyPath = "FirstName",
        TargetPropertyPath = "FullName"
    },
    new PropertyMapping
    {
        SourcePropertyPath = "Age",
        TargetPropertyPath = "Years"
    }
};

mapper.CreateMap<UserSource, UserTarget>(mappings);

var source = new UserSource { FirstName = "John", Age = 30 };
var target = mapper.Map<UserSource, UserTarget>(source);
// target.FullName = "John"
// target.Years = 30

Nested Property Flattening

Map nested properties to flat structure:

public class Source
{
    public string Name { get; set; }
    public Address Address { get; set; }
}

public class FlatTarget
{
    public string Name { get; set; }
    public string Street { get; set; }
    public string City { get; set; }
}

var mappings = new List<PropertyMapping>
{
    new PropertyMapping
    {
        SourcePropertyPath = "Address.Street",
        TargetPropertyPath = "Street"
    },
    new PropertyMapping
    {
        SourcePropertyPath = "Address.City",
        TargetPropertyPath = "City"
    }
};

mapper.CreateMap<Source, FlatTarget>(mappings);

Nested Property Mapping

Map flat structure to nested objects:

public class TargetWithNested
{
    public string Name { get; set; }
    public AddressConfig Address { get; set; }
}

public class AddressConfig
{
    public string StreetName { get; set; }
    public string Location { get; set; }
}

var mappings = new List<PropertyMapping>
{
    new PropertyMapping
    {
        SourcePropertyPath = "Address.Street",
        TargetPropertyPath = "Address.StreetName"
    },
    new PropertyMapping
    {
        SourcePropertyPath = "Address.City",
        TargetPropertyPath = "Address.Location"
    }
};

mapper.CreateMap<Source, TargetWithNested>(mappings);

Disabling Default Mapping

Control which properties get mapped:

var mappings = new List<PropertyMapping>
{
    new PropertyMapping
    {
        SourcePropertyPath = "FirstName",
        TargetPropertyPath = "Name"
    }
};

// Only map explicitly defined properties
mapper.CreateMap<Source, Target>(mappings, enableDefaultMapping: false);

Release 1.2.0: Collection Mapping Support

Map collections of objects using the updated Map method that now returns IEnumerable:

// Define your models
public class User
{
    public string Name { get; set; }
    public int Age { get; set; }
}

public class UserDto
{
    public string Name { get; set; }
    public int Age { get; set; }
}

// Create mapper instance
var mapper = serviceProvider.GetService<IMapper>();

// Map collections - the Map method now returns IEnumerable<TDestination>
var users = new List<User>
{
    new User { Name = "Alice", Age = 25 },
    new User { Name = "Bob", Age = 32 }
};

// Map to IEnumerable<T>
IEnumerable<UserDto> userDtos = mapper.Map<User, UserDto>(users);

// Convert to List if needed
List<UserDto> userList = userDtos.ToList();

// Works with any IEnumerable
IEnumerable<User> source = users;
IEnumerable<UserDto> result = mapper.Map<User, UserDto>(source);

// Convert to array if needed
UserDto[] userArray = result.ToArray();

Release 1.2.0: Ignored Properties

Skip specific properties during mapping:

// Using mapping modules
internal class UserModule : MappingModule<UserEntity, UserDto>
{
    public UserModule() : base(true) { }

    public override Action<IMappingExpression<UserEntity, UserDto>> CreateMappings()
    {
        return config =>
        {
            // Ignore sensitive properties
            config.ForMember(dest => dest.Name, src => src.Name)
                  .ForMember(dest => dest.Email, src => src.Email)
                  .Ignore(dest => dest.Id); // Skip ID mapping
        };
    }
}

// Using IMappingExpression directly
var expression = new MappingExpression<UserEntity, UserDto>();
expression.ForMember(dest => dest.Name, src => src.Name)
         .Ignore(dest => dest.Password) // Skip password mapping
         .Ignore(dest => dest.CreatedAt); // Skip timestamp

Release 1.2.0: Custom Type Converters

Register custom type conversion functions within mapping modules:

// Define a mapping module that registers converters
internal class OrderMappingModule : MappingModule<OrderEntity, OrderDto>
{
    public OrderMappingModule() : base(true)
    {
        // Register converters within the module
        RegisterConverter<string, Guid>(str => Guid.Parse(str));
        RegisterConverter<string, DateTime>(str => DateTime.Parse(str));
        RegisterConverter<DateTime, string>(dt => dt.ToString("yyyy-MM-dd"));
    }

    public override Action<IMappingExpression<OrderEntity, OrderDto>> CreateMappings()
    {
        return config =>
        {
            // Define any custom property mappings if needed
            // Otherwise, default mapping will apply with custom converters available
        };
    }
}

// The custom converters will be registered when the module is loaded
// Use the custom converters in mapping
public class OrderEntity
{
    public string OrderId { get; set; }
    public string OrderDate { get; set; }
}

public class OrderDto 
{
    public Guid OrderId { get; set; }
    public DateTime OrderDate { get; set; }
}

var entity = new OrderEntity 
{ 
    OrderId = "12345678-1234-1234-1234-123456789012", 
    OrderDate = "2024-01-15" 
};

var dto = mapper.Map<OrderEntity, OrderDto>(entity);
// Custom converters automatically convert string to Guid and string to DateTime

Release 1.2.0: Conditional Mapping

Map properties based on conditions:

// Using mapping modules
internal class ConditionalUserModule : MappingModule<UserEntity, UserDto>
{
    public ConditionalUserModule() : base(true) { }

    public override Action<IMappingExpression<UserEntity, UserDto>> CreateMappings()
    {
        return config =>
        {
            // Only map premium features if user is premium
            config.ForMember(dest => dest.Name, src => src.Name)
                  .When(dest => dest.PremiumFeatures, src => src.IsPremium)
                  .When(dest => dest.AccountLevel, src => src.Age > 18);
        };
    }
}

// Using IMappingExpression directly
var expression = new MappingExpression<UserEntity, UserDto>();
expression.ForMember(dest => dest.Name, src => src.Name)
         .When(dest => dest.Status, src => src.Age >= 18) // Map status only if adult
         .When(dest => dest.PremiumFeatures, src => src.IsPremium); // Map premium features conditionally

// Example models for conditional mapping
public class UserEntity
{
    public string Name { get; set; }
    public int Age { get; set; }
    public bool IsPremium { get; set; }
}

public class UserDto
{
    public string Name { get; set; }
    public string Status { get; set; }  // Only mapped if Age >= 18
    public List<string> PremiumFeatures { get; set; } = new List<string>(); // Only mapped if IsPremium
}

Release 1.2.0: Mapping Transformations

Transform values during mapping:

// Using mapping modules
internal class TransformModule : MappingModule<UserEntity, UserDto>
{
    public TransformModule() : base(true) { }

    public override Action<IMappingExpression<UserEntity, UserDto>> CreateMappings()
    {
        return config =>
        {
            config.ForMember(dest => dest.Name, src => src.Name)
                  .MapWith<UserEntity, string>(dest => dest.Name, src => $"Mr./Ms. {src.Name}")
                  .MapWith<UserEntity, string>(dest => dest.Email, src => src.Email.ToLower())
                  .MapWith<UserEntity, int>(dest => dest.Age, src => src.Age + 1); // Add 1 to age
        };
    }
}

// Using IMappingExpression directly
var expression = new MappingExpression<UserEntity, UserDto>();
expression.ForMember(dest => dest.Name, src => src.Name)
         .MapWith<UserEntity, string>(dest => dest.Greeting, src => $"Hello, {src.Name}")
         .MapWith<UserEntity, string>(dest => dest.Slug, src => src.Name.Replace(" ", "-").ToLower());

// Example: Transform phone numbers to masked format
public class ContactEntity
{
    public string Name { get; set; }
    public string Phone { get; set; }  // e.g., "123-456-7890"
}

public class ContactDto
{
    public string Name { get; set; }
    public string Phone { get; set; }  // e.g., "###-###-7890"
}

// Module to mask phone numbers
internal class ContactModule : MappingModule<ContactEntity, ContactDto>
{
    public override Action<IMappingExpression<ContactEntity, ContactDto>> CreateMappings()
    {
        return config =>
        {
            config.ForMember(dest => dest.Name, src => src.Name)
                  .MapWith<ContactEntity, string>(dest => dest.Phone, src => 
                      string.IsNullOrEmpty(src.Phone) ? src.Phone : 
                      $"###-###-{src.Phone.Substring(src.Phone.Length - 4)}");
        };
    }
}

Release 1.2.0: Configuration Validation

Validate mapping configurations at runtime:

// Get mapper instance
var mapper = serviceProvider.GetService<IMapper>();

// Create a mapping configuration
mapper.CreateMap<UserEntity, UserDto>(new List<PropertyMapping>
{
    new PropertyMapping
    {
        SourcePropertyPath = "Name",
        TargetPropertyPath = "FullName"
    }
});

// Validate the configuration before using it
var validationResult = mapper.ValidateMapping<UserEntity, UserDto>();
if (!validationResult.IsValid)
{
    foreach (var error in validationResult.Errors)
    {
        Console.WriteLine($"Mapping error: {error}");
    }
}

// Example: Validating in application startup
public void ConfigureServices(IServiceCollection services)
{
    services.AddTurboMapper();
    
    var mapper = services.BuildServiceProvider().GetService<IMapper>();
    
    // Validate critical mappings
    var validationResult = mapper.ValidateMapping<OrderEntity, OrderDto>();
    if (!validationResult.IsValid)
    {
        throw new InvalidOperationException($"Invalid order mapping: {string.Join(", ", validationResult.Errors)}");
    }
}

Release 1.2.0: Improved Nullable Type Handling

TurboMapper now handles nullable to non-nullable type conversions seamlessly:

public class Source
{
    public string Name { get; set; }
    public int? Age { get; set; }
    public DateTime? BirthDate { get; set; }
}

public class Target
{
    public string Name { get; set; }
    public int Age { get; set; }  // Non-nullable
    public DateTime BirthDate { get; set; }  // Non-nullable
}

var source = new Source 
{ 
    Name = "John", 
    Age = 30,  // Has value
    BirthDate = null  // Is null
};

var target = mapper.Map<Source, Target>(source);
// target.Name = "John"
// target.Age = 30
// target.BirthDate = DateTime.MinValue (default value for DateTime)

Mapping Modules

Mapping modules provide a clean, reusable way to organize mapping configurations.

Creating a Mapping Module

using TurboMapper;
using System;

internal class UserMappingModule : MappingModule<UserSource, UserTarget>
{
    public UserMappingModule() : base(enableDefaultMapping: true)
    {
    }

    public override Action<IMappingExpression<UserSource, UserTarget>> CreateMappings()
    {
        return config =>
        {
            config.ForMember(dest => dest.Name, src => src.FirstName)
                  .ForMember(dest => dest.Years, src => src.Age);
        };
    }
}

Module with Nested Mappings

internal class AddressMappingModule : MappingModule<AddressSource, AddressTarget>
{
    public AddressMappingModule() : base(enableDefaultMapping: true)
    {
    }

    public override Action<IMappingExpression<AddressSource, AddressTarget>> CreateMappings()
    {
        return config =>
        {
            config.ForMember(dest => dest.Address.Street, src => src.Address.StreetName)
                  .ForMember(dest => dest.Address.Location, src => src.Address.City);
        };
    }
}

Module Without Default Mapping

When you want complete control over what gets mapped:

internal class StrictMappingModule : MappingModule<Source, Target>
{
    public StrictMappingModule() : base(enableDefaultMapping: false)
    {
    }

    public override Action<IMappingExpression<Source, Target>> CreateMappings()
    {
        return config =>
        {
            config.ForMember(dest => dest.Name, src => src.FirstName)
                  .ForMember(dest => dest.Years, src => src.Age);
            // Only these properties will be mapped
        };
    }
}

Module Auto-Discovery

Mapping modules are automatically discovered and registered when using dependency injection:

services.AddTurboMapper();
// All mapping modules in loaded assemblies are automatically registered

Dependency Injection

Registration

using Microsoft.Extensions.DependencyInjection;
using TurboMapper;

var services = new ServiceCollection();
services.AddTurboMapper();
var serviceProvider = services.BuildServiceProvider();

Singleton Behavior

The mapper is registered as a singleton and thread-safe:

var mapper1 = serviceProvider.GetService<IMapper>();
var mapper2 = serviceProvider.GetService<IMapper>();
// mapper1 and mapper2 are the same instance

Using in ASP.NET Core

// Startup.cs or Program.cs
public void ConfigureServices(IServiceCollection services)
{
    services.AddTurboMapper();
    services.AddControllers();
}

// In a controller
public class UserController : ControllerBase
{
    private readonly IMapper _mapper;

    public UserController(IMapper mapper)
    {
        _mapper = mapper;
    }

    [HttpPost]
    public IActionResult CreateUser(UserDTO dto)
    {
        var entity = _mapper.Map<UserDTO, UserEntity>(dto);
        // Save entity
        return Ok();
    }
}

Scoped Services

The mapper works correctly across service scopes:

using (var scope1 = serviceProvider.CreateScope())
using (var scope2 = serviceProvider.CreateScope())
{
    var mapper1 = scope1.ServiceProvider.GetService<IMapper>();
    var mapper2 = scope2.ServiceProvider.GetService<IMapper>();
    // Both resolve to the same singleton instance
}

Performance Considerations

Thread Safety

TurboMapper is fully thread-safe for concurrent mapping operations:

var sources = GetLargeDataSet();
var results = new ConcurrentBag<Target>();

Parallel.ForEach(sources, source =>
{
    var target = mapper.Map<Source, Target>(source);
    results.Add(target);
});

Batch Processing

For large datasets, process in batches:

var sources = GetLargeDataSet(); // 10,000 items
var targets = new List<Target>(sources.Count);

foreach (var source in sources)
{
    targets.Add(mapper.Map<Source, Target>(source));
}

Reusing Mapper Instance

Always reuse the same mapper instance (singleton pattern):

// Good - Reuse singleton
var mapper = serviceProvider.GetService<IMapper>();
for (int i = 0; i < 1000; i++)
{
    var result = mapper.Map<Source, Target>(sources[i]);
}

// Bad - Creating new instances
for (int i = 0; i < 1000; i++)
{
    var mapper = new Mapper(); // Don't do this
    var result = mapper.Map<Source, Target>(sources[i]);
}

Configuration Caching

Mapping configurations are cached internally, so repeated mappings are efficient:

mapper.CreateMap<Source, Target>(mappings);

// Subsequent calls use cached configuration
for (int i = 0; i < 10000; i++)
{
    mapper.Map<Source, Target>(sources[i]); // Fast
}

API Reference

IMapper Interface

public interface IMapper
{
    TTarget Map<TSource, TTarget>(TSource source);
    IEnumerable<TDestination> Map<TSource, TDestination>(IEnumerable<TSource> source);
}

Map Method (Single Object)

  • Parameters:
    • TSource source - The source object to map from
  • Returns: TTarget - The mapped target object
  • Returns null if: Source is null

Map Method (Collection)

  • Parameters:
    • IEnumerable<TSource> source - The collection of source objects to map from
  • Returns: IEnumerable<TDestination> - The mapped collection of target objects
  • Returns null if: Source is null

IMappingExpression Interface

public interface IMappingExpression<TSource, TTarget>
{
    IMappingExpression<TSource, TTarget> ForMember<TValue>(
        Expression<Func<TTarget, TValue>> targetMember, 
        Expression<Func<TSource, TValue>> sourceMember);
}

ForMember Method

  • Configures custom mapping between properties
  • Supports nested property paths
  • Returns the expression for fluent chaining

MappingModule Abstract Class

public abstract class MappingModule<TSource, TTarget>
{
    protected MappingModule(bool enableDefaultMapping = true);
    
    public abstract Action<IMappingExpression<TSource, TTarget>> CreateMappings();
}

Constructor Parameters

  • enableDefaultMapping - When true, unmapped properties use name-based mapping

ServiceCollectionExtensions

public static class ServiceCollectionExtensions
{
    public static IServiceCollection AddTurboMapper(
        this IServiceCollection services);
}

AddTurboMapper Method

  • Registers IMapper as singleton
  • Auto-discovers and registers mapping modules
  • Returns IServiceCollection for chaining

PropertyMapping Class

internal class PropertyMapping
{
    public string SourcePropertyPath { get; set; }
    public string TargetPropertyPath { get; set; }
}

Best Practices

1. Use Dependency Injection

Always register TurboMapper with DI for automatic module discovery:

services.AddTurboMapper();

2. Organize with Mapping Modules

Group related mappings in modules:

// UserMappings.cs
internal class UserDTOMappingModule : MappingModule<UserDTO, UserEntity>
{
    // ...
}

internal class UserEntityMappingModule : MappingModule<UserEntity, UserDTO>
{
    // ...
}

3. Enable Default Mapping

Unless you need strict control, enable default mapping:

public MyMappingModule() : base(enableDefaultMapping: true)
{
    // Only specify differences from default
}

4. Handle Null Values

Always check for null in business logic when needed:

var entity = mapper.Map<DTO, Entity>(dto);
if (entity?.Address == null)
{
    entity.Address = new Address();
}

5. Use Type-Safe Expressions

Leverage lambda expressions for compile-time safety:

config.ForMember(dest => dest.FullName, src => src.FirstName)
      .ForMember(dest => dest.Years, src => src.Age);

6. Test Your Mappings

Always unit test custom mappings:

[Test]
public void Map_UserDTOToEntity_MapsCorrectly()
{
    var dto = new UserDTO { FirstName = "John", Age = 30 };
    var entity = mapper.Map<UserDTO, UserEntity>(dto);
    
    Assert.AreEqual("John", entity.Name);
    Assert.AreEqual(30, entity.Years);
}

7. Avoid Deep Nesting

Keep object hierarchies reasonable for performance:

// Good - 2-3 levels
User.Address.City

// Potentially slow - 5+ levels
User.Profile.Settings.Preferences.Display.Theme

8. Reuse Mapping Configurations

Don't create mappings repeatedly:

// Good - Create once
mapper.CreateMap<Source, Target>(mappings);

// Bad - Inside loop
for (int i = 0; i < 1000; i++)
{
    mapper.CreateMap<Source, Target>(mappings); // Don't do this
}

Troubleshooting

Common Issues

Properties Not Mapping

Problem: Properties with matching names aren't mapping.

Solutions:

  1. Ensure properties have public getters and setters
  2. Check property names match exactly (case-sensitive)
  3. Verify target property has a setter (set accessor)
// Won't map - no setter
public class Target
{
    public string Name { get; } // Read-only
}

// Will map - has setter
public class Target
{
    public string Name { get; set; }
}

Nested Objects Are Null

Problem: Nested objects aren't being created.

Solutions:

  1. Check if source nested object is null
  2. Ensure nested class has parameterless constructor
  3. Verify property types match or are compatible
public class Address
{
    // Must have parameterless constructor
    public Address() { }
    
    public string Street { get; set; }
}

Type Conversion Fails

Problem: Automatic type conversion not working.

Solutions:

  1. Check if types are compatible for conversion
  2. Implement explicit mapping for complex conversions
  3. Ensure enum names match string values (case-insensitive)
// Works - Compatible types
int age = 25;
string ageStr = age.ToString(); // TurboMapper handles this

// May fail - Incompatible types
object obj = new MyClass();
int value = (int)obj; // Need explicit handling

Mapping Module Not Found

Problem: Mapping module not being discovered.

Solutions:

  1. Ensure module class is not abstract
  2. Make module class internal or public (not private)
  3. Verify assembly is loaded in AppDomain
  4. Check that AddTurboMapper() is called
// Correct - Will be discovered
internal class MyMappingModule : MappingModule<Source, Target>
{
    // ...
}

// Wrong - Won't be discovered
private class MyMappingModule : MappingModule<Source, Target>
{
    // ...
}

Performance Issues

Problem: Mapping is slow with large datasets.

Solutions:

  1. Reuse mapper instance (singleton)
  2. Configure mappings once, not repeatedly
  3. Use Parallel processing for large collections
  4. Profile to identify specific bottlenecks
// Efficient approach
var mapper = serviceProvider.GetService<IMapper>();
var results = sources
    .AsParallel()
    .Select(s => mapper.Map<Source, Target>(s))
    .ToList();

Debug Tips

Enable Detailed Exceptions

Wrap mapping calls to get better error messages:

try
{
    var result = mapper.Map<Source, Target>(source);
}
catch (Exception ex)
{
    Console.WriteLine($"Mapping failed: {ex.Message}");
    Console.WriteLine($"Stack trace: {ex.StackTrace}");
    throw;
}

Verify Configuration

Test that your configuration is registered:

var mapper = serviceProvider.GetService<IMapper>();
var source = new Source { Name = "Test" };

try
{
    var target = mapper.Map<Source, Target>(source);
    Console.WriteLine("Mapping successful");
}
catch
{
    Console.WriteLine("Mapping configuration missing");
}

Check Assembly Loading

Verify assemblies containing modules are loaded:

var assemblies = AppDomain.CurrentDomain.GetAssemblies();
foreach (var assembly in assemblies)
{
    Console.WriteLine($"Loaded: {assembly.FullName}");
}

Examples

Example 1: Simple DTO Mapping

public class CreateUserRequest
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string Email { get; set; }
}

public class User
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string Email { get; set; }
    public DateTime CreatedAt { get; set; }
}

// Mapping
var request = new CreateUserRequest
{
    FirstName = "John",
    LastName = "Doe",
    Email = "[email protected]"
};

var user = mapper.Map<CreateUserRequest, User>(request);
user.CreatedAt = DateTime.UtcNow;

Example 2: Complex Business Object

public class OrderDTO
{
    public string OrderNumber { get; set; }
    public CustomerDTO Customer { get; set; }
    public List<OrderItemDTO> Items { get; set; }
}

public class OrderEntity
{
    public string OrderNumber { get; set; }
    public CustomerEntity Customer { get; set; }
    public List<OrderItemEntity> Items { get; set; }
}

// Map with nested objects
var dto = GetOrderDTO();
var entity = mapper.Map<OrderDTO, OrderEntity>(dto);

// Map collection
entity.Items = dto.Items
    .Select(item => mapper.Map<OrderItemDTO, OrderItemEntity>(item))
    .ToList();

Example 3: Flattening Hierarchy

internal class OrderFlatteningModule : MappingModule<Order, OrderFlat>
{
    public OrderFlatteningModule() : base(true)
    {
    }

    public override Action<IMappingExpression<Order, OrderFlat>> CreateMappings()
    {
        return config =>
        {
            config.ForMember(dest => dest.CustomerName, src => src.Customer.Name)
                  .ForMember(dest => dest.CustomerEmail, src => src.Customer.Email)
                  .ForMember(dest => dest.ShippingStreet, src => src.ShippingAddress.Street)
                  .ForMember(dest => dest.ShippingCity, src => src.ShippingAddress.City);
        };
    }
}

Version History

v1.2.0 - Enhanced Core & Mapping Features (Latest)

  • Performance Improvements: Significant performance enhancements (2x+) through compiled expression trees and metadata caching
  • Collection Mapping Support: Added Map method overload to IMapper for collection mapping operations that returns IEnumerable
  • Ignored Properties Option: Added Ignore method to IMappingExpression to skip properties during mapping
  • Custom Type Converters Registration: Added RegisterConverter method to MappingModule for custom type conversion functions that can be registered with type mappings
  • Improved Nullable Type Handling: Enhanced ConvertValue method to handle nullable types properly
  • Conditional Mapping: Added When method to IMappingExpression for conditional property mapping
  • Mapping Transformations: Added MapWith method for transformation functions during mapping
  • Comprehensive Type Conversions: Enhanced ConvertValue with DateTime, TimeSpan, and other common type conversions
  • Configuration Validation: Added ValidateMapping method to IMapper that returns ValidationResult for early validation
  • Improved Error Messages: Better debugging information for conversion failures
  • Refactored Code: Consolidated duplicate GetMemberPath methods into shared internal implementation
  • Optimized Object Creation: Replaced Activator.CreateInstance with factory delegates for faster instantiation
  • Configuration Caching: Added caching for mapping configurations and lookups for faster performance
  • Backward Compatibility: Maintains full backward compatibility with v1.0.0

v1.0.0

  • Initial release
  • Default name-based mapping
  • Custom property mapping
  • Nested object support
  • Type conversion
  • Mapping modules
  • Dependency injection integration
  • Multi-framework support

License

TurboMapper is licensed under the terms specified in the LICENSE file.


Support

For issues, questions, or contributions:


Contributing

Contributions are welcome! Please submit pull requests or open issues on GitHub.

Clone this wiki locally