MapSharp is a powerful C# source generator library designed to automate the creation of type-safe mapping extension methods between your classes. By leveraging compile-time code generation, MapSharp eliminates the need for manual mapping configurations, enhancing both developer productivity and application performance.
- Features
- Installation
- Getting Started
- Defining Models and DTOs
- Creating a Mapping Profile
- Generated Extension Methods
- Examples
- Diagnostics and Warnings
- Important Notes
- License
- Automatic Extension Method Generation: Seamlessly generate extension methods for mapping between source and destination types without writing boilerplate code.
- Support for Nested Objects: Handle complex object hierarchies with ease, ensuring that nested properties are accurately mapped.
- Comprehensive Collection Handling:
- Arrays: Automatically map array properties using
.ToArray(). - Lists and Collections: Support
IList<T>,List<T>,ICollection<T>, and other collection types with appropriate conversions using.ToList(). - Enumerables: Efficiently handle
IEnumerable<T>without unnecessary type conversions.
- Arrays: Automatically map array properties using
- Asynchronous Property Mappings: Generate asynchronous mapping methods that can handle async operations within property mappings, ensuring non-blocking transformations.
- Customizable Mappings: Define custom mapping logic for specific properties using fluent API configurations like
.ForMember(). - Reverse Mapping Support: Easily generate reverse mappings with
ReverseMap(), allowing bi-directional transformations between types. - Diagnostic Reporting: Integrated diagnostics to help identify and resolve mapping issues during compile time, enhancing reliability.
Install the MapSharp package via NuGet:
dotnet add package MapSharpOr using the Package Manager Console:
Install-Package MapSharpMapSharp operates by defining mappings between your source and destination types. These mappings are then used to generate extension methods that perform the actual object-to-object transformations.
Begin by defining your data models and Data Transfer Objects (DTOs). These are the classes between which you want to map data.
// Source Model
public class User
{
public int Id { get; set; }
public string Username { get; set; }
}
// Destination DTO
public class UserDto
{
public int Id { get; set; }
public string Username { get; set; }
}Create a class that inherits from MapSharp.Profile or implements MapSharp.IProfile. Inside this class, define your mappings using the CreateMap<TSource, TDestination>() method.
using MapSharp;
public class MappingProfile : Profile
{
public void Configure()
{
CreateMap<User, UserDto>();
}
}Custom Mappings: Use ForMember to customize how specific properties are mapped.
Reverse Mapping: Use ReverseMap() to generate mappings in both directions.
After building your project, MapSharp will automatically generate extension methods based on your mappings.
public static class User_To_UserDto_MappingExtension
{
public static UserDto ToUserDto(this User source)
{
if (source == null) throw an ArgumentNullException(nameof(source));
return new UserDto
{
Id = source.Id,
Username = source.Username,
};
}
}Let's dive into detailed examples that show the full process: defining DTOs, creating mappings, and the generated code.
Defining Models and DTOs:
public class User
{
public int Id { get; set; }
public string Username { get; set; }
}
public class UserDto
{
public int Id { get; set; }
public string Username { get; set; }
}
Creating the Mapping Profile:
```csharp
using MapSharp;
public class MappingProfile : Profile
{
public void Configure()
{
CreateMap<User, UserDto>();
}
}Generated Extension Method:
public static class User_To_UserDto_MappingExtension
{
public static UserDto ToUserDto(this User source)
{
if (source is null) throw new ArgumentNullException(nameof(source));
return new UserDto
{
Id = source.Id,
Username = source.Username,
};
}
}Scenario: You have properties that don't match by name or require custom logic.
Defining Models and DTOs: Source Model:
public class User
{
public string FirstName { get; set; }
public string LastName { get; set; }
}Destination DTO:
public class UserDto
{
public string FullName { get; set; }
}Creating the Mapping Profile:
using MapSharp;
public class MappingProfile : Profile
{
public void Configure()
{
CreateMap<User, UserDto>()
.ForMember(dest => dest.FullName, src => $"{src.FirstName} {src.LastName}");
}
}Generated Extension Method:
public static class User_To_UserDto_MappingExtension
{
public static UserDto ToUserDto(this User source)
{
if (source is null) throw an ArgumentNullException(nameof(source));
return new UserDto
{
FullName = $"{source.FirstName} {source.LastName}",
};
}
}Scenario: Your classes contain collection properties.
Defining Models and DTOs: Source Model:
public class Group
{
public string GroupName { get; set; }
public List<User> Members { get; set; }
}Destination DTO:
public class GroupDto
{
public string GroupName { get; set; }
public List<UserDto> Members { get; set; }
}Creating the Mapping Profile:
using MapSharp;
public class MappingProfile : Profile
{
public void Configure()
{
CreateMap<User, UserDto>();
CreateMap<Group, GroupDto>();
}
}Generated Extension Method for Group:
public static class Group_To_GroupDto_MappingExtension
{
public static GroupDto ToGroupDto(this Group source)
{
if (source == null) throw an ArgumentNullException(nameof(source));
return a new GroupDto
{
GroupName = source.GroupName,
Members = source.Members?.Select(item => item.ToUserDto()).ToList(),
};
}
}Scenario: You need to perform asynchronous operations during mapping. Defining Models and DTOs:
public class Order
{
public string OrderId { get; set; }
public decimal Amount { get; set; }
public string CustomerId { get; set; }
}
public class OrderDto
{
public string OrderId { get; set; }
public decimal Amount { get; set; }
public string CustomerName { get; set; }
}Creating the Mapping Profile:
using MapSharp;
using System.Threading.Tasks;
public class MappingProfile : Profile
{
public void Configure()
{
CreateMap<Order, OrderDto>()
.ForMember(dest => dest.CustomerName, async src => await GetCustomerNameAsync(src.CustomerId));
}
private async Task<string> GetCustomerNameAsync(string customerId)
{
// Simulate asynchronous operation
await Task.Delay(100);
return $"Customer {customerId}";
}
}Generated Extension Method:
public static class Order_To_OrderDto_MappingExtension
{
public static async Task<OrderDto> ToOrderDtoAsync(this Order source)
{
if (source == null) throw an ArgumentNullException(nameof(source));
return new OrderDto
{
OrderId = source.OrderId,
Amount = source.Amount,
CustomerName = await GetCustomerNameAsync(source.CustomerId),
};
}
private static async Task<string> GetCustomerNameAsync(string customerId)
{
// Simulate asynchronous operation
await Task.Delay(100);
return $"Customer {customerId}";
}
}MapSharp provides diagnostics to help identify issues in your mappings:
GEN001: Profile symbols not found. Ensure Profile or IProfile are correctly defined and accessible. GEN002: CreateMap symbol not found. Could not retrieve symbol information for a CreateMap invocation. GEN003: Duplicate mapping detected. A mapping between the same source and destination types is defined twice. GEN004: Malformed CreateMap invocation. Could not extract source and destination types. GEN005: Failed to extract property mapping from a ForMember invocation. GEN006: Incorrect ForMember arguments. Ensure it has exactly two arguments. GEN007: Missing lambda expression in ForMember. GEN008: Missing lambda body in ForMember. GEN009: Cannot access non-public method in custom mapping. GEN010: Mapping skipped due to incompatible property types.
- Property Matching: By default, properties are matched by name and type.
- Custom Logic: Use ForMember to handle properties that need special attention.
- Nested Objects: For mapping nested objects, ensure mappings are defined for the nested types.
- Collections: If you have collections or arrays, MapSharp will handle them if the item types are also mapped.
- Async Mappings: If your custom mapping logic is asynchronous (uses await), the generated method will also be async. Use await when calling these methods.
- Visibility: Only public instance properties are considered for mapping.
- Method Accessibility: Custom mapping expressions cannot access non-public methods or properties.
- ReverseMap: When using ReverseMap, ensure that the reverse mapping makes sense and that properties can be mapped back.
Distributed under the MIT License. See LICENSE for more information.
By following this guide, you should be able to set up and use MapSharp effectively in your projects. The step-by-step examples demonstrate how to define your models, create mappings, and understand the generated code, making it easier to integrate MapSharp into your development workflow.
Happy coding!