Tuesday, September 11, 2007

ASP.NET Web Application in Offline Mode Revisited

Probably most of You are aware of the app_offline.htm functionality. Basically if you put a html file named app_offline.htm in the root of a web application, the application goes to "offline" mode where all incoming requests are redirected to this file.

This seems great at first sight. You can temporarily disable the application while performing some maintenance. Specifically, you can delete all application files except the app_offline.htm and the application still works (even though the web.config is gone!) As long as the maintenance is completed you just remove or rename the app_offline.htm and the application is back from the void.

There is, however, one caveat and one drawback of this feature.

The caveat is that the app_offline.htm file must be longer that 512 bytes otherwise instead of its content you get ... 404 (can someone explain it?).

The drawback is that the offline mode affects everyone including you! Either anyone or noone can access the application. What would be nice, however, is to have the ability to temporarily redirect requests coming from outside but still be able to use the application locally.

The code below shows how to accomplish that requirement with really simple HTTP handler. To use it you have to:

  1. compile the source class
  2. add the handler section to the web.config file and put appoffline.htm (without the _ !) in the root of the application
   1: <httpHandlers>
   2:    <add verb="*" path="*.aspx" type="AppOffline.AppOfflineHandler, AppOffline" />
   3:  </httpHandlers>

 


Handler code:



   1: using System;
   2: using System.Collections.Generic;
   3: using System.Text;
   4: using System.Web;
   5: using System.IO;
   6: using System.Web.UI;
   7:  
   8: /* Wiktor Zychla, 2007 */
   9: namespace AppOffline
  10: {
  11:     public class AppOfflineHandler : IHttpHandler
  12:     {
  13:         string appOffline;
  14:         /// <summary>
  15:         /// appoffline.htm file content as singleton
  16:         /// </summary>
  17:         public string AppOffline
  18:         {
  19:             get
  20:             {
  21:                 if ( appOffline == null )
  22:                     appOffline = OfflineFileContent;
  23:  
  24:                 return appOffline;
  25:             }
  26:         }
  27:  
  28:         /// <summary>
  29:         /// Path to the appoffline.htm file
  30:         /// </summary>
  31:         public string OfflineFilePath
  32:         {
  33:             get
  34:             {
  35:                 return context.Server.MapPath( "~/appoffline.htm" );
  36:             }
  37:         }
  38:  
  39:         /// <summary>
  40:         /// appoffline.htm file content
  41:         /// </summary>
  42:         public string OfflineFileContent
  43:         {
  44:             get
  45:             {
  46:                 return File.ReadAllText( OfflineFilePath );
  47:             }
  48:         }
  49:  
  50:         /// <summary>
  51:         /// Is the application in the offline mode?
  52:         /// 
  53:         /// Yes - if the appoffline.htm exists
  54:         /// </summary>
  55:         public bool IsOffline
  56:         {
  57:             get
  58:             {
  59:                 return File.Exists( OfflineFilePath );
  60:             }
  61:         }
  62:  
  63:         HttpContext context;
  64:  
  65:         #region IHttpHandler Members
  66:         public bool IsReusable
  67:         {
  68:             get
  69:             {
  70:                 return true;
  71:             }
  72:         }
  73:  
  74:         public void ProcessRequest( HttpContext context )
  75:         {
  76:             this.context = context;
  77:  
  78:             // offline mode and remote request?
  79:             if ( !context.Request.IsLocal &&
  80:                  IsOffline
  81:                 )
  82:             {
  83:                 context.Response.Clear();
  84:                 context.Response.Write( AppOffline );
  85:  
  86:                 context.Response.End();
  87:             }
  88:             else
  89:                 // redirect to the default processing pipe
  90:                 PageParser.GetCompiledPageInstance( 
  91:                     context.Request.Path, 
  92:                     context.Request.PhysicalPath, 
  93:                     context ).ProcessRequest( context );             
  94:         }
  95:         #endregion
  96:     }
  97: }

This approach has its own drawback - it is not "maintenance" aware in a sense that when for example some crucial modules are gone from the /bin subfolder, the application will not load at all because of non-existent modules beeing referenced from within the web.config file.


The nice thing is that you can test the application locally but all remote requests are gracefully redirected to the appoffline.htm. I suggest then to use my feature side by side with the ASP.NET app_offline.htm.

Thursday, September 6, 2007

Query String Tampering

Query String Tampering is one of the easiest and most common Internet attacks. Each time a web application uses QueryString to pass sensitive information between pages, there is a risk that tampering is possible and would reveal otherwise unavaiable information.

