Archive for April 2011

30

April 2011

Formatting PowerShell Objects using the Razor Engine

I've been meaning to look into PowerShell for a while - it's been on an ever-growing list of things I'd like to learn more about. Recently I got around to spending some time with it and decided an interesting way to learn a little would be to create a module that allowed you to format objects using the Razor Engine.

Edit: When writing this article, I forgot to mention you need to make Powershell run .NET 4 to be able to use Razor. I did this by creating powershell.exe.config and powershell_ise.exe.config files as explained here.

The goal of the module is that you would be able to pipe some objects inand pass some template text, like this:

PS> Get-Command | Format-Razor "Command: @Model.Name, @for (int i = 0; i < 5; i++) { @i }"

Ok, so this sample isn't particularly useful, but you should get the idea. The string passed in could be read from a file, and the output could be written to a file. This will allow you to format objects using Razor, which I think it is a pretty cool templating language.

When I opened PowerShell ISE to start coding, I actually expected this to be only a line or two of code, but it turned out to be a little more complicated. Because Razor compiles to code, and you'd likely only want to do this once regardless of how many times the template would be used, it's not as simple as just passing an object and a template into Razor. First we had to create the Razor Engine:

# Create an instance of the Razor engine for C#
$language = New-Object System.Web.Razor.CSharpRazorCodeLanguage
$host = New-Object System.Web.Razor.RazorEngineHost($language)
    
# Set some default properties for the Razor-generated class
$host.DefaultBaseClass = "TemplateBase" # This is our base class (created below)
$host.DefaultNamespace = "RazorOutput"
$host.DefaultClassName = "Template"

# Add any default namespaces that will be useful to use in the templates
$host.NamespaceImports.Add("System") | Out-Null

New-Object System.Web.Razor.RazorTemplateEngine($host)

This sets up the Razor Engine with some fairly basic settings, including the name and namespace of the generated class, and also the base class. The base class is required to have a few things for Razor to be able to use it:

  • A virtual Execute() method
  • A Write(object) method, called when Razor outputs a variable/expression
  • A WiteLiteral(object) method, called when Razor outputs literal content from the template

Because it's not trivial (or certainly, I couldn't find a way) to create a class inside PowerShell, I cheated a little bit. To avoid including a binary .NET assembly, I compiled the base class from a string on the fly:

# HACK: To avoid shipping a DLL, we're going to just compile our TemplateBase class here
$baseClass = @"
using System.IO;
using System.Text;

	public abstract class TemplateBase
	{
		public StringBuilder Buffer { get; set; }
		public StringWriter Writer { get; set; }
		public dynamic $modelName { get; set; }

		public TemplateBase()
		{
			this.Buffer = new StringBuilder();
			this.Writer = new StringWriter(this.Buffer);
		}

		public abstract void Execute();

		public virtual void Write(object value)
		{
			WriteLiteral(value);
		}

		public virtual void WriteLiteral(object value)
		{
			Buffer.Append(value);
		}
	}
"@
    
