February 2005 - Posts

Enterprise Podcasting

This weekend I noticed patterns & practices superhero Ron Jacobs has made the jump into podcasting. His first two shows include an interview with Billy Hollis and a discussion about configuration contexts in Enterprise Library.

A bit later I was looking for local events in the area and discovered Steve Vai is bringing his superhuman guitar playing skills to town. Billy Sheehan will play bass on the tour, and believe me - having this much talent on stage at the same time should be illegal.

You might be wondering why I bring up these seemingly unrelated events.

Well, one of the photos on the right hand side is of Ron Jacobs, and the other photo is of Steve Vai. Both of these photos appear prominently on their respective web sites. One of these guys is known as PAG Daddy, and the other started his career with Frank Zappa.

Eerily similar - don’t you think?

posted by scott with 0 Comments

The Canonical Transaction Example Is Flawed

Almost every article ever written about database transactions includes the example of an ATM machine dispensing money. The articles will describe how the bank uses a transaction to prevent people from overdrawing the account during simultaneous access, or how the bank can rollback a transaction if an error occurs at the ATM.

They are all wrong.

About 7 years ago I went to a local ATM, punched in my PIN, and asked the ATM to withdraw $80 from my account. The machine thought about the request for a bit, and then started to make the happy fluttering noise ATM machines will do just before delivering cash. The happy fluttering noise was followed by a fateful POP sound in the distance, and then a total blackout. Power failure.

When power came back seconds later I still had no money, and the ATM had an OS/2 boot screen.

Cool! I remember thinking. The bank’s database just rolled back my transaction! I drove off in search of a working machine. Of course I was a bit younger then and had not learned the secret mission statement of every major financial institution: “Customer inconvenience is our first priority”. When my bank statement arrived the next month I was genuinely surprised to see the bank had committed the $80 transaction against my account.

One might think ATM machines would be equipped with some sort of UPS to keep them running in the event of a power failure during an open transaction, but one would be forgetting the second mission statement of all financial institutions: “Mistakes are acceptable, as long as we screw the customer and not ourselves”.

For the curious, here is how the story ends.

A month later I went to the bank, approached a teller, and told her if she gave me $80 in unmarked bills nobody would get hurt.

Well, not really, but it felt that way.

I showed the teller my statement and told her my story. She asked me to stand by a door to the left and walked through a back door behind the counter.

While waiting in the lobby I thought about the possibility of the teller returning to tell me the bank records were never wrong. Not only would I not get my $80 back, but they’d deduct a $10 service fee for seeing me in person. I’m always optimistic like that when dealing with banks. Thieves.

I was surprised when a woman burst out from the door beside the counter, ran over and practically hugged me. The bank had noticed an $80 discrepancy in the ATM records and this lady’s sole purpose was to find out what happened. I made her day and got my money back.

The End.

posted by scott with 3 Comments

Roadmap To Delegation

One of the thorny areas in writing a distributed application is keeping the logical thread of execution authenticated and authorized as calls hop from server to server. If you want to flow the original client’s identity across these servers you’ll quickly run into the “single network hop” restriction of NTLM (sometimes called the “double hop” issue). A client’s identity can only make a single hop. The first hop happens from the web browser to the web server. The web server can impersonate the client when accessing local resources, but it make a second hop to a third machine. Larry Osterman has details on this behavior.

The single hop issue turns up a lot these days as more products (Reporting Services, SharePoint) rely on Windows authentication, but we rarely see these applications on the same server as our ASP.NET applications.

One solution to the problem is the trusted subsystem model. However, the trusted subsystem model does not flow the original client’s identity automatically, and it becomes your application’s responsibility to perform authorization checks. Tricky.

Another solution is to use Kerberos delegation. If you want to enlighten yourself on the subject, I’d recommend the following roadmap.

Start with David Chappell’s “Exploring Kerberos, the Protocol for Distributed Security in Windows 2000”, and chase this article with Keith Brown’s “Exploring S4U Kerberos Extensions in Windows Server 2003”.

The next step is to watch delegation in action. A two part webcast walks through every detail of setting up delegation in a typical ASP.NET application environment: “Getting Delegation to Work with IIS and ASP.NET: The Ins and Outs of Protocol Transition” (Part 1 and Part 2).

At this point it’s time to take the IT department out to lunch, or perhaps send fruit baskets to their house. You’ll need their sign-off and support to pull it all off.

