Disabling an ASP.NET Button when Clicked


Published Friday, December 21, 2007 | Comments (0)

I was answering a question in the ASP.NET forums on Simple-Talk.com (click here you want to check it out) about how to avoid the issue of users submitting multiple page requests when they click on buttons.  My first inclination was hey, just disable the button after they click it and everything will be just fine.  So I fired up Visual Studio and threw the following code into a page:

<asp:Button runat="server" Text="My Button" OnClientClick="this.disabled=true;" />


And naturally it failed miserably.  Why?  Because nothing works on the first try -- unless fate is trying to store up some catastrophe points to use on you later.  And apparently if you disable a button in the onclick it doesn't post back for reasons that I haven't dug into very far.  Not to be outwitted by HTML, Javascript, .NET, or whatever else you can blame for your problems, I tried a myriad of random ideas until I hit upon this -- instead of disabling the button when you click it, set the onclick event to fire a function that returns false.  So it looks something like this:

<asp:Button runat="server" Text="My Button" OnClientClick="this.onclick=new Function('return false;');" />


This approach effectively disables the button, but doesn't gray it out.  So there you go.  If you want a gray button then this isn't the blog for you, but it does keep people from clicking on it the button more than once.  Naturally, this means that some sticklers out there will whine because the button stays disabled if the request times out or the viewer stops the request manually.  Part of me says leave it disabled as vengeful tribute to the multitudes who quintuple-clicked your button and created a horde of duplicate records for you to delete.  Then there's the other part of me that likes the JavaScript setTimeout function and a bit of a challenge:

<script type="text/javascript"
   
function disableButton(button, resetDelay)
   
{
        button.oldonclick
= button.onclick;
       
button.onclick=noClick; //new Function("return false;";
       
setTimeout("enableButton('"+ button.id + "');" resetDelay);                                    
   
}
   
function noClick()
   
{
        alert
("Chill - You already submitted this page once. "+
               
"Submitting it twice isn't going to make the "
               
"server go faster.  Quit hitting the "+
               
"freakin' button!";
       
return false;
   
}
   
function enableButton(buttonId)
   
{
        var button
= document.getElementById(buttonId);
       
if(button!=null)
       
{
            button.onclick
= button.oldonclick;
       
}
    }
script>


The disableButton function copies the "standard" onclick method into a fake property of the button for safe keeping, sets the new onclick to the noClick function (which displays a nice message to the user if they DO try to click), then sets up a delayed function call (read--asynchronous method) that executes after a delay that you set when calling the disableButton method (a value of 1000 for the resetDelay = 1 second, so multiply accordingly for your needs).  Here's an example of what it looks like in a button:

<asp:Button runat="server" Text="My Button" OnClientClick="disableButton(this,30000)" />


When you click this button, it gets disabled for 30 seconds.  And if a user clicks on that button in the interim, they even get a little feedback on what's going on.  Just remember, don't blame me when you get fired for pasting the script into your project and forgetting to change the message.

Bookmark this post: Dot Net Kicks
By Damon Armstrong | Permalink | Comments (0) | Comment on this Post
Filed Under: .NET Development

WPF: WindowsFormsHost Control Error


Published Tuesday, September 04, 2007 | Comments (0)

I had the code base for my first WPF application ready to go, so I compiled a release version of software and tried to run it from the .exe instead of directly from Visual Studio.  I was promptly greeted with the following error:

The tag 'WindowsFormsHost' does not exist in XML namespace 'http://schemas.microsoft.com/winfx/2006/xaml/presentation'.

It ran flawlessly from the Visual Studio .NET debugger, which I found a bit odd.  I tracked the issue back to the following problem.  When I originally referenced the WindowsFormsIntegration.DLL from my project, the "Copy Local" option was set to false.  When I went back in and set that value to True, the application promptly broke when it ran in the debugger.  I don't mind when things break as long as they break consistently, so this was a step in the right direction.  I had found a few helpful forum entries informing me that you have to specify the xml namespace for the WindowsFormsIntegration assembly.  So I added the following xmlns entry:

xmlns:wfi="clr-namespace:System.Windows.Forms.Integration;assembly=WindowsFormsIntegration"

And changed my WindowsFormsIntegration tag from


    ...

to


    ...

and it started working just fine.

Bookmark this post: Dot Net Kicks
By Damon Armstrong | Permalink | Comments (0) | Comment on this Post
Filed Under: none

Simple Code Performance Testing


Published Friday, August 24, 2007 | Comments (0)

After posting Performance: Caching vs. Reading from an In-Memory XML Document, there have been some questions about how I actually do the performance testing.  My approach to performance testing is really simple... I just write some code, run that code in a big for loop, and time how long it takes to run through all of those iterations.  Nothing too complicated.  Calculating the speed of the operation becomes a simple matter of (iterations / time).  I've packaged this testing routine into a class I call the PerformanceTimer class, which simplifies things even more.  The code for the class follows, and an example of how to use the class to test a routine follows even further down.


using System;

namespace Rebel.Performance
{
    
/// 


    ///   Delegate to a testing method
    /// 

    public delegate void TestingMethodDelegate(int iterations);

    
/// 
    ///   Executes a testing method and stores execution duration
    /// 

    
public class PerformanceTimer
    {
        
/// 
        ///   Property backer for the ExecutionSpan property
        /// 

        
private TimeSpan _propExecutionSpan;
        
        
/// 
        ///   Property backer for the Iterations property
        /// 

        
private int _propIterations;

        
/// 
        ///   Duration of testing method execution
        /// 

        
public TimeSpan ExecutionSpan
        {
            get { 
return _propExecutionSpan}
            
private set { _propExecutionSpan value}
        }

        
/// 
        ///   Number of iterations for the test
        /// 

        
public int Iterations
        {
            get { 
return _propIterations}
            
private set { _propIterations value}
        }

        
/// 
        ///   Executes testing method and determines execution duration
        /// 

        /// Delegate to the testing method
        
public void Run(int iterationsTestingMethodDelegate testingMethod)
        
{
            Iterations 
iterations;
            
long startTimeendTime;
            
startTime DateTime.Now.Ticks;
            
testingMethod.Invoke(Iterations);
            
endTime DateTime.Now.Ticks;
            
ExecutionSpan = new TimeSpan(endTime startTime);
        
}

        
/// 
        ///   Number of iterations per second
        /// 

        
public double IterationsPerSecond
        {
            get
            {
                
return (Iterations ExecutionSpan.TotalSeconds);
            
}
        }

        
/// 
        ///   Number of iterations per millisecond
        /// 

        
public double IterationsPerMillisecond
        {
            get
            {
                
return (Iterations ExecutionSpan.TotalMilliseconds);
            
}
        }

    } 
//class

//namespace


So that's the class, but how do you use it?  Here's a simple test application that uses the PerformanceTimer class to check the speed of concatenation operations.  You've always heard that using a StringBuilder to build a string is faster than repeatedly concatenating a string directly?  Here's a chance to actually prove it.   


using System;
using System.Text;
using Rebel.Performance;

namespace Rebel.PerformanceTest
{
    
/// 
    ///   Console Application
    /// 

    
class Program
    {

        
static void Main(string[] args)
        
{
            PerformanceTimer timer 
= new PerformanceTimer();
            
int iterations 100000;

            
//Run TestA
            
timer.Run(iterationsnew TestingMethodDelegate(TestMethodA));
            
Console.WriteLine(timer.IterationsPerMillisecond);
            
            
//Run TestB
            
timer.Run(iterationsnew TestingMethodDelegate(TestMethodB));
            
Console.WriteLine(timer.IterationsPerMillisecond);

            
Console.ReadLine();

        
}
        
        
/// 
        ///   Concatenation using a StringBuilder
        /// 

        
static void TestMethodA(int iterations)
        
{
            StringBuilder s 
= new StringBuilder();
            
for (int 0iterationsi++)
            
{
                s.Append
("A");
            
}
        }

        
/// 
        ///   Direct concatenation
        /// 

        
static void TestMethodB(int iterations)
        
{
            
string "";
            
for (int 0iterationsi++)
            
{
                s 
+"A";
            
}
        }

    } 
//class

//namespace
WARNING: You will want to use as large of an iteration count as possible when testing.  This performance testing approach does the "best" it can when calculating exact start and end times, so some fluctuations are bound to occur when capturing the start and end times.  When they do, the fluctuations throw off the calculations.  Larger iteration counts spread out the effect of the fluctuations and minimize the error.  Think of it this way:  if you are off by 1 in 10, then you have a 10% error.  If you are off by 1 in 10000, you have a 0.01% error.


On my machine, the StringBuilder operates at 14629 iterations / second and the direct concatenation approach runs at 13 iterations / second.  Pretty significant difference. 

Bookmark this post: Dot Net Kicks
By Damon Armstrong | Permalink | Comments (0) | Comment on this Post
Filed Under: .NET Development

Performance: Caching vs. Reading from an In-Memory XML Document


Published Wednesday, August 22, 2007 | Comments (0)

I've been working with a number of XML documents recently.  Basically I'm trying to expose configuration values stored in XML configuration files in an object-oriented structure.  I'm using an in-memory XML document and just referencing values as needed from that document.  The XML reading does not need to be super-high performance because it does not occur very often, but it got me to thinking... what if it did?

So I ran some performance tests to check the difference between accessing a value from an XML document vs. storing the value in cache and accessing it through a standard get property. 

The results (at least on my machine) are as follows:

 

Iterations / Millisecond

Performance Difference
XML (attribute value)

8,648

90% slower

XML (inner text value)

21,694

76% slower

Cached Property

90,140

 

I knew caching would beat out the other two methods since it's very fast to access a direct variable, but I was surprised that there was such a difference.  I figured there would be a bit of overhead for the inner text property, and a bit more for the attribute lookup, but I was thinking it would be in the range of 10-20% slower.  Live and learn.  Of course, reading from an in-memory XML document is still pretty dang fast, it's just that reading from a cached value is faster.

Bookmark this post: Dot Net Kicks
By Damon Armstrong | Permalink | Comments (0) | Comment on this Post
Filed Under: none

ADFS - Cookie Error


Published Thursday, August 16, 2007 | Comments (0)

I've been knee deep in Active Directory Federation Services for the past three months now, and when helping one our clients deploy a single-sign-on (SSO) application, we ran into a nasty error:

The request has been rejected because it appears to be a duplicate of a request from this same client browser session within the last 20 seconds

Here is a quick synopsis of the problem.  ADFS works by bouncing users back and forth between federation servers and the application, and somewhere along the way you end up with a cookie containing authentication information (a SAML token to be more precise).  One of the options in the web.config is the path to the cookie, which is found in


    
          ...
         
               ...
               
                    ...
                    /CookiePath
               

         

    

During the deployment, we placed the application in a directory that did not match the cookie path.  So, the user was going to the application, the application then sent the user to the federation server for authentication, the federation server issued the cookie, redirected the user back to the application, the cookie was not being sent when the user hit the application a second time, so the application sent the user back to the federation server for authentication, and the federation server doesn't like it when you try to login twice within 20 seconds.  So the moral of the story is to make sure you setup your cookie path correctly or you get weird ADFS errors.

Bookmark this post: Dot Net Kicks
By Damon Armstrong | Permalink | Comments (0) | Comment on this Post
Filed Under: none

Reclaim Microsoft Virtual PC Console that Displays Outside the Desktop


Published Thursday, May 31, 2007 | Comments (0)

I ran into a bit of a problem the other day with Microsoft Virtual PC staring outside of my viewable desktop area.  And it turns out that it wasn't just a fluke, because it's happened a couple of times since then.  And Robby, one of the guys I work with at Cogent, had the same problem.  Here's how you get around the issue (I assume this works for any window if you have this issue, not just VPC):

  1. Right click on the Microsoft Virtual PC Taskbar item and choose "Move" from the context menu
  2. Hold the CTRL button and hit your arrow keys a few times.  This makes the title bar of the window "stick" to the mouse.
  3. Wave your moue around wildly until you see the window
  4. Click the left mouse button and the title bar will "unstick"

That should get your window back.

Bookmark this post: Dot Net Kicks
By Damon Armstrong | Permalink | Comments (0) | Comment on this Post
Filed Under: none

Wordpad may corrupt your SharePoint (and other ASP.NET app's) Web.confg


Published Friday, May 18, 2007 | Comments (0)

I was remoting into a SharePoint 2007 (MOSS) box yesterday because I needed to update the configuration on a few of the servers in the farm.  Like most production systems, the server did not have Visual Studio, so I was using Notepad to edit the configuration.  As you may know, navigating through a configuration file in Notepad is a bit of pain compared to Visual Studio because it lacks the finesse of a full featured text editor.  My connection was also fairly slow so it was making my configuration task more irksome than normal.

Hoping to get a bit more control, I made the switch to Wordpad.  After making a couple of simple configuration changes, I refreshed the SharePoint site to make sure everything was okay.  I was greeted with an error screen.  Thinking that I must have made a typo, I checked over the configuration changes but didn’t see anything unusual.  So I decided to start removing things until the error went away.  Each time I removed a change, I refreshed the screen hoping to identify the issue.  The error screen persisted. 

Finally, I reverted back to the backup and the page popped back up no problem.  That’s when I started thinking something was really amiss.  I opened the working Web.config in Wordpad  and saved the file without making any changes.  The error was back.

I don’t know why, but Word pad randomly places little questions marks at various intervals throughout the Web.config.  I saw no rhyme or reason to the madness, but I confirmed it on multiple Windows 2003 servers.  So, if you’re configuring SharePoint, or any .NET application for that matter, on a remote machine, make sure to shy away from Wordpad because you’ll save yourself a headache.

Click on the following image to see the differences in the files:

Bookmark this post: Dot Net Kicks
By Damon Armstrong | Permalink | Comments (0) | Comment on this Post
Filed Under: none

Displaying Debugging Info for SharePoint Errors in Your Browser


Published Thursday, May 17, 2007 | Comments (0)

SharePoint does a good job of hiding errors from users.  Out of the box, unhandled errors in SharePoint result in a fairly non-descript page that says "An Error Occurred" (or something to that effect).  Although a good practice for your end-users, it's pretty annoying when you're trying to get a piece of code working or a configuration setting correct.  Here's how you can display debugging information in your browser:

  • Find the following line in your web.config:


  • Change the CallStack attribute to true
  • Change the AllowPageLevelTrace to true
  • Find the following line in your web.config:


  • Change the mode to "Off"

The next time you have an error, it displays the familiar ASP.NET error information page with exception details and the callstack information.  Much less frustrating than a blasé error message.

Bookmark this post: Dot Net Kicks
By Damon Armstrong | Permalink | Comments (0) | Comment on this Post
Filed Under: SharePoint

VS SDK Error Message: CTC : fatal error CTC2013: Can't start preprocessor (2)


Published Wednesday, April 11, 2007 | Comments (0)

After watching some of the demo videos of the latest Visual Studio SDK, I was excited to get started and try out my own add-in.  I installed the SDK, fired up Visual Studio, created an integration package, compiled, and got a nasty error:

CTC : fatal error CTC2013: Can't start preprocessor (2)

What does this mean?  Apparently, part of the integration package includes something that needs to be compiled by a C++ compiler.  When originally looking through the options of my Visual Studio install, I noticed that the C++ compiler likes to take up 1-2 gigs of space, and not being a C++ developer, I choose to forgo that option.  Apparently C++ has come back to bite me years after I thought I was done with it forever.

All you have to do to get around the error is go back and install the C++ compiler from your Visual Studio 2005 disc, then compile the project again.  It should work.

If you get a "Requires elevation" error during the compilation, remember that you're running Vista and you need to fire up Visual Studio in administrator mode.

Bookmark this post: Dot Net Kicks
By Damon Armstrong | Permalink | Comments (0) | Comment on this Post
Filed Under: none

SharePoint Permission Names


Published Friday, March 23, 2007 | Comments (0)

SharePoint 2007 has a new control called the SPSecurityTrimmedControl.  It's a container control that allows you to hide or display a section of your page depending on whether or not the currently logged-in user has the appropriate permissions.  To set the permissions, however, you need to know what SharePoint calls the permissions.  Took a little while to drudge it up, but here's the list:

http://msdn2.microsoft.com/en-us/library/microsoft.sharepoint.spbasepermissions.aspx

Bookmark this post: Dot Net Kicks
By Damon Armstrong | Permalink | Comments (0) | Comment on this Post
Filed Under: none