|
|||||||||||||||||||||
|
|||||||||||||||||||||
|
Announcements
Want a new Job?
Chapters
Services
Feature Zones
|
Contents
IntroductionWhen you open up Visual Studio 2008 to create a project, you will notice that it has two new web templates designed specifically for building AJAX controls: ASP.NET AJAX Server Control and ASP.NET AJAX Server Control Extender. You'll also find an old friend, the ASP.NET Server Control project template. What are the differences between the Server Control, the ASP.NET AJAX Server Control, and the ASP.NET AJAX Extender, and when should each be used?
At first glance, it would seem that the ASP.NET Server Control differs from the other two controls in that it doesn't support AJAX. This isn't completely true, however, and in the first part of this tutorial, I will demonstrate just how far you can go in developing an AJAX-enabled control based on the Server Control alone. While the ASP.NET Server Control does not provide direct access to AJAX scripts, it can implement AJAX scripts encapsulated in other controls such as the The AJAX Server Control and the AJAX Server Control Extender differ from the regular ASP.NET Server Control by coupling themselves with JavaScript files, and allowing mapping between properties of a control class and properties of a JavaScript class. When you need functionality not provided by other AJAX Server controls, or simply want to customize your control using client-side script in order to avoid the ASP.NET control life-cycle, then this is the best option. Finally, while the AJAX Server Control Extender is primarily used to add behavior (that is, JavaScript) to other controls on your ASP.NET page, the AJAX Server Control is a self-contained control in which any client-side script you write will apply, for the most part, only to the control itself, or to its children. In other words, an AJAX Extender will be aware of other controls on your page, while an AJAX Server Control will not. Of some interest is the fact that the ASP.NET AJAX Server Control template, like the ASP.NET Server Control template, implements a
A good way to look at the three types of controls we are discussing, then, is in terms of strategies which incrementally add developer features to your custom control, while preserving the features of the earlier controls. If you are only interested in adding AJAX functionality through child controls, then the ASP.NET Server Control is your best option. If you need to include some custom client-script to your control, then you should use the ASP.NET AJAX Server Control. If you additionally need to make your custom control aware of another control on your page, in order to interact with it, then the ASP.NET AJAX Server Control Extender should be used. Of course, given that an Extender Control can do everything the other two control types can do, you always have the option of using Extenders for all your AJAX control development -- and many people do. However, if you are the type of developer who likes to use only the right tools for the right situation, then it behooves you to put some consideration into which base class is most appropriate for your needs. In that vein, this tutorial will lead you through the construction of three different controls, based on the ASP.NET Server Control, the ASP.NET AJAX Server Control, and the ASP.NET AJAX Server Control Extender, respectively. Each subsequent control will include and extend the functionality of the previous control. This tutorial will also attempt to construct something generally useful, a session timeout watcher. Most strategies for handling session timeout involve reactive solutions which check the state of the session upon a user event, and then perform some task, such as a page redirect, if the session has expired. Passive solutions are common because of what I like to think of as a version of Heisenberg's Uncertainty Principle as applied to the web. There is no way to look at the session object surreptitiously, to see if it still exists, without extending its lifetime. And so, we wait for the user to do something, and then either redirect, if the session has expired, or do nothing. The idea here is that since the user is extending the session lifespan anyways, if it has not already expired, we can piggy-back on this event to do our own prodding of the session object. The problem with a passive solution is that it can be somewhat startling for the user of your website to be taken to a new page when they are trying to complete whatever they were in the middle of when the stepped away for a cup of coffee. A kinder, gentler way to handle session expiration would be to anticipate when the session is about to timeout, and then take some action on the user's behalf. In this case, the user will return to a session expired page, and (hopefully) know exactly what just happened to him. Given the mild complexity of this sort of control, I will also have the opportunity to illustrate various useful techniques and gotchas involved in building an AJAX-enabled control without excessive contrivance on my part. The intent of this tutorial is not only to provide you with the basics of how to develop an ASP.NET AJAX control, but also to provide you with helpful pointers on building your own complex solutions. I ask for your forbearance in the event that, in my attempts to accomplish one of these goals, I undermine the other, making this tutorial either too easy or too opaque, and not always finding the happy medium between the two. Note: This tutorial and all source code is built on the RTM version of Visual Studio 2008, rather than the VS 2008 beta. I had trouble opening up my beta projects using the RTM version, and would imagine that the reverse is also true. I. The ASP.NET Server ControlIn writing a proactive session timeout watcher control, it is necessary to anticipate what consumers of the control might like to do in the event of a session expiration. One possibility is that the user will want to automatically redirect to another web page, either a friendly page explaining what has just happened, or perhaps to a login page. Additionally, the consumer of this custom control may simply want to display a popup that does not require a redirect, but rather leaves the user on their current page. A third option is that the consumer wants the session to be extended, so that the session never dies as long as a web page is open. Fourth, the developer who consumes our session timeout control may want to handle the session timeout herself. Our provisional list of features includes:
In addition, the session timeout watcher will need:
We will accomplish this by consuming the Begin by creating a new ASP.NET Server Control project called SessionTimeoutTool. This will generate both a project and a solution for us. Add a second ASP.NET Web project to the solution called TestTimeoutTool. Open the web.config file for TestTimeoutTool and add a <system.web>
<sessionState timeout="2" mode="InProc"/>
</system.web>
This establishes the control development environment. Rename the default class in the SessionTimeoutTool project by right clicking on the default class in the Solution Explorer and renaming the file from ServerControl1.cs to TimeoutWatcherControl. The IDE will take care of renaming your class for you. The namespace SessionTimeoutTool
{
public class TimeoutWatcherControl : WebControl
{
}
}
Having gotten through the preliminaries, we can now start to build our control. We need to create an enum to keep track of the various timeout options our control will support. We will also expose the enum as a property that can be configured in ASP.NET markup. private mode _timeoutMode = mode.CustomHandler;
public enum mode
{
PageRedirect,
PopupMessage,
ExtendTime,
CustomHandler
}
public mode TimeoutMode
{
get { return _timeoutMode; }
set { _timeoutMode = value; }
}
We need to add public properties for a path to the redirect page, if that is the mode the consumer wants to use, a popup message, as well as a a popup The private string _redirectPage;
private string _message;
private string _popupCSSClass = string.Empty;
public event EventHandler Timeout;
private int _interval = 1000;
private readonly int MINUTES = 60000;
private readonly int SECONDS = 1000;
protected System.Web.UI.Timer _sessionTimer = null;
private UpdatePanel _timeoutPanel = null;
public string RedirectPage
{
get { return _redirectPage; }
set { _redirectPage = value; }
}
public string TimeoutMessage
{
get { return _message; }
set { _message = value; }
}
public string PopupCSSClass
{
get { return _popupCSSClass; }
set { _popupCSSClass = value; }
}
You will also need to add a reference to the
Now, it is time to implement the The second requirement, that our control be aware of any resets of the session timeout, is met by putting our code in the In consuming the
Finally, we said we want the By design, when an AJAX Extensions In nesting our controls, you will notice that we do not use the protected override void OnLoad(EventArgs e)
{
//retrieve session timeout period from server
System.Web.SessionState.HttpSessionState state
= HttpContext.Current.Session;
//convert minutes to milliseconds
_interval = state.Timeout * MINUTES;
//create new ajax components
UpdatePanel timeoutPanel = GetTimeoutPanel();
System.Web.UI.Timer sessionTimer = GetSessionTimer();
sessionTimer.Interval = _interval;
//add timer to update panel
timeoutPanel.ContentTemplateContainer.Controls.Add(
sessionTimer);
//add update panel to timeout watcher control
this.Controls.Add(timeoutPanel);
}
You can automatically create methods from our two method placehoders by right clicking on the method calls and selecting "Generate Method Stub". (While this is not actually a new feature, I must admit, with some embarrassment, that I never noticed it before VS 2008.)
The code should look pretty straightforward, so it is worth mentioning that there is something very strange going on here. First, we are recreating the A partial explanation involves the fact that it is being loaded into an But, here's a second mystery. Why doesn't recreating the With ASP.NET AJAX, however, this model no longer holds. Partial page postbacks can force us to iterate through the OnLoad and other methods of the code-behind page without making a single change to the physical web page. In the new model, both the page, presented in the browser, and the session preserve state, while our code-behind is the least stable element of the stack. While markup is rendered as a Here is the code we have just been discussing. While I have thrown in a null check for the protected void SessionTimer_Tick(object sender, EventArgs e)
{
}
private System.Web.UI.Timer GetSessionTimer()
{
if (null == _sessionTimer)
{
_sessionTimer = new System.Web.UI.Timer();
_sessionTimer.Tick +=
new EventHandler<EventArgs>(SessionTimer_Tick);
_sessionTimer.Enabled = true;
_sessionTimer.ID = this.ID + "SessionTimeoutTimer";
}
return _sessionTimer;
}
private UpdatePanel GetTimeoutPanel()
{
if (null == _timeoutPanel)
{
_timeoutPanel = new UpdatePanel();
_timeoutPanel.ID = this.ID + "SessionTimeoutPanel";
_timeoutPanel.UpdateMode =
UpdatePanelUpdateMode.Always;
}
return _timeoutPanel;
}
To finish up this control, we just need to handle the different cases for what should happen when we think the session has expired. Here, to simplify the code, I will once again insert some place holder calls: protected void SessionTimer_Tick(object sender, EventArgs e)
{
switch (TimeoutMode)
{
case mode.ExtendTime:
//do nothing, page has reposted
//45 seconds before timeout
break;
case mode.PageRedirect:
DisableTimer();
Redirect(RedirectPage);
break;
case mode.PopupMessage:
DisableTimer();
BuildPopup();
break;
case mode.CustomHandler:
default:
DisableTimer();
OnTimeout();
break;
}
}
Handling the System.Web.UI.Timer sessionTimer = GetSessionTimer();
if (TimeoutMode == mode.ExtendTime)
sessionTimer.Interval = _interval - (45 * SECONDS);
else
sessionTimer.Interval = _interval;
The private void Redirect(string redirectPage)
{
if (!string.IsNullOrEmpty(redirectPage))
{
Context.Response.Redirect(
VirtualPathUtility.ToAbsolute(
redirectPage));
}
}
For the popup message, we want to create a floating We probably also want the timer to re-enable itself on a non-postback page initialization. Fortunately, the viewstate object comes back as a new object on non-postbacks, and since we have set the public bool TimerEnabled
{
get
{
object timedOut = ViewState[this.ID + "TimedOutFlag"];
if (null == timedOut)
return true;
else
return Convert.ToBoolean(timedOut);
}
set
{
GetSessionTimer().Enabled = value;
ViewState[this.ID + "$TimedOutFlag"] = value;
}
}
private void DisableTimer()
{
this.TimerEnabled = false;
}
To fully implement the //_sessionTimer.Enabled = true;
_sessionTimer.Enabled = this.TimerEnabled;
Now, we just need to retrieve the current void but_Click(object sender, EventArgs e)
{
this.TimerEnabled = true;
}
private void BuildPopup()
{
UpdatePanel p = GetTimeoutPanel();
Panel popup = new Panel();
AddCSSStylesToPopupPanel(popup);
popup.Height = 50;
popup.Width = 125;
popup.Style.Add("position", "absolute");
popup.Style.Add("z-index", "999");
AddMessageToPopupPanel(popup, TimeoutMessage);
EventHandler handlePopupButton = new EventHandler(but_Click);
AddOKButtonToPopupPanel(popup, handlePopupButton);
p.ContentTemplateContainer.Controls.Add(popup);
}
Finally, in order to throw a timeout event to the control's host page, we implement the public event EventHandler Timeout;
protected void OnTimeout()
{
if (Timeout != null)
Timeout(this, EventArgs.Empty);
}
You can now build this solution to make the
In order to test the control, add a new WebForm to the TestTimeoutTool project. Drag into it an AJAX Extensions <div>
<asp:ScriptManager ID="ScriptManager1" runat="server">
</asp:ScriptManager>
This page first loaded at <%= DateTime.Now.ToLongTimeString() %>.
<asp:UpdatePanel ID="UpdatePanel1"
runat="server" UpdateMode="Conditional">
<ContentTemplate>
This panel refreshed at <%= DateTime.Now.ToLongTimeString() %>.
<br /><asp:Button Text="Refresh Panel" ID="Button1"
runat="server"/>
</ContentTemplate>
</asp:UpdatePanel>
</div>
The point of this test is to make sure that when a partial update occurs in the
And, mutatis mutandis, after about two minutes from the time in the
There's only one potential problem with our custom control. Since the AJAX Extensions II. The ASP.NET AJAX Server ControlWhat do you get when you create a new AJAX Server Control project? For the most part, it looks very similar to the ASP.NET Server Control, though it doesn't come with a default implementation of the For this section of the tutorial, I need you to create a new ASP.NET AJAX Server Control project. Since we want to extend the current implementation, rather than replace it, you will want to save all the code you have written so far (everything between the class declaration and the final close bracket the class) over to the toolbox. You may also want to save the
For simplicity, I'm going to use the same project name, SessionTimeoutTool, for this new AJAX Server Control, which requires me to remove the current project with that name and move it to a new location, before creating the new one. If you want to simply use a different project name, this is fine, be sure to remain cognizant of the minor naming discrepancies that will result from this between your code and the code I will be describing in this section of the tutorial. The first thing we want to do in our new control project is rename all the default files: ServerControl1.cs, TimeoutWatcherBehavior.js, and TimeoutWatcherBehavior.resx. These files are named this way by default, no matter the name of your project. Let's rename ServerControl1.cs to TimeoutWatcherAjaxControl.cs. VS generously takes care of renaming our class declaration and constructor for us. Let's also rename the JScript file as TimoutWatcherBehavior.js. Sadly, VS is a bit more miserly here, and we will have to open the JScript file and rename our methods and initializers a bit more manually. Do an Edit | Find and Replace | Quick Replace to switch all instances of "ClientControl1" with "TimeoutWatcherBehavior". You should do this for the entire project, rather than just this file, in order to make sure the cs file also gets updated with the correct JScript file reference. Here is what the JScript file should look like after the changes, if you have built it with the SessionTimeoutTool project name. If not, it should use the name of your specific project as a namespace. /// <reference name="MicrosoftAjax.js"/>
Type.registerNamespace("SessionTimeoutTool");
SessionTimeoutTool.TimeoutWatcherBehavior = function(element) {
SessionTimeoutTool.TimeoutWatcherBehavior.initializeBase(this, [element]);
}
SessionTimeoutTool.TimeoutWatcherBehavior.prototype = {
initialize: function() {
AjaxServerControl1.TimeoutWatcherBehavior.callBaseMethod(this, 'initialize');
// Add custom initialization here
},
dispose: function() {
//Add custom dispose actions here
SessionTimeoutTool.TimeoutWatcherBehavior.callBaseMethod(this, 'dispose');
}
}
SessionTimeoutTool.TimeoutWatcherBehavior.registerClass(
'SessionTimeoutTool.TimeoutWatcherBehavior', Sys.UI.Control);
if (typeof(Sys) !== 'undefined') Sys.Application.notifyScriptLoaded();
There are four interesting sections in this JScript file. The first is the call to Skipping over the body of the code (we will come back to this), there is a The last line, which begins " Going back to the body of the code, the final thing worthy of note is how your JScript class is split between a main function and a prototype function. This is the prototype convention promoted by Microsoft for building AJAX behaviors. It is a bit of a mix of native JavaScript functionality, repurposed to make JavaScript behave in a more object-oriented manner, and the Microsoft AJAX Library. Here are some cursory pointers on this programming convention:
Once the namespace, class name, and filename for the script file have been determined, you must also change the resource file name to match the script file: in this case, it should be renamed to TimeoutWatcherBehavior.resx. The way we are using it, the resource file basically serves as a place holder, letting the assembly know that there is a resource by that name. That name can now be used in order to set our script file up as a web resource. Go into the properties of the .js file, and set its Build Action property to Embedded Resource. Now, go into the project Properties folder, and open AssemblyInfo.cs for editing. It is here that we will set up metadata to make the JScript file available as a resource, and then as a web resource. As a web resource, it will be automatically referenced from a dynamic location through the ScriptResource.axd, rather than through a file location. If you have had a chance to examine the AjaxControlToolkit, a separate assembly of custom AJAX controls and extenders, you will notice that it handles web resources by applying custom attributes to each custom control class, specifying the script resource that are coupled with the class. This is possible because the Toolkit has implemented internal code that uses reflection to automatically hook up scripts as web resources. It's all rather cool. However, we will be writing this control without reference to third-party tools such as the Toolkit base classes. Instead, we will try to only use what Visual Studio 2008 provides.
In the AssemblyInfo file, you should find two [assembly: System.Web.UI.WebResource("SessionTimeoutTool.TimeoutWatcherBehavior.js",
"text/javascript")]
[assembly: System.Web.UI.ScriptResource("SessionTimeoutTool.TimeoutWatcherBehavior.js",
"SessionTimeoutTool.TimeoutWatcherBehavior", "SessionTimeoutTool.Resource")]
What we really want is to turn our script into a script resource, but in order to do this, we also have to mark it as a web resource in the assembly. Declaring it as a web resource only requires using the We use the Each parameter should include the namespace of the control project. Since we have placed our embedded resource in the project root, the namespace and filename are enough to identify it. If you want to place the script file in a subfolder, then you will need to specify it in the AssemblyInfo file by namespace, subfolder[s], and filename. For instance, if we had placed the TimeoutWatcherBehavior.js and resx files under a subfolder called common, our metadata entry would look like this: [assembly: System.Web.UI.WebResource("SessionTimeoutTool.Common.TimeoutWatcherBehavior.js",
"text/javascript")]
[assembly: System.Web.UI.ScriptResource("SessionTimeoutTool.Common.TimeoutWatcherBehavior.js",
"SessionTimeoutTool.Common.TimeoutWatcherBehavior", "SessionTimeoutTool.Resource")]
Even though we have added the necessary metadata to identify our script file as a Script Resource, we still need to make sure it gets instantiated. This is done back in the To implement the yield return new ScriptReference("SessionTimeoutTool.TimeoutWatcherBehavior.js",
this.GetType().Assembly.FullName);
Again, if the resource is in a subfolder, then the subfolder name will need to be included when you specify the resource name. Behind the scenes, this <script src="/ScriptResource.axd?d=8O8TXUV..." type="text/javascript"></script>
will be inserted into our web page, making our JavaScript file available to our code, though in a somewhat obfuscated (and arguably more secure) manner. This is actually all that this method is used for. The other method of the $create(SessionTimeoutTool.TimeoutWatcherBehavior,
{"interval":120000,"message":"Timed out"},
null, null, $get("TimeoutWatcherControl1"));
which instantiates our JavaScript behavior class. This method is a bit more complicated, because it can be used to modify the generated ScriptControlDescriptor descriptor =
new ScriptControlDescriptor("SessionTimeoutTool.TimeoutWatcherBehavior",
this.ClientID);
yield return descriptor;
The final step in coupling our custom control with our JavaScript behavior class is to set some property values in the generated Drag all of our previous code, saved to the toolbar, into the We will now allow the user to determine whether they want to use server-side code, which starts a new empty session when the current session expires, or pure client-side code, which does not. This is accomplished with a class level variable, a new enum, and a public property: public enum ScriptMode
{
ServerSide,
ClientSide
}
private ScriptMode _scriptMode = ScriptMode.ServerSide;
public ScriptMode RunMode
{
get { return _scriptMode; }
set { _scriptMode = value; }
}
We should modify our if (RunMode == ScriptMode.ServerSide)
{
//create new ajax components
UpdatePanel timeoutPanel = GetTimeoutPanel();
...
//add update panel to timeout watcher control
this.Controls.Add(timeoutPanel);
}
In the protected override IEnumerable<ScriptDescriptor>
GetScriptDescriptors()
{
if (RunMode == ScriptMode.ClientSide)
{
ScriptControlDescriptor descriptor =
new ScriptControlDescriptor("SessionTimeoutTool.TimeoutWatcherBehavior",
this.ClientID);
descriptor.AddProperty("interval", _interval);
descriptor.AddProperty("timeoutMode", _timeoutMode);
descriptor.AddProperty("message", _message);
descriptor.AddProperty("redirectPage",
string.IsNullOrEmpty(_redirectPage) ? "" :
VirtualPathUtility.ToAbsolute(_redirectPage));
yield return descriptor;
}
}
If you try to run this code now, however, you should get an exception message of some sort, since we still have to script these properties in our Creating properties in a JavaScript behavior class is similar to creating one in C#. The main difference is in where you place your code and the fact that you have to use the SessionTimeoutTool.TimeoutWatcherBehavior = function(element) {
SessionTimeoutTool.TimeoutWatcherBehavior.initializeBase(this, [element]);
this._interval = 1000;
this._message = null;
this._timeoutMode = null;
}
SessionTimeoutTool.TimeoutWatcherBehavior.prototype = {
initialize: function() {
SessionTimeoutTool.TimeoutWatcherBehavior.callBaseMethod(this
, 'initialize');
},
get_interval: function() {
return this._interval;
},
set_interval: function(value) {
this._interval = value;
}
...
}
While the new intellisense for JavaScript is generally very nice, a minor annoyance is that code in your prototype will not recognize variables in your main class. Thus, when you begin typing "this.", "_interval" is not one of the values that intellisense will suggest to you. It is a bit odd that this isn't supported in intellisense when, at the same time, this style of coding is also encouraged by Microsoft. You may remember that For readability, however, we are better off scripting a client-side enum for this. Client-side enums are yet one more feature supported by the MS Atlas Library. The code for the client-side enumerator should be placed just before the " //C#
public enum mode
{
PageRedirect,
PopupMessage,
ExtendTime,
CustomHandler
}
//JScript
SessionTimeoutTool.Mode = function(){};
SessionTimeoutTool.Mode.prototype =
{
PageRedirect: 0,
PopupMessage: 1,
ExtendTime: 2,
CustomHandler: 3
}
SessionTimeoutTool.Mode.registerEnum("SessionTimeoutTool.Mode");
In order to track the session timeout in client-side code, we need to create an internal timer for our JavaScript class. To this end, add an additional instance variable to the main class, this._timer = null;
This will be used to handle our internal timer. We could try to handle the Adding a new script file to our project and making it available to the
Here is the // (c) Copyright Microsoft Corporation.
// This source is subject to the Microsoft Permissive License.
// See http://www.microsoft.com/resources/sharedsource/
// licensingbasics/sharedsourcelicenses.mspx.
// All other rights reserved.
/// <reference name="MicrosoftAjax.js" />
/// <reference name="MicrosoftAjaxTimer.js" />
/// <reference name="MicrosoftAjaxWebForms.js" />
/////////////////////////////////////////////////////////////////////////
// Sys.Timer
Sys.Timer = function() {
Sys.Timer.initializeBase(this);
this._interval = 1000;
this._enabled = false;
this._timer = null;
}
Sys.Timer.prototype = {
get_interval: function() {
return this._interval;
},
set_interval: function(value) {
if (this._interval !== value) {
this._interval = value;
this.raisePropertyChanged('interval');
if (!this.get_isUpdating() && (this._timer !== null)) {
this._stopTimer();
this._startTimer();
}
}
},
get_enabled: function() {
return this._enabled;
},
set_enabled: function(value) {
if (value !== this.get_enabled()) {
this._enabled = value;
this.raisePropertyChanged('enabled');
if (!this.get_isUpdating()) {
if (value) {
this._startTimer();
}
else {
this._stopTimer();
}
}
}
},
add_tick: function(handler) {
this.get_events().addHandler("tick", handler);
},
remove_tick: function(handler) {
this.get_events().removeHandler("tick", handler);
},
dispose: function() {
this.set_enabled(false);
this._stopTimer();
Sys.Timer.callBaseMethod(this, 'dispose');
},
updated: function() {
Sys.Timer.callBaseMethod(this, 'updated');
if (this._enabled) {
this._stopTimer();
this._startTimer();
}
},
_timerCallback: function() {
var handler = this.get_events().getHandler("tick");
if (handler) {
handler(this, Sys.EventArgs.Empty);
}
},
_startTimer: function() {
this._timer = window.setInterval(Function.createDelegate(this,
this._timerCallback), this._interval);
},
_stopTimer: function() {
window.clearInterval(this._timer);
this._timer = null;
}
}
Sys.Timer.descriptor = {
properties: [ {name: 'interval', type: Number},
{name: 'enabled', type: Boolean} ],
events: [ {name: 'tick'} ]
}
Sys.Timer.registerClass('Sys.Timer', Sys.Component);
if (typeof(Sys) !== 'undefined')
Sys.Application.notifyScriptLoaded();
Here is the metadata information in AssemblyInfo: [assembly: WebResource("SessionTimeoutTool.Timer.js", "text/javascript")]
[assembly: ScriptResource("SessionTimeoutTool.Timer.js",
"SessionTimeoutTool.Timer", "SessionTimeoutTool.Resource")]
And, here is the modified // Generate the script reference
protected override IEnumerable<ScriptReference>
GetScriptReferences()
{
if (RunMode == ScriptMode.ClientSide)
{
yield return new ScriptReference("SessionTimeoutTool"
+ ".TimeoutWatcherBehavior.js"
, this.GetType().Assembly.FullName);
yield return new ScriptReference("SessionTimeoutTool.Timer.js"
, this.GetType().Assembly.FullName);
}
}
The The next thing we want to do is to make sure our internal timer resets itself every time a page reloads. We were able to do this in C# by handling the Finally, in our initialize: function() {
SessionTimeoutTool.TimeoutWatcherBehavior.callBaseMethod(this
, 'initialize');
//create timer
this._timer = new Sys.Timer();
//create timer handler
tickHandlerDelegate = Function.createDelegate(this, this.tickHandler);
this._timer.add_tick(tickHandlerDelegate);
//create onload handler
setTime = Function.createDelegate(this,this.setTimer);
Sys.Application.add_load(setTime);
},
tickHandler: function(){
},
setTimer:function()
{
if(this._timer)
{
this._timer.set_enabled(false);
this._timer.set_interval(this.get_interval());
this._timer.set_enabled(true);
}
},
...
dispose: function() {
SessionTimeoutTool.TimeoutWatcherBehavior.callBaseMethod(this
, 'dispose');
if (this._timer) {
this._timer.dispose();
this._timer = null;
}
$clearHandlers;
}
To complete this section of the code, we need to make sure that when our All that remains for us to do is to handle each possible value of the We'll start by creating a skeleton implementation for tickHandler: function(){
if(this._timeoutMode == SessionTimeoutTool.Mode.PageRedirect)
{
this.pageRedirect();
return;
}
if(this._timeoutMode == SessionTimeoutTool.Mode.PopupMessage)
{
this.popup();
return;
}
if(this._timeoutMode == SessionTimeoutTool.Mode.ExtendTime)
{
this.extendTime();
return;
}
if(this._timeoutMode == SessionTimeoutTool.Mode.CustomHandler)
{
this.customHandler();
return;
}
},
Both the pageRedirect: function(){
window.location = this._redirectPage;
},
popup: function(){
this._timer._stopTimer();
if(this._message == null)
{
alert("The session has expired.");
}
else
{
alert(this._message);
}
},
The astute reader will remark that in this section of the tutorial, we have not actually used any real AJAX functionality, up to this point. "Real" AJAX involves using the In order to implement the The basic solution is pretty simple. The MS AJAX Library supports calls to web services from JavaScript. So, all we need to do is create a web service that extends the session by writing a value to it. In your control project, add a reference to the assembly using System.ComponentModel;
using System.Web.Services;
using System.Web.Services.Protocols;
namespace SessionTimeoutTool
{
[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
[ToolboxItem(false)]
[System.Web.Script.Services.ScriptService]
public class WebService1 : System.Web.Services.WebService
{
[WebMethod(EnableSession = true)]
public void ExtendSessionTimeout()
{
Session["extendTimeout"]=true;
}
}
}
The standard way to use ASP.NET AJAX to call a web service requires adding a reference to our service in the var webRequest = Sys.Net.WebServiceProxy.invoke("WebService1.asmx"
, "ExtendSessionTimeout", false, null
, null, null, "User Context");
But here's the rub. In .NET, web services are implemented using *.asmx pages that reference underlying web service classes. The AJAX function that calls the code is run in the assembly that implements our custom control. The WebService1.asmx that the AJAX function calls, however, does not exist in that assembly. If we try to run our code as it stands now, we will throw an exception since the file cannot be found. There is also no way to set up an asmx page as a resource file in order to expose it in the client assembly. One solution would be to make the consumer of our control implement the WebService1.asmx in their own project. While this would work, it is rather un-cool. It would be preferable to have a self-contained AJAX Server Control that is able to find its own internal web service no matter where it is used.
First, let's set up our extendTime: function(){
this.callWebService();
},
callWebService: function()
{
var webRequest = Sys.Net.WebServiceProxy.invoke(
"SessionTimeoutTool.asmx" //path
, "ExtendSessionTimeout" //methodName
, false //useHttpGet
, null //parameters
, this.succeededCallback
, this.failedCallback
, "User Context");//userContext
},
succeededCallback: function(result, eventArgs)
{
if(result !== null)
alert(result);
},
failedCallback: function(error)
{
alert(error);
},
Specifications for this AJAX Library function can be found here. Even though our web method does not return a value, I have included stub methods for the callback functions, for reference. If you do not need callbacks, the success and fail parameters can be The project that consumes our control will need to include an <system.web>
<httpHandlers>
<add verb="*" path="SessionTimeoutTool.asmx"
type="SessionTimeoutTool.SessionTimeoutHandlerFactory"
validate="false"/>
...
</httpHandlers>
</system.web>
Writing our There is one additional element of complexity, however. All the I have streamlined his code a bit, since it is intended only to handle web service calls from JavaScript, and only for one specific web service. I've also added a little additional code, based on Reflection on the The implementation is pretty generic, and copy-and-paste ready. You will be able to re-use it, as-is, in your future projects. The only thing that ever changes is the value of the web service class, which is set in the using System;
using System.Collections.Generic;
using System.Text;
using System.Web;
using System.Reflection;
using System.Web.Services.Protocols;
| ||||||||||||||||||||