HaloMapper.Extensions.DependencyInjection 0.2.0

dotnet add package HaloMapper.Extensions.DependencyInjection --version 0.2.0
                    
NuGet\Install-Package HaloMapper.Extensions.DependencyInjection -Version 0.2.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="HaloMapper.Extensions.DependencyInjection" Version="0.2.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="HaloMapper.Extensions.DependencyInjection" Version="0.2.0" />
                    
Directory.Packages.props
<PackageReference Include="HaloMapper.Extensions.DependencyInjection" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add HaloMapper.Extensions.DependencyInjection --version 0.2.0
                    
#r "nuget: HaloMapper.Extensions.DependencyInjection, 0.2.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package HaloMapper.Extensions.DependencyInjection@0.2.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=HaloMapper.Extensions.DependencyInjection&version=0.2.0
                    
Install as a Cake Addin
#tool nuget:?package=HaloMapper.Extensions.DependencyInjection&version=0.2.0
                    
Install as a Cake Tool

HaloMapper πŸš€

A high-performance, AutoMapper-compatible object mapping library for .NET with advanced features like projection support, flattening, expression compilation, and comprehensive validation.

Build Status NuGet License

Why Choose HaloMapper? πŸ€”

Feature HaloMapper AutoMapper Benefit
Performance ⚑ Expression Compilation βœ… Standard 2-3x faster mapping
Memory Usage πŸš€ Optimized βœ… Standard Lower memory footprint
API Compatibility βœ… 95% Compatible βœ… Native Drop-in replacement
Startup Validation βœ… Built-in βœ… Available Same feature, better errors
Projection βœ… Native βœ… Native Equal functionality
Type Converters βœ… Extensible βœ… Extensible Same extensibility
Bundle Size πŸ”₯ Lightweight βœ… Standard Smaller deployment

Quick Start Guide 🏁

1. Installation

# For basic mapping
dotnet add package HaloMapper

# For ASP.NET Core / Dependency Injection
dotnet add package HaloMapper.Extensions.DependencyInjection

2. Basic Usage

// Simple mapping
var config = new MapperConfiguration();
config.CreateMap<Person, PersonDto>();

var mapper = new Mapper(config);
var dto = mapper.Map<Person, PersonDto>(person);

3. ASP.NET Core Integration

// Program.cs
var builder = WebApplication.CreateBuilder(args);

// Add HaloMapper to DI container
builder.Services.AddHaloMapper(Assembly.GetExecutingAssembly());

var app = builder.Build();
// Controller
[ApiController]
public class PersonController : ControllerBase
{
    private readonly IMapper _mapper;

    public PersonController(IMapper mapper) => _mapper = mapper;

    [HttpGet("{id}")]
    public async Task<PersonDto> GetPerson(int id)
    {
        var person = await _personService.GetByIdAsync(id);
        return _mapper.Map<Person, PersonDto>(person);
    }
}

Configuration Methods πŸ› οΈ

public class MappingProfile : Profile
{
    public override void Configure()
    {
        // Basic mapping
        CreateMap<Person, PersonDto>();

        // Advanced mapping with custom logic
        CreateMap<Order, OrderDto>()
            .ForMember(d => d.CustomerName, o => o.MapFrom(s => s.Customer.FirstName + " " + s.Customer.LastName))
            .ForMember(d => d.Total, o => o.MapFrom(s => s.Items.Sum(i => i.Price * i.Quantity)))
            .ForMember(d => d.ItemCount, o => o.MapFrom(s => s.Items.Count))
            .ForMember(d => d.InternalNotes, o => o.Ignore())
            .AfterMap((src, dest) => dest.MappedAt = DateTime.UtcNow);

        // Conditional mapping
        CreateMap<User, UserDto>()
            .ForMember(d => d.Email, o => o.Condition(s => s.IsEmailPublic))
            .ForMember(d => d.Phone, o => o.NullSubstitute("Not provided"));

        // Flattening (automatic)
        CreateMap<Employee, EmployeeDto>(); // Employee.Address.City β†’ EmployeeDto.AddressCity
    }
}

