<?xml version="1.0" encoding="utf-8"?><?xml-stylesheet type='text/xsl' href='http://mikeamundsen.spaces.live.com/mmm2008-07-24_12.50/rsspretty.aspx?rssquery=en-US;http%3a%2f%2fmikeamundsen.spaces.live.com%2ffeed.rss' version='1.0'?><rss version="2.0" xmlns:slash="http://purl.org/rss/1.0/modules/slash/" xmlns:msn="http://schemas.microsoft.com/msn/spaces/2005/rss" xmlns:live="http://schemas.microsoft.com/live/spaces/2006/rss" xmlns:dcterms="http://purl.org/dc/terms/" xmlns:cf="http://www.microsoft.com/schemas/rss/core/2005" xmlns:wfw="http://wellformedweb.org/CommentAPI/"><channel><title>Mike Amundsen's INETA Talks and Travels</title><description>Details on Mike Amundsen's INETA Talks and Travels</description><link>http://mikeamundsen.spaces.live.com/</link><language>en-US</language><pubDate>Sun, 05 Oct 2008 09:40:01 GMT</pubDate><lastBuildDate>Sun, 05 Oct 2008 09:40:01 GMT</lastBuildDate><generator>Microsoft Spaces v1.1</generator><docs>http://www.rssboard.org/rss-specification</docs><ttl>60</ttl><live:identity><live:id>8579671301203983569</live:id><live:alias>mikeamundsen</live:alias></live:identity><image><title>Mike Amundsen's INETA Talks and Travels</title><url>http://blufiles.storage.live.com/y1puO2p42v3F-DOUbvXMwt6AUFr_HKcH7QgVtAF_YSkFnSJ4fRnhgrMBmYcYcaqIj8m</url><link>http://mikeamundsen.spaces.live.com/</link></image><cf:listinfo><cf:group ns="http://schemas.microsoft.com/live/spaces/2006/rss" element="typelabel" label="Type" /><cf:group ns="http://schemas.microsoft.com/live/spaces/2006/rss" element="tag" label="Tag" /><cf:group element="category" label="Category" /><cf:sort element="pubDate" label="Date" data-type="date" default="true" /><cf:sort element="title" label="Title" data-type="string" /><cf:sort ns="http://purl.org/rss/1.0/modules/slash/" element="comments" label="Comments" data-type="number" /></cf:listinfo><item><title>Emulating Enumerators in Javascript</title><link>http://mikeamundsen.spaces.live.com/Blog/cns!77111D9765E07CD1!397.entry</link><description>&lt;p align=right&gt;
Download this code from my &lt;a href="http://groups.yahoo.com/group/mikeamundsen/"&gt;Yahoo! Groups web site&lt;/a&gt;.

&lt;p&gt;I've been doing a lot of work with JavaScript recently. My current work project has me thinking a lot about 'advanced' JavaScript, 'Object-Oriented' JavaScript, and things of that nature. And I've learned quite a bit! While sharing some of this with my friend &lt;a href="http://www.robertlair.com/blogs/lair/"&gt;Bob&lt;/a&gt; of &lt;a href="http://www.intensitysoftware.com/"&gt;Intensity Software&lt;/a&gt;, he encouraged me to blog more about what I'm finding. So I'm starting a set of short pieces on JavaScript that I hope prove interesting.
&lt;h3&gt;Enumerators Are Handy&lt;/h3&gt;
&lt;p&gt;Now that I'm writing more code in JavaScript, I'm finding that I use enumerators to make my code more readable and portable. While MSIE has an Enumerator built-in object, the Mozilla-based browsers do not. So I needed to come up with something that works across browsers. My solution takes advantage of a very powerful feature of JavaScript - associative arrays.
&lt;h3&gt;Associative Arrays&lt;/h3&gt;
&lt;p&gt;Associative arrays are collections that link &amp;quot;keys&amp;quot; to &amp;quot;values.&amp;quot; Nothing big there. But the way JavaScript deals with associative arrays is very powerful. You can access members of associative arrays either using the familiar key-index pattern (&lt;code&gt;MyOptions[&amp;quot;Yes&amp;quot;]=1&lt;/code&gt;) or you can use a property name pattern (&lt;code&gt;MyOptions.Yes=1&lt;/code&gt;). It is this second access method that I decided to use for an enumerator implementation.
&lt;h3&gt;Defining an Enumerator Object&lt;/h3&gt;
&lt;p&gt;So, using associative arrays, it's easy to create an enumerator pattern in JavaScript. Below is a simple code example:
&lt;pre&gt;
    // define enumerator
    var Role = 
    {
        Visitor     : 0,
        User        : 10,
        Author      : 20,
        Editor      : 30,
        Publisher   : 40,
        Admin       : 100
    }
&lt;/pre&gt;
&lt;p&gt;That's all there is, really. Now I can write familiar JavaScript to access the members as needed. For example, I can use &lt;code&gt;Role.User&lt;/code&gt; to get the value of &lt;code&gt;10&lt;/code&gt;. Of course, I could also use &lt;code&gt;Role[&amp;quot;User&amp;quot;]&lt;/code&gt; and get the same value. This second version points to a handy way to iterate through an enumeration collection.
&lt;h3&gt;Iterating an Enumerator&lt;/h3&gt;
&lt;p&gt;Sometimes I wants to step through a list of enumerator members. I might want to create a dropdown list to allow users to select a member. I might want to use this enumerator pattern to express a list of menu options and then iterate through those options to create a menu of links. There are lots of possibilities. Below is some code to 'walk' through an enumerator and create an unordered list to display on a web page.
&lt;pre&gt;
        var elm = document.getElementById(&amp;quot;output&amp;quot;);
        var str = &amp;quot;&amp;quot;;
        var cnt = 0;

        str = &amp;quot;&amp;lt;ul&amp;gt;&amp;quot;;

        for(r in Role)
            str += &amp;quot;&amp;lt;li&amp;gt;&amp;quot; + r + &amp;quot;: &amp;quot; + Role[r] + &amp;quot;&amp;lt;/li&amp;gt;&amp;quot;;

        str += &amp;quot;&amp;lt;/ul&amp;gt;&amp;quot;;

        elm.innerHTML = str;
&lt;/pre&gt;
&lt;h3&gt;Summary&lt;/h3&gt;
&lt;p&gt;So that's it. I used JavaScript's associative arrays pattern to emulate enumerators that can work across all browsers that support JavaScript. I can use typical enumerator access patterns as well as iteration patterns to access the contents as needed. There are lots of other possible ways to use associative arrays within JavaScript. I'll leave that to you to explore until my next JavaScript post[grin].
&lt;hr&gt;
&lt;h3&gt;Technorati Tags&lt;/h3&gt;
&lt;p&gt;I tag my posts for easy indexing at &lt;a href="http://www.technorati.com/"&gt;Technorati.com&lt;/a&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="http://www.technorati.com/tag/javascript" rel=tag&gt;javascript&lt;/a&gt;
&lt;li&gt;&lt;a href="http://www.technorati.com/tag/web" rel=tag&gt;web&lt;/a&gt;
&lt;li&gt;&lt;a href="http://www.technorati.com/tag/programming" rel=tag&gt;programming&lt;/a&gt;
&lt;/ul&gt;
&lt;hr&gt;&lt;img src="http://c.services.spaces.live.com/CollectionWebService/c.gif?cid=8579671301203983569&amp;page=RSS%3a+Emulating+Enumerators+in+Javascript&amp;referrer=" width="1px" height="1px" border="0" alt=""&gt;&lt;img style="position:absolute" alt="" width="0px" height="0px" src="http://c.live.com/c.gif?NC=31263&amp;amp;NA=1149&amp;amp;PI=73329&amp;amp;RF=&amp;amp;DI=3919&amp;amp;PS=85545&amp;amp;TP=mikeamundsen.spaces.live.com&amp;amp;GT1=mikeamundsen"&gt;</description><category>Articles</category><comments>http://mikeamundsen.spaces.live.com/Blog/cns!77111D9765E07CD1!397.entry#comment</comments><guid isPermaLink="true">http://mikeamundsen.spaces.live.com/Blog/cns!77111D9765E07CD1!397.entry</guid><pubDate>Sun, 23 Jul 2006 00:50:32 GMT</pubDate><slash:comments>6</slash:comments><msn:type>blogentry</msn:type><live:type>blogentry</live:type><live:typelabel>Blog entry</live:typelabel><wfw:commentRss>http://mikeamundsen.spaces.live.com/blog/cns!77111D9765E07CD1!397/comments/feed.rss</wfw:commentRss><wfw:comment>http://mikeamundsen.spaces.live.com/Blog/cns!77111D9765E07CD1!397.entry#comment</wfw:comment><dcterms:modified>2006-07-23T00:50:32Z</dcterms:modified></item><item><title>XML Namespaces and SQL2005</title><link>http://mikeamundsen.spaces.live.com/Blog/cns!77111D9765E07CD1!395.entry</link><description>&lt;p&gt;on my last INETA trip (to Plano, TX) i was asked a question about generating XML from SQL2005 that included support for XML namespaces.  Unfortunately, I didn't have my act together at that moment and was not able to show off this cool feature of SQL2005. But it is actually very easy to generate namespace-enabled XML using a new feature of SQL2005 - the NAMESPACES keyword
&lt;h3&gt;The NAMESPACES Keyword&lt;/h3&gt;
&lt;p&gt;There are a number of powerful new keywords in SQL2005 that make it easier to support XML. One of them is NAMESPACES. AS you would expect, this keyword is used to output XML from SQL Server that includes the proper namespace designations.
&lt;p&gt;For example, let's assume you want to create the following output from the AUTHORS data table from the PUBS database:

&lt;pre&gt;
&amp;lt;rdf:RDF xmlns:xmlp=&amp;quot;http://www.amundsen.com/rdf/xmlp/1.0/&amp;quot; xmlns:rdf=&amp;quot;http://www.w3.org/1999/02/22-rdf-syntax-ns#&amp;quot;&amp;gt;
  &amp;lt;rdf:Description&amp;gt;
    &amp;lt;xmlp:id&amp;gt;123-45-6789&amp;lt;/xmlp:id&amp;gt;
    &amp;lt;xmlp:firstname&amp;gt;mike&amp;lt;/xmlp:firstname&amp;gt;
    &amp;lt;xmlp:lastname&amp;gt;amundsen&amp;lt;/xmlp:lastname&amp;gt;
    &amp;lt;xmlp:phone&amp;gt;123-456-7890&amp;lt;/xmlp:phone&amp;gt;
    &amp;lt;xmlp:address&amp;gt;123 main st&amp;lt;/xmlp:address&amp;gt;
    &amp;lt;xmlp:city&amp;gt;byteville&amp;lt;/xmlp:city&amp;gt;
    &amp;lt;xmlp:state&amp;gt;md&amp;lt;/xmlp:state&amp;gt;
    &amp;lt;xmlp:zip&amp;gt;94609&amp;lt;/xmlp:zip&amp;gt;
  &amp;lt;/rdf:Description&amp;gt;
&amp;lt;/rdf:RDF&amp;gt;
&lt;/pre&gt;

&lt;p&gt;Creating the query that results in the above output is actually pretty straight forward. I'll go through the steps below and toss in a few other nice features of SQL2005 along the way.
&lt;h3&gt;Step-By-Step XML Namespace Output from SQL2005&lt;/h3&gt;
&lt;p&gt;All you need to do is to build your query as you normally would. First, the simple SELECT statement to get the data you need:

select * from pubs.dbo.authors

&lt;p&gt;Next you need to tell SQL Server to return an XML version of the data:

&lt;pre&gt;
select * from pubs.dbo.authors
for xml raw, elements
&lt;/pre&gt;

&lt;p&gt;The above query is nice, but is lacking a very important item - the root element. This was a classic problem with SQL2000 - so much so that when Microsoft released the SqlXml assembly for .NET, they included a workaround method that allowed programmers to set the root on the client site. However, the release of SQL2005 gave the SQL team a chance to fix this omission. Now, all you need to do is add the ROOT keyword to your query like this:


&lt;pre&gt;
select * from pubs.dbo.authors
for xml raw, elements, root('RDF')
&lt;/pre&gt;

&lt;p&gt;One more thing. With the above format, each collection of fields is enclosed in an element called &amp;quot;row.&amp;quot; Not too creative, and not what we need. You can control the enclosing element name for each row by decorating the ROW keyword with a string name like this:

&lt;pre&gt;
select * from pubs.dbo.authors
for xml raw('Description'), elements, root('RDF')
&lt;/pre&gt;

&lt;p&gt;So far, so good. We have solid (valid) XML output, but no namespaces yet. here's the secret sauce built into SQL2005. you preface the query with a list of namespaces to include at part of the root element. It works like this:

&lt;pre&gt;
with xmlnamespaces 
(
'http://www.w3.org/1999/02/22-rdf-syntax-ns#' as rdf,
'http://www.amundsen.com/rdf/xmlp/1.0/' as xmlp
)
select * from pubs.dbo.authors
for xml raw('Description'), elements, root('RDF')
&lt;/pre&gt;

&lt;p&gt;Now the output includes xmlns elements in the root tag. That's good - we have namespaces now! However, we also need to decorate the various elements in the output with the proper namespace prefixes. That works like this:

&lt;pre&gt;
with xmlnamespaces 
(
    'http://www.w3.org/1999/02/22-rdf-syntax-ns#' as rdf,
    'http://www.amundsen.com/rdf/xmlp/1.0/' as xmlp
)
select
    au_id as 'xmlp:id', 
    au_fname as 'xmlp:firstname',
    au_lname as 'xmlp:lastname',
    phone as 'xmlp:phone',
    address as 'xmlp:address',
    city as 'xmlp:city',
    state as 'xmlp:state',
    zip as 'xmlp:zip'
    from pubs.dbo.authors
for xml raw('rdf:Description'), elements, root('rdf:RDF')
&lt;/pre&gt;

&lt;p&gt;Note that I also cleaned up the element names and added namespace designations
to the row and root elements.  Now the final output will include XML namespace
designations for each element as needed.
&lt;hr&gt;
&lt;h3&gt;Technorati Tags&lt;/h3&gt;
&lt;p&gt;I tag my posts for easy indexing at &lt;a href="http://www.technorati.com/"&gt;Technorati.com&lt;/a&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="http://www.technorati.com/xml/" rel=tag&gt;xml&lt;/a&gt;
&lt;li&gt;&lt;a href="http://www.technorati.com/sqlserver/" rel=tag&gt;sqlserver&lt;/a&gt;
&lt;li&gt;&lt;a href="http://www.technorati.com/microsoft/" rel=tag&gt;microsoft&lt;/a&gt;
&lt;li&gt;&lt;a href="http://www.technorati.com/ineta/" rel=tag&gt;ineta&lt;/a&gt;
&lt;/ul&gt;
&lt;hr&gt;&lt;img src="http://c.services.spaces.live.com/CollectionWebService/c.gif?cid=8579671301203983569&amp;page=RSS%3a+XML+Namespaces+and+SQL2005&amp;referrer=" width="1px" height="1px" border="0" alt=""&gt;&lt;img style="position:absolute" alt="" width="0px" height="0px" src="http://c.live.com/c.gif?NC=31263&amp;amp;NA=1149&amp;amp;PI=73329&amp;amp;RF=&amp;amp;DI=3919&amp;amp;PS=85545&amp;amp;TP=mikeamundsen.spaces.live.com&amp;amp;GT1=mikeamundsen"&gt;</description><category>Articles</category><comments>http://mikeamundsen.spaces.live.com/Blog/cns!77111D9765E07CD1!395.entry#comment</comments><guid isPermaLink="true">http://mikeamundsen.spaces.live.com/Blog/cns!77111D9765E07CD1!395.entry</guid><pubDate>Sat, 24 Jun 2006 22:59:06 GMT</pubDate><slash:comments>6</slash:comments><msn:type>blogentry</msn:type><live:type>blogentry</live:type><live:typelabel>Blog entry</live:typelabel><wfw:commentRss>http://mikeamundsen.spaces.live.com/blog/cns!77111D9765E07CD1!395/comments/feed.rss</wfw:commentRss><wfw:comment>http://mikeamundsen.spaces.live.com/Blog/cns!77111D9765E07CD1!395.entry#comment</wfw:comment><dcterms:modified>2006-06-24T22:59:06Z</dcterms:modified></item><item><title>Basic WS-Auth Example Using ASP.NET 2.0</title><link>http://mikeamundsen.spaces.live.com/Blog/cns!77111D9765E07CD1!390.entry</link><description>&lt;p&gt;
During one of my recent INETA talks, several people asked for examples of Web Service authentication.  Unfortunately, my talk did not include any. It's been a while, but I finally found some time to put together a simple example built using ASP.NET 2.0 and Visual Studio 2005.


&lt;blockquote&gt;
&lt;b&gt;NOTE&lt;/b&gt;: I've posted the ASP.NET 2.0/VS2005 project for this article in the Files section of the &lt;a href="http://groups.yahoo.com/group/mikeamundsen"&gt;MikeAmundsen Yahoo! Group&lt;/a&gt;.
&lt;/blockquote&gt;

&lt;h3&gt;The SOAP Header&lt;/h3&gt;
&lt;p&gt;
First, the way SOAP authentication works is by adding a special header to the SOAP message. This is a fundamental part of the SOAP message model; the ability to add an arbitrary number of custom headers to the message payload without changing the actually body of the message itself. In effect, we are adding 'out-of-band' content to the SOAP message. As long as both the sender and receiver understand the header items, everything works fine. 

&lt;p&gt;
For this example, I've created a simple SOAP header with two elements: username and password.  Using VS2005 and ASP.NET 2.0, I can create class that holds this data. This will be the authentication header for my secure SOAP messages.

&lt;p&gt;
Below is the entire class definition for the custom authentication header:

&lt;pre&gt;&lt;code&gt;
using System;
using System.Web.Services.Protocols;

public class AuthSoapHeader : SoapHeader
{
	public AuthSoapHeader(){}

