我刚开始评估PostSharp Ultimate,我想在程序集中强制执行一些自定义架构约束。
程序集的结构如下: (基本上每个命名空间都有一个根接口和该接口的特定实现)
MycompanyNamespace.Core.CommandDispatcher
ICommandDispatcher
XCommandDispatcher
YCommandDispatcher
...
MycompanyNamespace.Core.Services
IService
XService
YService
...
MycompanyNamespace.Core.Provider
IProvider
XProvider
YProvider
...
我想强制执行的规则:
IProvider
接口的名称空间中的类型不允许引用声明IService
或ICommandDispatcher
类型的名称空间中的类型我已经尝试了PostSharp附带的ComponentInteral
约束,并且还创建了自定义ReferentialConstraint
。
我不确定
更好的是,使用积极或消极的规则?例如
[GrantAccess(TargetNamespace = typeof(IProvider),GrantedBy = typeof(ICommandDispatcher),typeof(IService)]
[ProhibitAccess(TargetNamespace = typeof(ICommandDispatcher),ProhibitedBy = typeof(IProvider),typeof(IService)]
我可以将规则放入 GlobalApsects.cs 文件中 assemlby还是我需要使用属性来装饰类型?
我可以重复使用预先处理的规则吗?或者如何实施这样的自定义规则?
答案 0 :(得分:0)
GrantAccess或ProhibitAccess
我不会说在GrantAccess和ProhibitAccess方法之间进行选择时,只有一个选项明显优于另一个选项。 这取决于您的设计细节以及您希望如何严格执行约束。
想到的问题是:
给定命名空间中的类是公共的还是内部的?
如果将来添加新的命名空间/组件会怎样? 默认情况下,新命名空间中的类是否应该能够访问此命名空间?或者应该明确授予此访问权限吗?
让我们说示例中的所有类都是内部的,并且您希望在它们之间强制执行引用约束。 在这种情况下,GrantAccess为您提供更严格的控制。新添加的命名空间将无法访问 现有名称空间除非您查看设计并明确授予此访问权限。
或者您可能希望将您的服务类公开,并限制它们从Provider名称空间使用。必须为使用服务的每个外部命名空间显式添加GrantAccess非常不方便。在这种情况下,限制较少的ProhibitAccess方法可能会更好。
这些只是您可以做出判断的例子,但最终取决于您的设计和项目。
程序集或类型级别属性
由于您希望将约束应用于给定命名空间中的所有类,因此将属性应用于程序集(在GlobalAspects.cs中)并在AttributeTargetTypes属性中指定命名空间更方便。
// Grant access to all classes in MycompanyNamespace.Core.Services namespace
[assembly: GrantAccess(..., AttributeTargetTypes = "MycompanyNamespace.Core.Services.*", ...)]
自定义约束
PostSharp提供的ComponentInternal
属性实现了GrantAccess方法,因此您可以将其应用于该用例。
但是,似乎有一个错误不允许它在程序集级别上多次应用。这应该在即将发布的版本之一中修复。
要使用类似的逻辑实现自定义约束,可以从以下示例开始:
[MulticastAttributeUsage(
MulticastTargets.AnyType | MulticastTargets.Method | MulticastTargets.InstanceConstructor | MulticastTargets.Field,
TargetTypeAttributes = MulticastAttributes.UserGenerated,
TargetMemberAttributes = (MulticastAttributes.AnyVisibility & ~MulticastAttributes.Private) | MulticastAttributes.UserGenerated)]
[AttributeUsage(
AttributeTargets.Assembly |
AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Interface | AttributeTargets.Enum | AttributeTargets.Delegate,
AllowMultiple = true)]
public class GrantAccessAttribute : ReferentialConstraint
{
private string[] namespaces;
public GrantAccessAttribute(params Type[] types)
{
this.namespaces = types.Select(t => t.Namespace).ToArray();
}
public override void ValidateCode(object target, Assembly assembly)
{
MemberInfo targetMember = target as MemberInfo;
if (targetMember != null)
{
Type targetType = target as Type;
if (targetType != null)
{
// validate derived types
foreach (TypeInheritanceCodeReference reference in ReflectionSearch.GetDerivedTypes(targetType, ReflectionSearchOptions.IncludeTypeElement))
{
Validate(reference);
}
// validate member references
foreach (MemberTypeCodeReference reference in ReflectionSearch.GetMembersOfType(targetType, ReflectionSearchOptions.IncludeTypeElement))
{
Validate(reference);
}
}
// validate references in methods
foreach (MethodUsageCodeReference methodUsageCodeReference in ReflectionSearch.GetMethodsUsingDeclaration(targetMember))
{
Validate(methodUsageCodeReference);
}
}
}
private void Validate(ICodeReference codeReference)
{
string usingNamespace = GetNamespace(codeReference.ReferencingDeclaration);
string usedNamespace = GetNamespace(codeReference.ReferencedDeclaration);
if (usingNamespace.Equals(usedNamespace, StringComparison.Ordinal))
return;
if (this.namespaces.Any(
x => usingNamespace.Equals(x, StringComparison.Ordinal) ||
(usingNamespace.StartsWith(x, StringComparison.Ordinal) && usingNamespace[x.Length] == '.')))
return;
object[] arguments = new object[] {/*...*/};
Message.Write(MessageLocation.Of(codeReference.ReferencingDeclaration), SeverityType.Warning, "ErrorCode", "Access error message.", arguments);
}
private string GetNamespace(object declarationObj)
{
Type declaringType = declarationObj as Type;
if (declaringType == null)
{
MemberInfo declaringMember;
ParameterInfo parameter;
LocationInfo location;
if ((declaringMember = declarationObj as MemberInfo) != null)
{
declaringType = declaringMember.DeclaringType;
}
else if ((location = declarationObj as LocationInfo) != null)
{
declaringType = location.DeclaringType;
}
else if ((parameter = declarationObj as ParameterInfo) != null)
{
declaringType = parameter.Member.DeclaringType;
}
else
{
throw new Exception("Invalid declaration object.");
}
}
return declaringType.Namespace;
}
}