#region License
/*
**************************************************************
* Author: Rick Strahl
* © West Wind Technologies, 2008 - 2009
* http://www.west-wind.com/
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
**************************************************************
*/
#endregion
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web;
using System.Text.RegularExpressions;
using System.Web.UI;
using System.Globalization;
using Westwind.Web;
using Westwind.Utilities;
namespace Westwind.Globalization
{
///
/// Http Handler that returns ASP.NET Local and Global Resources as JavaScript
/// objects. Supports both plain Resx Resources as well as DbResourceProvider
/// driven resources.
///
/// Objects are generated in the form of:
///
/// <<code lang="JavaScript">>var localRes = {
/// BackupFailed: "Backup was not completed",
/// Loading: "Loading"
/// );<</code>>
///
/// where the resource key becomes the property name with a string value.
///
/// The handler is driven through query string variables determines which
/// resources are returned:
///
/// ResourceSet - Examples: "resources" (global), "admin/somepage.aspx" "default.aspx" (local)
/// LocaleId - Examples: "de-de","de","" (empty=invariant)
/// ResourceType - Resx,ResDb
/// IncludeControls - if non-blank includes control values (. in name)
/// VarName - name of hte variable generated - if omitted localRes or globalRes is created.
/// ResourceMode - Flag required to find Resx resources on disk 0 - Local 1 - global 2 - plain resx
///
/// Resources retrieved are aggregated for the locale Id (ie. de-de returns
/// de-de,de and invariant) whichever matches first.
///
public class JavaScriptResourceHandler : IHttpHandler
{
public bool IsReusable
{
get { return true; }
}
public void ProcessRequest(HttpContext context)
{
HttpRequest Request = HttpContext.Current.Request;
HttpResponse Response = HttpContext.Current.Response;
string resourceSet = Request.Params["ResourceSet"];
string localeId = Request.Params["LocaleId"] ?? "";
string resourceType = Request.Params["ResourceType"] ?? "Resx"; // Resx/ResDb
bool includeControls = (Request.Params["IncludeControls"] ?? "") != "";
string varname = Request.Params["VarName"] ?? "localRes";
string resourceMode = (Request.Params["ResourceMode"] ?? "0");
// varname is embedded into script so validate to avoid script injection
// it's gotta be a valid C# and valid JavaScript name
Match match = Regex.Match(varname, @"^[\w|\d|_|$|@]*$");
if (match.Length < 1 || match.Groups[0].Value != varname)
this.SendErrorResponse("Invalid variable name passed.");
if (string.IsNullOrEmpty(resourceSet))
this.SendErrorResponse("Invalid ResourceSet specified.");
Dictionary resDict = null;
if (resourceType.ToLower() == "resdb")
{
DbResourceDataManager manager = new DbResourceDataManager();
resDict = manager.GetResourceSetNormalizedForLocaleId(localeId, resourceSet) as Dictionary;
}
else // Resx Resources
{
DbResXConverter converter = new DbResXConverter();
// must figure out the path
string resxPath = null;
if (DbResourceConfiguration.Current.ProjectType == GlobalizationProjectTypes.WebForms)
resxPath = converter.FormatWebResourceSetPath(resourceSet, (resourceMode == "0") );
else
resxPath = converter.FormatResourceSetPath(resourceSet);
resDict = converter.GetResXResourcesNormalizedForLocale(resxPath, localeId) as Dictionary;
}
if (resourceMode == "0" && !includeControls)
{
// filter the list to strip out controls (anything that contains a . in the ResourceId
// is considered a control value
resDict = resDict.Where(res => !res.Key.Contains('.') && res.Value is string)
.ToDictionary(dict => dict.Key, dict => dict.Value);
}
else
{
// return all resource strings
resDict = resDict.Where(res => res.Value is string)
.ToDictionary(dict => dict.Key, dict => dict.Value);
}
string javaScript = this.SerializeResourceDictionary(resDict, varname);
// client cache
if (!HttpContext.Current.IsDebuggingEnabled)
{
Response.ExpiresAbsolute = DateTime.UtcNow.AddDays(30);
Response.Cache.SetLastModified(DateTime.UtcNow);
Response.AppendHeader("Accept-Ranges", "bytes");
Response.AppendHeader("Vary", "Accept-Encoding");
//Response.Cache.SetETag("\"" + javaScript.GetHashCode().ToString("x") + "\"");
}
// OutputCache settings
HttpCachePolicy cache = Response.Cache;
cache.VaryByParams["LocaleId"] = true;
cache.VaryByParams["ResoureType"] = true;
cache.VaryByParams["IncludeControls"] = true;
cache.VaryByParams["VarName"] = true;
cache.VaryByParams["ResourceMode"] = true;
cache.SetOmitVaryStar(true);
DateTime now = DateTime.Now;
cache.SetCacheability(HttpCacheability.Public);
cache.SetExpires(now + TimeSpan.FromDays(365.0));
cache.SetValidUntilExpires(true);
cache.SetLastModified(now);
this.SendTextOutput(javaScript, "application/javascript");
}
///
/// Generates the actual JavaScript object map string makes up the
/// handler's result content.
///
///
///
///
private string SerializeResourceDictionary(Dictionary resxDict, string varname)
{
StringBuilder sb = new StringBuilder(2048);
sb.Append("var " + varname + " = {\r\n");
int anonymousIdCounter = 0;
foreach (KeyValuePair item in resxDict)
{
string value = item.Value as string;
if (value == null)
continue; // only encode string values
string key = item.Key;
if (string.IsNullOrEmpty(item.Key))
key = "__id" + anonymousIdCounter++.ToString();
key = key.Replace(".", "_");
if (key.Contains(" "))
key = StringUtils.ToCamelCase(key);
sb.Append("\t" + key + ": ");
sb.Append(WebUtils.EncodeJsString(value));
sb.Append(",\r\n");
}
sb.Append("}");
// strip off ,/r/n at end of string (if any)
sb.Replace(",\r\n}", "\r\n}");
sb.Append(";\r\n");
return sb.ToString();
}
///
/// Returns an error response to the client. Generates a 404 error
///
/// Error message to display
private void SendErrorResponse(string Message)
{
if (!string.IsNullOrEmpty(Message))
Message = "Invalid Web Resource";
HttpContext Context = HttpContext.Current;
Context.Response.StatusCode = 404;
Context.Response.StatusDescription = Message;
Context.Response.End();
}
///
/// Writes text output to server using UTF-8 encoding and GZip encoding
/// if supported by the client
///
///
///
///
private void SendTextOutput(string text, string contentType)
{
HttpResponse Response = HttpContext.Current.Response;
Response.ContentType = contentType;
Response.Charset = "utf-8";
// Trigger Gzip encoding and headers if supported
//WebUtils.GZipEncodePage();
//byte[] Output = Encoding.UTF8.GetBytes(text);
//Response.BinaryWrite(Output);
Response.Write(text);
}
///
/// Inserts global resources into the current page.
///
/// A control (typically) page needed to embed into the page
/// Name of the resourceSet to load
/// The Locale for which to load resources. Normalized from most specific to Invariant
/// Name of the variable generated
/// Resx or DbResourceProvider (database)
/// Determines whether control ids are included
public static void RegisterJavaScriptGlobalResources(Control control, string varName, string resourceSet, string localeId,
ResourceProviderTypes resourceType)
{
string url = GetJavaScriptGlobalResourcesUrl(varName, resourceSet, localeId, resourceType);
ClientScriptProxy.Current.RegisterClientScriptInclude(control, typeof(JavaScriptResourceHandler),
url, ScriptRenderModes.Header);
}
///
/// Embed global JavaScript resources into the page.
///
/// This version returns resources of the active Resx or DB Resource Provider
/// and includes no controls and creates a variable named "globalRes"
/// and uses the page's current UI culture
///
///
///
public static void RegisterJavaScriptGlobalResources(Control control, string varName, string resourceSet)
{
RegisterJavaScriptGlobalResources(control, varName, resourceSet,
CultureInfo.CurrentUICulture.IetfLanguageTag,
ResourceProviderTypes.AutoDetect);
}
///
/// Inserts local resources into the current page.
///
/// A control (typically) page needed to embed into the page
/// Name of the resourceSet to load
/// The Locale for which to load resources. Normalized from most specific to Invariant
/// Name of the variable generated
/// Resx or DbResourceProvider (database)
/// Determines whether control ids are included
public static void RegisterJavaScriptLocalResources(Control control, string varName, string localeId, string resourceSet,
ResourceProviderTypes resourceType, bool includeControls)
{
string url = GetJavaScriptLocalResourcesUrl(varName, localeId, resourceSet, resourceType, includeControls);
ClientScriptProxy.Current.RegisterClientScriptInclude(control, typeof(JavaScriptResourceHandler),
url, ScriptRenderModes.Header);
}
///
/// Embed global JavaScript resources into the page.
///
/// This version returns resources of the active Resx or DB Resource Provider,
/// includes no controls and uses the CurrentUICulture's locale id
///
/// A control or page instance required to
/// Name of the JavaScript object variable created
public static void RegisterJavaScriptLocalResources(Control control, string varName)
{
ResourceProviderTypes type = ResourceProviderTypes.AutoDetect;
// translate current page path into resource path
string resourceSet = WebUtils.GetAppRelativePath();
RegisterJavaScriptLocalResources(control, varName,
CultureInfo.CurrentUICulture.IetfLanguageTag, resourceSet, type, false);
}
///
/// Returns a URL to the JavaScriptResourceHandler.axd handler that retrieves
/// normalized resources for a given resource set and localeId and creates
/// a JavaScript object with the name specified.
///
/// This function returns only the URL - you're responsible for embedding
/// the URL into the page as a script tag to actually load the resources.
///
///
///
///
///
///
public static string GetJavaScriptGlobalResourcesUrl(string varName, string resourceSet, string localeId,
ResourceProviderTypes resourceType)
{
if (resourceType == ResourceProviderTypes.AutoDetect)
{
if (DbSimpleResourceProvider.ProviderLoaded || DbResourceProvider.ProviderLoaded)
resourceType = ResourceProviderTypes.DbResourceProvider;
}
StringBuilder sb = new StringBuilder(512);
sb.Append(WebUtils.ResolveUrl("~/") + "JavaScriptResourceHandler.axd?");
sb.AppendFormat("ResourceSet={0}&LocaleId={1}&VarName={2}&ResourceType={3}",
resourceSet, localeId, varName,
resourceType == ResourceProviderTypes.DbResourceProvider ? "resdb" : "resx");
sb.Append("&ResourceMode=1");
return sb.ToString();
}
///
/// Returns a URL to the JavaScriptResourceHandler.axd handler that retrieves
/// normalized resources for a given resource set and localeId and creates
/// a JavaScript object with the name specified.
///
/// This version assumes the current UI Culture and auto-detects the
/// provider type (Resx or DbRes) currently active.
///
///
///
///
public static string GetJavaScriptGlobalResourcesUrl(string varName, string resourceSet)
{
string localeId = CultureInfo.CurrentUICulture.IetfLanguageTag;
return GetJavaScriptGlobalResourcesUrl(varName, resourceSet, localeId, ResourceProviderTypes.AutoDetect);
}
///
/// Inserts local resources into the current page.
///
/// A control (typically) page needed to embed into the page
/// Name of the resourceSet to load
/// The Locale for which to load resources. Normalized from most specific to Invariant
/// Name of the variable generated
/// Resx or DbResourceProvider (database)
/// Determines whether control ids are included
public static string GetJavaScriptLocalResourcesUrl(string varName, string localeId, string resourceSet,
ResourceProviderTypes resourceType, bool includeControls)
{
if (resourceType == ResourceProviderTypes.AutoDetect)
{
if (DbSimpleResourceProvider.ProviderLoaded || DbResourceProvider.ProviderLoaded)
resourceType = ResourceProviderTypes.DbResourceProvider;
}
StringBuilder sb = new StringBuilder(512);
sb.Append(WebUtils.ResolveUrl("~/") + "JavaScriptResourceHandler.axd?");
sb.AppendFormat("ResourceSet={0}&LocaleId={1}&VarName={2}&ResourceType={3}&ResourceMode=0",
resourceSet, localeId, varName,
(resourceType == ResourceProviderTypes.DbResourceProvider ? "resdb" : "resx") );
if (includeControls)
sb.Append("&IncludeControls=1");
return sb.ToString();
}
///
/// Returns a URL to embed local resources into the page via JavaScriptResourceHandler.axd.
/// This method returns only a URL - you're responsible for embedding the script tag into the page
/// to actually load the resources.
///
/// This version assumes the local resource set for the current request/page and autodetected
/// resources (resdb or resx). It also uses the CurrentUICulture as the locale.
///
/// The name of the JavaScript variable to create
///
///
public static string GetJavaScriptLocalResourcesUrl(string varName, bool includeControls)
{
string resourceSet = WebUtils.GetAppRelativePath();
string localeId = CultureInfo.CurrentUICulture.IetfLanguageTag;
return GetJavaScriptLocalResourcesUrl(varName, localeId, resourceSet,
ResourceProviderTypes.AutoDetect, includeControls);
}
///
/// Returns a standard Resx resource based on it's . delimited resourceset name
/// this version defaults to automatic detection of type (ResX or Db) based
/// on configuration settings in provider section and the currently active
/// locale on the machine.
///
/// The name of the JavaScript variable to create
/// The name of the resource set
/// Example:
/// CodePasteMvc.Resources.Resources (~/Resources/Resources.resx in CodePasteMvc project)
///
///
public static string GetJavaScriptResourcesUrl(string varName, string resourceSet)
{
string localeId = CultureInfo.CurrentUICulture.IetfLanguageTag;
return GetJavaScriptResourcesUrl(varName, resourceSet, localeId, ResourceProviderTypes.AutoDetect);
}
///
/// Returns a standard Resx resource based on it's . delimited resourceset name
/// this version defaults to automatic detection of type (ResX or Db) based
/// on configuration settings in provider section.
///
/// The name of the JavaScript variable to create
/// The name of the resource set
/// Example:
/// CodePasteMvc.Resources.Resources (~/Resources/Resources.resx in CodePasteMvc project)
///
/// IETF locale id (2 or 4 en or en-US)
///
public static string GetJavaScriptResourcesUrl(string varName, string resourceSet, string localeId)
{
return GetJavaScriptResourcesUrl(varName, resourceSet, localeId, ResourceProviderTypes.AutoDetect);
}
///
/// Returns a standard Resx resource based on it's . delimited resourceset name
///
/// The name of the JavaScript variable to create
/// The name of the resource set
///
/// Example:
/// CodePasteMvc.Resources.Resources (~/Resources/Resources.resx in CodePasteMvc project)
///
/// IETF locale id (2 or 4 en or en-US)
/// ResDb or ResX
///
public static string GetJavaScriptResourcesUrl(string varName, string resourceSet, string localeId,
ResourceProviderTypes resourceType)
{
if (resourceType == ResourceProviderTypes.AutoDetect)
{
if (DbSimpleResourceProvider.ProviderLoaded || DbResourceProvider.ProviderLoaded)
resourceType = ResourceProviderTypes.DbResourceProvider;
}
StringBuilder sb = new StringBuilder(512);
sb.Append(WebUtils.ResolveUrl("~/") + "JavaScriptResourceHandler.axd?");
sb.AppendFormat("ResourceSet={0}&LocaleId={1}&VarName={2}&ResourceType={3}",
resourceSet, localeId, varName,
resourceType == ResourceProviderTypes.DbResourceProvider ? "resdb" : "resx");
sb.Append("&ResourceMode=2");
return sb.ToString();
}
}
///
/// Determines the resource provider type used
/// to retrieve resources.
///
/// Note only applies to the stock ResX provider
/// or the DbResourceProviders of this assembly.
/// Other custom resource providers are not supported.
///
public enum ResourceProviderTypes
{
Resx,
DbResourceProvider,
AutoDetect
}
}