StructureMap通用构造函数命名实例

4

更新

我用以下代码解决了这个问题,但这不是我正在寻找的解决方案。仍然需要更通用的解决方案。 如果我们有一个表不是使用 intstring 作为键值,那么我们必须手动添加来使其工作。

c.For(typeof(ILogDifferencesCommand<,>)).Use(typeof(LogDifferencesCommand<,>))
                .Ctor<ILogDifferencesLogger<int>>()
                .Named(AppSettingsManager.Get("logDifferences:Target"))
                .Ctor<string>()
                .Named(AppSettingsManager.Get("logDifferences:Target"));

原始问题

我有三种类型的记录器,并在容器中为它们定义了命名实例:

c.For(typeof(ILogDifferencesLogger<>))
    .Use(typeof(LogDifferencesAllLogger<>))
    .Named("all");
c.For(typeof(ILogDifferencesLogger<>))
    .Use(typeof(LogDifferencesNLogLogger<>))
    .Named("nlog");
c.For(typeof(ILogDifferencesLogger<>))
    .Use(typeof(LogDifferencesDatabaseLogger<>))
    .Named("database");
LogDifferencesCommand 接收一个 ILogDifferencesLogger<> 作为其唯一参数。该参数表示日志差异的记录器。
public LogDifferencesCommand(ILogDifferencesLogger<TKey> logDifferencesLogger)
{
    this.logDifferencesLogger = logDifferencesLogger;
}

我该如何正确配置ILogDifferencesCommand<>,以便基于应用程序设置获取正确的命名实例?目前我的代码类似于:

c.For(typeof(ILogDifferencesCommand<,>))
    .Use(typeof(LogDifferencesCommand<,>));

我遇到的问题是我无法引入Ctor<>,因为我不能使用那个签名的未绑定范型,所以我无法在Ctor上使用Named方法。
例如,我可以这样做,但这并不适用于所有可能的类型:
c.For(typeof(ILogDifferencesCommand<,>)).Use(typeof(LogDifferencesCommand<,>))
    .Ctor<ILogDifferencesLogger<int>>()
    .Named(AppSettingsManager.Get("logDifferences:Target"));

但问题是,我需要处理系统使用的每个 TKey 类型。

类和接口定义

public class LogDifferencesCommand<TModel, TKey> : ILogDifferencesCommand<TModel, TKey>
    where TModel : class, IIdModel<TKey>
{
    public LogDifferencesCommand(ILogDifferencesLogger<TKey> logDifferencesLogger)
    {
        this.logDifferencesLogger = logDifferencesLogger;
    }
}

public interface ILogDifferencesCommand<TModel, TKey>
    where TModel : class, IIdModel<TKey>
{
    List<LogDifference> CalculateDifferences(TModel x, TModel y);

    void LogDifferences(TModel x, TModel y, string tableName, string keyField, string userId, int? clientId);

    void RegisterCustomDisplayNameObserver(WeakReference<ICustomDisplayNameObserver<TModel>> observer);

    void RegisterCustomChangeDateObserver(WeakReference<ICustomChangeDateObserver<TModel>> observer);
}

public interface ILogDifferencesLogger<TKey>
{
    void LogDifferences(string tableName, string keyField, string userId, TKey id, List<LogDifference> differences, int? clientId);
}

TKey之所以必须是因为IIdModel接口的原因。


嗯,我觉得你有点想太多了。只需创建ILoggerFactory,并通过某个“Create”方法实例化你的ILogDifferencesCommand<>。说真的,为了简单起见,它将比滥用DI框架更易读。无论如何,你都可以使用约定来实现这种反射。 - eocron
@eocron,我明白你的意思。然而,这使我们可以使用 DI 来获取实例,而不必在众多构造函数中添加调用。它使我们的类非常简洁和一致。 - Mike Perrenoud
2个回答

3

我想到一个选项:

var loggers = new Dictionary<string, ConfiguredInstance>();
loggers.Add("all", c.For(typeof(ILogDifferencesLogger<>))
    .Use(typeof(LogDifferencesAllLogger<>)));
loggers.Add("nlog", c.For(typeof(ILogDifferencesLogger<>))
    .Use(typeof(LogDifferencesNLogLogger<>)));
