Output

Output is a .NET object-object mapper designed to be fast, powerful, extensible, easy to learn and to extend!

Some of features of the Output library includes:

  • No configuration required
  • Flattening and unflattening capabilities
  • LINQ Projections
  • Chained Mapping
  • Custom Configurations
  • Custom Providers and Resolvers

Getting Started

// Instantiate a provider...
var provider = new MappingProvider();

// ... and its respective mapper
var mapper = new Mapper(provider);

// Done! Now we are ready to go.
mapper.Map<DTO>(Model);

Dependency

.NET Standard 1.0+

License

MIT License

Repository

This project is available at GitHub

Getting Started

Syntax to perform mapping:

mapper.Map<TOutput>(TInput input);

TOutput is our destination type and TInput is our source one.

Initialization

A mapper requires a provider to be instantiated.

IMappingProvider provider = new Output.Providers.MappingProvider();

IMapper mapper = new Mapper(provider);

Entities to respectives DTOs

Given our entities:

public class Product
{
    public Guid Id { get; set; }
    public string Name { get; set; }
    public bool IsActive { get; set; }
    public ProductCategory Category { get;set; }
}

public class ProductCategory
{
    public Guid Id { get; set; }
    public string Name { get; set; }
}

And our DTOs:

public class ProductDto
{
    public Guid Id { get; set; }
    public string Name { get; set; }
    public bool IsActive { get; set; }
    public ProductCategoryDto Category { get;set; }
}

public class ProductCategoryDto
{
    public Guid Id { get; set; }
    public string Name { get; set; }
}

Sample:

var product = new Product()
{
    Id = Guid.NewGuid(),
    Name = "Coffee",
    IsActive = true,
    Category = new ProductCategory()
    {
        Id = Guid.NewGuid(),
        Name = "Heaven's Goods"
    }
}

var mapper = new Mapper(new MappingProvider());

// performing the mapping
mapper.Map<ProductDto>(product);

Transforming to a flat DTO

You can also transform the TInput The source type nested objects to a flatten TOutput The destination type one.

// ... previous code above

public class ProductFlatDto
{
    public Guid Id { get; set; }
    public string Name { get; set; }
    public bool IsActive { get; set; }
    public string CategoryId { get; set; }
    public string CategoryName { get; set; }
}

mapper.Map<ProductFlatDto>(product);

Read more about Flattening & Unflattening.

Existing TOutput object

You can also pass a instance of the TOutput The destination type as the second parameter of the Map’s method.

var dto = new ProductDto();

mapper.Map(product, dto);

Internally it uses this already instantiated object to perform the mappings.

See also Map Chain.

Providers

Providers are responsable to create the mapping function.

The implementation of the provider determines the support to property mappings, flattening, unflattening, collections, projections, etc.

The built-in provider is MappingProvider which deliveries every feature in this documentation.

MappingProvider

IMappingProvider provider = new Output.Providers.MappingProvider();

IMapper mapper = new Mapper(provider); // register the provider to a new mapper.
Read and Write capabilities

The MappingProvider can read Fields, Properties and parameterless Methods and can write to a destination Properties.

In general, the target property type must be equal to the source member type. This restrictions can vary based on the current resolver. Read more about Resolvers.

public class Sample
{
    public Sample()
    {
        One = 1;
        Two = true;
    }

    public int One;
    public bool Two { get; set; }
    public string Three()
    {
        return "Hello";
    }
}

public class SampleDto
{
    public int One { get; set; }
    public bool Two { get; set; }
    public string Three { get; set; }
}

var mapper = new Mapper( new MappingProvider() );

var dto = mapper.Map<SampleDto>(new Sample());
// -> dto.One == 1
// -> dto.Two == true
// -> dto.Three == "Hello"
Mapping Collections

There’s no secret to mapping collections, the syntax is the same, you just need pass the right TOutput The destination type and TInput The source type parameters.