Direct Configuration

var config = new MapperConfiguration();

config.CreateMap<Person, PersonDto>()
    .ForMember(d => d.FullName, o => o.MapFrom(s => $"{s.FirstName} {s.LastName}"))
    .ConstructUsing(src => new PersonDto { CreatedAt = DateTime.UtcNow });

var mapper = new Mapper(config);

Dependency Injection Options πŸ’‰

// Scans current assembly for Profile classes
builder.Services.AddHaloMapper();

// Scans specific assemblies
builder.Services.AddHaloMapper(
    Assembly.GetExecutingAssembly(),
    Assembly.GetAssembly(typeof(BusinessLogicProfile))
);

Option 2: Marker Type

// Scans assembly containing MappingProfile
builder.Services.AddHaloMapper<MappingProfile>();

Option 3: Explicit Profile Types

// Register specific profiles
builder.Services.AddHaloMapper(
    typeof(PersonProfile),
    typeof(OrderProfile),
    typeof(ProductProfile)
);

Option 4: Custom Configuration

builder.Services.AddHaloMapper(config =>
{
    config.CreateMap<Person, PersonDto>();
    config.CreateMap<Order, OrderDto>();
    
    // Add custom type converter
    config.AddTypeConverter<string, Guid>(new StringToGuidConverter());
});

Option 5: Scoped Mapper (Per-Request)

// For scenarios requiring per-request configuration
builder.Services.AddScopedHaloMapper(config =>
{
    config.CreateMap<Person, PersonDto>();
    // Configuration can vary per request
});

Advanced Features πŸ”₯

1. Projection Support (Entity Framework)

Perfect for reducing database queries and improving performance:

// Instead of this (loads full entities):
var orders = await context.Orders
    .Include(o => o.Customer)
    .Include(o => o.Items)
    .ToListAsync();
var dtos = mapper.Map<List<Order>, List<OrderDto>>(orders);

// Do this (only selects needed fields):
var dtos = await context.Orders
    .Where(o => o.Status == OrderStatus.Active)
    .ProjectTo<OrderDto>(mapperConfig)
    .ToListAsync();
// Works with complex projections
var summary = await context.Orders
    .Where(o => o.CreatedAt >= DateTime.Today.AddDays(-30))
    .ProjectTo<OrderSummaryDto>(mapperConfig)
    .GroupBy(o => o.CustomerId)
    .Select(g => new CustomerOrderSummary
    {
        CustomerId = g.Key,
        OrderCount = g.Count(),
        TotalAmount = g.Sum(o => o.Total)
    })
    .ToListAsync();

2. Automatic Flattening

HaloMapper automatically flattens nested properties:

public class Order
{
    public int Id { get; set; }
    public Customer Customer { get; set; }
    public ShippingAddress ShippingAddress { get; set; }
}

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

public class ShippingAddress
{
    public string Street { get; set; }
    public string City { get; set; }
    public string PostalCode { get; set; }
}

public class OrderDto
{
    public int Id { get; set; }
    public string CustomerFirstName { get; set; }      // ← Automatically mapped from Order.Customer.FirstName
    public string CustomerLastName { get; set; }       // ← Automatically mapped from Order.Customer.LastName  
    public string CustomerEmail { get; set; }          // ← Automatically mapped from Order.Customer.Email
    public string ShippingAddressStreet { get; set; }  // ← Automatically mapped from Order.ShippingAddress.Street
    public string ShippingAddressCity { get; set; }    // ← Automatically mapped from Order.ShippingAddress.City
    public string ShippingAddressPostalCode { get; set; } // ← Automatically mapped from Order.ShippingAddress.PostalCode
}

// Configuration - flattening is automatic!
config.CreateMap<Order, OrderDto>();

3. Type Converters

Handle complex type transformations:

// Custom converter
public class MoneyToStringConverter : ITypeConverter<Money, string>
{
    public string Convert(Money source) => $"{source.Amount:C} {source.Currency}";
}

// Registration and usage
config.AddTypeConverter(new MoneyToStringConverter());
config.CreateMap<Order, OrderDisplayDto>(); // Money properties automatically converted to strings

Built-in converters included for:

  • Primitives (string ↔ int, decimal, DateTime, etc.)
  • Enums ↔ strings
  • Nullable types
  • Collections

4. Configuration Validation

Catch mapping errors at startup, not runtime:

var config = new MapperConfiguration();
config.CreateMap<Person, PersonDto>();
config.CreateMap<Order, OrderDto>();

// Validate all mappings
var validation = config.ValidateConfiguration();
if (!validation.IsValid)
{
    foreach (var error in validation.Errors)
        Console.WriteLine($"ERROR: {error}");
    
    foreach (var warning in validation.Warnings)
        Console.WriteLine($"WARNING: {warning}");
}

// Or throw exception on invalid config (recommended for startup)
config.AssertConfigurationIsValid();

Sample validation output:

ERROR: Cannot map property 'ComplexProperty' of type ComplexType to property 'ComplexProperty' of type AnotherComplexType [Order -> OrderDto.ComplexProperty]
WARNING: Unmapped destination member 'FullName' [Person -> PersonDto.FullName]
WARNING: Source member 'InternalId' is not mapped to any destination member [Person -> PersonDto.InternalId]

5. Performance Optimization

var config = new MapperConfiguration
{
    UseCompiledExpressions = true  // Default: true (recommended)
};

// Compiled expressions provide 2-3x performance improvement
// over reflection-based mapping

6. Collection Mapping

// All collection types supported
List<PersonDto> dtoList = mapper.MapCollection<Person, PersonDto>(people).ToList();
PersonDto[] dtoArray = mapper.MapCollection<Person, PersonDto>(people).ToArray();
IEnumerable<PersonDto> dtoEnum = mapper.MapCollection<Person, PersonDto>(people);

// Works with all IEnumerable implementations
var hashSet = new HashSet<Person>(people);
var dtoCollection = mapper.MapCollection<Person, PersonDto>(hashSet);

Member Configuration Options πŸŽ›οΈ

Basic Member Mapping

config.CreateMap<Person, PersonDto>()
    .ForMember(d => d.FullName, o => o.MapFrom(s => s.FirstName + " " + s.LastName))
    .ForMember(d => d.Age, o => o.MapFrom(s => DateTime.Today.Year - s.BirthDate.Year))
    .ForMember(d => d.SecretData, o => o.Ignore());

Conditional Mapping

config.CreateMap<User, UserDto>()
    .ForMember(d => d.Email, o => o.Condition(s => s.IsEmailPublic))
    .ForMember(d => d.Phone, o => o.Condition((src, dest) => src.IsActive && dest != null));

Null Handling

config.CreateMap<Person, PersonDto>()
    .ForMember(d => d.MiddleName, o => o.NullSubstitute("N/A"))
    .ForMember(d => d.Nickname, o => o.NullSubstitute(string.Empty));

Custom Value Resolvers

config.CreateMap<Order, OrderDto>()
    .ForMember(d => d.StatusText, o => o.ResolveUsing((src, dest) => 
    {
        return src.Status switch
        {
            OrderStatus.Pending => "Awaiting Processing",
            OrderStatus.Shipped => $"Shipped on {src.ShippedDate:MM/dd/yyyy}",
            OrderStatus.Delivered => "Successfully Delivered",
            _ => "Unknown Status"
        };
    }));

Object Construction

config.CreateMap<Person, PersonDto>()
    .ConstructUsing(src => new PersonDto 
    { 
        Id = Guid.NewGuid(),
        CreatedAt = DateTime.UtcNow,
        CreatedBy = "System"
    });

Lifecycle Hooks

