April 2007 - Posts

Serve extensionless URL from ASP.NET without using ISAPI module or IIS 6 Wildcard mapping

If you want to serve extensionless URL from ASP.NET 2.0 like the following:

  • www.store.com/books
  • www.store.com/books/asp.net2.0
  • www.forum.com/post/how-to-serve-extensionless-url

You cannot, unless you use some third party ISAPI module or use IIS 6.0 Wildcard mapping feature. Third party ISAPI module needs to be installed on the server directly. If you don't have a dedicated server or a VPS, you cannot do this. IIS 6.0 Wildcard mapping makes each and every request go through ASP.NET 2.0 ISAPI handler including Urls with .gif, .css, .js, .html etc. So, it suffers from scalability problem. Some independent research shows there's 30% drop in performance in IIS 6.0 when you use wildcard mapping. So, this is not a good solution either.

Here's an approach which works without using ISAPI module or wildcard mapping in IIS 6.0. When you request extensionless URL, you get HTTP 404. This means IIS receives the request but it serves the page configured for HTTP 404. It does not send the request to ASP.NET ISAPI. So, if you can forward all HTTP 404 to ASP.NET, you can serve such extensionless URL. In order to forward HTTP 404 to ASP.NET ISAPI, all you need to do is configure IIS to redirect to some .aspx extension on HTTP 404.

Benefits of this approach:

  • No thirdparty ISAPI module required
  • No Wildcard mapping thus no performance sacrifice

Here's how to configure 404 redirection in IIS 6.0:

On IIS 6.0 change 404 default page to /404.aspx and the type to "URL".  On IIS 7.0, change 404 default page to /404.aspx and the type to "ExecuteURL". Also, change the default error response to "Custom error pages".

When you will request an URL like "www.shop.com/products/books" it will redirect to "www.shop.com/404.aspx?404;http://www.shop.com/products/books". From Global.asax BeginRequest event, capture this URL and see whether it's an extensionless URL request. If it is, do your URL rewriting stuff for such extensionless URL.

 

   1: protected void Application_BeginRequest(object sender, EventArgs e)
   2: {
   3:     string url = HttpContext.Current.Request.Url.AbsolutePath;
   4:  
   5:     // HTTP 404 redirection for extensionless URL or some missing file
   6:     if (url.Contains("404.aspx"))
   7:     {
   8:         // On 404 redirection, query string contains the original URL in this format:
   9:         // 404;http://localhost:80/Http404Test/OmarALZabir
  10:         
  11:         string[] urlInfo404 = Request.Url.Query.ToString().Split(';');
  12:         if (urlInfo404.Length > 1)
  13:         {
  14:             string originalUrl = urlInfo404[1]; 
  15:             
  16:             string[] urlParts = originalUrl.Split('?');
  17:             
  18:             string queryString = string.Empty;
  19:             string requestedFile = string.Empty;
  20:  
  21:             if (urlParts.Length > 1)
  22:             {
  23:                 requestedFile = urlParts[0];
  24:                 queryString = urlParts[1];
  25:             }
  26:             else
  27:             {
  28:                 requestedFile = urlParts[0];
  29:             }
  30:                 
  31:             if( requestedFile.IndexOf('.') > 0 )
  32:             {
  33:                 // There's some extension, so this is not an extensionless URL.
  34:                 // Don't handle such URL because these are really missing files
  35:             }
  36:             else
  37:             {
  38:                 // Extensionless URL. Use your URL rewriting logic to handle such URL
  39:                 // I will just add .aspx extension to the extension less URL.
  40:                 HttpContext.Current.RewritePath(requestedFile + ".aspx?" + queryString);
  41:             }
  42:         }
  43:     }
  44: }
Posted by omar with 100 comment(s)
Filed under:

Synchronously execute and get return parameters from Workflow

In my DropThings project, I have used Workflows to develop the business layer that run synchronously and do most of the work in the middle-tier. The business layer facade named DashboardFacade has no code but to call different workflows. Each of the workflow serve a particular operation like new user visit, existing user visit, adding a tab, moving a widget from one column to another etc. ASP.NET page calls DashboardFacade for each user action and DashboardFacade inturn calls a workflow to respond to that user action.

“This is insane!” you are thinking. I know. Please hear me out why I went for this approach:

Architects can “Design” business facade functions in terms of Activities and developers can just fill in small amount of unit code in each activity.

This is a really good reason because Architects can save time in writing Word Documents explaining how things should work. They can directly go into Workflow Designer, design the activities, connect them, design the flow, and verify whether all input and output are properly mapped or not. This is lot better than drawing flow-charts, writing pseudocode, explaining in stylish text how an operation should work. It is also helpful for developers because they can see the workflow and easily understand how to craft the whole operation. They just open up each activity and write small amount of very specific reusable code. They know what will be the input to the Activity (like function parameters) and they know what to produce (return value of function). This makes the activities reusable and architects can reuse one activity in many workflows. Workflows can be debugged right on Visual Studio Workflow Designer. So, developers can easily find out defects in their implementation by debugging the workflow. Architects can enforce many standards like validations, input output check, Fault handling on the workflow. Developers cannot but comply with those and thus produce really good code. Another great benefit for both architect and developer is that there’s no need to keep a separate technical specification document up-to-date because the workflow is always up-to-date and it speaks for itself. If someone wants to study how a particular operation works, one can just printout the workflow and read it through.

“But what about performance”, you say? Both you and I have heard workflow foundation is a pretty big library and can be memory hog. Also the workflow runtime is quite big and takes time to start up. I did some profiling on the overhead of workflow execution and it is very fast for synchronous execution. Here's proof from the log you get in Visual Studio output window:

   1:
b030692b-5181-41f9-a0c3-69ce309d9806 Activity: Get User Guid       
0.078125
   2:
b030692b-5181-41f9-a0c3-69ce309d9806 Activity: Get User Pages      
0.0625
   3:
b030692b-5181-41f9-a0c3-69ce309d9806 Activity: Get User Setting    
0.046875
   4:
b030692b-5181-41f9-a0c3-69ce309d9806 Activity: Get Widgets in page:
189 0.0625
   5:
b030692b-5181-41f9-a0c3-69ce309d9806 Total: Existing user visit    
0.265625

First four entries are the time taken by individual activities during data access only, not the total time taken to execute the whole activity. The time entries here are in seconds and the first four entries represent duration of database operations inside activities. The last one is the total time for running a workflow with the four activities shown above and some extra code. If you sum up all the individual activity execution time for database operations only, it is 0.2500 which is just 0.015625 sec less than the total execution time. This means, executing the workflow itself along with overhead of running activities takes around 0.015 sec which is almost nothing compared to the total effort of doing the complete operation.

An example of such workflow is as following:

   1: 
public 
void MoveWidgetInstance( 
int widgetInstanceId, 
int toColumn, 
int toRow )
   2: {
   3:   
using( 
new TimedLog(
this._UserName, 
"Move Widget:" + widgetInstanceId) )
   4:   {
   5:     var properties = 
new Dictionary<
string,
object>();
   6:     properties.Add(
"UserName", 
this._UserName);
   7:     properties.Add(
"WidgetInstanceId", widgetInstanceId);
   8:     properties.Add(
"ColumnNo", toColumn);
   9:     properties.Add(
"RowNo", toRow);
  10:     
  11:     WorkflowHelper.ExecuteWorkflow(

typeof( MoveWidgetInstanceWorkflow ),
properties );
  12:   }
  13: }

MoveWidgetInstance is a method in DashboardFacade. It just calls a workflow named MoveWidgetInstanceWorkflow which does the job for moving a widget from one column to another, rearrange the widgets on the new column, pull up widgets on the old column to remove the empty space, and update all widgets positions. If I wrote all these code in one function, it becomes quite a big function and requires documentation. But having a workflow means I have a flowchart of what's going on and I have some handy reusable Activities which I can reuse in other operations.

Implementing business layer as workflows has three important requirements:

  1. Execute workflow synchronoulsy within the ASP.NET request thread
  2. Get output parameters from workflow which is returned as return value from Business Facade methods
  3. Get exceptions raised from Activities in a synchronous manner inside the same request thread

WorkflowHelper is a handy class I made, which makes use of Workflow a breeze, especially from ASP.NET. In the business layer, I need synchronous execution of workflow where the default implementation of Workflow Foundation is to work asynchronously. Moreover, I need return values from Workflows after their execution complete, which is not so easily supported due to asynchronous nature of workflow. Both of these require some tweaking with workflow runtime in order to successfully run in ASP.NET environment. You will find the source code of WorkflowHelper in the code zip file from www.dropthings.com.

WorkflowHelper.Init function initializes Workflow Runtime for ASP.NET environment. It makes sure there’s only one workflow runtime per Application Domain. Workflow Runtime cannot be created twice in the same application domain. So, it stores the reference of the Workflow Runtime in Application Context.

   1: 
public 
static WorkflowRuntime Init()
   2: {
   3:   WorkflowRuntime workflowRuntime;
   4:  
   5:   
// Running in local mode, create an return new
runtime
   6:   
if( HttpContext.Current == 
null )
   7:     workflowRuntime = 
new WorkflowRuntime();
   8:   
else
   9:   {
  10:     
// running in web mode, runtime is initialized
only once per 
  11:     
// application
  12:     
if( HttpContext.Current.Application[
"WorkflowRuntime"] == 
null )
  13:       workflowRuntime = 
new WorkflowRuntime();
  14:     
else
  15:       
return HttpContext.Current.Application[
"WorkflowRuntime"] 
as WorkflowRuntime;
  16:   }   

The initialization takes care of both ASP.NET and Console/Winforms mode. After the initialization, it registers ManualWorkflowSchedulerService, which take care of synchronous execution of Workflow. Activities.CallWorkflowService is a handy service that executes workflow synchrnously from another workflow. Read this post for details. These two services make Workflow Foundation usable from ASP.NET environment for practical scenarios.

   1: var manualService = 
new ManualWorkflowSchedulerService();
   2:
workflowRuntime.AddService(manualService);
   3:  
   4: var syncCallService = 
new Activities.CallWorkflowService();
   5:
workflowRuntime.AddService(syncCallService);
   6:  
   7: workflowRuntime.StartRuntime();
   8:  
   9: 
// on web mode, store the runtime in application
context so that
  10: 
// it is initialized only once. On dekstop mode,
ignore
  11: 
if( 
null != HttpContext.Current )
  12:   HttpContext.Current.Application[
"WorkflowRuntime"] = workflowRuntime;
  13:  
  14: 
return workflowRuntime;
  15:  

Workflow Runtime is initialized from Application_Start event in Global.asax. This ensures the initialization happens only once per App Domain.

   1: 
void Application_Start(
object sender, EventArgs e) 
   2: {
   3:   
// Code that runs on application startup
   4:  
   5:  
DashboardBusiness.WorkflowHelper.Init();
   6: }

The runtime is disposed from Application_End event in Gloabal.asax:

   1: 
void Application_End(
object sender, EventArgs e) 
   2: {
   3:     
//  Code that runs on application shutdown
   4:    
DashboardBusiness.WorkflowHelper.Terminate();
   5: }

The most interesting function is the ExecuteWorkflow function which does the following:

  • Execute workflow synchronously
  • Pass parameters to workflow
  • Upon completion, get output parameters from workflow and return them
    Handle exceptions raised in Workflow and raise to ASP.NET exception handler

First ExecuteWorkflow creates an instance of Workflow and passes input parameters to it:

   1: 
public 
static 
void ExecuteWorkflow( Type workflowType,
Dictionary<
string,
object> properties)
   2: {
   3:   WorkflowRuntime workflowRuntime =
Init();
   4:  
   5:   ManualWorkflowSchedulerService
manualScheduler =
workflowRuntime.GetService<ManualWorkflowSchedulerService>();
   6:  
   7:   WorkflowInstance instance =
workflowRuntime.CreateWorkflow(workflowType, properties);        
   8:   instance.Start();

ManualWorkflowSchedulerService service executes the workflow synchronously. Next step is to hook WorkflowCompleted and WorkflowTerminated events of Workflow Runtime so that I can capture output parameters and exceptions and handle them properly.

   1:
EventHandler<WorkflowCompletedEventArgs> completedHandler = 
null;
   2: completedHandler = 
delegate(
object o, WorkflowCompletedEventArgs e)
   3: {
   4:   
if (e.WorkflowInstance.InstanceId
==instance.InstanceId)
   5:   {
   6:    
workflowRuntime.WorkflowCompleted -= completedHandler;
   7:     
   8:     
// copy the output parameters in the specified
properties dictionary
   9:     Dictionary<
string,
object>.Enumerator enumerator =
e.OutputParameters.GetEnumerator();
  10:     
while( enumerator.MoveNext() )
  11:     {
  12:       KeyValuePair<
string,
object> pair = enumerator.Current;
  13:       
if( properties.ContainsKey(pair.Key) )
  14:       {
  15:         properties[pair.Key] =
pair.Value;
  16:       }
  17:     }
  18:   }
  19: };

When workflow completes, WorkflowCompletedEventArgs gives me the OutputParameters dictionary. It contains all the public properties of Workflow. I read all the entries in the OutputParameters and update the input parameters Dictionary with the new values. This is required in the AddWidget function of DashboardFacade where I need to know the widget instance created by the workflow.

WorkflowTerminated fires when there’s an exception. When any activity inside the workflow raises exception, this event fires and workflow execution aborts. I capture this exception and throw it again so that ASP.NET can trap this exception using its default exception handler.

   1: Exception x  = 
null;
   2:
EventHandler<WorkflowTerminatedEventArgs> terminatedHandler =

null;            
   3: terminatedHandler = 
delegate(
object o, WorkflowTerminatedEventArgs e)
   4: {
   5:   
if (e.WorkflowInstance.InstanceId ==
instance.InstanceId)
   6:   {
   7:    
workflowRuntime.WorkflowTerminated -= terminatedHandler;           
        
   8:     Debug.WriteLine( e.Exception );
   9:  
  10:     x = e.Exception;
  11:   }
  12: };
  13: workflowRuntime.WorkflowCompleted
+= completedHandler;
  14: workflowRuntime.WorkflowTerminated
+= terminatedHandler;
  15:  
  16:
manualScheduler.RunWorkflow(instance.InstanceId);
  17:  
  18: 
if (
null != x)
  19:   
throw 
new WorkflowException(x);

This helps me get exceptions shown in ASP.NET Exception handler like this:

The WorkflowHelper is a reusable class that you can use in your work project. Just copy the class file out of my project and add in your own.

Posted by omar with 7 comment(s)
Filed under:

Gartner: Pageflakes is the "Cool Web 2.0" Personalized Homepage

It's been an award-winning spring season for Pageflakes!  This time Gartner, the highly respected technology industry analysis and research firm, has named Pageflakes the "Cool Web 2.0" personalized homepage for 2007.  Many Business and enterprise users have discovered our group collaboration and page publishing features, and are using Pageflakes for all kind of interesting applications at work such as internal company intranets and managing teams and projects.  Plus, they're doing this with no technical skills, no IT department approvals, no waiting - and no cost.

Gartner has taken notice, and in its "Cool Web 2.0 Vendors 2007" research report ($), analyst David Gootzit says "Pageflakes differentiates itself by adding some community building features" and "Pageflakes users can publish tabs from their own start pages to the general Pageflakes community or select groups of users. This feature enables Pageflakes users without programming know-how to design their own Web sites." The report goes on to say that, “Enterprise users should examine Pageflakes as a tactical collaboration tool” and that Pageflakes features ”are also easily leveraged by the average enterprise user and require no monetary investment.”

Thanks to Gartner and to all of our users working that are working hard at the office. We're glad to hear that Pageflakes is making life a little easier and more fun at work.

Posted by omar with 25 comment(s)
Filed under:

And the winner is ... Pageflakes. Duh!

Two weeks back, Read Write Web started a poll on who's the most popular Start Page in the world now. The result is astonishing:

Pageflakes has got 30% vote, ahead of Google's Personalized Homepage (26%) and way ahead of Netvibes (21%). Many doubted this poll and thought it must have been an arranged poll by Pageflakes guys. But Read Write Web has declared Google to be the winner and Pageflakes #2 for some reason. Well, no problem. The mighty PC World has declared Pageflakes to be the #1 Start Page!

Read the article here.

Let's see now, how many awards we got so far. First there was the Web 2.0 awards by SEOMoz where Pageflakes raked #1 Start Page ahead of Google IG and Microsoft Live.com. Then there was the "Ajax King" award by InformationWeek which said Google is no longer the Ajax King, Pageflakes is. Then we got highest vote on ReadWriteWeb poll. Recently we won #1 Start Page title in PC World. Soon we hope to win the Emmy Awards, the Oscar, Miss Universe ...

Posted by omar with 7 comment(s)
Filed under: