August 2006 - Posts

New Article on Windows Workflow Rules and Conditions

If you only look at one feature in Windows Workflow, look at the Policy activity. The Policy activity processes rules. Business rules, game rules - any type of declarative knowledge. Read more in the latest OdeToCode article - "Windows Workflow - Rules and Conditions".

The Policy activity is easy to use, and provides a boatload of functionality out of the box. You can prioritize rules, and track rule processing in detail. WF provides an API to modify rules at runtime, which provides a great deal of flexibility. Rules execute with forward chaining semantics, meaning the rules engine analyzes the dependencies and side effects of each rule, and can reevaluate rules when the underlying data changes. I can see applications using Windows Workflow solely for the Policy activity.

More Workflow articles on OTC:
Hello, Workflow - an introduction.
Authoring Workflows - a look at XAML, XOML, and Workflow compilation.
The Base Activity Library - a tour of the out-of-the-box activities in WF.
Hosting Windows Workflow - a look at using the persistence, scheduling, tracking, and transaction services in WF.

posted by scott with 7 Comments

What's Wrong With This Code? (#4)

This program throws an exception at runtime. If you've been burned by this problem before, you'll know what's wrong before you even see the definition for class Bar...

using System;

class Program
{
  
static void Main()
  {
    
Foo foo = new Bar();
  }
}

abstract class Foo
{
  
public Foo()
  {
    Init();
  }

  
protected abstract void Init();
}

class Bar : Foo
{
  
string message;

  
public Bar()
  {
    message =
"Hello!";
  }

  
protected override void Init()
  {
    
Console.WriteLine(message.ToUpper());
  }
}

posted by scott with 21 Comments

Weird Thread Behavior

I stumbled on a forum posting recently that led me to write the following code:

using System;
using System.Threading;

class Program
{
  
static void Main()
  {
    
ThreadStart doNothing = delegate { };

    
ThreadStart createThreads =
      
delegate
      {
        
for (int i = 0; i < 50; i++)
        {
          
Thread t = new Thread(doNothing);
          t.Priority =
ThreadPriority.BelowNormal;
          t.Start();
        }
      };

    
for (int i = 0; i < 2; i++)
    {
      
Thread t = new Thread(createThreads);
      t.Start();
    }

    
Console.ReadLine();
  }
}

This program behaves badly on a single processor machine, and pegs the CPU at 100% for over two minutes. On a multi processor machine, the program finishes all the threading work in the blink of an eye - only a brief CPU spike.

Strangely, if I remove a single line of code:

t.Priority = ThreadPriority.BelowNormal;

… then the program performs just as well on a single processor machine (only a brief spike - comparable to the MP scenario).

Could it be a bug?

posted by scott with 6 Comments

My $100 Spending Spree with Windows Workflow

The WF discussion that Brian Noyes kicked off continues with excellent points from Jon Flanders and Thomas Restrepo. The following posts are full of information from smart people who know WF very well:

You have to understand a technology to use it effectively…
Workflow Complexity
Workflow Complexity Part 2

Tech evangelist Matt Winkle then asked how to improve WF. In other words - quit bitching and offer us constructive ideas.

If I had a $100 budget to improve WF, I'd divide the money as follows:

$30 on designer improvements. The designer has a number of minor irritations, but one stands out at this very moment. Building declarative rules requires me to navigate a number of modal dialogs, and those dialogs prevent me from getting anywhere else in Visual Studio. When I double-click a .rules file, or ask for a new rule in the properties windows, I want a spiffy designer to appear in the editor. Each time a modal dialog appears in Visual Studio, another star falls from the heavens.

$30 on guidance. Simon Ince threw out the idea of a workflow factory / guidance toolkit. I'd also like to see more content covering common workflow patterns and best practices for hosting, versioning, and scaling.

