Click here to Skip to main content
Click here to Skip to main content

Converting DTOs <-> BOs using an Adapter

By , 18 Oct 2012
Rate this:
Please Sign up or sign in to vote.

Introduction

We recently began a new project of a distributed application. My main part consists of building a WPF Gui, and to get the job done, I need to convert the dumb DTOs received from my colleagues WCF services to clever business objects and vice versa. Now the challenge is to implement BO to DTO conversion in both directions as painless as possible. By painless I think about myself and the pain I would feel when I needed to write conversion code for every pair of BO/DTO.

This is why I tried to implement a generic adapter handling the BO/DTO conversion. Feel free to tell me if I succeeded and especially if you have any ideas about how to improve the underlying conventions and implementation. 

Some conventions about the architecture

First some info about architecture:

  • Based on the use cases, the server team designes the WCF services and as a part of this the DTOs being returned by the services and passed in as service call arguments.    
  • A DTO is a dump container of properties.    
  • Although being dumb, every DTO implements an interface which defines all the properties the DTO implements. This interface is called the data contract.    
  • On the client side, every BO mirroring a DTO implements the same data contract interface.

Differentiation from the prototype pattern 

The basic idea to create a new instance B by copying property values of an existing instance A is also common to the creational prototype pattern. But the prototype pattern always returns a new instance of the same type, meaning that the new instance B will be of the same type as instance A. This is not what we want, we need conversion. Furthermore, the prototype pattern requires you to implement a clone-like-method for returning a new instance with copied property values for each type. Once more, this is not what we want because of to much implementation effort required.

Introducing and using the adapter

The adapter class exposes three methods only and looks like this:

AdapterClass

Now let us assume one of our use cases has something to do with holidays. We therefore build a DTO and a BO holiday class both sharing the same data contract interface which looks like this:

HolidayClasses

At first glance, the HolidayBo doesn't look much more intelligent as the HolidayDo, but thanks to inheritance it gains some special abilities like validation. You may also notice that there is no connection between the holiday related classes and the adapter so far. To enable the adapter to convert a Bo to a Do and vice versa, we need to register both the Bo and the Do like this:

Dim testAdapter As New Adapter
 
testAdapter.Register(GetType(IHolidayContract), GetType(HolidayDo), GetType(HolidayBo))
testAdapter.Register(GetType(IHolidayContract), GetType(HolidayBo), GetType(HolidayDo))

The first argument in the register method designs the data contract. The data contract defines all the properties whose values will be copied when converting a dto to a bo and vice versa. The second paramter designs the source type which will be converted to the target type as defined by the third parameter.
This way the adapter gets all the info it needs to do the conversion. The conversion it-self is done by calling the convert method:

Dim myBo As New HolidayBo With {.HolidayDate = New Date(2011, 12, 24), .Comment = "Christmas"}
Dim myDo = DirectCast(testAdapter.Convert(myBo), HolidayDo)

Because the convert method returns an object we need to cast the result to the appropriate data type. The interesting thing is that the newly created myDo object's properties now have the same values as the orginal myBo object. As you might guess, the adapter uses reflection to iterate through the properties defined by the data contract and copies those.

Now you might wonder what happens if the property to copy is a not a simple value type. The answer is: it depends. If the property type to copy is a registered DTO or BO, no copy will be made, but instead it will also be converted following the same rules as before. If the property to copy is a none registered type, a simple copy will be made, meaning the DTO and BO now refer to the same object. If this is a problem, I haven't found it so far, because by convention every complex type will be designed as a DTO/BO pair implementing the shared data contract. Doing so you only need to register the complex type on the adapter as well and it will be converted.

Somewhat challenging was how to handle collection properties containing DTOs or BOs. In fact, the adapter so far supports collections implementing one of these interfaces:

  • IList(of T), where item of type T may be converted.
  • IDictionary(of Key, Value), where key or value may be converted.

If the adapter's conversion method encounters a collection implementing one of these interfaces, it will convert the contained items as well. This means that items in an ObservableCollection will be converted, because the ObservableCollection implements IList(of T). 

Take a look at the picture below as a summary of the conversion guidelines just mentioned:

Conversions

Additionally, thanks to the miracles of recursion, the conversion of property items does not stop after the first level as you might think when looking at the image above but is unlimited as long as the property of an object being converted is a registered DTO/BO it-self. If the adapter encounters a none registered object, the conversion chain is broken. Again, have a look at this:

ConversionChainBreak

You might wonder if just copying a DTO instead of converting it to a BO as shown above will result in an invalid cast exception. Generally, this is not the case, because the collections containing BOs/DTOs are of type "data contract", not of type BO/DTO. This is a must, because the data contract needs to be implemented by both BO/DTO.

If all this sounds confusing, why not download the code project and run the (n-)unit tests to see the adapter in action. The adapter's class code it-self is also quite manageable, being less than 200 lines so far. It makes heavy use of reflection. So far, I have no performance issues and everything went fine, but as always I am afraid that future will prove me to be wrong again.

Lessons learned I: Open source project Automapper

Thanks for everybody indicating that there already exists a solution to be problem I tried to address called Automapper.  I gave it a try and it passed all existing unit test of mine and offers much more functionality. So I recommend that, if you are in need of an object oriented mapper, take this one.

Lessons learned II: Common interface for DTO and BO sucks

Implementing a common interface for both DTO and BO sounded good to me at first. Until I realized that when a DTO/BO property holds another DTO/BO (single or collection) you need to implement this property with the type defined in the interface. Not only does this lead to more annoying conversion, but it also disturbs gui databinding. For example, when binding a collection of an interface type to a WPF datagrid, the datagrid looses it's ability to add a new row to the grid. This is because WPF does not know about the concrete object type it has to initialize when adding a new item to the collection. All it knows is the interface. Automapper does not need interfaces, because it works with conventions, which is a good way, I think.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Torsten Tiedt
Software Developer
Germany Germany
Currently I’m working as a database and application developer for a big auditing company in Frankfurt, Germany. At desk my daily doing is based on SQL-Server and .Net-programming, and I admit that in my off-time things can stay the same. But before worrying about my social life let me tell you that I love doing sports with my friends, to travel with my wife, to read ficitional literature and that I desperately try to learn russian as third foreign language.

Comments and Discussions

 
GeneralMy vote of 5 PinmemberM Rayhan9-Nov-13 16:39 
GeneralMy vote of 5 PinmemberGuirec Le Bars18-Oct-12 13:32 
GeneralRe: My vote of 5 PinmemberTorsten Tiedt18-Oct-12 22:07 
GeneralMy vote of 4 PinmemberFlorin Bombeanu16-Oct-12 20:13 
GeneralRe: My vote of 4 PinmemberTorsten Tiedt16-Oct-12 23:42 
GeneralMy vote of 5 PinmemberRichardSlater15-Oct-12 8:12 
GeneralRe: My vote of 5 PinmemberTorsten Tiedt15-Oct-12 23:19 
Generala good post Pinmemberericpxz15-Oct-12 4:52 
GeneralRe: a good post PinmemberTorsten Tiedt15-Oct-12 6:58 
GeneralRe: a good post Pinmemberericpxz15-Oct-12 7:02 
GeneralRe: a good post PinmemberTorsten Tiedt15-Oct-12 7:05 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web01 | 2.8.140415.2 | Last Updated 18 Oct 2012
Article Copyright 2012 by Torsten Tiedt
Everything else Copyright © CodeProject, 1999-2014
Terms of Use
Layout: fixed | fluid