.Net

Federated SignIn Requires Federated SignOut

Posted on Updated on

Using WIF and a Passive STS is cool, but it’s even cooler when your Passive STS is in a different machine.

Now that we have Federation SignIn and a Passive STS that lives in a Different box and all out web apps rely on that external STS… How can I sign out?

In my case I tried everything and of course, it worked in my machine. Then it got deployed and the user didn’t get signed out because the browser didn’t expire the WIF token.

I executed all this and custom code to make the cookies expire but there was one left, the one created by the STS.

FederatedAuthentication.SessionAuthenticationModule.SignOut();
FederatedAuthentication.SessionAuthenticationModule.DeleteSessionTokenCookie();
FederatedAuthentication.WSFederationAuthenticationModule.SignOut(false);
FormsAuthentication.SignOut();

Bloody cookie, die!!! But nothing. The browser wouldn’t expire it and then it all made sense, I can’t make expire cookies that I haven’t created myself. So I thought that there would be a solution to this and I run all this without any luck. I checked the WIF doco, I Goggled it with Bing and nothing.

Finally I found a reference to something and of course NO SAMPLES (Isn’t WIF wonderfull!!!)

The solution is using the FederatedSignOut method that redirects to the STS, this one signs you out and redirects the browser to the page that you wanted to go to let the user know that he’s out.

WSFederationAuthenticationModule authModule = FederatedAuthentication.WSFederationAuthenticationModule;
string signoutUrl = (WSFederationAuthenticationModule.GetFederationPassiveSignOutUrl(authModule.Issuer, authModule.Realm, null));
WSFederationAuthenticationModule.FederatedSignOut(new Uri(authModule.Issuer), new Uri(authModule.Realm + "LoggedOut.html"));

