DO NOT DOWNLOAD. DOWNLOAD LATEST STABLE FROM RELEASE TAB

ClientDependency work

[TFS Changeset #56528]
This commit is contained in:
Shandem
2009-07-15 14:29:11 +00:00
parent 198c2d2d06
commit 7dbe73c15d
9 changed files with 516 additions and 120 deletions

View File

@@ -6,6 +6,7 @@ using System.Web;
using umbraco.presentation.ClientDependency.Providers;
using System.Web.Configuration;
using System.Configuration.Provider;
using umbraco.presentation.ClientDependency.Config;
namespace umbraco.presentation.ClientDependency
{
@@ -13,91 +14,6 @@ namespace umbraco.presentation.ClientDependency
public class ClientDependencyHelper
{
private static ClientDependencyProvider m_Provider = null;
private static ClientDependencyProviderCollection m_Providers = null;
private static List<string> m_Extensions;
private static object m_Lock = new object();
public static ClientDependencyProvider DefaultProvider
{
get
{
LoadProviders();
return m_Provider;
}
}
public static ClientDependencyProviderCollection ProviderCollection
{
get
{
LoadProviders();
return m_Providers;
}
}
/// <summary>
/// The file extensions of Client Dependencies that are file based as opposed to request based.
/// Any file that doesn't have the extensions listed here will be request based, request based is
/// more overhead for the server to process.
/// </summary>
/// <example>
/// A request based JavaScript file may be a .ashx that dynamically creates JavaScript server side.
/// </example>
/// <remarks>
/// If this is not explicitly set, then the extensions 'js' and 'css' are the defaults.
/// </remarks>
public static List<string> FileBasedDependencyExtensionList
{
get
{
LoadProviders();
return m_Extensions;
}
}
private static void LoadProviders()
{
if (m_Provider == null)
{
lock (m_Lock)
{
// Do this again to make sure _provider is still null
if (m_Provider == null)
{
ClientDependencySection section = (ClientDependencySection)WebConfigurationManager.GetSection("system.web/clientDependency");
m_Providers = new ClientDependencyProviderCollection();
// if there is no section found, then add the standard providers to the collection with the standard
// default provider
if (section != null)
{
// Load registered providers and point _provider to the default provider
ProvidersHelper.InstantiateProviders(section.Providers, m_Providers, typeof(ClientDependencyProvider));
m_Provider = m_Providers[section.DefaultProvider];
if (m_Provider == null)
throw new ProviderException("Unable to load default ClientDependency provider");
}
else
{
//get the default settings
section = new ClientDependencySection();
m_Extensions = section.FileBasedDependencyExtensionList;
PageHeaderProvider php = new PageHeaderProvider();
php.Initialize(PageHeaderProvider.DefaultName, null);
m_Providers.Add(php);
ClientSideRegistrationProvider csrp = new ClientSideRegistrationProvider();
csrp.Initialize(ClientSideRegistrationProvider.DefaultName, null);
m_Providers.Add(csrp);
//set the default
m_Provider = m_Providers[section.DefaultProvider];
}
}
}
}
}
/// <summary>
/// Registers dependencies with the default provider
@@ -106,8 +22,8 @@ namespace umbraco.presentation.ClientDependency
/// <param name="paths"></param>
public static void RegisterClientDependencies(Control control, ClientDependencyPathCollection paths)
{
LoadProviders();
ClientDependencyRegistrationService service = new ClientDependencyRegistrationService(control, paths, m_Provider);
ClientDependencyRegistrationService service = new ClientDependencyRegistrationService(control, paths,
ClientDependencySettings.Instance.DefaultProvider);
service.ProcessDependencies();
}
@@ -119,8 +35,8 @@ namespace umbraco.presentation.ClientDependency
/// <param name="paths"></param>
public static void RegisterClientDependencies(string providerName, Control control, ClientDependencyPathCollection paths)
{
LoadProviders();
ClientDependencyRegistrationService service = new ClientDependencyRegistrationService(control, paths, m_Providers[providerName]);
ClientDependencyRegistrationService service = new ClientDependencyRegistrationService(control, paths,
ClientDependencySettings.Instance.ProviderCollection[providerName]);
service.ProcessDependencies();
}
@@ -130,10 +46,9 @@ namespace umbraco.presentation.ClientDependency
public static void RegisterClientDependencies<T>(Control control, ClientDependencyPathCollection paths)
where T: ClientDependencyProvider
{
LoadProviders();
//need to find the provider with the type
ClientDependencyProvider found = null;
foreach (ClientDependencyProvider p in m_Providers)
foreach (ClientDependencyProvider p in ClientDependencySettings.Instance.ProviderCollection)
{
if (p.GetType().Equals(typeof(T)))
{
@@ -149,7 +64,6 @@ namespace umbraco.presentation.ClientDependency
public static void RegisterClientDependencies(ClientDependencyProvider provider, Control control, ClientDependencyPathCollection paths)
{
LoadProviders();
ClientDependencyRegistrationService service = new ClientDependencyRegistrationService(control, paths, provider);
service.ProcessDependencies();
}

View File

@@ -4,6 +4,7 @@ using System.Text;
using System.Web.UI;
using System.Web;
using umbraco.presentation.ClientDependency.Providers;
using umbraco.presentation.ClientDependency.Config;
namespace umbraco.presentation.ClientDependency
{
@@ -69,12 +70,12 @@ namespace umbraco.presentation.ClientDependency
switch (EmbedType)
{
case ClientDependencyEmbedType.Header:
provider = ClientDependencyHelper.ProviderCollection[PageHeaderProvider.DefaultName];
provider = ClientDependencySettings.Instance.ProviderCollection[PageHeaderProvider.DefaultName];
provider.IsDebugMode = IsDebugMode;
ClientDependencyHelper.RegisterClientDependencies(provider, this.Page, Paths);
break;
case ClientDependencyEmbedType.ClientSideRegistration:
provider = ClientDependencyHelper.ProviderCollection[ClientSideRegistrationProvider.DefaultName];
provider = ClientDependencySettings.Instance.ProviderCollection[ClientSideRegistrationProvider.DefaultName];
provider.IsDebugMode = IsDebugMode;
ClientDependencyHelper.RegisterClientDependencies(provider, this.Page, Paths);
break;

View File

@@ -6,6 +6,8 @@ using System.Reflection;
using System.IO;
using System.IO.Compression;
using System.Net;
using System.Linq;
using umbraco.presentation.ClientDependency.Config;
namespace umbraco.presentation.ClientDependency
{
@@ -64,25 +66,80 @@ namespace umbraco.presentation.ClientDependency
if (string.IsNullOrEmpty(fileset))
throw new ArgumentException("Must specify a fileset in the request");
byte[] fileBytes = CombineFiles(fileset, context, type);
byte[] outputBytes = CompressBytes(context, fileBytes);
SetCaching(context);
string compositeFileName;
byte[] outputBytes;
CompositeFileMap map = CompositeFileXmlMapper.Instance.GetCompositeFile(fileset);
if (map == null || !map.HasFileBytes)
{
//get the file list
string[] strFiles = DecodeFrom64(fileset).Split(';');
//combine files
byte[] fileBytes = CombineFiles(strFiles, context, type);
//compress data
CompressionType cType = CompressBytes(context, fileBytes, out outputBytes);
SetContentEncodingHeaders(context, cType);
//save combined file
compositeFileName = SaveCompositeFile(outputBytes, type);
//Update the XML file map
CompositeFileXmlMapper.Instance.CreateMap(fileset, cType.ToString(),
strFiles.Select(x => new FileInfo(context.Server.MapPath(x))).ToList(),
compositeFileName);
}
else
{
//the saved file's bytes are already compressed.
outputBytes = map.GetCompositeFileBytes();
compositeFileName = map.CompositeFileName;
CompressionType cType = (CompressionType)Enum.Parse(typeof(CompressionType), map.CompressionType);
SetContentEncodingHeaders(context, cType);
}
SetCaching(context, compositeFileName);
context.Response.ContentType = type == ClientDependencyType.Javascript ? "text/javascript" : "text/css";
context.Response.OutputStream.Write(outputBytes, 0, outputBytes.Length);
}
private enum CompressionType
{
deflate, gzip, none
}
/// <summary>
/// Saves the file's bytes to disk with a hash of the byte array
/// </summary>
/// <param name="fileContents"></param>
/// <param name="type"></param>
/// <returns>The new file path</returns>
/// <remarks>
/// the extension will be: .cdj for JavaScript and .cdc for CSS
/// </remarks>
private string SaveCompositeFile(byte[] fileContents, ClientDependencyType type)
{
if (!ClientDependencySettings.Instance.CompositeFilePath.Exists)
ClientDependencySettings.Instance.CompositeFilePath.Create();
FileInfo fi = new FileInfo(
Path.Combine(ClientDependencySettings.Instance.CompositeFilePath.FullName,
fileContents.GetHashCode().ToString() + ".cd" + type.ToString().Substring(0, 1).ToLower()));
if (fi.Exists)
fi.Delete();
FileStream fs = fi.Create();
fs.Write(fileContents, 0, fileContents.Length);
fs.Close();
return fi.FullName;
}
/// <summary>
/// combines all files to a byte array
/// </summary>
/// <param name="fileList"></param>
/// <param name="context"></param>
/// <returns></returns>
private byte[] CombineFiles(string fileList, HttpContext context, ClientDependencyType type)
private byte[] CombineFiles(string[] strFiles, HttpContext context, ClientDependencyType type)
{
MemoryStream ms = new MemoryStream(5000);
//get the file list, and write the contents of each file to the output stream.
string[] strFiles = DecodeFrom64(fileList).Split(';');
StreamWriter sw = new StreamWriter(ms);
foreach (string s in strFiles)
{
@@ -91,7 +148,7 @@ namespace umbraco.presentation.ClientDependency
try
{
FileInfo fi = new FileInfo(context.Server.MapPath(s));
if (ClientDependencyHelper.FileBasedDependencyExtensionList.Contains(fi.Extension.ToLower().Replace(".", "")))
if (ClientDependencySettings.Instance.FileBasedDependencyExtensionList.Contains(fi.Extension.ToLower().Replace(".", "")))
{
//if the file doesn't exist, then we'll assume it is a URI external request
if (!fi.Exists)
@@ -213,7 +270,7 @@ namespace umbraco.presentation.ClientDependency
/// Sets the output cache parameters and also the client side caching parameters
/// </summary>
/// <param name="context"></param>
private void SetCaching(HttpContext context)
private void SetCaching(HttpContext context, string fileName)
{
//This ensures OutputCaching is set for this handler and also controls
//client side caching on the browser side. Default is 10 days.
@@ -235,47 +292,68 @@ namespace umbraco.presentation.ClientDependency
//This is the only way to set the max-age cachability header in ASP.Net!
FieldInfo maxAgeField = cache.GetType().GetField("_maxAge", BindingFlags.Instance | BindingFlags.NonPublic);
maxAgeField.SetValue(cache, duration);
//make this output cache dependent on the file.
context.Response.AddFileDependency(fileName);
}
/// <summary>
/// Sets the content encoding headers based on compressions
/// </summary>
/// <param name="context"></param>
/// <param name="type"></param>
private void SetContentEncodingHeaders(HttpContext context, CompressionType type)
{
if (type == CompressionType.deflate)
{
context.Response.AddHeader("Content-encoding", "deflate");
}
else if (type == CompressionType.gzip)
{
context.Response.AddHeader("Content-encoding", "gzip");
}
}
/// <summary>
/// Compresses the bytes if the browser supports it
/// </summary>
private byte[] CompressBytes(HttpContext context, byte[] fileBytes)
private CompressionType CompressBytes(HttpContext context, byte[] fileBytes, out byte[] outputBytes)
{
CompressionType type = CompressionType.none;
string acceptEncoding = context.Request.Headers["Accept-Encoding"];
//not compressed initially
outputBytes = fileBytes;
if (!string.IsNullOrEmpty(acceptEncoding))
{
MemoryStream ms = new MemoryStream();
Stream compressedStream = null;
acceptEncoding = acceptEncoding.ToLowerInvariant();
bool isCompressed = false;
//deflate is faster in .Net according to Mads Kristensen (blogengine.net)
if (acceptEncoding.Contains("deflate"))
{
context.Response.AddHeader("Content-encoding", "deflate");
compressedStream = new DeflateStream(ms, CompressionMode.Compress, true);
isCompressed = true;
type = CompressionType.deflate;
}
else if (acceptEncoding.Contains("gzip"))
{
context.Response.AddHeader("Content-encoding", "gzip");
compressedStream = new GZipStream(ms, CompressionMode.Compress, true);
isCompressed = true;
type = CompressionType.gzip;
}
if (isCompressed)
if (type != CompressionType.none)
{
//write the bytes to the compressed stream
compressedStream.Write(fileBytes, 0, fileBytes.Length);
compressedStream.Close();
byte[] outputBytes = ms.ToArray();
byte[] output = ms.ToArray();
ms.Close();
return outputBytes;
outputBytes = output;
}
}
//not compressed
return fileBytes;
return type;
}
private string DecodeFrom64(string toDecode)

View File

@@ -0,0 +1,69 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Linq;
using System.IO;
namespace umbraco.presentation.ClientDependency
{
/// <summary>
/// Deserialized structure of the XML stored in the map file
/// </summary>
public class CompositeFileMap
{
internal CompositeFileMap(string key, string compressionType, string file, List<FileInfo> files)
{
DependentFiles = files;
Base64Key = key;
CompositeFileName = file;
CompressionType = compressionType;
}
public string Base64Key { get; private set; }
public string CompositeFileName { get; private set; }
public string CompressionType { get; private set; }
private byte[] m_FileBytes;
/// <summary>
/// If for some reason the file doesn't exist any more or we cannot read the file, this will return false.
/// </summary>
public bool HasFileBytes
{
get
{
GetCompositeFileBytes();
return m_FileBytes != null;
}
}
/// <summary>
/// Returns the file's bytes
/// </summary>
public byte[] GetCompositeFileBytes()
{
if (m_FileBytes == null)
{
try
{
FileInfo fi = new FileInfo(CompositeFileName);
FileStream fs = fi.OpenRead();
byte[] fileBytes = new byte[fs.Length];
fs.Read(fileBytes, 0, fileBytes.Length);
fs.Close();
m_FileBytes = fileBytes;
}
catch
{
m_FileBytes = null;
}
}
return m_FileBytes;
}
public List<FileInfo> DependentFiles { get; private set; }
}
}

View File

@@ -0,0 +1,193 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Linq;
using System.Xml;
using System.IO;
using umbraco.presentation.ClientDependency.Config;
namespace umbraco.presentation.ClientDependency
{
/// <summary>
/// Creates an XML file to map a saved composite file to the URL requested for the
/// dependency handler.
/// This is used in order to determine which individual files are dependant on what composite file so
/// a user can remove it to clear the cache, and also if the cache expires but the file still exists
/// this allows the system to simply read the one file again instead of compiling all of the other files
/// into one again.
/// </summary>
public class CompositeFileXmlMapper
{
/// <summary>
/// Singleton
/// </summary>
public static CompositeFileXmlMapper Instance
{
get
{
return m_Mapper;
}
}
private CompositeFileXmlMapper()
{
Initialize();
}
private static readonly CompositeFileXmlMapper m_Mapper = new CompositeFileXmlMapper();
private const string MapFileName = "map.xml";
private XDocument m_Doc;
private FileInfo m_XmlFile;
private object m_Lock = new object();
/// <summary>
/// Loads in the existing file contents. If the file doesn't exist, it creates one.
/// </summary>
private void Initialize()
{
m_XmlFile = new FileInfo(
Path.Combine(ClientDependencySettings.Instance.CompositeFilePath.FullName, MapFileName));
EnsureXmlFile();
lock (m_Lock)
{
try
{
m_Doc = XDocument.Load(m_XmlFile.FullName);
}
catch (XmlException ex)
{
//if it's an xml exception, create a new one and try one more time... should always work.
CreateNewXmlFile();
m_Doc = XDocument.Load(m_XmlFile.FullName);
}
}
}
private void CreateNewXmlFile()
{
if (m_XmlFile.Exists)
m_XmlFile.Delete();
m_Doc = new XDocument(new XDeclaration("1.0", "UTF-8", "yes"),
new XElement("map"));
m_Doc.Save(m_XmlFile.FullName);
}
private void EnsureXmlFile()
{
if (!m_XmlFile.Exists)
{
lock (m_Lock)
{
//double check
if (!m_XmlFile.Exists)
{
CreateNewXmlFile();
}
}
}
}
/// <summary>
/// Returns the composite file map associated with the base 64 key of the URL
/// for the handler.
/// </summary>
/// <param name="base64Key"></param>
/// <returns></returns>
public CompositeFileMap GetCompositeFile(string base64Key)
{
XElement x = FindItem(base64Key);
//try
//{
return (x == null ? null : new CompositeFileMap(base64Key,
x.Attribute("compression").Value,
x.Attribute("file").Value,
x.Descendants("file")
.Select(f => new FileInfo(f.Attribute("name").Value))
.ToList()));
//}
//catch
//{
// return null;
//}
}
/// <summary>
///
/// </summary>
/// <param name="base64Key"></param>
/// <param name="dependentFiles"></param>
/// <param name="compositeFile"></param>
/// <example>
/// <![CDATA[
/// <map>
/// <item key="XSDFSDKJHLKSDIOUEYWCDCDSDOIUPOIUEROIJDSFHG"
/// file="C:\asdf\App_Data\ClientDependency\123456.cdj"
/// compresion="deflate">
/// <files>
/// <file name="C:\asdf\JS\jquery.js" />
/// <file name="C:\asdf\JS\jquery.ui.js" />
/// </files>
/// </item>
/// </map>
/// ]]>
/// </example>
public void CreateMap(string base64Key, string compressionType, List<FileInfo> dependentFiles, string compositeFile)
{
lock (m_Lock)
{
//see if we can find an item with the key already
XElement x = FindItem(base64Key);
if (x != null)
{
x.Attribute("file").Value = compositeFile;
//remove all of the files so we can re-add them.
x.Element("files").Remove();
x.Add(CreateFileNode(dependentFiles));
}
else
{
//if it doesn't exist, create it
m_Doc.Root.Add(new XElement("item",
new XAttribute("key", base64Key),
new XAttribute("file", compositeFile),
new XAttribute("compression", compressionType),
CreateFileNode(dependentFiles)));
}
m_Doc.Save(m_XmlFile.FullName);
}
}
private XElement FindItem(string key)
{
return m_Doc.Root.Elements("item")
.Where(e => (string)e.Attribute("key") == key)
.SingleOrDefault();
}
private XElement CreateFileNode(List<FileInfo> files)
{
XElement x = new XElement("files");
//add all of the files
files.ForEach(d =>
{
x.Add(new XElement("file",
new XAttribute("name", d.FullName)));
});
return x;
}
}
}

View File

@@ -4,7 +4,7 @@ using System.Text;
using System.Configuration;
using System.Linq;
namespace umbraco.presentation.ClientDependency.Providers
namespace umbraco.presentation.ClientDependency.Config
{
public class ClientDependencySection : ConfigurationSection
{
@@ -22,6 +22,16 @@ namespace umbraco.presentation.ClientDependency.Providers
set { base["defaultProvider"] = value; }
}
/// <summary>
/// This is the folder that composite script/css files will be stored once they are combined.
/// </summary>
[StringValidator(MinLength = 1)]
[ConfigurationProperty("compositeFilePath", DefaultValue = "~/App_Data/ClientDependency")]
public string CompositeFilePath
{
get { return (string)base["compositeFilePath"]; }
set { base["compositeFilePath"] = value; }
}
/// <summary>
/// The configuration section to set the FileBasedDependencyExtensionList. This is a comma separated list.

View File

@@ -0,0 +1,125 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web.Configuration;
using umbraco.presentation.ClientDependency.Providers;
using System.Configuration.Provider;
using System.IO;
using System.Web;
namespace umbraco.presentation.ClientDependency.Config
{
public class ClientDependencySettings
{
private ClientDependencySettings()
{
LoadProviders();
}
/// <summary>
/// Singleton
/// </summary>
public static ClientDependencySettings Instance
{
get
{
return m_Settings;
}
}
private static readonly ClientDependencySettings m_Settings = new ClientDependencySettings();
private object m_Lock = new object();
private ClientDependencyProvider m_Provider = null;
private ClientDependencyProviderCollection m_Providers = null;
private List<string> m_Extensions;
private DirectoryInfo m_CompositeFilePath;
/// <summary>
/// The file extensions of Client Dependencies that are file based as opposed to request based.
/// Any file that doesn't have the extensions listed here will be request based, request based is
/// more overhead for the server to process.
/// </summary>
/// <example>
/// A request based JavaScript file may be a .ashx that dynamically creates JavaScript server side.
/// </example>
/// <remarks>
/// If this is not explicitly set, then the extensions 'js' and 'css' are the defaults.
/// </remarks>
public List<string> FileBasedDependencyExtensionList
{
get
{
return m_Extensions;
}
}
public ClientDependencyProvider DefaultProvider
{
get
{
return m_Provider;
}
}
public ClientDependencyProviderCollection ProviderCollection
{
get
{
return m_Providers;
}
}
public DirectoryInfo CompositeFilePath
{
get
{
return m_CompositeFilePath;
}
}
private void LoadProviders()
{
if (m_Provider == null)
{
lock (m_Lock)
{
// Do this again to make sure _provider is still null
if (m_Provider == null)
{
ClientDependencySection section = (ClientDependencySection)WebConfigurationManager.GetSection("system.web/clientDependency");
m_Providers = new ClientDependencyProviderCollection();
// if there is no section found, then add the standard providers to the collection with the standard
// default provider
if (section != null)
{
// Load registered providers and point _provider to the default provider
ProvidersHelper.InstantiateProviders(section.Providers, m_Providers, typeof(ClientDependencyProvider));
}
else
{
//create a new section with the default settings
section = new ClientDependencySection();
PageHeaderProvider php = new PageHeaderProvider();
php.Initialize(PageHeaderProvider.DefaultName, null);
m_Providers.Add(php);
ClientSideRegistrationProvider csrp = new ClientSideRegistrationProvider();
csrp.Initialize(ClientSideRegistrationProvider.DefaultName, null);
m_Providers.Add(csrp);
}
//set the default
m_Provider = m_Providers[section.DefaultProvider];
if (m_Provider == null)
throw new ProviderException("Unable to load default ClientDependency provider");
m_Extensions = section.FileBasedDependencyExtensionList;
m_CompositeFilePath = new DirectoryInfo(HttpContext.Current.Server.MapPath(section.CompositeFilePath));
}
}
}
}
}
}

View File

@@ -46,11 +46,17 @@
<Reference Include="System.Web.Extensions, Version=1.0.61025.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL" />
<Reference Include="System.Data" />
<Reference Include="System.Xml" />
<Reference Include="System.Xml.Linq">
<RequiredTargetFramework>3.5</RequiredTargetFramework>
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="ClientDependencyInclude.cs" />
<Compile Include="ClientDependencyList.cs" />
<Compile Include="ClientDependencyType.cs" />
<Compile Include="CompositeFileMap.cs" />
<Compile Include="CompositeFileXmlMapper.cs" />
<Compile Include="Config\ClientDependencySettings.cs" />
<Compile Include="IClientDependencyFile.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Providers\ClientDependencyProvider.cs" />
@@ -61,7 +67,7 @@
<Compile Include="ClientDependencyPath.cs" />
<Compile Include="ClientDependencyPathCollection.cs" />
<Compile Include="Providers\ClientDependencyProviderCollection.cs" />
<Compile Include="Providers\ClientDependencySection.cs" />
<Compile Include="Config\ClientDependencySection.cs" />
<Compile Include="Providers\ClientSideRegistrationProvider.cs" />
<Compile Include="CompositeDependencyHandler.cs" />
<Compile Include="Providers\PageHeaderProvider.cs" />

View File

@@ -1,7 +1,7 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:2.0.50727.3053
// Runtime Version:2.0.50727.3082
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.