HaloMapper 0.2.0

dotnet add package HaloMapper --version 0.2.0
                    
NuGet\Install-Package HaloMapper -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" Version="0.2.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="HaloMapper" Version="0.2.0" />
                    
Directory.Packages.props
<PackageReference Include="HaloMapper" />
                    
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 --version 0.2.0
                    
#r "nuget: HaloMapper, 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@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&version=0.2.0
                    
Install as a Cake Addin
#tool nuget:?package=HaloMapper&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.
  • net8.0

    • No dependencies.

NuGet packages (1)

Showing the top 1 NuGet packages that depend on HaloMapper:

Package Downloads
HaloMapper.Extensions.DependencyInjection

Dependency injection extensions for HaloMapper, providing seamless integration with Microsoft.Extensions.DependencyInjection. Includes profile auto-discovery, assembly scanning, and configuration validation.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.2.0 25 9/17/2025
0.1.0 127 9/10/2025
0.1.0-ci.2 113 9/10/2025

v1.0.0 - Initial release with core mapping features:
     - High-performance expression compilation
     - AutoMapper-compatible API
     - Projection support for Entity Framework
     - Automatic flattening and unflattening
     - Comprehensive configuration validation
     - Type converter system
     - Profile-based configuration