Automapper 中的 ResolutionContext 创建自定义
ResolutionContext creation customization in Automapper
伙计们。
也许有人有同样的问题。
我在 MemoryCache
(Microsoft.Extensions.Caching.Memory.IMemoryCache
的标准非分布式内存实现)中有一些缓存变量。因此,我也有用于 Response/DTO 创建的映射。其中一些使用来自 MemoryCache
的变量。但现在我必须一直通过它
opts =>
{
opts.Items.Add(variableName1, variableValue1);
opts.Items.Add(variableName2, variableValue2);
...
}
或者我每次都需要MemoryCache
以同样的方式通过。
是否可以设置 ResolutionContext
的全局配置,允许我在创建 ResolutionContext
时传递我需要的 MemoryCache
中的所有变量?不幸的是,BeforeMap
不是解决方案——它没有用于 IMemoryCache
解析的 DI 机制。据我所知,它在映射结构中只能是一个 - Automapper 在第一个之后跳过所有 BeforeMap
。
谢谢。
您可以实现自定义 IMemberValueResolver
,而不是使用 ResolutionContext
,它可以注入 IMemoryCache
依赖项。
通过这样做,就没有必要用 key/value 对(从 IMemoryCache
复制)来播种 ResolutionContext
。
下面的 FromMemoryCacheResolver
从注入的 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);
}
}
例子
public class Source
{
public int Id { get; set; }
}
public class Target
{
public decimal DecimalValue { get; set; }
public string StringValue { get; set; }
}
鉴于上述 Source
和 Target
类,您可以定义一个 AutoMapper
映射,将目标 属性 设置为绑定到固定缓存键(参见 DecimalValue
规则)或动态缓存键(包括源对象的 属性 值,参见 StringValue
规则)。
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}"
));
您可以覆盖 AutoMapper 在您的依赖注入容器中注册的方式,并在它被解析之前执行一个操作。假设您使用标准的 Microsoft 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
这有两个缩减:
1) 从解析上下文的选项访问 Items
,而不是直接访问
在解析上下文中访问 Items
时会进行检查,以防止在默认上下文中访问它们,这是创建映射中使用的其他上下文的来源。幸运的是,从选项访问 Items
时没有这样的检查:
var items = resolutionContext.Options.Items;
所以不要那样做:
var items = resolutionContext.Items;
2) 不要将 Map() 与 Action<IMappingOperationOptions>
一起使用
您不能使用任何接受 Action<IMappingOperationOptions>
的 Map()
方法,因为它会有效地覆盖在使用映射操作选项中的条目初始化映射器时创建的 Items
字典的内容,即使如果设置了 none。所以,你不能这样做:
var result = mapper.Map(source, destination, opts => otps.Items["bar"] = "bar");
最后的笔记
总的来说,它有点乱,而且这段代码肯定不会赢得选美比赛,因此请考虑将它封装在一些体面的扩展方法中 IServiceCollection
。
伙计们。
也许有人有同样的问题。
我在 MemoryCache
(Microsoft.Extensions.Caching.Memory.IMemoryCache
的标准非分布式内存实现)中有一些缓存变量。因此,我也有用于 Response/DTO 创建的映射。其中一些使用来自 MemoryCache
的变量。但现在我必须一直通过它
opts =>
{
opts.Items.Add(variableName1, variableValue1);
opts.Items.Add(variableName2, variableValue2);
...
}
或者我每次都需要MemoryCache
以同样的方式通过。
是否可以设置 ResolutionContext
的全局配置,允许我在创建 ResolutionContext
时传递我需要的 MemoryCache
中的所有变量?不幸的是,BeforeMap
不是解决方案——它没有用于 IMemoryCache
解析的 DI 机制。据我所知,它在映射结构中只能是一个 - Automapper 在第一个之后跳过所有 BeforeMap
。
谢谢。
您可以实现自定义 IMemberValueResolver
,而不是使用 ResolutionContext
,它可以注入 IMemoryCache
依赖项。
通过这样做,就没有必要用 key/value 对(从 IMemoryCache
复制)来播种 ResolutionContext
。
下面的 FromMemoryCacheResolver
从注入的 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);
}
}
例子
public class Source
{
public int Id { get; set; }
}
public class Target
{
public decimal DecimalValue { get; set; }
public string StringValue { get; set; }
}
鉴于上述 Source
和 Target
类,您可以定义一个 AutoMapper
映射,将目标 属性 设置为绑定到固定缓存键(参见 DecimalValue
规则)或动态缓存键(包括源对象的 属性 值,参见 StringValue
规则)。
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}"
));
您可以覆盖 AutoMapper 在您的依赖注入容器中注册的方式,并在它被解析之前执行一个操作。假设您使用标准的 Microsoft 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
这有两个缩减:
1) 从解析上下文的选项访问 Items
,而不是直接访问
在解析上下文中访问 Items
时会进行检查,以防止在默认上下文中访问它们,这是创建映射中使用的其他上下文的来源。幸运的是,从选项访问 Items
时没有这样的检查:
var items = resolutionContext.Options.Items;
所以不要那样做:
var items = resolutionContext.Items;
2) 不要将 Map() 与 Action<IMappingOperationOptions>
一起使用
您不能使用任何接受 Action<IMappingOperationOptions>
的 Map()
方法,因为它会有效地覆盖在使用映射操作选项中的条目初始化映射器时创建的 Items
字典的内容,即使如果设置了 none。所以,你不能这样做:
var result = mapper.Map(source, destination, opts => otps.Items["bar"] = "bar");
最后的笔记
总的来说,它有点乱,而且这段代码肯定不会赢得选美比赛,因此请考虑将它封装在一些体面的扩展方法中 IServiceCollection
。