Enter ditto

I just spun off a little framework that performs Object-Object mapping. I was reluctant to throw another tool like this out there since others already exist, but I had difficulty bending existing solutions to my will without reflection tricks or lots of typing. 

The main motivation for this mapping implementation was the pattern found in CQRS solutions where a single denormalizer is responsible for updating a view model from multiple events. Also, I wanted to leverage my IoC container for conventions while mapping my Form model in MVC controller code.

If you have any comments or suggestions, I'd love to hear them!

The project is hosted on github at http://github.com/mnichols/ditto. Here are the docs from the project site.

ditto {''} - a small framework for mapping objects to objects in .NET.

Why?

There are good mapping tools out there already, but ditto was conceived to address a specific need for aggregating multiple source objects into a single instance. This is common for event handlers which all contribute their data to a single view model (ie, an event Denormalizer). From this simple model, it became real easy to extend its usage in other scenarios like mapping from a Form model in a web app to Commands in MVC controller code. Also, because it wires everything up using a IoC container, it is dead simple write resolvers, converters, or replace its behavior just by registering your implementations on your container.

Goals (in no particular order):

  • Convention-based assumptions to minimize configuration as much as possible
  • Aggregation of source objects into single destination
  • Deep validation checking
  • Garrulous
  • Fluent configuration without typedsturbating generic parameters
  • Fast enough
  • IoC, container-based

Limitations

  • Only properties are evaluated by convention right now
  • Flattening/Unflattening isn't a concern for me, but it'd be easy to create components to do this without changing Ditto

Assumptions

ditto assumes the objects you are mapping have the same property names. This simple configuration assumes the two objects have the same names:

cfg.Map<MyViewModel>().From<MyEvent>();

Containers

ditto leans heavily on an IoC container to do its work. This lets us easily register resolvers and other bits for easy configuration. Castle Windsor integration is the only container integration provided right now, but ditto doesn't really care how services are getting resolved. If you decide to use the Castle Windsor integration you can just add the provided Facility (DittoFacility). Here's the code installing all mapping services for my view model denormalization daemon:

container.AddFacility("mapping.facility", new DittoFacility());
container.RegisterAllConfigurationsIn(new[] { GetType().Assembly });
container.RegisterAllGlobalConventionsIn(new[]{GetType().Assembly});
container.Register(AllTypes.FromThisAssembly()
                       .BasedOn<IResolveValue>()
                       .WithService.AllInterfaces()
                       .Configure(c => c.LifeStyle.Transient));

Logging

ditto likes to talk alot for Debug builds, so be sure you use a Release build (the default if you use the provided psake script). log4net is the only logging implementation provider right now (baked into Castle Windsor Integration), but it'd be easy to swap that out since ditto doesn't care who is listening.

Configuration

To let ditto know what objects you need to map and their respective sources for data, you need to get the singleton implementation of IContainerDestinationConfiguration. The AbstractMappingConfiguration base class can be inherited from to save a few keystrokes. This assumes you can let your hair down about required services being provided without constructor injection.

public class MyViewModelConfiguration : AbstractMappingConfiguration 
{
    public override void Configure()
    {
        //'Cfg' is injected by your container
        Cfg.Map<MyViewModel>().From<MyEvent>();
    }
}

Conventions

Conventions may be applied globally. Conventions may impact your destination objects in one of two ways:

  • The resolution of a value based on the context or metadata of associated objects
  • The conversion of a value for across-the-board metamorphosis; ie, conversion of all DateTime's into UTC

Global conventions are applied to every mapping ditto knows about. To configure Global conventions, you need to add them to IContainGlobalConventions. For this, you may inherit from AbstractGlobalConventionConfiguration:

public class WebAppGlobalMappingConventions : AbstractGlobalConventionConfiguration
{
    private readonly MyCustomResolver myCustomResolver;

    public WebAppGlobalMappingConventions(MyCustomResolver myCustomResolver)
    {
        this.myCustomResolver = myCustomResolver;
    }

    public override void Configure()
    {
        Conventions.AddConverter(new EntityIdentifierConverter()); 
        Conventions.AddConverter(new DateTimeUtcConverter());
        Conventions.AddResolver(new PropertyNameCriterion("ConversationId"),new IgnoreResolver());
        Conventions.AddResolver(new PropertyNameCriterion("Id"),myCustomResolver);
    }
}

