ASP.NET, Presentation, Silverlight

.NET RIA Services Validation and Authentication Talk (Code)

Last month I did a presentation at SDDN in Melbourne on RIA Services and it’s in built authentication and validation features.

The code is posted here:

<Sample Code>

.NET Ria Services Authentication and Validation Demo Code

</Sample Code>

The talk started off by running through the UserDTO object – under Services/DomainObjects. This object is exposed through the UserRegistrationService under /Services.

Next you’ll want to familiarise with the LoginManager in the Silverlight project (under Model/LoginManager.cs).

I then added some custom validation to the UserDTO object.

Next I created a UserValitator.shared.cs file under Services/DomainObjects and applied a metadata attribute to UserDTO to connect the two. .shared.cs files will be automatically made available to Silverlight by RIA Services.

The login stuff is built using the standard ASP.NET membership SQL provider, and the built in RIA Services abilities around this.

Read through LoginManager.cs see how the forms authentication object is created, and how the system uses the AuthenciationDomainContext, which is created on the sever in Services/DomainObjects. AuthenticationDomainService on the server contains the goods to validate a user and also provides a User class, which you can extend.

There is also a UserRegistration context in LoginManager which allows you to create a new user. See UserRegistrationService.cs on the server (under Services/DomainObjects) to see how this works (by using the MembershipHelper class).

See the Web.config to note how the system is configured to use the SQL connection, and in the membership section note that it is configured to use this same SQL connection.
You’ll need to configure your SQL database as well – to create the ASP.NET membership tables… run aspnet_regsql.exe (in C:\Windows\Microsoft.NET\Framework\v2.0.50727) from an elevated prompt to start the wizard.

This should be enough to get you started.

BTW, there is some great doco on this in the .NET RIA Services PDF file…

Enjoy!

AJAX, ASP.NET, Silverlight

Control Silverlight by Using Browser Back and Forward Buttons

The Problem

Silverlight applications suffer from many of the same issues that AJAX applications suffer from. Both AJAX and Silverlight applications can be dynamically modified without a browser post back. This presents and interesting issue.

Say for example you have an application which presents the user with a number of wizard steps (much like a workflow of some kind). The user enters each step and clicks “Next”. The AJAX or Silverlight applications loads and displays the next step dynamically.

The user gets through a number of steps and realises they made a mistake one or two steps back. Many users will erroneously use the browser back button to perform this navigation. Most AJAX and Silverlight applications will not handle this action, and instead of correctly navigating to the previous step, the browser will actually navigate to the previous “page”. In many applications this may be a login page, or the site the user was on before they navigated to your site.

The Solution

I’ve seen a few solutions to this problem, although many of them not cross browser.

Synergist has a post here on using some of the new AJAX features in IE8. An updated post here shows this working using a dispatch timer in some more browsers.

Both of these solutions are okay but there is a lot of custom code here, and as Michael (Synergist) figured out, there were cross browser issues (IE7 problems etc).

A Better Solution

With .NET 3.5 SP1 came ASP.NET History – a great cross browser history navigation implementation. It’s very easy to use. As the user performs actions you set history points – a history point contains the page title and some data (this can be a complex JavaScript object etc). You hook in to the “user has navigated using the browser back or forward button” events and when they fire you get your piece of data returned. There is lots of information available about ASP.NET AJAX History, have a Google around. There is also a quick intro video here.

Note: ASP.NET AJAX History was added with .NET 3.5 SP1 (along with ADO.NET Data Services and Entity Framework amongst other things).

All that remains is to hook a Silverlight application in to these events and have it create the history points as required. This is all made very easy by Silverlight’s DOM bridge.

The steps involved here are:

  • Create classes in both Silverlight and JavaScript to manage the history communication between the two.
  • The Silverlight class will encapsulate communication of new history points to the JavaScript code. It will also receive notification of navigation changes and raise events to other Silverlight code.
  • The JavaScript class will watch for navigation changed events and notify the Silverlight code of the events. It will also provide an interface to the Silverlight class to add new history points.
  • The Silverlight application will instantiate the history manager and expose it to the JavaScript using HtmlPage.RegisterScriptableObject
  • Set EnableHistory to true on the ScriptManager control.

<Sample Code>

ASP.NET AJAX History and Silverlight Code Example

</Sample Code>

Please note: when running this code please ensure the web project is set to Start Up Project, and you default page is the sample ASPX page.

Demonstrate the Problem

Grab a copy of the sample code to make this section less painful 🙂

Create a new Silverlight Web Application Project (not a Web Site Project – never create one of these unless you really need to).

The first step is to create a little data model that we can use to mock up some data when the user moves back and forward through the application. Create a new class in the Silverlight application called SomeDataModel.

public class SomeDataModel
{
	public static string GetData(string dataId)
	{
		return string.Format("This is data item: {0}", dataId);
	}
}

Next add a couple of buttons to Page.xaml (back and forward) and a TextBlock to write out the result. In the Click event of each of these buttons increment/decrement a local variable and pass it to the SomeDataModel to mock up the data.

private void BtnForward_Click(object sender, RoutedEventArgs e)
{
	currentPoint++;
	TxtOutput.Text = SomeDataModel.GetData(currentPoint.ToString());
}

Now when you run the application you will be able to use your back and forward buttons to simulate paging through records or wizard steps.

Note at this stage of the application you cannot move back and forward using the browser back and forward buttons. If you had navigated to the Silverlight application from another page then clicking Back would take you back to that page, and not the previous record. A user may make this mistake.

Prepare Silverlight to Interact with ASP.NET AJAX History

If you have not previously done any JavaScript interaction from Silverlight 2 then I strongly suggest you have a bit of a Google around for terms like ScriptableMember and ScriptableType before moving forward.

Add a new class called HistoryManager to the Silverlight project. This class will be signalled from the JavaScript when a navigation event occurs and also allow other Silverlight code to create history points. This class needs to:

  • Expose events to signal when it has been signalled from JavaScript that a navigation event occurred
  • Expose methods to allow the addition of history points from other Silverlight code
  • .cs file will include a small EventHandler derived class to use when firing navigation events

Add the new event handler derived class to the bottom of the file after your new class.

public class HistoryEventArgs : EventArgs
{
	public string DataId { get; set; }
}

In the HistoryManager class expose some events to fire when a) class is ready to set history points and b) when a navigation notification is received from JavaScript. Also add a local variable to hold the reference to the instantiated JavaScript object.

//Raise this event when the JavaScript code notifies this class that Back or Forward was clicked.
public event EventHandler HistoryChanged;

//Raise this event when the object is set up and ready to accept new history points. This is 
//to ensure that history points are not set before the JavaScript code has initialised and 
//the JavaScript objects are known to this class
public event EventHandler HistoryReady;

//The JavaScript object that will be created on page load and passed in to this Silverlight class.
ScriptObject jsHistoryObj = null;

