Can you tell AutoMapper to globally ignore missing properties when mapping? - c#

I have quite a bit of entities and so far, I've been doing stuff like
Mapper.CreateMap<Employee, EmployeeDetailsDTO>()
.ForSourceMember(mem => mem.NewsPosts, opt => opt.Ignore());
I want to tell AutoMapper to simply ignore missing properties in the destination object without having to specify each of them. So far, I haven't found a way to do so with my multiple SO and Google searches. Anyone has a solution? I'm ready to do some sort of loop or anything, as long as it can be written once and that it will scale with model / dto changes or added properties.

When are you getting the error? Is it when you call AssertConfigurationIsValid ?
If yes, then simply dont call this method
You dont have to call this method, consider the following mapping which works:
public class Foo1
{
public string Field1 { get; set; }
}
public class Foo2
{
public string Field1 { get; set; }
public string Field2 { get; set; }
}
Mapper.CreateMap<Foo1, Foo2>();
var foo1 = new Foo1() {Field1 = "field1"};
var foo2 = new Foo2();
Mapper.Map(foo1, foo2);//maps correctly, no Exception
You may want to call AssertConfigurationIsValid for other mappings to ensure they are correct so instead what you need to do is organize your mappings into Profiles:
public class MyMappedClassesProfile: Profile
{
protected override void Configure()
{
CreateMap<Foo1, Foo2>();
//nb, make sure you call this.CreateMap and NOT Mapper.CreateMap
//I made this mistake when migrating 'static' mappings to a Profile.
}
}
Mapper.AddProfile<MyMappedClassesProfile>();
and then if you decide you want to check the validity of the mapping (case by case basis in your situation) then call
Mapper.AssertConfigurationIsValid(typeof(MyMappedClassesProfile).FullName);
important in your case and/or any case where you dont call AssertConfigurationIsValid you should use something like AutoFixture and a Unit Test to ensure your mapping is working. (which is the intent of AssertConfigurationIsValid)

Suggested in wal's answer "don't call AssertConfigurationIsValid()" is not safe, as it will hide potential errors in mappings.
It's better to explicitly ignore mapping between classes, for which you are sure that all needed properties already mapped correctly. You can use extensions created in AutoMapper: "Ignore the rest"? answer:
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<Src, Dest>();
cfg.IgnoreUnmapped<Src, Dest>(); // Ignores unmapped properties on specific map
});
The overload without parameters cfg.IgnoreUnmapped(this IProfileExpression profile)
ignores unmapped properties on all maps and not recommended, because it also hides any potential problems for all classes.

If I have many classes with many properties to ignore, I don't want to have exception when call AssertConfigurationIsValid(), but prefer to report it in a log and just review are all unmapped properties missed intentionally.
Because method to do validation is not exposed by AutoMapper, I catch AssertConfigurationIsValid and return error message as string.
public string ValidateUnmappedConfiguration(IMapper mapper)
{
try
{
mapper.ConfigurationProvider.AssertConfigurationIsValid();
}
catch (AutoMapperConfigurationException e)
{
return e.Message;
}
return "";
}
I am calling the ValidateUnmappedConfiguration method from unit test
[TestMethod]
public void LogUmmappedConfiguration()
{
var mapper = new MapperConfiguration((cfg =>
{
cfg.AddProfile(new AutoMapperProfile());
})).CreateMapper();
var msg=ValidateUnmappedConfiguration(mapper) ;
if (!msg.IsNullOrBlank())
{
TestContext.WriteString("Please review the list of unmapped fields and check that it is intentional: \n"+msg);
}
}

Related

How to handle encoded and decoded version of the same c# class

