15 min read

(For more resources related to this topic, see here.)

Implementing the validation logic using the Repository pattern

The Repository pattern abstracts out data-based validation logic. It is a common misconception that to implement the Repository pattern you require a relational database such as MS SQL Server as the backend. Any collection can be treated as a backend for a Repository pattern. The only point to keep in mind is that the business logic or validation logic must treat it as a database for saving, retrieving, and validating its data. In this recipe, we will see how to use a generic collection as backend and abstract out the validation logic for the same.

The validation logic makes use of an entity that represents the data related to the user and a class that acts as the repository for the data allowing certain operations. In this case, the operation will include checking whether the user ID chosen by the user is unique or not.

How to do it…

The following steps will help check the uniqueness of a user ID that is chosen by the user:

  1. Launch Visual Studio .NET 2012. Create a new project of Class Library project type. Name it CookBook.Recipes.Core.CustomValidation.

  2. Add a folder to the project and set the folder name to DataModel.

  3. Add a new class and name it User.cs.

  4. Open the User class and create the following public properties:

    Property name

    Data type

    UserName

    String

    DateOfBirth

    DateTime

    Password

    String

    Use the automatic property functionality of .NET to create the properties. The final code of the User class will be as follows:

    namespace CookBook.Recipes.Core.CustomValidation { /// <summary> /// Contains details of the user being registered /// </summary> public class User { public string UserName { get; set; } public DateTime DateOfBirth { get; set; } public string Password { get; set; } } }

  5. Next, let us create the repository. Add a new folder and name it Repository.

  6. Add an interface to the Repository folder and name it IRepository.cs.

  7. The interface will be similar to the following code snippet:

    public interface IRepository { }

  8. Open the IRepository interface and add the following methods:

    Name

    Description

    Parameter(s)

    Return Type

    AddUser

    Adds a new user

    User object

    Void

    IsUsernameUnique

    Determines whether the username is already taken or not

    string

    Boolean

    After adding the methods, IRepository will look like the following code:

    namespace CookBook.Recipes.Core.CustomValidation { public interface IRepository { void AddUser(User user); bool IsUsernameUnique(string userName); } }

  9. Next, let us implement IRepository. Create a new class in the Repository folder and name it MockRepository.

  10. Make the MockRepository class implement IRepository. The code will be as follows:

    namespace CookBook.Recipes.Core.Data.Repository { public class MockRepository:IRepository { #region IRepository Members /// <summary> /// Adds a new user to the collection /// </summary> /// <param name="user"></param> public void AddUser(User user) { } /// <summary> /// Checks whether a user with the username already ///exists /// </summary> /// <param name="userName"></param> /// <returns></returns> public bool IsUsernameUnique(string userName) { } #endregion } }

  11. Now, add a private variable of type List<Users> in the MockRepository class. Name it _users. It will hold the registered users. It is a static variable so that it can hold usernames across multiple instantiations.

  12. Add a constructor to the class. Then initialize the _users list and add two users to the list:

    public class MockRepository:IRepository { #region Variables Private static List<User> _users; #endregion public MockRepository() { _users = new List<User>(); _users.Add(new User() { UserName = "wayne27", DateOfBirth = new DateTime(1950, 9, 27), Password = "knight" }); DateOfBirth = new DateTime(1955, 9, 27), Password = "justice" }); } #region IRepository Members /// <summary> /// Adds a new user to the collection /// </summary> /// <param name="user"></param> public void AddUser(User user) { } /// <summary> /// Checks whether a user with the username already exists /// </summary> /// <param name="userName"></param> /// <returns></returns> public bool IsUsernameUnique(string userName) { } #endregion }

  13. Now let us add the code to check whether the username is unique. Add the following statements to the IsUsernameUnique method:

    bool exists = _users.Exists(u=>u.UserName==userName); return !exists;

    The method turns out to be as follows:

    public bool IsUsernameUnique(string userName) { bool exists = _users.Exists(u=>u.UserName==userName); return !exists; }

  14. Modify the AddUser method so that it looks as follows:

    public void AddUser(User user) { _users.Add(user); }

How it works…

The core of the validation logic lies in the IsUsernameUnique method of the MockRespository class. The reason to place the logic in a different class rather than in the attribute itself was to decouple the attribute from the logic to be validated. It is also an attempt to make it future-proof. In other words, tomorrow, if we want to test the username against a list generated from an XML file, we don’t have to modify the attribute. We will just change how IsUsernameUnique works and it will be reflected in the attribute. Also, creating a Plain Old CLR Object (POCO) to hold values entered by the user stops the validation logic code from directly accessing the source of input, that is, the Windows application.

Coming back to the IsUsernameUnique method, it makes use of the predicate feature provided by .NET. Predicate allows us to loop over a collection and find a particular item. Predicate can be a static function, a delegate, or a lambda. In our case it is a lambda.

bool exists = _users.Exists(u=>u.UserName==userName);

In the previous statement, .NET loops over _users and passes the current item to u. We then make use of the item held by u to check whether its username is equal to the username entered by the user. The Exists method returns true if the username is already present. However, we want to know whether the username is unique. So we flip the value returned by Exists in the return statement, as follows:

return !exists;

Creating a custom validation attribute by extending the validation data annotation

.NET provides data annotations as a part of the System.ComponentModel. DataAnnotation namespace. Data annotations are a set of attributes that provides out of the box validation, among other things. However, sometimes none of the in-built validations will suit your specific requirements. In such a scenario, you will have to create your own validation attribute. This recipe will tell you how to do that by extending the validation attribute. The attribute developed will check whether the supplied username is unique or not. We will make use of the validation logic implemented in the previous recipe to create a custom validation attribute named UniqueUserValidator.

How to do it…

The following steps will help you create a custom validation attribute to meet your specific requirements:

  1. Launch Visual Studio 2012. Open the CustomValidation solution.

  2. Add a reference to System.ComponentModel.DataAnnotations.

  3. Add a new class to the project and name it UniqueUserValidator.

  4. Add the following using statements:

    using System.ComponentModel.DataAnnotations; using CookBook.Recipes.Core.CustomValidation.MessageRepository; using CookBook.Recipes.Core.Data.Repository;

  5. Derive it from ValidationAttribute, which is a part of the System. ComponentModel.DataAnnotations namespace. In code, it would be as follows:

    namespace CookBook.Recipes.Core.CustomValidation { public class UniqueUserValidator:ValidationAttribute { } }

  6. Add a property of type IRepository to the class and name it Repository.

  7. Add a constructor and initialize Repository to an instance of MockRepository. Once the code is added, the class will be as follows:

    namespace CookBook.Recipes.Core.CustomValidation { public class UniqueUserValidator:ValidationAttribute { public IRepository Repository {get;set;} public UniqueUserValidator() { this.Repository = new MockRepository(); } } }

  8. Override the IsValid method of ValidationAttribute. Convert the object argument to string.

  9. Then call the IsUsernameUnique method of IRepository, pass the string value as a parameter, and return the result. After the modification, the code will be as follows:

    namespace CookBook.Recipes.Core.CustomValidation { public class UniqueUserValidator:ValidationAttribute { public IRepository Repository {get;set;} public UniqueUserValidator() { this.Repository = new MockRepository(); } public override bool IsValid(object value) { string valueToTest = Convert.ToString(value); return this.Repository.IsUsernameUnique(valueToTest); } } }

    We have completed the implementation of our custom validation attribute. Now let’s test it out.

  10. Add a new Windows Forms Application project to the solution and name it CustomValidationApp.

  11. Add a reference to the System.ComponentModel.DataAnnotations and CustomValidation projects.

  12. Rename Form1.cs to Register.cs.

  13. Open Register.cs in the design mode. Add controls for username, date of birth, and password and also add two buttons to the form. The form should look like the following screenshot:

  14. Name the input control and button as given in the following table:

    Control

    Name

    Textbox

    txtUsername

    Button

    btnOK

    Since we are validating the User Name field, our main concern is with the textbox for the username and the OK button. I have left out the names of other controls for brevity.

  15. Switch to the code view mode. In the constructor, add event handlers for the Click event of btnOK as shown in the following code:

    public Register() { InitializeComponent(); this.btnOK.Click += new EventHandler(btnOK_Click); } void btnOK_Click(object sender, EventArgs e) { }

  16. Open the User class of the CookBook.Recipes.Core.CustomValidation project. Annotate the UserName property with UniqueUserValidator. After modification, the User class will be as follows:

    namespace CookBook.Recipes.Core.CustomValidation { /// <summary> /// Contains details of the user being registered /// </summary> public class User { [UniqueUserValidator(ErrorMessage="User name already exists")] public string UserName { get; set; } public DateTime DateOfBirth { get; set; } public string Password { get; set; } } }

  17. Go back to Register.cs in the code view mode.

  18. Add the following using statements:

    using System.ComponentModel; using System.ComponentModel.DataAnnotations; using CookBook.Recipes.Core.CustomValidation; using CookBook.Recipes.Core.Data.Repository;

  19. Add the following code to the event handler of btnOK:

    //create a new user User user = new User() { UserName = txtUsername.Text, DateOfBirth=dtpDob.Value }; //create a validation context for the user instance ValidationContext context = new ValidationContext(user, null, null); //holds the validation errors IList<ValidationResult> errors = new List<ValidationResult>(); if (!Validator.TryValidateObject(user, context, errors, true)) { foreach (ValidationResult result in errors) MessageBox.Show(result.ErrorMessage); } else { IRepository repository = new MockRepository(); repository.AddUser(user); MessageBox.Show("New user added"); }

  20. Hit F5 to run the application. In the textbox add a username, say, dreamwatcher. Click on OK. You will get a message box stating User has been added successfully

  21. Enter the same username again and hit the OK button. This time you will get a message saying User name already exists. This means our attribute is working as desired.

  22. Go to File | Save Solution As…, enter CustomValidation for Name, and click on OK.

    We will be making use of this solution in the next recipe.

How it works…

To understand how UniqueUserValidator works, we have to understand how it is implemented and how it is used/called. Let’s start with how it is implemented. It extends ValidationAttribute. The ValidationAttribute class is the base class for all the validation-related attributes provided by data annotations. So the declaration is as follows:

public class UniqueUserValidator:ValidationAttribute

This allowed us to make use of the public and protected methods/attribute arguments of ValidationAttribute as if it is a part of our attribute. Next, we have a property of type IRepository, which gets initialized to an instance of MockRepository. We have used the interface-based approach so that the attribute will only need a minor change if we decide to test the username against a database table or list generated from a file. In such a scenario, we will just change the following statement:

this.Repository = new MockRepository();

The previous statement will be changed to something such as the following:

this.Repository = new DBRepository();

Next, we overrode the IsValid method. This is the method that gets called when we use UniqueUserValidator. The parameter of the IsValid method is an object. So we have to typecast it to string and call the IsUniqueUsername method of the Repository property. That is what the following statements accomplish:

string valueToTest = Convert.ToString(value); return this.Repository.IsUsernameUnique(valueToTest);

Now let us see how we used the validator. We did it by decorating the UserName property of the User class:

[UniqueUserValidator(ErrorMessage="User name already exists")] public string UserName {get; set;}

As I already mentioned, deriving from ValidatorAttribute helps us in using its properties as well. That’s why we can use ErrorMessage even if we have not implemented it.

Next, we have to tell .NET to use the attribute to validate the username that has been set. That is done by the following statements in the OK button’s Click handler in the Register class:

ValidationContext context = new ValidationContext(user, null, null); //holds the validation errors IList<ValidationResult> errors = new List<ValidationResult>(); if (!Validator.TryValidateObject(user, context, errors, true))

First, we instantiate an object of ValidationContext. Its main purpose is to set up the context in which validation will be performed. In our case the context is the User object. Next, we call the TryValidateObject method of the Validator class with the User object, the ValidationContext object, and a list to hold the errors. We also tell the method that we need to validate all properties of the User object by setting the last argument to true. That’s how we invoke the validation routine provided by .NET.

Using XML to generate a localized validation message

In the last recipe you saw that we can pass error messages to be displayed to the validation attribute. However, by default, the attributes accept only a message in the English language. To display a localized custom message, it needs to be fetched from an external source such as an XML file or database. In this recipe, we will see how to use an XML file to act as a backend for localized messages.

How to do it…

The following steps will help you generate a localized validation message using XML:

  1. Open CustomValidation.sln in Visual Studio .NET 2012.

  2. Add an XML file to the CookBook.Recipes.Core.CustomValidation project. Name it Messages.xml. In the Properties window, set Build Action to Embedded Resource.

  3. Add the following to the Messages.xml file:

    <?xml version="1.0" encoding="utf-8" ?> <messages> <en> <message key="not_unique_user">User name is not unique</message> </en> <fr> <message key="not_unique_user">Nom d'utilisateur n'est pas unique</message> </fr> </messages>

  4. Add a folder to the CookBook.Recipes.Core.CustomValidation project. Name it MessageRepository.

  5. Add an interface to the MessageRepository folder and name it IMessageRepository.

  6. Add a method to the interface and name it GetMessages. It will have IDictionary<string,string> as a return type and will accept a string value as parameter. The interface will look like the following code:

    namespace CookBook.Recipes.Core.CustomValidation.MessageRepository { public interface IMessageRepository { IDictionary<string, string> GetMessages(string locale); } }

  7. Add a class to the MessageRespository folder. Name it

    XmlMessageRepository

  8. Add the following using statements:

    using System.Xml;

  9. Implement the IMessageRepository interface. The class will look like the following code once we implement the interface:

    namespace CookBook.Recipes.Core.CustomValidation.MessageRepository { public class XmlMessageRepository:IMessageRepository { #region IMessageRepository Members public IDictionary<string, string> GetMessages(string locale) { return null; } #endregion } }

  10. Modify GetMessages so that it looks like the following code:

    ublic IDictionary<string, string> GetMessages(string locale) { XmlDocument xDoc = new XmlDocument(); xDoc.Load(Assembly.GetExecutingAssembly().GetManifestResourceS tream("CustomValidation.Messages.xml")); XmlNodeList resources = xDoc.SelectNodes("messages/"+locale+"/ message"); SortedDictionary<string, string> dictionary = new SortedDictionary<string, string>(); foreach (XmlNode node in resources) { dictionary.Add(node.Attributes["key"].Value, node. InnerText); } return dictionary; }

Next let us see how to modify UniqueUserValidator so that it can localize the error message.

How it works…

The Messages.xml file and the GetMessages method of XmlMessageRespository form the core of the logic to generate a locale-specific message. Message.xml contains the key to the message within the locale tag. We have created the locale tag using the two-letter ISO name of a locale. So, for English it is <en></en> and for French it is <fr></fr>.

Each locale tag contains a message tag. The key attribute of the tag will have the key that will tell us which message tag contains the error message. So our code will be as follows:

<message key="not_unique_user">User name is not unique</message>

not_unique_user is the key to the User is not unique error message. In the GetMessages method, we first load the XML file. Since the file has been set as an embedded resource, we read it as a resource. To do so, we first got the executing assembly, that is, CustomValidation. Then we called GetManifestResourceAsStream and passed the qualified name of the resource, which in this case is CustomValidation.Messages.xml. That is what we achieved in the following statement:

xDoc.Load(Assembly.GetExecutingAssembly().GetManifestResourceStream( "CustomValidation.Messages.xml"));

Then we constructed an XPath to the message tag using the locale passed as the parameter. Using the XPath query/expression we got the following message nodes:

XmlNodeList resources = xDoc.SelectNodes("messages/"+locale+"/ message");

After getting the node list, we looped over it to construct a dictionary. The value of the key attribute of the node became the key of the dictionary. And the value of the node became the corresponding value in the dictionary, as is evident from the following code:

SortedDictionary<string, string> dictionary = new SortedDictionary<string, string>(); foreach (XmlNode node in resources) { dictionary.Add(node.Attributes["key"].Value, node.InnerText); }

The dictionary was then returned by the method. Next, let’s understand how this dictionary is used by UniqueUserValidator.


Subscribe to the weekly Packt Hub newsletter

* indicates required

LEAVE A REPLY

Please enter your comment!
Please enter your name here