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:
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.
public interface IBaseValidator
{
ValidationResult Validate(BaseRequest request);
}
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.
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.
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
};
}
}
}
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:
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 }
};
}
}
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 }
};
}
}
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 }
};
}
}
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