Monday, June 18, 2007

Four ways of doing a test with a result.

I've been writing a framework for processing emails. These emails can be rejected for various reasons, but when they're rejected we also need to get the reason for the rejection too. It's a common and simple scenario where you need test a boolean value and get some information, but what's the best way of going about it? Well if you're going to be returning more than one thing from a method, in our case the boolean result plus the reason, the easiest way to do it is to wrap them up in a result object. Here's my first attempt:
RejectResult result = email.IsRejected;
if(result.Rejected)
{
    DoSomethingWith(result.Reason);
}
But I don't really like this because the if statement looks like it's saying "if result is rejected". The result's not rejected the email is. I'd like the code to say "if email is rejected", so how about this, using out parameters:
Reason reason;
if(email.IsRejected(out reason))
{
    DoSomethingWith(reason);   
}
OK, this is a bit better, but now it's saying "if email is rejected reason", leaving out 'out' which is just syntax, which still isn't what I really want. Also there's a practical problem here that my favorite unit test mocking framework NMock2 makes a real meal out of out parameters. OK, so how about this one:
if(email.IsRejected)
{
    DoSomethingWith(email.RejectReason);
}
The if statement reads nicely now, but there's a serious practical problem in that we're not expecting email's state to change between testing for rejection and examining the RejectReason, this would be especially serious if email wasn't thread safe but even if it is it's relying on convention; what does RejectReason mean before IsRejected has been tested? Not wrapping the IsRejected and RejectReason in a single method of email is just plain bad. My favorite solution was suggested by NMock2. I really like its conversational style, 'Expect.Once.On(mymock).With(myparam).Will(Return.Value(myreturnval));', so how about this:
Reason reason;
if(email.IsRejected.For(out reason)) 
{
    DoSomethingWith(reason);
}
It reads really nicely "if email is rejected for reason", mocks nicely because you can just Mock RejectResult (returned from IsRejected) and is essentially the first and very obvious method above with only a small change to the RejectResult class (the addition of the For method). Here's the Email class and RejectResult class:
public class Email
{
    public RejectResult IsRejected
    {
        get{ return new RejectResult(true, new Reason("The reason this was rejected")); }
    }
}

public class RejectResult
{
    bool _rejected;
    Reason _reason;

    public RejectResult(bool rejected, Reason reason)
    {        
        this._rejected = rejected;
        this._reason = reason;
    }

    public bool For(out Reason reason)
    {
        reason = this._reason;
        return this._rejected;
    }
}
I love code that just explains itself without the need for copious comments!

Thursday, May 24, 2007

A problem with ASP.NET client side validation and IIS host headers

I've just had an interesting issue when deploying an ASP.NET application on to a web site that uses host headers. Host headers is where you can point multiple domains at a single IIS and IIS can resolve them to multiple virtual directories. ASP.NET requires some javascript files in a virtual directory under the root called ‘aspnet_client’ for its client side functionality such as the validation controls. If you’re creating your application in a virtual directory, e.g: http://www.mydomain.com/myapplication/ Then installing the .net runtime will put those files in the right place for that app: http://www.mydomain.com/aspnet_client/ The problem comes when you use host headers to resolve additional subdomains to a virtual directory: http://subdomain.mydomain.com/ ASP.NET looks for its javascript files here: http://subdomain.mydomain.com/aspnet_client/ But it won’t find them unless you remember to manually copy that directory to the root of your application. It’s something to be aware of (especially since it happened to me twice, double duh!)

Tuesday, May 15, 2007

Playing with JSON

