I have a question regarding dependency injection for AutoMapper in ASP NET Core. I know that there is automapper extension for DI available when implementing custom IValueResolver and IMemberValueResolver. This works only if the custom value resolvers are created by AutoMapper/DI. This unfortunately does not work if I need to created the value resolver manually. Also please note that I DON'T WANT to pass items when I call mapper.Map<>() method, because I don't want the consumer of IMapper to have any awareness of any extra parameters at runtime
Consider the code below:
class Entity1
{
public int MyProperty1 { get; set; }
}
class Dto1
{
public int MyProperty1 { get; set; }
public string CustomProperty { get; set; }
}
In MyProfile.cs
CreateMap<Entity1, Dto1>()
.ForMember(x => x.CustomProperty,
opt => opt.ResolveUsing(new MyCustomPropertyResolver("Important value")));
And MyCustomPropertyResolver is like this:
public class MyCustomPropertyResolver : IValueResolver<Entity1, Dto1, string>
{
string _someValue;
public MyCustomPropertyResolver(string someValue)
{
_someValue = someValue;
}
string Resolve(Entity1 source, Dto1 destination, string destMember, ResolutionContext context)
{
//I need IHttpContextAccessor ..... How can I do that???
}
}
Of course, If I did this:
CreateMap<Entity1, Dto1>()
.ForMember(x => x.CustomProperty,
opt => opt.ResolveUsing<MyCustomPropertyResolver>()));
I can use DI and add IHttpContextAccessor to MyCustomPropertyResolver constructor, but then I won't be able to pass any extra params to the resolver that are also important to resolve the actual value. Is there a way to achieve this? The only way I was able to achieve this is by adding a static property on MyProfile class and set it via ActionFilter on the controller serving the request. Although this work but I don't like this solution because it creates unwanted dependencies. The official solution on AutoMapper is to do this:
// This solution is not good enough for my need
var dto = mapper.Map<Dto1>(entity, opt => { opt.Items["AnyThing"] = Whatever; });
With the above code I can pass HttpContext to the items dictionary, but this will make the mapper consumer required to pass in parameters that might not be aware of.
My code in reality is much more complex but it is conceptually using the same example above.
AutoMapper.Extensions.Microsoft.DependencyInjection will automatically register any services that inherit from IValueResolver (and some other interfaces) with transient scope.
For this reason you need to ensure your custom resolver has a default consructor (otherwise, if your only constructor takes a string argument, you will get at error when AutoMapper attempts to automatically register your custom resolver).
public MyCustomPropertyResolver()
{
}
You can then create a map using an actual instance that calls your non default constructor (with the string argument):
CreateMap<Source, Destination>()
.ForMember(x => x.CustomProperty,
opt => opt.MapFrom(new MyCustomPropertyResolver("Important value")));
Unfortunately, as you are not using DI to instantiate the custom resolver, DI will not be able to inject other services directly into the custom resolver. The work around is to inject these service into the resolver's consumer e.g. the controller, and pass them into the resolver through the Items collection:
_mapper.Map<Destination>(source, opts => { opts.Items["MyService"] = _myServiceInstance; });
EDIT 1:
For a more elegent solution where your custom resolver can avail of full dependency injection and also take a string argument in the constructor, consider the following:
Your custom resolver may look something like this:
public class MyCustomPropertyResolver : IValueResolver<Source, Destination, string>
{
private readonly MyService _myService;
// plus others you need/have registered
// still need at least one constructor without the string!
public MyCustomPropertyResolver(MyService _myService)
{
_myService = myService;
}
public MyCustomPropertyResolver(MyService myService, string fieldName)
{
_myService = myService;
// do something important with fieldName
}
// rest excluded
}
Create a factory class for your custom resolver:
public class MyCustomPropertyResolverFactory
{
static internal IServiceProvider _provider;
public static void Configure(IServiceProvider serviceProvider)
{
_provider = serviceProvider;
}
public static MyCustomPropertyResolver CreateResolverFor(string importantValue)
{
return ActivatorUtilities.CreateInstance<MyCustomPropertyResolver>(_provider, importantValue);
}
}
Configure it in Startup.cs
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
MyCustomPropertyResolverFactory.Configure(app.ApplicationServices);
// rest excluded
}
Use it in your map
CreateMap<Source, Destination>()
.ForMember(x => x.CustomProperty,
opt => opt.MapFrom(MyCustomPropertyResolverFactory.CreateResolverFor("Important value")));
Of course you could make the factory completely generic:
public class MyFactory
{
static internal IServiceProvider _provider;
public static void Configure(IServiceProvider serviceProvider)
{
_provider = serviceProvider;
}
public static T Create<T>(params object[] parameters)
{
return ActivatorUtilities.CreateInstance<T>(_provider, parameters);
}
}
and use thus:
.ForMember(d => d.MyProperty, opt => opt.MapFrom(MyFactory.Create<MyResolver>("some string or any number of other parameters")))
Related
class Person : IParticipant {}
class Doctor: Person {}
class RandomParticipantGenerator : IParticipantGenerator{
enum PersonType
{
Person,
Doctor
}
public IParticipant GetParticipant(PersonType type, State state)
{
}
public List<IParticipant> GetParticipants(PersonType type, State state, int numOfPeople)
{
}
}
I have these classes in my program, I need to be able to produce Person/Doctor with the GetParticipant method. It should return a Docter When the PersonType enum is Doctor a new Person when the provided type is equal to Person.
I am using a simple factory method but I want to implement this using Autofac.
I would love to get some help on the matter.
If you want to use Autofac in ASP.NET Core, firstly you should configure the CreateHostBuilder method to use "Autofac" as IoC Container.
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.UseServiceProviderFactory(new AutofacServiceProviderFactory())
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
Then you should add ConfigureContainer method in the Startup class to provide a custom configuration to the container builder as follow:
public void ConfigureContainer(ContainerBuilder builder)
{
builder.Register<IParticipant>((context, parameter) =>
{
var type = parameter.TypedAs<PersonType>();
return type switch
{
PersonType.Person => new Person(),
PersonType.Doctor => new Doctor(),
_ => new Person(),// Or throw an exception
};
}).As<IParticipant>();
}
Finally use Func<PersonType, IParticipant> instead of IParticipant for _participantFactory declaration and inject it like this:
public class SampleController : Controller
{
private readonly Func<PersonType, IParticipant> _participantFactory;
public SampleController (Func<PersonType, IParticipant> participantFactory)
{
_participantFactory = participantFactory;
}
[HttpGet]
public IActionResult Get()
{
var participant = _participantFactory(PersonType.Person);
return Ok(participant );
}
}
I have a class
public TimeSeriesBusinessComponent(IContextRepository contextRepository) { ...
I pass the dependency injection through my class ServiceExtension, which I call from my Startup.cs.
Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
services.AddRepositories(Configurations);
services.AddBusinessComponents();
ServiceExtension:
public static void AddRepositories(this IServiceCollection serviceCollection, Configurations con)
{
serviceCollection.AddScoped<IContextRepository>(serviceProvider =>
{
string createContextFunctionName = con.Get(CREATE_CONTEXT_FUNCTION_NAME);
return new ContextRepository(createContextFunctionName);
});
}
public static void AddBusinessComponents(this IServiceCollection serviceCollection)
{
serviceCollection.AddScoped<ITimeSeriesBusinessComponent, TimeSeriesBusinessComponent>();
}
Later I use my Class TimeSeriesBusinessComponent like that:
public class TimeSeriesController : Controller
{
private ITimeSeriesBusinessComponent BusinessComponent { get; }
public TimeSeriesController(
ITimeSeriesBusinessComponent businessComponent
)
{
BusinessComponent = businessComponent;
}
But now I need to transfer a argument from my ServicExtension to my TimeSeriesBusinessComponent too, like ..
public TimeSeriesBusinessComponent(IContextRepository contextRepository,. string value) { ...
The string value I get from my configurations (EnvirenmentVariables).
How can I use dependency injection and normal argument in the same time?
So, since your string value comes from Environment variable, you should use IOptions.
You create your MyOptions class (that will hold a value you need)
You load a value from your environment (or from wherever) in your Startup.cs
You register your options with DI container
You inject it into target class just like any other service
for example:
services.Configure<MyOptions>(options =>
{
options.MyString = Environment.GetEnvironmentVariable("MY_STRING_KEY");
});
and then you use it like:
public TimeSeriesBusinessComponent(IContextRepository contextRepository, IOptions<MyOptions> myOptions) {
_myOptionsOptions = options.MyString;
}
docs: https://learn.microsoft.com/en-us/aspnet/core/fundamentals/configuration/options?view=aspnetcore-2.2
I need the ASP.Net Core dependency injection to pass some parameters to the constructor of my GlobalRepository class which implements the ICardPaymentRepository interface.
The parameters are for configuration and come from the config file and the database, and I don't want my class to go and reference the database and config itself.
I think the factory pattern is the best way to do this but I can't figure out the best way to use a factory class which itself has dependencies on config and database.
My startup looks like this currently:
public class Startup
{
public IConfiguration _configuration { get; }
public IHostingEnvironment _environment { get; }
public Startup(IConfiguration configuration, IHostingEnvironment environment)
{
_configuration = configuration;
_environment = environment;
}
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped<IDbRepository, DbRepository>();
var connection = _configuration.GetConnectionString("DbConnection");
services.Configure<ConnectionStrings>(_configuration.GetSection("ConnectionStrings"));
services.AddDbContext<DbContext>(options => options.UseSqlServer(connection));
services.AddScoped<ICardPaymentRepository, GlobalRepository>();
...
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory, IRFDbRepository rFDbRepository)
{
...
}
}
The GlobalRepository constructor looks like this:
public GlobalRepository(string mode, string apiKey)
{
}
How do I now pass the mode from configuration and the apiKey from the DbRepository into the constructor from Startup?
Use the factory delegate overload when registering the repository
//...
string mode = "get value from config";
services.AddScoped<ICardPaymentRepository, GlobalRepository>(sp => {
IDbRepository repo = sp.GetRequiredService<IDbRepository>();
string apiKey = repo.GetApiKeyMethodHere();
return new GlobalRepository(mode, apiKey);
});
//...
Alternative using ActivatorUtilities.CreateInstance
//...
string mode = "get value from config";
services.AddScoped<ICardPaymentRepository>(sp => {
IDbRepository repo = sp.GetRequiredService<IDbRepository>();
string apiKey = repo.GetApiKeyMethodHere();
return ActivatorUtilities.CreateInstance<GlobalRepository>(sp, mode, apiKey);
});
//...
You might want to also check these links...
https://github.com/Microsoft/AspNetCoreInjection.TypedFactories
https://espressocoder.com/2018/10/08/injecting-a-factory-service-in-asp-net-core/
With regard to the last link the code is basically:
public class Factory<T> : IFactory<T>
{
private readonly Func<T> _initFunc;
public Factory(Func<T> initFunc)
{
_initFunc = initFunc;
}
public T Create()
{
return _initFunc();
}
}
public static class ServiceCollectionExtensions
{
public static void AddFactory<TService, TImplementation>(this IServiceCollection services)
where TService : class
where TImplementation : class, TService
{
services.AddTransient<TService, TImplementation>();
services.AddSingleton<Func<TService>>(x => () => x.GetService<TService>());
services.AddSingleton<IFactory<TService>, Factory<TService>>();
}
}
I think castle windsor's typed factories dispose of all they created when they themselves are disposed (which may not be always the best idea), with these links you would probably have to consider if you are still expecting that behaviour. When I reconsidered why I wanted a factory I ended up just creating a simple factory wrapping new, such as:
public class DefaultFooFactory: IFooFactory{
public IFoo create(){return new DefaultFoo();}
}
I'll show the minimal example for the factory that resolves ITalk implementation by a string key. The solution can be easily extended to a generic factory with any key and entity type.
For the sake of example let's define the interface ITalk and two implementations Cat and Dog:
public interface ITalk
{
string Talk();
}
public class Cat : ITalk
{
public string Talk() => "Meow!";
}
public class Dog : ITalk
{
public string Talk() => "Woof!";
}
Now define the TalkFactoryOptions and TalkFactory:
public class TalkFactoryOptions
{
public IDictionary<string, Type> Types { get; } = new Dictionary<string, Type>();
public void Register<T>(string name) where T : ITalk
{
Types.Add(name, typeof(T));
}
}
public class TalkFactory
{
private readonly IServiceProvider _provider;
private readonly IDictionary<string, Type> _types;
public TalkFactory(IServiceProvider provider, IOptions<TalkFactoryOptions> options)
{
_provider = provider;
_types = options.Value.Types;
}
public ITalk Resolve(string name)
{
if (_types.TryGetValue(name, out var type))
{
return (ITalk)_provider.GetRequiredService(type);
}
throw new ArgumentOutOfRangeException(nameof(name));
}
}
Add extension method for simple implementations registration:
public static class FactoryDiExtensions
{
public static IServiceCollection RegisterTransientSpeaker<TImplementation>(this IServiceCollection services, string name)
where TImplementation : class, ITalk
{
services.TryAddTransient<TalkFactory>();
services.TryAddTransient<TImplementation>();
services.Configure<TalkFactoryOptions>(options => options.Register<TImplementation>(name));
return services;
}
}
And register the Cat and Dog implementations:
services
.RegisterTransientSpeaker<Cat>("cat")
.RegisterTransientSpeaker<Dog>("dog");
Now you can inject the TalkFactory and resolve the implementation by the name:
var speaker = _factory.Resolve("cat");
var speech = speaker.Talk();
The trick here is Configure<TOptions(). This method is additive, which means you can call it multiple times to configure the same instance of TalkFactoryOptions.
As I said this example can be converted into a generic factory and add the ability to register factory delegate instead of a concrete type. But the code will be too long for SO.
I've been running up against the same issue and solved this by registering a set of open generics for IFactory<TService>, IFactory<T, TService>, IFactory<T1, T2, TService> etc. A single call on startup to add this facility then allows any IFactory<...> to be injected / resolved, which will instantiate an instance of TService for a given set of argument types, provided a constuctor exists whose last parameters match the T* types of the factory generic. Source code, NuGet package and explanatory blog article below:
https://github.com/jmg48/useful
https://www.nuget.org/packages/Ariadne.Extensions.ServiceCollection/
https://jon-glass.medium.com/abstract-factory-support-for-microsoft-net-dependency-injection-3c3834894c19
An alternative to the other answers. Follow the options pattern.
First introduce a strong type for your configuration;
public class RespositoryOptions {
public string Mode { get; set; }
public string ApiKey { get; set; }
}
public GlobalRepository(IOptions<RespositoryOptions> options) {
// use options.Value;
}
You could still use a service factory method to unwrap the IOptions<RespositoryOptions> if you prefer. But then you lose the ability to verify that your service dependencies have all been met.
Then you can seed your options from configuration;
public void ConfigureServices(IServiceCollection services) {
...
services.Configure<RespositoryOptions>(_configuration.GetSection(name));
...
}
And write another service to update that options instance from other services, like a database;
public class ConfigureRespositoryOptions : IConfigureOptions<RespositoryOptions> {
private readonly IDbRepository repo;
public ConfigureRespositoryOptions(IDbRepository repo) {
this.repo = repo;
}
public void Configure(RespositoryOptions config) {
string apiKey = repo.GetApiKeyMethodHere();
}
}
I have asp.net core application. I want to use IOptions pattern to inject values from appsettings.json. So I have a class SecurityHeaderOptions, and also have target class SecurityHeadersBuilder whose constructor takes IOptions<SecurityHeaderOptions> as parameter.
I know that .net core can implicitly create instance of SecurityHeadersBuilder by injecting IOptions<SecurityHeaderOptions> after registering both with container.
However i want to explicitly create instance of SecurityHeadersBuilder, call one of its method and then register the instance with the container.
public sealed class SecurityHeaderOptions
{
public string FrameOption { get; set; }
public string XssProtection { get; set; }
}
public class SecurityHeadersBuilder
{
private readonly SecurityHeaderOptions _options = null;
public SecurityHeadersBuilder(IOptions<SecurityHeaderOptions> options)
{
_options = options.Value;
}
public SecurityHeadersBuilder AddDefaultPolicy()
{
AddFrameOptions();
AddConetntSecurityPolicy();
return this;
}
}
ConfigureServices method
public void ConfigureServices(IServiceCollection services)
{
services.Configure<SecurityHeaderOptions>(Configuration.GetSection("SecurityHeaderOptions"));
services.AddScoped<SecurityHeadersBuilder>(provider =>
new SecurityHeadersBuilder(?????).AddDefaultPolicy())
}
Questions
1> If i am explicitly passing options into constructor, do i need to register SecurityHeaderOptions with the container using service.Configure method?
2> Configuration.GetSection("SecurityHeaderOptions") can't return instance of IOptions<SecurityHeaderOptions> , instead it returns IConfigurationSection?
3>Either way, how do I retrieve and pass SecurityHeaderOptions into SecurityHeadersBuilder's constructor?
Using .NET Core 2 and not having a provider available (or caring to add it) in ConfigureServices I opted to go with something like this (using OP code as example):
public void ConfigureServices(IServiceCollection services)
{
// secOpts available for use in ConfigureServices
var secOpts = Configuration
.GetSection("SecurityHeaderOptions")
.Get<SecurityHeaderOptions>();
...
}
This is how I register options and inject into SecurityHeadersBuilder
public void ConfigureServices(IServiceCollection services)
{
services.Configure<SecurityHeaderOptions>(Configuration.GetSection("SecurityHeaderOptions"));
services.AddScoped<SecurityHeadersBuilder>(provider =>
{
var option = provider.GetService<IOptions<SecurityHeaderOptions>>();
return new SecurityHeadersBuilder(option)
.AddDefaultPolicy();
});
}
Docs specifically say:
Don't use IOptions<TOptions> or IOptionsMonitor<TOptions> in Startup.ConfigureServices. An inconsistent options state may exist due to the ordering of service registrations.
So you'll have to access the configuration some other way from Startup.ConfigureServices, e.g. Quinton's answer
Firstly you need to add a second constructor to SecurityHeadersBuilder, that takes a plain SecurityHeaderOptions:
public class SecurityHeadersBuilder
{
private readonly SecurityHeaderOptions _options;
public SecurityHeadersBuilder(IOptions<SecurityHeaderOptions> options)
{
_options = options.Value;
}
public SecurityHeadersBuilder(SecurityHeaderOptions options)
{
_options = options;
}
public SecurityHeadersBuilder AddDefaultPolicy()
{
AddFrameOptions();
AddContentSecurityPolicy();
return this;
}
}
Then the answer entirely depends on whether or not you need to use those options outside of your Startup class.
If not, you can simply use the Microsoft.Extensions.Configuration.ConfigurationBinder.Get<T>() extension method:
services.AddScoped<SecurityHeadersBuilder>(provider =>
{
var options = Configuration.GetSection("SecurityHeaderOptions")
.Get<SecurityHeaderOptions>();
return new SecurityHeadersBuilder(options)
.AddDefaultPolicy();
});
(you can then delete the SecurityHeadersBuilder constructor that takes IOptions<SecurityHeaderOptions>).
If you will need to use these options elsewhere, then you can combine the above approach with the Microsoft.Extensions.DependencyInjection.OptionsConfigurationServiceCollectionExtensions.Configure() extension method:
var optionsSection = Configuration.GetSection("SecurityHeaderOptions");
services.Configure<SecurityHeaderOptions>(optionsSection);
services.AddScoped<SecurityHeadersBuilder>(provider =>
{
var options = optionsSection.Get<SecurityHeaderOptions>();
return new SecurityHeadersBuilder(options)
.AddDefaultPolicy();
});
Regarding your questions:
1. Yes, you need to register the options, but I believe you are doing it the wrong way (at least by your example). You should register as this:
services.Configure<SecurityHeaderOptions>(Configuration.GetSection("SecurityHeaderOptions"));
2. I believe that the correct registration I refer above returns what you are expecting.
3. Just registering it and placing it on the SecurityHeaderBuilder constructor is enough. You do not need (neither does the default .NET Core IOC container allows) to pass constructor parameters when registering it. For that you would need to use other IOC's such as Autofac.
But you need to register SecurityHeadersBuilder in order to use it within other classes.
Just use an interface for that.
public interface ISecurityHeadersBuilder
{
SecurityHeadersBuilder AddDefaultPolicy();
}
public class SecurityHeadersBuilder : ISecurityHeadersBuilder
{
private readonly SecurityHeaderOptions _options = null;
public SecurityHeadersBuilder(IOptions<SecurityHeaderOptions> options)
{
_options = options.Value;
}
public SecurityHeadersBuilder AddDefaultPolicy()
{
AddFrameOptions();
AddContentSecurityPolicy();
return this;
}
}
Then, just register it in startup.cs as this
services.AddScoped<ISecurityHeadersBuilder, SecurityHeadersBuilder>();
You could do something like this
public static class IConfigurationExtensions
{
public static TypedConfiguration<SecurityHeaderOptions> GetSecurityHeaderOptions(this IConfiguration configuration)
{
return new TypedConfiguration<SecurityHeaderOptions>(configuration.GetSection("SecurityHeaderOptions"));
}
}
public class TypedConfiguration<T> where T : class
{
public TypedConfiguration(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public T Value => Configuration.Get<T>();
public void InitializeOptions(IServiceCollection services)
{
services.Configure<T>(Configuration);
}
}
Now from single place you've created object that has both IConfiguration, typed SecurityHeaderOptions and helper method for registering IOptions injection for that class.
Use it like this
public void ConfigureServices(IServiceCollection services)
{
var wrappedOptions = Configuration.GetSecurityHeaderOptions();
wrappedOptions.InitializeOptions(services);
var options = Options.Create(wrappedOptions.Value);
services.AddScoped<SecurityHeadersBuilder>(provider =>
new SecurityHeadersBuilder(options).AddDefaultPolicy());
}
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
--