Large diffs are not rendered by default.

@@ -2,7 +2,7 @@
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">x86</Platform>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProductVersion>8.0.30703</ProductVersion>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>{3DADA59F-3E27-44F5-93A5-F3D4C0C77D9B}</ProjectGuid>
@@ -15,8 +15,7 @@
</TargetFrameworkProfile>
<FileAlignment>512</FileAlignment>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x86' ">
<PlatformTarget>x86</PlatformTarget>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
@@ -25,8 +24,7 @@
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x86' ">
<PlatformTarget>x86</PlatformTarget>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
@@ -0,0 +1,197 @@
using System;
using System.IO;
using System.Text;
using System.Web.Caching;
using System.Web.Hosting;
using System.Web.UI;
using System.Web.UI.WebControls;
using Talifun.Web.Crusher.Config;

namespace Talifun.Web.Crusher
{
public class CrusherHelper
{
protected WebControl Control = new WebControl(HtmlTextWriterTag.Object);
protected readonly ICacheManager CacheManager;
protected readonly string QuerystringKeyName;
protected readonly CssGroupElementCollection CssGroups;
protected readonly JsGroupElementCollection JsGroups;
protected readonly IRetryableFileOpener RetryableFileOpener;
protected readonly IHasher Hasher;
private static string CrusherHelperType = typeof(CrusherHelper).ToString();
private static string CssType = "css";
private static string JsType = "js";

private CrusherHelper()
{
CacheManager = new HttpCacheManager();
QuerystringKeyName = CurrentCrusherConfiguration.Current.QuerystringKeyName;
CssGroups = CurrentCrusherConfiguration.Current.CssGroups;
JsGroups = CurrentCrusherConfiguration.Current.JsGroups;
RetryableFileOpener = new RetryableFileOpener();
Hasher = new Hasher(RetryableFileOpener);
}

private static CrusherHelper Instance
{
get
{
return Nested.Instance;
}
}

private class Nested
{
// Explicit static constructor to tell C# compiler
// not to mark type as beforefieldinit
static Nested()
{
}

internal static readonly CrusherHelper Instance = new CrusherHelper();
}

public static string Css(string groupName)
{
return Instance.CssInternal(groupName);
}

public static string Js(string groupName)
{
return Instance.JsInternal(groupName);
}

public string CssInternal(string groupName)
{
var cssGroup = CssGroups[groupName];
var outputFilePath = cssGroup.OutputFilePath;
var scriptLinks = string.Empty;
var cacheKey = GetKey(CssType, outputFilePath);

var cachedValue = CacheManager.Get<string>(cacheKey);
if (cachedValue != null)
{
scriptLinks = cachedValue;
}
else
{
if (!cssGroup.Debug)
{
var fileInfo = new FileInfo(MapPath(outputFilePath));
var etag = Hasher.CalculateMd5Etag(fileInfo);
var url = string.IsNullOrEmpty(cssGroup.Url) ? this.ResolveUrl(outputFilePath) : cssGroup.Url;

scriptLinks = "<link rel=\"stylesheet\" type=\"text/css\" href=\"" + url + "?" + QuerystringKeyName + "=" + etag + "\" media=\"" + cssGroup.Media + "\" />";
}
else
{
var scriptLinksBuilder = new StringBuilder();
foreach (CssFileElement file in cssGroup.Files)
{
var fileInfo = new FileInfo(MapPath(file.FilePath));
var etag = Hasher.CalculateMd5Etag(fileInfo);
var url = this.ResolveUrl(file.FilePath);

var fileName = fileInfo.Name.ToLower();

if (fileName.EndsWith(".less") || fileName.EndsWith(".less.css"))
{
etag = "'" + etag + "'";
}

scriptLinksBuilder.Append("<link rel=\"stylesheet\" type=\"text/css\" href=\"" + url + "?" + QuerystringKeyName + "=" + etag + "\" media=\"" + cssGroup.Media + "\" />");
}
scriptLinks = scriptLinksBuilder.ToString();
}
AddToCache(CssType, outputFilePath, scriptLinks);
}

return scriptLinks;
}

public string JsInternal(string groupName)
{
var jsGroup = JsGroups[groupName];
var outputFilePath = jsGroup.OutputFilePath;
var scriptLinks = string.Empty;

var cacheKey = GetKey(JsType, outputFilePath);

var cachedValue = CacheManager.Get<string>(cacheKey);
if (cachedValue != null)
{
scriptLinks = cachedValue;
}
else
{
if (!jsGroup.Debug)
{
var fileInfo = new FileInfo(MapPath(outputFilePath));
var etag = Hasher.CalculateMd5Etag(fileInfo);
var url = string.IsNullOrEmpty(jsGroup.Url) ? this.ResolveUrl(outputFilePath) : jsGroup.Url;

scriptLinks = "<script language=\"javascript\" type=\"text/javascript\" src=\"" + url + "?" + QuerystringKeyName + "=" + etag + "\"></script>";
}
else
{
var scriptLinksBuilder = new StringBuilder();
foreach (JsFileElement file in jsGroup.Files)
{
var fileInfo = new FileInfo(MapPath(file.FilePath));
var etag = Hasher.CalculateMd5Etag(fileInfo);
var url = this.ResolveUrl(file.FilePath);

scriptLinksBuilder.Append("<script language=\"javascript\" type=\"text/javascript\" src=\"" + url + "?" + QuerystringKeyName + "=" + etag + "\"></script>");
}
scriptLinks = scriptLinksBuilder.ToString();
}
AddToCache(JsType, outputFilePath, scriptLinks);
}

return scriptLinks;
}

/// <summary>
/// Get the cache key to use for caching.
/// </summary>
/// <param name="type"> </param>
/// <param name="outputPath">The path for the crushed css file.</param>
/// <returns>The cache key to use for caching.</returns>
protected string GetKey(string type, string outputPath)
{
var prefix = CrusherHelperType + "|" + type + "|";
return prefix + outputPath;
}

/// <summary>
/// Cache the url generated for the crushed css file.
/// </summary>
/// <param name="type"> </param>
/// <param name="outputFilePath">The crushed css file to use as the cache key.</param>
/// <param name="scriptLinks">The link generated for the crushed file.</param>
protected void AddToCache(string type, string outputFilePath, string scriptLinks)
{
var cacheKey = GetKey(type, outputFilePath);
var filePath = this.MapPath(outputFilePath);

CacheManager.Insert(
cacheKey,
scriptLinks,
new CacheDependency(filePath, DateTime.Now),
Cache.NoAbsoluteExpiration,
Cache.NoSlidingExpiration,
CacheItemPriority.Normal,
null);
}

protected string ResolveUrl(string relativeUrl)
{
return Control.ResolveUrl(relativeUrl);
}

protected string MapPath(string virtualPath)
{
return HostingEnvironment.MapPath(virtualPath);
}
}
}
@@ -1,11 +1,6 @@
using System;
using System.IO;
using System.Text;
using System.Web.Caching;
using System.Web.Hosting;
using System.Web.UI;
using System.Web.UI.WebControls;
using Talifun.Web.Crusher.Config;

