View Full Version : Mapping DTOs to Models
sdhpublic
11-02-2006, 08:15 PM
Spring looks fascinating, look forward to learning more. I have an immediate need that I am trying to see if Spring will fulfill.
We are using Data Transfer Objects to pass information between our Views and our Controller/Presenter tier. I am looking for a way to automatically convert dtos to models and back. (Something similar to DynaDTO for Java, I think...)
What I am envisioning is some xml mapping config like the objects node for Spring where I can define that "classA1.classA2.propertyA3 = classB1.propertyB2" for example. Then, in C# code I would say something like
Model m = Model.GetById(5);
DTO d = Something.GenerateDTOFromModel(m);
Is this something that can be handled in Spring somehow? Or does anyone know of any projects out there better suited for this task?
Thanks...
Erich Eichinger
11-04-2006, 04:52 PM
Hi,
Maybe Spring.DataBinding.DataBindingManager and its use of Spring.Expressions in Spring.Core.dll is what you're looking for.
Since it's use is only little documented yet, here's a brief description of DataBindingManager (http://forum.springframework.net/showthread.php?t=381). It only describes usage on webpages, but nothing prevents you from using it "standalone".
Here's a good description of Spring.Expressions (http://www.springframework.net/doc-latest/reference/html/expressions.html).
I could imagine first defining some xml-format for describing the mapping:
<mappings>
<mapping src="propertyA1.propertxA2.propertyA3"
target=""propertyB1.propertyB2"
/>
<mapping ...
</mappings>
Second, write some code for reading in these mapping-definitions.
Third, build a DataBindingManager instance with Bindings according to the definitions:
DataBindingManager bindingMgr = new DataBindingManager();
foreach( Mapping m in mappings )
{
bindingMgr.AddBinding( m.Src, m.Target );
}
Finally use this bindingMgr instance for mapping the properties:
Model model;
DTO dto;
// model -> dto
ValidationErrors errors = new ValidationErrors();
bindingMgr.BindSourceToTarget( model, dto, errors );
// examine any errors here
// dto -> model
ValidationErrors errors = new ValidationErrors();
bindingMgr.BindTargetToSource( model, dto, errors );
// examine any errors here
Let us know, how you are doing. The possibility to externally describing DataBindings has been requested by others as well.
cheers,
Erich
Aleks Seovic
11-08-2006, 07:15 PM
As Erich pointed out, this would be fairly simple to accomplish using our standard data binding framework. You will have to do some of the work (reading the mappings from the config file and creating a manager class that will apply them as necessary), but for the most part all of the heavy-lifting has already been done.
Let us know if you run into any issues or have some suggestions for improvement of the data binding framework.
- Aleks
sdhpublic
11-08-2006, 11:32 PM
Thank you, this looks terrific.
I am running a little test to get my feet wet and figure out how to use the tool. I tried the following:
DataBindingManager bindingMgr = new DataBindingManager();
bindingMgr.AddBinding("Email","Text");
Spring.Validation.ValidationErrors e = new Spring.Validation.ValidationErrors();
bindingMgr.BindSourceToTarget(myClass as IAccount, txtAccountName, e);
The object that I am passing in as the Source is a class that explicitly implements an Interface. It has two Email properties - one for the class itself, and one as IAccount.Email. Regardless of whether I pass in the class directly or cast it as the Interface, the Email property on the class gets called. In addition, there are specific members on the Interface that are explicitly implemented that I need to reference.
Any ideas?
Erich Eichinger
11-09-2006, 12:10 AM
hi,
Casting doesn't help in this case - the object you pass is always the same. Internally the passed object is "simply" (sorry Aleks - I know it's a bit more than that;-)) reflected for the property you want to bind.
I don't believe this works: But have you tried binding to "IAccount.Email" instead of "Email"?
Otherwise I guess you'll need some kind of delegating proxy - a wrapper that implements the properties of IAccount directly and delegates set/get calls to the real object. There are various proxy generators available in Spring - but I'm afraid I don't know much about them. Hopefully Bruno can help us out on this.
cheers,
Erich
sdhpublic
11-09-2006, 02:20 PM
I tried IAccount.Email, and it did not work.
I am guessing there is a way to automate the proxy methodology?
Interesting topic. I haven't looked on any of the databinding stuff, but is mapping entities also covered somehow?
I.e.:
public class MyOtherEntity
{
public int Id;
}
public class MyModel
{
public MyOtherEntity someValue;
public string someString;
}
public class MyModelDto
{
string someString;
int someValueId;
}
I want to create the model from the dto. Is it possible to map the MyOtherEntity according it's id, or would be needed to get this working somehow.
We're currently working on several smart clients and we use Dto's to send our objects over the internet. We make 'full' objects of them on each end of the wire.Mapping the properties is not the most interesting code to write. So perhaps this can help.
Erich Eichinger
11-09-2006, 03:09 PM
Hi,
I'm not sure, if I understand what you want to do. Could you describe a little more detailed, what you are trying to do?
cheers,
Erich
sdhpublic
11-09-2006, 03:13 PM
Dynamic Proxy update ...
I tried the following:
ProxyFactory factory = new ProxyFactory(dto);
IAccount iface = (IAccount)factory.GetProxy();
Then when I make the call:
Spring.Validation.ValidationErrors e = new Spring.Validation.ValidationErrors();
bindingMgr.BindSourceToTarget(iface, txtAccountName, e);
I receive an error: "'Email' node cannot be resolved for the specified root context."
Am I using the ProxyFactory incorrectly?
I'll attempt to illustrate it better:
The 'problem' domain:
public class Car
{
public string Note;
public Person Owner;
public void Start()
{
//starts the car
}
public void Stop()
{
//stops the car
}
}
public class Person
{
public int Id;
public string Name;
}
public class CarDto
{
public int Id;
public string Note;
public int OwnerId;
}
The cars and persons can be retrieved via a webservice. They are returned in Dto format. You can view the CarDto above. Now I want to use my Domain Model on the client as well, so I can use the business logic both on the server and the client.
The client changes the owner of a car and commits the changes so all changed objs are send back. (domain->Dto->Internet->|Server|->Dto->Domain->Service/Dao/etc).
The assembler below is used on the server:
public class CarAssembler
{
private ICarDao m_carDao;
private IPersonDao m_personDao;
public CarDto CreateDto(Car domainObj)
{
CarDto dto = new CarDto();
dto.Note = domainObj.Note;
dto.OwnerId = domainObj.Owner.Id;
return dto;
}
public Car CreateDomainObj(CarDto dto)
{
Car newCar = new Car();
newCar.Owner = m_personDao.GetById(dto.OwnerId);
newCar.Note = dto.Note;
return newCar;
}
public Car UpdateDomainObj(CarDto dto)
{
Car updatedCar = m_carDao.GetById(dto.OwnerId);
updatedCar.Owner = m_personDao.GetById(dto.OwnerId);
updatedCar.Note = dto.Note;
return updatedCar;
}
}
Now my question was :) : can I use the databinding framework to do this. With the examples I saw here it seemed mapping 'simple' stuff (strings) was no problem. But what about the owner of the updated car as in UpdateDomainObj, can the databindingmanager do this too? Or should I start looking at something else. Writing dto's and assemblers is not the most interesting part of the application.
Bruno Baia
11-09-2006, 09:21 PM
Hi,
Dynamic Proxy update ...
I tried the following:
ProxyFactory factory = new ProxyFactory(dto);
IAccount iface = (IAccount)factory.GetProxy();
Then when I make the call:
Spring.Validation.ValidationErrors e = new Spring.Validation.ValidationErrors();
bindingMgr.BindSourceToTarget(iface, txtAccountName, e);
I receive an error: "'Email' node cannot be resolved for the specified root context."
Am I using the ProxyFactory incorrectly?
This is a trick... but it should work.
I guess you are using 1.0.2 or an old nightly build version ?
- Bruno
sdhpublic
11-09-2006, 10:24 PM
I am using 1.0.2.
Bruno Baia
11-09-2006, 10:34 PM
You should try with a latest nightly build (http://www.springframework.net/downloads/nightly).
This is related that properties was explicitly implemented before the fix.
Bruno
sdhpublic
11-14-2006, 08:09 PM
That works! Perfect, this is great...thank you!
Erich Eichinger
11-14-2006, 09:08 PM
Glad we could help!
cheers,
Erich
Aleks Seovic
11-28-2006, 02:17 PM
The 'problem' domain:
public class Car
{
public string Note;
public Person Owner;
public void Start()
{
//starts the car
}
public void Stop()
{
//stops the car
}
}
public class Person
{
public int Id;
public string Name;
}
public class CarDto
{
public int Id;
public string Note;
public int OwnerId;
}
The assembler below is used on the server:
public class CarAssembler
{
private ICarDao m_carDao;
private IPersonDao m_personDao;
public CarDto CreateDto(Car domainObj)
{
CarDto dto = new CarDto();
dto.Note = domainObj.Note;
dto.OwnerId = domainObj.Owner.Id;
return dto;
}
public Car CreateDomainObj(CarDto dto)
{
Car newCar = new Car();
newCar.Owner = m_personDao.GetById(dto.OwnerId);
newCar.Note = dto.Note;
return newCar;
}
public Car UpdateDomainObj(CarDto dto)
{
Car updatedCar = m_carDao.GetById(dto.OwnerId);
updatedCar.Owner = m_personDao.GetById(dto.OwnerId);
updatedCar.Note = dto.Note;
return updatedCar;
}
}
Now my question was :) : can I use the databinding framework to do this. With the examples I saw here it seemed mapping 'simple' stuff (strings) was no problem. But what about the owner of the updated car as in UpdateDomainObj, can the databindingmanager do this too? Or should I start looking at something else. Writing dto's and assemblers is not the most interesting part of the application.
Sorry it took me so long to reply to this, I've been meaning to do it for a while. The short answer is yes, you can use data binding framework to achieve what you need.
Basically, you need to set up two uni-directional bindings for your Owner/OwnerId properties:
BindingManager.AddBinding("Note", "Note");
BindingManager.AddBinding("OwnerId", "Owner.Id", BindingDirection.TargetToSource);
BindingManager.AddBinding("@(personDao).GetById(OwnerId)", "Owner", BindingDirection.SourceToTarget);
Then you can perform data binding by calling:
// first and third binding will be evaluated by the following statement
BindingManager.BindSourceToTarget(dto, domainObj);
or
// first and second binding will be evaluated by the following statement
BindingManager.BindTargetToSource(dto, domainObj);
First binding is very straight forward -- it simply sets up bi-directional binding rule for the Note property. The second one is uni-directional binding from your domain object to DTO, and the third one is a uni-directional binding from DTO to domain object.
While the second binding definition is fairly self-explanatory, the third one probably deserves some more attention. What you are basically doing here is invoking the finder method on the Spring-managed personDao object, using @(objectId) syntax to reference Spring-managed object. Whatever that method returns will be set as the value of the Owner property of your domain object, which is exactly what you wish to achieve.
With a little bit of work you could probably implement your own framework on top of Spring Data Binding that will make object assembly on the server much simpler and more of a declarative process.
HTH,
Aleks
Great! I will look into it.
house9
02-03-2007, 09:01 PM
I have not worked with Spring.net much, just sampled AOP (very nice stuff) and read bits of the documentation for other Spring.net features; I would have a use for this BindingManager technique, but I am not sure about the handling for collections
some sample code
// binding manager
public class SimpleBindingManager : Spring.DataBinding.BaseBindingManager
{}
// entity and entity dto
public class Person
{
public string FirstName = String.Empty;
public string LastName = String.Empty;
public List<Person> Childern = new List<Person>();
}
public class PersonDto
{
public string ThisFirstName = String.Empty;
public string LastName = String.Empty;
public List<Person> Childern = new List<Person>();
public List<PersonDto> ChildernDtos = new List<PersonDto>();
}
// test code
[Test]
public void Test1()
{
// test source to target
// where property names are the same and different
PersonDto dto = new PersonDto();
Person person = new Person();
person.FirstName = "Joe";
person.LastName = "Smith";
manager.BindSourceToTarget(person, dto, null);
Assert.AreEqual("Smith", dto.LastName);
Assert.AreEqual("Joe", dto.ThisFirstName);
}
[Test]
public void Test2()
{
// test copy of a generic list
// where the data type is the same
manager.AddBinding("Childern", "Childern");
PersonDto dto = new PersonDto();
Person person = new Person();
Person child = null;
person.FirstName = "Joe";
person.LastName = "Smith";
child = new Person();
child.FirstName = "Jr";
child.LastName = "Smith";
person.Childern.Add(child);
manager.BindSourceToTarget(person, dto, null);
Assert.AreEqual(1, dto.Childern.Count);
Assert.AreEqual("Jr", dto.Childern[0].FirstName);
}
[Test]
public void Test3()
{
// test copy generic list where the data type differs
manager.AddBinding("Childern", "ChildernDtos");
PersonDto dto = new PersonDto();
Person person = new Person();
Person child = null;
person.FirstName = "Joe";
person.LastName = "Smith";
child = new Person();
child.FirstName = "Jr";
child.LastName = "Smith";
person.Childern.Add(child);
// fails with Spring.Objects.TypeMismatchException
manager.BindSourceToTarget(person, dto, null);
Assert.AreEqual(1, dto.ChildernDtos.Count);
Assert.AreEqual("Smith", dto.ChildernDtos[0].LastName);
}
tests 1 and 2 work fine
test 3 fails with
UnitTests.Class1.Test3 : Spring.Objects.TypeMismatchException : Cannot convert property value of type [System.Collections.Generic.List`1[[ObjectBindingTest.Person, ObjectBindingTest, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]] to required type [System.Collections.Generic.List`1[[ObjectBindingTest.PersonDto, ObjectBindingTest, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]] for property 'ChildernDtos'.
----> System.ArgumentException : The value "ObjectBindingTest.Person" is not of type "ObjectBindingTest.PersonDto" and cannot be used in this generic collection.
Parameter name: value
this makes sense - but is there a way to accomplish this by modifying
manager.AddBinding("Childern", "ChildernDtos");
Thanks for any help!
.
Erich Eichinger
02-03-2007, 09:30 PM
Hi,
this should be possible by writing a custom TypeConverter that is able to convert between Person and PersonDto objects.
This basically requires 2 steps:
1)
Implement your TypeConverter by deriving from System.ComponentModel.TypeConverter (see MSDN (http://msdn2.microsoft.com/en-us/library/system.componentmodel.typeconverter.aspx))
For sourcecode samples look at the Spring.Core sources in the namespace Spring.Objects.TypeConverters
2)
Register your converter by either calling Spring.Object.TypeConverters.TypeConverterRegistry .RegisterConverter() or using CustomConverterConfigurer (see Spring.NET reference (http://www.springframework.net/doc-latest/reference/html/objects.html#objects-type-conversion-custom-overview))
hth,
Erich
house9
02-03-2007, 09:53 PM
Thanks for the quick response; I'll check it out
steinard
02-27-2007, 03:43 PM
How did this go? Did you succeed in creating a better way to map between domain-classes and their respective DTOs?
I am also mapping back and forth between domain-classes and DTOs, but I chose to use reflection to accomplish the dirty work. Any reason any of you did not choose this approach? Maybe I should also point out that I'm not constrained to create webpages, but thick and thin WinForms.
Cheers,
Steinar.
Aleks Seovic
02-27-2007, 04:11 PM
Well, it all boils down to reflection either way, but there are few advantages to using Spring data binding and.or SpEL expressions over raw reflection:
1. Spring will do type conversion for you if necessary and allow you to get a list of binding errors in case some values cannot be converted. You can also write and register your own type converters.
2. The code you need to write will typically be much shorter and more concise.
3. The reflection itself will be optimized and we will try to use Spring's dynamic reflection support in order to access properties at native speed if possible.
- Aleks
This is on my todo list. When my current project ships I hope to have some spare time to work on it.
jlamkw
08-18-2009, 08:27 PM
Sorry for digging up this old thread but I'd like to know what's the current state of art w.r.t. this domain model<->DTO mapping problem and if the current Spring.NET has improved support.
demonsout
01-13-2010, 03:02 PM
It's a highly regarded OSS project in the .NET space that appears to do exactly what you want:
AutoMapper uses a fluent configuration API to define an object-object mapping strategy. AutoMapper uses a convention-based matching algorithm to match up source to destination values. Currently, AutoMapper is geared towards model projection scenarios to flatten complex object models to DTOs and other simple objects, whose design is better suited for serialization, communication, messaging, or simply an anti-corruption layer between the domain and application layer.
http://automapper.codeplex.com/
Powered by vBulletin® Version 4.2.0 Copyright © 2013 vBulletin Solutions, Inc. All rights reserved.