code

NBuilder Utility for Child Complex Types

Posted on Updated on

NBuilder is a tool for .NET that allows the easy creation of test objects. It provides new instances of any class you provide (as a generic parameter) and those instances are populated with garbage data. I find that using NBuilder for test objects is a nice explicit way to show that the test does not care about the data but just needs an object in a decent enough state not to produce NullReferenceExceptions.

So given:


public class ComplexType
{
    public string Name { get; set; }
    public int Number {get;set;}
}

You could have the following test:


[Test]
public void Should_Return_Instantiated_Object_With_Test_Values()
{
    var result = Builder<ComplexObject>.CreateNew().Build();
    result.Should().NotBeNull();
    result.Name.Should().Be("Name1");
    result.Number = 1;
}

By the way that was also using FluentAssertions.

However, even after a fair bit of Googling I’m not sure why it does not include the creation of child complex objects, recursively. Instead these end up as null. So given:


public class OuterComplexType
{
    public int Number { get; set; }
    public InnerComplexType InnerComplexType { get; set; }
}

public class InnerComplexType
{
    public string Name { get; set; }
}

The following test would fail:


[Test]
public void Should_Return_Instantiated_Object_When_Given_Object_With_Complex_Properties()
{
    var result = Builder<OuterComplexType>.CreateNew().Build();
    result.InnerComplexType.Should().NotBeNull();
    result.InnerComplexType.Name.Should().Be("Name1");
    result.Number = 1;
}

So I wrote a little utility class that helped with this situation (see the Gist here). I have not used it in anger very much yet, so let me know if you spot any potential improvements.

using System;
using System.Linq;

using FizzWare.NBuilder;

namespace MarkGibaud.UnitTest.Utilities
{
    public static class NBuilderUtility
    {
        public static T Create<T>()
        {
            return (T)Create(typeof(T));
        }

        private static object Create(Type type)
        {
            if (type.IsInterface)
            {
                var firstConcreteImplementation = type.Assembly.GetTypes().FirstOrDefault(t => type.IsAssignableFrom(t) && !t.IsInterface);
                if (firstConcreteImplementation != null)
                    type = firstConcreteImplementation;
                else
                    return null;
            }

            var baseType = Build(type) ?? Build(Nullable.GetUnderlyingType(type));

            var complexTypeProperties = baseType.GetType().GetProperties().Where(p => !p.PropertyType.Namespace.Contains("System")).ToList();

            if (!complexTypeProperties.Any())
            return baseType;

            foreach (var complexTypeProperty in complexTypeProperties)
                complexTypeProperty.SetValue(baseType, Create(complexTypeProperty.PropertyType), null);

            return baseType;
        }

        private static object Build(Type type)
        {
            var builderClassType = typeof(Builder<>);
            Type[] args = { type };
            var genericBuilderType = builderClassType.MakeGenericType(args);
            var builder = Activator.CreateInstance(genericBuilderType);
            var createNewMethodInfo = builder.GetType().GetMethod("CreateNew");
            var objectBuilder = createNewMethodInfo.Invoke(builder, null);
            var buildMethodInfo = objectBuilder.GetType().GetMethod("Build");
            return buildMethodInfo.Invoke(objectBuilder, null);
        }
    }
}

Usage is something like this:

[Test]
public void Should_Return_Instantiated_Object_When_Given_Object_With_ComplexType_Properties()
{
    var result = NBuilderUtility.Create<OuterComplexType>();
    result.InnerComplexType.Should().NotBeNull();
    result.InnerComplexType.Name.Should().Be("Name1");
    result.Number = 1;
}

I guess the next step is to integrate this functionality into a fork of the NBuilder project…I’ll save that for a rainy day.

Solution to Combine, Minify and GZIP your JS and CSS

Posted on

I’ve recently completed a fair bit of research and consequent development at work that goes quite far to improve the performance of our web application’s front-end. I thought I’d share the basic solution here.

