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.

ASP.NET tips: Golden rules for Dynamic Controls.

1. Make sure your dynamic controls are Loaded on every postback.

Lets play with a very simple example,

ASPX

<%@ Page Language=”C#” AutoEventWireup=”true” CodeFile=”Default.aspx.cs” Inherits=”_Default” %>

<body>
    <form id=”form1″ runat=”server”>
    <div>
        <asp:PlaceHolder ID=”PlaceHolder1″ runat=”server”></asp:PlaceHolder>
        <asp:Button ID=”Button1″ runat=”server” Text=”Button” />
    </div>
    </form>
</body>
</html>


C# Code Behind

public partial class _Default : System.Web.UI.Page
{   
    protected void Page_Load(object sender, EventArgs e)
    {
        TextBox t = new TextBox();
        t.ID = “textBox”;
        this.PlaceHolder1.Controls.Add(t);        
    }

}

The above code works fine, but a common mistake is to try to conditionally load dynamic controls, if we tweak the code a little bit you will notice we loose our TextBox after any postback. The following code will not load the TextBox after our first postback.

public partial class _Default : System.Web.UI.Page
{   
    protected void Page_Load(object sender, EventArgs e)
    {      
       if (!IsPostBack)
        {
            TextBox t = new TextBox();
            t.ID = “textBox”;
            this.PlaceHolder1.Controls.Add(t);
        }
    }

}

Its recommended to load the dynamic controls during the Page_Init instead, because we may want to hook up our events with proper handler at an early stage.

public partial class _Default : System.Web.UI.Page
{
    protected void Page_Init(object sender, EventArgs e)
    {
        TextBox t = new TextBox();
        t.ID = “textBox”;
        t.TextChanged+=new EventHandler(t_TextChanged);
        this.PlaceHolder1.Controls.Add(t);
    }

}


2. Do not assigning properties of a dynamic control (viewstate enabled), during Page_Init, it will not be reflected.

Here is scenario of another common mistake, “123” assigned to the Text property during Page_Init,

public partial class _Default : System.Web.UI.Page
{
    protected void Page_Init(object sender, EventArgs e)
    {
        TextBox t = new TextBox();
        t.ID = “textBox”;
       t.Text = “123”;
        this.PlaceHolder1.Controls.Add(t);
    }

}

controllifecycle

the above code will not work because, Initialization happens before LoadViewState during the control lifecycle. The value assigned to the properties during Initialization will simply get overwritten by the ViewState values.

 

3. If you are expecting your ViewState to retain after the postback, always assign same ID to the dynamic control

The following piece of code will not work, as I am assigning a new ID to the dynamic control after each postback. The LoadViewState retrieves previously saved viewstate data using the control ID, as the control ID has changed, it doesn’t know anymore what to load, as a result it cannot load previously saved viewstate data any more.

public partial class _Default : System.Web.UI.Page
{
    protected void Page_Init(object sender, EventArgs e)
    {
        TextBox t = new TextBox();
        t.ID = Guid.NewGuid().ToString();
        this.form1.Controls.Add(t);       
    }
}

Thank you for being with me so far.

C# 3.0 tips, Automatic Property

Declaring a property in C# 3.0 is super easy and super short.

public class Student
{
  public string Name {  get; set; }
}

yes that’s it, the framework will take care of the rest, the private variables will be automatically created and the getter and setter will be automatically implemented.

Here is how we can assign value to an automatic property via the constructor

public class Student
{
    public string Name {  get; set; }
    public Student (string name)
    {

       this.Name = name;

    } 
}

And finally, here is how we can declare a Readonly property

public class Student

   public string Name {  get; private set; }  

   public Student (string name)
   {

     this.Name = name;

   } 
}

Hope this helps, Enjoy coding.

System.Net.WebClient().DownloadString(url) for Web Scrapeing

WebRequest is the abstract base class for the .NET Framework’s request/response model for accessing data from the Internet.

To get content of a website, in .NET 1.0. we used to use WebRequest, which is good and also works asynchronously.

public static string GetContent(string url)
{
System.Net.WebRequest request = System.Net.WebRequest.Create(url);
using (System.Net.WebResponse response = request.GetResponse())
{
  using (System.IO.StreamReader reader =new System.IO.StreamReader(response.GetResponseStream()))
  {
   return reader.ReadToEnd();
  }
}
}

But in .NET 2.0, we can also use the WebClient class. It can also work asynchronous and works the same as the other one.

public static string GetContent(string url)
{
using (System.Net.WebClient client =new System.Net.WebClient())
{
  return client.DownloadString(url);
}
}

We can use any of the above method for web scrapeing in .NET. But the second approach is probably more cleaner.