Sunday, January 11, 2009

ASP.NET Multiple Validation Group Button

In ASP.NET validation controls, the Validation Group allows to break down the set of page validation controls into different units that can be executed independently from each other. Then, a single validation group can be specified at button level so a particular action is glued to a validation controls subset. There are some situations though, where you might be interested in binding a button to multiple validation groups, one of these situations is described in this feedback entry at Microsoft Connect site.

That was back in 2005 when the Validation Group concept was a brand new upcoming feature in .NET 2.0, which by that time was still in beta version. Well, despite the fact that guys from Microsoft seemed to be receptive to the suggestion, it's 2009, ASP.NET is in it 3.5 version and we all know that this feature hasn't quite made it :(. The workaround suggested is to call Page.Validate(group) in server-side but then it means that we'd need to ditch client-side validation which I'm not very happy about.

So let's pick up this suggestion and create a new asp.net button control that includes a coma delimited validation group list property.
 
public class MultipleValidationGroupButton: Button
{
  /// <summary>
  /// Gets or sets the validation group list
  /// comma delimited.
  /// </summary>
  /// <value>The validation group list.</value>
  /// <remarks>
  /// Empty string represents validation controls that
  /// don't define validation group property
  /// </remarks>
  public string ValidationGroupList
  { 
      get 
      { 
          return ViewState["ValidationGroupList"] as string; 
      }
      set 
      { 
          ViewState["ValidationGroupList"] = value; 
      }
  }
 
  protected override void OnPreRender(EventArgs e)
  {
      if (!String.IsNullOrEmpty(this.ValidationGroupList))
      {
          this.CausesValidation = false;
          string script = String.Format("ValidateGroups('{0}','{1}');", 
              this.ClientID, this.ValidationGroupList);
          if (Page.IsAsyncPostBack())
          {
              ScriptManager.RegisterStartupScript(Page, typeof(Page), this.ClientID, script, true);
          }
          else
          {
              Page.ClientScript.RegisterStartupScript(typeof(Page), this.ClientID, script, true);
          }
      }
     
      base.OnPreRender(e);
  }
 
  /// <summary>
  /// Validates the groups.
  /// </summary>
  public void ValidateGroups()
  {
      if (!String.IsNullOrEmpty(this.ValidationGroupList))
      {
          string[] list = this.ValidationGroupList.Split(',');
          foreach (string item in list)
          {
              Page.Validate(item);
          }
      }
  }
}

The server side implementation is pretty straightforward: we check if the ValidationGroupList propety has been set and if so we disable the regular ASP.NET validation mechanism (CausesValidation=false) and we register the call to the script that will actually do the job (For the client-side registration I use the Page.IsAsyncPostBack partial method already implemented while creating my jQuery ConfirmationButton). The purpose of ValidateGroups method is to remove the need of hardcoding the validation groups in code behind to enforce server side validation, so instead of doing this:

protected void Button_Click(object sender, EventArgs e)
{
   Page.Validate("Group1");
   Page.Validate("Group2");
   Page.Validate("Group3"); 
   
   if (Page.IsValid)
   { 
       //Button Actions
   }
}

We don’t need to scatter our validation groups in both markup and code-behind because we can do this:

protected void Button_Click(object sender, EventArgs e)
{
   this.Button.ValidateGroups();
   if (Page.IsValid)
   { 
       //Button Actions
   }
}

And to end with, server-side wise, this is how we define our button in ASP.NET markup language.

<cc:MultipleValidationGroupButton ID="btn" runat="server"
ValidationGroupList="Group1,Group2" onclick="Button_Click" Text="Click" />

Now it's time to code the javascript function that performs validation on button click. For the implementation, I've used some of the syntactic sugar that jQuery offers but as I'm not using a great deal of things of it you could potentially rewrite this function with plain javascript without much complexity, although I must say that after you start using jQuery (if you haven't yet), you will never want to go back to plain javascript again!

function ValidateGroups(buttonId, validationGroupList) {
    //Bind the logic to the specified button with jQuery
    $("#" + buttonId).click(function(e) {
        var list = validationGroupList.split(',');
        
        //Loop through the entire set of page validators
        $.each(Page_Validators, function() {
        if ((this.validationGroup && ExistsGroup(list, this.validationGroup)) 
            ||(!this.validationGroup && ExistsGroup(list, ''))) {
                ValidatorValidate(this, this.validationGroup);
                Page_IsValid = Page_IsValid && this.isvalid;
            }
        });
        
        //Reflect validation in ValidationSummary's if there are any in the page
        $.each(list, function() {
            ValidationSummaryOnSubmit(this);
        });
        
        //If Page_IsValid is false the execution flow will be interrupted
        return Page_IsValid;
    });
}
 
function ExistsGroup(list, group) {
    var found = false;
    for (i = 0; i < list.length; i++) {
        if (list[i] == group) {
            found = true;
            break;
        }
    }
    return found;
}

And that’s it, the implementation is ready but before we wrap it up, there are a few things that are worth mentioning: The second clause in the if statement is there to support the scenario where we would potentially want to combine “ungrouped” with “grouped” validation controls and in that situation we’d define our control like this:

<cc:MultipleValidationGroupButton ID="btn" runat="server"
ValidationGroupList=",Group1" onclick="Button_Click" Text="Click" />

Finally, you may have noticed that I've used extensively the ASP.NET validation client-side API (ValidatorValidate, Page_Validators, etc), if you want to have more details about this API I recommend you using our friend Reflector to get acquainted to this library (System.Web assembly, Resources folder, WebUIValidation.js resource), that helped me a lot.

Hope this helps!

UPDATE: I've uploaded the code for this post here.
I've created a library project with ASP.NET custom controls for Button, ImageButton and LinkButton; the associated javascript code is embedded in that project (apart from jQuery js file).

kick it on DotNetKicks.com

16 comments:

  1. great job, many thanks for this! I've created an alternative solution all client-side, dinamically changing the validation group of controls that have a particular css class:

    http://www.gherarducci.it/2008/10/29/validation-group-multipli-una-soluzione-in-javascript/
    unfortunately it's in italian.

    But I think your solution is better and more secure.

    ReplyDelete
  2. Hi Carlo,

    Actually I spent one year in Milano in a Erasmus program so I'm fine with Italian :)

    I appreciate a lot your approach as well, although it seems to be a little bit more intrusive than mine (modifying controls' ValidationGroup)

    I'll stay tuned to your blog (e' molto piu bello del mio!); I've seen that you are talking about interesting stuff

    Javi

    ReplyDelete
  3. molto bene allora! :D
    You're right about my solution. I think I'm going to adopt yours in my current project.
    And I'm going to follow your blog too :)
    ciao!

    Carlo.

    ReplyDelete
  4. Looks good - quick question, though... I noticed there's no license or copyright info mentioned in the post or in the downloaded sources/demo. You *are* okay with people using your work in their projects, yes?

    Not a commercial release of your work or anything like that, but using your control library/copying your types into an internal library - that sort of stuff.

    ReplyDelete
  5. It's all yours, you can do with it whatever you want.. but don't blame me if it doesn't work as expected!

    ReplyDelete
  6. I wish I saw this before I wrote our multiple validators. I did something similar to what you did but I had extra requirements to color the background of the validated control red. So more work but my method actually works beautifully server side and client side.

    But I kind of like your approach of having a multiple validation group button. Only thing I wish it can do is auto validate the groups on the server side instead of manually doing Page.Validate("..."). Not really sure how one would go do this but that would save us a line or so of code to write.

    ReplyDelete
  7. Hi.
    I tried to use your solution but it seems to be not working in special case. Using your uploaded example I did following test:
    1. I open Default.aspx page and click "Multiple Validation Button" -> Group1, Group2 and No Group validators are Red what is ok
    2. I type anything into 1st, 2nd and 4rd TextBox and click the button again -> Now page post back what is ok again
    3. But now I type something into 3rd TextBox, then move focus out of the box, then delete what I’ve typed and move focus out again -> Group3 validator becomes Red and when I click MVP button it doesn’t submit the Page what is not ok (inputs 1,2 and 4 are filled with data and MVP should not validate 3rd input)
    Is there any way to overcome this issue?

    Regards.

    ReplyDelete
  8. Ok, I found a simple solution to my issue. It’s enough to set Page_IsValid to true before validationgroups are validated in ValidateGroups(buttonId, validationGroupList):
    (...)
    var list = validationGroupList.split(',');
    Page_IsValid = 1;
    //Loop through the entire set of page validators
    $.each(Page_Validators, function() {
    (...)
    Does it look correct?

    ReplyDelete
  9. Hi hocus,

    You're right, the variable Page_IsValid has to be reseted before the rules are evaluated so previous states don't affect the current one. I've already updated the code in the downloadable file, thanks for the patch!

    ReplyDelete
  10. Hello!

    Thanks for great sollution!
    However, there is a problem with displaying validation summaries. It comes from the following lines in ValidationSummaryOnSubmit(validationGroup) method.

    for (sums = 0; sums < Page_ValidationSummaries.length; sums++) {
    summary = Page_ValidationSummaries[sums];
    summary.style.display = "none";

    It seems only the ValidationSummary for last ValidationGroup is displayed. If it exist.

    I think the solution might be to see if Group ValidationSummary exists and to show it.

    Best regards,
    Andrija Frlan

    ReplyDelete
  11. It's a great solution but i got the same issue as the user above with ValidationSummary and found it was caused by MS code ValidationSummaryOnSubmit method, so I just made my own override on the method and got it to work.

    ReplyDelete
  12. Great post Javi, i have a page with multiple sections for registration. i have validation summery for each group,my page have only one button control. Error meassage will shown only for unfilled section using validation summery. help me on this.

    ReplyDelete
  13. Thanks Javi. This is Satish from Sapient Nitro. Really, It helped a lot.
    Thanks again. :-)
    /Satish Marreddy

    ReplyDelete
  14. Javi, I like your solution. But to make it complete, I would be nice to also have a ValidationSummary control that can show messages from multiple ValidationGroups?

    Hans

    ReplyDelete
  15. Good job, but i have a problem with displaying validation summary, any solution plz?

    ReplyDelete