In Automapper, we normally have 1 to 1 mapping defined but I have a case whereas the incoming stream as a json payload which then I cast it as a dynamic (using JObject parse) and in one of the property within the payload it defined which object that it needs to cast into. Lets take a look at the sample below
Input
Json payload to create a city
{ "requestId": "C4910016-C30D-415C-89D3-D08D724429A6", "messageType": "CITY_CREATED", "categoryName": "categoryA", "metadata": { "city": "sydney", "state": "NSW", "postcode": "2000", "country": "australia" } }
at the same time we can also have a Json payload to create a staff
{ "requestId":"C4910016-C30D-415C-89D3-D08D724429A6", "messageType": "STAFF_CREATED", "categoryName": "categoryB", "staffDetail": { "name": "fransiscus", "dateOfBirth": "01/01/1950" }, "location" : { "cityId" : "1" } }
So what we are doing in here, all the message will go into payload property (it can contain any object) and we add some extra information/header/metadata on the parent level
Desired Outputs
{ "messageType": "CITY_CREATED", "payload": { "city": "sydney", "state": "NSW", "postcode": "2000", "country": "australia" }, "provider": "abc", "providerRequestId": "C4910016-C30D-415C-89D3-D08D724429A6", "receivedAt": "2015-09-30T23:53:58.6118521Z", "lastUpdated": "2015-09-30T23:53:58.6128283Z", "lastUpdater": "Transformer", "attempt": 0 }
{ "messageType": "STAFF_CREATED", "payload": { "staffName": "fransiscus", "dateOfBirth": "01/01/1950", "cityId": "1" }, "provider": "abc", "providerRequestId": "C4910016-C30D-415C-89D3-D08D724429A6", "receivedAt": "2015-09-30T23:53:58.6118521Z", "lastUpdated": "2015-09-30T23:53:58.6128283Z", "lastUpdater": "Transformer", "attempt": 0 }
To map this to a concrete class 1:1 mapping is straight forward and easy. The problem here is that the “messageType” is the one that decided which object that it should be
Automapper Configuration:
1. POCO object
abstract class that stores all the metadata
public abstract class Metadata { public string MessageType { get; set; } public string Provider { get; set; } public string ProviderRequestId { get; set; } public DateTime ReceivedAt { get; set; } public DateTime LastUpdated { get; set; } public string LastUpdater { get; set; } public int Attempt { get; set; } public List<string> Errors { get; set; } }
public class City { public string CityName { get; set; } public string State { get; set; } public string PostCode { get; set; } public string Country { get; set; } }
public class StaffDetail { public string Name { get; set; } public string DateOfBirth { get; set; } public int CityId { get; set; } }
public class Message<T> : Metadata where T : class { public T Payload { get; set; } }
2. Lets create a TypeConverter for the base class which is Metadata and from this converter it will return the derived class
public class MetadataTypeConverter : TypeConverter<dynamic, Metadata> { protected override Metadata ConvertCore(dynamic source) { Metadata metadata; var type = (string)source.messageType.Value; switch (type) { case "STAFF_CREATED": metadata = new Message<StaffDetail> { Payload = Mapper.Map<dynamic, StaffDetail>(source) }; break; case "CITY_CREATED": metadata = new Message<City> { Payload = Mapper.Map<dynamic, City>(source) }; break; default: throw new Exception(string.Format("no mapping defined for {0}", source.messageType.Value)); } metadata.ProviderRequestId = source.requestId; metadata.Topic = string.Format("{0}.{1}.pregame", producerTopicName, source.categoryName ?? source.competition.categoryName); metadata.Provider = "My Provider"; metadata.MessageType = source.messageType; metadata.ReceivedAt = DateTime.UtcNow; metadata.LastUpdated = DateTime.UtcNow; metadata.LastUpdater = "Transformer"; metadata.Attempt = 0; return metadata; } }
3. Lets create a TypeConverter for the derived class which are Staff and City
public class CityTypeConverter : TypeConverter<dynamic, City> { protected override City ConvertCore(dynamic source) { City city = new City(); city.CityName = source.metadata.city; city.State = source.metadata.state; city.Postcode = source.metadata.postcode; city.Country = source.metadata.country; return city; } }
public class StaffDetailTypeConverter : TypeConverter<dynamic, StaffDetail> { protected override StaffDetail ConvertCore(dynamic source) { StaffDetail staffdetail = new StaffDetail(); staffdetail.Name = source.staffDetail.name; staffdetail.DateOfBirth = source.staffDetail.dateOfBirth; staffdetail.CityId = source.location.cityId; return staffdetail; } }
3. Define the Automapper mapping in the configuration
public class WhafflMessageMapping : Profile { public override string ProfileName { get { return this.GetType().Name; } } protected override void Configure() { this.CreateMap() .ConvertUsing(new MetadataTypeConverter()); this.CreateMap() .ConvertUsing(new StaffDetailTypeConverter()); this.CreateMap() .ConvertUsing(new CityTypeConverter()); } private Metadata BuildWhafflMessage(dynamic context) { var type = ((string)context.messageType.Value); switch (type) { case "STAFF_CREATED": return new Message { Payload = Mapper.Map(context) }; case "CITY_CREATED: return new Message { Payload = Mapper.Map(context) }; default: throw new Exception(string.Format("no mapping defined for {0}", context.messageType.Value)); } } }