1
votes

I am configuring AutoMapper to map domain objects from / to the view model ones in a Asp.Net MVC5 application with Autofac 4.3, Autofac.Owin 4, Autofac.Mvc5 4, AutoFac.Mvc5.Owin 4 and AutoMapper 5.2.

I did decide to create one AutoMapper profile per domain entity like the followin leaving only the constructor with no parameters because I want to set the profile's name.

Note: this code is in assembly A.

public partial class DoctorsMappingProfile : Profile
{
    #region Constructors

    public DoctorsMappingProfile() : base(typeof(DoctorsMappingProfile).FullName)
    {
        // Code of mapping removed because it is not part of the problem
    }

    #endregion Constructors
}

To register AutoMapper and the profiles I followed this and this guides well explained by the user mpetito there and that it works as the user has checked here. My code in the Startup partial class is this:

Note1: this code is in assembly B which references assembly A.

Note2: I perfom assembly scanning of registered assemblies, then I pass as paramerter the array of scanned assemblies to the method RegisterAssemblyTypes of ContainerBuilder

    public partial class Startup
{
    #region Methods

    public void ConfigureAutoFacContainer(IAppBuilder app)
    {

        var builder = new ContainerBuilder();
        builder.RegisterControllers(typeof(MvcApplication).Assembly); 
        builder.RegisterFilterProvider(); 
        builder.RegisterSource(new ViewRegistrationSource());
        // TODO: Logger!                        
        var assemblies = BuildManager.GetReferencedAssemblies().Cast<Assembly>().ToArray();         
        this.ConfigureAutoMapper(builder, assemblies);
        // TODO: Modules!
        var container = builder.Build();
        DependencyResolver.SetResolver(new AutofacDependencyResolver(container));                       
        app.UseAutofacMiddleware(container);
        app.UseAutofacMvc();
    }

    private void ConfigureAutoMapper(ContainerBuilder builder, Assembly[] registeredAssemblies)
    {
        //register your profiles, or skip this if you don't want them in your container
        builder.RegisterAssemblyTypes(registeredAssemblies).AssignableTo(typeof(Profile)).As<Profile>(); //.UsingConstructor(typeof(string));

        //register your configuration as a single instance
        builder.Register(ctx => new MapperConfiguration(cfg =>
        {
            //add your profiles (either resolve from container or however else you acquire them)
            foreach (var profile in ctx.Resolve<IEnumerable<Profile>>())
            {
                cfg.AddProfile(profile);
            }
        })).AsSelf().SingleInstance();

        //register your mapper
        builder.Register(ctx => ctx.Resolve<MapperConfiguration>()
            .CreateMapper(ctx.Resolve))
            .As<IMapper>()
            .InstancePerLifetimeScope();
    }

    #endregion Methods
}

When de application runs and try to resolve the profile the following exception happens:

Autofac.Core.DependencyResolutionException None of the constructors found with 'Autofac.Core.Activators.Reflection.DefaultConstructorFinder' on type 'AutoMapper.Configuration.MapperConfigurationExpression+NamedProfile' can be invoked with the available services and parameters: Cannot resolve parameter 'System.String profileName' of constructor 'Void .ctor(System.String, System.Action`1[AutoMapper.IProfileExpression])'.

the line of the error is the foreach loop when the context attempts to resolve the Profile:

foreach (var profile in ctx.Resolve<IEnumerable<Profile>>())

I believe it its due to the Autofac default constructor location convention it is looking for the constructor with most parameters, which is in case of the AutoMapper.Profile class:

protected Profile(string profileName, Action<IProfileExpression> configurationAction) 
    {
    }

To fix this I replaced the line that looks for profiles, the first line of code in in ConfigureAutoMapper, forcing it to use the constructor with no parameters:

builder.RegisterAssemblyTypes(registeredAssemblies).AssignableTo(typeof(Profile)).As<Profile>().UsingConstructor(); 

but still it does not work.

Solutions / workaround I have found:

  1. If I replace the class Profile by my DoctorsMappingProfile in method ConfigureAutoMapper it works but doing this will invalidate the assembly scanning for profiles.

    1. Finally I managed it to work by creating a base abstract class inheriting from AutoMapper.Profile and referencing it in my ConfigureAutoMapper method of the Startup class, this way I can perform assembly scanning for that class' descendants...

Note: this code is in Assembly C

public abstract class G2AutoMapperProfile : Profile
{
    #region Constructors

    protected G2AutoMapperProfile()
    {
    }

    protected G2AutoMapperProfile(string profileName) : base(profileName)
    {
    }

    #endregion Constructors
}

Note: And this code is in assembly A

public partial class DoctorsMappingProfile : G2AutoMapperProfile //Profile
{
    #region Constructors

    public DoctorsMappingProfile() : base(typeof(DoctorsMappingProfile).FullName)
    {
        //removed because it is not part of the problem
    }

    #endregion Constructors
}

and finally, this is the code of ConfigureAutoMapper in assembly B, that works:

private void ConfigureAutoMapper(ContainerBuilder builder, Assembly[] registeredAssemblies)
    {
        //register your profiles, or skip this if you don't want them in your container
        builder.RegisterAssemblyTypes(registeredAssemblies).AssignableTo(typeof(G2AutoMapperProfile)).As<G2AutoMapperProfile>();

        //register your configuration as a single instance
        builder.Register(ctx => new MapperConfiguration(cfg =>
        {
            //add your profiles (either resolve from container or however else you acquire them)
            foreach (var profile in ctx.Resolve<IEnumerable<G2AutoMapperProfile>>())
            {
                cfg.AddProfile(profile);
            }
        })).AsSelf().SingleInstance();

        //register your mapper
        builder.Register(ctx => ctx.Resolve<MapperConfiguration>()
            .CreateMapper(ctx.Resolve))
            .As<IMapper>()
            .InstancePerLifetimeScope();
    }

... but still I don't understand why the original code is not working for me.

1

1 Answers

0
votes

First, there's no need for you to explicitly call the protected constructor of Profile in your DoctorsMappingProfile. The reason is that the profileName you pass is the one used by default by AutoMapper as we can see here.

Also, regarding the first way you try to register your profiles in the Autofac container:

builder
    .RegisterAssemblyTypes(registeredAssemblies)
    .AssignableTo(typeof(Profile))
    .As<Profile>()
    .UsingConstructor(typeof(string));

This means that you explicitly ask Autofac to create instance of your Profile classes by using the constructor that has exactly one parameter that is of type string. The problem with that is Autofac has no idea what string it has to use in the constructor.

It's also wrong since you created a parameter-less constructor which in turn calls the parameterized constructor.

I'm not sure what calling UsingConstructor with no parameters does, but in my opinion, you'd be better off leaving Autofac choose the best constructor for you, especially when the defaut parameter-less one is the one you want it to use.

My feeling is reinforced by looking at the solution/workaround you provided. What you effectively did is:

  • create a base class that mimics exactly what Profile has, that is, a default parameter-less constructor and a constructor that takes a string
  • register the profiles in the Autofac container by not using UsingConstructor

So I'm quite sure that by:

  • getting rid of your custom base class
  • having your profiles inherit from Profile again
  • registering your profiles without using UsingConstructor

you should be fine.