When you observe the properties of a ConfigurationSection, you can notice that you must add a ConfigurationPropertyAttribute to each property, with the name of the property as a parameter to the constructor. So let's start to implement the aspect, so that it automatically adds this attribute. There is an aspect which does this thing in the documentation of PostSharp, so this part is easy:

[Serializable]
[MulticastAttributeUsage(MulticastTargets.Class, Inheritance = MulticastInheritance.Strict)]
public class ConfigurationSectionAspect : TypeLevelAspect, IAspectProvider
{
    public IEnumerable<AspectInstance> ProvideAspects(object targetElement)
    {
        Type targetType = (Type) targetElement;

        var attributeType = typeof (ConfigurationPropertyAttribute);
        var constructorInfo = attributeType.GetConstructor(new Type[] { typeof(string) });
            
        foreach (PropertyInfo property in targetType.GetProperties(BindingFlags.Public | BindingFlags.DeclaredOnly | BindingFlags.Instance))
        {
            if (property.IsDefined(typeof(ConfigurationPropertyAttribute), false)) 
                continue;

            ObjectConstruction objectConstruction = new ObjectConstruction(constructorInfo, property.Name);
                    
            CustomAttributeIntroductionAspect attributeIntroductionAspect = new CustomAttributeIntroductionAspect(objectConstruction);

            yield return new AspectInstance(property, attributeIntroductionAspect);
        }
    }

    #endregion
}

We make the aspect implement the IAspectProvider interface, which will allow us to enumerate the properties of the type on which the aspect is applied, and to introduce the attribute on each of them, unless the attribute is already present.

This will permit us to be able to implement a custom behavior for certain properties, in case the default configuration of the aspect doesn't fit your needs.

Given this class:

[ConfigurationSectionAspect]
public class DatabaseSection : ConfigurationSection
{
    public DatabaseSection()
    {
        Boolean = true;
    }

    public bool Boolean { get; set; }
    public FontElement Font { get; set; }
}

Here is what it looks like in Reflector after the compilation:

Reflector1.PNG

Well done, the attribute has been added. One improvement to this attribute would be to add the IsRequired named parameter to this attribute, which has a pretty straightforward name.

This step is quite easy too: we must use the NamedParameters property of the ObjectConstruction class, using a boolean value we can ask in the constructor of the aspect, which now becomes:

[Serializable]
[MulticastAttributeUsage(MulticastTargets.Class, Inheritance = MulticastInheritance.Strict)]
public class ConfigurationSectionAspect : TypeLevelAspect, IAspectProvider
{
    private readonly bool arePropertiesRequired;

    public ConfigurationSectionAspect(bool arePropertiesRequired = true)
    {
        this.arePropertiesRequired = arePropertiesRequired;
    }

    #region Implementation of IAspectProvider

    public IEnumerable<AspectInstance> ProvideAspects(object targetElement)
    {
        Type targetType = (Type) targetElement;

        var attributeType = typeof (ConfigurationPropertyAttribute);
        var constructorInfo = attributeType.GetConstructor(new Type[] { typeof(string) });
            
        foreach (PropertyInfo property in targetType.GetProperties(BindingFlags.Public | BindingFlags.DeclaredOnly | BindingFlags.Instance))
        {
             if (property.IsDefined(typeof(ConfigurationPropertyAttribute), false)) 
                continue;

            ObjectConstruction objectConstruction = new ObjectConstruction(constructorInfo, property.Name);
            objectConstruction.NamedArguments.Add("IsRequired", arePropertiesRequired);
                    
            CustomAttributeIntroductionAspect attributeIntroductionAspect = new CustomAttributeIntroductionAspect(objectConstruction);

            yield return new AspectInstance(property, attributeIntroductionAspect);
        }
    }

    #endregion
}

As you can see, the output is correct in Reflector:

Reflector2.PNG

Before we update the aspect to replace the getter and the setter of the class with the calls to the indexer, we can add a little improvement to the aspect. Indeed, you may want the section to be read-only. If this is what you wish, you need to override the IsReadOnly method to return true.

We can automate this within the aspect, with the IntroduceMemberAttribute:

