#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 } }