Model mapping

Object mapping made easy

Overview

Automatic object mapping is useful whenever you need to convert from type A to type B. Instead of manually assigning each member one at a time, an object mapper does this in a very generic way using reflection. For example, you can convert an entity type (such as Product) to a view model type (such as ProductModel) or vice-versa. Smartstore comes with object mapping utilities that are ultra-lightweight and fast.

Utility
Description
Call

Very simple object mapper

MiniMapper.Map(From, To)

Advanced, customizable and flexible mapper

MapperFactory.MapAsync(From, To)

MiniMapper

The MiniMapper is a lightweight and simple object mapping utility that attempts to map properties of the same name between two objects. It uses reflection under the hood to query and access properties. If matched properties have different types, the mapper tries to convert them using the type conversion system. If this fails, the property is simply skipped and no exception is thrown.

Example

BlogSettings settings = /* get instance somehow */;

// Maps BlogSettings --> BlogSettingsModel (must have a parameterless constructor)
// and returns the destination instance
var model = MiniMapper.Map<BlogSettings, BlogSettingsModel>(settings);

// Maps BlogSettings --> BlogSettingsModel by populating the given target instance.
var model = new BlogSettingsModel();
MiniMapper.Map(settings, model);

MapperFactory

The MapperFactory is a static factory that can create custom type mapper instances (IMapper<TFrom, TTo>).

Upon initialization, MapperFactory automatically scans for all concrete IMapper<TFrom, TTo> classes in all loaded assemblies. Because the mapper is DI-enabled it can depend on any registered service.

You can register multiple mappers for the same type pair without any conflicts. When a mapping operation is requested, the MapperFactory will combine all the registered mappers into a composite mapper. This composite mapper will internally delegate the object mapping to each individual mapper, ensuring that all the registered mappers contribute to the final mapping result.

Call
Description

MapperFactory.GetMapper<TFrom, TTo>()

Resolve a mapper instance (either single, composite or generic)

GetRegisteredMapper<TFrom, TTo>()

Resolve a mapper instance or return null if no mapper is registered for the given type pair.

Map*()

Map object instances (dynamic parameters are allowed)

If no mapper is found for a specific mapping operation, a generic mapper is used, which internally delegates object mapping to MiniMapper.

Implementing a mapper

To add a mapper using MapperFactory, create a class that implements IMapper<TFrom, TTo>. There is nothing wrong with having more than one interface implemented in a single class (e.g. IMapper<News, NewsModel> and IMapper<NewsModel, News>). There is no need for the mapper to be registered in DI.

It is good practice to keep the model and mapper classes in the same file.

public class NewsMapper :
    IMapper<News, NewsModel>,
    IMapper<NewsModel, News>
{
    public NewsItemMapper()
    {
         // Pass dependencies if required
    }

    public Task MapAsync(News from, NewsModel to, dynamic parameters = null)
    {
        // ... map News --> NewsModel ...

        return Task.CompletedTask;
    }

    public Task MapAsync(NewsModel from, News to, dynamic parameters = null)
    {
        // ... map NewsModel --> News ...

        return Task.CompletedTask;
    }
}

The following code shows different ways to map an entity to a model.

News entity = /* get entity instance somehow */;
NewsModel model = new NewsModel();

// Get the mapper = an instance of "NewsMapper" above,
// because "NewsMapper" implements "IMapper<News, NewsModel>"
var mapper = MapperFactory.GetMapper<News, NewsModel>();

// Map News --> NewsModel
await mapper.MapAsync(entity, model);
// - OR, short way without resolving the mapper first -
await MapperFactory.MapAsync(entity, model);

// Map News --> NewsModel with parameters
await mapper.MapAsync(entity, model, new { Option1 = true, Option2 = "stuff" });

Last updated

Was this helpful?