Amazon.com Widgets All posts tagged 'asp.net'

WilliaBlog.Net

I dream in code

About the author

Robert Williams is an internet application developer for the Salem Web Network.
E-mail me Send mail
Code Project Associate Logo
Go Daddy Deal of the Week: 30% off your order at GoDaddy.com! Offer expires 11/6/12

Recent comments

Archive

Authors

Tags

Disclaimer

The opinions expressed herein are my own personal opinions and do not represent my employer's view in anyway.


Asynchronous Programming with ASP.Net MVC Futures

Using the AsyncController

Introduction

The AsyncController is an experimental class offered inside the latest MVC Futures dll to allow developers to write asynchronous action methods.  The usage scenario for this is for action methods that have to make long-running requests, such as going out over the network or to a database, and don’t want to block the web server from performing useful work while the request is ongoing.

In general, the pattern is that the web server schedules Thread A to handle some incoming request, and Thread A is responsible for everything up to launching the action method, then Thread A goes back to the available pool to service another request.  When the asynchronous operation has completed, the web server retrieves a Thread B (which might be the same as Thread A) from the thread pool to process the remainder of the request, including rendering the response.  The diagram below illustrates this point.

Configuration

The asynchronous types are declared in the Microsoft.Web.Mvc namespace in the assembly Microsoft.Web.Mvc.dll.  The first change a developer needs to make is to declare a route as asynchronous.  There are MapAsyncRoute() extension methods to assist with this; these are analogs of the normal MapRoute() extension methods already provided by the MVC framework.  In Global.asax:

routes.MapAsyncRoute(

    "Default",

    "{controller}/{action}/{id}",

    new { controller = "Home", action = "Index", id = "" }

);

A route declared with MapAsyncRoute() can correctly handle both synchronous and asynchronous controllers, so there is no need to create complex routing logic such that sync controllers are serviced by the normal MapRoute() handler and async controllers are serviced by the MapAsyncRoute() handler.  However, a sync route [MapRoute()] cannot in general correctly execute async controllers and methods.

Secondly, if you are using IIS6 or IIS7 classic mode, you need to replace the standard *.mvc handler with its asynchronous counterpart.  Replace these lines in Global.asax (they don’t necessarily appear adjacent to one another):

<add verb="*" path="*.mvc" validate="false" type="System.Web.Mvc.MvcHttpHandler, System.Web.Mvc, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>

<add name="MvcHttpHandler" preCondition="integratedMode" verb="*" path="*.mvc" type="System.Web.Mvc.MvcHttpHandler, System.Web.Mvc, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>

With these:

<add verb="*" path="*.mvc" validate="false" type="Microsoft.Web.Mvc.MvcHttpAsyncHandler, Microsoft.Web.Mvc"/>

<add name="MvcHttpHandler" preCondition="integratedMode" verb="*" path="*.mvc" type="Microsoft.Web.Mvc.MvcHttpAsyncHandler, Microsoft.Web.Mvc"/>

Writing asynchronous action methods

In general, any asynchronous controllers that you author should subclass the AsyncController type.  Remember to import the Microsoft.Web.Mvc namespace.

public class MyAsyncController : AsyncController {

    // ...

}

The default constructor of the MyAsyncController sets the ActionInvoker property to an instance of the AsyncControllerActionInvoker.  This specialized invoker can understand several asynchronous patterns.  You can mix and match these patterns inside of your controllers.  If an ambiguity is found, we do our best to throw a detailed exception.

The AsyncController understands three async patterns.  These patterns can be mixed and matched within a single AsyncController.  Additionally, an AsyncController can contain normal (synchronous) action methods.  This makes it easy to change the base class of your controller from Controller to AsyncController in order to add async methods while allowing your original sync methods to run correctly.

The IAsyncResult pattern

The IAsyncResult pattern is a well-known pattern in the .NET Framework and is documented heavily.  A method pair signature which implements the IAsyncResult pattern is shown below.

