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

929 Views Asked by At

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;

2

There are 2 best solutions below

0
mohammadreza On BEST ANSWER

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!");

    }
}
1
Ognyan Dimitrov On

My solution to the problem is the following (you can find it here) :

  1. Interface and implementation class in order to be able to change the validation logic - there maybe bugs or change in validation logic.
  2. Make most of the methods void and throw exceptions with a global exception handler.
  3. Separate the implementation and interface in different assemblies. By this I gain performance benefit for the unit tests. I have separate assemblies for Messaging service, Caching services, Persistence services because their implementations are huge and have lots of dependencies which slow unit test runs to oblivion. When your unit tests reference only the interface assemblies the tests compile and run a lot faster. This point has very very big impact on huge and long lived projects. - this affects quality of the codebase!