    public string Username = string.Empty;
    public string Password = string.Empty;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
Note that my custom class &amp;quot;AuthSoapHeader&amp;quot; inherits from the SoapHeader class in the System.Web.Services.Protocols.  that's all I need for now. The magic of authentication comes later.

&lt;h3&gt;The Secure HelloWorld method&lt;/h3&gt;
&lt;p&gt;
Now, I'm ready to create a secure Web Service method. Using VS2005, I add a new Web Service class to my Web project. Since it already contains a sample HelloWorld method, I'll just modify that method to require a successful authentication before returning requested data.

&lt;p&gt;
Here's my service *before* adding the authentication header support:

&lt;pre&gt;&lt;code&gt;
public class StandardService : System.Web.Services.WebService {

    public StandardService () {}

    [WebMethod]
    public string HelloWorld() 
    {
        return &amp;quot;Hello World&amp;quot;;
    }
    
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
And here's my service *after* adding the authentication header support.

&lt;pre&gt;&lt;code&gt;
public class SecureService : WebService 
{
    public AuthSoapHeader AuthHeader;
    
    public SecureService (){}

    [WebMethod]
    [SoapHeader(&amp;quot;AuthHeader&amp;quot;)]
    public string HelloWorld() 
    {
        try
        {
            if (AuthHeader.Username == &amp;quot;hello&amp;quot; &amp;amp;&amp;amp; AuthHeader.Password == &amp;quot;world&amp;quot;)
                return &amp;quot;Hello World&amp;quot;;   // it's all good
            else
                return &amp;quot;unauthorized access&amp;quot;; // auth failed
        }
        catch (Exception ex)
        {
            // build and return soap exception
            XmlQualifiedName code = new XmlQualifiedName(&amp;quot;hello-world&amp;quot;);
            SoapException soapEx = new SoapException(ex.Message, code);
            throw soapEx;
        }
    }
    
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
Note that all I needed to do is add a public instance of the header class, then add an attribute reference that tells the ASP.NET runtime to expect a header.  Finally, I add some code to check the contents of the header and act accordingly. If the header is missing, I throw a SOAP exception back to the caller.

&lt;p&gt;
That's all there is to it. I now have a service that requires authentication. Next I need to create an ASP.NET WebForm that calls this service using the header.


&lt;h3&gt;Calling the Secure Service from a WebForm&lt;/h3&gt;
&lt;p&gt;
Creating an ASP.NET WebForm that calls the secure Web service is not much different from creating a WebForm that calls a standard, unsecured, Web service. After using VS2005 to add a Web Reference, I'm ready to get an instance of the remote SOAP header and Web service class.

&lt;p&gt;
I created a simple form with Username and Password input controls, a button, and a label to hold the results of the WS call. Below is the code that runs behind the button click event:

&lt;pre&gt;&lt;code&gt;
protected void login_Click(object sender, EventArgs e)
{
    // define locals
    string uname = string.Empty;
    string pword = string.Empty;
    string results = string.Empty;

    // get user inputs
    uname = username.Text;
    pword = password.Text;

    // get refs to remote objects
    wsSecureService.SecureService wsService = new wsSecureService.SecureService();
    wsSecureService.AuthSoapHeader wsHeader = new wsSecureService.AuthSoapHeader();

    // populate header and attach to service object
    wsHeader.Password = pword;
    wsHeader.Username = uname;
    wsService.AuthSoapHeaderValue = wsHeader;

    // get results
    results = wsService.HelloWorld();
    showresults.Text = results;

}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
Notice that way to call a secured Web service is to first get an instance of any required headers, fill them out as needed and then attach these headers to the usual SOAP object before making the service call.  As long as the headers are populated correctly, the call will complete as usual.

&lt;p&gt;
There's nothing more to it. Now you know how to add custom authentication headers to any Web Service.
&lt;p&gt;
&lt;hr&gt;
&lt;h3&gt;Technorati Tags&lt;/h3&gt;
&lt;p&gt;I tag my posts for easy indexing at &lt;a href="http://www.technorati.com/"&gt;Technorati.com&lt;/a&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="http://www.technorati.com/tag/ASPNET" rel=tag&gt;ASPNET&lt;/a&gt;
&lt;li&gt;&lt;a href="http://www.technorati.com/tag/WS" rel=tag&gt;WS&lt;/a&gt;
&lt;li&gt;&lt;a href="http://www.technorati.com/tag/MSFT" rel=tag&gt;MSFT&lt;/a&gt;
&lt;/ul&gt;
&lt;hr&gt;&lt;img src="http://c.services.spaces.live.com/CollectionWebService/c.gif?cid=8579671301203983569&amp;page=RSS%3a+Basic+WS-Auth+Example+Using+ASP.NET+2.0&amp;referrer=" width="1px" height="1px" border="0" alt=""&gt;&lt;img style="position:absolute" alt="" width="0px" height="0px" src="http://c.live.com/c.gif?NC=31263&amp;amp;NA=1149&amp;amp;PI=73329&amp;amp;RF=&amp;amp;DI=3919&amp;amp;PS=85545&amp;amp;TP=mikeamundsen.spaces.live.com&amp;amp;GT1=mikeamundsen"&gt;</description><category>Articles</category><comments>http://mikeamundsen.spaces.live.com/Blog/cns!77111D9765E07CD1!390.entry#comment</comments><guid isPermaLink="true">http://mikeamundsen.spaces.live.com/Blog/cns!77111D9765E07CD1!390.entry</guid><pubDate>Tue, 25 Apr 2006 02:05:12 GMT</pubDate><slash:comments>4</slash:comments><msn:type>blogentry</msn:type><live:type>blogentry</live:type><live:typelabel>Blog entry</live:typelabel><wfw:commentRss>http://mikeamundsen.spaces.live.com/blog/cns!77111D9765E07CD1!390/comments/feed.rss</wfw:commentRss><wfw:comment>http://mikeamundsen.spaces.live.com/Blog/cns!77111D9765E07CD1!390.entry#comment</wfw:comment><dcterms:modified>2006-04-25T02:05:12Z</dcterms:modified></item><item><title>Speaking in Chattanooga April 11, 2006</title><link>http://mikeamundsen.spaces.live.com/Blog/cns!77111D9765E07CD1!386.entry</link><description>&lt;p&gt;I'm looking forward to my trip to Chattanooga, TN on April 11th to deliver another INETA-sponsored talk. The Chattanooga Area .NET User Group (&lt;a href="http://www.chadnug.org/"&gt;CHADNUG&lt;/a&gt;) has selected my Message-Oriented Architecture (MOA) topic for the event. This is currently the most-requested talk in my list and I really enjoy delivering it.
I have been giving some variation on this talk for close to two years. I update it often and
this year is no exception. This time, I'll be adding a section on supporting Ajax-enabled web clients.
&lt;p&gt;If you are close Chattanooga, check out thier web site and come on out to the event. I look forward to seeing everyone there!

&lt;hr&gt;
&lt;h3&gt;Technorati Tags&lt;/h3&gt;
&lt;p&gt;I tag my posts for easy indexing at &lt;a href="http://www.technorati.com/"&gt;Technorati.com&lt;/a&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="http://www.technorati.com/tag/INETA" rel=tag&gt;INETA&lt;/a&gt;
&lt;li&gt;&lt;a href="http://www.technorati.com/tag/programming" rel=tag&gt;Programming&lt;/a&gt;
&lt;li&gt;&lt;a href="http://www.technorati.com/tag/MOA" rel=tag&gt;MOA&lt;/a&gt;
&lt;/ul&gt;
&lt;hr&gt;&lt;img src="http://c.services.spaces.live.com/CollectionWebService/c.gif?cid=8579671301203983569&amp;page=RSS%3a+Speaking+in+Chattanooga+April+11%2c+2006&amp;referrer=" width="1px" height="1px" border="0" alt=""&gt;&lt;img style="position:absolute" alt="" width="0px" height="0px" src="http://c.live.com/c.gif?NC=31263&amp;amp;NA=1149&amp;amp;PI=73329&amp;amp;RF=&amp;amp;DI=3919&amp;amp;PS=85545&amp;amp;TP=mikeamundsen.spaces.live.com&amp;amp;GT1=mikeamundsen"&gt;</description><category>News</category><comments>http://mikeamundsen.spaces.live.com/Blog/cns!77111D9765E07CD1!386.entry#comment</comments><guid isPermaLink="true">http://mikeamundsen.spaces.live.com/Blog/cns!77111D9765E07CD1!386.entry</guid><pubDate>Thu, 06 Apr 2006 07:01:33 GMT</pubDate><slash:comments>0</slash:comments><msn:type>blogentry</msn:type><live:type>blogentry</live:type><live:typelabel>Blog entry</live:typelabel><wfw:commentRss>http://mikeamundsen.spaces.live.com/blog/cns!77111D9765E07CD1!386/comments/feed.rss</wfw:commentRss><wfw:comment>http://mikeamundsen.spaces.live.com/Blog/cns!77111D9765E07CD1!386.entry#comment</wfw:comment><dcterms:modified>2006-04-06T07:01:33Z</dcterms:modified></item><item><title>Wanted: URI Designer</title><link>http://mikeamundsen.spaces.live.com/Blog/cns!77111D9765E07CD1!385.entry</link><description>&lt;p&gt;
Some regular readers know that, as part of my ongoing personal project to 
improve my ‘web-tech’ knowledge, I have been re-reading Tim Berners-Lee’s &amp;quot;&lt;a href="http://www.w3.org/Provider/Style/URI"&gt;Style 
Guide for online hypertext&lt;/a&gt;&amp;quot; and other related materials. One of the common 
messages from documents on this topic is the importance of well-composed and 
maintained web addresses or URIs. This got me thinking about (and paying more 
attention to) the common web addresses that I see in my browser. I must say, I 
don’t care much for what I see.


&lt;h3&gt;What’s bad about common URIs today?&lt;/h3&gt;
&lt;p&gt;
Too often the URI I see in my browser address line is gibberish. Just try 
visiting any of the top news sites (news.yahoo.com, www.msn.com, news.google.com, 
etc.) and click on any of the links on the page. Usually the URI contains 
additional state information (?x=13&amp;amp;y=29&amp;amp;_docid=DUsor93FH). Almost always, the 
URI contains company or technology-specific information (page.aspx, document.jsp, 
article.cfm, product.php). And almost never could I share the web address with a 
friend by merely speaking it. This is all bad.


&lt;h3&gt;So what is the definition of a good URI?&lt;/h3&gt;
&lt;p&gt;
The W3C has an excellent document called &amp;quot;&lt;a href="http://www.w3.org/TR/chips/"&gt;Common 
HTTP Implementation Problems&lt;/a&gt;&amp;quot;. In it there is a section devoted to &amp;quot;&lt;a href="http://www.w3.org/TR/chips/#uri"&gt;Understanding 
URIs&lt;/a&gt;.&amp;quot; This section reads like a ‘best practices’ list for creating solid 
URIs. I urge everyone to take a few minutes to read though it and to bookmark it 
for future reference. I’ll lift two quotes from that document to clarify the 
need for good URI design when deploying web applications.

&lt;p&gt;
Here’s the first quote: &lt;i&gt;&amp;quot;A URI is a reference to a resource, with fixed and 
independent semantics.&amp;quot;&lt;/i&gt; This sentence has quite a bit packed into it. For 
example:

&lt;ul&gt;
    &lt;li&gt;
        &lt;i&gt;&amp;quot;A URI is a reference...&amp;quot;&lt;/i&gt; In other words while a URI points to something, 
        that URI is not a serial number.
    
    &lt;li&gt;
        &lt;i&gt;&amp;quot;...with fixed [semantics]...&amp;quot;&lt;/i&gt; This means that the URI does not change 
        over time. Changing a URI breaks other people’s links to that resource – bad 
        stuff.
    
    &lt;li&gt;
        &lt;i&gt;&amp;quot;...with independent semantics.&amp;quot;&lt;/i&gt; This means that the URI stands alone. It 
        does not depend on state information such as cookies or session state.
    
&lt;/ul&gt;
&lt;p&gt;
So, a URI is a pointer so something. That pointer never goes bad, and that 
pointer stands alone (or, put another way, is easily shared).

&lt;p&gt;
Here’s the second quote: &lt;i&gt;&amp;quot;A common mistake … is to think [that a URI] is 
equivalent to a filename within a computer system. This is wrong. URIs have, 
conceptually, nothing to do with a file system.&amp;quot;&lt;/i&gt; This might come as a shock 
to some web programmers. It is so easy to expose physical folders and files via 
a web server that, by default, most web sites simply reflect the file structure 
behind a web domain root. This, too, is bad stuff. Move a file, and the URI 
breaks.

&lt;p&gt;
Ok, so URIs are non-changing, stand-alone pointers and *not* reflections of 
folder and file structures on disk. Maybe we do need a URI design!

&lt;h3&gt;URIs are web queries&lt;/h3&gt;
&lt;p&gt;
Once you get over the idea that URIs are not physical files in folders, you are 
free to start thinking about what URIs really represent. In my mind, a URI is a 
‘web query.’ By typing a URI, users are ‘looking for something’ out on the 
Internet. By now, most web users understand that there are up to three parts to 
a URI query:

&lt;ul&gt;
    &lt;li&gt;
        the server name or domain (www.someserver.com)
    
    &lt;li&gt;
        the folder name or location (/articles/2006/)
    
    &lt;li&gt;
        the document name (learning_to_program.html)
    
&lt;/ul&gt;
&lt;p&gt;
I suspect that most users do not think very much about the above details, but 
most intuit them as they surf. I am often especially surprised by the 
sophistication of young web surfers. I have observed children who are quite 
happy to ‘hack’ away at a URI in order to find a document. They truly use the 
browser address line as a search tool!

&lt;p&gt;
Anyway, if you accept the idea that a URI is (in some fashion) a web query, then 
you are free to actively *design* the URIs for your web application to support 
this kind of use. To paraphrase the words of Tim Berners-Lee, you can make your 
URIs ‘hack-able.’


&lt;h3&gt;Creating hackable URIs&lt;/h3&gt;
&lt;p&gt;
What’s a hackable URI? In its simplest form, it’s a URI that can be easily 
modified by a user in order to get a valid result from the same server. The most 
common way to think about hackable URIs is to make sure that all sub-parts of 
the URI return a valid document. As an example, the URI &amp;quot;www.myserver.com/content/programming/tutorials/hackable_uris.html&amp;quot; 
has several sub-parts. Users who 'land' at this location should be able to 'lop 
off' parts of the URI and get helpful results. That means that the URI &amp;quot;www.myserver.com/content/programming/tutorials/&amp;quot; 
should return something – maybe a page that lists all tutorials. And &amp;quot;www.myserver.com/content/programming/&amp;quot; 
might return a list of programming article classes such as &amp;quot;tutorials,&amp;quot; &amp;quot;reference,&amp;quot; 
&amp;quot;bookreviews,&amp;quot; etc. And so on.

&lt;p&gt;
But creating hackable URIs doesn’t mean just supporting sub-parts. It could also 
mean using a user-friendly URI scheme that actually *invites* URI hacking. For 
example, what can you assume if you land at a URI that looked like this?

&lt;code&gt;www.contentserver.com/archives/2005/11/03/dailyupdate.html&lt;/code&gt;
&lt;p&gt;
Not only can you assume that you can get valid documents at each sub part. You 
can also assume that you can change the value of some sub-parts to discover new 
documents, right?


&lt;h3&gt;So how do you implement a URI design?&lt;/h3&gt;
&lt;p&gt;
Once you start thinking about a URI design pattern that works for your site, you 
need to come up with a way to implement it. In the past, web programmers would 
start creating folders and files to match the stated design. This is not the way 
to go about it. Instead, web programmers should design a server-side scripts 
that can scan the incoming URI, treat it as a request query and assemble a 
response accordingly.

&lt;p&gt;
For example, given the following query:

&lt;code&gt;www.authorserver.com/fiction/poetry/&lt;/code&gt;

&lt;p&gt;
A server might return a list of poets. Users might also assume that they can get 
lists of other authors by changing the URI like this:

&lt;code&gt;www.authorserver.com/fiction/shortstories/&lt;br&gt;
www.authorserver.com/fiction/novels/&lt;/code&gt;

&lt;p&gt;
The point is that web servers should be able to do more than just serve up 
documents from a physical folder tree.

&lt;p&gt;
One way to do this (using ASP.NET, for example) is to use the Uri.Segments 
collection to inspect and parse the URI. Here’s a trivial example.

&lt;p&gt;
Given the URI www.server.com/archives/2005/12/ a server could create 
a query against a database table called &amp;quot;archives&amp;quot; for a list of documents added 
to the system in December of 2005.

&lt;p&gt;
Here’s some code to parse the URI:

&lt;code&gt;
&lt;pre&gt;&amp;lt;%@ page %&amp;gt;
&amp;lt;script runat=&amp;quot;server&amp;quot; language=&amp;quot;c#&amp;quot;&amp;gt;

    void Page_Load(object sender, EventArgs args)
    {
        string webaddress = &amp;quot;http://www.server.com/archives/2005/12/&amp;quot;;
        Uri thisUri = new Uri(webaddress);
        int segcount = thisUri.Segments.Length;
        string output = string.Empty;

        output = string.Format(&amp;quot;&amp;lt;p&amp;gt;webaddress:&amp;lt;br /&amp;gt;{0}&amp;lt;/p&amp;gt;&amp;quot;,webaddress);

        // get segments
        output +=string.Format(&amp;quot;&amp;lt;p&amp;gt;segments:&amp;lt;br/&amp;gt;&amp;quot;);
        for(int i=0;i&amp;lt;segcount;i++)
            output+=string.Format(&amp;quot;{0}: {1}&amp;lt;br /&amp;gt;&amp;quot;,i,thisUri.Segments[i]);
        output +=&amp;quot;&amp;lt;/p&amp;gt;&amp;quot;;

        // format data query
        string table = thisUri.Segments[1].Replace(&amp;quot;/&amp;quot;,&amp;quot;&amp;quot;);
        string yr = thisUri.Segments[2].Replace(&amp;quot;/&amp;quot;,&amp;quot;&amp;quot;);
        string mo = thisUri.Segments[3].Replace(&amp;quot;/&amp;quot;,&amp;quot;&amp;quot;);
        string query = &amp;quot;select * from {0} where yr={1} and mo={2}&amp;quot;;

        output+=string.Format(&amp;quot;&amp;lt;p&amp;gt;query:&amp;lt;br /&amp;gt;&amp;quot;+query+&amp;quot;&amp;lt;/p&amp;gt;&amp;quot;,table,yr,mo);

        // show results
        Response.Write(output);
    }

&amp;lt;/script&amp;gt;
&lt;/pre&gt;
&lt;/code&gt;
&lt;p&gt;
And here’s the output created by the above code:

&lt;code&gt;&lt;pre&gt;
webaddress:
http://www.server.com/archives/2005/12/
segments:
0: /
1: archives/
2: 2005/
3: 12/
4: 31/
query:
select * from archives where yr=2005 and mo=12
&lt;/pre&gt;&lt;/code&gt;
&lt;p&gt;
Submitting the above query might return a data set that could be formatted into
an HTML page containing a series of links for the user to explore.


&lt;h3&gt;OK, I get the idea, but there’s more to it, right?&lt;/h3&gt;
&lt;p&gt;
Well, yes. Knowing that URIs are static, independent resource pointers that
should be ‘hackable’ by users and that ASP.NET has features that allow you to
parse URIs into parts that can be used to create data queries is just the
beginning. But you can use this information to create a more flexible and long-
lived URI design for web apps. And with a URI design in place, you are no long
dependent on the existence (or lack there-of) of physical documents within your
web.

&lt;p&gt;
There are also a number of other operations needed to support a good URI design.
While good URIs don’t change, content does. Well-implemented URI responders will
need to handle moved documents (HTTP 301 and 302 events) through a lookup table
or some other means. Also, once you start to train users to ‘hack’ URIs at your
server, you’ll need to add improved support for 4xx (not found) and possibly 5xx
(server error) events to tell users when their creative URIs fail.

&lt;p&gt;
In a future article, I’ll outline a URI design that I’ve been contemplating for
some time. I also plan to share my implementation for this new URI design
sometime soon. But don’t wait for me. Start designing and implementing your own 
backable URIs!

&lt;hr&gt;
&lt;h3&gt;Technorati Tags&lt;/h3&gt;
&lt;p&gt;I tag my posts for easy indexing at &lt;a href="http://www.technorati.com/"&gt;Technorati.com&lt;/a&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="http://www.technorati.com/tag/ASPNET" rel=tag&gt;ASP.NET&lt;/a&gt;
&lt;li&gt;&lt;a href="http://www.technorati.com/tag/HTTP" rel=tag&gt;HTTP&lt;/a&gt;
&lt;li&gt;&lt;a href="http://www.technorati.com/tag/Web" rel=tag&gt;Web&lt;/a&gt;
&lt;/ul&gt;
&lt;hr&gt;&lt;img src="http://c.services.spaces.live.com/CollectionWebService/c.gif?cid=8579671301203983569&amp;page=RSS%3a+Wanted%3a+URI+Designer&amp;referrer=" width="1px" height="1px" border="0" alt=""&gt;&lt;img style="position:absolute" alt="" width="0px" height="0px" src="http://c.live.com/c.gif?NC=31263&amp;amp;NA=1149&amp;amp;PI=73329&amp;amp;RF=&amp;amp;DI=3919&amp;amp;PS=85545&amp;amp;TP=mikeamundsen.spaces.live.com&amp;amp;GT1=mikeamundsen"&gt;</description><category>Articles</category><comments>http://mikeamundsen.spaces.live.com/Blog/cns!77111D9765E07CD1!385.entry#comment</comments><guid isPermaLink="true">http://mikeamundsen.spaces.live.com/Blog/cns!77111D9765E07CD1!385.entry</guid><pubDate>Thu, 06 Apr 2006 06:52:08 GMT</pubDate><slash:comments>5</slash:comments><msn:type>blogentry</msn:type><live:type>blogentry</live:type><live:typelabel>Blog entry</live:typelabel><wfw:commentRss>http://mikeamundsen.spaces.live.com/blog/cns!77111D9765E07CD1!385/comments/feed.rss</wfw:commentRss><wfw:comment>http://mikeamundsen.spaces.live.com/Blog/cns!77111D9765E07CD1!385.entry#comment</wfw:comment><dcterms:modified>2006-04-06T06:52:08Z</dcterms:modified></item><item><title>CINNUG Event Re-Scheduled for March 28th</title><link>http://mikeamundsen.spaces.live.com/Blog/cns!77111D9765E07CD1!383.entry</link><description>&lt;p&gt;I'm happy to announce that my 'Formula for Web 2.0' talk for the Cincinnati .NET User Group (&lt;a href="http://cinnug.org/"&gt;CINNUG&lt;/a&gt;)  that was postponed due to unseasonable snow last Tuesday has been rescheduled for this coming Tuesday, March 28th.
&lt;p&gt;I'll be presenting at 6PM at the MaxTrain offices in Cincinnati, Ohio. Check out the &lt;a href="http://cinnug.org/"&gt;CINNUG.ORG web site&lt;/a&gt; for details.
&lt;hr&gt;
&lt;h3&gt;Technorati Tags&lt;/h3&gt;
&lt;p&gt;I tag my posts for easy indexing at &lt;a href="http://www.technorati.com/"&gt;Technorati.com&lt;/a&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="http://www.technorati.com/tag/INETA" rel=tag&gt;INETA&lt;/a&gt;
&lt;li&gt;&lt;a href="http://www.technorati.com/tag/Cincinnati" rel=tag&gt;Cincinnati&lt;/a&gt;
&lt;li&gt;&lt;a href="http://www.technorati.com/tag/Amundsen" rel=tag&gt;Amundsen&lt;/a&gt;
&lt;li&gt;&lt;a href="http://www.technorati.com/tag/web20" rel=tag&gt;Web 2.0&lt;/a&gt;
&lt;/ul&gt;
&lt;hr&gt;&lt;img src="http://c.services.spaces.live.com/CollectionWebService/c.gif?cid=8579671301203983569&amp;page=RSS%3a+CINNUG+Event+Re-Scheduled+for+March+28th&amp;referrer=" width="1px" height="1px" border="0" alt=""&gt;&lt;img style="position:absolute" alt="" width="0px" height="0px" src="http://c.live.com/c.gif?NC=31263&amp;amp;NA=1149&amp;amp;PI=73329&amp;amp;RF=&amp;amp;DI=3919&amp;amp;PS=85545&amp;amp;TP=mikeamundsen.spaces.live.com&amp;amp;GT1=mikeamundsen"&gt;</description><category>News</category><comments>http://mikeamundsen.spaces.live.com/Blog/cns!77111D9765E07CD1!383.entry#comment</comments><guid isPermaLink="true">http://mikeamundsen.spaces.live.com/Blog/cns!77111D9765E07CD1!383.entry</guid><pubDate>Sun, 26 Mar 2006 17:45:49 GMT</pubDate><slash:comments>0</slash:comments><msn:type>blogentry</msn:type><live:type>blogentry</live:type><live:typelabel>Blog entry</live:typelabel><wfw:commentRss>http://mikeamundsen.spaces.live.com/blog/cns!77111D9765E07CD1!383/comments/feed.rss</wfw:commentRss><wfw:comment>http://mikeamundsen.spaces.live.com/Blog/cns!77111D9765E07CD1!383.entry#comment</wfw:comment><dcterms:modified>2006-03-26T23:56:43Z</dcterms:modified></item><item><title>Forcing XHTML-compliance  with ASP.NET Response Filters</title><link>http://mikeamundsen.spaces.live.com/Blog/cns!77111D9765E07CD1!356.entry</link><description>&lt;p&gt;As part of my ongoing effort to build Web solutions that, by default, support open standards, I have committed to only emitting XHTML-compliant markup on all pages. While some of this will require rewriting static pages, the bigger task will be to ensure XHTML output for generated pages  usually the ones generated by ASP.NET from database queries.This means I need a process for scanning the markup before final output to the client. If anything is non-XHTML, I need to either correct it automatically or refuse to output it. While the last option is a bit harsh, its worth considering. If the output is wrong, dont allow it.

&lt;p&gt;
&lt;i&gt;NOTE: You can download the stand-alone C# source code for the TidyFilter class from &lt;a href="http://groups.yahoo.com/group/mikeamundsen"&gt;http://groups.yahoo.com/group/mikeamundsen&lt;/a&gt;. You need to register to access the downloads.&lt;/i&gt;


&lt;h3&gt;Response.Filter or HTTPModule?&lt;/h3&gt;
&lt;p&gt;
The real work is to hook into ASP.NET somewhere and scan the markup before final output to the client. There are a couple possibilities: HTTPModules and Response Filters. I recently started experimenting with using ASP.NET response filters to control output to the client. They are easy to install (much easier than setting up an HTTPModule) and provide quite a bit of flexibility. 

&lt;p&gt;
There are a number of resources on the Internet covering the pros and cons of HTTPModules or Response.Filters. The biggest tipping point IMHO is that HTTPModules can be implemented as stand-alone filters that can be easily plugged-in to any existing ASP.NET application. Of course, to do this, you need to set up some config items and, in some rare cases, need to be aware of other HTTPModules in the pipeline and how your module will be affected.

&lt;p&gt;
Reponse.Filters on the other hand are very simple. Basically, you implement a Stream object, write some rules on inspecting and modifying the stream as is goes by, and then hook this stream into the Response object when needed. Its more of an inline solution, IMHO.  One that works well when you want to integrate the filter right into the compiled solution instead of making it a plug-able component like HTTPModules.

&lt;h3&gt;Writing a Response.Filter Stream&lt;/h3&gt;
&lt;p&gt;
Since I want to make XHTML-compliance a fundamental part of my Web solutions, Ive decided to implement my filter as a Response.Filter stream instead of an HTTPModule. That means I need to write a stream object that can scan for outgoing markup and, if needed, modify the output or refuse to deliver it to the client. Its actually a pretty simple operation.

&lt;p&gt;
As mentioned above, Response.Filters are really just stream objects with a bit of smarts.  Implementing a stream object requires just a small bit of code. Below is a basic stream object that does nothing (yet).

&lt;p&gt;
&lt;code&gt;&lt;/code&gt;&lt;pre&gt;using System;
using System.Web;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;

namespace amundsen.xmlp
{
	public class TidyFilter : Stream
	{
		private Stream _sink;
		private long _position;
		StringBuilder sb;

		public TidyFilter()
		{
		}

		public TidyFilter(Stream sink)
		{
			_sink = sink;
			sb = new StringBuilder();
		}

		public override bool CanRead
		{
			get {return true;}
		}

		public override bool CanSeek
		{
			get {return true;}
		}

		public override bool CanWrite
		{
			get {return true;}
		}

		public override void Close()
		{
			_sink.Close();
		}

		public override void Flush()
		{
			_sink.Flush();
		}

		public override long Length
		{
			get {return 0;}
		}

		public override long Position
		{
			get {return _position;}
			set {_position = value;}
		}

		public override void SetLength(long length)
		{
			_sink.SetLength(length);
		}

		public override long Seek(long offset, System.IO.SeekOrigin direction)
		{
			return _sink.Seek(offset, direction);
		}

		public override int Read(byte[] buffer, int offset, int count)
		{
			return _sink.Read(buffer, offset, count);
		}

		public override void Write(byte[] buffer, int offset, int count)
		{
			_sink.Write(buffer, offset, count);
		}
	}
}&lt;/pre&gt;
&lt;p&gt;
Youll notice that the real work is done in the Write method. Currently, this class only passes the contents in the read buffer out to the write buffer without change. Its in the Write method that Ill addcode to look for any markup being sent out and do some magic to make sure its XHTML-compliant.

&lt;p&gt;
Before getting to the XHTML filtering part, its worth noting how Ill hook up my stream object to the ASP.NET Response stream. The process is very easy. Below is a bit of code I have in my HTTPHandler that processing outgoing requests:

&lt;code&gt;
context.Response.Filter = new TidyFilter(context.Response.Filter);
&lt;/code&gt;
&lt;p&gt;
Notice that I get an instance of my stream object, pass in the current Response.Filter pointer, and add that to any existing Reponse.Filters currently running.  Nothing complex here.  My stream object is now part of the ASP.NET response process.  All I need now is the ability to force XHTML-compliance on any HTML in the outgoing stream.

&lt;h3&gt;Using HTMLTidy to filter outgoing markup.&lt;/h3&gt;
&lt;p&gt;
Rather than slave over some complicated regexp or other routines to try to inspect and alter markup as it goes by, I decided to use a very powerful existing utility that already does all that  HTMLTidy. You can &lt;a href="http://tidy.sourceforge.net/"&gt;download the open source HTMLTidy project for free&lt;/a&gt;. Although it was originally built for the Unix/Linux platform, there is a very solid Win32 implementation available at the site. There is also &lt;a href="http://users.rcn.com/creitzel/tidy.html"&gt;a nice .NET binding for HTMLTidy implementation&lt;/a&gt; that makes it easy to use HTMLTidy as part of any ASP.NET application. The details on this binding set includes instructions on registering the DLL and assemblies to make them easily available within a .NET project.

&lt;p&gt;
Once I have the HTMLTidy library and .NET bindings installed and registered, theres the small matter of accessing HTMLTidy within my Filter stream at runtime. This is all done in the Write method of my stream.  Below is the complete code block I added to the Write method.

&lt;p&gt;
&lt;code&gt;&lt;/code&gt;&lt;pre&gt;public override void Write(byte[] buffer, int offset, int count)
{
	if (HttpContext.Current.Response.ContentType.ToLower().IndexOf(&amp;quot;html&amp;quot;) == -1)
		_sink.Write(buffer, offset, count);
	else
	{
		try
		{
			string inbuf = System.Text.UTF8Encoding.UTF8.GetString(buffer, offset, unt);

			Regex eof = new Regex(&amp;quot;&amp;lt;/html&amp;gt;&amp;quot;, RegexOptions.IgnoreCase);

			if (!eof.IsMatch(inbuf))
			{
				sb.Append(inbuf);
			}
			else
			{
				sb.Append(inbuf);
				string work = sb.ToString();
				Regex notidy = new Regex(&amp;quot;&amp;lt;tidyfilter='false'--&amp;gt;&amp;quot;,RegexOptions.IgnoreCase);
				if (!notidy.IsMatch(work))
				{
					string tidyconfig = &amp;quot;&amp;quot;;
					string tidyerrors = &amp;quot;&amp;quot;;
					tidyconfig = HttpUtilities.GetConfigValue(&amp;quot;tidyconfig&amp;quot;);
					tidyconfig = HttpContext.Current.Server.MapPath(tidyconfig);
					if (tidyconfig.Length == 0)
						return;

					tidyerrors = HttpUtilities.GetConfigValue(&amp;quot;tidyerrors&amp;quot;);
					tidyerrors = HttpContext.Current.Server.MapPath(tidyerrors);

					Tidy.DocumentClass tdoc = new Tidy.DocumentClass();

					tdoc.LoadConfig(tidyconfig);
					tdoc.SetErrorFile(tidyerrors);
					tdoc.ParseString(work);
					tdoc.CleanAndRepair();
					tdoc.RunDiagnostics();
					work = tdoc.SaveString();
				}

				byte[] outbuf = System.Text.UTF7Encoding.UTF8.GetBytes(work);
				_sink.Write(outbuf, 0, outbuf.GetLength(0));
			}
		}
		catch (Exception ex)
		{
			byte[] errbuf = System.Text.UTF7Encoding.UTF8.GetBytes(&lt;br&gt;				&amp;quot;&amp;lt;html&amp;gt;&amp;lt;body&amp;gt;&amp;lt;h2&amp;gt;xmlp&amp;lt;/h2&amp;gt;&amp;lt;h3&amp;gt;TidyFilter&amp;lt;/h3&amp;gt;&amp;quot; + &lt;br&gt;				ex.Message + &lt;br&gt;				&amp;quot;&amp;lt;/body&amp;gt;&amp;lt;/html&amp;gt;&amp;quot;);
			_sink.Write(errbuf, 0, errbuf.GetLength(0));
		}
	}
}&lt;/pre&gt;
&lt;p&gt;
You can see in the code above that I first check the content-type header to see if the stream passing through is part of an HTML document sent from the server. If it is an HTML document, I add the string contents of the in-bound buffer to a string object for later review. Once Ive reached the end of the html document (&amp;lt;/html&amp;gt;), I am ready to inspect the document for XHTML-compliance using the HTMLTidy library.

&lt;p&gt;
I have a little html-comment trick to allow me to tell the filter to ignore the tidy filter, but if thats not in the document, the code will check a couple configuration settings (I use an internal routine to get the config value, but you can use the standard AppSettings[key] collection if you like) and then load the HTMLTidy library and scan the completed document. Once the work is done, HTMLTidy can return me the resulting markup as a string and then I write that string to the out-bound buffer.

&lt;p&gt;
Thats all there is to it. Now I have a way to ensure XHTML-compliant markup for all my pages.

&lt;h3&gt;Some caveats&lt;/h3&gt;
&lt;p&gt;
Of course, there are some downsides to allthis. First, you need to install and use HTMLTidy (or some other code base) to inspect all outgoing markup. Second, most utilities for scanning markup need access to the entire document. That means you need to load the document into memory, scan it and output it. This is fine for relatively small documents, but large ones could chew up memory and slow the response-time of your application. 

&lt;p&gt;
Finally, while HTMLTidy is good, its not perfect. Once you start using a tool like this you start to see the challenge in validating markup documents. Especially ones that include HTML, XML, client-side script, embedded CSS, and more. Ive used HTMLTidy enough to know that its best to keep CSS and Javascript to a minimum in the page and to use external references to other documents when possible. Youre mileage may vary[grin].

&lt;h3&gt;Summary&lt;/h3&gt;
&lt;p&gt;
XHTML-compliant markup is the first step in building solid, standards-compliant Web solutions. ASP.NET has options for building and installing output filters that can inspect outgoing markup and modify the output as needed. HTMLTidy is a very solid open source library that provides the details of XHTML-compliance including a .NET binding for easy use within ASP.NET.&lt;hr&gt;
&lt;p&gt;
&lt;h3&gt;Technorati Tags&lt;/h3&gt;
&lt;p&gt;I tag my posts for easy indexing at &lt;a href="http://www.technorati.com/"&gt;Technorati.com&lt;/a&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="http://www.technorati.com/tag/programming" rel=tag&gt;programmming&lt;/a&gt;
&lt;li&gt;&lt;a href="http://www.technorati.com/tag/ASPNET" rel=tag&gt;ASPNET&lt;/a&gt;
&lt;li&gt;&lt;a href="http://www.technorati.com/tag/C#" rel=tag&gt;C#&lt;/a&gt;
&lt;li&gt;&lt;a href="http://www.technorati.com/tag/XHTML" rel=tag&gt;XHTML&lt;/a&gt;
&lt;/ul&gt;
&lt;hr&gt;&lt;img src="http://c.services.spaces.live.com/CollectionWebService/c.gif?cid=8579671301203983569&amp;page=RSS%3a+Forcing+XHTML-compliance++with+ASP.NET+Response+Filters&amp;referrer=" width="1px" height="1px" border="0" alt=""&gt;&lt;img style="position:absolute" alt="" width="0px" height="0px" src="http://c.live.com/c.gif?NC=31263&amp;amp;NA=1149&amp;amp;PI=73329&amp;amp;RF=&amp;amp;DI=3919&amp;amp;PS=85545&amp;amp;TP=mikeamundsen.spaces.live.com&amp;amp;GT1=mikeamundsen"&gt;</description><category>Articles</category><comments>http://mikeamundsen.spaces.live.com/Blog/cns!77111D9765E07CD1!356.entry#comment</comments><guid isPermaLink="true">http://mikeamundsen.spaces.live.com/Blog/cns!77111D9765E07CD1!356.entry</guid><pubDate>Mon, 20 Mar 2006 00:52:08 GMT</pubDate><slash:comments>6</slash:comments><msn:type>blogentry</msn:type><live:type>blogentry</live:type><live:typelabel>Blog entry</live:typelabel><wfw:commentRss>http://mikeamundsen.spaces.live.com/blog/cns!77111D9765E07CD1!356/comments/feed.rss</wfw:commentRss><wfw:comment>http://mikeamundsen.spaces.live.com/Blog/cns!77111D9765E07CD1!356.entry#comment</wfw:comment><dcterms:modified>2006-03-20T01:06:15Z</dcterms:modified></item><item><title>Added Cincinnati .NET User Group Talk for March</title><link>http://mikeamundsen.spaces.live.com/Blog/cns!77111D9765E07CD1!260.entry</link><description>&lt;p&gt;
I'm happy to announce that I will be doing a talk for the Cincinnati .NET User Group (&lt;a href="http://cinnug.org/"&gt;CINNUG&lt;/a&gt;) on March 21st. The topic is a new one I just added to my list - &amp;quot;The Web 2.0 Formula.&amp;quot; It's based on an article I posted &lt;a href="http://spaces.msn.com/mikeamundsen/blog/cns!77111D9765E07CD1!236.entry"&gt;here&lt;/a&gt; at my MSN spaces blog as well as a number of project initiatives I;m invlived with this year.


&lt;p&gt;If you're in the Cincinnati area, check out the &lt;a href="http://cinnug.org/"&gt;CINNUG web site&lt;/a&gt; and stop on in!

&lt;hr&gt;
&lt;h3&gt;Technorati Tags&lt;/h3&gt;
&lt;p&gt;I tag my posts for easy indexing at &lt;a href="http://www.technorati.com/"&gt;Technorati.com&lt;/a&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="http://www.technorati.com/tag/Cincinnati" rel=tag&gt;Cincinnati&lt;/a&gt;
&lt;li&gt;&lt;a href="http://www.technorati.com/tag/INETA" rel=tag&gt;INETA&lt;/a&gt;
&lt;li&gt;&lt;a href="http://www.technorati.com/tag/Web20" rel=tag&gt;Web20&lt;/a&gt;
&lt;/ul&gt;
&lt;hr&gt;&lt;img src="http://c.services.spaces.live.com/CollectionWebService/c.gif?cid=8579671301203983569&amp;page=RSS%3a+Added+Cincinnati+.NET+User+Group+Talk+for+March&amp;referrer=" width="1px" height="1px" border="0" alt=""&gt;&lt;img style="position:absolute" alt="" width="0px" height="0px" src="http://c.live.com/c.gif?NC=31263&amp;amp;NA=1149&amp;amp;PI=73329&amp;amp;RF=&amp;amp;DI=3919&amp;amp;PS=85545&amp;amp;TP=mikeamundsen.spaces.live.com&amp;amp;GT1=mikeamundsen"&gt;</description><category>News</category><comments>http://mikeamundsen.spaces.live.com/Blog/cns!77111D9765E07CD1!260.entry#comment</comments><guid isPermaLink="true">http://mikeamundsen.spaces.live.com/Blog/cns!77111D9765E07CD1!260.entry</guid><pubDate>Sat, 11 Mar 2006 23:09:46 GMT</pubDate><slash:comments>0</slash:comments><msn:type>blogentry</msn:type><live:type>blogentry</live:type><live:typelabel>Blog entry</live:typelabel><wfw:commentRss>http://mikeamundsen.spaces.live.com/blog/cns!77111D9765E07CD1!260/comments/feed.rss</wfw:commentRss><wfw:comment>http://mikeamundsen.spaces.live.com/Blog/cns!77111D9765E07CD1!260.entry#comment</wfw:comment><dcterms:modified>2006-03-18T02:20:46Z</dcterms:modified></item><item><title>The Web 2.0 Formula</title><link>http://mikeamundsen.spaces.live.com/Blog/cns!77111D9765E07CD1!259.entry</link><description>&lt;code&gt;&lt;pre&gt;
(XHTML+CSS2) * JS
------------------ = Web 2.0
XML+XSLT+RDBMS
&lt;/pre&gt;&lt;/code&gt;
&lt;p&gt;
In February of 2005 Jesse James Garret of Adaptive Path wrote an interesting article on the convergence of a number of existing technologies and how they could affect the future of programming for the Web. Now, a year later, this article is recognized as the first in a series of definitive works on what has come to be known as &amp;quot;Web 2.0.&amp;quot;  Based on concepts covered in that article, this talk presents a set of &amp;quot;principles and practices&amp;quot; that make up a &amp;quot;Web 2.0 formula&amp;quot; for building successful leading-edge Web-based solutions like the ones featured as Google, Yahoo, and Microsoft Live.

&lt;p&gt;
Topics covered include the use of compliant XHTML for page markup; Cascading Stylesheets for layout and design; and Javascript to power the client-side experience. In addition, the use of Relational databases as repositories; XML as a data transport format; and XSL technologies such as XSLT, XSL-FO, XPath, XInclude, and XQuery to transform and modify XML data is explored. The talk also includes several live code examples and references to valuable libraries and resources available to jumpstart your Web 2.0 applications.

&lt;p&gt;
Whether you are just exploring the idea of Web 2.0 or are already committed to rolling out Web 2.0-compliant solutions, this talk will help you learn more about the theory, practice, and effects of Web 2.0 on Internet-based applications.

&lt;p&gt;
&lt;hr&gt;
&lt;h3&gt;Technorati Tags&lt;/h3&gt;
&lt;p&gt;I tag my posts for easy indexing at &lt;a href="http://www.technorati.com/"&gt;Technorati.com&lt;/a&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="http://www.technorati.com/tag/INETA" rel=tag&gt;INETA&lt;/a&gt;
&lt;li&gt;&lt;a href="http://www.technorati.com/tag/Web20" rel=tag&gt;Web 2.0&lt;/a&gt;
&lt;li&gt;&lt;a href="http://www.technorati.com/tag/Ajax" rel=tag&gt;Ajax&lt;/a&gt;
&lt;li&gt;&lt;a href="http://www.technorati.com/tag/XML" rel=tag&gt;XML&lt;/a&gt;
&lt;li&gt;&lt;a href="http://www.technorati.com/tag/XSL" rel=tag&gt;XSL&lt;/a&gt;
&lt;li&gt;&lt;a href="http://www.technorati.com/tag/CSS" rel=tag&gt;CSS&lt;/a&gt;
&lt;/ul&gt;
&lt;hr&gt;&lt;img src="http://c.services.spaces.live.com/CollectionWebService/c.gif?cid=8579671301203983569&amp;page=RSS%3a+The+Web+2.0+Formula&amp;referrer=" width="1px" height="1px" border="0" alt=""&gt;&lt;img style="position:absolute" alt="" width="0px" height="0px" src="http://c.live.com/c.gif?NC=31263&amp;amp;NA=1149&amp;amp;PI=73329&amp;amp;RF=&amp;amp;DI=3919&amp;amp;PS=85545&amp;amp;TP=mikeamundsen.spaces.live.com&amp;amp;GT1=mikeamundsen"&gt;</description><category>Presentation Topics</category><comments>http://mikeamundsen.spaces.live.com/Blog/cns!77111D9765E07CD1!259.entry#comment</comments><guid isPermaLink="true">http://mikeamundsen.spaces.live.com/Blog/cns!77111D9765E07CD1!259.entry</guid><pubDate>Sat, 11 Mar 2006 22:52:40 GMT</pubDate><slash:comments>0</slash:comments><msn:type>blogentry</msn:type><live:type>blogentry</live:type><live:typelabel>Blog entry</live:typelabel><wfw:commentRss>http://mikeamundsen.spaces.live.com/blog/cns!77111D9765E07CD1!259/comments/feed.rss</wfw:commentRss><wfw:comment>http://mikeamundsen.spaces.live.com/Blog/cns!77111D9765E07CD1!259.entry#comment</wfw:comment><dcterms:modified>2006-03-18T02:22:37Z</dcterms:modified></item><item><title>Moved my Group To Yahoo</title><link>http://mikeamundsen.spaces.live.com/Blog/cns!77111D9765E07CD1!258.entry</link><description>&lt;p&gt;Just a quick note to alert everyone that I've moved my group from MSN to Yahoo!

&lt;p&gt;MSN was pretty slow. Also, I ran into problems uploading 'large' files (my presentation Powerpoints and PDFs). I've had realy good experiences with Yahoo! and was able to set up a new group there quickly and easily. I've uploaded my content from the Implemenation Misfortunes talk and will continue to add stuff throughout the year.

&lt;p&gt;
Also, to cut down on 'trolls' and spammer-types, the group has moderated membership. If you want to join in, you'll need to 'apply.' It's no big deal, but should keep out the 'riff-raff.'

&lt;p&gt;See you at &lt;a href="http://groups.yahoo.com/group/mikeamundsen/"&gt;http://groups.yahoo.com/group/mikeamundsen/&lt;/a&gt;&lt;img src="http://c.services.spaces.live.com/CollectionWebService/c.gif?cid=8579671301203983569&amp;page=RSS%3a+Moved+my+Group+To+Yahoo&amp;referrer=" width="1px" height="1px" border="0" alt=""&gt;&lt;img style="position:absolute" alt="" width="0px" height="0px" src="http://c.live.com/c.gif?NC=31263&amp;amp;NA=1149&amp;amp;PI=73329&amp;amp;RF=&amp;amp;DI=3919&amp;amp;PS=85545&amp;amp;TP=mikeamundsen.spaces.live.com&amp;amp;GT1=mikeamundsen"&gt;</description><category>News</category><comments>http://mikeamundsen.spaces.live.com/Blog/cns!77111D9765E07CD1!258.entry#comment</comments><guid isPermaLink="true">http://mikeamundsen.spaces.live.com/Blog/cns!77111D9765E07CD1!258.entry</guid><pubDate>Sat, 11 Mar 2006 06:50:52 GMT</pubDate><slash:comments>1</slash:comments><msn:type>blogentry</msn:type><live:type>blogentry</live:type><live:typelabel>Blog entry</live:typelabel><wfw:commentRss>http://mikeamundsen.spaces.live.com/blog/cns!77111D9765E07CD1!258/comments/feed.rss</wfw:commentRss><wfw:comment>http://mikeamundsen.spaces.live.com/Blog/cns!77111D9765E07CD1!258.entry#comment</wfw:comment><dcterms:modified>2006-03-11T06:50:52Z</dcterms:modified></item><item><title>A Configuration File Utility for .NET Apps</title><link>http://mikeamundsen.spaces.live.com/Blog/cns!77111D9765E07CD1!257.entry</link><description>&lt;p&gt;
The new .NET 2.0 has some nice classes to support custom configuration and settings files. I’ve been using a utility class to handle this since .NET 1.0.  I still have lots of 1.0 and 1.1 apps out there and will continue to support them for quite a while. I suspect many people are in the same boat. To that end, I offer up my ConfigurationFile class for others to use, if they wish.

&lt;p&gt;
I should point out that my code is based on a very nice solution posted by &lt;a href="http://staff.develop.com/woodring"&gt;Mike Woodring&lt;/a&gt; (http://staff.develop.com/woodring). He has a number of great code examples on his site.


&lt;h3&gt;Why a custom configuration file?&lt;/h3&gt;
&lt;p&gt;
While it’s easy to use the existing *.config files support built into .NET apps, that can be a drawback. First, I’m kind of a ‘neat freak’ when it comes to populating the standard config files. IMHO, these ‘belong’ to the .NET runtime and should only contain .NET runtime sections and settings.

&lt;p&gt;
Second, and more important, changes to the *.config files can wreak havoc to your running application. In the case of ASP.NET apps, any modification to the config file will cause an app unload and reload to take place. This means modifications to the config files in a critical app can really hamper performance.

&lt;h3&gt;So, how hard is this?&lt;/h3&gt;
&lt;p&gt;
Actually, the basic functionality is pretty simple. You need to be able to read an XML file with separate sections like the “appSettings” section in the standard .NET config files. Ideally, you should be able to select any section in the file, you should be able to select a single item from the section as well as iterate through all the items in a section.

&lt;p&gt;
The basic functions of a class to support the above would look like this:

&lt;code&gt;&lt;/code&gt;&lt;pre&gt;namespace amundsen.ConfigReader
{
    // interface for config implementations
    public interface IConfigurationFile
    {
        // get an item by name
        string this[string key] { get;}

        // get the default section 
        IDictionary Section { get;}

        // get a named section
        IDictionary GetSection(string sectionName);
    }
}
&lt;/pre&gt;
&lt;p&gt;
Implementing the above interface is pretty straightforward. Below is one way to do it.

&lt;code&gt;&lt;/code&gt;&lt;pre&gt;using System;
using System.Xml;
using System.Collections;
using System.Configuration;
using System.Web;

namespace amundsen.ConfigReader
{
    public class ConfigurationFile : IConfigurationFile
    {
        private IDictionary mSection;
        private XmlDocument mFile;

        public ConfigurationFile(string fileName)
        {
            Initialize(fileName, &amp;quot;appSettings&amp;quot;);
        }

        public ConfigurationFile(string fileName, string sectionName)
        {
            Initialize(fileName, sectionName);
        }

        // indexer
        public string this[string key]
        {
            get
            {
                string value = null;
                if (mSection != null)
                    value = mSection[key] as string;
                return (value == null ? &amp;quot;&amp;quot; : value);
            }
        }

        // returns collection of items in the default section
        public IDictionary Section
        {
            get { return (mSection); }
        }

        // returns collection of items in a named section
        public IDictionary GetSection(string sectionName)
        {
            try
            {
                XmlNodeList nodes = mFile.GetElementsByTagName(sectionName);
                foreach (XmlNode node in nodes)
                {
                    if (node.LocalName == sectionName)
                    {
                        DictionarySectionHandler handler = new DictionarySectionHandler();
                        return (IDictionary)handler.Create(null, null, node);
                    }
                }
            }
            catch { }

            return (null);
        }

        // handles the work of initialization
        private void Initialize(string fileName, string sectionName)
        {
            mFile = new XmlDocument();
            XmlTextReader reader = new XmlTextReader(fileName);
            mFile.Load(reader);
            reader.Close();

            mSection = GetSection(sectionName);
        }
    }
}
&lt;/pre&gt;
&lt;p&gt;
Here’s an example config file for testing:

&lt;code&gt;&lt;/code&gt;&lt;pre&gt;&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;utf-8&amp;quot; ?&amp;gt;
&amp;lt;!-- filename: special.config --&amp;gt;
&amp;lt;configuration&amp;gt;
	&amp;lt;appSettings&amp;gt;
		&amp;lt;add key=&amp;quot;item1&amp;quot; value=&amp;quot;this is item one&amp;quot;/&amp;gt;
		&amp;lt;add key=&amp;quot;item2&amp;quot; value=&amp;quot;this is item two&amp;quot;/&amp;gt;
		&amp;lt;add key=&amp;quot;item3&amp;quot; value=&amp;quot;this is item three&amp;quot;/&amp;gt;
	&amp;lt;/appSettings&amp;gt;

	&amp;lt;mySettings&amp;gt;
		&amp;lt;add key=&amp;quot;my1&amp;quot; value=&amp;quot;this is my one&amp;quot; /&amp;gt;
		&amp;lt;add key=&amp;quot;my2&amp;quot; value=&amp;quot;this is my two&amp;quot; /&amp;gt;
		&amp;lt;add key=&amp;quot;my3&amp;quot; value=&amp;quot;this is my three&amp;quot; /&amp;gt;
		&amp;lt;add key=&amp;quot;my4&amp;quot; value=&amp;quot;this is my four&amp;quot; /&amp;gt;
	&amp;lt;/mySettings&amp;gt;
&amp;lt;/configuration&amp;gt;
&lt;/pre&gt;
&lt;p&gt;
And here’s a simple console app to test the above class:

&lt;code&gt;&lt;/code&gt;&lt;pre&gt;using System;
using System.Collections;
using amundsen.ConfigReader;

namespace ConfigReaderConsole
{
    class Program
    {
        static void Main(string[] args)
        {
            ConfigurationFile cfile = new ConfigurationFile(&amp;quot;special.config&amp;quot;);
            
            // iterate through appSettings
            foreach(DictionaryEntry entry in cfile.Section)
                Console.WriteLine(&amp;quot;{0}={1}&amp;quot;,entry.Key,entry.Value);

            Console.WriteLine(cfile.Section[&amp;quot;item2&amp;quot;]);

            IDictionary  mySettings = cfile.GetSection(&amp;quot;mySettings&amp;quot;);
            foreach (DictionaryEntry entry in mySettings)
                Console.WriteLine(&amp;quot;{0}={1}&amp;quot;, entry.Key, entry.Value);

            Console.WriteLine(mySettings[&amp;quot;my3&amp;quot;]);
        }
    }
}
&lt;/pre&gt;
&lt;h3&gt;But what about caching the file for ASP.NET?&lt;/h3&gt;
&lt;p&gt;
The problem with the above implementation is that the file is read every time you create an instance of the class. This works fine for state-ful apps such as WinForm applications, but is very inefficient for stateless applications such as ASP.NET WebForms solutions. 

&lt;p&gt;
What we need is an implementation that supports in-memory caching. And here it is:

&lt;code&gt;&lt;/code&gt;&lt;pre&gt;using System;
using System.Xml;
using System.Collections;
using System.Configuration;
using System.Web;

namespace amundsen.ConfigReader
{
    public class CachedConfigurationFile : IConfigurationFile
    {
        private IDictionary mSection;
        private XmlDocument mFile;

        // constructors
        public CachedConfigurationFile(string fileName)
        {
            Initialize(fileName, &amp;quot;appSettings&amp;quot;, false);
        }
        public CachedConfigurationFile(string fileName, bool reload)
        {
            Initialize(fileName, &amp;quot;appSettings&amp;quot;, reload);
        }

        public CachedConfigurationFile(string fileName, string sectionName)
        {
            Initialize(fileName, sectionName, false);
        }

        public CachedConfigurationFile(string fileName, string sectionName, bool reload)
        {
            Initialize(fileName, sectionName, reload);
        }

        // indexer
        public string this[string key]
        {
            get
            {
                string value = null;
                if (mSection != null)
                    value = mSection[key] as string;
                return (value == null ? &amp;quot;&amp;quot; : value);
            }
        }

        // returns collection of items in the default section
        public IDictionary Section
        {
            get { return (mSection); }
        }

        // returns collection of items in a named section
        public IDictionary GetSection(string sectionName)
        {
            try
            {
                XmlNodeList nodes = mFile.GetElementsByTagName(sectionName);
                foreach (XmlNode node in nodes)
                {
                    if (node.LocalName == sectionName)
                    {
                        DictionarySectionHandler handler = new DictionarySectionHandler();
                        return (IDictionary)handler.Create(null, null, node);
                    }
                }
            }
            catch { }

            return (null);
        }

        // handles the work of initialization
        private void Initialize(string fileName, string sectionName, bool reload)
        {
            // if we can, load it from the web cache
            try
            {
                if (reload)
                    mFile = null;
                else
                {
                    if (System.Web.HttpContext.Current.Cache.Get(fileName) != null)
                    {
                        mFile = new XmlDocument();
                        mFile.LoadXml(System.Web.HttpContext.Current.Cache.Get(fileName).ToString());
                    }
                }
            }
            catch
            {
                mFile = null;
            }

            // if we have nothing yet, go get it from the disk
            if (mFile == null)
            {
                // load xml document
                mFile = new XmlDocument();
                XmlTextReader reader = new XmlTextReader(fileName);
                mFile.Load(reader);
                reader.Close();

                // set up file dependency and load into the web cache
                System.Web.Caching.CacheDependency cd = new System.Web.Caching.CacheDependency(fileName);
                System.Web.HttpContext.Current.Cache.Add
                    (
                    fileName, 
                    mFile.OuterXml, 
                    cd, 
                    DateTime.MaxValue, 
                    new TimeSpan(1, 0, 0), 
                    System.Web.Caching.CacheItemPriority.Normal, 
                    null
                    );
            }

            // set the collection for use
            mSection = GetSection(sectionName);
        }
    }
}
&lt;/pre&gt;
&lt;p&gt;
And a simple ASPX page to test it:

&lt;code&gt;&lt;/code&gt;&lt;pre&gt;&amp;lt;%@ Page language=&amp;quot;C#&amp;quot;%&amp;gt;
&amp;lt;%@ Import Namespace=&amp;quot;amundsen.ConfigReader&amp;quot; %&amp;gt;

&amp;lt;script runat=&amp;quot;server&amp;quot;&amp;gt;

    protected void Page_Load(object sender, EventArgs args)
    {
        CachedConfigurationFile cfile = new CachedConfigurationFile(Server.MapPath(&amp;quot;special.config&amp;quot;));
        lbItem.Text = cfile.Section[&amp;quot;item1&amp;quot;].ToString();
    }
&amp;lt;/script&amp;gt;
&amp;lt;html&amp;gt;
    &amp;lt;body&amp;gt;
        &amp;lt;asp:Label ID=&amp;quot;lbItem&amp;quot; runat=&amp;quot;server&amp;quot;/&amp;gt;
    &amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
&lt;/pre&gt;
&lt;p&gt;
If you run the above page in debug mode, you’ll find that the file is only read from the disk on the first pass. After that, all reads come from the in-memory copy of the file. Even better, as soon as you update the file, it fall sout of the cache (due to the dependency setting) and the next time the item is accessed, it will be loaded from the disk again.

&lt;h3&gt;There you have it&lt;/h3&gt;
&lt;p&gt;
Now you have a simple set of classes that support creating and using any number of custom configuration files for both your WinForm and WebForm solutions.

&lt;p&gt;
NOTE: You can download the source code for this article from &lt;a href="http://groups.yahoo.com/group/mikeamundsen/"&gt;http://groups.yahoo.com/group/mikeamundsen&lt;/a&gt;. The downloadable version was built with VS2005, but you can import the raw class files into VS2003 and recompile without any problem.


&lt;hr&gt;
&lt;h3&gt;Technorati Tags&lt;/h3&gt;
&lt;p&gt;I tag my posts for easy indexing at &lt;a href="http://www.technorati.com/"&gt;Technorati.com&lt;/a&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="http://www.technorati.com/tag/VisualStudio"&gt;VisualStudio&lt;/a&gt;
&lt;li&gt;&lt;a href="http://www.technorati.com/tag/ASPNET"&gt;ASP.NET&lt;/a&gt;
&lt;li&gt;&lt;a href="http://www.technorati.com/tag/Programming"&gt;Programming&lt;/a&gt;
&lt;/ul&gt;
&lt;hr&gt;&lt;img src="http://c.services.spaces.live.com/CollectionWebService/c.gif?cid=8579671301203983569&amp;page=RSS%3a+A+Configuration+File+Utility+for+.NET+Apps&amp;referrer=" width="1px" height="1px" border="0" alt=""&gt;&lt;img style="position:absolute" alt="" width="0px" height="0px" src="http://c.live.com/c.gif?NC=31263&amp;amp;NA=1149&amp;amp;PI=73329&amp;amp;RF=&amp;amp;DI=3919&amp;amp;PS=85545&amp;amp;TP=mikeamundsen.spaces.live.com&amp;amp;GT1=mikeamundsen"&gt;</description><category>Articles</category><comments>http://mikeamundsen.spaces.live.com/Blog/cns!77111D9765E07CD1!257.entry#comment</comments><guid isPermaLink="true">http://mikeamundsen.spaces.live.com/Blog/cns!77111D9765E07CD1!257.entry</guid><pubDate>Sat, 11 Mar 2006 06:43:05 GMT</pubDate><slash:comments>2</slash:comments><msn:type>blogentry</msn:type><live:type>blogentry</live:type><live:typelabel>Blog entry</live:typelabel><wfw:commentRss>http://mikeamundsen.spaces.live.com/blog/cns!77111D9765E07CD1!257/comments/feed.rss</wfw:commentRss><wfw:comment>http://mikeamundsen.spaces.live.com/Blog/cns!77111D9765E07CD1!257.entry#comment</wfw:comment><dcterms:modified>2006-03-11T06:43:05Z</dcterms:modified></item><item><title>Check out my photos from my Murray, KY trip</title><link>http://mikeamundsen.spaces.live.com/Blog/cns!77111D9765E07CD1!256.entry</link><description>&lt;p&gt;
&lt;img src="http://static.flickr.com/43/110000458_ba08e73517_m.jpg" align=left&gt;
I had a chance to snap a few pics while on my trip to Murray, KY for my February INETA talk.
You can check out the &lt;a href="http://www.flickr.com/photos/mikeamundsen/sets/72057594078243506/"&gt;images at flickr&lt;/a&gt;.
&lt;br clear=both&gt;
&lt;p&gt;
&lt;hr&gt;
&lt;h3&gt;Technorati Tags&lt;/h3&gt;
&lt;p&gt;I tag my posts for easy indexing at &lt;a href="http://www.technorati.com/"&gt;Technorati.com&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="http://www.technorati.com/tag/INETA"&gt;INETA&lt;/a&gt;
&lt;li&gt;&lt;a href="http://www.technorati.com/tag/flickr"&gt;flickr&lt;/a&gt;
&lt;li&gt;&lt;a href="http://www.technorati.com/tag/KY"&gt;KY&lt;/a&gt;
&lt;/ul&gt;
&lt;hr&gt;&lt;img src="http://c.services.spaces.live.com/CollectionWebService/c.gif?cid=8579671301203983569&amp;page=RSS%3a+Check+out+my+photos+from+my+Murray%2c+KY+trip&amp;referrer=" width="1px" height="1px" border="0" alt=""&gt;&lt;img style="position:absolute" alt="" width="0px" height="0px" src="http://c.live.com/c.gif?NC=31263&amp;amp;NA=1149&amp;amp;PI=73329&amp;amp;RF=&amp;amp;DI=3919&amp;amp;PS=85545&amp;amp;TP=mikeamundsen.spaces.live.com&amp;amp;GT1=mikeamundsen"&gt;</description><category>Flickr</category><comments>http://mikeamundsen.spaces.live.com/Blog/cns!77111D9765E07CD1!256.entry#comment</comments><guid isPermaLink="true">http://mikeamundsen.spaces.live.com/Blog/cns!77111D9765E07CD1!256.entry</guid><pubDate>Thu, 09 Mar 2006 07:51:47 GMT</pubDate><slash:comments>1</slash:comments><msn:type>blogentry</msn:type><live:type>blogentry</live:type><live:typelabel>Blog entry</live:typelabel><wfw:commentRss>http://mikeamundsen.spaces.live.com/blog/cns!77111D9765E07CD1!256/comments/feed.rss</wfw:commentRss><wfw:comment>http://mikeamundsen.spaces.live.com/Blog/cns!77111D9765E07CD1!256.entry#comment</wfw:comment><dcterms:modified>2006-03-09T07:55:05Z</dcterms:modified></item><item><title>Supporting Content-Negotiation for IIS Webs</title><link>http://mikeamundsen.spaces.live.com/Blog/cns!77111D9765E07CD1!255.entry</link><description>&lt;p&gt;
As part of my current project to implement XML-driven Web solutions, I am re-reading Tim Berners Lee's &lt;a href="http://www.w3.org/Provider/Style/"&gt;Style Guide for online hypertext&lt;/a&gt; for inspiration. One of the topics covered is called &lt;a href="http://www.w3.org/Provider/Style/URI"&gt;Cool URIs dont change&lt;/a&gt;. Most of it relates to planning and implementing hackable URIs (more from me on that soon). But, in a footnote called &amp;quot;How can I remove the file extensions...&amp;quot; the topic of content negotiation comes up. I was reminded of how nice it would be to be able to support c-neg for my IIS-hosted projects.


&lt;h3&gt;What is content negotiation?&lt;/h3&gt;
&lt;p&gt;
&lt;a href="http://en.wikipedia.org/wiki/Content_Negotiation"&gt;Content negotiation&lt;/a&gt; (c-neg for short) is the process where servers and clients negotiate with each other to decide exactly which file or file format will be sent from the server to the client. Typically, c-neg focuses on selecting the right language for a browser or identifying a clients form factor (hand-held device) or the extent of its graphics capabilities (only supports black and white images, etc.). 

&lt;p&gt;
However, c-neg has lots of other possible uses. For my purposes, I want to be able to know when a client is asking for stylesheets, images, or standard markup content. 


&lt;h3&gt;Why use content negotiation anyway?&lt;/h3&gt;
&lt;p&gt;
One of the big reasons for using c-neg is to hide some of the internal details of the web server tech from users. For example, if you could drop the tailfrom all file requests, users would not need to know whether your site is using HTML, ASP, ASPX, JSP, CFM, etc. in order to find a page at your site. Theoretically they would just need to know the title of the document or the topic.

&lt;p&gt;
For example, typing http://cool.server.com/shopping_list might return the document shopping_list.html, or shopping_list.asp, etc. depending on what the server has available. Users need to worry about the tail at all.

&lt;p&gt;
Even more to the point, hiding the tails can protect users when the hosting server switches technologies. For example, when I switched my servers from ASP to ASPX, I basically nullified all my URIs from the past since all my links included the .ASP at the end of URIs.  Had I been using c-neg for all documents, changing from ASP to ASPX would not have affected users at all and all my links would still work.

&lt;h3&gt;How does content negotiation really work?&lt;/h3&gt;
&lt;p&gt;
The process of c-neg is pretty straight forward. When a client (Web browser) makes a request to a server for some resource (document, image, etc.), that clients sends some additional information in the form of headersthat help detail the type of document requested and the preferred or supported formats for that client. For example, when a Web browse asks for an image, it can tell the server it prefers PNG over GIF. Or, if it is a hand-held cell phone, it might tell the server that it can only accept black and white BMP image format.

&lt;p&gt;
On each request, the server inspects these headers and, if allowed, can return the best format for the client.  Note that I used the phrase if allowed. More on that below.

&lt;h3&gt;Some ugly truths about content negotiation&lt;/h3&gt;
&lt;p&gt;
When it comes to negotiating document types and formats, the &lt;a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html"&gt;Accept-Header&lt;/a&gt; is the string of information sent by clients to tell servers what the client prefers. And the Mozilla family of browsers (Netscape and FireFox) do an excellent job of sending detailed format information with each request. For example, text/css, image/png, text/html, etc. are all examples of Accept-Header information sent by FireFox when negotiating with a Web server.

&lt;p&gt;
But MSIE is pretty awful at this. In fact, for as far back as I can document (at least MSIE 4), MSIE has sent the same inadequate Accept-Header for *every-single-request* - no matter what the resource type (css, javascript, html document, image, etc.).  Without going into the really nasty details, &lt;a href="http://ppewww.ph.gla.ac.uk/~flavell/www/content-type.html"&gt;MSIE makes it very difficult&lt;/a&gt; for servers to make decisions on what to send to MSIE clients.

&lt;p&gt;
So folks who want to create hackable URIs that support c-neg *and* work with MSIE, have to resort to some compromises and a few server hacks[sigh].

&lt;h3&gt;How do I support MSIE and still use server-side content negotiation?&lt;/h3&gt;
&lt;p&gt;
Even though you cannot count on MSIE to give adequate information on content-types when requesting documents, you can modify your URIs slightly to give your Web server strong hints. The method I settled on is the same one used by the &lt;a href="http://www.w3.org/"&gt;W3C.org&lt;/a&gt; site and many others. I decided to place certain document types in similar folders. 

&lt;p&gt;
For example, all stylesheets (*.CSS) will go in a folder named /stylesheets/. All image files (*.PNG, *.GIF, *.BMP, etc.) will go in a folder named /images/. Client scripts (*.JS,*.VB) will go in /scripts/, etc. Now, when a client asks for a document, the server can use part of the name as a hint. For example, a request for http://cool.server.com/stylesheets/default will allow the server to return default.css (if it is available). 

&lt;p&gt;
An even better example is in the case of images. If the server gets a request for http://cool.server.com/images/logo, the server might look for logo.png and, if it exists, return that. If log.png does not exist, the server might look for logo.jpg or logo.gif instead. Finally, if the current site uses only JPG files, but next year converts to all PNG format, all the URIs will still work just fine.

&lt;h3&gt;OK, so how do you implement c-neg for IIS?&lt;/h3&gt;
&lt;p&gt;My example of c-neg implementation for IIS is (admittedly) basic, but you should get the idea. Specifically, the implementation outlined here focuses only on standard Web browsers and ignores the details of supporting hand-held devices, etc.

&lt;p&gt;
First, I whip out my trusty ISAPI Rewrite tool to establish some rules for supporting stylesheets and image files. If you dont already use ISAPI Rewrite or some other utility, you can &lt;a href="http://www.isapirewrite.com/"&gt;get a free version of ISAPI Rewrite from the Helicon's web site&lt;/a&gt;. 

&lt;p&gt;
Below are two rules I added to my httpd.ini file:

&lt;code&gt;&lt;pre&gt;
# route any css requests
RewriteRule (.*)/styles/(.*) $1/stylesheets/$2.css [I,CL,L]

# reroute any image requests
RewriteRule (.*)/images/(.*) /imagehandler.ashx?_file=$1/images/$2 [I,CL,L]
&lt;/pre&gt;&lt;/code&gt;
&lt;p&gt;
Note that in the first rule simply adds .CSS: to the end of any resource request that has /stylesheets/ in the URI. Examples are: 

&lt;code&gt;&lt;pre&gt;
http://cool.server.com/stylesheets/default
http://cool.server.com/myapp/stylesheets/main
&lt;/pre&gt;&lt;/code&gt;
&lt;p&gt;
The second rule is a bit trickier. Any request that has /images/ in the URI will be rerouted to a special handler that will look for the proper file and send that to the browser. I wrote the handler in C#. 

&lt;p&gt;
Below is a snapshot of the main code loop for my imageHander:

&lt;code&gt;&lt;pre&gt;
public void ProcessRequest(HttpContext context)
{
	string file = string.Empty;
	string mimefile = string.Empty;
	string rtnfile = string.Empty;
	string[] tails;
	string[] accepts;
	ContentTypeCollection ctcoll = new ContentTypeCollection();

	// get the file to find
	file = GetQueryItem(context,&amp;quot;_file&amp;quot;);
	if (file.Length == 0)
		return;

	// get the list of tails and accepttypes
	tails = GetConfigList(&amp;quot;imagetails&amp;quot;);
	accepts = context.Request.AcceptTypes;
			
	// get assoc tails for accept-types
	mimefile = GetConfigItem(&amp;quot;mimefile&amp;quot;);
	if (mimefile.Length != 0)
	{
		mimefile = context.Server.MapPath(mimefile);
		if (File.Exists(mimefile) == false)
			CreateMimeTypesFile(mimefile);

		FileStream fs = new FileStream(mimefile, FileMode.OpenOrCreate, FileAccess.Read);
		ctcoll = (ContentTypeCollection)Serialization.Deserialize(ctcoll, fs);
		fs.Close();
		fs = null;
	}

	// go through accept-types first
	for (int i = 0; i &amp;lt; accepts.Length; i++)
	{
		if (accepts[i].IndexOf(&amp;quot;image&amp;quot;)!=-1 &amp;amp;&amp;amp; accepts[i].IndexOf(&amp;quot;*&amp;quot;) == -1)
		{
			string tail = ctcoll.GetExtension(accepts[i]);
			if (tail.Length != 0)
			{
				rtnfile = string.Format(&amp;quot;{0}.{1}&amp;quot;, file, tail);
				rtnfile = context.Server.MapPath(rtnfile);
				if (File.Exists(rtnfile))
				{
					SendFile(context, rtnfile, accepts[i]);
					return;
				}
			}
		}
	}

	// no go through pref tails
	for (int i = 0; i &amp;lt; tails.Length; i++)
	{
		rtnfile = string.Format(&amp;quot;{0}.{1}&amp;quot;, file, tails[i]);
		rtnfile = context.Server.MapPath(rtnfile);
		if (File.Exists(rtnfile))
		{
			SendFile(context, rtnfile, accepts[0]);
			return;
		}
	}

	// we failed!
	return;
}
&lt;/pre&gt;&lt;/code&gt;
&lt;p&gt;
There are a lot of loose ends in this code-snippet, but you probably get the idea. By installing this handler at the root of my IIS Webs, I now have basic c-neg support for most standard browsers. And there is quite a bit more that can be done to improve the flexibility and power of this routine - just takes a bit more coding[grin].

&lt;h3&gt;
Summary&lt;/h3&gt;
&lt;p&gt;
So, to build cool, long-lived URIs, you should use links that hide the technologies on the server. In the case of text documents, this can easily be done using a utility like ISAPI Rewrite. For images and other format-driven URIs, you will need to implement a server-side HTTP handler to work out the details of which format to send to the browser.

&lt;p&gt;
Implementing basic content negotiation is the first step toward creating solid URIs. My next step is to create a rational URI scheme that can live over a long period of time.  More on that later.
&lt;hr&gt;
&lt;h3&gt;Technorati Tags&lt;/h3&gt;
&lt;p&gt;I tag my posts for easy indexing at &lt;a href="http://www.technorati.com/"&gt;Technorati.com&lt;/a&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="http://www.technorati.com/tag/W3C"&gt;W3C&lt;/a&gt;
&lt;li&gt;&lt;a href="http://www.technorati.com/tag/IIS"&gt;IIS&lt;/a&gt;
&lt;li&gt;&lt;a href="http://www.technorati.com/tag/MSIE"&gt;MSIE&lt;/a&gt;
&lt;/ul&gt;
&lt;hr&gt;&lt;img src="http://c.services.spaces.live.com/CollectionWebService/c.gif?cid=8579671301203983569&amp;page=RSS%3a+Supporting+Content-Negotiation+for+IIS+Webs&amp;referrer=" width="1px" height="1px" border="0" alt=""&gt;&lt;img style="position:absolute" alt="" width="0px" height="0px" src="http://c.live.com/c.gif?NC=31263&amp;amp;NA=1149&amp;amp;PI=73329&amp;amp;RF=&amp;amp;DI=3919&amp;amp;PS=85545&amp;amp;TP=mikeamundsen.spaces.live.com&amp;amp;GT1=mikeamundsen"&gt;</description><category>Articles</category><comments>http://mikeamundsen.spaces.live.com/Blog/cns!77111D9765E07CD1!255.entry#comment</comments><guid isPermaLink="true">http://mikeamundsen.spaces.live.com/Blog/cns!77111D9765E07CD1!255.entry</guid><pubDate>Thu, 09 Mar 2006 07:20:18 GMT</pubDate><slash:comments>2</slash:comments><msn:type>blogentry</msn:type><live:type>blogentry</live:type><live:typelabel>Blog entry</live:typelabel><wfw:commentRss>http://mikeamundsen.spaces.live.com/blog/cns!77111D9765E07CD1!255/comments/feed.rss</wfw:commentRss><wfw:comment>http://mikeamundsen.spaces.live.com/Blog/cns!77111D9765E07CD1!255.entry#comment</wfw:comment><dcterms:modified>2006-03-09T07:25:32Z</dcterms:modified></item><item><title>Murray, KY INETA talk was fun</title><link>http://mikeamundsen.spaces.live.com/Blog/cns!77111D9765E07CD1!254.entry</link><description>&lt;p&gt;I had a great time traveling to Murray, KY to speak for &lt;a href="http://home.dev-source.com/wkdnug/"&gt;WKDNUG&lt;/a&gt; on the campus of Murray State University last week. I delivered the &lt;a href="http://spaces.msn.com/mikeamundsen/blog/cns!77111D9765E07CD1!194.entry?_c11_blogpart_blogpart=blogview&amp;amp;_c=blogpart#permalink"&gt;Implementation Misfortunes&lt;/a&gt; talk. I really enjoy this talk since it touches on several aspects of implementing software solutions. Not just how things can go wrong (although that is the hook for the talk), but also on how just a bit of planning and creativity can forestall many common misfortunes that befall long-lived coding projects.

&lt;p&gt;
Along with the talk itself, the trip was very nice. It involved a one-hour flight to Nashville, TN followed by a two-hour drive through the country to Murray, KY. For some that might not sound like fun, but it really was! I got to drive past the &lt;a href="http://www.opry.com/"&gt;Grand Ole Opry&lt;/a&gt; complex outside of Nashville. I also got a chance to drive through the &lt;a href="http://en.wikipedia.org/wiki/Land_Between_the_Lakes"&gt;Land Between the Lakes National Recreation Area&lt;/a&gt;.  And the weather was excellent - Sunny and mild. 

&lt;p&gt;
All-in-all, a great start to my 2006 speaking season.  I'm looking forward to visiting &lt;a href="http://www.chadnug.org/"&gt;CHADNUG&lt;/a&gt; in Chattanooga, TN on April 11th.
&lt;hr&gt;
&lt;h3&gt;Technorati Tags&lt;/h3&gt;
&lt;p&gt;I tag my posts for easy indexing at &lt;a href="http://www.technorati.com/"&gt;Technorati.com&lt;/a&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="http://www.technorati.com/tag/INETA"&gt;INETA&lt;/a&gt;
&lt;li&gt;&lt;a href="http://www.technorati.com/tag/programming"&gt;Programming&lt;/a&gt;
&lt;li&gt;&lt;a href="http://www.technorati.com/tag/KY"&gt;KY&lt;/a&gt;
&lt;/ul&gt;
&lt;hr&gt;&lt;img src="http://c.services.spaces.live.com/CollectionWebService/c.gif?cid=8579671301203983569&amp;page=RSS%3a+Murray%2c+KY+INETA+talk+was+fun&amp;referrer=" width="1px" height="1px" border="0" alt=""&gt;&lt;img style="position:absolute" alt="" width="0px" height="0px" src="http://c.live.com/c.gif?NC=31263&amp;amp;NA=1149&amp;amp;PI=73329&amp;amp;RF=&amp;amp;DI=3919&amp;amp;PS=85545&amp;amp;TP=mikeamundsen.spaces.live.com&amp;amp;GT1=mikeamundsen"&gt;</description><category>News</category><comments>http://mikeamundsen.spaces.live.com/Blog/cns!77111D9765E07CD1!254.entry#comment</comments><guid isPermaLink="true">http://mikeamundsen.spaces.live.com/Blog/cns!77111D9765E07CD1!254.entry</guid><pubDate>Thu, 09 Mar 2006 06:03:30 GMT</pubDate><slash:comments>0</slash:comments><msn:type>blogentry</msn:type><live:type>blogentry</live:type><live:typelabel>Blog entry</live:typelabel><wfw:commentRss>http://mikeamundsen.spaces.live.com/blog/cns!77111D9765E07CD1!254/comments/feed.rss</wfw:commentRss><wfw:comment>http://mikeamundsen.spaces.live.com/Blog/cns!77111D9765E07CD1!254.entry#comment</wfw:comment><dcterms:modified>2006-03-09T06:08:37Z</dcterms:modified></item><item><title>ISAPI Rewrite to the Rescue</title><link>http://mikeamundsen.spaces.live.com/Blog/cns!77111D9765E07CD1!247.entry</link><description>&lt;p&gt;
I've been using ISAPI Rewrite for the last three years to improve the quality of web site URLs. Its become such an important of my web work that its one of the first things I tell my clients to add to their server toolkit.  Infact, I continue to be amazed (and frustrated) that Microsoft has not included a powerful URL rewriter tool with every copy of Internet Information Server. 

&lt;p&gt;
Now, Ive seen lots of examples of using URL rewriters to hide query string arguments from search engines. Ive also seen a few examples of how to use URL rewriters to route users to special sites based on language or browser types. But there are several other important reasons to be using URL rewriters in your web deployments. 

&lt;p&gt;
Here are a couple of rewriter rules that I add to almost every public web site I am involved in.

&lt;h3&gt;Putting the WWW back in the World Wide Web&lt;/h3&gt;
&lt;p&gt;
Most public web sites respond to both www.mydomain.com and mydomain.com.  Ive gotten into the habit of *not* typing the www. since I know the plain address will work just fine. It means the same to me. However,it turns out that most search engines (Google, Yahoo!, MSN, etc.) do not treat these two addresses the same. Most engines will track links and indexes to both locations. If youre focusing on your web sites ranking with these search indexes, having both addresses can be a problem. To fix this, I use a simple rewriter rule to automatically re-route browsers that present mydomain.com to www.mydomain.com. This allows lazy users (like me) to keep typing the short name, but still get the full name in reply.

&lt;p&gt;
Heres the rule I use with ISAPI Rewrite:

&lt;p&gt;
&lt;code&gt;
# force proper subdomain on all requests&lt;br&gt;
RewriteCond %HTTP_HOST ^mydomain\.com&lt;br&gt;
RewriteRule ^/(.*) http://www.mydomain.com/$1 [RP,L]&lt;br&gt;
&lt;/code&gt;

&lt;p&gt;
Now, users will always see the full name. And keep in mind that some of the most important users of your web site are search engine spider bots! 

&lt;p&gt;
The ZIPmouse Internet Directory is one of the companies Ive worked with that has this rule in their rewriter set. Try typing &lt;a href="http://zipmouse.com"&gt;http://zipmouse.com&lt;/a&gt; in your browser and see what happens.


&lt;h3&gt;Fixing the Missing Slash&lt;/h3&gt;
&lt;p&gt;One annoying problem with web sites is how they handle missing slashes in URLs. For example, look at this address:

&lt;p&gt;
&lt;code&gt;
http://www.mydomain.com/members 
&lt;/code&gt;

&lt;p&gt;
Usually, what users want is the default page in the members folder of the site. They just forgot about the trailing slash. I use the following rewrite rule to automatically add the trailing slash:

&lt;p&gt;
&lt;code&gt;
# fix missing slash on folders&lt;br&gt;
RewriteCond Host: (.*)&lt;br&gt;
RewriteRule ([^.?]+[^.?/]) http\://$1$2/ [I,R]&lt;br&gt;
&lt;/code&gt;

&lt;p&gt;
Heres another example from the ZIPmouse site: &lt;a href="http://www.zipmouse.com/city/seattle"&gt;http://www.zipmouse.com/city/seattle&lt;/a&gt;


&lt;h3&gt;Dropping the Default&lt;/h3&gt;
&lt;p&gt;Finally, heres a rule I really like to use for public sites. Ill present it first to give you a chance to think about it.

&lt;p&gt;
&lt;code&gt;
RewriteRule (.*)/default.html $1/ [I,RP,L]
&lt;/code&gt;

&lt;p&gt;
We all know that typing just a folder will force the web server to return the default document. This rule does the opposite. It checks the URL for the registered default page for the site and strips the URL down to only the folder. A good rewriter file would probably do this for all the typical defaults registered on the server:

&lt;p&gt;
&lt;code&gt;
RewriteRule (.*)/default.htm $1/ [I,RP,L]&lt;br&gt;
RewriteRule (.*)/default.asp $1/ [I,RP,L]&lt;br&gt;
RewriteRule (.*)/default.aspx $1/ [I,RP,L]&lt;br&gt;
RewriteRule (.*)/index.htm $1/ [I,RP,L]&lt;br&gt;
&lt;/code&gt;

&lt;p&gt;
The ZIPmouse directory uses home.html as the default page for their site. You can use the following URL to test the above rule.

&lt;p&gt;
&lt;a href="http://www.zipmouse.com/shop/computers-and-internet/home.html"&gt;http://www.zipmouse.com/shop/computers-and-internet/home.html&lt;/a&gt;


&lt;h3&gt;Summary&lt;/h3&gt;
&lt;p&gt;So there are three handy URL rewrite rules that can improve the look and feel of your web sites URLs.  If you are not using a URL rewriter yet, I encourage you do start. You can &lt;a href="http://www.isapirewrite.com/"&gt;download a free for non-commercial version of ISAPI Rewriter&lt;/a&gt; for Microsoft server from their website. There are other rewriters out there, too.
&lt;hr&gt;
&lt;h3&gt;Technorati Tags&lt;/h3&gt;
&lt;p&gt;I tag my posts for easy indexing at &lt;a href="http://www.technorati.com/"&gt;Technorati.com&lt;/a&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="http://www.technorati.com/tag/Google"&gt;Google&lt;/a&gt;
&lt;li&gt;&lt;a href="http://www.technorati.com/tag/SEO"&gt;SEO&lt;/a&gt;
&lt;li&gt;&lt;a href="http://www.technorati.com/tag/Microsoft"&gt;Microsoft&lt;/a&gt;
&lt;li&gt;&lt;a href="http://www.technorati.com/tag/IIS"&gt;IIS&lt;/a&gt;
&lt;/ul&gt;
&lt;hr&gt;&lt;img src="http://c.services.spaces.live.com/CollectionWebService/c.gif?cid=8579671301203983569&amp;page=RSS%3a+ISAPI+Rewrite+to+the+Rescue&amp;referrer=" width="1px" height="1px" border="0" alt=""&gt;&lt;img style="position:absolute" alt="" width="0px" height="0px" src="http://c.live.com/c.gif?NC=31263&amp;amp;NA=1149&amp;amp;PI=73329&amp;amp;RF=&amp;amp;DI=3919&amp;amp;PS=85545&amp;amp;TP=mikeamundsen.spaces.live.com&amp;amp;GT1=mikeamundsen"&gt;</description><category>Articles</category><comments>http://mikeamundsen.spaces.live.com/Blog/cns!77111D9765E07CD1!247.entry#comment</comments><guid isPermaLink="true">http://mikeamundsen.spaces.live.com/Blog/cns!77111D9765E07CD1!247.entry</guid><pubDate>Mon, 27 Feb 2006 04:13:50 GMT</pubDate><slash:comments>1</slash:comments><msn:type>blogentry</msn:type><live:type>blogentry</live:type><live:typelabel>Blog entry</live:typelabel><wfw:commentRss>http://mikeamundsen.spaces.live.com/blog/cns!77111D9765E07CD1!247/comments/feed.rss</wfw:commentRss><wfw:comment>http://mikeamundsen.spaces.live.com/Blog/cns!77111D9765E07CD1!247.entry#comment</wfw:comment><dcterms:modified>2006-02-27T13:47:53Z</dcterms:modified></item><item><title>Hiking the Golden Gate Bridge</title><link>http://mikeamundsen.spaces.live.com/Blog/cns!77111D9765E07CD1!246.entry</link><description>&lt;p&gt;
&lt;a title="photo sharing" href="http://www.flickr.com/photos/mikeamundsen/103959826/"&gt;
&lt;img alt="Hiking the Golden Gate Bridge" src="http://static.flickr.com/34/103959826_592282b87b_m.jpg" align=left border=0&gt;&lt;/a&gt; 

&lt;p&gt;This past fall, I got a chance to spend several days relaxing in on of my favorite US cities - San Francisco. One of the fun things was to hike across the Golden Gate Bridge. I took public transportation from my hotel to the foot of the bridge and was able to hike across to a visitor center, enjoy the view and return. Couple hours, lots of fun.&lt;br&gt;
&lt;br&gt;
Next time you're in SanFran, take an afternoon to enjoy the view from the bridge.
&lt;p&gt;&lt;a href="http://www.flickr.com/people/mikeamundsen/"&gt;my flickr profile&lt;/a&gt;
&lt;p&gt;&lt;b&gt;tags:&lt;/b&gt; 
&lt;a href="http://www.technorati.com/tag/flickr" rel=tag&gt;flickr&lt;/a&gt;, 
&lt;a href="http://www.technorati.com/tag/amundsen" rel=tag&gt;amundsen&lt;/a&gt;, 
&lt;a href="http://www.techhnorati.com/tag/photo"&gt;photo&lt;/a&gt;
&lt;img src="http://c.services.spaces.live.com/CollectionWebService/c.gif?cid=8579671301203983569&amp;page=RSS%3a+Hiking+the+Golden+Gate+Bridge&amp;referrer=" width="1px" height="1px" border="0" alt=""&gt;&lt;img style="position:absolute" alt="" width="0px" height="0px" src="http://c.live.com/c.gif?NC=31263&amp;amp;NA=1149&amp;amp;PI=73329&amp;amp;RF=&amp;amp;DI=3919&amp;amp;PS=85545&amp;amp;TP=mikeamundsen.spaces.live.com&amp;amp;GT1=mikeamundsen"&gt;</description><category>Flickr</category><comments>http://mikeamundsen.spaces.live.com/Blog/cns!77111D9765E07CD1!246.entry#comment</comments><guid isPermaLink="true">http://mikeamundsen.spaces.live.com/Blog/cns!77111D9765E07CD1!246.entry</guid><pubDate>Sat, 25 Feb 2006 01:05:15 GMT</pubDate><slash:comments>0</slash:comments><msn:type>blogentry</msn:type><live:type>blogentry</live:type><live:typelabel>Blog entry</live:typelabel><wfw:commentRss>http://mikeamundsen.spaces.live.com/blog/cns!77111D9765E07CD1!246/comments/feed.rss</wfw:commentRss><wfw:comment>http://mikeamundsen.spaces.live.com/Blog/cns!77111D9765E07CD1!246.entry#comment</wfw:comment><dcterms:modified>2006-02-25T02:38:13Z</dcterms:modified></item><item><title>Military Misfortunes Author Interview</title><link>http://mikeamundsen.spaces.live.com/Blog/cns!77111D9765E07CD1!240.entry</link><description>&lt;div&gt;I'm in the final prep for my &lt;a href="http://ineta.org/DesktopDefault.aspx"&gt;INETA &lt;/a&gt;talk for &lt;a href="http://home.dev-source.com/wkdnug/"&gt;WKDNUG&lt;/a&gt; at Murray, KY next week. While trolling the Web for references, I found an &lt;a href="http://www.npr.org/templates/story/story.php?storyId=5161962"&gt;NPR interview&lt;/a&gt; with &lt;a href="http://apps.sais-jhu.edu/faculty_bios/faculty_bio1.php?ID=12&amp;amp;SMSESSION=NO"&gt;Eliot Cohen&lt;/a&gt;, one of the authors of &lt;a href="http://www.amazon.com/gp/product/0679732969/102-7561469-2747303?v=glance&amp;amp;n=283155"&gt;Military Misfortunes&lt;/a&gt;.  This book is the 'jumping off point' for my talk titled &lt;a href="http://spaces.msn.com/mikeamundsen/blog/cns!77111D9765E07CD1!194.entry"&gt;'Implementation Misforturnes or Why Some Well-Designed IT Projects Fail&lt;/a&gt;.'&lt;/div&gt;
&lt;div&gt; &lt;/div&gt;
&lt;div&gt;Even though the book was published in 1991, the material is timeless. Also, like so much that comes from historical research at military colleges, the key points are quite applicable to business.&lt;/div&gt;
&lt;div&gt; &lt;/div&gt;
&lt;div&gt;tags: &lt;a href="http://www.technorati.com/tag/INETA" rel=tag&gt;INETA&lt;/a&gt;, &lt;a href="http://www.technorati.com/tag/Cohen" rel=tag&gt;Cohen&lt;/a&gt;, &lt;a href="http://www.technorati.com/tag/WKDNUG" rel=tag&gt;WKDNUG&lt;/a&gt;&lt;/div&gt;&lt;img src="http://c.services.spaces.live.com/CollectionWebService/c.gif?cid=8579671301203983569&amp;page=RSS%3a+Military+Misfortunes+Author+Interview&amp;referrer=" width="1px" height="1px" border="0" alt=""&gt;&lt;img style="position:absolute" alt="" width="0px" height="0px" src="http://c.live.com/c.gif?NC=31263&amp;amp;NA=1149&amp;amp;PI=73329&amp;amp;RF=&amp;amp;DI=3919&amp;amp;PS=85545&amp;amp;TP=mikeamundsen.spaces.live.com&amp;amp;GT1=mikeamundsen"&gt;</description><category>News</category><comments>http://mikeamundsen.spaces.live.com/Blog/cns!77111D9765E07CD1!240.entry#comment</comments><guid isPermaLink="true">http://mikeamundsen.spaces.live.com/Blog/cns!77111D9765E07CD1!240.entry</guid><pubDate>Fri, 24 Feb 2006 17:19:12 GMT</pubDate><slash:comments>2</slash:comments><msn:type>blogentry</msn:type><live:type>blogentry</live:type><live:typelabel>Blog entry</live:typelabel><wfw:commentRss>http://mikeamundsen.spaces.live.com/blog/cns!77111D9765E07CD1!240/comments/feed.rss</wfw:commentRss><wfw:comment>http://mikeamundsen.spaces.live.com/Blog/cns!77111D9765E07CD1!240.entry#comment</wfw:comment><dcterms:modified>2006-02-24T17:20:21Z</dcterms:modified></item><item><title>My New Formula for Web 2.0</title><link>http://mikeamundsen.spaces.live.com/Blog/cns!77111D9765E07CD1!236.entry</link><description>&lt;p&gt;I've been working on several fronts to get a better handle on Web 2.0 and related items. As a result, I've developed a 'formula' - a kinda of shorthand mission statement - that describes what I think Web 2.0 means for the 'geeks' among us who need to implement Web 2.0 solutions. And that formula is:
&lt;p&gt;&lt;br&gt;&lt;code&gt;&lt;b&gt;(XHTML+CSS2) * JS &lt;br&gt;------------------ = Web 2.0&lt;br&gt;XML+XSLT+RDBMS&lt;/b&gt;&lt;/code&gt;
&lt;p&gt;&lt;br&gt;
&lt;p&gt;Now for the explanation... 
&lt;p&gt;Like any web solution approach, there are two perspectives: Server and Client. In my formula the client perspective focuses on markup, layout, and scripting. The server perspective focuses on XML (marking up data), XSLT (transforming that data into a useable form), and RDBMS (storing the data that is fed into XML documents for XSLT transformation. More about my thinking follows.
&lt;h3&gt;&lt;a title="XHTML at W3C" href="http://www.w3.org/TR/xhtml1/"&gt;XHTML&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Any serious attempt to build Web 2.0 solutions should start with fully validated XHTML. No slacking on this one. We need to start from a clean slate. Along with XML-validated HTML markup, we need to drop the habit of using tables to control layout. We also need to stop adding font/color and other style information directly to the markup. That leads to the next item in the formula.
&lt;h3&gt;&lt;a title="CSS2 at W3C" href="http://www.w3.org/TR/REC-CSS2/"&gt;CSS2&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;CSS2 achieved Recommendation level at W3C in 1998. Yep, 1998. Yet some browsers still don't fully support CSS2 features. In addition, many high traffic web sites still haven't adopted CSS2 as the default standard for controlling layout and style for (x)HTML documents. The really depressing news is that the W3C is already working on CSS3! It's time to bite the bullet and commit to using CSS2 as the default layout and styling service for online documents on the web. 
&lt;h3&gt;&lt;a title="JavaScript references" href="http://www.mozilla.org/js/language/"&gt;JS&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;JS means JavaScript. &lt;a title="ECMA web site" href="http://www.ecma-international.org/default.htm"&gt;Ecma International&lt;/a&gt; (the group formerly known as ECMA) is responsible for maintaining and advancing the JavaScript language. The current version (1.5) also known as ECMAScript was approved in 1999. I must admit, I thought JavaScript was a fading dinosaur. But, with the rise of Firefox and the &lt;a title="XUL web site" href="http://www.xulplanet.com/"&gt;XUL&lt;/a&gt; engine that drives it, JavaScript has continued to flourish. Now that &lt;a title="the article that started it all" href="http://www.adaptivepath.com/publications/essays/archives/000385.php"&gt;Ajax &lt;/a&gt;is becoming a key component of leading edge web solutions, there seems little reason to consider JavaScript to be on a downward slide.
&lt;p&gt;Lots can be said on the subject of making good use of JavaScript, but for now, I will point out that only recently have I seen good examples of object-oriented approaches to building JavaScript solutions. And most of those have come from folks already drinking the Web 2.0 punch. More emphasis needs to be placed on building clean, powerful JS objects and using them to animate the user interface.
&lt;p&gt;BTW - Ecma International started work on the next version of JavaScript (referred to as &lt;a title="XML scripting" href="http://www.ecma-international.org/publications/standards/Ecma-357.htm"&gt;ECMAScript for XML&lt;/a&gt;) in 2004. No telling how long it will take before we see that as a common scripting option in browsers.
&lt;h3&gt;&lt;a title="XML at W3C" href="http://www.w3.org/XML/"&gt;XML&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Not much needs to be said here except that, for my mind, all data should be presented in XML form. Regardless of where is used or how is it stored, data shipped around the web should be annotated - marked up. Most XML tutorials focus on XML data stored as physical files. This makes for easy tutorials, but not-so-good solution implementations. In fact, valuable data will almost always be stored in databases of some time, usually relational. But that's another element (see below).
&lt;p&gt;Again, Ajax solutions are already taking advantage of this idea by using the XMLHttpRequest object to pull XML data from servers into client browsers for manipulation. More of this needs to be done - including on the servers themselves.
&lt;h3&gt;&lt;a title="XSL at W3C" href="http://www.w3.org/Style/XSL/"&gt;XSLT&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;I've used XSLT as part of the formula, but that's a bit misleading. In fact, XSLT is just one of the related technologies I consider crucial for dealing with XML data. &lt;a title="XSLT at W3C" href="http://www.w3.org/TR/xslt"&gt;XSLT&lt;/a&gt; is needed to transform XML documents into usable forms. &lt;a title="XPath at W3C" href="http://www.w3.org/TR/xpath"&gt;XPath&lt;/a&gt; is needed as a way to filter and modify the XML data. Finally, &lt;a title="XSL-FO at W3C" href="http://www.w3.org/TR/xsl/"&gt;XSL-FO&lt;/a&gt; can be used to help format the output. For now, I want to concentrate on standard XSLT to produce XHTML. However XSL-FO was originally conceived as a way to produce XHTML. Up to now, XSL-FO has become synonymous with creating &lt;a title="PDF at Adobe" href="http://partners.adobe.com/public/developer/pdf/index_reference.html"&gt;PDF&lt;/a&gt; documents from XML.
&lt;p&gt;the point here is that XML data requires transformation and XSL/T should be the key to solving that problem. Even though XSL 1.0 reached Recommendation status in 2001, I still see way too many examples of XML DOM-grepping to pull out needed data and present it for users. Some of this is due to limitations (i.e. client browsers with poor or nonexistent XPath support), but much of it is also due to just plain not getting on board with the technology. It is important to commit to using declarative tools like XSLT and XPath to transform data effectively and efficiently. Both on the server and the client.
&lt;h3&gt;&lt;a title="RDBMS as Wikipedia" href="http://en.wikipedia.org/wiki/RDBMS"&gt;RDBMS&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Maybe some are surprised to see an 'ancient' term like RDBMS in a document about building Web 2.0 solutions. But the truth is, most important data is, and will be for the near future, stored in relation database systems. Sure, there are some object-oriented, even hierarchically-oriented data storage systems in use today. The disk files system is probably the best-known hierarchical model. However, we can't deny that businesses and even individuals understand and use relational models to store information. And this is a good thing.
&lt;p&gt;At the same time, we need to start requiring the RDBMS model to 'step it up a notch' and start supporting the XML+XSLT approach to shipping and presenting data. Most of the big RDBMS tools today support presenting queries as XML output. And some have decent tools for accepting XML data as input for inserts, updates, and other RDBMS tasks. It's time we all started taking advantage of these features and began demanding more of our RDBMS vendors. For now, we need to commit to always getting our data requests in XML form and always sending XML documents as part of our data update tasks.
&lt;h3&gt;So What?&lt;/h3&gt;
&lt;p&gt;So, what happens when you start using XSLT to start transforming XML data stored in RDBMS and then use XHTML and CSS2 to build solid user interfaces to access that data, *and* use JavaScript 1.5 to animate those interfaces? You have Web 2.0!. This 'formula' works no matter what technology or platform you are working with. All these standards are open. None of them assume an OS or proprietary service layer. Of course, none of this is new, right? The technologies and standards have been around for many years. There are already lots of folks doing some parts of this - a few doing it all.
&lt;p&gt;But - to be blunt - *I'm* not doing all this yet. And I should be. I would suspect there are many more out there not yet committed to this kind of formula on a fundamental level. And I would guess some, if not most, of them would like to be doing it, too. That's what this article is all about.
&lt;p&gt;Over the next several weeks and months, I'll be working to build a basic infrastructure to support this formula. This will include a server-side runtime built to present XML data from RDBMS transformed via XSLT. It will also include XHTML markup documents modified via CSS2 and animated by JavaScript. In the process, I hope to show how small, but meaningful, changes in the way we think about and implement solutions, can have a big impact on the final results.
&lt;hr&gt;

&lt;h3&gt;Technorati Tags&lt;/h3&gt;
&lt;p&gt;I tag my posts for easy indexing at &lt;a href="http://www.technorati.com/"&gt;Technorati.com&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="http://www.technorati.com/tag/XML"&gt;XML&lt;/a&gt;
&lt;li&gt;&lt;a href="http://www.technorati.com/tag/XSL"&gt;XSL&lt;/a&gt;
&lt;li&gt;&lt;a href="http://www.technorati.com/tag/CSS"&gt;CSS&lt;/a&gt;
&lt;li&gt;&lt;a href="http://www.technorati.com/tag/JavaScript"&gt;JavaScript&lt;/a&gt;
&lt;li&gt;&lt;a href="http://www.technorati.com/tag/ECMA"&gt;ECMA&lt;/a&gt;
&lt;li&gt;&lt;a href="http://www.technorati.com/tag/XPath"&gt;XPath&lt;/a&gt;
&lt;li&gt;&lt;a href="http://www.technorati.com/tag/XSLT"&gt;XSLT&lt;/a&gt;
&lt;li&gt;&lt;a href="http://www.technorati.com/tag/XSLFO"&gt;XSLFO&lt;/a&gt;&lt;/ul&gt;
&lt;hr&gt;&lt;div&gt;&lt;table cellspacing="0" border="0"&gt;&lt;tr height="8"&gt;&lt;td&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td valign="top"&gt;&lt;p&gt;&lt;a href="http://blufiles.storage.live.com&amp;#47;y1prX7ajszH4ijJXooRw1k41O1n3SGdXhkFIr-84VsE-0cOFUae0UZgXpiF3Nk53mGF"&gt;&lt;img src="http://storage.live.com&amp;#47;items&amp;#47;77111D9765E07CD1&amp;#33;237&amp;#58;thumbnail" border="0"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/td&gt;&lt;td width="15"&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;&lt;img src="http://c.services.spaces.live.com/CollectionWebService/c.gif?cid=8579671301203983569&amp;page=RSS%3a+My+New+Formula+for+Web+2.0&amp;referrer=" width="1px" height="1px" border="0" alt=""&gt;&lt;img style="position:absolute" alt="" width="0px" height="0px" src="http://c.live.com/c.gif?NC=31263&amp;amp;NA=1149&amp;amp;PI=73329&amp;amp;RF=&amp;amp;DI=3919&amp;amp;PS=85545&amp;amp;TP=mikeamundsen.spaces.live.com&amp;amp;GT1=mikeamundsen"&gt;</description><category>Articles</category><comments>http://mikeamundsen.spaces.live.com/Blog/cns!77111D9765E07CD1!236.entry#comment</comments><guid isPermaLink="true">http://mikeamundsen.spaces.live.com/Blog/cns!77111D9765E07CD1!236.entry</guid><pubDate>Mon, 20 Feb 2006 06:45:01 GMT</pubDate><slash:comments>3</slash:comments><msn:type>blogentry</msn:type><live:type>blogentry</live:type><live:typelabel>Blog entry</live:typelabel><wfw:commentRss>http://mikeamundsen.spaces.live.com/blog/cns!77111D9765E07CD1!236/comments/feed.rss</wfw:commentRss><wfw:comment>http://mikeamundsen.spaces.live.com/Blog/cns!77111D9765E07CD1!236.entry#comment</wfw:comment><dcterms:modified>2006-02-20T06:45:01Z</dcterms:modified></item><item><title>WKDNUG Selects Implementation Misfortunes Talk</title><link>http://mikeamundsen.spaces.live.com/Blog/cns!77111D9765E07CD1!235.entry</link><description>&lt;p&gt;
I recieved notice this past week that the folks at &lt;a href="http://home.dev-source.com/wkdnug/" title=WKDNUG&gt;WKDNUG in Murray, KY&lt;/a&gt; have selected my new &amp;quot;&lt;a href="http://spaces.msn.com/mikeamundsen/blog/cns!77111D9765E07CD1!194.entry?_c11_blogpart_blogpart=blogview&amp;amp;_c=blogpart#permalink" title="Implementation Misfortunes"&gt;Implementation Misfortunes&lt;/a&gt;&amp;quot; talk for my visit on February 28th, 2006. This talk is loosely based on the book &lt;a href="http://www.amazon.com/gp/product/0679732969/103-9201874-6815046?SubscriptionId=1KDHEGDEXZNBKYAEECR2&amp;amp;n=283155" title="see the book at Amazon.com"&gt;&amp;quot;Military Misfortunes&amp;quot; by Cohen and Gooch&lt;/a&gt;. It should be a lively talk.

&lt;p&gt;I am looking forward to visiting Murray, KY. while I've been in the area a few times for vacations, this will be the first time I will spend time 'working' in Murray. My travel schedule will be interesting, too. It turns out Murray is about two hours from any sizeable airport. Kentucky's own &lt;a href="http://en.wikiquote.org/wiki/O_Brother,_Where_Art_Thou?#Everett" title="quotes from O' Brother Where Art Thou?"&gt;geographical oddity&lt;/a&gt;, I guess. Anyway, I'll fly into Nashville, TN then rent a car and drive two hours northwest to Murray.  Should be a real 'trip.'
&lt;h3&gt;Technorati Tags&lt;/h3&gt;
&lt;p&gt;I tag my posts for easy indexing at &lt;a href="http://www.technorati.com/"&gt;Technorati.com&lt;/a&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="http://www.technorati.com/tag/Murray"&gt;Murray, KY&lt;/a&gt;
&lt;li&gt;&lt;a href="http://www.technorati.com/tag/Kentucky"&gt;Kentucky&lt;/a&gt;
&lt;li&gt;&lt;a href="http://www.technorati.com/tag/Amazon"&gt;amazon.com&lt;/a&gt;
&lt;/ul&gt;
&lt;hr&gt;&lt;img src="http://c.services.spaces.live.com/CollectionWebService/c.gif?cid=8579671301203983569&amp;page=RSS%3a+WKDNUG+Selects+Implementation+Misfortunes+Talk&amp;referrer=" width="1px" height="1px" border="0" alt=""&gt;&lt;img style="position:absolute" alt="" width="0px" height="0px" src="http://c.live.com/c.gif?NC=31263&amp;amp;NA=1149&amp;amp;PI=73329&amp;amp;RF=&amp;amp;DI=3919&amp;amp;PS=85545&amp;amp;TP=mikeamundsen.spaces.live.com&amp;amp;GT1=mikeamundsen"&gt;</description><category>News</category><comments>http://mikeamundsen.spaces.live.com/Blog/cns!77111D9765E07CD1!235.entry#comment</comments><guid isPermaLink="true">http://mikeamundsen.spaces.live.com/Blog/cns!77111D9765E07CD1!235.entry</guid><pubDate>Mon, 20 Feb 2006 05:06:42 GMT</pubDate><slash:comments>1</slash:comments><msn:type>blogentry</msn:type><live:type>blogentry</live:type><live:typelabel>Blog entry</live:typelabel><wfw:commentRss>http://mikeamundsen.spaces.live.com/blog/cns!77111D9765E07CD1!235/comments/feed.rss</wfw:commentRss><wfw:comment>http://mikeamundsen.spaces.live.com/Blog/cns!77111D9765E07CD1!235.entry#comment</wfw:comment><dcterms:modified>2006-02-20T05:06:42Z</dcterms:modified></item><item><title>Yahoo! doing some cool stuff!</title><link>http://mikeamundsen.spaces.live.com/Blog/cns!77111D9765E07CD1!228.entry</link><description>&lt;p&gt;I know Google has been getting lots of buzz but I have also noticed a number of interesting activity in Yahoo! land recently.
&lt;h3&gt;Opening up their UI library&lt;/h3&gt;I saw a post on O'Reilly Radar titled &lt;a href="http://radar.oreilly.com/archives/2006/02/yahoo_open_sources_uis_and_des.html"&gt;Yahoo! Open Sources UIs and Design Patterns&lt;/a&gt;. Turns out Yahoo! has put a good deal of work building some standard widgets (calendar &amp;amp; treeview) along with a set of common javascript APIs for handling Ajax, events and other DOM details. And they want to give it away! Good stuff!. They are even publishing thier own &lt;a href="http://yuiblog.com/"&gt;Yahoo! User Interface Blog&lt;/a&gt; to help web-heads dig into the API. I encourage everyone to &lt;a href="http://developer.yahoo.net/yui/index.html"&gt;download the API, docs, and examples&lt;/a&gt; to see how Yahoo! is working to standardize the Ajax experience. 
&lt;p&gt;
&lt;h3&gt;Updating their My Yahoo! functionality&lt;/h3&gt;
&lt;p&gt;Not long ago, Yahoo! made it really easy for users to include RSS feeds from blogs and other sites into the content on users' &amp;quot;My Yahoo!&amp;quot; pages. Now, I noticed this evening that some headline groups sport a tiny icon. No doubt, this is the icon associated with the RSS feed that is used to build the headlines. In addition, users can hover over the headline and see a 'preview' of the text behind the link. Pretty sweet!
&lt;h3&gt;Powerful Ajax email client&lt;/h3&gt;
&lt;p&gt;I have also been beta-testing the new Yahoo! mail client and like what I see. while Google was first out of the gate with an Ajax-enabled web mail client, Yahoo! has, imho, done a much better job of biulding a powerful, friendly tool for their users. While the Yahoo! Mail team has lots to do to integrate their other services (Calendar, Contacts, &amp;amp; Notepad) into the new interface, I really have few bad things to say about this major upgrade to thier mail client. Anyone who has a Yahoo! mail account can &lt;a href="http://surveylink.yahoo.com/wix/p0473306.aspx"&gt;sign up for the Yahoo! Mail beta&lt;/a&gt;
&lt;hr style="width:100%;height:2px"&gt;

&lt;h3&gt;Technorati Tags&lt;/h3&gt;
&lt;p&gt;I tag my posts for easy indexing at &lt;a href="http://www.technorati.com/"&gt;Technorati.com&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="http://www.technorati.com/tag/Google"&gt;Google&lt;/a&gt;
&lt;li&gt;&lt;a href="http://www.technorati.com/tag/Yahoo"&gt;Yahoo&lt;/a&gt;
&lt;li&gt;&lt;a href="http://www.technorati.com/tag/Ajax"&gt;Ajax&lt;/a&gt;&lt;/ul&gt;
&lt;hr&gt;&lt;div&gt;&lt;table cellspacing="0" border="0"&gt;&lt;tr height="8"&gt;&lt;td&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td valign="top"&gt;&lt;p&gt;&lt;a href="http://blufiles.storage.live.com&amp;#47;y1pYEV01-DZSvf817QX91U8848zXYb9HgGyiWkxlOOCHx6JbWwPEYQ37vztbNhX-3ou"&gt;&lt;img src="http://storage.live.com&amp;#47;items&amp;#47;77111D9765E07CD1&amp;#33;232&amp;#58;thumbnail" border="0"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/td&gt;&lt;td width="15"&gt;&lt;/td&gt;&lt;td valign="top"&gt;&lt;p&gt;&lt;a href="http://blufiles.storage.live.com&amp;#47;y1pvq-StwlmedvmXHqNthKsYIn9K7wdixZXpUlnyMg_2GIQuL1Mv5G8yG0yjwK3QZ-f"&gt;&lt;img src="http://storage.live.com&amp;#47;items&amp;#47;77111D9765E07CD1&amp;#33;234&amp;#58;thumbnail" border="0"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/td&gt;&lt;td width="15"&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;&lt;img src="http://c.services.spaces.live.com/CollectionWebService/c.gif?cid=8579671301203983569&amp;page=RSS%3a+Yahoo!+doing+some+cool+stuff!&amp;referrer=" width="1px" height="1px" border="0" alt=""&gt;&lt;img style="position:absolute" alt="" width="0px" height="0px" src="http://c.live.com/c.gif?NC=31263&amp;amp;NA=1149&amp;amp;PI=73329&amp;amp;RF=&amp;amp;DI=3919&amp;amp;PS=85545&amp;amp;TP=mikeamundsen.spaces.live.com&amp;amp;GT1=mikeamundsen"&gt;</description><category>Around the 'Net</category><comments>http://mikeamundsen.spaces.live.com/Blog/cns!77111D9765E07CD1!228.entry#comment</comments><guid isPermaLink="true">http://mikeamundsen.spaces.live.com/Blog/cns!77111D9765E07CD1!228.entry</guid><pubDate>Wed, 15 Feb 2006 03:28:53 GMT</pubDate><slash:comments>0</slash:comments><msn:type>blogentry</msn:type><live:type>blogentry</live:type><live:typelabel>Blog entry</live:typelabel><wfw:commentRss>http://mikeamundsen.spaces.live.com/blog/cns!77111D9765E07CD1!228/comments/feed.rss</wfw:commentRss><wfw:comment>http://mikeamundsen.spaces.live.com/Blog/cns!77111D9765E07CD1!228.entry#comment</wfw:comment><dcterms:modified>2006-02-15T07:21:26Z</dcterms:modified></item><item><title>Custom List: Related Sites</title><link>http://mikeamundsen.spaces.live.com/Lists/cns!77111D9765E07CD1!216</link><description>&lt;p&gt;Related Sites&lt;/p&gt;&lt;div&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;&lt;a href="http://ineta.org&amp;#47;DesktopDefault.aspx"&gt;INETA Home Page&lt;/a&gt;&lt;/p&gt;&lt;p&gt;The home page for the International .NET Association&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;a href="http://research.microsoft.com&amp;#47;"&gt;Microsoft Research&lt;/a&gt;&lt;/p&gt;&lt;p&gt;A great place to visit to find out what MSFT has coming in the future&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;a href="http://msdn.microsoft.com&amp;#47;"&gt;MSDN Home Page&lt;/a&gt;&lt;/p&gt;&lt;p&gt;The Uber Developer Resource for all things Microsoft&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;a href="http://technorati.com&amp;#47;claim&amp;#47;nw9n6yp7sf"&gt;Technorati Profile&lt;/a&gt;&lt;/p&gt;&lt;p&gt;Great place to get the pulse of the blog-o-sphere&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;a href="http://groups.yahoo.com&amp;#47;group&amp;#47;mikeamundsen"&gt;Mike Amundsen Group&lt;/a&gt;&lt;/p&gt;&lt;p&gt;Yahoo&amp;#33; Group where you&amp;#39;ll find recent uploads and discussions onMike Amundsen&amp;#39;s INETA Talks&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;img src="http://c.services.spaces.live.com/CollectionWebService/c.gif?cid=8579671301203983569&amp;page=RSS%3a+Custom+List%3a+Related+Sites&amp;referrer=" width="1px" height="1px" border="0" alt=""&gt;&lt;img style="position:absolute" alt="" width="0px" height="0px" src="http://c.live.com/c.gif?NC=31263&amp;amp;NA=1149&amp;amp;PI=73329&amp;amp;RF=&amp;amp;DI=3919&amp;amp;PS=85545&amp;amp;TP=mikeamundsen.spaces.live.com&amp;amp;GT1=mikeamundsen"&gt;</description><guid isPermaLink="false">cns!77111D9765E07CD1!216</guid><pubDate>Sat, 11 Mar 2006 06:44:57 GMT</pubDate><msn:type>list</msn:type><live:type>list</live:type><live:typelabel>List</live:typelabel><cf:itemRSS>http://mikeamundsen.spaces.live.com/Lists/cns!77111D9765E07CD1!216/feed.rss</cf:itemRSS><dcterms:modified>2006-03-11T06:44:57Z</dcterms:modified></item><item><title>Book List: Currently Reading...</title><link>http://mikeamundsen.spaces.live.com/Lists/cns!77111D9765E07CD1!221</link><description>&lt;p&gt;Currently Reading...&lt;/p&gt;&lt;div&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;&lt;a href="http://www.amazon.com&amp;#47;exec&amp;#47;obidos&amp;#47;redirect&amp;#37;3Ftag&amp;#61;msnspaces04-20&amp;#37;26link_code&amp;#61;sp1&amp;#37;26camp&amp;#61;2025&amp;#37;26creative&amp;#61;165953&amp;#37;26path&amp;#61;http&amp;#58;&amp;#47;&amp;#47;www.amazon.com&amp;#47;gp&amp;#47;redirect.html&amp;#37;253fASIN&amp;#61;073571245X&amp;#37;2526tag&amp;#61;msnspaces04-20&amp;#37;2526lcode&amp;#61;sp1&amp;#37;2526cID&amp;#61;2025&amp;#37;2526ccmID&amp;#61;165953&amp;#37;2526location&amp;#61;&amp;#47;o&amp;#47;ASIN&amp;#47;073571245X&amp;#37;25253FSubscriptionId&amp;#61;1KDHEGDEXZNBKYAEECR2"&gt;Eric Meyer: Eric Meyer on CSS&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&lt;a href="http://www.amazon.com&amp;#47;exec&amp;#47;obidos&amp;#47;redirect&amp;#37;3Ftag&amp;#61;msnspaces04-20&amp;#37;26link_code&amp;#61;sp1&amp;#37;26camp&amp;#61;2025&amp;#37;26creative&amp;#61;165953&amp;#37;26path&amp;#61;http&amp;#58;&amp;#47;&amp;#47;www.amazon.com&amp;#47;gp&amp;#47;redirect.html&amp;#37;253fASIN&amp;#61;073571245X&amp;#37;2526tag&amp;#61;msnspaces04-20&amp;#37;2526lcode&amp;#61;sp1&amp;#37;2526cID&amp;#61;2025&amp;#37;2526ccmID&amp;#61;165953&amp;#37;2526location&amp;#61;&amp;#47;o&amp;#47;ASIN&amp;#47;073571245X&amp;#37;25253FSubscriptionId&amp;#61;1KDHEGDEXZNBKYAEECR2"&gt;&lt;img src="http://images.amazon.com&amp;#47;images&amp;#47;P&amp;#47;073571245X.01._SCTHUMBZZZ_.jpg" valign="top" border="0"&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;An excellent in-depth workbook on CSS2&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;a href="http://www.amazon.com&amp;#47;exec&amp;#47;obidos&amp;#47;redirect&amp;#37;3Ftag&amp;#61;msnspaces04-20&amp;#37;26link_code&amp;#61;sp1&amp;#37;26camp&amp;#61;2025&amp;#37;26creative&amp;#61;165953&amp;#37;26path&amp;#61;http&amp;#58;&amp;#47;&amp;#47;www.amazon.com&amp;#47;gp&amp;#47;redirect.html&amp;#37;253fASIN&amp;#61;0679732969&amp;#37;2526tag&amp;#61;msnspaces04-20&amp;#37;2526lcode&amp;#61;sp1&amp;#37;2526cID&amp;#61;2025&amp;#37;2526ccmID&amp;#61;165953&amp;#37;2526location&amp;#61;&amp;#47;o&amp;#47;ASIN&amp;#47;0679732969&amp;#37;25253FSubscriptionId&amp;#61;1KDHEGDEXZNBKYAEECR2"&gt;Cohen and Gooch: Military Misfortunes&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&lt;a href="http://www.amazon.com&amp;#47;exec&amp;#47;obidos&amp;#47;redirect&amp;#37;3Ftag&amp;#61;msnspaces04-20&amp;#37;26link_code&amp;#61;sp1&amp;#37;26camp&amp;#61;2025&amp;#37;26creative&amp;#61;165953&amp;#37;26path&amp;#61;http&amp;#58;&amp;#47;&amp;#47;www.amazon.com&amp;#47;gp&amp;#47;redirect.html&amp;#37;253fASIN&amp;#61;0679732969&amp;#37;2526tag&amp;#61;msnspaces04-20&amp;#37;2526lcode&amp;#61;sp1&amp;#37;2526cID&amp;#61;2025&amp;#37;2526ccmID&amp;#61;165953&amp;#37;2526location&amp;#61;&amp;#47;o&amp;#47;ASIN&amp;#47;0679732969&amp;#37;25253FSubscriptionId&amp;#61;1KDHEGDEXZNBKYAEECR2"&gt;&lt;img src="http://images.amazon.com&amp;#47;images&amp;#47;P&amp;#47;0679732969.01._SCTHUMBZZZ_.jpg" valign="top" border="0"&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;The Anatomy of Failure In War&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;a href="http://www.amazon.com&amp;#47;exec&amp;#47;obidos&amp;#47;redirect&amp;#37;3Ftag&amp;#61;msnspaces04-20&amp;#37;26link_code&amp;#61;sp1&amp;#37;26camp&amp;#61;2025&amp;#37;26creative&amp;#61;165953&amp;#37;26path&amp;#61;http&amp;#58;&amp;#47;&amp;#47;www.amazon.com&amp;#47;gp&amp;#47;redirect.html&amp;#37;253fASIN&amp;#61;0764567586&amp;#37;2526tag&amp;#61;msnspaces04-20&amp;#37;2526lcode&amp;#61;sp1&amp;#37;2526cID&amp;#61;2025&amp;#37;2526ccmID&amp;#61;165953&amp;#37;2526location&amp;#61;&amp;#47;o&amp;#47;ASIN&amp;#47;0764567586&amp;#37;25253FSubscriptionId&amp;#61;1KDHEGDEXZNBKYAEECR2"&gt;Peter Kent: Search Engine Optimization for dummies&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&lt;a href="http://www.amazon.com&amp;#47;exec&amp;#47;obidos&amp;#47;redirect&amp;#37;3Ftag&amp;#61;msnspaces04-20&amp;#37;26link_code&amp;#61;sp1&amp;#37;26camp&amp;#61;2025&amp;#37;26creative&amp;#61;165953&amp;#37;26path&amp;#61;http&amp;#58;&amp;#47;&amp;#47;www.amazon.com&amp;#47;gp&amp;#47;redirect.html&amp;#37;253fASIN&amp;#61;0764567586&amp;#37;2526tag&amp;#61;msnspaces04-20&amp;#37;2526lcode&amp;#61;sp1&amp;#37;2526cID&amp;#61;2025&amp;#37;2526ccmID&amp;#61;165953&amp;#37;2526location&amp;#61;&amp;#47;o&amp;#47;ASIN&amp;#47;0764567586&amp;#37;25253FSubscriptionId&amp;#61;1KDHEGDEXZNBKYAEECR2"&gt;&lt;img src="http://images.amazon.com&amp;#47;images&amp;#47;P&amp;#47;0764567586.01._SCTHUMBZZZ_.jpg" valign="top" border="0"&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;One of the best resources on the subject&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;img src="http://c.services.spaces.live.com/CollectionWebService/c.gif?cid=8579671301203983569&amp;page=RSS%3a+Book+List%3a+Currently+Reading...&amp;referrer=" width="1px" height="1px" border="0" alt=""&gt;&lt;img style="position:absolute" alt="" width="0px" height="0px" src="http://c.live.com/c.gif?NC=31263&amp;amp;NA=1149&amp;amp;PI=73329&amp;amp;RF=&amp;amp;DI=3919&amp;amp;PS=85545&amp;amp;TP=mikeamundsen.spaces.live.com&amp;amp;GT1=mikeamundsen"&gt;</description><guid isPermaLink="false">cns!77111D9765E07CD1!221</guid><pubDate>Sun, 29 Jan 2006 22:09:33 GMT</pubDate><msn:type>booklist</msn:type><live:type>booklist</live:type><live:typelabel>Book list</live:typelabel><cf:itemRSS>http://mikeamundsen.spaces.live.com/Lists/cns!77111D9765E07CD1!221/feed.rss</cf:itemRSS><dcterms:modified>2006-01-29T22:09:33Z</dcterms:modified></item></channel></rss>