diff --git a/src/Umbraco.Core/Mapping/MapperContext.cs b/src/Umbraco.Core/Mapping/MapperContext.cs index b001300952..a7044a05b9 100644 --- a/src/Umbraco.Core/Mapping/MapperContext.cs +++ b/src/Umbraco.Core/Mapping/MapperContext.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; namespace Umbraco.Core.Mapping { @@ -112,6 +113,18 @@ namespace Umbraco.Core.Mapping } */ + /// + /// Maps an enumerable of source objects to a new list of target objects. + /// + /// The type of the source objects. + /// The type of the target objects. + /// The source objects. + /// A list containing the target objects. + public List MapEnumerable(IEnumerable source) + { + return source.Select(Map).ToList(); + } + #endregion } } diff --git a/src/Umbraco.Core/Mapping/UmbracoMapper.cs b/src/Umbraco.Core/Mapping/UmbracoMapper.cs index 2d7d9f97c4..0831edab4e 100644 --- a/src/Umbraco.Core/Mapping/UmbracoMapper.cs +++ b/src/Umbraco.Core/Mapping/UmbracoMapper.cs @@ -229,7 +229,7 @@ namespace Umbraco.Core.Mapping if (ctor != null && map != null) { // register (for next time) and do it now (for this time) - object NCtor(object s, MapperContext c) => MapEnumerable((IEnumerable) s, targetGenericArg, ctor, map, c); + object NCtor(object s, MapperContext c) => MapEnumerableInternal((IEnumerable) s, targetGenericArg, ctor, map, c); DefineCtors(sourceType)[targetType] = NCtor; DefineMaps(sourceType)[targetType] = Identity; return (TTarget) NCtor(source, context); @@ -241,7 +241,7 @@ namespace Umbraco.Core.Mapping throw new InvalidOperationException($"Don't know how to map {sourceType.FullName} to {targetType.FullName}."); } - private TTarget MapEnumerable(IEnumerable source, Type targetGenericArg, Func ctor, Action map, MapperContext context) + private TTarget MapEnumerableInternal(IEnumerable source, Type targetGenericArg, Func ctor, Action map, MapperContext context) { var targetList = (IList)Activator.CreateInstance(typeof(List<>).MakeGenericType(targetGenericArg)); @@ -379,6 +379,46 @@ namespace Umbraco.Core.Mapping throw new Exception("panic"); } + /// + /// Maps an enumerable of source objects to a new list of target objects. + /// + /// The type of the source objects. + /// The type of the target objects. + /// The source objects. + /// A list containing the target objects. + public List MapEnumerable(IEnumerable source) + { + return source.Select(Map).ToList(); + } + + /// + /// Maps an enumerable of source objects to a new list of target objects. + /// + /// The type of the source objects. + /// The type of the target objects. + /// The source objects. + /// A mapper context preparation method. + /// A list containing the target objects. + public List MapEnumerable(IEnumerable source, Action f) + { + var context = new MapperContext(this); + f(context); + return source.Select(x => Map(x, context)).ToList(); + } + + /// + /// Maps an enumerable of source objects to a new list of target objects. + /// + /// The type of the source objects. + /// The type of the target objects. + /// The source objects. + /// A mapper context. + /// A list containing the target objects. + public List MapEnumerable(IEnumerable source, MapperContext context) + { + return source.Select(x => Map(x, context)).ToList(); + } + #endregion } } diff --git a/src/Umbraco.Tests/Mapping/MappingTests.cs b/src/Umbraco.Tests/Mapping/MappingTests.cs index 5cfb0056b5..27fdbe765a 100644 --- a/src/Umbraco.Tests/Mapping/MappingTests.cs +++ b/src/Umbraco.Tests/Mapping/MappingTests.cs @@ -2,6 +2,8 @@ using System.Linq; using NUnit.Framework; using Umbraco.Core.Mapping; +using Umbraco.Core.Models; +using Umbraco.Web.Models.ContentEditing; namespace Umbraco.Tests.Mapping { @@ -11,11 +13,11 @@ namespace Umbraco.Tests.Mapping [Test] public void SimpleMap() { - var profiles = new MapDefinitionCollection(new IMapDefinition[] + var definitions = new MapDefinitionCollection(new IMapDefinition[] { - new Profile1(), + new MapperDefinition1(), }); - var mapper = new UmbracoMapper(profiles); + var mapper = new UmbracoMapper(definitions); var thing1 = new Thing1 { Value = "value" }; var thing2 = mapper.Map(thing1); @@ -36,11 +38,11 @@ namespace Umbraco.Tests.Mapping [Test] public void EnumerableMap() { - var profiles = new MapDefinitionCollection(new IMapDefinition[] + var definitions = new MapDefinitionCollection(new IMapDefinition[] { - new Profile1(), + new MapperDefinition1(), }); - var mapper = new UmbracoMapper(profiles); + var mapper = new UmbracoMapper(definitions); var thing1A = new Thing1 { Value = "valueA" }; var thing1B = new Thing1 { Value = "valueB" }; @@ -63,11 +65,11 @@ namespace Umbraco.Tests.Mapping [Test] public void InheritedMap() { - var profiles = new MapDefinitionCollection(new IMapDefinition[] + var definitions = new MapDefinitionCollection(new IMapDefinition[] { - new Profile1(), + new MapperDefinition1(), }); - var mapper = new UmbracoMapper(profiles); + var mapper = new UmbracoMapper(definitions); var thing3 = new Thing3 { Value = "value" }; var thing2 = mapper.Map(thing3); @@ -85,6 +87,20 @@ namespace Umbraco.Tests.Mapping Assert.AreEqual("value", thing2.Value); } + [Test] + public void CollectionsMap() + { + var definitions = new MapDefinitionCollection(new IMapDefinition[] + { + new MapperDefinition2(), + }); + var mapper = new UmbracoMapper(definitions); + + // can map a PropertyCollection + var source = new PropertyCollection(); + var target = mapper.Map>(source); + } + private class Thing1 { public string Value { get; set; } @@ -98,7 +114,7 @@ namespace Umbraco.Tests.Mapping public string Value { get; set; } } - private class Profile1 : IMapDefinition + private class MapperDefinition1 : IMapDefinition { public void DefineMaps(UmbracoMapper mapper) { @@ -110,5 +126,16 @@ namespace Umbraco.Tests.Mapping target.Value = source.Value; } } + + private class MapperDefinition2 : IMapDefinition + { + public void DefineMaps(UmbracoMapper mapper) + { + mapper.Define((source, context) => new ContentPropertyDto(), Map); + } + + private static void Map(Property source, ContentPropertyDto target, MapperContext context) + { } + } } } diff --git a/src/Umbraco.Web/Models/Mapping/ContentMapDefinition.cs b/src/Umbraco.Web/Models/Mapping/ContentMapDefinition.cs index f2ff4328fb..0898929bfe 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentMapDefinition.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentMapDefinition.cs @@ -64,7 +64,7 @@ namespace Umbraco.Web.Models.Mapping // Umbraco.Code.MapAll private static void Map(IContent source, ContentPropertyCollectionDto target, MapperContext context) { - target.Properties = source.Properties.Select(context.Map); + target.Properties = context.Map>(source.Properties); } // Umbraco.Code.MapAll -AllowPreview -Errors -PersistedContent @@ -98,7 +98,7 @@ namespace Umbraco.Web.Models.Mapping target.Variants = _contentVariantMapper.Map(source, context); target.ContentDto = new ContentPropertyCollectionDto(); - target.ContentDto.Properties = source.Properties.Select(context.Map); + target.ContentDto.Properties = context.Map>(source.Properties); } // Umbraco.Code.MapAll -Segment -Language @@ -127,7 +127,7 @@ namespace Umbraco.Web.Models.Mapping target.Owner = _commonMapper.GetOwner(source, context); target.ParentId = source.ParentId; target.Path = source.Path; - target.Properties = source.Properties.Select(context.Map); + target.Properties = context.Map>(source.Properties); target.SortOrder = source.SortOrder; target.State = _basicStateMapper.Map(source, context); target.Trashed = source.Trashed; diff --git a/src/Umbraco.Web/Models/Mapping/ContentTypeMapDefinition.cs b/src/Umbraco.Web/Models/Mapping/ContentTypeMapDefinition.cs index 394c5fb0cf..d5906e6d97 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentTypeMapDefinition.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentTypeMapDefinition.cs @@ -122,7 +122,7 @@ namespace Umbraco.Web.Models.Mapping target.AllowCultureVariant = source.VariesByCulture(); //sync templates - target.AllowedTemplates = source.AllowedTemplates.Select(context.Map).ToArray(); + target.AllowedTemplates = context.Map>(source.AllowedTemplates); if (source.DefaultTemplate != null) target.DefaultTemplate = context.Map(source.DefaultTemplate); @@ -312,7 +312,7 @@ namespace Umbraco.Web.Models.Mapping target.Name = source.Name; target.SortOrder = source.SortOrder; - target.Properties = source.Properties.Select(context.Map); + target.Properties = context.Map>(source.Properties); } // Umbraco.Code.MapAll -ContentTypeId -ParentTabContentTypes -ParentTabContentTypeNames @@ -325,7 +325,7 @@ namespace Umbraco.Web.Models.Mapping target.Name = source.Name; target.SortOrder = source.SortOrder; - target.Properties = source.Properties.Select(context.Map); + target.Properties = context.Map>(source.Properties); } // Umbraco.Code.MapAll -Editor -View -Config -ContentTypeId -ContentTypeName -Locked @@ -531,7 +531,7 @@ namespace Umbraco.Web.Models.Mapping { MapTypeToDisplayBase(source, target); - target.Groups = source.Groups.Select(context.Map>); + target.Groups = context.Map>>(source.Groups); } private IEnumerable MapLockedCompositions(IContentTypeComposition source) diff --git a/src/Umbraco.Web/Models/Mapping/DataTypeMapDefinition.cs b/src/Umbraco.Web/Models/Mapping/DataTypeMapDefinition.cs index 7240bdec50..2bcd788e5f 100644 --- a/src/Umbraco.Web/Models/Mapping/DataTypeMapDefinition.cs +++ b/src/Umbraco.Web/Models/Mapping/DataTypeMapDefinition.cs @@ -127,10 +127,10 @@ namespace Umbraco.Web.Models.Mapping private IEnumerable MapAvailableEditors(IDataType source, MapperContext context) { var contentSection = Current.Configs.Settings().Content; - return _propertyEditors + var properties = _propertyEditors .Where(x => !x.IsDeprecated || contentSection.ShowDeprecatedPropertyEditors || source.EditorAlias == x.Alias) - .OrderBy(x => x.Name) - .Select(context.Map); + .OrderBy(x => x.Name); + return context.Map>(properties); } private IEnumerable MapPreValues(IDataType dataType, MapperContext context) @@ -143,7 +143,7 @@ namespace Umbraco.Web.Models.Mapping throw new InvalidOperationException($"Could not find a property editor with alias \"{dataType.EditorAlias}\"."); var configurationEditor = editor.GetConfigurationEditor(); - var fields = configurationEditor.Fields.Select(context.Map).ToArray(); + var fields = context.MapEnumerable(configurationEditor.Fields); var configurationDictionary = configurationEditor.ToConfigurationEditor(dataType.Configuration); MapConfigurationFields(fields, configurationDictionary); @@ -151,7 +151,7 @@ namespace Umbraco.Web.Models.Mapping return fields; } - private void MapConfigurationFields(DataTypeConfigurationFieldDisplay[] fields, IDictionary configuration) + private void MapConfigurationFields(List fields, IDictionary configuration) { if (fields == null) throw new ArgumentNullException(nameof(fields)); if (configuration == null) throw new ArgumentNullException(nameof(configuration)); @@ -190,7 +190,7 @@ namespace Umbraco.Web.Models.Mapping var configurationEditor = source.GetConfigurationEditor(); - var fields = configurationEditor.Fields.Select(context.Map).ToArray(); + var fields = context.MapEnumerable(configurationEditor.Fields); var defaultConfiguration = configurationEditor.DefaultConfiguration; if (defaultConfiguration != null) diff --git a/src/Umbraco.Web/Models/Mapping/EntityMapDefinition.cs b/src/Umbraco.Web/Models/Mapping/EntityMapDefinition.cs index 6ff03b4f7d..78c25ced33 100644 --- a/src/Umbraco.Web/Models/Mapping/EntityMapDefinition.cs +++ b/src/Umbraco.Web/Models/Mapping/EntityMapDefinition.cs @@ -26,8 +26,8 @@ namespace Umbraco.Web.Models.Mapping mapper.Define((source, context) => new EntityBasic(), Map); mapper.Define((source, context) => new SearchResultEntity(), Map); mapper.Define((source, context) => new SearchResultEntity(), Map); - mapper.Define>((source, context) => source.Select(context.Map).ToList()); - mapper.Define, IEnumerable>((source, context) => source.Select(context.Map).ToList()); + mapper.Define>((source, context) => context.MapEnumerable(source)); + mapper.Define, IEnumerable>((source, context) => context.MapEnumerable(source)); } // Umbraco.Code.MapAll -Alias diff --git a/src/Umbraco.Web/Models/Mapping/LanguageMapDefinition.cs b/src/Umbraco.Web/Models/Mapping/LanguageMapDefinition.cs index 21d42f0bc2..28074d4817 100644 --- a/src/Umbraco.Web/Models/Mapping/LanguageMapDefinition.cs +++ b/src/Umbraco.Web/Models/Mapping/LanguageMapDefinition.cs @@ -45,7 +45,7 @@ namespace Umbraco.Web.Models.Mapping if (!(target is List list)) throw new NotSupportedException($"{nameof(target)} must be a List."); - var temp = source.Select(context.Map).ToList(); + var temp = context.MapEnumerable(source); //Put the default language first in the list & then sort rest by a-z var defaultLang = temp.SingleOrDefault(x => x.IsDefault); diff --git a/src/Umbraco.Web/Models/Mapping/MacroMapDefinition.cs b/src/Umbraco.Web/Models/Mapping/MacroMapDefinition.cs index 0d6f779b82..089f5d5d71 100644 --- a/src/Umbraco.Web/Models/Mapping/MacroMapDefinition.cs +++ b/src/Umbraco.Web/Models/Mapping/MacroMapDefinition.cs @@ -23,7 +23,7 @@ namespace Umbraco.Web.Models.Mapping public void DefineMaps(UmbracoMapper mapper) { mapper.Define((source, context) => new EntityBasic(), Map); - mapper.Define>((source, context) => source.Properties.Values.Select(context.Map).ToList()); + mapper.Define>((source, context) => context.MapEnumerable(source.Properties.Values)); mapper.Define((source, context) => new MacroParameter(), Map); } diff --git a/src/Umbraco.Web/Models/Mapping/MediaMapDefinition.cs b/src/Umbraco.Web/Models/Mapping/MediaMapDefinition.cs index b147687ed3..3da61bc9c0 100644 --- a/src/Umbraco.Web/Models/Mapping/MediaMapDefinition.cs +++ b/src/Umbraco.Web/Models/Mapping/MediaMapDefinition.cs @@ -42,7 +42,7 @@ namespace Umbraco.Web.Models.Mapping // Umbraco.Code.MapAll private static void Map(IMedia source, ContentPropertyCollectionDto target, MapperContext context) { - target.Properties = source.Properties.Select(context.Map); + target.Properties = context.MapEnumerable(source.Properties); } // Umbraco.Code.MapAll -Properties -Errors -Edited -Updater -Alias -IsContainer @@ -84,7 +84,7 @@ namespace Umbraco.Web.Models.Mapping target.Owner = _commonMapper.GetOwner(source, context); target.ParentId = source.ParentId; target.Path = source.Path; - target.Properties = source.Properties.Select(context.Map); + target.Properties = context.MapEnumerable(source.Properties); target.SortOrder = source.SortOrder; target.State = null; target.Trashed = source.Trashed; diff --git a/src/Umbraco.Web/Models/Mapping/MemberMapDefinition.cs b/src/Umbraco.Web/Models/Mapping/MemberMapDefinition.cs index 84f9340713..9b0407aa93 100644 --- a/src/Umbraco.Web/Models/Mapping/MemberMapDefinition.cs +++ b/src/Umbraco.Web/Models/Mapping/MemberMapDefinition.cs @@ -149,7 +149,7 @@ namespace Umbraco.Web.Models.Mapping // Umbraco.Code.MapAll private static void Map(IMember source, ContentPropertyCollectionDto target, MapperContext context) { - target.Properties = source.Properties.Select(context.Map); + target.Properties = context.MapEnumerable(source.Properties); } private MembershipScenario GetMembershipScenario() diff --git a/src/Umbraco.Web/Models/Mapping/TabsAndPropertiesMapper.cs b/src/Umbraco.Web/Models/Mapping/TabsAndPropertiesMapper.cs index d0382eb557..b8d76572fb 100644 --- a/src/Umbraco.Web/Models/Mapping/TabsAndPropertiesMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/TabsAndPropertiesMapper.cs @@ -105,7 +105,7 @@ namespace Umbraco.Web.Models.Mapping /// protected virtual List MapProperties(IContentBase content, List properties, MapperContext context) { - return properties.OrderBy(x => x.PropertyType.SortOrder).Select(context.Map).ToList(); + return context.MapEnumerable(properties.OrderBy(x => x.PropertyType.SortOrder)); } } diff --git a/src/Umbraco.Web/Models/Mapping/UserMapDefinition.cs b/src/Umbraco.Web/Models/Mapping/UserMapDefinition.cs index e2c7fb611e..53479ea4da 100644 --- a/src/Umbraco.Web/Models/Mapping/UserMapDefinition.cs +++ b/src/Umbraco.Web/Models/Mapping/UserMapDefinition.cs @@ -10,6 +10,7 @@ using Umbraco.Core.Models.Membership; using Umbraco.Web.Models.ContentEditing; using Umbraco.Core.Models; using Umbraco.Core.Models.Entities; +using Umbraco.Core.Models.Sections; using Umbraco.Core.Services; using Umbraco.Web.Actions; using Umbraco.Web.Services; @@ -286,7 +287,7 @@ namespace Umbraco.Web.Models.Mapping target.StartContentIds = GetStartNodes(source.StartContentIds.ToArray(), UmbracoObjectTypes.Document, "content/contentRoot", context); target.StartMediaIds = GetStartNodes(source.StartMediaIds.ToArray(), UmbracoObjectTypes.Media, "media/mediaRoot", context); target.UpdateDate = source.UpdateDate; - target.UserGroups = source.Groups.Select(context.Map); + target.UserGroups = context.MapEnumerable(source.Groups); target.Username = source.Username; target.UserState = source.UserState; } @@ -307,7 +308,7 @@ namespace Umbraco.Web.Models.Mapping target.Name = source.Name; target.ParentId = -1; target.Path = "-1," + source.Id; - target.UserGroups = source.Groups.Select(context.Map); + target.UserGroups = context.MapEnumerable(source.Groups); target.Username = source.Username; target.UserState = source.UserState; } @@ -336,7 +337,7 @@ namespace Umbraco.Web.Models.Mapping private void MapUserGroupBasic(UserGroupBasic target, IEnumerable sourceAllowedSections, int? sourceStartContentId, int? sourceStartMediaId, MapperContext context) { var allSections = _sectionService.GetSections(); - target.Sections = allSections.Where(x => sourceAllowedSections.Contains(x.Alias)).Select(context.Map
); + target.Sections = context.MapEnumerable(allSections.Where(x => sourceAllowedSections.Contains(x.Alias))); if (sourceStartMediaId > 0) target.MediaStartNode = context.Map(_entityService.Get(sourceStartMediaId.Value, UmbracoObjectTypes.Media));