ResolutionContext creation customization in Automapper - c#

guys.
Maybe someone has the same problem.
I have some cached variables in the MemoryCache (standard non-distributed in-memory implementation of Microsoft.Extensions.Caching.Memory.IMemoryCache). So, I also have mappings I use for Response/DTO creation. Some of them use variables from MemoryCache. But now I must always pass it through
opts =>
{
opts.Items.Add(variableName1, variableValue1);
opts.Items.Add(variableName2, variableValue2);
...
}
or I need to pass each time MemoryCache the same way.
Is it possible to set up a global configuration of ResolutionContext which allows me to pass all variables from MemoryCache I need in the time of the ResolutionContext creation? Unfortunately, BeforeMap isn't a solution - It has no DI mechanism for IMemoryCache resolving. And as I know It can be only one in the mapping structure - Automapper skips all BeforeMap after the first one.
Thank you.

Instead of using the ResolutionContext, you can implement a custom IMemberValueResolver, which can get the IMemoryCache dependency injected.
By doing so, there's no need to seed theResolutionContext with key/value pairs (being copied from the IMemoryCache).
The FromMemoryCacheResolver below resolves the value for the requested cache key from the injected IMemoryCache.
public class FromMemoryCacheResolver<TDestMember>
: IMemberValueResolver<object, object, object, TDestMember>
{
private readonly IMemoryCache _memoryCache;
public FromMemoryCacheResolver(IMemoryCache memoryCache)
{
_memoryCache = memoryCache;
}
public TDestMember Resolve(
object source, object destination, object cacheKey, TDestMember destMember,
ResolutionContext context
)
{
if (_memoryCache.TryGetValue(cacheKey, out object value)
&& (value != null)
)
{
return (TDestMember)value;
}
return default(TDestMember);
}
}
Example
public class Source
{
public int Id { get; set; }
}
public class Target
{
public decimal DecimalValue { get; set; }
public string StringValue { get; set; }
}
Given the above Source and Target classes, you can define an AutoMapper mapping that sets a target property to the value bound to a fixed cache key (see DecimalValue rule) or a dynamic cache key (including a property value of the source object, see StringValue rule).
CreateMap<Source, Target>()
.ForMember(
o => o.DecimalValue,
opt => opt.MapFrom<FromMemoryCacheResolver<decimal>, object>(
_ => "constant-cache-key"
))
.ForMember(
o => o.StringValue,
opt => opt.MapFrom<FromMemoryCacheResolver<string>, object>(
src => $"dynamic-cache-key-{src.Id}"
));

You could override the way AutoMapper is registered in your dependency injection container and perform an action just before it is resolved. Assuming you use standard Microsoft's DI:
// Your code adding AutoMapper
services.AddAutoMapper(assembliesOrMarkerTypes);
// Remove just the IMapper
services.RemoveAll(typeof(IMapper));
// Add it again, but with filling the Items dictionary from cache
services.Add(new ServiceDescriptor(
typeof(IMapper),
sp =>
{
var memoryCache = sp.GetRequiredService<IMemoryCache>();
var valueFromCache = memoryCache.Get<string>("foo");
var mapper = new Mapper(sp.GetRequiredService<IConfigurationProvider>(), sp.GetService);
// Does not work!
// mapper.DefaultContext.Items.Add("foo", valueFromCache);
// Use Items from Options:
mapper.DefaultContext.Options.Items.Add("foo", valueFromCache);
return mapper;
},
ServiceLifetime.Transient)); // <== Default AutoMapper lifetime
There are two downsizes to this:
1) Access Items from resolution context's options, not directly
There is a check made when accessing Items in resolution context preventing from accessing them in a default context, which is a source for creating other contexts used in mapping. Luckily, there is no such check when accessing Items from options:
var items = resolutionContext.Options.Items;
So don't do that:
var items = resolutionContext.Items;
2) Don't use Map() with Action<IMappingOperationOptions>
You can't use any Map() method accepting Action<IMappingOperationOptions> because it will effectively overwrite content of Items dictionary created when initializing the mapper with entries from the mapping operation options, even if none were set. So, you can't do this:
var result = mapper.Map(source, destination, opts => otps.Items["bar"] = "bar");
Final note
Overall it's a bit of a hack and surely this code wouldn't win the beauty contest, so consider encapsulating it in some decent extension method for IServiceCollection.

Related

Automapper custom value resolver reuse for multiple types

