/*
*
* http://mvcresourceloader.codeplex.com/license
*
* */
using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Configuration;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.HtmlControls;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Xml.Linq;
using IListSystem.Collections.IList;
namespace InteSoft.Web.ExternalResourceLoader{
internal delegate string ExternalResourceReference(string filename, string mimeType, string attributes);
public static class Extensions
{
private static readonly SHA1 sha;
static Extensions()
{
sha = new SHA1Managed();
}
public static string ExContent(this UrlHelper urlHelper, string contentPath)
{
var section = ConfigManager.GetSection();
string url = urlHelper.Content(contentPath);
if (section.Schemes.Count == 0)
{
// no external domain specified, leave url as it is
return url;
}
string currentScheme = urlHelper.RequestContext.HttpContext.Request.Url.Scheme;
SchemeElement scheme = section.Schemes[currentScheme];
// if there is no domain config for the current scheme or
// no external content roots specified then leave url as it is
if (scheme == null || scheme.Contents.Count == 0)
return url;
Uri baseUri;
// if there are multiple domains for external content then share requests between them otherwise use root domain
switch (scheme.Contents.Count)
{
case 1:
// only one specified ... use it (no need for distributed reference)
baseUri = new Uri(currentScheme + Uri.SchemeDelimiter + VirtualPathUtility.AppendTrailingSlash(scheme.Contents[0].ContentRoot));
break;
default:
// get a distributed hash key to consistently point to an available resource
// (all the content is loadable from any but caching is more efficient if it isn't just random)
// Create a SHA1 hash of the provided key
byte[] result;
byte[] data = BitConverter.GetBytes(contentPath.GetHashCode());
result = sha.ComputeHash(data);
// Split the hash into 4 integer numbers
uint n1 = BitConverter.ToUInt32(result, 0);
uint n2 = BitConverter.ToUInt32(result, 4);
uint n3 = BitConverter.ToUInt32(result, 8);
uint n4 = BitConverter.ToUInt32(result, 12);
uint n5 = BitConverter.ToUInt32(result, 16);
// Apply XOR to derive a combined number
uint index = n1 ^ n2 ^ n3 ^ n4 ^ n5;
// Calculate the bucket index
long pageSize = Convert.ToInt64(Math.Ceiling((double)(uint.MaxValue / scheme.Contents.Count)));
int contentIndex = Convert.ToInt32(Math.Floor((double)(index / pageSize)));
baseUri = new Uri(currentScheme + Uri.SchemeDelimiter + VirtualPathUtility.AppendTrailingSlash(scheme.Contents[contentIndex].ContentRoot));
break;
}
Uri uri = new Uri(baseUri, url);
url = uri.AbsoluteUri;
return url;
}
public static string ExternalResources(this HtmlHelper htmlHelper, string name)
{
return ExternalResources(htmlHelper, name, null);
}
public static string ExternalResources(this HtmlHelper htmlHelper, string name, object htmlAttributes)
{
return ExternalResources(htmlHelper, name, new RouteValueDictionary(htmlAttributes));
}
public static string ExternalResources(this HtmlHelper htmlHelper, string name, RouteValueDictionary htmlAttributes)
{
var section = ConfigManager.GetSection();
ReferenceElement settings = section.References[name];
if (settings == null)
throw new ConfigurationErrorsException(string.Format("External reference name {0} not found", name));
// get distinct show if / hide if conditions and group into files
IDictionary<Condition, IList<FileInfo>> conditions = new Dictionary<Condition, IList<FileInfo>>(new ConditionComparer());
foreach (FileInfo fileInfo in settings.Files)
{
Condition condition = new Condition { Action = fileInfo.Action, If = fileInfo.If };
if (conditions.Keys.Contains(condition))
{
conditions[condition].Add(fileInfo);
}
else
{
conditions.Add(condition, new List<FileInfo> { fileInfo });
}
}
string attributes = CreateAttributeList(htmlAttributes);
switch (settings.MimeType)
{
case "text/x-javascript":
case "text/javascript":
case "text/ecmascript":
{
ExternalResourceReference formatter = (filename, mimeType, attribs) => string.Format("<script src=\"{0}\" type=\"{1}\"{2}></script>", HttpUtility.HtmlAttributeEncode(filename), settings.MimeType, attribs);
return OutputReferences(htmlHelper.ViewContext, conditions, settings, attributes, formatter);
}
case "text/css":
{
ExternalResourceReference formatter = (filename, mimeType, attribs) => string.Format("<link rel=\"Stylesheet\" href=\"{0}\" type=\"{1}\"{2} />", HttpUtility.HtmlAttributeEncode(filename), settings.MimeType, attribs);
return OutputReferences(htmlHelper.ViewContext, conditions, settings, attributes, formatter);
}
// TODO: Decide any other reference types that we want to handle e.g. images
default:
{
return string.Empty;
}
}
}
private static string OutputReferences(ViewContext viewContext, IDictionary<Condition, IList<FileInfo>> conditions, ReferenceElement settings, string attributes, ExternalResourceReference formatter)
{
var section = ConfigManager.GetSection();
StringBuilder sb = new StringBuilder();
foreach (KeyValuePair<Condition, IList<FileInfo>> keyValuePair in conditions)
{
Condition condition = keyValuePair.Key;
IList<FileInfo> files = keyValuePair.Value;
// we need to wrap these files in conditional comments if the condition is anything but Action = Show with empty if (i.e. show to everything)
bool conditionalCommentRequired = (!condition.Action.Equals(Action.Show) || !string.IsNullOrEmpty(condition.If));
if (conditionalCommentRequired)
{
if (condition.Action.Equals(Action.Hide))
{
sb.AppendFormat("<!--[if {0}]>", condition.If);
sb.AppendLine();
}
else
{
sb.AppendFormat("<!--[if {0}]>-->", condition.If);
sb.AppendLine();
}
}
switch (section.Mode)
{
case Mode.Release:
VirtualPathData virtualPathData = RouteTable.Routes.GetVirtualPath(viewContext.RequestContext, new RouteValueDictionary(new { controller = "ExternalResource", action = "Index", name = settings.Name, version = section.Version, display = condition.Action, condition = condition.If }));
string url;
string currentScheme = viewContext.HttpContext.Request.Url.Scheme;
SchemeElement scheme = section.Schemes[currentScheme];
// if scheme doesn't exist or resourceRoot isn't set ...
if (scheme == null || String.IsNullOrEmpty(scheme.ResourceRoot))
{
// ... then use app relative url
url = virtualPathData.VirtualPath;
}
else
{
// ... otherwise, use absolute reference
string virtualPath = VirtualPathUtility.MakeRelative("~/", virtualPathData.VirtualPath);
Uri baseUri = new Uri(currentScheme + Uri.SchemeDelimiter + VirtualPathUtility.AppendTrailingSlash(scheme.ResourceRoot));
Uri uri = new Uri(baseUri, virtualPath);
url = uri.AbsoluteUri;
}
sb.AppendLine(formatter.Invoke(url, settings.MimeType, attributes));
break;
case Mode.Debug:
foreach (FileInfo fileInfo in files)
{
sb.AppendLine(formatter.Invoke(VirtualPathUtility.ToAbsolute(fileInfo.Filename), settings.MimeType, attributes));
}
break;
}
if (conditionalCommentRequired)
{
if (condition.Action.Equals(Action.Hide))
{
sb.AppendFormat("<![endif]-->");
sb.AppendLine();
}
else
{
sb.AppendFormat("<!--<![endif]-->");
sb.AppendLine();
}
}
}
return sb.ToString();
}
private static string CreateAttributeList(RouteValueDictionary attributes)
{
StringBuilder builder = new StringBuilder();
if (attributes != null)
{
foreach (string str in attributes.Keys)
{
string str2 = attributes[str].ToString();
if (attributes[str] is bool)
{
str2 = str2.ToLowerInvariant();
}
builder.AppendFormat(" {0}=\"{1}\"", str.ToLowerInvariant().Replace("_", ""), str2);
}
}
return builder.ToString();
}
/// <summary>
/// Resources the URL.
/// </summary>
/// <param name="urlHelper">The URL helper.</param>
/// <param name="name">The name.</param>
/// <returns></returns>
public static string WebResourceUrl(this UrlHelper urlHelper, string name)
{
var section = ConfigManager.GetSection();
ReferenceElement settings = section.References[name];
VirtualPathData virtualPathData = RouteTable.Routes.GetVirtualPath(urlHelper.RequestContext, new RouteValueDictionary(new { controller = "ExternalResource", action = "Index", name = settings.Name, version = section.Version, display = Action.Show, condition = "" }));
string url;
string currentScheme = urlHelper.RequestContext.HttpContext.Request.Url.Scheme;
SchemeElement scheme = section.Schemes[currentScheme];
// if scheme doesn't exist or resourceRoot isn't set ...
if (scheme == null || String.IsNullOrEmpty(scheme.ResourceRoot))
{
// ... then use app relative url
url = virtualPathData.VirtualPath;
}
else
{
// ... otherwise, use absolute reference
string virtualPath = VirtualPathUtility.MakeRelative("~/", virtualPathData.VirtualPath);
Uri baseUri = new Uri(currentScheme + Uri.SchemeDelimiter + VirtualPathUtility.AppendTrailingSlash(scheme.ResourceRoot));
Uri uri = new Uri(baseUri, virtualPath);
url = uri.AbsoluteUri;
}
return url;
}
}
internal class ConditionComparer : IEqualityComparer<Condition>
{
public bool Equals(Condition x, Condition y)
{
return x.Action.Equals(y.Action) && x.If.Equals(y.If);
}
public int GetHashCode(Condition obj)
{
return obj.Action.GetHashCode() + obj.If.GetHashCode();
}
}
}
|