namespace Talifun.Web.Crusher
{
@@ -14,22 +9,6 @@ namespace Talifun.Web.Crusher
/// </summary>
public class CssControl : WebControl
{
protected readonly ICacheManager CacheManager;
protected readonly string QuerystringKeyName;
protected readonly CssGroupElementCollection CssGroups;
protected readonly IRetryableFileOpener RetryableFileOpener;
protected readonly IHasher Hasher;
protected static string CssControlType = typeof(CssControl).ToString();

public CssControl()
{
CacheManager = new HttpCacheManager();
QuerystringKeyName = CurrentCrusherConfiguration.Current.QuerystringKeyName;
CssGroups = CurrentCrusherConfiguration.Current.CssGroups;
RetryableFileOpener = new RetryableFileOpener();
Hasher = new Hasher(RetryableFileOpener);
}

/// <summary>
/// The name of css group to generate the include headers for.
/// </summary>
@@ -55,83 +34,8 @@ public virtual string GroupName
/// </remarks>
protected override void Render(HtmlTextWriter writer)
{
var cssGroup = CssGroups[GroupName];
var outputFilePath = cssGroup.OutputFilePath;
var scriptLinks = string.Empty;

var cacheKey = GetKey(outputFilePath);

var cachedValue = CacheManager.Get<string>(cacheKey);
if (cachedValue != null)
{
scriptLinks = cachedValue;
}
else
{
if (!cssGroup.Debug)
{
var fileInfo = new FileInfo(HostingEnvironment.MapPath(outputFilePath));
var etag = Hasher.CalculateMd5Etag(fileInfo);
var url = string.IsNullOrEmpty(cssGroup.Url) ? this.ResolveUrl(outputFilePath) : cssGroup.Url;

scriptLinks = "<link rel=\"stylesheet\" type=\"text/css\" href=\"" + url + "?" + QuerystringKeyName + "=" + etag + "\" media=\"" + cssGroup.Media + "\" />";
}
else
{
var scriptLinksBuilder = new StringBuilder();
foreach (CssFileElement file in cssGroup.Files)
{
var fileInfo = new FileInfo(HostingEnvironment.MapPath(file.FilePath));
var etag = Hasher.CalculateMd5Etag(fileInfo);
var url = this.ResolveUrl(file.FilePath);

var fileName = fileInfo.Name.ToLower();

if (fileName.EndsWith(".less") || fileName.EndsWith(".less.css"))
{
etag = "'" + etag + "'";
}

scriptLinksBuilder.Append("<link rel=\"stylesheet\" type=\"text/css\" href=\"" + url + "?" + QuerystringKeyName + "=" + etag + "\" media=\"" + cssGroup.Media + "\" />");
}
scriptLinks = scriptLinksBuilder.ToString();
}
AddToCache(outputFilePath, scriptLinks);
}

var scriptLinks = CrusherHelper.Css(GroupName);
writer.Write(scriptLinks);
return;
}

/// <summary>
/// Cache the url generated for the crushed css file.
/// </summary>
/// <param name="outputFilePath">The crushed css file to use as the cache key.</param>
/// <param name="scriptLinks">The link generated for the crushed file.</param>
protected void AddToCache(string outputFilePath, string scriptLinks)
{
var cacheKey = GetKey(outputFilePath);
var filePath = this.MapPathSecure(outputFilePath);

CacheManager.Insert(
cacheKey,
scriptLinks,
new CacheDependency(filePath, DateTime.Now),
Cache.NoAbsoluteExpiration,
Cache.NoSlidingExpiration,
CacheItemPriority.Normal,
null);
}

/// <summary>
/// Get the cache key to use for caching.
/// </summary>
/// <param name="outputPath">The path for the crushed css file.</param>
/// <returns>The cache key to use for caching.</returns>
private static string GetKey(string outputPath)
{
var prefix = CssControlType + "|";
return prefix + outputPath;
}
}
}
@@ -1,11 +1,6 @@
using System;
using System.IO;
using System.Text;
using System.Web;
using System.Web.Caching;
using System.Web.UI;
using System.Web.UI.WebControls;
using Talifun.Web.Crusher.Config;