Let’s say that we have a service that gets a list of customers and we want to convert that list to an array of our DTO. It’s easy:

var customers = customerService.GetCustomersByCountry("Brazil");

mapper.Map<CustomerDto[]>(customers);

To know all collections support read about CollectionResolver.

Flattening and Unflattening

You can transform a nested object in a single flat object and vice-versa. Read more in Flattening & Unflattening.

Mapping Configuration

You can customize the mapping between a TInput The source type and TOutput The destination type . Read more in Mapping Configurations.

Create your own provider

You can also create your own provider.

Read more in Customizations.

Resolvers

Resolvers are used to solve the mapping between two members based on their types.

The Providers determines a sequence that the resolvers are executed and each one is only triggered if the previous one was not able solve the mapping.

You don’t need to worry about resolvers, this documentation is just to explain how each one works.

Bellow, we are listing all built-in Output’s resolvers and what they can do.

TypesAssignableResolver

Can map the members with the same type or when it’s underlying type matches.

Input Type Output Type Is able to map?
int int
int int?
int? int
byte int
decimal string
etc.    

PrimitiveResolver

When the type is primitive or is decimal type, this resolver tries to map by conversion.

Input Type Output Type Is able to map?
byte int
long decimal
int? int

GuidResolver

Can convert a System.Guid to string or to a byte array and vice-versa.

Input Type Output Type Is able to map?
Enum string or byte[]
string or byte[] Enum

Mapping from a string or byte array to a Guid, requires of course, a valid data format otherwise an Exception will occur.

EnumResolver

Can convert an Enum to all it’s approved types ( byte, sbyte, short, ushort, int, uint, long or ulong), to a string or to an equivalent Enum.

The reverse map also works.

Input Type Output Type Is able to map?
Approved Types / String Enum
Enum Approved Types / String
Enum Enum (Equivalent)

e.g:

public enum Level
{
    None = 0,
    Gold = 1,
    Silver = 2,
    Bronze = 3
}

public class Source
{
    public Level LevelA { get; set; }
    public Level LevelB { get; set; }
    public Level LevelC { get; set; }
}

public enum Another
{
    None = 0,
    Gold = 1,
    Silver = 2,
    Bronze = 3
}

public class Target
{
    public Another LevelA { get; set; }
    public string LevelB { get; set; }
    public int LevelC { get; set; }
}

// Mapping to the Target a source like:
new Source
{
    LevelA = Level.Gold,
    LevelB = Level.Silver,
    LevelC = Level.Bronze
});

// Will result in:
-> Target {
    LevelA == Another.Gold
    LevelB == "LevelB"
    LevelC == 3
}

DictionaryResolver

It can map a Dictionary<TKeyType, TValueType> to another Dictionary<TAnotherKeyType, TAnotherValueType>.

The types of Key and Value are resolved by the Resolvers described on this page.

In the example bellow, the dictionary Key is solved by TypesAssignableResolver and the dictionary Value by EnumResolver.

Input Type Output Type Is able to map?
Dictionary<int, MyEnum> Dictionary<int, string>
etc.

CollectionResolver

This resolver can convert many types derived from generics ICollection<T> or IReadOnlyCollection<T> to another type derived from the same.

There are many possibilities here, so, to simplify I will show just a few possibilities.

Input Type Output Type Is able to map?
List<T> T[]
T[] HashSet<T>
Stack<T> Queue<T>
etc.

ClassResolver

This resolver is used when the input and output members represents a class.

Internally, it just uses the current mapper to do a new mapping.

AnyToStringResolver

Converts any type to string calling the implementation of .ToString from the TInput The source type .


ConstructorResolver

The constructor resolver is a special kind of resolver used to determine how the destination object must be instantiated.

It prioritizes the parameterless constructor but when the TOutput The destination type object doesn’t have one, it tries to determine one based on TInput The source type properties.

e.g:

public class Sample
{
    public string Title { get; set; }
    public DateTime RegistrationDate { get; set; }
    public bool IsActive { get; set; }
}

public class SampleDto
{
    public SampleDto(string title, bool isActive)
    {
        Title = title;
        IsActive = isActive;
    }

    public string Title { get; set; }
    public DateTime RegistrationDate { get; set; }
    public bool IsActive { get; set; }
}

As we can see, SampleDto has only one constructor with title and isActive as parameters.

Doing a mapping from Sample to SampleDto, the ConstructorResolver will try find the public Properties “Title” and “IsActive” in our Sample instance to populate the constructor parameters.

It operates in case insensitive, so does not matter if we have “isActive”, “ISACTIVE” or “isactive”. All these are considered as long they are public Properties.

An AmbiguosMatchException can occur of course, if we have two public properties with the same name declared with different text cases.

In case of multiple constructors, the execution order is from the one with highest parameters amount to the one with lowest.

Constructor Execution order
SampleDto(string title, DateTime RegistrationDate, bool IsActive) 1
SampleDto(string, bool IsActive) 2
SampleDto(string title) 3
etc.  

Remember, the priority is the parameterless constructor, only if it doesn’t exists that the others are going to be used.

Complex Scenarios
Prefixed properties
public class FooDto
{
    public string Name { get; set; }
    public int CreatedDateYear { get; set; }
    public int CreatedDateMonth { get; set; }
    public int CreatedDateDay { get; set; }
    public string BarName { get; set; }
}

public class Foo
{
    public Foo(string name, DateTime createdDate, Bar bar)
    {
        Name = name;
        CreatedDate = createdDate;
        Bar = bar;
    }
    public string Name { get; }
    public DateTime CreatedDate { get; }
    public Bar Bar { get; }
}

public class Bar
{
    public Bar(string name)
    {
        Name = name;
    }

    public string Name { get; }
}

Mapping FooDto to Foo also works.

As FooDto doesn’t have a CreatedDate property of System.Date type, the ConstructorResolver look for all properties prefixed by CreatedDate.

In this case, CreatedDateYear, CreatedDateMonth and CreatedDateDay.

With Year, Month and Day the ConstructorResolver is able to build the CreatedDate of Foo using the constructor DateTime(year, month, day).

The same method is used to solve the Bar constructor.

Custom map properties
public class Customer
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
}

public class CustomerDto
{
    public CustomerDto(string fullName)
    {
        FullName = fullName;
    }

    public string FullName { get; set; }
}

To map the property CustomerDto.FullName with the values of Customer.FirstName and Customer.LastName we need write a custom configuration. See more in Mapping Configurations.

var provider = new MappingProvider();

provider.AddConfig<Customer, CustomerDto>(config =>
    config.Map(
        output => output.FullName,
        input => input.FirstName + " " + input.LastName
    )
);

var mapper = new Mapper(provider);

mapper.Map<CustomerDto>(customer);

Doing this, the ConstructorResolver will also use this configuration to determine how to resolve the fullName parameter of the constructor.

Create your own resolver

You can also create your own resolver.

Read more in Customizations.

Flattening & Unflattening

Let’s take this models for instance.

public class Track
{
    public Track(int id, string title, Album album)
    {
        Id = id;
        Title = title;
        Album = album;
    }

    public int Id { get; set; }
    public string Title { get; set; }
    public string Length { get; set; }

    public Album Album { get; set; }
    public Genre Genre { get; set; }
}

public class Album
{
    public Album(int id, string title, Artist artist)
    {
        Id = id;
        Title = title;
        Artist = artist;
    }

    public int Id { get; set; }
    public string Title { get; set; }
    public int Year { get; set; }
    public Artist Artist { get; set; }
}

public class Artist
{
    public Artist(int id, string name)
    {
        Id = id;
        Name = name;
    }
    public int Id { get; set; }
    public string Name { get; set; }
}

public class Genre
{
    public int Id { get; set; }
    public string Name { get; set; }
}

DTO representation:

