Automapper - Mapping the index into a property of collection - c#

I'm mapping a domain model to a DTO and vice versa. I'm trying to configure my API to accept a DTO with a collection, where the order of that collection will map to a int Sequence in my domain object for persistence.
public class Model {
public ICollection<Fields> Fields { get; set; }
}
public class Field {
public int Sequence { get; set; }
}
CreateMap<ModelView, Model>()
.ForMember(x => x.Fields, opt => opt...)
// here I want to specify that currentField.Sequence = Model.Fields.IndexOf(currentField)
// , or to set it equal to some counter++;
;
Is such a thing possible in Automapper, or would I have to write my own ConstructUsing() method to do this logic? I'm hesitant to use ConstructUsing() because I have a mapping specified for the Field DTO and I don't want to duplicate that logic.
I also would like to be able to configure it so that when I'm going back to my DTO (Model -> ModelView) that I can insert the Fields into the collection in the order specified by Sequence.

I think I found the solution I was looking for. Using AfterMap() I'm able to override these values from being mapped directly:
CreateMap<Model, ModelView>()
.AfterMap((m, v) =>
{
v.Fields = v.Fields?.OrderBy(x => x.Sequence).ToList();
//ensure that the DTO has the fields in the correct order
})
;
CreateMap<ModelView, Model>()
.AfterMap((v, m) =>
{
//override the sequence values based on the order they were provided in the DTO
var counter = 0;
foreach (var field in m.Fields)
{
field.Sequence = counter++;
}
})

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

Same property name in several child entities with Entity Framework Core 2.0

I'm currently using the simple model below. It's pretty straightforward: we have resources and they can be Room, EmptyOffice (...) or Service.
Room and EmptyOffice can have a capacity, but NOT Service.
public abstract class Resource : Entity
{
public string Name { get; set; }
}
public class Room : Resource
{
public int Capacity { get; set; }
}
public class EmptyOffice : Resource
{
public int Capacity { get; set; }
}
public class Service : Resource
{ }
To get the data from my SQL view, I use the mappings:
builder.Entity<Resource>(m =>
{
m.ToTable("resource", "facility");
m.HasKey(x => x.Id);
m.Property(x => x.Id)
.HasColumnName("ResourceId");
m.Property(x => x.Type)
.HasColumnName("ResourceTypeId");
m.HasDiscriminator(x => x.Type)
.HasValue<Room>(ResourceType.Room)
.HasValue<EmptyOffice>(ResourceType.EmptyOffice)
.HasValue<Service>(ResourceType.Service);
});
builder.Entity<Room>();
builder.Entity<EmptyOffice>();
builder.Entity<Service>();
When I run my code, EF Core throws the following exception:
System.Data.SqlClient.SqlException: 'Invalid column name 'Room_Capacity'.'
If I rename the Capacity property to Room_Capacity, it works but it's horrible.
How can I force EF Core 2.0 to target the capacity property for each of my child entities?
Thank you
Sebastien
This worked for me:
builder.Entity<Room>().Property(a => a.Capacity).HasColumnName("Capacity");
builder.Entity<EmptyRoom>().Property(a => a.Capacity).HasColumnName("Capacity");
You can't do that as the only inheritance pattern available in EF Core is table per class hierarchy. If you go with interfaces instead of base classes, you can, but each entity will be mapped to a different table. Mark any property you want to exclude with [NotMapped], or, using the code, with Ignore.
I did in project next code to make it more generic way.
private static void FindAndConfigureBackgroundJobResultTypes(ModelBuilder modelBuilder)
{
var backgroundJobResultTypes = typeof(BackgroundJobResult).Assembly.GetTypes().Where(x => x.IsSubclassOf(typeof(BackgroundJobResult))).ToList();
var sameTypeAndNameProperties = backgroundJobResultTypes
.SelectMany(x => x.GetProperties())
.GroupBy(d => new {d.Name, d.PropertyType})
.Select(grp => new
{
PropertyType = grp.Key.PropertyType,
PropertyName = grp.Key.Name,
Count = grp.Count()
})
.Where(x => x.Count > 1).ToList();
foreach (var backgroundJobResultType in backgroundJobResultTypes)
{
//Set base type , instead of exposing this type by DbSet
modelBuilder.Entity(backgroundJobResultType).HasBaseType(typeof(BackgroundJobResult));
//Map properties with the same name and type into one column, EF Core by default will create separate column for each type, and make it really strange way.
foreach (var propertyInfo in backgroundJobResultType.GetProperties())
{
if (sameTypeAndNameProperties.Any(x => x.PropertyType == propertyInfo.PropertyType && x.PropertyName == propertyInfo.Name))
{
modelBuilder.Entity(backgroundJobResultType).Property(propertyInfo.PropertyType, propertyInfo.Name).HasColumnName(propertyInfo.Name);
}
}
}
}

Find out mapped property using AutoMapper

I'm looking to 'map' errors when persisting DTOs back to the property in a viewmodel corresponding to the DTO property that caused the error, PersonDto.PreferedName causes a DB error, say Cannot be null, and this property maps to the viemodel PersonViewModel.Name, I want to be able to display an error message in the langiage of the view, e.g. "Name is required".
Is there any way I can query Mapper or some other AutoMapper object to find which viewmodel property PreferedName maps to?
It looks like you are getting the errors from the ModelState but to answer the original question you can use the following
using AutoMapper;
using System;
using System.Linq;
namespace ConsoleApplicationAutoMapper
{
class Program
{
static void Main(string[] args)
{
var config = new MapperConfiguration(cfg => cfg.CreateMap<Order, OrderDto>()
.ForMember(dest => dest.DtoMyProperty, x => x.MapFrom(y => y.MyProperty)
));
Order order = new Order() { MyProperty = 12 };
var typeMaps = config.CreateMapper()
.ConfigurationProvider
.GetAllTypeMaps()
.Where(t => t.SourceType == typeof(Order) && t.DestinationType == typeof(OrderDto))
.Single()
.GetPropertyMaps();
foreach (var map in typeMaps)
Console.WriteLine(map.SourceMember.Name + "->" + map.DestinationProperty.Name);
Console.ReadLine();
}
}
public class Order
{
public int MyProperty { get; set; }
}
public class OrderDto
{
public int DtoMyProperty { get; set; }
}
}
As per comments, you can annotate your viewmodel with [Required] (System.ComponentModel.DataAnnotations.RequiredAttribute) and check in your controller action for ModelState.IsValid.
Serializing the contents of ModelState.Values and returning to your WPF front end will allow you to display any validation errors in whatever way suits.

