Monday, June 26, 2006

This post will introduce the first part of a starter kit for doing Page Flows in ASP.NET with Windows Workflow.  The first Release Candidate for Workflow is now out as part of the June .NET 3.0 CTP.  If you are already working with the Beta's you will need to update your SQL Persistence and Tracking databases with the new scripts.

I have put in a request to CodePlex to host the starter kit.  I will add an update to this blog once it is approved.

The first thing the starter kit contains is the necessary configuration to get a PageFlow project started.  As I started learning workflow I discovered that there are several things required for using it from an application.

Hosting the Runtime

Windows Workflow does not offer any out of the box servers or special applications to take advantage of it.  It is entirely up to developers to host the Workflow runtime inside of an application.  This could be a Windows service, a .NET web service, a Windows application or an ASP.NET application.  The only requirement is that host can work with the .NET 3.0 Workflow assemblies.  In our case we will focus on hosting inside of ASP.NET.

The runtime only needs to be created once per application in order to host multiple workflows.  The blogs and examples I have found so far recommend creating the runtime in the global.asax.cs file.  I have chosen in the starter kit to instead use an httpModule.  By using a module it is easier to add Workflows to existing applications without having to cut and paste code.

All that is needed is to create an instance of WorkflowRuntime and then shove it into an application variable that we can access later in our ASP.NET pages.  We will use this variable in a PageFlowManager control so that direct interaction with the runtime is not even necessary from our applications. 

               WorkflowRuntime workflowRuntime = new WorkflowRuntime("WorkflowRuntime");
               context.Application["WorkflowRuntime"] = workflowRuntime;
               workflowRuntime.StartRuntime();

There are 2 important additional items to note in the code above.  When creating the runtime the constructor takes a configSectionName as string.  That string matches the workflow configuration element name in the web.config.  I find that consistently naming it keeps it simple and easy to manage.  The code would have to be changed if you named it something different.  The second thing is that StartRuntime is called.  This allows the runtime to read the configuration and initialize all of the services that you have added via the configuration itself.

Configuration

Lets take a look as what is in the basic configuration.

First we define a config section for the WorkflowRuntime.  Note that the name matches the name passed to the constructor when creating the runtime.

<configuration>
   <configSections>
      <section name="WorkflowRuntime" type="System.Workflow.Runtime.Configuration.WorkflowRuntimeSection, System.Workflow.Runtime, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
   </configSections>

We add a reference to the httpModule that initializes the runtime for us.

   <system.web>
      <httpModules>
         <add name="AspNetPageFlow" type="AspNetPageFlow.WorkflowRuntimeModule, AspNetPageFlow"/>
      </httpModules>
  <system.web>

Finally we configure the runtime with the typical services we need for most workflows.  Note that the root element of the section matches the name given when creating the configuration section above.  The services added via configuration are automatically loaded by the runtime when it starts.  Services can also be added via code, but for the common ones it makes sense to me to just put them in config.

   <WorkflowRuntime Name="WorkflowServiceContainer">
      <CommonParameters>
         <add name="ConnectionString" value="Data Source=.\SQLEXPRESS;database=WFServices;Integrated Security=True;"/>
      </CommonParameters>
      <Services>
         <add type="System.Workflow.Runtime.Hosting.ManualWorkflowSchedulerService, System.Workflow.Runtime, Version=3.0.00000.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
         <add type="System.Workflow.Runtime.Hosting.SqlWorkflowPersistenceService, System.Workflow.Runtime, Version=3.0.00000.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" UnloadOnIdle="false"/>
         <add type="System.Workflow.Runtime.Tracking.SqlTrackingService, System.Workflow.Runtime, Version=3.0.00000.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" UseDefaultProfile="true" IsTransactional="false" ProfileChangeCheckInterval="60000" PartitionOnCompletion="false"/>
         <add type="System.Workflow.Activities.ExternalDataExchangeService, System.Workflow.Activities, Version=3.0.00000.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
      </Services>
   </WorkflowRuntime>
</configuration>