The first obvious thing to do is benchmark your site using Yahoo’s YSlow and Google’s PageSpeed. These will highlight the "low-hanging fruit" performance improvements to make. Unless you’ve already done so, the resulting suggestions will almost certainly include combining, minifying and gzipping your static content.

The steps we’re going to perform are:

  1. Write a custom HTTPHandler to combine and minify CSS.
  2. Write a custom HTTPHandler to combine and minify JS.
  3. Include a mechanism to ensure that the above only do their magic when the application is not in debug mode.
  4. Write a custom server-side web control to easily maintain css/js file inclusion.
  5. Enable GZIP of certain content types on IIS 6.

Right, let’s start with CSSHandler.asax that implements the .NET IHttpHandler interface:

using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Web;

namespace WebApplication1
{
    public class CssHandler : IHttpHandler
    {
        public bool IsReusable { get { return true; } }

        public void ProcessRequest(HttpContext context)
        {
            string[] cssFiles = context.Request.QueryString["cssfiles"].Split(',');

            List<string> files = new List<string>();
            StringBuilder response = new StringBuilder();
            foreach (string cssFile in cssFiles)
            {
                if (!cssFile.EndsWith(".css", StringComparison.OrdinalIgnoreCase))
                {
                    //log custom exception
                    context.Response.StatusCode = 403;
                    return;
                }

                try
                {
                    string filePath = context.Server.MapPath(cssFile);
                    string css = File.ReadAllText(filePath);
                    string compressedCss = Yahoo.Yui.Compressor.CssCompressor.Compress(css);
                    response.Append(compressedCss);
                }
                catch (Exception ex)
                {
                    //log exception
                    context.Response.StatusCode = 500;
                    return;
                }
            }

            context.Response.Write(response.ToString());

            string version = "1.0"; //your dynamic version number 

            context.Response.ContentType = "text/css";
            context.Response.AddFileDependencies(files.ToArray());
            HttpCachePolicy cache = context.Response.Cache;
            cache.SetCacheability(HttpCacheability.Public);
            cache.VaryByParams["cssfiles"] = true;
            cache.SetETag(version);
            cache.SetLastModifiedFromFileDependencies();
            cache.SetMaxAge(TimeSpan.FromDays(14));
            cache.SetRevalidation(HttpCacheRevalidation.AllCaches);
        }
    }
}

Ok, now some explanation:

IsReUsable property:

We aren’t dealing with anything instance-specific, which means we can safely reuse the same instance of the handler to deal with multiple requests, because our ProcessRequest is threadsafe. More info.

ProcessRequest method:

Nothing too hectic going on here. We’re looping through the CSS files given to us (see the CSSControl below for how they’re coming in) and compressing each one, using a .NET port of Yahoo’s YUICompressor, before adding the contents to the outgoing response stream.

The remainder of the method deals with setting up some HTTP caching properties to further optimise the way the browser client downloads (or not, as the case may be) content.

  • We set Etags in code so that they may be the same across all machines in our server farm.
  • We set Response and Cache dependencies on our actual files so, should they be replaced, cache will be invalidated.
  • We set Cacheability such that proxies can cache.
  • We VaryByParams using our cssfiles attribute, so that we can cache per CSS file group submitted through the handler.

And here is the CSSControl, a custom server-side control inheriting the .NET LiteralControl.

Front:

<customcontrols:csscontrol id="cssControl" runat="server">
          <CustomControls:Stylesheet File="main.css" />
          <CustomControls:Stylesheet File="layout.css" />
          <CustomControls:Stylesheet File="formatting.css" />
</customcontrols:csscontrol>

Back:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Web;
using System.Web.UI;
using System.Linq;
using TTC.iTropics.Utilities;

namespace WebApplication1
{
    [DefaultProperty("Stylesheets")]
    [ParseChildren(true, "Stylesheets")]
    public class CssControl : LiteralControl
    {
        [PersistenceMode(PersistenceMode.InnerDefaultProperty)]
        public List<Stylesheet> Stylesheets { get; set; }

        public CssControl()
        {
            Stylesheets = new List<Stylesheet>();
        }

