Saturday, October 29, 2011

Autofac, AutoMapper and custom converter with dependency injection

Whenever I work with a library or a technology, one of the question in my mind is how good is it to support dependency injection. I came back to use Autofac in a company's project recently and that question is the one I need to find the answer. Basically, Autofac is a pretty cool library to help converting between domain objects and view models. For example:

Mapper.CreateMap<Order, OrderViewModel>();

Or even better:
Mapper.CreateMap<Order, OrderViewModel>()
      .ConvertUsing<OrderConverter>();

The good thing of Automapper is that it supports very good custom converters following the way above. Most of the cases, the custom converter is just a simple class to map from fields to fields when the structure of the domain object is so complicated. Well, it's good if we can make it simple at first place but life is not that easy. The project I'm working on was created by an offshore team and everyday looking to the code, I just wonder to myself: "WTF is this shit"
The domain object is so complicated to map to the View Model by convention. ANd the ViewMOdel itself is complicated as well. It has logic to access WCF service to fetch domain data and map to itself :D. I'm spending my time to move the code to where it should be and using AutoMapper to map things is one of the steps. I hope you will never face anything like this, but in case you would and need a custom converter to access a service, this is the post for you.
Now, my need is that the OrderConverter will need to call a service method to fetch some data while mapping objects. I also want the service interface to be the dependency of the converter, it will be the parameter in OrderCOnverter constructor and when we call Mapper.Map, the dependency will be injected by a IOC library, here i use Autofac. Below is the naive implementation of OrderConverter:
public class OrderConverter : ITypeConverter<Order, OrderViewModel>
{
    private readonly IOrderService _orderService;

    public OrderConverter(IOrderService orderService)
    {
        _orderService = orderService;
    }

    public OrderViewModel Convert(ResolutionContext context)
    {
        var order = context.SourceValue as Order;
        if (order == null)
        {
            return null;
        }

        var orderDetails = _orderService.GetOrderDetailsByOrderId(order.Id);
        return new OrderViewModel
        {
            Id = order.Id,
            Details = Mapper.Map<IEnumerable<OrderDetails>, IEnumerable<OrderDetailsViewModel>>(orderDetails).ToList()
        };
    }
}

I thought Autofac should have supported the way to resolve Converter. I didn't know how to do it until reading through the code. The Mapper static class has a method Initialize and through this, we can pass in custom logic to resolve objects.
// AutoMapper initialization
Mapper.Initialize(x =>
{
     x.ConstructServicesUsing(type => container.Resolve(type));
});

The tricky thing is we need to call that method before we register mapper classes. The way I register mapper classes is using AutoFac scanning assemblies and make the mapper class implement IStartable.
var builder = new ContainerBuilder();
// startable which include mapper classes
builder.RegisterAssemblyTypes(assemblies)
       .Where(t => typeof(IStartable).IsAssignableFrom(t))
       .As<IStartable>()
       .SingleInstance();

These lines suppose to be after the Initialize method. I made the wrong mistake to put them in wrong order so the code couldn't work :D. I use the same way to register all the custom converters:
// converters
builder.RegisterAssemblyTypes(assemblies)
       .AsClosedTypesOf(typeof(ITypeConverter<, >))
       .AsSelf();

These lines would be executed in the BootStrapper before the main application run:
BootStrapper.Run();

var order = new Order {Id = 1};

try
{
    var viewModel = Mapper.Map<Order, OrderViewModel>(order);

    Console.WriteLine(string.Format("\nThere are {0} order details", viewModel.Details.Count));
}
catch (Exception ex)
{
    Console.WriteLine(ex.StackTrace);
}
finally
{
    Console.ReadLine();
}

That's it. Please check out the full application on github: https://github.com/vanthoainguyen/Blog/tree/master/TestAutoMapper

2 comments:

Marcel81 said...

Is it correct that you are not Starting the IStartable classes anywhere in your example code on GitHub?

Van Nguyen said...

Not sure what you meant but IStartable classes suppose to be executed when the container build if we register them as IStartable with Autofac.

Post a Comment