Scenario
I am working on updating my .NET API to encode all database key fields so that the sequential key is not exposed to the end user. I'm using hashids.org for this and have built helper methods to quickly decode/encode properties in my automapper mapping. However, there are multiple versions of the API and only the most current version should be updated with this functionality, which means that I can't simply overwrite my existing classes. I've implemented a few solutions that work, but they all have a bad code smell that I'm hoping to clear up.
Solutions
I am currently performing the encoding at the controller layer. I can see the merits of doing this at the data access layer as well, but feel there is more risk of leaks/missed conversions at that layer, especially since the API has many different data sources. Plus, hiding keys is an issue with the outside world, for which the controller is the gatekeeper, so it feels appropriate there.
The application currently has the following model pattern, which cannot be changed: Model (model that exists in DB) > ValueObject (service model, VO) > DTO (API model).
(1) Initial attempt
Below is an example of a class that needs to support an encoded and decoded state, where Utils.Encode() and Utils.Decode() are helper methods that will convert the field between int and string using Hashids.
//EquipmentDTO.cs
public class EquipmentDTO //encoded class
{
public string Id {get; set;}
public string Name {get; set;}
}
public class EquipmentUnencodedDTO //decoded class
{
public int Id {get; set;}
public string Name {get; set;}
}
//Automapper.cs
CreateMap<EquipmentUnencodedDTO, EquipmentDTO>()
.ForMember(dst => dst.Id, opt => opt.MapFrom(src => Utils.Encode(src.Id)));
CreateMap<EquipmentDTO, EquipmentUnencodedDTO>()
.ForMember(dst => dst.Id, opt => opt.MapFrom(src => Utils.Decode(src.Id)));
CreateMap<EquipmentVO, EquipmentDTO>() //mapping from service model to controller model
.ForMember(dst => dst.Id, opt => opt.MapFrom(src => Utils.Encode(src.Id)));
CreateMap<EquipmentDTO, EquipmentVO>()
.ForMember(dst => dst.Id, opt => opt.MapFrom(src => Utils.Decode(src.Id)));
CreateMap<Equipment, EquipmentVO>() //mapping from DB model to service model
.ForMember(dst => dst.Id, opt => opt.MapFrom(src => src.Id));
I chose to make the existing EquipmentDTO the encoded version
because I want this to become the new standard, which would
eventually lead to the deprecation and removal of
EquipmentUnencodedDTO as the old controllers eventually get
updated.
I chose to not copy CreateMap<EquipmentVO, EquipmentDTO> for CreateMap<EquipmentVO, EquipmentUnencodedDTO> (and the reverse) because
it would lead to a lot of duplication in the AutoMapper file, which
is already huge (though maybe this isn't a real problem?)
I do not like this solution because in my old controllers, the mapping is now confusing. In a POST, for example, the unencoded input DTO has to be converted to the service model via: Mapper.Map<EquipmentVO>(Mapper.Map<EquipmentDTO>(unencodedEquipmentInput)) which is super ugly.
That being said, this is supposedly a temporary problem, so is this a real problem?
This problem would go away if I created CreateMap<EquipmentVO, EquipmentUnencodedDTO>
I do not like this solution because my classes have a lot of duplicated fields that are not changing between the encoded and decoded versions
(2) Second Attempt
The two bullet points above led me to refactor to this:
public class EquipmentDTO
{
public string Id {get; set;}
public string Name {get; set;}
public Decoded Decode(){
return Mapper.Map<Decoded>(this);
}
public class Decoded: EquipmentDTO {
public new int Id {get; set;}
public EquipmentDTO Encode(){
return Mapper.Map<EquipmentDTO>(this);
}
}
}
// Automappers are the same, except EquipmentUnencodedDTO is now EquipmentDTO.Decoded
I like how simple it is to switch between encoded and decoded states now, reducing my double mapping above to: Mapper.Map<EquipmentVO>(unencodedEquipmentInput.Encode());
I like the nested class because it codifies the relationship between the two classes and also does a better job at identifying which fields get encoded/decoded
I think this smells a lot worse
(3) Next Attempt
My next attempt was to add in the missing mappings for the decoded class to the service model and to undo the changes from attempt #2. This created a ton of duplicated mapping code, I'm still stuck with duplicated properties in both classes without a clear indication to which fields get decoded/encoded, and it all feels much more cumbersome than necessary.
Thanks for any advice!
This is one of those answers that doesn't really answer your question directly, but is a different kind of approach to the problem at hand. Based on my comment above.
I would not try to bake in a "hardcoded" transformation, or make the aliasing some intrinsic part of object lifecycle. The idea here is that the transformation of identifiers should be obvious, explicit, and pluggable.
Let's start with an interface:
public interface IObscuredIDProvider
{
public string GetObscuredID(int id);
public void SetObscuredID(int id, string obscuredID);
}
Then, for our testing, a very simple mapper that just returns the int as a string. Your production version can be backed by the hashids.org project or whatever you like:
public class NonObscuredIDProvider : IObscuredIDProvider
{
public string GetObscuredID(int id)
{
return id.ToString();
}
public void SetObscuredID(int id, string obscuredID)
{
// noop
}
}
You'll need to inject the instance of IObscuredIDProvider into whatever layer transforms your "outside/untrusted" data into "trusted/domain" data. This is the place where you will assign the entity IDs from the obscured version to the internal version, and vice versa.
Does that make sense? Hopefully, this is a much more simple to understand and implement solution than baking in a complex, nested transformation....
After a lot of playing around, I ended up going the route of not using automapper and only having a single DTO for both the encoded/unencoded states by using custom getters/setters to control what value would be returned based on a readonly property isEncoded.
My problem with automapper and having multiple DTOs was that there was too much duplication and way too much code to write to add a new decodable DTO. Also, there were too many ways to break the relationship between encodedDTO and unencodedDTO, especially since there are other developers on the team (not to mention future hires) who could forget to create the encoded DTO or to create a mapping to properly encode or decode the ID values.
While I still have separate util methods to perform the encoding of a value, I moved all of the automapper "logic" into a base class EncodableDTO, which would allow a user to run Decode() or Encode() on a DTO to toggle its encoded state, including the encoded state for all of its encodable properties via reflection. Having a DTO inherit EncodableDTO also serves as a clear indicator to developers to what's going on, while custom getters/setters clearly indicate what I'm trying to do for specific fields.
Here's a sample:
public class EquipmentDTO: EncodableDTO
{
private int id;
public string Id {
get
{
return GetIdValue(id);
}
set
{
id = SetIdValue(value);
}
}
public List<PartDTO> Parts {get; set;}
public string Name {get; set;}
}
public class PartDTO: EncodableDTO
{
private int id;
public string Id {
get
{
return GetIdValue(id);
}
set
{
id = SetIdValue(value);
}
}
public string Name {get; set;}
}
public class EncodableDTO
{
public EncodableDTO()
{
// encode models by default
isEncoded = true;
}
public bool isEncoded { get; private set; }
public void Decode()
{
isEncoded = false;
RunEncodableMethodOnProperties(MethodBase.GetCurrentMethod().Name);
}
public void Encode()
{
isEncoded = true;
RunEncodableMethodOnProperties(MethodBase.GetCurrentMethod().Name);
}
protected string GetIdValue(int id)
{
return isEncoded ? Utils.EncodeParam(id) : id.ToString();
}
// TryParseInt() is a custom string extension method that does an int.TryParse and outputs the parameter if the string is not an int
protected int SetIdValue(string id)
{
// check to see if the input is an encoded value, otherwise try to parse it.
// the added logic to test if the 'id' is an encoded value allows the inheriting DTO to be received both in
// unencoded and encoded forms (unencoded/encoded http request) and still populate the correct numerical value for the ID
return id.TryParseInt(-1) == -1 ? Utils.DecodeParam(id) : id.TryParseInt(-1);
}
private void RunEncodableMethodOnProperties(string methodName)
{
var self = this;
var selfType = self.GetType();
// Loop through properties and check to see if any of them should be encoded/decoded
foreach (PropertyInfo property in selfType.GetProperties())
{
var test = property;
// if the property is a list, check the children to see if they are decodable
if (property is IList || (
property.PropertyType.IsGenericType
&& (property.PropertyType.GetGenericTypeDefinition() == typeof(List<>)
|| property.PropertyType.GetGenericTypeDefinition() == typeof(IList<>))
)
)
{
var propertyInstance = (IList)property.GetValue(self);
if (propertyInstance == null || propertyInstance.Count == 0)
{
continue;
}
foreach (object childInstance in propertyInstance)
{
CheckIfObjectEncodable(childInstance, methodName);
}
continue;
}
CheckIfObjectEncodable(property.GetValue(self), methodName);
}
}
private void CheckIfObjectEncodable(object instance, string methodName)
{
if (instance != null && instance.GetType().BaseType == typeof(EncodableDTO))
{
// child instance is encodable. Run the same decode/encode method we're running now on the child
var method = instance.GetType().GetMethod(methodName);
method.Invoke(instance, new object[] { });
}
}
}
An alternative to RunEncodableMethodOnProperties() was the explicitly decode/encode child properties in the inheriting class:
public class EquipmentDTO: EncodableDTO
{
private int id;
public string Id {
get
{
return GetIdValue(id);
}
set
{
id = SetIdValue(value);
}
}
public List<PartDTO> Parts {get; set;}
public string Name {get; set;}
public new void Decode() {
base.Decode();
// explicitly decode child properties
Parts.ForEach(p => p.Decode());
}
}
I chose not to do the above because it created more work for DTO creators to have to remember to explicitly add (1) the override method, and (2) any new decodable properties to the override method. That being said, I'm sure I'm taking some sort of a performance hit by looping through every class of my class' properties and its children, so in time I may have to migrate towards this solution instead.
Regardless of the method I chose to decode/encode properties, here was the end result in the controllers:
// Sample controller method that does not support encoded output
[HttpPost]
public async Task<IHttpActionResult> AddEquipment([FromBody] EquipmentDTO equipment)
{
// EquipmentDTO is 'isEncoded=true' by default
equipment.Decode();
// send automapper the interger IDs (stored in a string)
var serviceModel = Mapper.Map<EquipmentVO>(equipment);
var addedServiceModel = myService.AddEquipment(serviceModel);
var resultValue = Mapper.Map<EquipmentDTO>(addedServiceModel);
resultValue.Decode();
return Created("", resultValue);
}
// automapper
CreateMap<EquipmentVO, EquipmentDTO>().ReverseMap();
CreateMap<Equipment, EquipmentVO>();
While I don't think its the cleanest solution, it hides a lot of the necessary logic to make encoding/decoding work with the least amount of work for future developers

Automapper: Map property in list of objects

I have a list of DTOs and want to map this list to a list of entites. The entity itself has one property which comes from another source. Can I map this property to all items of the list with one map.
My classes:
Entity:
public class Account
{
public int Id {get;set;}
public string Name {get;set;}
public Guid ExternalId {get;set;}
}
DTO:
public class ExternalAccountDto
{
public int Id {get;set;}
public string Name {get;set;}
}
My Service:
public class AccountService
{
public async Task AddExternalAccounts(Guid externalId, List<ExternalAccountDto> accounts)
{
var entities = _mapper.Map(accounts);
// TODO: Map 'externalId' to all entities
// _mapper.Map(externalId, entities); // DOES NOT WORK!
_context.Create(entities);
}
}
Mapping
public class AccountProfile: Profile
{
public AccountProfile()
{
CreateMap<ExternalAccountDto, Account>();
// TODO: CreateMap for Guid on every Account
}
}
Can anyone give me some advice!
You should use the AfterMap function to do some postprocessing on the mapped items.
There are two ways to go about this. One is using something statically defined in the mapping profile. But in your case, you have something that's dynamic at runtime, like the ExternalId. Doing the aftermap in your AccountService then makes perfect sense.
I've found these kind of constructions very useful, especially when I want to consult other injected services for additional information.
public void AddExternalAccounts(Guid externalId, List<ExternalAccountDto> accounts)
{
var entities = _mapper.Map<List<ExternalAccountDto>, List<Account>>(accounts,
options => options.AfterMap((source, destination) =>
{
destination.ForEach(account => account.ExternalId = externalId);
}));
}
Two more cents regarding the AccountProfile class:
You can check upon creation of the mapping profile if the mapping profile is correct. This will save you a headache running into this problem later at runtime. You'll know immediately that there is a problem with the configuration.
var config = new MapperConfiguration(cfg =>
{
cfg.AddProfile<MappingProfile>();
cfg.AllowNullDestinationValues = false;
});
// Check that there are no issues with this configuration, which we'll encounter eventually at runtime.
config.AssertConfigurationIsValid();
_mapper = config.CreateMapper();
This also notified me that an .Ignore() on the ExternalId member of the Account class was required:
CreateMap<ExternalAccountDto, Account>().ForMember(d => d.ExternalId, a => a.Ignore());

Nested Collections Not Working in AutoMapper 5.1

Trying to upgrade to AutoMapper 5.1 from v4.2 and am finding that a collection isn't mapping at runtime - the source object has items in the collection, but the mapped destination property is empty.
Under 4.2, everything worked exactly as expected with the same mapping configuration (save for the MemberList.None in the CreateMap() ctor)
I have DTOs like so
public class GeographicEntity
{
...
}
public class County : GeographicEntity
{
...
}
public class State : GeographicEntity
{
public List<County> Counties { get; } = new List<County>();
}
And viewmodels like so
public class GeographicEntityViewModel
{
...
}
public class CountyViewModel : GeographicEntityViewModel
{
...
}
public class StateViewModel : GeographicEntityViewModel
{
public List<CountyViewModel> Counties { get; } = new List<CountyViewModel>();
}
And Mapping confirmation like so
Mapper.Initialize(configuration =>
{
configuration.CreateMap<GeographicEntity, GeographicEntityViewModel>(MemberList.None);
configuration.CreateMap<County, CountyViewModel>(MemberList.None)
.IncludeBase<GeographicEntity, GeographicEntityViewModel>();
configuration.CreateMap<State, StateViewModel>(MemberList.None)
.IncludeBase<GeographicEntity, GeographicEntityViewModel>();
});
After the Mapper.Map<> call, the Counties collection of the StateViewModel is empty (a list with 0 items) even though the source object has items in its .Counties collection:
var st = new State()
... (initialize the state, including the .Counties list)
var stateViewModel = Mapper.Map<StateViewModel>(st);
Any clues would be appreciated!
After some digging, it turns out that the AutoMapper 5 upgrade introduced some breaking changes. Specifically, the behavior has changed in cases like mine where the destination collection has a getter but no setter. In AutoMapper 4, the default behavior was to use the destination property by default, rather than trying to create a new instance. AutoMapper 5 does NOT do that by default.
The solution is to tell AutoMapper to use the destination value explicitly:
.ForMember(dest => dest.Counties, o => o.UseDestinationValue())
I'm sure there's a good reason for introducing a breaking change like this, but it causes no end of heartache when you've implemented a broad pattern and now have to hunt down and fix every mapped object that might be affected by this change.
I'm almost tempted to just bail on the upgrade and stick with Automapper 4.2, as it did exactly what I needed it to without a lot of extra and unnecessary configuration required.
For more detail, refer to https://github.com/AutoMapper/AutoMapper/issues/1599

AutoMapper ProjectUsing not being called

I'm trying to use the ProjectUsing feature of AutoMapper to only select the columns I need through a LINQ expression but it seems the expression itself does not get called at run-time or via unit testing.
As a test I am just putting a fixed value into the AlternateId property but the assert below always fails. This also fails with single instances (not in a queryable list) and at run-time via Entity Framework 6.
class MapFrom
{
public int Id { get; set; }
}
class MapTo
{
public int AlternateId { get; set; }
}
[TestMethod]
public void Automapper_projectusing_test()
{
AutoMapper
.Mapper
.CreateMap<MapFrom, MapTo>()
.ProjectUsing(src => new MapTo { AlternateId = 88 });
var products = new List<MapFrom>();
products.Add(new MapFrom());
var mapped = products
.AsQueryable() // Just in case ProjectUsing only works with IQueryable.
.Project()
.To<MapTo>()
.ToList();
Assert.AreEqual(88, mapped.Single().AlternateId); // Fails, AlternateId equals 0.
}
Using AutoMapper v3.3.1, NCrunch confirms that the mapping expression code is never executed.
Why is AutoMapper not executing this expression, perhaps I'm missing a key step?
According to Jimmy Bogarde 'This works as designed, you want ConstructUsing.'. See
https://github.com/AutoMapper/AutoMapper/issues/677#event-289561571
Obviously it's caught us all out so I think the documentation could be clearer here.
I was trying to do the same thing and came across this issue and could not figure it out either. I downloaded the Automapper source and had a look at the associated tests.
public ProjectEnumTest()
{
Mapper.CreateMap<Customer, CustomerDto>();
Mapper.CreateMap<CustomerType, string>().ProjectUsing(ct => ct.ToString().ToUpper());
}
[Fact]
public void ProjectingEnumToString()
{
var customers = new[] { new Customer() { FirstName = "Bill", LastName = "White", CustomerType = CustomerType.Vip } }.AsQueryable();
var projected = customers.Project().To<CustomerDto>();
projected.ShouldNotBeNull();
Assert.Equal(customers.Single().CustomerType.ToString().ToUpper(), projected.Single().CustomerType);
}
The short answer is this only appears to work on member level mappings and not first class level mappings? Substituting the mapping with below does not work. The projected value is just a CustomerDto object with null properties.
Mapper.CreateMap<Customer, CustomerDto>().ProjectUsing(c => new CustomerDto{FirstName = "Test"});
From the article listed here: http://lostechies.com/jimmybogard/2014/12/23/automapper-3-3-feature-projection-conversions/ I would assume the above functionality might be intended, but does not work. Either that, or there is a configuration problem with both our understanding of how this works.

Automapper (C#): Nested mappings not working

I have a simple mapping and it is working but it's not filling in Output.Details.
I am a bit confused, I think it maybe because I am using the source as "Task" for each one.
Mapper.CreateMap<Task, Output>();
Mapper.CreateMap<Task, Output.Details>().ForMember(
dest => dest.Item, opt => opt.MapFrom(src => src.Name));
As far as i know i have to create 2 maps, 1 for the object and 1 for object contained within.
Problem is the source for the OUTPUT and OUTPUT.DETAILS can be found in TASK
I tried delving into Details within the first map and specifying Mapfrom but it gives the following error which is why i must create 2 maps
must resolve to top-level member. Parameter name: lambdaExpression error
IList<Task> tempItems= GetItems();
IList<Output> items =
Mapper.Map<IList<Task>, IList<Output>>(tempItems);
The map works but my property "Item" availble in Output.Details is NULL
What am I doing wrong? Here is my Destination object.
It fills in Name no problem, but nothing inside DETAILS... they are left NULL.
Task is not my class, but I checked it and all values are there to be copied hence Tag has a value and is a STRING.
public class Output
{
public string Name { get; set; }
public Details Summary { get; private set; }
public class Details
{
public string Item{ get; set; }
}
public Output()
{
Summary = new Details();
}
}
EDIT
Here is an example of the Task class.
EDIT
They is a sample vs 2010 project here and it shows exactly the problem.
http://dl.dropbox.com/u/20103903/AutomapperNotWorking.zip
and here is an image showing the issue, as you can see Summary Item is "NULL" but it should contain the NAME from Task.
First off, always use Mapper.AssertConfigurationIsValid(); to make sure your mapping configuration is valid. I added it to your code and it immediately highlighted the problem: You didn't tell Automapper what to do with the Summary property. Since Task doesn't contain a property called Summary, Automapper needs to know what to do with it.
So the problem isn't really how to map a nested class, you just need to tell Automapper what to do with Summary. Here's the Automapper configuration that works for your example:
Mapper.CreateMap<Task, Output>()
.ForMember(d => d.Summary, o => o.MapFrom(t => new Output.Details {Item = t.Name}));
Mapper.AssertConfigurationIsValid();
That's all you need.
for the new version, it can be performed as follow:
var config = new MapperConfiguration(cfg => {
cfg.CreateMap<UdtDevQuestion, QuestionViewModel>();
});
config.AssertConfigurationIsValid();

Categories