public class TrackDto
{
    public int Id { get; set; }
    public string Title { get; set; }
    public string Length { get; set; }

    public int AlbumId { get; set; }
    public string AlbumTitle { get; set; }
    public int AlbumYear { get; set; }

    public int AlbumArtistId { get; set; }
    public string AlbumArtistName { get; set; }

    public int? GenreId { get; set; }
    public string GenreName { get; set; }
}

Flattening

Flattening objects by hand is a repetitive and boring task.

var trackDto = new TrackDto
{
    Id = track.Id,
    Length = track.Length,
    Title = track.Title,

    AlbumId = track.Album?.Id,
    AlbumTitle = track.Album?.Title,
    AlbumYear = track.Album?.Year,

    AlbumArtistId = track.Album?.Artist?.Id,
    AlbumArtistName = track.Album?.Artist?.Name,

    GenreId = track.Genre?.Id,
    GenreName = track.Genre?.Name
}

With Output this task is dead easy.

var trackDto = mapper.Map<TrackDto>(track);

Unflattening

Unflattening is also possible and even with non-public constructors the Ouput is able to determine which constructor to use based on your DTO representation.

var track = mapper.Map<Track>(trackDto);

Read more about ConstructorResolver in Resolvers.

Mapping Configurations

Mapping configurations allow us to create custom mappings between TInput The source type and TOutput The destination type objects.

There are three methods that we can use.

.Map: Determines how a property in TOutput The destination type should be mapped.

.Map<TProperty>(
    Expression<Func<TOutput, TProperty>> output,
    Expression<Func<TInput, TProperty>> input
);

.Ignore: Removes a property from mapping operation.

.Ignore<TProperty>(
    Expression<Func<TOutput, TProperty>> output
);

.Instance: Determines how the TOutput The destination type should be instantiate.

.Instance<TProperty>(
    Expression<Func<TOutput, TProperty>> output
);

Let’s look into it.

public class Customer
{
    public Guid Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public DateTime RegistrationDate { get; set; }
}

public class CustomerDto
{
    public Guid Id { get; set; }
    public string FullName { get; set; }
    public DateTime RegistrationDate { get; set; }
}

In your DTO we do not have the “FirstName” and “LastName” but we have the “FullName”. Let’s configure that.

var customersConfig = new MappingConfiguration<Customer, CustomerDto>();

customersConfig.Map(p => p.FullName, p => p.FirstName + " " + p.LastName);

Now we just need to register this to the provider before instantiate the mapper.

var provider = new MappingProvider();

provider.AddConfig(customersConfig); // registers the configuration

var mapper = new Mapper(provider);

You can add as many configuration as you want.

provider.AddConfig(customersConfig);

var employeesConfig = new MappingConfiguration<Employee, EmployeeDto>()
    .Ignore(p => p.BirthDate)
    .Ignore(p => p.HireDate);

var productsConfig = new MappingConfiguration<Product, ProductDto>()
    .Map(p => p.FullName, p => p.Category.Name + " " + p.Name)
    .Instantiate(p => new Product(p.Id, p.Name));

provider.AddConfig(employeesConfig);
provider.AddConfig(productsConfig);

We can use an alternative syntax to register the configuration as well.

using Output.Extensions;

...

provider.AddConfig<Customer, CustomerDto>(config =>

    // config is a instance of MappingConfiguration<Customer, CustomerDto>
    config.Map(...)
    config.Ignore(...);
    // ...etc
);

provider.AddConfig<Employee, EmployeeDto>(config => ...);
provider.AddConfig<Product, ProductDto>(config => ...);

LINQ Projections

For query projections we must use the following syntax:

mapper.Project<TOutput>(IQueryable<TInput> input);

Let’s see how this works and what is the difference between this syntax and the mapper.Map.

Suppose we have this entity represented in our database.

