April 21st 2008
YSlow and ASP.NET – Expires Header – Expression Builders (Part 2 of 3)
Continuing our task of caching indefinitely static content by versioning it, we want to build the framework so that instead of code like this:
<asp:image imageurl="~/images/rose.jpg" runat="server" id="image1">
we have this:
<asp:Image id="image1" imageurl="<%$ Image: rose.jpg %>" runat="server"/>
The key difference is that we can dynamically assign where the image path for rose.jpg resolves in the latter case. Specifically, we can resolve to applicationPath/versionX.X.X/Images/rose.jpg (or whatever url scheme meets your fancy).
We need two things. First we create an expressionBuilder object and then tie the custom expressionBuilder into the build. I’m using the VS “WebSite” project, but this should work for “Web Application Projects”.
The following code draws heavily from this post on Dave Reed’s excellent blog infinitiesloop.
Imports System.CodeDom
Imports System.Web
Namespace StaticContent
Public NotInheritable Class PathBuilder
Private Shared CssPath As String = String.Format(System.Globalization.CultureInfo.InvariantCulture, "{0}/StaticContent/{1}/Css/{{0}}", System.Web.HttpContext.Current.Request.ApplicationPath, System.Configuration.ConfigurationManager.AppSettings("versionNumber")))
Private Shared ImagePath As String = String.Format(System.Globalization.CultureInfo.InvariantCulture, "{0}/StaticContent/{1}/Images/{{0}}", System.Web.HttpContext.Current.Request.ApplicationPath, System.Configuration.ConfigurationManager.AppSettings("versionNumber")))
Private Shared JavascriptPath As String = String.Format(System.Globalization.CultureInfo.InvariantCulture, "{0}/StaticContent/{1}/Javascripts/{{0}}", System.Web.HttpContext.Current.Request.ApplicationPath, System.Configuration.ConfigurationManager.AppSettings("versionNumber")))
Public Shared Function ConvertCssUrl(ByVal path As String) As String
Return String.Format(System.Globalization.CultureInfo.InvariantCulture, CssPath, path)
End Function
Public Shared Function ConvertImageUrl(ByVal path As String) As String
Return String.Format(System.Globalization.CultureInfo.InvariantCulture, ImagePath, path)
End Function
Public Shared Function ConvertJavascriptUrl(ByVal path As String) As String
Return String.Format(System.Globalization.CultureInfo.InvariantCulture, JavascriptPath, path)
End Function
Private Sub New()
End Sub
End Class
MustInherit Class StaticContentExpressionBuilder
Inherits Compilation.ExpressionBuilder
Protected Shared evaluationExpression As String
Protected MustOverride ReadOnly Property StaticContentExpression() As String
Public Overrides Function ParseExpression(ByVal expression As String, ByVal propertyType As System.Type, ByVal context As Compilation.ExpressionBuilderContext) As Object
Return String.Format(System.Globalization.CultureInfo.InvariantCulture, StaticContentExpression, expression)
End Function
Public Overloads Overrides Function GetCodeExpression(ByVal entry As System.Web.UI.BoundPropertyEntry, ByVal parsedData As Object, ByVal context As System.Web.Compilation.ExpressionBuilderContext) As System.CodeDom.CodeExpression
Return New CodeSnippetExpression(parsedData.ToString())
End Function
End Class
<Compilation.ExpressionPrefix("Js")> _
Class JavascriptExpressionBuilder
Inherits StaticContentExpressionBuilder
Protected Overrides ReadOnly Property StaticContentExpression() As String
Get
Return "StaticContent.PathBuilder.ConvertJavascriptUrl( ""{0}"" )"
End Get
End Property
End Class
<Compilation.ExpressionPrefix("Image")> _
Class ImageExpressionBuilder
Inherits StaticContentExpressionBuilder
Protected Overrides ReadOnly Property StaticContentExpression() As String
Get
Return "StaticContent.PathBuilder.ConvertImageUrl( ""{0}"" )"
End Get
End Property
End Class
<Compilation.ExpressionPrefix("Css")> _
Class CssExpressionBuilder
Inherits StaticContentExpressionBuilder
Protected Overrides ReadOnly Property StaticContentExpression() As String
Get
Return "StaticContent.PathBuilder.ConvertCssUrl( ""{0}"" )"
End Get
End Property
End Class
End Namespace
I’ve chosen to implement several types of static content (css, images, and js) but that’s just a matter of convenience. Clearly this could all be done with a single expression. Note the reason I have placed the content type specific methods on PathBuilder is that I need to call these methods in part three (code-behind) where accessing the expressionBuilder assemblies would be pretty awkward.
To tie our new expression builders into the build we need to add some entries to the web.config.
<compilation debug="true" strict="true" explicit="true"> <expressionBuilders> <add expressionPrefix="Css" type="StaticContent.CssExpressionBuilder"/> <add expressionPrefix="Image" type="StaticContent.ImageExpressionBuilder"/> <add expressionPrefix="Js" type="StaticContent.JavascriptExpressionBuilder"/> </expressionBuilders> </compilation>
That’s it. Now we can do things in our markup like:
<asp:image imageurl="Image: rose.jpg" runat="server" id="image1"> <link rel="stylesheet" href="Css: Fonts.css %>" type="text/css" id="FontsCss" runat="server">
the image “rose.jpg” and css file “Fonts.css” will be rendered to the page as urls:
“applicationPath/StaticContent/1.0.0/Images/rose.jpg” and “applicationPath/StaticContent/1.0.0/Css/Fonts.css” respectively.