Monday, August 27, 2007

WebClient: Handling Cookies

WebClient class is very useful when you need to download or upload date from or to the Web.  However, when you need to make a sequence of calls you find that WebClient does not preserve cookies set by the server between requests.  Fortunately, WebClient gives you an opportunity to handle cookies by yourself. 

The very simple solution is to override GetWebRequest method of the WebClient class and set CookieContainer property.

Here is my implementation:

    public class CookieAwareWebClient : WebClient
{

private CookieContainer m_container = new CookieContainer();

protected override WebRequest GetWebRequest(Uri address)
{
WebRequest request = base.GetWebRequest(address);
if (request is HttpWebRequest)
{
(request as HttpWebRequest).CookieContainer = m_container;
}
return request;
}
}

Saturday, August 18, 2007

Calling Virtual Method of a Class Base to Base Class

You will probably never really need to do this, but if it happens, here is how it can be accomplished.

So, assume you have two classes Base and Derived.  The Base class introduces a virtual method named Test.  The Derived class overrides this method.

Now, as I said in very rare circumstances, you may need to derive another class from the Derived class and in the overridden method Test call original version of Test method.  By original I mean implementation provided by the Base class.

C# and VB.Net, the most popular .Net languages do not provide any way to write something similar to:

class DerivedFromDerived : Derived
{
public override void Test()
{
base.base.Test();
}
}

When you call a base class version of Test() C# compiler generates:

.method public hidebysig virtual instance void Test() cil managed
{
.maxstack 8
L_0000:
nop
L_0001:
ldarg.0
L_0002:
call instance void Derived::Test()
L_0007:
nop
L_0008:
ret
}


The goal is to make compiler generate:


L_0002: call instance void Base::Test()


The syntax of method name used in IL reminded me that C++ always allowed invoking an implementation of the very base class.  And indeed, the straightforward solution is to implement it in C++/CLI:

    public ref class DerivedFromDerived: public Derived
{
public:
virtual void Test()
override
{
Base::Test();
}
};

This C++ code generates verifiable safe IL code. 

Wednesday, August 1, 2007

Restoring Lost Focus in the Update Panel with Auto Post-Back Controls

UpdatePanel control is used for different purposes like reducing flickering of the page and reducing network traffic generated by a web site. Developers often wrap input boxes into an UpdatePanel to implement cascading drop-downs and update other related controls.  Cascading drop-downs can be implemented with a help of Ajax Control Toolkit control extenders, but in general case you will either need to write script code or place controls in an update panel.  The latest approach is very easy to implement, but it also has a lot of drawbacks.  If you wrap input boxes (and other input controls) in the update panel, you must be aware about the following consequences:

  1. If a user changes value of an auto post-back control and "tabs" to the next control, he or she starts asynchronous (AJAX) post-back.  However, as the post-back is asynchronous the user can continue typing (selecting value) in the next selected control. If the server responds to asynchronous post-back after the user has started typing in the next control, everything typed in this control is lost once AJAX post-back completes.

    If the server responds quite fast, this situation is unlikely to happen, but if the server is busy or web site is browsed from the dial-up connection, it is likely to happen.  It is still less probable than with full-post back, but possible.
  2. The second annoying behavior you may experience with input controls inside an UpdatePanel control is lost focus.  Input (keyboard) focus moves to nowhere once AJAX post-back completes.  Web site users who prefer to use keyboard need to use mouse to activate appropriate input box or press TAB multiple times.  (The exact behavior is a little different for Internet Explorer and Mozilla FireFox).

You can place this UpdatePanel to an ASPX page to see the second problem by yourself:

<asp:UpdatePanel runat="server" ID="up">
<ContentTemplate
>
<div
>
<asp:DropDownList ID="ddl1" runat="server" AutoPostBack
="True">
<asp:ListItem>1</asp:ListItem
>
<asp:ListItem>2</asp:ListItem
>
<asp:ListItem>3</asp:ListItem
>
</asp:DropDownList></div
>
<div
>
<asp:DropDownList ID="ddl2" runat="server" AutoPostBack
="True">
<asp:ListItem>1</asp:ListItem
>
<asp:ListItem>2</asp:ListItem
>
<asp:ListItem>3</asp:ListItem
>
</asp:DropDownList></div
>
<div
>
<asp:TextBox runat="server" ID="tb"></asp:TextBox><br
/>
</div
>
</ContentTemplate
>
</
asp:UpdatePanel>

