Autofac 和合同 类
Autofac and Contract classes
假设我们有以下内容:
[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
除了 合同 class(es) 的 classes。我如何防止 all 我的合同 classes 在不将它们完全移动到单独的程序集的情况下解决?
我可以这样做:
builder.RegisterAssemblyTypes(ThisAssembly)
.Where(t=> t.GetCustomAttribute<ContractClassForAttribute>() == null)
.As<IFoo>();
但我需要为每个组件注册执行此操作。影响所有注册的东西会更好。如果它们具有 ContractClassForAttribute
属性,是否可以对从 Autofac 解析的类型进行全局排除?
EDIT 正如史蒂文在评论中所解释的那样,ContractClass
和 ContractClassFor
标记为 [Conditional("CONTRACTS_FULL")]
,此解决方案可能会引入错误这些属性。请参阅史蒂文的评论以获得更好的解释。
我不知道有什么机制可以对 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 自定义注册方式。
解决此问题的更好方法是正确定义合同 class。建议当您创建包含合同的 class 时,class 是 private
,并且 abstract
:
[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();
}
}
现在,class 是 private
,因此 AutoFac 应该看不到它 — 但当然可以,因为它可能正在使用反射;但由于它是 private
,因此不应尝试注册它。除此之外,它是 abstract
,因此无论如何都不能完全实例化。这解决了所有问题。
另外,合同 class 中的所有方法都应该 throw new NotImplementedException();
。这样,如果您忘记将其标记为 private
或 abstract
,所有方法都会抛出。您应该在开发过程中很快发现这一点。仅使用退化形式的方法可能会引起您的注意。
这是代码合同手册和社区推荐的模式。
假设我们有以下内容:
[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
除了 合同 class(es) 的 classes。我如何防止 all 我的合同 classes 在不将它们完全移动到单独的程序集的情况下解决?
我可以这样做:
builder.RegisterAssemblyTypes(ThisAssembly)
.Where(t=> t.GetCustomAttribute<ContractClassForAttribute>() == null)
.As<IFoo>();
但我需要为每个组件注册执行此操作。影响所有注册的东西会更好。如果它们具有 ContractClassForAttribute
属性,是否可以对从 Autofac 解析的类型进行全局排除?
EDIT 正如史蒂文在评论中所解释的那样,ContractClass
和 ContractClassFor
标记为 [Conditional("CONTRACTS_FULL")]
,此解决方案可能会引入错误这些属性。请参阅史蒂文的评论以获得更好的解释。
我不知道有什么机制可以对 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 自定义注册方式。
解决此问题的更好方法是正确定义合同 class。建议当您创建包含合同的 class 时,class 是 private
,并且 abstract
:
[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();
}
}
现在,class 是 private
,因此 AutoFac 应该看不到它 — 但当然可以,因为它可能正在使用反射;但由于它是 private
,因此不应尝试注册它。除此之外,它是 abstract
,因此无论如何都不能完全实例化。这解决了所有问题。
另外,合同 class 中的所有方法都应该 throw new NotImplementedException();
。这样,如果您忘记将其标记为 private
或 abstract
,所有方法都会抛出。您应该在开发过程中很快发现这一点。仅使用退化形式的方法可能会引起您的注意。
这是代码合同手册和社区推荐的模式。