LavaBlast Software Blog

Help your franchise business get to the next level.
AddThis Feed Button

Gotcha: ASP.NET and exceptions in asynchronous tasks

clock October 30, 2008 13:00 by author JKealey

Awesomeness Here's a little piece of information you might not know about how ASP.NET 2.0 and above operate. Simply put, if an unhandled exception occurs in a secondary thread which has been launched by your ASP.NET application, IIS will force your application to restart. This can mean lost sessions, bad server side state, slow reloads and, in general, is a bad thing. However, these kinds of exceptions are a pain to discover and resolve if you don't know what you're looking for because the built-in ASP.NET error handler does not get fired and your user is not redirected to an error page, unless something breaks after the web server restarts. (This article explains the cryptic messages you will see in the Event Log).

Here's a simple scenario based on the integration between our interactive kiosk created for The Code Factory co-working space and Twitter. We want our web application to post messages on Twitter when certain events occur (member check-in and member check out). Because we did not want to re-invent the wheel, we found a Twitter C# Library with a permissive software license. Noticing that posting to Twitter slowed down our application and because we didn't want our application to depend on Twitter's availability, we decided to run this code asynchronously. Being careless, we supposed that should anything bad occur in this second thread (timeouts, invalid login/password, etc.), the system will simply not be able to post, which was not a problem for us. We were wrong, and the whole web application restarted because of this. (This was not the case in ASP.NET 1.x and was previously discussed by others).

In any case, here's some sample code to run the Twitter library asynchronously, including a try/catch.

using System;
using System.Collections.Generic;
using System.Text;
 
namespace LavaBlast.Util.Twitter
{
    public class AsyncTwitter
    {
        public void Update(string userName, string password, string status)
        {
            TwitterDelegate caller = new TwitterDelegate(UpdateTwitter);
            caller.BeginInvoke(userName, password, status, new AsyncCallback(CallbackMethod), caller);
        }
 
        protected delegate void TwitterDelegate(string userName, string password, string status);
        protected static void CallbackMethod(IAsyncResult ar)
        {
            TwitterDelegate caller = (TwitterDelegate)ar.AsyncState;
            caller.EndInvoke(ar);
        }
        protected static void UpdateTwitter(string userName, string password, string status)
        {
            try
            {
                Twitter t = new Twitter();
                t.Update(userName, password, status, Twitter.OutputFormatType.XML);
            }
            catch (Exception ex)
            {
                LavaBlast.ElectronicJournal.Error("Unable to post to twitter.", ex);
                // ignore. 
            }
        }
    }
}

The lesson learned is unhandled exceptions in other threads can wreak havoc on your ASP.NET application. To help debug these kinds of errors when they occur, I do suggest you setup the HttpModule developed by Peter A. Bromberg that adds the actual exception in your Event Log. Peter describes the problem in more detail than I do, and is worth a read.