        protected override void Render(HtmlTextWriter output)
        {
            if (HttpContext.Current.IsDebuggingEnabled)
            {
                const string format = "<link rel="Stylesheet" href="stylesheets/{0}"></link>";

                foreach (Stylesheet sheet in Stylesheets)
                    output.Write(format, sheet.File);
            }
            else
            {
                const string format = "<link type="text/css" rel="Stylesheet" href="stylesheets/CssHandler.ashx?cssfiles={0}&version={1}"/>";
                IEnumerable<string> stylesheetsArray = Stylesheets.Select(s => s.File);
                string stylesheets = String.Join(",", stylesheetsArray.ToArray());
                string version = "1.00" //your version number

                output.Write(format, stylesheets, version);
            }

        }
    }

    public class Stylesheet
    {
        public string File { get; set; }
    }
}

HttpContext.Current.IsDebuggingEnabled is hooked up to the following setting in your web.config:

<system.web>
    <compilation debug="false">
</system.web>

So, basically, if your site is in debug mode you get HTML markup like this:

<link rel="Stylesheet" href="stylesheets/formatting.css"></link>
<link rel="Stylesheet" href="stylesheets/layout.css"></link
<link rel="Stylesheet" href="stylesheets/main.css"></link>

But if you’re in production mode (debug=false), you’ll get markup like this:

<link type="text/css" rel="Stylesheet" href="CssHandler.ashx?cssfiles=main.css,layout.css,formatting.css&version=1.0"/>

The latter will then obviously invoke the CSSHandler, which will take care of combining, minifying and cache-readying your static CSS content.

All of the above can then also be duplicated for your static JavaScript content:

`JSHandler.ashx:

using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Web;

namespace WebApplication1
{
    public class JSHandler : IHttpHandler
    {
        public bool IsReusable { get { return true; } }

        public void ProcessRequest(HttpContext context)
        {
            string[] jsFiles = context.Request.QueryString["jsfiles"].Split(',');

            List<string> files = new List<string>();
            StringBuilder response = new StringBuilder();

            foreach (string jsFile in jsFiles)
            {
                if (!jsFile.EndsWith(".js", StringComparison.OrdinalIgnoreCase))
                {
                    //log custom exception
                    context.Response.StatusCode = 403;
                    return;
                }

                try
                {
                    string filePath = context.Server.MapPath(jsFile);
                    string js = File.ReadAllText(filePath);
                    string compressedJS = Yahoo.Yui.Compressor.JavaScriptCompressor.Compress(js);
                    response.Append(compressedJS);
                }
                catch (Exception ex)
                {
                    //log exception
                    context.Response.StatusCode = 500;
                    return;
                }
            }

            context.Response.Write(response.ToString());

            string version = "1.0"; //your dynamic version number here

            context.Response.ContentType = "application/javascript";
            context.Response.AddFileDependencies(files.ToArray());
            HttpCachePolicy cache = context.Response.Cache;
            cache.SetCacheability(HttpCacheability.Public);
            cache.VaryByParams["jsfiles"] = true;
            cache.VaryByParams["version"] = true;
            cache.SetETag(version);
            cache.SetLastModifiedFromFileDependencies();
            cache.SetMaxAge(TimeSpan.FromDays(14));
            cache.SetRevalidation(HttpCacheRevalidation.AllCaches);
        }
    }
}

And its accompanying JSControl:

Front:

<customcontrols:JSControl ID="jsControl" runat="server">
    <customcontrols:Script File="jquery/jquery-1.3.2.js" />
    <customcontrols:Script File="main.js" />
    <customcontrols:Script File="creditcardpayments.js" />
</customcontrols:JSControl>

Back:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Web;
using System.Web.UI;
using System.Linq;

namespace WebApplication1
{
    [DefaultProperty("Scripts")]
    [ParseChildren(true, "Scripts")]
    public class JSControl : LiteralControl
    {
        [PersistenceMode(PersistenceMode.InnerDefaultProperty)]
        public List<Script> Scripts { get; set; }

        public JSControl()
        {
            Scripts = new List<Script>();
        }

        protected override void Render(HtmlTextWriter writer)
        {
            if (HttpContext.Current.IsDebuggingEnabled)
            {
                const string format = "<script src="scripts\{0}"></script>";

                foreach (Script script in Scripts)
                    writer.Write(format, script.File);
            }
            else
            {
                IEnumerable<string> scriptsArray = Scripts.Select(s => s.File);
                string scripts = String.Join(",", scriptsArray.ToArray());
                string version = "1.0" //your dynamic version number
                const string format = "<script src="scripts/JsHandler.ashx?jsfiles={0}&version={1}"></script>";

                writer.Write(format, scripts, version);
            }
        }
    }

    public class Script
    {
        public string File { get; set; }
    }
}

Enabling GZIP:

As Jeff Atwood says, enabling Gzip on your web site server is a no-brainer. After some tracing, I decided to enable Gzip on the following file types:

  • .css
  • .js
  • .axd (Microsoft Javascript files)
  • .aspx (Usual ASP.NET Web Forms content)
  • .ashx (Our handlers)

To enable HTTP Compression on your IIS 6.0 web server:

  1. Open IIS, Right click Web Sites, Services tab, enable Compress Application Files and Compress Static Files
  2. Stop IIS
  3. Open up IIS Metabase in Notepad (C:WINDOWSsystem32inetsrvMetaBase.xml) – and make a back up if you’re nervous about these things 🙂
  4. Locate and overwrite the two IIsCompressionScheme and one IIsCompressionSchemes elements with the following:
<IIsCompressionScheme    Location ="/LM/W3SVC/Filters/Compression/deflate"
        HcCompressionDll="%windir%system32inetsrvgzip.dll"
        HcCreateFlags="0"
        HcDoDynamicCompression="TRUE"
        HcDoOnDemandCompression="TRUE"
        HcDoStaticCompression="TRUE"
        HcDynamicCompressionLevel="9"
        HcFileExtensions="htm
            html
            txt
            css"
        HcOnDemandCompLevel="9"
        HcPriority="1"
        HcScriptFileExtensions="asp
            dll
            exe
            aspx
            js
            ashx
            axd"
    >
</IIsCompressionScheme>
<IIsCompressionScheme    Location ="/LM/W3SVC/Filters/Compression/gzip"
        HcCompressionDll="%windir%system32inetsrvgzip.dll"
        HcCreateFlags="1"
        HcDoDynamicCompression="TRUE"
        HcDoOnDemandCompression="TRUE"
        HcDoStaticCompression="TRUE"
        HcDynamicCompressionLevel="9"
        HcFileExtensions="htm
            html
            txt
            css"
        HcOnDemandCompLevel="9"
        HcPriority="1"
        HcScriptFileExtensions="asp
            dll
            exe
            aspx
            js
            ashx
            axd"
    >
</IIsCompressionScheme>
<IIsCompressionSchemes    Location ="/LM/W3SVC/Filters/Compression/Parameters"
        HcCacheControlHeader="max-age=86400"
        HcCompressionBufferSize="8192"
        HcCompressionDirectory="%windir%IIS Temporary Compressed Files"
        HcDoDiskSpaceLimiting="FALSE"
        HcDoDynamicCompression="TRUE"
        HcDoOnDemandCompression="TRUE"
        HcDoStaticCompression="TRUE"
        HcExpiresHeader="Wed, 01 Jan 1997 12:00:00 GMT"
        HcFilesDeletedPerDiskFree="256"
        HcIoBufferSize="8192"
        HcMaxDiskSpaceUsage="99614720"
        HcMaxQueueLength="1000"
        HcMinFileSizeForComp="1"
        HcNoCompressionForHttp10="FALSE"
        HcNoCompressionForProxies="FALSE"
        HcNoCompressionForRange="FALSE"
        HcSendCacheHeaders="FALSE"
    >

And that’s it! This saved us heaps of bandwidth and resulted in a more responsive web application throughout.

Enjoy!