Showing posts with label Reusable Control. Show all posts
Showing posts with label Reusable Control. Show all posts

Tuesday, 24 January 2012

ASP.NET MVC 3 Custom Validation using Data Annotations

ASP.NET MVC 3 System.ComponentModel.DataAnnotations package provides a vast range of Data Annotations attribute, but there are certain scenarios where we need something which is very specific to our business requirements and we need to implement our own Validation Attributes. ASP.NET being an extensible framework which makes it possible for the developer to add user defined custom business validations using custom data annotations.

In this article I am going to demonstrate Custom Validation using ASP.NET Data Annotations.

First let us take a scenario where I need to create a form where I have to Draw a graph based on Date Range. In this form I have a graph control and From and To Date. Using the ‘Required’ DataAnnotation I can check if the Date is entered or not, Date Format can be validated using the RegularExpression Validator, but to validate some of the specific case like Comparing the From Date and To Date, Minimum date should not less than 2005, etc we need to create our Custom Validation Classes.

To start with this example lets first…

  1. Create a Telerik MVC 3 Web Application(Razor) from New Project. This is optional you can also use normal ASP.NET MVC 3 Web Project too.
  2. In the model folder create a Class ‘DateValidationAttribute.cs’ here we have to suffix the class with Attribute as this is a standard which is recognized by our ASP.NET MVC Application for any class to be used for DataAnnotation. Here the ‘Attribute’ suffix is suppressed and only the first part ‘DateValidation’ is used in the View Model Class as Data Annotation.
  3. Add a reference to System.ComponentModel.DataAnnotations.dll to our Project.
  4. Add a namespace reference of System.ComponentModel.DataAnnotation class in our DateValidationAttribute.cs class.
  5. In our DateValidationAttribute class implement ValidationAttribute base class, ValidationAttribute Class is the base class for all the System defined and user defined Custom Validation attribute classes.

With the steps given above our initial class will look something similar to the one below.

   1: public class DateValidationAttribute: ValidationAttribute



   2: {



   3:     public DateValidationAttribute()



   4:     { }



   5:     



   6: }




In this example I am creating a class named DateValidationAttribute.cs which extends base class ‘ValidationAttribute’  of System.ComponentModel.DataAnnotation package.



Now to provide the custom Validation Logic in this class we have to override the IsValid method of ValidationAttribute class and to make this class more generic, I have created a enum, which tells us what is the type of the validation I am going to use my DateValidation class.





   1: public enum ValidationType



   2: {



   3:     RangeValidation,



   4:     Compare



   5: }






And before we override the IsValid method we have to pass the initialization parameters to our DateValidationAttribute class constructor with the fromDate, toDate ValidationType, defaultErrorMessage and basePropertyName, here propertyNameToCompare is the property name of the ‘FromDate’.  We are going to need ‘FromDate’ to compare with ToDate and Validate the Dates.





   1: private ValidationType _validationType;



   2: private DateTime? _fromDate;



   3: private DateTime _toDate;



   4: private string _defaultErrorMessage;



   5: private string _propertyNameToCompare;



   6:  



   7: public DateValidationAttribute(ValidationType validationType, string message, string compareWith = "", string fromDate= "")



   8: {



   9:     _validationType = validationType;



  10:     switch (validationType)



  11:     {



  12:         case ValidationType.Compare:



  13:             {



  14:                 _propertyNameToCompare = compareWith;



  15:                 _defaultErrorMessage = message;



  16:                 break;



  17:             }



  18:         case ValidationType.RangeValidation:



  19:             {



  20:                 _fromDate = new DateTime(2000,1,1);



  21:                 _toDate = DateTime.Today;



  22:                 _defaultErrorMessage = message;



  23:                 break;



  24:             }



  25:  



  26:     }



  27: }