Validation

ditto is uptight, so if there are properties on destination objects that are not mapped after everything is bound together, it can let you know. This is done by default if you are using the provided WindsorMappingInitializer. This will throw an exception if there are any properties for which ditto has not been configured, either via global conventions or local mappings.

Intialization

ditto can be initialized in your bootstrapping code by simply getting your instance of IInitializeDitto and calling its Initialize method. Again, there is a WindsorMappingInitializer that demonstrates the things that ditto needs to do for starting up. These are done, in order:

  1. Configure all global conventions
  2. Configure all destination objects from their sources
  3. Bind all these configurations together
  4. Cache configuration metadata (optional, but madness to do without)
  5. Validate/Assert all configuration

Mapping

Once you have initialized ditto you may now just grab your instance of IMap and call it by either having ditto create the destination object for you, or passing in your own instance:

var myViewModel = mapper.Map<MyViewModel>(mySourceEvent); // => this creates an instance of MyViewModel and maps from your instance of the source
mapper.Map(mySourceEvent,myViewModel); // => this simply maps from the source to your own instance of MyViewModel

Please note that ditto currently assumes a default constructor for its own activation of objects. This would be dead simple to improve upon though since it is container-driven. Just replace IActivator and provide your own strategy(s).

Acknowledgements

  • Fasterflect - ditto developer is lazy so relies on for reflection optimization stuff. It's a great little tool for the toolbox.
  • Castle Windsor - Makes it easy to keep concerns split up.

Posted 04-07-2011 3:01 PM by Michael Nichols
Filed under: ,

[Advertisement]

Comments

Ruben Bartelink wrote re: Enter ditto
on 04-07-2011 8:25 PM

Might be useful to explain where this fits wrt AutoMapper.

Michael Nichols wrote re: Enter ditto
on 04-07-2011 8:58 PM

@Ruben

I tried to explain the need it fills, but you have a good point due to AutoMapper's popularity.

This replaced AutoMapper on my project.

My main purpose in avoiding too much mention of other tools is avoiding the perception of a critical spirit.  I'll follow up with a post pointing some differences if it seems prudent to do so.

Krzysztof Koźmic wrote re: Enter ditto
on 04-08-2011 8:26 AM

Hey Michael,

interesting timing indeed. Adding Automapper is first task that awaits me on Monday when I come back to work, but now I think I might give ditto a spin too (honestly, youve won me with the OOTB integration into Windsor :) ).

How long have you been working on this? How stable is it in terms of the API? Looking forward to more info and especially the vs. AutoMapper post.

Michael Nichols wrote re: Enter ditto
on 04-08-2011 9:36 AM

@K

I hope you like what you see there.  With the help you have given me via your hard work on Windsor on so many of my projects it'd be nice to know I can return some value.

RE: API

I have been using ditto for about 3 months. There are really only two primary interfaces to interact with ...mostly you'll be touching the configuration interface and that hasn't had much change for about a month.  

About The CodeBetter.Com Blog Network
CodeBetter.Com FAQ

Our Mission

Advertisers should contact Brendan

Subscribe
Google Reader or Homepage

del.icio.us CodeBetter.com Latest Items
Add to My Yahoo!
Subscribe with Bloglines
Subscribe in NewsGator Online
Subscribe with myFeedster
Add to My AOL
Furl CodeBetter.com Latest Items
Subscribe in Rojo

Member Projects
DimeCasts.Net - Derik Whittaker

Friends of Devlicio.us
Red-Gate Tools For SQL and .NET

NDepend

SlickEdit
 
SmartInspect .NET Logging
NGEDIT: ViEmu and Codekana
LiteAccounting.Com
DevExpress
Fixx
NHibernate Profiler
Unfuddle
Balsamiq Mockups
Scrumy
JetBrains - ReSharper
Umbraco
NServiceBus
RavenDb
Web Sequence Diagrams
Ducksboard<-- NEW Friend!

 



Site Copyright © 2007 CodeBetter.Com
Content Copyright Individual Bloggers

 

Community Server (Commercial Edition)