7

I'm currently evaluation whether AutoMapper can be of benefit to our project. I'm working on a RESTful Web API using ASP.NET Web API, and one of the things I must return is a resource that contains links. Consider this simplified example, using the following domain object:

public class Customer
{
    public string Name { get; set; }
}

I need to map this into a resource object, sort of like a DTO but with added properties to facilitate REST. This is what my resource object may look like:

public class CustomerResource
{
    public string Name { get; set; }
    public Dictionary<string, string> Links { get; set; }
}

The Links property will need to contain links to related resources. Right now, I could construct them using the following approach:

public IEnumerable<CustomerResource> Get()
{
    Func<Customer, CustomerResource> map = customer => 
        new CustomerResource
        {
            Name = customer.Name,
            Links = new Dictionary<string, string>()
            {
                {"self", Url.Link("DefaultApi", new { controller = "Customers", name = customer.Name })}
            }
        }

    var customers = Repository.GetAll();
    return customers.Select(map);
}

...but this is pretty tedious and I have a lot of nested resources and such. The problem that I see is that I can't use AutoMapper because it doesn't let me provide certain things needed during projection that are scoped to the point where the mapping operation is performed. In this case, the Url property of the ApiController provides the UrlHelper instance that I need to create the links for me, but there may be other cases.

How would you solve this conundrum?

P.S. I typed up this code specifically for this question, and it compiled in your head but may fail in your favorite IDE.

Dave Van den Eynde
  • 17,020
  • 7
  • 59
  • 90

2 Answers2

2

I would look in to using a Custom Type Converter. The type converter could have contextual information injected via an IOC container. Or, since the converter is instantiated at configuration time, it could have a reference to a factory which would return contextual information each time the type converter is run.

Simple Example

You could define an interface for getting your current "context" (what that means depends on what you're doing and how you implement things so for this example I'll just the current HttpContext which gets you access to Session, Server, Items, etc...):

public interface IContextFactory
{
    HttpContext GetContext();
}

And the implementation is simply:

public class WebContextFactory : IContextFactory
{
    public HttpContext GetContext()
    {
        return HttpContext.Current;
    }
}

Your custom type converter could take an instance of IContextFactory from your IOC container and each time the mapping is run, you can call GetContext() to get the context for the current request.

Accessing the Url Property

The UrlHelper comes from the Request object attached to the current controller's context. Unfortunately, that is not available in the HttpContext. However, you could override the Initialize method on your ApiController and store the controllerContext in the HttpContext.Items collection:

protected override void Initialize(System.Web.Http.Controllers.HttpControllerContext controllerContext)
{
    HttpContext.Current.Items["controllerContext"] = controllerContext;
    base.Initialize(controllerContext);
}

You can then access that from the current HttpContext:

var helper = ((HttpControllerContext) HttpContext.Current.Items["controllerContext"]).Request.GetUrlHelper();

I'm not sure it's the best solution, but it can get you the UrlHelper instance inside your custom type mapper.

PatrickSteele
  • 14,489
  • 2
  • 51
  • 54
  • How would you get the context with IoC? Say you're executing `Mapper.Map` in a MVC controller. How would you inject the instance that you're executing in into the resolver/converter? – carlpett Apr 06 '13 at 14:57
  • @carlpett, I updated with a same of grabbing the current Http Context via a factory. – PatrickSteele Apr 06 '13 at 16:19
  • But HttpContext is not what I need when resolving. – Dave Van den Eynde Apr 07 '13 at 07:48
  • What is the source for the "links to related resources" you mention in your original question? – PatrickSteele Apr 07 '13 at 14:14
  • The UrlHelper instance that is instantiated for the request and available through the ApiController's Url property. – Dave Van den Eynde Apr 07 '13 at 19:08
  • Yes, that could work, even though that makes the code less testable (not that it was really testable when it referred to the ApiController's Url property) – Dave Van den Eynde Apr 08 '13 at 07:56
  • I was thinking the call to grab the helper from the `HttpContext.Current.Items` would be abstracted out via an interface (the one injected into your custom type mapper). Should make testing a little easier. – PatrickSteele Apr 08 '13 at 12:11
2

This is not a pretty solution, but after reading through the docs it appears that there isn't one... We're currently throwing in contextual stuff by mapping Tuple<TDomainType, TContextStuff> to TDataTransfer. So in your case you'd Mapper.CreateMap<Tuple<Customer, Controller>, CustomerResource>.

Not pretty, but it works.

carlpett
  • 12,203
  • 5
  • 48
  • 82
  • It's not pretty, but it's creative. I'm pondering trying this, but I'm already thinking about the mess it would create when configuring nested mappings. – Dave Van den Eynde Apr 07 '13 at 07:49