How to configure Automapper to automatically ignore properties with ReadOnly attribute? - c#

Context:
Let's say I have the following "destination" class:
public class Destination
{
public String WritableProperty { get; set; }
public String ReadOnlyProperty { get; set; }
}
and a "source" class with the ReadOnly attribute on one of it's properties:
public class Source
{
public String WritableProperty { get; set; }
[ReadOnly(true)]
public String ReadOnlyProperty { get; set; }
}
It's obvious, but to be clear: I am going to map from Source class to Destination class in the following way:
Mapper.Map(source, destination);
Problem:
What are the ways to configure Automapper to automatically ignore property with ReadOnly(true) attribute?
Constraints:
I use Automapper's Profile classes for configuration. I don't want to dirty up classes with Automapper-specific attributes. I don't want to configure Automapper for every single read-only property and cause a lot of duplication by this way.
Possible (but not suited) solutions:
1) Add attribute IgnoreMap to the property:
[ReadOnly(true)]
[IgnoreMap]
public String ReadOnlyProperty { get; set; }
I don't want to dirty up classes with automapper-specific attributes and make it dependent from it. Also I don't want to add additional attribute along with ReadOnly attribute.
2) Configure Automapper to ignore the property:
CreateMap<Source, Destination>()
.ForSourceMember(src => src.ReadOnlyProperty, opt => opt.Ignore())
It is not a way because it forces me to do that for every single property everywhere and also causes a lot of duplication.

Write Extension Method as shown below:
public static class IgnoreReadOnlyExtensions
{
public static IMappingExpression<TSource, TDestination> IgnoreReadOnly<TSource, TDestination>(
this IMappingExpression<TSource, TDestination> expression)
{
var sourceType = typeof(TSource);
foreach (var property in sourceType.GetProperties())
{
PropertyDescriptor descriptor = TypeDescriptor.GetProperties(sourceType)[property.Name];
ReadOnlyAttribute attribute = (ReadOnlyAttribute) descriptor.Attributes[typeof(ReadOnlyAttribute)];
if(attribute.IsReadOnly == true)
expression.ForMember(property.Name, opt => opt.Ignore());
}
return expression;
}
}
To call extension method:
Mapper.CreateMap<ViewModel, DomainModel>().IgnoreReadOnly();

Now you could also use ForAllPropertyMaps to disable it globally:
configure.ForAllPropertyMaps(map =>
map.SourceMember.GetCustomAttributes().OfType<ReadOnlyAttribute>().Any(x => x.IsReadOnly),
(map, configuration) =>
{
configuration.Ignore();
});

If you wanted to only map properties that have a certain attribute, in my case the [DataMember] attribute, I wrote a method based on the excellent reply above to handle this for both the source and destination:
public static class ClaimMappingExtensions
{
public static IMappingExpression<TSource, TDestination> IgnoreAllButMembersWithDataMemberAttribute<TSource, TDestination>(
this IMappingExpression<TSource, TDestination> expression)
{
var sourceType = typeof(TSource);
var destinationType = typeof(TDestination);
foreach (var property in sourceType.GetProperties())
{
var descriptor = TypeDescriptor.GetProperties(sourceType)[property.Name];
var hasDataMemberAttribute = descriptor.Attributes.OfType<DataMemberAttribute>().Any();
if (!hasDataMemberAttribute)
expression.ForSourceMember(property.Name, opt => opt.Ignore());
}
foreach (var property in destinationType.GetProperties())
{
var descriptor = TypeDescriptor.GetProperties(destinationType)[property.Name];
var hasDataMemberAttribute = descriptor.Attributes.OfType<DataMemberAttribute>().Any();
if (!hasDataMemberAttribute)
expression.ForMember(property.Name, opt => opt.Ignore());
}
return expression;
}
}
It will be called as the other method did:
Mapper.CreateMap<ViewModel,DomainModel>().IgnoreAllButMembersWithDataMemberAttribute();

Related

AutoMapper throws exception when projecting to nullable enum