The CommonParameters section is nice because it allows us to share configuration for elements that have the same configuration.  In our case we are sharing a database for Workflow tracking and persistence.  You will find the SQL scripts at C:\windows\Microsoft.NET\Framework\v3.0\Windows Workflow Foundation\SQL\EN for the latest release anyhow.

The other services we have added here are the ManualWorkflowSchedulerService and the ExternalDataExchangeService.  The ManualWorkflowSchedulerService was created specifically for ASP.NET.  It requires us to do a little more management because we have to explicitly run the workflow when we need it.  This will be a non-issue once we add the PageFlowManager to our pages, but nevertheless it is required.  What this service does is it allows the runtime to borrow the current thread from ASP.NET itself.  That is important to understand as it will have affect on security and page postbacks.  A separate thread is automatically managed by the runtime for delay activities, but otherwise ASP.NET hosted workflows run in the same thread as the hosting application.

Just today I read that the ExternalDataExchangeService should be unique for each workflow instance.  I need to do a little more research on this one and I may amend this post if that is indeed true.  This service is used for communication between the host and the workflow.  We will use it with a simple data pipe service in order to simplify sending objects back and forth between the host and the workflow.  Of course the complexties involded will be wrapped up into the PageFlowManager and some workflow activities to make our jobs much easier.

In a future post I will examine the PageFlowManager and how it keeps the pages and the workflow in sync driving navigation and abstracting the management of the runtime from the application developer.

6/26/2006 11:17:13 PM (Mountain Daylight Time, UTC-06:00)  #    Disclaimer  |  Comments [1]  |  Trackback
 Sunday, June 18, 2006

Having just returned from TechEd 2006 I am excited to begin sharing my passion for Windows Workflow Foundation (WF).  It is my opinion that WF is the tool that will enable model driven development that will become the de facto standard for application and system development.  If you are not familiar with WF yet, then its time to start reading up on it over at www.netfx3.com.  Currently Microsoft is shipping WF beta 2.2 with both Sequential and State Machine workflow activities.  Later this year they are planning to release WF and a CTP of a new WF activity know as PageFlow.  While you wait you might check out the Webcast from Israel Hilerio to see some of the ideas going into the page flow activities.  I really enjoyed meeting Israel and the other members of the WF team as well as Kashif Alam.  Kashif is the point of contact for the ASP.NET team when it comes to WF integration and the coming Page Flow activities.

I have spent the last few weeks trying to build a page flow model on top of a State Machine since I was not aware of the model Microsoft is working on.  Now that I have a better understanding of where Microsoft is heading as well as a working prototype of my own I would like to begin sharing my code and ideas.  I look forward to other feedback and ideas for improvement.  I am ready to use WF now!  My hope is that, together with anyone who reads this, we can create and maintain a set of tools that will enable powerful WF solutions underneath very thin UI layers. 

I will be focusing on starting a WF powered web application from scratch.  What will make it different from a traditional application is that all logic and flow will be driven by the WF engine, activities, and abstraction layers.  In the end I will have a web UI that is completely brainless.  All short and long term persistence will be managed by WF activities and WF code.  All navigation and page flow will be driven by WF models, yet equally supportive of non-linear processes and browser back buttons.  Running a Windows UI on top of the workflow will be fully supported, though at this time I do not plan to explore that scenario, except to ensure that system.web is not referenced anywhere except the UI layer.  I will be demonstrating an ASP.NET WorkflowManager (WFM), but it should be trivial to create a similar Smart Client WorkflowManager as well.  The WFM will by the abstraction layer between the UI and the WF making it simple for the developer to pass objects between the 2 and manage the runtime and WF instances.

In addition I will share my implementation of the Simple Read and Write activities that were introduced in the MS Help Desk sample application.  The Help Desk sample application was also written by Israel Hilerio to demonstrate an option for simplifying the sharing for data between a workflow and the hosting application.  I have found it to be very effective for many scenarios and I look forward to presenting some enhancements to it for enabling additional scenarios.  These will be incorporated into the WFM and added to the WF Runtime as DataExchange services.

I look forward to interacting with the WF community so fell free to introduce yourself and comment with questions as I post articles and code.

 