Now we are ready to override the IsValid method where we are going to implement the actual business rule validation. I have used Switch statement here to check which Validation Type the user is requested for.





   1: protected override ValidationResult IsValid(object value, ValidationContext validationContext)



   2: {



   3:     switch (_validationType)



   4:     {



   5:         case ValidationType.Compare:



   6:             {



   7:                 var baseProperyInfo = validationContext.ObjectType.GetProperty(_propertyNameToCompare);



   8:                 var startDate = (DateTime)baseProperyInfo.GetValue(validationContext.ObjectInstance, null);



   9:  



  10:                 if(value!=null)



  11:                 {



  12:                     DateTime thisDate = (DateTime)value;



  13:                     Type classType = typeof(TelerikMvcCustomValidationApp.Models.AccountModel);



  14:                     PropertyInfo methodInfo = classType.GetProperty(_propertyNameToCompare);



  15:                     DisplayAttribute displayAttr = (DisplayAttribute)Attribute.GetCustomAttribute(methodInfo, typeof(DisplayAttribute));



  16:                     if (thisDate <= startDate)



  17:                     {



  18:                         string message = string.Format(_defaultErrorMessage, validationContext.DisplayName, displayAttr.Name);



  19:                         return new ValidationResult(message);



  20:                     }



  21:                 }



  22:                 break;



  23:             }



  24:         case ValidationType.RangeValidation:



  25:             {



  26:                //Range Validation Logic Here



  27:                 break;



  28:             }



  29:        



  30:     }



  31:     return null;



  32: }



  33:     }






IsValid method above returns the ValidationResult which is a member of System.ComponentModel.DataAnnotation. Here ValidationResult represents a container for the results of a validation request. Now with these pieces of code our DateValidationAttribute class is ready to consume. I understand that there are lots of tight coupling with the implementation class with this Validation component, this code is just for indicative usage of the Custom Data Annotation an extensibility feature provided by ASP.NET. In actual this class may require few changes.



Now lets come to my model class where I am going to use this as Data Annotation Attribute. In this example I have created a AccountModel.cs class and declared just two properties one is ToDate and another is FromDate for this example.





   1: public class AccountModel



   2: {



   3:     [Display(Name = "From Date")]



   4:     [Required]



   5:     public DateTime FromDate { get; set; }



   6:  



   7:     [Display(Name = "To Date")]



   8:     [Required]



   9:     [DateValidation(ValidationType.Compare, "Selected dates should be Less than From Date.", compareWith: "FromDate")]



  10:     public DateTime ToDate { get; set; }



  11: }






In this example I have used Telerik controls for DatePicker control. Telerik controls for MVC can be installed from Tools—>Extension Manager—> Online Templates. This Date controls I have created as a partial view in Views—>Shared folder, so that this can be used across my applications.





   1: @model TelerikMvcCustomValidationApp.Models.AccountModel



   2: @{



   3:     ViewBag.Title = "Home Page";



   4: }



   5: <form action="/Home/DateValidate" method="post">



   6: <h2>@ViewBag.Message</h2>



   7: <fieldset>



   8:     <span>



   9:         @Html.LabelFor(m => m.FromDate)



  10:         @Html.Telerik().DatePickerFor(m => m.FromDate)



  11:         @Html.ValidationMessageFor(m => m.FromDate)



  12:     </span>



  13:     <br />



  14:     <span>



  15:         @Html.LabelFor(m => m.ToDate)



  16:         @Html.Telerik().DatePickerFor(m => m.ToDate)



  17:         @Html.ValidationMessageFor(m => m.ToDate)



  18:     </span>



  19: </fieldset>



  20: <input type="submit" value="Validate Date" name="btnValidate" id="btnValidate" />



  21: </form>




In the code above I am keeping the form method as post and Action to my DateValidation Action of HomeController. When the user clicks the Submit button the form Post the result to Controller and triggers the Validation logic defined in our Custom Validation Logic.



Now to embed this partial view into my Index page I have to write just one line as below in ‘Index.cshtml’ page.





@Html.Partial("DateValidate")



And that’s it in the View Side, if you are already familiar with MVC and Razor this code will be very simple for you to understand.

Now coming to my Home Controller, where I have created an Action method which Provides custom action on Validation failure and Success.





public ActionResult DateValidate(AccountModel model)



{



    if (!ModelState.IsValid)



        return View(model);



    return RedirectToAction("About");



}






