Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

Improving WCF Service Quality (Part 2 - Validation)

0.00/5 (No votes)
28 Feb 2022CPOL4 min read 4.8K  
How to add validation to WCF service requests
This is Part 2 of a 4 part series of articles that discuss how to improve WCF services with Authentication, Authorization, Validation and Logging with Aspect Oriented Programming. This article discusses how to add Validation to WCF services.

Introduction

We have discussed the basics of this refactoring process in the previous part here. This part explains how I added Validation to WCF services.

Using the Code

To add validation, let's add a new Class Library project named WCFService.Utils to the solution and then add a folder named Validation to the project.

Then, before proceeding any further, let's go back to WCFService.Shared project and add a class named ValidationResult under Objects folder as follows:

C#
public class ValidationResult
{
    public bool IsValidated { get; set; }
    public List<string> Messages { get; set; }
}

where IsValidated property will hold the validation result and Messages property will hold validation error messages if there are any.

Now returning to WCFService.Utils project, add an interface named IBaseValidator and a class named BaseValidator which inherits the IBaseValidator under the Validation folder.

C#
public interface IBaseValidator
{
    ValidationResult Validate(BaseRequest request);
}
C#
public class BaseValidator : IBaseValidator
{
    public virtual ValidationResult Validate(BaseRequest request)
    {
        bool validationResult = false;
        List<string> messages = new List<string>();
        if (string.IsNullOrWhiteSpace(request.Username))
        {
            validationResult = false;
            messages.Add("Empty username.");
        }
        if (string.IsNullOrWhiteSpace(request.Password))
        {
            validationResult = false;
            messages.Add("Empty password");
        }
        return new ValidationResult
        {
            IsValidated = validationResult,
            Messages = messages
        };
    }
}

There are several points I would like to note about the BaseValidator class. First of all, since we are going to use authentication, and will inherit every request object from BaseRequest class, every incoming request will hold Username and Password value. As these fields are required for authentication, every request should be validated for the existence of their properties. This is why we place these validations in the base class. Also mark that the Validate function is marked as virtual. This serves two purposes. First, to allow any Validation classes which inherit from this base class implement their own validation processes (even for the Username and Password properties) and unify the implementation of all derived validation classes.

Then we start to implement validators for each and every request needed by creating them under the Validators folder under each Service Library. Let's create and examine our first validator which will validate the request for player creation process.