public class Product
{
    public int ProductID { get; set; }
    public string ProductName { get; set; }
    public int? CategoryID { get; set; }
    public decimal? UnitPrice { get; set; }
    public bool Discontinued { get; set; }
    public DateTime? LastSupply { get; set; }
}

And we want project this to a very simple DTO:

public class ProductDto
{
    public int ProductID { get; set; }
    public string ProductName { get; set; }
    public int? CategoryID { get; set; }
}

Telling to Output get a list of ProductDto writting mapper.Map<List<ProductDto>>(dbContext.Products), behind the scenes, the SQL produced will be:

SELECT
    [t0].[ProductID],
    [t0].[ProductName],
    [t0].[CategoryID],
    [t0].[UnitPrice],
    [t0].[Discontinued],
    [t0].[LastSupply]
FROM [Products] AS [t0]

Notice that even our DTO represents only 3 properties of the Product Entity, all the 6 properties was requested.

Using the mapper.Project<ProductDto>(dbContext.Products) we fix that.

SELECT [t0].[ProductID], [t0].[ProductName], [t0].[CategoryID]
FROM [Products] AS [t0]

This also prevent the lazy loading SELECT N+1 problems.

Let’s add the category entity to illustrate that.

public class Category
{
        public int CategoryID { get; set; }
        public string CategoryName { get; set; }
        public List<Product> Products { get; set; }
}

And it’s respective DTO.

public class CategoryDto
{
        public int CategoryID { get; set; }
        public string CategoryName { get; set; }
        public List<ProductDto> Products { get; set; }
}

When we run mapper.Map<List<CategoryDto>>(dbContext.Categories), one SQL will be produced for the Category and for each record of it another SQL will be produced to get all Products associated to that record.

Running mapper.Project<CategoryDto>(dbContext.Categories) we run against the database only once:

SELECT
    [t0].[CategoryID],
    [t0].[CategoryName],
    [t0].[Description],
    [t1].[ProductID],
    [t1].[ProductName],
    [t1].[CategoryID] AS [CategoryID2], (
        SELECT COUNT(*)
        FROM [Products] AS [t2]
        WHERE [t2].[CategoryID] = [t0].[CategoryID]
    ) AS [value]
FROM [Categories] AS [t0]
LEFT OUTER JOIN [Products] AS [t1] ON [t1].[CategoryID] = [t0].[CategoryID]
ORDER BY [t0].[CategoryID], [t1].[ProductID]

As we can see, when we are working with IQueryable is much better use mapper.Project instead of mapper.Map. For all other cases use mapper.Map.

Map Chain

Map chain is just a consecutive mapping in a shared TOutput object.

We use the syntax bellow multiple times using the same shared object as the second parameter.

mapper.Map<TOutput>(TInput input, TOuput output);

Sometimes all information that we need must be obtained from different sources.

Suppose that we have three services, the customer service:

customerService.getInfoByCustomerId(customerId);

// returns
CustomerInfo {
    Guid CustomerId;
    string Email;
    string Name;
    DateTime Registration;
}

The financial service:

financialService.getUserAccountInfo(userId);

// returns
UserAccessInfo {
    Guid UserId;
    string AccountNumber;
    string BankNumber;
    string BankName;
}

And the access service:

accessService.getAccessInfoByUserId(userId);

// returns
UserAccessInfo {
    Guid UserId;
    string UserName;
    bool IsAuthorized;
    DateTime LastAccess;
}

Using a shared object

Now we want put all this information together in a single DTO.

public class FullCustomerInfoDto
{
    // data from customer service
    public Guid CustomerId { get; set; }
    public string Email { get; set; }
    public string Name { get; set; }
    public DateTime Registration { get; set; }

    // data from financial service except UserId
    public string AccountNumber { get; set; }
    public string BankNumber { get; set; }
    public string BankName { get; set; }

    // data from access service except UserId
    public string UserName { get; set; }
    public bool IsAuthorized { get; set; }
    public DateTime LastAccess { get; set; }
}