namespace Talifun.Web.Crusher
{
@@ -14,22 +9,6 @@ namespace Talifun.Web.Crusher
/// </summary>
public class JsControl : WebControl
{
protected readonly ICacheManager CacheManager;
protected readonly string QuerystringKeyName;
protected readonly JsGroupElementCollection JsGroups;
protected readonly IRetryableFileOpener RetryableFileOpener;
protected readonly IHasher Hasher;
protected static string JsControlType = typeof(JsControl).ToString();

public JsControl()
{
CacheManager = new HttpCacheManager();
QuerystringKeyName = CurrentCrusherConfiguration.Current.QuerystringKeyName;
JsGroups = CurrentCrusherConfiguration.Current.JsGroups;
RetryableFileOpener = new RetryableFileOpener();
Hasher = new Hasher(RetryableFileOpener);
}

/// <summary>
/// The name of js group to generate the include headers for.
/// </summary>
@@ -55,77 +34,8 @@ public virtual string GroupName
/// </remarks>
protected override void Render(HtmlTextWriter writer)
{
var jsGroup = JsGroups[GroupName];
var outputFilePath = jsGroup.OutputFilePath;
var scriptLinks = string.Empty;

var cacheKey = GetKey(outputFilePath);

var cachedValue = CacheManager.Get<string>(cacheKey);
if (cachedValue != null)
{
scriptLinks = cachedValue;
}
else
{
if (!jsGroup.Debug)
{
var fileInfo = new FileInfo(HttpContext.Current.Server.MapPath(outputFilePath));
var etag = Hasher.CalculateMd5Etag(fileInfo);
var url = string.IsNullOrEmpty(jsGroup.Url) ? this.ResolveUrl(outputFilePath) : jsGroup.Url;

scriptLinks = "<script language=\"javascript\" type=\"text/javascript\" src=\"" + url + "?" + QuerystringKeyName + "=" + etag + "\"></script>";
}
else
{
var scriptLinksBuilder = new StringBuilder();
foreach (JsFileElement file in jsGroup.Files)
{
var fileInfo = new FileInfo(HttpContext.Current.Server.MapPath(file.FilePath));
var etag = Hasher.CalculateMd5Etag(fileInfo);
var url = this.ResolveUrl(file.FilePath);

scriptLinksBuilder.Append("<script language=\"javascript\" type=\"text/javascript\" src=\"" + url + "?" + QuerystringKeyName + "=" + etag + "\"></script>");
}
scriptLinks = scriptLinksBuilder.ToString();
}
AddToCache(outputFilePath, scriptLinks);
}

var scriptLinks = CrusherHelper.Js(GroupName);
writer.Write(scriptLinks);
return;

}

/// <summary>
/// Cache the url generated for the crushed js file.
/// </summary>
/// <param name="outputFilePath">The crushed js file to use as the cache key.</param>
/// <param name="scriptLinks">The link generated for the crushed js file.</param>
protected void AddToCache(string outputFilePath, string scriptLinks)
{
var cacheKey = GetKey(outputFilePath);
var filePath = this.MapPathSecure(outputFilePath);

CacheManager.Insert(
cacheKey,
scriptLinks,
new CacheDependency(filePath, DateTime.Now),
Cache.NoAbsoluteExpiration,
Cache.NoSlidingExpiration,
CacheItemPriority.Normal,
null);
}

/// <summary>
/// Get the cache key to use for caching.
/// </summary>
/// <param name="outputPath">The path for the crushed js file.</param>
/// <returns>The cache key to use for caching.</returns>
private static string GetKey(string outputPath)
{
var prefix = JsControlType + "|";
return prefix + outputPath;
}
}
}
@@ -83,6 +83,7 @@
<Compile Include="..\GlobalAssemblyInfo.cs">
<Link>Properties\GlobalAssemblyInfo.cs</Link>
</Compile>
<Compile Include="Crusher\CrusherHelper.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Cache\HttpCacheManager.cs" />
<Compile Include="Compress\CompressionModuleHelper.cs" />
@@ -7,39 +7,61 @@
<cssGroups>
<!-- Css group to crush -->
<!--
<cssGroup name="SiteCss" debug="false" appendHash="true" outputFilePath="~/Static/Css/site.css">
<cssGroup name="SiteCss" debug="false" appendHash="true" outputFilePath="~/Static/Css/crushed.site.css">
<files>
<file name="JQueryUI" filePath="~/Static/Css/jquery-ui-1.8.16.css" compressionType="Hybrid" />
<file name="Default" filePath="~/Static/Css/default.css" compressionType="Hybrid" />
<file name="Default" filePath="~/Static/Css/site.css" compressionType="Hybrid" />
<file name="JQueryUI" filePath="~/Static/Css/base/jquery.ui.all.css" compressionType="Hybrid" />
</files>
</cssGroup>
<cssGroup name="PrintSiteCss" Media="print" debug="false" appendHash="true" outputFilePath="~/Static/Css/crushed.print.site.css">
<files>
<file name="Default" filePath="~/Static/Css/print.site.css" compressionType="Hybrid" />
</files>
</cssGroup>
-->
</cssGroups>
<!-- outputFilePath is the identifier for the jsGroup, so make sure its unique for each jsGroup -->
<jsGroups>
<!-- Js group to demo the crushing -->
<!--
<jsGroup name="SiteJs" debug="false" outputFilePath="~/Static/Js/site.js">
<files>
<file name="JQuery" filePath="~/Static/Js/jquery-1.6.4.min.js" compressionType="Min"/>
<file name="JQueryUI" filePath="~/Static/Js/jquery-ui-1.8.16.min.js" compressionType="Min"/>
</files>
</jsGroup>
-->
<!-- Js group to demo the crushing -->
<!--
<jsGroup name="SiteJs" debug="false" outputFilePath="~/Scripts/crushed.site.js">
<files>
<file name="JQuery" filePath="~/Scripts/jquery-1.5.1.js" compressionType="Min"/>
<file name="JQueryUI" filePath="~/Scripts/jquery-ui-1.8.11.js" compressionType="Min"/>
<file name="JQueryUnobtrusiveAjax" filePath="~/Scripts/jquery.unobtrusive-ajax.js" compressionType="Min"/>
<file name="JQueryValidate" filePath="~/Scripts/jquery.validate.js" compressionType="Min"/>
<file name="JQueryValidateUnobtrusive" filePath="~/Scripts/jquery.validate.unobtrusive.js" compressionType="Min"/>
<file name="Modernizr" filePath="~/Scripts/modernizr-1.7.js" compressionType="Min"/>

<file name="MicrosoftAjax" filePath="~/Scripts/MicrosoftAjax.debug.js" compressionType="Min"/>
<file name="MicrosoftMvcAjax" filePath="~/Scripts/MicrosoftMvcAjax.debug.js" compressionType="Min"/>
<file name="MicrosoftMvcValidation" filePath="~/Scripts/MicrosoftMvcValidation.debug.js" compressionType="Min"/>
</files>
</jsGroup>
-->
</jsGroups>
</Crusher>
<system.web>
<pages>
<controls>
<add tagPrefix="talifun" namespace="Talifun.Web.Crusher" assembly="Talifun.Web"/>
</controls>
<httpModules>
<!-- Only recommended when doing local development / rather use Talifun.Crusher.exe as part of your build script -->
<!--
<add name="CrusherModule" type="Talifun.Web.Crusher.CrusherModule, Talifun.Web"/>
-->
</httpModules>
</pages>
<pages>
<controls>
<add tagPrefix="talifun" namespace="Talifun.Web.Crusher" assembly="Talifun.Web"/>
</controls>
</pages>
<httpModules>
<!-- Only recommended when doing local development or using a CMS thats allows people to change stylesheets
rather use Talifun.Crusher.exe as part of your build script -->
<!--
<add name="CrusherModule" type="Talifun.Web.Crusher.CrusherModule, Talifun.Web"/>
-->
</httpModules>
</system.web>
<system.webServer>
<modules>
<!-- Only recommended when doing local development or using a CMS thats allows people to change stylesheets
rather use Talifun.Crusher.exe as part of your build script -->
<!--
<add name="CrusherModule" type="Talifun.Web.Crusher.CrusherModule, Talifun.Web" />
-->
</modules>
</system.webServer>
</configuration>