# Set up the compiler params, including any references required for the compilation
$codeProvider = New-Object Microsoft.CSharp.CSharpCodeProvider
$assemblies = @(
	[System.Reflection.Assembly]::LoadWithPartialName("System.Core")
		.CodeBase.Replace("file:///", ""),
	[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.CSharp")
		.CodeBase.Replace("file:///", "")
)    
$compilerParams = New-Object System.CodeDom.Compiler.CompilerParameters(,$assemblies)
    
# Compile the template base class    
$templateBaseResults = $codeProvider.CompileAssemblyFromSource($compilerParams, $baseClass);
    
# Add the (just-generated) template base assembly to the compile parameters
$assemblies = $assemblies + $templateBaseResults.CompiledAssembly
	.CodeBase.Replace("file:///", "")
$compilerParams = New-Object System.CodeDom.Compiler.CompilerParameters(,$assemblies)
    
# Compile the Razor-generated code
$codeProvider.CompileAssemblyFromDom($compilerParams, $razorResult.GeneratedCode)

This code first compiles the base class into an assembly, and then calls Razor to generate its own code, which is then compiled with a reference to the first assembly, which contains the base template class.

Finally, we create an instance of the Razor-generated class which we'll finally use for the transformation:

# Grab the assembly that contains the Razor-generated classes
$assembly = $results.CompiledAssembly
    
# Create an instance of our Razor-generated class (this name is hard-coded above)
$type = $assembly.GetType("RazorOutput.Template")
[System.Activator]::CreateInstance($type)

To perform a transformation, all we need to do is set the Model property on the Template, and then call the Execute method:

# Set the model to the current object, using the $modelName param so the user can customise it
$template.$modelName = $model
        
# Execute the code, which writes the output to our buffer
$template.Execute()
        
# "Return" the output for this item
$template.Buffer.ToString()

And that's it, now we can format our objects using a Razor template, as shown earlier:

PS> Get-Command | Format-Razor "Command: @Model.Name, @for (int i = 0; i < 5; i++) { @i }"

The full source code is available here. Feel free to do with it as you wish. If you make any useful changes to it, please do send them over (you can send a Pull Request on Bitbucket if you like). This is my first PowerShell module, so I'm sure there's plenty that can be improved!

27

April 2011

MarkdownHelper on NuGet, using MarkdownDeep

Last month I posted a small HtmlHelper to make transforming Markdown in an ASP.NET MVC application a little easier. Unfortunately, getting it up and running wasn't quite so easy... You had to go and download MarkdownSharp (or copy the code file from the Google Code site) and put it in your project, then copy/paste my code into a file, add the namespace to a Views/Web.config, and blah blah, you gave up already.

Not any more!

I created a package called MarkdownHelper on NuGet. It took me less time to create the package than it took to get up and running previously, but now using the package is as simple as typing the following into the Package Manager Console...

PM> Install-Package MarkdownHelper

... that's it! All done! Now in your views, you can simply type:

@Html.Markdown(Model.YourMarkdownPropertyHere)

This means you no longer have to copy/paste code around. This is mighty useful if you're using Markdown in multiple ASP.NET MVC applications.

Rather than creating another assembly to import, the helper class will be put directly into your project (inside a cryptically named "Helpers" folder), though if this isn't the done thing, I can change it easily enough.

Also worth noting that I changed from MarkdownSharp to MarkdownDeep.NET. In addition to being faster (which isn't really important unless you're transforming a lot of text), it has a Javascript version that works transforms 100% the same, which will come in handy if you're writing an editor. Currently the package imports only the .NET version, though this might change as I add functionality.

04

April 2011

Improving Performance of the Disqus Commenting System

With search engines using page loads times as a factor in ranking results, making sure your website loads quickly is becoming more and more important. Gone are the days when we used to say "If your website doesn't load in 10 seconds, people will go elsewhere". Nowadays, people expect websites to load in tenths of seconds, not seconds.

There are a huge number of browser addons/tools that will help you measure the performance of your website, but my current favourite way to check is using loads.in. Some of the things highlighted by loads.in when I entered the address of this blog were trivial to fix (loading Google's API and jQuery when neither are being used, for example!). Some others required a little more thought :(

The next-slowest target was the Disqus comments system. It wasn't particularly slow, but there was one part that frustrated me:

The request for http://dantup.disqus.com/count.js takes around 600ms and just returns a 302 redirect to http://mediacdn.disqus.com/1301599987/build/system/count.js.

I'm not entirely sure of the reason for this redirect, but it's adding 600ms to my page being ready. This extra round-trip will also be far slower on a mobile device, and with 3G tablets become more popular, it makes sense to try and eliminate this.

This looks pretty easy to "fix", but before changing the path to be hard-coded to the mediacdn address, I asked Disqus if they saw any problems with this (after all, the redirect might be because the mediacdn address may change). Unfortunately, they weren't really sure if this would be a problem :(

Another possible problem is that the redirect could be different depending on the user agent. If I hard-code it, this would be gone. After a little testing, I decided this wasn't the case, and the redirect was always the same (and this makes sense, because the work is still exactly the same regardless of the user agent - not to mention browser-sniffing this way being a terrible idea).

I decided to give it a try anyway. It's just comment counts. If the URL changes, the comment counts will be broken until I notice. That's hardly the end of the world. So, my change looks like this:

// Old Code
<script type="text/javascript">
    var disqus_shortname = 'dantup';

    (function () {
        var s = document.createElement('script'); s.async = true;
        s.type = 'text/javascript';
        s.src = 'http://' + disqus_shortname + '.disqus.com/count.js';
        (document.getElementsByTagName('HEAD')[0] || document.getElementsByTagName('BODY')[0]).appendChild(s);
    } ());
</script>

// New Code
<script type="text/javascript">
    var disqus_shortname = 'dantup';

    (function () {
        var s = document.createElement('script'); s.async = true;
        s.type = 'text/javascript';
        s.src = 'http://mediacdn.disqus.com/13015111111199987/build/system/count.js';
        (document.getElementsByTagName('HEAD')[0] || document.getElementsByTagName('BODY')[0]).appendChild(s);
    } ());
</script>

I tested this using loads.in, and it actually took two full seconds off the time until the onload event fired, from around 4s to 2s! As well as eliminating the extra round trip which was pretty slow, there's no longer a DNS lookup for dantup.disqus.com.

So, assuming Disqus don't change this, it looks like a fairly safe way to speed up a site with Disqus comment counts. If you're going to try it yourself, be sure you work out the correct count.js path by following your own redirect! I edited the numbers in the URL of my sample code to avoid people copy/pasting my URL in.

I'd love to see Disqus eliminate this roundtrip themselves, so everybody using Disqus would get this little boost. Until then, this seems to be a decent workaround.