I have a project which I am trying to use AutoMapper to map from multiple classes in each of these classes there are properties where I would like to use some custom logic to parse the source value to the destination.
I have tried to use custom resolver methods as documented on the AutoMapper docs.
Here is my code:
public class CustomDateTextHandler : IValueResolver<object, object, string>
{
public string Resolve(object source, object destination, string destMember, ResolutionContext context)
{
string txt = source.ToString();
txt.Replace("AM/PM", "tt");
txt.Replace("HH:MM", "hh:mm");
if (txt.Contains("format"))
{
txt.Replace("mmm", "MMM");
}
return txt;
}
}
public class SMapping : Profile
{
public SMapping()
{
CreateMap<SourceForm, s_form>()
.ForMember(dest => dest.id, opt => opt.Ignore())
.ForMember(dest => dest.cell_text, opt => opt.MapFrom<CustomDateTextHandler>())
.ForMember(dest => dest.fn_def, opt => opt.MapFrom<CustomCodeTextResolver>());
}
What I am trying to get is the cell_text value processed with my replace logic in the resolver method but the issue I am facing is that what is being passed to the resolver is the entire SMapping instance, I would like to be able to reuse the resolver code across different classes where the property names will be different, however looking at what it going on at the moment I could not really use the resolver code across my different classes.
Can someone help me?
Thank you in advance.
Use IMemberValueResolver instead of IValueResolver.
Compared to IValueResolver, its Resolve function gets one more parameter: value.
Registering mapping with IMemberValueResolver requires you to pass 1 extra parameter - not the 'value' directly, but a lambda that will produce a 'value' from given source object.
public class CustomDateTextHandler :
IMemberValueResolver< // note: different interface
object, object,
string, string // note: 1 more parameter
>
{
public string Resolve(
object source, object destination,
string sourceValue, string destMember, // note: 1 more parameter
ResolutionContext context
)
{
// here, see the difference:
// source - source object, whole
// sourceMember - value produced by extra lambda passed in mapping
}
}
public class SMapping : Profile
{
public SMapping()
{
CreateMap<SourceForm, s_form>()
...
.ForMember(
dest => dest.cell_text,
opt => opt.MapFrom<CustomDateTextHandler, string>(source => source.PROPERTY11)) // note: this produces that sourceValue
.ForMember(
dest => dest.fn_def,
opt => opt.MapFrom<CustomCodeTextResolver, string>(source => source.PROPERTY22)); // note: this produces that sourceValue
}

Can't access automapper context items after upgrade to 9

I have a mapper like this:
CreateMap<Source, ICollection<Dest>>()
.ConvertUsing((src, dst, context) =>
{
return context.Mapper.Map<ICollection<Dest>>
(new SourceItem[] { src.Item1, src.Item2 ... }.Where(item => SomeFilter(item)),
opts => opts.Items["SomethingFromSource"] = src.Something);
});
CreateMap<SourceItem, Dest>()
.ForMember(d => d.Something, opts => opts.MapFrom((src, dst, dstItem, context)
=> (string)context.Items["SomethingFromSource"]));
This gives me an exception saying You must use a Map overload that takes Action<IMappingOperationOptions>. Well, I do use the Map overload that takes this action. How else can I do this?
This is because of this change:
https://github.com/AutoMapper/AutoMapper/pull/3150
You can get the the Items by accessing the ResolutionContext's Options property.
Change context.Items["SomethingFromSource"] to context.Options.Items["SomethingFromSource"].
When there is no Items, the ResolutionContext is the same with DefaultContext. Therefore The ResolutionContext.Items property will throw the exception.
However, if there is, the ResolutionContext.Items wouldn't be the same with DefaultContext. Therefore the ResolutionContext.Items will return the list.
While ResolutionContext.Options.Items will always return the list, it would not throw any exception, whether it's empty or not. I believe it's what the error message meant, because ResolutionContext.Options is an IMappingOperationOptions
This extension can help with migrating to AutoMapper 12
public static class AutoMapperExtensions
{
public static TDestination MapOptions<TDestination>(this IMapper mapper, object source, Action<IMappingOperationOptions<object, TDestination>> OptionalOptions = null)
{
return mapper.Map(source, OptionalOptions ?? (_ => {}) );
}
}
In a Class where IMapper has been injected
public class Command
{
protected readonly IMapper AutoMapper;
public Command(IMapper mapper)
{
AutoMapper = mapper;
}
private SomethingToDo()
{
var list = new List<string>();
// change for 12
var result = AutoMapper.MapOptions<IList<Item>>(list);
// prior to 12
//var result = AutoMapper.Map<IList<Item>>(list);
}
}
Several points of consideration when you use inner mapper (i.e. context.Mapper)
First, try not to use context.Mapper.Map<TDestination>(...), use context.Mapper.Map<TSource, TDestination>(...) instead, it behaves much better.
Second, use of context in inner mappers will break encapsulation. If you need to set the values in inner objects, consider these two solutions:
In case you want to set the values after the inner map
context.Mapper.Map<Source, Dest> (source, opts => opts.AfterMap((s, d) =>
d.Something = source.Something))
In case you want to set the values before the inner map
context.Mapper.Map<Source, Dest> (source, new Dest()
{
Something = source.Something
})

Automapper initialize for T type

I want to have generic method to get data from database and pass model of how output data should look like.
I wrote simple method:
public IEnumerable<T> GetUsers<T>()
{
Mapper.Initialize(cfg =>
cfg.CreateMap<IQueryable<User>, IQueryable<T>>());
return OnConnect<IEnumerable<T>>(db =>
{
return db.Users.ProjectTo<T>().ToList();
});
}
Now I expected that I can do this:
var users = repo.GetUsers<UserViewModel>(); // it should be IEnumerable<UserViewModel>
var anotherUsers = repo.GetUsers<AnotherUserViewModel>(); // it should be IEnumerable<AnotherUserViewModel>
But I cant reinitialize automapper again. What should I do to make it working?
Initialize automapper only once per application startup.
You should know what types can be mapped from User already at the moment when you design a code in that case you can register all of them at a startup like this:
Mapper.Initialize(cfg => {
cfg.CreateMap<User, UserDto1>();
cfg.CreateMap<User, UserDto2>();
...
cfg.CreateMap<User, UserDtoN>();
});
Even if you will achieve it - it will not make a sense to try to map User to Order, but your architectural design will give that possibility
If you still want to do it(like I wrote in comments) - you can add somekind of marker attribute for Instance - MappableFrom(Type from), mark all DTO objects that can be used in scope of automapper. Then on initialization of your application - scan the assembly for all types that contains that attribute and register in Automapper.
You can use Profile to create all mappers follow this link http://docs.automapper.org/en/stable/Configuration.html
Another approach you can initialize in a static constructor all the mapping you want by using some naming convention
In the below code, I'm mapping from same object type to same object type
// Data or View Models
public class AddressViewModel : BaseViewModel
{
public string Address {get;set;}
public AddressViewModel()
{
this.Address ="Address";
}
}
public class UserViewModel : BaseViewModel
{
public string Name {get;set;}
public UserViewModel()
{
this.Name ="Name";
}
}
public class BaseViewModel
{
}
Repository -- here I'm using same view model you should create Models here
public class CrudRepo
{
public IEnumerable<T> GetData<T>() where T : class, new ()
{
var data = new List<T> { new T() };
return AutoMapper.Mapper.Map<IEnumerable<T>>(data);
}
}
Then in of the static constructor initialize the mappers
static HelperClass()
{
// In this case all classes are present in the current assembly
var items = Assembly.GetExecutingAssembly()
.GetTypes().Where(x =>
typeof(BaseViewModel)
.IsAssignableFrom(x))
.ToList();
AutoMapper.Mapper.Initialize(cfg =>
{
items.ForEach(x =>
{
// Here use some naming convention or attribute to find out the Source and Destination Type
//Or use a dictionary which gives you source and destination type
cfg.CreateMap(x, x);
});
});
}
Now you can create the instance of crud repository and get mapped items
var userRepo = new CrudRepo();
var users = userRepo.GetData<UserViewModel>();
var address = addressRepo.GetData<AddressViewModel>();
Note: As long as property names and types are same the data will be mapped else you have to create ForMember

How to use the new IValueResolver of AutoMapper?

I am at a loss as to how to use the new IValueResolver interface in the new version of AutoMapper. Perhaps I used them improperly in the previous versions of AutoMapper...
I have a lot of model classes, some of them are generated from several databases on several database servers, using sqlmetal.
Some of these classes has a string property, PublicationCode, which identifies which publication the subscription, or offer, or invoice, or whatever it is, belongs to.
The publication can exist in either of two systems (the old and the new system), hence I have a bool property on the destination model classes which tells whether the publication is in the old or the new system.
Using the old version (<5?) of AutoMapper, I used a ValueResolver<string, bool> which took the PublicationCode as an input parameter, and returned a bool indicating the location of the publication (old or new system).
With the new version (5+?) of AutoMapper, this seems to no longer be possible. The new IValueResolver requires a unique implementation of each and every combination of source and destination models that I have, where src.PublicationCode needs to be resolved into a dst.IsInNewSystem.
Am I just trying to use the value resolvers in the wrong way? Is there a better way? The main reason I would like to use a resolver is that I would prefer to have services injected into the constructor, and not having to use DependencyResolver and the like in the code (I'm using Autofac).
Currently, I use it in the following way:
// Class from Linq-to-SQL, non-related properties removed.
public class FindCustomerServiceSellOffers {
public string PublicationCode { get; set; }
}
This is one of several data model classes I have, which contains a PublicationCode property). This particular class is mapped to this view model:
public class SalesPitchViewModel {
public bool IsInNewSystem { get; set; }
}
The mapping definition for these two classes is (where expression is an IProfileExpression), non-related mappings removed:
expression.CreateMap<FindCustomerServiceSellOffers, SalesPitchViewModel>()
.ForMember(d => d.IsInNewSystem, o => o.ResolveUsing<PublicationSystemResolver>().FromMember(s => s.PublicationCode));
And the resolver:
public class PublicationSystemResolver : ValueResolver<string, bool>
{
private readonly PublicationService _publicationService;
public PublicationSystemResolver(PublicationService publicationService)
{
_publicationService = publicationService;
}
protected override bool ResolveCore(string publicationCode)
{
return _publicationService.IsInNewSystem(publicationCode);
}
}
And the use of the mapper:
var result = context.FindCustomerServiceSellOffers.Where(o => someCriteria).Select(_mapper.Map<SalesPitchViewModel>).ToList();
You can create a more general value resolver by implementing IMemberValueResolver<object, object, string, bool> and using that in your mapping configuration. You can provide a source property resolution function as before:
public class PublicationSystemResolver : IMemberValueResolver<object, object, string, bool>
{
private readonly PublicationService _publicationService;
public PublicationSystemResolver(PublicationService publicationService)
{
this._publicationService = publicationService;
}
public bool Resolve(object source, object destination, string sourceMember, bool destMember, ResolutionContext context)
{
return _publicationService.IsInNewSystem(sourceMember);
}
}
cfg.CreateMap<FindCustomerServiceSellOffers, SalesPitchViewModel>()
.ForMember(dest => dest.IsInNewSystem,
src => src.ResolveUsing<PublicationSystemResolver, string>(s => s.PublicationCode));
So from my side, I want to add a few little things; try it
builder.Services.AddAutoMapper(typeof(TransactionProfile).Assembly); // working
builder.Services.AddAutoMapper(x => x.AddProfile<(TransactionProfile)>()); // not working
builder.Services.AddAutoMapper(x => x.AddMaps("Outlay.Infrastructure")); // not working

autofac: How to resolve collection of named types?

I have a bunch of TaskParametes class instances registered in container, like:
builder.Register(c => [some type instantiation]
)).Named<TaskParameters>("someTask").InstancePerDependency();
builder.Register(c => [some type instantiation]
)).Named<TaskParameters>("someOtherTask").InstancePerDependency();
These classes can be registered in any module of the application. I'd like to get the list of available named instances to sent it to client, which should instantiate and execute it by name.
Is there an option to get the list of the names, without actually instantiating types?
Currently I'm digging ComponentRegistry of IComponentContext, which I get from, var ctx = Container.Resolve<IComponentContext>();, am I on the right direction?
Metadata is more appropriate than naming in this case.
For the strongly-typed variant, define an interface to hold the metadata:
public interface ITaskMetadata
{
string Name { get; }
}
Then associate the metadata at build time:
builder.Register(c => [some type instantiation]))
.As<TaskParameters>()
.WithMetadata<ITaskMetadata>(m =>
m.For(tm => tm.Name, "someTask"));
builder.Register(c => [some type instantiation]))
.As<TaskParameters>()
.WithMetadata<ITaskMetadata>(m =>
m.For(tm => tm.Name, "someOtherTask"));
(The InstancePerDependency() is omitted because it is the default behaviour.)
Then, the component that needs to examine the names can take a dependency on IEnumerable<Lazy<T,TMetadata>> like so:
class SomeComponent : ISomeComponent
{
public SomeComponent(
IEnumerable<Lazy<TaskParameters,ITaskMetadata>> parameters)
{
// Here the names can be examined without causing instantiation.
}
}
This uses relationship types to avoid the need to look anything up in the container.
Note, the Lazy<,> type is from .NET 4. For details on achieving this in .NET 3.5, and alternative syntax, see the Autofac wiki.
If the name of the service is important to your application, maybe that should be modeled into your code. For example, you have TaskParameters; maybe you want something like:
public class Descriptor<T>
{
private readonly string _description;
private readonly Func<T> _create;
public Descriptor(string description, Func<T> create)
{
_description = description;
_create = create;
}
public string Description { get { return _description; } }
public T Create() { return _create(); }
}
And then you can register descriptors for your types. Then you could easily call
var descriptors = container.Resolve<IEnumerable<Descriptor<TaskParameters>>>();
I did'n find any solution rather than querying the context:
var ctx = Container.Resolve<IComponentContext>();
var taskNames = ctx.ComponentRegistry.Registrations
.Select(c => c.Services.FirstOrDefault() as KeyedService)
.Where(s => s != null && s.ServiceType == typeof (TaskParameters))
.Select(s => s.ServiceKey).ToList();
It seems that this approach doesn't instantiate nor activate anything.

Categories

Resources