Search code examples
c#validationcastle-windsorsimple-injectorcross-cutting-concerns

Implement a cross cutting validator for very different methods using c#


I have a small framework with Client/Server Architecture I use this Tools in my Business Layer:

DI = SimpleInjector

DynamicProxy For Interception = Castle.Core

now i need to validate some validations! for example look at this method:

public void DeleteFakeItem (Guid userId, Guid fakeItemId)
{
    userAccountService.IsAuthorized(userId, FeatureIndex.DeleteFakeItem);

    if (fakeItemId == Guid.EmptyGuid || userId == Guid.EmptyGuid) 
        throw new ArgumentNullException("parameters are not correct!");

    if (!repo.IsFakeItemIsDeletable(fakeItemId))
        throw new Exception("you can not delete this item!");

    var fakeItem = repo.GetFakeItem(fakeItemId);

    if (fakeItem == null)
        throw new Exception("this fakeItem dose not exists!");

    repo.DeleteActivityCenter(fakeItem);
}

but, i have a lot of methods, my methods are very different to each other, so where is the solution? because i can not create a good abstraction for my methods.

how can i implement a cross cutting feature to validate my parameters?

i think i can do it using interceptor and attributes, for example an attribute like [Validate(ValidateEnum.NotNull)] for each parameter.

what is the correct way?

and second question for my entities: can i get the fluent API validation Rules to validate entities based on them using reflection with a interceptor?

for example i wanna get rules, if there is a IsRequired() rule, validate as not null.

i don't wanna use decorator pattern because it's makes me refactoring a lot;


Solution

  • I just solve my problem with combine reflection with generic interfaces, so the only thing I need is to implement the generic interface for every entity.

    I have an interceptor that intercepts all methods. and its works for me. but can anyone give me some information about the performance? is it a correct way to do validation? interceptor:

    public class ValidatorInterceptor : IInterceptor
    {
    
        private readonly IServiceFactory factory;
    
        public ValidatorInterceptor(IServiceFactory _factory)
        {
            factory = _factory;
        }
    
        public void Intercept(IInvocation invocation)
        {
            var methodParameterSet = invocation.InvocationTarget.GetType().GetMethod(invocation.Method.Name).GetParameters().ToList();
            for (var index = 0; index < methodParameterSet.Count; index++)
            {
                var parameter = methodParameterSet[index];
                var paramType = parameter.ParameterType;
                var customAttributes = new List<object>();
                var factoryMethod = factory.GetType().GetMethod("GetService");
                var baseValidatorType = typeof(IValidator<>);
                var validatorType = baseValidatorType.MakeGenericType(paramType);
                factoryMethod = factoryMethod.MakeGenericMethod(validatorType);
                var validator = factoryMethod.Invoke(factory, null);
    
                customAttributes.AddRange(parameter.GetCustomAttributes(true).Where(item => item.GetType().Name.StartsWith("Validate")));
                foreach (var attr in customAttributes)
                {
                    dynamic attribute = attr;
                    var method = validator.GetType().GetMethod("Validate");
                    method = method.MakeGenericMethod(paramType);
                    object[] parameterSet = {invocation.Arguments[index], attribute.Rule, attribute.IsNullCheck};
                    method.Invoke(validator, parameterSet);
                }
            }
    
            invocation.Proceed();
        }
    }
    

    and the implementation of IValidator for UserAccount Entity is like this:

    public class ValidateUserAccount<T> : IValidator<T> where T : UserAccount
    {
        public void Validate<T>(T entity, object obj1 = null, object obj2 = null) where T : class
        {
            var item = (UserAccount) Convert.ChangeType(entity, typeof(UserAccount));
    
            if (item == null)
                throw new ArgumentNullException("user account cant be null");
        }
    
    }
    

    and for string validator:

    public class ValidateString : IValidator<string>
    {
        public void Validate<T>(T entity, object rukeObj = null, object nullChekcObj = null) where T : class
        {
            var item = (string) Convert.ChangeType(entity, typeof(string));
            var rule = (Regex)Convert.ChangeType(rukeObj, typeof(Regex));
            var reqItem = Convert.ChangeType(nullChekcObj, typeof(bool));
            var isRequire = reqItem != null && (bool) reqItem;
    
            if (isRequire && string.IsNullOrEmpty(item))
                throw new ArgumentException("value can not be null!");
    
            if (!rule.Match(item).Success)
                throw new ArgumentException("[" + item + "] is not a valid input!");
    
        }
    }