UI Configurator

⭐ UI panel for mapping configs

MappingGenerator in the premium version provides a dedicated UI panel for controlling different aspects of code generation. For non-premium users, this panel is available in preview mode which means you can check how different options affect the generated code but you can’t use the output.

General settings

Clone Collections of simple types

This option allows controlling how mapping code generation should handle collections of simple types (primitives, enums, Guid or string). When this option is selected, the source collection is cloned by adding ToList() or ToArray() method invocation. When the option is unchecked then source collection is directly assigned to the target.

Initialize members of readonly target

MappingGenerator always respects access modifiers while searching for mapping sources and targets. When a type member is not accessible in a given context then it’s excluded from the mapping. However, C# allows for setting fields and properties of readonly members using a special initialization block syntax.

For complex types:

public static UserDTO Map(UserEntity entity)
{
    return new UserDTO
    {
        Id = entity.Id,
        Version = entity.Version,
        // INFO: MainAddress is a property with private setter       
        MainAddress =
        {
            City = entity.MainAddress.City,
            ZipCode = entity.MainAddress.ZipCode,
            Street = entity.MainAddress.Street,
            FlatNo = entity.MainAddress.FlatNo,
            BuildingNo = entity.MainAddress.Building_No
        }
    };
}

For collections (when defines Add(IEnumerable<T> items) method):

    public static UserDTO Map(UserEntity entity)
    {
        return new UserDTO
        {
            Id = entity.Id,
            Version = entity.Version,
            // INFO: Addresses is a collection property with private setter. This collection type expose Add(IEnumerable<T> items) method
            Addresses = {
                entity.Addresses.ConvertAll(entityAddress => new AddressDTO
                {
                    City = entityAddress.City,
                    ZipCode = entityAddress.ZipCode,
                    Street = entityAddress.Street,
                    FlatNo = entityAddress.FlatNo,
                    BuildingNo = entityAddress.Building_No
                })
            }
        };
    }
}

This option works only inside the initialization block.

There’s no way to determine if the readonly member’s value was already assigned to an existing object instance. If the reference points to null value then initialization block execution results in NullReferenceException in the runtime.

Use named arguments for constructor invocations

This option expresses the preference for constructor invocation syntax. Depends on the selection, when there’s a need to call the existing constructor to satisfy type compatibility between source and target, the named arguments are used or not.

Option checked:

public static UserDTO Map(UserEntity entity)
{
    return new UserDTO
    {
        Id = entity.Id,
        Version = entity.Version,
        // INFO: Constructor called with named arguments
        Name = new FullNameDTO(firstName: entity.Name.FirstName, lastName: entity.Name.LastName)
    };
}

Option unchecked:

public static UserDTO Map(UserEntity entity)
{
    return new UserDTO
    {
        Id = entity.Id,
        Version = entity.Version,
        // INFO: Constructor called without specifying parameters' names
        Name = new FullNameDTO(entity.Name.FirstName, entity.Name.LastName)
    };
}

Use target typed constructor invocation

This option allows to change the constructor invocation’s syntax to the new() notation introduced in C# 9:

public static UserDTO Map(UserEntity entity)
{
    return new()
    {
        Id = entity.Id,
        Version = entity.Version,
        Name = new(entity.Name.FirstName, entity.Name.LastName),
        Age = entity.Age,
        Account = new()
        {
            BankName = entity.Account.BankName,
            Number = entity.Account.Number
        }
    };
}

Format constructor as multiline

This option switches constructor invocation’s formatting to multi-line mode, where every argument is put in a separated line.

public static UserDTO Map(UserEntity entity)
{
    return new UserDTO
    {
        Id = entity.Id,
        Version = entity.Version,
        Name = new FullNameDTO
        (
            firstName: entity.Name.FirstName,
            lastName: entity.Name.LastName
        )
    };
}

Target fields order

This option controls the order of mapping expression based on the targets. There are available the following sorting criteria:

  • Alphabetical order - expressions are defined in order by sorting targets alphabetically (ascending or descending)
  • Declaration order - expressions are defined in the same order as the targets are declared in the containing type. If there is a type inheritance, then members from the base type have higher priority. Sorting can be ascending or descending based on the selection.

