Friday, April 8, 2011

ASP.Net Validation and Sharepoint forms

Wanted to keep the topic simple and practical: 

"A validator is a control that checks one input control for a specific type of error condition and displays a description of that problem."

 ASP.Net is generous enough to provide the following types of validators:

RequiredFieldValidatorChecks that the user has entered or selected anything.
RegularExpressionValidatorChecks user input against a regular expression. This allows a wide variety of checks to be made and can be used for things like ZIP codes and phone numbers.
CompareValidatorCompares an input control to a fixed value or another input control. It can be used for password verification fields, for example. It is also possible to do typed date and number comparisons.
RangeValidatorMuch like CompareValidator, but can check that the input is between two fixed values.
CustomValidatorThis allows you to write your own code to take part in the validation framework.
 Here is the old article but still useful "How-to use different types of validators"

It's really must-to-know: Server-Side Validation Sequence
First load of the page - no validation takes place
Now, when a user clicks on a button or similar control, it goes back to the server and does a similar sequence. This is called the post-back sequence: 
And after the load of the page -the validation happens.

If this timing is not to your liking and you prefer to evaluate everything in Page_Load, you can do this by explicitly triggering the validation during this event by calling Page.Validate. After this has been called you can then check the result of Page.IsValid.
  protected override void OnLoad(EventArgs e)
       {
          
           base.OnLoad(e);
           Page.Validate();
       }

Using the custom validator, you can add the javascript to it to provide more interactivity to your page through ClientValidationFunction attribute

 A couple thoughts regarding Client-Side Validation
Security considerations. Someone can very easily take a page with script and disable or change it. You should not rely on script to stop bad data getting into to your system, only to provide more immediate feedback to your users. For this reason, if you are using a CustomValidator, you should not provide a client validation function without a corresponding server validation function.
The client-side validation occurs:
  -  The validation conditions are re-evaluated when a dependent field is changed, and the validator is made visible or invisible as appropriate.

 -  When the user pushes a button that has the CausesValidation property set to True(by default it set to True), the validators are all re-evaluated. If they are all valid, then the form is posted to the server.


There is a mini-API that you can use on the client to achieve various effects with your own client-side code.

NameTypeDescription
Page_IsValidBoolean variableIndicates whether the page is currently valid. The validation scripts keep this up to date at all times.
Page_ValidatorsArray of elementsThis is an array containing all of the validators on the page.
Page_ValidationActiveBoolean variableIndicates whether validation should take place. Set this variable to False to turn off validation programmatically.
isvalidBoolean propertyThis is a property on each client validator indicating whether it is currently valid. 

You can use the in javascript  Page_ClienValidate to check the current state of validators. It doesn't fire the Page.Validate(), it just checks the state.

Java script page validation Page_clientValidate()

Sharepoint Forms - Server side validation
    Since you have a sense of type of validation, let's focus how we can incorporate it into SharePoint form.
Let's begin with server side validation:
 1. Inherit the  form from your custom class (inherit your class from Microsoft.SharePoint.WebPartPages.WebPartPage):
   Inherits="Tour.SharePoint.Travel.Pages.WFTourPlan,Tour.SharePoint.Travel.Pages, Version=1.0.0.0, Culture=neutral, PublicKeyToken=a2604a0155bbaeb2" %> 
   public class WFTourPlan: WebPartPage
   {

   }
  2. Include in the form custom validation
   - on the page to check logic is not related to specific value in the specific field:
<asp:CustomValidator ID="ActionValidator" runat="server"
            ControlToValidate="" ErrorMessage="Tour is not available. Cannot complete the task."
            onservervalidate="TourValidator_ServerValidate"></asp:CustomValidator> 
  -  on the page for particular control:
  <td width="400px" valign="top" class="ms-formbody">
                                <ebtwc:EBTDropDownChoice id="LeaveType" FieldName="Leave_Type" ControlMode="Edit" runat="server" />
                                <asp:CustomValidator ID="LeaveTypeValidator" runat="server"
            ControlToValidate="LeaveType" ErrorMessage="Error Message"
            onservervalidate="LeaveTypeValidator_ServerValidate"></asp:CustomValidator>
                            </td>
(DropDownChoiceField can't be validated by default - it doesn't have the ValidationProperty, to overcome this limitation we just created our own control and added this property):
[ValidationProperty("Value")]
    public class EBTDropDownChoice : DropDownChoiceField
    {
    }


  3. In your custom class   implement the the method to validate:
 protected void LeaveTypeValidator_ServerValidate(object source, ServerValidateEventArgs args)
        {
            var listItem = SPContext.Current.ListItem;
            string errorMessage;
            args.IsValid=ScheduledAbsenceValidations.LeaveTypeValidation(listItem,out errorMessage);
            LeaveTypeValidator.ErrorMessage = errorMessage;
        }

Sharepoint Forms - Client side validation
  I'll show the most sophisticated scenario when you want to call server side validation through javascript when the button submit is clicked and based on the result validation - cancel submission or continue it
  This real world scenario has appeared when I wanted to control the SPD Workflow task submission - in case the specific items were deleted - block the tasks to be completed. I don't like idea if the user open the task first and then delete the items - the task can be completed and wf will continue.
  That leads me to implement asynchronous javascript call to get the validation result before the submit has been completed -to block the button.
1.Add to web.config :
 <SafeControl Assembly="System.Web.Extensions, Version=1.0.61025.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" Namespace="System.Web.UI" TypeName="*" Safe="True" />

2. On the form add the ScriptManager :
 <asp:ScriptManager ID='ScriptManager1' runat='server' EnablePageMethods='true' />
 

3. Add the script (if the form uses the web part -add it inside <xsl:stylesheet> , if the controls - wherever you feel you want it)
<script type='text/javascript'>
        var res;
   function gup( name )
{
  name = name.replace(/[\[]/,"\\\[").replace(/[\]]/,"\\\]");
  var regexS = "[\\?&]"+name+"=([^&#]*)";
  var regex = new RegExp( regexS );
  var results = regex.exec( window.location.href );
  if( results == null )
    return "";
  else
    return results[1];
}

          function GetMessage() {
            var id=gup( 'ID' );
            PageMethods.Message(id,OnGetMessageSuccess, OnGetMessageFailure);
        }
        function OnGetMessageSuccess(result, userContext, methodName) {  
           res=result;
          
        }
        function OnGetMessageFailure(error, userContext, methodName) {
            return alert(error.message);
        }
      
    </script>
4. Add static web method to the page class:
  [System.Web.Services.WebMethod]
            public static bool Message(string id)
        {
           var  id_ = Convert.ToInt32(id);
           var task = SPContext.Current.Web.Lists["Tasks"].GetItemById(id_);
           var arcase = new AtRiskCase(Convert.ToInt32(task["WorkflowItemId"].ToString()), SPContext.Current.Web);
           return (arcase.GetCorrectiveActions().Count != 0);
       }
           

5.Add the submit button:
 <input type="button" value="Submit" name="btnSubmit">
     <xsl:attribute name="onclick">
      GetMessage();
      if(res)
      {
    <xsl:value-of select="ddwrt:GenFireServerEvent('__commit;__redirectsource')" />
       }
     </xsl:attribute>
    </input>

Detailed explanation here: Validation on SharePoint Forms – Part Two
Regarding ddwrt:GenFireServerEvent read on here- webpart xslt extension functions

P.S When  you want to javascript to check the value in the controls on the form refer to :!PreSaveItem()

Additional reading:
        -  ASP.NET Validation in Depth
        - Custom SharePoint Edit Forms with Validation
           
Validate and be Happy!