Wednesday, May 25, 2005

Dynamic control generation using XSLT

In a recent project, we needed to render .net controls dynamically. Instead of using ascx controls, we used XSLT to transform static Xml data into dynamic presentation layers; a viable approach to providing multiple look and feels for the site. Here's a quick walkthrough of how it works.

Dynamically loading the HTML is simple, using the built-in ParseControl method of the System.Web.UI.Page class.

Control c = Page.ParseControl( "<b> Literal Text </b>");

The same logic can be applied to parsing the output of an Xslt transformation.

string transformOutput = xslTransformer.Transform();
Control c = Page.ParseControl(transformOutput);

If you want to add server-side controls into the transformation, there are a few gotchas:

  1. XSLT parser needs namespace definitions for .net and custom tag prefixes.
  2. Namespaces appear as attributes in the formatted output, which will need to be stripped out before adding to the Page.Controls collection.

If you're adding asp.net controls or custom controls into your XSLT file, you'll need to declare the namespace at the top of the xslt header, otherwise the XML Parser will complain:

xmlns:asp="http://schemas.microsoft.com/AspNet/WebControls"

Incidentally, if you're using Visual Studio 2003, adding this namespace declaration will give you Intellisense for your asp.net controls in your XSLT file.

With all the proper namespaces defined, you'll bypass any nasty Xslt Parser errors, however, these namespaces will now appear scattered throughout your transformed output as attributes. If you leave these values in and try to parse the output into a control, the .net runtime will try to resolve the namespaces as attributes of the controls and will generate runtime errors.

Fortunately, a quick regular expression can strip all these namespaces out of the transformed output:

string transformedOutput = transformedOutput.Replace("xmlns:\w+=\"[^"]+\"", "");

With the namespaces stripped out of the transformed output, you should be able to parse the string into a control.

using System.Xml;
using System.Xml.Xsl;
using System.Web;

XslTransform transform = new XslTransform();
transform.resolver = new System.Uri("http://localhost"); 
string xslFilePath = Server.MapPath("/controls.xslt");
transform.Load( xslFilePath );

string xmlFilePath = Server.MapPath("/data.xml");
string transformedOutput = transform.Transform( xmlFilePath );
transformedOutput = transformedOutput.Replace("xmlns:\w+=\"[^"]+\"", "");

Control c = Page.ParseControl( transformedOutput ); 
Page.Controls.Add( c );

Cheers

Monday, May 23, 2005

Poking around in SMTP

A few years back, I had seen a mailer engine that spit out email messages in EML format. I found this engine to be extremely helpful because I could turn off the SMTP service, run the mailer-engine, and then manually inspect the messages in Outlook Express. If the messages were fine, I re-enabled the SMTP service.

At the time, it was clear that the engine was leveraging the CDO.Message object's ability to persist to a file.

Recently, I wondered if this was possible using the .net framework, but was dissapointed to find out that the System.Web.Mail.SMTPServer object only exposed the method "Send".

Enter Reflector.

The SMTPServer implementation is very interesting. Reflection shows that there are three internal classes:

  • CdoSysHelper
  • CdoNtHelper
  • LateBoundAccessHelper

As it turns out, the SMTPServer object acts as a proxy between the legacy COM objects, "CDO.Message" and "CDONTS.NewMail" -- it detects which environment you are using, then delegates the sending of the message to the "Send" method to the appropriate COM object.

What's equally interesting, is that the .net framework doesn't use COM Interop to talk to the COM objects. Instead, it uses the LateBoundAccessHelper to instantiate the COM object by it's ProgId, and then manually sets its properties and methods using Late Binding.

To stream my messages out into EML format I suppose I could use COM Interop to the CDO library, but that would mean I would have to package the interop wrapper (and its dependencies) with my assembly.

I'll have to take a look at extracting this code using a Reflector add-in, and extending the class to save to a file instead of sending directly. It'll be interesting if I can do this without adding an Interop wrapper to CDO. I wonder if there'll be any performance drawbacks in this approach...

yet another side-project...

Saturday, May 21, 2005

The saga ends

At the end of Star Wars: Episode III Revenge of the Sith, everything wraps up nicely, making a decent bridge between episode III and IV. However, it's a surreal experience, to see the end of a saga complete itself in the middle.

So how was it? Prior to going to the show, everyone gave me one of two reviews. Either "Good, the first two were bad." or "Awesome, the first two sucked." Clearly the delta between good and bad and awesome and sucked or equal.

Personally, I enjoyed it -- or rather, parts of it. Unlike all other star-wars movies there seems to be a whole lot more story to tell: from Annakin's fall to the darkside and the dawning of Darth Vader, to the end of the clone wars and the death of the republic, not to mention the wrapping up of one trilogy and the segway to the next.

This bulk of multiple stories can't be told with action alone, so there's a whole lot of Lucas-style dialogue that has to find its way into two hours and twenty minutes. As result, I found it to be very fragmented -- only a few seconds of setup for a scene, some forced dialogue, followed by the traditional Star Wars "swipe" to the next story.

But it's done well, and clearly one of the lessons Lucas has learned since the last two films is the special effects are the vehicle, not the story. Revenge of the Sith uses the technology from the last two films transparently. There isn't a twenty minute Pod race, nor a pointless cgi character that doesn't seem like a ILM "show off". Yoda and General Grievous are done so well, you become absorbed by the complexities of their character rather than than their near-life-like representation.

All in all -- it may be Lucas's redemption for the Star Wars saga. I might even see it again.

Monday, May 09, 2005

Thursday, May 05, 2005

Star Wars III hype

The new star wars movie is upon us in a few weeks. I'm a little undecided.

I grew up as a star wars fan. I had all the toys, the books ... the works. Back then we didn't have VCR's, so the trip to the theatre meant your brain was on full record. I had known since I was a kid that there was supposed to be nine movies. I had played it out in my head what they were supposed to be about, and I always figured that the 9th one was where Darth Vader bit it. (Side rant: How surprised was I when Luke pulled Darth Vader's helmet off in ROTJ???)

Needless to say, I had some high expectations for Episode I. But I played it down, was cool about the whole thing. I remember sitting in the theatre, just before the show was about to start, and it felt like any other movie experience. But when the green lucas film logo faded and the bright blue familiar "far far away" catch-phrase started to fade it dawned on me... "I have no idea what comes next" and I was instantly 7 years old again.

The last two have been pretty disappointing. I don't need to go into the details of that. The whole thing seems like a bad car wreck, you don't want to watch but you're strangely compelled.

At this point, the trailer looks pretty good. Actually, the trailer looks *too* good. But still, Lucas could screw it up. After all, he's considered some sort of editing genius -- he could have given very specific instructions for the trailer crew, "Be sure to exclude all suck-ass parts".

You never know, Jar-Jar could show up with a light-saber screaming "Mesa using-da force!"

Please Mr Lucas, don't screw this one up. If you do, my enter childhood memories will be forfeit. At the very least, please tell me you hired a dialogue coach.

I'll show up with my brain set for record.