Next add some methods that will be called from JavaScript to deal with a navigation event and to set the JavaScript object on initialisation.

/// <summary>
/// Provides an interface for the JavaScript to call when a navigation JavaScript event is fired by ASP.NET AJAX
/// </summary>
/// The JavaScript object that is raising the event (will be managed ScriptObject at this point)
/// The data that was passed as part of the navigation event (in this case our "DataID")
[ScriptableMember()]
public void LoadPoint(object sender, object args)
{
	if (HistoryChanged != null &amp;&amp; args != null)
	{
		//Raising the HistoryChanged event so that subscribers can handle approriately
		HistoryChanged(this, new HistoryEventArgs() { DataId = args.ToString() });
	}
}

/// <summary>
/// This method is called from JavaScript to pass in the JavaScript class which will be used to add history points.
/// </summary>
/// The instantiated JavaScript class
[ScriptableMember()]
public void SetJSHistoryObject(ScriptObject sender)
{
	jsHistoryObj = sender;
	if (HistoryReady != null)
	{
		HistoryReady(this, EventArgs.Empty);
	}
}

Finish the class off with methods to add new history points and to set the browser page title.

 /// <summary>
/// Consumed by the Silverlight project to add a new history point. 
/// Uses the object passed in originally to SetJSHistoryObject from JavaScript.
/// </summary>
/// The internal Silverlight "DataID" - i.e. the piece of data to store
/// The page title to set (which will show up in the history of the browser)
public void AddPointData(string dataId, string title)
{
	jsHistoryObj.Invoke("addHistPoint", new object[] { dataId, title });
}

/// <summary>
/// Set the title in the browser.
/// </summary>        
public void SetPageTitle(string title)
{
	HtmlPage.Window.Eval(string.Format("document.title = '{0}'", title));            
}

Expose the HistoryManager to JavaScript

Silverlight classes need to be exposed before they may be consumed from JavaScript. The steps are:

  • Instantiate the class and store in to a local variable (usually in the App.cs file)
  • Use HtmlPage.RegisterScriptableObject to name and register the object in JavaScript

Add a local to hold the instanciated HistoryManger and alter the App() constructor to instantiate and register the object. Add a property to get the HistoryManager from other classes (like Page.xaml.cs)

//Holds the instanciated HistoryManager object for later reference.
HistoryManager historyManager;

public App()
{
	.........

	InitializeComponent(); //place the change after this

	//Instantiate the HistoryManager on application start and register it as a scriptable object.
	historyManager = new HistoryManager();
	HtmlPage.RegisterScriptableObject("silverlightHistoryManager", historyManager);
}

public HistoryManager History
{
	get
	{
		return historyManager;
	}
}

Get the JavaScript Ready

We need to create a little helper class in JavaScript to assist with the history events and to create new history points. This is the class that the code above will call.

Add a new JavaScript file to the web project and include it in the page (as an asp:ScriptReference on the ScriptManager control).

The break down of this JavaScript class will be:

  • Created as a class (JavaScript prototype)
  • On creation, navigationEventHandler function is set as a handler for Sys.Application.add_navigate which is fired when the user moves back and forward using browser buttons
  • On initiation gets the Silverlight object passed in and stores as local variable. This variable will be used to call methods back in Silverlight across the JavaScript DOM bridge
  • Exposes a method to set a new history point
  • File also includes code to instantiate the history object as well as provide an event handler for the Silverlight object OnPluginLoaded event to initialise the history controller class

Create a new JavaScript class. Include methods to initialise, handle a navigation event and to create a new history point:

historyManager = function() {
    this._silverlightControl = null;
    this._sHM = null;    
}

historyManager.prototype = {
    init: function(sender) {
        
    },
    navigationEventHandler: function(sender, args) { //This method will be called by ASP.NET AJAX when the user uses the back and forward buttons.
        if (this._sHM != null) {
            this._sHM.LoadPoint(sender, args.get_state().data);
        }
    },
    addHistPoint: function(pointData, pageTitle) { //This method is called from Silverlight to add a new history point.
        Sys.Application.addHistoryPoint({ data: pointData }, pageTitle);
    },
    setPageTitle: function(title) {
        document.title = title;
    }    
}

After the historyManager prototype definition, add some code to instantiate the object and hook it up to the ASP.NET AJAX History navigation events.

//Instantiate the historyManager. 
var historyInstance = new historyManager();

//Create a delegate to preserve scope when the navigation event handler fires.
var handler = Function.createDelegate(historyInstance, historyInstance.navigationEventHandler);

//Add the delegate tot he add_navigate event. This will cause the navigationEventHandler method of 
//historyManager to fire when the user uses the back and forward buttons in the browser.
Sys.Application.add_navigate(handler);

Add some code above the historyManager class to handle the Silverlight object’s load event.

function slLoad(sender) 
{
    var run = Function.createDelegate(historyInstance, historyInstance.init);
    run(sender);
}

In the ASPX page, add an OnPluginLoaded event to the asp:Silverlight control. This will call the JavaScript function and pass itself (the Silverlight control) in as the parameter.


The slLoad functio then creates a delegate (to preserve function scope) and calls the init function on the historyManager object.

The init function then gets out the Silverlight object and stores a reference to it. It then calls in to the Silverlight object using it’s SetJSHistoryObject method which then provides a reference to the instanciated JavaScript object from the Silverlight managed code.

Set Some Points and Off We Go!

Back in Silverlight now, open Page.xaml.cs. Add some code to create a history point in both the back and forward button event handlers that were created earlier. Add a call to addHistoryPoint from both these event handlers.

void addHistoryPoint()
{
	(App.Current as App).History.AddPointData(currentPoint.ToString(), string.Format("History when data was: {0}", currentPoint));
}

In the page constructor subscribe to the HistoryManager’s HistoryChanged and HistoryReady events.

//Hook up to the HistoryManager's HistoryChanged and HistoryReady events.
(App.Current as App).History.HistoryChanged += new EventHandler(History_HistoryChanged);
(App.Current as App).History.HistoryReady += new EventHandler(History_HistoryReady);

Add the following code to the event handlers that were created:

/// <summary>
/// Called when the HistoryManager object signals that it is initialised and ready to start accepting history point additions.
/// </summary>        
void History_HistoryReady(object sender, EventArgs e)
{
	//Create the first default history point.
	addHistoryPoint();
}

/// <summary>
/// When the ASP.NET AJAX framework signals to the JavaScript that there was a navigation event, the JavaScript object 
/// signals to the Silverlight HistoryManager object, which in-turn signals the event that this class subscribed
/// to in the Page() constuctor. This is the event handler at the end of that process.
/// </summary>
/// 
/// 
void History_HistoryChanged(object sender, HistoryEventArgs e)
{
	//Grab the history data that was passed as part of the event.
	int historyData = Convert.ToInt32(e.DataId);
	
	//Ensure that the navigation actually moved... don't perform any useless moves.
	if (historyData != currentPoint)
	{
		//Logic to load out and perform actions on the loaded data.
		currentPoint = historyData;
		TxtOutput.Text = SomeDataModel.GetData(currentPoint.ToString());
		//Manually re-set the browser title to appropriate text.
		(App.Current as App).History.SetPageTitle(string.Format("History when data was: {0}", currentPoint));                
	}
}