6/18/2006 9:19:04 PM (Mountain Daylight Time, UTC-06:00)  #    Disclaimer  |  Comments [2]  |  Trackback
 Monday, May 15, 2006

I just discovered the latest in open Source Control options know as CodePlex.  It is built on top of Team Foundation Server (TFS) and makes use of all of the TFS goodness such as work items and full integration with Visual Studio 2005 through Team Explorer.  This one looks like a great way for people to get a taste of working with Team Foundation Server.  If it doesn't have the problems that GotDotNet had when it launched then it should be a real winner.  I have to say that if I were to get involved in another open source project I would definitely give this one a try.  I have tasted of the goodness of TFS and I love it!

Now, I wonder how I can get this nice web UI of CodePlex for my in house TFS projects at Pets Best.

 

5/15/2006 8:52:52 AM (Mountain Daylight Time, UTC-06:00)  #    Disclaimer  |  Comments [0]  |  Trackback
 Friday, March 31, 2006

If you have not heard the Waterfall method of software development is coming back strong.  Check out the Waterfall 2006 conference taking place tomorrow.  (For those who are stiull using BlondeStar, this conferce is on April first.  The folks at BlondeStar can help you get there.)

If you still feel that Agile methodologies are better then perhaps you would prefer the upcoming Test Driven Development seminar in Boise, ID.

3/31/2006 10:19:27 AM (Mountain Daylight Time, UTC-06:00)  #    Disclaimer  |  Comments [0]  |  Trackback
 Thursday, March 23, 2006

Today I found out that I will be attending TechEd 2006 in Boston.  Thanks of course to my new employer Pets Best Pet Insurance.  I am looking forward to seeing many friends again and checking out the latest and greatest technologies.  Look for me at the Windows Workflow Foundation and other WinFX sessions.  I will probably hit an IIS 7 session or 2 as well.  Of course I will probably spend a fair amount of time in the cabana area also.

If you are going to be there and would like to say hello in person then lets meet up.

3/23/2006 10:57:03 PM (Mountain Daylight Time, UTC-06:00)  #    Disclaimer  |  Comments [2]  |  Trackback
 Friday, March 03, 2006

Time is nearing for the first Boise Code Camp.  I will be doing 2 presentations on DotNetNuke and several others will be presenting a lot of other great material.  I put a lot of effort in getting door prizes for the event.  We are close to having enough prizes that most attendees should go home with something.  Of course the session material is the most valuable aspect, but winning a copy of Visual Studio Team Suite with MSDN wouldn't hurt either.

3/3/2006 10:19:09 AM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [0]  |  Trackback

This week I started reading Professional ASP.NET 2.0 Security, Membership, and Role Management by Stefan Schackow.  I can't recommend this book enough.  Stefan is a great guy as well.  I had opportunity to meet him on a couple different visits to Redmond.  I wish I had this book 2 years ago when I started looking at the Membership and Role API's in .NET 2.0.  (Yes, I was looking these things 2 years ago.  They were announced at the PDC in Oct. 2003).

Today I found a bonus nugget of goodness I had to share that is indirectly related to ASP.NET 2.0 security.  On Page 429 Stefan explains why all the SQL objects in the Microsoft SQL providers for .NET 2.0 explicitly reference DBO as the object owner.  (ex. dbo.aspnet_Memberhsip)  It turns out that SQL 2000 takes a performance hit if the object owners are not explicitly declared in stored procedures, views, etc.  The performance hit is significant enough that Microsoft did a QFE on ASP.NET 1.1 SQL Session state.  I had no idea and I figured many others also were clueless about the patch for Session State as well as the performance issue.

Check out Todd Carter's blog for some more details.  It also includes a link to the .NET 1.1 Session State fix.

3/3/2006 10:11:53 AM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [0]  |  Trackback
 Friday, January 20, 2006

This week has been absolutely amazing.  Sr. level .NET developer jobs are showing up everywhere.  Here is a list off the top of my head.

Amphire
Healthwise
Micron
Comsys
TEK systems
Idaho Commerce and Labor
Innova Systems International
WillowTree Software
neoreef