In my project I have a Linq queryable (actually an EF6 collection) that I need to convert to a collection of data transfer objects. I'm using AutoMapper throughout the project, especially for its ability to do type projection thereby reducing the amount of SQL generated by the Linq query.
But I've got a small problem with one of my DTO classes. The associated database column contains a nullable string, which I wish to map to a nullable enum. But at runtime an exception is thrown
No coercion operator is defined between types 'System.String' and 'System.Nullable`1[AutomapperEnumTest.Method]'.
Here's a complete test program that demonstrates the problem: (see .Net Fiddle)
using System;
using System.Linq;
using AutoMapper;
using AutoMapper.QueryableExtensions;
namespace AutomapperEnumTest
{
public class Program
{
public static void Main()
{
Configure();
var src = new SourceType[] { new SourceType { Name = "Rob", MethodName = "AUTO" } };
var results = src.AsQueryable()
.ProjectTo<DestType>();
foreach(var item in results)
{
Console.WriteLine(String.Format("DestType Name={0} Method={1}", item.Name, item.Method));
}
Console.WriteLine("Done");
}
private static void Configure()
{
Mapper.Initialize(cfg =>
{
cfg.CreateMap<string, Method?>().ProjectUsing(src => src == "MANUAL" ? Method.MANUAL : Method.AUTO);
cfg.CreateMap<SourceType, DestType>()
.ForMember(dest => dest.Method, opt => opt.MapFrom(src => src.MethodName));
});
}
}
public enum Method
{
MANUAL=0,
AUTO=1
}
public class DestType
{
public string Name {get; set; }
public Method? Method {get; set; }
}
public class SourceType
{
public string Name {get; set; }
public string MethodName {get; set; }
}
}
Now if I change the property from Method? to Method it works fine (see this modification in .Net Fiddle). But I don't want to do this, so my question is how do I inform Linq/AutoMapper that it should use my ProjectUsing for the nullable enum?
Many thanks.
The same happens in the latest AutoMapper v5.2.0.
After looking at the source code, I think it is a bug inside the ExpressionBuilder class because for some unknown reason NullableExpressionBinder is given higher priority than CustomProjectionExpressionBinder (and others), so basically when you map non nullable type to nullable, all the custom mappings are ignored.
I would suggest you to report it on their repository. Until then, I could suggest you the following workaround (hack). Include the following custom class in your project:
using System.Linq.Expressions;
using System.Reflection;
using AutoMapper;
using AutoMapper.QueryableExtensions;
using AutoMapper.QueryableExtensions.Impl;
class NullableExpressionBinderEx : IExpressionBinder
{
public static void Install()
{
var bindersField = typeof(ExpressionBuilder).GetField("Binders", BindingFlags.NonPublic | BindingFlags.Static);
var binders = (IExpressionBinder[])bindersField.GetValue(null);
binders[0] = new NullableExpressionBinderEx();
}
IExpressionBinder baseBinder = new NullableExpressionBinder();
private NullableExpressionBinderEx() { }
public bool IsMatch(PropertyMap propertyMap, TypeMap propertyTypeMap, ExpressionResolutionResult result)
{
if (propertyTypeMap != null && propertyTypeMap.CustomProjection != null)
return false;
return baseBinder.IsMatch(propertyMap, propertyTypeMap, result);
}
public MemberAssignment Build(IConfigurationProvider configuration, PropertyMap propertyMap, TypeMap propertyTypeMap, ExpressionRequest request, ExpressionResolutionResult result, IDictionary<ExpressionRequest, int> typePairCount)
{
return baseBinder.Build(configuration, propertyMap, propertyTypeMap, request, result, typePairCount);
}
}
then add the following line to your Configure method:
NullableExpressionBinderEx.Install();
and the issue should be solved.
You could map MethodName to Method manually, unless I'm missing something in your question.
private static void Configure()
{
Mapper.Initialize(cfg =>
{
cfg.CreateMap<SourceType, DestType>()
.ForMember(dest => dest.Method, opt => opt.MapFrom(src =>
src.MethodName == "MANUAL" ? Method.MANUAL : Method.AUTO));
});
}

AutoMapper auto create createMap

I have a services that is calling another services. Both of the services are using "the same classes". The classes are named same and have the same properties but has different namespace so I need to use AutoMapper to map from one of the type to the other type.
No it's pretty simple since all I have to do is the CreateMap<>, but the problem is that we have around hundreds of classes that I manually needs to write the CreateMap<> from, and it's works wired to me. Isn't there any Auto CreateMap function. So if I say CreateMap() then AutoMapper workes thru Organisation and finds all classes and automatically does the CreateMap for these Classes and it's subclasses etc etc…
Hope for a simple solution, or I guess some reflection can fix it...
Just set CreateMissingTypeMaps to true in the options:
var dto = Mapper.Map<FooDTO>
(foo, opts => opts.CreateMissingTypeMaps = true);
If you need to use it often, store the lambda in a delegate field:
static readonly Action<IMappingOperationOptions> _mapperOptions =
opts => opts.CreateMissingTypeMaps = true;
...
var dto = Mapper.Map<FooDTO>(foo, _mapperOptions);
UPDATE:
The approach described above no longer works in recent versions of AutoMapper.
Instead, you should create a mapper configuration with CreateMissingTypeMaps set to true and create a mapper instance from this configuration:
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMissingTypeMaps = true;
// other configurations
});
var mapper = config.CreateMapper();
If you want to keep using the old static API (no longer recommended), you can also do this:
Mapper.Initialize(cfg =>
{
cfg.CreateMissingTypeMaps = true;
// other configurations
});
UPDATE 2 - Automapper 9 and later:
Starting from Automapper version 9.0, the CreateMissingTypeMaps API was removed. Automapper documentation now suggests to explicitly configure maps, manually or using reflection.
https://docs.automapper.org/en/stable/9.0-Upgrade-Guide.html#automapper-no-longer-creates-maps-automatically-createmissingtypemaps-and-conventions
CreateMissingTypeMaps can be set within your profile. It's however recommended to explicitly use CreateMap for each mapping and call AssertConfigurationIsValid in your unit tests for each profile to prevent silent errors.
public class MyProfile : Profile {
CreateMissingTypeMaps = true;
// Mappings...
}
AutoMapper has a DynamicMap method which you might be able to use: here's an example unit test illustrating it.
[TestClass]
public class AutoMapper_Example
{
[TestMethod]
public void AutoMapper_DynamicMap()
{
Source source = new Source {Id = 1, Name = "Mr FooBar"};
Target target = Mapper.DynamicMap<Target>(source);
Assert.AreEqual(1, target.Id);
Assert.AreEqual("Mr FooBar", target.Name);
}
private class Target
{
public int Id { get; set; }
public string Name { get; set; }
}
private class Source
{
public int Id { get; set; }
public string Name { get; set; }
}
}
Set CreateMissingTypeMaps option to true. This is package AutoMapper.Extensions.Microsoft.DependencyInjection's example for ASP.NET Core:
public class Startup {
//...
public void ConfigureServices(IServiceCollection services) {
//...
services.AddAutoMapper(cfg => { cfg.CreateMissingTypeMaps = true; });
//...
}
//...
}
In case someone is still interested in this topic, I've created a NuGet package that gives the automatic mapping functionality since AutoMapper removed it in a certain version.
It's available under wakiter.AutoMapper.Extensions name.
To use it, invoke the CreateAutoMap extension method and it'll do the work for you.
Today I needed this in some generic code as well. I tried something like this:
private static IMapper CreateMapper<T1, T2>()
{
return new MapperConfiguration(cfg => FillMapperConfig(cfg, typeof(T1), typeof(T2)))
.CreateMapper();
}
private static void FillMapperConfig(IMapperConfigurationExpression cfg, Type T1, Type T2)
{
if (T1 == T2)
{
return;
}
cfg.CreateMap(T1, T2);
foreach (PropertyInfo propertyInfo in T1.GetProperties())
{
PropertyInfo correspondingProperty =
T2.GetProperties()
.FirstOrDefault(p =>
p.Name == propertyInfo.Name);
if (correspondingProperty != null)
{
if (propertyInfo.PropertyType.IsGenericType &&
correspondingProperty.PropertyType.IsGenericType)
{
FillMapperConfig(
cfg,
propertyInfo.PropertyType.GetGenericArguments()[0],
correspondingProperty.PropertyType.GetGenericArguments()[0]);
}
else if (propertyInfo.PropertyType.IsClass &&
correspondingProperty.PropertyType.IsClass)
{
FillMapperConfig(
cfg,
propertyInfo.PropertyType,
correspondingProperty.PropertyType);
}
}
}
}
Then I can do something like this:
IMapper mapper = CreateMapper<ClassA, ClassB>();
Which creates a map from ClassA to ClassB with all sub properties of ClassA and ClassB if they have the same name and recursively for sub sub properties.
Example:
public class ClassA {
public int IntProperty { get; set; }
public ClassASubProperty SubProperty { get; set; }
public List<ClassAListItem> ListItems { get; set; }
}
public class ClassB {
public int IntProperty { get; set; }
public ClassBSubProperty SubProperty { get; set; }
public List<ClassBListItem> ListItems { get; set; }
}
This should result in the IMapper equivalent:
new MapperConfiguration(cfg => {
cfg.CreateMap<ClassA, ClassB>();
cfg.CreateMap<ClassASubProperty, ClassBSubProperty>();
cfg.CreateMap<ClassAListItem, ClassBListItem>()
}).CreateMapper();