Basically the code creates the inital start up point when the HistoryManager signalls that it’s completed initilisation (i.e. all JavaScript objects are ready).
When the HistoryManager signals that the user has used back or forward browser buttons, the History_HistoryChanged is called at the end of the chain and it is ultimately what uses the history data to change the screen presentation.

A little side note is that you must re-set the page title youself.

That’s about it!

I know this is a bloody long post but I beleive this to be an important problem that needed solving before Silverlight could fill a true line of business application’s needs.

ASP.NET, Silverlight, Visual Studio, WCF, Windows Live

Silverlight and Windows Live ID Video

Complementing my earlier post here is a video cast of the same content.

It’s available in browser and in two download formats (WMV and iPod).

Check it out here.

NOTE: In the video I refer to Windows Live ID and services like it as delegated authentication providers… this isn’t 100% correct. Delegated authentication is a part of Windows Live ID. For more information see here.

AJAX, ASP.NET, C#, MVC, Silverlight, WCF, Windows Live

Silverlight, WCF, Membership, Forms Authentication and Windows Live ID

Watch the companion screen cast here

Nobody wants to be in the business of password management. It’s too risky and creation of username/password storage and authentication mechanisms adds a lot of overhead to any project. There are a number of ways to get around this and still provide secure access to your site.

One such way is by using an authentication provider such as Windows Live ID (Windows Live Login). Other providers include Yahoo! ID and Open ID.

This article will show you how to set up a Windows Live ID based login and then consume it from Silverlight using a combination of ASP.NET Membership, ASP.NET Application Services (the Authentication application service) and ASP.NET Forms Authentication.

The beauty of using this combination of technologies is that it negates the need for you to store and manage passwords, but you still get to manage profiles (name, email etc) using your own custom code and Membership provider. It also integrates with the existing ASP.NET Forms Authentication controls, like the login status control etc. Finally we can hook up to the Membership application service and consume login information from the Silverlight application.

Configuring Windows Live ID

Start by downloading the Windows Live ID Web Authentication SDK here. An overview of the SDK and general HOWTO can be found here.

Continue reading once you have the SDK and test site configured (files are installed to C:\Program Files\Windows Live ID\WebAuth, Create an IIS virtual called WebAuth and point to this location).

To use Windows Live ID (WLID from here on) in your web app you first need to register for a new application key on the Windows Live site. This process sets up your site to authenticate against the Windows Live ID service and provides the security keys to ensure that other parties cannot spoof your site’s identity.

Filling out this form is relatively straight forward, just follow the prompts. Leave Application verifier set to 0 for web applications.

You’ll note the code here. This secret code will be used to encrypt all information between your site and Windows Live servers. This key will be needed at a later stage so ensure you jot it down.

Part of this process is providing a return URL for your site which can make debugging a little difficult – it will try to return to your production site, and will not work with “localhost” etc. Luckily the SDK includes a sample account for testing.

For brevity we will be using the sample account which is present in the SDK which works with localhost. To see this example account in action navigate to http://localhost/webauth/sample/default.aspx and click the Sign in button.

Create a Demonstration Project

<Sample Code>

http://www.webjak.net/files/FileDetail.aspx?fileId=d3c01c17-4056-4d3d-b6a3-1c7d159866c2

</Sample Code>

All said and done, all you get out of a Windows Live ID login is a key that is unique to both your site and the current user. This key will never be the same for any user on any other site in the world.

To demonstrate we will create a simple TextBlock which will show the login status of the user. Examples will focus only on the authentication side of things, so there is some assumed knowledge (you can see how the rest is done in the code download).

Start by creating a new Silverlight application in Visual Studio called “Silverlight Comments”. Ensure you select “add a new Web to the Solution”. Like all true developers should select “Web Application Project” in the Project Type drop down. NOTE: This stuff needs Visual Studio 08.

Handling the Login Response

In the Web Applicaton project add a new class library called WindowsLiveLogin.cs and copy in the code from C:\Program Files\Windows Live ID\WebAuth\App_Code\WindowsLiveLogin.cs. This class allows you to decode the tokens that are returned from the Windows Live system.

Copy and paste in the default app settings from the example project:


 
 
 

When you are ready to go live with a real site you will change these settings to reflect the real values from the Live ID registration site.

Next we will create the page that Windows Live Login returns to (this is the page that you would normally configure in the Return URL in the Live ID configuration site). This page takes the response and performs an action (logout, clearcookie and default) depending on the data. It also creates the login cookie that we will use to store the login.

Because this page needs to work with the sample WLID account, create the new page in Sample/webauth-handler.aspx. In production you can create this page where ever you like, as long as you set the correct return URL in the WLID configuration page (dev.live.com).

Open C:\Program Files\Windows Live ID\WebAuth\Sample\webauth-handler.aspx.cs and copy the code from between the two class { } delimiters in to the code behind of the new page you just created.

e.g.


public partial class HandlerPage : System.Web.UI.Page
{

    //Copy all this 
    const string LoginPage = "default.aspx";
    const string LogoutPage = LoginPage;
    const string LoginCookie = "webauthtoken";
    static DateTime ExpireCookie = DateTime.Now.AddYears(-10);
    static DateTime PersistCookie = DateTime.Now.AddYears(10);

    //... 

}

Move the const and static fields in to the WindowsLiveLogin class as we will need to access these from other areas of code. Ensure you make them public.

e.g.


