I'm using automapper library to convert my Model into my ViewModel. For each Model, I create profile which inside i add my maps using CreateMap.
I want to use custom ValueResolver in which it will get the logged user ID from IContext, so i need to pass implementation of IContext using Ninject.
Inside my profile class:
Mapper.CreateMap<ViewModel, BusinessModel>()
.ForMember(dest => dest.ManagerId, opt => opt.ResolveUsing<GetManagerResolver>());
Then my GetManagerResolver:
public class GetManagerResolver : ValueResolver<BusinessModel, int>
{
private IContext context;
public GetManagerResolver(IContext context)
{
this.context = context;
}
protected override int GetManagerResolver(BusinessModel source)
{
return context.UserId;
}
}
But i get this exception message {"Type needs to have a constructor with 0 args or only optional args\r\nParameter name: type"}.
Any Ideas on how make automapper use ninject for object creation?
UPDATE
My code to add automapper configuration:
public static class AutoMapperWebConfiguration
{
public static void Configure()
{
Mapper.Initialize(cfg =>
{
cfg.AddProfile(new Profile1());
cfg.AddProfile(new Profile2());
// now i want to add this line, but how to get access to kernel in static class?
// cfg.ConstructServicesUsing(t => Kernel.Get(t));
});
}
}
You can use the ConstructedBy function to configure how Automapper should create your GetManagerResolver after calling ResolveUsing:
Mapper.CreateMap<ViewModel, BusinessModel>()
.ForMember(dest => dest.ManagerId,
opt => opt.ResolveUsing<GetManagerResolver>()
.ConstructedBy(() => kernel.Get<GetManagerResolver>());
Or you can globally sepecify your Ninject kernel to be used by Automapper when resolving any type with Mapper.Configuration.ConstructServicesUsing method:
Mapper.Configuration.ConstructServicesUsing((type) => kernel.Get(type));
What I ended up doing was to create NinjectModule for Automapper where I put all my automapper configuration and tell automapper to use Ninject Kernel to construct objects. Here is my code:
public class AutoMapperModule : NinjectModule
{
public override void Load()
{
Mapper.Initialize(cfg =>
{
cfg.ConstructServicesUsing(t => Kernel.Get(t));
cfg.AddProfile(new Profile1());
cfg.AddProfile(new Profile2());
});
}
}
Related
Example: I have a countries catalog stored in another DB and I need to use it as a property in some ContentParts. I'm trying to make the connection without interfering much with Orchard wiring.
public class MoviePart : ContentPart<MoviePartRecord>
{
public IEnumerable<CountryRecord> Countries
{
get
{
return Record.Countries.Select(r => r.CountryRecord);
}
}
}
The relation between CountryRecords and MovieParts will be on the Orchard DB, but the CountryRecord data is in another DB. I only need Read access, but I don't get which and how to override the Handler to use the other source.
Do I need to create a ContentHandler and override all methods, and create another StorageFilter that uses the new repository with the external source? And how would I inject the new repo into the handler?
public class CountryPartHandler : ContentHandler
{
public CountryPartHandler(IRepository<CountryPartRecord> repository)
{
Filters.Add(StorageFilter.For(repository));
}
protected override void Loading(LoadContentContext context)
{
base.Loading(context);
}
}
Update:
In this Using External Data with Orchard (around 25th min) video, he seems to be doing what I need with this code:
public ProductPartHandler(IRepository<ProductPartRecord> repository, Work<IProductService> productServiceWork)
{
Filters.Add(StorageFilter.For(repository));
OnActivated<ProductPart>((context, part) => {
part.ProductField.Loader(() => productServiceWork.Value.GetProduct(part.Id));
});
}
But in my code it can't find the "Loader" function, even though I have all the references from the video too, so maybe ProductField is a custom type?
So that is a lazy field on the part, something like this:
public class MyPart : ContentPart {
internal readonly LazyField<CustomData> CustomDataField = new LazyField<CustomData>();
public CustomData CustomData {
get { return CustomDataField.Value; }
}
}
public class CustomData {
...
}
public class MyPartHandler : ContentPartHandler {
private ICustomService _customService;
public MyPartHandler(ICustomService customService){
_customService = customService;
OnActivated<MyPart>(Initialize);
}
private void Initialize(ActivatedContentContext context, MyPart part){
part.CustomDataField.Loader(() => {
return _customService.Get(part.ContentItem.Id);
});
}
}
I don't know how you are loading your external data, whether via rest, wcf etc., but the logic can just be thrown into the custom service
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
The application I'm working on has several places where we use AutoMapper to map entities.
The problem is if I had a model entity from one side to the other of the project, many times I forget to add the mapping for the new entity (I just need a copy paste from other entities), ending up that the solution compiles and I get no exception.
It just launches the application without full functionality and no debugging messages, which makes difficult to figure out what I've missed.
Is there any way to force the compiler at compile time to give me an error in case I forget to do a mapping?
AFAIK, there isn't a possibility to force compile-time checking for Automapper.
Nevertheless, there is a possibility to verify the correctness of your mappings:
After you've defined all your mappings, call the AssertConfigurationIsValid method which will throws an AutoMapperConfigurationException exception if the defined mappings are broken.
You can make this a part of your unit or integration test suite.
I had the same problem and decided to solve it by wrapping up AutoMapper. For each source-destination map I provide a method that I create after I've added it to my AutoMapper profile.
This may take away some of the ease of implementing AutoMapper but I find the compile time checking worth it.
public class MyType {
public string SomeProperty { get;set; }
}
public class MyOtherType {
public string SomeProperty { get;set; }
}
public class MyAlternateType {
public string AlternateProperty {get;set;}
}
public class AutoMapperProfile : Profile {
public AutoMapperProfile() {
CreateMap<MyType, MyOtherType>();
CreateMap<MyAlternateType, MyOtherType>()
.ForMember(ot => ot.SomeProperty, options => options.MapFrom(at => at.AlternateProperty));
}
}
public interface IMyMappingProvider {
// Uncomment below for Queryable Extensions
//IQueryable<TDestination> ProjectTo<TSource, TDestination>(IQueryable<TSource> source, params Expression<Func<TDestination, object>>[] membersToExpand);
//IQueryable<TDestination> ProjectTo<TSource, TDestination>(IQueryable<TSource> source, IDictionary<string, object> parameters, params string[] membersToExpand);
/*
* Add your mapping declarations below
*/
MyOtherType MapToMyOtherType(MyType source);
MyOtherType MapToMyOtherType(MyAlternateType source);
}
public class MyMappingProvider : IMyMappingProvider {
private IMapper Mapper { get; set; }
public MyMappingProvider(IMapper mapper) {
Mapper = mapper;
}
/* Uncomment this for Queryable Extensions
public IQueryable<TDestination> ProjectTo<TSource, TDestination>(IQueryable<TSource> source, params Expression<Func<TDestination, object>>[] membersToExpand) {
return new ProjectionExpression(source, Mapper.ConfigurationProvider.ExpressionBuilder).To<TDestination>(null, membersToExpand);
}
public IQueryable<TDestination> ProjectTo<TSource, TDestination>(IQueryable<TSource> source, IDictionary<string, object> parameters, params string[] membersToExpand) {
return new ProjectionExpression(source, Mapper.ConfigurationProvider.ExpressionBuilder).To<TDestination>(parameters, membersToExpand);
}
*/
/*
* Implement your mapping methods below
*/
public MyOtherType MapToMyOtherType(MyType source) {
return Mapper.Map<MyType, MyOtherType>(source);
}
public MyOtherType MapToMyOtherType(MyAlternateType source) {
return Mapper.Map<MyAlternateType, MyOtherType>(source);
}
}
If you are using the AutoMapper's Queryable extensions you can add the following class and uncomment the Queryable Extensions code above.
public static class QueryableExtensions {
/*
* Implement your extension methods below
*/
public static IQueryable<MyOtherType> ProjectToMyOtherType(this IQueryable<MyType> source, IMyMappingProvider mapper, params Expression<Func<MyOtherType, object>>[] membersToExpand)
{
return mapper.ProjectTo<MyType, MyOtherType>(source, membersToExpand);
}
public static IQueryable<MyOtherType> ProjectToMyOtherType(this IQueryable<MyAlternateType> source, IMyMappingProvider mapper, params Expression<Func<MyOtherType, object>>[] membersToExpand)
{
return mapper.ProjectTo<MyAlternateType, MyOtherType>(source, membersToExpand);
}
}
Tested with AutoMapper 6.1.1 using LinqPad:
var autoMapperConfig = new MapperConfiguration(cfg => { cfg.AddProfile(new AutoMapperProfile()); });
IMyMappingProvider mapper = new MyMappingProvider(autoMapperConfig.CreateMapper());
var myTypes = new List<MyType>()
{
new MyType() {SomeProperty = "Test1"},
new MyType() {SomeProperty = "Test2"},
new MyType() {SomeProperty = "Test3"}
};
myTypes.AsQueryable().ProjectToMyOtherType(mapper).Dump();
var myAlternateTypes = new List<MyAlternateType>()
{
new MyAlternateType() {AlternateProperty = "AlternateTest1"},
new MyAlternateType() {AlternateProperty = "AlternateTest2"},
new MyAlternateType() {AlternateProperty = "AlternateTest3"}
};
myAlternateTypes.AsQueryable().ProjectToMyOtherType(mapper).Dump();
mapper.MapToMyOtherType(myTypes[0]).Dump();
As #serge.karalenka said, don't forget to still test your mapping configuration by calling AssertConfigurationIsValid().
I'm sure I am missing something simple. First, I'll show all the code I have written to wire up the plumbing, then I'll show the exception message. Then, I'll set out what I have tried to fix it.
LicenceTrackerProfile
public class LicenceTrackerProfile : Profile
{
const string LicenceTrackerProfileName = "LicenceTrackerProfile";
public override string ProfileName
{
get { return LicenceTrackerProfileName; }
}
protected override void Configure()
{
// initialize mappings here
new ViewModelMappings(this).Initialize();
}
}
MapperBootstrapper
public class MapperBootstrapper
{
public void Configure()
{
var profile = new LicenceTrackerProfile();
AutoMapper.Mapper.Initialize(p => p.AddProfile(profile));
}
}
MappingBase
public abstract class MappingBase
{
private readonly Profile _profile;
protected MappingBase(Profile profile)
{
_profile = profile;
_profile.SourceMemberNamingConvention = new PascalCaseNamingConvention();
_profile.DestinationMemberNamingConvention = new PascalCaseNamingConvention();
}
public Profile Profile
{
get { return _profile; }
}
}
UniversalMapper
public class UniversalMapper : IUniversalMapper
{
private readonly IMappingEngine _mappingEngine;
public UniversalMapper(IMappingEngine mappingEngine)
{
_mappingEngine = mappingEngine;
}
public virtual TDestination Map<TSource, TDestination>(TSource source, TDestination destination)
{
return _mappingEngine.Map(source, destination);
}
}
ViewModelMappings
public class ViewModelMappings : MappingBase, IMappingInitializer
{
private readonly Profile _profile;
public ViewModelMappings(Profile profile) : base(profile)
{
_profile = profile;
_profile.SourceMemberNamingConvention = new PascalCaseNamingConvention();
_profile.DestinationMemberNamingConvention = new PascalCaseNamingConvention();
}
public void Initialize()
{
// data to domain mappings
Profile.CreateMap<EFDTO.Enums.FileTypes, Domain.FileTypes>();
Profile.CreateMap<EFDTO.Licence, Domain.Licence>();
Profile.CreateMap<EFDTO.LicenceAllocation, Domain.LicenceAllocation>();
Profile.CreateMap<EFDTO.Person, Domain.Person>();
Profile.CreateMap<EFDTO.Software, Domain.Software>();
Profile.CreateMap<EFDTO.SoftwareFile, Domain.SoftwareFile>();
Profile.CreateMap<EFDTO.SoftwareType, Domain.SoftwareType>();
}
}
Note, the initialize method and Configure method are being called, so they're not being "missed".
Exception
Missing type map configuration or unsupported mapping.
Mapping types: Software -> Software LicenceTracker.Entities.Software
-> LicenceTracker.DomainEntities.Software
Destination path: Software
Source value: LicenceTracker.Entities.Software
Troubleshooting
Ignoring columns. I planned to ignore columns, starting with all and then eliminating them by un-ignoring them 1 by 1 until I found the problem columns. However, to my surprise, the error occurs when I ignore all columns:
Profile.CreateMap<EFDTO.Software, Domain.Software>()
.ForMember(software => software.Licences, e => e.Ignore())
.ForMember(software => software.Name, e => e.Ignore())
.ForMember(software => software.SoftwareFiles, e => e.Ignore())
.ForMember(software => software.Type, e => e.Ignore())
.ForMember(software => software.Description, e => e.Ignore())
.ForMember(software => software.Id, e => e.Ignore())
.ForMember(software => software.TypeId, e => e.Ignore()
.ForMember(software => software.ObjectState, e => e.Ignore());
The Domain entities have [DataContract] (at class level) and [DataMember] (at method level) attributes. I added each of those attributes to the EF entities as well.
Other than that, I am out of ideas. It all seems to be wired up correctly.
What did I miss?
I'm back to heroically answer my question.
The problem was in the Service which created the UniversalMapper object (forgive the sloppy code, it is not final yet):
public class LicenceTrackerService : ILicenceTrackerService, IDisposable
{
LicenceTrackerContext context = new LicenceTrackerContext();
private MapperBootstrapper mapperBootstrapper;
private IUniversalMapper mapper = new UniversalMapper(Mapper.Engine);
private IUnitOfWork unitOfWork;
public LicenceTrackerService()
{
unitOfWork = new UnitOfWork(context, new RepositoryProvider(new RepositoryFactories()));
mapperBootstrapper = new MapperBootstrapper();
mapperBootstrapper.Configure();
Database.SetInitializer(new LicenceTrackerInitializer());
context.Database.Initialize(true);
}
public int GetNumber()
{
return 42;
}
public List<LicenceTracker.DomainEntities.Software> GetSoftwareProducts()
{
var productsRepo = unitOfWork.Repository<Software>();
var list = productsRepo.Query().Select().ToList();
var softwareList = new List<LicenceTracker.DomainEntities.Software>();
foreach (var software in list)
{
var softwareProduct = new LicenceTracker.DomainEntities.Software();
softwareList.Add(Mapper.Map(software, softwareProduct));
}
return softwareList;
}
public void Dispose()
{
unitOfWork.Dispose();
}
}
I'm still not sure why, but initializing the mapper outside of the constructor (default value style) was not happy. By moving that instantiation into the constructor of the service, it worked:
private IUniversalMapper mapper;
public LicenceTrackerService()
{
mapper = new UniversalMapper(Mapper.Engine);
...
}
There's obviously something about static properties (Mapper.Engine) and default instantiations that I'm not understanding.
Anyway, no big deal as I was planning to inject the UniversalMapper into the service anyway.
Edit
I've actually figured out the problem for real now. It is an ordering thing. With Automapper, I had to initialize the mapper with the Profile before inserting the Mapper.Engine into the UniversalMapper.
Obviously, the Get aspect of the Mapper.Engine property is not just a memory reference to an object. And yes, a quick glance at the code inside Automapper confirms that.
So, assigning the result of the Get property to the _mappingEngine field of the UniversalMapper must happen after that engine has been configured.
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.