From 72bdf56ddf69e110beccac0ef004170ef7e84d59 Mon Sep 17 00:00:00 2001 From: Stephan Date: Tue, 26 Mar 2019 18:47:35 +0100 Subject: [PATCH] Cleanup and fix mappers --- src/Umbraco.Core/Mapping/Mapper.cs | 315 ++++++++++++------ src/Umbraco.Tests/App.config | 96 +++--- src/Umbraco.Tests/Mapping/MappingTests.cs | 28 ++ .../Models/Mapping/AutoMapper6Tests.cs | 198 ----------- .../Models/Mapping/AutoMapperTests.cs | 86 ----- .../Mapping/ContentTypeModelMappingTests.cs | 18 - .../Mapping/ContentWebModelMappingTests.cs | 3 +- .../Models/Mapping/UserModelMapperTests.cs | 3 +- .../Repositories/ContentTypeRepositoryTest.cs | 5 +- .../Testing/UmbracoTestAttribute.cs | 10 +- src/Umbraco.Tests/Testing/UmbracoTestBase.cs | 23 +- src/Umbraco.Tests/Umbraco.Tests.csproj | 2 - .../Filters/UserGroupValidateAttribute.cs | 4 +- .../Models/Mapping/CommonMapper.cs | 101 ++++++ .../Models/Mapping/ContentMapperProfile.cs | 9 +- .../Mapping/ContentPropertyBasicMapper.cs | 20 +- .../Mapping/ContentPropertyDisplayMapper.cs | 32 +- .../Mapping/ContentPropertyDtoMapper.cs | 16 +- .../Mapping/ContentTypeMapperProfile.cs | 9 +- .../Models/Mapping/MediaMapperProfile.cs | 103 +----- .../Models/Mapping/TabsAndPropertiesMapper.cs | 10 +- .../Models/Mapping/UserMapperProfile.cs | 6 +- src/Umbraco.Web/Umbraco.Web.csproj | 1 + 23 files changed, 451 insertions(+), 647 deletions(-) delete mode 100644 src/Umbraco.Tests/Models/Mapping/AutoMapper6Tests.cs delete mode 100644 src/Umbraco.Tests/Models/Mapping/AutoMapperTests.cs create mode 100644 src/Umbraco.Web/Models/Mapping/CommonMapper.cs diff --git a/src/Umbraco.Core/Mapping/Mapper.cs b/src/Umbraco.Core/Mapping/Mapper.cs index 377575fed6..5c6b5b76d8 100644 --- a/src/Umbraco.Core/Mapping/Mapper.cs +++ b/src/Umbraco.Core/Mapping/Mapper.cs @@ -5,8 +5,13 @@ using System.Linq; namespace Umbraco.Core.Mapping { - // FIXME needs documentation and cleanup! + // notes: + // AutoMapper maps null to empty arrays, lists, etc + // AutoMapper maps derived types - we have to be explicit (see DefineAs) - fixme / really? + /// + /// Umbraco Mapper. + /// public class Mapper { private readonly Dictionary>> _ctors @@ -15,6 +20,10 @@ namespace Umbraco.Core.Mapping private readonly Dictionary>> _maps = new Dictionary>>(); + /// + /// Initializes a new instance of the class. + /// + /// public Mapper(MapperProfileCollection profiles) { foreach (var profile in profiles) @@ -23,14 +32,85 @@ namespace Umbraco.Core.Mapping #region Define + private TTarget ThrowCtor(TSource source, MapperContext context) + => throw new InvalidOperationException($"Don't know how to create {typeof(TTarget).FullName} instances."); + + private void Identity(TSource source, TTarget target, MapperContext context) + { } + + /// + /// Defines a mapping. + /// + /// The source type. + /// The target type. public void Define() - => Define((source, target, context) => { }); + => Define(ThrowCtor, Identity); + /// + /// Defines a mapping. + /// + /// The source type. + /// The target type. + /// A mapping method. public void Define(Action map) - => Define((source, context) => throw new NotSupportedException($"Don't know how to create {typeof(TTarget)} instances."), map); + => Define(ThrowCtor, map); + /// + /// Defines a mapping. + /// + /// The source type. + /// The target type. + /// A constructor method. public void Define(Func ctor) - => Define(ctor, (source, target, context) => { }); + => Define(ctor, Identity); + + /// + /// Defines a mapping. + /// + /// The source type. + /// The target type. + /// A constructor method. + /// A mapping method. + public void Define(Func ctor, Action map) + { + var sourceType = typeof(TSource); + var targetType = typeof(TTarget); + + var sourceCtors = DefineCtors(sourceType); + if (ctor != null) + sourceCtors[targetType] = (source, context) => ctor((TSource)source, context); + + var sourceMaps = DefineMaps(sourceType); + sourceMaps[targetType] = (source, target, context) => map((TSource)source, (TTarget)target, context); + } + + /// + /// Defines a mapping as a clone of an already defined mapping. + /// + /// The source type. + /// The target type. + /// The equivalent source type. + /// The equivalent target type. + public void DefineAs() + { + var sourceType = typeof(TSource); + var targetType = typeof(TTarget); + + var asSourceType = typeof(TAsSource); + var asTargetType = typeof(TAsTarget); + + var asCtors = DefineCtors(asSourceType); + var asMaps = DefineMaps(asSourceType); + + var sourceCtors = DefineCtors(sourceType); + var sourceMaps = DefineMaps(sourceType); + + if (!asCtors.TryGetValue(asTargetType, out var ctor) || !asMaps.TryGetValue(asTargetType, out var map)) + throw new InvalidOperationException($"Don't know hwo to map from {asSourceType.FullName} to {targetType.FullName}."); + + sourceCtors[targetType] = ctor; + sourceMaps[targetType] = map; + } private Dictionary> DefineCtors(Type sourceType) { @@ -46,25 +126,26 @@ namespace Umbraco.Core.Mapping return sourceMap; } - public void Define(Func ctor, Action map) - { - var sourceType = typeof(TSource); - var targetType = typeof(TTarget); - - var sourceCtors = DefineCtors(sourceType); - sourceCtors[targetType] = (source, context) => ctor((TSource)source, context); - - var sourceMaps = DefineMaps(sourceType); - sourceMaps[targetType] = (source, target, context) => map((TSource)source, (TTarget)target, context); - } - #endregion #region Map + /// + /// Maps a source object to a new target object. + /// + /// The target type. + /// The source object. + /// The target object. public TTarget Map(object source) => Map(source, new MapperContext(this)); + /// + /// Maps a source object to a new target object. + /// + /// The target type. + /// The source object. + /// A mapper context preparation method. + /// The target object. public TTarget Map(object source, Action f) { var context = new MapperContext(this); @@ -72,16 +153,63 @@ namespace Umbraco.Core.Mapping return Map(source, context); } + /// + /// Maps a source object to a new target object. + /// + /// The target type. + /// The source object. + /// A mapper context. + /// The target object. public TTarget Map(object source, MapperContext context) + => Map(source, source?.GetType(), context); + + /// + /// Maps a source object to a new target object. + /// + /// The source type. + /// The target type. + /// The source object. + /// The target object. + public TTarget Map(TSource source) + => Map(source, new MapperContext(this)); + + /// + /// Maps a source object to a new target object. + /// + /// The source type. + /// The target type. + /// The source object. + /// A mapper context preparation method. + /// The target object. + public TTarget Map(TSource source, Action f) + { + var context = new MapperContext(this); + f(context); + return Map(source, context); + } + + /// + /// Maps a source object to a new target object. + /// + /// The source type. + /// The target type. + /// The source object. + /// A mapper context. + /// The target object. + public TTarget Map(TSource source, MapperContext context) + => Map(source, typeof(TSource), context); + + private TTarget Map(object source, Type sourceType, MapperContext context) { if (source == null) throw new ArgumentNullException(nameof(source)); - var sourceType = source.GetType(); var targetType = typeof(TTarget); - var ctor = GetCtor(sourceType, typeof(TTarget)); - var map = GetMap(sourceType, typeof(TTarget)); + var ctor = GetCtor(sourceType, targetType); + var map = GetMap(sourceType, targetType); + + // if there is a direct constructor, map if (ctor != null && map != null) { var target = ctor(source, context); @@ -89,36 +217,22 @@ namespace Umbraco.Core.Mapping return (TTarget)target; } - bool IsOk(Type type) + // else, handle enumerable-to-enumerable mapping + if (IsGenericOrArray(sourceType) && IsGenericOrArray(targetType)) { - // note: we're not going to work with just plain enumerables of anything, - // only on arrays or anything that is generic and implements IEnumerable<> - - if (type.IsArray && type.GetArrayRank() == 1) return true; - if (type.IsGenericType && type.GenericTypeArguments.Length == 1) return true; - return false; - } - - Type GetGenericArg(Type type) - { - if (type.IsArray) return type.GetElementType(); - if (type.IsGenericType) return type.GenericTypeArguments[0]; - throw new Exception("panic"); - } - - if (IsOk(sourceType) && IsOk(targetType)) - { - var sourceGenericArg = GetGenericArg(sourceType); - var targetGenericArg = GetGenericArg(targetType); + var sourceGenericArg = GetGenericOrArrayArg(sourceType); + var targetGenericArg = GetGenericOrArrayArg(targetType); var sourceEnumerableType = typeof(IEnumerable<>).MakeGenericType(sourceGenericArg); var targetEnumerableType = typeof(IEnumerable<>).MakeGenericType(targetGenericArg); + // if both are ienumerable if (sourceEnumerableType.IsAssignableFrom(sourceType) && targetEnumerableType.IsAssignableFrom(targetType)) { ctor = GetCtor(sourceGenericArg, targetGenericArg); map = GetMap(sourceGenericArg, targetGenericArg); + // if there is a constructor for the underlying type, map if (ctor != null && map != null) { var sourceEnumerable = (IEnumerable)source; @@ -139,72 +253,26 @@ namespace Umbraco.Core.Mapping throw new InvalidOperationException($"Don't know how to map {sourceType.FullName} to {targetType.FullName}."); } - // TODO: when AutoMapper is completely gone these two methods can merge - - public TTarget Map(TSource source) - => Map(source, new MapperContext(this)); - - public TTarget Map(TSource source, Action f) - { - var context = new MapperContext(this); - f(context); - return Map(source, context); - } - - public TTarget Map(TSource source, MapperContext context) - { - if (source == null) - throw new ArgumentNullException(nameof(source)); - - var sourceType = typeof(TSource); - var targetType = typeof(TTarget); - - var ctor = GetCtor(sourceType, typeof(TTarget)); - var map = GetMap(sourceType, targetType); - if (ctor != null && map != null) - { - var target = ctor(source, context); - map(source, target, context); - return (TTarget) target; - } - - if (sourceType.IsGenericType && targetType.IsGenericType) - { - var sourceGeneric = sourceType.GetGenericTypeDefinition(); - var targetGeneric = targetType.GetGenericTypeDefinition(); - var ienumerable = typeof(IEnumerable<>); - - if (sourceGeneric == ienumerable && targetGeneric == ienumerable) - { - var sourceGenericType = sourceType.GenericTypeArguments[0]; - var targetGenericType = targetType.GenericTypeArguments[0]; - - ctor = GetCtor(sourceGenericType, targetGenericType); - map = GetMap(sourceGenericType, targetGenericType); - - if (ctor != null && map != null) - { - var sourceEnumerable = (IEnumerable)source; - var targetEnumerable = (IList)Activator.CreateInstance(typeof(List<>).MakeGenericType(targetGenericType)); - - foreach (var sourceItem in sourceEnumerable) - { - var targetItem = ctor(sourceItem, context); - map(sourceItem, targetItem, context); - targetEnumerable.Add(targetItem); - } - - return (TTarget)targetEnumerable; - } - } - } - - throw new InvalidOperationException($"Don't know how to map {sourceType.FullName} to {targetType.FullName}."); - } - + /// + /// Maps a source object to an existing target object. + /// + /// The source type. + /// The target type. + /// The source object. + /// The target object. + /// The target object. public TTarget Map(TSource source, TTarget target) => Map(source, target, new MapperContext(this)); + /// + /// Maps a source object to an existing target object. + /// + /// The source type. + /// The target type. + /// The source object. + /// The target object. + /// A mapper context preparation method. + /// The target object. public TTarget Map(TSource source, TTarget target, Action f) { var context = new MapperContext(this); @@ -212,18 +280,32 @@ namespace Umbraco.Core.Mapping return Map(source, target, context); } + /// + /// Maps a source object to an existing target object. + /// + /// The source type. + /// The target type. + /// The source object. + /// The target object. + /// A mapper context. + /// The target object. public TTarget Map(TSource source, TTarget target, MapperContext context) { - // fixme should we deal with enumerables? + var sourceType = typeof(TSource); + var targetType = typeof(TTarget); - var map = GetMap(source.GetType(), typeof(TTarget)); - if (map == null) + var map = GetMap(sourceType, targetType); + + // if there is a direct map, map + if (map != null) { - throw new InvalidOperationException($"Don't know how to map {typeof(TSource).FullName} to {typeof(TTarget).FullName}."); + map(source, target, context); + return target; } - map(source, target, context); - return target; + // we cannot really map to an existing enumerable - give up + + throw new InvalidOperationException($"Don't know how to map {typeof(TSource).FullName} to {typeof(TTarget).FullName}."); } private Func GetCtor(Type sourceType, Type targetType) @@ -252,6 +334,23 @@ namespace Umbraco.Core.Mapping return sourceMap.TryGetValue(targetType, out var map) ? map : null; } + private static bool IsGenericOrArray(Type type) + { + // note: we're not going to work with just plain enumerables of anything, + // only on arrays or anything that is generic and implements IEnumerable<> + + if (type.IsArray && type.GetArrayRank() == 1) return true; + if (type.IsGenericType && type.GenericTypeArguments.Length == 1) return true; + return false; + } + + private static Type GetGenericOrArrayArg(Type type) + { + if (type.IsArray) return type.GetElementType(); + if (type.IsGenericType) return type.GenericTypeArguments[0]; + throw new Exception("panic"); + } + #endregion } } diff --git a/src/Umbraco.Tests/App.config b/src/Umbraco.Tests/App.config index 49de625450..91047ba6a2 100644 --- a/src/Umbraco.Tests/App.config +++ b/src/Umbraco.Tests/App.config @@ -54,62 +54,46 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Umbraco.Tests/Mapping/MappingTests.cs b/src/Umbraco.Tests/Mapping/MappingTests.cs index c5f4593c6d..a3ee676316 100644 --- a/src/Umbraco.Tests/Mapping/MappingTests.cs +++ b/src/Umbraco.Tests/Mapping/MappingTests.cs @@ -65,11 +65,39 @@ namespace Umbraco.Tests.Mapping //Assert.AreEqual("value", thing2.Value); } + [Test] + public void InheritedMap() + { + var profiles = new MapperProfileCollection(new IMapperProfile[] + { + new Profile1(), + }); + var mapper = new Mapper(profiles); + + var thing3 = new Thing3 { Value = "value" }; + var thing2 = mapper.Map(thing3); + + Assert.IsNotNull(thing2); + Assert.AreEqual("value", thing2.Value); + + thing2 = mapper.Map(thing3); + + Assert.IsNotNull(thing2); + Assert.AreEqual("value", thing2.Value); + + thing2 = new Thing2(); + mapper.Map(thing3, thing2); + Assert.AreEqual("value", thing2.Value); + } + private class Thing1 { public string Value { get; set; } } + private class Thing3 : Thing1 + { } + private class Thing2 { public string Value { get; set; } diff --git a/src/Umbraco.Tests/Models/Mapping/AutoMapper6Tests.cs b/src/Umbraco.Tests/Models/Mapping/AutoMapper6Tests.cs deleted file mode 100644 index 18bceaae49..0000000000 --- a/src/Umbraco.Tests/Models/Mapping/AutoMapper6Tests.cs +++ /dev/null @@ -1,198 +0,0 @@ -using System; -using AutoMapper; -using NUnit.Framework; - -namespace Umbraco.Tests.Models.Mapping -{ - [TestFixture] - public class AutoMapper6Tests - { - [Test] - public void Test1() - { - ThingProfile.CtorCount = 0; - Assert.AreEqual(0, ThingProfile.CtorCount); - - var config = new MapperConfiguration(cfg => - { - cfg.AddProfile(); - }); - - Assert.AreEqual(1, ThingProfile.CtorCount); - Assert.AreEqual(0, MemberValueResolver.CtorCount); - - var mapper = config.CreateMapper(); - - Assert.AreEqual(1, ThingProfile.CtorCount); - Assert.AreEqual(0, MemberValueResolver.CtorCount); - - var thingA = new ThingA { ValueInt = 42, ValueString = "foo" }; - var thingB = mapper.Map(thingA); - Assert.AreEqual(42, thingB.ValueInt); - Assert.AreEqual("!!foo!!", thingB.ValueString); - - mapper.Map(thingA); - mapper.Map(thingA); - mapper.Map(thingA); - Assert.AreEqual(1, ThingProfile.CtorCount); // one single profile - Assert.AreEqual(4, MemberValueResolver.CtorCount); // many resolvers - } - - [Test] - public void Test2() - { - var config = new MapperConfiguration(cfg => - { - cfg.AddProfile(); - }); - - var mapper = config.CreateMapper(); - - Assert.AreEqual(0, ValueResolver.CtorCount); - - var thingA = new ThingA { ValueInt = 42, ValueString = "foo" }; - var thingB = mapper.Map(thingA); - Assert.AreEqual(42, thingB.ValueInt); - Assert.AreEqual("!!foo!!", thingB.ValueString); - - mapper.Map(thingA); - mapper.Map(thingA); - mapper.Map(thingA); - Assert.AreEqual(4, ValueResolver.CtorCount); // many resolvers - } - - [Test] - public void Test3() - { - var config = new MapperConfiguration(cfg => - { - cfg.AddProfile(); - }); - - var mapper = config.CreateMapper(); - - var thingA = new ThingA { ValueInt = 42, ValueString = "foo" }; - var thingB = mapper.Map(thingA); - Assert.AreEqual(42, thingB.ValueInt); - Assert.AreEqual("!!foo!!", thingB.ValueString); - - mapper.Map(thingA); - mapper.Map(thingA); - mapper.Map(thingA); - } - - // Resolve destination member using a custom value resolver - // void ResolveUsing() - // where TValueResolver : IValueResolver; - - // Resolve destination member using a custom value resolver from a source member - // void ResolveUsing(Expression> sourceMember) - // where TValueResolver : IMemberValueResolver; - // void ResolveUsing(string sourceMemberName) - // where TValueResolver : IMemberValueResolver; - - // Resolve destination member using a custom value resolver instance - // void ResolveUsing(IValueResolver valueResolver); - // void ResolveUsing(IMemberValueResolver valueResolver, Expression> sourceMember); - - // Resolve destination member using a custom value resolver callback - // void ResolveUsing(Func resolver); - // void ResolveUsing(Func resolver); - // void ResolveUsing(Func resolver); - // void ResolveUsing(Func resolver); - - // read https://stackoverflow.com/questions/14875075/automapper-what-is-the-difference-between-mapfrom-and-resolveusing - // about the diff between MapFrom and ResolveUsing... keeping ResolveUsing in our code - - public class ThingProfile : Profile - { - public static int CtorCount { get; set; } - - public ThingProfile(int ver) - { - CtorCount++; - - var map = CreateMap() - .ForMember(dest => dest.ValueInt, opt => opt.MapFrom(src => src.ValueInt)); - - switch (ver) - { - case 0: - break; - case 1: - map - .ForMember(dest => dest.ValueString, opt => opt.MapFrom(src => src.ValueString)); - break; - case 2: - map - .ForMember(dest => dest.ValueString, opt => opt.MapFrom()); - break; - case 3: - // in most cases that should be perfectly enough? - map - .ForMember(dest => dest.ValueString, opt => opt.MapFrom(source => "!!" + source.ValueString + "!!")); - break; - default: - throw new ArgumentOutOfRangeException(nameof(ver)); - } - } - } - - public class ThingProfile1 : ThingProfile - { - public ThingProfile1() : base(1) { } - } - - public class ThingProfile2 : ThingProfile - { - public ThingProfile2() : base(2) { } - } - - public class ThingProfile3 : ThingProfile - { - public ThingProfile3() : base(3) { } - } - - public class ValueResolver : IValueResolver - { - public static int CtorCount { get; set; } - - public ValueResolver() - { - CtorCount++; - } - - public string Resolve(ThingA source, ThingB destination, string destMember, ResolutionContext context) - { - return "!!" + source.ValueString + "!!"; - } - } - - public class MemberValueResolver : IMemberValueResolver - { - public static int CtorCount { get; set; } - - public MemberValueResolver() - { - CtorCount++; - } - - public string Resolve(ThingA source, ThingB destination, string sourceMember, string destMember, ResolutionContext context) - { - return "!!" + sourceMember + "!!"; - } - } - - public class ThingA - { - public int ValueInt { get; set; } - public string ValueString { get; set; } - } - - public class ThingB - { - public int ValueInt { get; set; } - public string ValueString { get; set; } - } - } -} diff --git a/src/Umbraco.Tests/Models/Mapping/AutoMapperTests.cs b/src/Umbraco.Tests/Models/Mapping/AutoMapperTests.cs deleted file mode 100644 index 57d38e342e..0000000000 --- a/src/Umbraco.Tests/Models/Mapping/AutoMapperTests.cs +++ /dev/null @@ -1,86 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using AutoMapper; -using NUnit.Framework; -using Umbraco.Core.Cache; -using Umbraco.Core.Manifest; -using Umbraco.Core.PropertyEditors; -using Umbraco.Tests.TestHelpers; -using Umbraco.Tests.Testing; - -namespace Umbraco.Tests.Models.Mapping -{ - [TestFixture] - [UmbracoTest(WithApplication = true)] - public class AutoMapperTests : UmbracoTestBase - { - protected override void Compose() - { - base.Compose(); - - var manifestBuilder = new ManifestParser( - AppCaches.Disabled, - new ManifestValueValidatorCollection(Enumerable.Empty()), - Composition.Logger) - { - Path = TestHelper.CurrentAssemblyDirectory - }; - Composition.RegisterUnique(_ => manifestBuilder); - - Func> typeListProducerList = Enumerable.Empty; - Composition.WithCollectionBuilder() - .Clear() - .Add(typeListProducerList); - } - - [Test] - public void AssertConfigurationIsValid() - { - var profiles = Factory.GetAllInstances().ToArray(); - - var config = new MapperConfiguration(cfg => - { - foreach (var profile in profiles) - cfg.AddProfile(profile); - }); - - // validate each profile (better granularity for error reports) - - Console.WriteLine("Validate each profile:"); - foreach (var profile in profiles) - { - try - { - config.AssertConfigurationIsValid(profile.GetType().FullName); - //Console.WriteLine("OK " + profile.GetType().FullName); - } - catch (Exception e) - { - Console.WriteLine("KO " + profile.GetType().FullName); - Console.WriteLine(e); - } - } - - Console.WriteLine(); - Console.WriteLine("Validate each profile and throw:"); - foreach (var profile in profiles) - { - try - { - config.AssertConfigurationIsValid(profile.GetType().FullName); - } - catch - { - Console.WriteLine("KO " + profile.GetType().FullName); - throw; - } - } - - // validate the global config - Console.WriteLine(); - Console.WriteLine("Validate global config:"); - config.AssertConfigurationIsValid(); - } - } -} diff --git a/src/Umbraco.Tests/Models/Mapping/ContentTypeModelMappingTests.cs b/src/Umbraco.Tests/Models/Mapping/ContentTypeModelMappingTests.cs index 2e6cfaae89..21180ce51b 100644 --- a/src/Umbraco.Tests/Models/Mapping/ContentTypeModelMappingTests.cs +++ b/src/Umbraco.Tests/Models/Mapping/ContentTypeModelMappingTests.cs @@ -1,6 +1,5 @@ using System; using System.Linq; -using AutoMapper; using Moq; using NUnit.Framework; using Umbraco.Core; @@ -11,7 +10,6 @@ using Umbraco.Core.Services; using Umbraco.Tests.TestHelpers.Entities; using Umbraco.Tests.Testing; using Umbraco.Web.Models.ContentEditing; -using Umbraco.Web.Models.Mapping; using Umbraco.Web.PropertyEditors; namespace Umbraco.Tests.Models.Mapping @@ -28,22 +26,6 @@ namespace Umbraco.Tests.Models.Mapping private readonly Mock _fileService = new Mock(); private Mock _editorsMock; - public override void SetUp() - { - base.SetUp(); - - // FIXME: are we initializing mappers that... have already been? - Mapper.Reset(); - Mapper.Initialize(configuration => - { - //initialize our content type mapper - var profile1 = new ContentTypeMapperProfile(_editorsMock.Object, _dataTypeService.Object, _fileService.Object, _contentTypeService.Object, Mock.Of(), Mock.Of()); - configuration.AddProfile(profile1); - var profile2 = new EntityMapperProfile(); - configuration.AddProfile(profile2); - }); - } - protected override void Compose() { base.Compose(); diff --git a/src/Umbraco.Tests/Models/Mapping/ContentWebModelMappingTests.cs b/src/Umbraco.Tests/Models/Mapping/ContentWebModelMappingTests.cs index 7ebdfabbb0..6a4054d5ae 100644 --- a/src/Umbraco.Tests/Models/Mapping/ContentWebModelMappingTests.cs +++ b/src/Umbraco.Tests/Models/Mapping/ContentWebModelMappingTests.cs @@ -1,6 +1,5 @@ using System.Globalization; using System.Linq; -using AutoMapper; using Moq; using NUnit.Framework; using Umbraco.Core; @@ -22,7 +21,7 @@ using Current = Umbraco.Web.Composing.Current; namespace Umbraco.Tests.Models.Mapping { [TestFixture] - [UmbracoTest(AutoMapper = true, Database = UmbracoTestOptions.Database.NewSchemaPerFixture)] + [UmbracoTest(Mapper = true, Database = UmbracoTestOptions.Database.NewSchemaPerFixture)] public class ContentWebModelMappingTests : TestWithDatabaseBase { private IContentTypeService _contentTypeService; diff --git a/src/Umbraco.Tests/Models/Mapping/UserModelMapperTests.cs b/src/Umbraco.Tests/Models/Mapping/UserModelMapperTests.cs index bdab736cd1..797fce2bd1 100644 --- a/src/Umbraco.Tests/Models/Mapping/UserModelMapperTests.cs +++ b/src/Umbraco.Tests/Models/Mapping/UserModelMapperTests.cs @@ -1,5 +1,4 @@ using System.Collections.Generic; -using AutoMapper; using Newtonsoft.Json; using NUnit.Framework; using Umbraco.Core.Models.Membership; @@ -10,7 +9,7 @@ using Umbraco.Web.Models.ContentEditing; namespace Umbraco.Tests.Models.Mapping { [TestFixture] - [UmbracoTest(AutoMapper = true, Database = UmbracoTestOptions.Database.NewSchemaPerFixture)] + [UmbracoTest(Mapper = true, Database = UmbracoTestOptions.Database.NewSchemaPerFixture)] public class UserModelMapperTests : TestWithDatabaseBase { [Test] diff --git a/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs index 03aae74920..22e0b7e211 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs @@ -1,12 +1,9 @@ using System; using System.Collections.Generic; using System.Linq; -using AutoMapper; -using Moq; using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Cache; -using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Models; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.Dtos; @@ -20,7 +17,7 @@ using Umbraco.Web.Models.ContentEditing; namespace Umbraco.Tests.Persistence.Repositories { [TestFixture] - [UmbracoTest(AutoMapper = true, Database = UmbracoTestOptions.Database.NewSchemaPerTest)] + [UmbracoTest(Mapper = true, Database = UmbracoTestOptions.Database.NewSchemaPerTest)] public class ContentTypeRepositoryTest : TestWithDatabaseBase { public override void SetUp() diff --git a/src/Umbraco.Tests/Testing/UmbracoTestAttribute.cs b/src/Umbraco.Tests/Testing/UmbracoTestAttribute.cs index 4013d93cd3..b33a0ad69a 100644 --- a/src/Umbraco.Tests/Testing/UmbracoTestAttribute.cs +++ b/src/Umbraco.Tests/Testing/UmbracoTestAttribute.cs @@ -12,17 +12,17 @@ namespace Umbraco.Tests.Testing /// /// Default is false. /// This is for tests that inherited from TestWithApplicationBase. - /// Implies AutoMapper = true (, ResetPluginManager = false). + /// Implies Mapper = true (, ResetPluginManager = false). /// public bool WithApplication { get => _withApplication.ValueOrDefault(false); set => _withApplication.Set(value); } private readonly Settable _withApplication = new Settable(); /// - /// Gets or sets a value indicating whether to compose and initialize AutoMapper. + /// Gets or sets a value indicating whether to compose and initialize the mapper. /// /// Default is false unless WithApplication is true, in which case default is true. - public bool AutoMapper { get => _autoMapper.ValueOrDefault(WithApplication); set => _autoMapper.Set(value); } - private readonly Settable _autoMapper = new Settable(); + public bool Mapper { get => _mapper.ValueOrDefault(WithApplication); set => _mapper.Set(value); } + private readonly Settable _mapper = new Settable(); // FIXME: to be completed /// @@ -59,7 +59,7 @@ namespace Umbraco.Tests.Testing base.Merge(other); - _autoMapper.Set(attr._autoMapper); + _mapper.Set(attr._mapper); _publishedRepositoryEvents.Set(attr._publishedRepositoryEvents); _logger.Set(attr._logger); _database.Set(attr._database); diff --git a/src/Umbraco.Tests/Testing/UmbracoTestBase.cs b/src/Umbraco.Tests/Testing/UmbracoTestBase.cs index fedc94d45b..97588bb26f 100644 --- a/src/Umbraco.Tests/Testing/UmbracoTestBase.cs +++ b/src/Umbraco.Tests/Testing/UmbracoTestBase.cs @@ -1,7 +1,6 @@ using System; using System.IO; using System.Reflection; -using AutoMapper; using Examine; using Moq; using NUnit.Framework; @@ -37,6 +36,7 @@ using Umbraco.Web.PublishedCache; using Umbraco.Web.Routing; using Umbraco.Web.Trees; using Umbraco.Core.Composing.CompositionExtensions; +using Umbraco.Core.Mapping; using Umbraco.Web.Composing.CompositionExtensions; using Umbraco.Web.Sections; using Current = Umbraco.Core.Composing.Current; @@ -108,6 +108,8 @@ namespace Umbraco.Tests.Testing protected IMapperCollection Mappers => Factory.GetInstance(); + protected Mapper Mapper => Factory.GetInstance(); + #endregion #region Setup @@ -148,7 +150,7 @@ namespace Umbraco.Tests.Testing protected virtual void Compose() { - ComposeAutoMapper(Options.AutoMapper); + ComposeMapper(Options.Mapper); ComposeDatabase(Options.Database); ComposeApplication(Options.WithApplication); @@ -165,7 +167,6 @@ namespace Umbraco.Tests.Testing protected virtual void Initialize() { - InitializeAutoMapper(Options.AutoMapper); InitializeApplication(Options.WithApplication); } @@ -249,7 +250,7 @@ namespace Umbraco.Tests.Testing Composition.WithCollectionBuilder(); } - protected virtual void ComposeAutoMapper(bool configure) + protected virtual void ComposeMapper(bool configure) { if (configure == false) return; @@ -371,18 +372,6 @@ namespace Umbraco.Tests.Testing #region Initialize - protected virtual void InitializeAutoMapper(bool configure) - { - if (configure == false) return; - - Mapper.Initialize(configuration => - { - var profiles = Factory.GetAllInstances(); - foreach (var profile in profiles) - configuration.AddProfile(profile); - }); - } - protected virtual void InitializeApplication(bool withApplication) { if (withApplication == false) return; @@ -430,8 +419,6 @@ namespace Umbraco.Tests.Testing UriUtility.ResetAppDomainAppVirtualPath(); SettingsForTests.Reset(); // FIXME: should it be optional? - Mapper.Reset(); - // clear static events DocumentRepository.ClearScopeEvents(); MediaRepository.ClearScopeEvents(); diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index 17cb425eef..b5cdad5241 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -163,7 +163,6 @@ - @@ -271,7 +270,6 @@ - diff --git a/src/Umbraco.Web/Editors/Filters/UserGroupValidateAttribute.cs b/src/Umbraco.Web/Editors/Filters/UserGroupValidateAttribute.cs index fe6a049768..d91114c816 100644 --- a/src/Umbraco.Web/Editors/Filters/UserGroupValidateAttribute.cs +++ b/src/Umbraco.Web/Editors/Filters/UserGroupValidateAttribute.cs @@ -3,8 +3,8 @@ using System.Net; using System.Net.Http; using System.Web.Http.Controllers; using System.Web.Http.Filters; -using AutoMapper; using Umbraco.Core; +using Umbraco.Core.Mapping; using Umbraco.Core.Composing; using Umbraco.Core.Models.Membership; using Umbraco.Core.Services; @@ -27,6 +27,8 @@ namespace Umbraco.Web.Editors.Filters _userService = userService; } + private static Mapper Mapper => Current.Mapper; + private IUserService UserService => _userService ?? Current.Services.UserService; // TODO: inject public override void OnActionExecuting(HttpActionContext actionContext) diff --git a/src/Umbraco.Web/Models/Mapping/CommonMapper.cs b/src/Umbraco.Web/Models/Mapping/CommonMapper.cs new file mode 100644 index 0000000000..fab12494ff --- /dev/null +++ b/src/Umbraco.Web/Models/Mapping/CommonMapper.cs @@ -0,0 +1,101 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Web; +using System.Web.Mvc; +using Umbraco.Core; +using Umbraco.Core.Mapping; +using Umbraco.Core.Models; +using Umbraco.Core.Models.ContentEditing; +using Umbraco.Core.Models.Membership; +using Umbraco.Core.Services; +using Umbraco.Web.ContentApps; +using Umbraco.Web.Models.ContentEditing; +using Umbraco.Web.Trees; +using UserProfile = Umbraco.Web.Models.ContentEditing.UserProfile; + +namespace Umbraco.Web.Models.Mapping +{ + internal class CommonMapper + { + private readonly IUserService _userService; + private readonly IContentTypeBaseServiceProvider _contentTypeBaseServiceProvider; + private readonly IUmbracoContextAccessor _umbracoContextAccessor; + private readonly ContentAppFactoryCollection _contentAppDefinitions; + private readonly ILocalizedTextService _localizedTextService; + + public CommonMapper(IUserService userService, IContentTypeBaseServiceProvider contentTypeBaseServiceProvider, IUmbracoContextAccessor umbracoContextAccessor, + ContentAppFactoryCollection contentAppDefinitions, ILocalizedTextService localizedTextService) + { + _userService = userService; + _contentTypeBaseServiceProvider = contentTypeBaseServiceProvider; + _umbracoContextAccessor = umbracoContextAccessor; + _contentAppDefinitions = contentAppDefinitions; + _localizedTextService = localizedTextService; + } + + public UserProfile GetOwner(IContentBase source, Mapper mapper) + { + var profile = source.GetCreatorProfile(_userService); + return profile == null ? null : mapper.Map(profile); + } + + public UserProfile GetCreator(IContent source, Mapper mapper) + { + var profile = source.GetWriterProfile(_userService); + return profile == null ? null : mapper.Map(profile); + } + + public ContentTypeBasic GetContentType(IContentBase source, Mapper mapper) + { + // TODO: We can resolve the UmbracoContext from the IValueResolver options! + // OMG + if (HttpContext.Current != null && Composing.Current.UmbracoContext != null && Composing.Current.UmbracoContext.Security.CurrentUser != null + && Composing.Current.UmbracoContext.Security.CurrentUser.AllowedSections.Any(x => x.Equals(Constants.Applications.Settings))) + { + var contentType = _contentTypeBaseServiceProvider.GetContentTypeOf(source); + var contentTypeBasic = mapper.Map(contentType); + + return contentTypeBasic; + } + //no access + return null; + } + + public string GetTreeNodeUrl(IContentBase source) + where TController : ContentTreeControllerBase + { + var umbracoContext = _umbracoContextAccessor.UmbracoContext; + if (umbracoContext == null) return null; + + var urlHelper = new UrlHelper(umbracoContext.HttpContext.Request.RequestContext); + return urlHelper.GetUmbracoApiService(controller => controller.GetTreeNode(source.Key.ToString("N"), null)); + } + + public string GetMemberTreeNodeUrl(IContentBase source) + { + var umbracoContext = _umbracoContextAccessor.UmbracoContext; + if (umbracoContext == null) return null; + + var urlHelper = new UrlHelper(umbracoContext.HttpContext.Request.RequestContext); + return urlHelper.GetUmbracoApiService(controller => controller.GetTreeNode(source.Key.ToString("N"), null)); + } + + public IEnumerable GetContentApps(IContentBase source) + { + var apps = _contentAppDefinitions.GetContentAppsFor(source).ToArray(); + + // localize content app names + foreach (var app in apps) + { + var localizedAppName = _localizedTextService.Localize($"apps/{app.Alias}"); + if (localizedAppName.Equals($"[{app.Alias}]", StringComparison.OrdinalIgnoreCase) == false) + { + app.Name = localizedAppName; + } + } + + return apps; + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Models/Mapping/ContentMapperProfile.cs b/src/Umbraco.Web/Models/Mapping/ContentMapperProfile.cs index 05d6828cc4..faa601cd41 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentMapperProfile.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentMapperProfile.cs @@ -55,11 +55,18 @@ namespace Umbraco.Web.Models.Mapping public void SetMaps(Mapper mapper) { + mapper.Define((source, context) => new ContentPropertyCollectionDto(), Map); mapper.Define((source, context) => new ContentItemDisplay(), Map); mapper.Define((source, context) => new ContentVariantDisplay(), Map); mapper.Define>((source, context) => new ContentItemBasic(), Map); } + // Umbraco.Code.MapAll + private static void Map(IContent source, ContentPropertyCollectionDto target, MapperContext context) + { + target.Properties = source.Properties.Select(context.Mapper.Map); + } + // Umbraco.Code.MapAll -AllowPreview -Errors -PersistedContent private void Map(IContent source, ContentItemDisplay target, MapperContext context) { @@ -120,7 +127,7 @@ namespace Umbraco.Web.Models.Mapping target.Owner = _commonMapper.GetOwner(source, context.Mapper); target.ParentId = source.ParentId; target.Path = source.Path; - target.Properties = context.Mapper.Map>(source.Properties); + target.Properties = source.Properties.Select(context.Mapper.Map); target.SortOrder = source.SortOrder; target.State = _basicStateMapper.Map(source, context); target.Trashed = source.Trashed; diff --git a/src/Umbraco.Web/Models/Mapping/ContentPropertyBasicMapper.cs b/src/Umbraco.Web/Models/Mapping/ContentPropertyBasicMapper.cs index fc20fd3c53..e7c53f5728 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentPropertyBasicMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentPropertyBasicMapper.cs @@ -31,7 +31,7 @@ namespace Umbraco.Web.Models.Mapping /// Assigns the PropertyEditor, Id, Alias and Value to the property /// /// - public virtual TDestination Map(Property property, TDestination dest, MapperContext context) + public virtual void Map(Property property, TDestination dest, MapperContext context) { var editor = _propertyEditors[property.PropertyType.PropertyEditorAlias]; if (editor == null) @@ -44,19 +44,16 @@ namespace Umbraco.Web.Models.Mapping editor = _propertyEditors[Constants.PropertyEditors.Aliases.Label]; } - var result = new TDestination - { - Id = property.Id, - Alias = property.Alias, - PropertyEditor = editor, - Editor = editor.Alias - }; + dest.Id = property.Id; + dest.Alias = property.Alias; + dest.PropertyEditor = editor; + dest.Editor = editor.Alias; // if there's a set of property aliases specified, we will check if the current property's value should be mapped. // if it isn't one of the ones specified in 'includeProperties', we will just return the result without mapping the Value. var includedProperties = context.GetIncludedProperties(); if (includedProperties != null && !includedProperties.Contains(property.Alias)) - return result; + return; //Get the culture from the context which will be set during the mapping operation for each property var culture = context.GetCulture(); @@ -68,11 +65,10 @@ namespace Umbraco.Web.Models.Mapping //set the culture to null if it's an invariant property type culture = !property.PropertyType.VariesByCulture() ? null : culture; - result.Culture = culture; + dest.Culture = culture; // if no 'IncludeProperties' were specified or this property is set to be included - we will map the value and return. - result.Value = editor.GetValueEditor().ToEditor(property, DataTypeService, culture); - return result; + dest.Value = editor.GetValueEditor().ToEditor(property, DataTypeService, culture); } } } diff --git a/src/Umbraco.Web/Models/Mapping/ContentPropertyDisplayMapper.cs b/src/Umbraco.Web/Models/Mapping/ContentPropertyDisplayMapper.cs index 756fb44dba..8a45548e9c 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentPropertyDisplayMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentPropertyDisplayMapper.cs @@ -19,9 +19,9 @@ namespace Umbraco.Web.Models.Mapping { _textService = textService; } - public override ContentPropertyDisplay Map(Property originalProp, ContentPropertyDisplay dest, MapperContext context) + public override void Map(Property originalProp, ContentPropertyDisplay dest, MapperContext context) { - var display = base.Map(originalProp, dest, context); + base.Map(originalProp, dest, context); var config = DataTypeService.GetDataType(originalProp.PropertyType.DataTypeId).Configuration; @@ -32,37 +32,35 @@ namespace Umbraco.Web.Models.Mapping // - does it make any sense to use a IDataValueEditor without configuring it? // configure the editor for display with configuration - var valEditor = display.PropertyEditor.GetValueEditor(config); + var valEditor = dest.PropertyEditor.GetValueEditor(config); //set the display properties after mapping - display.Alias = originalProp.Alias; - display.Description = originalProp.PropertyType.Description; - display.Label = originalProp.PropertyType.Name; - display.HideLabel = valEditor.HideLabel; + dest.Alias = originalProp.Alias; + dest.Description = originalProp.PropertyType.Description; + dest.Label = originalProp.PropertyType.Name; + dest.HideLabel = valEditor.HideLabel; //add the validation information - display.Validation.Mandatory = originalProp.PropertyType.Mandatory; - display.Validation.Pattern = originalProp.PropertyType.ValidationRegExp; + dest.Validation.Mandatory = originalProp.PropertyType.Mandatory; + dest.Validation.Pattern = originalProp.PropertyType.ValidationRegExp; - if (display.PropertyEditor == null) + if (dest.PropertyEditor == null) { //display.Config = PreValueCollection.AsDictionary(preVals); //if there is no property editor it means that it is a legacy data type // we cannot support editing with that so we'll just render the readonly value view. - display.View = "views/propertyeditors/readonlyvalue/readonlyvalue.html"; + dest.View = "views/propertyeditors/readonlyvalue/readonlyvalue.html"; } else { //let the property editor format the pre-values - display.Config = display.PropertyEditor.GetConfigurationEditor().ToValueEditor(config); - display.View = valEditor.View; + dest.Config = dest.PropertyEditor.GetConfigurationEditor().ToValueEditor(config); + dest.View = valEditor.View; } //Translate - display.Label = _textService.UmbracoDictionaryTranslate(display.Label); - display.Description = _textService.UmbracoDictionaryTranslate(display.Description); - - return display; + dest.Label = _textService.UmbracoDictionaryTranslate(dest.Label); + dest.Description = _textService.UmbracoDictionaryTranslate(dest.Description); } } } diff --git a/src/Umbraco.Web/Models/Mapping/ContentPropertyDtoMapper.cs b/src/Umbraco.Web/Models/Mapping/ContentPropertyDtoMapper.cs index bdb9c16c6a..f192cd32ce 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentPropertyDtoMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentPropertyDtoMapper.cs @@ -16,17 +16,15 @@ namespace Umbraco.Web.Models.Mapping : base(dataTypeService, logger, propertyEditors) { } - public override ContentPropertyDto Map(Property property, ContentPropertyDto dest, MapperContext context) + public override void Map(Property property, ContentPropertyDto dest, MapperContext context) { - var propertyDto = base.Map(property, dest, context); + base.Map(property, dest, context); - propertyDto.IsRequired = property.PropertyType.Mandatory; - propertyDto.ValidationRegExp = property.PropertyType.ValidationRegExp; - propertyDto.Description = property.PropertyType.Description; - propertyDto.Label = property.PropertyType.Name; - propertyDto.DataType = DataTypeService.GetDataType(property.PropertyType.DataTypeId); - - return propertyDto; + dest.IsRequired = property.PropertyType.Mandatory; + dest.ValidationRegExp = property.PropertyType.ValidationRegExp; + dest.Description = property.PropertyType.Description; + dest.Label = property.PropertyType.Name; + dest.DataType = DataTypeService.GetDataType(property.PropertyType.DataTypeId); } } } diff --git a/src/Umbraco.Web/Models/Mapping/ContentTypeMapperProfile.cs b/src/Umbraco.Web/Models/Mapping/ContentTypeMapperProfile.cs index 48f35f83cb..74bdd542a0 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentTypeMapperProfile.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentTypeMapperProfile.cs @@ -383,9 +383,12 @@ namespace Umbraco.Web.Models.Mapping target.AllowedAsRoot = source.AllowAsRoot; target.AllowedContentTypes = source.AllowedContentTypes.Select((t, i) => new ContentTypeSort(t, i)); - target.Variations = ContentVariation.Nothing; - if (!(target is IMemberType) && source.AllowCultureVariant) - target.Variations |= ContentVariation.Culture; + if (!(target is IMemberType)) + { + target.Variations = ContentVariation.Nothing; + if (source.AllowCultureVariant) + target.Variations |= ContentVariation.Culture; + } // handle property groups and property types // note that ContentTypeSave has diff --git a/src/Umbraco.Web/Models/Mapping/MediaMapperProfile.cs b/src/Umbraco.Web/Models/Mapping/MediaMapperProfile.cs index f2cc0b91d8..aa2405779d 100644 --- a/src/Umbraco.Web/Models/Mapping/MediaMapperProfile.cs +++ b/src/Umbraco.Web/Models/Mapping/MediaMapperProfile.cs @@ -1,20 +1,12 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Web; -using System.Web.Mvc; +using System.Linq; using Umbraco.Core; using Umbraco.Core.Composing; using Umbraco.Core.Logging; using Umbraco.Core.Mapping; using Umbraco.Core.Models; -using Umbraco.Core.Models.ContentEditing; -using Umbraco.Core.Models.Membership; using Umbraco.Core.Services; -using Umbraco.Web.ContentApps; using Umbraco.Web.Models.ContentEditing; using Umbraco.Web.Trees; -using UserProfile = Umbraco.Web.Models.ContentEditing.UserProfile; namespace Umbraco.Web.Models.Mapping { @@ -42,10 +34,17 @@ namespace Umbraco.Web.Models.Mapping public void SetMaps(Mapper mapper) { + mapper.Define((source, context) => new ContentPropertyCollectionDto(), Map); mapper.Define((source, context) => new MediaItemDisplay(), Map); mapper.Define>((source, context) => new ContentItemBasic(), Map); } + // Umbraco.Code.MapAll + private static void Map(IMedia source, ContentPropertyCollectionDto target, MapperContext context) + { + target.Properties = source.Properties.Select(context.Mapper.Map); + } + // Umbraco.Code.MapAll -Properties -Errors -Edited -Updater -Alias -IsContainer private void Map(IMedia source, MediaItemDisplay target, MapperContext context) { @@ -85,7 +84,7 @@ namespace Umbraco.Web.Models.Mapping target.Owner = _commonMapper.GetOwner(source, context.Mapper); target.ParentId = source.ParentId; target.Path = source.Path; - target.Properties = context.Mapper.Map>(source.Properties); + target.Properties = source.Properties.Select(context.Mapper.Map); target.SortOrder = source.SortOrder; target.State = null; target.Trashed = source.Trashed; @@ -101,88 +100,4 @@ namespace Umbraco.Web.Models.Mapping return parent != null && (parent.ContentType.IsContainer || _mediaTypeService.HasContainerInPath(parent.Path)); } } - - // fixme temp + rename - internal class CommonMapper - { - private readonly IUserService _userService; - private readonly IContentTypeBaseServiceProvider _contentTypeBaseServiceProvider; - private readonly IUmbracoContextAccessor _umbracoContextAccessor; - private readonly ContentAppFactoryCollection _contentAppDefinitions; - private readonly ILocalizedTextService _localizedTextService; - - public CommonMapper(IUserService userService, IContentTypeBaseServiceProvider contentTypeBaseServiceProvider, IUmbracoContextAccessor umbracoContextAccessor, - ContentAppFactoryCollection contentAppDefinitions, ILocalizedTextService localizedTextService) - { - _userService = userService; - _contentTypeBaseServiceProvider = contentTypeBaseServiceProvider; - _umbracoContextAccessor = umbracoContextAccessor; - _contentAppDefinitions = contentAppDefinitions; - _localizedTextService = localizedTextService; - } - - public UserProfile GetOwner(IContentBase source, Mapper mapper) - { - var profile = source.GetCreatorProfile(_userService); - return profile == null ? null : mapper.Map(profile); - } - - public UserProfile GetCreator(IContent source, Mapper mapper) - { - var profile = source.GetWriterProfile(_userService); - return profile == null ? null : mapper.Map(profile); - } - - public ContentTypeBasic GetContentType(IContentBase source, Mapper mapper) - { - // TODO: We can resolve the UmbracoContext from the IValueResolver options! - // OMG - if (HttpContext.Current != null && Composing.Current.UmbracoContext != null && Composing.Current.UmbracoContext.Security.CurrentUser != null - && Composing.Current.UmbracoContext.Security.CurrentUser.AllowedSections.Any(x => x.Equals(Constants.Applications.Settings))) - { - var contentType = _contentTypeBaseServiceProvider.GetContentTypeOf(source); - var contentTypeBasic = mapper.Map(contentType); - - return contentTypeBasic; - } - //no access - return null; - } - - public string GetTreeNodeUrl(IContentBase source) - where TController : ContentTreeControllerBase - { - var umbracoContext = _umbracoContextAccessor.UmbracoContext; - if (umbracoContext == null) return null; - - var urlHelper = new UrlHelper(umbracoContext.HttpContext.Request.RequestContext); - return urlHelper.GetUmbracoApiService(controller => controller.GetTreeNode(source.Key.ToString("N"), null)); - } - - public string GetMemberTreeNodeUrl(IContentBase source) - { - var umbracoContext = _umbracoContextAccessor.UmbracoContext; - if (umbracoContext == null) return null; - - var urlHelper = new UrlHelper(umbracoContext.HttpContext.Request.RequestContext); - return urlHelper.GetUmbracoApiService(controller => controller.GetTreeNode(source.Key.ToString("N"), null)); - } - - public IEnumerable GetContentApps(IContentBase source) - { - var apps = _contentAppDefinitions.GetContentAppsFor(source).ToArray(); - - // localize content app names - foreach (var app in apps) - { - var localizedAppName = _localizedTextService.Localize($"apps/{app.Alias}"); - if (localizedAppName.Equals($"[{app.Alias}]", StringComparison.OrdinalIgnoreCase) == false) - { - app.Name = localizedAppName; - } - } - - return apps; - } - } } diff --git a/src/Umbraco.Web/Models/Mapping/TabsAndPropertiesMapper.cs b/src/Umbraco.Web/Models/Mapping/TabsAndPropertiesMapper.cs index a9fdea587d..89cf58aaeb 100644 --- a/src/Umbraco.Web/Models/Mapping/TabsAndPropertiesMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/TabsAndPropertiesMapper.cs @@ -105,14 +105,8 @@ namespace Umbraco.Web.Models.Mapping /// protected virtual List MapProperties(IContentBase content, List properties, MapperContext context) { - //we need to map this way to pass the context through, I don't like it but we'll see what AutoMapper says: https://github.com/AutoMapper/AutoMapper/issues/2588 - var result = context.Mapper.Map, IEnumerable>( - properties.OrderBy(prop => prop.PropertyType.SortOrder), - null, - context) - .ToList(); - - return result; + // must pass the context through + return properties.OrderBy(x => x.PropertyType.SortOrder).Select(x => context.Mapper.Map(x, context)).ToList(); } } diff --git a/src/Umbraco.Web/Models/Mapping/UserMapperProfile.cs b/src/Umbraco.Web/Models/Mapping/UserMapperProfile.cs index d8c61fe6cc..83b186fac9 100644 --- a/src/Umbraco.Web/Models/Mapping/UserMapperProfile.cs +++ b/src/Umbraco.Web/Models/Mapping/UserMapperProfile.cs @@ -42,7 +42,7 @@ namespace Umbraco.Web.Models.Mapping { mapper.Define((source, context) => new UserGroup { CreateDate = DateTime.UtcNow }, Map); mapper.Define(Map); - mapper.Define(Map); + mapper.Define((source, context) => new ContentEditing.UserProfile(), Map); mapper.Define((source, context) => new UserGroupBasic(), Map); mapper.Define((source, context) => new UserGroupBasic(), Map); mapper.Define((source, context) => new AssignedUserGroupPermissions(), Map); @@ -115,8 +115,8 @@ namespace Umbraco.Web.Models.Mapping private void Map(UserSave source, IUser target, MapperContext context) { target.Name = source.Name; - target.StartContentIds = source.StartContentIds; - target.StartMediaIds = source.StartMediaIds; + target.StartContentIds = source.StartContentIds ?? Array.Empty(); + target.StartMediaIds = source.StartMediaIds ?? Array.Empty(); target.Language = source.Culture; target.Email = source.Email; target.Key = source.Key; diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 9c8d64ab08..370596078e 100755 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -204,6 +204,7 @@ +