Introduction
In most n-tiers applications, business entities and service or presentation entities are separated, and the developers must implement methods or constructors in order to create the presentation objects from the business data. This tip will explain how to work with PresentationMapper
Nuget package to automate this task, and thus avoid potential mistakes.
Background
There certainly are other tools out there that already do objects mapping, but I believe the tool I propose here offers many benefits as listed below:
- Attribute mapping. Types are mapped directly in the business entity code through attributes.
- API mapping. Types are mapping by using a fluent API (V2.0).
- Speed. Reflections are often used in other tools to do the job, and we all know that this brings a negative impact on performance.
PresentationMapper
only uses reflection on static initialization to create dynamic func
delegates. Once the initialization is done (once for the whole application lifetime), the delegates are used to build the objects. This is as fast as what a developer could do if he had to do it manually.
- Use of a context to build the objects. Similarly to a unit of work, the context caches the objects that are created and reuse them instead of rebuilding them.
- Possibility to specify the objects to include, just like what the "
Include
" does when using Entity Framework. This is useful when the objects are big and we don't want to pass all data to the presentation object.
- A business object can map multiple presentation objects.
- Collections can be passed to the presentation objects independently on the type of the collection used. For example, we can map a
List<int>
to an int[]
or ISet<int>
. However, the tools only support arrays and generic collections. Hashtable
, or IList
are not supported for example.
- Support for open generic types (V2.0)
- Support for polymorphism (V2.0)
Installation
To install the package, use Nuget package manager. You can find the package by typing "PresentationMapper
" in the search textbox, or by typing "Install-Package PresentationMapper
" in the console manager.
Class Samples
At first, we need two classes. One for the business side, and another one for the service (or presentation) side.
Let's make them simple:
public class BusinessClass
{
public int Key { get; set; }
public string Description { get; set; }
}
public class PresentationClass
{
public int Id { get; set; }
public string Description { get; set; }
}
So, we want to map BusinessClass
with PresentationClass
. This can be done either by using attribute mapping, or API mapping.
Attribute mapping
This can be done by using the "Presentation
" attribute.
Also, by default, the engine will try to map anything that has the same name, so the "Description
" property will be mapped by default, but not "Key
". To do so, we will use the "LinkTo
" attribute.
So, now the BusinessClass
will look like this:
[Presentation(typeof(PresentationClass))]
public class BusinessClass
{
[LinkTo("Id")]
public int Key { get; set; }
public string Description { get; set; }
}
That's it. The two classes are now properly mapped. Please note that if a member is not mapped to a business member or is not assignable, its value will be the default one.
Note that we're using auto-properties in our example, but the library recently supports fields as well.
Moreover, the use of "typeof()
" in the Presentation
attribute requires that the class libraries containing the presentation types must be referenced in the libraries containing the business types. Technically, it should not be a problem, but it's not pure in terms of design. To work around this, the Presentation
attribute also includes another constructor that takes a string
as a parameter. This string
represents the name of the presentation object, which can be one of the forms:
- <namespace>.<typename>, <assembly name>
[Presentation("MyNamespace.PresentationClass, MyLibrary"))]
public class MyClas
- <namespace>.<typename>
In this case, we omit the assembly. Therefore, the tool will search the type in all assemblies.
[Presentation("MyNamespace.MyClass"))]
public class MyClass
- <typename>
In this case, we omit the assembly and the namespace. Therefore, the tool will search the type in all assemblies, but will throw an exception if more than one type matches the name. However, it is common that business types and presentation types have the same name (and differ only by their namespace). The tool ignores the class where the Presentation
attribute is put, so such declaration works very well:
[Presentation("MyClass"))]
public class MyClass
Initialization
During startup, the engine needs to discover all the attributes mapping that is written by the developers. To do so, it will look into the working directory for all types of files (DLL, EXE). This will work, but can be time consuming in a big solutions containing hundreds of files or assemblies. This is specially too bad if those files do not contain our business entities. The tool offers initialization methods that are optional but can help to start up a little bit faster (helpful during development phase).
The first one lets us specify a search pattern (used by the Directory.GetFiles()
method). For example, if we know that all our business entities are stored into *.business.dll, we will use:
MapContext.Initialize("*.business.dll");
Multiple patterns can be specified bu using the | character:
MapContext.Initialize("*.dll|*.exe");
The second one lets us specify a list of assemblies directly.
MapContext.Initialize(new [] {"myBusinessAssembly1.dll", "myBusinessAssembly2.dll"});
API mapping
Mapping with a fluent API is possible since verison 2.0 of PresentationMapper. Please refer to the version 2 article to learn about this feature.
Creating the Objects
Now, we can use the MapContext
class to build our presentation object by passing an instance of a business class:
var bClass = new BusinessClass() { Key = 55, Description = "desc" };
using (var ctx = new MapContext())
{
var pClass = ctx.Create<PresentationClass>(bClass);
}
Let's now have another business class and its associated presentation class:
[Presentation(typeof(Presentation2Class))]
public class Business2Class
{
public string Name { get; set; }
}
public class Presentation2Class
{
public string Name { get; set; }
}
Now our first business class and our first presentation class both should contain a reference to these new classes:
[Presentation(typeof(PresentationClass))]
public class BusinessClass
{
[LinkTo("Id")]
public int Key { get; set; }
public string Description { get; set; }
public Business2Class AnObject {get; set; }
}
public class PresentationClass
{
public int Id { get; set; }
public string Description { get; set; }
public Presentation2Class AnObject { get; set; }
}
If we call the Create<>
method of the MapContext
, we will also get the value of AnObject
. And Presentation2Class
also contains a reference to its parent (PresentationClass
), this will be retrieved as well. Please be aware though that bidirectional references can cause some serialization issue, if we need to pass these objects as standard SOAP objects (WCF can solve this problem).
Collections
In the case of collections, the tool supports arrays, IList<T>
, List<T>
, ICollection<T>
, Collection<T>
, ISet<T>
, and Set<T>
. Any of these types can be on the business side or presentation side. The tool will convert the collection type if necessary. For example, an int[]
in the business type can be converted to a List<int>
in the presentation type.
Specifying Inclusions
Automatically retrieving all sub-objects is a nice feature, but in the case of big objects, this is not what we always want. There is a way to tell the tool to only get objects that we specify under Linq queries form.
If we don't any of the sub-objects, we can pass NULL
as the second argument of the Create
function.
var pClass = ctx.Create<PresentationClass>(bClass, null);
If we want to include the "AnObject
" sub-object, we will specify it this way:
var pClass = ctx.Create<PresentationClass>(bClass, x=>x.AnObject);
The second parameter of the Create
function is a params, so we can add other inclusions if we want.
If we need to include a property of collection, we can use the "Select
" method of Linq:
x=>x.MyCollection.Select(y=>y.Property)
In this case, it is not necessary to include MyCollection
too, it will be done automatically.
Dealing with Multiple Presentation Objects
We can map as many presentation objects as we want to a single business entity. To do so, simply add a Presentation
attribute for each of them.
If a single property of the business type must match different presentation property (coming from different presentation objects), we can prefix the property name with the name of the presentation type in the LinkTo
attribute.
For example, if we have another presentation object:
public class AnotherPresentationClass
{
public int Id { get; set; }
public string Description { get; set; }
public Presentation2Class AnotherObject { get; set; }
}
We can bind it this way:
[Presentation(typeof(PresentationClass))]
[Presentation(typeof(AnotherPresentationClass))]
public class BusinessClass
{
[LinkTo("Id")]
public int Key { get; set; }
public string Description { get; set; }
[LinkTo("AnotherPresentationClass.AnotherObject")]
public Business2Class AnObject { get; set; }
}
That's it for the tutorial. I hope that this tool will be helpful for you.
If you have any suggestions, or bugs to fix, you can email me at minhl.huong@gmail.com.
Polymorphism and generic classes support
Since version 2.0, polymorphis and generic classes are supported. Please refer to the version 2 article to learn about these features.
Related Tools
History
- 29th April, 2015: Initial version
- 19th May, 2015: Added new features (Fields, Presentation constructor)