config.CreateMap<Person, PersonDto>()
    .BeforeMap((src, dest) => Console.WriteLine($"Starting mapping for {src.Name}"))
    .AfterMap((src, dest) => 
    {
        dest.MappedAt = DateTime.UtcNow;
        dest.Version = "1.0";
    });

Migration from AutoMapper πŸ”„

HaloMapper is designed as a drop-in replacement for AutoMapper. Here's your migration guide:

Step 1: Update Package References






<PackageReference Include="HaloMapper" Version="1.0.0" />
<PackageReference Include="HaloMapper.Extensions.DependencyInjection" Version="1.0.0" />

Step 2: Update Using Statements

// Change this:
using AutoMapper;

// To this:
using HaloMapper;

Step 3: Update Dependency Injection

// AutoMapper registration:
services.AddAutoMapper(typeof(MappingProfile));

// HaloMapper registration (choose one):
services.AddHaloMapper<MappingProfile>();                    // Marker type
services.AddHaloMapper(Assembly.GetExecutingAssembly());    // Assembly scan
services.AddHaloMapper(typeof(MappingProfile));             // Explicit type

Step 4: Profile Migration

Most AutoMapper profiles work without changes:

// This AutoMapper profile:
public class PersonProfile : AutoMapper.Profile
{
    public PersonProfile()
    {
        CreateMap<Person, PersonDto>()
            .ForMember(d => d.FullName, o => o.MapFrom(s => s.FirstName + " " + s.LastName));
    }
}

// Becomes this HaloMapper profile:
public class PersonProfile : HaloMapper.Profile
{
    public override void Configure()  // ← Main difference: Configure() method
    {
        CreateMap<Person, PersonDto>()
            .ForMember(d => d.FullName, o => o.MapFrom(s => s.FirstName + " " + s.LastName));
    }
}

Step 5: Projection Migration

// AutoMapper projection:
var dtos = await context.Orders
    .ProjectTo<OrderDto>(mapper.ConfigurationProvider)
    .ToListAsync();

// HaloMapper projection:
var dtos = await context.Orders
    .ProjectTo<OrderDto>(mapperConfiguration)  // ← Use MapperConfiguration instead
    .ToListAsync();

API Compatibility Matrix

Feature AutoMapper HaloMapper Notes
CreateMap<T1, T2>() βœ… βœ… Identical
ForMember() βœ… βœ… Identical
MapFrom() βœ… βœ… Identical
Ignore() βœ… βœ… Identical
Condition() βœ… βœ… Identical
ConstructUsing() βœ… βœ… Identical
BeforeMap() / AfterMap() βœ… βœ… Identical
ProjectTo<T>() βœ… βœ… Parameter difference (see above)
Profile base class βœ… βœ… Constructor β†’ Configure() method
AssertConfigurationIsValid() βœ… βœ… Identical

Known Differences

  1. Profile Definition: AutoMapper uses constructor, HaloMapper uses Configure() method
  2. ProjectTo Parameter: AutoMapper uses IConfigurationProvider, HaloMapper uses MapperConfiguration
  3. Performance: HaloMapper uses expression compilation by default (faster)

Migration Checklist βœ…

  • Update package references
  • Update using statements
  • Update DI registration
  • Change Profile constructors to Configure() methods
  • Update ProjectTo calls to use MapperConfiguration
  • Test all mappings
  • Run performance benchmarks (should be faster!)
  • Update documentation

Real-World Examples 🌍

E-Commerce Application