Why Does It Happen?

I need to remind some details of how UpdatePanel works to answer this question.


If ScriptManager EnablePartialRendering property is set to true, controls on the web page can initiate asynchronous post-back.  When it happens web page send asynchronous request to the server.  The web page instance at the server goes through all the phases in its normal lifecycle, but instead of full rendering only partial rendering happens.  The html content for updated panels is sent back to the client in the format that client script manager control can recognize.  The client part of the ScriptManager control parses the response and set innerHTML properties of all DIV elements generated by UpdatePanels.


So, what happens to the UpdatePanel with input controls?  When client script receives a response from the server with new Html content for the UpdatePanel it assigns this content to the DIV element generated by the UpdatePanel.  Input focus at this time is in one of the controls inside the DIV element (UpdatePanel).  The browser destroys the old content of the UpdatePanel including input controls and creates new elements by parsing new content assigned to the innerHTML property.  Input focus moves out of the update panel when focused input controls is being destroyed and it is not restored later when the new controls are created from the assigned html.


Solution of the Problem with the Lost Input Focus

The basic idea behind the solution is to save the ID of the control with input focus before the update panel is updated and set input focus back to that control after the update panel is updated.


I come with the following JavaScript which restores the lost focus in the update panel. 

var lastFocusedControlId = "";

function focusHandler(e) {
document.activeElement = e.originalTarget;
}

function appInit() {
if (typeof(window.addEventListener) !== "undefined") {
window.addEventListener("focus", focusHandler, true);
}
Sys.WebForms.PageRequestManager.getInstance().add_pageLoading(pageLoadingHandler);
Sys.WebForms.PageRequestManager.getInstance().add_pageLoaded(pageLoadedHandler);
}

function pageLoadingHandler(sender, args) {
lastFocusedControlId = typeof(document.activeElement) === "undefined"
? "" : document.activeElement.id;
}

function focusControl(targetControl) {
if (Sys.Browser.agent === Sys.Browser.InternetExplorer) {
var focusTarget = targetControl;
if (focusTarget && (typeof(focusTarget.contentEditable) !== "undefined")) {
oldContentEditableSetting = focusTarget.contentEditable;
focusTarget.contentEditable = false;
}
else {
focusTarget = null;
}
targetControl.focus();
if (focusTarget) {
focusTarget.contentEditable = oldContentEditableSetting;
}
}
else {
targetControl.focus();
}
}

function pageLoadedHandler(sender, args) {
if (typeof(lastFocusedControlId) !== "undefined" && lastFocusedControlId != "") {
var newFocused = $get(lastFocusedControlId);
if (newFocused) {
focusControl(newFocused);
}
}
}

Sys.Application.add_init(appInit);

If you save this code to FixFocus.js file, it can be used as:

    <asp:ScriptManager ID="c_scriptManager" runat="server">
<Scripts
>
<asp:ScriptReference Path="~/FixFocus.js"
/>
</Scripts
>
</asp:ScriptManager>

Unfortunately, different browsers handle input focus a little differently.  Mozilla FireFox browser does not provide an easy way to get currently focus element at all.  The script, therefore, handles these differences between browsers. 


You may find the focusControl function a little strange.  I cannot explain the magic it does, but this is really required to set focus to the control in the Internet Explorer.  ASP.NET AJAX extensions use this code itself when setting focus set by ScriptManager.SetFocus method.


I tested this code with Internet Explorer 7 and the most recent build of Mozilla FireFox


Sample Web Site

You can download the sample web site here.


UPDATE

You can often solve the problem by placing each input control into its own update panel.  However, this approach does not solve the problem if user press TAB to go to the next control, which is in its own UpdatePanel. 


The code above is a workaround.  If you need to implement cascading drop-downs it is better to go with Ajax Control Toolkit Cascading Drop-Down Control Extenders.  If you need to use UpdatePanel or several UpdatePanels use this code, but be aware of problem #1 which is not solved by this script.