We just need to perform the mapping three times over the same DTO instance.

var sharedObj = new FullCustomerInfoDto();

mapper.Map(customerService.getInfoByCustomerId(userId), sharedObj);
mapper.Map(financialService.getUserAccountInfo(userId), sharedObj);
mapper.Map(accessService.getAccessInfoByUserId(userId), sharedObj);

There’s a easier way to do that through an extension method.

using Output.Extensions;

// ...

var sharedObj = new FullCustomerInfoDto();

mapper.Map(sharedObj,
    customerService.getInfoByCustomerId(userId),
    financialService.getUserAccountInfo(userId),
    accessService.getAccessInfoByUserId(userId)
);

Customizations

Is very simple to customize and improve the Output mapping engine, it’s require just a basic knowledgement about Reflections and System.Linq.Expressions.

Basically we need create our own provider inherited from Output.Providers.MappingProvider and start to override some methods.

Let’s get an example:

// ENTITY

public class Customer
{
    public Guid Id { get; set; }
    public string Name { get; set; }
    public string Email { get; set; }
    public DateTime BirthDate { get; set; }
    public int GetAge()
    {
        var days = (DateTime.Today - BirthDate).TotalDays;
        if (days > 0 && days < int.MaxValue)
            return (int)Math.Floor(days / 365.2524);

        return -1; // invalid
    }
}

// DTO

public class CustomerDto
{
    public Guid Id { get; set; }
    public string Name { get; set; }
    public string Email { get; set; }
    public DateTime BirthDate { get; set; }
    public int Age { get; set; }
}

Mapping Customer to CustomerDto will result in a empty (0 in this case) Age value. It occurs because the names must match and in the Customer entity we have the method named “GetAge()” and not “Age()”.

We can change the name “Age” in our DTO to “GetAge” but of course this is not even close a good solution.

Maybe we can create a custom mapping as described in Mapping Configurations but this is restricted to a pair of types so every time that a method GetSomething() apper again we need create or update a configuration. Not good either.

The best is tell to Output ignore the “Get” prefix of every Method found in our TInput The source type .

To do so, we just need make a very simple change.

Overriding the GetMember

public class MyMappingProvider : MappingProvider
{
    protected override Expression GetMember(Expression input, string name)
    {
        // first we let the base try to find an equivalent
        // Field, Property or Method with name's parameter
        var result = base.GetMember(input, name);
        if (result == null)
        {
            // As it didn't find anything,
            // we will look for a method prefixed with "Get"
            var method = input.Type.GetMethod("Get" + name, Type.EmptyTypes);

            // if found, we return the equivalent expression
            if (method != null)
                return Expression.Call(input, method);
        }

        return result;
    }
}


// using it

var mapper = new Mapper( new MyMappingProvider() );

mapper.Map<CustomerDto>(customer); // Age will be mapped now.

Now every “GetSomething()” methods will be mapped to a property “Something”.

Easy right?

But now, let’s make the Output engine even better creating a custom resolver.

Customizing Resolvers

Instead of representing our “Email” property as a string in our entity model, it’s more interesting create a custom type “Email” where we can put all the validation logic into it.

public class Email
{
    public Email(string value)
    {
        if (IsValid(value))
            Value = value;
    }

    public string Value { get; private set; }

    public static bool IsValid(string value)
    {
        var reg = new Regex("@"); // our naive regular expression to validate the e-mail
        return reg.IsMatch(value);
    }
}

// ... Customer changes
public Email Email { get; set; }
// ...

To map the Customer.Email to CustomerDto.Email we have two common ways:

Change the CustomerDto.Email to CustomerDto.EmailValue leaving the mapping job to the flattening engine or override the Customer.ToString() implementation returning Customer.Value and then the AnyToStringResolver deal with it.

Maybe this is enough in some situations but there is a better way to do that by creating an EmailResolver.

To create it, we must inherit the contract Output.Resolvers.IResolver and implement the Resolve method.