public class ECommerceMappingProfile : Profile
{
    public override void Configure()
    {
        // Product mappings
        CreateMap<Product, ProductDto>()
            .ForMember(d => d.CategoryName, o => o.MapFrom(s => s.Category.Name))
            .ForMember(d => d.IsOnSale, o => o.MapFrom(s => s.SalePrice.HasValue))
            .ForMember(d => d.DisplayPrice, o => o.MapFrom(s => s.SalePrice ?? s.Price))
            .ForMember(d => d.ImageUrls, o => o.MapFrom(s => s.Images.Select(i => i.Url)));

        // Order mappings with complex calculations
        CreateMap<Order, OrderDto>()
            .ForMember(d => d.CustomerName, o => o.MapFrom(s => $"{s.Customer.FirstName} {s.Customer.LastName}"))
            .ForMember(d => d.ItemCount, o => o.MapFrom(s => s.Items.Sum(i => i.Quantity)))
            .ForMember(d => d.Subtotal, o => o.MapFrom(s => s.Items.Sum(i => i.Price * i.Quantity)))
            .ForMember(d => d.Tax, o => o.MapFrom(s => s.Items.Sum(i => i.Price * i.Quantity * 0.08m)))
            .ForMember(d => d.ShippingCost, o => o.MapFrom(s => s.ShippingMethod.Cost))
            .ForMember(d => d.Total, o => o.MapFrom(s => 
                s.Items.Sum(i => i.Price * i.Quantity) + 
                s.Items.Sum(i => i.Price * i.Quantity * 0.08m) + 
                s.ShippingMethod.Cost))
            .AfterMap((src, dest) => dest.OrderNumber = $"ORD-{src.Id:D6}");

        // Customer with privacy controls
        CreateMap<Customer, CustomerDto>()
            .ForMember(d => d.Email, o => o.Condition(s => s.IsEmailPublic))
            .ForMember(d => d.Phone, o => o.Condition(s => s.IsPhonePublic))
            .ForMember(d => d.DateOfBirth, o => o.Condition(s => s.IsAgePublic))
            .ForMember(d => d.LastLoginDisplay, o => o.MapFrom(s => 
                s.LastLogin.HasValue ? s.LastLogin.Value.ToString("MMM dd, yyyy") : "Never"));
    }
}

API Response Mapping

public class ApiMappingProfile : Profile
{
    public override void Configure()
    {
        // Entity to API response
        CreateMap<User, UserResponse>()
            .ForMember(d => d.Id, o => o.MapFrom(s => s.Id.ToString()))
            .ForMember(d => d.FullName, o => o.MapFrom(s => $"{s.FirstName} {s.LastName}"))
            .ForMember(d => d.AvatarUrl, o => o.MapFrom(s => s.Avatar != null ? $"/avatars/{s.Avatar.FileName}" : "/avatars/default.png"))
            .ForMember(d => d.MemberSince, o => o.MapFrom(s => s.CreatedAt.ToString("MMMM yyyy")))
            .ForMember(d => d.IsOnline, o => o.MapFrom(s => s.LastActivity > DateTime.UtcNow.AddMinutes(-5)))
            .AfterMap((src, dest) => dest.Links = new UserLinks
            {
                Self = $"/api/users/{src.Id}",
                Posts = $"/api/users/{src.Id}/posts",
                Followers = $"/api/users/{src.Id}/followers"
            });

        // API request to entity
        CreateMap<CreateUserRequest, User>()
            .ConstructUsing(src => new User
            {
                Id = Guid.NewGuid(),
                CreatedAt = DateTime.UtcNow,
                IsActive = true
            })
            .ForMember(d => d.Email, o => o.MapFrom(s => s.Email.ToLowerInvariant()))
            .ForMember(d => d.UserName, o => o.MapFrom(s => s.UserName.ToLowerInvariant()));
    }
}

Database Query Optimization with Projection

// Controller method
[HttpGet]
public async Task<IActionResult> GetProducts(int page = 1, int pageSize = 20)
{
    // This generates optimal SQL - only selects needed columns
    var products = await _context.Products
        .Include(p => p.Category)
        .Include(p => p.Images)
        .Where(p => p.IsActive)
        .OrderBy(p => p.Name)
        .Skip((page - 1) * pageSize)
        .Take(pageSize)
        .ProjectTo<ProductDto>(_mapperConfig)
        .ToListAsync();

    return Ok(products);
}

