diff --git a/src/Umbraco.Core/TypeExtensions.cs b/src/Umbraco.Core/TypeExtensions.cs index 15f3e56924..70639d7ff1 100644 --- a/src/Umbraco.Core/TypeExtensions.cs +++ b/src/Umbraco.Core/TypeExtensions.cs @@ -398,7 +398,7 @@ namespace Umbraco.Core /// /// the source type /// - internal static Type GetEnumeratedType(this Type type) + public static Type GetEnumeratedType(this Type type) { if (typeof(IEnumerable).IsAssignableFrom(type) == false) return null; diff --git a/src/Umbraco.ModelsBuilder.Embedded/Building/TextBuilder.cs b/src/Umbraco.ModelsBuilder.Embedded/Building/TextBuilder.cs index 43771eb46a..8328afb822 100644 --- a/src/Umbraco.ModelsBuilder.Embedded/Building/TextBuilder.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/Building/TextBuilder.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Linq; using System.Text; using System.Text.RegularExpressions; -using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.Models; namespace Umbraco.ModelsBuilder.Embedded.Building @@ -254,7 +253,7 @@ namespace Umbraco.ModelsBuilder.Embedded.Building sb.AppendFormat(" {0} => ", property.ClrName); WriteNonGenericClrType(sb, GetModelsNamespace() + "." + mixinClrName); - sb.AppendFormat(".{0}(this);\n", + sb.AppendFormat(".{0}(this, _publishedValueFallback);\n", MixinStaticGetterName(property.ClrName)); } @@ -311,7 +310,7 @@ namespace Umbraco.ModelsBuilder.Embedded.Building { sb.Append("\t\tpublic "); WriteClrType(sb, property.ClrTypeName); - sb.AppendFormat(" {0} => {1}(this);\n", + sb.AppendFormat(" {0} => {1}(this, _publishedValueFallback);\n", property.ClrName, MixinStaticGetterName(property.ClrName)); } else @@ -351,7 +350,7 @@ namespace Umbraco.ModelsBuilder.Embedded.Building WriteGeneratedCodeAttribute(sb, "\t\t"); sb.Append("\t\tpublic static "); WriteClrType(sb, property.ClrTypeName); - sb.AppendFormat(" {0}(I{1} that) => that.Value", + sb.AppendFormat(" {0}(I{1} that, IPublishedValueFallback publishedValueFallback) => that.Value", mixinStaticGetterName, mixinClrName); if (property.ModelClrType != typeof(object)) { @@ -359,7 +358,7 @@ namespace Umbraco.ModelsBuilder.Embedded.Building WriteClrType(sb, property.ClrTypeName); sb.Append(">"); } - sb.AppendFormat("(\"{0}\");\n", + sb.AppendFormat("(publishedValueFallback, \"{0}\");\n", property.Alias); } diff --git a/src/Umbraco.ModelsBuilder.Embedded/RoslynCompiler.cs b/src/Umbraco.ModelsBuilder.Embedded/RoslynCompiler.cs index 1321078f98..37aeb75b35 100644 --- a/src/Umbraco.ModelsBuilder.Embedded/RoslynCompiler.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/RoslynCompiler.cs @@ -69,8 +69,13 @@ namespace Umbraco.ModelsBuilder.Embedded // Not entirely certain that assemblyIdentityComparer is nececary? assemblyIdentityComparer: DesktopAssemblyIdentityComparer.Default)); - compilation.Emit(savePath); + var emitResult = compilation.Emit(savePath); + if (!emitResult.Success) + { + throw new InvalidOperationException("Roslyn compiler could not create ModelsBuilder dll:\n" + + string.Join("\n", emitResult.Diagnostics.Select(x=>x.GetMessage()))); + } } } } diff --git a/src/Umbraco.Web.BackOffice/Controllers/ContentController.cs b/src/Umbraco.Web.BackOffice/Controllers/ContentController.cs index eb7cf7e411..895f2f76b5 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/ContentController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/ContentController.cs @@ -45,6 +45,8 @@ namespace Umbraco.Web.BackOffice.Controllers /// [PluginController(Constants.Web.Mvc.BackOfficeApiArea)] [Authorize(Policy = AuthorizationPolicies.TreeAccessDocuments)] + [ParameterSwapControllerActionSelector(nameof(GetById), "id", typeof(int), typeof(Guid), typeof(Udi))] + [ParameterSwapControllerActionSelector(nameof(GetNiceUrl), "id", typeof(int), typeof(Guid), typeof(Udi))] public class ContentController : ContentControllerBase { private readonly PropertyEditorCollection _propertyEditors; @@ -332,7 +334,6 @@ namespace Umbraco.Web.BackOffice.Controllers /// [OutgoingEditorModelEvent] [Authorize(Policy = AuthorizationPolicies.ContentPermissionBrowseById)] - [DetermineAmbiguousActionByPassingParameters] public ActionResult GetById(int id) { var foundContent = GetObjectFromRequest(() => _contentService.GetById(id)); @@ -351,7 +352,6 @@ namespace Umbraco.Web.BackOffice.Controllers /// [OutgoingEditorModelEvent] [Authorize(Policy = AuthorizationPolicies.ContentPermissionBrowseById)] - [DetermineAmbiguousActionByPassingParameters] public ActionResult GetById(Guid id) { var foundContent = GetObjectFromRequest(() => _contentService.GetById(id)); @@ -371,7 +371,6 @@ namespace Umbraco.Web.BackOffice.Controllers /// [OutgoingEditorModelEvent] [Authorize(Policy = AuthorizationPolicies.ContentPermissionBrowseById)] - [DetermineAmbiguousActionByPassingParameters] public ActionResult GetById(Udi id) { var guidUdi = id as GuidUdi; @@ -389,7 +388,6 @@ namespace Umbraco.Web.BackOffice.Controllers /// /// [OutgoingEditorModelEvent] - [DetermineAmbiguousActionByPassingParameters] public ActionResult GetEmpty(string contentTypeAlias, int parentId) { var contentType = _contentTypeService.Get(contentTypeAlias); @@ -398,7 +396,7 @@ namespace Umbraco.Web.BackOffice.Controllers return NotFound(); } - return GetEmpty(contentType, parentId); + return GetEmptyInner(contentType, parentId); } @@ -416,10 +414,10 @@ namespace Umbraco.Web.BackOffice.Controllers return NotFound(); } - return GetEmpty(contentType, parentId); + return GetEmptyInner(contentType, parentId); } - private ContentItemDisplay GetEmpty(IContentType contentType, int parentId) + private ContentItemDisplay GetEmptyInner(IContentType contentType, int parentId) { var emptyContent = _contentService.Create("", parentId, contentType.Alias, _backofficeSecurityAccessor.BackOfficeSecurity.GetUserId().ResultOr(0)); var mapped = MapToDisplay(emptyContent); @@ -436,8 +434,7 @@ namespace Umbraco.Web.BackOffice.Controllers } [OutgoingEditorModelEvent] - [DetermineAmbiguousActionByPassingParameters] - public ActionResult GetEmpty(int blueprintId, int parentId) + public ActionResult GetEmptyBlueprint(int blueprintId, int parentId) { var blueprint = _contentService.GetBlueprintById(blueprintId); if (blueprint == null) @@ -462,7 +459,6 @@ namespace Umbraco.Web.BackOffice.Controllers /// /// /// - [DetermineAmbiguousActionByPassingParameters] public IActionResult GetNiceUrl(int id) { var url = _publishedUrlProvider.GetUrl(id); @@ -474,7 +470,6 @@ namespace Umbraco.Web.BackOffice.Controllers /// /// /// - [DetermineAmbiguousActionByPassingParameters] public IActionResult GetNiceUrl(Guid id) { var url = _publishedUrlProvider.GetUrl(id); @@ -486,7 +481,6 @@ namespace Umbraco.Web.BackOffice.Controllers /// /// /// - [DetermineAmbiguousActionByPassingParameters] public IActionResult GetNiceUrl(Udi id) { var guidUdi = id as GuidUdi; @@ -504,7 +498,6 @@ namespace Umbraco.Web.BackOffice.Controllers /// /// [FilterAllowedOutgoingContent(typeof(IEnumerable>), "Items")] - [DetermineAmbiguousActionByPassingParameters] public PagedResult> GetChildren( int id, string includeProperties, @@ -1641,7 +1634,7 @@ namespace Umbraco.Web.BackOffice.Controllers } var toMoveResult = ValidateMoveOrCopy(move); - if (!(toMoveResult is null)) + if (!(toMoveResult.Result is null)) { return toMoveResult.Result; } diff --git a/src/Umbraco.Web.BackOffice/Controllers/ContentTypeController.cs b/src/Umbraco.Web.BackOffice/Controllers/ContentTypeController.cs index ea34005a87..e3437cbd11 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/ContentTypeController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/ContentTypeController.cs @@ -34,6 +34,7 @@ namespace Umbraco.Web.BackOffice.Controllers /// An API controller used for dealing with content types /// [PluginController(Constants.Web.Mvc.BackOfficeApiArea)] + [ParameterSwapControllerActionSelector(nameof(GetById), "id", typeof(int), typeof(Guid), typeof(Udi))] public class ContentTypeController : ContentTypeControllerBase { // TODO: Split this controller apart so that authz is consistent, currently we need to authz each action individually. @@ -113,7 +114,6 @@ namespace Umbraco.Web.BackOffice.Controllers /// /// Gets the document type a given id /// - [DetermineAmbiguousActionByPassingParameters] [Authorize(Policy = AuthorizationPolicies.TreeAccessDocumentTypes)] public ActionResult GetById(int id) { @@ -130,7 +130,6 @@ namespace Umbraco.Web.BackOffice.Controllers /// /// Gets the document type a given guid /// - [DetermineAmbiguousActionByPassingParameters] [Authorize(Policy = AuthorizationPolicies.TreeAccessDocumentTypes)] public ActionResult GetById(Guid id) { @@ -147,7 +146,6 @@ namespace Umbraco.Web.BackOffice.Controllers /// /// Gets the document type a given udi /// - [DetermineAmbiguousActionByPassingParameters] [Authorize(Policy = AuthorizationPolicies.TreeAccessDocumentTypes)] public ActionResult GetById(Udi id) { diff --git a/src/Umbraco.Web.BackOffice/Controllers/DataTypeController.cs b/src/Umbraco.Web.BackOffice/Controllers/DataTypeController.cs index 4cc1f80f9e..eb47b7457e 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/DataTypeController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/DataTypeController.cs @@ -21,7 +21,6 @@ using Umbraco.Web.Common.ActionsResults; using Umbraco.Web.Common.Attributes; using Umbraco.Web.Common.Authorization; using Umbraco.Web.Models.ContentEditing; -using Constants = Umbraco.Core.Constants; namespace Umbraco.Web.BackOffice.Controllers { @@ -34,6 +33,7 @@ namespace Umbraco.Web.BackOffice.Controllers /// [PluginController(Constants.Web.Mvc.BackOfficeApiArea)] [Authorize(Policy = AuthorizationPolicies.TreeAccessDocumentsOrDocumentTypes)] + [ParameterSwapControllerActionSelector(nameof(GetById), "id", typeof(int), typeof(Guid), typeof(Udi))] public class DataTypeController : BackOfficeNotificationsController { private readonly PropertyEditorCollection _propertyEditors; @@ -90,7 +90,6 @@ namespace Umbraco.Web.BackOffice.Controllers /// /// /// - [DetermineAmbiguousActionByPassingParameters] public ActionResult GetById(int id) { var dataType = _dataTypeService.GetDataType(id); @@ -107,7 +106,6 @@ namespace Umbraco.Web.BackOffice.Controllers /// /// /// - [DetermineAmbiguousActionByPassingParameters] public ActionResult GetById(Guid id) { var dataType = _dataTypeService.GetDataType(id); @@ -124,7 +122,6 @@ namespace Umbraco.Web.BackOffice.Controllers /// /// /// - [DetermineAmbiguousActionByPassingParameters] public ActionResult GetById(Udi id) { var guidUdi = id as GuidUdi; diff --git a/src/Umbraco.Web.BackOffice/Controllers/DetermineAmbiguousActionByPassingParameters.cs b/src/Umbraco.Web.BackOffice/Controllers/DetermineAmbiguousActionByPassingParameters.cs deleted file mode 100644 index fc6c3b6814..0000000000 --- a/src/Umbraco.Web.BackOffice/Controllers/DetermineAmbiguousActionByPassingParameters.cs +++ /dev/null @@ -1,129 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; -using System.Net.Http; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Mvc.Abstractions; -using Microsoft.AspNetCore.Mvc.ActionConstraints; -using Microsoft.AspNetCore.Routing; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using Umbraco.Core; -using Umbraco.Extensions; -using Umbraco.Web.BackOffice.ModelBinders; - -namespace Umbraco.Web.BackOffice.Controllers -{ - /// - /// This method determine ambiguous controller actions by making a tryparse of the string (from request) into the type of the argument. - /// - /// - /// By using this methods you are allowed to to have multiple controller actions named equally. E.g. GetById(Guid id), GetById(int id),... - /// - public class DetermineAmbiguousActionByPassingParameters : ActionMethodSelectorAttribute - { - private string _requestBody = null; - - public override bool IsValidForRequest(RouteContext routeContext, ActionDescriptor action) - { - var parameters = action.Parameters; - if (parameters.Any()) - { - var canUse = true; - foreach (var parameterDescriptor in parameters) - { - var values = GetValue(parameterDescriptor, routeContext); - - var type = parameterDescriptor.ParameterType; - - if(typeof(IEnumerable).IsAssignableFrom(type) && typeof(string) != type) - { - type = type.GetElementType(); - } - - if (values is null ) - { - canUse &= Nullable.GetUnderlyingType(type) != null; - } - else - { - foreach (var value in values) - { - if (type == typeof(Udi)) - { - canUse &= UdiParser.TryParse(value.ToString(), out _); - } - else if (type == typeof(int)) - { - canUse &= int.TryParse(value.ToString(), out _); - } - else if (type == typeof(Guid)) - { - canUse &= Guid.TryParse(value.ToString(), out _); - } - else if (type == typeof(string)) - { - canUse &= true; - } - else if (type == typeof(bool)) - { - canUse &= bool.TryParse(value, out _); - } - else if (type == typeof(Direction)) - { - canUse &= Enum.TryParse(value, out _); - } - else - { - canUse &= true; - } - } - } - - } - - return canUse; - } - - - return true; - } - - private IEnumerable GetValue(ParameterDescriptor descriptor, RouteContext routeContext) - { - if (routeContext.HttpContext.Request.Query.ContainsKey(descriptor.Name)) - { - return routeContext.HttpContext.Request.Query[descriptor.Name]; - } - - if (descriptor.BindingInfo.BinderType == typeof(FromJsonPathAttribute.JsonPathBinder)) - { - // IMPORTANT: Ensure the requestBody can be read multiple times. - routeContext.HttpContext.Request.EnableBuffering(); - - // We need to use the asynchronous method here if synchronous IO is not allowed (it may or may not be, depending - // on configuration in UmbracoBackOfficeServiceCollectionExtensions.AddUmbraco()). - // We can't use async/await due to the need to override IsValidForRequest, which doesn't have an async override, so going with - // this, which seems to be the least worst option for "sync to async" (https://stackoverflow.com/a/32429753/489433). - var body = _requestBody ??= Task.Run(() => routeContext.HttpContext.Request.GetRawBodyStringAsync()).GetAwaiter().GetResult(); - - var jToken = JsonConvert.DeserializeObject(body); - - return jToken[descriptor.Name].Values(); - } - - if (routeContext.HttpContext.Request.Method.InvariantEquals(HttpMethod.Post.ToString()) && routeContext.HttpContext.Request.Form.ContainsKey(descriptor.Name)) - { - return routeContext.HttpContext.Request.Form[descriptor.Name]; - } - - return null; - - } - } - -} - - diff --git a/src/Umbraco.Web.BackOffice/Controllers/DictionaryController.cs b/src/Umbraco.Web.BackOffice/Controllers/DictionaryController.cs index 6a03c6ef89..519dcc6c6a 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/DictionaryController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/DictionaryController.cs @@ -2,22 +2,21 @@ using System; using System.Collections.Generic; using System.Linq; using System.Net.Http; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; using Umbraco.Core; +using Umbraco.Core.Configuration.Models; using Umbraco.Core.Mapping; using Umbraco.Core.Models; using Umbraco.Core.Security; using Umbraco.Core.Services; -using Umbraco.Web.Common.Attributes; -using Umbraco.Web.Models.ContentEditing; -using Constants = Umbraco.Core.Constants; -using Umbraco.Core.Configuration.Models; -using Microsoft.Extensions.Options; -using Microsoft.AspNetCore.Authorization; using Umbraco.Extensions; using Umbraco.Web.Common.ActionsResults; +using Umbraco.Web.Common.Attributes; using Umbraco.Web.Common.Authorization; +using Umbraco.Web.Models.ContentEditing; namespace Umbraco.Web.BackOffice.Controllers { @@ -31,6 +30,7 @@ namespace Umbraco.Web.BackOffice.Controllers /// [PluginController(Constants.Web.Mvc.BackOfficeApiArea)] [Authorize(Policy = AuthorizationPolicies.TreeAccessDictionary)] + [ParameterSwapControllerActionSelector(nameof(GetById), "id", typeof(int), typeof(Guid), typeof(Udi))] public class DictionaryController : BackOfficeNotificationsController { private readonly ILogger _logger; @@ -141,8 +141,7 @@ namespace Umbraco.Web.BackOffice.Controllers /// /// The . Returns a not found response when dictionary item does not exist /// - [DetermineAmbiguousActionByPassingParameters] - public ActionResult GetById(int id) + public ActionResult GetById(int id) { var dictionary = _localizationService.GetDictionaryItemById(id); if (dictionary == null) @@ -160,7 +159,6 @@ namespace Umbraco.Web.BackOffice.Controllers /// /// The . Returns a not found response when dictionary item does not exist /// - [DetermineAmbiguousActionByPassingParameters] public ActionResult GetById(Guid id) { var dictionary = _localizationService.GetDictionaryItemById(id); @@ -179,7 +177,6 @@ namespace Umbraco.Web.BackOffice.Controllers /// /// The . Returns a not found response when dictionary item does not exist /// - [DetermineAmbiguousActionByPassingParameters] public ActionResult GetById(Udi id) { var guidUdi = id as GuidUdi; diff --git a/src/Umbraco.Web.BackOffice/Controllers/EntityController.cs b/src/Umbraco.Web.BackOffice/Controllers/EntityController.cs index 13ef66fa15..bf15621fae 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/EntityController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/EntityController.cs @@ -43,6 +43,12 @@ namespace Umbraco.Web.BackOffice.Controllers /// Some objects such as macros are not based on CMSNode /// [PluginController(Constants.Web.Mvc.BackOfficeApiArea)] + [ParameterSwapControllerActionSelector(nameof(GetPagedChildren), "id", typeof(int), typeof(string))] + [ParameterSwapControllerActionSelector(nameof(GetPath), "id", typeof(int), typeof(Guid), typeof(Udi))] + [ParameterSwapControllerActionSelector(nameof(GetUrlAndAnchors), "id", typeof(int), typeof(Guid), typeof(Udi))] + [ParameterSwapControllerActionSelector(nameof(GetById), "id", typeof(int), typeof(Guid), typeof(Udi))] + [ParameterSwapControllerActionSelector(nameof(GetByIds), "ids", typeof(int[]), typeof(Guid[]), typeof(Udi[]))] + [ParameterSwapControllerActionSelector(nameof(GetUrl), "id", typeof(int), typeof(Udi))] public class EntityController : UmbracoAuthorizedJsonController { private readonly ITreeService _treeService; @@ -201,7 +207,6 @@ namespace Umbraco.Web.BackOffice.Controllers /// /// /// - [DetermineAmbiguousActionByPassingParameters] public IConvertToActionResult GetPath(int id, UmbracoEntityTypes type) { var foundContentResult = GetResultForId(id, type); @@ -220,7 +225,6 @@ namespace Umbraco.Web.BackOffice.Controllers /// /// /// - [DetermineAmbiguousActionByPassingParameters] public IConvertToActionResult GetPath(Guid id, UmbracoEntityTypes type) { var foundContentResult = GetResultForKey(id, type); @@ -239,7 +243,6 @@ namespace Umbraco.Web.BackOffice.Controllers /// /// /// - [DetermineAmbiguousActionByPassingParameters] public IActionResult GetPath(Udi id, UmbracoEntityTypes type) { var guidUdi = id as GuidUdi; @@ -257,7 +260,6 @@ namespace Umbraco.Web.BackOffice.Controllers /// UDI of the entity to fetch URL for /// The culture to fetch the URL for /// The URL or path to the item - [DetermineAmbiguousActionByPassingParameters] public IActionResult GetUrl(Udi udi, string culture = "*") { var intId = _entityService.GetId(udi); @@ -291,7 +293,6 @@ namespace Umbraco.Web.BackOffice.Controllers /// /// We are not restricting this with security because there is no sensitive data /// - [DetermineAmbiguousActionByPassingParameters] public IActionResult GetUrl(int id, UmbracoEntityTypes type, string culture = null) { culture = culture ?? ClientCulture(); @@ -363,7 +364,6 @@ namespace Umbraco.Web.BackOffice.Controllers } [HttpGet] - [DetermineAmbiguousActionByPassingParameters] public ActionResult GetUrlAndAnchors(Udi id, string culture = "*") { var intId = _entityService.GetId(id); @@ -373,7 +373,6 @@ namespace Umbraco.Web.BackOffice.Controllers return GetUrlAndAnchors(intId.Result, culture); } [HttpGet] - [DetermineAmbiguousActionByPassingParameters] public UrlAndAnchors GetUrlAndAnchors(int id, string culture = "*") { culture = culture ?? ClientCulture(); @@ -400,7 +399,6 @@ namespace Umbraco.Web.BackOffice.Controllers /// /// /// - [DetermineAmbiguousActionByPassingParameters] public ActionResult GetById(int id, UmbracoEntityTypes type) { return GetResultForId(id, type); @@ -412,7 +410,6 @@ namespace Umbraco.Web.BackOffice.Controllers /// /// /// - [DetermineAmbiguousActionByPassingParameters] public ActionResult GetById(Guid id, UmbracoEntityTypes type) { return GetResultForKey(id, type); @@ -424,7 +421,6 @@ namespace Umbraco.Web.BackOffice.Controllers /// /// /// - [DetermineAmbiguousActionByPassingParameters] public ActionResult GetById(Udi id, UmbracoEntityTypes type) { var guidUdi = id as GuidUdi; @@ -449,8 +445,7 @@ namespace Umbraco.Web.BackOffice.Controllers /// [HttpGet] [HttpPost] - [DetermineAmbiguousActionByPassingParameters] - public ActionResult> GetByIds([FromJsonPath]int[] ids, UmbracoEntityTypes type) + public ActionResult> GetByIds([FromJsonPath]int[] ids, [FromQuery]UmbracoEntityTypes type) { if (ids == null) { @@ -471,8 +466,7 @@ namespace Umbraco.Web.BackOffice.Controllers /// [HttpGet] [HttpPost] - [DetermineAmbiguousActionByPassingParameters] - public ActionResult> GetByIds([FromJsonPath]Guid[] ids, UmbracoEntityTypes type) + public ActionResult> GetByIds([FromJsonPath]Guid[] ids, [FromQuery]UmbracoEntityTypes type) { if (ids == null) { @@ -495,7 +489,6 @@ namespace Umbraco.Web.BackOffice.Controllers /// [HttpGet] [HttpPost] - [DetermineAmbiguousActionByPassingParameters] public ActionResult> GetByIds([FromJsonPath]Udi[] ids, [FromQuery]UmbracoEntityTypes type) { if (ids == null) @@ -572,7 +565,6 @@ namespace Umbraco.Web.BackOffice.Controllers /// /// /// - [DetermineAmbiguousActionByPassingParameters] public ActionResult> GetPagedChildren( string id, UmbracoEntityTypes type, @@ -634,7 +626,6 @@ namespace Umbraco.Web.BackOffice.Controllers /// /// /// - [DetermineAmbiguousActionByPassingParameters] public ActionResult> GetPagedChildren( int id, UmbracoEntityTypes type, diff --git a/src/Umbraco.Web.BackOffice/Controllers/IconController.cs b/src/Umbraco.Web.BackOffice/Controllers/IconController.cs index a856306118..2d481b627f 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/IconController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/IconController.cs @@ -20,7 +20,6 @@ namespace Umbraco.Web.BackOffice.Controllers /// /// /// - [DetermineAmbiguousActionByPassingParameters] public IconModel GetIcon(string iconName) { return _iconService.GetIcon(iconName); diff --git a/src/Umbraco.Web.BackOffice/Controllers/MacrosController.cs b/src/Umbraco.Web.BackOffice/Controllers/MacrosController.cs index 1ad7442289..c8a943d92a 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/MacrosController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/MacrosController.cs @@ -1,24 +1,23 @@ -using Umbraco.Core.Services; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Net.Http; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; -using Umbraco.Core.Hosting; -using Umbraco.Core.Models; -using Umbraco.Core.Strings; -using Umbraco.Web.Models.ContentEditing; -using Constants = Umbraco.Core.Constants; -using Umbraco.Core.PropertyEditors; -using Umbraco.Web.Common.Attributes; using Umbraco.Core; +using Umbraco.Core.Hosting; using Umbraco.Core.Mapping; +using Umbraco.Core.Models; +using Umbraco.Core.PropertyEditors; using Umbraco.Core.Security; -using Microsoft.AspNetCore.Authorization; +using Umbraco.Core.Services; +using Umbraco.Core.Strings; using Umbraco.Web.Common.ActionsResults; +using Umbraco.Web.Common.Attributes; using Umbraco.Web.Common.Authorization; +using Umbraco.Web.Models.ContentEditing; namespace Umbraco.Web.BackOffice.Controllers { @@ -28,6 +27,7 @@ namespace Umbraco.Web.BackOffice.Controllers /// [PluginController(Constants.Web.Mvc.BackOfficeApiArea)] [Authorize(Policy = AuthorizationPolicies.TreeAccessMacros)] + [ParameterSwapControllerActionSelector(nameof(GetById), "id", typeof(int), typeof(Guid), typeof(Udi))] public class MacrosController : BackOfficeNotificationsController { private readonly ParameterEditorCollection _parameterEditorCollection; @@ -109,7 +109,6 @@ namespace Umbraco.Web.BackOffice.Controllers } [HttpGet] - [DetermineAmbiguousActionByPassingParameters] public ActionResult GetById(int id) { var macro = _macroService.GetById(id); @@ -125,7 +124,6 @@ namespace Umbraco.Web.BackOffice.Controllers } [HttpGet] - [DetermineAmbiguousActionByPassingParameters] public ActionResult GetById(Guid id) { var macro = _macroService.GetById(id); @@ -141,7 +139,6 @@ namespace Umbraco.Web.BackOffice.Controllers } [HttpGet] - [DetermineAmbiguousActionByPassingParameters] public ActionResult GetById(Udi id) { var guidUdi = id as GuidUdi; diff --git a/src/Umbraco.Web.BackOffice/Controllers/MediaController.cs b/src/Umbraco.Web.BackOffice/Controllers/MediaController.cs index e822e7df84..b230664b28 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/MediaController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/MediaController.cs @@ -50,6 +50,8 @@ namespace Umbraco.Web.BackOffice.Controllers /// [PluginController(Constants.Web.Mvc.BackOfficeApiArea)] [Authorize(Policy = AuthorizationPolicies.SectionAccessMedia)] + [ParameterSwapControllerActionSelector(nameof(GetById), "id", typeof(int), typeof(Guid), typeof(Udi))] + [ParameterSwapControllerActionSelector(nameof(GetChildren), "id", typeof(int), typeof(Guid), typeof(Udi))] public class MediaController : ContentControllerBase { private readonly IShortStringHelper _shortStringHelper; @@ -170,7 +172,6 @@ namespace Umbraco.Web.BackOffice.Controllers /// [OutgoingEditorModelEvent] [Authorize(Policy = AuthorizationPolicies.MediaPermissionPathById)] - [DetermineAmbiguousActionByPassingParameters] public MediaItemDisplay GetById(int id) { var foundMedia = GetObjectFromRequest(() => _mediaService.GetById(id)); @@ -191,7 +192,6 @@ namespace Umbraco.Web.BackOffice.Controllers /// [OutgoingEditorModelEvent] [Authorize(Policy = AuthorizationPolicies.MediaPermissionPathById)] - [DetermineAmbiguousActionByPassingParameters] public MediaItemDisplay GetById(Guid id) { var foundMedia = GetObjectFromRequest(() => _mediaService.GetById(id)); @@ -212,7 +212,6 @@ namespace Umbraco.Web.BackOffice.Controllers /// [OutgoingEditorModelEvent] [Authorize(Policy = AuthorizationPolicies.MediaPermissionPathById)] - [DetermineAmbiguousActionByPassingParameters] public ActionResult GetById(Udi id) { var guidUdi = id as GuidUdi; @@ -299,7 +298,6 @@ namespace Umbraco.Web.BackOffice.Controllers /// Returns the child media objects - using the entity INT id /// [FilterAllowedOutgoingMedia(typeof(IEnumerable>), "Items")] - [DetermineAmbiguousActionByPassingParameters] public PagedResult> GetChildren(int id, int pageNumber = 0, int pageSize = 0, @@ -377,7 +375,6 @@ namespace Umbraco.Web.BackOffice.Controllers /// /// [FilterAllowedOutgoingMedia(typeof(IEnumerable>), "Items")] - [DetermineAmbiguousActionByPassingParameters] public ActionResult>> GetChildren(Guid id, int pageNumber = 0, int pageSize = 0, @@ -407,7 +404,6 @@ namespace Umbraco.Web.BackOffice.Controllers /// /// [FilterAllowedOutgoingMedia(typeof(IEnumerable>), "Items")] - [DetermineAmbiguousActionByPassingParameters] public ActionResult>> GetChildren(Udi id, int pageNumber = 0, int pageSize = 0, diff --git a/src/Umbraco.Web.BackOffice/Controllers/MediaTypeController.cs b/src/Umbraco.Web.BackOffice/Controllers/MediaTypeController.cs index b8952e580f..769bac0868 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/MediaTypeController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/MediaTypeController.cs @@ -22,6 +22,8 @@ namespace Umbraco.Web.BackOffice.Controllers /// An API controller used for dealing with content types /// [PluginController(Constants.Web.Mvc.BackOfficeApiArea)] + [ParameterSwapControllerActionSelector(nameof(GetById), "id", typeof(int), typeof(Guid), typeof(Udi))] + [ParameterSwapControllerActionSelector(nameof(GetAllowedChildren), "contentId", typeof(int), typeof(Guid), typeof(Udi))] public class MediaTypeController : ContentTypeControllerBase { // TODO: Split this controller apart so that authz is consistent, currently we need to authz each action individually. @@ -74,7 +76,6 @@ namespace Umbraco.Web.BackOffice.Controllers /// /// /// - [DetermineAmbiguousActionByPassingParameters] [Authorize(Policy = AuthorizationPolicies.TreeAccessMediaOrMediaTypes)] public ActionResult GetById(int id) { @@ -93,7 +94,6 @@ namespace Umbraco.Web.BackOffice.Controllers /// /// /// - [DetermineAmbiguousActionByPassingParameters] [Authorize(Policy = AuthorizationPolicies.TreeAccessMediaOrMediaTypes)] public ActionResult GetById(Guid id) { @@ -112,7 +112,6 @@ namespace Umbraco.Web.BackOffice.Controllers /// /// /// - [DetermineAmbiguousActionByPassingParameters] [Authorize(Policy = AuthorizationPolicies.TreeAccessMediaOrMediaTypes)] public ActionResult GetById(Udi id) { @@ -338,7 +337,6 @@ namespace Umbraco.Web.BackOffice.Controllers /// /// [Authorize(Policy = AuthorizationPolicies.TreeAccessMediaOrMediaTypes)] - [DetermineAmbiguousActionByPassingParameters] public IEnumerable GetAllowedChildren(int contentId) { if (contentId == Constants.System.RecycleBinContent) @@ -385,7 +383,6 @@ namespace Umbraco.Web.BackOffice.Controllers /// /// [Authorize(Policy = AuthorizationPolicies.TreeAccessMediaOrMediaTypes)] - [DetermineAmbiguousActionByPassingParameters] public ActionResult> GetAllowedChildren(Guid contentId) { var entity = _entityService.Get(contentId); @@ -402,7 +399,6 @@ namespace Umbraco.Web.BackOffice.Controllers /// /// [Authorize(Policy = AuthorizationPolicies.TreeAccessMediaOrMediaTypes)] - [DetermineAmbiguousActionByPassingParameters] public ActionResult> GetAllowedChildren(Udi contentId) { var guidUdi = contentId as GuidUdi; diff --git a/src/Umbraco.Web.BackOffice/Controllers/MemberGroupController.cs b/src/Umbraco.Web.BackOffice/Controllers/MemberGroupController.cs index 43df969ef5..c7d64c550d 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/MemberGroupController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/MemberGroupController.cs @@ -10,7 +10,6 @@ using Umbraco.Core.Services; using Umbraco.Web.Common.Attributes; using Umbraco.Web.Common.Authorization; using Umbraco.Web.Models.ContentEditing; -using Constants = Umbraco.Core.Constants; namespace Umbraco.Web.BackOffice.Controllers { @@ -19,6 +18,7 @@ namespace Umbraco.Web.BackOffice.Controllers /// [PluginController(Constants.Web.Mvc.BackOfficeApiArea)] [Authorize(Policy = AuthorizationPolicies.TreeAccessMemberGroups)] + [ParameterSwapControllerActionSelector(nameof(GetById), "id", typeof(int), typeof(Guid), typeof(Udi))] public class MemberGroupController : UmbracoAuthorizedJsonController { private readonly IMemberGroupService _memberGroupService; @@ -42,7 +42,6 @@ namespace Umbraco.Web.BackOffice.Controllers /// /// /// - [DetermineAmbiguousActionByPassingParameters] public ActionResult GetById(int id) { var memberGroup = _memberGroupService.GetById(id); @@ -61,7 +60,6 @@ namespace Umbraco.Web.BackOffice.Controllers /// /// /// - [DetermineAmbiguousActionByPassingParameters] public ActionResult GetById(Guid id) { var memberGroup = _memberGroupService.GetById(id); @@ -78,7 +76,6 @@ namespace Umbraco.Web.BackOffice.Controllers /// /// /// - [DetermineAmbiguousActionByPassingParameters] public ActionResult GetById(Udi id) { var guidUdi = id as GuidUdi; diff --git a/src/Umbraco.Web.BackOffice/Controllers/MemberTypeController.cs b/src/Umbraco.Web.BackOffice/Controllers/MemberTypeController.cs index 7944da1f0a..728245e042 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/MemberTypeController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/MemberTypeController.cs @@ -22,6 +22,7 @@ namespace Umbraco.Web.BackOffice.Controllers /// [PluginController(Constants.Web.Mvc.BackOfficeApiArea)] [Authorize(Policy = AuthorizationPolicies.TreeAccessMemberTypes)] + [ParameterSwapControllerActionSelector(nameof(GetById), "id", typeof(int), typeof(Guid), typeof(Udi))] public class MemberTypeController : ContentTypeControllerBase { private readonly IMemberTypeService _memberTypeService; @@ -61,7 +62,6 @@ namespace Umbraco.Web.BackOffice.Controllers /// /// /// - [DetermineAmbiguousActionByPassingParameters] public ActionResult GetById(int id) { var mt = _memberTypeService.Get(id); @@ -79,7 +79,6 @@ namespace Umbraco.Web.BackOffice.Controllers /// /// /// - [DetermineAmbiguousActionByPassingParameters] public ActionResult GetById(Guid id) { var memberType = _memberTypeService.Get(id); @@ -97,7 +96,6 @@ namespace Umbraco.Web.BackOffice.Controllers /// /// /// - [DetermineAmbiguousActionByPassingParameters] public ActionResult GetById(Udi id) { var guidUdi = id as GuidUdi; diff --git a/src/Umbraco.Web.BackOffice/Controllers/ParameterSwapControllerActionSelectorAttribute.cs b/src/Umbraco.Web.BackOffice/Controllers/ParameterSwapControllerActionSelectorAttribute.cs new file mode 100644 index 0000000000..5e423af270 --- /dev/null +++ b/src/Umbraco.Web.BackOffice/Controllers/ParameterSwapControllerActionSelectorAttribute.cs @@ -0,0 +1,176 @@ +using System; +using System.Linq; +using System.Net.Http; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc.ActionConstraints; +using Microsoft.AspNetCore.Mvc.Controllers; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Umbraco.Core; +using Umbraco.Extensions; + +namespace Umbraco.Web.BackOffice.Controllers +{ + [AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = true)] + internal class ParameterSwapControllerActionSelectorAttribute : Attribute, IActionConstraint + { + private readonly string _actionName; + private readonly string _parameterName; + private readonly Type[] _supportedTypes; + private string _requestBody; + + public ParameterSwapControllerActionSelectorAttribute(string actionName, string parameterName, params Type[] supportedTypes) + { + _actionName = actionName; + _parameterName = parameterName; + _supportedTypes = supportedTypes; + } + + /// + public int Order { get; set; } = 101; + + /// + public bool Accept(ActionConstraintContext context) + { + if (!IsValidCandidate(context.CurrentCandidate)) + { + return true; + } + + var chosenCandidate = SelectAction(context); + + var found = context.CurrentCandidate.Equals(chosenCandidate); + return found; + } + + private ActionSelectorCandidate? SelectAction(ActionConstraintContext context) + { + if (TryBindFromUri(context, out var candidate)) + { + return candidate; + } + + //if it's a post we can try to read from the body and bind from the json value + if (context.RouteContext.HttpContext.Request.Method == HttpMethod.Post.ToString()) + { + // We need to use the asynchronous method here if synchronous IO is not allowed (it may or may not be, depending + // on configuration in UmbracoBackOfficeServiceCollectionExtensions.AddUmbraco()). + // We can't use async/await due to the need to override IsValidForRequest, which doesn't have an async override, so going with + // this, which seems to be the least worst option for "sync to async" (https://stackoverflow.com/a/32429753/489433). + var strJson = _requestBody ??= Task.Run(() => context.RouteContext.HttpContext.Request.GetRawBodyStringAsync()).GetAwaiter().GetResult(); + + var json = JsonConvert.DeserializeObject(strJson); + + if (json == null) + { + return null; + } + + var requestParam = json[_parameterName]; + + if (requestParam != null) + { + var paramTypes = _supportedTypes; + + foreach (var paramType in paramTypes) + { + try + { + var converted = requestParam.ToObject(paramType); + if (converted != null) + { + var foundCandidate = MatchByType(paramType, context); + if (foundCandidate.HasValue) + { + return foundCandidate; + } + } + } + catch (JsonReaderException) + { + //can't convert + } + catch (JsonSerializationException) + { + //can't convert + } + } + } + } + + return null; + } + + private bool TryBindFromUri(ActionConstraintContext context, out ActionSelectorCandidate? foundCandidate) + { + + string requestParam = null; + if (context.RouteContext.HttpContext.Request.Query.TryGetValue(_parameterName, out var stringValues)) + { + requestParam = stringValues.ToString(); + } + + if (requestParam == string.Empty && _supportedTypes.Length > 0) + { + //if it's empty then in theory we can select any of the actions since they'll all need to deal with empty or null parameters + //so we'll try to use the first one available + foundCandidate = MatchByType(_supportedTypes[0], context); + if (foundCandidate.HasValue) + { + return true; + } + + } + + if (requestParam != null) + { + foreach (var paramType in _supportedTypes) + { + //check if this is IEnumerable and if so this will get it's type + //we need to know this since the requestParam will always just be a string + var enumType = paramType.GetEnumeratedType(); + + var converted = requestParam.TryConvertTo(enumType ?? paramType); + if (converted) + { + foundCandidate = MatchByType(paramType, context); + if (foundCandidate.HasValue) + { + return true; + } + } + } + } + + foundCandidate = null; + return false; + } + + private ActionSelectorCandidate? MatchByType(Type idType, ActionConstraintContext context) + { + if (context.Candidates.Count() > 1) + { + //choose the one that has the parameter with the T type + var candidate = context.Candidates.FirstOrDefault(x => x.Action.Parameters.FirstOrDefault(p => p.Name == _parameterName && p.ParameterType == idType) != null); + + return candidate; + } + return null; + } + + private bool IsValidCandidate(ActionSelectorCandidate candidate) + { + if (!(candidate.Action is ControllerActionDescriptor controllerActionDescriptor)) + { + return false; + } + + if (controllerActionDescriptor.ActionName != _actionName) + { + return false; + } + + return true; + } + } +} diff --git a/src/Umbraco.Web.BackOffice/Controllers/RelationTypeController.cs b/src/Umbraco.Web.BackOffice/Controllers/RelationTypeController.cs index 0a6d174bdd..8ef1a24951 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/RelationTypeController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/RelationTypeController.cs @@ -2,19 +2,18 @@ using System; using System.Collections.Generic; using System.Linq; using System.Net.Http; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; using Umbraco.Core; +using Umbraco.Core.Mapping; using Umbraco.Core.Models; using Umbraco.Core.Services; using Umbraco.Core.Strings; -using Umbraco.Web.Models.ContentEditing; -using Constants = Umbraco.Core.Constants; -using Umbraco.Core.Mapping; -using Umbraco.Web.Common.Attributes; -using Microsoft.AspNetCore.Authorization; using Umbraco.Web.Common.ActionsResults; +using Umbraco.Web.Common.Attributes; using Umbraco.Web.Common.Authorization; +using Umbraco.Web.Models.ContentEditing; namespace Umbraco.Web.BackOffice.Controllers { @@ -23,6 +22,7 @@ namespace Umbraco.Web.BackOffice.Controllers /// [PluginController(Constants.Web.Mvc.BackOfficeApiArea)] [Authorize(Policy = AuthorizationPolicies.TreeAccessRelationTypes)] + [ParameterSwapControllerActionSelector(nameof(GetById), "id", typeof(int), typeof(Guid), typeof(Udi))] public class RelationTypeController : BackOfficeNotificationsController { private readonly ILogger _logger; @@ -47,7 +47,6 @@ namespace Umbraco.Web.BackOffice.Controllers /// /// The relation type ID. /// Returns the . - [DetermineAmbiguousActionByPassingParameters] public ActionResult GetById(int id) { var relationType = _relationService.GetRelationTypeById(id); @@ -66,7 +65,6 @@ namespace Umbraco.Web.BackOffice.Controllers /// /// The relation type ID. /// Returns the . - [DetermineAmbiguousActionByPassingParameters] public ActionResult GetById(Guid id) { var relationType = _relationService.GetRelationTypeById(id); @@ -82,7 +80,6 @@ namespace Umbraco.Web.BackOffice.Controllers /// /// The relation type ID. /// Returns the . - [DetermineAmbiguousActionByPassingParameters] public ActionResult GetById(Udi id) { var guidUdi = id as GuidUdi; diff --git a/src/Umbraco.Web.BackOffice/Controllers/TemplateController.cs b/src/Umbraco.Web.BackOffice/Controllers/TemplateController.cs index 560e3bc78c..5b85c381c4 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/TemplateController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/TemplateController.cs @@ -12,12 +12,12 @@ using Umbraco.Core.Strings; using Umbraco.Web.Common.Attributes; using Umbraco.Web.Common.Authorization; using Umbraco.Web.Models.ContentEditing; -using Constants = Umbraco.Core.Constants; namespace Umbraco.Web.BackOffice.Controllers { [PluginController(Constants.Web.Mvc.BackOfficeApiArea)] [Authorize(Policy = AuthorizationPolicies.TreeAccessTemplates)] + [ParameterSwapControllerActionSelector(nameof(GetById), "id", typeof(int), typeof(Guid), typeof(Udi))] public class TemplateController : BackOfficeNotificationsController { private readonly IFileService _fileService; @@ -59,7 +59,6 @@ namespace Umbraco.Web.BackOffice.Controllers /// /// /// - [DetermineAmbiguousActionByPassingParameters] public ActionResult GetById(int id) { var template = _fileService.GetTemplate(id); @@ -75,7 +74,6 @@ namespace Umbraco.Web.BackOffice.Controllers /// /// /// - [DetermineAmbiguousActionByPassingParameters] public ActionResult GetById(Guid id) { var template = _fileService.GetTemplate(id); @@ -90,7 +88,6 @@ namespace Umbraco.Web.BackOffice.Controllers /// /// /// - [DetermineAmbiguousActionByPassingParameters] public ActionResult GetById(Udi id) { var guidUdi = id as GuidUdi; diff --git a/src/Umbraco.Web.BackOffice/Filters/CheckIfUserTicketDataIsStaleAttribute.cs b/src/Umbraco.Web.BackOffice/Filters/CheckIfUserTicketDataIsStaleAttribute.cs index 6e14468a2a..e6e2d579b5 100644 --- a/src/Umbraco.Web.BackOffice/Filters/CheckIfUserTicketDataIsStaleAttribute.cs +++ b/src/Umbraco.Web.BackOffice/Filters/CheckIfUserTicketDataIsStaleAttribute.cs @@ -13,6 +13,7 @@ using Umbraco.Core.Configuration.Models; using Umbraco.Core.Mapping; using Umbraco.Core.Models; using Umbraco.Core.Models.Membership; +using Umbraco.Core.Scoping; using Umbraco.Core.Security; using Umbraco.Core.Services; using Umbraco.Extensions; @@ -22,7 +23,7 @@ using Umbraco.Web.Common.Security; namespace Umbraco.Web.BackOffice.Filters { /// - /// + /// /// internal sealed class CheckIfUserTicketDataIsStaleAttribute : TypeFilterAttribute { @@ -41,6 +42,7 @@ namespace Umbraco.Web.BackOffice.Filters private readonly IOptions _globalSettings; private readonly IBackOfficeSignInManager _backOfficeSignInManager; private readonly IBackOfficeAntiforgery _backOfficeAntiforgery; + private readonly IScopeProvider _scopeProvider; public CheckIfUserTicketDataIsStaleFilter( IRequestCache requestCache, @@ -50,7 +52,8 @@ namespace Umbraco.Web.BackOffice.Filters ILocalizedTextService localizedTextService, IOptions globalSettings, IBackOfficeSignInManager backOfficeSignInManager, - IBackOfficeAntiforgery backOfficeAntiforgery) + IBackOfficeAntiforgery backOfficeAntiforgery, + IScopeProvider scopeProvider) { _requestCache = requestCache; _umbracoMapper = umbracoMapper; @@ -60,6 +63,7 @@ namespace Umbraco.Web.BackOffice.Filters _globalSettings = globalSettings; _backOfficeSignInManager = backOfficeSignInManager; _backOfficeAntiforgery = backOfficeAntiforgery; + _scopeProvider = scopeProvider; } @@ -95,7 +99,9 @@ namespace Umbraco.Web.BackOffice.Filters private async Task CheckStaleData(ActionExecutingContext actionContext) { - if (actionContext?.HttpContext.Request == null || actionContext.HttpContext.User?.Identity == null) + using (var scope = _scopeProvider.CreateScope(autoComplete: true)) + { + if (actionContext?.HttpContext.Request == null || actionContext.HttpContext.User?.Identity == null) { return; } @@ -151,6 +157,7 @@ namespace Umbraco.Web.BackOffice.Filters { await ReSync(user, actionContext); } + } } /// diff --git a/src/Umbraco.Web.Common/AspNetCore/UmbracoViewPage.cs b/src/Umbraco.Web.Common/AspNetCore/UmbracoViewPage.cs index cd429ec458..74b40403f0 100644 --- a/src/Umbraco.Web.Common/AspNetCore/UmbracoViewPage.cs +++ b/src/Umbraco.Web.Common/AspNetCore/UmbracoViewPage.cs @@ -1,6 +1,5 @@ using System; using Microsoft.AspNetCore.Html; -using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Extensions; using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Mvc.Razor; @@ -11,7 +10,6 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Umbraco.Core; using Umbraco.Core.Configuration.Models; -using Umbraco.Core.Events; using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.Models.PublishedContent; @@ -19,6 +17,7 @@ using Umbraco.Core.Strings; using Umbraco.Extensions; using Umbraco.Web.Common.ModelBinders; using Umbraco.Web.Models; +using Umbraco.Web.Website; namespace Umbraco.Web.Common.AspNetCore { @@ -32,6 +31,7 @@ namespace Umbraco.Web.Common.AspNetCore public abstract class UmbracoViewPage : RazorPage { private IUmbracoContext _umbracoContext; + private UmbracoHelper _helper; private IUmbracoContextAccessor UmbracoContextAccessor => Context.RequestServices.GetRequiredService(); @@ -43,6 +43,29 @@ namespace Umbraco.Web.Common.AspNetCore private IIOHelper IOHelper => Context.RequestServices.GetRequiredService(); + + /// + /// Gets the Umbraco helper. + /// + public UmbracoHelper Umbraco + { + get + { + if (_helper != null) return _helper; + + var model = ViewData.Model; + var content = model as IPublishedContent; + if (content == null && model is IContentModel) + content = ((IContentModel) model).Content; + + _helper = Context.RequestServices.GetRequiredService(); + + if (content != null) + _helper.AssignedContentItem = content; + + return _helper; + } + } /// /// Gets the /// @@ -190,7 +213,7 @@ namespace Umbraco.Web.Common.AspNetCore // need to check whether that is possible Type viewDataModelType = viewDataType.GenericTypeArguments[0]; - if (viewDataModelType.IsAssignableFrom(modelType)) + if (viewDataModelType != typeof(object) && viewDataModelType.IsAssignableFrom(modelType)) { return viewData; } diff --git a/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs b/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs index 4b9953edf7..b81b0382aa 100644 --- a/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs +++ b/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs @@ -52,6 +52,7 @@ using Umbraco.Web.Macros; using Umbraco.Web.Security; using Umbraco.Web.Telemetry; using Umbraco.Web.Templates; +using Umbraco.Web.Website; using IHostingEnvironment = Umbraco.Core.Hosting.IHostingEnvironment; namespace Umbraco.Web.Common.DependencyInjection @@ -275,6 +276,8 @@ namespace Umbraco.Web.Common.DependencyInjection builder.Services.AddSingleton(); + builder.Services.AddScoped(); + builder.AddHttpClients(); // TODO: Does this belong in web components?? diff --git a/src/Umbraco.Web.Common/Extensions/HttpRequestExtensions.cs b/src/Umbraco.Web.Common/Extensions/HttpRequestExtensions.cs index 31e65edf65..29c178e655 100644 --- a/src/Umbraco.Web.Common/Extensions/HttpRequestExtensions.cs +++ b/src/Umbraco.Web.Common/Extensions/HttpRequestExtensions.cs @@ -1,4 +1,4 @@ -using System.IO; +using System.IO; using System.Net; using System.Text; using System.Threading.Tasks; @@ -74,24 +74,36 @@ namespace Umbraco.Extensions public static string GetRawBodyString(this HttpRequest request, Encoding encoding = null) { - request.Body.Seek(0, SeekOrigin.Begin); + if (request.Body.CanSeek) + { + request.Body.Seek(0, SeekOrigin.Begin); + } using (var reader = new StreamReader(request.Body, encoding ?? Encoding.UTF8, leaveOpen: true)) { var result = reader.ReadToEnd(); - request.Body.Seek(0, SeekOrigin.Begin); + if (request.Body.CanSeek) + { + request.Body.Seek(0, SeekOrigin.Begin); + } return result; } } public static async Task GetRawBodyStringAsync(this HttpRequest request, Encoding encoding = null) { + if (!request.Body.CanSeek) + { + request.EnableBuffering(); + } + request.Body.Seek(0, SeekOrigin.Begin); using (var reader = new StreamReader(request.Body, encoding ?? Encoding.UTF8, leaveOpen: true)) { var result = await reader.ReadToEndAsync(); request.Body.Seek(0, SeekOrigin.Begin); + return result; } } diff --git a/src/Umbraco.Web.Common/Extensions/UrlHelperExtensions.cs b/src/Umbraco.Web.Common/Extensions/UrlHelperExtensions.cs index 03329547bc..22c4f5fc6a 100644 --- a/src/Umbraco.Web.Common/Extensions/UrlHelperExtensions.cs +++ b/src/Umbraco.Web.Common/Extensions/UrlHelperExtensions.cs @@ -1,16 +1,15 @@ using System; +using System.Globalization; using System.Linq; using System.Linq.Expressions; using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Routing; using Umbraco.Core; +using Umbraco.Core.Configuration; +using Umbraco.Core.Hosting; +using Umbraco.Core.WebAssets; using Umbraco.Web.Common.Controllers; using Umbraco.Web.WebApi; -using Umbraco.Web.Common.Install; -using Microsoft.AspNetCore.Routing; -using Umbraco.Core.Hosting; -using System.Globalization; -using Umbraco.Core.Configuration; -using Umbraco.Core.WebAssets; namespace Umbraco.Extensions { @@ -18,7 +17,7 @@ namespace Umbraco.Extensions public static class UrlHelperExtensions { - + /// /// Return the back office url if the back office is installed diff --git a/src/Umbraco.Web.Website/UmbracoHelper.cs b/src/Umbraco.Web.Common/UmbracoHelper.cs similarity index 99% rename from src/Umbraco.Web.Website/UmbracoHelper.cs rename to src/Umbraco.Web.Common/UmbracoHelper.cs index ed6d5d36b0..54aeda6b09 100644 --- a/src/Umbraco.Web.Website/UmbracoHelper.cs +++ b/src/Umbraco.Web.Common/UmbracoHelper.cs @@ -1,13 +1,13 @@ using System; using System.Collections.Generic; +using System.Threading.Tasks; using System.Xml.XPath; using Umbraco.Core; using Umbraco.Core.Dictionary; using Umbraco.Core.Models.PublishedContent; -using Umbraco.Core.Templates; using Umbraco.Core.Strings; +using Umbraco.Core.Templates; using Umbraco.Core.Xml; -using System.Threading.Tasks; namespace Umbraco.Web.Website { @@ -36,15 +36,13 @@ namespace Umbraco.Web.Website /// /// /// Sets the current page to the context's published content request's content item. - public UmbracoHelper(IPublishedContent currentPage, - ICultureDictionaryFactory cultureDictionary, + public UmbracoHelper(ICultureDictionaryFactory cultureDictionary, IUmbracoComponentRenderer componentRenderer, IPublishedContentQuery publishedContentQuery) { _cultureDictionaryFactory = cultureDictionary ?? throw new ArgumentNullException(nameof(cultureDictionary)); _componentRenderer = componentRenderer ?? throw new ArgumentNullException(nameof(componentRenderer)); _publishedContentQuery = publishedContentQuery ?? throw new ArgumentNullException(nameof(publishedContentQuery)); - _currentPage = currentPage; } /// diff --git a/src/Umbraco.Web.UI.Client/package-lock.json b/src/Umbraco.Web.UI.Client/package-lock.json index 452b5c2071..dea27a0d69 100644 --- a/src/Umbraco.Web.UI.Client/package-lock.json +++ b/src/Umbraco.Web.UI.Client/package-lock.json @@ -1842,7 +1842,8 @@ "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "dev": true + "dev": true, + "optional": true }, "base64id": { "version": "1.0.0", @@ -2051,7 +2052,8 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=", - "dev": true + "dev": true, + "optional": true }, "got": { "version": "8.3.2", @@ -2129,6 +2131,7 @@ "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-2.0.1.tgz", "integrity": "sha1-2N0ZeVldLcATnh/ka4tkbLPN8Dg=", "dev": true, + "optional": true, "requires": { "p-finally": "^1.0.0" } @@ -2170,6 +2173,7 @@ "resolved": "https://registry.npmjs.org/bl/-/bl-1.2.3.tgz", "integrity": "sha512-pvcNpa0UU69UT341rO6AYy4FVAIkUHuZXRIWbq+zHnsVcRzDDjIAhGuuYoi0d//cwIwtt4pkpKycWEfjdV+vww==", "dev": true, + "optional": true, "requires": { "readable-stream": "^2.3.5", "safe-buffer": "^5.1.1" @@ -2179,13 +2183,15 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true + "dev": true, + "optional": true }, "readable-stream": { "version": "2.3.7", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", "dev": true, + "optional": true, "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -2201,6 +2207,7 @@ "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, + "optional": true, "requires": { "safe-buffer": "~5.1.0" } @@ -2341,6 +2348,7 @@ "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", "dev": true, + "optional": true, "requires": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" @@ -2366,7 +2374,8 @@ "version": "0.2.13", "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=", - "dev": true + "dev": true, + "optional": true }, "buffer-equal": { "version": "1.0.0", @@ -2563,6 +2572,7 @@ "resolved": "https://registry.npmjs.org/caw/-/caw-2.0.1.tgz", "integrity": "sha1-bDygcfwZRyCIPC3F2psHS/x+npU=", "dev": true, + "optional": true, "requires": { "get-proxy": "^2.0.0", "isurl": "^1.0.0-alpha5", @@ -2994,7 +3004,8 @@ "version": "2.20.3", "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true + "dev": true, + "optional": true }, "component-bind": { "version": "1.0.0", @@ -3086,6 +3097,7 @@ "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.12.tgz", "integrity": "sha1-D96NCRIA616AjK8l/mGMAvSOTvo=", "dev": true, + "optional": true, "requires": { "ini": "^1.3.4", "proto-list": "~1.2.1" @@ -3141,6 +3153,7 @@ "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", "integrity": "sha1-4TDK9+cnkIfFYWwgB9BIVpiYT70=", "dev": true, + "optional": true, "requires": { "safe-buffer": "5.1.2" } @@ -3582,6 +3595,7 @@ "resolved": "https://registry.npmjs.org/decompress/-/decompress-4.2.1.tgz", "integrity": "sha512-e48kc2IjU+2Zw8cTb6VZcJQ3lgVbS4uuB1TfCHbiZIP/haNXm+SVyhu+87jts5/3ROpd82GSVCoNs/z8l4ZOaQ==", "dev": true, + "optional": true, "requires": { "decompress-tar": "^4.0.0", "decompress-tarbz2": "^4.0.0", @@ -3598,6 +3612,7 @@ "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz", "integrity": "sha1-ecEDO4BRW9bSTsmTPoYMp17ifww=", "dev": true, + "optional": true, "requires": { "pify": "^3.0.0" }, @@ -3606,7 +3621,8 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", - "dev": true + "dev": true, + "optional": true } } } @@ -3617,6 +3633,7 @@ "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz", "integrity": "sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M=", "dev": true, + "optional": true, "requires": { "mimic-response": "^1.0.0" } @@ -3626,6 +3643,7 @@ "resolved": "https://registry.npmjs.org/decompress-tar/-/decompress-tar-4.1.1.tgz", "integrity": "sha1-cYy9P8sWIJcW5womuE57pFkuWvE=", "dev": true, + "optional": true, "requires": { "file-type": "^5.2.0", "is-stream": "^1.1.0", @@ -3636,7 +3654,8 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/file-type/-/file-type-5.2.0.tgz", "integrity": "sha1-LdvqfHP/42No365J3DOMBYwritY=", - "dev": true + "dev": true, + "optional": true } } }, @@ -3645,6 +3664,7 @@ "resolved": "https://registry.npmjs.org/decompress-tarbz2/-/decompress-tarbz2-4.1.1.tgz", "integrity": "sha1-MIKluIDqQEOBY0nzeLVsUWvho5s=", "dev": true, + "optional": true, "requires": { "decompress-tar": "^4.1.0", "file-type": "^6.1.0", @@ -3657,7 +3677,8 @@ "version": "6.2.0", "resolved": "https://registry.npmjs.org/file-type/-/file-type-6.2.0.tgz", "integrity": "sha1-5QzXXTVv/tTjBtxPW89Sp5kDqRk=", - "dev": true + "dev": true, + "optional": true } } }, @@ -3666,6 +3687,7 @@ "resolved": "https://registry.npmjs.org/decompress-targz/-/decompress-targz-4.1.1.tgz", "integrity": "sha1-wJvDXE0R894J8tLaU+neI+fOHu4=", "dev": true, + "optional": true, "requires": { "decompress-tar": "^4.1.1", "file-type": "^5.2.0", @@ -3676,7 +3698,8 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/file-type/-/file-type-5.2.0.tgz", "integrity": "sha1-LdvqfHP/42No365J3DOMBYwritY=", - "dev": true + "dev": true, + "optional": true } } }, @@ -3685,6 +3708,7 @@ "resolved": "https://registry.npmjs.org/decompress-unzip/-/decompress-unzip-4.0.1.tgz", "integrity": "sha1-3qrM39FK6vhVePczroIQ+bSEj2k=", "dev": true, + "optional": true, "requires": { "file-type": "^3.8.0", "get-stream": "^2.2.0", @@ -3696,13 +3720,15 @@ "version": "3.9.0", "resolved": "https://registry.npmjs.org/file-type/-/file-type-3.9.0.tgz", "integrity": "sha1-JXoHg4TR24CHvESdEH1SpSZyuek=", - "dev": true + "dev": true, + "optional": true }, "get-stream": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-2.3.1.tgz", "integrity": "sha1-Xzj5PzRgCWZu4BUKBUFn+Rvdld4=", "dev": true, + "optional": true, "requires": { "object-assign": "^4.0.1", "pinkie-promise": "^2.0.0" @@ -3712,7 +3738,8 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", - "dev": true + "dev": true, + "optional": true } } }, @@ -4000,7 +4027,8 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", - "dev": true + "dev": true, + "optional": true } } }, @@ -4017,7 +4045,8 @@ "version": "0.1.4", "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=", - "dev": true + "dev": true, + "optional": true }, "duplexify": { "version": "3.7.1", @@ -4662,6 +4691,7 @@ "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", "dev": true, + "optional": true, "requires": { "cross-spawn": "^6.0.0", "get-stream": "^4.0.0", @@ -4803,6 +4833,7 @@ "resolved": "https://registry.npmjs.org/ext-list/-/ext-list-2.2.2.tgz", "integrity": "sha1-C5jmTtgvWs8PKTG6v2khLvUt3Tc=", "dev": true, + "optional": true, "requires": { "mime-db": "^1.28.0" } @@ -4812,6 +4843,7 @@ "resolved": "https://registry.npmjs.org/ext-name/-/ext-name-5.0.0.tgz", "integrity": "sha1-cHgZgdGD7hXROZPIgiBFxQbI8KY=", "dev": true, + "optional": true, "requires": { "ext-list": "^2.0.0", "sort-keys-length": "^1.0.0" @@ -5049,6 +5081,7 @@ "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", "integrity": "sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4=", "dev": true, + "optional": true, "requires": { "pend": "~1.2.0" } @@ -5087,13 +5120,15 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/filename-reserved-regex/-/filename-reserved-regex-2.0.0.tgz", "integrity": "sha1-q/c9+rc10EVECr/qLZHzieu/oik=", - "dev": true + "dev": true, + "optional": true }, "filenamify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/filenamify/-/filenamify-2.1.0.tgz", "integrity": "sha1-iPr0lfsbR6v9YSMAACoWIoxnfuk=", "dev": true, + "optional": true, "requires": { "filename-reserved-regex": "^2.0.0", "strip-outer": "^1.0.0", @@ -5442,7 +5477,8 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", "integrity": "sha1-a+Dem+mYzhavivwkSXue6bfM2a0=", - "dev": true + "dev": true, + "optional": true }, "fs-mkdirp-stream": { "version": "1.0.0", @@ -5489,7 +5525,8 @@ "ansi-regex": { "version": "2.1.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "aproba": { "version": "1.2.0", @@ -5510,12 +5547,14 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, + "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -5530,17 +5569,20 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "core-util-is": { "version": "1.0.2", @@ -5657,7 +5699,8 @@ "inherits": { "version": "2.0.3", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "ini": { "version": "1.3.5", @@ -5669,6 +5712,7 @@ "version": "1.0.0", "bundled": true, "dev": true, + "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -5683,6 +5727,7 @@ "version": "3.0.4", "bundled": true, "dev": true, + "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -5690,12 +5735,14 @@ "minimist": { "version": "0.0.8", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "minipass": { "version": "2.3.5", "bundled": true, "dev": true, + "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -5714,6 +5761,7 @@ "version": "0.5.1", "bundled": true, "dev": true, + "optional": true, "requires": { "minimist": "0.0.8" } @@ -5794,7 +5842,8 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "object-assign": { "version": "4.1.1", @@ -5806,6 +5855,7 @@ "version": "1.4.0", "bundled": true, "dev": true, + "optional": true, "requires": { "wrappy": "1" } @@ -5891,7 +5941,8 @@ "safe-buffer": { "version": "5.1.2", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "safer-buffer": { "version": "2.1.2", @@ -5927,6 +5978,7 @@ "version": "1.0.2", "bundled": true, "dev": true, + "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -5946,6 +5998,7 @@ "version": "3.0.1", "bundled": true, "dev": true, + "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -5989,12 +6042,14 @@ "wrappy": { "version": "1.0.2", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "yallist": { "version": "3.0.3", "bundled": true, - "dev": true + "dev": true, + "optional": true } } }, @@ -6021,6 +6076,7 @@ "resolved": "https://registry.npmjs.org/get-proxy/-/get-proxy-2.1.0.tgz", "integrity": "sha1-NJ8rTZHUTE1NTpy6KtkBQ/rF75M=", "dev": true, + "optional": true, "requires": { "npm-conf": "^1.1.0" } @@ -6029,13 +6085,15 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz", "integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=", - "dev": true + "dev": true, + "optional": true }, "get-stream": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", "dev": true, + "optional": true, "requires": { "pump": "^3.0.0" }, @@ -6045,6 +6103,7 @@ "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", "dev": true, + "optional": true, "requires": { "end-of-stream": "^1.1.0", "once": "^1.3.1" @@ -6157,7 +6216,8 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true + "dev": true, + "optional": true }, "pump": { "version": "3.0.0", @@ -7262,7 +7322,8 @@ "version": "1.4.2", "resolved": "https://registry.npmjs.org/has-symbol-support-x/-/has-symbol-support-x-1.4.2.tgz", "integrity": "sha1-FAn5i8ACR9pF2mfO4KNvKC/yZFU=", - "dev": true + "dev": true, + "optional": true }, "has-symbols": { "version": "1.0.0", @@ -7275,6 +7336,7 @@ "resolved": "https://registry.npmjs.org/has-to-string-tag-x/-/has-to-string-tag-x-1.4.1.tgz", "integrity": "sha1-oEWrOD17SyASoAFIqwql8pAETU0=", "dev": true, + "optional": true, "requires": { "has-symbol-support-x": "^1.4.1" } @@ -7480,7 +7542,8 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "dev": true + "dev": true, + "optional": true }, "ignore": { "version": "4.0.6", @@ -7620,7 +7683,8 @@ "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true + "dev": true, + "optional": true }, "svgo": { "version": "1.3.2", @@ -7692,6 +7756,7 @@ "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz", "integrity": "sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=", "dev": true, + "optional": true, "requires": { "repeating": "^2.0.0" } @@ -8018,7 +8083,8 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.1.0.tgz", "integrity": "sha512-cdyMtqX/BOqqNBBiKlIVkytNHm49MtMlYyn1zxzvJKWmFMlGzm+ry5BBfYyeY9YmNKbRSo/o7OX9w9ale0wg3w==", - "dev": true + "dev": true, + "optional": true }, "is-fullwidth-code-point": { "version": "1.0.0", @@ -8068,7 +8134,8 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/is-natural-number/-/is-natural-number-4.0.1.tgz", "integrity": "sha1-q5124dtM7VHjXeDHLr7PCfc0zeg=", - "dev": true + "dev": true, + "optional": true }, "is-negated-glob": { "version": "1.0.0", @@ -8106,13 +8173,15 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-object/-/is-object-1.0.2.tgz", "integrity": "sha512-2rRIahhZr2UWb45fIOuvZGpFtz0TyOZLf32KxBbSoUCeZR495zCKlWUKKUByk3geS2eAs7ZAABt0Y/Rx0GiQGA==", - "dev": true + "dev": true, + "optional": true }, "is-plain-obj": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=", - "dev": true + "dev": true, + "optional": true }, "is-plain-object": { "version": "2.0.4", @@ -8182,13 +8251,15 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-1.2.0.tgz", "integrity": "sha1-13hIi9CkZmo76KFIK58rqv7eqLQ=", - "dev": true + "dev": true, + "optional": true }, "is-stream": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", - "dev": true + "dev": true, + "optional": true }, "is-svg": { "version": "3.0.0", @@ -8283,6 +8354,7 @@ "resolved": "https://registry.npmjs.org/isurl/-/isurl-1.0.0.tgz", "integrity": "sha1-sn9PSfPNqj6kSgpbfzRi5u3DnWc=", "dev": true, + "optional": true, "requires": { "has-to-string-tag-x": "^1.2.0", "is-object": "^1.0.1" @@ -9178,7 +9250,8 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", "integrity": "sha1-b54wtHCE2XGnyCD/FabFFnt0wm8=", - "dev": true + "dev": true, + "optional": true }, "lpad-align": { "version": "1.1.2", @@ -9248,7 +9321,8 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=", - "dev": true + "dev": true, + "optional": true }, "map-visit": { "version": "1.0.0", @@ -9416,7 +9490,8 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", "integrity": "sha1-SSNTiHju9CBjy4o+OweYeBSHqxs=", - "dev": true + "dev": true, + "optional": true }, "minimatch": { "version": "3.0.4", @@ -12763,6 +12838,7 @@ "resolved": "https://registry.npmjs.org/npm-conf/-/npm-conf-1.1.3.tgz", "integrity": "sha1-JWzEe9DiGMJZxOlVC/QTvCGSr/k=", "dev": true, + "optional": true, "requires": { "config-chain": "^1.1.11", "pify": "^3.0.0" @@ -12772,7 +12848,8 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", - "dev": true + "dev": true, + "optional": true } } }, @@ -12781,6 +12858,7 @@ "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", "dev": true, + "optional": true, "requires": { "path-key": "^2.0.0" } @@ -13149,7 +13227,8 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", - "dev": true + "dev": true, + "optional": true }, "p-is-promise": { "version": "1.1.0", @@ -13186,6 +13265,7 @@ "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-1.2.1.tgz", "integrity": "sha1-XrOzU7f86Z8QGhA4iAuwVOu+o4Y=", "dev": true, + "optional": true, "requires": { "p-finally": "^1.0.0" } @@ -13376,7 +13456,8 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=", - "dev": true + "dev": true, + "optional": true }, "performance-now": { "version": "2.1.0", @@ -13883,7 +13964,8 @@ "version": "1.2.4", "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", "integrity": "sha1-IS1b/hMYMGpCD2QCuOJv85ZHqEk=", - "dev": true + "dev": true, + "optional": true }, "prr": { "version": "1.0.1", @@ -14241,6 +14323,7 @@ "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz", "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=", "dev": true, + "optional": true, "requires": { "is-finite": "^1.0.0" } @@ -14595,6 +14678,7 @@ "resolved": "https://registry.npmjs.org/seek-bzip/-/seek-bzip-1.0.6.tgz", "integrity": "sha512-e1QtP3YL5tWww8uKaOCQ18UxIT2laNBXHjV/S2WYCiK4udiv8lkG89KRIoCjUagnAmCBurjF4zEVX2ByBbnCjQ==", "dev": true, + "optional": true, "requires": { "commander": "^2.8.1" } @@ -14989,6 +15073,7 @@ "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-1.1.2.tgz", "integrity": "sha1-RBttTTRnmPG05J6JIK37oOVD+a0=", "dev": true, + "optional": true, "requires": { "is-plain-obj": "^1.0.0" } @@ -14998,6 +15083,7 @@ "resolved": "https://registry.npmjs.org/sort-keys-length/-/sort-keys-length-1.0.1.tgz", "integrity": "sha1-nLb09OnkgVWmqgZx7dM2/xR5oYg=", "dev": true, + "optional": true, "requires": { "sort-keys": "^1.0.0" } @@ -15327,6 +15413,7 @@ "resolved": "https://registry.npmjs.org/strip-dirs/-/strip-dirs-2.1.0.tgz", "integrity": "sha1-SYdzYmT8NEzyD2w0rKnRPR1O1sU=", "dev": true, + "optional": true, "requires": { "is-natural-number": "^4.0.1" } @@ -15335,7 +15422,8 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", - "dev": true + "dev": true, + "optional": true }, "strip-final-newline": { "version": "2.0.0", @@ -15365,6 +15453,7 @@ "resolved": "https://registry.npmjs.org/strip-outer/-/strip-outer-1.0.1.tgz", "integrity": "sha1-sv0qv2YEudHmATBXGV34Nrip1jE=", "dev": true, + "optional": true, "requires": { "escape-string-regexp": "^1.0.2" } @@ -15490,6 +15579,7 @@ "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-1.6.2.tgz", "integrity": "sha1-jqVdqzeXIlPZqa+Q/c1VmuQ1xVU=", "dev": true, + "optional": true, "requires": { "bl": "^1.0.0", "buffer-alloc": "^1.2.0", @@ -15504,13 +15594,15 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true + "dev": true, + "optional": true }, "readable-stream": { "version": "2.3.7", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", "dev": true, + "optional": true, "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -15526,6 +15618,7 @@ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, + "optional": true, "requires": { "safe-buffer": "~5.1.0" } @@ -15536,13 +15629,15 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-1.0.0.tgz", "integrity": "sha1-CnwOom06Oa+n4OvqnB/AvE2qAR0=", - "dev": true + "dev": true, + "optional": true }, "tempfile": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/tempfile/-/tempfile-2.0.0.tgz", "integrity": "sha1-awRGhWqbERTRhW/8vlCczLCXcmU=", "dev": true, + "optional": true, "requires": { "temp-dir": "^1.0.0", "uuid": "^3.0.1" @@ -15637,7 +15732,8 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/timed-out/-/timed-out-4.0.1.tgz", "integrity": "sha1-8y6srFoXW+ol1/q1Zas+2HQe9W8=", - "dev": true + "dev": true, + "optional": true }, "timers-ext": { "version": "0.1.7", @@ -15694,7 +15790,8 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.1.1.tgz", "integrity": "sha1-STvUj2LXxD/N7TE6A9ytsuEhOoA=", - "dev": true + "dev": true, + "optional": true }, "to-fast-properties": { "version": "2.0.0", @@ -15796,6 +15893,7 @@ "resolved": "https://registry.npmjs.org/trim-repeated/-/trim-repeated-1.0.0.tgz", "integrity": "sha1-42RqLqTokTEr9+rObPsFOAvAHCE=", "dev": true, + "optional": true, "requires": { "escape-string-regexp": "^1.0.2" } @@ -15931,6 +16029,7 @@ "resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz", "integrity": "sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==", "dev": true, + "optional": true, "requires": { "buffer": "^5.2.1", "through": "^2.3.8" @@ -16139,7 +16238,8 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/url-to-options/-/url-to-options-1.0.1.tgz", "integrity": "sha1-FQWgOiiaSMvXpDTvuu7FBV9WM6k=", - "dev": true + "dev": true, + "optional": true }, "use": { "version": "3.1.1", @@ -16633,6 +16733,7 @@ "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", "integrity": "sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk=", "dev": true, + "optional": true, "requires": { "buffer-crc32": "~0.2.3", "fd-slicer": "~1.1.0" diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js index b6f5d99d9f..70861c9a86 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js @@ -547,7 +547,7 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { $http.get( umbRequestHelper.getApiUrl( "contentApiBaseUrl", - "GetEmpty", + "GetEmptyBlueprint", { blueprintId: blueprintId, parentId: parentId })), 'Failed to retrieve blueprint for id ' + blueprintId) .then(function (result) { @@ -639,7 +639,7 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { else if (options.orderDirection === "desc") { options.orderDirection = "Descending"; } - + //converts the value to a js bool function toBool(v) { if (Utilities.isNumber(v)) { diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js index 838e8f1b80..0e271c1cc8 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js @@ -220,7 +220,7 @@ function entityResource($q, $http, umbRequestHelper) { }), 'Failed to anchors data for rte content ' + rteContent); }, - + /** * @ngdoc method * @name umbraco.resources.entityResource#getByIds @@ -246,6 +246,10 @@ function entityResource($q, $http, umbRequestHelper) { */ getByIds: function (ids, type) { + if (!ids || ids.length === 0) { + return $q.when([]); + } + var query = "type=" + type; return umbRequestHelper.resourcePromise( @@ -321,7 +325,7 @@ function entityResource($q, $http, umbRequestHelper) { getAll: function (type, postFilter) { //need to build the query string manually var query = "type=" + type + "&postFilter=" + (postFilter ? encodeURIComponent(postFilter) : ""); - + return umbRequestHelper.resourcePromise( $http.get( umbRequestHelper.getApiUrl(