OdeToCode IC Logo

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

Tuesday, October 10, 2006 by scott

This time, Joe Developer is building a web application for the company intranet. Most of the site is available to anonymous users, but one directory - the adminPages directory, should only be accessible to users in the machine's local administrators group. Joe added the following to the bottom of his web.config, and is feeling pretty secure.

<configuration>
  <location path="adminPages">
    <system.web>
      <authorization>
        <allow roles=
"BUILTIN\Administrators" />
        <deny users=
"?" />
      </authorization>
    </system.web>
  </location>  
</configuration>

Should Joe be worried?

Celebrities in MSDN Documentation

Tuesday, October 3, 2006 by scott

I imagine the MS legal department frowns on the appearance of recognizable names in documentation. Nevertheless, there are a few out there. For instance, the documentation for IXmlSerializable.WriteXml contains a celebrity from Bedrock:

// Create a person object.
Person fred = new Person("Fred Flintstone");

Over in the Visual FoxPro world, we find a local Microsoft celebrity:

IF UPPER(userID) = 'BILLG' && only one user may add tables
   RETURN .T.
ENDIF
   RETURN .F.
ENDIF

Finally, perhaps someone had a particular GeorgeW in mind when describing sp_denylogin to aspiring DBAs:


EXEC sp_denylogin 'Corporate\GeorgeW'

If only it were that easy…

So, know of any others?

Workflow Policy, Rules, and Collections

Monday, October 2, 2006 by scott

Q: Great article on the WWF Rules engine. I am curious how one would implement a rule that is applied to a collection of items, something akin to "IF request.Payments[n].Amount > 10000 THEN request.Payments[n].RequiresApproval = true"

A: There are quite a few approaches to choose from. As usual - it depends on the application!

One solution is to use a WhileActivity and loop through the collection. This explicit approach would run a PolicyActivity for each item in the collection.

The Windows SDK contains an interesting alternative approach in the "Processing Collections in Rules" entry. This is approach is worthy of note because it uses the features and flexibility of the rules engine to perform the iteration without any outside help. The approach specifically relies on the following features:

  • Every rule in a Windows Workflow rule set has an associated priority number. The priority number determines when the rule will execute relative to other rules.
  • The rules engine in WF analyzes the dependencies between rules. When a rule updates a dependency, the rules engine can re-evaluate previous rules that used the dependency.

With a Payment class defined in your domain model, the workflow class can accept an array of Payment references as a parameter:

public partial class Workflow1 : SequentialWorkflowActivity
{      
  
public Payment[] Payments
   {
      
get { return _payments; }
      
set { _payments = value; }
   }
  
  
Payment[] _payments;
  
IEnumerator _enumerator;
  
Payment _currentPayment;
}

Next comes the RuleSet:

Name Priority Rule
GetEnumerator 3 IF 1=1 THEN
  this._enumerator = this._payments.GetEnumerator()
MoveEnumerator 2 IF this._enumerator.MoveNext() THEN
  this._currentPayment = (Payment)this._enumerator.Current
One Or More Biz Rules 1 this._currentPayment.Amount > 10000 THEN
  this._currentPayment.RequiresApproval = True
ELSE
  this._currentPayment.RequiresApproval = False
Force re-eval 0 IF this._currentPayment == this._currentPayment THEN
  Update("this/_enumerator/")

GetEnumerator runs first because it has the highest priority. MoveEnumerator runs second - again because of priority. After these two rules finish, _currentPayment will reference the first Payment object in the array. All the "business" rules could now execute on that payment and decide on an outcome.

The interesting piece happens in the last rule, which always evaluates to true and performs an explicit "Update" on the _enumerator field. "Update" is a rule action that explicitly tells the engine about a side effect. In this rule, we are telling the engine that we've changed _enumerator. Even though we haven't actually changed _enumerator, we've forced the engine to look for previous rules with a dependency on _enumerator.

The rules engine knows the MoveEnumerator rule has a dependency on _enumerator, so it re-executes this rule. If MoveNext returns true in this rule, we update the _currentPayment field. The rules engine also detects this implicit side effect, and will reevaluate all the business rules that depend on _currentPayment. If MoveNext returns false, there are no more side effects and the Policy activity can close.

This is a clever pattern which keeps collection processing inside of a single activity.

Custom Build Numbers in Team Build

Friday, September 29, 2006 by scott

The Team Build service in Team Foundation Server includes the current date in the build number by default. To me, the string looks like the random gibberish of a disk head crash.

FooBarBuild_20060928.1
FooBarBuild_20060928.2

I know some people are fond of including the date in a build label, but it's a turn off for me. Build labels have a tendency to show up in many places, and a friendly number is easier on the eyes.

FooBar_2.5.1
FooBar_2.5.2

FooBar_2.5.176

Fortunately, it's easier to change Team Build with a custom MSBuild task. There are some examples of how to do this out there, but none that generate a truly friendly name. Ideally, the task will start with a base name like "FooBar_2.5" and just generate an extra identity digit. One of the properties in play during a team build is LastBuildNumber, which we can inspect during the task and use to generate the build number we want. The code would look something like this:

using System;
using Microsoft.Build.Utilities;
using Microsoft.Build.Framework;

namespace TfsBuildUtilities
{
  
public class GetBuildNumber : Task
   {
      
public override bool Execute()
      {
        
bool result = true;

        
try
         {
            ValidateProperties();

            
string buildNumber = GetLastBuildNumber();
            _buildNumber = BaseBuildName +
"." + buildNumber;
         }
        
catch (Exception e)
         {
            result =
false;

            BuildErrorEventArgs eventArgs;
            eventArgs =
new BuildErrorEventArgs
                (
                    
"",
                    
"",
                    BuildEngine.ProjectFileOfTaskNode,
                    BuildEngine.LineNumberOfTaskNode,
                    BuildEngine.ColumnNumberOfTaskNode,
                    0, 0,
                    
"GetBuildNumber failed: " + e.Message,
                    
"", ""
                );

            BuildEngine.LogErrorEvent(eventArgs);

            
throw;
         }

        
return result;
      }

      
private string GetLastBuildNumber()
      {
        
string buildNumber = null;

        
// if there is no last build, or it looks as if the
         // last build was not using our name,
         // we will reset to ".1";
         if (String.IsNullOrEmpty(LastBuildNumber) ||
            !LastBuildNumber.StartsWith(BaseBuildName))
         {
            buildNumber =
"1";
         }
        
// otherwise we need to parse out the last number and increment it
         else
         {
            
string[] parts = LastBuildNumber.Split('.');
            
int number = 1;
            
bool parseResult = Int32.TryParse(
                  parts[parts.Length - 1],
                  
out number
               );
            
            
if (parseResult)
            {
               number++;
               buildNumber = number.ToString();
            }
         }

        
if (String.IsNullOrEmpty(buildNumber))
         {
            
throw new InvalidOperationException(
                  
"Could not generate a valid build number"
               );
         }

        
return buildNumber;
      }

      
private void ValidateProperties()
      {
        
if (String.IsNullOrEmpty(BaseBuildName))
         {
            
throw new ArgumentException("BaseBuildName is null");
         }
      }

      [Output]
      
public string BuildNumber
      {
        
get { return _buildNumber; }
        
set { _buildNumber = value; }
      }
      
private string _buildNumber = String.Empty;

      [Required]
      
public string BaseBuildName
      {
        
get { return _baseBuildName; }
        
set { _baseBuildName = value; }
      }
      
private string _baseBuildName;

      
public string LastBuildNumber
      {
        
get { return _lastBuildNumber; }
        
set { _lastBuildNumber = value; }
      }
      
private string _lastBuildNumber;
   }
}

Then, register the task in TFSBuild.proj file. I like to deploy the assembly into the MSBuild extensions path to use from multiple projects.

<UsingTask
 TaskName="TfsBuildUtilities.GetBuildNumber"  
 AssemblyFile=
"$(MSBuildExtensionsPath)\MyDir\TfsBuildUtilities.dll"/>

Finally, in the same .proj file, override the build number target to spit a new build label into the system.

<Target Name = "BuildNumberOverrideTarget" >    
  <GetBuildNumber BaseBuildName=
"FooBar_2.5"
                  LastBuildNumber=
"$(LastBuildNumber)">
    <Output TaskParameter=
"BuildNumber"
             PropertyName=
"BuildNumber"/>      
  </GetBuildNumber>
</Target>

TFS is making the CM job easy…

Removing Features Considered Harmful

Wednesday, September 27, 2006 by scott

I get the occasional earful from people who are upset with Microsoft. These rants might come electronically, or they might come in person. If I know anything about the particular problem the person is having, I might try to give them some perspective or background on why Microsoft made the software do whatever it is that is causing the person great pain and suffering. I might know a workaround, or I might not. I might make them happier, or I might not. I do try. C'est la vie.

Today, I am on the other side of the fence.

I updated ActiveSync from 3.something to 4.2 today, only to find they removed the ability to synch a device over the network.

Let me express my feelings about this decision by saying - it sucks. It sucks really large, rotten, ostrich eggs.

What's Wrong With This Code (#6)

Tuesday, September 26, 2006 by scott

Joe Developer is working on a new application for a book publisher. Authors can publish zero or more books. Books can have zero or more authors.

Book and Author schema

Joe wrote a query to get a total count of all authors, and a total count of all books. Joe read on the Internet that the DISTINCT keyword is good to use in these scenarios.

SELECT DISTINCT
  COUNT(Authors.Id) AS TotalAuthors,
  COUNT(Books.Id) As TotalBooks
FROM
  Authors  
  FULL JOIN AuthorsBooks AB ON Authors.Id = AB.AuthorID
  FULL JOIN Books ON AB.BookID = Books.Id

The problem is - the numbers seem too high. What's wrong? Is it easy to fix?

State Machines In Windows Workflow

Sunday, September 24, 2006 by scott

A new article on OdeToCode: State Machines In Windows Workflow. State machines have been a powerful abstraction in software for many years. Using a state machine in Windows Workflow means we get all the tracking, persistence, and meta-data support the workflow runtume offers, which is quite a bonus.

The state machine designer