Unmapped sources

By default, MappingGenerator ignores sources when it is not able to locate the corresponding mapping target (the target is not accessible or doesn’t exist). This option allows for overriding this behavior and we have the following strategies for handling unmatched sources:

  • Ignore - This is the default behavior.
  • Comment out - The unfinished initialization expression is emitted as //TODO: ??? = {SourceName} comment
  • Leave code non-compilable - The source is assigned to the invalid ??? target resulting in the non-compilable output code.

Unmapped targets

By default, MappingGenerator ignores targets when it is not able to locate the corresponding mapping source (the source is not accessible or doesn’t exist). This option allows for overriding this behavior and we have the following strategies for handling unmatched targets:

  • Ignore - This is the default behavior.
  • Assign default - The default keyword is used to initialize the target’s members
  • Comment out - The unfinished initialization expression is emitted as //TODO: {TargetName} = ??? comment
  • Leave code non-compilable - The target is assigned to the invalid ??? value resulting in the non-compilable output code.

Nullable values

This option defines how mapping from nullable value source to non-nullable target should look like. We have the following options for this setting:

  • Throw exception when null
    target.Property = source.Property ?? throw new ArgumentNullException(nameof(source), "The value of 'source.Property' should not be null")
    
  • Fallback to default
    target.Property = source.Property.GetValueOrDefault()
    
  • Force accessing Value
    target.Property = source.Property.Value
    

Matching

Allow for type flattening

This option affects the source-to-target matching algorithm by extending source searching on nested properties when there is no direct match. It allows achieving mappings that project complex objects to a flat structure.

public static UserDTO Map(UserEntity entity)
{
    return new UserDTO
    {
        Id = entity.Id,
        Version = entity.Version,
        // INFO: Properties from the 'Name' sub-object matched directly to the main object properties
        FirstName = entity.Name.FirstName,
        LastName = entity.Name.LastName,
        // INFO: Properties from the 'MainAddress' sub-object matched directly to the main object properties
        City = entity.MainAddress.City,
        ZipCode = entity.MainAddress.ZipCode,
        Street = entity.MainAddress.Street,
        BuildingNo = entity.MainAddress.Building_No,
        FlatNo = entity.MainAddress.FlatNo
    };
}

Allow for congruent matching

This option extends source-to-target matching algorithm with another fallback that tries to match both sides by treating them as prefix/suffix of each other. It allows to achieve more complete mapping with one of the sides contains extra prefix/suffix and matching is only partial:

public static UserDTO Map(UserEntity entity)
{
    return new UserDTO
    {
        // INFO: 'Id' matched with 'UserId'
        Id = entity.UserId,
        Version = entity.Version,
        // INFO: 'FullName' matched with 'Name'
        FullName = entity.Name,
        Age = entity.Age
    };
}

Generated conversion

Sub mappings

These settings determine how the sub-mappings of a complex object should be handled. There are the following options to chose from:

Option Explanation
Inline All sub-mappings are generated inline as a part of one expression. Code duplication possible.
Always extract methods Mapping of every complex sub-property is extracted to separated methods.
Avoid duplication and extract methods Mappings of complex objects are extracted to separated methods only when occurring more than once.

Method extraction

This option specifies the location of the generated sub-mapping methods. They can be emitted as local functions inside the currently implemented method or as containing type members.

Local function:

public static UserDTO Map(UserEntity entity)
{
    // INFO: Mapping for Address object extracted to local function
    AddressDTO MapFromAddressEntityToAddressDTO(AddressEntity source)
    {
        return new AddressDTO
        {
            City = source.City,
            ZipCode = source.ZipCode,
            Street = source.Street,
            FlatNo = source.FlatNo,
            BuildingNo = source.BuildingNo
        };
    }

    return new UserDTO
    {
        Id = entity.Id,
        FirstName = entity.FirstName,               
        LastName = entity.LastName,                
        Address = MapFromAddressEntityToAddressDTO(entity.Address),                
    };
}

Containing type members:

public class TestMapper
{
    public static UserDTO Map(UserEntity entity)
    {
        return new UserDTO
        {
            Id = entity.Id,
            FirstName = entity.FirstName,               
            LastName = entity.LastName,                
            Address = MapFromAddressEntityToAddressDTO(entity.Address),                
        };
    }

    // INFO: Mapping for Address object extracted to member function
    private static AddressDTO MapFromAddressEntityToAddressDTO(AddressEntity source)
    {
        return new AddressDTO
        {
            City = source.City,
            ZipCode = source.ZipCode,
            Street = source.Street,
            FlatNo = source.FlatNo,
            BuildingNo = source.BuildingNo
        };
    }
}

Extracted method naming

This option defines the naming of the extracted method sub-mapping methods:

Strategy Example for mapping AddressEntity to AddressDTO
Map{SourceTypeName}To{TargetTypeName} MapAddressEntityToAddressDTO
Map{CommonTypeNamePart} MapAddress
Map Map

Enum mappings

This setting allows for controlling how to perform the conversions between two different enum types. We can choose one of the following options:

  • Cast - The source is explicitly cast to the target enum type.

    public static UserDTO Map(UserEntity entity)
    {
        return new UserDTO
        {
            Id = entity.Id,
            Version = entity.Version,
            // INFO: Enum value casted to the target enum type
            Authentication = (AuthenticationKindAPI)entity.Authentication
        };
    }
    
  • Map based on the option name - The switch expression is generated that maps source enum options to the matched targe options. Options from a source are matched to target based on the option names. Names are compared in a case insensitive manner ignoring underscore characters. Unmatched options are controller by Unmapped targets settings. For Visual Studio 2017, instead of switch expression, the switch statement is generated and extracted to a separated function.

    public static UserDTO Map(UserEntity entity)
    {
        return new UserDTO
        {
            Id = entity.Id,
            Version = entity.Version,
            // INFO: Enum values mapped with switch expression by matching enum option names
            Authentication = entity.Authentication switch
            {
                AuthenticationKind.Password => AuthenticationKindAPI.Password,
                AuthenticationKind.Active_Directory => AuthenticationKindAPI.ActiveDirectory,
                AuthenticationKind.Social_Media => AuthenticationKindAPI.SocialMedia,
                _ => throw new InvalidEnumArgumentException(nameof(entity.Authentication), (int)entity.Authentication, typeof(AuthenticationKind))
            }
        };
    }
    
  • Map based on the option value - The switch expression is generated that maps source enum options to the matched targe options. Options from a source are matched to target based on the option values. Unmatched options are controller by Unmapped targets settings. For Visual Studio 2017, instead of switch expression, the switch statement is generated and extracted to a separated function.

    public enum AuthenticationKind
    {
        Password,
        Windows,
        SSO
    }
    
    public enum AuthenticationKindAPI
    {
        Password,
        ActiveDirectory,
        SocialMedia
    }
    
    public static UserDTO Map(UserEntity entity)
    {
        return new UserDTO
        {
            Id = entity.Id,
            Version = entity.Version,
            // INFO: Enum values mapped with switch expression by matching enum option values
            Authentication = entity.Authentication switch
            {
                AuthenticationKind.Password => AuthenticationKindAPI.Password,
                AuthenticationKind.Windows => AuthenticationKindAPI.ActiveDirectory,
                AuthenticationKind.SSO => AuthenticationKindAPI.SocialMedia,
                _ => throw new InvalidEnumArgumentException(nameof(entity.Authentication), (int)entity.Authentication, typeof(AuthenticationKind))
            }
        };
    }
    

Existing conversion sources

MappingGenerator can re-use existing methods for conversions between different types. A method is classified as a conversion method when accepts a single parameter and returns a non-void type. The following options allow for enabling different sources of the existing conversion methods:

Option Explanation
Containing type methods Mapping generator scans currently implemented method’s containing type for conversion methods
Containing type dependencies Mapping generator scans currently implemented method’s containing type’s properties and fields for conversion methods
Source instance methods Conversion methods are searched among source type’s instance methods
Source static methods Conversion methods are searched among source type’s static methods
Target static methods Conversion methods are searched among target’s type static methods
Extension methods Conversion methods are searched among extension methods available in the current scope