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 }
+ );
}