1/20/2006 11:55:42 AM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [0]  |  Trackback
 Thursday, January 12, 2006

.NET guru Scott Hanselman has started a new podcast.  No excuses, just get over there and subscribe to it.

www.hanselminutes.com

1/12/2006 10:20:34 AM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [0]  |  Trackback

I checked today and the script I wrote yesterday is working!  I was feeding the entire URI to the tracker which makes the document path show up with the protocol as the folder name in the tool.  I decided to modify the TrackIt function slightly.  I am peeling off the protocol from the link and prepending it with my own folder name to make it easy to distinguish document downloads for other content.

Enjoy!

function TrackIt(link)
{
// Remove the conversion to Lowercase if you are on a Case sensitive web server
var slashes = link.href.indexOf("//") + 2;
var docPath = link.href.toLowerCase().substring(slashes, link.href.length);
urchinTracker(
"/#docs/" + docPath);
}

1/12/2006 9:42:59 AM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [0]  |  Trackback
 Wednesday, January 11, 2006

Previously I blogged about possible solutions for tracking downloads with Google Analytics.  The solution I am going to experiment with first is to dynamically add the tracking code to my download links via JavaScript.  I spent quite a bit of time on my script to make it cross-browser compatible and so that it would handle proper timing of execution based on the loading of the links.  I also used a regular expression to limit the tracking to specific link types.  Hopefully those match most cases.  If all goes well I will probably wrap all of this into a simple ASP.NET control that can be added to an page you want tracked. 

First off you need the standard Google tracking scripts:

<script src="http://www.google-analytics.com/urchin.js" type="text/javascript"></script>
<script type="text/javascript">
_uacct = "UA-XXXXXX-X";
urchinTracker();
</script>

Second is my new download tracking script.  I have placed it in a separate file for browser caching and easy updating.  Also it has the defer="defer" attribute so that IE will not load the script until the content has finished loading.  XHTML compliance required me to include a value for the attribute.  Please let me know if you come up with any improvements to the script or if you find any errors.  Thanks to quirksmode for info on the dynamic event models and dean edwards for info on deferred script execution.

<script type="text/javascript" src="downloadtracker.js" defer="defer"></script>

Here is the actual downloadtracker.js code

// We dont want the try adding the tracking code until the page links are loaded
if (document.addEventListener) {
document.addEventListener(
"DOMContentLoaded", addEvents, null); // Firefox
} else {
addEvents();
// IE : Call the function immediately because the script is referenced with the defer attribute supported by IE
}

function addEvents()
{
// quit if this function has already been called
if (arguments.callee.done) return;
// flag this function so we don't do the same thing twice
arguments.callee.done = true;
for (i=0; i <document.links.length; i++)
{
var x = document.links[i];
// Only attach tracking code to specific file types
var extensions = new RegExp(".+\.(zip|pdf|xls|doc|csv|txt|ppt|xml|rtf)$");
var doc = x.href.toLowerCase().match(extensions);
if (doc)
{
if (x.attachEvent)
{
x.attachEvent(
'onclick', function () {TrackIt(window.event.srcElement)}); // IE
} else {
x.addEventListener(
'click', function () {TrackIt(this)}, false); // Firefox
}
}
}
}

function TrackIt(link)
{
// Remove the conversion to Lowercase if you are on a Case sensitive web server
urchinTracker(link.href.toLowerCase());
}

1/11/2006 12:02:45 PM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [1]  |  Trackback
 Friday, January 06, 2006

Rick Strahl says: Get Excited About IIS 7.0.  Having seen it myself I wholeheartedly agree. 

1/6/2006 9:05:05 AM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [0]  |  Trackback
 Thursday, December 01, 2005

Like many others I am trying Google Analytics.  I first heard of the JavaScript tracking technique with LiveStats 7.  One feature that kept me from liking the technique they use for tracking is that it is difficult to track file downloads.  I work with a lot of content management systems that would require quite a bit of modification to add the required scripts to file download links.  However, the information you can get from this tracking technique is otherwise far superior to anything you can ever get from  traditional log analysis.  Ideally I would like to dynamically add the tracking code to all download links in my content.  The 2 ideas floating in my head at the moment are:

#1  Add an ISAPI filter in IIS that would parse all output and add the tracking code to any download links that match RegEx patterns.  Perhaps all PDF download links for example.

#2  If possible, use JavaScript injected in the content pages to dynamically add events like onClick to elements like the anchor tag.  I already have to inject the tracker scripts so it would be great if I could inject a page parser script as well. 

Anyone have other ideas or thoughts on the possibility of these options?  I don't do ISAPI so #1 is out for me unless I can hire it out.

12/1/2005 11:47:57 AM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [0]  |  Trackback
 Wednesday, November 23, 2005

It seems like forever ago that I started recommending Fredrik Normen's Permission Manager.  Recently I was tasked to find a way to restrict nodes in a SiteMap based on Permission.  Today I completed that task and thought I would share it with the world.  First of all, I appoligize for writing the code in VB.NET (employers standard), but you can all compile it and reference it from C# so get over it.

What I did to create the PermissionXmlSiteMapProvider was to inherit directly from XmlSiteMapProvider and then extend 2 methods.  First of all I extended the Initialize method so that I could verify that it was configured properly.  Since it is dependent on the presence of Permission Manager and it properly working I added a simple check to verify that it has a provider.

The second method to override for the addtion of Permission to a SiteNode was IsAccessibleToUser.  Thankfully the siteNode can have extra attributes on it without complaint.  I used that to check for the permission attribute.  If it is there then a call to Permission Manager is done for the named permission and the currently logged in user.

After configuring my applications to use my new Provider I restrict access to nodes as easy as this:

<siteMapNode url="Edit.aspx" title="Edit" permission="Modify" />

Since I built on top of the XmlSiteMapProvider I also get the ability to do Roles restriction and a combination of Roles and permission:

<siteMapNode url="Delete.aspx" title="Delete" Roles="Admins, Editors" permission="Delete" />

It is important to note a few things that I ran into while working on this solution.  You must enable Security Trimming.  If you will restrict based on Roles you also must enable RoleManager.  Restricting via Roles is a little quirky as described in the MSDN Site Map Providers document.  Specifically review the Security Trimming section.

Here is the siteMap section of Web.config that implements the PermssionXmlSiteMapProvider:

<siteMap enabled="true" defaultProvider="PersmissionXmlSiteMapProvider">
  <
providers>
    <
add name="PersmissionXmlSiteMapProvider
           
type="IDCL.Web.Security.PersmissionXmlSiteMapProvider, IDCL.Web"
           
description="Permission xml site map provider"
          
securityTrimmingEnabled="true"
          
siteMapFile="Web.sitemap"/>
  providers>
siteMap>

Obviously you also need to have Permission Manager installed and configured or this will not work.

Here is the code for the provider:

Imports System
Imports System.Web
Imports System.Configuration.Provider
Imports System.Web.Configuration
Imports Nsquared2.Security

Public Class PersmissionXmlSiteMapProvider
Inherits XmlSiteMapProvider

Public Overrides Sub Initialize(ByVal name As String, ByVal config As System.Collections.Specialized.NameValueCollection)

' Verify that config isn't null
If config Is Nothing Then
  Throw New ArgumentNullException("config")
End If

' Verify that Permission Manager is configured
If PermissionManager.Provider Is Nothing Then
  Throw New ProviderException("Permission Manager default provider missing.")
End If

' Add a default "description" attribute to config if the
' attribute doesn't exist or is empty
If String.IsNullOrEmpty(config("description")) Then
  config.Remove("description")
  config.Add(
"description", "PermissionXmlSiteMap provider")
End If

' Call the base class's Initialize method
MyBase.Initialize(name, config)
End Sub

Public Overrides Function IsAccessibleToUser(ByVal context As System.Web.HttpContext, ByVal node As System.Web.SiteMapNode) As Boolean

  Dim authorized As Boolean = MyBase.IsAccessibleToUser(context, node)

  'Pull the permission out of the sitemap node as an attribute
  Dim Permission As String = node.Item("permission")
  If Not Permission Is Nothing And authorized Then
    If context.User.Identity.IsAuthenticated Then
      ' Only check permission for known users
      Return PermissionManager.HasUserPermission(context.User.Identity.Name, Permission)
    Else
      ' The presence of a permission requires a known user
      Return False
    End If
  End If

Return authorized

End Function
End
Class

The PermissionSiteMapProvider.zip file with this article includes the Provider solution and a sample web.config

11/23/2005 4:27:33 PM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [0]  |  Trackback
 Tuesday, November 22, 2005
I love my Zen Micro.  Tiger Direct has one now for $129.99 after rebate.  Get one of these and then sign up for Yahoo Music Unlimited for a year while the price is still only $59.88.  You won't be disappointed!
11/22/2005 8:13:31 PM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [0]  |  Trackback
 Thursday, November 10, 2005

Today I discovered that Microsoft is answering the community rant against losing web projects in VS 2005.  They have introduced Web Deployment Projects as an add-in for VS 2005.  They have essentially wrapped MS Build into a project type for the web.  Most exciting is that they did us right and took it beyond just providing a way to do exclusions and assembly naming.  My favorite new thing is the ability to do configuration management as part of the build process.  I can now have multiple configuration documents that target different deployment options.  Thank you ScottGu and the ASP.NET team!  This is exactly the kind of thing that we needed!

11/10/2005 10:02:31 AM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [1]  |  Trackback
 Monday, November 07, 2005

Today is the official launch date for Microsoft Visual Studio 2005 and SQL 2005.  The team at DotNetNuke surprised us all be releasing their newest builds today as well.  Most surprising was the 4.0 release which runs on the .NET 2.0 framework.  I hadn't expected that for a few months still, but I am thrilled to see it.  I will be pushing hard at the office now to move from Beta 2 to the final release of .NET 2.0 so that we can run DNN 4.0 and begin building ASP.NET 2.0 modules.  Thanks DNN crew for your relentless efforts on this product.

11/7/2005 11:32:53 PM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [2]  |  Trackback
 Thursday, October 27, 2005

DotNetNuke (DNN) has an ISearchable interface that module developers can implement to allow their content to be indexed by the internal search system. 

This system has some significant limitations with the way it handles indexing.  Since it is not crawler driven it does not index anything except the text explicitly fed to it through modules that implement ISearchable.  You could give your page a great title and the search will never find it.  In fact the only thing I like about the ISearchable solution is that is can be made security aware and that it is provider based.  Of course if you attempt to use a different provider you are still limited by the overall solution, so even that is not great.

My next big frustration with ISearchable is that it is used to tap into the built-in Syndication feature for modules.  If you want the RSS feed for your module to work with the integrated syndication option then you have to implement ISearchable.  However there is a major problem in the implementation.  The links in the RSS can only get you back as far as the module itself.  If you look at the announcements module RSS output you will see what I mean.  The item links should get you to the articles, not just the list of articles.  I attempted to use this for syndication on a recent module and ran into this brick wall.  The solution is to write you own RSS output and forget about ISearchable for syndication.  Hopefully DNN will include a new interface specifically for the syndication feature in the future.  DNN team, if you are listening I would be happy to write this functionality if you will include it in the core!

My recommendation at this point is to forget about ISearchable completely.  For search and syndication it is very weak and simplistic.  My favorite alternative at this point is my own implementation of EasySearchASP into DNN.  On my new DNNSecrets site I am using the articles module from Scott McCulloch.  It happens to implement ISearchable.  I have added the EasySearchASP module to the search results page so you can compare the built in results with this much improved alternative.  Of course EasySearchASP is not free, but I think it is worth it to provide site users with a search that works!

I would love to hear your feedback!  What do you think of the ISearchable interface?  How do you like the EasySearchASP alternative?

10/27/2005 8:49:10 PM (Mountain Daylight Time, UTC-06:00)  #    Disclaimer  |  Comments [5]  |  Trackback