1
votes

I learned from here that cast cannot be done with the type variable.

I use fluent validation for entity validation in my project, and for this I wrote an aspect using postsharp. But I got stuck somewhere. In the constructor method of my aspect class, I get the type of my validation class. In the Entry method, I dynamically create an example of this class and cast to IValidator. But since I cast to IValidator, I cannot pass the ruleset parameter to the Validate method. I need to cast IValidator . This is also impossible. How should I go about this problem? I hope I could tell. I add the codes below for the example . Thanks.

[PSerializable]
public class FluentValidationAspect : OnMethodBoundaryAspect
{
    private readonly Type _validatorType;
    private readonly string _ruleSet;

    public FluentValidationAspect(Type validatorType, string ruleSet = null)
    {
        _validatorType = validatorType;
        _ruleSet = ruleSet;
    }

    public override void OnEntry(MethodExecutionArgs args)
    {
        var entityType = _validatorType.BaseType?.GetGenericArguments()[0];
        var entities = args.Arguments.Where(x => x.GetType() == entityType);


        if (string.IsNullOrEmpty(_ruleSet))
        {
            var validator = (IValidator)Activator.CreateInstance(_validatorType);
            foreach (var entity in entities)
                ValidatorTool.FluentValidate(validator, entity);
        }
        else
        {
            var validator = (IValidator<entityType>)Activator.CreateInstance(_validatorType);
            foreach (var entity in entities)
                ValidatorTool.FluentValidate<entityType>(validator, entity);
        }

    }
}

public static class ValidatorTool
{

    public static void FluentValidate(IValidator validator, object entity)
    {
        var result = validator.Validate(entity);
        if (result.Errors.Any())
            throw new ValidationException(result.Errors);
    }

    public static void FluentValidate<T>(IValidator<T> validator, T entity, string ruleSet) where T : class, new()
    {
        var result = validator.Validate(entity, ruleSet: ruleSet);
        if (result.Errors.Any())
            throw new ValidationException(result.Errors);
    }
}


2

2 Answers

2
votes

You have two options. You can use reflection to call a proper instance of FluentValidate generic method, or you need the aspect itself be a generic type.

The latter option will be faster at runtime. The following code uses IAspectProvider that programmatically adds a generic aspect to the target method. The provider type does not have to be an aspect (but can). The provided generic aspect cannot derive from usual types because attribute types cannot be generic, so it implements aspect interfaces.

[AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Class | AttributeTargets.Method)]
[MulticastAttributeUsage(MulticastTargets.Method)]
public class FluentValidationAspect : MulticastAttribute, IAspectProvider
{
    private readonly Type _validatorType;
    private readonly string _ruleSet;

    public FluentValidationAspect(Type validatorType, string ruleSet = null)
    {
        _validatorType = validatorType;
        _ruleSet = ruleSet;
    } 

    public IEnumerable<AspectInstance> ProvideAspects(object targetElement)
    {
        var entityType = _validatorType.BaseType?.GetGenericArguments()[0];
        var genericAspectType = typeof(FluentValidationAspect<>).MakeGenericType(entityType);

        yield return new AspectInstance(targetElement, new ObjectConstruction(genericAspectType, new object[] { _validatorType, _ruleSet }));
    }
}

[PSerializable]
public class FluentValidationAspect<T> : IMethodLevelAspect
{
    private Type _validatorType;
    private string _ruleSet;

    public FluentValidationAspect(Type validatorType, string ruleSet = null)
    {
        _validatorType = validatorType;
        _ruleSet = ruleSet;
    }

    public void RuntimeInitialize(MethodBase method)
    {
    }

    [SelfPointcut]
    [OnMethodEntryAdvice]
    public void OnEntry(MethodExecutionArgs args)
    {
        var entities = args.Arguments.Where(x => x is T).Select(x => (T)x);

        var validator = (IValidator<T>)Activator.CreateInstance(_validatorType);
        foreach (var entity in entities)
            ValidatorTool.FluentValidate(validator, entity);
    }
}

Note that there is no reflection involved at runtime. However I would consider removing LINQ completely from OnEntry advice to get better performance.

1
votes

This was solved my problem someone will come for this again I believe

public static void Validate(IValidator validator, object entity)
        {
            var context = new ValidationContext<object>(entity);
            var result = validator.Validate(context);
            if (result.Errors.Count > 0)
            {
                throw new ValidationException(result.Errors);
            }
        }