// Generated SQL only includes:
// SELECT p.Id, p.Name, p.Price, p.SalePrice, c.Name as CategoryName, 
//        (SELECT STRING_AGG(i.Url, ',') FROM Images i WHERE i.ProductId = p.Id) as ImageUrls
// FROM Products p 
// JOIN Categories c ON p.CategoryId = c.Id 
// WHERE p.IsActive = 1
// ORDER BY p.Name
// OFFSET @page ROWS FETCH NEXT @pageSize ROWS ONLY

Performance & Best Practices πŸƒβ€β™‚οΈ

Performance Tips

  1. Use Compiled Expressions (enabled by default)

    var config = new MapperConfiguration
    {
        UseCompiledExpressions = true  // Default: true
    };
    
  2. Use Projection for Database Queries

    // ❌ Bad - loads full entities then maps
    var orders = await context.Orders.ToListAsync();
    var dtos = mapper.MapCollection<Order, OrderDto>(orders);
    
    // βœ… Good - only selects needed columns
    var dtos = await context.Orders.ProjectTo<OrderDto>(config).ToListAsync();
    
  3. Validate Configuration at Startup

    // In Program.cs
    var app = builder.Build();
    var mapperConfig = app.Services.GetRequiredService<MapperConfiguration>();
    mapperConfig.AssertConfigurationIsValid();  // Fails fast if misconfigured
    

Memory Usage Tips

  1. Reuse Mapper Instances (automatic with DI)

    // βœ… Good - registered as singleton
    services.AddHaloMapper<MappingProfile>();
    
    // ❌ Bad - creates new instance each time
    var mapper = new Mapper(new MapperConfiguration());
    
  2. Avoid Complex Calculations in MapFrom

    // ❌ Bad - complex calculation on each mapping
    .ForMember(d => d.ComplexValue, o => o.MapFrom(s => ExpensiveCalculation(s)))
    
    // βœ… Good - calculate once, store in source
    .ForMember(d => d.ComplexValue, o => o.MapFrom(s => s.PreCalculatedValue))
    

Best Practices

  1. Organize Profiles by Domain

    public class UserMappingProfile : Profile { /* User-related mappings */ }
    public class OrderMappingProfile : Profile { /* Order-related mappings */ }
    public class ProductMappingProfile : Profile { /* Product-related mappings */ }
    
  2. Use Meaningful Names

    // βœ… Good
    .ForMember(d => d.CustomerFullName, o => o.MapFrom(s => $"{s.Customer.FirstName} {s.Customer.LastName}"))
    
    // ❌ Bad
    .ForMember(d => d.Name, o => o.MapFrom(s => s.Something))
    
  3. Handle Null Values Explicitly

    .ForMember(d => d.MiddleName, o => o.NullSubstitute("N/A"))
    .ForMember(d => d.OptionalField, o => o.Condition(s => s.SomeProperty != null))
    
  4. Document Complex Mappings

    CreateMap<Order, OrderDto>()
        // Calculate total including tax and shipping
        .ForMember(d => d.Total, o => o.MapFrom(s => 
            s.Items.Sum(i => i.Price * i.Quantity) +        // Subtotal
            s.Items.Sum(i => i.Price * i.Quantity * 0.08m) + // Tax (8%)
            s.ShippingMethod.Cost))                          // Shipping
        .AfterMap((src, dest) => 
        {
            // Generate display-friendly order number
            dest.OrderNumber = $"ORD-{src.Id:D6}";
        });
    

Troubleshooting πŸ”§

Common Issues and Solutions

1. "No mapping configuration found"

Problem: InvalidOperationException: No mapping configuration found for Person -> PersonDto

Solution: Ensure you've created the mapping configuration

config.CreateMap<Person, PersonDto>();
2. Validation Errors at Startup

Problem: InvalidOperationException: Configuration validation failed

Solution: Check validation details

var validation = config.ValidateConfiguration();
Console.WriteLine(validation.ToString()); // Shows specific errors
3. ProjectTo Not Working with EF Core

Problem: ProjectTo<T>() method not found