public class WindowsLiveLogin
{
        public const string LoginPage = "default.aspx";
        public const string LogoutPage = LoginPage;
        public const string LoginCookie = "webauthtoken";
        public static DateTime ExpireCookie = DateTime.Now.AddYears(-10);
        public static DateTime PersistCookie = DateTime.Now.AddYears(10);
        /// <summary>
        /// Stub implementation for logging debug output. You can run
        /// a tool such as 'dbmon' to see the output.
        /// </summary>

Update any broken references that this causes.

Remove all content from webauth-handler.aspx except for the page directive

e.g.




Create the Login Page

The login page is very easy to create. The way that WLID works is you simply insert an IFRAME in to your page which then provides the Sign in link for you. You have control over the basic formatting. See here for more information.

Start by creating a new ASPX page to host the sign in IFRAME. In this example I created a page called Login.aspx in the WebAuth sub folder. In a production application you may want to put your “return” page in this location also.

Place this IFRAME in between the body tags in Login.aspx.


<iframe 
       id="WebAuthControl" 
       name="WebAuthControl"
       src="http://login.live.com/controls/WebAuth.htm?appid=&context=myContext&style=font-size%3A+10pt%3B+font-family%3A+verdana%3B+background%3A+white%3B"
       width="80px"
       height="20px"
       marginwidth="0"
       marginheight="0"
       align="middle"
       frameborder="0"
       scrolling="no"
       style="border-style: hidden; border-width: 0">
   

Note the appid parameter here has been changed from the example to load from the application settings you pasted in earlier.

First Test Run

Before the log in can work correctly the site needs to be running in a virtual called WebAuth (like the sample).

– Start by opening IIS and removing the sample site (WebAuth) you added earlier.
– Next in Visual Studio, right click on the Web Application project and select properties.
– Select the Web tab.
– Select “Use Local IIS Web server” option.
– In the Project URL field, enter http://localhost/WebAuth.
– Click “Create Virtual Directory”
– Close the properties window.

Ensure that Login.aspx is set as the start-up page, and press F5. You should see a Sign in link. Click it, sign in and it will log you in! You will see a 404 error after you log in, fear not as this is expected. This is because in webauth-handler.aspx there is a line which redirects on successful login:


	res.Redirect(WindowsLiveLogin.LoginPage);

Change the LoginPage constant in WindowsLiveLogin.cs to the following to redirect back to the homepage.

public const string LoginPage = "~/default.aspx";

Keeping Them Logged In

Simply creating the WLID cookies doesn’t mean the user is logged in to your site, and certainly doesn’t mean that the system is working with Forms Authentication. Also there hasn’t been any mention of custom Membership yet. The only way to tell the user is logged in at this stage is to check the WLID cookie. This may be okay in some instances, but we want the full implementation with all the bells and whistles!

In this scenario by the time the user has logged in to WLID and WLID has returned to our “return page” the user is authenticated. So calling off to Membership’s ValidateUser() serves only to create the new user in your system. The typical idea here is when they first log in to your site you ask them to enter some information like nickname and email address etc.

Create a new folder off the web root called Membership and add anew class called WLIDMembership.cs. We will implement a very simple membership provider.

For the full code review the sample code available in this article.

Note: The actual implementation of this membership provider is outside the scope of this article. This includes the back-end database look-ups and schema etc. In this example I return hard-coded values.


public class WLIDMembership : MembershipProvider
{
	//Lots of methods that look like this:
	public override bool ChangePassword(string username, string oldPassword, string newPassword)
	{
		throw new NotImplementedException();
	}

	public override MembershipUser GetUser(string username, bool userIsOnline)
	{

		//Normally you would perform a lookup in your own database for this user.

		//This example is hardcoded for brevity.

		//MyUser currentUser = UserManager.GetItem(username);
		//if (currentUser != null)
		//{
		//    return convertUser(currentUser);
		//}
		//throw new ApplicationException("User not found");            

		MembershipUser u = new MembershipUser("WLIDMembership", "SomeNickname", "WLID_From_Database", "", "", "", true, false, DateTime.Now, DateTime.Now, DateTime.Now, DateTime.Now, DateTime.Now);
		return u;
	}

	public override MembershipUser GetUser(object providerUserKey, bool userIsOnline)
	{
		//Lookup user by WLID key

		MembershipUser u = new MembershipUser("WLIDMembership", "SomeNickname", "WLID_From_Database", "", "", "", true, false, DateTime.Now, DateTime.Now, DateTime.Now, DateTime.Now, DateTime.Now);
		return u;
	}

	public override string GetUserNameByEmail(string email)
	{
		//Lookup username and convert to email.
		return "SomeNickname";            
	}

