From 9469b0b844d47328a557cf66e517c3059f646ebd Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 19 Mar 2015 13:53:15 +1100 Subject: [PATCH] Fixes: U4-6402 Grid config file should be merged with package.manifests & U4-6427 Grid config file has caching problems because it's downloaded as a static file This also fixes up the OutputCaching params on the BackOfficeController since OutputCache is bypassed when an action requires authentication, so now we manually do some caching when not in debug mode for authorized actions (of course auth happens before any cached response can occur). This also fixes up the static caching that was happening with the ManifestBuilder so now when that is not in use it gives back it's memory. This also fixes up any client side caching that was happening on BackOfficeController - before we were allowing client cache to happen for a few actions on that controller which is incorrect, we need to disable all client cache for all actions on that controller. --- src/Umbraco.Core/CoreBootManager.cs | 11 +- src/Umbraco.Core/Manifest/ManifestBuilder.cs | 97 ++-- src/Umbraco.Core/Manifest/ManifestParser.cs | 47 +- src/Umbraco.Core/Manifest/PackageManifest.cs | 5 + .../PropertyEditors/GridEditor.cs | 62 +++ .../ParameterEditorResolver.cs | 11 +- .../PropertyEditors/PropertyEditorResolver.cs | 14 +- src/Umbraco.Core/Umbraco.Core.csproj | 1 + .../Manifest/ManifestParserTests.cs | 55 +++ .../src/common/services/grid.service.js | 2 +- .../Editors/BackOfficeController.cs | 427 +++++++++++------- .../Mvc/DisableClientCacheAttribute.cs | 25 + .../Mvc/MinifyJavaScriptResultAttribute.cs | 5 +- 13 files changed, 533 insertions(+), 229 deletions(-) create mode 100644 src/Umbraco.Core/PropertyEditors/GridEditor.cs create mode 100644 src/Umbraco.Web/Mvc/DisableClientCacheAttribute.cs diff --git a/src/Umbraco.Core/CoreBootManager.cs b/src/Umbraco.Core/CoreBootManager.cs index f59b6e37ab..16ffc828a9 100644 --- a/src/Umbraco.Core/CoreBootManager.cs +++ b/src/Umbraco.Core/CoreBootManager.cs @@ -1,10 +1,12 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Web; using AutoMapper; using Umbraco.Core.Cache; using Umbraco.Core.Configuration; +using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.Models.Mapping; using Umbraco.Core.Models.PublishedContent; @@ -21,6 +23,7 @@ using Umbraco.Core.PropertyEditors; using Umbraco.Core.PropertyEditors.ValueConverters; using Umbraco.Core.Publishing; using Umbraco.Core.Macros; +using Umbraco.Core.Manifest; using Umbraco.Core.Services; using Umbraco.Core.Sync; using Umbraco.Core.Strings; @@ -259,8 +262,12 @@ namespace Umbraco.Core /// protected virtual void InitializeResolvers() { - PropertyEditorResolver.Current = new PropertyEditorResolver(() => PluginManager.Current.ResolvePropertyEditors()); - ParameterEditorResolver.Current = new ParameterEditorResolver(() => PluginManager.Current.ResolveParameterEditors()); + var builder = new ManifestBuilder( + ApplicationCache.RuntimeCache, + new ManifestParser(new DirectoryInfo(IOHelper.MapPath("~/App_Plugins")), ApplicationCache.RuntimeCache)); + + PropertyEditorResolver.Current = new PropertyEditorResolver(() => PluginManager.Current.ResolvePropertyEditors(), builder); + ParameterEditorResolver.Current = new ParameterEditorResolver(() => PluginManager.Current.ResolveParameterEditors(), builder); //setup the validators resolver with our predefined validators ValidatorsResolver.Current = new ValidatorsResolver(new[] diff --git a/src/Umbraco.Core/Manifest/ManifestBuilder.cs b/src/Umbraco.Core/Manifest/ManifestBuilder.cs index d62c215c8d..5f95deea22 100644 --- a/src/Umbraco.Core/Manifest/ManifestBuilder.cs +++ b/src/Umbraco.Core/Manifest/ManifestBuilder.cs @@ -1,6 +1,8 @@ -using System.Collections.Concurrent; +using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; +using Umbraco.Core.Cache; using Umbraco.Core.IO; using Umbraco.Core.PropertyEditors; @@ -11,73 +13,92 @@ namespace Umbraco.Core.Manifest /// internal class ManifestBuilder { + private readonly IRuntimeCacheProvider _cache; + private readonly ManifestParser _parser; - private static readonly ConcurrentDictionary StaticCache = new ConcurrentDictionary(); - - private const string ManifestKey = "manifests"; + public ManifestBuilder(IRuntimeCacheProvider cache, ManifestParser parser) + { + _cache = cache; + _parser = parser; + } + + private const string GridEditorsKey = "grideditors"; private const string PropertyEditorsKey = "propertyeditors"; private const string ParameterEditorsKey = "parametereditors"; /// - /// Returns all property editors found in the manfifests + /// Returns all grid editors found in the manfifests /// - internal static IEnumerable PropertyEditors + internal IEnumerable GridEditors { get { - return (IEnumerable) StaticCache.GetOrAdd( - PropertyEditorsKey, - s => + return _cache.GetCacheItem>( + typeof (ManifestBuilder) + GridEditorsKey, + () => + { + var editors = new List(); + foreach (var manifest in _parser.GetManifests()) { - var editors = new List(); - foreach (var manifest in GetManifests()) + if (manifest.GridEditors != null) { - if (manifest.PropertyEditors != null) - { - editors.AddRange(ManifestParser.GetPropertyEditors(manifest.PropertyEditors)); - } - + editors.AddRange(ManifestParser.GetGridEditors(manifest.GridEditors)); } - return editors; - }); + + } + return editors; + }, new TimeSpan(0, 10, 0)); + } + } + + /// + /// Returns all property editors found in the manfifests + /// + internal IEnumerable PropertyEditors + { + get + { + return _cache.GetCacheItem>( + typeof(ManifestBuilder) + PropertyEditorsKey, + () => + { + var editors = new List(); + foreach (var manifest in _parser.GetManifests()) + { + if (manifest.PropertyEditors != null) + { + editors.AddRange(ManifestParser.GetPropertyEditors(manifest.PropertyEditors)); + } + + } + return editors; + }, new TimeSpan(0, 10, 0)); } } /// /// Returns all parameter editors found in the manfifests and all property editors that are flagged to be parameter editors /// - internal static IEnumerable ParameterEditors + internal IEnumerable ParameterEditors { get { - return (IEnumerable)StaticCache.GetOrAdd( - ParameterEditorsKey, - s => + return _cache.GetCacheItem>( + typeof (ManifestBuilder) + ParameterEditorsKey, + () => { var editors = new List(); - foreach (var manifest in GetManifests()) + foreach (var manifest in _parser.GetManifests()) { if (manifest.ParameterEditors != null) { - editors.AddRange(ManifestParser.GetParameterEditors(manifest.ParameterEditors)); + editors.AddRange(ManifestParser.GetParameterEditors(manifest.ParameterEditors)); } } return editors; - }); + }, new TimeSpan(0, 10, 0)); } } - - /// - /// Ensures the manifests are found and loaded into memory - /// - private static IEnumerable GetManifests() - { - return (IEnumerable) StaticCache.GetOrAdd(ManifestKey, s => - { - var parser = new ManifestParser(new DirectoryInfo(IOHelper.MapPath("~/App_Plugins"))); - return parser.GetManifests(); - }); - } - + } } \ No newline at end of file diff --git a/src/Umbraco.Core/Manifest/ManifestParser.cs b/src/Umbraco.Core/Manifest/ManifestParser.cs index 6794377001..7847eccf6e 100644 --- a/src/Umbraco.Core/Manifest/ManifestParser.cs +++ b/src/Umbraco.Core/Manifest/ManifestParser.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Text.RegularExpressions; using Newtonsoft.Json; using Newtonsoft.Json.Linq; +using Umbraco.Core.Cache; using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.PropertyEditors; @@ -17,15 +18,28 @@ namespace Umbraco.Core.Manifest internal class ManifestParser { private readonly DirectoryInfo _pluginsDir; - + private readonly IRuntimeCacheProvider _cache; + //used to strip comments private static readonly Regex CommentsSurround = new Regex(@"/\*([^*]|[\r\n]|(\*+([^*/]|[\r\n])))*\*+/", RegexOptions.Compiled); private static readonly Regex CommentsLine = new Regex(@"^\s*//.*?$", RegexOptions.Compiled | RegexOptions.Multiline); - public ManifestParser(DirectoryInfo pluginsDir) + public ManifestParser(DirectoryInfo pluginsDir, IRuntimeCacheProvider cache) { if (pluginsDir == null) throw new ArgumentNullException("pluginsDir"); _pluginsDir = pluginsDir; + _cache = cache; + } + + /// + /// Parse the grid editors from the json array + /// + /// + /// + internal static IEnumerable GetGridEditors(JArray jsonEditors) + { + return JsonConvert.DeserializeObject>( + jsonEditors.ToString()); } /// @@ -57,11 +71,17 @@ namespace Umbraco.Core.Manifest /// Get all registered manifests /// /// + /// + /// This ensures that we only build and look for all manifests once per Web app (based on the IRuntimeCache) + /// public IEnumerable GetManifests() { - //get all Manifest.js files in the appropriate folders - var manifestFileContents = GetAllManifestFileContents(_pluginsDir); - return CreateManifests(manifestFileContents.ToArray()); + return _cache.GetCacheItem>(typeof (ManifestParser) + "GetManifests", () => + { + //get all Manifest.js files in the appropriate folders + var manifestFileContents = GetAllManifestFileContents(_pluginsDir); + return CreateManifests(manifestFileContents.ToArray()); + }, new TimeSpan(0, 10, 0)); } /// @@ -154,6 +174,20 @@ namespace Umbraco.Core.Manifest throw new FormatException("The manifest is not formatted correctly contains more than one 'propertyEditors' element"); } + //validate the parameterEditors section + var paramEditors = deserialized.Properties().Where(x => x.Name == "parameterEditors").ToArray(); + if (paramEditors.Length > 1) + { + throw new FormatException("The manifest is not formatted correctly contains more than one 'parameterEditors' element"); + } + + //validate the gridEditors section + var gridEditors = deserialized.Properties().Where(x => x.Name == "gridEditors").ToArray(); + if (gridEditors.Length > 1) + { + throw new FormatException("The manifest is not formatted correctly contains more than one 'gridEditors' element"); + } + var jConfig = init.Any() ? (JArray)deserialized["javascript"] : new JArray(); ReplaceVirtualPaths(jConfig); @@ -181,7 +215,8 @@ namespace Umbraco.Core.Manifest JavaScriptInitialize = jConfig, StylesheetInitialize = cssConfig, PropertyEditors = propEditors.Any() ? (JArray)deserialized["propertyEditors"] : new JArray(), - ParameterEditors = propEditors.Any() ? (JArray)deserialized["parameterEditors"] : new JArray() + ParameterEditors = propEditors.Any() ? (JArray)deserialized["parameterEditors"] : new JArray(), + GridEditors = propEditors.Any() ? (JArray)deserialized["gridEditors"] : new JArray() }; result.Add(manifest); } diff --git a/src/Umbraco.Core/Manifest/PackageManifest.cs b/src/Umbraco.Core/Manifest/PackageManifest.cs index 3475a60312..dea1eb9877 100644 --- a/src/Umbraco.Core/Manifest/PackageManifest.cs +++ b/src/Umbraco.Core/Manifest/PackageManifest.cs @@ -26,5 +26,10 @@ namespace Umbraco.Core.Manifest /// The json array of parameter editors /// public JArray ParameterEditors { get; set; } + + /// + /// The json array of grid editors + /// + public JArray GridEditors { get; set; } } } \ No newline at end of file diff --git a/src/Umbraco.Core/PropertyEditors/GridEditor.cs b/src/Umbraco.Core/PropertyEditors/GridEditor.cs new file mode 100644 index 0000000000..2fd24a2e99 --- /dev/null +++ b/src/Umbraco.Core/PropertyEditors/GridEditor.cs @@ -0,0 +1,62 @@ +using System.Collections.Generic; +using Newtonsoft.Json; + +namespace Umbraco.Core.PropertyEditors +{ + internal class GridEditor + { + public GridEditor() + { + Config = new Dictionary(); + } + + [JsonProperty("name", Required = Required.Always)] + public string Name { get; set; } + + [JsonProperty("alias", Required = Required.Always)] + public string Alias { get; set; } + + [JsonProperty("view", Required = Required.Always)] + public string View { get; set; } + + [JsonProperty("render")] + public string Render { get; set; } + + [JsonProperty("icon", Required = Required.Always)] + public string Icon { get; set; } + + [JsonProperty("config")] + public IDictionary Config { get; set; } + + protected bool Equals(GridEditor other) + { + return string.Equals(Alias, other.Alias); + } + + /// + /// Determines whether the specified is equal to the current . + /// + /// + /// true if the specified object is equal to the current object; otherwise, false. + /// + /// The object to compare with the current object. + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((GridEditor) obj); + } + + /// + /// Serves as a hash function for a particular type. + /// + /// + /// A hash code for the current . + /// + public override int GetHashCode() + { + return Alias.GetHashCode(); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/PropertyEditors/ParameterEditorResolver.cs b/src/Umbraco.Core/PropertyEditors/ParameterEditorResolver.cs index ef01b3a891..2130476485 100644 --- a/src/Umbraco.Core/PropertyEditors/ParameterEditorResolver.cs +++ b/src/Umbraco.Core/PropertyEditors/ParameterEditorResolver.cs @@ -15,11 +15,14 @@ namespace Umbraco.Core.PropertyEditors /// internal class ParameterEditorResolver : LazyManyObjectsResolverBase { - public ParameterEditorResolver(Func> typeListProducerList) + private readonly ManifestBuilder _builder; + + public ParameterEditorResolver(Func> typeListProducerList, ManifestBuilder builder) : base(typeListProducerList, ObjectLifetimeScope.Application) { + _builder = builder; } - + /// /// Returns the parameter editors /// @@ -38,9 +41,9 @@ namespace Umbraco.Core.PropertyEditors //exclude the non parameter editor c# property editors .Except(filtered) //include the manifest parameter editors - .Union(ManifestBuilder.ParameterEditors) + .Union(_builder.ParameterEditors) //include the manifest prop editors that are parameter editors - .Union(ManifestBuilder.PropertyEditors.Where(x => x.IsParameterEditor)); + .Union(_builder.PropertyEditors.Where(x => x.IsParameterEditor)); } } diff --git a/src/Umbraco.Core/PropertyEditors/PropertyEditorResolver.cs b/src/Umbraco.Core/PropertyEditors/PropertyEditorResolver.cs index 90c6e184aa..4e17915712 100644 --- a/src/Umbraco.Core/PropertyEditors/PropertyEditorResolver.cs +++ b/src/Umbraco.Core/PropertyEditors/PropertyEditorResolver.cs @@ -1,6 +1,8 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; +using Umbraco.Core.IO; using Umbraco.Core.Manifest; using Umbraco.Core.ObjectResolution; @@ -17,7 +19,17 @@ namespace Umbraco.Core.PropertyEditors public PropertyEditorResolver(Func> typeListProducerList) : base(typeListProducerList, ObjectLifetimeScope.Application) { - _unioned = new Lazy>(() => Values.Union(ManifestBuilder.PropertyEditors).ToList()); + var builder = new ManifestBuilder( + ApplicationContext.Current.ApplicationCache.RuntimeCache, + new ManifestParser(new DirectoryInfo(IOHelper.MapPath("~/App_Plugins")), ApplicationContext.Current.ApplicationCache.RuntimeCache)); + + _unioned = new Lazy>(() => Values.Union(builder.PropertyEditors).ToList()); + } + + internal PropertyEditorResolver(Func> typeListProducerList, ManifestBuilder builder) + : base(typeListProducerList, ObjectLifetimeScope.Application) + { + _unioned = new Lazy>(() => Values.Union(builder.PropertyEditors).ToList()); } private readonly Lazy> _unioned; diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index d9d37cb436..dec8a9c1fc 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -347,6 +347,7 @@ + diff --git a/src/Umbraco.Tests/Manifest/ManifestParserTests.cs b/src/Umbraco.Tests/Manifest/ManifestParserTests.cs index b51d5b84c9..603ff56838 100644 --- a/src/Umbraco.Tests/Manifest/ManifestParserTests.cs +++ b/src/Umbraco.Tests/Manifest/ManifestParserTests.cs @@ -63,6 +63,61 @@ namespace Umbraco.Tests.Manifest Assert.AreEqual(0, parser.ElementAt(0).PreValueEditor.Fields.ElementAt(1).Validators.Count()); } + [Test] + public void Parse_Grid_Editors() + { + var a = JsonConvert.DeserializeObject(@"[ + { + alias: 'Test.Test1', + name: 'Test 1', + view: 'blah', + icon: 'hello' + }, + { + alias: 'Test.Test2', + name: 'Test 2', + config: { key1: 'some default val' }, + view: '/hello/world.cshtml', + icon: 'helloworld' + }, + { + alias: 'Test.Test3', + name: 'Test 3', + config: { key1: 'some default val' }, + view: '/hello/world.html', + render: '/hello/world.cshtml', + icon: 'helloworld' + } +]"); + var parser = ManifestParser.GetGridEditors(a).ToArray(); + + Assert.AreEqual(3, parser.Count()); + + Assert.AreEqual("Test.Test1", parser.ElementAt(0).Alias); + Assert.AreEqual("Test 1", parser.ElementAt(0).Name); + Assert.AreEqual("blah", parser.ElementAt(0).View); + Assert.AreEqual("hello", parser.ElementAt(0).Icon); + Assert.IsNull(parser.ElementAt(0).Render); + Assert.AreEqual(0, parser.ElementAt(0).Config.Count); + + Assert.AreEqual("Test.Test2", parser.ElementAt(1).Alias); + Assert.AreEqual("Test 2", parser.ElementAt(1).Name); + Assert.AreEqual("/hello/world.cshtml", parser.ElementAt(1).View); + Assert.AreEqual("helloworld", parser.ElementAt(1).Icon); + Assert.IsNull(parser.ElementAt(1).Render); + Assert.AreEqual(1, parser.ElementAt(1).Config.Count); + Assert.AreEqual("some default val", parser.ElementAt(1).Config["key1"]); + + Assert.AreEqual("Test.Test3", parser.ElementAt(2).Alias); + Assert.AreEqual("Test 3", parser.ElementAt(2).Name); + Assert.AreEqual("/hello/world.html", parser.ElementAt(2).View); + Assert.AreEqual("helloworld", parser.ElementAt(2).Icon); + Assert.AreEqual("/hello/world.cshtml", parser.ElementAt(2).Render); + Assert.AreEqual(1, parser.ElementAt(2).Config.Count); + Assert.AreEqual("some default val", parser.ElementAt(2).Config["key1"]); + + } + [Test] public void Parse_Property_Editors() { diff --git a/src/Umbraco.Web.UI.Client/src/common/services/grid.service.js b/src/Umbraco.Web.UI.Client/src/common/services/grid.service.js index f64701b8d8..898d1293fe 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/grid.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/grid.service.js @@ -1,7 +1,7 @@ angular.module('umbraco.services') .factory('gridService', function ($http, $q){ - var configPath = "../config/grid.editors.config.js"; + var configPath = Umbraco.Sys.ServerVariables.umbracoUrls.gridConfig; var service = { getGridEditors: function () { return $http.get(configPath); diff --git a/src/Umbraco.Web/Editors/BackOfficeController.cs b/src/Umbraco.Web/Editors/BackOfficeController.cs index 003104de63..37fbd32e22 100644 --- a/src/Umbraco.Web/Editors/BackOfficeController.cs +++ b/src/Umbraco.Web/Editors/BackOfficeController.cs @@ -14,7 +14,10 @@ using Umbraco.Core.Configuration; using Umbraco.Core.IO; using Umbraco.Core.Manifest; using Umbraco.Core; +using Umbraco.Core.Cache; +using Umbraco.Core.Logging; using Umbraco.Core.Models; +using Umbraco.Core.PropertyEditors; using Umbraco.Core.Security; using Umbraco.Web.Models.ContentEditing; using Umbraco.Web.Mvc; @@ -31,6 +34,7 @@ namespace Umbraco.Web.Editors /// A controller to render out the default back office view and JS results /// [UmbracoUseHttps] + [DisableClientCache] public class BackOfficeController : UmbracoController { /// @@ -81,11 +85,11 @@ namespace Umbraco.Web.Editors /// /// [MinifyJavaScriptResult(Order = 0)] - [OutputCache(Order = 1, VaryByParam = "none", Location = OutputCacheLocation.Any, Duration = 5000)] + [OutputCache(Order = 1, VaryByParam = "none", Location = OutputCacheLocation.Server, Duration = 5000)] public JavaScriptResult Application() { var plugins = new DirectoryInfo(Server.MapPath("~/App_Plugins")); - var parser = new ManifestParser(plugins); + var parser = new ManifestParser(plugins, ApplicationContext.ApplicationCache.RuntimeCache); var initJs = new JsInitialization(parser); var initCss = new CssInitialization(parser); @@ -106,16 +110,75 @@ namespace Umbraco.Web.Editors [HttpGet] public JsonNetResult GetManifestAssetList() { - var plugins = new DirectoryInfo(Server.MapPath("~/App_Plugins")); - var parser = new ManifestParser(plugins); - var initJs = new JsInitialization(parser); - var initCss = new CssInitialization(parser); - var jsResult = initJs.GetJavascriptInitializationArray(HttpContext, new JArray()); - var cssResult = initCss.GetStylesheetInitializationArray(HttpContext); + Func getResult = () => + { + var plugins = new DirectoryInfo(Server.MapPath("~/App_Plugins")); + var parser = new ManifestParser(plugins, ApplicationContext.ApplicationCache.RuntimeCache); + var initJs = new JsInitialization(parser); + var initCss = new CssInitialization(parser); + var jsResult = initJs.GetJavascriptInitializationArray(HttpContext, new JArray()); + var cssResult = initCss.GetStylesheetInitializationArray(HttpContext); + ManifestParser.MergeJArrays(jsResult, cssResult); + return jsResult; + }; - ManifestParser.MergeJArrays(jsResult, cssResult); + //cache the result if debugging is disabled + var result = HttpContext.IsDebuggingEnabled + ? getResult() + : ApplicationContext.ApplicationCache.RuntimeCache.GetCacheItem( + typeof (BackOfficeController) + "GetManifestAssetList", + () => getResult(), + new TimeSpan(0, 10, 0)); - return new JsonNetResult {Data = jsResult, Formatting = Formatting.Indented}; + return new JsonNetResult { Data = result, Formatting = Formatting.Indented }; + } + + [UmbracoAuthorize(Order = 0)] + [HttpGet] + public JsonNetResult GetGridConfig() + { + Func> getResult = () => + { + var editors = new List(); + var gridConfig = Server.MapPath("~/Config/grid.editors.config.js"); + if (System.IO.File.Exists(gridConfig)) + { + try + { + var arr = JArray.Parse(System.IO.File.ReadAllText(gridConfig)); + //ensure the contents parse correctly to objects + var parsed = ManifestParser.GetGridEditors(arr); + editors.AddRange(parsed); + } + catch (Exception ex) + { + LogHelper.Error("Could not parse the contents of grid.editors.config.js into a JSON array", ex); + } + } + + var plugins = new DirectoryInfo(Server.MapPath("~/App_Plugins")); + var parser = new ManifestParser(plugins, ApplicationContext.ApplicationCache.RuntimeCache); + var builder = new ManifestBuilder(ApplicationContext.ApplicationCache.RuntimeCache, parser); + foreach (var gridEditor in builder.GridEditors) + { + //no duplicates! (based on alias) + if (editors.Contains(gridEditor) == false) + { + editors.Add(gridEditor); + } + } + return editors; + }; + + //cache the result if debugging is disabled + var result = HttpContext.IsDebuggingEnabled + ? getResult() + : ApplicationContext.ApplicationCache.RuntimeCache.GetCacheItem>( + typeof(BackOfficeController) + "GetGridConfig", + () => getResult(), + new TimeSpan(0, 10, 0)); + + return new JsonNetResult { Data = result, Formatting = Formatting.Indented }; } /// @@ -124,168 +187,172 @@ namespace Umbraco.Web.Editors /// [UmbracoAuthorize(Order = 0)] [MinifyJavaScriptResult(Order = 1)] - [OutputCache(Order = 2, VaryByParam = "none", Location = OutputCacheLocation.Any, Duration = 5000)] public JavaScriptResult ServerVariables() { - //authenticationApiBaseUrl - - //now we need to build up the variables - var d = new Dictionary + Func> getResult = () => new Dictionary + { { + "umbracoUrls", new Dictionary { - "umbracoUrls", new Dictionary - { - {"legacyTreeJs", Url.Action("LegacyTreeJs", "BackOffice")}, - {"manifestAssetList", Url.Action("GetManifestAssetList", "BackOffice")}, - {"serverVarsJs", Url.Action("Application", "BackOffice")}, - //API URLs - { - "embedApiBaseUrl", Url.GetUmbracoApiServiceBaseUrl( - controller => controller.GetEmbed("",0,0)) - }, - { - "userApiBaseUrl", Url.GetUmbracoApiServiceBaseUrl( - controller => controller.PostDisableUser(0)) - }, - { - "contentApiBaseUrl", Url.GetUmbracoApiServiceBaseUrl( - controller => controller.PostSave(null)) - }, - { - "mediaApiBaseUrl", Url.GetUmbracoApiServiceBaseUrl( - controller => controller.GetRootMedia()) - }, - { - "imagesApiBaseUrl", Url.GetUmbracoApiServiceBaseUrl( - controller => controller.GetBigThumbnail(0)) - }, - { - "sectionApiBaseUrl", Url.GetUmbracoApiServiceBaseUrl( - controller => controller.GetSections()) - }, - { - "treeApplicationApiBaseUrl", Url.GetUmbracoApiServiceBaseUrl( - controller => controller.GetApplicationTrees(null, null, null)) - }, - { - "contentTypeApiBaseUrl", Url.GetUmbracoApiServiceBaseUrl( - controller => controller.GetAllowedChildren(0)) - }, - { - "mediaTypeApiBaseUrl", Url.GetUmbracoApiServiceBaseUrl( - controller => controller.GetAllowedChildren(0)) - }, - { - "macroApiBaseUrl", Url.GetUmbracoApiServiceBaseUrl( - controller => controller.GetMacroParameters(0)) - }, - { - "authenticationApiBaseUrl", Url.GetUmbracoApiServiceBaseUrl( - controller => controller.PostLogin(null)) - }, - { - "currentUserApiBaseUrl", Url.GetUmbracoApiServiceBaseUrl( - controller => controller.GetMembershipProviderConfig()) - }, - { - "legacyApiBaseUrl", Url.GetUmbracoApiServiceBaseUrl( - controller => controller.DeleteLegacyItem(null, null, null)) - }, - { - "entityApiBaseUrl", Url.GetUmbracoApiServiceBaseUrl( - controller => controller.GetById(0, UmbracoEntityTypes.Media)) - }, - { - "dataTypeApiBaseUrl", Url.GetUmbracoApiServiceBaseUrl( - controller => controller.GetById(0)) - }, - { - "dashboardApiBaseUrl", Url.GetUmbracoApiServiceBaseUrl( - controller => controller.GetDashboard(null)) - }, - { - "logApiBaseUrl", Url.GetUmbracoApiServiceBaseUrl( - controller => controller.GetEntityLog(0)) - }, - { - "memberApiBaseUrl", Url.GetUmbracoApiServiceBaseUrl( - controller => controller.GetByKey(Guid.Empty)) - }, - { - "packageInstallApiBaseUrl", Url.GetUmbracoApiServiceBaseUrl( - controller => controller.Fetch(string.Empty)) - }, - { - "rteApiBaseUrl", Url.GetUmbracoApiServiceBaseUrl( - controller => controller.GetConfiguration()) - }, - { - "stylesheetApiBaseUrl", Url.GetUmbracoApiServiceBaseUrl( - controller => controller.GetAll()) - }, - { - "memberTypeApiBaseUrl", Url.GetUmbracoApiServiceBaseUrl( - controller => controller.GetAllTypes()) - }, - { - "updateCheckApiBaseUrl", Url.GetUmbracoApiServiceBaseUrl( - controller => controller.GetCheck()) - }, - { - "tagApiBaseUrl", Url.GetUmbracoApiServiceBaseUrl( - controller => controller.GetAllTags(null)) - }, - { - "memberTreeBaseUrl", Url.GetUmbracoApiServiceBaseUrl( - controller => controller.GetNodes("-1", null)) - }, - { - "mediaTreeBaseUrl", Url.GetUmbracoApiServiceBaseUrl( - controller => controller.GetNodes("-1", null)) - }, - { - "contentTreeBaseUrl", Url.GetUmbracoApiServiceBaseUrl( - controller => controller.GetNodes("-1", null)) - }, - { - "tagsDataBaseUrl", Url.GetUmbracoApiServiceBaseUrl( - controller => controller.GetTags("")) - }, - { - "examineMgmtBaseUrl", Url.GetUmbracoApiServiceBaseUrl( - controller => controller.GetIndexerDetails()) - }, - { - "xmlDataIntegrityBaseUrl", Url.GetUmbracoApiServiceBaseUrl( - controller => controller.CheckContentXmlTable()) - } - } - }, + {"legacyTreeJs", Url.Action("LegacyTreeJs", "BackOffice")}, + {"manifestAssetList", Url.Action("GetManifestAssetList", "BackOffice")}, + {"gridConfig", Url.Action("GetGridConfig", "BackOffice")}, + {"serverVarsJs", Url.Action("Application", "BackOffice")}, + //API URLs + { + "embedApiBaseUrl", Url.GetUmbracoApiServiceBaseUrl( + controller => controller.GetEmbed("", 0, 0)) + }, + { + "userApiBaseUrl", Url.GetUmbracoApiServiceBaseUrl( + controller => controller.PostDisableUser(0)) + }, + { + "contentApiBaseUrl", Url.GetUmbracoApiServiceBaseUrl( + controller => controller.PostSave(null)) + }, + { + "mediaApiBaseUrl", Url.GetUmbracoApiServiceBaseUrl( + controller => controller.GetRootMedia()) + }, + { + "imagesApiBaseUrl", Url.GetUmbracoApiServiceBaseUrl( + controller => controller.GetBigThumbnail(0)) + }, + { + "sectionApiBaseUrl", Url.GetUmbracoApiServiceBaseUrl( + controller => controller.GetSections()) + }, + { + "treeApplicationApiBaseUrl", Url.GetUmbracoApiServiceBaseUrl( + controller => controller.GetApplicationTrees(null, null, null)) + }, + { + "contentTypeApiBaseUrl", Url.GetUmbracoApiServiceBaseUrl( + controller => controller.GetAllowedChildren(0)) + }, + { + "mediaTypeApiBaseUrl", Url.GetUmbracoApiServiceBaseUrl( + controller => controller.GetAllowedChildren(0)) + }, + { + "macroApiBaseUrl", Url.GetUmbracoApiServiceBaseUrl( + controller => controller.GetMacroParameters(0)) + }, + { + "authenticationApiBaseUrl", Url.GetUmbracoApiServiceBaseUrl( + controller => controller.PostLogin(null)) + }, + { + "currentUserApiBaseUrl", Url.GetUmbracoApiServiceBaseUrl( + controller => controller.GetMembershipProviderConfig()) + }, + { + "legacyApiBaseUrl", Url.GetUmbracoApiServiceBaseUrl( + controller => controller.DeleteLegacyItem(null, null, null)) + }, + { + "entityApiBaseUrl", Url.GetUmbracoApiServiceBaseUrl( + controller => controller.GetById(0, UmbracoEntityTypes.Media)) + }, + { + "dataTypeApiBaseUrl", Url.GetUmbracoApiServiceBaseUrl( + controller => controller.GetById(0)) + }, + { + "dashboardApiBaseUrl", Url.GetUmbracoApiServiceBaseUrl( + controller => controller.GetDashboard(null)) + }, + { + "logApiBaseUrl", Url.GetUmbracoApiServiceBaseUrl( + controller => controller.GetEntityLog(0)) + }, + { + "memberApiBaseUrl", Url.GetUmbracoApiServiceBaseUrl( + controller => controller.GetByKey(Guid.Empty)) + }, + { + "packageInstallApiBaseUrl", Url.GetUmbracoApiServiceBaseUrl( + controller => controller.Fetch(string.Empty)) + }, + { + "rteApiBaseUrl", Url.GetUmbracoApiServiceBaseUrl( + controller => controller.GetConfiguration()) + }, + { + "stylesheetApiBaseUrl", Url.GetUmbracoApiServiceBaseUrl( + controller => controller.GetAll()) + }, + { + "memberTypeApiBaseUrl", Url.GetUmbracoApiServiceBaseUrl( + controller => controller.GetAllTypes()) + }, + { + "updateCheckApiBaseUrl", Url.GetUmbracoApiServiceBaseUrl( + controller => controller.GetCheck()) + }, + { + "tagApiBaseUrl", Url.GetUmbracoApiServiceBaseUrl( + controller => controller.GetAllTags(null)) + }, + { + "memberTreeBaseUrl", Url.GetUmbracoApiServiceBaseUrl( + controller => controller.GetNodes("-1", null)) + }, + { + "mediaTreeBaseUrl", Url.GetUmbracoApiServiceBaseUrl( + controller => controller.GetNodes("-1", null)) + }, + { + "contentTreeBaseUrl", Url.GetUmbracoApiServiceBaseUrl( + controller => controller.GetNodes("-1", null)) + }, + { + "tagsDataBaseUrl", Url.GetUmbracoApiServiceBaseUrl( + controller => controller.GetTags("")) + }, + { + "examineMgmtBaseUrl", Url.GetUmbracoApiServiceBaseUrl( + controller => controller.GetIndexerDetails()) + }, + { + "xmlDataIntegrityBaseUrl", Url.GetUmbracoApiServiceBaseUrl( + controller => controller.CheckContentXmlTable()) + } + } + }, + { + "umbracoSettings", new Dictionary { - "umbracoSettings", new Dictionary - { - {"umbracoPath", GlobalSettings.Path}, - {"mediaPath", IOHelper.ResolveUrl(SystemDirectories.Media).TrimEnd('/')}, - {"appPluginsPath", IOHelper.ResolveUrl(SystemDirectories.AppPlugins).TrimEnd('/')}, - { - "imageFileTypes", - string.Join(",", UmbracoConfig.For.UmbracoSettings().Content.ImageFileTypes) - }, - {"keepUserLoggedIn", UmbracoConfig.For.UmbracoSettings().Security.KeepUserLoggedIn}, - } - }, + {"umbracoPath", GlobalSettings.Path}, + {"mediaPath", IOHelper.ResolveUrl(SystemDirectories.Media).TrimEnd('/')}, + {"appPluginsPath", IOHelper.ResolveUrl(SystemDirectories.AppPlugins).TrimEnd('/')}, + { + "imageFileTypes", + string.Join(",", UmbracoConfig.For.UmbracoSettings().Content.ImageFileTypes) + }, + {"keepUserLoggedIn", UmbracoConfig.For.UmbracoSettings().Security.KeepUserLoggedIn}, + } + }, + { + "umbracoPlugins", new Dictionary { - "umbracoPlugins", new Dictionary - { - {"trees", GetTreePluginsMetaData()} - } - }, - {"isDebuggingEnabled", HttpContext.IsDebuggingEnabled}, - {"application", GetApplicationState()} - }; + {"trees", GetTreePluginsMetaData()} + } + }, + {"isDebuggingEnabled", HttpContext.IsDebuggingEnabled}, + {"application", GetApplicationState()} + }; + //cache the result if debugging is disabled + var result = HttpContext.IsDebuggingEnabled + ? getResult() + : ApplicationContext.ApplicationCache.RuntimeCache.GetCacheItem>( + typeof(BackOfficeController) + "ServerVariables", + () => getResult(), + new TimeSpan(0, 10, 0)); - return JavaScript(ServerVariablesParser.Parse(d)); + return JavaScript(ServerVariablesParser.Parse(result)); } /// @@ -346,18 +413,30 @@ namespace Umbraco.Web.Editors /// [UmbracoAuthorize(Order = 0)] [MinifyJavaScriptResult(Order = 1)] - [OutputCache(Order = 2, VaryByParam = "none", Location = OutputCacheLocation.Any, Duration = 5000)] public JavaScriptResult LegacyTreeJs() - { - var javascript = new StringBuilder(); - javascript.AppendLine(LegacyTreeJavascript.GetLegacyTreeJavascript()); - javascript.AppendLine(LegacyTreeJavascript.GetLegacyIActionJavascript()); - //add all of the menu blocks - foreach (var file in GetLegacyActionJs(LegacyJsActionType.JsBlock)) + { + Func getResult = () => { - javascript.AppendLine(file); - } - return JavaScript(javascript.ToString()); + var javascript = new StringBuilder(); + javascript.AppendLine(LegacyTreeJavascript.GetLegacyTreeJavascript()); + javascript.AppendLine(LegacyTreeJavascript.GetLegacyIActionJavascript()); + //add all of the menu blocks + foreach (var file in GetLegacyActionJs(LegacyJsActionType.JsBlock)) + { + javascript.AppendLine(file); + } + return javascript.ToString(); + }; + + //cache the result if debugging is disabled + var result = HttpContext.IsDebuggingEnabled + ? getResult() + : ApplicationContext.ApplicationCache.RuntimeCache.GetCacheItem( + typeof(BackOfficeController) + "LegacyTreeJs", + () => getResult(), + new TimeSpan(0, 10, 0)); + + return JavaScript(result); } /// diff --git a/src/Umbraco.Web/Mvc/DisableClientCacheAttribute.cs b/src/Umbraco.Web/Mvc/DisableClientCacheAttribute.cs new file mode 100644 index 0000000000..448e04222c --- /dev/null +++ b/src/Umbraco.Web/Mvc/DisableClientCacheAttribute.cs @@ -0,0 +1,25 @@ +using System; +using System.Web; +using System.Web.Mvc; + +namespace Umbraco.Web.Mvc +{ + /// + /// Will ensure that client-side cache does not occur by sending the correct response headers + /// + public class DisableClientCacheAttribute : ActionFilterAttribute + { + public override void OnResultExecuting(ResultExecutingContext filterContext) + { + if (filterContext.IsChildAction) base.OnResultExecuting(filterContext); + + filterContext.HttpContext.Response.Cache.SetExpires(DateTime.UtcNow.AddDays(-1)); + filterContext.HttpContext.Response.Cache.SetValidUntilExpires(false); + filterContext.HttpContext.Response.Cache.SetRevalidation(HttpCacheRevalidation.AllCaches); + filterContext.HttpContext.Response.Cache.SetCacheability(HttpCacheability.NoCache); + filterContext.HttpContext.Response.Cache.SetNoStore(); + + base.OnResultExecuting(filterContext); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Mvc/MinifyJavaScriptResultAttribute.cs b/src/Umbraco.Web/Mvc/MinifyJavaScriptResultAttribute.cs index 498c6acc76..8281af7412 100644 --- a/src/Umbraco.Web/Mvc/MinifyJavaScriptResultAttribute.cs +++ b/src/Umbraco.Web/Mvc/MinifyJavaScriptResultAttribute.cs @@ -1,5 +1,4 @@ -using System.Web; -using System.Web.Mvc; +using System.Web.Mvc; using System.Web.UI; using ClientDependency.Core; using ClientDependency.Core.CompositeFiles; @@ -7,7 +6,7 @@ using ClientDependency.Core.CompositeFiles; namespace Umbraco.Web.Mvc { /// - /// Minifies and caches the result for the JavaScriptResult + /// Minifies the result for the JavaScriptResult /// /// /// Only minifies in release mode