Just to keep this simple I am redirecting the user to About Page of my ASP.NET MVC Application, which is already provided. And for Validation failure I am keeping the user in the same page with the validation message displayed. Now lets assemble all the pieces of codes and test it. I am entering the invalid data where I am keeping the From Date more than To Date, with this I am getting the result as given in the screen below.



image



And for the built in Required Field Validation



image



If you can look into the code above for DateValidationAttribute class I have also provided an additional Validation for Date Range Validation, this can be used to restrict the user to enter dates in some Range based on the Business requirements.  I am leaving it for you to implement. In order to Implement the same attribute multiple times we have to just fine tune some lines of my code as below.



image



But you can see above by default you cannot use the same Data Annotation Attribute multiple times in the same Property. To Get this working you have to provide additional Attribute to our DateValidationAttribute Class as below.



image



With AllowMultiple=true we can use the same DataAnnotation attribute multiple times in the same Property.



This code is just an extract of the actual implementation which is more structured and bigger. But I hope this example will give you a quick start on how to use the Custom Validation Data Annotation attributes. For further reading you can follow the links below from MSDN.





You can also download the sample code from here.

Share:

Saturday, 1 November 2008

TwoSelect User Control, Moving Items between ListBox Controls - Part 2

As I promised in this post I am showing you how to implement TwoSelect Control, which I created in my Previous Post

You can download the Complete Source with implementation from here

In this post to get the Available Users and Added Users I am fetching from DataBase using LINQ to SQL, but you can also use either sql or Stored Procedure as per your convenience.

So the first step in Consuming this control in our code is by Creating the Tables, whose structure will look something like this

image

In the User Table I have stored all the User Information, in Group Table I have stored all the Group Details and in UserGroup Table I have related the User and Groups.

In the current project I have not given any screen to enter the User Details or Group Details, as this was out of scope of the current topics, so you can insert the User and Group directly in the Database Tables.

Secondly, Now coming to .NET Code, first I have created a LINQ to SQL and named this as UserGroupData.dbml, and added all the three tables into the designer.

After this I have added the Class to Handle the Data from LINQ to SQL and pass the data to Presentation Layer.

 

In the below given code snippet I am Retrieving all the User who is already present in the Group, this function accepts the groupId as input parameter and return IDataReader to the Presentation Layer, so that we can pass the DataReader directly to the DataSourceAdded Property of the TwoSelect Control and Bind the Data to the Added Users ListBox.

Code Snippet

1:   /// <summary>
2:  /// Gets the group user.
3:  /// </summary>
4:  /// <param name="groupId">The group id.</param>
5:  /// <returns>IDataReader</returns>
6:  public IDataReader GetGroupUser(int groupId)
7:   {
8:   UserGroupDataDataContext data = new UserGroupDataDataContext();
9:   var user = from Grp in data.Groups
10:   join UsrGrp in data.UserGroups on Grp.GroupId equals UsrGrp.GroupId
11:   join Usr in data.Users on UsrGrp.UserId equals Usr.UserId
12:   where Grp.GroupId == groupId
13:   orderby Usr.UserId
14:   select new
15:  {
16:   UserName = Usr.UserName,
17:   GroupName = Grp.GroupName,
18:   UserId = Usr.UserId
19:   };
20:   IDbCommand command = data.GetCommand(user);
21:   command.Connection = data.Connection;
22:   if(command.Connection.State == ConnectionState.Closed)
23:   command.Connection.Open();
24:   IDataReader reader = command.ExecuteReader(CommandBehavior.CloseConnection);
25:   return reader;
26:   }



 




In the below given code snippet I am Retriving all the User who not present in the Group, this function accepts the groupId as input parameter and return IDataReader to the Presentation Layer, so that we can pass the DataReader directly to the DataSourceAvailable Property of the TwoSelect Control and Bind the Data to the Available Users ListBox.






Code Snippet

