带有postsharp的自定义类型引用规则

时间:2014-04-03 08:25:49

标签: c# postsharp

我刚开始评估PostSharp Ultimate,我想在程序集中强制执行一些自定义架构约束。

程序集的结构如下: (基本上每个命名空间都有一个根接口和该接口的特定实现)

MycompanyNamespace.Core.CommandDispatcher
  ICommandDispatcher
    XCommandDispatcher
    YCommandDispatcher
    ...

MycompanyNamespace.Core.Services
  IService
    XService
    YService
    ...

MycompanyNamespace.Core.Provider
  IProvider
    XProvider
    YProvider
    ...

我想强制执行的规则:

  • 不允许上游参考,例如声明IProvider接口的名称空间中的类型不允许引用声明IServiceICommandDispatcher类型的名称空间中的类型
  • 允许下游参考

我已经尝试了PostSharp附带的ComponentInteral约束,并且还创建了自定义ReferentialConstraint

我不确定

  • 更好的是,使用积极或消极的规则?例如

    [GrantAccess(TargetNamespace = typeof(IProvider),GrantedBy = typeof(ICommandDispatcher),typeof(IService)]

    [ProhibitAccess(TargetNamespace = typeof(ICommandDispatcher),ProhibitedBy = typeof(IProvider),typeof(IService)]

  • 我可以将规则放入 GlobalApsects.cs 文件中 assemlby还是我需要使用属性来装饰类型?

  • 我可以重复使用预先处理的规则吗?或者如何实施这样的自定义规则?

1 个答案:

答案 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;
    }
}