Suppose that I am allowed to see the list of my order (567), and clicking on the application-generated link I am supposed to see the details.

http://the.address.com/details.aspx?orderid=567

 

What if I try to manually alter the parameter value and see an order from someone else?

http://the.address.com/details.aspx?orderid=568

 

If it works then we can say that the application is vulnerable to QST.

There are two possible solutions to this issue:

  1. sign query string parameters with additional signature. You can read about it here
  2. encrypt the whole query string so that there is only single encrypted parameter visible on the client-side. You can read about it here

 

From these two possibilities I prefer the latter since it seems to be slightly more ellegant. Custom implementation is easy and straightforward.

Wednesday, August 29, 2007

Context Help Made Easy - Reloaded

Yet another issue

A month ago I've written about context sensitive help. The article can be found here and please refer to it before further reading.

Although the concept works pretty well, there are some obvious drawbacks which apply also to the original concept of Help Providers. What is the drawback?

Well, you are allowed to assign one and only one help topic to a single control.

Suppose however that some controls are "data-polymorphic" in a sense that they store completely different data in different contexts. Three examples:

  • trees usually keep different node types at different levels of their hierarchies
  • lists, listviews etc. often show different types of objects when initialized in different contexts
  • some generic forms are initialized with different types of data but behave in the same way each time

Every time such situation occurs there is a problem with binding a proper help topic to such control. Ideally, since the context is determined by the data, you would like to have something like that in the application code:

   1: string ProvideHelpTopicForControl( Control TheControl )
   2: {
   3:   if ( TheControl == treeViewStructure )
   4:      return ProvideHelpTopicForTreeView( treeViewStructure );
   5:   if ( TheControl == lstListViewWithItems )
   6:      return ProvideHelpTopicForListView( lstListViewWithItems );
   7: }
   8:  
   9: ...
  10:  
  11: string ProvideHelpTopicForTreeView( TreeView Tree )
  12: {
  13:   if ( Tree.SelectedNode != null )
  14:   {
  15:     switch ( Tree.SelectedNode.Text )
  16:     ...
  17:  
  18:     or
  19:  
  20:     switch ( Tree.SelectedNode.GetType().Name )
  21:     ...
  22:   }
  23:   else
  24:     return someDefaultMapping;
  25: }

(the code is not complete but I hope you get the idea)


While such solution could be perfect for a small application with just few different forms, it could be extremely tedious to implement and maintain it in a complex code.


And of cource, such hardcoded mapping would completely ruin the whole idea of moving the topic mapping out of the application!


Solution - additional context binding


I've spent few hours on thinking of a solution which would just extend the existing idea with features powerful enough to handle such "data-polymorphic" controls.


What I finally came up with can be summarized as follows:



  • each topic mapping gets another set of so-called binding contexts (no relation to data-binding!)
  • each binding context is identified by its name and holds complete description of help topic (including HelpKeyword and HelpNavigator)
  • when a topic mapping is found for a control (but before the topic is shown to the user), the engine tries to locate a binding context which could be more precise for selected control than the parent topic. If such binding context is found - it is used to show the help topic instead of the parent mapping.

