I organize my project into class libraries and a main caller (now is a console application, then will Apis).
DAL library
BL library
Models (entity) library
Main (console application)
I added Automapper and configured it to work between DAL and BL (Models rapresents all the entity that exposes the BL layer as point in common with other projects).
That's good, but i decided to inject a IMapper via an IoC Container so i can pass the interface to constructors.
Keeping in mind my architecture how can i configure Ninject for this purpose?
I'm using Automapper with "Api Instance" like this:
var config = new MapperConfiguration(cfg => {
cfg.AddProfile<AppProfile>();
cfg.CreateMap<Source, Dest>();
});
var mapper = config.CreateMapper();
Thanks
SOLUTION:
In the Business Logic layer i added a Ninject module:
public class AutomapperModule : NinjectModule
{
public StandardKernel Nut { get; set; }
public override void Load()
{
Nut = new StandardKernel();
var mapperConfiguration = new MapperConfiguration(cfg => { CreateConfiguration(); });
Nut.Bind<IMapper>().ToConstructor(c => new AutoMapper.Mapper(mapperConfiguration)).InSingletonScope();
}
public IMapper GetMapper()
{
return Nut.Get<IMapper>();
}
private MapperConfiguration CreateConfiguration()
{
var config = new MapperConfiguration(cfg =>
{
cfg.AddProfiles(Assembly.GetExecutingAssembly());
cfg.AddProfiles(Assembly.Load("DataAccess"));
});
return config;
}
}
It's a mix between the examples on AutoMapper site and the answer of Jan Muncinsky.
I also added a Get method for returing the context mapper, just for helper.
The client just have to call something like this:
var ioc = new AutomapperModule();
ioc.Load();
var mapper = ioc.GetMapper();
in then passing mapper to constructors...
If you have a better solution feel free to post.
In the simplest form it's easy as:
var kernel = new StandardKernel();
var mapperConfiguration = new MapperConfiguration(cfg => { cfg.AddProfile<AppProfile>(); });
kernel.Bind<IMapper>().ToConstructor(c => new Mapper(mapperConfiguration)).InSingletonScope();
var mapper = kernel.Get<IMapper>();
With usage of Ninject modules:
public class AutoMapperModule : NinjectModule
{
public override void Load()
{
var mapperConfiguration = new MapperConfiguration(cfg => { cfg.AddProfile<AppProfile>(); });
this.Bind<IMapper>().ToConstructor(c => new Mapper(mapperConfiguration)).InSingletonScope();
this.Bind<Root>().ToSelf().InSingletonScope();
}
}
public class Root
{
public Root(IMapper mapper)
{
}
}
...
var kernel = new StandardKernel(new AutoMapperModule());
var root = kernel.Get<Root>();
Related
I have created a test project for my Asp.Net Core 2 app.
Here's my test:
[Fact]
public void GetBlogs()
{
var builder = new DbContextOptionsBuilder<Context>();
builder.UseInMemoryDatabase();
var options = builder.Options;
using (var context = new Context(options))
{
//add new objects (removed for example)
context.AddRange(blogs);
context.SaveChanges();
}
using (var context = new Context(options))
{
var config = new AutoMapper.MapperConfiguration(cfg =>
{
cfg.AddProfile(new DomainToViewModelMappingProfile());
cfg.AddProfile(new ViewModelToDomainMappingProfile());
});
var mapper = config.CreateMapper();
var repository = new BlogRepository(context, mapper);
var blogs = repository.GetBlogs();
TODO: Add Asserts
}
}
Here's my GetBlogs method:
public IEnumerable<GetBlogsQuery> GetBlogs()
{
//UpdateBlogsAsync();
CheckInactiveBlogs();
return _context.Blogs.Where(x => x.IsActive).ProjectTo<GetBlogsQuery>();
}
and the contructor of BlogRepository class:
public BlogRepository(Context context, IMapper mapper)
{
_context = context;
_mapper = mapper;
}
But then the test try to invoke ProjectTo I get an error message:
System.InvalidOperationException: 'Mapper not initialized. Call
Initialize with appropriate configuration. If you are trying to use
mapper instances through a container or otherwise, make sure you do
not have any calls to the static Mapper.Map methods, and if you're
using ProjectTo or UseAsDataSource extension methods, make sure you
pass in the appropriate IConfigurationProvider instance.'
Could you please tell what I'm doing wrong?
Thanks
Update:
Here's my Automapper config class:
public class AutoMapperConfig
{
public static MapperConfiguration RegisterMappings()
{
return new MapperConfiguration(cfg =>
{
cfg.AddProfile(new DomainToViewModelMappingProfile());
cfg.AddProfile(new ViewModelToDomainMappingProfile());
});
}
}
If you do not provide an instance of MapperConfiguration as a parameter on the ProjectTo Extension in which case you do not then you have to use the Static API to Initialization Automapper.
You need to initialize Automapper like the following:
Mapper.Initialize(cfg =>
{
cfg.AddProfile(new DomainToViewModelMappingProfile());
cfg.AddProfile(new ViewModelToDomainMappingProfile());
});
The documentation for ProjectTo: https://github.com/AutoMapper/AutoMapper/wiki/Queryable-Extensions#parameterization
I have tried various permutations of this but my current configuration (as it relates to AutoMapper) is like this:
builder.RegisterAssemblyTypes().AssignableTo(typeof(Profile)).As<Profile>();
builder.Register(c => new MapperConfiguration(cfg =>
{
foreach (var profile in c.Resolve<IEnumerable<Profile>>())
{
cfg.AddProfile(profile);
}
})).AsSelf().SingleInstance();
builder.Register(c => c.Resolve<MapperConfiguration>().CreateMapper(c.Resolve)).As<IMapper>().InstancePerLifetimeScope();
builder.RegisterType<MappingEngine>().As<IMappingEngine>();
I have a constructor using IMapper mapper, however I continue to get the YSOD:
None of the constructors found with'Autofac.Core.Activators.Reflection.DefaultConstructorFinder'
on type '' can be invoked with the available services and parameters:
Cannot resolve parameter 'AutoMapper.IMapper mapper' of constructor
'Void .ctor(...,...,..., AutoMapper.IMapper)'.
This class works perfectly without the automapper reference so I'm certain that the trouble lies with my automapper configuration.
I'm not sure what I'm missing here as I'm very new to both AutoFac and AutoMapper.
Edit:
I've also tried:
builder.Register(c => new MapperConfiguration(cfg =>
{
cfg.CreateMap<IdentityUser, AspNetUser>().ReverseMap();
})).AsSelf().SingleInstance();
builder.Register(ctx => ctx.Resolve<MapperConfiguration>().CreateMapper()).As<IMapper>();
//I've tried both of these lines separately, neither work
builder.Register(c => c.Resolve<MapperConfiguration>().CreateMapper(c.Resolve)).As<IMapper>().InstancePerLifetimeScope();
I've also tried manually adding the profiles per the suggestion in the comments
As I mentioned in a comment, your AutoFac code appears to be correct (except for the assembly scanning portion).
I created the following test app, and it does in fact run without any exceptions and puts a 3 into the Output window (as intended):
using System.Diagnostics;
using Autofac;
using AutoMapper;
namespace Sandbox
{
public partial class App
{
public App()
{
var builder = new ContainerBuilder();
builder.Register(
c => new MapperConfiguration(cfg =>
{
cfg.AddProfile(new TestProfile());
}))
.AsSelf()
.SingleInstance();
builder.Register(
c => c.Resolve<MapperConfiguration>().CreateMapper(c.Resolve))
.As<IMapper>()
.InstancePerLifetimeScope();
builder.RegisterType<MappingEngine>()
.As<IMappingEngine>();
builder.RegisterType<Test>().AsSelf();
var container = builder.Build();
container.Resolve<Test>();
}
}
public class TestProfile : Profile
{
protected override void Configure()
{
CreateMap<Source, Destination>();
}
}
public class Test
{
public Test(IMapper mapper)
{
var source = new Source { Id = 3 };
var destination = mapper.Map<Destination>(source);
Debug.Print(destination.Id.ToString());
}
}
public class Source
{
public int Id { get; set; }
}
public class Destination
{
public int Id { get; set; }
}
}
I would suggest creating a new branch of your app in version control and stripping things out until it works.
This is worked for me...
builder = new ContainerBuilder();
builder.Register(
c => new MapperConfiguration(cfg =>
{
cfg.AddProfile(new TestProfile());
}))
.AsSelf()
.SingleInstance();
builder.Register(
c => c.Resolve<MapperConfiguration>().CreateMapper(c.Resolve))
.As<IMapper>()
.InstancePerLifetimeScope();
builder.RegisterType<MappingEngine>()
.As<IMappingEngine>();
builder.RegisterType<Test>().AsSelf();
var container = builder.Build();
container.Resolve<Test>();
I am trying to figure out the proper way to configure AutoMapper in my application's Startup.cs file and then use it throughout my application.
I am trying to use this documentation which somewhat explains how to still give AutoMapper a static feel without the old static API. The example uses StructureMap.
I would like to know how I can do something similar, but in a Core 1.0 app using the built in services container.
I am assuming that in the Configure function I would configure AutoMapper and then in the ConfigureServices function I would add it as a transient.
I am assuming in the end the cleanest and most proper way to do this is using dependency injection. Here is my current attempt but it is not working:
Startup.cs
public IMapper Mapper { get; set; }
private MapperConfiguration MapperConfiguration { get; set; }
public void ConfigureServices(IServiceCollection services)
{
services.AddTransient<IMapper, Mapper>();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
MapperConfiguration MapperConfiguration = new MapperConfiguration(cfg =>
{
cfg.CreateMap<Product, ProductViewModel>().ReverseMap();
});
Mapper = MapperConfiguration.CreateMapper();
}
In my controller:
private IMapper _mapper { get; set; }
// Constructor
public ProductsController(IMapper mapper)
{
_mapper = mapper;
}
public IActionResult Create(ProductViewModel vm)
{
Product product = _mapper.Map<ProductViewModel, Product>(vm);
}
It just isn't working at all... I must be missing some step or doing something wrong.
This answer suits the MVC 6 approach a little more around the Controller layer:
I migrated from AutoMapper 4.1.1 to 4.2.0, had a few issues figuring out the intricacies but got there in the end.
First I separated the AutoMapper Profile build into a new class (see below) to save clogging up the Startup class.
using AutoMapper;
using YourModels;
using YourViewModels;
namespace YourNamespace
{
public class AutoMapperProfileConfiguration : Profile
{
protected override void Configure()
{
CreateMap<Application, ApplicationViewModel>();
CreateMap<ApplicationViewModel, Application>();
...
}
}
}
I made the following amendments to the Startup class.
I added private member variable of type MapperConfiguration.
private MapperConfiguration _mapperConfiguration { get; set; }
In the Startup constructor I added the following code to instantiate my new AutoMapper Profile.
_mapperConfiguration = new MapperConfiguration(cfg =>
{
cfg.AddProfile(new AutoMapperProfileConfiguration());
});
In ConfigureServices() I dropped my new AutoMapper Profile into a Singleton.
services.AddSingleton<IMapper>(sp => _mapperConfiguration.CreateMapper());
It was then just a simple operation to inject it the relevant controllers.
using AutoMapper;
using ...
namespace YourNamespace
{
public class ApplicationsController : BaseController
{
[FromServices]
private IMapper _mapper { get; set; }
[FromServices]
private IApplicationRepository _applicationRepository { get; set; }
public ApplicationsController(
IMapper mapper,
IApplicationRepository applicationRepository)
{
_mapper = mapper;
_applicationRepository = applicationRepository;
}
// GET: Applications
public async Task<IActionResult> Index()
{
IEnumerable<Application> applications = await _applicationRepository.GetForIdAsync(...);
if (applications == null)
return HttpNotFound();
List<ApplicationViewModel> viewModel = _mapper.Map<List<ApplicationViewModel>>(applications);
return View(viewModel);
}
...
}
Thanks to Rexebin over at https://pintoservice.wordpress.com/2016/01/31/dependency-injection-for-automapper-4-2-in-asp-net-vnext-mvc-project/ for his post which help enourmously.
You can also use the extension package from the automapper creator.
You can pass in a configuration, specify assemblies to scan or pass nothing and let it scan assemblies from the DependencyContext.
https://github.com/AutoMapper/AutoMapper.Extensions.Microsoft.DependencyInjection
https://www.nuget.org/packages/AutoMapper.Extensions.Microsoft.DependencyInjection/
public void ConfigureServices(IServiceCollection services)
{
//configure DI
services.AddTransient<IFoo, Foo>();
//Add automapper - scans for Profiles
services.AddAutoMapper();
//or specify
services.AddAutoMapper(cfg =>
{
cfg.AddProfile<ViewModelProfile>();
...
});
...
In your ConfigurationServices you can new up an instance of MapperConfiguration and then create your maps and add them.
public void ConfigureServices(IServiceCollection services)
{
MapperConfiguration configuration = new MapperConfiguration(cfg =>
{
cfg.AddProfile<MappingProfile.Profile1>();
cfg.AddProfile<MappingProfile.Profile2>();
});
services.AddInstance(typeof (IMapper), configuration.CreateMapper());
}
Then you just inject the IMapper in your constructor and map
public class Handler
{
private readonly ProfileContext _db;
private readonly IMapper _mapper;
public Handler(ProfileContext db, IMapper mapper)
{
_db = db;
_mapper = mapper;
}
public void Handle(Profile1 request)
{
ProfileModel profile = _mapper.Map<Profile1, ProfileModel>(request);
_db.Profiles.Add(profile);
try
{
db.SaveChanges();
}
catch (Exception ex)
{
throw;
}
return profile;
}
}
The posted answer is a great solution, but I figured I would let you guys know how I am currently doing it with Core 1.0 and AutoMapper v5.1.1. I feel like it is a very nice approach and easy to understand.
In Startup.cs at the top of the Configure method simply write the following code to initialize your mappings:
Mapper.Initialize(config =>
{
config.CreateMap<ProductViewModel, Product>().ReverseMap();
config.CreateMap<CustomerViewModel, Customer>().ReverseMap();
});
Then in your controller or any other part of your code where you need to map:
Mapper.Map<Product>(productViewModel);
I am just experimenting with moving a Web API over from .net 4.5 to .net core and needed this. You were very close, as #DavidGouge suggested you need to register the mapper instance you have created in the IOC. So the AutoMapper configuration needs to be done in ConfigureServices. (Have included in line here, but in my project it calls a method in an AutoMapperConfiguration class, so Startup stays as clean as possible, same with the IOC registrations). The IMapper will then be correctly populated in your controller.
Startup.cs becomes
public IMapper Mapper { get; set; }
private MapperConfiguration MapperConfiguration { get; set; }
public void ConfigureServices(IServiceCollection services)
{
MapperConfiguration MapperConfiguration = new MapperConfiguration(cfg =>
{
cfg.CreateMap<Product, ProductViewModel>().ReverseMap();
});
Mapper = MapperConfiguration.CreateMapper();
services.AddInstance(Mapper);
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
// AutoMapper Configuration moved out of here.
}
https://github.com/AutoMapper/AutoMapper/wiki/Migrating-from-static-API
this change breaks my system.
Before update, I use:
===> Startup.cs
public class Startup
{
public Startup(IHostingEnvironment env)
{
...
MyAutoMapperConfiguration.Configure();
}
}
===> MyAutoMapperConfiguration.cs
public class MyAutoMapperConfiguration
{
public static void Configure()
{
Mapper.Initialize(a =>
{
a.AddProfile<AbcMappingProfile>();
a.AddProfile<XyzMappingProfile>();
a.AddProfile<QweMappingProfile>();
});
}
}
===> AbcMappingProfile.cs
public class AbcMappingProfile : Profile
{
protected override void Configure()
{
Mapper.CreateMap<AbcEditViewModel, Abc>();
Mapper.CreateMap<Abc, AbcEditViewModel>();
...
}
}
ERROR:
'Mapper.CreateMap()' is obsolete: 'The static API will be removed in version 5.0. Use a MapperConfiguration instance and store statically as needed. Use CreateMapper to create a mapper instanace.'
I can use Mapper.Map. Now How can I use it
Instead of:
Mapper.CreateMap<AbcEditViewModel, Abc>();
The new syntax is:
var config = new MapperConfiguration(cfg => {
cfg.CreateMap<AbcEditViewModel, Abc>();
});
Then:
IMapper mapper = config.CreateMapper();
var source = new AbcEditViewModel();
var dest = mapper.Map<AbcEditViewModel, Abct>(source);
(Source with more examples)
Instead of Automapper Profile use IMapperConfigurationExpression extension:
Mapping configuration:
public static class AutoMapperConfig
{
public static IMapperConfigurationExpression AddAdminMapping(
this IMapperConfigurationExpression configurationExpression)
{
configurationExpression.CreateMap<Job, JobRow>()
.ForMember(x => x.StartedOnDateTime, o => o.PreCondition(p => p.StartedOnDateTimeUtc.HasValue))
.ForMember(x => x.StartedOnDateTime, o => o.MapFrom(p => p.StartedOnDateTimeUtc.Value.DateTime.ToLocalTime()))
.ForMember(x => x.FinishedOnDateTime, o => o.PreCondition(p => p.FinishedOnDateTimeUtc.HasValue))
.ForMember(x => x.FinishedOnDateTime, o => o.MapFrom(p => p.FinishedOnDateTimeUtc.Value.DateTime.ToLocalTime()));
return configurationExpression;
}
}
Integration (Startup.cs etc.):
var mappingConfig = new AutoMapper.MapperConfiguration(cfg =>
{
cfg.AddAdminMapping();
});
services.AddSingleton(x => mappingConfig.CreateMapper());
Dependency injection added a whole level of complexity to my legacy project that I just didn't want to deal with. As the same library is called with many different technologies, Webforms, MVC, Azure Service, etc...
Also dependency injection would of forced me to rewrite several methods or pass an IMapper around.
So I just reverse engineered what it was doing in 8.0 and wrote a wrapper for it.
public static class MapperWrapper
{
private const string InvalidOperationMessage = "Mapper not initialized. Call Initialize with appropriate configuration. If you are trying to use mapper instances through a container or otherwise, make sure you do not have any calls to the static Mapper.Map methods, and if you're using ProjectTo or UseAsDataSource extension methods, make sure you pass in the appropriate IConfigurationProvider instance.";
private const string AlreadyInitialized = "Mapper already initialized. You must call Initialize once per application domain/process.";
private static IConfigurationProvider _configuration;
private static IMapper _instance;
private static IConfigurationProvider Configuration
{
get => _configuration ?? throw new InvalidOperationException(InvalidOperationMessage);
set => _configuration = (_configuration == null) ? value : throw new InvalidOperationException(AlreadyInitialized);
}
public static IMapper Mapper
{
get => _instance ?? throw new InvalidOperationException(InvalidOperationMessage);
private set => _instance = value;
}
public static void Initialize(Action<IMapperConfigurationExpression> config)
{
Initialize(new MapperConfiguration(config));
}
public static void Initialize(MapperConfiguration config)
{
Configuration = config;
Mapper = Configuration.CreateMapper();
}
public static void AssertConfigurationIsValid() => Configuration.AssertConfigurationIsValid();
}
Initialize it just like you did in previous versions
public static class AutoMapperConfig
{
public static void Configure()
{
MapperWrapper.Initialize(cfg =>
{
cfg.CreateMap<Foo1, Foo2>();
});
MapperWrapper.AssertConfigurationIsValid();
}
}
And just call it in your startup, (Global.asax etc..)
AutoMapperConfig.Configure();
Then all you have to do is add MapperWrapper before all your static calls. And everything works as it did before.
MapperWrapper.Mapper.Map<Foo2>(Foo1);
Ben Walters: Dependency injection added a whole level of complexity to
my legacy project that I just didn't want to deal with...
HI
Furthermore, you can apply the class alias using statement
and no need to change the code, just change the using statement.
Define a using directive and a using alias for a class:
https://learn.microsoft.com/zh-tw/dotnet/csharp/language-reference/keywords/using-directive#example-2
--
.Your class implementation for compatibility.
namespace AutoMappers
{
public class Mapper
{
public static void Initialize(Action<AutoMapper.IMapperConfigurationExpression> config)
{
...
}
}
}
.Change "using AutoMapper" to "using Mapper = AutoMappers.Mapper".
using Mapper = AutoMappers.Mapper; <-- using statement changed
namespace ...
{
public class ...
{
public ...(...)
{
Mapper.Initialize(cfg => cfg.CreateMap<TSource1, TDestination1>()); <-- other code line kept originally
--
I have base controlller I try property injection but not work...
public class BaseController : Controller
{
public ILoggingService loggingService { get; set; }
public BaseController()
{
}
}
This is my Autofac config...
var builder = new ContainerBuilder();
// builder.Register(c => new BaseController { loggingService = c.Resolve<ILoggingService>() });
builder.RegisterControllers(Assembly.GetExecutingAssembly())
.PropertiesAutowired();
builder.Register(c => new BaseController()).OnActivated(e =>
{
e.Instance.loggingService = e.Context.Resolve<ILoggingService>();
});
builder.RegisterAssemblyTypes(Assembly.Load("Aizen.Services"))
.Where(t => t.Name.EndsWith("Service"))
.AsImplementedInterfaces()
.InstancePerRequest();
You haven't registered a concrete type for your ILoggingService interface. Without that, Autofac doesn't know what it actually needs to set your property to.
Add something like this (replacing with the real object of course):
builder.RegisterType<YourLoggingServiceGoesHere>().As<ILoggingService>();
You can try this solution:
builder.Register(c => new BaseController { loggingService = c.Resolve<ILoggingService>() });