C#
internal class CreatePlayerValidator : BaseValidator
{
    public override ValidationResult Validate(BaseRequest req)
    {
        ValidationResult result = base.Validate(req);
        if (!result.IsValidated)
            return result;
        else
        {
            bool validationResult = true;
            List<string> messages = new List<string>();
            CreatePlayerRequest request = req as CreatePlayerRequest;
            if (request == null)
                return new ValidationResult
                {
                    IsValidated = false,
                    Messages = new List<string> { "Can not convert request object 
                                                   or request object null" }
                };
            if (string.IsNullOrWhiteSpace(request.Name))
            {
                validationResult = false;
                messages.Add("Name attribute can not be null when creating a player");
            }
            if (request.DateOfBirth.AddYears(10) > DateTime.Now)
            {
                validationResult = false;
                messages.Add("A player must be at least 10 years old to be registered");
            }
            if (request.Height.HasValue && request.Height.Value < 100)
            {
                validationResult = false;
                messages.Add("A player must be at least 10 cm tall to be registered");
            }
            if (request.Height.HasValue && request.Height.Value > 220)
            {
                validationResult = false;
                messages.Add("A player must be most 220 cm tall to be registered");
            }
            if (request.Weight.HasValue && request.Weight.Value < 40)
            {
                validationResult = false;
                messages.Add("A player must be at least 40 kg to be registered");
            }
            if (request.Weight.HasValue && request.Weight.Value > 140)
            {
                validationResult = false;
                messages.Add("A player must be most 140 kg tall to be registered");
            }
            return new ValidationResult
            {
                IsValidated = validationResult,
                Messages = messages
            };
        }
    }
}

Let's note that we have changed the access modifier for this CreatePlayerValidator class from public to internal. Since we are going to use this class within the current assembly only, this is the most appropriate access modifier to set. Also, mark that we are overriding the Validate method which is inherited from the base class, but still don't want to lose the validation process done in the base class. In order to keep the base validation, we first run the validation process from the base class and return without further execution if the base validation fails. Although this is not mandatory (a developer may choose to return all validation results at once), this is a design choice. If we proceed the base validation process next step is to recover the original request by using as keyword. Since we have defined the input parameter as BaseRequest,to increase reusability, where all other request objects are derived from, it is possible to send all request objects to this method. So to secure the process, we should check the type and assign it into its original type first to check type validation, then to access properties of the request object. Then we create two properties validationResult and messages which are identical to the ValidatonResult class properties and the rest is quite straightforward itself. First, check for empty or null values if necessary, then check values against business rules. In this example, we require age to be more than 10, height to be between 100 and 220 cm and weight to be between 40 and 140 kg. These are just business rules which are not constant and even may not exist if not needed. At the end, all we have to do is create a return object of type ValidationResult and set the values we have gathered from rule checks.

The same rule applies for all request objects where necessary.

C#
internal class GetClubPlayersValidator : BaseValidator
{
    public override ValidationResult Validate(BaseRequest req)
    {
        ValidationResult result = base.Validate(req);
        if (!result.IsValidated)
            return result;
        else
        {
            bool validationResult = true;
            List<string> messages = new List<string>();
            GetClubPlayersRequest request = req as GetClubPlayersRequest;
            if (request == null)
                return new ValidationResult
                {
                    IsValidated = false,
                    Messages = new List<string> { "Can not convert request object 
                                                   or request object null" }
                };
            if (string.IsNullOrWhiteSpace(request.Club))
            {
                validationResult = false;
                messages.Add("Club name attribute can not be null 
                              when searching for club players");
            }
            return new ValidationResult
            {
                IsValidated = validationResult,
                Messages = messages
            };
        }
    }
}
C#
internal class GetPlayerByIdValidator : BaseValidator
{
    public override ValidationResult Validate(BaseRequest req)
    {
        ValidationResult result = base.Validate(req);
        if (!result.IsValidated)
            return result;
        else
        {
            bool validationResult = true;
            List<string> messages = new List<string>();
            GetPlayerByIdRequest request = req as GetPlayerByIdRequest;
            if (request == null)
                return new ValidationResult
                {
                    IsValidated = false,
                    Messages = new List<string> { "Can not convert request object 
                                                   or request object null" }
                };
            if (request.PlayerId < 1)
            {
                validationResult = false;
                messages.Add("Player identifier should be a positive integer");
            }
            return new ValidationResult
            {
                IsValidated = validationResult,
                Messages = messages
            };
        }
    }
}

After creating necessary validation classes, we should update our service methods to include validation processes. But before going any further, just a quick reminder if you have not noticed yet, that we have not created a validator class named GetAllPlayersValidator to validate the GetAllPlayers method. Since the method only expects an input object of type BaseRequest, BaseValidator class will be more than enough to validate the incoming request for this method.

These changes will make our service methods look like:

C#
public CreatePlayerResponse CreatePlayer(CreatePlayerRequest request)
{
    try
    {
        CreatePlayerValidator validator = new CreatePlayerValidator();
        ValidationResult valResult = validator.Validate(request);
        if (!valResult.IsValidated)
            return new CreatePlayerResponse
            {
                IsException = false,
                IsSuccess = false,
                Messages = valResult.Messages.ToArray()
            };
        return new CreatePlayerResponse
        {
            PlayerId = PlayerRepository.CreateNewPlayer(request.Name, 
                       request.DateOfBirth, request.Height, request.Weight, request.Club),
            IsException = false,
            IsSuccess = true,
            Messages = new string[] { "Operation successful" }
        };
    }
    catch (Exception ex)
    {
        return new CreatePlayerResponse
        {
            IsException = true,
            IsSuccess = false,
            Messages = new string[] { ex.Message }
        };
    }
}
C#
public GetAllPlayersResponse GetAllPlayers(BaseRequest request)
{
    try
    {
        BaseValidator validator = new BaseValidator();
        ValidationResult valResult = validator.Validate(request);
        if (!valResult.IsValidated)
            return new GetAllPlayersResponse
            {
                IsException = false,
                IsSuccess = false,
                Messages = valResult.Messages.ToArray()
            };
        return new GetAllPlayersResponse
        {
            PlayerList = PlayerRepository.GetAllPlayers(),
            IsException = false,
            IsSuccess = true,
            Messages = new string[] { "Operation successful" }
        };
    }
    catch (Exception ex)
    {
        return new GetAllPlayersResponse
        {
            IsException = true,
            IsSuccess = false,
            Messages = new string[] { ex.Message }
        };
    }
}
C#
public GetClubPlayersResponse GetClubPlayers(GetClubPlayersRequest request)
{
    try
    {
        GetClubPlayersValidator validator = new GetClubPlayersValidator();
        ValidationResult valResult = validator.Validate(request);
        if (!valResult.IsValidated)
            return new GetClubPlayersResponse
            {
                IsException = false,
                IsSuccess = false,
                Messages = valResult.Messages.ToArray()
            };
        return new GetClubPlayersResponse
        {
            PlayerList = PlayerRepository.GetClubPlayers(request.Club),
            IsException = false,
            IsSuccess = true,
            Messages = new string[] { "Operation successful" }
        };
    }
    catch (Exception ex)
    {
        return new GetClubPlayersResponse
        {
            IsException = true,
            IsSuccess = false,
            Messages = new string[] { ex.Message }
        };
    }
}
C#
public GetPlayerByIdResponse GetPlayerById(GetPlayerByIdRequest request)
{
    try
    {
        GetPlayerByIdValidator validator = new GetPlayerByIdValidator();
        ValidationResult valResult = validator.Validate(request);
        if (!valResult.IsValidated)
            return new GetPlayerByIdResponse
            {
                IsException = false,
                IsSuccess = false,
                Messages = valResult.Messages.ToArray()
            };
        return new GetPlayerByIdResponse
        {
            Player = PlayerRepository.GetPlayerById(request.PlayerId),
            IsException = false,
            IsSuccess = true,
            Messages = new string[] { "Operation successful" }
        };
    }
    catch (Exception ex)
    {
        return new GetPlayerByIdResponse
        {
            IsException = true,
            IsSuccess = false,
            Messages = new string[] { ex.Message }
        };
    }
}

This was the second part of our series, explaining adding the validation process to our service requests.

You can read the next part (Authentication / Authorization) here.

History

  • 28th February, 2022: Initial version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)