posts - 216, comments - 175, trackbacks - 68

My Links

News




I am a Microsoft Certified Application Developer MCAD Chartered Member (C# .Net) and born in Bangladesh.
I work for Ocean Informatics Pty Ltd as a Senior Developer - Analyst.
I am also co-founder and core developer of Pageflakes www.pageflakes.com
and most recently created SmartCodeGenerator

My Articles
Flexible and Plugin based .Net Application..
Mass Emailing Functionality with C#, .NET 2.0, and Microsoft® SQL Server 2005 Service Broker'
Write your own Code Generator or Template Engine in .NET
Smart Code Generator .NET: Usage Overview
Smart Code Generator .NET: Architectural Overview
Smart Code Generator .NET: using with NAnt and Cassini

Archives

Free Programming Language Training

Tuesday, June 17, 2008

ASP.NET tips, Making Custom Validators work in Partial Rendering mode.

Introduction

There are many situations where we need to identify if partial rendering is supported in a page, especially when a control uses javascript, to get the control work in partial rendering mode, the script needs to be registered using a ScriptManager Type instead. A classic example will be Validators.

The ASP.NET Page class exposes the Validators property, which is a list of all the IValidator types on the page. A page keeps track of its validators, and registers a javascript array of validators automatically to the page. Example, When we add 3 RequiredFieldValidator in a page the following javascript Array will be automatically generated and added in our page automatically during the page load. 

Page_Validators = new Array(document.getElementById("RequiredFieldValidator1"),
document.getElementById("RequiredFieldValidator2"),
document.getElementById("RequiredFieldValidator3"));

The ASP.NET Page also registers couple of other script which eventually hooks up different events ( onclick, onkeypress, onchange, onblur ) to the the target control (ControlToValidate), to some predefined javascript functions that resides in WebUIValidation.js file. So when we add a validator in our Page we also notice the following script is automatically added. [WebUIValidation.js ships with ASP.NET and resides in the following folder "/aspnet_client/system_web/<version>/WebUIValidation.js".]

<script type="text/javascript">
<!--
var Page_ValidationActive = false;
if (typeof(ValidatorOnLoad) == "function") {
ValidatorOnLoad();
}

function ValidatorOnSubmit() {
if (Page_ValidationActive) {
return ValidatorCommonOnSubmit();
}
else {
return true;
}
}
// -->
</script>

ValidatorOnLoad plays the big role of hooking up the the events mentioned above, and here is a code snippet from this function,

for (i = 0; i < Page_Validators.length; i++) {
val = Page_Validators[i];
if (typeof(val.evaluationfunction) == "string") {
eval("val.evaluationfunction = " + val.evaluationfunction + ";");
}
...

if (typeof(val.controltovalidate) == "string") {
ValidatorHookupControlID(val.controltovalidate, val);
}
...
}

keen eyes may have already noticed the val.evaluationfunction property, yes every validators needs to have this property for it to work properly under the ASP.NET validation framework. Custom validators takes advantage of this property to point to custom js functions. Custom validator developers normally use RegisterExpandoAttribute method to register this attribute.

protected override void AddAttributesToRender(System.Web.UI.HtmlTextWriter writer)
{
   base.AddAttributesToRender(writer);
   if (this.RenderUplevel)
   {
      string clientID = this.ClientID;
      Page.ClientScript.RegisterExpandoAttribute(clientID, "evaluationfunction", "EntryValidatorEvaluateIsValid");
   }
}

Problem
When I used Update Panel with partial rendering enabled the Page.ClientScript.RegisterExpandoAttribute did not work for me. My validators always stopped working after the first postback, which was performed via partial rendering and triggering. I found the "evaluationfunction" in the javascript to be undefined.

Solution
I started looking under the hood, and soon discovered, that the ASP.NET Validators that ships out of the box, ( eg. RangeValidator, RequiredFieldValidator ) uses a different internal method "AddExpandoAttribute" to register the property. Here is a code snippet from the RangeValidator.

protected override void AddAttributesToRender(HtmlTextWriter writer)
{
    base.AddAttributesToRender(writer);
    if (base.RenderUplevel)
    {
        string clientID = this.ClientID;
        HtmlTextWriter writer2 = base.EnableLegacyRendering ? writer : null;
        base.AddExpandoAttribute(writer2, clientID, "evaluationfunction", "RangeValidatorEvaluateIsValid", false);  
        ...
    }
}

and code snippet from BaseValidator, the internal method AddExpandoAttribute.

internal void AddExpandoAttribute(HtmlTextWriter writer, string controlId, string attributeName, string attributeValue, bool encode)
{
    AddExpandoAttribute(this, writer, controlId, attributeName, attributeValue, encode);
}

After digging further I realized, AddExpandoAttribute checks the ASP.Page whether partial rendering is supported, then it registers the attribute using ScriptManager instead. I did the same with my validation control and it works for me. Here is the piece of code that solved my problem.

protected override void AddAttributesToRender(System.Web.UI.HtmlTextWriter writer)
       {
           base.AddAttributesToRender(writer);
           if (this.RenderUplevel)
           {
               string clientID = this.ClientID;
               if (!this.IsPartialRenderingSupported)
               {
                   Page.ClientScript.RegisterExpandoAttribute(clientID, "evaluationfunction", "EntryValidatorEvaluateIsValid");                   
               }
               else
               {
                   Type scriptManagerType = BuildManager.GetType("System.Web.UI.ScriptManager", false);
                   scriptManagerType.InvokeMember("RegisterExpandoAttribute", BindingFlags.InvokeMethod | BindingFlags.Public | BindingFlags.Static, null, null, new object[] { this, clientID, "evaluationfunction", "QuantityEntryValidatorEvaluateIsValid", false });
               }
           }
       }

Note, the I am first checking whether Partial Rendering is Supported and using the ScriptManager  Type to register the property instead.

The following piece of code uses Reflection to figure out whether partial rendering is supported.

internal bool IsPartialRenderingSupported
{
    get
    {
        if (!this.PartialRenderingChecked)
        {
            Type scriptManagerType = BuildManager.GetType("System.Web.UI.ScriptManager", false);
            if (scriptManagerType != null)
            {
                object obj2 = this.Page.Items[scriptManagerType];
                if (obj2 != null)
                {
                    PropertyInfo property = scriptManagerType.GetProperty("SupportsPartialRendering");
                    if (property != null)
                    {
                        object obj3 = property.GetValue(obj2, null);
                        this.IsPartialRenderingEnabled = (bool)obj3;
                    }
                }
            }
            this.PartialRenderingChecked = true;
        }
        return this.IsPartialRenderingEnabled;
    }

}

private bool PartialRenderingChecked
{
    get
    {
        object val = ViewState["PartialRenderingChecked"];
        if (val != null)
            return (bool)val;
        return false;
    }
    set
    {
        ViewState["PartialRenderingChecked"] = value;
    }
}

private bool IsPartialRenderingEnabled
{
    get
    {
        object val = ViewState["IsPartialRenderingEnabled"];
        if (val != null)
            return (bool)val;
        return false;
    }
    set
    {
        ViewState["IsPartialRenderingEnabled"] = value;
    }
}

 

Conclusion

The Page.ClientScript.RegisterExpandoAttribute may not work in Partial Rendiring mode, when a postback is performed via triggering,
to get this work we need to determine whether partial rendering is supported and use the ScriptManager Type instead like described above.

Hope this helps, and saves some of your time, Thank you for being with me so far.

posted @ Tuesday, June 17, 2008 5:43 AM | Feedback (0) |

Powered by: