diff --git a/src/Umbraco.Web/Editors/BackOfficeController.cs b/src/Umbraco.Web/Editors/BackOfficeController.cs index 74e616e756..199ffd9bed 100644 --- a/src/Umbraco.Web/Editors/BackOfficeController.cs +++ b/src/Umbraco.Web/Editors/BackOfficeController.cs @@ -262,7 +262,7 @@ namespace Umbraco.Web.Editors }, { "mediaTypeApiBaseUrl", Url.GetUmbracoApiServiceBaseUrl( - controller => controller.GetAllowedChildren("0")) + controller => controller.GetAllowedChildren(0)) }, { "macroApiBaseUrl", Url.GetUmbracoApiServiceBaseUrl( diff --git a/src/Umbraco.Web/Editors/MediaController.cs b/src/Umbraco.Web/Editors/MediaController.cs index 2ed01c0192..77ab9ff6e5 100644 --- a/src/Umbraco.Web/Editors/MediaController.cs +++ b/src/Umbraco.Web/Editors/MediaController.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.ComponentModel; using System.IO; using System.Net; using System.Net.Http; @@ -27,6 +28,7 @@ using Umbraco.Web.Mvc; using Umbraco.Web.WebApi; using System.Linq; using System.Runtime.Serialization; +using System.Web.Http.Controllers; using Umbraco.Web.WebApi.Binders; using Umbraco.Web.WebApi.Filters; using umbraco; @@ -45,9 +47,22 @@ namespace Umbraco.Web.Editors /// access to ALL of the methods on this controller will need access to the media application. /// [PluginController("UmbracoApi")] - [UmbracoApplicationAuthorizeAttribute(Constants.Applications.Media)] + [UmbracoApplicationAuthorize(Constants.Applications.Media)] + [MediaControllerControllerConfiguration] public class MediaController : ContentControllerBase { + /// + /// Configures this controller with a custom action selector + /// + private class MediaControllerControllerConfigurationAttribute : Attribute, IControllerConfiguration + { + public void Initialize(HttpControllerSettings controllerSettings, HttpControllerDescriptor controllerDescriptor) + { + controllerSettings.Services.Replace(typeof(IHttpActionSelector), new ParameterSwapControllerActionSelector( + new ParameterSwapControllerActionSelector.ParameterSwapInfo("GetChildren", "id", typeof(int), typeof(Guid), typeof(string)))); + } + } + /// /// Constructor /// @@ -173,40 +188,10 @@ namespace Umbraco.Web.Editors } /// - /// Returns the child media objects + /// Returns the child media objects - using the entity INT id /// [FilterAllowedOutgoingMedia(typeof(IEnumerable>), "Items")] - public PagedResult> GetChildren(string id, - int pageNumber = 0, - int pageSize = 0, - string orderBy = "SortOrder", - Direction orderDirection = Direction.Ascending, - bool orderBySystemField = true, - string filter = "") - { - int idInt; Guid idGuid; - - if (Guid.TryParse(id, out idGuid)) - { - var entity = Services.EntityService.GetByKey(idGuid); - if (entity != null) - { - return GetChildren(entity.Id, pageNumber, pageSize, orderBy, orderDirection, orderBySystemField, filter); - } - else - { - throw new HttpResponseException(HttpStatusCode.NotFound); - } - } - else if (int.TryParse(id, out idInt)) - { - return GetChildren(idInt, pageNumber, pageSize, orderBy, orderDirection, orderBySystemField, filter); - } - - throw new HttpResponseException(HttpStatusCode.NotFound); - } - - private PagedResult> GetChildren(int id, + public PagedResult> GetChildren(int id, int pageNumber = 0, int pageSize = 0, string orderBy = "SortOrder", @@ -240,6 +225,58 @@ namespace Umbraco.Web.Editors return pagedResult; } + /// + /// Returns the child media objects - using the entity GUID id + /// + /// + /// + /// + /// + /// + /// + /// + /// + [FilterAllowedOutgoingMedia(typeof(IEnumerable>), "Items")] + public PagedResult> GetChildren(Guid id, + int pageNumber = 0, + int pageSize = 0, + string orderBy = "SortOrder", + Direction orderDirection = Direction.Ascending, + bool orderBySystemField = true, + string filter = "") + { + var entity = Services.EntityService.GetByKey(id); + if (entity != null) + { + return GetChildren(entity.Id, pageNumber, pageSize, orderBy, orderDirection, orderBySystemField, filter); + } + throw new HttpResponseException(HttpStatusCode.NotFound); + } + + [Obsolete("Do not use this method, use either the overload with INT or GUID instead, this will be removed in future versions")] + [EditorBrowsable(EditorBrowsableState.Never)] + [UmbracoTreeAuthorize(Constants.Trees.MediaTypes, Constants.Trees.Media)] + public PagedResult> GetChildren(string id, + int pageNumber = 0, + int pageSize = 0, + string orderBy = "SortOrder", + Direction orderDirection = Direction.Ascending, + bool orderBySystemField = true, + string filter = "") + { + foreach (var type in new[] { typeof(int), typeof(Guid) }) + { + var parsed = id.TryConvertTo(type); + if (parsed) + { + //oooh magic! will auto select the right overload + return GetChildren((dynamic)parsed.Result); + } + } + + throw new HttpResponseException(HttpStatusCode.NotFound); + } + /// /// Moves an item to the recycle bin, if it is already there then it will permanently delete it /// diff --git a/src/Umbraco.Web/Editors/MediaTypeController.cs b/src/Umbraco.Web/Editors/MediaTypeController.cs index 67b8ae6d1f..db2a806572 100644 --- a/src/Umbraco.Web/Editors/MediaTypeController.cs +++ b/src/Umbraco.Web/Editors/MediaTypeController.cs @@ -13,6 +13,9 @@ using Umbraco.Web.WebApi; using Umbraco.Core.Services; using Umbraco.Core.Models.EntityBase; using System; +using System.ComponentModel; +using System.Web.Http.Controllers; +using Umbraco.Core; namespace Umbraco.Web.Editors { @@ -26,8 +29,21 @@ namespace Umbraco.Web.Editors [PluginController("UmbracoApi")] [UmbracoTreeAuthorize(Constants.Trees.MediaTypes)] [EnableOverrideAuthorization] + [MediaTypeControllerControllerConfigurationAttribute] public class MediaTypeController : ContentTypeControllerBase { + /// + /// Configures this controller with a custom action selector + /// + private class MediaTypeControllerControllerConfigurationAttribute : Attribute, IControllerConfiguration + { + public void Initialize(HttpControllerSettings controllerSettings, HttpControllerDescriptor controllerDescriptor) + { + controllerSettings.Services.Replace(typeof(IHttpActionSelector), new ParameterSwapControllerActionSelector( + new ParameterSwapControllerActionSelector.ParameterSwapInfo("GetAllowedChildren", "contentId", typeof(int), typeof(Guid), typeof(string)))); + } + } + /// /// Constructor /// @@ -172,26 +188,11 @@ namespace Umbraco.Web.Editors /// - /// Returns the allowed child content type objects for the content item id passed in + /// Returns the allowed child content type objects for the content item id passed in - based on an INT id /// /// [UmbracoTreeAuthorize(Constants.Trees.MediaTypes, Constants.Trees.Media)] - public IEnumerable GetAllowedChildren(string contentId) - { - Guid idGuid = Guid.Empty; - int idInt; - if (Guid.TryParse(contentId, out idGuid)) { - var entity = ApplicationContext.Services.EntityService.GetByKey(idGuid); - return GetAllowedChildrenInternal(entity.Id); - } else if (int.TryParse(contentId, out idInt)) - { - return GetAllowedChildrenInternal(idInt); - } - - throw new HttpResponseException(HttpStatusCode.NotFound); - } - - private IEnumerable GetAllowedChildrenInternal(int contentId) + public IEnumerable GetAllowedChildren(int contentId) { if (contentId == Constants.System.RecycleBinContent) return Enumerable.Empty(); @@ -231,6 +232,40 @@ namespace Umbraco.Web.Editors return basics; } + /// + /// Returns the allowed child content type objects for the content item id passed in - based on a GUID id + /// + /// + [UmbracoTreeAuthorize(Constants.Trees.MediaTypes, Constants.Trees.Media)] + public IEnumerable GetAllowedChildren(Guid contentId) + { + var entity = ApplicationContext.Services.EntityService.GetByKey(contentId); + if (entity != null) + { + return GetAllowedChildren(entity.Id); + } + + throw new HttpResponseException(HttpStatusCode.NotFound); + } + + [Obsolete("Do not use this method, use either the overload with INT or GUID instead, this will be removed in future versions")] + [EditorBrowsable(EditorBrowsableState.Never)] + [UmbracoTreeAuthorize(Constants.Trees.MediaTypes, Constants.Trees.Media)] + public IEnumerable GetAllowedChildren(string contentId) + { + foreach (var type in new[] { typeof(int), typeof(Guid) }) + { + var parsed = contentId.TryConvertTo(type); + if (parsed) + { + //oooh magic! will auto select the right overload + return GetAllowedChildren((dynamic)parsed.Result); + } + } + + throw new HttpResponseException(HttpStatusCode.NotFound); + } + /// /// Move the media type /// diff --git a/src/Umbraco.Web/Editors/ParameterSwapControllerActionSelector.cs b/src/Umbraco.Web/Editors/ParameterSwapControllerActionSelector.cs new file mode 100644 index 0000000000..7f17fb9f8b --- /dev/null +++ b/src/Umbraco.Web/Editors/ParameterSwapControllerActionSelector.cs @@ -0,0 +1,84 @@ +using System; +using System.Linq; +using System.Web; +using System.Web.Http.Controllers; +using Umbraco.Core; + +namespace Umbraco.Web.Editors +{ + /// + /// This is used to auto-select specific actions on controllers that would otherwise be ambiguous based on a single parameter type + /// + /// + /// As an example, lets say we have 2 methods: GetChildren(int id) and GetChildren(Guid id), by default Web Api won't allow this since + /// it won't know what to select, but if this Tuple is passed in new Tuple{string, string}("GetChildren", "id") + /// + internal class ParameterSwapControllerActionSelector : ApiControllerActionSelector + { + private readonly ParameterSwapInfo[] _actions; + + /// + /// Constructor accepting a list of action name + parameter name + /// + /// + public ParameterSwapControllerActionSelector(params ParameterSwapInfo[] actions) + { + _actions = actions; + } + public override HttpActionDescriptor SelectAction(HttpControllerContext controllerContext) + { + var found = _actions.FirstOrDefault(x => controllerContext.Request.RequestUri.GetLeftPart(UriPartial.Path).InvariantEndsWith(x.ActionName)); + + if (found != null) + { + var id = HttpUtility.ParseQueryString(controllerContext.Request.RequestUri.Query).Get(found.ParamName); + + if (id != null) + { + var idTypes = found.SupportedTypes; + + foreach (var idType in idTypes) + { + var converted = id.TryConvertTo(idType); + if (converted) + { + var method = MatchByType(idType, controllerContext, found); + if (method != null) + return method; + } + } + } + } + return base.SelectAction(controllerContext); + } + + private static ReflectedHttpActionDescriptor MatchByType(Type idType, HttpControllerContext controllerContext, ParameterSwapInfo found) + { + var controllerType = controllerContext.Controller.GetType(); + var methods = controllerType.GetMethods().Where(info => info.Name == found.ActionName).ToArray(); + if (methods.Length > 1) + { + //choose the one that has the parameter with the T type + var method = methods.FirstOrDefault(x => x.GetParameters().FirstOrDefault(p => p.Name == found.ParamName && p.ParameterType == idType) != null); + + return new ReflectedHttpActionDescriptor(controllerContext.ControllerDescriptor, method); + } + return null; + } + + internal class ParameterSwapInfo + { + public string ActionName { get; private set; } + public string ParamName { get; private set; } + public Type[] SupportedTypes { get; private set; } + + public ParameterSwapInfo(string actionName, string paramName, params Type[] supportedTypes) + { + ActionName = actionName; + ParamName = paramName; + SupportedTypes = supportedTypes; + } + } + + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 31aa38b1bf..d15c510819 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -278,6 +278,7 @@ +