	public override bool ValidateUser(string id, string providerName)
	{
		try
		{
			//Sample of how this may look in a real system

			//MyUser user = UserManager.GetItem(id);
			//if (user == null)
			//{
			//    user = new MyUser();
			//    user.Id = id;
			//    user.CreatedDate = DateTime.Now;                
			//}

			//user.LastLoginDate = DateTime.Now;

			//UserManager.Save(user);                
		}
		catch (Exception ex)
		{
			throw ex;
		}
		return true;
	}  
}

Next set up the Membership provider in the web.config (find the <authentication mode=”Windows”/< tag and place it above that.



  
	
	
  


This sets the membership provider to the be default provider for the site. Now all components that understand Membership can use the new class.

In webauth-handler.cs add the following code to call the Membership Validate method and create the account if it doesn’t already exist:

if (user.UsePersistentCookie)
{
	loginCookie.Expires = WindowsLiveLogin.PersistCookie;
}

//Add this code.
System.Web.Security.Membership.ValidateUser(user.Id, "Windows Live ID");         

Forms Authentication

This is all well and good but the user is _still_ not logged in (as far as Forms Authentication goes)!

Simply calling Membership.Validate() doesn’t actually log the user in, it merely tells the system if the user has provided correct authentication and leaves it at that. There are a few more steps required to hook this up with Forms Authentication.

First there is another change required in the web.config. Overwrite <authentication mode=”Windows”/< with the following:



  


This is a pretty basic Forms Authentication configuration – of interest are the loginUrl and name attributes. loginUrl tells Forms Authentication controls where to redirect the user to log in. The name attribute tells Forms Authentication which cookie to use to verify login status, and also how to log a user out (by removing the cookie, or setting its expiry to the past).

Now all that is left is to make the WLID handler register the user as having logged in with Forms Authentication. This is done by creating a Forms Authentication token in code. Normally you don’t have to perform this step with Forms Authentication as the login controls do it for you, but we are using a more customised login process.

Back in webauth-handler.aspx.cs add the following method:

private static bool createTicket(string userName, bool stayLoggedIn, string type, DateTime cookieTime)
{
	FormsAuthentication.Initialize();

	FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(1, userName, DateTime.Now, cookieTime, stayLoggedIn, type, FormsAuthentication.FormsCookiePath);

	string hash = FormsAuthentication.Encrypt(ticket);

	HttpCookie cookie = new HttpCookie(FormsAuthentication.FormsCookieName, hash);

	if (ticket.IsPersistent) cookie.Expires = ticket.Expiration;

	HttpContext.Current.Response.Cookies.Add(cookie);

	return true;
}

Thanks to Andreas Kraus for the FormsAuthenticationTicket example 🙂

This code creates a new authentication ticket based on the name attribute that is on the forms element in the web.config.

Add a call to this new method right below where you added the call to Membership.ValidateUser():

createTicket(user.Id, user.UsePersistentCookie, "", WindowsLiveLogin.PersistCookie);

Note here the UsePersistentCookie value from WLID is passed through to Forms Authentication. This means that the system will obey their selection to not keep them logged in between browser sessions (“remember me” option).

If you where you add an asp:LoginName control to the homepage now you would see something like 85bf44d80fbed3f5900adfca1e269199 after you Sign in. This is they WLID token unique to the site and the user. You will see what ever you pass in to createTicket… so if you set up your site to request the select a unique nickname on first Sign in and pass that in to createTicket the users will see that nickname in the login control.

Once more small thing is needed to support Forms Authentication Sign out when the user clicks the Sign Out link (which will be automatically shown in the Sign in IFRAME after they have authenticated ie. go back to login page once signed in).

– In webauth-handler.aspx.cs, add FormsAuthentication.SignOut(); to both the action==”logout” section and the action==”clearcookie” section.

Expose a WCF Service

Before Silverlight can read the sign in status we need to configure a WCF service.

– Add a new folder called WebServices
– Add a new Text File called Authentication.svc. We don’t add a normal Silverlight WCF here as we want to have manual control over its configuration etc.
– Past the following code in to the new file:


This tells the service to expose the System.Web.ApplicationServices.AuthenticationService.

Note the Factory attribute here. This is required if you are running the service on a shared server (like an ISP or external web hoster). This prevents WCF services from getting all sorts of strange errors like 404 and “already bound to this address” errors. If you need to implement this then follow these steps. If not remove the Factory attribute and continue.

– Add a reference to System.ServiceModel.
– Add a new class to the same folder called Factory.cs.
– Copy in the following code:

public class AuthenticationServiceHostFactory : ServiceHostFactory
{
    public override System.ServiceModel.ServiceHostBase CreateServiceHost(string constructorString, System.Uri[] baseAddresses)
    {

        return base.CreateServiceHost(constructorString, baseAddresses);

    }
    protected override System.ServiceModel.ServiceHost CreateServiceHost(Type serviceType, System.Uri[] baseAddresses)
    {
        Uri webServiceAddress = new Uri("http://localhost/WebAuth/WebServices/Authentication.svc");
        ServiceHost webServiceHost = new AuthenticationServiceHost(serviceType, webServiceAddress);
        return webServiceHost;
    }
}

public class AuthenticationServiceHost : ServiceHost
{

    public AuthenticationServiceHost(Type serviceType, Uri baseAddresses)
    {

        UriSchemeKeyedCollection BaseAddressScheme = new UriSchemeKeyedCollection();

        BaseAddressScheme.Add(baseAddresses);
        base.InitializeDescription(serviceType, BaseAddressScheme);

    }
    protected override void ApplyConfiguration()
    {

        base.ApplyConfiguration();

    }
    protected override void InitializeRuntime()
    {

        base.InitializeRuntime();

    }
}

You can override the service address by altering the URI above (change it to the real URL of the service in production). It’s probably best to have this as an app setting so you can have prod and dev versions.

Finally you need to expose the service in the web.config. Add the following configuration right above the </configuration> element:


    
      
        
          
        
      
    
    
              
        
          
          
        
      
    
    
          
      
        
        
      
    
  

  
    
      
        
      
    
  

Finally Some Silverlight!

Now we can import the WCF service in to the Silverlight and consume it!

– In your Silverlight project, right click references and select “Add Service Reference”.
– Click Discover. You will see the authentication service.
– In the Namespace field enter “AuthenticationService”
– Click OK.

We will keep the Silverlight application very basic. It will simply be a text block which tells the user if they are logged in or not.

Add a TextBlock to Page.xaml:


In Page.xaml.cs add the following code:

private void performLoginCheck()
{
	AuthenticationService.AuthenticationServiceClient authClient = new Silverlight_Comments.AuthenticationService.AuthenticationServiceClient();            
	authClient.IsLoggedInCompleted += new EventHandler(authClient_IsLoggedInCompleted);
	authClient.IsLoggedInAsync();            
}       

void authClient_IsLoggedInCompleted(object sender, Silverlight_Comments.AuthenticationService.IsLoggedInCompletedEventArgs e)
{
	bool loginState = e.Result;
	LoginStatus.Text = string.Format("The user is{0} logged in!", loginState ? "" : " not");
}

Place a call to performLoginCheck(); right after InitializeComponent(); in the constructor.

You are just about ready to test out your shiny new system! Before you can test it, ensure you Silverlight app is placed in your Default.aspx file – examples on how to do this where added when you created the application called something like “SilverlightCommentsTestPage.aspx”.

Press F5 and see how you go!

NOTE: If you are getting a 404 error, see here.

With any luck your Silverlight application is detecting if the user is authenticated by using Windows Live ID!

NOTE: When you download and run the sample application, ensure you set the Web application as the start-up project or you will have problems.

AJAX, ASP.NET, C#, Visual Studio, Web Services

Client Side Template AJAX Control

Since the release of ASP.NET AJAX it’s been relatively easy to update portions of a page with new data from the server without performing a full page post back.

Place any grid in an UpdatePanel and voila you have a nice AJAX style grid… but is this really a good thing? Sure it’s easy, but that doesn’t make it “good”. UpdatePanels are great for small chunks of data, but not so good for larger amounts of data (like grids etc).

How do you fix this problem? Well, you could ask the server for some JSON and parse it yourself, loop through a set of elements and update them using JS. This method performs very well… but comes at the cost of difficulty and code complexity. It would also be hard to maintain.

What about a way to get the best of both worlds. Client side controls that use JSON data from the server (so no markup is sent to client after initial load) and easy to use!

Thanks to Nikhil Kothari I had a cool idea to allow an easy implementation of client site templating.

<Sample Code>

http://www.webjak.net/files/FileDetail.aspx?fileId=af820273-6a56-47a2-8f70-eb9dea4a4f34

</Sample Code>

<Live Example>

http://dev.webjak.net/ClientTemplateControlExample/

</Live Example>

Check out the example site! How fast is it?? Load up Firebug or something and test it out for yourself!

Introducing the ClientTemplateControl

The ClientTemplateControl is an ASP.NET AJAX Client Control. It works very similar to the DataList in ASP.NET. Create an item template with markup and bind attributes to data elements. Then assign a datasource to the control and bind it. The control will then take care of iterating through the data source, repeating the item template for you.

When you reload the data, for example after paging – the ClientTemplateControl detects that the elements are already added to the page and REUSES THEM! That’s right, it uses the template to whip through and only update their attributes etc! This means no setting of innerHTML etcetera on a refresh! Well, except where your template “bindings” set innerHTML (like in spans etc).

This HTML DOM reuse means that there is absolutely no flicker either… and there is no DOM rendering time on large lists… just the time it takes to go through and update attributes etc… this is very fast.

To bind an attribute, use the { and } to delineate a bound attribute. The control detects { and compiles the attribute text in to a function, which is then run, passing in the current data context (the current row from the data source and a few other parameters).

Example

This will write out the value in dataContext.Surname into the title:

<span title="”{return">Some Span</span>

To set innerHTML etc, which don’t have attributes you can use the “this” JavaScript keyword.

Example

This will write out the value in dataContext.Surname into the title and innerHTML:


<span title="”{this.innerHTML"></span>

You might want to alternate color between odd and even rows.

Example

Note here I am using title to set the style. You could use any parameter you like. Note also that title above does not return anything. This is fine, and will result in an empty title attribute.

<div title="{this.style.backgroundColor=pageRowIndex % 2 == 0?'lightblue':'#eeeeee';}">rest of the template</div>

. pageRowIndex is another parameter passed into the functions by default.

There is a special circumstance when working with anchors and images. “src” and “href” don’t like the special “{}” syntax. You can still set href and src, just do it using the “this.” syntax.

Example

<img style="border:none;" title="{this.src='ImageLoader.ashx?id=' + dataContext.ProductID;}" />

Getting data in

Providing data to the control is very easy. You may want to get some JSON data from a webservice and bind to the control in pages.
Note: Pagination is not handled by the control, so only return the data for a single page at a time from the server.

E.g. getting data from a AJAX-enabled WCF service is easy!

AdventureWorksService.GetProducts(page,$get("pageSize").value,endGet);

function endGet(result)
{
	c.set_itemsSource(result);
	c.dataBind();
}

How easy is that?!

The “result” variable above lives in the WCF service as List<AWProductBusinessObject>, thus serialising as an array of AWProductBusinessObject to JSON. AWProductBusinessObject looks like this:


[DataContract]
public class AWProductBusinessObject
{
	 [DataMember]
	 public int ProductID { get; set; }
	 [DataMember]
	 public string Name { get; set; }
	 [DataMember]
	 public string ProductNumber { get; set; }
	 [DataMember]
	 public string Color { get; set; }
	 [DataMember]
	 public decimal StandardCost { get; set; }
	 [DataMember]
	 public decimal ListPrice { get; set; }
	 [DataMember]
	 public decimal? Weight { get; set; }
	 [DataMember]
	 public int DaysToManufacture { get; set; }
	 [DataMember]
	 public DateTime SellStartDate { get; set; }
	 [DataMember]
	 public DateTime ModifiedDate { get; set; }
	 [DataMember]
	 public Byte[] SmallImage { get; set; }
	 [DataMember]
	 public Byte[] LargeImage { get; set; }
}

I’ll leave the rest of this detail to the sample code.

Something interesting in the control is that it caches the evaluated functions from the “bindings” so they are only evaluated on the creation of the first instance of the template on the first page. That’s right, only the very first record on the first page has this overhead, all other items on that page (say you have 10 records, 9 will use the cache) and all other page refresh items (10 from 10 on subsequent refreshes) will use the cache and be very fast.

Note: Code does not check for the last page in the data… it will just keep paging into oblivion 🙂
Note: You may have noticed every time I write “bind” or “binding” it’s in quotes. This is because it’s not real binding… it just chose to call it that because it was familar 🙂
Note: This has no relation to the ASP.NET AJAX Futures databinding feature.
Note: This is by no means W3C compliant – so don’t bother commenting about it – unless that is you can find a way to upgrade it to make it W3C compliant 🙂
Note: This code has been tested with Firefox 2 and 3, Opera 9.5, IE 7 and Safari 3.1.2.

AJAX, ASP.NET, C#, Visual Studio

UpdatePanel Trigger Chainer

I present a little JavaScript class I wrote which allows you to queue up a list of UpdatePanel trigger events, which will each be executed in order, one after the other when the previous update finishes. For example, you may want to refresh UpdatePanel1 then UpdatePanel2 and so on.

With a bit of ingenuity you could string together updates in such a way to improve user load times and UX.

Some ideas may be:

  • Load sections of the page as the scroll into view.
  • Load panels in tabs one at a time. This would be good because the first tab would load very quickly, the rest would load in the background whilst user has attention on the first tab.
  • Pre-load other data in the background – e.g. Load a list of images, then background-load their detail into hidden DIV elements. Show the hidden div when user clicks.
  • A random looking recursively loading control that shows date times with random colours. Err… see an example of the UpdatePanelEx.Chainer class here: http://dev.webjak.net/updatepanelchainerexample/.

Download the class and example site here: UpdatePanelChainer.zip

I’m sure there are heaps of cool things you could do with this… it’s basically a great way of chaining up asynchronous post backs that run after the main page has loaded.

It works by queuing up the names of items in the page which can trigger an asynchronous postback, like buttons (hidden or visible) and UpdatePanels themselves.

Triggers may be added at any time, even if an UpdatePanelEx.Chainer object is already processing other items in the queue. Triggers may be added from both client side and from server side by filling a property on the UpdatePanelEx.Chainer default object (with a JSON array – example provided).

The UpdatePanelEx.Chainer creates a default known instance on startup so server side code as something to send trigger additions to, but you may create as many instances of the class yourself as you like. UpdatePanelEx.Chainer will continue to operate when multiple instances are attempting to update at once – they will each enter a one second timeout before retrying to initiate an async postback.

Usage is quite simple:

var chainer = new UpdatePanelEx.Chainer(["ItemOne", "ItemTwo"], true);

The first parameter is an array of items to update. These must be the full client ID, separated by the $ sign – e.g. MyControl$Button1.
The second parameter sets if the UpdatePanelEx.Chainer should automatically start posting back when items are added.

Items may also be added by calling the addTrigger method:

//Note: MyControl$Button1 is not required when the items are in the base page (only when the triggers are in a user control or other control container)
chainer.addTrigger("MyControl$Button1");

Full Property List

  • beginUpdating() – start the post backs. Use this when beginUpdateOnAddTrigger (set by property below, or in constructor) is set to False to being the post backs.
  • set_beginUpdateOnAddTrigger(boolean) – When set to true, post backs will automatically commence when the next item is added to the triggers array.
  • addTrigger(triggerName) – Add a trigger to the queue.
  • set_jsonUpdateTriggerList(jsonArrayString) – Adds a JSON array of objects to the trigger queue. More on this below.

NOTE: There are some properties and functions in the class beginning with _. These are not intended to be called directly – doing so may cause unpredictable results.

Updating the trigger list from the server

Updating the trigger list from the server is quite simple. Either you can use the default known “static” object called ChainerStatic, or you can use your own instance of the UpdatePanelEx.Chainer class – it doesn’t matter as long as you know the name.

System.Web.Script.Serialization.JavaScriptSerializer ser = new System.Web.Script.Serialization.JavaScriptSerializer();
string[] updates = { string.Format("{0}$btnHidden", this.ClientID.Replace('_', '$')) };
string json = ser.Serialize(updates);
ScriptManager.RegisterStartupScript(this, this.GetType(), "AddJsonTriggers_" + this.ClientID, string.Format("ChainerStatic.set_jsonUpdateTriggerList('{0}');", json), true);

This sample code is from the recursive random colour example app linked above. The full source code is available a the bottom of this article.

The first line creates a JavaScriptSerializer object which can be used to serialise .NET objects into a JSON notation string.
Next we create an array of names that are the triggers for the updates. In this example we are only adding one object to the array, but you could add as many as you like. Note here how the client ID is appended to the front of the name, and the _ are converted to $.
The next line serialises the object to the JSON string. This string will be set into the set_jsonUpdateTriggerList property on the line after using ScriptManager.RegisterStartupScript.

About the Sample

Link: http://dev.webjak.net/updatepanelchainerexample/

The sample app includes a WebUserControl called DataPiece which queues its own asynchronous update on initial load. Variables are set into the view state to ensure it only does this once. When the queue reaches the control, the post back is fired on the button. This creates two more instances of the control and adds them to the two panels (thus repeating the process). There is a counter to ensure this only happens four times.

The Default.aspx page then adds two of these Data Piece controls on to itself, kicking off the recursive control add processes.

Chainer class listing and explanation


Type.registerNamespace('UpdatePanelEx');

UpdatePanelEx.Chainer = function(updateTriggers, beginUpdateOnAddTrigger)
{
    //construct some initial values
    this._updateTriggers = updateTriggers;
    this._initialised = false;
    this._beginUpdateOnAddTrigger = beginUpdateOnAddTrigger;
    this._doing = false;
    this._jsonUpdateTriggerList = "";
}

First a new class is created and some constructor values are set. This class accepts two parameters: an array of items to add to the trigger collection and a boolean which sets the beginUpdateOnTrigger property.


UpdatePanelEx.Chainer.prototype = {
    _init : function()
    {
        if(!this._initialised)
        {
            //hook up the events, ensure this only happens once
            this._initialised = true;
            var prm = Sys.WebForms.PageRequestManager.getInstance();
            var callBackDelegate = Function.createDelegate(this, this.endRequestHandler);
            prm.add_endRequest(callBackDelegate);
        }
    },

_init is automcatically called the first time doUpdate is called. This function hooks up the event which instructs the chainer when the previous update has completed. Take note of the call to Function.createDelegate(). This method ensures that the scope of the callback is set to “this”… meaning the callback can access all the local members as you would expect. Without this the callback would have another scope (probably the event handler or something) and you would not be able to call any methods on the chainer class.


    endRequestHandler : function(sender, args)
    {
        //continue the update chain
        this.doUpdate();
    },    

This function is called when an asynchronous postback has completed. It calls the doUpdate() method to continue on with the chain. Note that without the Function.createDelegate call above, this.doUpdate() would not be available.


    doUpdate : function()
    {
        if(!this._initialised)
        {
            this._init();
        }
        this._doing = true;
        //while there are triggers left, continue the update process
        if(this._updateTriggers.length > 0)
        {
            var prm = Sys.WebForms.PageRequestManager.getInstance();
            if(prm.get_isInAsyncPostBack())
            {
                var pnl = this._updateTriggers[0];
                Sys.Debug.trace("Update wait: " + pnl);
                this._doUpdateWait();
                return;
            }
            var panel = this._updateTriggers[0];
            Array.removeAt(this._updateTriggers, 0);
            prm._doPostBack(panel, '');
        }
        else
        {
            //if there are no triggers left, then end the process
            this._doing = false;
        }
    },

This function forms the central part to the chainer. First it checks if the init function has been run. The next part only runs if there are items in the _updateTriggers array. Next the PageRequestmanager object instance is populated in to the prm variable and a check is performed to see if there is already an asynchronous post back in operation. If there is then the _doUpdateWait() method is called. This provides some kind of concurrent chainer object protection.
If all is clear then the system will grab the next object from the _updateTriggers array, then remove it from the array and call the PageRequestManager’s _doPostBack method. The trigger array acts as a FIFO stack… first in first out (this is achieved by getting the first object out).


    _doUpdateWait : function()
    {
        //this performs some basic sychronisation between two or more Chainers running at one time
        var doUpdateDelegate = Function.createDelegate(this, this.doUpdate);
        window.setTimeout(doUpdateDelegate, '1000');
    },

This function is called when there was already an asynchronous post back running. It sets a one second timeout before retrying. Note another call to Function.createDelegate to ensure the callback has the correct scope.


    beginUpdating : function()
    {
        //_doing ensures we dont accidentally kick off two update chains.
        if(!this._doing)
        {
            this.doUpdate();
        }
    },

This function kicks off the chain of asynchronous post backs. This method should be used over the doUpdate() method as it checks first if this class is already running the update chain.


    addTrigger : function(triggerName)
    {
        //add more triggers to the update array
        Array.add(this._updateTriggers, triggerName);
        if(this._beginUpdateOnAddTrigger)
        {
            this.beginUpdating();
        }
    },

This function allows the addition of a trigger at any stage. If beginUpdateOnAddTrigger is set to true then the update chain is started.


    set_beginUpdateOnAddTrigger : function(autoBeginMode)
    {
        this._beginUpdateOnAddTrigger = autoBeginMode;
    },

Turns the auto start when add trigger on or off.


    set_jsonUpdateTriggerList : function(jsonString)
    {
        //this method handles adding triggers that have been sent from server
        var serializer = Sys.Serialization.JavaScriptSerializer;
        //convert the serialised json data into a proper array
        var arrAdd = serializer.deserialize(jsonString);
        //create a little delegate to pass into the Array.forEach below. There are easier ways, but this is good practice 🙂
        var arrayAdd = Function.createDelegate(this, function(item) {
            Array.add(this._updateTriggers, item);
            Sys.Debug.trace ("Added: " + item);
            });
        Array.forEach(arrAdd, arrayAdd);
        if(this._beginUpdateOnAddTrigger)
        {
            this.beginUpdating();
        }
    }

This method is used to add items to the trigger array from the server side. First a serializer object is created which is used to take the input JSON string and convert it to a JavaScript object. Next the array that was in the JSON string is copied into the triggers array. Note how I decided to use a function delegate and the Array.forEach method… there are simpler ways, but I wanted to try this out as I had not used it before :). Once again, if beginUpdateOnAddTrigger is set then this method will start the update chain process.

}

UpdatePanelEx.Chainer.registerClass('UpdatePanelEx.Chainer');

var ChainerStatic = new UpdatePanelEx.Chainer([], true);

The last part here registers the class with the ASP.NET AJAX type system and creates a known “static” type object mainly for use from the server (a known object is required from server to update using the set_jsonUpdateTriggerList function.

Known Issues

  • I noticed that the progress templates for the panels aren’t working – I will investigate and update the post.
  • Refreshing the sample app will not cause the updates to start again due to a bug where the viewstate isnt cleared out 🙂

So there you have it.

Questions and comments are welcome as always.
This work is licenced under a Creative Commons Licence.

AJAX, ASP.NET, C#, Visual Studio

UpdatePanel Addiction

Update panel addiction is a nasty thing. Projects start, it’s decided they are to be “Ajaxified” – the answer: chuck in 50 million update panels to do the trick. The more the better is the general principal, as this means there are smaller sections of the page being updated. UpdatePanels are designed to be used to update small portions of the page – the less content encompassed by an UpdatePanel, the better.

With a sufficiently advanced application the need may arise to refresh update panels from other update panels by calling the Update() method.

One problem that can occur in such advanced applications (especially those which use some kind of framework for events and control instantiation and such) is that slowdowns can be caused when update panel refreshing is too liberal. Perhaps only a small section of the page should be being updated, but for some reason it’s parent UpdatePanel is also refreshing, or there are simply too many updates per post back. Slowdowns can also be caused when large amounts of content, like in an un-paged grid or table of data is sent back to the client for rendering… the delays are not only in the transmission of the data to client but also while the browser is rendering the new data (especially when tearing down the existing DOM elements, more on that in another post).

What ever the case, sometimes it’s nice to see what is going on under to hood during an async postback.

Ajax Response

The ASP.NET Ajax system sends back async updates as special tokenised text. E.g.

247|updatePanel|UpdatePanel1|
askdljfjlkasdf aklsdflkasfjlkadjkldas
<br /><br /><span id="Label1">29/04/2008 6:40:36 AM</span><br />
<input type="submit" name="Button1" value="Button" id="Button1" />
|128|hiddenField|__VIEWSTATE|/asdfasdf|48|hiddenField|__EVENTVALIDATION|/wEWAgLWzvmRBwKM54rGBpmraqo+tCbnoafT7bqYDaCZ2bH5|
0|asyncPostBackControlIDs|||0|postBackControlIDs|||13|updatePanelIDs||tUpdatePanel1|
0|childUpdatePanelIDs|||12|panelsToRefreshIDs||UpdatePanel1|2|asyncPostBackTimeout||90|12|formAction||Default.aspx|13|pageTitle||Untitled Page|

To view the tokenised output of your page, try something like the following code in your page:


protected override void Render(HtmlTextWriter writer)
{
   ScriptManager sm = ScriptManager.GetCurrent(this);
   if (sm == null || !sm.IsInAsyncPostBack)
   {
      //this is a normal postback
      base.Render(writer);
   }
   else
   {
      //this is an async post back (partial render) so lets see what is going to be output
      HtmlTextWriter textWrite = new HtmlTextWriter(new System.IO.StringWriter());
      base.Render(textWrite);
      //have a look at content variable after the next line has run
      string content = textWrite.InnerWriter.ToString();
      writer.Write(content);
   }
}

The basic breakdown of this output is: [SectionLength]|[TokenCommand]|[TargetID]|[Content]

As you can see there are various commands. Some of the more interesting ones are:

  • updatePanel – refreshes the target UpdatePanel with the new content. UpdatePanels are simply either DIVs or SPANs (depending on the the RenderMode attribute is set to Block or Inline) in the final rendered content – so this content will just overwrite the existing content.
  • hiddenField – update any field’s value with the new value here
  • expando – update any element’s attribute with a new value
  • pageTitle – updates the page title in the browser window
  • focus – after the render has completed this control will be given focus.

You can find lots more information and some cool tricks on Siderite Zackwehdex’s blog here: http://siderite.blogspot.com/2007/05/messing-with-updatepanel-to-speed-up.html.

Browser Add-ins

There are a number of ways to view the innards of a server round trip. For starters, every developer should have these tools: Firebug for Firefox and Web Development Helper for IE – you do test in at least these two browsers right?

Web Development Helper (Internet Explorer only) (by Nikhil Kothari of ASP.NET team fame) allows very close inspection of the AJAX response. Grab the installer from here: http://www.codeplex.com/webdevhelper.

Start the add-in by selecting View -> Explorer Bar -> Web Development Helper.

Fire up your latest AJAX based work of art, and select the “Enable Logging” checkbox. Perform an action that will kick off an UpdatePanel refresh, then double click the response in the logging window. Select the Response Content tab from the bottom section of the popup and voila – you have your AJAX information. Fold out updatePanel and select one of the items – you will see the content that was sent back to the client.

WebDevHelperSShot

Trace debugging UpdatePanel movements

In Visual Studio, it’s sometimes nice to write out some custom debugging information into the output window to get a broader view of what is happening with your program. Previously this has not been straight forward to achieve with client script (often JS debug sessions ended in “alert” style debugging) – but with the MS AJAX Client Library it’s a breeze.

Using the Web Development Helper add-in is a bit like bringing up a quick watch every time you want to view the information… but what if you want to survey an application over a period of time or you have a lot of action going on and want to see what’s happening without having to stop each time to bring up the debug results.

Luckily you can hook into the PageScriptManager object to receive notifications when the AJAX framework is doing things – then show some information about what is going on in the Visual Studio output window (and in the Script Console window in Web Development Helper).

Here is something I have put together for you to try.

First create a new JS file in your project and place the following code in it:


var updatePanelHook =
{
    initialised : false,
    init : function()
    {
        if (!this.initialised)
        {
            //hook up to the various page loading events to provide extended update panel functionality
            var prm = Sys.WebForms.PageRequestManager.getInstance();
            prm.add_pageLoading(pageLoadingHandler);
            this.initialised = true;
        }
        function pageLoadingHandler(sender, args)
        {
            var arr = args.get_panelsUpdating();
            for(var i =0; i< arr.length;i++)
            {
                var updatedPanel = arr[i];
                Sys.Debug.trace(String.format("Updated: {0}", updatedPanel.id));
            }
        }
    }
}

if (typeof(Sys) != 'undefined' )
{
    Sys.Application.notifyScriptLoaded();
}

Ensure the script is included in your page (or master page etc). It's best to use the ScriptManager object to do this:

Place the following code in your page to kick off the init function:


      updatePanelHook.init();

NOTE: The init() function checks that it has not already performed initiation, because the updatePanelHook variable and associated event subscriptions will persist though partial updates.

In Web Development Helper, switch to the Script Console view by clicking on HTTPLogging and selecting the option from the drop down.

Perform an action that causes an UpdatePanel post back and you will see the results of you postback in the script window. If you have run your project by debugging from Visual Studio, you will also see the results in the output window there. Slot that into a larger ASP.NET AJAX based app and it should assist you figuring out what your app is doing under the hood!

An interesting point here is that the objects in the “arr” variable in this code are DOM elements (DIVs and SPAN’s)… so you can do all sorts of things with them if you like.

This process helped me figure out why a large commercial web app I was working on was slowing down, and with some ingenuity I managed to get the number and size of the UpdatePanel requests right down, gaining more than a 2x speed improvement across the entire app.

One other small point: when performing HTTPLogging in Web Development Helper, keep an eye on the Response Size column… obviously the smaller you can get your responses the better.