From 449613de3ea4a043bc620af1d86dce313a063dbf Mon Sep 17 00:00:00 2001 From: "Hendy@Blueberry" Date: Mon, 24 Sep 2012 20:54:26 -0100 Subject: [PATCH 01/22] updated check for uQuery.IGetProperty added XPathCheckBoxList return obj for uQuery .GetProperty("alias") --- .../umbraco/uQuery/ContentExtensions.cs | 2 +- .../umbraco/uQuery/NodeExtensions.cs | 2 +- .../XPathCheckBoxList/XPathCheckBoxList.cs | 23 +++++++++++++++++++ .../umbraco.editorControls.csproj | 1 + 4 files changed, 26 insertions(+), 2 deletions(-) create mode 100644 src/umbraco.editorControls/XPathCheckBoxList/XPathCheckBoxList.cs diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/uQuery/ContentExtensions.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/uQuery/ContentExtensions.cs index 0b1eba0cbc..0ff0dba254 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/uQuery/ContentExtensions.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/uQuery/ContentExtensions.cs @@ -37,7 +37,7 @@ namespace umbraco public static T GetProperty(this Content item, string propertyAlias) { // check to see if return object handles it's own object hydration - if (typeof(T).GetInterface(typeof(uQuery.IGetProperty).Name) != null) + if (typeof(uQuery.IGetProperty).IsAssignableFrom(typeof(T))) { // create new instance of T with empty constructor uQuery.IGetProperty t = (uQuery.IGetProperty)Activator.CreateInstance(); diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/uQuery/NodeExtensions.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/uQuery/NodeExtensions.cs index 2bebaf2fd6..7ba77ac082 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/uQuery/NodeExtensions.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/uQuery/NodeExtensions.cs @@ -271,7 +271,7 @@ namespace umbraco public static T GetProperty(this Node node, string propertyAlias) { // check to see if return object handles it's own object hydration - if (typeof(T).GetInterface(typeof(uQuery.IGetProperty).Name) != null) + if (typeof(uQuery.IGetProperty).IsAssignableFrom(typeof(T))) { // create new instance of T with empty constructor uQuery.IGetProperty t = (uQuery.IGetProperty)Activator.CreateInstance(); diff --git a/src/umbraco.editorControls/XPathCheckBoxList/XPathCheckBoxList.cs b/src/umbraco.editorControls/XPathCheckBoxList/XPathCheckBoxList.cs new file mode 100644 index 0000000000..b15a91eb71 --- /dev/null +++ b/src/umbraco.editorControls/XPathCheckBoxList/XPathCheckBoxList.cs @@ -0,0 +1,23 @@ +using System.Collections.Generic; +using umbraco.NodeFactory; + +namespace umbraco.editorControls.XPathCheckBoxList +{ + public class XPathCheckBoxList : uQuery.IGetProperty + { + public XPathCheckBoxList() + { + } + + void uQuery.IGetProperty.LoadPropertyValue(string value) + { + this.SelectedNodes = uQuery.GetNodesByXml(value); + } + + public IEnumerable SelectedNodes + { + get; + private set; + } + } +} diff --git a/src/umbraco.editorControls/umbraco.editorControls.csproj b/src/umbraco.editorControls/umbraco.editorControls.csproj index 6fd470981f..67ce867795 100644 --- a/src/umbraco.editorControls/umbraco.editorControls.csproj +++ b/src/umbraco.editorControls/umbraco.editorControls.csproj @@ -415,6 +415,7 @@ + From 1bc3943f33962c579be7feaf042bf5e51f9b818d Mon Sep 17 00:00:00 2001 From: Shannon Deminick Date: Tue, 25 Sep 2012 11:06:32 +0700 Subject: [PATCH 02/22] Fixes: #U4-894 - moves the int Priority out of the IThumbnailProvider class as it is only metadata, moved this into a new generic WeightedPluginAttribute which can be used for other resolves/objects that require a weight. --- src/Umbraco.Core/IThumbnailProvider.cs | 1 - .../ManyObjectsResolverBase.cs | 36 ++ .../WeightedPluginAttribute.cs | 19 + src/Umbraco.Core/Umbraco.Core.csproj | 1 + src/Umbraco.Web.UI/Web.config | 503 ++++++++---------- .../web.Template.ShandemVaio.Debug.config | 2 +- .../AbstractThumbnailProvider.cs | 2 - .../FileExtensionIconThumbnailProvider.cs | 7 +- .../ImageThumbnailProvider.cs | 9 +- .../MediaTypeIconThumbnailProvider.cs | 8 +- .../ThumbnailProvidersResolver.cs | 8 +- 11 files changed, 302 insertions(+), 294 deletions(-) create mode 100644 src/Umbraco.Core/ObjectResolution/WeightedPluginAttribute.cs diff --git a/src/Umbraco.Core/IThumbnailProvider.cs b/src/Umbraco.Core/IThumbnailProvider.cs index 0948f1d8eb..c05f50bb62 100644 --- a/src/Umbraco.Core/IThumbnailProvider.cs +++ b/src/Umbraco.Core/IThumbnailProvider.cs @@ -7,7 +7,6 @@ namespace Umbraco.Core { public interface IThumbnailProvider { - int Priority { get; } bool CanProvideThumbnail(string fileUrl); string GetThumbnailUrl(string fileUrl); } diff --git a/src/Umbraco.Core/ObjectResolution/ManyObjectsResolverBase.cs b/src/Umbraco.Core/ObjectResolution/ManyObjectsResolverBase.cs index bab284c7f4..2b8d1c43c7 100644 --- a/src/Umbraco.Core/ObjectResolution/ManyObjectsResolverBase.cs +++ b/src/Umbraco.Core/ObjectResolution/ManyObjectsResolverBase.cs @@ -1,10 +1,12 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Threading; using System.Web; namespace Umbraco.Core.ObjectResolution { + internal abstract class ManyObjectsResolverBase : ResolverBase where TResolved : class where TResolver : class @@ -94,6 +96,40 @@ namespace Umbraco.Core.ObjectResolution /// protected ObjectLifetimeScope LifetimeScope { get; private set; } + private int _defaultPluginWeight = 10; + + /// + /// Used in conjunction with GetSortedValues and WeightedPluginAttribute, if any of the objects + /// being resolved do not contain the WeightedPluginAttribute then this will be the default weight applied + /// to the object. + /// + protected virtual int DefaultPluginWeight + { + get { return _defaultPluginWeight; } + set { _defaultPluginWeight = value; } + } + + /// + /// If a resolver requries that objects are resolved with a specific order using the WeightedPluginAttribute + /// then this method should be used instead of the Values property. + /// + /// + protected IEnumerable GetSortedValues() + { + var vals = Values.ToList(); + //ensure they are sorted + vals.Sort((f1, f2) => + { + Func getWeight = o => + { + var weightAttribute = f1.GetType().GetCustomAttribute(true); + return weightAttribute != null ? weightAttribute.Weight : DefaultPluginWeight; + }; + return getWeight(f1).CompareTo(getWeight(f2)); + }); + return vals; + } + /// /// Returns the list of new object instances. /// diff --git a/src/Umbraco.Core/ObjectResolution/WeightedPluginAttribute.cs b/src/Umbraco.Core/ObjectResolution/WeightedPluginAttribute.cs new file mode 100644 index 0000000000..9ea4fec101 --- /dev/null +++ b/src/Umbraco.Core/ObjectResolution/WeightedPluginAttribute.cs @@ -0,0 +1,19 @@ +using System; + +namespace Umbraco.Core.ObjectResolution +{ + /// + /// Some many object resolvers require that the objects that they resolve have weights applied to them so that + /// the objects are returned in a sorted order, this attribute is used in these scenarios. + /// + [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] + internal class WeightedPluginAttribute : Attribute + { + public WeightedPluginAttribute(int weight) + { + Weight = weight; + } + + public int Weight { get; private set; } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 20bd19baf1..5a362dfaa2 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -90,6 +90,7 @@ + diff --git a/src/Umbraco.Web.UI/Web.config b/src/Umbraco.Web.UI/Web.config index 29683c3913..df98fa6eee 100644 --- a/src/Umbraco.Web.UI/Web.config +++ b/src/Umbraco.Web.UI/Web.config @@ -1,280 +1,249 @@  - -
- -
-
- - -
- - -
-
-
-
- - - -
-
- - - - - - - - - - - + +
+
+
+ +
+ +
+
+
+
+ + +
+
+ + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Umbraco.Web.UI/web.Template.ShandemVaio.Debug.config b/src/Umbraco.Web.UI/web.Template.ShandemVaio.Debug.config index 0235436f34..002a7db03c 100644 --- a/src/Umbraco.Web.UI/web.Template.ShandemVaio.Debug.config +++ b/src/Umbraco.Web.UI/web.Template.ShandemVaio.Debug.config @@ -19,7 +19,7 @@ + value="4.10.0"/> SupportedExtensions { get; } public bool CanProvideThumbnail(string fileUrl) diff --git a/src/Umbraco.Web/Media/ThumbnailProviders/FileExtensionIconThumbnailProvider.cs b/src/Umbraco.Web/Media/ThumbnailProviders/FileExtensionIconThumbnailProvider.cs index 60a5f2ccfa..a96eea0475 100644 --- a/src/Umbraco.Web/Media/ThumbnailProviders/FileExtensionIconThumbnailProvider.cs +++ b/src/Umbraco.Web/Media/ThumbnailProviders/FileExtensionIconThumbnailProvider.cs @@ -3,17 +3,14 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; +using Umbraco.Core.ObjectResolution; using umbraco.IO; namespace Umbraco.Web.Media.ThumbnailProviders { + [WeightedPlugin(2000)] public class FileExtensionIconThumbnailProvider : AbstractThumbnailProvider { - public override int Priority - { - get { return 2000; } - } - protected override IEnumerable SupportedExtensions { get { return new List { "*" }; } diff --git a/src/Umbraco.Web/Media/ThumbnailProviders/ImageThumbnailProvider.cs b/src/Umbraco.Web/Media/ThumbnailProviders/ImageThumbnailProvider.cs index c5c20a24d7..b78b199054 100644 --- a/src/Umbraco.Web/Media/ThumbnailProviders/ImageThumbnailProvider.cs +++ b/src/Umbraco.Web/Media/ThumbnailProviders/ImageThumbnailProvider.cs @@ -5,17 +5,14 @@ using System.Linq; using System.Text; using Umbraco.Core; using Umbraco.Core.IO; +using Umbraco.Core.ObjectResolution; using umbraco.IO; namespace Umbraco.Web.Media.ThumbnailProviders { + [WeightedPlugin(1000)] public class ImageThumbnailProvider : AbstractThumbnailProvider - { - public override int Priority - { - get { return 1000; } - } - + { protected override IEnumerable SupportedExtensions { get { return new List { ".jpeg", ".jpg", ".gif", ".bmp", ".png", ".tiff", ".tif" }; } diff --git a/src/Umbraco.Web/Media/ThumbnailProviders/MediaTypeIconThumbnailProvider.cs b/src/Umbraco.Web/Media/ThumbnailProviders/MediaTypeIconThumbnailProvider.cs index e4c8b569d7..ce6ee054a4 100644 --- a/src/Umbraco.Web/Media/ThumbnailProviders/MediaTypeIconThumbnailProvider.cs +++ b/src/Umbraco.Web/Media/ThumbnailProviders/MediaTypeIconThumbnailProvider.cs @@ -3,17 +3,15 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; +using Umbraco.Core.ObjectResolution; using umbraco.IO; namespace Umbraco.Web.Media.ThumbnailProviders { + [WeightedPlugin(3000)] public class MediaTypeIconThumbnailProvider : AbstractThumbnailProvider { - public override int Priority - { - get { return 3000; } - } - + protected override IEnumerable SupportedExtensions { get { return new List { "*" }; } diff --git a/src/Umbraco.Web/Media/ThumbnailProviders/ThumbnailProvidersResolver.cs b/src/Umbraco.Web/Media/ThumbnailProviders/ThumbnailProvidersResolver.cs index b6e15324d1..52c8d4ac66 100644 --- a/src/Umbraco.Web/Media/ThumbnailProviders/ThumbnailProvidersResolver.cs +++ b/src/Umbraco.Web/Media/ThumbnailProviders/ThumbnailProvidersResolver.cs @@ -29,13 +29,7 @@ namespace Umbraco.Web.Media.ThumbnailProviders /// public IEnumerable Providers { - get - { - var vals = Values.ToList(); - //ensure they are sorted - vals.Sort((f1, f2) => f1.Priority.CompareTo(f2.Priority)); - return vals; - } + get { return GetSortedValues(); } } public string GetThumbnailUrl(string fileUrl) From 083b18139a21e44d97586ef277ee6c7e91e86c32 Mon Sep 17 00:00:00 2001 From: Shannon Deminick Date: Tue, 25 Sep 2012 11:20:05 +0700 Subject: [PATCH 03/22] Adds code to RenderViewEngine to ensure the /Views folder exists and also that the razor web.config file exists there too. I've also added this web.config file to the sln so it should output in the build by default though it will still be created automatically if its not there. --- src/Umbraco.Web.UI/Umbraco.Web.UI.csproj | 2 +- src/Umbraco.Web.UI/Web.config | 503 ++++++++++++----------- src/Umbraco.Web/Mvc/RenderViewEngine.cs | 21 + src/Umbraco.Web/Mvc/Strings.Designer.cs | 78 ++++ src/Umbraco.Web/Mvc/Strings.resx | 124 ++++++ src/Umbraco.Web/Mvc/web.config.template | 58 +++ src/Umbraco.Web/Umbraco.Web.csproj | 10 + 7 files changed, 559 insertions(+), 237 deletions(-) create mode 100644 src/Umbraco.Web/Mvc/Strings.Designer.cs create mode 100644 src/Umbraco.Web/Mvc/Strings.resx create mode 100644 src/Umbraco.Web/Mvc/web.config.template diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index 96bbeb685e..5e840610a5 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -1662,6 +1662,7 @@ + Web.Template.config Designer @@ -2195,7 +2196,6 @@ - diff --git a/src/Umbraco.Web.UI/Web.config b/src/Umbraco.Web.UI/Web.config index df98fa6eee..a860662208 100644 --- a/src/Umbraco.Web.UI/Web.config +++ b/src/Umbraco.Web.UI/Web.config @@ -1,249 +1,280 @@  - -
-
-
- -
- -
-
-
-
- - -
-
- - - - - - - - - + +
+ +
+
+ + +
+ + +
+
+
+
+ + + +
+
+ + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Umbraco.Web/Mvc/RenderViewEngine.cs b/src/Umbraco.Web/Mvc/RenderViewEngine.cs index 53de5e7180..2ebca40323 100644 --- a/src/Umbraco.Web/Mvc/RenderViewEngine.cs +++ b/src/Umbraco.Web/Mvc/RenderViewEngine.cs @@ -1,7 +1,9 @@ using System.Collections.Generic; +using System.IO; using System.Linq; using System.Web.Mvc; using Umbraco.Core; +using Umbraco.Core.IO; namespace Umbraco.Web.Mvc { @@ -31,6 +33,25 @@ namespace Umbraco.Web.Mvc AreaPartialViewLocationFormats = new string[] { }; AreaViewLocationFormats = new string[] { }; + + EnsureFolderAndWebConfig(); + } + + /// + /// Ensures that the correct web.config for razor exists in the /Views folder. + /// + private void EnsureFolderAndWebConfig() + { + var viewFolder = IOHelper.MapPath(Constants.ViewLocation); + //ensure the web.config file is in the ~/Views folder + Directory.CreateDirectory(viewFolder); + if (!File.Exists(Path.Combine(viewFolder, "web.config"))) + { + using (var writer = File.CreateText(Path.Combine(viewFolder, "web.config"))) + { + writer.Write(Strings.web_config); + } + } } public override ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName, bool useCache) diff --git a/src/Umbraco.Web/Mvc/Strings.Designer.cs b/src/Umbraco.Web/Mvc/Strings.Designer.cs new file mode 100644 index 0000000000..4848bc4806 --- /dev/null +++ b/src/Umbraco.Web/Mvc/Strings.Designer.cs @@ -0,0 +1,78 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.544 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Umbraco.Web.Mvc { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Strings { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Strings() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Umbraco.Web.Mvc.Strings", typeof(Strings).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to <?xml version="1.0"?> + /// + ///<configuration> + /// <configSections> + /// <sectionGroup name="system.web.webPages.razor" type="System.Web.WebPages.Razor.Configuration.RazorWebSectionGroup, System.Web.WebPages.Razor, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"> + /// <section name="host" type="System.Web.WebPages.Razor.Configuration.HostSection, System.Web.WebPages.Razor, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" requirePermission="false" /> + /// <section name="page [rest of string was truncated]";. + /// + internal static string web_config { + get { + return ResourceManager.GetString("web_config", resourceCulture); + } + } + } +} diff --git a/src/Umbraco.Web/Mvc/Strings.resx b/src/Umbraco.Web/Mvc/Strings.resx new file mode 100644 index 0000000000..270e6fc616 --- /dev/null +++ b/src/Umbraco.Web/Mvc/Strings.resx @@ -0,0 +1,124 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + web.config.template;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;utf-8 + + \ No newline at end of file diff --git a/src/Umbraco.Web/Mvc/web.config.template b/src/Umbraco.Web/Mvc/web.config.template new file mode 100644 index 0000000000..4c30ef2275 --- /dev/null +++ b/src/Umbraco.Web/Mvc/web.config.template @@ -0,0 +1,58 @@ + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 5c54122f58..7ce6953df8 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -271,6 +271,11 @@ + + True + True + Strings.resx + @@ -1959,6 +1964,7 @@ + Reference.map @@ -2096,6 +2102,10 @@ MSDiscoCodeGenerator Reference.cs + + ResXFileCodeGenerator + Strings.Designer.cs + database.ascx.cs Designer From 0b7f04ab37d1ee54617dc74eeb84fff68b867a89 Mon Sep 17 00:00:00 2001 From: Shannon Deminick Date: Tue, 25 Sep 2012 13:09:59 +0700 Subject: [PATCH 04/22] Added all code to support auto-routing surface controllers and started implementing the code for BeginUmbracoForm. --- src/Umbraco.Core/ActivatorHelper.cs | 20 +++ .../Configuration/GlobalSettings.cs | 2 +- src/Umbraco.Core/Mandate.cs | 107 ++++++++++++++++ src/Umbraco.Core/Umbraco.Core.csproj | 2 + src/Umbraco.Web/AreaRegistrationExtensions.cs | 87 +++++++++++++ src/Umbraco.Web/HtmlHelperRenderExtensions.cs | 2 +- .../MergeModelStateToChildActionAttribute.cs | 42 +++++++ src/Umbraco.Web/Mvc/PostedDataProxyInfo.cs | 13 ++ .../Mvc/RedirectToUmbracoPageResult.cs | 111 +++++++++++++++++ .../Mvc/RenderControllerFactory.cs | 19 ++- src/Umbraco.Web/Mvc/RenderRouteHandler.cs | 110 +++++++++++++++++ src/Umbraco.Web/Mvc/RouteDefinition.cs | 3 +- src/Umbraco.Web/Mvc/SurfaceController.cs | 115 ++++++++++++++++++ src/Umbraco.Web/Mvc/SurfaceControllerArea.cs | 63 ++++++++++ .../Mvc/SurfaceControllerAttribute.cs | 18 +++ .../Mvc/SurfaceControllerFactory.cs | 67 ++++++++++ .../Mvc/SurfaceControllerMetadata.cs | 15 +++ .../Mvc/SurfaceControllerResolver.cs | 23 ++++ src/Umbraco.Web/Mvc/UmbracoPageResult.cs | 50 ++++++++ src/Umbraco.Web/PluginManagerExtensions.cs | 7 +- src/Umbraco.Web/RouteCollectionExtensions.cs | 89 ++++++++++++++ src/Umbraco.Web/Routing/NiceUrlProvider.cs | 9 +- src/Umbraco.Web/Umbraco.Web.csproj | 12 ++ src/Umbraco.Web/UmbracoContext.cs | 5 + src/Umbraco.Web/WebBootManager.cs | 24 +++- .../umbraco.MacroEngines.csproj | 1 + 26 files changed, 1001 insertions(+), 15 deletions(-) create mode 100644 src/Umbraco.Core/ActivatorHelper.cs create mode 100644 src/Umbraco.Core/Mandate.cs create mode 100644 src/Umbraco.Web/AreaRegistrationExtensions.cs create mode 100644 src/Umbraco.Web/Mvc/MergeModelStateToChildActionAttribute.cs create mode 100644 src/Umbraco.Web/Mvc/PostedDataProxyInfo.cs create mode 100644 src/Umbraco.Web/Mvc/RedirectToUmbracoPageResult.cs create mode 100644 src/Umbraco.Web/Mvc/SurfaceController.cs create mode 100644 src/Umbraco.Web/Mvc/SurfaceControllerArea.cs create mode 100644 src/Umbraco.Web/Mvc/SurfaceControllerAttribute.cs create mode 100644 src/Umbraco.Web/Mvc/SurfaceControllerFactory.cs create mode 100644 src/Umbraco.Web/Mvc/SurfaceControllerMetadata.cs create mode 100644 src/Umbraco.Web/Mvc/SurfaceControllerResolver.cs create mode 100644 src/Umbraco.Web/Mvc/UmbracoPageResult.cs create mode 100644 src/Umbraco.Web/RouteCollectionExtensions.cs diff --git a/src/Umbraco.Core/ActivatorHelper.cs b/src/Umbraco.Core/ActivatorHelper.cs new file mode 100644 index 0000000000..88ec01ae35 --- /dev/null +++ b/src/Umbraco.Core/ActivatorHelper.cs @@ -0,0 +1,20 @@ +using System; + +namespace Umbraco.Core +{ + /// + /// Helper methods for Activation + /// + internal static class ActivatorHelper + { + /// + /// Creates an instance of a type using that type's default constructor. + /// + /// + /// + public static T CreateInstance() where T : class, new() + { + return Activator.CreateInstance(typeof(T)) as T; + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Configuration/GlobalSettings.cs b/src/Umbraco.Core/Configuration/GlobalSettings.cs index f330049fb7..424e7b2f24 100644 --- a/src/Umbraco.Core/Configuration/GlobalSettings.cs +++ b/src/Umbraco.Core/Configuration/GlobalSettings.cs @@ -115,7 +115,7 @@ namespace Umbraco.Core.Configuration /// We will use the 'Path' (default ~/umbraco) to create it but since it cannot contain '/' and people may specify a path of ~/asdf/asdf/admin /// we will convert the '/' to '-' and use that as the path. its a bit lame but will work. /// - internal static string MvcArea + internal static string UmbracoMvcArea { get { diff --git a/src/Umbraco.Core/Mandate.cs b/src/Umbraco.Core/Mandate.cs new file mode 100644 index 0000000000..54d589b90a --- /dev/null +++ b/src/Umbraco.Core/Mandate.cs @@ -0,0 +1,107 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Umbraco.Core +{ + /// + /// Helper class for mandating values, for example on method parameters. + /// + internal static class Mandate + { + /// + /// Mandates that the specified parameter is not null. + /// + /// The value. + /// Name of the param. + /// If is null. + public static void ParameterNotNull(T value, string paramName) where T : class + { + That(value != null, () => new ArgumentNullException(paramName)); + } + + + /// + /// Mandates that the specified parameter is not null. + /// + /// The value. + /// Name of the param. + /// If is null or whitespace. + public static void ParameterNotNullOrEmpty(string value, string paramName) + { + That(!string.IsNullOrWhiteSpace(value), () => new ArgumentNullException(paramName)); + } + + /// + /// Mandates that the specified sequence is not null and has at least one element. + /// + /// + /// The sequence. + /// Name of the param. + public static void ParameterNotNullOrEmpty(IEnumerable sequence, string paramName) + { + ParameterNotNull(sequence, paramName); + ParameterCondition(sequence.Any(), paramName); + } + + + /// + /// Mandates that the specified parameter matches the condition. + /// + /// The condition to check. + /// Name of the param. + /// If the condition is false. + public static void ParameterCondition(bool condition, string paramName) + { + ParameterCondition(condition, paramName, (string)null); + } + + /// + /// Mandates that the specified parameter matches the condition. + /// + /// The condition to check. + /// Name of the param. + /// The message. + /// If the condition is false. + public static void ParameterCondition(bool condition, string paramName, string message) + { + // Warning: don't make this method have an optional message parameter (removing the other ParameterCondition overload) as it will + // make binaries compiled against previous Framework libs incompatible unneccesarily + message = message ?? "A parameter passed into a method was not a valid value"; + That(condition, () => new ArgumentException(message, paramName)); + } + + /// + /// Mandates that the specified condition is true, otherwise throws an exception specified in . + /// + /// The type of the exception. + /// if set to true, throws exception . + /// An exception of type is raised if the condition is false. + public static void That(bool condition) where TException : Exception, new() + { + if (!condition) + throw ActivatorHelper.CreateInstance(); + } + + /// + /// Mandates that the specified condition is true, otherwise throws an exception specified in . + /// + /// The type of the exception. + /// if set to true, throws exception . + /// Deffered expression to call if the exception should be raised. + /// An exception of type is raised if the condition is false. + public static void That(bool condition, Func defer) where TException : Exception, new() + { + if (!condition) + { + throw defer.Invoke(); + } + + // Here is an example of how this method is actually called + //object myParam = null; + //Mandate.That(myParam != null, + // textManager => new ArgumentNullException(textManager.Get("blah", new {User = "blah"}))); + } + } +} diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 5a362dfaa2..59142f2652 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -49,6 +49,7 @@ Properties\SolutionInfo.cs + @@ -89,6 +90,7 @@ + diff --git a/src/Umbraco.Web/AreaRegistrationExtensions.cs b/src/Umbraco.Web/AreaRegistrationExtensions.cs new file mode 100644 index 0000000000..ccdf7d6395 --- /dev/null +++ b/src/Umbraco.Web/AreaRegistrationExtensions.cs @@ -0,0 +1,87 @@ +using System; +using System.Collections.Generic; +using System.Text.RegularExpressions; +using System.Web.Mvc; +using System.Web.Routing; +using Umbraco.Core; +using Umbraco.Core.Configuration; + +namespace Umbraco.Web +{ + internal static class AreaRegistrationExtensions + { + /// + /// Creates a custom individual route for the specified controller plugin. Individual routes + /// are required by controller plugins to map to a unique URL based on ID. + /// + /// An PluginAttribute + /// + /// + /// An existing route collection + /// the data token name for the controller plugin + /// + /// The suffix name that the controller name must end in before the "Controller" string for example: + /// ContentTreeController has a controllerSuffixName of "Tree" + /// + /// + /// The base name of the URL to create for example: Umbraco/[PackageName]/Trees/ContentTree/1 has a baseUrlPath of "Trees" + /// + /// + /// + /// + /// + /// The DataToken value to set for the 'umbraco' key, this defaults to 'backoffice' + internal static void RouteControllerPlugin(this AreaRegistration area, Guid controllerId, string controllerName, Type controllerType, RouteCollection routes, + string routeIdParameterName, string controllerSuffixName, string baseUrlPath, string defaultAction, object defaultId, + string umbracoTokenValue = "backoffice") + { + Mandate.ParameterNotNullOrEmpty(controllerName, "controllerName"); + Mandate.ParameterNotNullOrEmpty(routeIdParameterName, "routeIdParameterName"); + Mandate.ParameterNotNullOrEmpty(controllerSuffixName, "controllerSuffixName"); + Mandate.ParameterNotNullOrEmpty(defaultAction, "defaultAction"); + Mandate.ParameterNotNull(controllerType, "controllerType"); + Mandate.ParameterNotNull(routes, "routes"); + Mandate.ParameterNotNull(defaultId, "defaultId"); + + var umbracoArea = GlobalSettings.UmbracoMvcArea; + + //routes are explicitly name with controller names. + var url = baseUrlPath.IsNullOrWhiteSpace() + ? umbracoArea + "/" + area.AreaName + "/" + controllerName + "/{action}/{id}" + : umbracoArea + "/" + area.AreaName + "/" + baseUrlPath + "/" + controllerName + "/{action}/{id}"; + + //create a new route with custom name, specified url, and the namespace of the controller plugin + var controllerPluginRoute = routes.MapRoute( + //name + string.Format("umbraco-{0}-{1}", controllerName, controllerId), + //url format + url, + //set the namespace of the controller to match + new[] { controllerType.Namespace }); + + //set defaults + controllerPluginRoute.Defaults = new RouteValueDictionary( + new Dictionary + { + { "controller", controllerName }, + { routeIdParameterName, controllerId.ToString("N") }, + { "action", defaultAction }, + { "id", defaultId } + }); + + //constraints: only match controllers ending with 'controllerSuffixName' and only match this controller's ID for this route + controllerPluginRoute.Constraints = new RouteValueDictionary( + new Dictionary + { + { "controller", @"(\w+)" + controllerSuffixName }, + { routeIdParameterName, Regex.Escape(controllerId.ToString("N")) } + }); + + + //match this area + controllerPluginRoute.DataTokens.Add("area", area.AreaName); + controllerPluginRoute.DataTokens.Add("umbraco", umbracoTokenValue); //ensure the umbraco token is set + + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/HtmlHelperRenderExtensions.cs b/src/Umbraco.Web/HtmlHelperRenderExtensions.cs index 6590854ad1..ff17d1a7b5 100644 --- a/src/Umbraco.Web/HtmlHelperRenderExtensions.cs +++ b/src/Umbraco.Web/HtmlHelperRenderExtensions.cs @@ -167,7 +167,7 @@ namespace Umbraco.Web IDictionary htmlAttributes) { var settings = DependencyResolver.Current.GetService(); - var area = Umbraco.Core.Configuration.GlobalSettings.MvcArea; + var area = Umbraco.Core.Configuration.GlobalSettings.UmbracoMvcArea; var formAction = html.ViewContext.HttpContext.Request.Url.AbsolutePath; return html.RenderForm(formAction, FormMethod.Post, htmlAttributes, controllerName, action, area, null); diff --git a/src/Umbraco.Web/Mvc/MergeModelStateToChildActionAttribute.cs b/src/Umbraco.Web/Mvc/MergeModelStateToChildActionAttribute.cs new file mode 100644 index 0000000000..7d2df43962 --- /dev/null +++ b/src/Umbraco.Web/Mvc/MergeModelStateToChildActionAttribute.cs @@ -0,0 +1,42 @@ +using System.Linq; +using System.Web.Mvc; + +namespace Umbraco.Web.Mvc +{ + /// + /// When a ChildAction is executing and we want the ModelState from the Parent context to be merged in + /// to help with validation, this filter can be used. + /// + /// + /// By default, this filter will only merge when an Http POST is detected but this can be modified in the ctor + /// + public class MergeModelStateToChildActionAttribute : ActionFilterAttribute + { + private readonly string[] _verb; + + public MergeModelStateToChildActionAttribute() + : this(HttpVerbs.Post) + { + + } + + public MergeModelStateToChildActionAttribute(params HttpVerbs[] verb) + { + _verb = verb.Select(x => x.ToString().ToUpper()).ToArray(); + } + + public override void OnActionExecuting(ActionExecutingContext filterContext) + { + //check if the verb matches, if so merge the ModelState before the action is executed. + if (_verb.Contains(filterContext.HttpContext.Request.HttpMethod)) + { + if (filterContext.Controller.ControllerContext.IsChildAction) + { + filterContext.Controller.ViewData.ModelState.Merge( + filterContext.Controller.ControllerContext.ParentActionViewContext.ViewData.ModelState); + } + } + base.OnActionExecuting(filterContext); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Mvc/PostedDataProxyInfo.cs b/src/Umbraco.Web/Mvc/PostedDataProxyInfo.cs new file mode 100644 index 0000000000..eb664fba0c --- /dev/null +++ b/src/Umbraco.Web/Mvc/PostedDataProxyInfo.cs @@ -0,0 +1,13 @@ +using System; + +namespace Umbraco.Web.Mvc +{ + /// + /// Represents the data required to proxy a request to a surface controller for posted data + /// + internal class PostedDataProxyInfo : RouteDefinition + { + public string Area { get; set; } + public Guid SurfaceId { get; set; } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Mvc/RedirectToUmbracoPageResult.cs b/src/Umbraco.Web/Mvc/RedirectToUmbracoPageResult.cs new file mode 100644 index 0000000000..9f74385458 --- /dev/null +++ b/src/Umbraco.Web/Mvc/RedirectToUmbracoPageResult.cs @@ -0,0 +1,111 @@ +using System; +using System.Web.Mvc; +using Umbraco.Core; +using Umbraco.Core.Models; +using Umbraco.Web.Routing; + +namespace Umbraco.Web.Mvc +{ + /// + /// Redirects to an Umbraco page by Id or Entity + /// + public class RedirectToUmbracoPageResult : ActionResult + { + private IDocument _document; + private readonly int _pageId; + private readonly UmbracoContext _umbracoContext; + private string _url; + public string Url + { + get + { + if (!_url.IsNullOrWhiteSpace()) return _url; + + if (Document == null) + { + throw new InvalidOperationException("Cannot redirect, no entity was found for id " + _pageId); + } + + var result = _umbracoContext.RoutingContext.NiceUrlProvider.GetNiceUrl(Document.Id); + if (result != NiceUrlProvider.NullUrl) + { + _url = result; + return _url; + } + + throw new InvalidOperationException("Could not route to entity with id " + _pageId + ", the NiceUrlProvider could not generate a URL"); + + } + } + + public IDocument Document + { + get + { + if (_document != null) return _document; + + //need to get the URL for the page + _document = PublishedContentStoreResolver.Current.PublishedContentStore.GetDocumentById(_umbracoContext, _pageId); + + return _document; + } + } + + /// + /// Creates a new RedirectToUmbracoResult + /// + /// + public RedirectToUmbracoPageResult(int pageId) + : this(pageId, UmbracoContext.Current) + { + } + + /// + /// Creates a new RedirectToUmbracoResult + /// + /// + public RedirectToUmbracoPageResult(IDocument document) + : this(document, UmbracoContext.Current) + { + } + + /// + /// Creates a new RedirectToUmbracoResult + /// + /// + /// + public RedirectToUmbracoPageResult(IDocument document, UmbracoContext umbracoContext) + { + _document = document; + _pageId = document.Id; + _umbracoContext = umbracoContext; + } + + /// + /// Creates a new RedirectToUmbracoResult + /// + /// + /// + public RedirectToUmbracoPageResult(int pageId, UmbracoContext umbracoContext) + { + _pageId = pageId; + _umbracoContext = umbracoContext; + } + + public override void ExecuteResult(ControllerContext context) + { + if (context == null) throw new ArgumentNullException("context"); + + if (context.IsChildAction) + { + throw new InvalidOperationException("Cannot redirect from a Child Action"); + } + + var destinationUrl = UrlHelper.GenerateContentUrl(Url, context.HttpContext); + context.Controller.TempData.Keep(); + + context.HttpContext.Response.Redirect(destinationUrl, endResponse: false); + } + + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Mvc/RenderControllerFactory.cs b/src/Umbraco.Web/Mvc/RenderControllerFactory.cs index 12b34f2e45..2934bc7b56 100644 --- a/src/Umbraco.Web/Mvc/RenderControllerFactory.cs +++ b/src/Umbraco.Web/Mvc/RenderControllerFactory.cs @@ -29,12 +29,23 @@ namespace Umbraco.Web.Mvc /// The request. /// true if this instance can handle the specified request; otherwise, false. /// - public bool CanHandle(RequestContext request) + public virtual bool CanHandle(RequestContext request) { var dataToken = request.RouteData.DataTokens["area"]; return dataToken == null || string.IsNullOrWhiteSpace(dataToken.ToString()); } + /// + /// Returns the controller type for the controller name otherwise null if not found + /// + /// + /// + /// + protected Type GetControllerType(RequestContext requestContext, string controllerName) + { + return _innerFactory.GetControllerType(requestContext, controllerName); + } + /// /// Creates the specified controller by using the specified request context. /// @@ -42,9 +53,9 @@ namespace Umbraco.Web.Mvc /// The controller. /// /// The request context.The name of the controller. - public IController CreateController(RequestContext requestContext, string controllerName) + public virtual IController CreateController(RequestContext requestContext, string controllerName) { - Type controllerType = _innerFactory.GetControllerType(requestContext, controllerName) ?? + Type controllerType = GetControllerType(requestContext, controllerName) ?? _innerFactory.GetControllerType(requestContext, ControllerExtensions.GetControllerName(typeof(RenderMvcController))); return _innerFactory.GetControllerInstance(requestContext, controllerType); @@ -77,7 +88,7 @@ namespace Umbraco.Web.Mvc /// this nested class changes the visibility of 's internal methods in order to not have to rely on a try-catch. /// /// - public class OverridenDefaultControllerFactory : DefaultControllerFactory + internal class OverridenDefaultControllerFactory : DefaultControllerFactory { public new IController GetControllerInstance(RequestContext requestContext, Type controllerType) { diff --git a/src/Umbraco.Web/Mvc/RenderRouteHandler.cs b/src/Umbraco.Web/Mvc/RenderRouteHandler.cs index a637d236d1..e2f5603b95 100644 --- a/src/Umbraco.Web/Mvc/RenderRouteHandler.cs +++ b/src/Umbraco.Web/Mvc/RenderRouteHandler.cs @@ -1,9 +1,12 @@ using System; +using System.Linq; +using System.Text; using System.Web; using System.Web.Mvc; using System.Web.Routing; using Umbraco.Core; using Umbraco.Core.Logging; +using Umbraco.Core.Models; using Umbraco.Web.Routing; using umbraco.cms.businesslogic.template; @@ -55,6 +58,113 @@ namespace Umbraco.Web.Mvc #endregion + /// + /// Checks the request and query strings to see if it matches the definition of having a Surface controller + /// posted value, if so, then we return a PostedDataProxyInfo object with the correct information. + /// + /// + /// + private static PostedDataProxyInfo GetPostedFormInfo(RequestContext requestContext) + { + if (requestContext.HttpContext.Request.RequestType != "POST") + return null; + + //this field will contain a base64 encoded version of the surface route vals + if (requestContext.HttpContext.Request["uformpostroutevals"].IsNullOrWhiteSpace()) + return null; + + var encodedVal = requestContext.HttpContext.Request["uformpostroutevals"]; + var decodedString = Encoding.UTF8.GetString(Convert.FromBase64String(encodedVal)); + //the value is formatted as query strings + var decodedParts = decodedString.Split('&').Select(x => new { Key = x.Split('=')[0], Value = x.Split('=')[1] }).ToArray(); + + //validate all required keys exist + + //the controller + if (!decodedParts.Any(x => x.Key == "c")) + return null; + //the action + if (!decodedParts.Any(x => x.Key == "a")) + return null; + //the area + if (!decodedParts.Any(x => x.Key == "ar")) + return null; + + //the surface id + if (decodedParts.Any(x => x.Key == "i")) + { + Guid id; + if (Guid.TryParse(decodedParts.Single(x => x.Key == "i").Value, out id)) + { + return new PostedDataProxyInfo + { + ControllerName = requestContext.HttpContext.Server.UrlDecode(decodedParts.Single(x => x.Key == "c").Value), + ActionName = requestContext.HttpContext.Server.UrlDecode(decodedParts.Single(x => x.Key == "a").Value), + Area = requestContext.HttpContext.Server.UrlDecode(decodedParts.Single(x => x.Key == "ar").Value), + SurfaceId = id, + }; + } + } + + //return the proxy info without the surface id... could be a local controller. + return new PostedDataProxyInfo + { + ControllerName = requestContext.HttpContext.Server.UrlDecode(decodedParts.Single(x => x.Key == "c").Value), + ActionName = requestContext.HttpContext.Server.UrlDecode(decodedParts.Single(x => x.Key == "a").Value), + Area = requestContext.HttpContext.Server.UrlDecode(decodedParts.Single(x => x.Key == "ar").Value), + }; + + } + + /// + /// Handles a posted form to an Umbraco Url and ensures the correct controller is routed to and that + /// the right DataTokens are set. + /// + /// + /// + /// + /// The original route definition that would normally be used to route if it were not a POST + private IHttpHandler HandlePostedValues(RequestContext requestContext, PostedDataProxyInfo postedInfo, IDocument document, RouteDefinition routeDefinition) + { + + //set the standard route values/tokens + requestContext.RouteData.Values["controller"] = postedInfo.ControllerName; + requestContext.RouteData.Values["action"] = postedInfo.ActionName; + requestContext.RouteData.DataTokens["area"] = postedInfo.Area; + + IHttpHandler handler = new MvcHandler(requestContext); + + //ensure the surface id is set if found, meaning it is a plugin, not locally declared + if (postedInfo.SurfaceId != default(Guid)) + { + requestContext.RouteData.Values["surfaceId"] = postedInfo.SurfaceId.ToString("N"); + //find the other data tokens for this route and merge... things like Namespace will be included here + using (RouteTable.Routes.GetReadLock()) + { + var surfaceRoute = RouteTable.Routes.OfType() + .Where(x => x.Defaults != null && x.Defaults.ContainsKey("surfaceId") && + x.Defaults["surfaceId"].ToString() == postedInfo.SurfaceId.ToString("N")) + .SingleOrDefault(); + if (surfaceRoute == null) + throw new InvalidOperationException("Could not find a Surface controller route in the RouteTable for id " + postedInfo.SurfaceId); + //set the 'Namespaces' token so the controller factory knows where to look to construct it + if (surfaceRoute.DataTokens.ContainsKey("Namespaces")) + { + requestContext.RouteData.DataTokens["Namespaces"] = surfaceRoute.DataTokens["Namespaces"]; + } + handler = surfaceRoute.RouteHandler.GetHttpHandler(requestContext); + } + + } + + //store the original URL this came in on + requestContext.RouteData.DataTokens["umbraco-item-url"] = requestContext.HttpContext.Request.Url.AbsolutePath; + //store the original route definition + requestContext.RouteData.DataTokens["umbraco-route-def"] = routeDefinition; + + return handler; + } + /// /// Returns a RouteDefinition object based on the current renderModel /// diff --git a/src/Umbraco.Web/Mvc/RouteDefinition.cs b/src/Umbraco.Web/Mvc/RouteDefinition.cs index a157c034e6..997815db0b 100644 --- a/src/Umbraco.Web/Mvc/RouteDefinition.cs +++ b/src/Umbraco.Web/Mvc/RouteDefinition.cs @@ -1,4 +1,5 @@ using System.Web.Mvc; +using Umbraco.Web.Routing; namespace Umbraco.Web.Mvc { @@ -18,7 +19,7 @@ namespace Umbraco.Web.Mvc /// /// The current RenderModel found for the request /// - public object DocumentRequest { get; set; } + public DocumentRequest DocumentRequest { get; set; } /// /// Gets/sets whether the current request has a hijacked route/user controller routed for it diff --git a/src/Umbraco.Web/Mvc/SurfaceController.cs b/src/Umbraco.Web/Mvc/SurfaceController.cs new file mode 100644 index 0000000000..5b7c38a6ce --- /dev/null +++ b/src/Umbraco.Web/Mvc/SurfaceController.cs @@ -0,0 +1,115 @@ +using System; +using System.Collections.Concurrent; +using System.Web.Mvc; +using Umbraco.Core.Models; +using Umbraco.Core; + +namespace Umbraco.Web.Mvc +{ + /// + /// The base controller that all Presentation Add-in controllers should inherit from + /// + [MergeModelStateToChildAction] + public abstract class SurfaceController : Controller, IRequiresUmbracoContext + { + ///// + ///// stores the metadata about surface controllers + ///// + //private static ConcurrentDictionary _metadata = new ConcurrentDictionary(); + + public UmbracoContext UmbracoContext { get; set; } + + /// + /// Useful for debugging + /// + internal Guid InstanceId { get; private set; } + + /// + /// Default constructor + /// + /// + protected SurfaceController(UmbracoContext umbracoContext) + { + UmbracoContext = umbracoContext; + InstanceId = Guid.NewGuid(); + } + + /// + /// Empty constructor, uses Singleton to resolve the UmbracoContext + /// + protected SurfaceController() + { + UmbracoContext = UmbracoContext.Current; + InstanceId = Guid.NewGuid(); + } + + /// + /// Redirects to the Umbraco page with the given id + /// + /// + /// + protected RedirectToUmbracoPageResult RedirectToUmbracoPage(int pageId) + { + return new RedirectToUmbracoPageResult(pageId, UmbracoContext); + } + + /// + /// Redirects to the Umbraco page with the given id + /// + /// + /// + protected RedirectToUmbracoPageResult RedirectToUmbracoPage(IDocument pageDocument) + { + return new RedirectToUmbracoPageResult(pageDocument, UmbracoContext); + } + + /// + /// Redirects to the currently rendered Umbraco page + /// + /// + protected RedirectToUmbracoPageResult RedirectToCurrentUmbracoPage() + { + return new RedirectToUmbracoPageResult(CurrentPage, UmbracoContext); + } + + /// + /// Returns the currently rendered Umbraco page + /// + /// + protected UmbracoPageResult CurrentUmbracoPage() + { + return new UmbracoPageResult(); + } + + /// + /// Gets the current page. + /// + protected IDocument CurrentPage + { + get + { + if (!ControllerContext.RouteData.DataTokens.ContainsKey("umbraco-route-def")) + throw new InvalidOperationException("Can only use " + typeof(UmbracoPageResult).Name + " in the context of an Http POST when using the BeginUmbracoForm helper"); + + var routeDef = (RouteDefinition)ControllerContext.RouteData.DataTokens["umbraco-route-def"]; + return routeDef.DocumentRequest.Document; + } + } + + /// + /// Returns the metadata for this instance + /// + internal SurfaceControllerMetadata GetMetadata() + { + var controllerId = this.GetType().GetCustomAttribute(false); + + return new SurfaceControllerMetadata() + { + ControllerId = controllerId == null ? null : (Guid?) controllerId.Id, + ControllerName = ControllerExtensions.GetControllerName(this.GetType()), + ControllerNamespace = this.GetType().Namespace, + ControllerType = this.GetType() + }; + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Mvc/SurfaceControllerArea.cs b/src/Umbraco.Web/Mvc/SurfaceControllerArea.cs new file mode 100644 index 0000000000..9712cd547d --- /dev/null +++ b/src/Umbraco.Web/Mvc/SurfaceControllerArea.cs @@ -0,0 +1,63 @@ +using System.Collections.Generic; +using System.Linq; +using System.Web.Mvc; +using System.Web.Routing; +using Umbraco.Core; +using Umbraco.Core.Configuration; + +namespace Umbraco.Web.Mvc +{ + /// + /// A custom area for surface controller routes + /// + internal class SurfaceControllerArea : AreaRegistration + { + private readonly IEnumerable _surfaceControllers; + + public SurfaceControllerArea(IEnumerable surfaceControllers) + { + _surfaceControllers = surfaceControllers; + } + + public override void RegisterArea(AreaRegistrationContext context) + { + MapRouteSurfaceControllers(context.Routes, _surfaceControllers); + } + + public override string AreaName + { + get { return "Surface"; } + } + + /// + /// Registers all surface controller routes + /// + /// + /// + private void MapRouteSurfaceControllers(RouteCollection routes, IEnumerable surfaceControllers) + { + var areaName = GlobalSettings.UmbracoMvcArea; + + //local surface controllers do not contain the attribute + var localSurfaceControlleres = surfaceControllers.Where(x => TypeExtensions.GetCustomAttribute(x.GetType(), false) == null); + foreach (var s in localSurfaceControlleres) + { + var meta = s.GetMetadata(); + var route = routes.MapRoute( + string.Format("umbraco-{0}-{1}", "surface", meta.ControllerName), + areaName + "/Surface/" + meta.ControllerName + "/{action}/{id}",//url to match + new { controller = meta.ControllerName, action = "Index", id = UrlParameter.Optional }, + new[] { meta.ControllerNamespace }); //only match this namespace + route.DataTokens.Add("area", areaName); //only match this area + route.DataTokens.Add("umbraco", "surface"); //ensure the umbraco token is set + } + + var pluginSurfaceControllers = surfaceControllers.Where(x => x.GetType().GetCustomAttribute(false) != null); + foreach (var s in pluginSurfaceControllers) + { + var meta = s.GetMetadata(); + this.RouteControllerPlugin(meta.ControllerId.Value, meta.ControllerName, meta.ControllerType, routes, "surfaceId", "Surface", "", "Index", UrlParameter.Optional, "surface"); + } + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Mvc/SurfaceControllerAttribute.cs b/src/Umbraco.Web/Mvc/SurfaceControllerAttribute.cs new file mode 100644 index 0000000000..8facd8ed91 --- /dev/null +++ b/src/Umbraco.Web/Mvc/SurfaceControllerAttribute.cs @@ -0,0 +1,18 @@ +using System; + +namespace Umbraco.Web.Mvc +{ + /// + /// An attribute applied to surface controllers that are not locally declared (i.e. they are shipped as part of a package) + /// + [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] + public class SurfaceControllerAttribute : Attribute + { + public Guid Id { get; private set; } + + public SurfaceControllerAttribute(Guid id) + { + Id = id; + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Mvc/SurfaceControllerFactory.cs b/src/Umbraco.Web/Mvc/SurfaceControllerFactory.cs new file mode 100644 index 0000000000..a6eeda68d6 --- /dev/null +++ b/src/Umbraco.Web/Mvc/SurfaceControllerFactory.cs @@ -0,0 +1,67 @@ +using System; +using System.Web.Mvc; +using System.Web.Routing; + +namespace Umbraco.Web.Mvc +{ + /// + /// Creates SurfaceControllers + /// + public class SurfaceControllerFactory : RenderControllerFactory + { + /// + /// Check if the correct data tokens are in the route values so that we know its a surface controller route + /// + /// + /// + public override bool CanHandle(RequestContext request) + { + var area = request.RouteData.DataTokens["area"]; + + //if its a non-area route don't handle, all surface controllers will be in the 'umbraco' area + if (area == null || string.IsNullOrWhiteSpace(area.ToString())) + return false; + + //ensure there is an umbraco token set + var umbracoToken = request.RouteData.DataTokens["umbraco"]; + if (umbracoToken == null || string.IsNullOrWhiteSpace(umbracoToken.ToString())) + return false; + + return true; + } + + /// + /// Create the controller + /// + /// + /// + /// + public override IController CreateController(RequestContext requestContext, string controllerName) + { + //first try to instantiate with the DependencyResolver, if that fails, try with the UmbracoContext as a param, if that fails try with no params. + var controllerType = GetControllerType(requestContext, controllerName); + if (controllerType == null) + throw new InvalidOperationException("Could not find a controller type for the controller name " + controllerName); + + object controllerObject; + try + { + controllerObject = DependencyResolver.Current.GetService(controllerType); + } + catch (Exception) + { + try + { + controllerObject = Activator.CreateInstance(controllerType, UmbracoContext.Current); + } + catch (Exception) + { + //if this throws an exception, we'll let it + controllerObject = Activator.CreateInstance(controllerType); + } + } + //if an exception is thrown here, we want it to be thrown as its an invalid cast. + return (IController)controllerObject; + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Mvc/SurfaceControllerMetadata.cs b/src/Umbraco.Web/Mvc/SurfaceControllerMetadata.cs new file mode 100644 index 0000000000..ca95965de8 --- /dev/null +++ b/src/Umbraco.Web/Mvc/SurfaceControllerMetadata.cs @@ -0,0 +1,15 @@ +using System; + +namespace Umbraco.Web.Mvc +{ + /// + /// Represents some metadata about the surface controller + /// + internal class SurfaceControllerMetadata + { + internal Type ControllerType { get; set; } + internal string ControllerName { get; set; } + internal string ControllerNamespace { get; set; } + internal Guid? ControllerId { get; set; } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Mvc/SurfaceControllerResolver.cs b/src/Umbraco.Web/Mvc/SurfaceControllerResolver.cs new file mode 100644 index 0000000000..ec835ee00f --- /dev/null +++ b/src/Umbraco.Web/Mvc/SurfaceControllerResolver.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using Umbraco.Core.ObjectResolution; + +namespace Umbraco.Web.Mvc +{ + internal class SurfaceControllerResolver : ManyObjectsResolverBase + { + public SurfaceControllerResolver(IEnumerable surfaceControllers) + : base(surfaceControllers) + { + + } + + /// + /// Gets the surface controllers + /// + public IEnumerable SurfaceControllers + { + get { return Values; } + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Mvc/UmbracoPageResult.cs b/src/Umbraco.Web/Mvc/UmbracoPageResult.cs new file mode 100644 index 0000000000..63919c1d5c --- /dev/null +++ b/src/Umbraco.Web/Mvc/UmbracoPageResult.cs @@ -0,0 +1,50 @@ +using System; +using System.Web.Mvc; +using Umbraco.Core; + +namespace Umbraco.Web.Mvc +{ + /// + /// Used by posted forms to proxy the result to the page in which the current URL matches on + /// + public class UmbracoPageResult : ActionResult + { + public override void ExecuteResult(ControllerContext context) + { + + //since we could be returning the current page from a surface controller posted values in which the routing values are changed, we + //need to revert these values back to nothing in order for the normal page to render again. + context.RouteData.DataTokens["area"] = null; + context.RouteData.DataTokens["Namespaces"] = null; + + //validate that the current page execution is not being handled by the normal umbraco routing system + if (!context.RouteData.DataTokens.ContainsKey("umbraco-route-def")) + { + throw new InvalidOperationException("Can only use " + typeof(UmbracoPageResult).Name + " in the context of an Http POST when using a SurfaceController form"); + } + + var routeDef = (RouteDefinition)context.RouteData.DataTokens["umbraco-route-def"]; + + //ensure ModelState is copied across + routeDef.Controller.ViewData.ModelState.Merge(context.Controller.ViewData.ModelState); + + //ensure TempData and ViewData is copied across + foreach (var d in context.Controller.ViewData) + routeDef.Controller.ViewData[d.Key] = d.Value; + routeDef.Controller.TempData = context.Controller.TempData; + + using (DisposableTimer.TraceDuration("Executing Umbraco RouteDefinition controller", "Finished")) + { + try + { + ((IController)routeDef.Controller).Execute(context.RequestContext); + } + finally + { + routeDef.Controller.DisposeIfDisposable(); + } + } + + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/PluginManagerExtensions.cs b/src/Umbraco.Web/PluginManagerExtensions.cs index 5ff0e9f81d..07a3ce9127 100644 --- a/src/Umbraco.Web/PluginManagerExtensions.cs +++ b/src/Umbraco.Web/PluginManagerExtensions.cs @@ -2,7 +2,7 @@ using System; using System.Collections.Generic; using System.Threading; using Umbraco.Core; - +using Umbraco.Web.Mvc; using Umbraco.Web.Routing; using umbraco; using umbraco.interfaces; @@ -14,6 +14,11 @@ namespace Umbraco.Web /// public static class PluginManagerExtensions { + internal static IEnumerable ResolveSurfaceControllers(this PluginManager resolver) + { + return resolver.ResolveTypes(); + } + /// /// Returns all available ITrees in application /// diff --git a/src/Umbraco.Web/RouteCollectionExtensions.cs b/src/Umbraco.Web/RouteCollectionExtensions.cs new file mode 100644 index 0000000000..e0950e99b2 --- /dev/null +++ b/src/Umbraco.Web/RouteCollectionExtensions.cs @@ -0,0 +1,89 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Web.Mvc; +using System.Web.Routing; + +namespace Umbraco.Web +{ + internal static class RouteCollectionExtensions + { + + public static void IgnoreStandardExclusions(this RouteCollection routes) + { + // Ignore standard stuff... + using (routes.GetWriteLock()) + { + var exclusions = new Dictionary() + { + {"{resource}.axd/{*pathInfo}", null}, + {"{*allaxd}", new { allaxd = @".*\.axd(/.*)?" }}, + {"{*allashx}", new { allashx = @".*\.ashx(/.*)?" }}, + {"{*allaspx}", new { allaspx = @".*\.aspx(/.*)?" }}, + {"{*favicon}", new { favicon = @"(.*/)?favicon.ico(/.*)?" }}, + }; + //ensure they're not re-added + foreach (var e in exclusions.Where(e => !routes.OfType().Any(x => x.Url == e.Key))) + { + if (e.Value == null) + { + routes.IgnoreRoute(e.Key); + } + else + { + routes.IgnoreRoute(e.Key, e.Value); + } + } + } + } + + /// + /// Extension method to manually regsiter an area + /// + /// + /// + public static void RegisterArea(this RouteCollection routes) + where T : AreaRegistration + { + + // instantiate the area registration + var area = Activator.CreateInstance(); + + // create a context, which is just the name and routes collection + var context = new AreaRegistrationContext(area.AreaName, routes); + + // register it! + area.RegisterArea(context); + } + + ///// + ///// Extension method to manually regsiter an area from the container + ///// + ///// + ///// + ///// + //public static void RegisterArea(this RouteCollection routes, Framework.DependencyManagement.IDependencyResolver container) + // where T : AreaRegistration + //{ + + // var area = container.Resolve() as AreaRegistration; + // if (area == null) + // { + // throw new InvalidCastException("Could not resolve type " + typeof(T).FullName + " to AreaRegistration"); + // } + + // // create a context, which is just the name and routes collection + // var context = new AreaRegistrationContext(area.AreaName, routes); + + // // register it! + // area.RegisterArea(context); + //} + + + public static void RegisterArea(this RouteCollection routes, T area) where T : AreaRegistration + { + area.RegisterArea(new AreaRegistrationContext(area.AreaName, routes)); + } + + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Routing/NiceUrlProvider.cs b/src/Umbraco.Web/Routing/NiceUrlProvider.cs index 21fb08e5bb..efb1a25cf8 100644 --- a/src/Umbraco.Web/Routing/NiceUrlProvider.cs +++ b/src/Umbraco.Web/Routing/NiceUrlProvider.cs @@ -17,7 +17,10 @@ namespace Umbraco.Web.Routing /// Provides nice urls for a nodes. /// internal class NiceUrlProvider - { + { + + internal const string NullUrl = "#"; + /// /// Initializes a new instance of the class. /// @@ -81,7 +84,7 @@ namespace Umbraco.Web.Routing "Couldn't find any page with nodeId={0}. This is most likely caused by the page not being published.", nodeId); - return "#"; + return NullUrl; } // walk up from that node until we hit a node with a domain, @@ -169,7 +172,7 @@ namespace Umbraco.Web.Routing { var node = _publishedContentStore.GetDocumentById(_umbracoContext, nodeId); if (node == null) - return new string[] { "#" }; // legacy wrote to the log here... + return new string[] { NullUrl }; // legacy wrote to the log here... var pathParts = new List(); int id = nodeId; diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 7ce6953df8..cce5ce9568 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -240,6 +240,7 @@ Properties\SolutionInfo.cs + @@ -271,17 +272,28 @@ + + + True True Strings.resx + + + + + + + + ASPXCodeBehind diff --git a/src/Umbraco.Web/UmbracoContext.cs b/src/Umbraco.Web/UmbracoContext.cs index 31cd89979b..bfbaf2ae38 100644 --- a/src/Umbraco.Web/UmbracoContext.cs +++ b/src/Umbraco.Web/UmbracoContext.cs @@ -16,6 +16,11 @@ using Examine; namespace Umbraco.Web { + public interface IRequiresUmbracoContext + { + UmbracoContext UmbracoContext { get; set; } + } + /// /// Class that encapsulates Umbraco information of a specific HTTP request /// diff --git a/src/Umbraco.Web/WebBootManager.cs b/src/Umbraco.Web/WebBootManager.cs index 25b8a8617e..483a3b090f 100644 --- a/src/Umbraco.Web/WebBootManager.cs +++ b/src/Umbraco.Web/WebBootManager.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Web.Mvc; using System.Web.Routing; using Umbraco.Core; +using Umbraco.Core.Configuration; using Umbraco.Core.Dictionary; using Umbraco.Core.Dynamics; using Umbraco.Core.PropertyEditors; @@ -13,6 +14,7 @@ using Umbraco.Web.PropertyEditors; using Umbraco.Web.Routing; using umbraco.businesslogic; + namespace Umbraco.Web { /// @@ -55,8 +57,6 @@ namespace Umbraco.Web //set model binder ModelBinders.Binders.Add(new KeyValuePair(typeof(RenderModel), new RenderModelBinder())); - //set routes - CreateRoutes(); //find and initialize the application startup handlers, we need to initialize this resolver here because //it is a special resolver where they need to be instantiated first before any other resolvers in order to bind to @@ -100,6 +100,9 @@ namespace Umbraco.Web { base.Complete(afterComplete); + //set routes + CreateRoutes(); + //call OnApplicationStarting of each application events handler ApplicationEventsResolver.Current.ApplicationEventHandlers .ForEach(x => x.OnApplicationStarted(_umbracoApplication, ApplicationContext)); @@ -112,15 +115,25 @@ namespace Umbraco.Web /// protected internal void CreateRoutes() { + //set routes - var route = RouteTable.Routes.MapRoute( + var defaultRoute = RouteTable.Routes.MapRoute( "Umbraco_default", "Umbraco/RenderMvc/{action}/{id}", new { controller = "RenderMvc", action = "Index", id = UrlParameter.Optional } ); - route.RouteHandler = new RenderRouteHandler(ControllerBuilder.Current.GetControllerFactory()); + defaultRoute.RouteHandler = new RenderRouteHandler(ControllerBuilder.Current.GetControllerFactory()); + + //now we need to find the surface controllers and route them too + var surfaceControllers = SurfaceControllerResolver.Current.SurfaceControllers; + //create a custom area for them + var surfaceControllerArea = new SurfaceControllerArea(surfaceControllers); + //register it + RouteTable.Routes.RegisterArea(surfaceControllerArea); } + + /// /// Initializes all web based and core resolves /// @@ -128,6 +141,9 @@ namespace Umbraco.Web { base.InitializeResolvers(); + SurfaceControllerResolver.Current = new SurfaceControllerResolver( + PluginManager.Current.ResolveSurfaceControllers()); + //the base creates the PropertyEditorValueConvertersResolver but we want to modify it in the web app and replace //the TinyMcePropertyEditorValueConverter with the RteMacroRenderingPropertyEditorValueConverter PropertyEditorValueConvertersResolver.Current.RemoveType(); diff --git a/src/umbraco.MacroEngines/umbraco.MacroEngines.csproj b/src/umbraco.MacroEngines/umbraco.MacroEngines.csproj index 58786d2ce8..38b25e1993 100644 --- a/src/umbraco.MacroEngines/umbraco.MacroEngines.csproj +++ b/src/umbraco.MacroEngines/umbraco.MacroEngines.csproj @@ -54,6 +54,7 @@ + False ..\..\lib\WebPages\System.Web.Razor.dll From 7b7e3b82f1c4e5c0cff4280329b5b2ec527bf627 Mon Sep 17 00:00:00 2001 From: Shannon Deminick Date: Tue, 25 Sep 2012 13:33:47 +0700 Subject: [PATCH 05/22] Started adding tests for surface controller routing. They are working but not quite correctly yet. --- .../Surface/SurfaceControllerAreaTests.cs | 11 +++++++++++ src/Umbraco.Tests/Umbraco.Tests.csproj | 1 + src/Umbraco.Web/AreaRegistrationExtensions.cs | 4 ++++ src/Umbraco.Web/Mvc/SurfaceController.cs | 9 +++++++++ src/Umbraco.Web/Mvc/SurfaceControllerArea.cs | 2 +- src/Umbraco.Web/Mvc/SurfaceControllerAttribute.cs | 12 ++++++++++-- 6 files changed, 36 insertions(+), 3 deletions(-) create mode 100644 src/Umbraco.Tests/Surface/SurfaceControllerAreaTests.cs diff --git a/src/Umbraco.Tests/Surface/SurfaceControllerAreaTests.cs b/src/Umbraco.Tests/Surface/SurfaceControllerAreaTests.cs new file mode 100644 index 0000000000..96fde34574 --- /dev/null +++ b/src/Umbraco.Tests/Surface/SurfaceControllerAreaTests.cs @@ -0,0 +1,11 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Umbraco.Tests.Surface +{ + public class SurfaceControllerAreaTests + { + } +} diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index a4a7b46302..09c1e80977 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -70,6 +70,7 @@ + diff --git a/src/Umbraco.Web/AreaRegistrationExtensions.cs b/src/Umbraco.Web/AreaRegistrationExtensions.cs index ccdf7d6395..ba9697246f 100644 --- a/src/Umbraco.Web/AreaRegistrationExtensions.cs +++ b/src/Umbraco.Web/AreaRegistrationExtensions.cs @@ -59,6 +59,10 @@ namespace Umbraco.Web //set the namespace of the controller to match new[] { controllerType.Namespace }); + //TODO: FIx this!! + //By setting the default this will always route even without specifying the surfaceId syntax in the URL, + // we need to unit test this and ensure it is correct + //set defaults controllerPluginRoute.Defaults = new RouteValueDictionary( new Dictionary diff --git a/src/Umbraco.Web/Mvc/SurfaceController.cs b/src/Umbraco.Web/Mvc/SurfaceController.cs index 5b7c38a6ce..a4ceeed4f6 100644 --- a/src/Umbraco.Web/Mvc/SurfaceController.cs +++ b/src/Umbraco.Web/Mvc/SurfaceController.cs @@ -6,6 +6,15 @@ using Umbraco.Core; namespace Umbraco.Web.Mvc { + [SurfaceController("DD307F95-6D90-4593-8C97-093AC7C12573")] + public class TestSurfaceController : SurfaceController + { + public ActionResult Index() + { + return Content("hello"); + } + } + /// /// The base controller that all Presentation Add-in controllers should inherit from /// diff --git a/src/Umbraco.Web/Mvc/SurfaceControllerArea.cs b/src/Umbraco.Web/Mvc/SurfaceControllerArea.cs index 9712cd547d..7ac8b29ee8 100644 --- a/src/Umbraco.Web/Mvc/SurfaceControllerArea.cs +++ b/src/Umbraco.Web/Mvc/SurfaceControllerArea.cs @@ -39,7 +39,7 @@ namespace Umbraco.Web.Mvc var areaName = GlobalSettings.UmbracoMvcArea; //local surface controllers do not contain the attribute - var localSurfaceControlleres = surfaceControllers.Where(x => TypeExtensions.GetCustomAttribute(x.GetType(), false) == null); + var localSurfaceControlleres = surfaceControllers.Where(x => x.GetType().GetCustomAttribute(false) == null); foreach (var s in localSurfaceControlleres) { var meta = s.GetMetadata(); diff --git a/src/Umbraco.Web/Mvc/SurfaceControllerAttribute.cs b/src/Umbraco.Web/Mvc/SurfaceControllerAttribute.cs index 8facd8ed91..ac08ca9886 100644 --- a/src/Umbraco.Web/Mvc/SurfaceControllerAttribute.cs +++ b/src/Umbraco.Web/Mvc/SurfaceControllerAttribute.cs @@ -10,9 +10,17 @@ namespace Umbraco.Web.Mvc { public Guid Id { get; private set; } - public SurfaceControllerAttribute(Guid id) + public SurfaceControllerAttribute(string id) { - Id = id; + Guid gid; + if (Guid.TryParse(id, out gid)) + { + Id = gid; + } + else + { + throw new InvalidCastException("Cannot convert the value " + id + " to a Guid"); + } } } } \ No newline at end of file From 48a3a920d26195485b674c3a80df5d75b6aa9bfd Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Tue, 25 Sep 2012 11:53:27 -0200 Subject: [PATCH 06/22] Web.config file was not included, making the build fail --- src/Umbraco.Web.UI/Umbraco.Web.UI.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index 5e840610a5..7e9b82cd71 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -1662,7 +1662,7 @@ - + Web.Template.config Designer From 97472c6ce6ef8405150274eb27050230739321e3 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Tue, 25 Sep 2012 11:56:33 -0200 Subject: [PATCH 07/22] Ignore file excluded the web.config file in the views folder, which is why the build failed, not because it wasn't included --- .hgignore | 3 +- src/Umbraco.Web.UI/Views/Web.config | 58 +++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+), 1 deletion(-) create mode 100644 src/Umbraco.Web.UI/Views/Web.config diff --git a/.hgignore b/.hgignore index a6ddc03595..980273f4f7 100644 --- a/.hgignore +++ b/.hgignore @@ -47,5 +47,6 @@ src/Umbraco.Tests/config/trees.config src/Umbraco.Web.UI/web.config *.orig src/Umbraco.Tests/config/404handlers.config -src/Umbraco.Web.UI/Views/* +src/Umbraco.Web.UI/Views/*.cshtml +src/Umbraco.Web.UI/Views/*.vbhtml src/Umbraco.Tests/config/umbracoSettings.config diff --git a/src/Umbraco.Web.UI/Views/Web.config b/src/Umbraco.Web.UI/Views/Web.config new file mode 100644 index 0000000000..4c30ef2275 --- /dev/null +++ b/src/Umbraco.Web.UI/Views/Web.config @@ -0,0 +1,58 @@ + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 332b42aa49fc226513f3b327c6f6d77b3ee01648 Mon Sep 17 00:00:00 2001 From: Shannon Deminick Date: Wed, 26 Sep 2012 11:04:07 +0700 Subject: [PATCH 08/22] Changed how surface controllers are routed: because we don't load plugins in via App_Plugins/[PackageName] like we did in v5 which meant that we can autoroute all plugin controllers with their own areas, in v4 we have to do something different. Instead (because we still want areas), we have an attribute called PluginControllerAttribute which allows a dev to assign an area name as a string. A package developer should always ensure that all of their plugin controllers are assigned to the same area. This also lets us get rid of the GUID id part of the plugin (hopefully we can leave it out, just need to run some tests). Since we have areas, package devs can then put their views into their own folders and not clutter up the local devs ~/Views folder. Perhaps we use App_Plugins/[PackageName] for the views like we did in v5 but need to ask on the mail list. Otherwise it will be the standard MVC: ~/Areas/[AreaName]/Views which will still also clutter up the local devs view folder if they are using Areas. --- src/Umbraco.Web.UI/Umbraco.Web.UI.csproj | 2 + src/Umbraco.Web.UI/Web.config | 2 +- src/Umbraco.Web/AreaRegistrationExtensions.cs | 33 +++------ src/Umbraco.Web/Mvc/PluginController.cs | 61 ++++++++++++++++ src/Umbraco.Web/Mvc/PluginControllerArea.cs | 70 +++++++++++++++++++ .../Mvc/PluginControllerAttribute.cs | 26 +++++++ ...etadata.cs => PluginControllerMetadata.cs} | 4 +- src/Umbraco.Web/Mvc/SurfaceController.cs | 46 +++--------- src/Umbraco.Web/Mvc/SurfaceControllerArea.cs | 63 ----------------- .../Mvc/SurfaceControllerAttribute.cs | 26 ------- src/Umbraco.Web/Umbraco.Web.csproj | 7 +- src/Umbraco.Web/WebBootManager.cs | 43 ++++++++++-- 12 files changed, 224 insertions(+), 159 deletions(-) create mode 100644 src/Umbraco.Web/Mvc/PluginController.cs create mode 100644 src/Umbraco.Web/Mvc/PluginControllerArea.cs create mode 100644 src/Umbraco.Web/Mvc/PluginControllerAttribute.cs rename src/Umbraco.Web/Mvc/{SurfaceControllerMetadata.cs => PluginControllerMetadata.cs} (73%) delete mode 100644 src/Umbraco.Web/Mvc/SurfaceControllerArea.cs delete mode 100644 src/Umbraco.Web/Mvc/SurfaceControllerAttribute.cs diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index 5e840610a5..373cd65436 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -172,6 +172,7 @@ False ..\..\lib\WebPages\System.Web.Helpers.dll + False ..\..\lib\WebPages\System.Web.Razor.dll @@ -1663,6 +1664,7 @@ + Web.Template.config Designer diff --git a/src/Umbraco.Web.UI/Web.config b/src/Umbraco.Web.UI/Web.config index a860662208..3dd62de184 100644 --- a/src/Umbraco.Web.UI/Web.config +++ b/src/Umbraco.Web.UI/Web.config @@ -36,7 +36,7 @@ - + diff --git a/src/Umbraco.Web/AreaRegistrationExtensions.cs b/src/Umbraco.Web/AreaRegistrationExtensions.cs index ba9697246f..539b8efc16 100644 --- a/src/Umbraco.Web/AreaRegistrationExtensions.cs +++ b/src/Umbraco.Web/AreaRegistrationExtensions.cs @@ -14,29 +14,24 @@ namespace Umbraco.Web /// Creates a custom individual route for the specified controller plugin. Individual routes /// are required by controller plugins to map to a unique URL based on ID. /// - /// An PluginAttribute /// /// /// An existing route collection - /// the data token name for the controller plugin /// /// The suffix name that the controller name must end in before the "Controller" string for example: - /// ContentTreeController has a controllerSuffixName of "Tree" - /// - /// - /// The base name of the URL to create for example: Umbraco/[PackageName]/Trees/ContentTree/1 has a baseUrlPath of "Trees" + /// ContentTreeController has a controllerSuffixName of "Tree", this is used for route constraints. /// /// /// /// - /// /// The DataToken value to set for the 'umbraco' key, this defaults to 'backoffice' - internal static void RouteControllerPlugin(this AreaRegistration area, Guid controllerId, string controllerName, Type controllerType, RouteCollection routes, - string routeIdParameterName, string controllerSuffixName, string baseUrlPath, string defaultAction, object defaultId, + /// + /// + internal static void RouteControllerPlugin(this AreaRegistration area, string controllerName, Type controllerType, RouteCollection routes, + string controllerSuffixName, string defaultAction, object defaultId, string umbracoTokenValue = "backoffice") { Mandate.ParameterNotNullOrEmpty(controllerName, "controllerName"); - Mandate.ParameterNotNullOrEmpty(routeIdParameterName, "routeIdParameterName"); Mandate.ParameterNotNullOrEmpty(controllerSuffixName, "controllerSuffixName"); Mandate.ParameterNotNullOrEmpty(defaultAction, "defaultAction"); Mandate.ParameterNotNull(controllerType, "controllerType"); @@ -45,30 +40,24 @@ namespace Umbraco.Web var umbracoArea = GlobalSettings.UmbracoMvcArea; - //routes are explicitly name with controller names. - var url = baseUrlPath.IsNullOrWhiteSpace() - ? umbracoArea + "/" + area.AreaName + "/" + controllerName + "/{action}/{id}" - : umbracoArea + "/" + area.AreaName + "/" + baseUrlPath + "/" + controllerName + "/{action}/{id}"; + //routes are explicitly name with controller names and IDs + var url = umbracoArea + "/" + area.AreaName + "/" + controllerName + "/{action}/{id}"; //create a new route with custom name, specified url, and the namespace of the controller plugin var controllerPluginRoute = routes.MapRoute( //name - string.Format("umbraco-{0}-{1}", controllerName, controllerId), + string.Format("umbraco-{0}", controllerType.FullName), //url format url, //set the namespace of the controller to match new[] { controllerType.Namespace }); - - //TODO: FIx this!! - //By setting the default this will always route even without specifying the surfaceId syntax in the URL, - // we need to unit test this and ensure it is correct - + //set defaults controllerPluginRoute.Defaults = new RouteValueDictionary( new Dictionary { { "controller", controllerName }, - { routeIdParameterName, controllerId.ToString("N") }, + { "controllerType", controllerType.FullName }, { "action", defaultAction }, { "id", defaultId } }); @@ -78,7 +67,7 @@ namespace Umbraco.Web new Dictionary { { "controller", @"(\w+)" + controllerSuffixName }, - { routeIdParameterName, Regex.Escape(controllerId.ToString("N")) } + { "controllerType", controllerType.FullName } }); diff --git a/src/Umbraco.Web/Mvc/PluginController.cs b/src/Umbraco.Web/Mvc/PluginController.cs new file mode 100644 index 0000000000..355aca89e6 --- /dev/null +++ b/src/Umbraco.Web/Mvc/PluginController.cs @@ -0,0 +1,61 @@ +using System; +using System.Collections.Concurrent; +using System.Web.Mvc; +using Umbraco.Core; + +namespace Umbraco.Web.Mvc +{ + /// + /// A base class for all plugin controllers to inherit from + /// + public abstract class PluginController : Controller, IRequiresUmbracoContext + { + /// + /// stores the metadata about plugin controllers + /// + private static readonly ConcurrentDictionary Metadata = new ConcurrentDictionary(); + + /// + /// Default constructor + /// + /// + protected PluginController(UmbracoContext umbracoContext) + { + UmbracoContext = umbracoContext; + InstanceId = Guid.NewGuid(); + } + + /// + /// Useful for debugging + /// + internal Guid InstanceId { get; private set; } + + public UmbracoContext UmbracoContext { get; set; } + + /// + /// Returns the metadata for this instance + /// + internal PluginControllerMetadata GetMetadata() + { + PluginControllerMetadata meta; + if (Metadata.TryGetValue(this.GetType(), out meta)) + { + return meta; + } + + var attribute = this.GetType().GetCustomAttribute(false); + + meta = new PluginControllerMetadata() + { + AreaName = attribute == null ? null : attribute.AreaName, + ControllerName = ControllerExtensions.GetControllerName(this.GetType()), + ControllerNamespace = this.GetType().Namespace, + ControllerType = this.GetType() + }; + + Metadata.TryAdd(this.GetType(), meta); + + return meta; + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Mvc/PluginControllerArea.cs b/src/Umbraco.Web/Mvc/PluginControllerArea.cs new file mode 100644 index 0000000000..fe981b815a --- /dev/null +++ b/src/Umbraco.Web/Mvc/PluginControllerArea.cs @@ -0,0 +1,70 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Web.Mvc; +using System.Web.Routing; +using Umbraco.Core; +using Umbraco.Core.Configuration; + +namespace Umbraco.Web.Mvc +{ + /// + /// A custom area for controllers that are plugins + /// + internal class PluginControllerArea : AreaRegistration + { + private readonly IEnumerable _surfaceControllers; + private readonly string _areaName; + + /// + /// The constructor accepts all types of plugin controllers and will verify that ALL of them have the same areaName assigned to them + /// based on their PluginControllerAttribute. If they are not the same an exception will be thrown. + /// + /// + public PluginControllerArea(IEnumerable pluginControllers) + { + //TODO: When we have other future plugin controllers we need to combine them all into one list here to do our validation. + var controllers = pluginControllers.ToArray(); + + if (controllers.Any(x => x.GetMetadata().AreaName.IsNullOrWhiteSpace())) + { + throw new InvalidOperationException("Cannot create a PluginControllerArea unless all plugin controllers assigned have a PluginControllerAttribute assigned"); + } + _areaName = controllers.First().GetMetadata().AreaName; + foreach(var c in controllers) + { + if (c.GetMetadata().AreaName != _areaName) + { + throw new InvalidOperationException("Cannot create a PluginControllerArea unless all plugin controllers assigned have the same AreaName. The first AreaName found was " + _areaName + " however, the controller of type " + c.GetType().FullName + " has an AreaName of " + c.GetMetadata().AreaName); + } + } + + //get the surface controllers + _surfaceControllers = controllers.OfType(); + } + + public override void RegisterArea(AreaRegistrationContext context) + { + MapRouteSurfaceControllers(context.Routes, _surfaceControllers); + } + + public override string AreaName + { + get { return _areaName; } + } + + /// + /// Registers all surface controller routes + /// + /// + /// + private void MapRouteSurfaceControllers(RouteCollection routes, IEnumerable surfaceControllers) + { + foreach (var s in surfaceControllers) + { + var meta = s.GetMetadata(); + this.RouteControllerPlugin(meta.ControllerName, meta.ControllerType, routes, "Surface", "Index", UrlParameter.Optional, "surface"); + } + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Mvc/PluginControllerAttribute.cs b/src/Umbraco.Web/Mvc/PluginControllerAttribute.cs new file mode 100644 index 0000000000..064f39826d --- /dev/null +++ b/src/Umbraco.Web/Mvc/PluginControllerAttribute.cs @@ -0,0 +1,26 @@ +using System; +using System.Linq; + +namespace Umbraco.Web.Mvc +{ + /// + /// An attribute applied to a plugin controller that requires that it is routed to its own area + /// + [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] + public class PluginControllerAttribute : Attribute + { + public string AreaName { get; private set; } + + public PluginControllerAttribute(string areaName) + { + //validate this, only letters and digits allowed. + if (areaName.Any(c => !Char.IsLetterOrDigit(c))) + { + throw new FormatException("The areaName specified " + areaName + " can only contains letters and digits"); + } + + AreaName = areaName; + } + + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Mvc/SurfaceControllerMetadata.cs b/src/Umbraco.Web/Mvc/PluginControllerMetadata.cs similarity index 73% rename from src/Umbraco.Web/Mvc/SurfaceControllerMetadata.cs rename to src/Umbraco.Web/Mvc/PluginControllerMetadata.cs index ca95965de8..257fc25fe5 100644 --- a/src/Umbraco.Web/Mvc/SurfaceControllerMetadata.cs +++ b/src/Umbraco.Web/Mvc/PluginControllerMetadata.cs @@ -5,11 +5,11 @@ namespace Umbraco.Web.Mvc /// /// Represents some metadata about the surface controller /// - internal class SurfaceControllerMetadata + internal class PluginControllerMetadata { internal Type ControllerType { get; set; } internal string ControllerName { get; set; } internal string ControllerNamespace { get; set; } - internal Guid? ControllerId { get; set; } + internal string AreaName { get; set; } } } \ No newline at end of file diff --git a/src/Umbraco.Web/Mvc/SurfaceController.cs b/src/Umbraco.Web/Mvc/SurfaceController.cs index a4ceeed4f6..6d13f3b130 100644 --- a/src/Umbraco.Web/Mvc/SurfaceController.cs +++ b/src/Umbraco.Web/Mvc/SurfaceController.cs @@ -6,12 +6,13 @@ using Umbraco.Core; namespace Umbraco.Web.Mvc { - [SurfaceController("DD307F95-6D90-4593-8C97-093AC7C12573")] + [PluginController("MyTestSurfaceController")] public class TestSurfaceController : SurfaceController { public ActionResult Index() { - return Content("hello"); + return View(); + //return Content("hello"); } } @@ -19,37 +20,24 @@ namespace Umbraco.Web.Mvc /// The base controller that all Presentation Add-in controllers should inherit from /// [MergeModelStateToChildAction] - public abstract class SurfaceController : Controller, IRequiresUmbracoContext - { - ///// - ///// stores the metadata about surface controllers - ///// - //private static ConcurrentDictionary _metadata = new ConcurrentDictionary(); - - public UmbracoContext UmbracoContext { get; set; } - - /// - /// Useful for debugging - /// - internal Guid InstanceId { get; private set; } + public abstract class SurfaceController : PluginController + { /// /// Default constructor /// /// protected SurfaceController(UmbracoContext umbracoContext) - { - UmbracoContext = umbracoContext; - InstanceId = Guid.NewGuid(); + : base(umbracoContext) + { } /// /// Empty constructor, uses Singleton to resolve the UmbracoContext /// protected SurfaceController() - { - UmbracoContext = UmbracoContext.Current; - InstanceId = Guid.NewGuid(); + : base(UmbracoContext.Current) + { } /// @@ -105,20 +93,6 @@ namespace Umbraco.Web.Mvc } } - /// - /// Returns the metadata for this instance - /// - internal SurfaceControllerMetadata GetMetadata() - { - var controllerId = this.GetType().GetCustomAttribute(false); - - return new SurfaceControllerMetadata() - { - ControllerId = controllerId == null ? null : (Guid?) controllerId.Id, - ControllerName = ControllerExtensions.GetControllerName(this.GetType()), - ControllerNamespace = this.GetType().Namespace, - ControllerType = this.GetType() - }; - } + } } \ No newline at end of file diff --git a/src/Umbraco.Web/Mvc/SurfaceControllerArea.cs b/src/Umbraco.Web/Mvc/SurfaceControllerArea.cs deleted file mode 100644 index 7ac8b29ee8..0000000000 --- a/src/Umbraco.Web/Mvc/SurfaceControllerArea.cs +++ /dev/null @@ -1,63 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using System.Web.Mvc; -using System.Web.Routing; -using Umbraco.Core; -using Umbraco.Core.Configuration; - -namespace Umbraco.Web.Mvc -{ - /// - /// A custom area for surface controller routes - /// - internal class SurfaceControllerArea : AreaRegistration - { - private readonly IEnumerable _surfaceControllers; - - public SurfaceControllerArea(IEnumerable surfaceControllers) - { - _surfaceControllers = surfaceControllers; - } - - public override void RegisterArea(AreaRegistrationContext context) - { - MapRouteSurfaceControllers(context.Routes, _surfaceControllers); - } - - public override string AreaName - { - get { return "Surface"; } - } - - /// - /// Registers all surface controller routes - /// - /// - /// - private void MapRouteSurfaceControllers(RouteCollection routes, IEnumerable surfaceControllers) - { - var areaName = GlobalSettings.UmbracoMvcArea; - - //local surface controllers do not contain the attribute - var localSurfaceControlleres = surfaceControllers.Where(x => x.GetType().GetCustomAttribute(false) == null); - foreach (var s in localSurfaceControlleres) - { - var meta = s.GetMetadata(); - var route = routes.MapRoute( - string.Format("umbraco-{0}-{1}", "surface", meta.ControllerName), - areaName + "/Surface/" + meta.ControllerName + "/{action}/{id}",//url to match - new { controller = meta.ControllerName, action = "Index", id = UrlParameter.Optional }, - new[] { meta.ControllerNamespace }); //only match this namespace - route.DataTokens.Add("area", areaName); //only match this area - route.DataTokens.Add("umbraco", "surface"); //ensure the umbraco token is set - } - - var pluginSurfaceControllers = surfaceControllers.Where(x => x.GetType().GetCustomAttribute(false) != null); - foreach (var s in pluginSurfaceControllers) - { - var meta = s.GetMetadata(); - this.RouteControllerPlugin(meta.ControllerId.Value, meta.ControllerName, meta.ControllerType, routes, "surfaceId", "Surface", "", "Index", UrlParameter.Optional, "surface"); - } - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Web/Mvc/SurfaceControllerAttribute.cs b/src/Umbraco.Web/Mvc/SurfaceControllerAttribute.cs deleted file mode 100644 index ac08ca9886..0000000000 --- a/src/Umbraco.Web/Mvc/SurfaceControllerAttribute.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System; - -namespace Umbraco.Web.Mvc -{ - /// - /// An attribute applied to surface controllers that are not locally declared (i.e. they are shipped as part of a package) - /// - [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] - public class SurfaceControllerAttribute : Attribute - { - public Guid Id { get; private set; } - - public SurfaceControllerAttribute(string id) - { - Guid gid; - if (Guid.TryParse(id, out gid)) - { - Id = gid; - } - else - { - throw new InvalidCastException("Cannot convert the value " + id + " to a Guid"); - } - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index cce5ce9568..caea0cb4ea 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -273,6 +273,7 @@ + @@ -281,10 +282,10 @@ Strings.resx - - + + - + diff --git a/src/Umbraco.Web/WebBootManager.cs b/src/Umbraco.Web/WebBootManager.cs index 483a3b090f..e44528525c 100644 --- a/src/Umbraco.Web/WebBootManager.cs +++ b/src/Umbraco.Web/WebBootManager.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Web.Mvc; using System.Web.Routing; using Umbraco.Core; @@ -124,12 +125,42 @@ namespace Umbraco.Web ); defaultRoute.RouteHandler = new RenderRouteHandler(ControllerBuilder.Current.GetControllerFactory()); - //now we need to find the surface controllers and route them too - var surfaceControllers = SurfaceControllerResolver.Current.SurfaceControllers; - //create a custom area for them - var surfaceControllerArea = new SurfaceControllerArea(surfaceControllers); - //register it - RouteTable.Routes.RegisterArea(surfaceControllerArea); + var umbracoPath = GlobalSettings.UmbracoMvcArea; + + //we need to find the surface controllers and route them + var surfaceControllers = SurfaceControllerResolver.Current.SurfaceControllers.ToArray(); + + //local surface controllers do not contain the attribute + var localSurfaceControlleres = surfaceControllers.Where(x => x.GetType().GetCustomAttribute(false) == null); + foreach (var s in localSurfaceControlleres) + { + var meta = s.GetMetadata(); + var route = RouteTable.Routes.MapRoute( + string.Format("umbraco-{0}-{1}", "surface", meta.ControllerName), + umbracoPath + "/Surface/" + meta.ControllerName + "/{action}/{id}",//url to match + new { controller = meta.ControllerName, action = "Index", id = UrlParameter.Optional }, + new[] { meta.ControllerNamespace }); //only match this namespace + route.DataTokens.Add("area", umbracoPath); //only match this area + route.DataTokens.Add("umbraco", "surface"); //ensure the umbraco token is set + } + + //need to get the plugin controllers that are unique to each area (group by) + //TODO: One day when we have more plugin controllers, we will need to do a group by on ALL of them to pass into the ctor of PluginControllerArea + var groupedAreas = surfaceControllers.GroupBy(controller => controller.GetMetadata().AreaName); + //loop through each area defined amongst the controllers + foreach(var g in groupedAreas) + { + //create an area for the controllers (this will throw an exception if all controllers are not in the same area) + var pluginControllerArea = new PluginControllerArea(g); + //register it + RouteTable.Routes.RegisterArea(pluginControllerArea); + } + + RouteTable.Routes.MapRoute( + "Account", + "account/{action}/{id}", + new { controller = "Account", action = "Index", id = UrlParameter.Optional } + ); } From 5ece0b5af01c00f9572f8ef67746e9de5cbd3620 Mon Sep 17 00:00:00 2001 From: Shannon Deminick Date: Wed, 26 Sep 2012 13:42:03 +0700 Subject: [PATCH 09/22] Surface controllers now auto-routing, both locally declared and plugin types (which require a [PluginController] attribute and an Area Name assigned). BeginUmbracoForm working for both local and plugin SurfaceControllers. SurfaceController class now inherits from PluginController which exposes all things a dev would need including making IPublishedContentStore and IPublishedMediaStore public which can be accessed simply by properties on the controller. Updated web.config for views to include Umbraco.Web namespace for all of our helper methods. BeginUmbracoForm now has many more overrides for which you can specify a type or a generic type (we no longer require GUIDs :) --- .../Surface/PluginControllerAreaTests.cs | 94 +++++++++ .../Surface/SurfaceControllerAreaTests.cs | 11 - src/Umbraco.Tests/Umbraco.Tests.csproj | 2 +- src/Umbraco.Web/AreaRegistrationExtensions.cs | 4 +- src/Umbraco.Web/HtmlHelperRenderExtensions.cs | 191 ++++++++++++------ src/Umbraco.Web/IPublishedContentStore.cs | 2 +- src/Umbraco.Web/IPublishedMediaStore.cs | 2 +- src/Umbraco.Web/IPublishedStore.cs | 2 +- src/Umbraco.Web/Mvc/PluginController.cs | 58 +++++- src/Umbraco.Web/Mvc/PluginControllerArea.cs | 19 +- src/Umbraco.Web/Mvc/PostedDataProxyInfo.cs | 1 - src/Umbraco.Web/Mvc/RenderRouteHandler.cs | 53 ++--- src/Umbraco.Web/Mvc/SurfaceController.cs | 20 ++ .../Mvc/SurfaceControllerResolver.cs | 8 + src/Umbraco.Web/Mvc/UmbracoPageResult.cs | 3 + src/Umbraco.Web/Mvc/web.config.template | 1 + src/Umbraco.Web/UmbracoContext.cs | 4 - src/Umbraco.Web/WebBootManager.cs | 11 +- 18 files changed, 351 insertions(+), 135 deletions(-) create mode 100644 src/Umbraco.Tests/Surface/PluginControllerAreaTests.cs delete mode 100644 src/Umbraco.Tests/Surface/SurfaceControllerAreaTests.cs diff --git a/src/Umbraco.Tests/Surface/PluginControllerAreaTests.cs b/src/Umbraco.Tests/Surface/PluginControllerAreaTests.cs new file mode 100644 index 0000000000..ce0895e98f --- /dev/null +++ b/src/Umbraco.Tests/Surface/PluginControllerAreaTests.cs @@ -0,0 +1,94 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using NUnit.Framework; +using Umbraco.Tests.TestHelpers; +using Umbraco.Web; +using Umbraco.Web.Mvc; + +namespace Umbraco.Tests.Surface +{ + [TestFixture] + public class PluginControllerAreaTests : BaseWebTest + { + protected override bool RequiresDbSetup + { + get { return false; } + } + + [Test] + public void Ensure_Same_Area1() + { + Assert.Throws(() => + new PluginControllerArea(new PluginControllerMetadata[] + { + PluginController.GetMetadata(typeof(Controller1)), + PluginController.GetMetadata(typeof(Controller2)), + PluginController.GetMetadata(typeof(Controller3)) //not same area + })); + } + + [Test] + public void Ensure_Same_Area3() + { + Assert.Throws(() => + new PluginControllerArea(new PluginControllerMetadata[] + { + PluginController.GetMetadata(typeof(Controller1)), + PluginController.GetMetadata(typeof(Controller2)), + PluginController.GetMetadata(typeof(Controller4)) //no area assigned + })); + } + + [Test] + public void Ensure_Same_Area2() + { + var area = new PluginControllerArea(new PluginControllerMetadata[] + { + PluginController.GetMetadata(typeof(Controller1)), + PluginController.GetMetadata(typeof(Controller2)) + }); + Assert.Pass(); + } + + #region Test classes + + [PluginController("Area1")] + public class Controller1 : PluginController + { + public Controller1(UmbracoContext umbracoContext) : base(umbracoContext) + { + } + } + + [PluginController("Area1")] + public class Controller2 : PluginController + { + public Controller2(UmbracoContext umbracoContext) + : base(umbracoContext) + { + } + } + + [PluginController("Area2")] + public class Controller3 : PluginController + { + public Controller3(UmbracoContext umbracoContext) + : base(umbracoContext) + { + } + } + + public class Controller4 : PluginController + { + public Controller4(UmbracoContext umbracoContext) + : base(umbracoContext) + { + } + } + + #endregion + + } +} diff --git a/src/Umbraco.Tests/Surface/SurfaceControllerAreaTests.cs b/src/Umbraco.Tests/Surface/SurfaceControllerAreaTests.cs deleted file mode 100644 index 96fde34574..0000000000 --- a/src/Umbraco.Tests/Surface/SurfaceControllerAreaTests.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace Umbraco.Tests.Surface -{ - public class SurfaceControllerAreaTests - { - } -} diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index 09c1e80977..2d5c4776d1 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -70,7 +70,7 @@ - + diff --git a/src/Umbraco.Web/AreaRegistrationExtensions.cs b/src/Umbraco.Web/AreaRegistrationExtensions.cs index 539b8efc16..1ee790de62 100644 --- a/src/Umbraco.Web/AreaRegistrationExtensions.cs +++ b/src/Umbraco.Web/AreaRegistrationExtensions.cs @@ -57,7 +57,6 @@ namespace Umbraco.Web new Dictionary { { "controller", controllerName }, - { "controllerType", controllerType.FullName }, { "action", defaultAction }, { "id", defaultId } }); @@ -66,8 +65,7 @@ namespace Umbraco.Web controllerPluginRoute.Constraints = new RouteValueDictionary( new Dictionary { - { "controller", @"(\w+)" + controllerSuffixName }, - { "controllerType", controllerType.FullName } + { "controller", @"(\w+)" + controllerSuffixName } }); diff --git a/src/Umbraco.Web/HtmlHelperRenderExtensions.cs b/src/Umbraco.Web/HtmlHelperRenderExtensions.cs index ff17d1a7b5..e91f754140 100644 --- a/src/Umbraco.Web/HtmlHelperRenderExtensions.cs +++ b/src/Umbraco.Web/HtmlHelperRenderExtensions.cs @@ -1,10 +1,12 @@ using System; using System.Collections.Generic; using System.IO; +using System.Linq; using System.Text; using System.Web.Mvc; using System.Web.Mvc.Html; using Umbraco.Core; +using Umbraco.Web.Mvc; using umbraco; namespace Umbraco.Web @@ -19,32 +21,27 @@ namespace Umbraco.Web /// internal class UmbracoForm : MvcForm { + /// + /// Creates an UmbracoForm + /// + /// + /// + /// + /// + /// public UmbracoForm( ViewContext viewContext, string surfaceController, string surfaceAction, - string area, - Guid? surfaceId, + string area, object additionalRouteVals = null) : base(viewContext) { //need to create a params string as Base64 to put into our hidden field to use during the routes - string surfaceRouteParams; - if (surfaceId.HasValue) - { - surfaceRouteParams = string.Format("c={0}&a={1}&i={2}&ar={3}", - viewContext.HttpContext.Server.UrlEncode(surfaceController), - viewContext.HttpContext.Server.UrlEncode(surfaceAction), - viewContext.HttpContext.Server.UrlEncode(surfaceId.Value.ToString("N")), - area); - } - else - { - surfaceRouteParams = string.Format("c={0}&a={1}&ar={2}", - viewContext.HttpContext.Server.UrlEncode(surfaceController), - viewContext.HttpContext.Server.UrlEncode(surfaceAction), - area); - } + var surfaceRouteParams = string.Format("c={0}&a={1}&ar={2}", + viewContext.HttpContext.Server.UrlEncode(surfaceController), + viewContext.HttpContext.Server.UrlEncode(surfaceAction), + area); var additionalRouteValsAsQuery = additionalRouteVals.ToDictionary().ToQueryString(); if (!additionalRouteValsAsQuery.IsNullOrWhiteSpace()) @@ -131,8 +128,7 @@ namespace Umbraco.Web /// /// /// - public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, string controllerName, - object additionalRouteVals) + public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, string controllerName, object additionalRouteVals) { return html.BeginUmbracoForm(action, controllerName, additionalRouteVals, new Dictionary()); } @@ -166,11 +162,11 @@ namespace Umbraco.Web object additionalRouteVals, IDictionary htmlAttributes) { - var settings = DependencyResolver.Current.GetService(); + Mandate.ParameterNotNullOrEmpty(action, "action"); + Mandate.ParameterNotNullOrEmpty(controllerName, "controllerName"); + var area = Umbraco.Core.Configuration.GlobalSettings.UmbracoMvcArea; - var formAction = html.ViewContext.HttpContext.Request.Url.AbsolutePath; - - return html.RenderForm(formAction, FormMethod.Post, htmlAttributes, controllerName, action, area, null); + return html.BeginUmbracoForm(action, controllerName, area, additionalRouteVals, htmlAttributes); } /// @@ -178,11 +174,24 @@ namespace Umbraco.Web /// /// /// - /// The surface controller to route to + /// The surface controller to route to /// - public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, Guid surfaceId) + public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, Type surfaceType) { - return html.BeginUmbracoForm(action, surfaceId, null, new Dictionary()); + return html.BeginUmbracoForm(action, surfaceType, null, new Dictionary()); + } + + /// + /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin + /// + /// + /// + /// + /// + public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action) + where T: SurfaceController + { + return html.BeginUmbracoForm(action, typeof (T)); } /// @@ -190,13 +199,27 @@ namespace Umbraco.Web /// /// /// - /// The surface controller to route to + /// The surface controller to route to /// /// - public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, Guid surfaceId, + public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, Type surfaceType, object additionalRouteVals) { - return html.BeginUmbracoForm(action, surfaceId, additionalRouteVals, new Dictionary()); + return html.BeginUmbracoForm(action, surfaceType, additionalRouteVals, new Dictionary()); + } + + /// + /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin + /// + /// + /// + /// + /// + /// + public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, object additionalRouteVals) + where T : SurfaceController + { + return html.BeginUmbracoForm(action, typeof (T), additionalRouteVals); } /// @@ -204,15 +227,32 @@ namespace Umbraco.Web /// /// /// - /// The surface controller to route to + /// The surface controller to route to /// /// /// - public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, Guid surfaceId, + public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, Type surfaceType, object additionalRouteVals, object htmlAttributes) { - return html.BeginUmbracoForm(action, surfaceId, additionalRouteVals, htmlAttributes.ToDictionary()); + return html.BeginUmbracoForm(action, surfaceType, additionalRouteVals, htmlAttributes.ToDictionary()); + } + + /// + /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin + /// + /// + /// + /// + /// + /// + /// + public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, + object additionalRouteVals, + object htmlAttributes) + where T: SurfaceController + { + return html.BeginUmbracoForm(action, typeof (T), additionalRouteVals, htmlAttributes); } /// @@ -220,40 +260,67 @@ namespace Umbraco.Web /// /// /// - /// The surface controller to route to + /// The surface controller to route to /// /// /// - public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, Guid surfaceId, + public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, Type surfaceType, object additionalRouteVals, IDictionary htmlAttributes) + { + Mandate.ParameterNotNullOrEmpty(action, "action"); + Mandate.ParameterNotNull(surfaceType, "surfaceType"); + + var area = Umbraco.Core.Configuration.GlobalSettings.UmbracoMvcArea; + var surfaceController = SurfaceControllerResolver.Current.SurfaceControllers + .SingleOrDefault(x => x.Metadata.ControllerType == surfaceType); + if (surfaceController == null) + throw new InvalidOperationException("Could not find the surface controller of type " + surfaceType.FullName); + if (!surfaceController.Metadata.AreaName.IsNullOrWhiteSpace()) + { + //set the area to the plugin area + area = surfaceController.Metadata.AreaName; + } + return html.BeginUmbracoForm(action, surfaceController.Metadata.ControllerName, area, additionalRouteVals, htmlAttributes); + } + + /// + /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin + /// + /// + /// + /// + /// + /// + /// + public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, + object additionalRouteVals, + IDictionary htmlAttributes) + where T: SurfaceController { - //TODO: Make me work :) + return html.BeginUmbracoForm(action, typeof (T), additionalRouteVals, htmlAttributes); + } - throw new NotImplementedException(); + /// + /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin + /// + /// + /// + /// + /// + /// + /// + /// + public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, string controllerName, string area, + object additionalRouteVals, + IDictionary htmlAttributes) + { + Mandate.ParameterNotNullOrEmpty(area, "area"); + Mandate.ParameterNotNullOrEmpty(action, "action"); + Mandate.ParameterNotNullOrEmpty(controllerName, "controllerName"); - //var settings = DependencyResolver.Current.GetService(); - //var area = Umbraco.Core.Configuration.GlobalSettings.MvcArea; - //var formAction = html.ViewContext.HttpContext.Request.Url.AbsolutePath; - - //var surfaceMetadata = DependencyResolver.Current.GetService() - // .SurfaceControllers - // .Where(x => x.Metadata.Id == surfaceId) - // .SingleOrDefault(); - //if (surfaceMetadata == null) - // throw new InvalidOperationException("Could not find the surface controller with id " + surfaceId); - ////now, need to figure out what area this surface controller belongs too... - //var pluginDefition = surfaceMetadata.Metadata.PluginDefinition; - //if (pluginDefition.HasRoutablePackageArea()) - //{ - // //a plugin def CAN be null, if the plugin is actually in our Web DLL folder or if someone drops their - // //dll into the bin... though if they do that it still wont work since they wont get an area registered. - // //area = PluginManager.GetPackageFolderFromPluginDll(pluginDefition.OriginalAssemblyFile).Name; - // area = pluginDefition.PackageName; - //} - - ////render the form - //return html.RenderForm(formAction, FormMethod.Post, htmlAttributes, surfaceMetadata.Metadata.ControllerName, action, area, surfaceId); + var formAction = UmbracoContext.Current.RequestUrl.ToString(); + return html.RenderForm(formAction, FormMethod.Post, htmlAttributes, controllerName, action, area, additionalRouteVals); } /// @@ -265,8 +332,7 @@ namespace Umbraco.Web /// /// /// - /// - /// + /// /// /// /// @@ -279,7 +345,6 @@ namespace Umbraco.Web string surfaceController, string surfaceAction, string area, - Guid? surfaceId, object additionalRouteVals = null) { @@ -298,7 +363,7 @@ namespace Umbraco.Web htmlHelper.ViewContext.Writer.Write(tagBuilder.ToString(TagRenderMode.StartTag)); //new UmbracoForm: - var theForm = new UmbracoForm(htmlHelper.ViewContext, surfaceController, surfaceAction, area, surfaceId, additionalRouteVals); + var theForm = new UmbracoForm(htmlHelper.ViewContext, surfaceController, surfaceAction, area, additionalRouteVals); if (traditionalJavascriptEnabled) { diff --git a/src/Umbraco.Web/IPublishedContentStore.cs b/src/Umbraco.Web/IPublishedContentStore.cs index 9195e8bc7d..c1ea212343 100644 --- a/src/Umbraco.Web/IPublishedContentStore.cs +++ b/src/Umbraco.Web/IPublishedContentStore.cs @@ -5,7 +5,7 @@ namespace Umbraco.Web /// /// Defines the methods to access published content /// - internal interface IPublishedContentStore : IPublishedStore + public interface IPublishedContentStore : IPublishedStore { IDocument GetDocumentByRoute(UmbracoContext umbracoContext, string route, bool? hideTopLevelNode = null); IDocument GetDocumentByUrlAlias(UmbracoContext umbracoContext, int rootNodeId, string alias); diff --git a/src/Umbraco.Web/IPublishedMediaStore.cs b/src/Umbraco.Web/IPublishedMediaStore.cs index 82f8b8e15b..bedb5cb459 100644 --- a/src/Umbraco.Web/IPublishedMediaStore.cs +++ b/src/Umbraco.Web/IPublishedMediaStore.cs @@ -3,7 +3,7 @@ namespace Umbraco.Web /// /// Defines the methods to access published media /// - internal interface IPublishedMediaStore : IPublishedStore + public interface IPublishedMediaStore : IPublishedStore { } } \ No newline at end of file diff --git a/src/Umbraco.Web/IPublishedStore.cs b/src/Umbraco.Web/IPublishedStore.cs index 807730550a..c3867a1bff 100644 --- a/src/Umbraco.Web/IPublishedStore.cs +++ b/src/Umbraco.Web/IPublishedStore.cs @@ -5,7 +5,7 @@ namespace Umbraco.Web /// /// Defines the methods for published documents /// - internal interface IPublishedStore + public interface IPublishedStore { IDocument GetDocumentById(UmbracoContext umbracoContext, int nodeId); } diff --git a/src/Umbraco.Web/Mvc/PluginController.cs b/src/Umbraco.Web/Mvc/PluginController.cs index 355aca89e6..62c51e2172 100644 --- a/src/Umbraco.Web/Mvc/PluginController.cs +++ b/src/Umbraco.Web/Mvc/PluginController.cs @@ -8,12 +8,13 @@ namespace Umbraco.Web.Mvc /// /// A base class for all plugin controllers to inherit from /// - public abstract class PluginController : Controller, IRequiresUmbracoContext + public abstract class PluginController : Controller { /// /// stores the metadata about plugin controllers /// - private static readonly ConcurrentDictionary Metadata = new ConcurrentDictionary(); + private static readonly ConcurrentDictionary MetadataStorage = new ConcurrentDictionary(); + /// /// Default constructor @@ -21,39 +22,76 @@ namespace Umbraco.Web.Mvc /// protected PluginController(UmbracoContext umbracoContext) { + if (umbracoContext == null) throw new ArgumentNullException("umbracoContext"); UmbracoContext = umbracoContext; InstanceId = Guid.NewGuid(); } + public IPublishedContentStore PublishedContentStore + { + get { return PublishedContentStoreResolver.Current.PublishedContentStore; } + } + + public IPublishedMediaStore PublishedMediaStore + { + get { return PublishedMediaStoreResolver.Current.PublishedMediaStore; } + } + /// /// Useful for debugging /// internal Guid InstanceId { get; private set; } - public UmbracoContext UmbracoContext { get; set; } + /// + /// Returns the current UmbracoContext + /// + public UmbracoContext UmbracoContext { get; private set; } + + /// + /// Returns the current ApplicationContext + /// + public ApplicationContext ApplicationContext + { + get { return UmbracoContext.Application; } + } /// /// Returns the metadata for this instance /// - internal PluginControllerMetadata GetMetadata() + internal PluginControllerMetadata Metadata { + get { return GetMetadata(this.GetType()); } + } + + /// + /// Returns the metadata for a PluginController + /// + /// + /// + internal static PluginControllerMetadata GetMetadata(Type type) + { + if (!TypeHelper.IsTypeAssignableFrom(type)) + { + throw new InvalidOperationException("Cannot get metadata from a type that is not a PluginController"); + } + PluginControllerMetadata meta; - if (Metadata.TryGetValue(this.GetType(), out meta)) + if (MetadataStorage.TryGetValue(type, out meta)) { return meta; } - var attribute = this.GetType().GetCustomAttribute(false); + var attribute = type.GetCustomAttribute(false); meta = new PluginControllerMetadata() { AreaName = attribute == null ? null : attribute.AreaName, - ControllerName = ControllerExtensions.GetControllerName(this.GetType()), - ControllerNamespace = this.GetType().Namespace, - ControllerType = this.GetType() + ControllerName = ControllerExtensions.GetControllerName(type), + ControllerNamespace = type.Namespace, + ControllerType = type }; - Metadata.TryAdd(this.GetType(), meta); + MetadataStorage.TryAdd(type, meta); return meta; } diff --git a/src/Umbraco.Web/Mvc/PluginControllerArea.cs b/src/Umbraco.Web/Mvc/PluginControllerArea.cs index fe981b815a..9e221fe667 100644 --- a/src/Umbraco.Web/Mvc/PluginControllerArea.cs +++ b/src/Umbraco.Web/Mvc/PluginControllerArea.cs @@ -13,7 +13,7 @@ namespace Umbraco.Web.Mvc /// internal class PluginControllerArea : AreaRegistration { - private readonly IEnumerable _surfaceControllers; + private readonly IEnumerable _surfaceControllers; private readonly string _areaName; /// @@ -21,26 +21,26 @@ namespace Umbraco.Web.Mvc /// based on their PluginControllerAttribute. If they are not the same an exception will be thrown. /// /// - public PluginControllerArea(IEnumerable pluginControllers) + public PluginControllerArea(IEnumerable pluginControllers) { //TODO: When we have other future plugin controllers we need to combine them all into one list here to do our validation. var controllers = pluginControllers.ToArray(); - if (controllers.Any(x => x.GetMetadata().AreaName.IsNullOrWhiteSpace())) + if (controllers.Any(x => x.AreaName.IsNullOrWhiteSpace())) { throw new InvalidOperationException("Cannot create a PluginControllerArea unless all plugin controllers assigned have a PluginControllerAttribute assigned"); } - _areaName = controllers.First().GetMetadata().AreaName; + _areaName = controllers.First().AreaName; foreach(var c in controllers) { - if (c.GetMetadata().AreaName != _areaName) + if (c.AreaName != _areaName) { - throw new InvalidOperationException("Cannot create a PluginControllerArea unless all plugin controllers assigned have the same AreaName. The first AreaName found was " + _areaName + " however, the controller of type " + c.GetType().FullName + " has an AreaName of " + c.GetMetadata().AreaName); + throw new InvalidOperationException("Cannot create a PluginControllerArea unless all plugin controllers assigned have the same AreaName. The first AreaName found was " + _areaName + " however, the controller of type " + c.GetType().FullName + " has an AreaName of " + c.AreaName); } } //get the surface controllers - _surfaceControllers = controllers.OfType(); + _surfaceControllers = controllers.Where(x => TypeHelper.IsTypeAssignableFrom(x.ControllerType)); } public override void RegisterArea(AreaRegistrationContext context) @@ -58,12 +58,11 @@ namespace Umbraco.Web.Mvc /// /// /// - private void MapRouteSurfaceControllers(RouteCollection routes, IEnumerable surfaceControllers) + private void MapRouteSurfaceControllers(RouteCollection routes, IEnumerable surfaceControllers) { foreach (var s in surfaceControllers) { - var meta = s.GetMetadata(); - this.RouteControllerPlugin(meta.ControllerName, meta.ControllerType, routes, "Surface", "Index", UrlParameter.Optional, "surface"); + this.RouteControllerPlugin(s.ControllerName, s.ControllerType, routes, "Surface", "Index", UrlParameter.Optional, "surface"); } } } diff --git a/src/Umbraco.Web/Mvc/PostedDataProxyInfo.cs b/src/Umbraco.Web/Mvc/PostedDataProxyInfo.cs index eb664fba0c..fef1ba8647 100644 --- a/src/Umbraco.Web/Mvc/PostedDataProxyInfo.cs +++ b/src/Umbraco.Web/Mvc/PostedDataProxyInfo.cs @@ -8,6 +8,5 @@ namespace Umbraco.Web.Mvc internal class PostedDataProxyInfo : RouteDefinition { public string Area { get; set; } - public Guid SurfaceId { get; set; } } } \ No newline at end of file diff --git a/src/Umbraco.Web/Mvc/RenderRouteHandler.cs b/src/Umbraco.Web/Mvc/RenderRouteHandler.cs index e2f5603b95..0dc3b629c9 100644 --- a/src/Umbraco.Web/Mvc/RenderRouteHandler.cs +++ b/src/Umbraco.Web/Mvc/RenderRouteHandler.cs @@ -90,21 +90,17 @@ namespace Umbraco.Web.Mvc if (!decodedParts.Any(x => x.Key == "ar")) return null; - //the surface id - if (decodedParts.Any(x => x.Key == "i")) - { - Guid id; - if (Guid.TryParse(decodedParts.Single(x => x.Key == "i").Value, out id)) - { - return new PostedDataProxyInfo - { - ControllerName = requestContext.HttpContext.Server.UrlDecode(decodedParts.Single(x => x.Key == "c").Value), - ActionName = requestContext.HttpContext.Server.UrlDecode(decodedParts.Single(x => x.Key == "a").Value), - Area = requestContext.HttpContext.Server.UrlDecode(decodedParts.Single(x => x.Key == "ar").Value), - SurfaceId = id, - }; - } - } + ////the controller type, if it contains this then it is a plugin controller, not locally declared. + //if (decodedParts.Any(x => x.Key == "t")) + //{ + // return new PostedDataProxyInfo + // { + // ControllerName = requestContext.HttpContext.Server.UrlDecode(decodedParts.Single(x => x.Key == "c").Value), + // ActionName = requestContext.HttpContext.Server.UrlDecode(decodedParts.Single(x => x.Key == "a").Value), + // Area = requestContext.HttpContext.Server.UrlDecode(decodedParts.Single(x => x.Key == "ar").Value), + // ControllerType = requestContext.HttpContext.Server.UrlDecode(decodedParts.Single(x => x.Key == "t").Value) + // }; + //} //return the proxy info without the surface id... could be a local controller. return new PostedDataProxyInfo @@ -122,10 +118,10 @@ namespace Umbraco.Web.Mvc /// /// /// - /// /// The original route definition that would normally be used to route if it were not a POST - private IHttpHandler HandlePostedValues(RequestContext requestContext, PostedDataProxyInfo postedInfo, IDocument document, RouteDefinition routeDefinition) + private IHttpHandler HandlePostedValues(RequestContext requestContext, PostedDataProxyInfo postedInfo, RouteDefinition routeDefinition) { + var standardArea = Umbraco.Core.Configuration.GlobalSettings.UmbracoMvcArea; //set the standard route values/tokens requestContext.RouteData.Values["controller"] = postedInfo.ControllerName; @@ -134,19 +130,21 @@ namespace Umbraco.Web.Mvc IHttpHandler handler = new MvcHandler(requestContext); - //ensure the surface id is set if found, meaning it is a plugin, not locally declared - if (postedInfo.SurfaceId != default(Guid)) + //ensure the controllerType is set if found, meaning it is a plugin, not locally declared + if (postedInfo.Area != standardArea) { - requestContext.RouteData.Values["surfaceId"] = postedInfo.SurfaceId.ToString("N"); + //requestContext.RouteData.Values["controllerType"] = postedInfo.ControllerType; //find the other data tokens for this route and merge... things like Namespace will be included here using (RouteTable.Routes.GetReadLock()) { var surfaceRoute = RouteTable.Routes.OfType() - .Where(x => x.Defaults != null && x.Defaults.ContainsKey("surfaceId") && - x.Defaults["surfaceId"].ToString() == postedInfo.SurfaceId.ToString("N")) - .SingleOrDefault(); + .SingleOrDefault(x => x.Defaults != null && + x.Defaults.ContainsKey("controller") && + x.Defaults["controller"].ToString() == postedInfo.ControllerName && + x.DataTokens.ContainsKey("area") && + x.DataTokens["area"].ToString() == postedInfo.Area); if (surfaceRoute == null) - throw new InvalidOperationException("Could not find a Surface controller route in the RouteTable for id " + postedInfo.SurfaceId); + throw new InvalidOperationException("Could not find a Surface controller route in the RouteTable for controller name " + postedInfo.ControllerName + " and within the area of " + postedInfo.Area); //set the 'Namespaces' token so the controller factory knows where to look to construct it if (surfaceRoute.DataTokens.ContainsKey("Namespaces")) { @@ -235,6 +233,13 @@ namespace Umbraco.Web.Mvc { var routeDef = GetUmbracoRouteDefinition(requestContext, documentRequest); + //Need to check for a special case if there is form data being posted back to an Umbraco URL + var postedInfo = GetPostedFormInfo(requestContext); + if (postedInfo != null) + { + return HandlePostedValues(requestContext, postedInfo, routeDef); + } + //here we need to check if there is no hijacked route and no template assigned, if this is the case //we want to return a blank page, but we'll leave that up to the NoTemplateHandler. if (!documentRequest.HasTemplate && !routeDef.HasHijackedRoute) diff --git a/src/Umbraco.Web/Mvc/SurfaceController.cs b/src/Umbraco.Web/Mvc/SurfaceController.cs index 6d13f3b130..95aa12a544 100644 --- a/src/Umbraco.Web/Mvc/SurfaceController.cs +++ b/src/Umbraco.Web/Mvc/SurfaceController.cs @@ -14,6 +14,26 @@ namespace Umbraco.Web.Mvc return View(); //return Content("hello"); } + + public ActionResult PostVals(string name) + { + ModelState.AddModelError("name", "bad name!"); + return CurrentUmbracoPage(); + } + } + + public class LocalSurfaceController : SurfaceController + { + public ActionResult Index() + { + return View(); + } + + public ActionResult PostVals([Bind(Prefix = "blah")]string name) + { + ModelState.AddModelError("name", "you suck!"); + return this.RedirectToCurrentUmbracoPage(); + } } /// diff --git a/src/Umbraco.Web/Mvc/SurfaceControllerResolver.cs b/src/Umbraco.Web/Mvc/SurfaceControllerResolver.cs index ec835ee00f..e9e60e1c58 100644 --- a/src/Umbraco.Web/Mvc/SurfaceControllerResolver.cs +++ b/src/Umbraco.Web/Mvc/SurfaceControllerResolver.cs @@ -19,5 +19,13 @@ namespace Umbraco.Web.Mvc { get { return Values; } } + + /// + /// Gets all of the surface controller types + /// + public IEnumerable RegisteredSurfaceControllers + { + get { return InstanceTypes; } + } } } \ No newline at end of file diff --git a/src/Umbraco.Web/Mvc/UmbracoPageResult.cs b/src/Umbraco.Web/Mvc/UmbracoPageResult.cs index 63919c1d5c..db0f352645 100644 --- a/src/Umbraco.Web/Mvc/UmbracoPageResult.cs +++ b/src/Umbraco.Web/Mvc/UmbracoPageResult.cs @@ -25,6 +25,9 @@ namespace Umbraco.Web.Mvc var routeDef = (RouteDefinition)context.RouteData.DataTokens["umbraco-route-def"]; + //ensure the original template is reset + context.RouteData.Values["action"] = routeDef.ActionName; + //ensure ModelState is copied across routeDef.Controller.ViewData.ModelState.Merge(context.Controller.ViewData.ModelState); diff --git a/src/Umbraco.Web/Mvc/web.config.template b/src/Umbraco.Web/Mvc/web.config.template index 4c30ef2275..6685adb973 100644 --- a/src/Umbraco.Web/Mvc/web.config.template +++ b/src/Umbraco.Web/Mvc/web.config.template @@ -16,6 +16,7 @@ + diff --git a/src/Umbraco.Web/UmbracoContext.cs b/src/Umbraco.Web/UmbracoContext.cs index bfbaf2ae38..2804c00dc6 100644 --- a/src/Umbraco.Web/UmbracoContext.cs +++ b/src/Umbraco.Web/UmbracoContext.cs @@ -16,10 +16,6 @@ using Examine; namespace Umbraco.Web { - public interface IRequiresUmbracoContext - { - UmbracoContext UmbracoContext { get; set; } - } /// /// Class that encapsulates Umbraco information of a specific HTTP request diff --git a/src/Umbraco.Web/WebBootManager.cs b/src/Umbraco.Web/WebBootManager.cs index e44528525c..9fff62b2db 100644 --- a/src/Umbraco.Web/WebBootManager.cs +++ b/src/Umbraco.Web/WebBootManager.cs @@ -128,13 +128,13 @@ namespace Umbraco.Web var umbracoPath = GlobalSettings.UmbracoMvcArea; //we need to find the surface controllers and route them - var surfaceControllers = SurfaceControllerResolver.Current.SurfaceControllers.ToArray(); + var surfaceControllers = SurfaceControllerResolver.Current.RegisteredSurfaceControllers.ToArray(); //local surface controllers do not contain the attribute - var localSurfaceControlleres = surfaceControllers.Where(x => x.GetType().GetCustomAttribute(false) == null); + var localSurfaceControlleres = surfaceControllers.Where(x => PluginController.GetMetadata(x).AreaName.IsNullOrWhiteSpace()); foreach (var s in localSurfaceControlleres) { - var meta = s.GetMetadata(); + var meta = PluginController.GetMetadata(s); var route = RouteTable.Routes.MapRoute( string.Format("umbraco-{0}-{1}", "surface", meta.ControllerName), umbracoPath + "/Surface/" + meta.ControllerName + "/{action}/{id}",//url to match @@ -146,12 +146,13 @@ namespace Umbraco.Web //need to get the plugin controllers that are unique to each area (group by) //TODO: One day when we have more plugin controllers, we will need to do a group by on ALL of them to pass into the ctor of PluginControllerArea - var groupedAreas = surfaceControllers.GroupBy(controller => controller.GetMetadata().AreaName); + var pluginSurfaceControlleres = surfaceControllers.Where(x => !PluginController.GetMetadata(x).AreaName.IsNullOrWhiteSpace()); + var groupedAreas = pluginSurfaceControlleres.GroupBy(controller => PluginController.GetMetadata(controller).AreaName); //loop through each area defined amongst the controllers foreach(var g in groupedAreas) { //create an area for the controllers (this will throw an exception if all controllers are not in the same area) - var pluginControllerArea = new PluginControllerArea(g); + var pluginControllerArea = new PluginControllerArea(g.Select(PluginController.GetMetadata)); //register it RouteTable.Routes.RegisterArea(pluginControllerArea); } From 05add9629411dac2dc848d928169b07230c8f486 Mon Sep 17 00:00:00 2001 From: Shannon Deminick Date: Wed, 26 Sep 2012 13:43:47 +0700 Subject: [PATCH 10/22] Ensures web.config includes new namespace reference --- src/Umbraco.Web.UI/Views/Web.config | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Umbraco.Web.UI/Views/Web.config b/src/Umbraco.Web.UI/Views/Web.config index 4c30ef2275..6685adb973 100644 --- a/src/Umbraco.Web.UI/Views/Web.config +++ b/src/Umbraco.Web.UI/Views/Web.config @@ -16,6 +16,7 @@ + From 53a3ad24e658fdf3a32df6f748769a14ef732539 Mon Sep 17 00:00:00 2001 From: Shannon Deminick Date: Wed, 26 Sep 2012 13:44:23 +0700 Subject: [PATCH 11/22] comments out test surface controllers for now --- src/Umbraco.Web/Mvc/SurfaceController.cs | 52 ++++++++++++------------ 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/src/Umbraco.Web/Mvc/SurfaceController.cs b/src/Umbraco.Web/Mvc/SurfaceController.cs index 95aa12a544..0bd4938d0f 100644 --- a/src/Umbraco.Web/Mvc/SurfaceController.cs +++ b/src/Umbraco.Web/Mvc/SurfaceController.cs @@ -6,35 +6,35 @@ using Umbraco.Core; namespace Umbraco.Web.Mvc { - [PluginController("MyTestSurfaceController")] - public class TestSurfaceController : SurfaceController - { - public ActionResult Index() - { - return View(); - //return Content("hello"); - } + //[PluginController("MyTestSurfaceController")] + //public class TestSurfaceController : SurfaceController + //{ + // public ActionResult Index() + // { + // return View(); + // //return Content("hello"); + // } - public ActionResult PostVals(string name) - { - ModelState.AddModelError("name", "bad name!"); - return CurrentUmbracoPage(); - } - } + // public ActionResult PostVals(string name) + // { + // ModelState.AddModelError("name", "bad name!"); + // return CurrentUmbracoPage(); + // } + //} - public class LocalSurfaceController : SurfaceController - { - public ActionResult Index() - { - return View(); - } + //public class LocalSurfaceController : SurfaceController + //{ + // public ActionResult Index() + // { + // return View(); + // } - public ActionResult PostVals([Bind(Prefix = "blah")]string name) - { - ModelState.AddModelError("name", "you suck!"); - return this.RedirectToCurrentUmbracoPage(); - } - } + // public ActionResult PostVals([Bind(Prefix = "blah")]string name) + // { + // ModelState.AddModelError("name", "you suck!"); + // return this.RedirectToCurrentUmbracoPage(); + // } + //} /// /// The base controller that all Presentation Add-in controllers should inherit from From d3676b376d88b54b53e4db9bb99f212e44da992f Mon Sep 17 00:00:00 2001 From: Shannon Deminick Date: Wed, 26 Sep 2012 13:59:24 +0700 Subject: [PATCH 12/22] Removed test route --- src/Umbraco.Web/WebBootManager.cs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/Umbraco.Web/WebBootManager.cs b/src/Umbraco.Web/WebBootManager.cs index 9fff62b2db..36c4b2ff44 100644 --- a/src/Umbraco.Web/WebBootManager.cs +++ b/src/Umbraco.Web/WebBootManager.cs @@ -156,12 +156,6 @@ namespace Umbraco.Web //register it RouteTable.Routes.RegisterArea(pluginControllerArea); } - - RouteTable.Routes.MapRoute( - "Account", - "account/{action}/{id}", - new { controller = "Account", action = "Index", id = UrlParameter.Optional } - ); } From 587542ace76d951ac4b30f7b29666c66cfbeb9d8 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Wed, 26 Sep 2012 06:48:11 -0200 Subject: [PATCH 13/22] The build.bat file now won't exit when there's an error so you can inspect it, if there's no errors it will just exit Excluded missing index.cshtml file from project (was giving build error) --- build/Build.bat | 9 ++++++++- src/Umbraco.Web.UI/Umbraco.Web.UI.csproj | 2 +- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/build/Build.bat b/build/Build.bat index 76cf1b11fe..aca687fdcc 100644 --- a/build/Build.bat +++ b/build/Build.bat @@ -1,2 +1,9 @@ +@ECHO OFF %windir%\Microsoft.NET\Framework\v4.0.30319\msbuild.exe "Build.proj" -REM pause \ No newline at end of file + +if ERRORLEVEL 1 goto :showerror + +goto :EOF + +:showerror +pause \ No newline at end of file diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index 94a9ca9eb1..1f249c977b 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -1664,7 +1664,6 @@ - Web.Template.config Designer @@ -2198,6 +2197,7 @@ + From f76e90990f3e94121c98eec2da1faf620e8de4be Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Wed, 26 Sep 2012 11:58:52 -0200 Subject: [PATCH 14/22] Include packages.config anyway, just tell it to not output in the build --- src/Umbraco.Web.UI/Umbraco.Web.UI.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index 1f249c977b..95de9cc05b 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -317,6 +317,7 @@ Dashboard.config + UI.xml From 530e7ae44ed5b9c80d63e2dbef7580fcd9b4cdb8 Mon Sep 17 00:00:00 2001 From: Shannon Deminick Date: Thu, 27 Sep 2012 07:06:14 +0700 Subject: [PATCH 15/22] removed ref to missing template file --- src/Umbraco.Web.UI/Umbraco.Web.UI.csproj | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index 95de9cc05b..140edb2b31 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -2198,7 +2198,6 @@ - From f7f83bc05727bbbe0483b52feaf403ca68f6fa76 Mon Sep 17 00:00:00 2001 From: Shannon Deminick Date: Thu, 27 Sep 2012 08:30:35 +0700 Subject: [PATCH 16/22] Changed IPublishedContentStore and IPublishedMediaStore back to internal as we don't need to expose these yet. Instead have added an UmbracoHelper property to the PluginController class which will expose any methods needed by devs to retreive media or content. Have updated UmbracoHelper with loads of new helpful methods and moved the Wrap methods to HtmlHelper extensions because this is purely to do with rendering html (have written unit tests for it too). Updated some 'library' methods to proxy calls to UmbracoHelper so we only have to maintain one set of code. Added a convenience property to UmbracoContext to return the current NiceUrlProvider so you don't have to go through the RoutingContext to get it. --- .../HtmlHelperExtensionMethodsTests.cs | 33 +++ src/Umbraco.Tests/StringExtensionsTests.cs | 1 - src/Umbraco.Tests/Umbraco.Tests.csproj | 1 + src/Umbraco.Web.UI/Web.config | 2 +- src/Umbraco.Web/HtmlHelperRenderExtensions.cs | 244 ++++++++++------ src/Umbraco.Web/IPublishedContentStore.cs | 2 +- src/Umbraco.Web/IPublishedMediaStore.cs | 2 +- src/Umbraco.Web/Mvc/PluginController.cs | 16 +- src/Umbraco.Web/Mvc/RenderViewPage.cs | 9 +- src/Umbraco.Web/UmbracoContext.cs | 16 ++ src/Umbraco.Web/UmbracoHelper.cs | 262 +++++++++++++----- .../umbraco.presentation/library.cs | 67 ++--- .../RazorCore/RazorMacroEngine.cs | 10 +- .../RazorDynamicNode/RazorLibraryCore.cs | 20 +- 14 files changed, 447 insertions(+), 238 deletions(-) create mode 100644 src/Umbraco.Tests/HtmlHelperExtensionMethodsTests.cs diff --git a/src/Umbraco.Tests/HtmlHelperExtensionMethodsTests.cs b/src/Umbraco.Tests/HtmlHelperExtensionMethodsTests.cs new file mode 100644 index 0000000000..5efabd891e --- /dev/null +++ b/src/Umbraco.Tests/HtmlHelperExtensionMethodsTests.cs @@ -0,0 +1,33 @@ +using System.Web.Mvc; +using NUnit.Framework; +using Umbraco.Web; + +namespace Umbraco.Tests +{ + [TestFixture] + public class HtmlHelperExtensionMethodsTests + { + [SetUp] + public virtual void Initialize() + { + //create an empty htmlHelper + _htmlHelper = new HtmlHelper(new ViewContext(), new ViewPage()); + } + + private HtmlHelper _htmlHelper; + + [Test] + public void Wrap_Simple() + { + var output = _htmlHelper.Wrap("div", "hello world"); + Assert.AreEqual("
hello world
", output.ToHtmlString()); + } + + [Test] + public void Wrap_Object_Attributes() + { + var output = _htmlHelper.Wrap("div", "hello world", new {style = "color:red;", onclick = "void();"}); + Assert.AreEqual("
hello world
", output.ToHtmlString()); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Tests/StringExtensionsTests.cs b/src/Umbraco.Tests/StringExtensionsTests.cs index 73e828ac73..49f8f830d7 100644 --- a/src/Umbraco.Tests/StringExtensionsTests.cs +++ b/src/Umbraco.Tests/StringExtensionsTests.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.Security; using System.Text; -using System.Web.Mvc; using NUnit.Framework; using Umbraco.Core; diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index 2d5c4776d1..b924753e6c 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -55,6 +55,7 @@ + diff --git a/src/Umbraco.Web.UI/Web.config b/src/Umbraco.Web.UI/Web.config index 3dd62de184..a860662208 100644 --- a/src/Umbraco.Web.UI/Web.config +++ b/src/Umbraco.Web.UI/Web.config @@ -36,7 +36,7 @@ - + diff --git a/src/Umbraco.Web/HtmlHelperRenderExtensions.cs b/src/Umbraco.Web/HtmlHelperRenderExtensions.cs index e91f754140..f353a07214 100644 --- a/src/Umbraco.Web/HtmlHelperRenderExtensions.cs +++ b/src/Umbraco.Web/HtmlHelperRenderExtensions.cs @@ -6,6 +6,7 @@ using System.Text; using System.Web.Mvc; using System.Web.Mvc.Html; using Umbraco.Core; +using Umbraco.Core.Dynamics; using Umbraco.Web.Mvc; using umbraco; @@ -16,59 +17,6 @@ namespace Umbraco.Web ///
public static class HtmlHelperRenderExtensions { - /// - /// Used for rendering out the Form for BeginUmbracoForm - /// - internal class UmbracoForm : MvcForm - { - /// - /// Creates an UmbracoForm - /// - /// - /// - /// - /// - /// - public UmbracoForm( - ViewContext viewContext, - string surfaceController, - string surfaceAction, - string area, - object additionalRouteVals = null) - : base(viewContext) - { - //need to create a params string as Base64 to put into our hidden field to use during the routes - var surfaceRouteParams = string.Format("c={0}&a={1}&ar={2}", - viewContext.HttpContext.Server.UrlEncode(surfaceController), - viewContext.HttpContext.Server.UrlEncode(surfaceAction), - area); - - var additionalRouteValsAsQuery = additionalRouteVals.ToDictionary().ToQueryString(); - if (!additionalRouteValsAsQuery.IsNullOrWhiteSpace()) - surfaceRouteParams = "&" + additionalRouteValsAsQuery; - - _base64String = Convert.ToBase64String(Encoding.UTF8.GetBytes(surfaceRouteParams)); - - _textWriter = viewContext.Writer; - } - - - private bool _disposed; - private readonly string _base64String; - private readonly TextWriter _textWriter; - - protected override void Dispose(bool disposing) - { - if (this._disposed) - return; - this._disposed = true; - - //write out the hidden surface form routes - _textWriter.Write(""); - - base.Dispose(disposing); - } - } public static MvcHtmlString EditorFor(this HtmlHelper htmlHelper, string templateName = "", string htmlFieldName = "", object additionalViewData = null) where T : new() @@ -108,6 +56,63 @@ namespace Umbraco.Web return filteredHtmlHelper.ValidationSummary(excludePropertyErrors, message, htmlAttributes); } + #region BeginUmbracoForm + + /// + /// Used for rendering out the Form for BeginUmbracoForm + /// + internal class UmbracoForm : MvcForm + { + /// + /// Creates an UmbracoForm + /// + /// + /// + /// + /// + /// + public UmbracoForm( + ViewContext viewContext, + string surfaceController, + string surfaceAction, + string area, + object additionalRouteVals = null) + : base(viewContext) + { + //need to create a params string as Base64 to put into our hidden field to use during the routes + var surfaceRouteParams = string.Format("c={0}&a={1}&ar={2}", + viewContext.HttpContext.Server.UrlEncode(surfaceController), + viewContext.HttpContext.Server.UrlEncode(surfaceAction), + area); + + var additionalRouteValsAsQuery = additionalRouteVals.ToDictionary().ToQueryString(); + if (!additionalRouteValsAsQuery.IsNullOrWhiteSpace()) + surfaceRouteParams = "&" + additionalRouteValsAsQuery; + + _base64String = Convert.ToBase64String(Encoding.UTF8.GetBytes(surfaceRouteParams)); + + _textWriter = viewContext.Writer; + } + + + private bool _disposed; + private readonly string _base64String; + private readonly TextWriter _textWriter; + + protected override void Dispose(bool disposing) + { + if (this._disposed) + return; + this._disposed = true; + + //write out the hidden surface form routes + _textWriter.Write(""); + + base.Dispose(disposing); + } + } + + /// /// Helper method to create a new form to execute in the Umbraco request pipeline against a locally declared controller /// @@ -143,8 +148,8 @@ namespace Umbraco.Web /// /// public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, string controllerName, - object additionalRouteVals, - object htmlAttributes) + object additionalRouteVals, + object htmlAttributes) { return html.BeginUmbracoForm(action, controllerName, additionalRouteVals, htmlAttributes.ToDictionary()); } @@ -159,11 +164,11 @@ namespace Umbraco.Web /// /// public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, string controllerName, - object additionalRouteVals, - IDictionary htmlAttributes) + object additionalRouteVals, + IDictionary htmlAttributes) { Mandate.ParameterNotNullOrEmpty(action, "action"); - Mandate.ParameterNotNullOrEmpty(controllerName, "controllerName"); + Mandate.ParameterNotNullOrEmpty(controllerName, "controllerName"); var area = Umbraco.Core.Configuration.GlobalSettings.UmbracoMvcArea; return html.BeginUmbracoForm(action, controllerName, area, additionalRouteVals, htmlAttributes); @@ -189,9 +194,9 @@ namespace Umbraco.Web /// /// public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action) - where T: SurfaceController + where T : SurfaceController { - return html.BeginUmbracoForm(action, typeof (T)); + return html.BeginUmbracoForm(action, typeof(T)); } /// @@ -203,7 +208,7 @@ namespace Umbraco.Web /// /// public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, Type surfaceType, - object additionalRouteVals) + object additionalRouteVals) { return html.BeginUmbracoForm(action, surfaceType, additionalRouteVals, new Dictionary()); } @@ -219,7 +224,7 @@ namespace Umbraco.Web public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, object additionalRouteVals) where T : SurfaceController { - return html.BeginUmbracoForm(action, typeof (T), additionalRouteVals); + return html.BeginUmbracoForm(action, typeof(T), additionalRouteVals); } /// @@ -232,8 +237,8 @@ namespace Umbraco.Web /// /// public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, Type surfaceType, - object additionalRouteVals, - object htmlAttributes) + object additionalRouteVals, + object htmlAttributes) { return html.BeginUmbracoForm(action, surfaceType, additionalRouteVals, htmlAttributes.ToDictionary()); } @@ -247,12 +252,12 @@ namespace Umbraco.Web /// /// /// - public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, - object additionalRouteVals, - object htmlAttributes) - where T: SurfaceController + public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, + object additionalRouteVals, + object htmlAttributes) + where T : SurfaceController { - return html.BeginUmbracoForm(action, typeof (T), additionalRouteVals, htmlAttributes); + return html.BeginUmbracoForm(action, typeof(T), additionalRouteVals, htmlAttributes); } /// @@ -265,9 +270,9 @@ namespace Umbraco.Web /// /// public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, Type surfaceType, - object additionalRouteVals, - IDictionary htmlAttributes) - { + object additionalRouteVals, + IDictionary htmlAttributes) + { Mandate.ParameterNotNullOrEmpty(action, "action"); Mandate.ParameterNotNull(surfaceType, "surfaceType"); @@ -279,7 +284,7 @@ namespace Umbraco.Web if (!surfaceController.Metadata.AreaName.IsNullOrWhiteSpace()) { //set the area to the plugin area - area = surfaceController.Metadata.AreaName; + area = surfaceController.Metadata.AreaName; } return html.BeginUmbracoForm(action, surfaceController.Metadata.ControllerName, area, additionalRouteVals, htmlAttributes); } @@ -293,12 +298,12 @@ namespace Umbraco.Web /// /// /// - public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, - object additionalRouteVals, - IDictionary htmlAttributes) - where T: SurfaceController + public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, + object additionalRouteVals, + IDictionary htmlAttributes) + where T : SurfaceController { - return html.BeginUmbracoForm(action, typeof (T), additionalRouteVals, htmlAttributes); + return html.BeginUmbracoForm(action, typeof(T), additionalRouteVals, htmlAttributes); } /// @@ -339,13 +344,13 @@ namespace Umbraco.Web /// This code is pretty much the same as the underlying MVC code that writes out the form /// private static MvcForm RenderForm(this HtmlHelper htmlHelper, - string formAction, - FormMethod method, - IDictionary htmlAttributes, - string surfaceController, - string surfaceAction, - string area, - object additionalRouteVals = null) + string formAction, + FormMethod method, + IDictionary htmlAttributes, + string surfaceController, + string surfaceAction, + string area, + object additionalRouteVals = null) { var tagBuilder = new TagBuilder("form"); @@ -372,6 +377,75 @@ namespace Umbraco.Web return theForm; } + #endregion + + + #region Wrap + + public static HtmlTagWrapper Wrap(this HtmlHelper html, string tag, string innerText, params IHtmlTagWrapper[] children) + { + var item = html.Wrap(tag, innerText, (object)null); + foreach (var child in children) + { + item.AddChild(child); + } + return item; + } + + public static HtmlTagWrapper Wrap(this HtmlHelper html, string tag, object inner, object anonymousAttributes, params IHtmlTagWrapper[] children) + { + string innerText = null; + if (inner != null && inner.GetType() != typeof(DynamicNull)) + { + innerText = string.Format("{0}", inner); + } + var item = html.Wrap(tag, innerText, anonymousAttributes); + foreach (var child in children) + { + item.AddChild(child); + } + return item; + } + public static HtmlTagWrapper Wrap(this HtmlHelper html, string tag, object inner) + { + string innerText = null; + if (inner != null && inner.GetType() != typeof(DynamicNull)) + { + innerText = string.Format("{0}", inner); + } + return html.Wrap(tag, innerText, (object)null); + } + + public static HtmlTagWrapper Wrap(this HtmlHelper html, string tag, string innerText, object anonymousAttributes, params IHtmlTagWrapper[] children) + { + var wrap = new HtmlTagWrapper(tag); + if (anonymousAttributes != null) + { + wrap.ReflectAttributesFromAnonymousType(anonymousAttributes); + } + if (!string.IsNullOrWhiteSpace(innerText)) + { + wrap.AddChild(new HtmlTagWrapperTextNode(innerText)); + } + foreach (var child in children) + { + wrap.AddChild(child); + } + return wrap; + } + + public static HtmlTagWrapper Wrap(this HtmlHelper html, bool visible, string tag, string innerText, object anonymousAttributes, params IHtmlTagWrapper[] children) + { + var item = html.Wrap(tag, innerText, anonymousAttributes, children); + item.Visible = visible; + foreach (var child in children) + { + item.AddChild(child); + } + return item; + } + + #endregion } } \ No newline at end of file diff --git a/src/Umbraco.Web/IPublishedContentStore.cs b/src/Umbraco.Web/IPublishedContentStore.cs index c1ea212343..9195e8bc7d 100644 --- a/src/Umbraco.Web/IPublishedContentStore.cs +++ b/src/Umbraco.Web/IPublishedContentStore.cs @@ -5,7 +5,7 @@ namespace Umbraco.Web /// /// Defines the methods to access published content /// - public interface IPublishedContentStore : IPublishedStore + internal interface IPublishedContentStore : IPublishedStore { IDocument GetDocumentByRoute(UmbracoContext umbracoContext, string route, bool? hideTopLevelNode = null); IDocument GetDocumentByUrlAlias(UmbracoContext umbracoContext, int rootNodeId, string alias); diff --git a/src/Umbraco.Web/IPublishedMediaStore.cs b/src/Umbraco.Web/IPublishedMediaStore.cs index bedb5cb459..82f8b8e15b 100644 --- a/src/Umbraco.Web/IPublishedMediaStore.cs +++ b/src/Umbraco.Web/IPublishedMediaStore.cs @@ -3,7 +3,7 @@ namespace Umbraco.Web /// /// Defines the methods to access published media /// - public interface IPublishedMediaStore : IPublishedStore + internal interface IPublishedMediaStore : IPublishedStore { } } \ No newline at end of file diff --git a/src/Umbraco.Web/Mvc/PluginController.cs b/src/Umbraco.Web/Mvc/PluginController.cs index 62c51e2172..dd08ba08a7 100644 --- a/src/Umbraco.Web/Mvc/PluginController.cs +++ b/src/Umbraco.Web/Mvc/PluginController.cs @@ -25,16 +25,7 @@ namespace Umbraco.Web.Mvc if (umbracoContext == null) throw new ArgumentNullException("umbracoContext"); UmbracoContext = umbracoContext; InstanceId = Guid.NewGuid(); - } - - public IPublishedContentStore PublishedContentStore - { - get { return PublishedContentStoreResolver.Current.PublishedContentStore; } - } - - public IPublishedMediaStore PublishedMediaStore - { - get { return PublishedMediaStoreResolver.Current.PublishedMediaStore; } + Umbraco = new UmbracoHelper(umbracoContext); } /// @@ -42,6 +33,11 @@ namespace Umbraco.Web.Mvc /// internal Guid InstanceId { get; private set; } + /// + /// Returns an UmbracoHelper object + /// + public UmbracoHelper Umbraco { get; private set; } + /// /// Returns the current UmbracoContext /// diff --git a/src/Umbraco.Web/Mvc/RenderViewPage.cs b/src/Umbraco.Web/Mvc/RenderViewPage.cs index 101d7fba5e..8dcf775672 100644 --- a/src/Umbraco.Web/Mvc/RenderViewPage.cs +++ b/src/Umbraco.Web/Mvc/RenderViewPage.cs @@ -51,8 +51,6 @@ namespace Umbraco.Web.Mvc /// public dynamic CurrentPage { get; private set; } - private ICultureDictionary _cultureDictionary; - /// /// Returns the dictionary value for the key specified /// @@ -60,12 +58,7 @@ namespace Umbraco.Web.Mvc /// public string GetDictionaryValue(string key) { - if (_cultureDictionary == null) - { - var factory = CultureDictionaryFactoryResolver.Current.Factory; - _cultureDictionary = factory.CreateDictionary(); - } - return _cultureDictionary[key]; + return Umbraco.GetDictionaryValue(key); } private UmbracoHelper _helper; diff --git a/src/Umbraco.Web/UmbracoContext.cs b/src/Umbraco.Web/UmbracoContext.cs index 2804c00dc6..916daddc1c 100644 --- a/src/Umbraco.Web/UmbracoContext.cs +++ b/src/Umbraco.Web/UmbracoContext.cs @@ -199,6 +199,22 @@ namespace Umbraco.Web get { return DocumentRequest != null; } } + /// + /// A shortcut to the UmbracoContext's RoutingContext's NiceUrlProvider + /// + /// + /// If the RoutingContext is null, this will throw an exception. + /// + internal NiceUrlProvider NiceUrlProvider + { + get + { + if (RoutingContext == null) + throw new InvalidOperationException("Cannot access the NiceUrlProvider when the UmbracoContext's RoutingContext is null"); + return RoutingContext.NiceUrlProvider; + } + } + /// /// Gets/sets the RoutingContext object /// diff --git a/src/Umbraco.Web/UmbracoHelper.cs b/src/Umbraco.Web/UmbracoHelper.cs index 30f88c5014..3ba60902ca 100644 --- a/src/Umbraco.Web/UmbracoHelper.cs +++ b/src/Umbraco.Web/UmbracoHelper.cs @@ -5,22 +5,27 @@ using System.Linq; using System.Text; using System.Web; using System.Web.Configuration; +using System.Web.Security; using System.Web.UI; using System.Xml.Linq; using System.Xml.XPath; using HtmlAgilityPack; using Umbraco.Core; +using Umbraco.Core.Dictionary; using Umbraco.Core.Dynamics; +using Umbraco.Core.IO; using Umbraco.Core.Models; using Umbraco.Web.Mvc; using umbraco; using System.Collections.Generic; +using umbraco.cms.businesslogic.member; +using umbraco.cms.businesslogic.web; using umbraco.presentation.templateControls; using HtmlTagWrapper = Umbraco.Web.Mvc.HtmlTagWrapper; namespace Umbraco.Web { - + /// /// A helper class that provides many useful methods and functionality for using Umbraco in templates /// @@ -191,7 +196,7 @@ namespace Umbraco.Web var attributesForItem = new AttributeCollectionAdapter( new AttributeCollection( new StateBag())); - foreach(var i in attributes) + foreach (var i in attributes) { attributesForItem.Add(i.Key, i.Value); } @@ -201,7 +206,7 @@ namespace Umbraco.Web Field = fieldAlias, TextIfEmpty = altText, LegacyAttributes = attributesForItem - }; + }; var containerPage = new FormlessPage(); containerPage.Controls.Add(item); @@ -217,8 +222,120 @@ namespace Umbraco.Web #endregion + #region Dictionary + + private ICultureDictionary _cultureDictionary; + + /// + /// Returns the dictionary value for the key specified + /// + /// + /// + public string GetDictionaryValue(string key) + { + if (_cultureDictionary == null) + { + var factory = CultureDictionaryFactoryResolver.Current.Factory; + _cultureDictionary = factory.CreateDictionary(); + } + return _cultureDictionary[key]; + } + + #endregion + + #region Membership + + /// + /// Check if a document object is protected by the "Protect Pages" functionality in umbraco + /// + /// The identifier of the document object to check + /// The full path of the document object to check + /// True if the document object is protected + public bool IsProtected(int documentId, string path) + { + return Access.IsProtected(documentId, path); + } + + /// + /// Check if the current user has access to a document + /// + /// The identifier of the document object to check + /// The full path of the document object to check + /// True if the current user has access or if the current document isn't protected + public bool MemberHasAccess(int nodeId, string path) + { + if (IsProtected(nodeId, path)) + { + return Member.IsLoggedOn() && Access.HasAccess(nodeId, path, Membership.GetUser()); + } + return true; + } + + /// + /// Whether or not the current member is logged in (based on the membership provider) + /// + /// True is the current user is logged in + public bool MemberIsLoggedOn() + { + /* + MembershipUser u = Membership.GetUser(); + return u != null; + */ + return Member.IsLoggedOn(); + } + + #endregion + + #region NiceUrls + + /// + /// Returns a string with a friendly url from a node. + /// IE.: Instead of having /482 (id) as an url, you can have + /// /screenshots/developer/macros (spoken url) + /// + /// Identifier for the node that should be returned + /// String with a friendly url from a node + public string NiceUrl(int nodeId) + { + var niceUrlsProvider = UmbracoContext.Current.NiceUrlProvider; + return niceUrlsProvider.GetNiceUrl(nodeId); + } + + /// + /// This method will always add the domain to the path if the hostnames are set up correctly. + /// + /// Identifier for the node that should be returned + /// String with a friendly url with full domain from a node + public string NiceUrlWithDomain(int nodeId) + { + var niceUrlsProvider = UmbracoContext.Current.NiceUrlProvider; + return niceUrlsProvider.GetNiceUrl(nodeId, UmbracoContext.Current.UmbracoUrl, true); + } + + #endregion + #region Content + public IDocument GetContentById(int id) + { + return GetDocumentById(id, PublishedContentStoreResolver.Current.PublishedContentStore); + } + + public IDocument GetContentById(string id) + { + return GetDocumentById(id, PublishedContentStoreResolver.Current.PublishedContentStore); + } + + public IEnumerable GetContentByIds(params int[] ids) + { + return GetDocumentByIds(PublishedContentStoreResolver.Current.PublishedContentStore, ids); + } + + public IEnumerable GetContentByIds(params string[] ids) + { + return GetDocumentByIds(PublishedContentStoreResolver.Current.PublishedContentStore, ids); + } + public dynamic ContentById(int id) { return DocumentById(id, PublishedContentStoreResolver.Current.PublishedContentStore); @@ -238,11 +355,31 @@ namespace Umbraco.Web { return DocumentByIds(PublishedContentStoreResolver.Current.PublishedContentStore, ids); } - + #endregion #region Media + public IDocument GetMediaById(int id) + { + return GetDocumentById(id, PublishedMediaStoreResolver.Current.PublishedMediaStore); + } + + public IDocument GetMediaById(string id) + { + return GetDocumentById(id, PublishedMediaStoreResolver.Current.PublishedMediaStore); + } + + public IEnumerable GetMediaByIds(params int[] ids) + { + return GetDocumentByIds(PublishedMediaStoreResolver.Current.PublishedMediaStore, ids); + } + + public IEnumerable GetMediaByIds(params string[] ids) + { + return GetDocumentByIds(PublishedMediaStoreResolver.Current.PublishedMediaStore, ids); + } + public dynamic MediaById(int id) { return DocumentById(id, PublishedMediaStoreResolver.Current.PublishedMediaStore); @@ -267,6 +404,29 @@ namespace Umbraco.Web #region Used by Content/Media + private IDocument GetDocumentById(int id, IPublishedStore store) + { + return store.GetDocumentById(UmbracoContext.Current, id); + } + + private IDocument GetDocumentById(string id, IPublishedStore store) + { + int docId; + return int.TryParse(id, out docId) + ? DocumentById(docId, store) + : null; + } + + private IEnumerable GetDocumentByIds(IPublishedStore store, params int[] ids) + { + return ids.Select(eachId => GetDocumentById(eachId, store)); + } + + private IEnumerable GetDocumentByIds(IPublishedStore store, params string[] ids) + { + return ids.Select(eachId => GetDocumentById(eachId, store)); + } + private dynamic DocumentById(int id, IPublishedStore store) { var doc = store.GetDocumentById(UmbracoContext.Current, id); @@ -278,8 +438,8 @@ namespace Umbraco.Web private dynamic DocumentById(string id, IPublishedStore store) { int docId; - return int.TryParse(id, out docId) - ? DocumentById(docId, store) + return int.TryParse(id, out docId) + ? DocumentById(docId, store) : new DynamicNull(); } @@ -361,6 +521,29 @@ namespace Umbraco.Web #region Strings + /// + /// Replaces text line breaks with html line breaks + /// + /// The text. + /// The text with text line breaks replaced with html linebreaks (
)
+ public string ReplaceLineBreaksForHtml(string text) + { + if (bool.Parse(Umbraco.Core.Configuration.GlobalSettings.EditXhtmlMode)) + return text.Replace("\n", "
\n"); + else + return text.Replace("\n", "
\n"); + } + + /// + /// Returns an MD5 hash of the string specified + /// + /// The text to create a hash from + /// Md5 has of the string + public string CreateMd5Hash(string text) + { + return text.ToMd5(); + } + public HtmlString StripHtml(IHtmlString html, params string[] tags) { return StripHtml(html.ToHtmlString(), tags); @@ -445,7 +628,7 @@ namespace Umbraco.Web internal string Join(string seperator, params object[] args) { - var results = args.Where(arg => arg != null && arg.GetType() != typeof (TIgnore)).Select(arg => string.Format("{0}", arg)).Where(sArg => !string.IsNullOrWhiteSpace(sArg)).ToList(); + var results = args.Where(arg => arg != null && arg.GetType() != typeof(TIgnore)).Select(arg => string.Format("{0}", arg)).Where(sArg => !string.IsNullOrWhiteSpace(sArg)).ToList(); return string.Join(seperator, results); } @@ -645,71 +828,6 @@ namespace Umbraco.Web #endregion - #region Wrap - public HtmlTagWrapper Wrap(string tag, string innerText, params IHtmlTagWrapper[] children) - { - var item = Wrap(tag, innerText, (object)null); - foreach (var child in children) - { - item.AddChild(child); - } - return item; - } - - public HtmlTagWrapper Wrap(string tag, object inner, object anonymousAttributes, params IHtmlTagWrapper[] children) - { - string innerText = null; - if (inner != null && inner.GetType() != typeof(DynamicNull)) - { - innerText = string.Format("{0}", inner); - } - var item = Wrap(tag, innerText, anonymousAttributes); - foreach (var child in children) - { - item.AddChild(child); - } - return item; - } - public HtmlTagWrapper Wrap(string tag, object inner) - { - string innerText = null; - if (inner != null && inner.GetType() != typeof(DynamicNull)) - { - innerText = string.Format("{0}", inner); - } - return Wrap(tag, innerText, (object)null); - } - - public HtmlTagWrapper Wrap(string tag, string innerText, object anonymousAttributes, params IHtmlTagWrapper[] children) - { - var wrap = new HtmlTagWrapper(tag); - if (anonymousAttributes != null) - { - wrap.ReflectAttributesFromAnonymousType(anonymousAttributes); - } - if (!string.IsNullOrWhiteSpace(innerText)) - { - wrap.AddChild(new HtmlTagWrapperTextNode(innerText)); - } - foreach (var child in children) - { - wrap.AddChild(child); - } - return wrap; - } - - public HtmlTagWrapper Wrap(bool visible, string tag, string innerText, object anonymousAttributes, params IHtmlTagWrapper[] children) - { - var item = Wrap(tag, innerText, anonymousAttributes, children); - item.Visible = visible; - foreach (var child in children) - { - item.AddChild(child); - } - return item; - } - - #endregion } } diff --git a/src/Umbraco.Web/umbraco.presentation/library.cs b/src/Umbraco.Web/umbraco.presentation/library.cs index fb4ebc887b..53049cf2c3 100644 --- a/src/Umbraco.Web/umbraco.presentation/library.cs +++ b/src/Umbraco.Web/umbraco.presentation/library.cs @@ -9,8 +9,8 @@ using System.Web; using System.Web.UI; using System.Xml; using System.Xml.XPath; - - +using Umbraco.Core; +using Umbraco.Web; using umbraco.BusinessLogic; using umbraco.cms.businesslogic; using umbraco.cms.businesslogic.media; @@ -25,11 +25,11 @@ using umbraco.DataLayer; using System.Web.Security; using umbraco.cms.businesslogic.language; using umbraco.IO; -using umbraco.presentation; using System.Collections; using System.Collections.Generic; using umbraco.cms.businesslogic.cache; using umbraco.NodeFactory; +using UmbracoContext = umbraco.presentation.UmbracoContext; namespace umbraco { @@ -42,6 +42,14 @@ namespace umbraco ///
public class library { + /// + /// Returns a new UmbracoHelper so that we can start moving the logic from some of these methods to it + /// + /// + private static UmbracoHelper GetUmbracoHelper() + { + return new UmbracoHelper(Umbraco.Web.UmbracoContext.Current); + } #region Declarations @@ -370,8 +378,7 @@ namespace umbraco /// String with a friendly url from a node public static string NiceUrl(int nodeID) { - var niceUrlsProvider = Umbraco.Web.UmbracoContext.Current.RoutingContext.NiceUrlProvider; - return niceUrlsProvider.GetNiceUrl(nodeID); + return GetUmbracoHelper().NiceUrl(nodeID); } /// @@ -393,8 +400,7 @@ namespace umbraco /// String with a friendly url with full domain from a node public static string NiceUrlWithDomain(int nodeID) { - var niceUrlsProvider = Umbraco.Web.UmbracoContext.Current.RoutingContext.NiceUrlProvider; - return niceUrlsProvider.GetNiceUrl(nodeID, Umbraco.Web.UmbracoContext.Current.UmbracoUrl, true); + return GetUmbracoHelper().NiceUrlWithDomain(nodeID); } /// @@ -413,7 +419,7 @@ namespace umbraco public static string ResolveVirtualPath(string path) { - return IOHelper.ResolveUrl(path); + return Umbraco.Core.IO.IOHelper.ResolveUrl(path); } @@ -659,12 +665,7 @@ namespace umbraco /// True is the current user is logged in public static bool IsLoggedOn() { - /* - MembershipUser u = Membership.GetUser(); - return u != null; - */ - - return Member.IsLoggedOn(); + return GetUmbracoHelper().MemberIsLoggedOn(); } public static XPathNodeIterator AllowedGroups(int documentId, string path) @@ -684,7 +685,7 @@ namespace umbraco /// True if the document object is protected public static bool IsProtected(int DocumentId, string Path) { - return Access.IsProtected(DocumentId, Path); + return GetUmbracoHelper().IsProtected(DocumentId, Path); } /// @@ -695,39 +696,18 @@ namespace umbraco /// True if the current user has access or if the current document isn't protected public static bool HasAccess(int NodeId, string Path) { - if (IsProtected(NodeId, Path)) - { - if (Member.IsLoggedOn()) - return Access.HasAccess(NodeId, Path, Membership.GetUser()); - else - return false; - } - else - return true; - + return GetUmbracoHelper().MemberHasAccess(NodeId, Path); } /// - /// Encrypts the string using md5 + /// Returns an MD5 hash of the string specified /// - /// The text. - /// Md5 encrupted string + /// The text to create a hash from + /// Md5 has of the string public static string md5(string text) { - System.Security.Cryptography.MD5CryptoServiceProvider x = new System.Security.Cryptography.MD5CryptoServiceProvider(); - byte[] bs = System.Text.Encoding.UTF8.GetBytes(text); - - bs = x.ComputeHash(bs); - - System.Text.StringBuilder s = new System.Text.StringBuilder(); - - foreach (byte b in bs) - { - s.Append(b.ToString("x2").ToLower()); - } - - return s.ToString(); + return text.ToMd5(); } /// @@ -1060,10 +1040,7 @@ namespace umbraco /// The text with text line breaks replaced with html linebreaks (
)
public static string ReplaceLineBreaks(string text) { - if (bool.Parse(GlobalSettings.EditXhtmlMode)) - return text.Replace("\n", "
\n"); - else - return text.Replace("\n", "
\n"); + return GetUmbracoHelper().ReplaceLineBreaksForHtml(text); } /// diff --git a/src/umbraco.MacroEngines/RazorCore/RazorMacroEngine.cs b/src/umbraco.MacroEngines/RazorCore/RazorMacroEngine.cs index 7ea5cc9928..e30d8c5dca 100644 --- a/src/umbraco.MacroEngines/RazorCore/RazorMacroEngine.cs +++ b/src/umbraco.MacroEngines/RazorCore/RazorMacroEngine.cs @@ -6,6 +6,7 @@ using System.Threading; using System.Web; using System.Web.Compilation; using System.Web.WebPages; +using Umbraco.Core; using umbraco.cms.businesslogic.macro; using umbraco.interfaces; using umbraco.IO; @@ -24,14 +25,7 @@ namespace umbraco.MacroEngines } public string GetMd5(string text) { - var x = new System.Security.Cryptography.MD5CryptoServiceProvider(); - var bs = System.Text.Encoding.UTF8.GetBytes(text); - bs = x.ComputeHash(bs); - var s = new System.Text.StringBuilder(); - foreach (var b in bs) { - s.Append(b.ToString("x2").ToLower()); - } - return s.ToString(); + return text.ToMd5(); } /// diff --git a/src/umbraco.MacroEngines/RazorDynamicNode/RazorLibraryCore.cs b/src/umbraco.MacroEngines/RazorDynamicNode/RazorLibraryCore.cs index 6c59eb4244..7239330fb9 100644 --- a/src/umbraco.MacroEngines/RazorDynamicNode/RazorLibraryCore.cs +++ b/src/umbraco.MacroEngines/RazorDynamicNode/RazorLibraryCore.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Text; +using System.Web.Mvc; using Umbraco.Core.Dynamics; using Umbraco.Web; using umbraco.interfaces; @@ -17,6 +18,12 @@ namespace umbraco.MacroEngines.Library { private readonly INode _node; private readonly UmbracoHelper _umbracoHelper; + + /// + /// An empty HtmlHelper with a blank ViewContext, used only to access some htmlHelper extension methods + /// + private readonly HtmlHelper _htmlHelper; + public INode Node { get { return _node; } @@ -25,6 +32,7 @@ namespace umbraco.MacroEngines.Library { this._node = node; _umbracoHelper = new UmbracoHelper(UmbracoContext.Current); + _htmlHelper = new HtmlHelper(new ViewContext(), new ViewPage()); } public dynamic NodeById(int Id) @@ -194,24 +202,24 @@ namespace umbraco.MacroEngines.Library } public Umbraco.Web.Mvc.HtmlTagWrapper Wrap(string tag, string innerText, params Umbraco.Web.Mvc.IHtmlTagWrapper[] children) - { - return _umbracoHelper.Wrap(tag, innerText, children); + { + return _htmlHelper.Wrap(tag, innerText, children); } public Umbraco.Web.Mvc.HtmlTagWrapper Wrap(string tag, object inner, object anonymousAttributes, params Umbraco.Web.Mvc.IHtmlTagWrapper[] children) { - return _umbracoHelper.Wrap(tag, inner, anonymousAttributes, children); + return _htmlHelper.Wrap(tag, inner, anonymousAttributes, children); } public Umbraco.Web.Mvc.HtmlTagWrapper Wrap(string tag, object inner) { - return _umbracoHelper.Wrap(tag, inner); + return _htmlHelper.Wrap(tag, inner); } public Umbraco.Web.Mvc.HtmlTagWrapper Wrap(string tag, string innerText, object anonymousAttributes, params Umbraco.Web.Mvc.IHtmlTagWrapper[] children) { - return _umbracoHelper.Wrap(tag, innerText, anonymousAttributes, children); + return _htmlHelper.Wrap(tag, innerText, anonymousAttributes, children); } public Umbraco.Web.Mvc.HtmlTagWrapper Wrap(bool visible, string tag, string innerText, object anonymousAttributes, params Umbraco.Web.Mvc.IHtmlTagWrapper[] children) { - return _umbracoHelper.Wrap(visible, tag, innerText, anonymousAttributes, children); + return _htmlHelper.Wrap(visible, tag, innerText, anonymousAttributes, children); } public IHtmlString Truncate(IHtmlString html, int length) From 7e5db90b3bd33265e3d55fef4d301d38d708d962 Mon Sep 17 00:00:00 2001 From: PerPloug Date: Thu, 27 Sep 2012 06:21:59 -0200 Subject: [PATCH 17/22] Adds IO Helper overload for the views and masterpages Adds UmbracoSettings to toggle mvc templating support --- src/umbraco.businesslogic/IO/IOHelper.cs | 7 ++++++- src/umbraco.businesslogic/UmbracoSettings.cs | 13 +++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/src/umbraco.businesslogic/IO/IOHelper.cs b/src/umbraco.businesslogic/IO/IOHelper.cs index 8bd27eab9c..8bc3f886b2 100644 --- a/src/umbraco.businesslogic/IO/IOHelper.cs +++ b/src/umbraco.businesslogic/IO/IOHelper.cs @@ -77,7 +77,12 @@ namespace umbraco.IO return Umbraco.Core.IO.IOHelper.ValidateEditPath(filePath, validDir); } - public static bool ValidateFileExtension(string filePath, List validFileExtensions) + public static bool ValidateEditPath(string filePath, string[] validDirs) + { + return Umbraco.Core.IO.IOHelper.ValidateEditPath(filePath, validDirs); + } + + public static bool ValidateFileExtension(string filePath, List validFileExtensions) { return Umbraco.Core.IO.IOHelper.ValidateFileExtension(filePath, validFileExtensions); } diff --git a/src/umbraco.businesslogic/UmbracoSettings.cs b/src/umbraco.businesslogic/UmbracoSettings.cs index 6e30200699..bd070fb659 100644 --- a/src/umbraco.businesslogic/UmbracoSettings.cs +++ b/src/umbraco.businesslogic/UmbracoSettings.cs @@ -546,6 +546,19 @@ namespace umbraco get { return Umbraco.Core.Configuration.UmbracoSettings.ResolveUrlsFromTextString; } } + + /// + /// Enables MVC, and at the same time disable webform masterpage templates. + /// This ensure views are automaticly created instead of masterpages. + /// Views are display in the tree instead of masterpages and a MVC template editor + /// is used instead of the masterpages editor + /// + /// true if umbraco defaults to using MVC views for templating, otherwise false. + public static bool EnableMvcSupport + { + get { return Umbraco.Core.Configuration.UmbracoSettings.EnableMvcSupport; } + } + /// /// Configuration regarding webservices /// From 2c995f7fa713a3b45da486f0cdf34272ae5fab6d Mon Sep 17 00:00:00 2001 From: PerPloug Date: Thu, 27 Sep 2012 06:23:09 -0200 Subject: [PATCH 18/22] Moves masterpage and view creation away from the template class and into seperate helpers. --- .../template/MasterpageHelper.cs | 261 ++++++++++++++++++ .../businesslogic/template/Template.cs | 119 ++++---- .../businesslogic/template/ViewHelper.cs | 85 ++++++ src/umbraco.cms/umbraco.cms.csproj | 2 + 4 files changed, 418 insertions(+), 49 deletions(-) create mode 100644 src/umbraco.cms/businesslogic/template/MasterpageHelper.cs create mode 100644 src/umbraco.cms/businesslogic/template/ViewHelper.cs diff --git a/src/umbraco.cms/businesslogic/template/MasterpageHelper.cs b/src/umbraco.cms/businesslogic/template/MasterpageHelper.cs new file mode 100644 index 0000000000..8f3866472a --- /dev/null +++ b/src/umbraco.cms/businesslogic/template/MasterpageHelper.cs @@ -0,0 +1,261 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using Umbraco.Core.IO; + +namespace umbraco.cms.businesslogic.template +{ + internal class MasterpageHelper + { + internal static readonly string DefaultMasterTemplate = SystemDirectories.Umbraco + "/masterpages/default.master"; + internal static string CreateMasterpageFile(Template t, bool overWrite = false) + { + string masterpageContent = ""; + + if (!File.Exists(t.MasterPageFile) || overWrite) + masterpageContent = saveTemplateToFile(t, t.Alias); + else + { + System.IO.TextReader tr = new StreamReader(t.MasterPageFile); + masterpageContent = tr.ReadToEnd(); + tr.Close(); + } + + return masterpageContent; + } + + internal static string GetMasterpageFile(Template t) + { + string masterpageContent = ""; + if (File.Exists(t.MasterPageFile)){ + System.IO.TextReader tr = new StreamReader(t.MasterPageFile); + masterpageContent = tr.ReadToEnd(); + tr.Close(); + } + + return masterpageContent; + } + + internal static string UpdateMasterpageFile(Template t, string currentAlias) + { + return saveTemplateToFile(t, currentAlias); + } + + internal static string saveTemplateToFile(Template template, string currentAlias) + { + var masterPageContent = template.Design; + if (!isMasterPageSyntax(masterPageContent)) + masterPageContent = ConvertToMasterPageSyntax(template); + + // Add header to master page if it doesn't exist + if (!masterPageContent.TrimStart().StartsWith("<%@")) + { + masterPageContent = getMasterPageHeader(template) + "\n" + masterPageContent; + } + else + { + // verify that the masterpage attribute is the same as the masterpage + string masterHeader = + masterPageContent.Substring(0, masterPageContent.IndexOf("%>") + 2).Trim( + Environment.NewLine.ToCharArray()); + + // find the masterpagefile attribute + MatchCollection m = Regex.Matches(masterHeader, "(?\\S*)=\"(?[^\"]*)\"", + RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace); + + foreach (Match attributeSet in m) + { + if (attributeSet.Groups["attributeName"].Value.ToLower() == "masterpagefile") + { + // validate the masterpagefile + string currentMasterPageFile = attributeSet.Groups["attributeValue"].Value; + string currentMasterTemplateFile = parentTemplatePath(template); + + if (currentMasterPageFile != currentMasterTemplateFile) + { + masterPageContent = + masterPageContent.Replace( + attributeSet.Groups["attributeName"].Value + "=\"" + currentMasterPageFile + "\"", + attributeSet.Groups["attributeName"].Value + "=\"" + currentMasterTemplateFile + + "\""); + + } + } + } + + } + + //we have a Old Alias if the alias and therefor the masterpage file name has changed... + //so before we save the new masterfile, we'll clear the old one, so we don't up with + //Unused masterpage files + if (!string.IsNullOrEmpty(currentAlias) && currentAlias != template.Alias) + { + + //Ensure that child templates have the right master masterpage file name + if (template.HasChildren) + { + //store children array here because iterating over an Array property object is very inneficient. + var c = template.Children; + foreach (CMSNode cmn in c) + UpdateMasterpageFile(new Template(cmn.Id), null); + } + + //then kill the old file.. + string _oldFile = IOHelper.MapPath(SystemDirectories.Masterpages + "/" + currentAlias.Replace(" ", "") + ".master"); + if (System.IO.File.Exists(_oldFile)) + System.IO.File.Delete(_oldFile); + } + + // save the file in UTF-8 + System.IO.File.WriteAllText(template.MasterPageFile, masterPageContent, System.Text.Encoding.UTF8); + + return masterPageContent; + } + + internal static string ConvertToMasterPageSyntax(Template template) + { + string masterPageContent = GetMasterContentElement(template) + "\n"; + + masterPageContent += template.Design; + + // Parse the design for getitems + masterPageContent = EnsureMasterPageSyntax(template.Alias, masterPageContent); + + // append ending asp:content element + masterPageContent += "\n" + Environment.NewLine; + + return masterPageContent; + } + + private static bool isMasterPageSyntax(string code) + { + return code.Contains("<%@ Master") || code.Contains("", parentTemplatePath(template)) + Environment.NewLine; + } + + private static string parentTemplatePath(Template template) + { + var masterTemplate = DefaultMasterTemplate; + if (template.MasterTemplate != 0) + masterTemplate = SystemDirectories.Masterpages + "/" + new Template(template.MasterTemplate).Alias.Replace(" ", "") + ".master"; + + return masterTemplate; + } + + private static string GetMasterContentElement(Template template) + { + if (template.MasterTemplate != 0) + { + string masterAlias = new Template(template.MasterTemplate).Alias.Replace(" ", ""); + return + String.Format("", + template.Alias.Replace(" ", ""), masterAlias); + } + else + return + String.Format("", + template.Alias.Replace(" ", "")); + + } + + internal static string EnsureMasterPageSyntax(string templateAlias, string masterPageContent) + { + replaceElement(ref masterPageContent, "?UMBRACO_GETITEM", "umbraco:Item", true); + replaceElement(ref masterPageContent, "?UMBRACO_GETITEM", "umbraco:Item", false); + + // Parse the design for macros + replaceElement(ref masterPageContent, "?UMBRACO_MACRO", "umbraco:Macro", true); + replaceElement(ref masterPageContent, "?UMBRACO_MACRO", "umbraco:Macro", false); + + // Parse the design for load childs + masterPageContent = masterPageContent.Replace("", createDefaultPlaceHolder(templateAlias)) + .Replace("", createDefaultPlaceHolder(templateAlias)); + // Parse the design for aspnet forms + getAspNetMasterPageForm(ref masterPageContent, templateAlias); + masterPageContent = masterPageContent.Replace("", ""); + // Parse the design for aspnet heads + masterPageContent = masterPageContent.Replace("", String.Format("", templateAlias.Replace(" ", ""))); + masterPageContent = masterPageContent.Replace("", ""); + return masterPageContent; + } + + + private static void getAspNetMasterPageForm(ref string design, string templateAlias) + { + Match formElement = Regex.Match(design, getElementRegExp("?ASPNET_FORM", false), RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace); + + if (formElement != null && formElement.Value != "") + { + string formReplace = String.Format("
", templateAlias.Replace(" ", "")); + if (formElement.Groups.Count == 0) + { + formReplace += ""; + } + design = design.Replace(formElement.Value, formReplace); + } + } + + private static string createDefaultPlaceHolder(string templateAlias) + { + return String.Format("", templateAlias.Replace(" ", "")); + } + + private static void replaceElement(ref string design, string elementName, string newElementName, bool checkForQuotes) + { + MatchCollection m = + Regex.Matches(design, getElementRegExp(elementName, checkForQuotes), + RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace); + + foreach (Match match in m) + { + GroupCollection groups = match.Groups; + + // generate new element (compensate for a closing trail on single elements ("/")) + string elementAttributes = groups[1].Value; + // test for macro alias + if (elementName == "?UMBRACO_MACRO") + { + Hashtable tags = helpers.xhtml.ReturnAttributes(match.Value); + if (tags["macroAlias"] != null) + elementAttributes = String.Format(" Alias=\"{0}\"", tags["macroAlias"].ToString()) + elementAttributes; + else if (tags["macroalias"] != null) + elementAttributes = String.Format(" Alias=\"{0}\"", tags["macroalias"].ToString()) + elementAttributes; + } + string newElement = "<" + newElementName + " runat=\"server\" " + elementAttributes.Trim() + ">"; + if (elementAttributes.EndsWith("/")) + { + elementAttributes = elementAttributes.Substring(0, elementAttributes.Length - 1); + } + else if (groups[0].Value.StartsWith(""; + + if (checkForQuotes) + { + // if it's inside quotes, we'll change element attribute quotes to single quotes + newElement = newElement.Replace("\"", "'"); + newElement = String.Format("\"{0}\"", newElement); + } + design = design.Replace(match.Value, newElement); + } + } + + private static string getElementRegExp(string elementName, bool checkForQuotes) + { + if (checkForQuotes) + return String.Format("\"<[^>\\s]*\\b{0}(\\b[^>]*)>\"", elementName); + else + return String.Format("<[^>\\s]*\\b{0}(\\b[^>]*)>", elementName); + + } + + } +} diff --git a/src/umbraco.cms/businesslogic/template/Template.cs b/src/umbraco.cms/businesslogic/template/Template.cs index a87488768c..f8a00f842e 100644 --- a/src/umbraco.cms/businesslogic/template/Template.cs +++ b/src/umbraco.cms/businesslogic/template/Template.cs @@ -132,12 +132,12 @@ namespace umbraco.cms.businesslogic.template _mastertemplate = dr.IsNull("master") ? 0 : dr.GetInt("master"); } dr.Close(); + + if (UmbracoSettings.EnableMvcSupport && Template.HasView(this)) + _design = ViewHelper.GetViewFile(this); + else + _design = MasterpageHelper.GetMasterpageFile(this); - // test for masterpages - if (UmbracoSettings.UseAspNetMasterPages) - { - _design = getMasterPageContent(); - } } private bool isMasterPageSyntax(string code) @@ -145,18 +145,6 @@ namespace umbraco.cms.businesslogic.template return code.Contains("<%@ Master") || code.Contains("") + 3).Trim(Environment.NewLine.ToCharArray()); - if (UmbracoSettings.UseAspNetMasterPages && isMasterPageSyntax(_design)) - { - SaveMasterPageFile(_design); - SqlHelper.ExecuteNonQuery("Update cmsTemplate set design = @design where NodeId = @id", - SqlHelper.CreateParameter("@design", value), - SqlHelper.CreateParameter("@id", Id)); + //we only switch to MVC View editing if the template has a view file, and MVC editing is enabled + if (UmbracoSettings.EnableMvcSupport && !isMasterPageSyntax(_design)) + _design = ViewHelper.UpdateViewFile(this); + else if (UmbracoSettings.UseAspNetMasterPages) + _design = MasterpageHelper.UpdateMasterpageFile(this, _oldAlias); + - } - else - SqlHelper.ExecuteNonQuery("Update cmsTemplate set design = @design where NodeId = @id", - SqlHelper.CreateParameter("@design", value), + SqlHelper.ExecuteNonQuery("Update cmsTemplate set design = @design where NodeId = @id", + SqlHelper.CreateParameter("@design", _design), SqlHelper.CreateParameter("@id", Id)); } } @@ -331,11 +318,17 @@ namespace umbraco.cms.businesslogic.template public static Template MakeNew(string Name, BusinessLogic.User u, Template master) { - Template t = MakeNew(Name, u); t.MasterTemplate = master.Id; - t.Design = ""; + if (UmbracoSettings.EnableMvcSupport) + ViewHelper.CreateViewFile(t, true); + else + MasterpageHelper.CreateMasterpageFile(t, true); + + + + /* if (UmbracoSettings.UseAspNetMasterPages) { string design = t.getMasterPageHeader() + "\n"; @@ -346,7 +339,7 @@ namespace umbraco.cms.businesslogic.template } t.Design = design; - } + }*/ t.Save(); return t; @@ -367,6 +360,8 @@ namespace umbraco.cms.businesslogic.template if (name.Length > 100) name = name.Substring(0, 95) + "..."; + + SqlHelper.ExecuteNonQuery("INSERT INTO cmsTemplate (NodeId, Alias, design, master) VALUES (@nodeId, @alias, @design, @master)", SqlHelper.CreateParameter("@nodeId", n.Id), @@ -378,6 +373,12 @@ namespace umbraco.cms.businesslogic.template NewEventArgs e = new NewEventArgs(); t.OnNew(e); + if (UmbracoSettings.EnableMvcSupport) + t._design = ViewHelper.CreateViewFile(t); + else + t._design = MasterpageHelper.CreateMasterpageFile(t); + + return t; } @@ -504,13 +505,17 @@ namespace umbraco.cms.businesslogic.template if (System.IO.File.Exists(MasterPageFile)) System.IO.File.Delete(MasterPageFile); + if (System.IO.File.Exists(Umbraco.Core.IO.IOHelper.MapPath(ViewHelper.ViewPath(this)))) + System.IO.File.Delete(Umbraco.Core.IO.IOHelper.MapPath(ViewHelper.ViewPath(this))); + FireAfterDelete(e); } } - public void SaveAsMasterPage() + [Obsolete("This method, doesnt actually do anything, as the file is created when the design is set", false)] + public void _SaveAsMasterPage() { - SaveMasterPageFile(ConvertToMasterPageSyntax(Design)); + //SaveMasterPageFile(ConvertToMasterPageSyntax(Design)); } public string GetMasterContentElement(int masterTemplateId) @@ -526,7 +531,6 @@ namespace umbraco.cms.businesslogic.template return String.Format("", Alias.Replace(" ", "")); - } public List contentPlaceholderIds() @@ -591,8 +595,13 @@ namespace umbraco.cms.businesslogic.template return masterPageContent; } + + public void ImportDesign(string design) { + Design = design; + + /* if (!isMasterPageSyntax(design)) { Design = ConvertToMasterPageSyntax(design); @@ -600,26 +609,17 @@ namespace umbraco.cms.businesslogic.template else { Design = design; - } + }*/ } - private string getMasterPageHeader() - { - return String.Format("<%@ Master Language=\"C#\" MasterPageFile=\"{0}\" AutoEventWireup=\"true\" %>", - currentMasterTemplateFileName()) + Environment.NewLine; - } - - private string currentMasterTemplateFileName() - { - if (MasterTemplate != 0) - return SystemDirectories.Masterpages + "/" + new Template(MasterTemplate).Alias.Replace(" ", "") + ".master"; - else - return UmbracoMasterTemplate; - } - public void SaveMasterPageFile(string masterPageContent) { + //this will trigger the helper and store everything + this.Design = masterPageContent; + + /* + // Add header to master page if it doesn't exist if (!masterPageContent.StartsWith("<%@")) { @@ -681,6 +681,21 @@ namespace umbraco.cms.businesslogic.template // save the file in UTF-8 File.WriteAllText(MasterPageFile, masterPageContent, System.Text.Encoding.UTF8); + * */ + } + + private string getMasterPageHeader() + { + return String.Format("<%@ Master Language=\"C#\" MasterPageFile=\"{0}\" AutoEventWireup=\"true\" %>", + currentMasterTemplateFileName()) + Environment.NewLine; + } + + private string currentMasterTemplateFileName() + { + if (MasterTemplate != 0) + return SystemDirectories.Masterpages + "/" + new Template(MasterTemplate).Alias.Replace(" ", "") + ".master"; + else + return UmbracoMasterTemplate; } private void getAspNetMasterPageForm(ref string design) @@ -802,12 +817,18 @@ namespace umbraco.cms.businesslogic.template } t.Alias = alias; - t.ImportDesign(xmlHelper.GetNodeValue(n.SelectSingleNode("Design"))); return t; } + public static bool HasView(Template t) + { + var path = Umbraco.Core.IO.SystemDirectories.MvcViews + "/" + t.Alias.Replace(" ", "") + ".cshtml"; + return System.IO.File.Exists(Umbraco.Core.IO.IOHelper.MapPath(path)); + } + + #region Events //EVENTS /// diff --git a/src/umbraco.cms/businesslogic/template/ViewHelper.cs b/src/umbraco.cms/businesslogic/template/ViewHelper.cs new file mode 100644 index 0000000000..c3e9306b5e --- /dev/null +++ b/src/umbraco.cms/businesslogic/template/ViewHelper.cs @@ -0,0 +1,85 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using Umbraco.Core.IO; + +namespace umbraco.cms.businesslogic.template +{ + class ViewHelper + { + internal static string GetViewFile(Template t) + { + string viewContent = ""; + string path = IOHelper.MapPath(ViewPath(t)); + + if (File.Exists(path)) + { + System.IO.TextReader tr = new StreamReader(path); + viewContent = tr.ReadToEnd(); + tr.Close(); + } + + return viewContent; + } + + internal static string CreateViewFile(Template t, bool overWrite = false) + { + string viewContent = ""; + string path = IOHelper.MapPath(ViewPath(t)); + + if (!File.Exists(path) || overWrite) + viewContent = saveTemplateToFile(t, t.Alias); + else + { + System.IO.TextReader tr = new StreamReader(path); + viewContent = tr.ReadToEnd(); + tr.Close(); + } + + return viewContent; + } + + internal static string saveTemplateToFile(Template template, string currentAlias) + { + var design = EnsureInheritedLayout(template); + System.IO.File.WriteAllText(IOHelper.MapPath(ViewPath(template)), design, Encoding.UTF8); + + return template.Design; + } + + internal static string UpdateViewFile(Template t) + { + var path = IOHelper.MapPath(ViewPath(t)); + System.IO.File.WriteAllText(path, t.Design, Encoding.UTF8); + return t.Design; + } + + public static string ViewPath(Template t) + { + return Umbraco.Core.IO.SystemDirectories.MvcViews + "/" + t.Alias.Replace(" ", "") + ".cshtml"; + } + + + + private static string EnsureInheritedLayout(Template template) + { + string design = template.Design; + + if (string.IsNullOrEmpty(design)) + { + design = @"@inherits Umbraco.Web.Mvc.RenderViewPage +@{ + Layout = null; +}"; + + if (template.MasterTemplate > 0) + design = design.Replace("null", "\"" + new Template(template.MasterTemplate).Alias.Replace(" ", "") + "\""); + + } + + return design; + } + } +} diff --git a/src/umbraco.cms/umbraco.cms.csproj b/src/umbraco.cms/umbraco.cms.csproj index e293cc1585..1db1c56de3 100644 --- a/src/umbraco.cms/umbraco.cms.csproj +++ b/src/umbraco.cms/umbraco.cms.csproj @@ -261,6 +261,8 @@ + + From 9f7814591ed4ed8e442c3ad4218c2f3143539894 Mon Sep 17 00:00:00 2001 From: PerPloug Date: Thu, 27 Sep 2012 06:24:04 -0200 Subject: [PATCH 19/22] Adds Seperate View Editor for mvc templates Changes the template tree to look for existing views to override the .master --- src/Umbraco.Web/Umbraco.Web.csproj | 4 + .../umbraco/Trees/loadScripts.cs | 1 - .../umbraco/Trees/loadTemplates.cs | 37 +++- .../umbraco/create/templateTasks.cs | 9 +- .../settings/scripts/editScript.aspx.cs | 19 +- .../umbraco/settings/views/editView.aspx | 160 ++++++++++++++ .../umbraco/settings/views/editView.aspx.cs | 195 ++++++++++++++++++ .../webservices/codeEditorSave.asmx.cs | 3 +- 8 files changed, 412 insertions(+), 16 deletions(-) create mode 100644 src/Umbraco.Web/umbraco.presentation/umbraco/settings/views/editView.aspx create mode 100644 src/Umbraco.Web/umbraco.presentation/umbraco/settings/views/editView.aspx.cs diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index caea0cb4ea..3f0f7bc65d 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -309,6 +309,9 @@ ASPXCodeBehind + + editView.aspx + @@ -1911,6 +1914,7 @@ + diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/loadScripts.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/loadScripts.cs index f5d0077441..7d368bc634 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/loadScripts.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/loadScripts.cs @@ -33,7 +33,6 @@ namespace umbraco public class loadScripts : FileSystemTree { public loadScripts(string application) : base(application) { } - protected override void CreateRootNode(ref XmlTreeNode rootNode) { rootNode.NodeType = "init" + TreeAlias; diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/loadTemplates.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/loadTemplates.cs index e898f6debd..97a6c11a0f 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/loadTemplates.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/loadTemplates.cs @@ -32,21 +32,32 @@ namespace umbraco [Tree("settings", "templates", "Templates", sortOrder: 1)] public class loadTemplates : BaseTree { - public loadTemplates(string application) : base(application) { } + public loadTemplates(string application) : base(application) {} protected override void CreateRootNode(ref XmlTreeNode rootNode) - { - rootNode.NodeType = "init" + TreeAlias; + { + if (!Umbraco.Core.Configuration.UmbracoSettings.EnableMvcSupport) + rootNode.NodeType = "init" + TreeAlias; + else + rootNode.NodeType = "initviews"; + + rootNode.NodeID = "init"; } + public override void RenderJS(ref StringBuilder Javascript) { + Javascript.Append( @" function openTemplate(id) { UmbClientMgr.contentFrame('settings/editTemplate.aspx?templateID=' + id); - } + } + + function openView(id) { + UmbClientMgr.contentFrame('settings/views/editView.aspx?templateID=' + id); + } function openSkin(id) { UmbClientMgr.contentFrame('settings/editSkin.aspx?skinID=' + id); @@ -54,6 +65,7 @@ namespace umbraco "); } + public override void Render(ref XmlTree tree) { string folder = umbraco.library.Request("folder"); @@ -70,7 +82,6 @@ namespace umbraco } } - private void RenderTemplateFolderItems(string folder, string folderPath, ref XmlTree tree) { string relPath = IO.SystemDirectories.Masterpages + "/" + folder; @@ -184,11 +195,20 @@ namespace umbraco XmlTreeNode xNode = XmlTreeNode.Create(this); xNode.NodeID = t.Id.ToString(); xNode.Text = t.Text; - xNode.Action = "javascript:openTemplate(" + t.Id + ");"; - xNode.Icon = "settingTemplate.gif"; - xNode.OpenIcon = "settingTemplate.gif"; xNode.Source = GetTreeServiceUrl(t.Id); xNode.HasChildren = t.HasChildren; + + if (UmbracoSettings.EnableMvcSupport && Template.HasView(t)) + { + xNode.Action = "javascript:openView(" + t.Id + ");"; + xNode.Icon = "settingView.gif"; + xNode.OpenIcon = "settingView.gif"; + }else{ + xNode.Action = "javascript:openTemplate(" + t.Id + ");"; + xNode.Icon = "settingTemplate.gif"; + xNode.OpenIcon = "settingTemplate.gif"; + } + if (t.HasChildren) { xNode.Icon = "settingMasterTemplate.gif"; @@ -196,6 +216,7 @@ namespace umbraco } OnBeforeNodeRender(ref tree, ref xNode, EventArgs.Empty); + if (xNode != null) { tree.Add(xNode); diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/create/templateTasks.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/create/templateTasks.cs index 7ef55bc8ac..78e6025d27 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/create/templateTasks.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/create/templateTasks.cs @@ -44,15 +44,20 @@ namespace umbraco { int masterId = ParentID; BusinessLogic.Log.Add(LogTypes.Debug, -1, "tp id:" + masterId.ToString()); + + var editor = "settings/editTemplate.aspx"; + if(UmbracoSettings.EnableMvcSupport) + editor = "settings/views/editView.aspx"; + if (masterId > 0) { int id = cms.businesslogic.template.Template.MakeNew(Alias, BusinessLogic.User.GetUser(_userID), new cms.businesslogic.template.Template(masterId)).Id; - m_returnUrl = string.Format("settings/editTemplate.aspx?templateID={0}", id); + m_returnUrl = string.Format("{1}?templateID={0}", id, editor); } else { int id = cms.businesslogic.template.Template.MakeNew(Alias, BusinessLogic.User.GetUser(_userID)).Id; - m_returnUrl = string.Format("settings/editTemplate.aspx?templateID={0}", id); + m_returnUrl = string.Format("{1}?templateID={0}", id, editor); } return true; diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/settings/scripts/editScript.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/settings/scripts/editScript.aspx.cs index b8fcaa6d86..a489c11f5a 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/settings/scripts/editScript.aspx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/settings/scripts/editScript.aspx.cs @@ -42,8 +42,6 @@ namespace umbraco.cms.presentation.settings.scripts NameTxt.Text = file; - - string path = ""; if (file.StartsWith("~/")) path = IOHelper.ResolveUrl(file); @@ -53,10 +51,23 @@ namespace umbraco.cms.presentation.settings.scripts lttPath.Text = "" + path + ""; + var exts = UmbracoSettings.ScriptFileTypes.Split(',').ToList(); + if (UmbracoSettings.EnableMvcSupport) + { + exts.Add("cshtml"); + exts.Add("vbhtml"); + } + + var dirs = Umbraco.Core.IO.SystemDirectories.Scripts; + if (UmbracoSettings.EnableMvcSupport) + dirs += "," + Umbraco.Core.IO.SystemDirectories.MvcViews; + // validate file - IOHelper.ValidateEditPath(IOHelper.MapPath(path), SystemDirectories.Scripts); + IOHelper.ValidateEditPath(IOHelper.MapPath(path), dirs.Split(',')); + // validate extension - IOHelper.ValidateFileExtension(IOHelper.MapPath(path), UmbracoSettings.ScriptFileTypes.Split(',').ToList()); + IOHelper.ValidateFileExtension(IOHelper.MapPath(path), exts); + StreamReader SR; string S; diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/settings/views/editView.aspx b/src/Umbraco.Web/umbraco.presentation/umbraco/settings/views/editView.aspx new file mode 100644 index 0000000000..6edda41599 --- /dev/null +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/settings/views/editView.aspx @@ -0,0 +1,160 @@ +<%@ Page Language="C#" MasterPageFile="../../masterpages/umbracoPage.Master" AutoEventWireup="true" + CodeBehind="editView.aspx.cs" Inherits="umbraco.cms.presentation.settings.views.editView" + ValidateRequest="False" %> + +<%@ Register TagPrefix="cc1" Namespace="umbraco.uicontrols" Assembly="controls" %> +<%@ Register TagPrefix="umb" Namespace="ClientDependency.Core.Controls" Assembly="ClientDependency.Core" %> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/settings/views/editView.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/settings/views/editView.aspx.cs new file mode 100644 index 0000000000..3b7d4b024e --- /dev/null +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/settings/views/editView.aspx.cs @@ -0,0 +1,195 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Web.UI; +using System.Web.UI.WebControls; +using umbraco.BasePages; +using umbraco.BusinessLogic; +using umbraco.cms.businesslogic.skinning; +using umbraco.cms.businesslogic.template; +using umbraco.cms.presentation.Trees; +using umbraco.DataLayer; +using umbraco.IO; +using umbraco.uicontrols; + +namespace umbraco.cms.presentation.settings.views +{ + public partial class editView : BasePages.UmbracoEnsuredPage + { + private Template _template; + + protected global::ClientDependency.Core.Controls.CssInclude CssInclude1; + protected global::ClientDependency.Core.Controls.JsInclude JsInclude; + + protected global::umbraco.uicontrols.UmbracoPanel Panel1; + protected global::umbraco.uicontrols.Pane Pane7; + protected global::umbraco.uicontrols.PropertyPanel pp_name; + protected global::System.Web.UI.WebControls.TextBox NameTxt; + protected global::umbraco.uicontrols.PropertyPanel pp_alias; + protected global::System.Web.UI.WebControls.TextBox AliasTxt; + protected global::umbraco.uicontrols.PropertyPanel pp_masterTemplate; + protected global::System.Web.UI.WebControls.DropDownList MasterTemplate; + protected global::umbraco.uicontrols.PropertyPanel pp_source; + protected global::umbraco.uicontrols.CodeArea editorSource; + protected global::System.Web.UI.WebControls.Repeater rpt_codeTemplates; + protected global::System.Web.UI.WebControls.Repeater rpt_macros; + + + public editView() + { + CurrentApp = BusinessLogic.DefaultApps.settings.ToString(); + } + + protected void Page_Load(object sender, EventArgs e) + { + MasterTemplate.Attributes.Add("onchange", "changeMasterPageFile()"); + + if (!IsPostBack) + { + MasterTemplate.Items.Add(new ListItem(ui.Text("none"), "0")); + + foreach (Template t in Template.GetAllAsList()) + { + if (t.Id != _template.Id) + { + var li = new ListItem(t.Text, t.Id.ToString()); + + li.Attributes.Add("id", t.Alias.Replace(" ", "")); + + if (t.Id == _template.MasterTemplate) + { + try + { + li.Selected = true; + } + catch + { + } + } + MasterTemplate.Items.Add(li); + } + } + + NameTxt.Text = _template.GetRawText(); + AliasTxt.Text = _template.Alias; + editorSource.Text = _template.Design; + + + ClientTools + .SetActiveTreeType(TreeDefinitionCollection.Instance.FindTree().Tree.Alias) + .SyncTree("-1,init," + _template.Path.Replace("-1,", ""), false); + } + } + + + + #region Web Form Designer generated code + protected override void OnInit(EventArgs e) + { + _template = new Template(int.Parse(Request.QueryString["templateID"])); + // + // CODEGEN: This call is required by the ASP.NET Web Form Designer. + // + InitializeComponent(); + base.OnInit(e); + Panel1.hasMenu = true; + + MenuIconI save = Panel1.Menu.NewIcon(); + save.ImageURL = SystemDirectories.Umbraco + "/images/editor/save.gif"; + save.OnClickCommand = "doSubmit()"; + save.AltText = ui.Text("save"); + save.ID = "save"; + + Panel1.Text = ui.Text("edittemplate"); + pp_name.Text = ui.Text("name", base.getUser()); + pp_alias.Text = ui.Text("alias", base.getUser()); + pp_masterTemplate.Text = ui.Text("mastertemplate", base.getUser()); + + // Editing buttons + Panel1.Menu.InsertSplitter(); + MenuIconI umbField = Panel1.Menu.NewIcon(); + umbField.ImageURL = UmbracoPath + "/images/editor/insField.gif"; + umbField.OnClickCommand = + ClientTools.Scripts.OpenModalWindow( + IOHelper.ResolveUrl(SystemDirectories.Umbraco) + "/dialogs/umbracoField.aspx?objectId=" + + editorSource.ClientID + "&tagName=UMBRACOGETDATA", ui.Text("template", "insertPageField"), 640, 550); + umbField.AltText = ui.Text("template", "insertPageField"); + + + // TODO: Update icon + MenuIconI umbDictionary = Panel1.Menu.NewIcon(); + umbDictionary.ImageURL = GlobalSettings.Path + "/images/editor/dictionaryItem.gif"; + umbDictionary.OnClickCommand = + ClientTools.Scripts.OpenModalWindow( + IOHelper.ResolveUrl(SystemDirectories.Umbraco) + "/dialogs/umbracoField.aspx?objectId=" + + editorSource.ClientID + "&tagName=UMBRACOGETDICTIONARY", ui.Text("template", "insertDictionaryItem"), + 640, 550); + umbDictionary.AltText = "Insert umbraco dictionary item"; + + //uicontrols.MenuIconI umbMacro = Panel1.Menu.NewIcon(); + //umbMacro.ImageURL = UmbracoPath + "/images/editor/insMacro.gif"; + //umbMacro.AltText = ui.Text("template", "insertMacro"); + //umbMacro.OnClickCommand = umbraco.BasePages.ClientTools.Scripts.OpenModalWindow(umbraco.IO.IOHelper.ResolveUrl(umbraco.IO.SystemDirectories.Umbraco) + "/dialogs/editMacro.aspx?objectId=" + editorSource.ClientID, ui.Text("template", "insertMacro"), 470, 530); + + Panel1.Menu.NewElement("div", "splitButtonMacroPlaceHolder", "sbPlaceHolder", 40); + + if (UmbracoSettings.UseAspNetMasterPages) + { + Panel1.Menu.InsertSplitter(); + + MenuIconI umbContainer = Panel1.Menu.NewIcon(); + umbContainer.ImageURL = UmbracoPath + "/images/editor/masterpagePlaceHolder.gif"; + umbContainer.AltText = ui.Text("template", "insertContentAreaPlaceHolder"); + umbContainer.OnClickCommand = + ClientTools.Scripts.OpenModalWindow( + IOHelper.ResolveUrl(SystemDirectories.Umbraco) + + "/dialogs/insertMasterpagePlaceholder.aspx?&id=" + _template.Id, + ui.Text("template", "insertContentAreaPlaceHolder"), 470, 320); + + MenuIconI umbContent = Panel1.Menu.NewIcon(); + umbContent.ImageURL = UmbracoPath + "/images/editor/masterpageContent.gif"; + umbContent.AltText = ui.Text("template", "insertContentArea"); + umbContent.OnClickCommand = + ClientTools.Scripts.OpenModalWindow( + IOHelper.ResolveUrl(SystemDirectories.Umbraco) + "/dialogs/insertMasterpageContent.aspx?id=" + + _template.Id, ui.Text("template", "insertContentArea"), 470, 300); + } + + + //Spit button + Panel1.Menu.InsertSplitter(); + Panel1.Menu.NewElement("div", "splitButtonPlaceHolder", "sbPlaceHolder", 40); + + // Help + Panel1.Menu.InsertSplitter(); + + MenuIconI helpIcon = Panel1.Menu.NewIcon(); + helpIcon.OnClickCommand = + ClientTools.Scripts.OpenModalWindow( + IOHelper.ResolveUrl(SystemDirectories.Umbraco) + "/settings/modals/showumbracotags.aspx?alias=" + + _template.Alias, ui.Text("template", "quickGuide"), 600, 580); + helpIcon.ImageURL = UmbracoPath + "/images/editor/help.png"; + helpIcon.AltText = ui.Text("template", "quickGuide"); + } + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + + } + + + + protected override void OnPreRender(EventArgs e) + { + base.OnPreRender(e); + ScriptManager.GetCurrent(Page).Services.Add(new ServiceReference("../webservices/codeEditorSave.asmx")); + ScriptManager.GetCurrent(Page).Services.Add(new ServiceReference("../webservices/legacyAjaxCalls.asmx")); + } + #endregion + + } +} diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/codeEditorSave.asmx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/codeEditorSave.asmx.cs index db232b9e04..722abb3d18 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/codeEditorSave.asmx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/codeEditorSave.asmx.cs @@ -402,7 +402,7 @@ namespace umbraco.presentation.webservices } return "false"; } - + [WebMethod] public string SaveTemplate(string templateName, string templateAlias, string templateContents, int templateID, int masterTemplateID) { @@ -417,6 +417,7 @@ namespace umbraco.presentation.webservices _template.Alias = templateAlias; _template.MasterTemplate = masterTemplateID; _template.Design = templateContents; + _template.Save(); retVal = "true"; From c409cfa421942937fc926153b18d959150ad3c05 Mon Sep 17 00:00:00 2001 From: PerPloug Date: Thu, 27 Sep 2012 06:29:41 -0200 Subject: [PATCH 20/22] Create/Ui.xml file for templates umbracoSettings.config file --- src/Umbraco.Web.UI/Umbraco.Web.UI.csproj | 1 + .../config/umbracoSettings.Release.config | 1 + src/Umbraco.Web.UI/config/umbracoSettings.config | 3 ++- src/Umbraco.Web.UI/umbraco/config/create/UI.xml | 1 + .../umbraco/developer/Xslt/editXslt.aspx | 2 +- src/Umbraco.Web.UI/umbraco/dialogs/umbracoField.aspx | 10 +++++----- 6 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index 140edb2b31..adc04ef21f 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -350,6 +350,7 @@ + diff --git a/src/Umbraco.Web.UI/config/umbracoSettings.Release.config b/src/Umbraco.Web.UI/config/umbracoSettings.Release.config index 5f649fac98..057945415a 100644 --- a/src/Umbraco.Web.UI/config/umbracoSettings.Release.config +++ b/src/Umbraco.Web.UI/config/umbracoSettings.Release.config @@ -130,6 +130,7 @@ true + false diff --git a/src/Umbraco.Web.UI/config/umbracoSettings.config b/src/Umbraco.Web.UI/config/umbracoSettings.config index dcb71c0538..f614615947 100644 --- a/src/Umbraco.Web.UI/config/umbracoSettings.config +++ b/src/Umbraco.Web.UI/config/umbracoSettings.config @@ -89,7 +89,6 @@ false - @@ -113,9 +112,11 @@ aa + true true + true diff --git a/src/Umbraco.Web.UI/umbraco/config/create/UI.xml b/src/Umbraco.Web.UI/umbraco/config/create/UI.xml index b988bb864a..eaa028c34b 100644 --- a/src/Umbraco.Web.UI/umbraco/config/create/UI.xml +++ b/src/Umbraco.Web.UI/umbraco/config/create/UI.xml @@ -303,6 +303,7 @@ +
Package
/create/simple.ascx diff --git a/src/Umbraco.Web.UI/umbraco/developer/Xslt/editXslt.aspx b/src/Umbraco.Web.UI/umbraco/developer/Xslt/editXslt.aspx index 451a3d294d..4e96930591 100644 --- a/src/Umbraco.Web.UI/umbraco/developer/Xslt/editXslt.aspx +++ b/src/Umbraco.Web.UI/umbraco/developer/Xslt/editXslt.aspx @@ -57,7 +57,7 @@ xsltSnippet = UmbEditor.IsSimpleEditor ? jQuery("#<%= editorSource.ClientID %>").getSelection().text - : UmbEditor._editor.getSelection(); + : UmbEditor._editor.selection(); if (xsltSnippet == '') { xsltSnippet = UmbEditor.IsSimpleEditor diff --git a/src/Umbraco.Web.UI/umbraco/dialogs/umbracoField.aspx b/src/Umbraco.Web.UI/umbraco/dialogs/umbracoField.aspx index 9f3bda9703..39144f52b0 100644 --- a/src/Umbraco.Web.UI/umbraco/dialogs/umbracoField.aspx +++ b/src/Umbraco.Web.UI/umbraco/dialogs/umbracoField.aspx @@ -9,7 +9,8 @@ { margin-top: 0px !important; padding-top: 0px !important; -