How to use AutoMapper?

First time using AutoMapper and I'm have a hard time figuring out how to use it.
I'm trying to map a ViewModel to my Database Tables.
My ViewModel looks like this...
public class AddressEditViewModel
{
public AddressEdit GetOneAddressByDistrictGuid { get; private set; }
public IEnumerable<ZipCodeFind> GetZipCodes { get; private set; }
public AddressEditViewModel(AddressEdit editAddress, IEnumerable<ZipCodeFind> Zips)
{
this.GetOneAddressByDistrictGuid = editAddress;
this.GetZipCodes = Zips;
}
}
The Mapping I'm trying to use is...
CreateMap<Address, AddressEditViewModel>();
When I run this test...
public void Should_map_dtos()
{
AutoMapperConfiguration.Configure();
Mapper.AssertConfigurationIsValid();
}
I get this error...
AutoMapper.AutoMapperConfigurationException: The following 2 properties on JCIMS_MVC2.DomainModel.ViewModels.AddressEditViewModel
are not mapped:
GetOneAddressByDistrictGuid
GetZipCodes
Add a custom mapping expression, ignore, or rename the property on JCIMS_MVC2.DomainModel.Address.
I'm not sure how I am supposed to map those 2 properties. I would appreciate any direction. Thanks
Mark
Ok so I can see a few things you are doing that probably won't help.
Firstly this AutoMapper is used to copy Properties in one object to Properties in a diff object. Along the way it might interrogate or manipulate them to get the end result viewmodel in the correct state.
The properties are named 'Get...' which sounds more like a method to me.
The setters on your properties are private so AutoSetter won't be able to find them. Change these to minimum internal.
Use of a parametrized constructor is no longer needed when you use AutoMapper - as you are converting directly from one object to another. The parametised constructor is there mainly to show what is explicitly required by this object.
CreateMap<Address, AddressEditViewModel>()
.ForMember( x => x.GetOneAddressByDistrictGuid ,
o => o.MapFrom( m => m."GetOneAddressByDistrictGuid") )
.ForMember( x => x.GetZipCodes,
o => o.MapFrom( m => m."GetZipCodes" ) );
What Automapper is really good for is copying from DataObjects into POCO objects, or View Model objects.
public class AddressViewModel
{
public string FullAddress{get;set;}
}
public class Address
{
public string Street{get;set;}
public string Suburb{get;set;}
public string City{get;set;}
}
CreateMap<Address, AddressViewModel>()
.ForMember( x => x.FullAddress,
o => o.MapFrom( m => String.Format("{0},{1},{2}"), m.Street, m.Suburb, m.City ) );
Address address = new Address(){
Street = "My Street";
Suburb= "My Suburb";
City= "My City";
};
AddressViewModel addressViewModel = Mapper.Map(address, Address, AddressViewModel);

AutoMapper - How to pass parameters into a Custom Resolver using ConstructedBy method?

In my ASP.NET MVC 2 (RC) project - I'm using AutoMapper to map between a Linq to Sql class (Media) and a view model (MediaVM). The view model has a SelectList property for a drop down in the view. I have a custom value resolver to populate the SelectList property items from the db, but am wondering if there's a way to pass a couple values from the source model into the resolver (using ConstructedBy method?) to a) define the selected item and b) filter the items from the db. The source object gets passed into the custom resolver - but the resolver is used on several different view models with different types of source objects, so would rather define where to get the values from in my mapping config. Here is my view model:
public class MediaVM
{
public bool Active { get; set; }
public string Name { get; set; }
[UIHint("DropDownList")]
[DisplayName("Users")]
public SelectList slUsers { get; private set; }
}
The automapper mapping config:
Mapper.CreateMap<Media, MediaVM>()
.ForMember(dest => dest.slUsers, opt => opt.ResolveUsing<UsersSelectListResolver>());
It would be nice to be able to do something like this on the .ForMember mapping clause:
.ConstructedBy(src => new UsersSelectListResolver(src.UserID, src.FilterVal))
Is there a way to accomplish this?
I like that idea as a feature request. You can do something like that right now, with MapFrom:
ForMember(dest => dest.slUsers, opt => opt.MapFrom(src => new UsersSelectListResolver(src).Resolve(src));
I found your posting trying to do the same thing. I decided on a simple approach and skip trying to map to my select list directly via AutoMaper. I simply return an array into my ViewModel and reference that object for my select list. The array gets mapped, select list object does not. Simple, effective. And, IMHO each is doing it's intended task - the mapper maps, the ViewModel does the layout
View Model code:
[DisplayName("Criterion Type")]
public virtual CriterionType[] CriterionTypes { get; set; }
[DisplayName("Criterion Type")]
public SelectList CriterionTypeList
{
get
{
return new SelectList(CriterionTypes, "Id", "Key");
}
}
my mapper:
Mapper.CreateMap<Criterion, CriterionForm>()
.ForMember(dest => dest.CriterionTypeList, opt => opt.Ignore());

Categories