Automapper 中的 ResolutionContext 创建自定义

ResolutionContext creation customization in Automapper

伙计们。

也许有人有同样的问题。

我在 MemoryCacheMicrosoft.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; } 
}

鉴于上述 SourceTarget 类,您可以定义一个 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