How to map a Domain model to a Presentation Model, can this be something?
If you have read my previous post about Presentation Model, I decided to try to see if I could make the mapping between the Presentation Model and the Domain Model simple by creating an Object to Object Mapper. I don’t want to use a messy XML file or attributes on the classes to handle the configuration of how object should be mapped to each other. Instead I wanted to use the nice Object Initializer feature in C# 3.0 which I think is easy to read and easy to use. Here is a modified version of my Mapping method I had in my CustomersController class in my previous post:
public PresentationCustomer CustomerToPresentationCustomer(Customer customer) { return new PresentationCustomer() { CustomerID = customer.ID, FirstName = customer.FirstName, Lastname = customer.Lastname, CompanyName = customer.CompanyName }; }
The code above is easy to read, at least I think so. I can easy see that I map a Customer to a PresentationCustomer, and it's also easy to see the mapping of properties. Compare the above code with the following XML:
<mapping name="CustomerToPresentationCustomer" sourceType="MyApp.DomainLayer.Customer" destinationType="MyApp.PresentationLayer.PresentationCustomer"> <map sourceField="ID" destinationField="CustomerID"/> <map sourceField="FirstName" destinationField="FirstName"/> <map sourceField="LastName" destinationField="LastName"/> <map sourceField="CompnayName" destinationField="CompanyName"/> </mapping>
What do you think is easier to read? I think the part where Property = Property, rather than <map sourceField="Property" destinationField="Property"/>.
To map two object with each other, I also need the type of the two objects. So either if I hard code the mapping like in the first example or use XML I need to specify the types. I also need to give the mapping configuration a name. In the XML file the name attribute of the <mapping> element specifies the name of the mapping. In the C# code, the name is specified as a method name. The source object is specified in the XML file by using the sourceType attribute, where the whole type is specified. In the C# code, the argument of the method is the source. The destination object (the object to map the source object with) also need to be specified. In XML, an attribute is used, and in the C# code as a return type. Then we need to specify what field should be mapped to what field.
What I decided to do, is to reuse my mapping method written in C#, but I wanted to make sure I could do the mapping in a text file, so I don't need to recompile if I need to change the mapping in the future. I created a simple method to handle the execution of the mapping, and add it to my Infrastructure Layer. Here is how I now map my Customer to the PresentationCustomer:
DynamicMapper dynMap = new DynamicMapper(); var presentationCustomer = dynMap.Map<PresentationCustomer>(customer, "CustomerToPresentationCustomer");
The Map method is a generic method where I specify the type of the Destination class "PresentationCustomer", the method has two arguments, the source where I pass the Customer (the domain object), and the mapKey which is used to specify what mapping configuration I want to use.
My mapping file where I have my mapping configuration has the following content:
public PresentationCustomer CustomerToPresentationCustomer(Customer customer) { return new PresentationCustomer() { CustomerID = customer.ID, FirstName = customer.FirstName, Lastname = customer.Lastname, CompanyName = customer.CompanyName }; }
Plain C# 3.0 code. The mapKey passed to the Map method is the name of the method which should be executed and perform the mapping. I used Dynamic Compilation to compile the code and then execute it. Here is part of the code so you can see the structure of it:
public Destination Map<Destination>(object source, string mapKey) { string mappingCode = this.ReadContentFromFile("..."); Type sourceType = source.GetType(); Type destinationType = typeof(Destination); CompilerParameters compilerParameters = this.SetupCompilerParameters(sourceType, destinationType); string sourceCode = CreateMappingClassSourceCode(mappingCode, sourceType, destinationType); var compiler = new CSharpCodeProvider(new Dictionary<string, string>() { { "CompilerVersion", "v3.5" } }); CompilerResults compiledResult = compiler.CompileAssemblyFromSource(compilerParameters, sourceCode); if (compiledResult.Errors.HasErrors) throw new ApplicationException(CreateErrorMessage(compiledResult) + "\r\n\r\n" + sourceCode); return ExecuteMapping<Destination>(mapKey, source, compiledResult.CompiledAssembly); }
I'm not sure if I will use this in production yet, I need to do some testing and need to cache the compiled code while there aren't any changes to the mapping configuration file etc. By using Reflection I could easy add the namespace and assembly of the source and destination objects, no need to specify them in the mapping file.
private CompilerParameters SetupCompilerParameters(Type sourceType, Type destinationType) { CompilerParameters compilerParameters = new CompilerParameters(); compilerParameters.GenerateInMemory = false; compilerParameters.ReferencedAssemblies.Add("System.dll"); compilerParameters.ReferencedAssemblies.Add("System.Core.dll"); compilerParameters.ReferencedAssemblies.Add(sourceType.Assembly.ManifestModule.ScopeName); if (!compilerParameters.ReferencedAssemblies.Contains(destinationType.Assembly.ManifestModule.ScopeName)) compilerParameters.ReferencedAssemblies.Add(destinationType.Assembly.ManifestModule.ScopeName); return compilerParameters; }