Search code examples
c#.netvalidation.net-7.0

How can I improve this validation interface in .NET?


I was wondering is there any way to improve this interface validation, before we start implementing it. In summary, this code defines an interface and related methods for validating connections, specifically focusing on parameter validation and connection-specific validation.

public interface IConnValidation
{
    Task<ResultTuple> Validate(ConnectionSide side)
    {
        ResultTuple result = ValidateParameters(side);

        return result.Ok
                    ? ValidateConnection(side)
                    : result.ToTask();
    }

    ResultTuple ValidateParameters(ConnectionSide side)
    {
        try
        {
            return ValidateParametersInternal(side);
        }
        catch (Exception ex)
        {
            string message = $"{INTERNAL_ERROR} {ex.ToReport(false)}";
            return ResultTuple.Failure(message);
        }
    }

    async Task<ResultTuple> ValidateConnection(ConnectionSide side)
    {
        try
        {
            //DO NOT return the internal task as we need to handle the failure in this method
            ResultTuple result = await ValidateConnectionInternal(side);
            return result;
        }
        catch (Exception ex)
        {
            string message = $"{INTERNAL_ERROR} {ex.ToReport(false)}";
            return ResultTuple.Failure(message);
        }
    }

    protected ResultTuple ValidateParametersInternal(ConnectionSide side);

    protected Task<ResultTuple> ValidateConnectionInternal(ConnectionSide side);
}

I've tried something like this:

public interface IConnValidation
{
    Task<ResultTuple> Validate(ConnectionSide side);
}

public class ConcreteConnValidation : IConnValidation
{
    public async Task<ResultTuple> Validate(ConnectionSide side)
    {
        try
        {
            ResultTuple parameterValidationResult = await ValidateParameters(side);

            if (!parameterValidationResult.Ok)
            {
                return parameterValidationResult;
            }

            return await ValidateConnection(side);
        }
        catch (Exception ex)
        {
            string message = $"{INTERNAL_ERROR} {ex.ToReport(false)}";
            return ResultTuple.Failure(message);
        }
    }

    protected virtual Task<ResultTuple> ValidateParameters(ConnectionSide side)
    {
        try
        {
            // Validate parameters asynchronously and return ResultTuple
        }
        catch (Exception ex)
        {
            string message = $"{INTERNAL_ERROR} {ex.ToReport(false)}";
            return Task.FromResult(ResultTuple.Failure(message));
        }
    }

    protected virtual async Task<ResultTuple> ValidateConnection(ConnectionSide side)
    {
        try
        {
            // Validate connection asynchronously and return ResultTuple
        }
        catch (Exception ex)
        {
            string message = $"{INTERNAL_ERROR} {ex.ToReport(false)}";
            return ResultTuple.Failure(message);
        }
    }
}

But I was wondering is there any better approach?


Solution

  • You might be better off with an abstract base class rather than an interface. Concrete validation classes would then inherit the base class, overriding the internal validation methods. The base class would look something like this:

    public abstract class ConnValidationBase
    {
        public async Task<ResultTuple> Validate(ConnectionSide side)
        {
            var result = ValidateParameters(side);
    
            return result.Ok
                ? await ValidateConnection(side)
                : await result.ToTask();
        }
    
        private ResultTuple ValidateParameters(ConnectionSide side)
        {
            try
            {
                return ValidateParametersInternal(side);
            }
            catch (Exception ex)
            {
                var message = $"{INTERNAL_ERROR} {ex.ToReport(false)}";
                return ResultTuple.Failure(message);
            }
        }
    
        private async Task<ResultTuple> ValidateConnection(ConnectionSide side)
        {
            try
            {
                //DO NOT return the internal task as we need to handle the failure in this method
                var result = await ValidateConnectionInternal(side);
                return result;
            }
            catch (Exception ex)
            {
                var message = $"{INTERNAL_ERROR} {ex.ToReport(false)}";
                return ResultTuple.Failure(message);
            }
        }
    
        protected abstract ResultTuple ValidateParametersInternal(ConnectionSide side);
    
        protected abstract Task<ResultTuple> ValidateConnectionInternal(ConnectionSide side);
    }
    

    The concrete implementation could look like this:

    public sealed class ConcreteConnValidation : ConnValidationBase
    {
        protected override ResultTuple ValidateParametersInternal(ConnectionSide side)
        {
            // Validate parameters asynchronously and return ResultTuple
        }
    
        protected override Task<ResultTuple> ValidateConnectionInternal(ConnectionSide side)
        {
            // Validate connection asynchronously and return ResultTuple
        }
    }
    

    And the concrete validation would be called like this (or similar):

    var validator = new ConcreteConnValidation();
    var connection = new ConnectionSide();
    var result = await validator.Validate(connection);