loggers.Add("database", c.For(typeof(ILogDifferencesLogger<>))
    .Use(typeof(LogDifferencesDatabaseLogger<>)));
foreach (var kv in loggers) {
    // if you still need them named
    // if you only used names for this concrete scenario - you probably don't
    // so can remove it
    kv.Value.Named(kv.Key);
}
c.For(typeof(LogDifferencesCommand<>))
    .Use(typeof(LogDifferencesCommand<>))
    // add explicit instance as dependency
    .Dependencies.Add(typeof(ILogDifferencesLogger<>), loggers[AppSettingsManager.Get("logDifferences:Target")]); 

更新。正如我们在评论中发现的那样,对于 LogDifferencesCommand 有多个类型参数的情况,这种方法不起作用。由于某些原因(我认为是一个错误)- 结构映射器尝试创建封闭泛型类型 ILogDifferencesLogger<>,但在 doing 时 - 使用来自 LogDifferencesCommand 的泛型类型参数。也许值得在他们的GitHub上提出问题。你可以像这样解决它:

public class GenericTypesWorkaroundInstance : Instance
{
    private readonly Instance _target;
    private readonly Func<Type[], Type[]> _chooseTypes;
    public GenericTypesWorkaroundInstance(Instance target, Func<Type[], Type[]> chooseTypes) {
        _target = target;
        _chooseTypes = chooseTypes;
        ReturnedType = _target.ReturnedType;
    }

    public override Instance CloseType(Type[] types) {
        // close type correctly by ignoring wrong type arguments
        return _target.CloseType(_chooseTypes(types));
    }

    public override IDependencySource ToDependencySource(Type pluginType) {
        throw new NotSupportedException();
    }

    public override string Description => "Correctly close types over open generic instance";    
    public override Type ReturnedType { get; }
}

接着做

commandReg.Dependencies.Add(
    commandReg.Constructor.GetParameters().First(
        p => p.ParameterType.IsGenericType && p.ParameterType.GetGenericTypeDefinition() == typeof(ILogDifferencesLogger<>)).Name, 
        new GenericTypesWorkaroundInstance(
            loggers[AppSettingsManager.Get("logDifferences:Target")],
            // specify which types are correct
            types => types.Skip(1).ToArray()));

虽然它能够正常运行,但我并不喜欢它。


这是一个非常优雅的解决方案,正是我正在寻找的,但现在它会抛出这个错误 提供的泛型参数数量与泛型类型定义的元数不相等。我现在正在检查看看我可能做错了什么。 - Mike Perrenoud
也许你可以发布更完整的类定义,因为在发布之前我已经测试过了(使用基于您描述的类和接口)。 - Evk
是的,我想我搞砸了 - 我的错老板 - 问题在于 ILogDifferencesCommand 需要两种类型 - TModelTKey - 所以我只需稍微更改代码即可构建到这个 For(typeof(ILogDifferencesCommand<,>))。这就是构造函数获取 ILogDifferencesLoggerTKey 的地方。 - Mike Perrenoud
1
我注意到了 ILogDifferencesCommand<,> 中的那个逗号。很奇怪,在这种情况下它不起作用,而当 ILogDifferencesCommand 只有一个类型参数时却可以。无法理解它如何影响构造函数依赖项,因为在两种情况下它都是相同的(但不知何故它确实会影响)。 - Evk
我同意这个观点,因为我曾经有一个动态解决方案,它可以与Named一起使用,并为logDifferencesLogger提供了一个值,但当该类需要TKey时,它就开始失败了。非常令人沮丧! - Mike Perrenoud
显示剩余2条评论

2

StructureMap的作者(即我)强烈建议您在应用程序启动时尝试使用条件注册来通过检查配置值选择默认的日志记录器注册,然后只需允许自动连线处理运行时依赖项。


1
你能提供一个解决方案的例子吗?它不必完整,但我并不100%确定我理解了解决方案,一个小的配置示例会帮助很多。 - Mike Perrenoud
哦,我想我知道你在指什么了 - 你是在指条件if语句吗?如果是这样,那行不通,因为这是一个配置值,不是基于构建类型的。 - Mike Perrenoud

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接