It’s up to you to find out how to solve the new issue, the redirection to LoggedOut.html sends you back to the Login page in the STS because you where logged out (this is good fun if you use Windows Authentication because you get logged in again without knowing it.

The second catch is… once you are in the LoggedOut.html page, press the back button in the browser 🙂

Have fun

Advertisements

Serializing/Deserializing the bootstrap token

Posted on Updated on

I’ve been doing some work with WCF and WIF. Yes this new foundation thing called Windows Identity Foundation.

If you have seen the PDC09 demos than you probably thought that it’s so easy to add security to an application… well yes and no. It’s easy if you only want the standard functionality out of the box, but if you need to do something different… it’s quite complicated to get everything working. However, once you understand what’s going on then it is a lot smoother and you don’t need to worry anymore about how it works, it just works.

The last tornado I had to deal with was the serialization of security tokens. You may want to know why you would want to serialize tokens if WIF does it for you adding them to cookies or WCF heathers. Well that’s like asking why you would generate plain HTML to add custom CSS when you can use ASP.Net controls that spit out heaps of formatted html with colors. In this case, my reason is that I wanted to be able to open a windows application from the web browser and keeping the credentials I had in the browser.

To do that, you need to serialize the token into a file and your browser opens the application associated to that extension. It’s like when you download an excel sheet and instead of saving the file you open it directly in Excel or the same with a PDF. But in our scenario, we send the information that WIF had put in a cookie to the windows app so that we can call some WCF services. Easy. Serialize that monster and you deserialize it in the client to create the Channel. Go for it. If you are reading this is because it wasn’t that easy, isn’t it? Then, try to deserialize it now that you managed to serialize it. Ha!!.

The Idea is that you serialise your bootstrap token into an XML string


var bootstrapToken = ((IClaimsPrincipal)Thread.CurrentPrincipal).Identities[0].BootstrapToken; 

// Serialize
var req = new SamlSecurityTokenRequirement();
var handler = new Saml11SecurityTokenHandler(req);
var sb = new StringBuilder();
using (var writer = XmlWriter.Create(sb))
{
   handler.WriteToken(writer, bootstrapToken);                   
}

string serializedToken = sb.ToString();

Serializarion is quite simple, but to deserialize the token we need to have the public key of the signing certificate because the classes that do the deserialization want to validate that moster. This sample shows how to read the certificate from a file, but the constructors allow reading from a stream (or you may want to serialize the x509 certificate together with the token in the file that you send to your win app).

// Deserialize
string path = @"c:\temp\STSPublic.cer";
// It can be deserialised from a byte[]
var cert = new X509Certificate2(path);
var token = new X509SecurityToken(cert);
var tokens = new List<SecurityToken>() {token}; 

var resolver = SecurityTokenResolver.CreateDefaultSecurityTokenResolver(tokens.AsReadOnly(), false);
var conf = new SecurityTokenHandlerConfiguration();
conf.IssuerTokenResolver = resolver;
handler.Configuration = conf; 

using (var reader = XmlReader.Create(new StringReader(sb.ToString())))
{
   bootstrapToken2 = handler.ReadToken(reader);
}

Instead of reading the certificate from a file you can read the certificate from the list of installed certificated from the local machine you can also use the following code.


var token = new X509SecurityToken(CertificateUtil.GetCertificate(StoreName.My, StoreLocation.LocalMachine, "CN=STSCertificateName"));

Now it’s up to you to do whatever you want with the token in the windows app.

I hope this helps.

R.

How to create a complete AJAX Server Control. Part 6.

Posted on Updated on

This is the last (planned) post for the series is related to using the control inside an update panel.

You can access the previous ones here:

When you put a control inside an UpdatePannel (with UpdateMode = “Conditional” ) what it happens is that the de AJAX Framework will call the implementation of the methods in the interface Sys.IDisposable to release the resources before destroying everything inside it using the dispose() method. And once the result is returned from the server, the control gets constructed and initialised again with the initialize() method.

It’s important to do that to avoid side effects and help the system with the garbage collection and avoid memory leaks.

ShoppingCart.js

// As the control implements Sys.IDisposable, the MS Ajax Framework requires of this method.
$ShoppingCart.prototype.initialize = function()
{
	$ShoppingCart.callBaseMethod(this, "initialize");
	this._addHandlers();
}
// As the control implements Sys.IDisposable, the MS Ajax Framework requires of this method.
// This method will be use release used resources (specially required to work with Update Panels)
$ShoppingCart.prototype.dispose = function()
{
	$ShoppingCart.callBaseMethod(this, 'dispose');
	this._clearHandlers();
}
$ShoppingCart.prototype._addHandlers = function()
{
	$addHandlers(this._cartImage, {
		dblclick: Function.createDelegate(this, this._cartDoubleClick)
	});
	$addHandlers(this._infoImage, {
		click: Function.createDelegate(this, this._infoImageClick)
	});
	$addHandlers(this._closeItemsImage, {
		click: Function.createDelegate(this, this._closeItemsImageClick)
	});
}
$ShoppingCart.prototype._clearHandlers = function()
{
	// remove event handlers to avoid memory leaks
	$clearHandlers(this._cartImage);
	$clearHandlers(this._infoImage);
	$clearHandlers(this._closeItemsImage);
}

Once these methods are correctly called, we can put our control inside the UpdatePanel (UpdateMode=”Conditional”). But before doing that, let’s check what’s going behind the scenes with the full postback.

To do that enable the Web Development Helper bar (it’s included in IE8 and you can download it for IE7).

image

Then enable logging and click on the Add item button.

image

Well, these are the calls to the server and the size of the downloaded elements (you can double click in each of them to see what’s what).

Now let’s put the control in the UpdatePanel, restart the page and enable logging one more time.

ShoppingCart.aspx

        
            
                
            
        

It seems that nothing happened because only the shopping cart control was updated. Check how the amount of data received has been reduces dramatically, 3 connections instead of 7 and the page connection reduced from 10Kb to 6Kb. If we open the received data we can see how the creation of the control was sent from the server along with the html representation as I had explained before.

If you have any problems with JS running inside Update Panels and Postbacks, this is the tool you can use to check what was sent and returned.

image

Well, I hope that you learnt few things about AJAX Server controls and the MS AJAX Framework. A good way to keep learning about it is downloading the source code for the AjaxControlToolkit and have a look at the samples and controls, believe me, there’s plenty there.

You can download the code here.
Important: I couldn’t upload the file as it was a zip file, so I added the extension jpg. After saving the file, remove the extension and unzip it normally.

How to create a complete AJAX Server Control. Part 5.

Posted on Updated on

In the previous post we saw that the Items were disappeared like cowards once we executed a postback. That’s the standard behaviour of the GridView control as well, so don’t worry.

To maintain the Items we have several choices:

  1. The basic implementation is to keep the list in the view state (that’s the implementation in the samples). This will work with no issues but it’s not very configurable
  2. 		List items
    		{
    			get { return GetPropertyValue("items", (List)null); }
    			set { SetPropertyValue("items", value); }
    		}
    
  3. A bit more sofisticated solution, and the way GridView works, would be taking into consideration the EnableViewState property and depending on the value, then saving it in the View State or in a member
  4. 		List _items = new List();
    		List items
    		{
    			get
    			{
    				if (EnableViewState)
    					return GetPropertyValue("items", (List)null);
    				else
    					return _items;
    			}
    			set
    			{
    				if( value == null)
    					value = new List();
    
    				if (EnableViewState)
    					SetPropertyValue("items", value);
    				else
    					_items = value;
    			}
    		}
    

If we run the example now, we can see how all the items are there after the postback and the new ones are added to the list, just as expected. Just remember that the more you add to the ViewState, de bigger the page.

You can download the code here.
Important: I couldn’t upload the file as it was a zip file, so I added the extension jpg. After saving the file, remove the extension and unzip it normally.

How to create a complete AJAX Server Control. Part 4.

Posted on Updated on

In this post I’m going to add functionality in the client side that will be executed in the server side (for previous posts of the series please refer to part 1, part 2 and part 3).

To achieve this, there are 2 interfaces that have been defined:

  • System.Web.UI.IPostBackEventHandler: used to implement postbacks
  • System.Web.UI.ICallbackEventHandler: used to implement callbacks

In the sample I’ll be implementing both of them; however the one that we are going to find more useful will be the one used for the postbacks, as it involves a change of state in the server control. Callbacks can be useful if we want to run something in the server and get a value as response but doesn’t require changes in the server side of control.

Before implementing the interfaces we need to define the events that we want to implement in the server side. To do that I have defined the ServerEvent enum with the 2 events that I want to capture in the JS and run in the server, DoubleClick and ItemAdded (I have also created 2 delegates for the events to show how to fully customise everything; ClickEventHandler and ItemAddedEventHandler). And then 2 methods to raise the events, OnDoubleClick and OnItemAdded.

So far, so good, but the client side needs to know which events have been hooked in the server side so that we only call the server side if it’s required. RunAtServerEvents checks which events have to be raised and sends to the client the list of events (a string with events separated by “;” and implemented as a client side property). Then we have the property AutoPostBack so we can decide if we want to execute them as postbacks or callbacks (for simplicity I’ll be assuming all as postbacks or all as callbacks but we could customise for each of them in the JS code). And finally we need the property CallbackID, required to implement the callbacks and postbacks (initialised to UniqueID).

Something interesting to notice is that on OnPreRender there is a piece of code that I found somewhere in the code of one of the AjaxControlToolkit controls, it seems that we need to add a call to Page.ClientScript.GetCallbackEventReference(this, “”, “”, “”) to make the callbacks work (required code to register callbacks in the client).

ShoppingCart.cs

	/// 
	/// Click delegate definition
	/// 
	/// 
	public delegate void ClickEventHandler(object sender);

	/// 
	/// Item added delegate
	/// 
	/// 
	/// new item
	public delegate void ItemAddedEventHandler(object sender, ShoppingCartItem addedItem);

	public class ShoppingCart : ScriptControlBase
	{

		/// 
		/// Server events raised from the client
		/// 
		enum ServerEvent
		{
			DoubleClick,
			ItemAdded
		}

		//...

		protected override void OnPreRender(EventArgs e)
		{
			if (!this.DesignMode)
			{
				if (ScriptManager.GetCurrent(Page) == null)
				{
					throw new HttpException("A ScriptManager control must exist on the current page.");
				}
				InitialiseControls();

				CreateHTMLItems();

				CallbackID = UniqueID; // needed for the callbacks and postbacks

				// Create JavaScript function for ClientCallBack WebForm_DoCallBack
				// Not sure why we need it, but the callback doesn't get registered on the client side properly without it.
				Page.ClientScript.GetCallbackEventReference(this, "", "", "");

			}

			base.OnPreRender(e);
		}

		#region Server Events
		/// 
		/// If we want to execute server events with a postback.
		/// True: A full postback happens and the page will be refreshed.
		/// Use UpdatePanel control to refresh certain areas.
		/// False: The events will run on the server with no postback (it'll use a Callback).
		/// If any data is updated a manual UI refresh needs to be forced (full page or UpdatePanel).
		/// 
		[Description("If we want to execute server events with a postback.")]
		[ExtenderControlProperty]
		public bool AutoPostBack
		{
			[DebuggerStepThrough]
			get { return GetPropertyValue("AutoPostback", true); }
			[DebuggerStepThrough]
			set { SetPropertyValue("AutoPostback", value); }
		}

		/// 
		/// Control ID to be used when doing callbacks from the client
		/// 
		[ExtenderControlProperty]
		//[ClientPropertyName("CallbackID")]
		[Browsable(false)]
		public string CallbackID
		{
			[DebuggerStepThrough]
			get { return GetPropertyValue("CallbackID", UniqueID); }
			[DebuggerStepThrough]
			set { SetPropertyValue("CallbackID", value); }
		}

		/// 
		/// Events that will be run at server
		/// 
		[Browsable(false)]
		[ExtenderControlProperty]
		[ClientPropertyName("runAtServerEvents")]
		public string RunAtServerEvents
		{
			get
			{
				string runAtServerEvents = "";

				if (DoubleClick != null) runAtServerEvents += "DoubleClick;";
				if (ItemAdded != null) runAtServerEvents += "ItemAdded;";

				if (runAtServerEvents.Length > 0)
					return runAtServerEvents.Substring(0, runAtServerEvents.Length - 1);
				else
					return "";
			}
			set { }
		}

		/// 
		/// Event raised when the a cell is double clicked
		/// 
		[Description("Event raised when the a cell is double clicked.")]
		public event ClickEventHandler DoubleClick;

		/// 
		/// Raise DoubleClick event
		/// 
		/// 
		protected virtual void OnDoubleClick()
		{
			if (DoubleClick != null)
			{
				DoubleClick(this);
			}
		}

		/// 
		/// Event raised when the a cell is double clicked
		/// 
		[Description("Event raised when the a cell is double clicked.")]
		public event ItemAddedEventHandler ItemAdded;

		/// 
		/// Raise DoubleClick event
		/// 
		/// 
		protected virtual void OnItemAdded(ShoppingCartItem item)
		{
			if (ItemAdded != null)
			{
				ItemAdded(this, item);
			}
		}
		#endregion
	}

The next step is the implementation of the interfaces code that will be called by the AJAX framework, IPostBackEventHandler and ICallbackEventHandler. To keep it simple I’ll implement the same code in both.

The method that does the work is ExecuteServerEvent, in where the parameter coming from the client will define the event name and the parameters (I’ve kept it simple and I’ve used “eventname;jsonParameters”). Double click doesn’t receive any parameters, but ItemAdded receives as a parameter the new item that has been added to the shopping cart. From there, all is standard C#.

ShoppingCart.cs

	public class ShoppingCart : ScriptControlBase, IPostBackEventHandler, ICallbackEventHandler
	{

		//...
		#region Server Events

		//...

		/// 
		/// Executes the server event
		/// 
		/// 
		/// Event Parameters created in the client that follows this structure.
		/// EventName;JSON object serialised
		/// i.e:
		///		ItemAdded;JSON serialised
		/// 
		/// 
		private string ExecuteServerEvent(string eventFromClient)
		{
			// Get the event name
			int separator = eventFromClient.IndexOf(";");
			string serverEvent = eventFromClient.Substring(0, separator);
			ServerEvent e = (ServerEvent)Enum.Parse(typeof(ServerEvent), serverEvent);

			// Get the event args (JSON serialised string)
			string eventArgs = eventFromClient.Substring(separator + 1);

			switch (e)
			{
				case ServerEvent.DoubleClick:
					OnDoubleClick();
					break;
				case ServerEvent.ItemAdded:
					ShoppingCartItem addedItem = new JavaScriptSerializer().Deserialize(eventArgs);
					AddItem(addedItem);
					break;
				default:
					throw new Exception("Wrong event!");
					break;
			}
			return "";
		}

		/// 
		/// Adds an item
		/// 
		/// 
		private void AddItem(ShoppingCartItem addedItem)
		{
			Items.Add(addedItem);

			OnItemAdded(addedItem);
		}
		#endregion 

		#region IPostBackEventHandler Members

		/// 
		/// Raise postback event in the server.
		/// 
		/// argument from JScript
		public void RaisePostBackEvent(string eventArgument)
		{
			ExecuteServerEvent(eventArgument);
		}
		#endregion

		#region ICallbackEventHandler Members
		/// 
		/// Return value for the callback call
		/// 
		string callbackResult = "";

		/// 
		/// Returns the value generated by the event (if any).
		/// 
		/// 
		string ICallbackEventHandler.GetCallbackResult()
		{
			return callbackResult;
		}

		/// 
		/// Raises the server event in the server.
		/// 
		/// argument from JScript
		void ICallbackEventHandler.RaiseCallbackEvent(string eventArgument)
		{
			callbackResult = ExecuteServerEvent(eventArgument);
		}
		#endregion
	}

In the client side we need to add some code to raise the events (and implement JS properties). The method that will call the server is _raiseServerEvent.

  1. It checks the the event that we want to fire has been hooked up in the server side
  2. It creates the parameters with the event name to call and the parameters required for that event
  3. Depending if we want to execute a postback or a callback it calls the server in a different way.
  4. As Callbacks run asynchronously, we need to provide the methods to execute in case it was successful and if there was an error.

ShoppingCart.js

// -------------------------------------- Client side Properties --------------------------------------
$ShoppingCart.prototype.get_items = function() { return this._items; }
$ShoppingCart.prototype.set_items = function(value)
{
	this._items = Sys.Serialization.JavaScriptSerializer.deserialize(value);
}

$ShoppingCart.prototype.get_CallbackID = function() { return this._CallbackID; }
$ShoppingCart.prototype.set_CallbackID = function(value) { this._CallbackID = value; }

$ShoppingCart.prototype.get_AutoPostBack = function() { return this._AutoPostBack; }
$ShoppingCart.prototype.set_AutoPostBack = function(value) { this._AutoPostBack = value; }

// -------------------------------------- Server Side Events --------------------------------------
$ShoppingCart.prototype.get_runAtServerEvents = function()
{
	return this._runAtServerEvents;
}

$ShoppingCart.prototype.set_runAtServerEvents = function(value)
{
	this._runAtServerEvents = value.split(';');
}

$ShoppingCart.prototype._raiseServerEvent = function(eventName, eventArgs)
{
	if (!Array.contains(this._runAtServerEvents, eventName))	return;

	var eventArgsSerialised = Sys.Serialization.JavaScriptSerializer.serialize(eventArgs);
	var args = String.format("{0};{1}", eventName, eventArgsSerialised);
	var id = this._CallbackID;

	if (this._AutoPostBack)
	{// calls IPostBackEventHandler.RaisePostBackEvent in the server control
		__doPostBack(id, args);
	}
	else
	{
		// calls ICallbackEventHandler.RaiseCallbackEvent
		// the return value from ICallbackEventHandler.GetCallbackResult() is sent
		// to the callback handler this._receiveServerData
		WebForm_DoCallback(id, args, this._receiveServerData, this, this._onCallbackError, true)
	}
}

// Handler for successful return from callback
$ShoppingCart.prototype._receiveServerData = function(arg, context)
{
	// context is the instance of the class (this)

	// update the UI or Raise an event to inform that we finished the callback
	alert("Back from the callback!");
	//context.updateUI();
	//context.raiseEndClientCallback(arg);
}

// Error handler for the callback
$ShoppingCart.prototype._onCallbackError = function(message, context)
{
	alert(String.format(AjaxControlToolkit.Resources.Rating_CallbackError, message));
}

// -------------------------------------- Control methods --------------------------------------
$ShoppingCart.prototype.addItem = function(itemText, itemDescription, itemQuantity, itemTotalPrice)
{
	var newItem = new $ShoppingCartItem(itemText, itemDescription, itemQuantity, itemTotalPrice);

	this._raiseServerEvent("ItemAdded", newItem);
}

// Classes to handle event arguments and data transfer to the server ---------------------------------
var $ShoppingCartItem = AjaxControl.ShoppingCartItem = function(itemText, itemDescription, itemQuantity, itemTotalPrice)
{
	this.Text = itemText;
	this.Description = itemDescription;
	this.Quantity = itemQuantity;
	this.TotalPrice = itemTotalPrice;
}

To test it, let’s add some code to our web page. I have added 4 text boxes to introduce the values and and a button that will add the new item. Additionally I added a GridView control that is Databinded with the items in the shopping cart (server event of ItemAdded).

ShoppingCart.aspx


    

        function addNewItem() 
        {
            $find("Cart1").addItem(
                    $get("newItemText").value,
                    $get("newItemDescription").value,
                    $get("newItemQuantity").value,
                    $get("newItemTotalPrice").value
                    );
        
        }
    



    
    
    
    <div>
        <table>
            <tr>
                <td>Text</td><td></td>
            </tr>
            <tr>
                <td>Description</td><td></td>
            </tr>
            <tr>
                <td>Quantity</td><td></td>
            </tr>
            <tr>
                <td>TotalPrice</td><td></td>
            </tr>
            <tr>
                <td colspan="2" align="right"></td>
            </tr>
         </table>
    </div>
    <div>
        

        
    </div>
    

ShoppingCart.aspx.cs

	public partial class ShoppingCart : System.Web.UI.Page
	{
		protected void Page_Load(object sender, EventArgs e)
		{
			if(! IsPostBack)
			{
				var items = new List
				{
					//...
				};

				Cart1.Items = items;

				GridView1.DataSource = Cart1.Items;
				GridView1.DataBind();
			}
		}

		protected void Cart1_ItemAdded(object sender, ShoppingCartItem addedItem)
		{
			GridView1.DataSource = Cart1.Items;
			GridView1.DataBind();
		}
	}

As you will notice the old items are lost after the postback and only the latest item is rendered. This is the expected behaviour (Gridview implementation behaves the same). Next post I’ll show how to make the control remember the previous items.

Well, that’s all. I know that there’s a lot to digest here but believe me, if you had to figure it out by yourself it would take a lot longer.

You can download the code here.
Important: I couldn’t upload the file as it was a zip file, so I added the extension jpg. After saving the file, remove the extension and unzip it normally.

How to create a complete AJAX Server Control. Part 3.

Posted on Updated on

In the previous posts we have created the server control and JS representation in the client. We have also added functionality to add new items in the server and add a client event to display and hide the items in the shopping cart (see part 1 and part 2).

The next step in the development of the server control will be to enable the client side to access the data stored in the control and fire client events that could be handled in the web page.

You may probably think that from the client side you can access to the items stored in the cart by inspecting the DOM and using JQuery, for instance. True. But it’s not the natural way of doing it and it gives you very little margin if you want to change the html that is rendered because then, you should have to go and update all the code that reads from the control.

To implement this functionality I’m going to define some helper methods in the class to interact easily with the Viewstate an avoid casting in the code.

ShoppingCart.cs

 
	public class ShoppingCart : ScriptControlBase
	{
		//...
		#region Helper methods
		/// 
		/// Retrieves the property value from the ViewState
		/// 
		/// Type of value
		/// Property to retrieve
		/// value to be used in case it's not initialised.
		/// 
		[DebuggerStepThrough]
		protected V GetPropertyValue(string propertyName, V nullValue)
		{
			if (ViewState[propertyName] == null)
			{
				return nullValue;
			}
			return (V)ViewState[propertyName];
		}

		/// 
		/// Save the property value in the ViewState
		/// 
		/// Property type
		/// Property name
		/// Value
		[DebuggerStepThrough]
		protected void SetPropertyValue(string propertyName, V value)
		{
			ViewState[propertyName] = value;
		}
		#endregion
		//...
	}

Once we have these methods we can start adding the real juicy stuff.

First of all we need to add a namespace to get the JS serialisation classes to be able to serialise and de-serialise JSON objects.

Now to add a client event (I’ve chosen Double Click on the Cart image) we need to add a property in where we will set the name of the JS function that we need to call for the event. In the code below you can see the property OnClientCartDoubleClick. Notice all the attributes added to the property:

  • CategoryAttribute and Description are the standard attributes to get the nice integration with the properties window in Visual Studio
  • ExtenderControlEvent is used to say that this property is used to define a control event in the client (it’s declared in the AjaxControlToolkit and used via reflexion at runtime)
  • ClientPropertyName(“cartDoubleClick”) helps us to change the name of the event in the client (by default uses the property name unless it’s renamed using this attribute)

I’m also going to add a property in the client called items that will contain the items in the cart the same way they are available in the server so that you can read then in the web page. To do that, the only difference from the event is the attribute that we need to use to do that, now we’ll use ExtenderControlProperty instead of ExtenderControlEvent. To send the items to the client I’m using JSON serialisation so they can be easily serialised to string and they will be deserialised in the client side.

ShoppingCart.cs

	using System.Web.Script.Serialization;

	public class ShoppingCart : ScriptControlBase
	{
		//...
		#region Client Events
		/// 
		/// Client function that will be executed for CartDoubleClick
		/// 
		[CategoryAttribute("Client Events")]
		[Description("Client function that will be executed for OnClientCartDoubleClick.")]
		[ExtenderControlEvent]
		[ClientPropertyName("cartDoubleClick")]
		public string OnClientCartDoubleClick
		{
			[DebuggerStepThrough]
			get { return GetPropertyValue("OnClientCellDoubleClick", (string)null); }
			[DebuggerStepThrough]
			set { SetPropertyValue("OnClientCellDoubleClick", value); }
		}

		#endregion

		#region Client Properties
		/// 
		/// Client property
		/// 
		[Browsable(false)]
		[ExtenderControlProperty]
		[ClientPropertyName("items")]
		public string ClientItems
		{
			get
			{
				JavaScriptSerializer jser = new JavaScriptSerializer();
				string jsonItems = jser.Serialize(items);
				return jsonItems;
			}
			set { }
		}
		#endregion
		//...
	}

To implement the client side is even easier.

Event (cartDoubleClick):

  1. We have to add the methods to hook and unhook the event in the class
    • add_cartDoubleClick: Convention used by MS AJAX Framework to hook (add_ + “eventname”)
    • remove_cartDoubleClick: Convention used by MS AJAX Framework to unhook (remove_ + “eventname”)
  2. Now we can add a handler to handle the double click event of the image ( _addHandlers and _clearHandlers )
  3. In the section HTML Events of the JS class I have created a method to call the client event (_onCartDoubleClick) in where I check if the event is hooked, I call the function that was fetined in the server side

Property (items):

  1. We have to add the methods for the get/set in the class
    • get_items: Convention used by MS AJAX Framework for the get (get_ + “propertyname”)
    • set_items: Convention used by MS AJAX Framework for the set (set_ + “propertyname”)
  2. In the set property I deserialise the items into objects and assign the array to the _items property (remember that as a convention it’s used underscore to express the meaning of private in a class)

ShoppingCart.js

$ShoppingCart.prototype._addHandlers = function()
{
	$addHandlers(this._cartImage, {
		dblclick: Function.createDelegate(this, this._cartDoubleClick)
	});

	//...
}

$ShoppingCart.prototype._clearHandlers = function()
{
	// remove event handlers to avoid memory leaks
	$clearHandlers(this._cartImage);
	//...
}

// ---------------------- HTML Events -----------------------

$ShoppingCart.prototype._infoImageClick = function(e)
{
	this._itemsDiv.style.display = "block";
}

$ShoppingCart.prototype._closeItemsImageClick = function(e)
{
	this._itemsDiv.style.display = "none";
}

$ShoppingCart.prototype._cartDoubleClick = function(e)
{
	e.stopPropagation();

	// raise the client event
	this._onCartDoubleClick(e);
}
// --------------------- Client side Events -------------
// defined in the server side as:
//		[ExtenderControlEvent]
//		[ClientPropertyName("cartDoubleClick")]

$ShoppingCart.prototype.add_cartDoubleClick = function(handler)
{
	this.get_events().addHandler("cartDoubleClick", handler);
}
$ShoppingCart.prototype.remove_cartDoubleClick = function(handler)
{
	this.get_events().removeHandler("cartDoubleClick", handler);
}
$ShoppingCart.prototype._onCartDoubleClick = function(e)
{
	if (!this._events) return;
	var handler = this._events.getHandler("cartDoubleClick");
	if (handler) handler(e);
}

// ---------------------- Client side Properties ----------------------------
$ShoppingCart.prototype.get_items = function() { return this._items; }
$ShoppingCart.prototype.set_items = function(value)
{
	this._items = Sys.Serialization.JavaScriptSerializer.deserialize(value);
}

To use the newly created event and property I have defined a JS function in the web form that enumerated the items in the control and displays a message box when the cart image is double clicked.

ShoppingCart.aspx


    
    
        function onCartDoubleClickInTheClient(e)
        {
            // we use $find to retrieve an AJAX component ( $get to retrieve the HTML element )
            var cart = $find("Cart1");

            var items = cart.get_items();
            var itemsStr = "";
            for (var i = 0; i &lt; items.length; i++)
            {
                itemsStr += &quot;\n  &quot; + i + &quot; - &quot; + items[i].Text;
            }

            alert(&quot;Double Click! : &quot; + itemsStr);          
        }
        
    


    
    
    
    <div>
        
    </div>
    

If you view source in the browser you can see how the new control has been created passing all the added elements.

Sys.Application.initialize();
Sys.Application.add_init(function() {
    $create(
    AjaxControl.ShoppingCart, 
    {"items":"[{\"DateAdded\":\"\\/Date(1245633460294)\\/\",\"Text\":\"Learn Ajax in 1h!\",\"Description\":\"If you want to learn Ajax in 1h, this is your book.\",\"Quantity\":1,\"TotalPrice\":28.9},{\"DateAdded\":\"\\/Date(1245633460295)\\/\",\"Text\":\"Ajax for Dummies!\",\"Description\":\"If you think that you are hopeless, this is your book.\",\"Quantity\":2,\"TotalPrice\":70},{\"DateAdded\":\"\\/Date(1245633460295)\\/\",\"Text\":\"Advanced Ajax Server Controls\",\"Description\":\"When you want to go further, this is your book.\",\"Quantity\":1,\"TotalPrice\":86.5}]"}, 
    {"cartDoubleClick":onCartDoubleClickInTheClient}, 
    null, 
    $get("Cart1"));
});

We are closer to have all the functionality ready to be used.

You can download the code here.

Important: I couldn’t upload the file as it was a zip file, so I added the extension jpg. After saving the file, remove the extension and unzip it normally.

How to create a complete AJAX Server Control. Part 2.

Posted on Updated on

In this post I’m going to continue with the sample of the part 1.

Now it’s time to start adding functionality to the control.

First of all I’m going to add Items to the control.

To do that I have declared a class that contains the data that I wanted to show. I could have defined an interface and then implement it in a class out of the project, but for this presentation I’ve kept it simple.

ShoppingCartItem.cs

public class ShoppingCartItem
	{
		public DateTime DateAdded { get; set; }
		public string Text { get; set; }
		public string Description { get; set; }
		public int Quantity { get; set; }
		public float TotalPrice { get; set; }
	}

And now, I’ll define in the control class a collection property in where I’ll store all the items in the shopping cart.

I’ll call the property Items.

I’ve also added code to render some HTML for the items (<table><tr><td> style). And to show how to add client side functionality, I put it inside a non visible div that will be made visible in JS when we click on the cart.

The HTML elements are created in InitialiseControls(), CreateTableItemsHeader() and CreateHTMLItems() functions.

ShoppingCart.cs

	public class ShoppingCart : ScriptControlBase
	{
		List items = new List();

		/// 
		/// Div control used as a pop-up container to display the selected items
		/// 
		HtmlGenericControl itemsDiv;

		/// 
		/// Table where the items are rendered
		/// 
		HtmlTable tableItems;

		/// 
		/// Shopping Cart Items
		/// 
		public List Items{get{return items;}
			set
			{
				if (value == null) items = new List();
				else items = value;
			}
		}

		public ShoppingCart(): base(false, HtmlTextWriterTag.Div)
		{
		}

		#region Control Rendering

		protected override void OnPreRender(EventArgs e)
		{
			if (!this.DesignMode)
			{
				if (ScriptManager.GetCurrent(Page) == null)
				{
					throw new HttpException("ScriptManager control must.");
				}
				InitialiseControls();

				CreateHTMLItems();
			}

			base.OnPreRender(e);
		}

		private void InitialiseControls()
		{
			Image cartImage = new Image();
			cartImage.ID = "cartImage";
			cartImage.Attributes["class"] = "cartImage";
			cartImage.ToolTip = "Shopping Cart";
			cartImage.ImageUrl = Page.ClientScript.GetWebResourceUrl(this.GetType(),"AjaxControl.Images.cart.png");
			this.Controls.Add(cartImage);

			itemsDiv = new HtmlGenericControl("div");
			itemsDiv.ID = "itemsDiv";
			itemsDiv.Attributes["class"] = "itemsDiv";
			itemsDiv.Style.Add(HtmlTextWriterStyle.Display, "none");
			this.Controls.Add(itemsDiv);

			tableItems = new HtmlTable();
			tableItems.Attributes["class"] = "itemsTable";
			itemsDiv.Controls.Add(tableItems);

		}

		/// 
		/// Creates the items to be rendered
		/// 
		public void CreateHTMLItems()
		{
			tableItems.Rows.Clear();

			CreateTableItemsHeader();

			// Add data rows
			foreach (var item in items.OrderBy(i => i.DateAdded))
			{
				HtmlTableRow rowItem = new HtmlTableRow();
				rowItem.Attributes["class"] = "itemRow";
				tableItems.Rows.Add(rowItem);

				HtmlTableCell cellText = new HtmlTableCell();
				//...
				rowItem.Cells.Add(cellText);
				//...
			}
		}

		/// 
		/// Creates a heading to close the table
		/// 
		private void CreateTableItemsHeader()
		{
			//...

			Image closeImage = new Image();
			closeImage.ID = "closeItemsImage";
			closeImage.ImageUrl = Page.ClientScript.GetWebResourceUrl(this.GetType(), "AjaxControl.Images.cancel.png");
			//...
			cellHead.Controls.Add(closeImage);
		}

		#endregion

	}

Once we have the server side of the control ready we can start with the client side.

In the JS class I have added some event handling. I first get the html elements that represent the cart (to add a onclick event), then the items div (to make the items visible) and finally the close image to hide the items again.

The method _addHandlers() will hook up all the events and the method _clearHandlers() will remove them.

Important: Something to notice is that in JS we don’t have the concept of delegates and if we add a handler using the standard method, then inside the method we won’t have access to the current object, this. In the MS AJAX Framework, they have implemented the concept using Function.createDelegate(this, this._cartImageClick), in there the firs parameter is the instance of the object that we want to access as this inside the event handler and the second is the handler. Now we can have access to all the methods and members of the class inside the handler.

ShoppingCart.js

var $ShoppingCart = AjaxControl.ShoppingCart = function(element)
{
	this._element = element; // div control that contains everything

	AjaxControl.ShoppingCart.initializeBase(this, [element]);

	this._cartImage = $get(element.id + "_cartImage");
	this._itemsDiv = $get(element.id + "_itemsDiv");
	this._closeItemsImage = $get(element.id + "_closeItemsImage"); 

}

// ------------------- Control Initialisation and Destruction ------------------------
$ShoppingCart.prototype.initialize = function()
{
	$ShoppingCart.callBaseMethod(this, "initialize");

	this._addHandlers();
}

// As the control implements Sys.IDisposable, the MS Ajax Framework requires of this method.
// This method will be use release used resources (specially required to work with Update Panels)
$ShoppingCart.prototype.dispose = function()
{
	$ShoppingCart.callBaseMethod(this, 'dispose');
}

$ShoppingCart.prototype._addHandlers = function()
{
	$addHandlers(this._cartImage, {
		click: Function.createDelegate(this, this._cartImageClick)
	});
	$addHandlers(this._closeItemsImage, {
		click: Function.createDelegate(this, this._closeItemsImageClick)
	});
}

$ShoppingCart.prototype._clearHandlers = function()
{
	// remove event handlers to avoid memory leaks
	$clearHandlers(this._cartImage);
	$clearHandlers(this._closeItemsImage);

}

// -------------------- HTML Events ---------------------

$ShoppingCart.prototype._cartImageClick = function(e)
{
	this._itemsDiv.style.display = "block";
}

$ShoppingCart.prototype._closeItemsImageClick = function(e)
{
	this._itemsDiv.style.display = "none";
}

In the web page I have added some items to the shopping cart so we can test the functionality.

ShoppingCart.aspx.cs

	public partial class ShoppingCart : System.Web.UI.Page
	{
		protected void Page_Load(object sender, EventArgs e)
		{
			if(! IsPostBack)
			{
				var items = new List
				{
					new ShoppingCartItem{DateAdded = DateTime.Now.AddMinutes(-20),Text = "Learn Ajax in 1h!",
										Description = "If you want to learn Ajax in 1h, this is your book.",
										Quantity = 1,TotalPrice = 28.90f},
					new ShoppingCartItem{DateAdded = DateTime.Now.AddMinutes(-20),Text = "Ajax for Dummies!",
										Description = "If you think that you are hopeless, this is your book.",
										Quantity = 2,TotalPrice = 70.00f},
					new ShoppingCartItem{DateAdded = DateTime.Now.AddMinutes(-20),Text = "Advanced Ajax Server Controls",
										Description = "When you want to go further, this is your book.",
										Quantity = 1,TotalPrice = 86.50f},

				};

				Cart1.Items = items;
			}
		}
	}

Now you can run the example and click on the cart and close image so you can see how we have a server control that handles all the data in the server side and still can run some client functionality without doing any post back.

You can download the code here.

Important: I couldn’t upload the file as it was a zip file, so I added the extension jpg. After saving the file, remove the extension and unzip it normally.