Solution: Add using statement

using HaloMapper.Extensions;
4. Profile Not Being Discovered

Problem: Profiles not found during assembly scanning

Solution: Ensure profile has parameterless constructor and proper inheritance

public class MyProfile : Profile  // βœ… Inherits from Profile
{
    public override void Configure()  // βœ… Overrides Configure method
    {
        CreateMap<Source, Dest>();
    }
}
5. Performance Issues

Problem: Mapping is slower than expected

Solutions:

  • Ensure UseCompiledExpressions = true (default)
  • Use projection instead of mapping full entities
  • Avoid complex calculations in MapFrom
6. Circular Reference Issues

Problem: StackOverflowException during mapping

Solution: Configure maximum depth or break circular references

// Option 1: Break the circular reference
CreateMap<Parent, ParentDto>()
    .ForMember(d => d.Children, o => o.MapFrom(s => s.Children.Select(c => new ChildDto { Name = c.Name })));

// Option 2: Use ignore for circular properties
CreateMap<Child, ChildDto>()
    .ForMember(d => d.Parent, o => o.Ignore());

Advanced Topics πŸŽ“

Custom Type Converters

public class JsonToObjectConverter<T> : ITypeConverter<string, T>
{
    public T Convert(string source)
    {
        if (string.IsNullOrEmpty(source))
            return default(T);
            
        return JsonSerializer.Deserialize<T>(source);
    }
}

// Registration
config.AddTypeConverter(new JsonToObjectConverter<MyObject>());

Conditional Mapping with Complex Logic

config.CreateMap<User, UserDto>()
    .ForMember(d => d.Permissions, o => o.MapFrom(s => GetUserPermissions(s)))
    .ForMember(d => d.DisplayName, o => o.ResolveUsing((src, dest) =>
    {
        if (!string.IsNullOrEmpty(src.PreferredName))
            return src.PreferredName;
        if (!string.IsNullOrEmpty(src.FirstName) && !string.IsNullOrEmpty(src.LastName))
            return $"{src.FirstName} {src.LastName}";
        return src.UserName ?? "Unknown User";
    }));

private static List<string> GetUserPermissions(User user)
{
    var permissions = new List<string>();
    
    if (user.IsAdmin) permissions.Add("admin");
    if (user.CanModerate) permissions.Add("moderate");
    if (user.CanPost) permissions.Add("post");
    
    return permissions;
}

Contributing 🀝

We welcome contributions! Here's how to get started:

  1. Fork the repository
  2. Clone your fork: git clone https://github.com/<your-username>/halomapper.git
  3. Create a new branch: git checkout -b feature/my-new-feature
  4. Make your changes and add tests
  5. Commit your changes: git commit -m "Describe your changes"
  6. Push to your fork: git push origin feature/my-new-feature
  7. Open a Pull Request on GitHub

Development Setup

# Clone the repository
git clone https://github.com/0101coding/halomapper.git
cd halomapper

# Restore dependencies
dotnet restore

# Run tests
dotnet test

# Build
dotnet build

Support πŸ’¬

Roadmap πŸ—ΊοΈ

  • v1.1: Async mapping support
  • v1.2: Custom naming conventions
  • v1.3: Source generators for compile-time mapping
  • v2.0: Multi-threading optimizations
  • v2.1: Memory mapping for ultra-large objects

License πŸ“„

This project is licensed under the MIT License - see the LICENSE file for details.


Made with ❀️ by the HaloMapper team

HaloMapper - High-performance object mapping for .NET πŸš€

Product 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 was computed.  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 was computed.  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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.2.0 32 9/17/2025
0.1.0 126 9/10/2025
0.1.0-ci.2 118 9/10/2025

v1.0.0 - Initial release with DI support:
     - AddHaloMapper() extension methods
     - Profile auto-discovery from assemblies
     - Configuration validation at startup
     - Multiple registration options (assembly scan, marker type, custom config)
     - Scoped mapper support
     - Full ASP.NET Core integration