09 Apr 2008

Getting IIS 7 to Compress JavaScript

13 Comments Uncategorized

One of the many recommendations that Yahoo makes on optimizing your web site for high amounts of traffic, and to make the response time speedier to your user is GZip encoding all your static content. I usually do this as a standard for setting up any of my Web Servers, in addition to setting expiration headers on my static content, to ensure that I am serving as little content as possible.

IIS 7 has improved and simplified the compression and serving of static files by making it easier to setup and configure than previously in IIS 6.0. The IIS 7.0 compression works perfectly for CSS, HTML, and Text files, however JavaScript is another story. The JavaScript files on my IIS 7.0 server were not being compressed and served with GZip encoding, which is a major problem for any Web 2.0 site where 75% of your content severed per request is JavaScript. (I just made that number up, but it sounds right!)

I found Rick Strahl’s post on this very subject that he wrote up about a 9 months ago. It was helpful in diagnosing my problem, however it didn’t solve it. The HTTP compression is configured in IIS 7.0′s ApplicationHost.config file (c:\windows\system32\inetsrv\config\applicationhost.config), see below for the default settings:

<httpCompression directory="%SystemDrive%\websites\_compressed" minFileSizeForComp="0">
    <scheme name="gzip" dll="%Windir%\system32\inetsrv\gzip.dll" />
    <staticTypes>
        <add mimeType="text/*" enabled="true" />
        <add mimeType="message/*" enabled="true" />
        <add mimeType="application/javascript" enabled="true" />
        <add mimeType="*/*" enabled="false" />
    </staticTypes>
</httpCompression>

As you can see anything that starts with the MIME type of text or message is GZip encoded just fine. However there is also application/javascript as a compressible MIME type, there is nothing wrong with that, because there are 3 accepted ways to set a JavaScript MIME type.

  1. text/javascript
  2. application/x-javascript
  3. application/javascript

However the problem comes in when you look at the default MIME type mappings setup, in the same ApplicationHost.config file, a little further down.

...
    <mimeMap fileExtension=".jpg" mimeType="image/jpeg" />
    <mimeMap fileExtension=".js" mimeType="application/x-javascript" />
    <mimeMap fileExtension=".jsx" mimeType="text/jscript" /
...

As you may notice the MIME type for JavaScript files is set to application/x-javascript, which is not the same as the default in the compression section above. So I added the following MIME type, application/javascript, to my Web.config thinking I had the problem licked, and all that I had to do was change the default MIME type for JavaScript files.

<system.webServer>
    <staticContent>
        <remove fileExtension=".js" />
        <mimeMap fileExtension=".js" mimeType="application/javascript" />
    </staticContent>
</system.webServer>

However that didn’t work either, and it should have because the MIME type now matched my compression MIME type. I even verified the MIME type in fiddler. So I then tried my last option to change the MIME type to text/javascript, which is the defacto standard on the internet for JavaScript MIME types.

<system.webServer>
    <staticContent>
        <remove fileExtension=".js" />
        <mimeMap fileExtension=".js" mimeType="text/javascript" />
    </staticContent>
</system.webServer>

Finally, this was the key to getting the JavaScript GZip Compression working IIS 7.0. And this didn’t require me to modify the ApplicationHost.config file get it done. Which is something I love about the new IIS 7.0, I can do my whole server configuration through FTP and my Web.config file.

24 Aug 2007

Creating a more accurate JSON .NET Serializer

3 Comments Uncategorized

Recently I have been diving in to the world of REST and all the great things that come along with that. If you are not familiar with REST and what it means to have a REST Web Service for your site, you can go through the Digg API, which should give you a pretty good idea. My company has been contracted to build the framework for a new Web 2.0 initiative of one of our clients. You cannot do Web 2.0 if you are not using some kind of AJAX/REST combination.

With the inclusion of Microsoft AJAX.NET are some very useful tools that have been added to the web services library. My current focus in System.Web.Script.Serialization.JavaScriptSerializer, which takes you objects and turns them in to a JSON string that can be evaluated by JavaScript and reversed in to an object. JSON (pronounced Jason) is very useful in AJAX because you do not have to retrieve and parse XML through the XML Browser Request that powers current AJAX implementations.

I found the attribute support lacking in the JavaScriptSerializer when compared to the XmlSerializer, ScriptIgnoreAttribute compared to XmlRootAttribute, XmlAttributeAttribute, XmlIgnoreAttribute, XmlElementAttribute, XmlArrayAttribute, and XmlArrayItemAttribute. So I decided to extend the JavaScriptSerializer to use the XML attributes to serialize my objects and give me greater control over how they were written in to JSON text form. The added benefit was that my XML and JSON outputs serialized the exact same way when the web service generated them. Below I have included the code.

using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Reflection;
using System.Web.Script;
using System.Web.Script.Serialization;
using System.Xml.Serialization;

namespace ManagedFusion.Script.Serializer
{
	public class XmlJavaScriptConverter<T> : JavaScriptConverter
	{
		public override object Deserialize(IDictionary<string,string> dictionary, Type type, JavaScriptSerializer serializer)
		{
			throw new Exception("The method or operation is not implemented.");
		}

		public override IDictionary<string,string> Serialize(object obj, JavaScriptSerializer serializer)
		{
			return SerializeObject(obj);
		}