Binding contexts are build in a different way for different types of controls but it is still done outside of the application code. In my implementation I handle just few different types of controls and leave the default implementation for other controls (the context is retrieved from the control's text).



   1: public static string[] GetBindingContext( Control Control )
   2: {
   3:     /* obsługa szczególnych typów formantów */
   4:  
   5:     #region TreeView
   6:     if ( Control is ListView )
   7:     {
   8:         ListView l = Control as ListView;
   9:         if ( l.SelectedItems.Count > 0 &&
  10:              l.SelectedItems[0].Tag != null
  11:             )
  12:             return new string[] 
  13:             { string.Format( 
  14:                 "element {0}", 
  15:                 l.SelectedItems[0].Tag.GetType().Name ) };
  16:         else
  17:             return new string[] { string.Empty };
  18:     }
  19:  
  20:     if ( Control is TreeView )
  21:     {
  22:         TreeView t = Control as TreeView;
  23:         if ( t.SelectedNode != null )
  24:             return BuildBindingContext( t.SelectedNode );
  25:         else
  26:             return new string[] { string.Empty };
  27:     }
  28:     #endregion
  29:  
  30:     #region TabControl
  31:     if ( Control is TabControl )
  32:     {
  33:         TabControl tb = Control as TabControl;
  34:         if ( tb.SelectedTab != null )
  35:             return new string[] { tb.SelectedTab.Text };
  36:         else
  37:             return new string[] { string.Empty };
  38:     }
  39:     #endregion
  40:  
  41:     return new string[] { Control.Text };
  42: }
  43:  

So how it works?


Well, suppose the Topic is mapped for the TreeView and additional binding-contexts are defined. One named Context1 and mapped to Topic1 and Context2 mapped to Topic2.


Then, when F1 is pressed for any node except for Conext1 and Context2, the Topic is shown. However, when the node's text is Context1, Topic1 is shown and when the node's text is Context2, Topic2 is shown.


What's more interesting, the case of TreeView imposes that binding-contexts for controls are matched not only by the context name but, when there's no binding-context defined for selected node's text, the engine tries to find a context for selected node's parent, then for it's parent and so on.


This correctly handles a common scenario where a node name is fixed and set from within the code (e.g. Clients) but it's subnodes come from the database and thus their names are dynamic while the mapping expects a static, fixed set of names provided in a mapping file. In this scenario the same context will be assigned to Clients and all its subnodes (assuming that no specific binding context is assigned to subnodes).


There can be arbitrarily many of these additional binding-contexts for a control. 


As you can see in above code excerpt, the binding context for a ListView is determined by the item's Tag type but note also that this can be easily adopted to specific needs.


And how is the additional binding-context provided within the instrumentation mechanism?


Well, you just provide a topic mapping for the control as usual and then click "+" next to "Context" listbox.



The context's name is retrieved automatically from the application automatically and you just provide a topic mapping for it. You can also remove a context when it's not required anymore.


Conclusion


That's it. The new implementation just extends the former one which means that it should work correctly with help.mapping files generated with previous version of the library. When you compile the new source code, you would be able to extend the topic mapping with additional binding-contexts which will further refine the assignment of help topics to application's controls.


Feel free to use the code without any explicit permission. Leave a comment if you like this approach.

Monday, July 16, 2007

Context Help Made Easy - Revisited

Some time ago I've read a great article Context Help Made Easy by Tom Clement. The article can be found here.

Tom's idea is great - rather than putting the HelpProvider on each of your application's forms, you keep the information about the topic mapping in a single XML file. There is also an additional message filter executing in the application context which traps F1 presses, reads the provided topic mapping and redirects the help provider to the specific topic.

The most interesing part of Tom's article is the idea of instrumenting the execution and providing a dialog form which is used to provide topic mapping for current context. It is then possible to train someone to use this feature to map user interface contexts to help topics - the mapping is provided while the application is actually running and not in the design time.

Below is the brief description of differences between Tom's and my implementation.

  • in Tom's implementation the Main routine of the application has to be augmented with the message filter injection. In my implementation - a custom component is used so that the only thing you have to do is to put the component on the main form of the appliaction
  • in Tom's implementation all user interface controls have to implement the mapping interface. I belive that this is not necessary - help topics can be mapped to a context defined by the FormClassName/Control1Name/Control2Name/Control3Name/..../ControlNName built directly from the runtime information (parent-child relation of controls and controls' names)
  • From the point of view of someone who actually maps controls to help topics the hierarchical view is much more natural than the flat ListView. In my implementation, topics which are not mapped are shown as red and topics already mapped - as black (please forgive localized labels, feel free to customize it)

  • in my implementation the XML file used to store the topic mapping, help.mapping, can be stored as a resource in any application module. If the file is found in the same directory as the main executable file of the application - it is used as a primary source of the topic mapping.
  • in Tom's implementation the ability to provide topic mapping is available always by pressing Ctrl+F1 instead of F1 which just shows the help topic. In my implementation you have to provide a special runtime commandline parameter, helpbuilder, to be able to use the map builder
  • the Control.FromHandle method used by Tom to retrieve the control instance from the window handle does not always work as expected. For example - when a ComboBox in DropDown mode is active then the message filter gets not the handle of the combo but rather the handle of the combo's internal textbox. The Control.FromHandle does not work then because the combo's internal textbox is not a control instantiated by the .NET. In my implementation when Control.FromHandle fails, the application tries to retrieve the control's parent control and repeats the Control.FromHandle. This correctly resolves the ComboBox (and other composite controls) issue.

In order to make your application compatible with my library you have to:

  • recompile the library from the provided source
  • put the HelpComponent on the main form of your application
  • run the application with helpprovider commandline parameter
  • use Ctrl+F1 to map help topics to controls. Remember about the hierarchy - you do not have to provide topics for all controls, it is sufficient to provide the topic for a container control and all its child controls inherit the mapping.
  • remember to provide a help file name (must be a *.chm file located beside the application main executable)
  • when the mapping is complete, include the help.mapping file stored beside the application main executable as an embedded resource in one of assemblies
  • remove the help.mapping file located beside the application main executable
  • rebuild the application

