c# - 使用 postsharp 的自定义类型引用规则

标签 c# postsharp

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

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

MycompanyNamespace.Core.CommandDispatcher
  ICommandDispatcher
    XCommandDispatcher
    YCommandDispatcher
    ...

MycompanyNamespace.Core.Services
  IService
    XService
    YService
    ...

MycompanyNamespace.Core.Provider
  IProvider
    XProvider
    YProvider
    ...

我要执行的规则:

  • 不允许引用上游,例如不允许在声明了 IProvider 接口(interface)的命名空间中的类型引用 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 还是我需要用属性修饰类型?

  • 我可以重复使用预装规则吗?或者有人会如何实现这样的自定义规则?

最佳答案

GrantAccess 或 ProhibitAccess

在 GrantAccess 和 ProhibitAccess 方法之间进行选择时,我不会说只有一个选项绝对优于另一个选项。 这取决于您的设计细节以及您想要执行约束的严格程度。

想到的问题是:

给定命名空间中的类对于该程序集是公共(public)类还是内部类?

如果将来添加新的命名空间/组件怎么办? 默认情况下,新命名空间中的类是否应该能够访问该命名空间?还是应该明确授予此访问权限?

假设示例中的所有类都是内部类,您希望在它们之间强制执行引用约束。 在这种情况下,GrantAccess 为您提供更严格的控制。新添加的命名空间将无法访问 现有命名空间,除非您审查设计并明确授予此访问权限。

或者您可能希望公开您的服务类,并限制它们在 Provider 命名空间中的使用。必须为每个使用服务的外部 namespace 显式添加 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;
    }
}

关于c# - 使用 postsharp 的自定义类型引用规则,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/22831894/

相关文章:

c# - EF Core 5.0.4 - 从 core 3.1 升级后,通过 Include() 进行预加载不起作用

c# - 异步/等待调用不返回。强制同步确实

c# - C# 中的非惰性静态初始化 block

c# - 如何 Hook 一个全局的 PASTE EVENT?

C# 抽象属性可以在构造函数中初始化但之后只读?

c# - Linux Mono + Postsharp + Log4Net = 没有自动异常捕获

xamarin.ios - 具有MonoTouch的AOP

nuget - 可以使用 NuGet 获取以前的版本吗?

dependency-injection - 使用上下文信息记录日志

c# - 使用 PostSharp 在 C# 中面向方面的设计模式 Cuckoo's Egg