public class EmailResolver : IResolver
{
    public Expression Resolve(Expression input, Expression output);
    {
        // First we check if the input and output types is of what we expect. (Email and string)
        if (input.Type == typeof(Email) && output.Type == typeof(string))

            // if so, we return the string property Value from our input.
            return Expression.Property(input, "Value");

        return null; // otherwise we return null.
    }
}

The result above represents this input.Email.Value but there is some changes to make.

If our input instance has not a valid email, the property will be null and consequently we get a NullReferenceException when we try to access the Value property. Thus, to avoid that, a better result should be input.Email != null ? input.Email.Value : null.

Of course you can implement the verifications by yourself but the Output already has a built-in ExpressionVisitor that do this. Let’s apply the changes:

using Output.Visitors;

public class EmailResolver : IResolver
{
    public Expression Resolve(Expression input, Expression output)
    {
        if (input.Type == typeof(Email) && output.Type == typeof(string))
        {
            var value = Expression.Property(input, "Value");

            // Handle null references
            return new NullCheckVisitor(value).Visit();
        }
        return null;
    }
}

The code above is able to map an Email to a string. But what about from a string to an Email. Let’s complete the code.

public class EmailResolver : IResolver
{
    public Expression Resolve(Expression input, Expression output)
    {
        if (input.Type == typeof(Email) && output.Type == typeof(string))
        {
            var value = Expression.Property(input, "Value");

            return new NullCheckVisitor(value).Visit();
        }
        else if (input.Type == typeof(Email) && output.Type == typeof(string))
        {
            var emailCtor = typeof(Email).GetConstructor(new [] { typeof(string) });

            // initializes the Email type passing the input (string) as parameter
            return Expression.New(emailCtor, input);
        }

        return null;
    }
}

Now our custom resolver is complete, we just need register it into our custom provider.

Overriding the GetResolvers

Member resolvers are retrieved from .GetResolvers method. Let’s override it.

public class MyMappingProvider : MappingProvider
{
    // ... GetMember(...)

    protected override IEnumerable<IResolver> GetResolvers()
    {
        yield return new EmailResolver();

        foreach (var resolver in base.GetResolvers())
            yield return resolver;
    }
}

Note

Is recommended that you return your custom resolvers before the built-in ones, otherwise you may get unwanted results because another resolver was used instead of what you created.

Dependency Injection

Nowadays is difficult to think in not use dependency injection in our applications, everything is much easier with DI.

We can easily inject the mapper instance passing the contract IMapper where we need it.

Here is an example using the Microsoft.Extensions.DependencyInjection but we can use it with any DI container.

public void ConfigureServices(IServiceCollection services)
{
    // ...

    // Mapping Engine
    var provider = new MappingProvider();

    MappingConfigurationSetup.ConfigCustomMappings(provider);

    services.AddSingleton<IMapper, Mapper>(s => new Mapper(provider));

    // ...
}

// this is just for instance

public class MappingConfigurationSetup
{
    public static void ConfigCustomMappings(IMappingProvider provider)
    {
        // ... custom configurations
    }
}

Now is just inject it where we need.

public abstract class ApplicationService
{
    private readonly IMapper _mapper;

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

    public TOutput Map<TOutput>(object input)
    {
        return _mapper.Map<TOutput>(input);
    }
}


public class CustomerAppService : ApplicationService
{
    private readonly ICustomerService _customerService;

    public CustomerAppService(ICustomerService customerService, IMapper mapper) : base(mapper)
    {
        _customerService = service;
    }

    public List<CustomerDto> GetCustomers()
    {
        return Map<List<CustomerDto>>(_customerService.GetCustomers());
    }

    public CustomerDto FindById(int id)
    {
        return Map<CustomerDto>(_customerService.FindById(id));
    }

    // ...
}

Performance

The benchmarking is performed using the powerful library BenchmarkDotNet.

The tests was runned against a handwritten implementation of the object-object mappings and against the libraries:

  • AutoMapper 6.2.2 (probably the most popular one)
  • Mapster 3.1.8 (one of the fastest mappers)
  • A handwritten implementation which is used as the base-line of all tests.

The benchmark test project is also available at GitHub.

Benchmarking Results

Here is just a short resume of all summary results. You can see the full log running the benchmark project.

BenchmarkDotNet=v0.10.12, OS=Windows 10 Redstone 3 [1709, Fall Creators Update] (10.0.16299.248)
Intel Core i7-4790 CPU 3.60GHz (Haswell), 1 CPU, 8 logical cores and 4 physical cores

Frequency=3515641 Hz, Resolution=284.4431 ns, Timer=TSC
Basic Test
ShortRun : .NET Core 2.0.5 (Framework 4.6.26020.03), 64bit RyuJIT | .NET Core SDK=2.1.4
Method Mean Scaled Rank Allocated
HandwrittenMap 237.5 ns 1.00 I 512 B
MapsterMap 309.8 ns 1.30 II 528 B
AutoMapperMap 429.1 ns 1.81 III 544 B
OutputMap 609.5 ns 2.57 IV 624 B
ShortRun : .NET Framework 4.6.1 (CLR 4.0.30319.42000), 64bit RyuJIT-v4.7.2633.0
Method Mean Scaled Rank Allocated
MapsterMap 684.9 ns 0.83 I 528 B
HandwrittenMap 829.9 ns 1.00 II 608 B
OutputMap 1,056.9 ns 1.27 III 624 B
AutoMapperMap 1,418.3 ns 1.71 IV 640 B
Complex Test
ShortRun : .NET Core 2.0.5 (Framework 4.6.26020.03), 64bit RyuJIT | .NET Core SDK=2.1.4
Method Mean Scaled Rank Allocated
HandwrittenMap 620.5 ns 1.00 I 1.36 KB
MapsterMap 1,194.9 ns 1.93 II 1.86 KB
OutputMap 2,640.6 ns 4.26 III 1.9 KB
AutoMapperMap 48,040.1 ns 77.43 IV 16.53 KB
ShortRun : .NET Framework 4.6.1 (CLR 4.0.30319.42000), 64bit RyuJIT-v4.7.2633.0
Method Mean Scaled Rank Allocated
HandwrittenMap 771.8 ns 1.00 I 1.59 KB
MapsterMap 1,996.4 ns 2.59 II 1.86 KB
OutputMap 3,468.6 ns 4.49 III 1.9 KB
AutoMapperMap 44,083.8 ns 57.12 IV 17.15 KB
Intense Test
ShortRun : .NET Core 2.0.5 (Framework 4.6.26020.03), 64bit RyuJIT | .NET Core SDK=2.1.4
Method Mean Scaled Rank Allocated
HandwrittenMap 2.910 us 1.00 I 4.98 KB
MapsterMap 6.419 us 2.21 II 6.32 KB
OutputMap 9.410 us 3.23 III 6.68 KB
AutoMapperMap 141.356 us 48.59 IV 47.54 KB
ShortRun : .NET Framework 4.6.1 (CLR 4.0.30319.42000), 64bit RyuJIT-v4.7.2633.0
Method Mean Scaled Rank Allocated
HandwrittenMap 4.243 us 1.00 I 6.05 KB
MapsterMap 9.133 us 2.15 II 6.34 KB
OutputMap 13.505 us 3.18 III 6.68 KB
AutoMapperMap 133.597 us 31.49 IV 49.43 KB

Legends:

  • Mean : Arithmetic mean of all measurements
  • Scaled : Mean(CurrentBenchmark) / Mean(BaselineBenchmark)
  • Rank : Relative position of current benchmark mean among all benchmarks (Roman style)
  • Allocated : Allocated memory per single operation (managed only, inclusive, 1KB = 1024B)
  • 1 ns : 1 Nanosecond (0.000000001 sec)