1:   /// <summary>
2:  /// Users not in the selected group.
3:  /// </summary>
4:  /// <param name="groupId">The group id.</param>
5:  /// <returns>IDataReader</returns>
6:  public IDataReader UserNotInGroup(int groupId)
7:   {
8:   UserGroupDataDataContext data = new UserGroupDataDataContext();
9:   var user = from Grp in data.Groups
10:   join UsrGrp in data.UserGroups on Grp.GroupId equals UsrGrp.GroupId
11:   join Usr in data.Users on UsrGrp.UserId equals Usr.UserId
12:   where Grp.GroupId != groupId
13:   orderby Usr.UserId
14:   select new
15:  {
16:   UserName = Usr.UserName,
17:   GroupName = Grp.GroupName,
18:   UserId = Usr.UserId
19:   };
20:   IDbCommand command = data.GetCommand(user);
21:   command.Connection = data.Connection;
22:   if(command.Connection.State == ConnectionState.Closed)
23:   command.Connection.Open();
24:   IDataReader reader = command.ExecuteReader(CommandBehavior.CloseConnection);
25:   return reader;
26:   }


 



The function below will get all the Group names and Bind the Group DropDown, to display all the Groups so that we can select the Groups whose user I want to manage.






Code Snippet

1:   /// <summary>
2:  /// Gets all group.
3:  /// </summary>
4:  /// <returns>IDataReader</returns>
5:  public IDataReader GetAllGroup()
6:   {
7:   UserGroupDataDataContext data = new UserGroupDataDataContext();
8:   var groups = from Grp in data.Groups
9:   orderby Grp.GroupName
10:   select new
11:   {
12:   GroupId = Grp.GroupId,
13:   GroupName = Grp.GroupName
14:   };
15:   IDbCommand command = data.GetCommand(groups);
16:   command.Connection = data.Connection;
17:   if (command.Connection.State == ConnectionState.Closed)
18:   command.Connection.Open();
19:   IDataReader reader = command.ExecuteReader(CommandBehavior.CloseConnection);

20:   return reader;
21:   }


 


This function will update the DataBase once we have selected the Users in a particular Group.





Code Snippet

1:   /// <summary>
2:  /// Saves the group user.
3:  /// </summary>
4:  /// <param name="group">The group.</param>
5:  public void SaveGroupUser(List<UserGroup> usrGroup)
6:   {
7:   UserGroupDataDataContext data = new UserGroupDataDataContext();

8:   //first delete all the existing user data from User Group
9:  foreach (UserGroup match in usrGroup)
10:   {
11:   var deleteUserGroup = from userGroups in data.UserGroups
12:   where userGroups.UserId == match.UserId
13:   select userGroups;
14:   foreach (var userGroup in deleteUserGroup)
15:   data.UserGroups.DeleteOnSubmit(userGroup);

16:   data.SubmitChanges();
17:   }

18:   //MISSING : Logic to implement Save new Data related to User and Group
19: 
20:   }


 



The same things you can do without using the LINQ to SQL either by writing the Stored Procedure or using the SQL Queries.



I am giving below the sample stored procedure which can replace the function GetGroupUser and GetUserNoInGroup



  • GetGroupUser

   1:  SET ANSI_NULLS ON

   2:  GO

   3:  SET QUOTED_IDENTIFIER ON

   4:  GO

   5:  CREATE PROCEDURE [dbo].[GetGroupUser]

   6:      

   7:      (

   8:      @GroupId int

   9:      )

  10:      

  11:  AS

  12:      SET NOCOUNT ON 

  13:      

  14:      SELECT 

  15:          Usr.UserName, 

  16:          Grp.GroupName,

  17:          Usr.UserId 

  18:      From  

  19:          GROUPS Grp

  20:      INNER JOIN 

  21:          UserGroup UsrGrp

  22:      ON 

  23:          Grp.GroupId = UsrGrp.GroupId

  24:      INNER JOIN USERS Usr ON

  25:          UsrGrp.UserId = Usr.UserId

  26:      WHERE 

  27:          Grp.GroupId=@GroupId

  28:      ORDER BY Usr.UserId

  29:      

  30:      RETURN

 



  • GetUserNotInGroup


   1:  SET ANSI_NULLS ON

   2:  GO

   3:  SET QUOTED_IDENTIFIER ON

   4:  GO

   5:  ALTER PROCEDURE [dbo].[UserNotInGroup]

   6:      

   7:      (

   8:      @GroupId int

   9:      )

  10:      

  11:  AS

  12:      SET NOCOUNT ON

  13:      SELECT 

  14:          Usr.UserName,

  15:          Usr.UserId, 

  16:          Grp.GroupName 

  17:      From  

  18:          GROUPS Grp

  19:      INNER JOIN 

  20:          UserGroup UsrGrp

  21:      ON 

  22:          Grp.GroupId = UsrGrp.GroupId

  23:      INNER JOIN USERS Usr ON

  24:          UsrGrp.UserId = Usr.UserId

  25:      WHERE 

  26:          Grp.GroupId!=@GroupId

  27:      ORDER BY Usr.UserId

  28:      

  29:      RETURN



 


 



