Where to place AutoMapper map registration in referenced dll - c#

This is my first AutoMapper project and may be obvious to some but the tutorials and examples are not clicking with me. I am trying to understand where and to a certain degree how to register(I think I want profiles) my maps for use. There are plenty of MVC examples saying to use the global asax and this makes sense but what is the equivalent in a library project?
In my sandbox I have a winform app and a core library. The winform app calls methods made available by the library and it is one of these library methods that makes use of automapper.
So for some background here is my map:
(and to be clear the mapping is in the SAME core library project)
public class Raw_Full_Map
{
public Raw_Full_Map()
{
Mapper.CreateMap<IEnumerable<RawData>, FullData>()
.ForMember(d => d.Acres, m => m.ResolveUsing(new RawLeadDataNameResolver("Acres")));
//this is clearly just a snip to show it's a basic map
}
}
This is the core library method being called: (note it is a static..which means I won't have a constructor...if this is the problem am I to understand then that AutoMapper can't be utilized by static helper classes...that doesn't make sense....so likely I'm just not doing it right.
public static class RawDataProcessing
{
public static FullData HTMLDataScrape(string htmlScrape)
{
HtmlDocument doc = new HtmlDocument();
doc.LoadHtml(htmlScrape);
var list = Recurse(doc.DocumentNode);
//HTML agility stuff that turns my html doc into a List<RawData> object
return Mapper.Map<FullData>(list);
}
My test harness calls it like this:
var _data = RawDataProcessing.HTMLDataScrape(rawHTML);
This of course errors because the map isn't "registered".
If I do this in the test harness:
var x = new RawData_FullData();
var _data = RawDataProcessing.HTMLDataScrape(rawHTML);
Then everything works as my map get's registered albeit I think in a really bogus way...but it does work.
So the question is how do I register my mapping in the core library project...so that ANY method can use it...there isn't really an equivalent global.asax in a dll is there?
Thank you for helping me connect the missing pieces.

Put it in the static constructor of either the source or the target type of the mapping.
public class FullData
{
static FullData()
{
Mapper.CreateMap<IEnumerable<RawData>, FullData>()
.ForMember(d => d.Acres, m => m.ResolveUsing(new RawLeadDataNameResolver("Acres")));
}
}
The static constructor will automatically get called the first time you try to use the type FullData for anything (for example a mapping).

You can use PreApplicationStartMethod for any class and it's method in your class library which will be referenced from your startup project if you want automatically to call this on startup. And then you can register all your mappings in that method. By the way, I suggest to use AddProfile for registering all mappings.
[assembly: PreApplicationStartMethod(typeof(MyClassLibrary.Startup), "Start")]
namespace MyClassLibrary
{
public class Startup
{
// Automatically will work on startup
public static void Start()
{
Mapper.Initialize(cfg =>
{
Assembly.GetExecutingAssembly().FindAllDerivedTypes<Profile>().ForEach(match =>
{
cfg.AddProfile(Activator.CreateInstance(match) as Profile);
});
});
}
}
}
You just need to create new classes which derived from Profile class and then override it's Configure() method:
...
public class FooMapperProfile:Profile
{
protected override void Configure()
{
Mapper.CreateMap<OtherFoo, Foo>()
.ForMember(...
... // so on
}
}
public class AnotherFooMapperProfile:Profile
{
protected override void Configure()
{
Mapper.CreateMap<OtherFoo, AnotherFoo>()
.ForMember(...
... // so on;
}
}
...
// and so on
Additional information:
If you have seen I have initialized all mappings with that code:
Mapper.Initialize(cfg =>
{
Assembly.GetExecutingAssembly().FindAllDerivedTypes<Profile>().ForEach(match =>
{
cfg.AddProfile(Activator.CreateInstance(match) as Profile);
});
});
It will automatically find all types derived from Profile and will add all profiles after createing their new instances.
Update1:
As #Scott Chamberlain commented, PreApplicationStartMethod only works for ASP.NET applications. This would not work with a desktop app. If you are working with Wpf, then you can use Application.OnStartup method. Or just call Start.Startup (); in load event.
Update2:
FindAllDerivedTypes extension method:
public static class AssemblyExtensions
{
public static List<Type> FindAllDerivedTypes<T>(this Assembly assembly)
{
var derivedType = typeof(T);
return assembly.GetTypes()
.Where(t => t != derivedType && derivedType.IsAssignableFrom(t))
.ToList();
}
}

Related

StructureMap does not see types in ASP.NET Core .NET5

I am creating sample project based on DDD.
I created SharedKernel project where I have my class for DomainEvents
public static class DomainEvents
{
public static IContainer Container { get; set; }
static DomainEvents()
{
Container = StructureMap.Container.For<GenericTypesScanning>();
}
public static void Raise<T>(T args) where T : IDomainEvent
{
foreach (var handler in Container.GetAllInstances<IHandle<T>>())
{
handler.Handle(args);
}
}
}
and this is class GenericTypesScanning
public class GenericTypesScanning : Registry
{
public GenericTypesScanning()
{
Scan(scan =>
{
// 1. Declare which assemblies to scan
scan.Assembly("MyLib");
// 2. Built in registration conventions
scan.AddAllTypesOf(typeof(IHandle<>));
scan.WithDefaultConventions();
});
}
}
In MyLib project I have class AppointmentConfirmedEvent and handler for this event:
public class EmailConfirmationHandler: IHandle<AppointmentConfirmedEvent>
{
public void Handle(AppointmentConfirmedEvent appointmentConfirmedEvent)
{
// TBD
}
}
I have temporary rest api controller where I wanted to check if everything is correctly registered and I am doing this:
[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
{
// GET: api/<ValuesController>
[HttpGet]
public IEnumerable<string> Get()
{
var appointmentConfirmedEvent = new AppointmentConfirmedEvent();
DomainEvents.Raise(appointmentConfirmedEvent);
return new string[] { "value1", "value2" };
}
}
but when DomainEvents.Raise is called the event is not handled because internal call Container.GetAllInstances<IHandle<T>>() returns empty array.
I did analogous example with Console app and there everything works fine. Any idea why it does not work in case of ASP.NET Core .NET 5 project?
-Jacek
The AddAllTypesOf() method does not work with open generics. See the ConnectImplementationsToTypesClosing() method in the StructureMap docs: http://structuremap.github.io/generics/
And just a reminder, StructureMap is no longer supported. Moreover, 2.6.4.1 was the "haunted" version of StructureMap that was admittedly buggy.
The first thing to do is to check out the type scanning diagnostics:
http://structuremap.github.io/diagnostics/type-scanning/.
The type scanning can be a little brittle if there are missing assemblies. The diagnostics might point out where things are going wrong. Also, try your WhatDoIHave() diagnostics too.
And also, just making sure that you know that StructureMap is no longer supported and has been replaced by Lamar:
https://jeremydmiller.com/2018/01/29/sunsetting-structuremap/
https://jasperfx.github.io/lamar

AutoMapper 5.2 how to configure

What is the correct way to configure AutoMapper for global use.
I want to set it once and then used though out the app.
i have a strong feeling this is wrong.
in fact i know this is wrong as this calls an new instance.
I want a global config and then how do you call it.
Can not find a good example!
this is what ive got: but its not what im wanting
public static class AutoMapperConfig
{
public static IMapper GetMapper()
{
var config = new MapperConfiguration(cfg => {
cfg.CreateMap<R_Logo, LogoDto>();
//lots more maps...?
});
IMapper mapper = config.CreateMapper();
return mapper;
}
}
and then usage:
var imapper = AutoMapperConfig.GetMapper();
var dest = imapper.Map<R_Logo, LogoDto>(logo);
UPDATE based on: pinkfloydx33
Call this once and then the config is done.
public static class AutoMapperConfig
{
public static void RegisterMappings()
{
AutoMapper.Mapper.Initialize(cfg => {
cfg.CreateMap<R_Logo, LogoDto>();
/* etc */
});
}
}
Here is the steps to configure the automapper in asp.net core mvc.
1. Create the mapping profile class which extends from Profile
public class ClientMappingProfile : Profile
{
public ClientMappingProfile ()
{
CreateMap<R_Logo, LogoDto>().ReverseMap();
}
}
2. Create the AutoMapper Configuration Class and add your mapping profile class here.
public class AutoMapperConfiguration
{
public MapperConfiguration Configure()
{
var config = new MapperConfiguration(cfg =>
{
cfg.AddProfile<ClientMappingProfile>();
});
return config;
}
}
3. How we can use it.
var config = new AutoMapperConfiguration().Configure();
var iMapper = config.CreateMapper();
var dest = iMapper.Map<R_Logo, LogoDto>(logo);
Set this in your StartupConfig or StartUp file.
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Web API configuration and services
.....
MappingDTOModelToModel.Configure();
}
}
Configuration of Mappings,
public static class MappingDTOModelToModel
{
private static void Configure()
{
Mapper.Initialize(cfg =>
{
cfg.CreateMap<R_Logo, LogoDto>()
.ForMember(x => x.ID,
m => m.MapFrom(a => a.ID))
.ForMember(x => x.FirstName,
m => m.MapFrom(a => a.FirstName)).ReverseMap();
}
}
}
Calling it in a method,
public class MyService
{
public void MyMethod(var model)
{
var myModel = Mapper.Map<LogoDto, R_Logo>(model);
}
}
Hope this helps,
You can use the static mapper api as outlined here.
For example, somewhere in your application, probably during startup you would configure the static (global) mapper using something like:
AutoMapper.Mapper.Initialize(cfg => {
cfg.CreateMap<Type1, Type2>();
/* etc */
});
Then, any time you need to use your "globally" configured mapper, you access it via the static Mapper property (which is an IMapper):
Type1 objectOfType1 = new Type1();
var result = AutoMapper.Mapper.Map<Type2>(objectOfType1);
You then have one mapper that has been configured for all the types/configurations/profiles you provide for the duration of your application without needing to configure individual mapper instances.
In short, you configure it once (perhaps at application startup). The static mapper instance (the IMapper) is then available anywhere throughout your application by accessing it via AutoMapper.Mapper.
Access via this static property is what you refer to as "globally" in your comments. Anywhere you need it just use AutoMapper.Mapper.Map(...) So long as you've called Initialize once first.
Note that if you call Initialize more than once on the static instance, each subsequent call overwrites the existing configuration.
WARNING
In a previous release of AutoMapper, the static mapper was removed. It was later added back in and I don't know if they guarantee that it will remain in future versions. The recommendation is to use your own configured instances of a mapper. You can store it in a static property somewhere if you need it. Otherwise you can look into profiles, etc for easy ways to configure your mapper so that having your own instance isn't necessarily a "hassle".
Our solution to this problem was to first create a selection of attributes that can decorate a class as being "Mappable" (either To, From or Both). Then you can initialize the AutoMapper in a single location, usually post application initialization and use Reflection to dynamically create a map for each instance of the decorated classes.
Here's an example:
var types = _myTypeFinder.Find(type =>
type.IsDefined(typeof(AutoMapperAttribute)) ||
type.IsDefined(typeof(AutoMapperFromAttribute)) ||
type.IsDefined(typeof(AutoMapperToAttribute))
);
Mapper.Initialize(cfg =>
{
foreach (var type in types)
{
AutoMapperHelper.CreateMap(type, cfg);
}
});
I have find best solution for configuration auto mapper in .Net Core.
Multiple Profile.
Just use this:
services.AddSingleton(provider => new MapperConfiguration(cfg =>
{
cfg.AddProfile(new sampleProfileMapper());
}).CreateMapper());

How can I add a mapping in AutoMapper after Initialize has been called?

I have a couple of ASP.Net apps that share mapping code, so I've created a generic automapper init class.
However, in one of my apps, I have some specific classes that I want added to the configuration.
I have the following code:
public class AutoMapperMappings
{
public static void Init()
{
AutoMapper.Mapper.Initialize(cfg =>
{
... A whole bunch of mappings here ...
}
}
}
and
// Call into the global mapping class
AutoMapperMappings.Init();
// This erases everything
AutoMapper.Mapper.Initialize(cfg => cfg.CreateMap<CustomerModel, CustomerInfoModel>());
How do I add this unique mapping without destroying what is already initialized?
A quick sample that allows you to initialize your AutoMapper 5.x several times...
Ok it's not very nice ;)
public static class MapperInitializer
{
/// <summary>
/// Initialize mapper
/// </summary>
public static void Init()
{
// Static mapper
Mapper.Initialize(Configuration);
// ...Or instance mapper
var mapperConfiguration = new MapperConfiguration(Configuration);
var mapper = mapperConfiguration.CreateMapper();
// ...
}
/// <summary>
/// Mapper configuration
/// </summary>
public static MapperConfigurationExpression Configuration { get; } = new MapperConfigurationExpression();
}
// First config
MapperInitializer.Configuration.CreateMap(...);
MapperInitializer.Init(); // or not
//...
MapperInitializer.Configuration.CreateMap(...);
MapperInitializer.Init();
The idea is to store the MapperConfigurationExpression instead of the MapperConfiguration instance.
This should be possible if you use the instance API that AutoMapper provides instead of the static API. This wiki page details the differences between the two.
Essentially instead of calling AutoMapper.Mapper.Initialize(cfg => ...) again for your additional mapping, which overwrites the entire global mapper configuration with that single mapping, you'll need to create another mapper object with the instance API using:
var config = new MapperConfiguration(cfg =>
cfg.CreateMap<CustomerModel, CustomerInfoModel>()
);
var mapper = config.CreateMapper();
Of course in order to use this new mapper you will have to do something like var mappedModel = mapper.Map<CustomerInfoModel>(new CustomerModel()); specifically when mapping objects using your additional mapping configuration. Whether that's practical in your case, I don't know, but I believe this is the only way to do what you require.
You can't, but rather than initialize the Mappings from your Init method, you could get it to return a function that can be called inside a Mapper.Initialize() call.
So, your Init method looks like this:
public static Action<IMapperConfigurationExpression> Init()
{
return (cfg) => {
... A whole bunch of mappings here ...
};
}
Then from your app where you want extra mappings:
var mappingsFunc = MyClass.Init();
Mapper.Initialize((cfg) => {
mappingsFunc(cfg);
... Extra mappings here ...
});
or you could reduce it a little...
Mapper.Initialize((cfg) => {
MyClass.Init()(cfg);
... Extra mappings here ...
});
Hope this helps.
Automapper 5+
I have an initialiser class in my main assembly
public static class Mapping
{
public static void Initialize()
{
// Or marker types for assemblies:
Mapper.Initialize(cfg =>
cfg.AddProfiles(new[] {
typeof(MapperFromImportedAssemblyA),
typeof(MapperFromImportedAssemblyB),
typeof(MapperFromImportedAssemblyC)
})
);
}
}
Then in each assembly that requires a Mapper
public class MapperFromImportedAssemblyA : Profile
{
public MapperFromImportedAssemblyA()
{
// Use CreateMap here (Profile methods are the same as configuration methods)
}
}
This is what I hacked up for my requirement.
Actual Configurator
public static void Configure(params Action<MapperConfigurationExpression>[] registerCallbacks)
{
MapperConfigurationExpression configuration = new MapperConfigurationExpression();
foreach (Action<MapperConfigurationExpression> regCallBack in registerCallbacks)
{
regCallBack.Invoke(configuration);
}
AutoMapper.Mapper.Initialize(configuration);
}
Mapping Group 1
public class AutoMapperConfigSet1
{
public static void RegisterTypes(MapperConfigurationExpression configuration)
{
configuration.CreateMap<Foo, Bar>();
}
}
Mapping Group 2
public class AutoMapperConfigSet2
{
public static void RegisterTypes(MapperConfigurationExpression configuration)
{
configuration.CreateMap<Foo1, Bar1>();
}
}
When initializing
Configure(AutoMapperConfigSet1.RegisterTypes,AutoMapperConfigSet2.RegisterTypes);

Initialize Automapper from a (GAC) Class library

I'm currently experimenting with AutoMapper ( latest .NET 3.5 version).
To make AutoMapper work, you have to provide it with configuration details on how to map from one object to another.
Mapper.CreateMap<ContactDTO, Contact>();
Mapper.CreateMap<Contact, ContactDTO>();
you should do this when an application, service, website starts. (using global.asax etc)
Problem is, I'm using Automapper in a GAC'd DLL to map LINQ2SQL objects to their BO counterpart.
In order to avoid having to specify the .CreateMap<> details all the time I want map 2 objects, where can I specify this configuration once if possible?
I believe the solution is in AutoMapper itself.
Use AutoMapper Profiles and register them at startup.
The example bellow doesn't even require an IOC container if your Profiles do not require any dependencies.
/// <summary>
/// Helper class for scanning assemblies and automatically adding AutoMapper.Profile
/// implementations to the AutoMapper Configuration.
/// </summary>
public static class AutoProfiler
{
public static void RegisterReferencedProfiles()
{
AppDomain.CurrentDomain
.GetReferencedTypes()
.Where(type => type != typeof(Profile)
&& typeof(Profile).IsAssignableFrom(type)
&& !type.IsAbstract)
.ForEach(type => Mapper.Configuration.AddProfile(
(Profile)Activator.CreateInstance(type)));
}
}
And them just implement Profiles just like this example:
public class ContactMappingProfile : Profile
{
protected override void Configure()
{
this.CreateMap<Contact, ContactDTO>();
this.CreateMap<ContactDTO, Contact>();
}
}
But if your Profiles have dependencies that need to be resolved you could create an abstraction for AutoMapper and register all Profiles just before registering the abstraction - IObjectMapper - as a singleton like this:
public class AutoMapperModule : Module
{
protected override void Load(ContainerBuilder builder)
{
base.Load(builder);
// register all profiles in container
AppDomain.CurrentDomain
.GetReferencedTypes()
.Where(type => type != typeof(Profile)
&& typeof(Profile).IsAssignableFrom(type)
&& !type.IsAbstract)
.ForEach(type => builder
.RegisterType(type)
.As<Profile>()
.PropertiesAutowired());
// register mapper
builder
.Register(
context =>
{
// register all profiles in AutoMapper
context
.Resolve<IEnumerable<Profile>>()
.ForEach(Mapper.Configuration.AddProfile);
// register object mapper implementation
return new AutoMapperObjectMapper();
})
.As<IObjectMapper>()
.SingleInstance()
.AutoActivate();
}
}
Since I abstract all my tech in the domain this seemed the best approach for me.
Now go and code dude, gooooo!
PS- the code could be using some helpers and extensions, but the core stuff it's there.

Using Registry in StructureMap to create a pluggable architechture

I have a web project and I want it to work with ravendb and ravendb-embedded.
So this is how I think I should solve it.
Two projects, MvcRavendb and MvcRavendb-Embedded where the two projects references two different nuget packages, one for ravendb and one for ravendb embedded.
In my core project I have an interface IDocumentStoreInitializer which has one method, IDocumentStore InitializeDocumentStore()
MvcRavenDb and MvcRavenDb-Embedded has one class like this
public class RegisterRavenDb : IDocumentStoreInitializer {
public IDocumentStore InitializeDocumentStore() {
return new DocumentStore OR EmbeddableDocumentStore();
}
}
Then I have a class that registers the concrete implementation like this
public class RavenRegistry : Registry {
public RavenRegistry() {
For<IDocumentStoreInitializer>().Use<RegisterRavenDb>();
}
}
So far so good but then I have bootstrapper that configures structuremap like this
public class Bootstrapper {
public static IContainer Initialize() {
ObjectFactory.Initialize(x =>
{
// here I want to use the registered concrete implmentaiton of IDocumentStore
var documentStore = new DocumentStore { ConnectionStringName = "RavenDB" };
documentStore.Initialize();
}
}
}
So how can I tell structuremap to use InitializeDocumentStore from the RavenRegistry class?
Maybe I have missed something or I'm taking the wrong approach here
Just use the EmbeddableDocumentStore instance, using the connection string, you can control whatever it will be embedded or server/client.
This should do as you wish. In OnCreation the ConnectionString can be set, too.
ObjectFactory.Initialize(x =>
{
// here I want to use the registered concrete implmentaiton of IDocumentStore
x.Scan(scan =>
{
scan.TheCallingAssembly();
scan.AssembliesFromApplicationBaseDirectory();
scan.LookForRegistries();
});
x.For<IDocumentStore>().Use(c =>
c.GetInstance<IDocumentStoreInitializer>().
InitializeDocumentStore()).OnCreation<IDocumentStore>(z => z.Initialize());
});

Categories