Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Injecting dependencies to an Automapper TypeConverter in ASP.NET Core

To get a DI framework to inject dependencies into an Automapper custom TypeConverter, you normally use the ConstructServicesUsing method of the MapperConfiguration object. So with ASP.NET Core DI, I expect to be able to configure AutoMapper like this:

public static IMapperConfiguration Configure(IServiceProvider provider)
{
    var config = new MapperConfiguration(cfg => {
         cfg.AddProfile<MyProfile>();
         cfg.ConstructServicesUsing(type => provider.GetService(type));
    });
    config.AssertConfigurationIsValid();
    return config;
}

The MapperConfiguration object would be configured as an injectable service in startup.cs thus:

public void ConfigureServices(IServiceCollection services)
{
    //other service configuration omitted for simplicity

    //Automapper config
    var provider = services.BuildServiceProvider();
    var config = AutoMapperConfig.Configure(provider);
    services.AddInstance(config);
}

And the dependency (in this case Automapper itself) would be injected into the TypeConverter constructor like this.

public class MyConverter : ITypeConverter<ThisType, ThatType>
{
    private IMapper _mapper;

    public MyConverter(IMapperConfiguration mapperConfig)
    {
        var mc = mapperConfig as MapperConfiguration;
        _mapper = mc.CreateMapper();
    }

    public ThatType Convert(ResolutionContext context)
    {
        //do something with _mapper here
    }
}

I have used this pattern successfully with several DI frameworks, but I can't get it to work with ASP.NET Core. At a guess, I think it might be that Automapper needs to be given the real IServiceProvider instance that is built by .NET after the ConfigureServices method has completed. However, even when I postpone that part of the configuration until the Configure method (see below), the dependency still doesn't get injected into the TypeConverter.

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory, IServiceProvider provider)
{
    var config = provider.GetService<IMapperConfiguration>();
    config.ConstructServicesUsing(type => provider.GetService(type));
}

So my question is: how do I configure Automapper with ASP.NET Core so that it injects dependencies into custom TypeConverters?

like image 483
Paul Taylor Avatar asked Feb 07 '23 00:02

Paul Taylor


2 Answers

I found the solution to this problem lies in the correct configuration of the ConstructServicesUsing factory method. As @Tseng pointed out, using the IServiceCollection.AddSingleton method allows Automapper to be configured in the ConfigureServices method of Startup.cs, which is where it should be done:

public void ConfigureServices(IServiceCollection services)
{
    //other service configuration omitted for simplicity

    //Automapper config
    services.AddSingleton(provider => AutoMapperConfig.Configure(provider));
}

But crucially, Automapper must be configured to use .NET Core's ActivatorUtilities class to create service instances (credit to this article for giving me the idea):

public static IMapperConfiguration Configure(IServiceProvider provider)
{
    var config = new MapperConfiguration(cfg => {
         cfg.AddProfile<MyProfile>();
         cfg.ConstructServicesUsing(type => ActivatorUtilities.CreateInstance(provider, type));
    });
    config.AssertConfigurationIsValid();
    return config;
}

With this approach, Automapper is configured to inject any service dependencies into custom TypeConverters and ValueResolvers. Just make sure that any such services are also added to the IServiceCollection instance in ConfigureServices.

like image 73
Paul Taylor Avatar answered Feb 13 '23 19:02

Paul Taylor


You can/should use a factory method to inject/instantiate the service.

services.AddSingleton<IMapperConfiguration>( (serviceProvider) => AutoMapperConfig.Configure(serviceProvider) );

This way you delay the call to Configure until the first time IMapperConfiguration is being resolved. Since it's singleton, it will stay alive for the rest of your application container and further calls to RequestService will return the same instance.

Edit:

Try removing the config.AssertConfigurationIsValid(); from your AutoMapperConfig.Configure method and add the following code in Startup.cs

public void Configure(IServiceProvider app) 
{
    app.VerifyAutoMapperConfig();
}

AutoMapperConfigExtensions.cs

// So one doesn't have to add namespaces to become available 
// inside Startup.cs; Recommended way all middleware register their
// AddXxx and UseXxx extension methods
namespace Microsoft.Extensions.DependencyInjection {
    public static class AutoMapperConfigExtensions
    {
        public static void VerifyAutoMapperConfig(this IServiceProvider app) 
        {
            var config = app.RequestService<IMapperConfiguration>();
            config.AssertConfigurationIsValid();
        }
    }
}
like image 45
Tseng Avatar answered Feb 13 '23 19:02

Tseng