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.
Related
I'm trying to implement caching in our Data Access layer in an ASP.NET Core MVC project as painlessly as possible. The main issue is that we don't want to read from the cache on all pages, only on some. The example below should illustrate the kind of setup we have:
[UseCache]
public class ControllerA : Controller
{
public ControllerA(IBuilder builder)
{
// Should resolve an IBuilder with a CacheService
}
}
public class ControllerB : Controller
{
public ControllerB(IBuilder builder)
{
// Should resolve an IBuilder with a NullCacheService
}
}
public class Builder : IBuilder
{
public Builder(ICacheService cacheService)
{
// The type of the resolved ICacheService depends on the UseCache
// attribute on any of the object that depends on this IBuilder
}
}
public class CacheService : ICacheService
{
public Object Get(string key, Func<Object> getValue)
{
// Check if the value is cached against Key and return it if it's not
// Obviously needs a lot more here regarding caching timeframes, expiry etc
}
}
public class NullCacheService : ICacheService
{
public Object Get(string key, Func<Object> getValue)
{
// Don't do anything with key, just do the work in getValue and return it
}
}
public class UseCacheAttribute : Attribute
{
}
I know Autofac can deal with resolving dependencies using attributes but
The Autofac.Extras.AttributeMetadata package is not support in ASP.NET Core MVC
Even if it were supported, I can't see how it would support attribute detection on the objects that contain this one.
I'm happy to introduce a new IoC framework, we're not tied to Autofac or the default IoC implemention.
Is what I'm trying to achieve possible? What would be considered a better caching solution?
I'm happy to introduce a new IoC framework, we're not tied to Autofac or the default IoC implemention.
I'm not that familiar with Autofac, but I am familiar with Simple Injector, so I can show you how to apply such registration with Simple Injector:
var cache = new CacheService();
container.RegisterConditional(typeof(IBuilder),
Lifestyle.Transient.CreateRegistration<Builder>(
() => new Builder(cache),
container),
c => c.Consumer.ImplementationType.GetCustomAttribute<UseCacheAttribute>() != null);
container.RegisterConditional(typeof(IBuilder),
Lifestyle.Transient.CreateRegistration<Builder>(
() => new Builder(new NullCacheService()),
container),
c => !c.Handled);
This registration is a bit complicated because you wish to change the dependency of the Builder type based on the consumer of Builder. Lookup up the 'chain' up to the consumer of the consumer is something that Simple Injector does not support, because it can easily result in incorrect behavior, especially when the middle consumer has a lifestyle other than transient. That's the conditional registration are for IBuilder and not for ICacheService.
I'm currently working on a ASP.NET Core Project and want to use the built-in Dependency Injection (DI) functionality.
Well, I started with an interface:
ICar
{
string Drive();
}
and want to implement the ICar interface multiple times like
public class BMW : ICar
{
public string Drive(){...};
}
public class Jaguar : ICar
{
public string Drive(){...};
}
and add the following in the Startup class
public void ConfigureServices(IServiceCollection services)
{
// Add framework services.
services.AddMvc();
services.AddTransient<ICar, BMW>();
// or
services.AddTransient<ICar, Jaguar>();
}
Now I have to make a decision between two implementations and my decided class will set in every constructor that needs an ICar implementation. But my idea was to say, if the requested Controller is BMWController, then use BMW implementation or use Jaguar if the JaguarController is requested.
Otherwise DI don't make sense for me. How can i handle this issue properly?
For better understanding my problem take a look to this pic: https://media-www-asp.azureedge.net/media/44907/dependency-injection-golf.png?raw=true
How does the dependency resolver work and where can i set it up in ASP.NET Core?
In Unity it's possible to make something like this
container.RegisterType<IPerson, Male>("Male");
container.RegisterType<IPerson, Female>("Female");
and call the correct type like this
[Dependency("Male")]IPerson malePerson
The functionality you are looking for isn't easy to implement, at least when you are using it in the controller because controllers are treated a bit specially (By default, controllers aren't registered with ServiceCollection and hence not resolved/instantiated by the container and instead instantiated by ASP.NET Core during the request, see also the explanation and example on my related answer).
With built-in IoC container, you can only do it via factory method, here with an example on a BmwCarFactory class:
services.AddScoped<ICar, BmwCar>();
services.AddScoped<BmwCar>();
services.AddScoped<BmwCarFactory>(p => new BmwCarFactory(p.GetRequiredService<BmwCar>())));
The default IoC container is intentionally kept simple to provide basics of dependency injection to get you started and for other IoC containers to be able to easily plugin in there and replace the default implementation.
For more advanced scenarios the users are encouraged to use an IoC of their choice which supports more advanced features (assembly scan, decorators, conditional/parameterized dependencies, etc.
AutoFac (which I use in my projects) supports such advanced scenarios. In the AutoFac documentation there are 4 scenarios (altogether with the 3rd which #pwas suggested in the comments):
##1. Redesign your classes
Needs some additional overhead of refactoring your code and class hierarchy but heavily simplifies the consumption of injected services
##2. Change the registrations
The docs describe it here, if you are unwilling or unable to change the code.
// Attach resolved parameters to override Autofac's
// lookup just on the ISender parameters.
builder.RegisterType<ShippingProcessor>()
.WithParameter(
new ResolvedParameter(
(pi, ctx) => pi.ParameterType == typeof(ISender),
(pi, ctx) => ctx.Resolve<PostalServiceSender>()));
builder.RegisterType<CustomerNotifier>();
.WithParameter(
new ResolvedParameter(
(pi, ctx) => pi.ParameterType == typeof(ISender),
(pi, ctx) => ctx.Resolve<EmailNotifier>()));
var container = builder.Build();
##3. Using keyed services (here)
It is pretty similar to the previous approach to 2. but resolves the services based on a key, rather than their concrete type
##4. Use Metadata
This is quite similar to 3. but you define the keys via attribute.
Other containers like Unity have special attributes, like DependencyAttribute which you can use to annotate the dependency, like
public class BmwController : Controller
{
public BmwController([Dependency("Bmw")ICar car)
{
}
}
But this and the 4th option of Autofac make the IoC container leak into your services and you should consider the other approaches.
Alternatively you create classes and factories which resolve your services based on some conventions. For example a ICarFactory:
public ICarFactory
{
ICar Create(string carType);
}
public CarFactory : ICarFactory
{
public IServiceProvider provider;
public CarFactory(IServiceProvider provider)
{
this.provider = provider;
}
public ICar Create(string carType)
{
if(type==null)
throw new ArgumentNullException(nameof(carType));
var fullQualifedName = $"MyProject.Business.Models.Cars.{carType}Car";
Type carType = Type.GetType(fullQualifedName);
if(carType==null)
throw new InvalidOperationException($"'{carType}' is not a valid car type.");
ICar car = provider.GetService(carType);
if(car==null)
throw new InvalidOperationException($"Can't resolve '{carType.Fullname}'. Make sure it's registered with the IoC container.");
return car;
}
}
Then use it like
public class BmwController : Controller
{
public ICarFactory carFactory;
public BmwController(ICarFactory carFactory)
{
this.carFactory = carFactory;
// Get the car
ICar bmw = carFactory.Create("Bmw");
}
}
##Alternative to IServiceProvider
// alternatively inject IEnumerable<ICar>
public CarFactory : ICarFactory
{
public IEnumerable<ICar> cars;
public CarFactory(IEnumerable<ICar> cars)
{
this.cars = cars;
}
public ICar Create(string carType)
{
if(type==null)
throw new ArgumentNullException(nameof(carType));
var carName = "${carType}Car";
var car = cars.Where(c => c.GetType().Name == carName).SingleOrDefault();
if(car==null)
throw new InvalidOperationException($"Can't resolve '{carName}.'. Make sure it's registered with the IoC container.");
return car;
}
}
I want to implement dependency injection in ASP.NET CORE 1. I know everything is about DI in .Net Core. For example
public void ConfigureServices(IServiceCollection services)
{
// Add application services.
services.AddTransient<IDateTime, SystemDateTime>();
}
But for Big projects which has more than 20 entities and Services, it is so difficult and unreadable writing all of these code lines inside ConfigureServices. I want to know Is this possible implement dependency injection outside of Startup.cs and then add it to services.
Thanks for answers.
you can write extension methods of IServiceCollection to encapsulate a lot of service registrations into 1 line of code in Startup.cs
for example here is one from my project:
using cloudscribe.Core.Models;
using cloudscribe.Core.Models.Setup;
using cloudscribe.Core.Web;
using cloudscribe.Core.Web.Components;
using cloudscribe.Core.Web.Components.Editor;
using cloudscribe.Core.Web.Components.Messaging;
using cloudscribe.Core.Web.Navigation;
using cloudscribe.Web.Common.Razor;
using cloudscribe.Web.Navigation;
using cloudscribe.Web.Navigation.Caching;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Razor;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.Options;
using System.Reflection;
using Microsoft.AspNetCore.Authorization;
namespace Microsoft.Extensions.DependencyInjection
{
public static class StartupExtensions
{
public static IServiceCollection AddCloudscribeCore(this IServiceCollection services, IConfigurationRoot configuration)
{
services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.Configure<MultiTenantOptions>(configuration.GetSection("MultiTenantOptions"));
services.Configure<SiteConfigOptions>(configuration.GetSection("SiteConfigOptions"));
services.Configure<UIOptions>(configuration.GetSection("UIOptions"));
services.Configure<CkeditorOptions>(configuration.GetSection("CkeditorOptions"));
services.Configure<CachingSiteResolverOptions>(configuration.GetSection("CachingSiteResolverOptions"));
services.AddMultitenancy<SiteContext, CachingSiteResolver>();
services.AddScoped<CacheHelper, CacheHelper>();
services.AddScoped<SiteManager, SiteManager>();
services.AddScoped<GeoDataManager, GeoDataManager>();
services.AddScoped<SystemInfoManager, SystemInfoManager>();
services.AddScoped<IpAddressTracker, IpAddressTracker>();
services.AddScoped<SiteDataProtector>();
services.AddCloudscribeCommmon();
services.AddScoped<ITimeZoneIdResolver, RequestTimeZoneIdResolver>();
services.AddCloudscribePagination();
services.AddScoped<IVersionProviderFactory, VersionProviderFactory>();
services.AddScoped<IVersionProvider, CloudscribeCoreVersionProvider>();
services.AddTransient<ISiteMessageEmailSender, SiteEmailMessageSender>();
services.AddTransient<ISmsSender, SiteSmsSender>();
services.AddSingleton<IThemeListBuilder, SiteThemeListBuilder>();
services.TryAddScoped<ViewRenderer, ViewRenderer>();
services.AddSingleton<IOptions<NavigationOptions>, SiteNavigationOptionsResolver>();
services.AddScoped<ITreeCacheKeyResolver, SiteNavigationCacheKeyResolver>();
services.AddScoped<INodeUrlPrefixProvider, FolderTenantNodeUrlPrefixProvider>();
services.AddCloudscribeNavigation(configuration);
services.AddCloudscribeIdentity();
return services;
}
}
}
and in Startup.cs I call that method with one line of code
services.AddCloudscribeCore(Configuration);
There are several approaches that can be taken, but some are simply moving code between classes; I suggest you consider Assembly Scanning as I describe as the second option below:
1. 'MOVE THE PROBLEM': EXTENSION METHODS
The initial option is to use extension methods for configuration of Services.
Here is one example that wraps multiple service reigstrations into one extension method:
public static IServiceCollection AddCustomServices(this IServiceCollection services)
{
services.AddScoped<IBrowserConfigService, BrowserConfigService>();
services.AddScoped<IManifestService, ManifestService>();
services.AddScoped<IRobotsService, RobotsService>();
services.AddScoped<ISitemapService, SitemapService>();
services.AddScoped<ISitemapPingerService, SitemapPingerService>();
// Add your own custom services here e.g.
// Singleton - Only one instance is ever created and returned.
services.AddSingleton<IExampleService, ExampleService>();
// Scoped - A new instance is created and returned for each request/response cycle.
services.AddScoped<IExampleService, ExampleService>();
// Transient - A new instance is created and returned each time.
services.AddTransient<IExampleService, ExampleService>();
return services;
}
This can be called within ConfigureServices:
services.AddCustomServices();
Note: This is useful as a 'builder pattern', for specific configurations (for example, when a service needs multiple options to be passed to it), but, does not solve the problem of having to register multiple services by hand coding; it is essentially no different to writing the same code but in a different class file, and it still needs manual maintenance.
2. 'SOLVE THE PROBLEM': ASSEMBLY SCANNING
The 'best practice' option is Assembly Scanning which is used to automatically find and Register components based on their Implemented Interfaces; below is an Autofac example:
var assembly= Assembly.GetExecutingAssembly();
builder.RegisterAssemblyTypes(assembly)
.Where(t => t.Name.EndsWith("Repository"))
.AsImplementedInterfaces();
One trick to handle lifetime (or scope) of registration, is to use a marker interface (an empty interface), for example IScopedService, and use that to scan for and register services with the appropriate lifetime. This is the lowest friction approach to registering multiple services, which is automatic, and therefore 'zero maintenance'.
Note: The built in ASP.Net Core DI implementation does not support Assembly Scanning (as pf current, 2016 release); however, the Scrutor project on Github (and Nuget) adds this functionality, which condenses Service and Type registration to:
var collection = new ServiceCollection();
collection.Scan(scan => scan
.FromAssemblyOf<ITransientService>()
.AddClasses(classes => classes.AssignableTo<ITransientService>())
.AsImplementedInterfaces()
.WithTransientLifetime()
.AddClasses(classes => classes.AssignableTo<IScopedService>())
.As<IScopedService>()
.WithScopedLifetime());
SUMMARY:
Assembly Scanning, in combination with Extension Methods (where applicable) will save you a considerable amount of maintenance, and is performed once at application startup, and subsequently cached. It obviates the need to hand code service registrations.
You can write an extension method for batch registration:
public static void AddScopedFromAssembly(this IServiceCollection services, Assembly assembly)
{
var allServices = assembly.GetTypes().Where(p =>
p.GetTypeInfo().IsClass &&
!p.GetTypeInfo().IsAbstract);
foreach (var type in allServices)
{
var allInterfaces = type.GetInterfaces();
var mainInterfaces = allInterfaces.Except
(allInterfaces.SelectMany(t => t.GetInterfaces()));
foreach (var itype in mainInterfaces)
{
services.AddScoped(itype, type); // if you want you can pass lifetime as a parameter
}
}
}
And usage:
services.AddScopedFromAssembly(assembly);
Add DependenciesManager class to your project and implement AddApplicationRepositories method.
public static class DependenciesManager
{
public static void AddApplicationRepositories(this IServiceCollection service)
{
var assembly = Assembly.GetExecutingAssembly();
var services = assembly.GetTypes().Where(type =>
type.GetTypeInfo().IsClass && type.Name.EndsWith("Repository") &&
!type.GetTypeInfo().IsAbstract);
foreach (var serviceType in services)
{
var allInterfaces = serviceType.GetInterfaces();
var mainInterfaces = allInterfaces.Except
(allInterfaces.SelectMany(t => t.GetInterfaces()));
foreach (var iServiceType in mainInterfaces)
{
service.AddScoped(iServiceType, serviceType);
}
}
}
}
In Startup class add services.AddApplicationRepositories(); in ConfigureServices method.
public void ConfigureServices(IServiceCollection services)
{
services.AddApplicationRepositories();
}
In case you need to register different services, just implement more methods in DependenciesManager class. For example, if you need to register some Authorization Handler services, just implement AddAuthorizationHandlers method:
public static void AddAuthorizationHandlers(this IServiceCollection service)
{
var assembly = Assembly.GetExecutingAssembly();
var services = assembly.GetTypes().Where(type =>
type.GetTypeInfo().IsClass && type.Name.EndsWith("Handler") &&
!type.GetTypeInfo().IsAbstract);
foreach (var serviceType in services)
{
var allInterfaces = serviceType.GetInterfaces();
var mainInterfaces = allInterfaces.Except
(allInterfaces.SelectMany(t => t.GetInterfaces()));
foreach (var iServiceType in mainInterfaces)
{
service.AddScoped(iServiceType, serviceType);
}
}
}
And in Startup class add:
services.AddAuthorizationHandlers();
Notes: the names of the services and its implementation you want to register must end with "Repository" or "Handler" according to my answer.
I recently implemented the Assembly scanning approach (successfully), but in the end found the cluster_registrations_in_a_few_extension_methods approach a lot clearer to read for myself and for other programmers working on it.
If you keep the clustering of registrations close to where the registered classes are defined, maintenance is always a lot less work than the maintenance involved with the registered classes themselves.
Struggling a little on how to use automapper in my project class libraries (dlls). See my structure of my overall solution below.
The WebApp fires up, and in Global.asax App Start, the AutoMapper.Configure() method is called to add the mapping profiles. For now I am just adding the Services.AutoMapperViewModelProfile. But I need to somehow account for the profiles in each of the WebStoreAdapters (BigCommerce and Shopify in the example below). I was hoping not to add references to each WebStoreAdapter in WebApp, just for the sake of being able to add the profiles during the AutoMapperConfig. If I add another call to AutoMapper.Initialize in WebStoreFactory, it overrides the one in WebApp.
Is there another way that I am missing or totally off base here in some other way?
WebApp
- AutoMapperConfig
- AddProfile Services.AutoMapperViewModelProfile
Services.dll
- AutoMapperViewModelProfile
Scheduler.dll (uses HangFire to execute cron jobs to get data from shop carts. Its UI is accessed via the WebApp)
WebStoreAdapter.dll
-WebStoreFactory
BigCommerceAdapter.dll
- AutoMapperBigCommerceDTOProfile
ShopifyAdapter.dll
- AutoMapperShopifyDTOProfile
Initializing as called from Global.asax:
public static class AutoMapperConfiguration
{
public static void Configure()
{
Mapper.Initialize(am =>
{
am.AddProfile<AutoMapperViewModelProfile>();
});
}
}
Profile:
public class AutoMapperViewModelProfile : Profile
{
public override string ProfileName
{
get { return this.GetType().ToString(); }
}
protected override void Configure()
{
CreateMap<InventoryContainerHeader, InventoryContainerLabelPrintZPLViewModel>()
.ForMember(vm => vm.StatusDescription, opt => opt.MapFrom(entity => entity.InventoryContainerStatus.DisplayText))
.ForMember(dest => dest.ContainerDetails, option => option.Ignore())
;
...
}
}
One way to do this is to use reflection to load up all profiles:
var assembliesToScan = AppDomain.CurrentDomain.GetAssemblies();
var allTypes = assembliesToScan.SelectMany(a => a.ExportedTypes).ToArray();
var profiles =
allTypes
.Where(t => typeof(Profile).GetTypeInfo().IsAssignableFrom(t.GetTypeInfo()))
.Where(t => !t.GetTypeInfo().IsAbstract);
Mapper.Initialize(cfg =>
{
foreach (var profile in profiles)
{
cfg.AddProfile(profile);
}
});
You don't directly reference any one Automapper profile, but just load all Profile's from the current AppDomain.
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();
}
}