Two documents that can help during the implementation phase are “HOW TO: Configure an ASP.NET Application for a Delegation Scenario” and “Troubleshooting Kerberos Delegation”.

Just imagine how popular you’ll be at the next neighborhood social event if you can hang out at the punch bowl and explain the nuances of S4U2Self and S4U2Proxy.

posted by scott with 4 Comments

OpenMyMind.net

Karl Seguin’s site (OpenMyMind.net) has some good articles for ASP.NET developers. The two articles on creating multilingual websites (Part I, Part II) jump out at me, and the “Mastering Page-UserControl Communication” is a must read if you are starting to write user controls which interact with the parent page. (Hint: using Request.Form or FindControl to pull values from a user control is path filled with peril).  

posted by scott with 0 Comments

Reporting Services & VSLive! Miscellany

Maxim Karpov has a nice post about his VSLive! experience. Max is a bright guy and a blast to hang out with.

I’m still tying up lose ends and requests for information from my presentation on SQL Server Reporting Services Integration.

If you have any performance or exporting problems, make sure you have Service Pack 1 in place. To determine the version of Reporting Services, go to the base URL for the report server (typically http://machinename/reportserver/). At the bottom of the browser page will be the version number - 8.00.878.00 is SP1.

To see what is the future holds with Service Pack 2, see Geoff Snowman’s webcast: “New Features in Reporting Services Service Pack 2”. We are still waiting for the blog to update, Geoff.

In ASP.NET we are used to sorting data in a DataGrid by clicking on a column header. This same functionality is difficult to achieve in SSRS, but see MSFTie Bruce Johnson’s newsgroup post to see one approach (copy out the inline RDL and paste into a new report). I believe sorting will be much easier to implement in the 2005 version of Reporting Services.

If you have the Enterprise edition of Reporting Services and want to look at custom authentication schemes, try the article “Using Forms Authentication in Reporting Services” for some background information and a code to build on. Also, there is a data processing extension to read XML files on GotDotNet.

Finally, there are some articles here on OdeToCode to help with learning the web service API:

For anyone looking on information on setting up Kerberos delegation with ASP.NET, I have a post in the works for that topic…

posted by scott with 0 Comments

CodeBetter.com

Raymond is wondering what people think of CodeBetter.com. Let me tell you what I think.

The real problem with group blogs is the number of people who post breaking news – like “Microsoft is going to release IE7!”. Oh, wait – that’s not a problem on CodeBetter.com.

It’s good to see the CB’ers function as a group. Instead of sounding like a scratched CD, they feed off each other to build better content. It’s collaborative and interesting. CodeBetter.com is what a group blog should be. I like Darrell’s agile news, Brendan’s forays into the .NET framework, Geoff’s quirkiness, and then there is Sahil. Let me tell you about Sahil. Sahil came to the Columbia user group meeting last month to give a talk on serialization. During the talk he serialized DataSet objects, customer objects, and Star Trek characters with two different versions of .NET. I thought I was going to leave the building as a blob of 1,568 bytes on Sahil’s hard drive.

Keep up the great work CB’ers, and Raymond – keep the OOP articles coming. These articles are well written and I know many people will benefit.

P.S. I know many people don’t like the amount off “off-topic” material on other group blogs. Me? I like to know what people are feeding their cat every now and then. For example, you never know what you’ll see or hear on the John Elliot and Bill Ryan blogs, but I find their blogs delightfully entertaining. Perhaps they are never boring because John cuts code 20 hours a day and Bill is a philosophy major and insomniac. I say this even though John calls me names and Bill leaves me hanging in San Francisco. Thanks, guys.

posted by scott with 11 Comments

Reporting Services and Scriptomatic

This week I’m going to tie up some lose strings from my VSLive! presentation…

One can programmatically interact with Reporting Services across the network by sending requests via an URL, via a web service request (SOAP), and via Windows Management Instrumentation (WMI). Of these three – I spend no time covering WMI because it’s typically not an interface used to deliver features to end users. I’ll provide some more information here.

In general, WMI exposes classes to configure and mange both hardware and software on computers throughout the network. Reporting Services provides WMI classes that let us query and make changes to report server configurations.

WMI is one of those pieces of software that requires a reference manual nearby unless you work with it everyday, but there is a very easy way to get started: Scriptomatic 2.0. Just pick a WMI class and the tool will code-gen a runnable sample script in VBScript, Jscript, Python, or Perl. Scriptomatic itself is an interesting application written as a single HTA file and run in IE.

Here is a script from Scriptomatic showing the properties of the MSReportServer_ConfigurationSetting class from the root\Microsoft\SqlServer\ReportingServices\v8 namespace:

On Error Resume Next

 

Const wbemFlagReturnImmediately = &h10

Const wbemFlagForwardOnly = &h20

 

arrComputers = Array("REPORTING")

For Each strComputer In arrComputers

   WScript.Echo

   WScript.Echo "=========================================="

   WScript.Echo "Computer: " & strComputer

   WScript.Echo "=========================================="

 

   Set objWMIService = GetObject("winmgmts:\\" & strComputer & _

            "\root\Microsoft\SqlServer\ReportingServices\v8")

   Set colItems = objWMIService.ExecQuery _

           ("SELECT * FROM MSReportServer_ConfigurationSetting", "WQL", _

            wbemFlagReturnImmediately + wbemFlagForwardOnly)

 

   For Each objItem In colItems

      WScript.Echo "DatabaseIntegratedSecurity: " & _

                       objItem.DatabaseIntegratedSecurity

      WScript.Echo "DatabaseLogonName: " & objItem.DatabaseLogonName

      WScript.Echo "DatabaseLogonPassword: " & objItem.DatabaseLogonPassword

      WScript.Echo "DatabaseLogonTimeout: " & objItem.DatabaseLogonTimeout

      WScript.Echo "DatabaseName: " & objItem.DatabaseName

      WScript.Echo "DatabaseQueryTimeout: " & objItem.DatabaseQueryTimeout

      WScript.Echo "DatabaseServerName: " & objItem.DatabaseServerName

      WScript.Echo "Impersonate: " & objItem.Impersonate

      WScript.Echo "ImpersonateDomain: " & objItem.ImpersonateDomain

      WScript.Echo "ImpersonatePassword: " & objItem.ImpersonatePassword

      WScript.Echo "ImpersonateUserName: " & objItem.ImpersonateUserName

      WScript.Echo "InstallationID: " & objItem.InstallationID

      WScript.Echo "InstanceName: " & objItem.InstanceName

      WScript.Echo "PathName: " & objItem.PathName

      WScript.Echo "UnattendedExecutionLogonDomain: " & _

                      objItem.UnattendedExecutionLogonDomain

      WScript.Echo "UnattendedExecutionLogonName: " & _

                      objItem.UnattendedExecutionLogonName

      WScript.Echo "UnattendedExecutionLogonPassword: " & _

                      objItem.UnattendedExecutionLogonPassword

      WScript.Echo "VirtualRoot: " & objItem.VirtualRoot

      WScript.Echo

   Next

Next

 

 

If you’d rather have the code in .NET, then the System.Management namespace contains all the WMI goo. WMI is probably underutilized due to the quirky mixture of SQL like syntax with a namespace traversed like a file system. It seems powerful, and it’s something I need to keep in mind next time I need a configuration script.

posted by scott with 4 Comments

The 5 Stages Of Mocking

When we write unit tests, we write a piece of test code to verify a piece of production code. The trouble is, production code has the nasty habit of relying on dependencies that are hard to control. Dependencies may be infrastructure dependencies like SMTP servers and databases, or may be software dependencies that are non-trivial to setup and get into the correct state for testing.

Mock objects remove these dependencies and simplify unit test code. A mock object is a stand-in replacement for a real-world domain object. For more details about mock objects, see “Mock Objects to the Rescue!”.

When I first began reading about mock objects, I did not like what I was reading. In fact, I disliked the idea so much, I entered ….

Stage 1: Denial

I found my eyes leaping over any pages containing the four letter m word. I felt confident in my gut reaction that m*ck objects were a fad –

- the same gut feeling I had when I heard of a new language by the name of C#.

As time passed, I realized the m*ck object talk was still picking up steam, and I entered ….

Stage 2: Resentment

I decided ‘object mockery’ was no longer a fad, but a placebo in source code form. Software development teams relying on mock objects would develop overconfidence in the quality of their product. These teams could watch their house of mock playing cards crumble in the cruel hands of the real world.

“Fools!”, my inner voice would yell in a Gandalf like tone. My inner voice always sounds like Gandalf or Ed Norton Jr. when I’m agitated.

As time went on, I began to write unit tests, and watch others write unit tests. Slowly … gradually … I entered ….

Stage 3: Bargaining

Some methods cry out for a unit test. Perhaps the method has logic to react to an SMTP error message, or perhaps the method has an exception handler to recuperate from a network error. These error conditions are difficult to trigger at just the right time, but I have to know exactly how the code will react. Unit tests are addictive that way. I found myself thinking about writing mock objects just to force these error conditions.

Suddenly, I was an object mocker.

The days grew long, and grey. One rainy night while sitting in my car - I hit bottom. I found myself tuning the radio to a country music station, and singing along to an old George Jones tune. I had officially entered …

Stage 4: Depression

I thought about the burden faced by the universe of software development. Any object I can think of will need to be mocked somewhere, in some program. Forget databases and mail servers. We will mock water fountains and pizza ovens. We will mock robot arms and booster engines. We will mock the stars and the sun. Yes, even the moon. The moon must be mocked.

Mock me, will you?

I’ll mock you, too….

I thought about starting a web site (Mock The World) and selling t-shirts, but I figured only lunatics and government agents would be interested and I didn’t follow through. It took some time to work out of my funk, but eventually, I reached …

Stage 5: A Wary Acceptance

I accept mock objects for what they are – a technique to simplify the creation of effective unit tests. I’m always worried, however, about assumptions. Production code contains assumptions about how other objects will behave. Mock objects have a high probability of encoding those same assumptions, and we end up testing tautologies.

 
Assert.IsTrue( mockWeather.RainTommorow || !mockWeather.RainTommorow). 

In software development, we always find a better way to build a mousetrap…

So, anyone using Mock Objects or NMock?

posted by scott with 9 Comments

Unit Testing In Enterprise Library

Let me start by pointing to Tom Hollander, who is looking for feedback and input to build the next version of Enterprise Library.

I now return you to my irregularly scheduled ramblings...

Unit testing is here to stay. The only questions about unit testing revolve around the details. Do we test non-public types and methods? Do we use mock objects? I’m always interested in seeing real unit testing code from other projects to understand what decisions others have made. (For an intro to unit testing, see: “Improve the Design and Flexibility of Your Project with Extreme Programming Techniques”).

Enterprise Library uses the NUnit testing framework. It will be interesting to see how easily this will port to the VS 2005 unit testing framework (or even if the tests are ported to 2005, considering the unit testing framework may only be included with the Team System version of Visual Studio 2005). I know I’m chiming in late, but I hope we see unit testing in all versions of Visual Studio.

Enterprise Library includes unit tests in the same assembly as the classes being tested. Tests are surrounded with an #ifdef, presumably to keep them out of production builds and also to allow the library to compile on systems without NUnit.

The unit tests do access members with internal scope.

Unit testing code in EntLib is not in short supply. Here is a snippet from ConnectionStringFixture.cs:

   44 [Test]

   45 public void CanGetCredentialsFromRealSqlDataClass()

   46 {

   47    string initialConnectionString =

   48       String.Format("server=localhost; database=JoeRandom; uid={0}; pwd={1}; ;",

   49          userName, password);

   50    this.connectionString = new ConnectionString(initialConnectionString, userIdTokens, passwordTokens);

   51    Assert.AreEqual(userName, this.connectionString.UserName);

   52    Assert.AreEqual(password, this.connectionString.Password);

   53 }

The ConnectionStringFixture class has 10 tests in all:

InvalidOperationExceptionThrownWhenConnectionStringIsNull()
EmptyCredentialsReturnedForEmptyConnectionString()
CanGetCredentialsFromRealSqlDataClass()
CreateNewConnectionStringTest()
CreateNewConnectionStringTest()
CanAddCredentialsToConnectionStringThatDoesNotHaveThem()
CanSetUserIdAndPasswordInConnectionStringThatAlreadyHasOne()
RemovingCredentialsFromConnectionStringWithoutThemIsOk()
WillRemoveCredentialsFromConnectionString()
NullTests()

The overall style and naming conventions stick to accepted practices for writing unit tests. One issue up for debate is the testing of internal methods. I like the approach. Internal members are good candidates for testing since they often form the surface area of an API one layer beneath the public façade. After a short conversation with Rocky this week - I realize there is at least one person who doesn’t agree.

What do you think? (Regarding ‘what to test that is’ – I know some of you think about a lot of things).

Coming soon: Mock classes - and why they make me uncomfortable.

posted by scott with 6 Comments

Assembly Count

The word excessive is a relative term. What might seem excessive to you could be normal to me, and vice versa. Walt mentioned last week that he has a client who insists on shipping everything in a single assembly – which sounded excessively restrictive to us. On the other end of the scale, someone in the newsgroups posted about the slow startup time of an ASP.NET application composed of 300 assemblies. 300! Certainly 300 is an excessive number, right?

Loading a DLL isn’t a hugely expensive operation - but it doesn’t come free either. Years ago we used to use tools like Rebase to minimize load times. (For the curious, Visual Studio 2003 appears to use 0x11000000 as the preferred load address for a DLL, you can see this under Project -> Properties -> Configuration Properties –> Advanced -> Base Address).

The real problem with having 300 assemblies on hand isn’t the load time, but the time it takes to manage the dependencies and versioning among all the units. I favor starting with rather coarse grained units of deployment. As time progress one can always factor out pieces to be put in separate assemblies to reuse or deploy in distributed fashion.

As William Blake once wrote: Excessive sorrow laughs. Excessive Joy Weeps.

posted by scott with 0 Comments

VSLive! Day 2

I didn’t get a chance to blog about the morning keynote by Eric Rudder, but plenty of others have, including Sam, Rocky, and Richard.

I went to hear the Don Box and Steve Schwartz breakout session on Indigo. Don wrote some Indigo code with VB.NET – I have pictures to prove it. Indigo is looking exciting – but during the presentation I became a bit distracted. I realized in a few hours I’d be giving my presentation in the same building as these people - felt a bit humbled - wondered what I had gotten myself into - and went off to do some rehearsing at the hotel.

I think my presentation went well – I had a few good comments afterwards. I guess I’ll know more when the evaluations are tallied. One tip I can heartily recommend is to have the first 5 minutes of a presentation down cold. I rehearsed my first 5 minutes endless times. I could have spoken for the first 5 minutes even if a herd of zebras ridden by scantily clad dancing girls had broken into the room and paraded around. The 5 minutes I spent operating in “automatic mode” gave me time to get used to the microphone, the lights, the audience, and got me through the sometimes awkward introductory phase. After 5 minutes I'm into the meatier part of a presentation and can just start talking about what I know.

At this point I could prepare for tomorrow’s flight by packing, or I can go join the evenings festivities.

Easy choice, this one.

posted by scott with 2 Comments

VSLive! Keynote

I’m sitting in the keynote at VSLive! by Soma Somasegar (VP, Developer Division, Microsoft).

Interesting statistic: In February of 2002 there were 300 “.NET developer wanted” ads on monster.com, today there are over 10,000. Also interesting: in 2002 many of these ads wanted developers with 3+ years of .NET development experience. Gotta love the people that come up with those types of ads.

The Enterprise Library was plugged during the keynote, but the general thrust of the speech was to promote smart clients. I believe the success of Microsoft’s smart client push hinges entirely on ClickOnce. In my experience it’s often difficult to sell the CTOs and CIOs of the world on applications that requires IT to touch an end user’s machine. Many hold a religious like belief that browser applications are the solution to every problem, or they’ve been burned by deployment scenarios in the past. Microsoft will need to promote ClickOnce success stories to the execs more than the developers.

Yesterday was a travel day and was a little rough. Delayed flights - and I had nothing to eat the entire day. When I hit downtown San Fran I sank an entire fleet of small wooden sushi-carrying ships at restaurant called “Sushi Boat”. Yum.

posted by scott with 1 Comments

Argument Validation

I’m still poking around the common validation areas of Enterprise Library.

The ArgumentValidation class has 6 public static methods:

public static void CheckForEmptyString(string variable, string variableName) ...
public static void CheckForNullReference(object variable, string variableName) ...
public static void CheckForInvalidNullNameReference(string name, string) ...
public static void CheckForZeroBytes(byte[] bytes, string variableName) ...
public static void CheckExpectedType(object variable, Type type) ...
public static void CheckEnumeration(Type enumType, object variable, string) ... 

CheckForEmpty string has the following implementation:

   21 public static void CheckForEmptyString(string variable, string variableName)

   22 {

   23    CheckForNullReference(variable, variableName);

   24    CheckForNullReference(variableName, "variableName");

   25    if (variable.Length == 0)

   26    {

   27         throw new ArgumentException(SR.ExceptionEmptyString(variableName));

   28    }

   29 }

I like this approach to validation. It feels much cleaner to start a method off with a handful of static method calls to validate arguments compared to starting with a handful of conditional blocks sprinkled with throw statements. The checks are explicit and readable here.

I know some people go nuts defining custom exception hierarchies – I’ve never been a big fan of that approach. The EntLib uses exception classes from the BCL, and keeps the error messages in a resource file and ready for localization. Clean. Flexible.

Most checks are in the form of if (variable == null), although there is at least one place where the code looks like: if (null == variable). I understand the reasons why we did this in C++: it was easy to mix up the assignment operator with the equality operator, but I never cared for expressions in this form and never subscribed to the practice. The C# compilers are better at warning about this subtle error, so I don't plan on ever switching either.

Next up: unit tests in EntLib. I might be looking these over during the trek to VSLive! tomorrow.

posted by scott with 4 Comments

I've Been Waiting

I’m not right all of the time – just ask anyone I work with! I’ve been waiting for someone to take me to task over something I’ve written badly, and this evening I was introduced to Dinis Cruiz in his post Some Comments to Misleading and False Information…”.  

Dinis makes a great point – an application domain is not a secure boundary if code is running with full trust. Unfortunately, it seems most ASP.NET shared hosting providers are running ASP.NET code with full trust. I’ve updated my article on application domains to make this point stand out.

posted by scott with 0 Comments

Error Handling

When looking at the code for a product or library, I find I’m always drawn to look at the error handling and validation pieces. Since every piece of software has these pieces in one form or another it’s always interesting to compare and contrast the different practices and methodologies used throughout the industry. The newly released Enterprise Library, being from the patterns and practices group, obviously had some thought put into this area, so I was keenly interested to peek at the code.

Here is one of the constructors from the Enterprise Library’s ConfigurationBuilder class:

   66 public ConfigurationBuilder(string configurationFile)

   67 {

   68    ArgumentValidation.CheckForNullReference(configurationFile, "configurationFile");

   69    ArgumentValidation.CheckForEmptyString(configurationFile, "configurationFile");

   70 

   71    LoadMetaConfiguration(configurationFile);

   72 }

The code snippet is short, but tells a tale. It tells me I can expect the Enterprise Library to validate explicitly all the parameters I pass.

The code snippet also tells me the team made a decision to let constructors throw exceptions. Deciding to let exceptions escape from a constructor is not an easy decision, particularly in this case because the ConfigurationBuilder class inherits from IDisposable. If the constructor acquires a resource (say, opens a configuration file) and then throws - there is no object reference returned by the constructor for the client to invoke Dispose upon.

On the other hand, covering this edge condition always increases the complexity of the internal implementation, and can also make the public interface harder to use (for instance if the client has to invoke both a constructor and an Initialize method).

It's a tough tradeoff - 'simple elegance' or 'paranoid error handling'. Which do you prefer?

posted by scott with 7 Comments

Global.asax or an HttpModule?

We know HttpApplication events like BeginRequest, AuthorizeRequest, and Error can be extremely useful for implementing functionality like URL re-writing and custom authentication schemes. We can choose to catch these events in one of two places: inside of the methods provided by Global.asax, or inside a custom HttpModule.

I favor the global.asax approach only if the logic inside the events is heavily application specific. In general, an HttpModule is the best approach for a number of reasons.

Firstly, I don’t want global.asax to become a dumping ground for code snippets that need to execute during the request lifetime. Using one or more HttpModules allows me to cleanly factor out responsibilities into distinct classes. A clean design also allows me to place the HttpModule in a class library for reuse across multiple projects.

Secondly, HttpModules are a bit more flexible since I can add them and remove them at runtime with a modification to web.config (or machine.config). A testament to the flexibility and power of an HttpModule is the ValidatePathModule released by Microsoft last year to avoid canonicalization bugs in ASP.NET (note to self: make sure the spell checker doesn’t replace canonicalization with cannibalization). Looking far down the road to the Longhorn timeframe, HttpModules will have even more flexibility in the componentized .NET-aware IIS 7.0. More details on new features in IIS 7.0 next week…

posted by scott with 3 Comments