		private IDictionary<string,string> SerializeObject(object obj)
		{
			IDictionary<string,string> values = new Dictionary<string,string>();
			Type type = obj.GetType();

			foreach (FieldInfo info in type.GetFields(BindingFlags.Public | BindingFlags.Instance))
				if (!info.IsDefined(typeof(XmlIgnoreAttribute), true))
					values.Add(SerializeName(info), SerializeValue(info.GetValue(obj), info));

			foreach (PropertyInfo info2 in type.GetProperties(BindingFlags.GetProperty | BindingFlags.Public | BindingFlags.Instance))
			{
				if (!info2.IsDefined(typeof(XmlIgnoreAttribute), true))
				{
					MethodInfo getMethod = info2.GetGetMethod();
					if ((getMethod != null) && (getMethod.GetParameters().Length <= 0))
						values.Add(SerializeName(info2), SerializeValue(getMethod.Invoke(obj, null), info2));
				}
			}

			return values;
		}

		private string SerializeName(MemberInfo member)
		{
			string name = null;

			if (member.IsDefined(typeof(XmlElementAttribute), true))
			{
				object[] attrs = member.GetCustomAttributes(typeof(XmlElementAttribute), true);

				if (attrs.Length > 0)
				{
					XmlElementAttribute attr = attrs[0] as XmlElementAttribute;

					name = attr.ElementName;
				}
			}

			if (member.IsDefined(typeof(XmlAttributeAttribute), true))
			{
				object[] attrs = member.GetCustomAttributes(typeof(XmlAttributeAttribute), true);

				if (attrs.Length > 0)
				{
					XmlAttributeAttribute attr = attrs[0] as XmlAttributeAttribute;

					name = attr.AttributeName;
				}
			}

			if (member.IsDefined(typeof(XmlArrayAttribute), true))
			{
				object[] attrs = member.GetCustomAttributes(typeof(XmlArrayAttribute), true);

				if (attrs.Length > 0)
				{
					XmlArrayAttribute attr = attrs[0] as XmlArrayAttribute;

					name = attr.ElementName;
				}
			}

			if (String.IsNullOrEmpty(name))
				name = null;

			return name ?? member.Name;
		}

		private object SerializeValue(object obj, MemberInfo member)
		{
			if (obj == null)
				return obj;

			// make sure the object isn't an easily handled primity type
			if (Type.GetTypeCode(obj.GetType()) != TypeCode.Object)
				return obj;

			if (obj is IDictionary)
				return obj;

			if (obj is ICollection)
			{
				IList<object> list = new List<object>();
				object[] attrs = member.GetCustomAttributes(typeof(XmlArrayItemAttribute), true);
				string arrayName = null;

				if (attrs.Length > 0)
				{
					XmlArrayItemAttribute attr = attrs[0] as XmlArrayItemAttribute;

					arrayName = attr.ElementName;
				}

				if (String.IsNullOrEmpty(arrayName))
				{
					foreach (object o in (obj as ICollection))
					{
						if (Type.GetTypeCode(o.GetType()) != TypeCode.Object)
							list.Add(o);
						else
							list.Add(SerializeObject(o));
					}
				}
				else
				{
					foreach (object o in (obj as ICollection))
					{
						IDictionary<string,object> list2 = new Dictionary<string,object>();

						if (Type.GetTypeCode(o.GetType()) != TypeCode.Object)
							list2.Add(arrayName, o);
						else
							list2.Add(arrayName, SerializeObject(o));

						list.Add(list2);
					}
				}

				return list;
			}

			return SerializeObject(obj);
		}

		public override IEnumerable<Type> SupportedTypes
		{
			get { return new ReadOnlyCollection<Type>(new List<Type>(new Type[] { typeof(T) })); }
		}
	}
}

Then to serialize the object in my code I have the following code in place.

JavaScriptSerializer jsSerializer = new JavaScriptSerializer();
jsSerializer.RegisterConverters(new List<JavaScriptConverter>(new JavaScriptConverter[] { new XmlJavaScriptConverter<T>() }));
string response = jsSerializer.Serialize(SerializableObject);

The serializer from above outputs the following code.

{"timestamp":"\/Date(1187968133328)\/","maxCount":10,"count":1,"terms":[{"term":"dvd hollywood"}]}

From this object as the reference.

[XmlRoot("search")]
public class GetTermsResponse
{
	private string[] _terms;
	private int _maxCount;

	[XmlAttribute("timestamp")]
	public DateTime TimeStamp
	{
		get { return DateTime.Now; }
		set { ; }
	}

	[XmlAttribute("maxCount")]
	public int MaxCount
	{
		get { return _maxCount; }
		set { _maxCount = value; }
	}

	[XmlAttribute("count")]
	public int Count
	{
		get { return Terms.Length; }
		set { ; }
	}

	[XmlArray("terms")]
	[XmlArrayItem("term")]
	public string[] Terms
	{
		get { return _terms; }
		set { _terms = value; }
	}
}

So that wasn’t too hard the next step for this code is to make it so it can deserialize the JSON string back to an object, but the chances of that happening are almost 100/1 against the use of that functionality, because POST using JSON is a very dangerous activity and shouldn’t be attempted unless you know all the problems that may occur. So this code above should work in most real world examples. Happy coding.