|
| 1 | +# TurboMapper Implementation Guide |
| 2 | + |
| 3 | +## Overview |
| 4 | +TurboMapper is a high-performance object mapping library designed to simplify the process of copying data between objects with similar structures. It provides automatic property mapping, supports complex nested objects, and offers flexible configuration options. |
| 5 | + |
| 6 | +## Core Concepts |
| 7 | + |
| 8 | +### 1. Basic Mapping |
| 9 | +The fundamental operation in TurboMapper is mapping properties from a source object to a target object based on property names. |
| 10 | + |
| 11 | +```csharp |
| 12 | +var source = new SourceObject { Name = "John", Age = 30 }; |
| 13 | +var target = mapper.Map<SourceObject, TargetObject>(source); |
| 14 | +``` |
| 15 | + |
| 16 | +### 2. Complex Type Handling |
| 17 | +TurboMapper automatically identifies complex types (custom classes) and handles their mapping recursively. |
| 18 | + |
| 19 | +### 3. Property Matching |
| 20 | +Properties are matched by name and write accessibility. Only properties with matching names and compatible types are mapped. |
| 21 | + |
| 22 | +## Architecture |
| 23 | + |
| 24 | +### Core Components |
| 25 | + |
| 26 | +#### Mapper Class |
| 27 | +The main entry point for all mapping operations. |
| 28 | + |
| 29 | +**Key Methods:** |
| 30 | +- `Map<TSource, TTarget>(TSource source)`: Creates a new target instance and maps all properties |
| 31 | +- `ApplyNameBasedMapping<TSource, TTarget>(TSource source, TTarget target)`: Maps properties from source to existing target |
| 32 | +- `CreateMap<TSource, TTarget>()`: Configures custom mapping rules |
| 33 | + |
| 34 | +#### Mapping Process |
| 35 | + |
| 36 | +1. **Type Analysis**: Determine source and target property types |
| 37 | +2. **Property Matching**: Match properties by name |
| 38 | +3. **Value Conversion**: Convert values between compatible types |
| 39 | +4. **Recursive Mapping**: Handle nested complex objects |
| 40 | +5. **Assignment**: Set values on target properties |
| 41 | + |
| 42 | +### Complex Type Detection |
| 43 | + |
| 44 | +The `IsComplexType(Type type)` method determines whether a type should be treated as a complex object: |
| 45 | + |
| 46 | +```csharp |
| 47 | +private bool IsComplexType(Type type) |
| 48 | +{ |
| 49 | + // Strings are not considered complex types |
| 50 | + if (type == typeof(string)) |
| 51 | + return false; |
| 52 | + |
| 53 | + // Value types (int, bool, etc.) are not complex |
| 54 | + if (type.IsValueType) |
| 55 | + return false; |
| 56 | + |
| 57 | + // Arrays are not considered complex for mapping purposes |
| 58 | + if (type.IsArray) |
| 59 | + return false; |
| 60 | + |
| 61 | + // Collections are not considered complex for mapping purposes |
| 62 | + if (typeof(System.Collections.IEnumerable).IsAssignableFrom(type) && type != typeof(string)) |
| 63 | + return false; |
| 64 | + |
| 65 | + // Everything else is considered a complex type |
| 66 | + return true; |
| 67 | +} |
| 68 | +``` |
| 69 | + |
| 70 | +### Nested Object Mapping |
| 71 | + |
| 72 | +When mapping complex types, TurboMapper handles nested objects through recursive calls: |
| 73 | + |
| 74 | +1. Check if source property value is not null |
| 75 | +2. Get or create target property instance |
| 76 | +3. Recursively map nested object properties |
| 77 | +4. Assign mapped instance to target property |
| 78 | + |
| 79 | +### Value Conversion |
| 80 | + |
| 81 | +The `ConvertValue(object value, Type targetType)` method handles type conversions: |
| 82 | + |
| 83 | +- Null value handling |
| 84 | +- Direct assignment when types are compatible |
| 85 | +- Enum parsing from strings |
| 86 | +- String conversion for all types |
| 87 | +- GUID parsing from strings |
| 88 | +- Numeric type conversions with special handling for truncation |
| 89 | +- Complex type mapping through recursive calls |
| 90 | + |
| 91 | +## Advanced Features |
| 92 | + |
| 93 | +### Custom Mapping Configuration |
| 94 | + |
| 95 | +TurboMapper supports custom mapping configurations through the `MappingModule` system: |
| 96 | + |
| 97 | +```csharp |
| 98 | +public class UserMappingModule : MappingModule<UserSource, UserTarget> |
| 99 | +{ |
| 100 | + public override Action<IMappingExpression<UserSource, UserTarget>> CreateMappings() |
| 101 | + { |
| 102 | + return config => config |
| 103 | + .ForMember(dest => dest.FullName, src => src.FirstName + " " + src.LastName) |
| 104 | + .ForMember(dest => dest.YearsOld, src => src.Age); |
| 105 | + } |
| 106 | +} |
| 107 | +``` |
| 108 | + |
| 109 | +### Property Path Mapping |
| 110 | + |
| 111 | +Support for mapping deeply nested properties using dot notation: |
| 112 | + |
| 113 | +```csharp |
| 114 | +config.ForMember(dest => dest.Address.PostalCode, src => src.ZipCode); |
| 115 | +``` |
| 116 | + |
| 117 | +## Error Handling |
| 118 | + |
| 119 | +### Null Source Handling |
| 120 | +When a source object is null, TurboMapper returns the default value for the target type. |
| 121 | + |
| 122 | +### Type Conversion Errors |
| 123 | +When type conversion fails, TurboMapper returns the default value for the target type rather than throwing exceptions. |
| 124 | + |
| 125 | +### Circular Reference Prevention |
| 126 | +TurboMapper includes built-in protection against infinite recursion in circular object graphs. |
| 127 | + |
| 128 | +## Performance Considerations |
| 129 | + |
| 130 | +### Caching |
| 131 | +Mapped property information is cached to avoid repeated reflection calls. |
| 132 | + |
| 133 | +### Memory Efficiency |
| 134 | +TurboMapper minimizes object allocation during mapping operations. |
| 135 | + |
| 136 | +### Thread Safety |
| 137 | +All mapping operations are thread-safe and can be used concurrently. |
| 138 | + |
| 139 | +## Extension Points |
| 140 | + |
| 141 | +### Custom Value Resolvers |
| 142 | +Implement custom logic for calculating property values during mapping. |
| 143 | + |
| 144 | +### Conditional Mapping |
| 145 | +Apply mappings only when certain conditions are met. |
| 146 | + |
| 147 | +### Pre/Post Processing |
| 148 | +Execute custom code before or after mapping operations. |
| 149 | + |
| 150 | +## Best Practices |
| 151 | + |
| 152 | +### 1. Use Nullable Reference Types |
| 153 | +Enable nullable reference types to catch potential null reference issues at compile time. |
| 154 | + |
| 155 | +### 2. Configure Mappings at Startup |
| 156 | +Register all mapping modules during application initialization for optimal performance. |
| 157 | + |
| 158 | +### 3. Handle Exceptions Appropriately |
| 159 | +While TurboMapper minimizes exceptions, always validate mapped data when business rules require it. |
| 160 | + |
| 161 | +### 4. Profile Performance |
| 162 | +For high-volume mapping scenarios, profile your application to ensure optimal performance. |
| 163 | + |
| 164 | +## Common Patterns |
| 165 | + |
| 166 | +### Simple DTO Mapping |
| 167 | +Map between similar domain objects and data transfer objects. |
| 168 | + |
| 169 | +### Flattening Objects |
| 170 | +Map nested properties to flat target structures. |
| 171 | + |
| 172 | +### Reverse Mapping |
| 173 | +Create bidirectional mappings for round-trip data transformations. |
| 174 | + |
| 175 | +### Collection Mapping |
| 176 | +Map collections of objects using the same mapping configurations. |
| 177 | + |
| 178 | +## Troubleshooting |
| 179 | + |
| 180 | +### Properties Not Mapping |
| 181 | +- Check property names match exactly (case-sensitive) |
| 182 | +- Verify properties have public getters/setters |
| 183 | +- Ensure target properties are writable |
| 184 | +- Confirm property types are compatible or convertible |
| 185 | + |
| 186 | +### Nested Objects Always Null |
| 187 | +- Verify complex type detection is working correctly |
| 188 | +- Check that source nested objects are not null |
| 189 | +- Ensure target object constructors properly initialize nested properties |
| 190 | + |
| 191 | +### Performance Issues |
| 192 | +- Review mapping configurations for complexity |
| 193 | +- Consider caching mapped objects when appropriate |
| 194 | +- Profile reflection usage in hot paths |
| 195 | + |
| 196 | +## Integration |
| 197 | + |
| 198 | +### Dependency Injection |
| 199 | +TurboMapper integrates seamlessly with DI containers through extension methods: |
| 200 | + |
| 201 | +```csharp |
| 202 | +services.AddTurboMapper(); |
| 203 | +``` |
| 204 | + |
| 205 | +### ASP.NET Core |
| 206 | +Use TurboMapper in controllers for view model transformation: |
| 207 | + |
| 208 | +```csharp |
| 209 | +[ApiController] |
| 210 | +public class UserController : ControllerBase |
| 211 | +{ |
| 212 | + private readonly IMapper _mapper; |
| 213 | + |
| 214 | + public UserController(IMapper mapper) |
| 215 | + { |
| 216 | + _mapper = mapper; |
| 217 | + } |
| 218 | + |
| 219 | + [HttpGet("{id}")] |
| 220 | + public ActionResult<UserDto> GetUser(int id) |
| 221 | + { |
| 222 | + var user = _userRepository.GetById(id); |
| 223 | + var dto = _mapper.Map<User, UserDto>(user); |
| 224 | + return Ok(dto); |
| 225 | + } |
| 226 | +} |
| 227 | +``` |
| 228 | + |
| 229 | +## Testing Strategies |
| 230 | + |
| 231 | +### Unit Testing Mappings |
| 232 | +Create dedicated tests for each mapping configuration to ensure correctness. |
| 233 | + |
| 234 | +### Performance Testing |
| 235 | +Benchmark mapping operations for critical paths in your application. |
| 236 | + |
| 237 | +### Integration Testing |
| 238 | +Test end-to-end scenarios that involve multiple mapping operations. |
| 239 | + |
| 240 | +## Limitations |
| 241 | + |
| 242 | +### Dynamic Properties |
| 243 | +TurboMapper does not support mapping to dynamic properties or ExpandoObjects. |
| 244 | + |
| 245 | +### Private Members |
| 246 | +Only public properties are considered for mapping. |
| 247 | + |
| 248 | +### Indexers |
| 249 | +Properties with indexers are not supported. |
| 250 | + |
| 251 | +## Future Enhancements |
| 252 | + |
| 253 | +### Expression-Based Mapping |
| 254 | +Compile mapping expressions for even better performance. |
| 255 | + |
| 256 | +### Convention-Based Mapping |
| 257 | +Support configurable naming conventions (camelCase, PascalCase, etc.). |
| 258 | + |
| 259 | +### Validation Integration |
| 260 | +Built-in validation during mapping operations. |
| 261 | + |
| 262 | +## API Reference |
| 263 | + |
| 264 | +### IMapper Interface |
| 265 | + |
| 266 | +#### Methods: |
| 267 | +- `TTarget Map<TSource, TTarget>(TSource source)`: Primary mapping method |
| 268 | +- `void CreateMap<TSource, TTarget>()`: Configure mapping between types |
| 269 | + |
| 270 | +### IMappingExpression Interface |
| 271 | + |
| 272 | +#### Methods: |
| 273 | +- `IMappingExpression<TSource, TTarget> ForMember<TMember>(Expression<Func<TTarget, TMember>> destination, Func<TSource, TMember> source)`: Configure property mapping |
| 274 | + |
| 275 | +### MappingModule<TSrc, TDest> Abstract Class |
| 276 | + |
| 277 | +#### Methods: |
| 278 | +- `abstract Action<IMappingExpression<TSrc, TDest>> CreateMappings()`: Define custom mapping rules |
| 279 | + |
| 280 | +## Examples |
| 281 | + |
| 282 | +### Basic Mapping |
| 283 | +```csharp |
| 284 | +public class PersonSource |
| 285 | +{ |
| 286 | + public string FirstName { get; set; } |
| 287 | + public string LastName { get; set; } |
| 288 | + public int Age { get; set; } |
| 289 | +} |
| 290 | + |
| 291 | +public class PersonTarget |
| 292 | +{ |
| 293 | + public string FirstName { get; set; } |
| 294 | + public string LastName { get; set; } |
| 295 | + public int Age { get; set; } |
| 296 | +} |
| 297 | + |
| 298 | +// Usage |
| 299 | +var source = new PersonSource |
| 300 | +{ |
| 301 | + FirstName = "John", |
| 302 | + LastName = "Doe", |
| 303 | + Age = 30 |
| 304 | +}; |
| 305 | + |
| 306 | +var target = mapper.Map<PersonSource, PersonTarget>(source); |
| 307 | +``` |
| 308 | + |
| 309 | +### Nested Object Mapping |
| 310 | +```csharp |
| 311 | +public class OrderSource |
| 312 | +{ |
| 313 | + public string OrderId { get; set; } |
| 314 | + public CustomerSource Customer { get; set; } |
| 315 | +} |
| 316 | + |
| 317 | +public class CustomerSource |
| 318 | +{ |
| 319 | + public string Name { get; set; } |
| 320 | + public AddressSource Address { get; set; } |
| 321 | +} |
| 322 | + |
| 323 | +public class AddressSource |
| 324 | +{ |
| 325 | + public string Street { get; set; } |
| 326 | + public string City { get; set; } |
| 327 | +} |
| 328 | + |
| 329 | +// Target classes follow the same structure |
| 330 | +var source = new OrderSource |
| 331 | +{ |
| 332 | + OrderId = "123", |
| 333 | + Customer = new CustomerSource |
| 334 | + { |
| 335 | + Name = "John Doe", |
| 336 | + Address = new AddressSource |
| 337 | + { |
| 338 | + Street = "123 Main St", |
| 339 | + City = "Anytown" |
| 340 | + } |
| 341 | + } |
| 342 | +}; |
| 343 | + |
| 344 | +var target = mapper.Map<OrderSource, OrderTarget>(source); |
| 345 | +// target.Customer.Address.Street will be "123 Main St" |
| 346 | +``` |
| 347 | + |
| 348 | +### Custom Mapping Module |
| 349 | +```csharp |
| 350 | +public class OrderMappingModule : MappingModule<OrderSource, OrderTarget> |
| 351 | +{ |
| 352 | + public override Action<IMappingExpression<OrderSource, OrderTarget>> CreateMappings() |
| 353 | + { |
| 354 | + return config => config |
| 355 | + .ForMember(dest => dest.OrderNumber, src => src.OrderId) |
| 356 | + .ForMember(dest => dest.CustomerName, src => src.Customer.Name); |
| 357 | + } |
| 358 | +} |
| 359 | +``` |
0 commit comments