Как внедрить атрибут, используя атрибут PostSharp? - PullRequest
7 голосов
/ 21 октября 2011

Как я могу написать аспект PostSharp, чтобы применить атрибут к классу? Сценарий, который я рассматриваю, - это объект WCF (или объект домена), который должен быть украшен атрибутом DataContract. Он также должен иметь свойство Namespace. Как это:

using System.Runtime.Serialization;

namespace MWS.Contracts.Search.V1
{
    namespace Domain
    {
        [DataContract(Namespace = XmlNamespaces.SchemaNamespace)]
        public class PagingContext
        {
            [DataMember]
            public int Page { get; set; }

            [DataMember]
            public int ResultsPerPage { get; set; }

            [DataMember]
            public int MaxResults { get; set; }
        }
    }
}

В приведенном выше примере вы можете видеть, как я хочу, чтобы вывод выглядел. Он имеет атрибут DataContract, примененный к классу. Делать это вручную утомительно и не уникально. Мне бы очень хотелось написать один аспект, который можно применить к моему пространству имен «Домен». Затем он применил бы атрибуты, связанные с сериализацией, для меня. Таким образом, я могу сосредоточиться на разработке объектов-сущностей и не беспокоиться о деталях, связанных с сериализацией.

На сайте PostSharp я нашел документацию для внедрения кода до, после и вместо методов. Однако то, что я ищу, - это способ вставить атрибут в тип.


Вот решение!

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.Serialization;
using PostSharp.Aspects;
using PostSharp.Extensibility;
using PostSharp.Reflection;

namespace MWS.Contracts.Aspects
{
    // We set up multicast inheritance so  the aspect is automatically added to children types.
    [MulticastAttributeUsage(MulticastTargets.Class, Inheritance = MulticastInheritance.Strict)]
    [Serializable]
    public sealed class AutoDataContractAttribute : TypeLevelAspect, IAspectProvider
    {
        private readonly string xmlNamespace;

        public AutoDataContractAttribute(string xmlNamespace)
        {
            this.xmlNamespace = xmlNamespace;
        }

        // This method is called at build time and should just provide other aspects.
        public IEnumerable<AspectInstance> ProvideAspects(object targetElement)
        {
            var targetType = (Type) targetElement;

            var introduceDataContractAspect =
                new CustomAttributeIntroductionAspect(
                    new ObjectConstruction(typeof (DataContractAttribute).GetConstructor(Type.EmptyTypes)));

            introduceDataContractAspect.CustomAttribute.NamedArguments.Add("Namespace", xmlNamespace);

            var introduceDataMemberAspect =
                new CustomAttributeIntroductionAspect(
                    new ObjectConstruction(typeof (DataMemberAttribute).GetConstructor(Type.EmptyTypes)));

            // Add the DataContract attribute to the type.
            yield return new AspectInstance(targetType, introduceDataContractAspect);

            // Add a DataMember attribute to every relevant property.)))
            foreach (var property in
                targetType.GetProperties(BindingFlags.Public | BindingFlags.DeclaredOnly | BindingFlags.Instance)
                    .Where(property =>
                           property.CanWrite &&
                           !property.IsDefined(typeof (NotDataMemberAttribute), false)))
                yield return new AspectInstance(property, introduceDataMemberAspect);
        }
    }

    [AttributeUsage(AttributeTargets.Property)]
    public sealed class NotDataMemberAttribute : Attribute
    {
    }
}

1 Ответ

2 голосов
/ 21 октября 2011

См. http://www.sharpcrafters.com/blog/post/PostSharp-Principals-Day-12-e28093-Aspect-Providers-e28093-Part-1.aspx

Вот рабочий пример. Применение этого аспекта к классу приведет к применению атрибута XmlIgnore к любому общедоступному свойству, к которому еще не применен XmlElement или XmlAttribute. хитрость заключается в использовании CustomAttributeIntroductioinAspect, который встроен в Postsharp. Вам просто нужно создать экземпляр, указав тип атрибута и детали конструктора, а затем создать провайдера, чтобы применить его к целевым объектам.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using PostSharp.Extensibility;
using PostSharp.Aspects;
using PostSharp.Reflection;
using System.Xml.Serialization;

namespace ApplyingAttributes
{
    [MulticastAttributeUsage(MulticastTargets.Field | MulticastTargets.Property,
                            TargetMemberAttributes = MulticastAttributes.Public | MulticastAttributes.Instance)]
    public sealed class AddXmlIgnoreAttribute : LocationLevelAspect, IAspectProvider
    {
        private static readonly CustomAttributeIntroductionAspect customAttributeIntroductionAspect =
            new CustomAttributeIntroductionAspect(
                new ObjectConstruction(typeof(XmlIgnoreAttribute).GetConstructor(Type.EmptyTypes)));

        public IEnumerable<AspectInstance> ProvideAspects(object targetElement)
        {
            LocationInfo memberInfo = (LocationInfo)targetElement;

            if (memberInfo.PropertyInfo.IsDefined(typeof(XmlElementAttribute), false) ||
                memberInfo.PropertyInfo.IsDefined(typeof(XmlAttributeAttribute), false))
                yield break;

            yield return new AspectInstance(memberInfo.PropertyInfo, customAttributeIntroductionAspect);
        }
    }

}

Чтобы использовать атрибуты, указав параметры, я использую

 public class MyAspect : TypeLevelAspect, IAspectProvider
{
    public IEnumerable<AspectInstance> ProvideAspects(object targetElement)
    {
        yield return Create<MethodInfo>(mi, "Value");

    }

    private AspectInstance Create<T>(T target, string newName)
    {
        var x = new CustomAttributeIntroductionAspect(
            new ObjectConstruction(typeof(NewMethodName).GetConstructor(new Type[] { typeof(string) }), new object[] { newName })
            );

        return new AspectInstance(target, x);
    }
}
...