Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to inject service into AutoMapper?

I am using AutoMapper for mapping Entity to ViewModel. One of the property of the entity is datetime. I wanted to convert that datetime to local datetime using the TimeZone. The TimeZone is stored in user's session object. I already have ISessionManager which retrives information from User's session. I am not sure how do i inject this ISessionManager into AutoMapper

In code below how do i pass ISessionManager.CurrentTimeZone property into FromUTCToLocal() method?

    public class SessionManager:ISessionManager
    {
       public TimeZoneInfo CurrentTimeZone
       {
          return (TimeZoneInfo)Session["CurrentTimeZone"];
       }
    }


    public static class DateTimeExtenstions
    {
        public static DateTime FromLocalToUTC(this DateTime dateTime, TimeZoneInfo timeZone)
        {
            // Here instead of taking timeZone as parameter i can
            // use servicelocator to get instance of ISessionManager
            // like var timeZone = ServiceLocator.GetInstance<ISessionManager>()
            // However i don't want to use servicelocator pattern since
            // everywhere else i am using IOC
            // also it doesnt make sense to use ServiceLocator inside extension method

            return TimeZoneInfo.ConvertTimeToUtc(dateTime, timeZone);
        }        
    }

    public class AutoMapperConfig
    {
        public static IMapper Config()
        {
            var config = new MapperConfiguration(cfg =>
            {
                //Create all maps here               
                cfg.CreateMap<MyEntity, MyViewModel>()
                    .ForMember(vm => vm.CreatedDateTime,
                            y => y.MapFrom(entity => entity.CreatedOn.FromUTCToLocal()))
            });

            return config.CreateMapper();
        }
    }   


    public static class UnityConfig
    {
        public static void RegisterComponents(IMapper mapper)
        {
            var container = new UnityContainer();

            container.RegisterType<ISessionManager, SessionManager>(new HierarchicalLifetimeManager());

            container.RegisterInstance(mapper);            

            UnityServiceLocator locator = new UnityServiceLocator(container);
            ServiceLocator.SetLocatorProvider(() => locator);
        }
    }

   public class MvcApplication : System.Web.HttpApplication
   {
    protected void Application_Start()
    {
        AreaRegistration.RegisterAllAreas();
        UnityConfig.RegisterComponents(AutoMapperConfig.Config());                
    }
   }
like image 655
LP13 Avatar asked Oct 17 '25 02:10

LP13


2 Answers

I think that's an anti-pattern, because mappers are supposed to be dumb, and thus you might want to resist the temptation of putting too much logic inside them. Querying data is the responsibility of your domain implementations, but you're right about not using the ServiceLocator if you've managed without it until now (especially not inside an extension method).

Perhaps you should enrich the domain object you're mapping from rather than trying to add missing information (TimeZoneInfo) during the mapping process.

like image 150
Alexandru Marculescu Avatar answered Oct 18 '25 16:10

Alexandru Marculescu


Far from being an anti-pattern, AutoMapper was actually designed to handle exactly what you want, you'll just need to take a different approach (oddly enough I solved this while also working on a UTC problem in which I also initially attempted a solution based on extension methods).

Instead of using an extension method what you'll want is to utilize AutoMapper's ValueResolvers. Depending on if this is a mapping specific to a particular class or if it's a general mapping for a certain datatype you'll want to implement either IValueResolver (for the former) or IMemberValueResolver (for the latter).

This is a good resource that will get you most of the way there: https://github.com/AutoMapper/AutoMapper/wiki/Custom-value-resolvers

What that doesn't address is injecting into the resolver.

An example would be something like this:

public class DateTimeUtcResolver : IMemberValueResolver<object, object, DateTime, DateTime>
{
    ISessionManager sessionManager;

    public DateTimeUtcResolver(ISessionManager sessionManager)
    {
        this.sessionManager = sessionManager;
    }

    public DateTime Resolve(object source, object destination, DateTime sourceMember, DateTime destinationMember, ResolutionContext context)
    {
        //logic
    }
}

Your mapper would then have something like this:

cfg.CreateMap<MyEntity, MyViewModel>()
    .ForMember(vm => vm.CreatedDateTime,
               y => y.ResolveUsing<DateTimeUtcResolver, DateTime>(entity => entity.CreatedOn))

From there just remember to register your IOC container with AutoMapper so that the implementation for ISessionManager can be determined. That itself can be tricky but will depend on what type of container you are using (this one helped me quite a bit, ymmv: Automapper and request specific resources)

like image 43
Stu Avatar answered Oct 18 '25 14:10

Stu



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!