From now, the included resource is used as a primary help mapping provider. If you need to customize it further, repeat above process starting with the 3rd step (the included resource will be copied as a file beside the application main executable and used as a primary help mapping provider.

Download the source code here. You are free to use and modify it without any explicit permission.

Please leave a comment if you find this small library usable.

Edit: Please also take a look at the second part of this article: http://netpl.blogspot.com/2007/08/context-help-made-easy-reloaded.html

Tuesday, July 3, 2007

Tiny ASP.NET Ajax Framework Contest Continued...

Few days ago we've held a tiny contest of three ASP.NET Ajax frameworks. This time, three more contestants await to be compared:

These three above were chosen because the integration of an existing application is as simple as placing a single Ajax Manager on a web form - few other frameworks (and here) were rejected because they are either dead now, force you to use specific set of components or rely on writing client-side javascript code.

The result chart is available here:

  • ComfortASP.NET has a terrible requests-per-second ratio. It served only 35 (!) requests per second, while sending 6 and receiving 83 MB of data
  • Anthem.NET was able to process 133 request per second, sending 6 and receving 121 MB of data
  • Telerik's RAD Ajax did 292 requests per second, sending 7 and receiving 172 MB (!) of data

Gathering all results together, we had 2 non-Ajax contestants: pure Web.Forms application, Javascript version of it and 6 Ajax frameworks: Microsoft's ASP.NET AJAX, Ajaxium, VisualWebGUI, ComfortASP.NET, Anthem.NET and Telerik's RAD Ajax.

Regarding requests-per-second we have following results:

 

 

requests-per-second

reference

VWG

422

198,1220657

Javascript

400

187,7934272

Telerik

292

137,0892019

ASP.NET AJAX

222

104,2253521

Web.Forms

213

100

Anthem

133

62,44131455

Ajaxium

111

52,11267606

Comfort

35

16,43192488

 

Regarding number-of-bytes-sent we have following results:

 

 

sent data

reference

Javascript

1938800

24,23560589

VWG

2066800

25,83564589

Ajaxium

5029800

62,87407185

ASP.NET AJAX

5600600

70,00925023

Comfort

5880400

73,50683767

Anthem

6161400

77,01942549

Telerik

7293200

91,16727918

Web.Forms

7999800

100

 

Regarding number-of-bytes-received we have following results:

 

 

received data

reference

VWG

23348807

29,11700146

Javascript

37121000

46,29153905

Ajaxium

47756301

59,55423272

Web.Forms

80189600

100

Comfort

83742600

104,4307491

Anthem

121547800

151,575516

ASP.NET AJAX

129531800

161,5319194

Telerik

172294800

214,8592835

 

Few comments:

  • the overall winner is VWG, however the application had to be completely rewritten in order to run under this framework
  • the javascript version did not win (even in the bytes-received test!) and though this may seem surprising, it is caused by the script size
  • the Ajaxium, Anthem and Comfort reduce the requests-per-second ratio singificantly. The latter two are able to correctly handle dynamic, non-ajax content sent to the browser
  • regarding the requests-per-second, Telerik is significantly better than ASP.NET AJAX, however it downloads huge amout of data comparing to all other frameworks

Please feel free to study these resulsts and pick up your own winner. Thanks again to Krzysiek Owczarek for contributing to this contest.

Thursday, June 28, 2007

Tiny ASP.NET Ajax Framework Contest

 

Contest context

More than two years ago we've built a small web application used to collect and print documents required to participate in secondary school final examinations in Poland. The application is used quite extensively by people from all over the country.

The application, maturzysta.vulcan.pl, is located here.

From the web developer's point of view there is one interesting aspect of the main form of the application: there are about 30 dropdown lists with complicated logic between their selections: when you change the selection in one list, few others should be cleared or repopulated with context-bound data. There is also quite a lot of validation of various dependencies between user's selections.

The first version of the application was built as a server-side application: all SelectedIndexChanged events were auto-postbacked and processed on the application server. This caused a lot of trouble since the heavy form had to be sent to and from the server.

The year later we've completely redesigned the application, moving the interface logic to the javascript. The web traffic problem has been solved but two other appear. First of all - no one likes javascript, it's clumsy and error-prone. Then - we had to disable ASP.NET page validation mechanism because otherwise the page content modified on the client-side would be rejected on the server-side as potentially dangerous.

In the meantime, few promising AJAX frameworks appeared and we thought that it would be possible to go back to the initial version of the application but instead of expensive postbacks we could take the AJAX approach and replace postbacks with callbacks. This could solve all problems: reduce the traffic and still keep the application logic on the server-side.

Contest participants

Three AJAX frameworks were chosen for the contest:

The first and the third one were chosen because it's fairly straightforward to extend existing application with AJAX capabilities. The second one is perhaps one of the most promising AJAX frameworks out there, designed to fill the gap between Windows.Forms and Web.Forms programming.

In order to make the application compatible with participants, following steps have been taken:

  • in case of ASP.NET AJAX, the ScriptManager has been put on the form page and the whole page content has been put into the UpdatePanel. The concept of several small UpdatePanels with triggers between them has been rejected because of the complexity of the application logic and dependencies between controls.
  • in case of the Ajaxium framework, the Ajaxium initialization routine has been put into the OnBeginRequest event of the global application class as well as into the Page_Load method of the form.
  • in case of the VisualWebGUI framework, the application has been completely rewritten using the VWG library and components.

Compare the layout of the WebForms/AJAX application (save images to see them full size)

with the layout of the rewritten VWG version

 The judge

Microsoft Application Center Test has been choosen as the contest judgement tool. Tests were performed on the Windows XP machine serving as the application server. Exactly 200 sessions have been simulated for 5 concurrent users. Each session has been recorded as the complete user session - forms have been filled with data and dropdown selections have been made.

Each session ended with the application showing the print-ready document.

The results

The results are available here.

Observations:

  • both the Microsoft AJAX.NET and the Ajaxium frameworks are not capable of delivering dynamically created content to the web browser from within the AJAX callback [or at least we do not know how to do that]. This means that when the application produces, let say, XML data file and sends it to the application, instead of a typical "Save - Open - Cancel" prompt, you get ... nothing. Fortunately, this was not considered as a test case.
  • the original Web.Forms server-side application, numer (1) in the result chart, has been able to process 213 requests per second and ended with almost 8 MB of data sent to the server and 80 MB of data received from the server. These results has been considered as reference results.
  • Microsoft ASP.NET AJAX application, numer (2) in the result chart, has been able to process 222 requests per second with 5 MB of data sent to the server and 129 MB (!) of data received from the server.
  • The Ajaxium version, number (3) in the result chart, has been able to process only 111 request per second. It sent 5 MB and received 47 MB of data.
  • The VWG version, numer (4), did 422 requests per second with 2 MB sent and 23 MB received (!)
  • The javascript version, numer (5), did 400 requests per second, with 2 MB sent to the server and 37 MB received.

Results:

  • despite its simplicity, Ajaxium did unexpectably bad. We belive that the framework initialization takes just too much time (look at the "time to first byte") and this downgrades the performance.
  • Microsoft ASP.NET AJAX didn't really help regarding server-side resource consumption. It also sends large amount of data to the client - in our case this could be because large form has been put into single UpdatePanel. In overall it did a nice job of ajaxing the application but nothing else.
  • The javascript version was considered as an easy winner and, to our suprise, it sends quite a lot of data to the client. Well then, scripts are over 90 kB. Multiply it by 200 clients and you have the bandwitch consumption.
  • The VWG is an undoubt winner. It did a great job of serving the highest number of requests per second, 422, and is also a clear winner regarding the amount of data sent to and from the server. The only problem with VWG is that it is still in beta version and is not reliable for the time (it still has stability and compatibility problems, let's hope it will get better every other release).

What could be interesting is that the VWG version seems to be the slowest one from the end-user's point of view. It just feels sluggish inside the web browser. We belive that this is caused by the constant callbacks to the server and as you can see it does not slow down the processing pipeline at all - it is just slow inside your web browser.

I would like to thank my friend Krzysiek Owczarek for helping me to perform the tests.

 

Update 2007-07-15

Mark Krapovek, Ajaxium Support Manager, made a comment on the testing environment:

"The evaluation edition available freely on vendor's website DOES NOT SUPPORT CONCURRENT CONNECTIONS. That's just a limitation of the evaluation edition. Before testing the component for "200 sessions simulated for 5 concurrent users" you had to ask vendor for a version with all limitations removed.
So: if Ajaxium Evaluation Edition was able to serve more than 100 requests per second, being limited for only 1 connection at a time, it is the fastest component among all compared."

Mark, thank you for the information.