Have a Happy Halloween! (Halloween will be weird for us in 2008 because we were hit by the year's first snowstorm on Tuesday.)

halloween

kick it on DotNetKicks.com


Gotcha: WebKit (Safari 3 and Google Chrome) Bug with ASP.NET AJAX

clock October 20, 2008 23:38 by author JKealey

image Tonight we fixed a severe compatibility issue that I feel you should all be aware of.

Executive summary: ASP.NET AJAX breaks down completely in some circumstances when using WebKit-based browsers. Reference the JavaScript provided below to solve these problems.

As you know, we develop web applications using ASP.NET AJAX and tonight we were notified that one of our users was not able to proceed to the payment page on one of our e-commerce websites. (As you can imagine, preventing payment is the worst thing that can happen in an e-commerce site!). After some investigation, we discovered the user was using Safari 3 on an Intel-based MacBook Pro just like the one I recently purchased. I picked up my MBP and re-tested the site, going through exactly the same steps this user did (thanks to the auditing capabilities we've built into the e-commerce engine). The site works fine in general, except at one particular page deep within the bowels of the website, when clicking on a button, the ASP.NET UpdateProgress control would show and never go away during the asynchronous postback.

I first thought this was an SSL issue such as the evil IE White Screen Bug, but I managed to replicate on my local machine without using SSL. This button is contained inside a dozen layers of user controls. Fortunately, I use this user control in another location. I tested one of these locations (where it isn't as nested) and this one worked. I then proceeded to hide my user controls, layer by layer and narrowed the issue to ASP.NET validators on one of my pages.

<asp:RequiredFieldValidator ID="rfvName" runat="server" ErrorMessage="Required"
    Display="Dynamic" ControlToValidate="txtName"="true" />


Adding Visible="false" to this (and the other validators) on my user control unfroze the UpdateProgress. My first thought was that I must have reached a limit in identifier name lengths because my validator was nested very deeply. I proceeded to rename a few layers to make the name shorter. This changed nothing. I then discovered how to enable a FireBug-like tool in Safari called Web Inspector. This helped me discover an obscure JavaScript error.

Sys.ScriptLoadFailedException: The script 'http://localhost:2241/ScriptResource.axd?[...]' failed to load.


After changing from ScriptMode="Release" to ScriptMode="Debug", I got additional details.

Check for:
Inaccessible path.
Script errors. (IE) Enable 'Display a notification about every script error' under advanced settings.
Missing call to Sys.Application.notifyScriptLoaded().


This finally lead me to an old post on the ASP.NET forums. It appears that Safari 2 needed a few hacks in the ASP.NET AJAX JavaScript code to work properly. These hacks are no longer needed in Safari 3 (or Google Chrome) because WebKit works out of the box. However, these hacks sometimes broke WebKit-based browsers as I discovered today. The first solution in the forums is to change the JavaScript files used by the framework but we didn't like that solution very much. The second comment provided a solution which we found reasonable. 

Workaround

The workaround simply tells ASP.NET AJAX that Safari 3 and Google Chrome are a new type of browser instead of the old Safari for which workarounds had to be programmed.

1) Create a new file called webkit.js

Sys.Browser.WebKit = {}; //Safari 3 is considered WebKit
if( navigator.userAgent.indexOf( 'WebKit/' ) > -1 )
{
  Sys.Browser.agent = Sys.Browser.WebKit;
  Sys.Browser.version = parseFloat( navigator.userAgent.match(/WebKit\/(\d+(\.\d+)?)/)[1]);
  Sys.Browser.name = 'WebKit';
}


2) Reference this webkit.js from your ScriptManager

<ajax:ToolkitScriptManager ID="scripts" runat="server" ScriptMode="Release" EnableHistory="true" 
EnableSecureHistoryState="false" EnablePageMethods="True" CombineScripts="true" 
OnAsyncPostBackError="Page_OnAsyncError" OnNavigate="OnHistoryNavigate">
    <Scripts>
        <asp:ScriptReference Path="~/js/webkit.js" />
    </Scripts>
</ajax:ToolkitScriptManager>


We hope this will help some of you!

kick it on DotNetKicks.com


SQL Server - Restore a database backup via the command line

clock October 14, 2008 12:45 by author EtienneT

image Anyone who's ever developed a web application in .NET has had to play with a database management system, most probably SQL Server or its free cousin, SQL Server Express.  One of the tasks I personally hate doing with our SQL Server Express 2005 databases is restoring them from a backup, using SQL Management Studio.  We sometimes restore the point of sale database used by our customers to track down various issues or to build reports using their data as our test set. The process is not that long when you restore a backup from your own machine (restoring the MDF and LDF files to their original directory). If you restore databases from foreign systems, the process is simple only if both systems stored their databases in the same directory, which is rarely the case.

For example, I use Windows Vista x64 and our dedicated server uses a 32-bit version of Windows 2003.  Our data is stored in the default SQL Server directory, which is in the Program Files folder.  However, when using a 64-bit operating system, the program files directory is different (C:\Program Files (x86)).  Since the location of the MDF and LDF files are encoded directly in the bak file generated by SQL Server, restoring them via the command line is especially challenging when you don't control the original locations of the MDF and LDF files, nor their Logical Names.

Our goal is to be able to restore a database by executing a simple command such as this:

restore.bat LavaBlast

This command would look for LavaBlast.bak in the current directory and would restore the LavaBlast database to a default location on your computer where you want to store your MDF and LDF files.

Here is the code for restore.bat:

sqlcmd -S .\SQLEXPRESS -i attachDB.sql -v database="%1" -v root="%CD%"

We are simply calling sqlcmd (added to our path) to connect to our local instance of SQL Server Express and we are executing an SQL file (attachDB.sql) which includes two variables: database and root (the current path).

Here is the code for attachDB.sql:

USE MASTER
GO
IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[$(database)]') AND type in (N'U'))
  ALTER DATABASE $(database) SET SINGLE_USER WITH ROLLBACK IMMEDIATE;
 
create table #backupInformation (LogicalName varchar(100),
PhysicalName varchar(100),
Type varchar(1),
FileGroupName varchar(50) ,
Size bigint ,
MaxSize bigint,
FileId int,
CreateLSN int,
DropLSN int,
UniqueId uniqueidentifier,
ReadOnlyLSN int,
ReadWriteLSN int,
BackupSizeInBytes int,
SourceBlockSize int,
FileGroupId int,
LogGroupGUID uniqueidentifier,
DifferentialBaseLSN bigint,
DifferentialBaseGUID uniqueidentifier,
IsReadOnly bit, IsPresent bit )
 
insert into #backupInformation exec('restore filelistonly from disk = ''$(root)\$(database).bak''')
 
DECLARE @logicalNameD varchar(255);
DECLARE @logicalNameL varchar(255);
 
select top 1 @logicalNameD = LogicalName from #backupInformation where Type = 'D';
select top 1 @logicalNameL = LogicalName from #backupInformation where Type = 'L';
 
DROP TABLE #backupInformation 
 
RESTORE DATABASE $(database)
FROM DISK = '$(root)\$(database).bak'
WITH REPLACE,
MOVE @logicalNameD TO 'C:\Program Files (x86)\Microsoft SQL Server\MSSQL.1\MSSQL\Data\$(database).mdf',
MOVE @logicalNameL TO 'C:\Program Files (x86)\Microsoft SQL Server\MSSQL.1\MSSQL\Data\$(database).ldf'
GO

Simply put, we are extracting the logical names (and other metadata) from the .bak file into a temporary table. We then use those values to restore the MDF and LDF to the correct location, instead of the ones specified in the .bak file.

If you want to use this script, simply ensure you change the location of your SQL Server data files (the last lines in the SQL file) and you should be good to go. Please note that in its current form, the script only supports files with one MDF and one LDF file in the database backup. Furthermore, it assumes your .bak file has the same name as the database you want to import. We could also enhance the script by automatically adding permissions to the ASP.NET user after restoring the database. Feel free to post any enhancements you make in this post's comments and I hope you'll find this script useful! Enjoy.

kick it on DotNetKicks.com


Month List

Disclaimer

The opinions expressed herein are my own personal opinions and do not represent my employer's view in anyway.

© Copyright 2017

Sign in