I recently had to do some web development work which involved passing an object graph from the ASP.NET server code through to the browser to be consumed by javascript. The last time I had to do something like this was for an intranet application where the browser platform was alwasy going to be IE on Windows. I used XML as the serialization format and consumed it on the browser by instantiating an MSXML DOM. It worked, but it was strictly a Microsoft platform solution and the javascript code was pretty ugly, all that digging around with xpath, urgh! This time the requirement was for an internet application, so I had to choose something that would run on any modern browser. I remembered reading about JSON somewhere as the serialization format of choice for javascript developers, so I decided to give it a whirl. I did a bit of googling and found a couple of .NET JSON libraries, Json.NET by James Newton-King, and Jayrock by Atif Aziz. Atif is also the author of this great introduction to JSON on MSDN, well worth a read. Both of them seemed to fit the bill, but my client was a strictly .NET 1.1 shop, so I had to go with Jayrock since it was the only library with 1.1 support. Jayrock provides quite a comprehensive collection of JSON serialization libraries as well as a JSON RPC IHttpHandler implementation that lets you write JSON 'web serives'. However, all I needed was the serialization function. It worked like a dream for my simple scenario, just one line of code to serialize my object graph to a string:
string jsonText = JsonConvert.ExportToString(myObjectGraph);
Because JSON is effectively native javascript you 'de-serialize' your graph on the client by simply calling 'eval' with your JSON text:
val myObjectGraph = eval(jsonText);
For this simple scenario to work, the objects in your graph have to have a default public constructor and all properties must be settable and gettable, just like when you use the .NET XmlSerializer. But really, so much easier than all that messing around with XML. Of course JSON is extensively used in AJAX applications, even though the 'X' stands for XML :) And there's JSON support built into ASP.NET AJAX (formerly Atlas) in the System.Web.Script.Serialization Namespace, specifically the JavaScriptSerializer class. So if you are doing ASP.NET 2.0 work, you can use that instead of Jayrock. All I need now is an excuse to do some serious Ajax programming.

Monday, May 07, 2007

My new Web Service Test tool, WsdlWorks

I've been working on an open source web service test framework for Visual Studio over the last few months. It's called WsdlWorks. That's why I was blogging about creating new VS project types here and here and problems with WCF and WSDL. I've decided to host it on CodePlex because it seems to be the preferred place for Microsoft related open source projects. I just created a new project for it here and uploaded the source. The CodePlex source control client is a pretty basic command line based thing, but it's prefectly fine for a little project like this. Here's a screen shot of WsdlWorks: As you can see it creates a new Visual Studio project, you right click the project node, select 'Add WSDL from URL', enter your WSDL's URL in the dialog that appears and then WsdlWorks automatically creates nodes for all the services and their operations. It also creates initial requests for each operation, but you can add as many as you want. To execute the request, you just right-click on the request node and select 'Run'. When you create a request it reads the WSDL's XSD for the request types and creates example data in the request. I used XmlSampleGenerator, by Priya Lakshminarayanan to do this. I tried to get it working with WCF multipart WSDLs, but haven't succeeded yet. The error handling, icons and other stuff is also still on the 'to do' list, so it's far from production ready, but I thought I'd release it now in the best open source tradition since it's in its initial working state. You can't release Visual Studio packages without a package load key, and you can't get a package load key without having a homepage for your project. But now I've got a homepage I can get the PLK. Expect an installer pretty soon. For the time being, if you want to play with WsdlWorks you'll need .NET 3.0 and the Visual Studio SDK so that you can run it in the experimental hive.

Thursday, May 03, 2007

Permissions needed to access the IIS metabase remotely using WMI

In my last two posts I've been talking about programatically reading and editing the IIS metabase using WMI via System.Management. In my examples I've assumed that the code has access to an administrator account's username and password. Of course you wouldn't want to do that in real life, putting an admin password as a literal in your managed code is not a good idea. A much better idea would be to have your application run under a low privilege account and just give it the permissions it needs to access the metabase using windows integrated security. To do this you need these permissions:
  1. Create a new account with minimum rights that your application will run under.
  2. On the web server with the metabase you want to access, add the user to the 'Distributed COM Users' group. This is because WMI uses DCOM.
  3. Open 'Administrative Tools' -> 'Computer Management'. Expand 'Services and Applications', right click 'WMI Control' and under the Security tab, expand 'Root'. Find the 'MicrosoftIISv2' node and give the user the required permissions.
  4. Finally you have to give access to the nodes you require in the metabase. Using the metabase explorer (from the IIS Resource Kit), find the node you want to access, right click it and choose permissions. If the node doesn't have any permissions set a dialog will ask you if you want to copy the permissions of the parent node or edit the parent node permissions, make your choice and then set the permissions you require in the permissions dialog.

Thursday, April 26, 2007

How to walk the IIS metabase using WMI and System.Management

Here's a little addition to my last post where I described how to set a property (HttpRedirect) in the IIS 6.0 metabase. I got curious about how one could output the entire metabase using WMI by walking the hierarchy. The trick is the ManagementObject.GetRelated() method which gets all the objects related to the current object. You have to be carefull because that includes the parent and my first attempt at recursively walking the hierarchy got stuck in a loop. I just use a hashtable to keep track of objects that I've already found. Some properties can be arrays and sometimes they are arrays of ManagementObjects that themselves have properties so WritePropertiesAsArray calls back to WriteProperties. The whole thing is written to a file by the Output static class. You'll need to set your own file path to the outputPath constant.
using System;
using System.Collections;
using System.Management;

namespace WMITest.MetabaseWalker
{
 /// 
 /// Summary description for Program.
 /// 
 public class Program
 {
        const string serverName = "192.168.0.166";
        const string userName = "Administrator";
        const string password = "mike";
        const string wmiPathToDefaultWebsite = "IIsComputer.Name=\"LM\"";

        Hashtable outputObjects = new Hashtable();

        [STAThread]
        public static void Main()
        {
            Program program = new Program();
            program.WalkMetabase();
        }

        private void WalkMetabase()
        {
            ConnectionOptions options = new ConnectionOptions();
            options.Username = userName;
            options.Password = password;
            options.Authentication = AuthenticationLevel.PacketPrivacy;

            ManagementPath path = new ManagementPath();
            path.Server = serverName;
            path.NamespacePath = "root/MicrosoftIISv2";

            ManagementScope scope = new ManagementScope(path, options);

            using(ManagementObject obj = new ManagementObject(
                      scope,
                      new ManagementPath(wmiPathToDefaultWebsite), null))
            {
                OutputObject(obj);
            }
        }

        private void OutputObject(ManagementObject obj)
        {
            outputObjects.Add(obj.Path.RelativePath, new object());

            Output.WriteLine();
            Output.WriteLine("{0}", true, obj.Path.RelativePath);
            Output.WriteLine();

            WriteProperties(obj.Properties);
            WriteProperties(obj.SystemProperties);

            foreach(ManagementObject relatedObject in obj.GetRelated())
            {
                if(!outputObjects.ContainsKey(relatedObject.Path.RelativePath))
                {
                    Output.TabIn();
                    OutputObject(relatedObject);
                    Output.TabOut();
                }
            }
        }

        private void WriteProperties(PropertyDataCollection properties)
        {
            Output.TabIn();
            foreach(PropertyData property in properties)
            {
                Output.WriteLine("{0}:\t{1}, \t{2}",
                    property.Name,
                    (property.Value == null) ? "null" : property.Value.ToString(),
                    property.Type.ToString());

                WritePropertyAsArray(property);
            }
            Output.TabOut();
        }

        private void WritePropertyAsArray(PropertyData property)
        {
            if(property.IsArray && property.Value != null)
            {
                ICollection propertyArray = property.Value as ICollection;
                if(propertyArray == null)
                    throw new ApplicationException("can't cast property.Value as ICollection");
                Output.TabIn();
                if(propertyArray.Count == 0)
                {
                    Output.WriteLine("No Items");
                }
                int counter = 0;
                foreach(object item in propertyArray)
                {
                    ManagementBaseObject managementObject = item as ManagementBaseObject;
                    if(managementObject != null)
                    {
                        Output.WriteLine("{0}[{1}]", property.Name, counter.ToString());
                        WriteProperties(managementObject.Properties);
                        counter++;
                    }
                    else
                    {
                        Output.WriteLine("{0}", item.ToString());
                    }
                }
                Output.TabOut();
            }
        }
 }
}
And here's the output class
using System;
using System.IO;

namespace WMITest.MetabaseWalker
{
    /// 
    /// Summary description for Output.
    /// 
    public class Output
    {
        const string outputPath = @"C:\VisualStudio\WMITest\WMITest.MetabaseWalker\Output.txt";
        static int tabs = 0;

        static Output()
        {
            using(File.Create(outputPath)){}
        }

        public static void WriteLine()
        {
            WriteLine("");
        }

        public static void WriteLine(string format, params object[] args)
        {
            WriteLine(format, false, args);
        }

        public static void WriteLine(string format, bool writeToConsole, params object[] args)
        {
            if(writeToConsole)
            {
                Console.WriteLine(new string('\t', tabs) + format, args);
            }
            using(StreamWriter writer = File.AppendText(outputPath))
            {
                writer.WriteLine(new string('\t', tabs) + format, args);
            }
        }

        public static void TabIn()
        {
            tabs++;
        }

        public static void TabOut()
        {
            tabs--;
        }
    }
}

Wednesday, April 25, 2007

IIS Redirects by setting the HttpRedirect metabase property with WMI

In my last post I talked about how to use ISAPI_Rewrite to do redirects in IIS. I said "It looks like the only way of doing this is to use an ISAPI extension." How wrong I was! That'll teach you, dear reader, to believe everything you read in Code Rant. In fact I guy I once worked with (hello Chris) used to say, "Everything you say is wrong Mike":) What I've discovered since then is that you can do the same thing directly in IIS by changing a metabase property 'HttpRedirect' using WMI. This took a bit of digging since I'm not particularly familiar with the IIS metabase or WMI, but discovering the IIS Metabase Explorer made all the difference. It's part of the IIS 6.0 Resource Kit and you can use it to dig around the metabase and find out the name and type of the objects you need to access to get the property you're after. The main reference for the metabase is also quite comprehensive. I wanted to programatically set up redirects from C# so the first thing to dig into was the System.Management namespace which is the managed API for WMI. The documentation's not bad, but as soon as you start doing anything WMI related you're in the world of the sys admin , a world you don't want to go to where VBScript rules. You'll find plenty of examples for what you want to do, but not using System.Management. A bit of translation is required. Here's a little console app which sets up a couple of redirects on the default web site. The first redirects any requests for the 'google' directory to Google, so if you type the following into your browser (you'll have to change the server name) :
http://192.168.0.166/google/
You'll get redirected to the Google homepage. The other redirect redirects /bbc/ to the BBC News homepage.
using System;
using System.Management;

namespace WMITest
{
    public class Program
    {
        const string serverName = "192.168.0.166";
        const string userName = "Administrator"; 
        const string password = "mike";
        const string wmiPathToDefaultWebsite = "IIsWebVirtualDirSetting='W3SVC/1/ROOT'";
        const string redirectValue = 
            "*; /google/; http://www.google.com/" +
            "; /bbc/; http://news.bbc.co.uk/" +
            ", EXACT_DESTINATION";

        public static void Main()
        {
            ConnectionOptions options = new ConnectionOptions();
            options.Username = userName;
            options.Password = password;
            options.Authentication = AuthenticationLevel.PacketPrivacy;

            ManagementPath path = new ManagementPath();
            path.Server = serverName;
            path.NamespacePath = "root/MicrosoftIISv2";

            ManagementScope scope = new ManagementScope(path, options);

            using(ManagementObject obj = new ManagementObject(
                      scope, 
                      new ManagementPath(wmiPathToDefaultWebsite), null))
            {
                Console.WriteLine("{0}", obj.Path.RelativePath);
                if(obj.Properties.Count == 0)
                    Output.WriteLine("No properties found");

                obj.SetPropertyValue("HttpRedirect", redirectValue);
                obj.Put();

                string httpRedirectValue = obj.GetPropertyValue("HttpRedirect").ToString();
                Console.WriteLine("HttpRedirect='{0}'", httpRedirectValue);
            }
            Console.ReadLine();
        }
    }
}
The tricky stuff is knowing what value to put into the wmiPathToDefaultWebsite variable. The syntax seems to be ='', but it took me a while to find the correct type to get to the HttpRedirect property. The Metabase Explorer displayed 'IIsWebVirtualDir' as the type for the 'W3SVC/1/ROOT' node, but that property is actually only returned when you set the type to 'IIsWebVirtualDirSetting'. The actual path to the default web site (W3SVC/1/ROOT) is pretty easy to work out from looking at the Metabase Explorer. The WMI provider for the IIS metabase is 'root/MicrosoftIISv2' and also note that you have to call Put() on the ManagementObject after you set a property value for it to be persisted. You'll probabaly want to experiment with a virtual PC IIS, because it's easy to totally muck up your web site by playing with metabase values. The amount of information that's exposed by WMI is enormous. If you find yourself needing to do anything sys-admin-like programmatically it's often the only way to go. A lot of the main Microsoft applications now expose WMI APIs, SQL Server being one of them, so it can often be a choice for that kind of thing too. It has to remain a choice of last resort though, because it's a real pain trying to work out how to manipulate the given WMI object model. It's loosely typed, runtime discoverable stuff, so any managed API always has to be preferable to WMI.

Tuesday, April 24, 2007

URL Rewriting

I’ve been digging into URL rewriting recently. I want to take a request for a directory e.g: Http://www.mikehadlow.com/special/ And redirect it to a resource on a sub domain e.g: Http://features.mikehadlow.com/special_features.htm I first thought of using an HttpModule, but the problem is that it requires the request to be routed to the ASP.NET pipeline, but the ASP.NET pipeline is only invoked if the requested resource has an ASP.NET related file extension (.aspx, .asmx etc), a request for a directory will never invoke the ASP.NET pipeline, even if we ask HTTP.SYS to process all requests by doing a wildcard mapping to the ASP.NET handler (which is a bad idea anyway, since it creates a huge overhead for static content). It looks like the only way of doing this is to use an ISAPI extension like ISAPI_Rewrite (http://www.isapirewrite.com/). Reading up on it, this could be quite an elegant solution. It has built in redirection, so a simple configuration entry is all I would need to satisfy my requirements. The configuration file can be updated on the fly, so a little management interface would only have to do a simple file manipulation task to add, remove or edit redirects. It picks up the changes without requiring IIS to restart. I’ve downloaded the trial version, it seems to do the trick. I’ve experimented redirecting 'http://locahost/google' to 'http://www.google.com/', the only possible issue is that, because the browser is asked to redirect the url shown in the address bar changes to 'http://www.google.com/', but I’d get that with whatever solution I chose. The configuration file looks like this:
[ISAPI_Rewrite]
RewriteRule /google http://www.google.com [I,R]
There’s a free version that does per server URL rewriting, or a ‘pro’ version that costs $99 and allows per site configuration. Jeff Altwood talks about here. Update In my next post I realise that I can do the same thing by setting the HttpRedirect property in the IIS metabase. Also Travis Hawkins has a nice article here on using ISAPI_Rewrite. He also mentions another technique I hadn't considered before, of using the 404 error page to redirect unknown page requests to a generic handler. Travis also mentions RemapUrl, a tool that comes with the IIS 6.0 resource kit, but as he says, it's pretty limited.

Friday, April 13, 2007

Resolving mutipart WSDL documents with DiscoveryClientProtocol

Following on from yesterday's post on the WSDL that's generated by WCF, I ran up Relector and started digging into WSDL.exe. It doesn't do exactly the same thing that I want to do, its job is to generate proxy classes for web services, but still has to download and resolve mutiple linked WSDL and XSD files when given the URL of the root WSDL file. It turns out that is uses a class in the .NET framework, DiscoveryClientProtocol that does all the work of resolving and downloading the WSDL and XSD classes. It can even save them all to disk. I got quite excited when I also found out about ServiceDescriptionImporter, the name suggested that it could create a single ServiceDescription (the class that represents a WSDL file in memory) instance from a number of files, but alas it's the class that actually generates proxy class code. Matt Ward has an interesting article about how he used these classes to write the web reference functionality in SharpDevelop. Here's a little NUnit test showing the basic functionality of DiscoveryClientProtocol, it loads all the parts of the WSDL into its Documents collection, I then write the Keys of the documents (actually the URLs) to the console and finally save the documents to disk in 'My Documents\Wsdl":
using System;
using System.IO;
using System.Collections;
using System.Net;
using System.Web;
using System.Web.Services;
using System.Web.Services.Discovery;
using NUnit.Framework;

namespace MH.WsdlWorks.Tests
{
    [TestFixture]
    public class DiscoveryClientProtocolSpike
    {
        const string wsdlUrl = "http://localhost:1105/EchoService.svc?wsdl";

        [Test]
        public void DiscoveryClientProtocolTest()
        {
            DiscoveryClientProtocol client = new DiscoveryClientProtocol();
            client.Credentials = CredentialCache.DefaultCredentials;
            client.DiscoverAny(wsdlUrl);
            client.ResolveAll();

            foreach (DictionaryEntry dictionaryEntry in client.Documents)
            {
                Console.WriteLine("Key: {0}", dictionaryEntry.Key.ToString());
            }

            string myDocuments = Path.Combine(
                System.Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "Wsdl");
            client.WriteAll(myDocuments, "wsdlDocumentsMap.xml");
        }
    }
}

Thursday, April 12, 2007

WCF and WSDL

I've recently started using WCF to develop Web Services. It's a very compelling model that sits much better with a service oriented view of web services than ASMX. The flexibility it gives you is wonderful, you can have any executable host a web service, you can serialize your messages any way you want and you can use any transport. However, if you're just doing standard SOAP, XML over HTTP then it's almost as easy as ASMX.

Part of the change to a more standards oriented web service infratructure in WCF is the way it produces WSDL documents. ASMX by default will always spew out a WSDL document when you appended '?WSDL' to your web services' URL and that WSDL document is always a single file. WCF doesn't create a WSDL document by default, you have to configure that behaviour by adding a serviceMetadata behaviour to your config file:

<serviceBehaviors>
  <behavior name="myServiceBehaviour">
    <serviceMetadata httpGetEnabled="true" />
  </behavior>
</serviceBehaviors>

Also the default pattern for the WSDL file itself has changed, it follows best practices and devides the WSDL document up by namespaces, so if your portTypes namespace is different from your service namespace, you'll get two WSDL files and the service WSDL will reference the portTypes WSDL with a wsdl:import element. The types schemas are always produced as seperate files, again, one for each namespace.

The problem with factoring a WSDL document into seperate files is that many tools don't understand wsdl:import (or xml:include and xml:import) and will simply choke if you try to feed them it. You can override this default behaviour as Tomas Restrepo outlines in this blog post 'Inline XSD in WSDL with WCF', but of course it requires you to have access to the web service.

This behaviour has been giving me a headache because I'm currently trying to create a generic web service test tool. I'm using System.Web.Services.Description.ServiceDescription to load and parse a WSDL file that's given by the user as a URL, but when I tried to run it against a WCF service is choked because the URL only points to the root WSDL document which is in fact exported as several linked files. It looks like I'm going to have load the WSDL more intelligently. I tried running the wsdl.exe tool against the same URL and it works fine, so there might be solution there, I'll have to open up Reflector and have a look:)

Update: I resoloved how to do this in my next post.

More update: Christian Weyer has an interesting post on WCF and multipart WSDLs. He shows how to get WCF to output a single unfactored WSDL document.