public IAsyncResult BeginFoo(int id, AsyncCallback callback, object state);

public ActionResult EndFoo(IAsyncResult asyncResult);

This is the asynchronous analog of a synchronous method with the signature public ActionResult Foo(int id).  Note that the BeginFoo() method takes the same parameters as the Foo() method plus two extra – an AsyncCallback and a state object – and returns an IAsyncResult.  The EndFoo() method takes a single parameter – an IAsyncResult – and has the same return type as the Foo() method.

Standard model binding takes place for the normal parameters of the BeginFoo() method.  The invoker will pass a callback and state object for the BeginFoo() method to consume when its asynchronous task has finished.  When the callback is called, we automatically invoke the EndFoo() method and capture the result, then execute the result just as we would have in a synchronous request.

Only filter attributes placed on the BeginFoo() method are honored.  If a filter attribute is placed on EndFoo(), it will be ignored.  If an [ActionName] attribute is placed on the BeginFoo() method in order to alias it, we will look for an EndFoo() method based on the method name of BeginFoo(), not the aliased action name.  For example:

[ActionName("Bar")]

public IAsyncResult BeginFoo(int id, AsyncCallback callback, object state);

public ActionResult EndFoo(IAsyncResult asyncResult);

This will cause the BeginFoo() method to match requests for Bar rather than Foo.  Note that the completion method is still called EndFoo() instead of EndBar() since it matches the name of the entry method, not the entry alias.

The event pattern

In this pattern, the action method is divided into a setup method and a completion method.  The signatures are below:

public void Foo(int id);

public ActionResult FooCompleted(...);

When a request comes for Foo, we execute the Foo() method.  When the asynchronous operations are completed, we invoke the FooCompleted() method and execute the returned ActionResult.

The invoker will model bind parameters to the Foo() method in the standard way.  Parameters to FooCompleted() are not provided using model binders.  Rather, they come from the AsyncController.AsyncManager.Parameters dictionary, which can be populated as part of the asynchronous setup.  Keys in the dictionary correspond to parameter names of the FooCompleted() method, and any parameters which do not have corresponding keys in the dictionary are given a value of default(T).

The invoker must keep track of the number of outstanding asynchronous operations so that it does not invoke the FooCompleted() method prematurely.  To do this, a counter has been provided, accessible from AsyncController.AsyncManager.OutstandingOperations.  This counter can be incremented or decremented to signal that an operation has kicked off or concluded, and when the counter hits zero the invoker will invoke the completion routine.  For example:

public void Foo(int id) {

    AsyncManager.Parameters["p"] = new Person();

    AsyncManager.OutstandingOperations.Increment();

    ThreadPool.QueueUserWorkItem(o => {

        Thread.Sleep(2000); // simulate some work

        AsyncManager.OutstandingOperations.Decrement();

    }, null);

}

public ActionResult FooCompleted(Person p) {

    // consume 'p'

}

There is also an AsyncController.AsyncManager.RegisterTask() method that is helpful for wrapping calls to IAsyncResult pattern methods from within an event pattern method.  The RegisterTask() method also handles incrementing and decrementing the counter correctly.  For example:

public void Foo(int id) {

    AsyncManager.RegisterTask(

        callback => BeginGetPersonById(id, callback, null),

        ar => {

            AsyncManager.Parameters["p"] = EndGetPersonById(ar);

        });

    AsyncManager.RegisterTask(

        callback => BeginGetTotalUsersOnline(callback, null),

        ar => {

            AsyncManager.Parameters["numOnline"] = EndGetTotalUsersOnline(ar);

        });

}

public ActionResult FooCompleted(Person p, int numOnline) {

    // ...

}

This will kick off two asynchronous tasks and wait for both to finish before executing the FooCompleted() method with the values that were returned.

Only filter attributes placed on the Foo() method are honored.  If a filter attribute is placed on FooCompleted(), it will be ignored.  If an [ActionName] attribute is placed on the Foo() method in order to alias it, we will look for an FooCompleted() method based on the method name of Foo(), not the aliased action name.  For example:

[ActionName("Bar")]

public void Foo(int id);

public ActionResult FooCompleted(...);

This will cause the Foo() method to match requests for Bar rather than Foo.  Note that the completion method is still called FooCompleted() instead of BarCompleted() since it matches the name of the entry method, not the entry alias.

The Foo() method is allowed to return anything.  The invoker ignores the return value of this method; it only cares about the return value of the FooCompleted() method.  This is to allow writing Foo() methods that are easier to unit test.

The delegate pattern

This pattern is very similar to the event pattern, except that the method Foo() returns a parameterless delegate type and there is no FooCompleted() method.  For example:

public Func<ActionResult> Foo(int id) {

    Person p = null;

    int numOnline = 0;

    AsyncManager.RegisterTask(

        callback => BeginGetPersonById(id, callback, null),

        ar => {

            p = EndGetPersonById(ar);

        });

    AsyncManager.RegisterTask(

        callback => BeginGetTotalUsersOnline(callback, null),

        ar => {

            numOnline = EndGetTotalUsersOnline(ar);

        });

    return () => {

        ViewData["p"] = p;

        ViewData["numOnline"] = numOnline;

        return View();

    };

}

Or, more succinctly:

public Func<ActionResult> Foo(int id) {

    AsyncManager.RegisterTask(

        callback => BeginGetPersonById(id, callback, null),

        ar => {

            ViewData["p"] = EndGetPersonById(ar);

        });

    AsyncManager.RegisterTask(

        callback => BeginGetTotalUsersOnline(callback, null),

        ar => {

            ViewData["numOnline"] = EndGetTotalUsersOnline(ar);

        });

    return View;

}

Since this pattern supports only parameterless delegates instead of parameterful delegates, the earlier discussion about AsyncController.AsyncManager.Parameters is not applicable.

Timeouts

There is a Timeout property accessible from AsyncController.AsyncManager.Timeout that specifies the number of milliseconds to wait for a response from the action method before canceling the request.  The default value is 30000 (equal to 30 seconds).  If the action method has not returned by the specified time, we throw a TimeoutException.  Action filters and exception filters may handle this particular exception type if they wish.  Setting the Timeout property to System.Threading.Timeout.Infinite signifies that we will never throw this exception.

The timeout duration can be specified on a per-controller or per-action basis by attributing a class or method with [AsyncTimeout] or [NoAsyncTimeout].

Known issues

-          Asynchronous actions generally cannot be called by synchronous invokers or handlers.  If you receive an exception message about an action being unable to be executed synchronously, ensure that you’re using the MapAsyncRoute() extension method in your Global.asax and that your controller subclasses AsyncController.

-          The asynchronous invoker will not match any method beginning with Begin or End or ending with Completed.  This is to prevent web calls to the BeginFoo(), EndFoo(), and FooCompleted() methods directly.  If you need to make an action with this name accessible to web users, use the [ActionName] attribute to alias the method:

[ActionName("Begin")]

public ActionResult DoSomething();

The above is an example of a normal synchronous method that has been renamed to Begin to work around the invoker’s blocking of this name.

-          If the route that normally handles requests for the application root (/) is an asynchronous route, the Default.aspx file should be removed from the web application.  The Default.aspx file included in the template only works with synchronous requests.


Categories: ASP.Net | Asynchronous | C# | MVC
Posted by Williarob on Thursday, June 18, 2009 6:03 AM
Permalink | Comments (0) | Post RSSRSS comment feed

Accessing Global Resource Files from a Class Library in ASP.NET