[Serializable]
[MulticastAttributeUsage(MulticastTargets.Class, Inheritance = MulticastInheritance.Strict)]
public class ConfigurationSectionAspect : TypeLevelAspect, IAspectProvider
{
    private readonly bool arePropertiesRequired;
    private readonly bool isSectionReadonly;

    public ConfigurationSectionAspect(bool arePropertiesRequired = true, bool isSectionReadonly = false)
    {
        this.arePropertiesRequired = arePropertiesRequired;
        this.isSectionReadonly = isSectionReadonly;
    }

    [IntroduceMember(Visibility = Visibility.Public, IsVirtual = true, OverrideAction = MemberOverrideAction.OverrideOrFail)]
    public bool IsReadOnly()
    {
        return isSectionReadonly;
    }

    #region Implementation of IAspectProvider

    public IEnumerable<AspectInstance> ProvideAspects(object targetElement)
    {
        Type targetType = (Type) targetElement;

        var attributeType = typeof (ConfigurationPropertyAttribute);
        var constructorInfo = attributeType.GetConstructor(new Type[] { typeof(string) });
            
        foreach (PropertyInfo property in targetType.GetProperties(BindingFlags.Public | BindingFlags.DeclaredOnly | BindingFlags.Instance))
        {
            if (property.IsDefined(typeof(ConfigurationPropertyAttribute), false)) 
                continue;

            ObjectConstruction objectConstruction = new ObjectConstruction(constructorInfo, property.Name);
            objectConstruction.NamedArguments.Add("IsRequired", arePropertiesRequired);
                    
            CustomAttributeIntroductionAspect attributeIntroductionAspect = new CustomAttributeIntroductionAspect(objectConstruction);

            yield return new AspectInstance(property, attributeIntroductionAspect);
        }
    }

    #endregion
}

One more time, all is correct in Reflector:

Reflector3.PNG

It is now time to implement the getter and the setter of the properties. In order to do that, we will create a sub-aspect which will be returned by our main aspect for each property. This sub-aspect will intercept the calls to the getter and the setter, and will replace the default behavior with a call to the indexer, like in the original version.

We need to make this sub-aspect inherit from LocationInterceptionAspect, so we can override the OnGetValue and OnSetValue methods.

The next thing to do is to import in the aspect the indexer from the class, using the ImportMemberAttribute. But we can't use this attribute without making our aspect implement the interface IInstanceScopedAspect.

[Serializable]
public class ConfigurationPropertyInterceptor : LocationInterceptionAspect, IInstanceScopedAspect
{
    #region Implementation of IInstanceScopedAspect

    public object CreateInstance(AdviceArgs adviceArgs)
    {
        return this.MemberwiseClone();
    }

    public void RuntimeInitializeInstance()
    {

    }

    #endregion

    [ImportMember("Item", IsRequired = true)]
    public Property<object, string> IndexerMethod;
}

As you can see, importing the indexer is damn simple. We just have to use the Property<TValue, TIndex> class with the correct generic parameters.

The last thing to do, is now to replace the default getter and setter, so that we instead use this indexer. As I said earlier, we just need to override OnGetValue and OnSetValue:

public override void OnGetValue(LocationInterceptionArgs args)
{
    object value = IndexerMethod.Get(args.LocationName);

    args.Value = value;
}

public override void OnSetValue(LocationInterceptionArgs args)
{
    IndexerMethod.Set(args.LocationName, args.Value);
}

And we are now finished :)

Before the end of this article, I should mention that you are not limited to simple properties. You can use nested properties like that for example:

[ConfigurationSectionAspect(ArePropertiesRequired = true, IsSectionReadonly = true)]
public class DatabaseSection : ConfigurationSection
{
    public bool Boolean { get { return false; } }
    public FontElement Font { get; set; }
}

[ConfigurationSectionAspect(ArePropertiesRequired = true, IsSectionReadonly = false)]
public class FontElement : ConfigurationElement
{
    public string Name { get; set; }
    public int Size { get; set; }
    public Color Color { get; set; }
}

The end is here. I hope you will admit that this makes things much easier, and that PostSharp can help you in a wide variety of applications!

See you soon!