$30 on "activity packs". Incrementally augment the base activity library with "activity packs". Microsoft could make activity packs available for download as they are developed and in-between major releases of WF. Each activity pack could cover a specific technology domain. Examples: a database activity pack, a WCF activity pack, a file system activity pack, and an XML activity pack. We could download just the activity packs we need.

$10 on an open source base activity library.WF will really shine in scenarios where domain specific activities are available (look at casey's article with a Speech Server workflow to see the flexibility and power of WF). First, however, a team has to build good looking and well-behaved domain specific activities. As Thomas says in his post - "Creating new activities is easy. Creating good activities can be pretty hard." The WF site already includes a number of custom activities, but activities are the lifeblood of WF, and we can always use more.

Earlier this year, the ASP.NET team released source code for all the built-in ASP.NET 2.0 providers, so this idea isn't unthinkable. Providing source and documentation for the BAL would be a tremendous boon for custom activity development.

posted by scott with 0 Comments

My Gripes with Windows Workflow

Brian Noyes' post "Understanding Windows Workflow and its complexities" has me thinking.

I know a few people who have given up on Windows Workflow altogether. WF imposes its own paradigm. If your way of thinking is different from the WF way of thinking, you are going to live in a house of pain.

Brian's first complaint is about the designer. I've found the designer to be clunky. There are behaviors you come to expect in Visual Studio, like being able to double-click somewhere and have the designer perform some work. The WF designer never acts intuitively. There are also places where the behavior seems inconsistent - some properties permit data binding and some properties do not. It's not until you learn about dependency properties and meta-properties that this behavior make sense.

Brian's third point was about the HandleExternalEvent activity. Setting up communications between a workflow and its host is a tremendous amount of work, and it's easy to get wrong (a working title for my InfoQ article was "How To Screw-up the HandleExternalEvent activity"). The work doesn't payoff until you see an event arrive for a workflow that has been living in a database table for 8 days. The runtime can re-load the correct workflow and still deliver the event! It's amazing to see this happen, but requires a lot of work to get there.

There are some common gotchas in WF programming:

1) Spawned execution contexts. The rule is that an activity executes once and only once. If you write code that forgets this rule, it's going to hurt!

2) Serialization. You don't need database persistence to see exceptions from the BinarySerializer - spawned execution contexts also make a deep copy of activities using serialization. Make sure those custom properties are serializable!

3) Transactions. It's easy to get MSDTC involved in heavy duty transactions with WF. Just using a persistence service and transactional tracking service at the same time will get the DTC involved (unless a special SharedConnectionWorfklowCommitWorkBatchService is added to the runtime). Something makes me nervous when a class name has 46 characters.

WF isn't as easy as it first appears. It's powerful, but requires an investment of time.  

posted by scott with 15 Comments

Powershell: Attach Debugger To ASP.NET Worker Process By Name

For those who don't like the "Attach To Process" dialog box, just pass the application pool name to this Powershell function:

function debug-wp([string]$name)
{
  if([String]::IsNullOrEmpty($name))
  {
    throw "Usage: debug-wp -Name "<appPoolName>"
  }

  $wplist = get-wmiobject Win32_Process -f "Name='w3wp.exe'"
  foreach($wp in $wplist)
  {
    if($wp.CommandLine -match "-ap `"(.+)`"")
    {
      if($name -eq $matches[1])
      {
        & vsjitdebugger.exe -p $wp.ProcessID
        break
      }
    }
  }

  if($name -ne $matches[1])
  {
    write-host "Could not find AppPool" $name
  }
}

 

posted by scott with 2 Comments

Haack for President

Phil Haack's blog has been on fire this month. Here is a sampling:

Open Source Is Free Like A Flower
Tiny Trick For Viewstate Backed Properties
Log4Net and External Configuration file In ASP.NET 2.0
Fun Iterating PagedCollections With Generics and Iterators

All great posts, but the pièces de résistance are "ASP.NET Supervising Controller (Model View Presenter) From Schematic To Unit Tests To Code" and "Tying MVP To the ASP.NET Event Model"

I particularly enjoyed the last post. Some people will freak out at the thought of tying the V in MVP to a specific technology. For the business layer and below, the thought is cause for alarm, but model-view patterns are all about testability these days. The generalizations needed to make a view work for multiple technologies are more of a hindrance than a benefit.

 

posted by scott with 3 Comments

What's Wrong With this Code? (#3)

Here is a Visual Basic program with bowling scores in a multi-dimensional array. Two players bowled three games each. The program tries to display the total score for each bowler, but has a small problem. Can you spot it?

Module Module1

  
Sub Main()

    
Dim allScores As Integer(,) = _
        {{101, 128, 143}, {123, 115, 116}}

    
' for each player
    For i As Integer = 0 To allScores.GetUpperBound(0)

      
Dim score As Integer

      ' for each game
      For j As Integer = 0 To allScores.GetUpperBound(1)
        score = score + allScores(i, j)
      
Next

      Console.WriteLine("Player {0} score: {1}", i + 1, score)

    
Next

  End Sub

End
Module

posted by scott with 10 Comments

Windows Workflow as a Rule Engine

One interesting facet to Windows Workflow is how I can combine procedural knowledge with declarative knowledge. Procedural knowledge is the "how-to" knowledge of performing a task. If a bank requires me to make three web service calls to process a payment, I can arrange those three calls inside a Sequence activity and model the exact ordering of calls required by the bank.

WF also provides a Policy activity to process declarative knowledge. Declarative knowledge is the knowledge of facts and relationships between data. Instead of "how-to" knowledge, declarative knowledge provides the "what-is" knowledge. For example, if I want to expedite a payment, the bank will charge me an extra 3%. "All bankers are thieves" would be another piece of declarative knowledge.

Rules engines are pieces of software specialized for processing declarative knowledge. There are a few .NET rules engines around. The most recent entry, as I found out from Larry Obrien's blog, is Drools.NET. BizTalk Server also provides a rules engine, and Charles Young has a good overview of Biztalk rule processing versus Windows Workflow rule processing.

There are some great "rules-centric" Windows Workflow samples on netfx3.com. Samples include a rules driven user interface, rules in Microsoft Excel, how to use an external ruleset (think of rules stored in a database), and more.

posted by scott with 0 Comments

Treat Warnings as Errors in ASP.NET 2.0

The web.config file controls all compilation settings in a default web site project. To treat compiler warnings as errors, you'll need the following in web.config:

<system.codedom>
  <
compilers>
    <
compiler            
      
language="c#;cs;csharp" extension=".cs"
      
compilerOptions="/warnaserror"
       type="Microsoft.CSharp.CSharpCodeProvider,
             System, Version=2.0.0.0, Culture=neutral,
             PublicKeyToken=b77a5c561934e089
" />
  </
compilers>
</
system.codedom>

Note: You can only twiddle with compiler settings under full trust, but then you'll only need this setting at build time.

posted by scott with 0 Comments

7 Virtues for Software Developers

The Perfect Programmer

Diligence - Diligent developers take ownership of their work without being possessive. Diligent programmers fix broken windows.

Humility - Humble developers take pride in their code, but don’t snub constructive criticism. Humble developers know they can always improve themselves.

Patience - Patient developers remain calm during times of stress, and don't surrender to the temptations of a quick fix. Patient developers have the endurance to carry a product across the finish line.

Liberality - Broad-minded developers base their decisions on proofs and particulars instead of preconceptions and prejudices. Broad-minded developers listen to the other side and attempt understanding.

Creativeness - Creative developers find a way around the brick wall in front of them, and do so without creating a mess. Creative developers find the elegant solution to a difficult problem.

Adaptability - Adaptable developers have the ability and willingness to learn new skills and accept new responsibilities. Adaptable developers continue to grow.

Resilience - Resilient developers bounce back from boneheaded bugs and bursting dotcom bubbles to code again another day. Resilient developers appreciate failure as the upward slope of the learning curve.

Eric Gunnerson's "Seven Deadly Sins of Programming" (and others) inspired this post.

I still have to work on 6 or 7 of these.

posted by scott with 14 Comments

Dear Candidate

Every so often, I get an email that looks like:

Dear Candidate,

While conducting a search for our client we came across your resume and it appears to be a good match for this opportunity. Blah blah fast-paced blah blah great benefits blah blah and so on.

Using the word "candidate" is not only presupposing, but a sign of laziness. Even the male enhancement spammers can personalize their messages. I'm sure the recruiter will have his pick of top talent with emails like this.

I think I'll reply with the following.

Dear [Recruiter Name],

Thank you for getting in touch about the [Position Name] offer with [Company Name]. Unfortunately, I'm busy on a project that will generate automated responses to mass mail job offers. I'm still working out a few bugs.

Sincerely,

[My Name]

posted by scott with 7 Comments

What's Wrong With this Code? (#2)

A developer wanted to keep track of some birthdays with the following table design and data.

CREATE TABLE [Birthdays]
(
  [Name]
varchar(50),
  [BirthDate]
datetime   
)

INSERT INTO [Birthdays] VALUES('Gene Wilder',  '6/11/1935')
INSERT INTO [Birthdays] VALUES('Nicola Tesla', '7/9/1856')
INSERT INTO [Birthdays] VALUES('Miles Davis',  '5/26/1926')

To sort the data by name and by date, the developer wrote a single stored procedure to handle both cases.

CREATE PROC GetBirthdays
  @Ordering
int
AS
BEGIN
  SELECT [Name], [BirthDate] FROM [Birthdays]
  
ORDER BY
    CASE @Ordering
      
WHEN 1 THEN [Birthdate]
      
WHEN 2 THEN [Name]
    
END
END

The developer tested the proc by passing a value of 1, and was pleased to see a resultset ordered by birth date.

What can go wrong? (Hint: Try passing a 2).

How could the developer fix the problem by changing only 1 line?

posted by scott with 17 Comments

Windows Workflow Hosting

Hosting Windows Workflow is a new article covering the WorkflowRuntime class and the WF services. The article shows how to configure and use the scheduling, persistence, and tracking services provided by Windows Workflow.

Feedback is appreciated.

posted by scott with 6 Comments

ASP.NET Best Practice Analyzer

The alpha release of the ASP.NET Best Practice Analyzer was about 5 weeks ago. Similar to the popular SQL Server BPA, the ASP.NET BPA evaluates a set of best practice rules and tells you about configuration problems in your applications. The tool checks both machine level and application level config files. Currently, the tool only has a handful of rules. It will raise red flags if the application runs in full trust, or if debug / trace flags are enabled, and a few others.

Ironically, the tool suggest AutoEventWireup="false", which isn't the default for C# web forms in VS2005.

I can't think of too many hard and fast rules for web.config settings, but here are a few: more that could be useful:

  • No plaintext passwords in the <identity> section.
  • Make sure the <httpHandlers> section maps appropriate extensions to the HttpForbiddenHandler.
  • Make sure the <pages> section has smartNavigation disabled and validateRequest enabled.
  • No enabled trace sources inside <system.diagnostics>.

posted by scott with 4 Comments

Unit Testing Workflow Activities

I've been kicking around the best approach for unit testing a custom activity for Windows Workflow. I haven't found an approach I'm comfortable with as yet.

Here is a simple, contrived custom activity.

using System;
using System.Workflow.ComponentModel;
using System.Workflow.ComponentModel.Serialization;

[assembly:
XmlnsDefinition("http://odetocode.com/wf/activities",
                          
"OdeToCode.WF.Activities")]
namespace OdeToCode.WF.Activities
{
   
public class CreateMessageActivity : Activity
   {

    
private string _recipient;
    
public string Recipient
    {
      
set { _recipient = value; }
    }

    
private string _message;
    
public string Message
    {
      
get { return _message; }
    }
   
    
protected override ActivityExecutionStatus
      Execute(
ActivityExecutionContext executionContext)
    {

      _message =
String.Format("Message to {0}", _recipient);
      
return ActivityExecutionStatus.Closed;
    }
   }
}

It's tempting to create a unit test that instantiates the activity and calls Execute directly, but most activities are not this simple. The ActivityExecutionContext class is sealed, and mocking out all the scheduling services, queues, and other dependencies inside is a task for Sisyphus.

Another approach is to test the activity using the WF runtime. A little bit of hacking produces:

[TestFixture]
public class CreateMessageActivityTests
{
  [
TestFixtureSetUp]
  
public void StartWfRuntime()
  {
    _runtime =
new WorkflowRuntime();
    
    
// use manual scheduler for synchronous execution
    _scheduler = new ManualWorkflowSchedulerService();
    _runtime.AddService(_scheduler);

    
// type provider needed to resolve custom activity in XAML
    TypeProvider typeProvider = new TypeProvider(_runtime);
    typeProvider.AddAssembly(
Assembly.Load("ActivityLibrary2"));
    _runtime.AddService(typeProvider);

    _runtime.WorkflowCompleted +=
        
new EventHandler<WorkflowCompletedEventArgs>
          (_runtime_WorkflowCompleted);
    _runtime.WorkflowTerminated +=
        
new EventHandler<WorkflowTerminatedEventArgs>
          (_runtime_WorkflowTerminated);
  }

  [
TestFixtureTearDown]
  
public void ShutdownWfRuntime()
  {
    _runtime.StopRuntime();
    _runtime.Dispose();
  }

  [
SetUp]
  
public void SetupTest()
  {
    _exception =
null;
    _outputs =
null;
  }

  
void _runtime_WorkflowTerminated(
      
object sender,
      
WorkflowTerminatedEventArgs e
    )
  {
    _exception = e.Exception;
  }

  
void _runtime_WorkflowCompleted(
      
object sender,
      
WorkflowCompletedEventArgs e
    )
  {
    _outputs = e.OutputParameters;
  }

  
WorkflowRuntime _runtime = null;
  
ManualWorkflowSchedulerService _scheduler;
  
Dictionary<string, object> _outputs;
  
Exception _exception;

  
// create custom activity as the root of a workflow
  string _xaml =
    
@"<otc:CreateMessageActivity
         xmlns:otc=""http://odetocode.com/wf/activities"">
      </otc:CreateMessageActivity>"
;
}

This is a lot of code, and I haven't even written a test yet. Unit testing a custom activity is an indirect process because the workflow runtime tries to shield a workflow instance from the callous hands of the outside world.

[Test]
public void CreatesMessageForScott()
{
  
XmlReader reader = XmlReader.Create(
                      
new StringReader(_xaml)
                     );

  
Dictionary<string, object> parameters;
  parameters =
new Dictionary<string, object>();
  parameters.Add(
"Recipient", "Scott");

  
WorkflowInstance instance;
  instance = _runtime.CreateWorkflow(reader,
null, parameters);
  instance.Start();
  _scheduler.RunWorkflow(instance.InstanceId);

  
Assert.IsNull(_exception, "Workflow threw an exception");
  
Assert.AreEqual(_outputs.Count, 1);

  
// .. and so on

}

I'm feeding a XAML definition of a workflow to the runtime in an attempt to simplify the process and isolate the activity. Still, this code is still far too lengthy and complex to maintain in a unit test. My plan is to create a project of utility classes to simplify the code even further, but I'm still apprehensive.

Anyone else unit testing activities or workflows?

posted by scott with 21 Comments

Something You Don't Want To See In Your Event Log

Source: MSSQL Server
17052 : Cannot recover the master database. Exiting.

The chkdsk result was ugly, too.....

posted by scott with 1 Comments