If you add an App_GlobalResources Folder to your asp.net 2.0 web application you may want to access some of the values within it from a class library - such as a Business Logic Layer (BLL). Since there is unlikely to be a reference from your BLL to the main site, and even if there were, the designer class that shadows the resource file is declared with "Friend" keywords so that class would not be visible to the BLL. Therefore trying to call it using the Resources.[Filename].[key] syntax that works from anywhere within the site will not work. Instead you can simply create a wrapper like the following somwhere within your class library:

    1 Imports System.Resources

    2 Imports System.Web

    3 

    4 Namespace Resources

    5     Public Class ResourceWrapper

    6 

    7         Public Shared Function Strings1(ByVal key As String) As String

    8             Return HttpContext.GetGlobalResourceObject("Strings1", key)

    9         End Function

   10 

   11         Public Shared Function Strings2(ByVal key As String) As String

   12             Return HttpContext.GetGlobalResourceObject("Strings2", key)

   13         End Function

   14 

   15         Public Shared Function Strings3(ByVal key As String) As String

   16             Return HttpContext.GetGlobalResourceObject("Strings3", key)

   17         End Function

   18 

   19     End Class

   20 End Namespace

 

Then you can retrieve values from the resource files from within your class library simply by passing in the key you want to retrieve:

 

   Resources.ResourceWrapper.Strings1("SupportEmail")

 

This example assumes that you have created three .resx resource files inside the App_GlobalResources folder of an ASP.Net Web Application, and that you created them within Visual Studio (as opposed to a text editor for example), and that you named the files "Strings1.resx", "Strings2.resx" and "Strings3.resx", and that Strings1.resx contains the key "SupportEmail".

 

The Great thing about adding .resx files in this fashion is that you can actually edit the values in a text editor on the live site without having to recompile and republish your entire project. Having said that, adding new key value pairs in the text editor will not create the properties in the class that shadows it, and removing or renaming existig key value pairs could cause runtime errors so if you need to do anything other than change the value of an existing key, do so within visual studio and republish.


Posted by Williarob on Thursday, November 20, 2008 6:06 PM
Permalink | Comments (0) | Post RSSRSS comment feed

A Virtual Form Web Custom Control

I recently ran into an issue where a site I was developing had form fields in the header area of the page (for logging in or searching) and that if I had my cursor in a form field further down the page and I hit the enter key, it it was the click event of the first button on the page (the button in the header, rather than the button I wanted it to use) that fired. The setup was a common one: the master page contains a form tag with a runat="server" attribute and that form wraps the entire content of the page. It all seemed to work fine, until I found myself on the registration page and instead of clicking the register button I just hit enter. Instead of registering me, a message appeared to explain that a username and password were required. Since those fields also exist on the registration form I was puzzled, more so when I used the mouse to click on the 'Register Now' button and it all worked. Stepping through the code I quickly realized what was happening, and rummaged around my code archives until I found some JavaScript I wrote a couple of years ago that would listen for the enter key, cancel the default behavior, and call another method. I added this snippet to the page and all was well, until I found another page with the same issue. It was at that point I thought "wouldn't it be nice if there was a control - like a panel control - that you could simply use to wrap some input controls, set a single property (to the id of the control that should be 'clicked' when the enter key is pushed), and that was all you needed to do?".

Well now there is such a control.

Edit: Actually, the standard asp Panel control has a "DefaultButton" property which implements similar functionality, however it only allows you to use asp button controls as your designated button. If you want to use an asp LinkButton control or some other type of control as your default button it does nothing for you. So if you are using asp Button Controls exclusively, I recommend you use that property. If not, then read on...

Using the control

You can register the control on a page by page basis as needed or you can add it to the <Pages> section of the web.config file to make it available to any page on the site (Listing 1).

Listing 1

  <pages enableSessionState="true" enableViewStateMac="true" enableEventValidation="true">

      <controls>

        <add tagPrefix="WBN" namespace="WilliaBlog.Net.Examples" assembly="WilliaBlog.Net.Examples"/>

      </controls>

  </pages>

Then add the control to the page in such a way that it wraps your inputs:

Listing 2

  <WBN:VirtualForm id="vf1" runat="server" SubmitButtonId="Button1" UseDebug="false">

    <asp:TextBox ID="TextBox1" runat="server"></asp:TextBox> <asp:Button ID="Button1" runat="server" Text="Button1" OnClick="Button1_Click" />

  </WBN:VirtualForm>

As you can see from Listing 2 you should use the Server side ID of the button (this will be automatically converted to the clientside ID by the virtual form control. Test it live.

How It works

The actual server side Virtual Form web custom control is really very simple. It inherits System.Web.UI.WebControls.Panel and contains a property to allow you to set the id of the button or linkbutton you want to push when hitting Enter. I chose to embed the Javascript file into the dll for the sake of portability. (Originally I had it in an external .JS file that was added to the header in the master page, but if I - or somebody else - wanted to use this control on another site, that adds an additional layer of complexity in the setup - they have to remember to load that Javascript file and while we could host it in a central location for all sites to share, embedding the file in the dll seemed the wisest choice.) The downside to this technique is that the js file has to travel over the wire every time the page is requested and cannot therefore be cached on the client, which will negatively impact any low-bandwidth users of the web application. For that reason, I have used the YUI Compressor to strip out all the comments and additional whitespace and included 2 versions of the script in the dll. When you set the property UseDebug to true, it will use the long verbose version which makes it much easier to debug in firebug, but when it is all working, use the compressed version by omitting this property from the control declaration or by setting it to false.

To make files accessible from your server control’s assembly, simply add the file to your project, go to the Properties pane, and set the Build Action to Embedded Resource. To expose the file to a web request, you need to add code like lines 7 and 8 in Listing 3 below. These entries expose the embedded resource so that the ClientScriptManager can both get to the file and know what kind of file it is. You could also embed CSS, images and other types of files in this way. Note: The project’s default namespace (as defined in the Application tab of the project's Properties page) needs to be added as a prefix to the filename of embedded resources.

So, besides exposing these properties, all the control really does is override the onPreRender event and inject some Javascript into the page. Lines 75 to 79 in Listing 3 inject the link to the embedded Javascript file, which appears in the page source as something like this:

<script src="/WebResource.axd?d=j246orv_38DeKtGbza6y6A2&amp;t=633439907968639849" type="text/javascript"></script>

Next it dynamically generates a script to create a new instance of the virtual form object, passing in the clientid of the VirtualForm Server control and the clientid of the button we want it to use, and register this as a startup script on the page.

Listing 3

    1 using System;

    2 using System.Collections.Generic;

    3 using System.ComponentModel;

    4 using System.Text;

    5 using System.Web;

    6 using System.Web.UI;

    7 using System.Web.UI.WebControls;

    8 

    9 // Script Resources

   10 [assembly: WebResource("WilliaBlog.Net.Examples.VirtualForm_Debug.js", "text/javascript")]

   11 [assembly: WebResource("WilliaBlog.Net.Examples.VirtualForm_min.js", "text/javascript")]

   12 

   13 namespace WilliaBlog.Net.Examples

   14 {

   15 

   16     [ToolboxData("<{0}:VirtualForm runat=server></{0}:VirtualForm>")]

   17     public class VirtualForm : System.Web.UI.WebControls.Panel

   18     {

   19         [Bindable(true), DefaultValue("")]

   20         public string SubmitButtonId

   21         {

   22             get

   23             {

   24                 string s = (string)ViewState["SubmitButtonId"];

   25                 if (s == null)

   26                 {

   27                     return string.Empty;

   28                 }

   29                 else

   30                 {

   31                     return s;

   32                 }

   33             }

   34             set { ViewState["SubmitButtonId"] = value; }

   35         }

   36 

   37         [DefaultValue(false)]

   38         public bool UseDebug

   39         {

   40             get

   41             {

   42                 string s = (string)ViewState["UseDebug"];

   43                 if (string.IsNullOrEmpty(s))

   44                 {

   45                     return false;

   46                 }

   47                 else

   48                 {

   49                     return s.ToLower() == "true";

   50                 }

   51             }

   52             set { ViewState["UseDebug"] = value; }

   53         }

   54 

   55         public VirtualForm() : base() { }

   56 

   57         protected override void OnPreRender(System.EventArgs e)

   58         {

   59             if (!string.IsNullOrEmpty(this.SubmitButtonId))

   60             {

   61                 Control theButton = this.FindControl(this.SubmitButtonId);

   62                 if ((theButton != null))

   63                 {

   64                     string resourceName;

   65                     if (this.UseDebug)

   66                     {

   67                         resourceName = "WilliaBlog.Net.Examples.VirtualForm_Debug.js";

   68                     }

   69                     else

   70                     {

   71                         resourceName = "WilliaBlog.Net.Examples.VirtualForm_min.js";

   72                     }

   73 

   74                     ClientScriptManager cs = this.Page.ClientScript;

   75 

   76                     string scriptLocation = cs.GetWebResourceUrl(this.GetType(), resourceName);

   77                     if (!cs.IsClientScriptIncludeRegistered("VirtualFormScript"))

   78                     {

   79                         cs.RegisterClientScriptInclude("VirtualFormScript", scriptLocation);

   80                     }

   81 

   82                     // New script checks for "Sys" Object, if found events will be rewired after updatepanel refresh.

   83                     StringBuilder sbScript = new StringBuilder(333);

   84                     sbScript.AppendFormat("<script type=\"text/javascript\">{0}", Environment.NewLine);

   85                     sbScript.AppendFormat("    // Ensure postback works after update panel returns{0}", Environment.NewLine);

   86                     sbScript.AppendFormat("    function ResetEventsForMoreInfoForm() {{{0}", Environment.NewLine);

   87                     sbScript.AppendFormat("        var vf_{0} = new WilliaBlog.Net.Examples.VirtualForm(document.getElementById('{0}'),'{1}');{2}", this.ClientID, theButton.ClientID, Environment.NewLine);

   88                     sbScript.AppendFormat("    }}{0}", Environment.NewLine);

   89                     sbScript.AppendFormat("    if (typeof(Sys) !== \"undefined\"){{{0}", Environment.NewLine);

   90                     sbScript.AppendFormat("        Sys.WebForms.PageRequestManager.getInstance().add_endRequest(ResetEventsForMoreInfoForm);{0}", Environment.NewLine);

   91                     sbScript.AppendFormat("    }}{0}", Environment.NewLine);

   92                     sbScript.AppendFormat("    var vf_{0} = new WilliaBlog.Net.Examples.VirtualForm(document.getElementById('{0}'),'{1}');{2}", this.ClientID, theButton.ClientID, Environment.NewLine);

   93                     sbScript.AppendFormat("</script>");

   94 

   95                     string scriptKey = string.Format("initVirtualForm_" + this.ClientID);

   96 

   97                     if (!cs.IsStartupScriptRegistered(scriptKey))

   98                     {

   99                         cs.RegisterStartupScript(this.GetType(), scriptKey, sbScript.ToString(), false);

  100                     }

  101                 }

  102             }

  103             base.OnPreRender(e);

  104         }

  105     }

  106 }

The JavaScript

Most of the code is of course JavaScript. Lines 13 to 62 simply create the WilliaBlog Namespace and I 'borrowed' it from the The Yahoo! User Interface Library (YUI). The WilliaBlog.Net.Examples.VirtualForm object begins on line 65. Essentially, it loops through every input control (lines 159-164) within the parent Div (the id of this div is passed as an argument to the contructor function) and assigns an onkeypress event (handleEnterKey) to each of them. All keystrokes other than the enter key pass through the code transparently, but as soon as enter is detected, the default behavior is cancelled and the submitVirtual function is called instead. That function simply checks to see if the button you supplied is an input (image or submit button) or an anchor (Hyperlink or link button), and simulates a click on it, either by calling the click() method of the former or by navigating to the href property of the latter. The removeEvent and stopEvent methods are never actually called, but I included them for good measure.

Listing 4

    1 /****************************************************************************************************************************  

    2 *

    3 * File    : VirtualForm_Debug.js

    4 * Created : April 08

    5 * Author  : Rob Williams

    6 * Purpose : This is the fully annotated, easy to understand and modify version of the file, however I would recommend you use

    7 * something like the YUI Compressor (http://developer.yahoo.com/yui/compressor/) to minimize load time.

    8 * This file has its Build Action Property set to "Embedded Resource" which embeds the file inside the dll, so we never have to

    9 * worry about correctly mapping a path to it.

   10 *

   11 *****************************************************************************************************************************/

   12 

   13 if (typeof WilliaBlog == "undefined" || !WilliaBlog) {

   14     /**

   15     * The WilliaBlog global namespace object.  If WilliaBlog is already defined, the

   16     * existing WilliaBlog object will not be overwritten so that defined

   17     * namespaces are preserved.

   18     * @class WilliaBlog

   19     * @static

   20     */

   21     var WilliaBlog = {};

   22 }

   23 

   24 /**

   25  * Returns the namespace specified and creates it if it doesn't exist

   26  * <pre>

   27  * WilliaBlog.namespace("property.package");

   28  * WilliaBlog.namespace("WilliaBlog.property.package");

   29  * </pre>

   30  * Either of the above would create WilliaBlog.property, then

   31  * WilliaBlog.property.package

   32  *

   33  * Be careful when naming packages. Reserved words may work in some browsers

   34  * and not others. For instance, the following will fail in Safari:

   35  * <pre>

   36  * WilliaBlog.namespace("really.long.nested.namespace");

   37  * </pre>

   38  * This fails because "long" is a future reserved word in ECMAScript

   39  *

   40  * @method namespace

   41  * @static

   42  * @param  {String*} arguments 1-n namespaces to create

   43  * @return {Object}  A reference to the last namespace object created

   44  */

   45 WilliaBlog.RegisterNamespace = function() {

   46     var a=arguments, o=null, i, j, d;

   47     for (i=0; i<a.length; i=i+1) {

   48         d=a[i].split(".");

   49         o=WilliaBlog;

   50 

   51         // WilliaBlog is implied, so it is ignored if it is included

   52         for (j=(d[0] == "WilliaBlog") ? 1 : 0; j<d.length; j=j+1) {

   53             o[d[j]]=o[d[j]] || {};

   54             o=o[d[j]];

   55         }

   56     }

   57 

   58     return o;

   59 };

   60 

   61 //declare the 'WilliaBlog.Net.Examples' Namespace

   62 WilliaBlog.RegisterNamespace("WilliaBlog.Net.Examples");

   63 

   64 //declare Virtual Form Object

   65 WilliaBlog.Net.Examples.VirtualForm = function(formDiv,submitBtnId)

   66 {

   67     this.formDiv = formDiv; //The id of the div that represents our Virtual Form

   68     this.submitBtnId = submitBtnId;   //The id of the button or Linkbutton that should be clicked when pushing Enter

   69 

   70     // When using these functions as event delegates the this keyword no longer points to this object as it is out of context

   71     // so instead, create an alias and call that instead.

   72     var me = this;

   73 

   74     this.submitVirtual = function()

   75     {

   76         var target = document.getElementById(me.submitBtnId);

   77         //check the type of the target: If a button then call the click method.

   78         if(target.tagName.toLowerCase() === 'input')

   79         {

   80             document.getElementById(me.submitBtnId).click();

   81         }

   82         //If a link button then simulate a click.

   83         if(target.tagName === 'A')

   84         {

   85             window.location.href = target.href;

   86         }

   87     };

   88 

   89     this.handleEnterKey = function(event){ 

   90         var moz = window.Event ? true : false;

   91         if (moz) {

   92             return me.MozillaEventHandler_KeyDown(event);

   93         } else {

   94             return me.MicrosoftEventHandler_KeyDown();

   95         }

   96     };

   97 

   98     //Mozilla handler (also Handles Safari)

   99     this.MozillaEventHandler_KeyDown = function(e)

  100     {

  101         if (e.which == 13) {

  102             e.returnValue = false;

  103             e.cancel = true;

  104             e.preventDefault();           

  105             me.submitVirtual(); // call the delegate function that simulates the correct button click

  106             return false;       

  107         }

  108         return true;

  109     };

  110 

  111     //IE Handler

  112     this.MicrosoftEventHandler_KeyDown = function()

  113     {

  114         if (event.keyCode == 13) {

  115             event.returnValue = false;

  116             event.cancel = true;

  117             me.submitVirtual(); // call the delegate function that simulates the correct button click

  118             return false;

  119         }

  120         return true;

  121     };

  122 

  123     this.addEvent = function(ctl, eventType, eventFunction)

  124     {

  125         if (ctl.attachEvent){

  126             ctl.attachEvent("on" + eventType, eventFunction);

  127         }else if (ctl.addEventListener){

  128             ctl.addEventListener(eventType, eventFunction, false);

  129         }else{

  130             ctl["on" + eventType] = eventFunction;

  131         }

  132     };

  133 

  134     this.removeEvent = function(ctl, eventType, eventFunction)

  135     {

  136         if (ctl.detachEvent){

  137             ctl.detachEvent("on" + eventType, eventFunction);

  138         }else if (ctl.removeEventListener){

  139             ctl.removeEventListener(eventType, eventFunction, false);

  140         }else{

  141             ctl["on" + eventType] = function(){};

  142         }

  143     };

  144 

  145     this.stopEvent = function(e)

  146     {

  147         if (e.stopPropagation){

  148         // for DOM-friendly browsers

  149             e.stopPropagation();

  150             e.preventDefault();

  151         }else{

  152         // For IE

  153             e.returnValue = false;

  154             e.cancelBubble = true;

  155         }

  156     };

  157 

  158     //Grab all input elements within virtual form (contents of a div with divID)

  159     this.inputs = this.formDiv.getElementsByTagName("input");

  160 

  161     //loop through them and add the keypress event to each to listen for the enter key

  162     for (var i = 0; i < this.inputs.length; i++){

  163         this.addEvent(this.inputs[i],"keypress",this.handleEnterKey);

  164     }

  165 }


Posted by Williarob on Monday, April 28, 2008 8:31 AM
Permalink | Comments (0) | Post RSSRSS comment feed

Move View State to the Server

In his talk "Hidden Gems in ASP.NET 2.0" at Tech Ed 2007 Jeff Prosise showcased a number of really useful but little known features built into ASP.Net 2.0. The one feature that I implemented immediately on all my sites after his talk was his SessionPageStateAdapter class.

This class moves the bulk of the Viewstate text out of the hidden field on the page and into a session variable on the server, and requires remarkably few lines of code. Follow these steps to implement it on your site:

  1. Create  a folder in your application root and name it "App_Browsers"
  2. Add a new XML File to this folder. It doesn't matter what you call it as long as it has a ".browser" extention. I called mine "Default.browser".
  3. Paste in the following code:

    <browsers>
      <browser refID="Default">
        <controlAdapters>
          <adapter controlType="System.Web.UI.Page" adapterType="SessionPageStateAdapter" />
        </controlAdapters>
      </browser>
    </browsers>

  4. Create a new class file (in your App_Code dir for a web site or anywhere in a Web Project) and name it "SessionPageStateAdapter"
  5. Paste in the following code:

    using System;
    using System.Web.UI;
    /// <summary>
    /// Summary description for SessionPageStateAdapter
    /// </summary>
    public class SessionPageStateAdapter : System.Web.UI.Adapters.PageAdapter
    {
      public override PageStatePersister GetStatePersister()
      {
        return new SessionPageStatePersister(this.Page);
      }
    }

That's it! It reduced the viewstate field on this page (at the time of writing) from 9440 characters to just 129 characters!

  


Categories: ASP.Net
Posted by Williarob on Thursday, October 25, 2007 1:15 PM
Permalink | Comments (0) | Post RSSRSS comment feed