【问题标题】:Autofac and Contract classesAutofac 和 Contract 类
【发布时间】:2016-03-02 05:03:47
【问题描述】:

假设我们有以下内容:

[ContractClass(typeof(ContractClassForIFoo))]
public interface IFoo
{
    int DoThing(string x);
}

public class Foo : IFoo { ... }

[ContractClassFor(typeof(IFoo))]
public class ContractClassForIFoo : IFoo
{
    public int DoThing(string x)
    {
        Contract.Requires<ArgumentNullException>(x != null);
        return 0;
    }
}

我正在使用 Autofac 注册所有实现 IFoo 的组件:

builder.RegisterAssemblyTypes(ThisAssembly).As<IFoo>();

当我稍后解决我的依赖关系时:

var dependencies = container.Resolve<IFoo[]>();

我应该得到所有实现IFoo的类除了合同类。如何防止我的所有合同类在不完全将它们移动到单独的程序集的情况下解决?

我可以这样做:

builder.RegisterAssemblyTypes(ThisAssembly)
    .Where(t=> t.GetCustomAttribute<ContractClassForAttribute>() == null)
    .As<IFoo>();

但我需要为每个组件注册执行此操作。影响所有注册的东西会更好。如果它们具有ContractClassForAttribute 属性,是否可以对从 Autofac 解析的类型进行全局排除?

【问题讨论】:

    标签: c# dependency-injection autofac code-contracts


    【解决方案1】:

    编辑 正如 Steven 在评论中所解释的,ContractClassContractClassFor 标有 [Conditional("CONTRACTS_FULL")],此解决方案可能会为这些属性引入错误。请参阅 Steven 的评论以获得更好的解释。


    我不知道任何允许对RegisterAssemblyTypes 方法注册的注册进行全局过滤的机制。使用此方法过滤注册的唯一解决方案是使用代码示例中所示的Where 方法。

    当注册在ComponentRegistry 中注册时,无法将其从注册表中删除。

    如果您不想在每次注册时使用Where 方法,您可以创建另一个方法。

    public static class ContractClassRegistrationExtensions
    {
        public static IRegistrationBuilder<TLimit, TScanningActivatorData, TRegistrationStyle> NotContractClass<TLimit, TScanningActivatorData, TRegistrationStyle>(this IRegistrationBuilder<TLimit, TScanningActivatorData, TRegistrationStyle> registration) where TScanningActivatorData : ScanningActivatorData
        {
            if (registration == null)
            {
                throw new ArgumentNullException("registration");
            }
    
            return registration.Where(t => t.GetCustomAttribute<ContractClassForAttribute>() == null); 
        }
    }
    

    使用这种方法,而不是

    builder.RegisterAssemblyTypes(ThisAssembly)
           .Where(t=> t.GetCustomAttribute<ContractClassForAttribute>() == null)
           .As<IFoo>();
    

    你会写:

    builder.RegisterAssemblyTypes(ThisAssembly)
           .NotContractClass()
           .As<IFoo>();
    

    这不是一个真正的解决方案,但它是我在类似情况下会使用的解决方案。

    顺便说一句,如果你真的想要使用 Autofac 的魔法,你可以实现一个IRegistrationSource

    public class FilterRegistrationSource : IRegistrationSource
    {
        private static MethodInfo _createFilteredRegistrationMethod = typeof(FilterRegistrationSource).GetMethod("CreateFilteredRegistration");
    
        public Boolean IsAdapterForIndividualComponents
        {
            get
            {
                return false;
            }
        }
    
        public IEnumerable<IComponentRegistration> RegistrationsFor(Service service, Func<Service, IEnumerable<IComponentRegistration>> registrationAccessor)
        {
            IServiceWithType serviceWithType = service as IServiceWithType;
    
            if (serviceWithType == null)
            {
                yield break;
            }
    
            Type serviceType = serviceWithType.ServiceType;
            if (!serviceType.IsClosedTypeOf(typeof(IEnumerable<>)))
            {
                yield break;
            }
            Type elementType = new Type[] { serviceType }.Concat(serviceType.GetInterfaces())
                                          .Where(t => t.IsGenericType && t.GetGenericTypeDefinition() == typeof(IEnumerable<>))
                                          .Select(t => t.GetGenericArguments()[0])
                                          .First();
    
            yield return (IComponentRegistration)FilterRegistrationSource._createFilteredRegistrationMethod.MakeGenericMethod(elementType)
                                                                         .Invoke(this, new Object[] { serviceWithType });
        }
    
        public IComponentRegistration CreateFilteredRegistration<T>(IServiceWithType serviceWithType)
        {
            return RegistrationBuilder.ForDelegate((cc, p) => cc.ComponentRegistry
                                                                .RegistrationsFor(serviceWithType.ChangeType(typeof(T)))
                                                                .Where(r => !r.Activator.LimitType.GetCustomAttributes(typeof(ContractClassForAttribute), false).Any())
                                                                .Select(r => r.Activator.ActivateInstance(cc, p))
                                                                .Cast<T>())
                                      .As((Service)serviceWithType)
                                      .CreateRegistration();
    
        }
    }
    

    你可以这样注册:builder.RegisterSource(new FilterRegistrationSource())

    我没有测试过此解决方案的性能损失,请谨慎使用。

    另一个有趣的解决方案是使用 AOP 自定义注册注册的方式。

    【讨论】:

    • 这非常脆弱,我建议不要尝试过滤掉标有[ContractClass][ContractClassFor] 的类型,因为这些属性本身标有[Conditional("CONTRACTS_FULL")] 属性,这意味着当程序集在没有条件编译符号CONTRACTS_FULL 的情况下构建时,该类型将变为“正常”类型并再次注册。开发人员可能会在 RELEASE 配置中删除此符号。所以问题是这个错误只会出现在你实际使用发布版本的 UAT 或生产环境中。
    • @Steven 感谢您提供此信息,我不知道此特性。我编辑了我的答案。
    【解决方案2】:

    解决此问题的更好方法是正确定义您的合同类。建议当您为程序集创建包含合同的类时,该类为 privateabstract

    [ContractClass(typeof(ContractClassForIFoo))]
    public interface IFoo
    {
        int DoThing(string x);
    }
    
    public class Foo : IFoo { ... }
    
    [ContractClassFor(typeof(IFoo))]
    private abstract class ContractClassForIFoo : IFoo
    {
        public int DoThing(string x)
        {
            Contract.Requires<ArgumentNullException>(x != null);
            throw new NotImplementedException();
        }
    }
    

    现在,类是private,所以 AutoFac 应该看不到它——但当然可以,因为它可能正在使用反射;但由于它是private,它不应该尝试注册它。除此之外,它是abstract,因此无论如何都不能直接实例化。这解决了所有问题。

    另外,合约类中的所有方法都应该throw new NotImplementedException();。这样,如果您忘记将其标记为privateabstract,所有方法都会抛出。您应该在开发过程中很快发现这一点。仅使用退化形式的方法可能会引起您的注意。

    这是代码合同手册和社区推荐的模式。

    【讨论】:

      猜你喜欢
      • 2017-09-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-10-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多