Third, I have included the UserControl in my Project, and added in Default.asp page simply by Drag and Drop.




Also I have added one DropDown Control to get the List of all the Groups and Added a Save Button to Save the Changes back to the database.




After designing the Page will look something similar to this



image


 

 

Now in Code Behind I tried to implement only the logic which is related to the TwoSelect User controls,




Code Snippet

1:   public partial class _Default : System.Web.UI.Page
2:  {
3:   UserData data = new UserData();

4:   private int GetGroupId
5:   {
6:   get { return Convert.ToInt32(ddlGroup.SelectedValue); }
7:   }
8:   /// <summary>
9:  /// Handles the Load event of the Page control.
10:  /// </summary>
11:  /// <param name="sender">The source of the event.</param>
12:  /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
13:  protected void Page_Load(object sender, EventArgs e)
14:   {
15:   if (!IsPostBack)
16:   {
17:   ddlGroup.DataSource = data.GetAllGroup();
18:   ddlGroup.DataTextField = "GroupName";
19:   ddlGroup.DataValueField = "GroupId";
20:   ddlGroup.DataBind();
21:   ListItem item = new ListItem();
22:   item.Text = "Please Select";
23:   item.Value = "0";
24:   ddlGroup.Items.Insert(0,item);
25:   }
26:   }

27:   protected void ddlGroup_SelectedIndexChanged(object sender, EventArgs e)
28:   {
29:   //Bind the User Control Available List Box
30:  uclTwoSelect.DataSourceAvailable = data.GetGroupUser(this.GetGroupId);
31:   uclTwoSelect.DataTextFieldAvailable = "UserName";
32:   uclTwoSelect.DataValueFieldAvailable = "UserId";

33:   //Bind the User Control User Already Added List Box
34:  uclTwoSelect.DataSourceAdded = data.UserNotInGroup(this.GetGroupId);
35:   uclTwoSelect.DataTextFieldAdded = "UserName";
36:   uclTwoSelect.DataValueFieldAdded = "UserId";

37:   uclTwoSelect.BindControl();
38:   }

39:   /// <summary>
40:  /// Saves the User associated with the Groups.
41:  /// </summary>
42:  /// <param name="sender">The source of the event.</param>
43:  /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
44:  protected void btnSave_Click(object sender, EventArgs e)
45:   {
46:   UserGroup user = new UserGroup();
47:   List<UserGroup> group = new List<UserGroup>();
48:   ListItemCollection userGroup = uclTwoSelect.AddedItems;
49:   foreach (ListItem item in userGroup)
50:   {
51:   user.GroupId = Convert.ToInt32(ddlGroup.SelectedValue);
52:   user.UserId = Convert.ToInt32(item.Value);

53:   group.Add(user);
54:   }

55:   data.SaveGroupUser(group);
56:   }
57:   }



You can see in the code above I am binding the DropDown List of the Group and on SelectedIndexChanged even of the DropDown I am Binding the twoSelect Controls Data, with the DataSourceAvailable and DataSourceAdded Properties.



Sorry for the Code Formatting Guys, but you can always refer my asp.net weblog for my neat and clear code formatting.



This is just the one of the implementation you can download the Control and its implementation code from here and Enhance the Control.


 


References


http://www.west-wind.com/WebLog/posts/141435.aspx


http://msdn.microsoft.com/en-us/library/system.web.ui.webcontrols.listbox.aspx


http://msdn.microsoft.com/en-us/library/fb3w5b53(VS.85).aspx


 


Thanks


Brij Mohan

Share: