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
--
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 an asp.net core 3.1 web application
I have an Interface which is implemented by 3 classes to configure database mapping. I want to call the method automatically during application configuration setup.
Following is my interface and their implementation.
public interface IMongoMapper
{
void Configure();
}
class TenantMap : IMongoMapper
{
public void Configure()
{
BsonClassMap.RegisterClassMap<Entities.Tenant>(cm =>
{
cm.AutoMap();
});
}
}
class CourseMap : IMongoMapper
{
public void Configure()
{
BsonClassMap.RegisterClassMap<Course>(cm =>
{
cm.AutoMap();
});
}
}
How to get all the classes that implement interface and call Configure
method appropriately?
You can use scope.ServiceProvider.GetServices<IMongoMapper>(); to get all classes that implement IMongoMapper interface.
You can use an extension method and call it in Configure method in startup class.
public static void IntializeMapping(this IApplicationBuilder app)
{
using (var scope = app.ApplicationServices.GetRequiredService<IServiceScopeFactory>().CreateScope())
{
var mappers = scope.ServiceProvider.GetServices<IMongoMapper>();
foreach (var map in mappers)
{
map.Configure();
}
}
}
and use it in startup class
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.IntializeMapping();
}
Update
According to Microsoft documentation better way is use this
public static async Task Main(string[] args)
{
var host = CreateHostBuilder(args).Build();
using (var scope = host.Services.CreateScope())
{
try
{
var mappers = scope.ServiceProvider.GetServices<IMongoMapper>();
foreach (var map in mappers)
{
map.Configure();
}
}
catch (Exception ex)
{
var logger = service.GetService<ILogger<Program>>();
logger.LogError(ex, "An error occurred mapping");
}
}
await host.RunAsync();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
Microsoft Documentation
In older tutorials, you may see similar code in the Configure method in Startup.cs. We recommend that you use the Configure method only to set up the request pipeline. Application startup code belongs in the Main method.
Now the first time you run the application, the database will be created and seeded with test data. Whenever you change your data model, you can delete the database, update your seed method, and start afresh with a new database the same way. In later tutorials, you'll see how to modify the database when the data model changes, without deleting and re-creating it.
Assuming that you have empty constructor for the derived classes as mentioned in your example,you can do the below code you can get the interface by reflection and check which type can be assignable and !c.IsInterface so as it doesn't return the interface itself:
var result = typeof("Any Class").Assembly.GetTypes().Where(c => typeof(IMongoMapper).IsAssignableFrom(c) && !c.IsInterface);
foreach (var i in result)
{
((IMongoMapper)Activator.CreateInstance(i)).Configure();
}
You can do it via Reflection.
I just tried the code below and it works in dotnet core 3.1.
It works with the interface and the implementation class:
in the same assembly
in separate assemblies
var asm = Assembly.GetAssembly(typeof(YourClass));
var mapperTypes = asm.GetTypes().Where(x => x.GetInterface(nameof(IMongoMapper)) != null);
foreach(var mapperType in mapperTypes)
{
var mapper = (IMongoMapper)Activator.CreateInstance(mapperType);
mapper.Configure();
}
You can also plug any parameters you need to create an instance of your objects:
IConfiguration _configuration;
public Startup(IConfiguration configuration)
{
_configuration = configuration;
}
//other code
foreach(var mapperType in mapperTypes)
{
var mapper = (IMongoMapper)Activator.CreateInstance(mapperType, _configuration);
mapper.Configure();
}
There is also this question that has lots of examples (some do not work in dotnet core anymore): Getting all types that implement an interface
In Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped<IPersonals, Personals>();
}
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 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")))
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());
}