I'm confused about the extent to which Autofac can be used with Automapper to map objects. I've read quite a bit of material online about integrating the two packages, but almost all seem to focus on how to create instances of IMapper from Automapper Profiles, using code something like this, which defines an Autofac Module (CSContainer.Instance is a static instance of Autofac's IContainer):
public class AutoMapperModule : Module
{
private static object ServiceConstructor( Type type )
{
return CSContainer.Instance.Resolve( type );
}
protected override void Load( ContainerBuilder builder )
{
base.Load( builder );
var assemblies = AppDomain.CurrentDomain.GetAssemblies();
builder.RegisterAssemblyTypes( assemblies )
.Where( t => typeof(Profile).IsAssignableFrom( t ) && !t.IsAbstract && t.IsPublic )
.As<Profile>();
builder.Register( c => new MapperConfiguration( cfg =>
{
cfg.ConstructServicesUsing( ServiceConstructor );
foreach( var profile in c.Resolve<IEnumerable<Profile>>() )
{
cfg.AddProfile( profile );
}
} ) )
.AsSelf()
.AutoActivate()
.SingleInstance();
builder.Register( c => c.Resolve<MapperConfiguration>().CreateMapper( c.Resolve ) )
.As<IMapper>()
.SingleInstance();
}
}
(see http://www.protomatter.co.uk/blog/development/2017/02/modular-automapper-registrations-with-autofac/ for an explanation of this approach)
What I'd like to do is have Automapper call Autofac to create objects. Right now the only way I can see to do this is by doing something like this:
CreateMap()
.ConstructUsing( src => CSContainer.Instance.Resolve() );
This works, but feels kludgy. It'd be neater if Automapper tried to discover how to resolve instances using Autofac automagically, behind the scenes as it were.
I thought something like this might do the trick (this is a modified version of that first Autofac Module I cited above):
public class AutoMapperModule : Module
{
protected override void Load( ContainerBuilder builder )
{
base.Load( builder );
var assemblies = AppDomain.CurrentDomain.GetAssemblies();
builder.RegisterAssemblyTypes( assemblies )
.Where( t => typeof(Profile).IsAssignableFrom( t ) && !t.IsAbstract && t.IsPublic )
.As<Profile>();
builder.Register( c => new MapperConfiguration( cfg =>
{
cfg.ForAllMaps( ( map, opts ) =>
opts.ConstructUsing( ( x, y ) => CSContainer.Instance.Resolve(map.DestinationType) ) );
foreach( var profile in c.Resolve<IEnumerable<Profile>>() )
{
cfg.AddProfile( profile );
}
} ) )
.AsSelf()
.AutoActivate()
.SingleInstance();
builder.Register( c => c.Resolve<MapperConfiguration>().CreateMapper( c.Resolve ) )
.As<IMapper>()
.SingleInstance();
}
but this resulted in Autofac throwing an exception, complaining about me trying to re-use a builder that had already been defined (apologies, I don't have the exact wording handy).
Is it possible to integrate Automapper and Autofac so that Automapper resolves new instances via Autofac? If so, what's the best way to do it?
Additional Info
So I implemented the suggested answer as follows:
protected override void Load( ContainerBuilder builder )
{
base.Load( builder );
var assemblies = AppDomain.CurrentDomain.GetAssemblies();
builder.RegisterAssemblyTypes( assemblies )
.Where( t => typeof(Profile).IsAssignableFrom( t ) && !t.IsAbstract && t.IsPublic )
.As<Profile>();
builder.Register( c => new MapperConfiguration( cfg =>
{
cfg.ConstructServicesUsing( ServiceConstructor );
foreach( var profile in c.Resolve<IEnumerable<Profile>>() )
{
cfg.AddProfile( profile );
}
} ) )
.AsSelf()
.AutoActivate()
.SingleInstance();
builder.Register( c =>
{
// these are the changed lines
var scope = c.Resolve<ILifetimeScope>();
return new Mapper(c.Resolve<MapperConfiguration>(), scope.Resolve );
} )
.As<IMapper>()
.SingleInstance();
}
But this leads to an Automapper exception, complaining about the object I'm trying to create via a call to Map() must have zero arguments, or only optional arguments. Yet all the object's constructor arguments are also registered with Autofac, and it has no problem creating instances of the objects by itself elsewhere in my code. It's just when Automapper tries to create an instance that things go haywire.
From the docs and the source it looks like you can pass a function to the Mapper constructor that will run all service location through the function.
public Mapper(IConfigurationProvider configurationProvider, Func<Type, object> serviceCtor)
This blog article has more detailed examples and explains how to get ASP.NET Core DI wired up with AutoMapper. Don't get hung up on it being ASP.NET Core - you'll follow the same principle for Autofac.
builder.Register(
ctx =>
{
var scope = ctx.Resolve<ILifetimeScope>();
return new Mapper(
ctx.Resolve<IConfigurationProvider>(),
scope.Resolve);
})
.As<IMapper>()
.InstancePerLifetimeScope();
Your job will just be to figure out how to register your configuration as IConfigurationProvider.
I am posting this as an answer, but not >>the<< answer, because I want to give credit to #TravisIllig, because without his input I wouldn't have been able to solve my problem.
But there's an important, but not obvious (to me, anyway) extra step you need to implement for Automapper to use Autofac to create instances: when you define a map, you have to explicitly state that you want the object constructed using the service locator. Example:
CreateMap<CommunityUserModel, CommunityUserModel>()
.ConstructUsingServiceLocator()
.AfterMap( ( src, dest ) => dest.ClearChanged() );
If you leave that second line out, Automapper uses its default approach of looking for a parameterless or only-optional-parameters constructor.
Related
For some reason AutoMapper gives me a list of the type I need, where all properties are 0 or null. When I debug on data I see my list with all object and properties containing data. But .Map() gives me a list with no data in the properties (but the correct amount of objects). I'm new to AutoMapper, but this does seem very weird. Any suggestions?
public static IMapper Initialize()
{
var config = new MapperConfiguration(cfg => {
cfg.CreateMap<PlantSelectResult, IPlant>()
.ForMember(
dest => dest.description,
opt => opt.MapFrom(src => src.description));
});
return new Mapper(config);
}
And my DataProvider:
public IEnumerable<IPlant> GetPlants()
{
using (var dbCtx = new DataClasses1DataContext(_connectionString.String))
{
var data = dbCtx.PlantSelect().ToList();
return automapper.Map<List<PlantSelectResult>, IPlant[]>(data);
}
}
I didn't realize I had removed set; on the properties. Fixing the interfaces so they were settable fixed the problem.
Previously when I used Automapper v3.x ignoring unmapped properties could be done by simply adding a .IgnoreUnmappedProperties() extension which looked like this
public static class AutoMapperExtensions
{
public static IMappingExpression<TSource, TDestination> IgnoreUnmappedProperties<TSource, TDestination>(this IMappingExpression<TSource, TDestination> expression)
{
var typeMap = Mapper.FindTypeMapFor<TSource, TDestination>();
if (typeMap != null)
{
foreach (var unmappedPropertyName in typeMap.GetUnmappedPropertyNames())
{
expression.ForMember(unmappedPropertyName, opt => opt.Ignore());
}
}
return expression;
}
}
How can this extension be rewritten to work with Version 5.x. I can of course add the following to each property.
.ForMember(dest => dest.LastUpdatedBy, opt => opt.Ignore())
or not call
Mapper.AssertConfigurationIsValid();
You can do that using the CreateMap method's memberList parameter to specify the validation that you want.
CreateMap<TSource, TDestination>(MemberList.None)
The MemberList.None should do the trick. You can also switch between the source or destination validations.
Automapper - Selecting members to validate
For certain properties, when the value is 0, I want to convert the value to NULL.
When I create my map I use a custom resolver to handle the conversion for me, which looks like:
AutoMapper.Mapper.CreateMap<ContactData, Contact>()
.ForMember(x => x.ContactTypeId, y => y.ResolveUsing<ContactTypeIdZeroToNullResolver>());
And my resolver looks like:
using AutoMapper;
namespace MyCompany.MyProduct.Customers.Data.Helpers
{
public class ContactTypeIdZeroToNullResolver : ValueResolver<ContactData, int?>
{
protected override int? ResolveCore(ContactData source)
{
if (source.ContactTypeId == 0)
return null;
return source.ContactTypeId;
}
}
}
This works just fine!
My question is, is it possible to make this more generic? I tried replace ContactData with just int, but errors keep being thrown.
How about something like this:
AutoMapper.Mapper.CreateMap<int, int?>()
.ConvertUsing(src => src == 0 ? (int?)null : src);
AutoMapper.Mapper.CreateMap<ContactData, Contact>();
AutoMapper.Mapper.AssertConfigurationIsValid();
(assuming of course that ContactTypeId is an int in ContactData and an int? in Contact)
You should be careful with this as it will apply to ALL your mappings for these types.
You can also inline it like this:
AutoMapper.Mapper.CreateMap<ContactData, Contact>()
.ForMember(dest => dest.ContactTypeId,
opt => opt.MapFrom(src => src.ContactTypeId == 0
? (int?)null
: src.ContactTypeId));
which will achieve the same thing, but wont be as generic.
I have a custom module. Migrations.cs looks like this.
public int Create()
{
SchemaBuilder.CreateTable("MyModuleRecord", table => table
.ContentPartRecord()
...
);
ContentDefinitionManager.AlterPartDefinition(
typeof(MyModulePart).Name, cfg => cfg.Attachable());
ContentDefinitionManager.AlterTypeDefinition("MyModule",
cfg => cfg
.WithPart("MyModulePart")
.WithPart("CommonPart")
.Creatable()
);
return 1;
}
This is the code I have in the controller.
var newcontent = _orchardServices.ContentManager.New<MyModulePart>("MyModule");
...
_orchardServices.ContentManager.Create(newcontent);
I get the invalid cast error from this New method in Orchard.ContentManagement ContentCreateExtensions.
public static T New<T>(this IContentManager manager, string contentType) where T : class, IContent {
var contentItem = manager.New(contentType);
if (contentItem == null)
return null;
var part = contentItem.Get<T>();
if (part == null)
throw new InvalidCastException();
return part;
}
Any idea what I am doing wrong?
Thanks.
This is the handler.
public class MyModuleHandler : ContentHandler
{
public MyModuleHandler(IRepository<MyModuleRecord> repository)
{
Filters.Add(StorageFilter.For(repository));
}
}
You are getting the InvalidCastException because the content item doesn't appear to have your MyModulePart attached.
If there were a driver for your part, then there is an implicit link somewhere that allows your part to be shown on a content item (I'm not sure how this is done, maybe someone else could elaborate - but it is something to do with how shapes are harvested and picked up by the shape table deep down in Orchard's core).
However since you don't have a driver, adding an ActivatingFilter to your part's handler class will make the link explicitly:
public MyModulePartHandler : ContentHandler {
public MyModulePartHandler() {
Filters.Add(StorageFilter.For(repository));
Filters.Add(new ActivatingFilter<MyModulePart>("MyModule");
}
}
Your part table name is wrong. Try renaming it to this (so the part before "Record" matches your part model name exactly):
SchemaBuilder.CreateTable("MyModulePartRecord", table => table
.ContentPartRecord()
...
);
I have been using AutoMapper for some time now. I have a profile setup like so:
public class ViewModelAutoMapperConfiguration : Profile
{
protected override string ProfileName
{
get { return "ViewModel"; }
}
protected override void Configure()
{
AddFormatter<HtmlEncoderFormatter>();
CreateMap<IUser, UserViewModel>();
}
}
I add this to the mapper using the following call:
Mapper.Initialize(x => x.AddProfile<ViewModelAutoMapperConfiguration>());
However, I now want to pass a dependency into the ViewModelAutoMapperConfiguration constructor using IoC. I am using Autofac. I have been reading through the article here: http://www.lostechies.com/blogs/jimmy_bogard/archive/2009/05/11/automapper-and-ioc.aspx but I can't see how this would work with Profiles.
Any ideas?
Thanks
Well, I found a way of doing it by using an overload of AddProfile. There is an overload that takes an instance of a profile, so I can resolve the instance before passing it into the AddProfile method.
A customer of mine was wondering the same thing as DownChapel and his answer triggered me in writing some sample application.
What I've done is the following.
First retrieve all Profile types from the asseblies and register them in the IoC container (I'm using Autofac).
var loadedProfiles = RetrieveProfiles();
containerBuilder.RegisterTypes(loadedProfiles.ToArray());
While registering the AutoMapper configuration I'm resolving all of the Profile types and resolve an instance from them.
private static void RegisterAutoMapper(IContainer container, IEnumerable<Type> loadedProfiles)
{
AutoMapper.Mapper.Initialize(cfg =>
{
cfg.ConstructServicesUsing(container.Resolve);
foreach (var profile in loadedProfiles)
{
var resolvedProfile = container.Resolve(profile) as Profile;
cfg.AddProfile(resolvedProfile);
}
});
}
This way your IoC-framework (Autofac) will resolve all dependencies of the Profile, so it can have dependencies.
public class MyProfile : Profile
{
public MyProfile(IConvertor convertor)
{
CreateMap<Model, ViewModel>()
.ForMember(dest => dest.Id, opt => opt.MapFrom(src => src.Identifier))
.ForMember(dest => dest.Name, opt => opt.MapFrom(src => convertor.Execute(src.SomeText)))
;
}
}
The complete sample application can be found on GitHub, but most of the important code is shared over here.