AutoMapper Flattening an Extension method

I'm looking for the simplest / most elegant way to flatten a source object utilizing extension methods of the source object.
Source:
class Source
{
public int Value1 { get; set; }
public int Value2 { get; set; }
}
Extension method I'd like to elegantly map:
static class SourceExtensions
{
public static int GetTotal(this Source source)
{
return source.Value1 + source.Value2;
}
}
Destination:
class Destination
{
public int Value1 { get; set; }
public int Value2 { get; set; }
public int Total { get; set; }
}
Is there a better way than this (one where I don't have to call out every extension method)?
using NamespaceContainingMyExtensionMethods;
...
Mapper.CreateMap<Source, Destination>()
.ForMember(destination => destination.Total,
opt => opt.ResolveUsing(source => source.GetTotal()));
Something like:
Mapper.CreateMap<Source, Destination>()
.ResolveUsingExtensionsInNamespace("NamespaceContainingMyExtensionMethods");
I know I can use an inheritance heirarchy on the source objects, but in my situation, it isn't ideal.
I've researched:
Does AutoMapper's convention based mappings work with LINQ extension methods? and https://github.com/AutoMapper/AutoMapper/issues/34
Added commit to my fork and make a pull request for this. Works like a charm!
commit: https://github.com/claycephus/AutoMapper/commit/e1aaf9421c63fb15daca02607d0fc3dff871fbd1
pull request: https://github.com/AutoMapper/AutoMapper/pull/221
Configure it by specifying assemblies to search:
Assembly[] extensionMethodSearch = new Assembly[] { Assembly.Load("Your.Assembly") };
Mapper.Initialize(config => config.SourceExtensionMethodSearch = extensionMethodSearch);
Mapper.CreateMap<Source, Destination>();
Clay stuff is still there but it has been refactored over time. For those searching for this in automapper, you should use:
var config = new MapperConfiguration(cfg => {
cfg.IncludeSourceExtensionMethods(typeof(SourceExtensions));
cfg.CreateMap<Source, Destination>();
});
var mapper = config.CreateMapper();

A better way to use AutoMapper to flatten nested objects?

I have been flattening domain objects into DTOs as shown in the example below:
public class Root
{
public string AParentProperty { get; set; }
public Nested TheNestedClass { get; set; }
}
public class Nested
{
public string ANestedProperty { get; set; }
}
public class Flattened
{
public string AParentProperty { get; set; }
public string ANestedProperty { get; set; }
}
// I put the equivalent of the following in a profile, configured at application start
// as suggested by others:
Mapper.CreateMap<Root, Flattened>()
.ForMember
(
dest => dest.ANestedProperty
, opt => opt.MapFrom(src => src.TheNestedClass.ANestedProperty)
);
// This is in my controller:
Flattened myFlattened = Mapper.Map<Root, Flattened>(myRoot);
I have looked at a number of examples, and so far this seems to be the way to flatten a nested hierarchy. If the child object has a number of properties, however, this approach doesn't save much coding.
I found this example:
http://consultingblogs.emc.com/owainwragg/archive/2010/12/22/automapper-mapping-from-multiple-objects.aspx
but it requires instances of the mapped objects, required by the Map() function, which won't work with a profile as I understand it.
I am new to AutoMapper, so I would like to know if there is a better way to do this.
I much prefer avoiding the older Static methods and do it like this.
Place our mapping definitions into a Profile. We map the Root first, and then apply the mappings of the Nested afterwards. Note the use of the Context.
public class MappingProfile : Profile
{
public MappingProfile()
{
CreateMap<Root, Flattened>()
.AfterMap((src, dest, context) => context.Mapper.Map(src.TheNestedClass, dest));
CreateMap<Nested, Flattened>();
}
}
The advantage of defining both the mapping from Root to Flattened and Nested to Flatterned is that you retain full control over the mapping of the properties, such as if the destination property name is different or you want to apply a transformation etc.
An XUnit test:
[Fact]
public void Mapping_root_to_flattened_should_include_nested_properties()
{
// ARRANGE
var myRoot = new Root
{
AParentProperty = "my AParentProperty",
TheNestedClass = new Nested
{
ANestedProperty = "my ANestedProperty"
}
};
// Manually create the mapper using the Profile
var mapper = new MapperConfiguration(cfg => cfg.AddProfile(new MappingProfile())).CreateMapper();
// ACT
var myFlattened = mapper.Map<Root, Flattened>(myRoot);
// ASSERT
Assert.Equal(myRoot.AParentProperty, myFlattened.AParentProperty);
Assert.Equal(myRoot.TheNestedClass.ANestedProperty, myFlattened.ANestedProperty);
}
By adding AutoMapper's serviceCollection.AddAutoMapper() from the AutoMapper.Extensions.Microsoft.DependencyInjection nuget package to your start up, the Profile will be picked up automatically, and you can simply inject IMapper into wherever you are applying the mapping.
In the latest version of AutoMapper, there's a naming convention you can use to avoid multiple .ForMember statements.
In your example, if you update your Flattened class to be:
public class Flattened
{
public string AParentProperty { get; set; }
public string TheNestedClassANestedProperty { get; set; }
}
You can avoid the use of the ForMember statement:
Mapper.CreateMap<Root, Flattened>();
Automapper will (by convention) map Root.TheNestedClass.ANestedProperty to Flattened.TheNestedClassANestedProperty in this case. It looks less ugly when you're using real class names, honest!
2 more possible solutions:
Mapper.CreateMap<Nested, Flattened>()
.ForMember(s=>s.AParentProperty, o=>o.Ignore());
Mapper.CreateMap<Root, Flattened>()
.ForMember(d => d.ANestedProperty, o => o.MapFrom(s => s.TheNestedClass));
An alternative approach would be the below, but it would not pass the Mapper.AssertConfigurationIsValid().
Mapper.CreateMap<Nested, Flattened>()
//.ForMember map your properties here
Mapper.CreateMap<Root, Flattened>()
//.ForMember... map you properties here
.AfterMap((s, d) => Mapper.Map(s.TheNestedClass, d));
Not sure if this adds value to the previous solutions, but you could do it as a two-step mapping. Be careful to map in correct order if there are naming conflicts between the parent and child (last wins).
Mapper.CreateMap<Root, Flattened>();
Mapper.CreateMap<Nested, Flattened>();
var flattened = new Flattened();
Mapper.Map(root, flattened);
Mapper.Map(root.TheNestedClass, flattened);
To improve upon another answer, specify MemberList.Source for both mappings and set the nested property to be ignored. Validation then passes OK.
Mapper.Initialize(cfg =>
{
cfg.CreateMap<SrcNested, DestFlat>(MemberList.Source);
cfg.CreateMap<SrcRoot, DestFlat>(MemberList.Source)
.ForSourceMember(s => s.Nested, x => x.Ignore())
.AfterMap((s, d) => Mapper.Map(s.Nested, d));
});
Mapper.AssertConfigurationIsValid();
var dest = Mapper.Map<SrcRoot, DestFlat>(src);
I wrote extension method to solve similar problem:
public static IMappingExpression<TSource, TDestination> FlattenNested<TSource, TNestedSource, TDestination>(
this IMappingExpression<TSource, TDestination> expression,
Expression<Func<TSource, TNestedSource>> nestedSelector,
IMappingExpression<TNestedSource, TDestination> nestedMappingExpression)
{
var dstProperties = typeof(TDestination).GetProperties().Select(p => p.Name);
var flattenedMappings = nestedMappingExpression.TypeMap.GetPropertyMaps()
.Where(pm => pm.IsMapped() && !pm.IsIgnored())
.ToDictionary(pm => pm.DestinationProperty.Name,
pm => Expression.Lambda(
Expression.MakeMemberAccess(nestedSelector.Body, pm.SourceMember),
nestedSelector.Parameters[0]));
foreach (var property in dstProperties)
{
if (!flattenedMappings.ContainsKey(property))
continue;
expression.ForMember(property, opt => opt.MapFrom((dynamic)flattenedMappings[property]));
}
return expression;
}
So in your case it can be used like this:
var nestedMap = Mapper.CreateMap<Nested, Flattened>()
.IgnoreAllNonExisting();
Mapper.CreateMap<Root, Flattened>()
.FlattenNested(s => s.TheNestedClass, nestedMap);
IgnoreAllNonExisting() is from here.
Though it's not universal solution it should be enough for simple cases.
So,
You don't need to follow flattening convention in destination's properties
Mapper.AssertConfigurationIsValid() will pass
You can use this method in non-static API (a Profile) as well
I created a simple example using AutoMappers new naming convention rules to map from flattened to nested objects, hope this helps
https://dotnetfiddle.net/i55UFK

Map only changed properties?

Using AutoMapper, is it possible to map only the changed properties from the View Model to the Domain Object?
The problem I am coming across is that if there are properties on the View Model that are not changed (null), then they are overwriting the Domain Objects and getting persisted to the database.
Yes, it can be done, but you have to specify when to skip the destination property using Condition() in your mapping configuration.
Here's an example. Consider the following classes:
public class Source
{
public string Text { get; set; }
public bool Map { get; set; }
}
public class Destination
{
public string Text { get; set; }
}
The first map won't overwrite destination.Text, but the second will.
Mapper.CreateMap<Source, Destination>()
.ForMember(dest => dest.Text, opt => opt.Condition(src => src.Map));
var source = new Source { Text = "Do not map", Map = false };
var destination = new Destination { Text = "Leave me alone" };
Mapper.Map(source, destination);
source.Map = true;
var destination2 = new Destination { Text = "I'll be overwritten" };
Mapper.Map(source, destination2);
#Matthew Steven Monkan is correct, but seems AutoMapper changed API. I will put new one for others refer.
public static IMappingExpression<TSource, TDestination> MapOnlyIfChanged<TSource, TDestination>(this IMappingExpression<TSource, TDestination> map)
{
map.ForAllMembers(source =>
{
source.Condition((sourceObject, destObject, sourceProperty, destProperty) =>
{
if (sourceProperty == null)
return !(destProperty == null);
return !sourceProperty.Equals(destProperty);
});
});
return map;
}
that's all
For Automapper version < 6.0
Yes; I wrote this extension method to map only dirty values from a model to Entity Framework.
public static IMappingExpression<TSource, TDestination> MapOnlyIfDirty<TSource, TDestination>(
this IMappingExpression<TSource, TDestination> map)
{
map.ForAllMembers(source =>
{
source.Condition(resolutionContext =>
{
if (resolutionContext.SourceValue == null)
return !(resolutionContext.DestinationValue == null);
return !resolutionContext.SourceValue.Equals(resolutionContext.DestinationValue);
});
});
return map;
}
Example:
Mapper.CreateMap<Model, Domain>().MapOnlyIfDirty();
No.
This is exactly one of the reasons you never map from viewmodel to domain model. Domain/business model changes are too important for a tool to handle.
Automapper is not designed to handle this scenario:
AutoMapper works because it enforces a convention. It assumes that
your destination types are a subset of the source type. It assumes
that everything on your destination type is meant to be mapped. It
assumes that the destination member names follow the exact name of the
source type. It assumes that you want to flatten complex models into
simple ones.
From: https://jimmybogard.com/automappers-design-philosophy/
Manually:
customer.LastName = viewModel.LastName
Changing business state is too important to do otherwise.

Categories

Resources