Search code examples
c#aoppostsharp

How call a specific method from property aspect with property name and prefix?


I want to develop a NotifyDataErrorInfoAspect with possharp.

The validation of the values depends on several changeable properties (MinValue, MaxValue ...). The contract can't work with variable parameters.

I want to build something similar to the DependencyPropertyAspect. Each property with [DependencyProperty] has a number of optional methods. For example ValidatePropertyName.

[DependencyProperty]
public string Email { get; set; }

private bool ValidateEmail(string value)
{
    return EmailRegex.IsMatch(value);
}

How can I do this?

[NotifyDataErrorInfo]
public string Name{ get; set; }
private IList<DataErrorInfo> ValidateName(string value)
{
    return this.IsValidName(value);
}

[NotifyDataErrorInfo]
public int Age{ get; set; }
private IList<DataErrorInfo> ValidateAge(int value)
{
    return this.IsValidAge(value);
}

[NotifyDataErrorInfo]
public string Email { get; set; }
private IList<DataErrorInfo> ValidateEmail(string value)
{
    return this.IsValidEmail(value);
}

The attribute ImportMethod () only allows a fixed method name. What is the best way?


Solution

  • When you need to import a method that doesn't have a fixed predefined name, you can implement IAdviceProvider interface in your aspect and provide ImportMethodAdviceInstance that takes a method name as a string argument.

    Another important point is that your Validate methods take arguments of a specific type instead of object. Currently, it's not possible to create a generic attribute in C#, so you need to create two aspect classes to handle this case: an attribute that is an aspect provider and a generic aspect implementation.

    Below is a sample implementation of NotifyDataErrorInfo aspect:

    [PSerializable]
    public class NotifyDataErrorInfoAttribute : LocationLevelAspect, IAspectProvider
    {
        public IEnumerable<AspectInstance> ProvideAspects(object targetElement)
        {
            Type propertyType = ((LocationInfo)targetElement).LocationType;
            Type aspectType = typeof(NotifyDataErrorInfoAspectImpl<>).MakeGenericType(propertyType);
    
            yield return new AspectInstance(
                targetElement, (IAspect) Activator.CreateInstance(aspectType));
        }
    }
    
    [PSerializable]
    public class NotifyDataErrorInfoAspectImpl<T> : ILocationInterceptionAspect,
                                                    IInstanceScopedAspect,
                                                    IAdviceProvider
    {
        public Func<T, IList<DataErrorInfo>> ValidateDelegate;
    
        public IEnumerable<AdviceInstance> ProvideAdvices(object targetElement)
        {
            LocationInfo property = (LocationInfo)targetElement;
            string validateMethodName = "Validate" + property.Name;
    
            yield return new ImportMethodAdviceInstance(
                typeof(NotifyDataErrorInfoAspectImpl<>).GetField("ValidateDelegate"),
                validateMethodName,
                true);
        }
    
        public void OnSetValue(LocationInterceptionArgs args)
        {
            if (ValidateDelegate((T) args.Value)?.Any() == true)
                throw new ArgumentException("...");
    
            args.ProceedSetValue();
        }
    
        public void OnGetValue(LocationInterceptionArgs args)
        {
            args.ProceedGetValue();
        }
    
        public void RuntimeInitialize(LocationInfo locationInfo)
        {
        }
    
        public object CreateInstance(AdviceArgs adviceArgs)
        {
            return this.MemberwiseClone();
        }
    
        public void RuntimeInitializeInstance()
        {
        }
    }