Cleanup and fix mappers

This commit is contained in:
Stephan
2019-03-26 18:47:35 +01:00
parent 70c2090a56
commit 72bdf56ddf
23 changed files with 451 additions and 647 deletions

View File

@@ -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?
/// <summary>
/// Umbraco Mapper.
/// </summary>
public class Mapper
{
private readonly Dictionary<Type, Dictionary<Type, Func<object, MapperContext, object>>> _ctors
@@ -15,6 +20,10 @@ namespace Umbraco.Core.Mapping
private readonly Dictionary<Type, Dictionary<Type, Action<object, object, MapperContext>>> _maps
= new Dictionary<Type, Dictionary<Type, Action<object, object, MapperContext>>>();
/// <summary>
/// Initializes a new instance of the <see cref="Mapper"/> class.
/// </summary>
/// <param name="profiles"></param>
public Mapper(MapperProfileCollection profiles)
{
foreach (var profile in profiles)
@@ -23,14 +32,85 @@ namespace Umbraco.Core.Mapping
#region Define
private TTarget ThrowCtor<TSource, TTarget>(TSource source, MapperContext context)
=> throw new InvalidOperationException($"Don't know how to create {typeof(TTarget).FullName} instances.");
private void Identity<TSource, TTarget>(TSource source, TTarget target, MapperContext context)
{ }
/// <summary>
/// Defines a mapping.
/// </summary>
/// <typeparam name="TSource">The source type.</typeparam>
/// <typeparam name="TTarget">The target type.</typeparam>
public void Define<TSource, TTarget>()
=> Define<TSource, TTarget>((source, target, context) => { });
=> Define<TSource, TTarget>(ThrowCtor<TSource, TTarget>, Identity);
/// <summary>
/// Defines a mapping.
/// </summary>
/// <typeparam name="TSource">The source type.</typeparam>
/// <typeparam name="TTarget">The target type.</typeparam>
/// <param name="map">A mapping method.</param>
public void Define<TSource, TTarget>(Action<TSource, TTarget, MapperContext> map)
=> Define((source, context) => throw new NotSupportedException($"Don't know how to create {typeof(TTarget)} instances."), map);
=> Define(ThrowCtor<TSource, TTarget>, map);
/// <summary>
/// Defines a mapping.
/// </summary>
/// <typeparam name="TSource">The source type.</typeparam>
/// <typeparam name="TTarget">The target type.</typeparam>
/// <param name="ctor">A constructor method.</param>
public void Define<TSource, TTarget>(Func<TSource, MapperContext, TTarget> ctor)
=> Define(ctor, (source, target, context) => { });
=> Define(ctor, Identity);
/// <summary>
/// Defines a mapping.
/// </summary>
/// <typeparam name="TSource">The source type.</typeparam>
/// <typeparam name="TTarget">The target type.</typeparam>
/// <param name="ctor">A constructor method.</param>
/// <param name="map">A mapping method.</param>
public void Define<TSource, TTarget>(Func<TSource, MapperContext, TTarget> ctor, Action<TSource, TTarget, MapperContext> 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);
}
/// <summary>
/// Defines a mapping as a clone of an already defined mapping.
/// </summary>
/// <typeparam name="TSource">The source type.</typeparam>
/// <typeparam name="TTarget">The target type.</typeparam>
/// <typeparam name="TAsSource">The equivalent source type.</typeparam>
/// <typeparam name="TAsTarget">The equivalent target type.</typeparam>
public void DefineAs<TSource, TTarget, TAsSource, TAsTarget>()
{
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<Type, Func<object, MapperContext, object>> DefineCtors(Type sourceType)
{
@@ -46,25 +126,26 @@ namespace Umbraco.Core.Mapping
return sourceMap;
}
public void Define<TSource, TTarget>(Func<TSource, MapperContext, TTarget> ctor, Action<TSource, TTarget, MapperContext> 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
/// <summary>
/// Maps a source object to a new target object.
/// </summary>
/// <typeparam name="TTarget">The target type.</typeparam>
/// <param name="source">The source object.</param>
/// <returns>The target object.</returns>
public TTarget Map<TTarget>(object source)
=> Map<TTarget>(source, new MapperContext(this));
/// <summary>
/// Maps a source object to a new target object.
/// </summary>
/// <typeparam name="TTarget">The target type.</typeparam>
/// <param name="source">The source object.</param>
/// <param name="f">A mapper context preparation method.</param>
/// <returns>The target object.</returns>
public TTarget Map<TTarget>(object source, Action<MapperContext> f)
{
var context = new MapperContext(this);
@@ -72,16 +153,63 @@ namespace Umbraco.Core.Mapping
return Map<TTarget>(source, context);
}
/// <summary>
/// Maps a source object to a new target object.
/// </summary>
/// <typeparam name="TTarget">The target type.</typeparam>
/// <param name="source">The source object.</param>
/// <param name="context">A mapper context.</param>
/// <returns>The target object.</returns>
public TTarget Map<TTarget>(object source, MapperContext context)
=> Map<TTarget>(source, source?.GetType(), context);
/// <summary>
/// Maps a source object to a new target object.
/// </summary>
/// <typeparam name="TSource">The source type.</typeparam>
/// <typeparam name="TTarget">The target type.</typeparam>
/// <param name="source">The source object.</param>
/// <returns>The target object.</returns>
public TTarget Map<TSource, TTarget>(TSource source)
=> Map<TSource, TTarget>(source, new MapperContext(this));
/// <summary>
/// Maps a source object to a new target object.
/// </summary>
/// <typeparam name="TSource">The source type.</typeparam>
/// <typeparam name="TTarget">The target type.</typeparam>
/// <param name="source">The source object.</param>
/// <param name="f">A mapper context preparation method.</param>
/// <returns>The target object.</returns>
public TTarget Map<TSource, TTarget>(TSource source, Action<MapperContext> f)
{
var context = new MapperContext(this);
f(context);
return Map<TSource, TTarget>(source, context);
}
/// <summary>
/// Maps a source object to a new target object.
/// </summary>
/// <typeparam name="TSource">The source type.</typeparam>
/// <typeparam name="TTarget">The target type.</typeparam>
/// <param name="source">The source object.</param>
/// <param name="context">A mapper context.</param>
/// <returns>The target object.</returns>
public TTarget Map<TSource, TTarget>(TSource source, MapperContext context)
=> Map<TTarget>(source, typeof(TSource), context);
private TTarget Map<TTarget>(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, TTarget>(TSource source)
=> Map<TSource, TTarget>(source, new MapperContext(this));
public TTarget Map<TSource, TTarget>(TSource source, Action<MapperContext> f)
{
var context = new MapperContext(this);
f(context);
return Map<TSource, TTarget>(source, context);
}
public TTarget Map<TSource, TTarget>(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}.");
}
/// <summary>
/// Maps a source object to an existing target object.
/// </summary>
/// <typeparam name="TSource">The source type.</typeparam>
/// <typeparam name="TTarget">The target type.</typeparam>
/// <param name="source">The source object.</param>
/// <param name="target">The target object.</param>
/// <returns>The target object.</returns>
public TTarget Map<TSource, TTarget>(TSource source, TTarget target)
=> Map(source, target, new MapperContext(this));
/// <summary>
/// Maps a source object to an existing target object.
/// </summary>
/// <typeparam name="TSource">The source type.</typeparam>
/// <typeparam name="TTarget">The target type.</typeparam>
/// <param name="source">The source object.</param>
/// <param name="target">The target object.</param>
/// <param name="f">A mapper context preparation method.</param>
/// <returns>The target object.</returns>
public TTarget Map<TSource, TTarget>(TSource source, TTarget target, Action<MapperContext> f)
{
var context = new MapperContext(this);
@@ -212,18 +280,32 @@ namespace Umbraco.Core.Mapping
return Map(source, target, context);
}
/// <summary>
/// Maps a source object to an existing target object.
/// </summary>
/// <typeparam name="TSource">The source type.</typeparam>
/// <typeparam name="TTarget">The target type.</typeparam>
/// <param name="source">The source object.</param>
/// <param name="target">The target object.</param>
/// <param name="context">A mapper context.</param>
/// <returns>The target object.</returns>
public TTarget Map<TSource, TTarget>(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<object, MapperContext, object> 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
}
}

View File

@@ -54,62 +54,46 @@
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="Microsoft.CodeAnalysis" publicKeyToken="31bf3856ad364e35" culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-2.8.0.0" newVersion="2.8.0.0"/>
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="Microsoft.CodeAnalysis.CSharp" publicKeyToken="31bf3856ad364e35" culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-2.8.0.0" newVersion="2.8.0.0"/>
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="Microsoft.Owin" publicKeyToken="31bf3856ad364e35" culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-4.0.1.0" newVersion="4.0.1.0"/>
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="Microsoft.Owin.Security" publicKeyToken="31bf3856ad364e35" culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-4.0.1.0" newVersion="4.0.1.0"/>
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="Microsoft.Owin.Security.Cookies" publicKeyToken="31bf3856ad364e35" culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-4.0.1.0" newVersion="4.0.1.0"/>
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="Microsoft.Owin.Security.OAuth" publicKeyToken="31bf3856ad364e35" culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-4.0.1.0" newVersion="4.0.1.0"/>
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed"/>
<bindingRedirect oldVersion="0.0.0.0-12.0.0.0" newVersion="12.0.0.0"/>
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Collections.Immutable" publicKeyToken="b03f5f7f11d50a3a" culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-1.2.2.0" newVersion="1.2.2.0"/>
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Net.Http.Formatting" publicKeyToken="31bf3856ad364e35" culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-5.2.7.0" newVersion="5.2.7.0"/>
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Reflection.Metadata" publicKeyToken="b03f5f7f11d50a3a" culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-1.4.2.0" newVersion="1.4.2.0"/>
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Text.Encoding.CodePages" publicKeyToken="b03f5f7f11d50a3a" culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-4.1.0.0" newVersion="4.1.0.0"/>
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Web.Http" publicKeyToken="31bf3856ad364e35" culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-5.2.7.0" newVersion="5.2.7.0"/>
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Web.Mvc" publicKeyToken="31bf3856ad364e35" culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-5.2.7.0" newVersion="5.2.7.0"/>
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Threading.Tasks.Extensions" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-4.1.1.0" newVersion="4.1.1.0"/>
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="Microsoft.Owin" publicKeyToken="31bf3856ad364e35" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.0.1.0" newVersion="4.0.1.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="Microsoft.Owin.Security" publicKeyToken="31bf3856ad364e35" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.0.1.0" newVersion="4.0.1.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="Microsoft.Owin.Security.Cookies" publicKeyToken="31bf3856ad364e35" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.0.1.0" newVersion="4.0.1.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="Microsoft.Owin.Security.OAuth" publicKeyToken="31bf3856ad364e35" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.0.1.0" newVersion="4.0.1.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-12.0.0.0" newVersion="12.0.0.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Collections.Immutable" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-1.2.3.0" newVersion="1.2.3.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Web.Http" publicKeyToken="31bf3856ad364e35" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-5.2.7.0" newVersion="5.2.7.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Web.Mvc" publicKeyToken="31bf3856ad364e35" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-5.2.7.0" newVersion="5.2.7.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.ValueTuple" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.0.3.0" newVersion="4.0.3.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Net.Http.Formatting" publicKeyToken="31bf3856ad364e35"/>
<bindingRedirect oldVersion="0.0.0.0-5.2.7.0" newVersion="5.2.7.0"/>
</dependentAssembly>
</assemblyBinding>
</runtime>

View File

@@ -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, Thing2>(thing3);
Assert.IsNotNull(thing2);
Assert.AreEqual("value", thing2.Value);
thing2 = mapper.Map<Thing2>(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; }

View File

@@ -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<ThingProfile1>();
});
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, ThingB>(thingA);
Assert.AreEqual(42, thingB.ValueInt);
Assert.AreEqual("!!foo!!", thingB.ValueString);
mapper.Map<ThingA, ThingB>(thingA);
mapper.Map<ThingA, ThingB>(thingA);
mapper.Map<ThingA, ThingB>(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<ThingProfile2>();
});
var mapper = config.CreateMapper();
Assert.AreEqual(0, ValueResolver.CtorCount);
var thingA = new ThingA { ValueInt = 42, ValueString = "foo" };
var thingB = mapper.Map<ThingA, ThingB>(thingA);
Assert.AreEqual(42, thingB.ValueInt);
Assert.AreEqual("!!foo!!", thingB.ValueString);
mapper.Map<ThingA, ThingB>(thingA);
mapper.Map<ThingA, ThingB>(thingA);
mapper.Map<ThingA, ThingB>(thingA);
Assert.AreEqual(4, ValueResolver.CtorCount); // many resolvers
}
[Test]
public void Test3()
{
var config = new MapperConfiguration(cfg =>
{
cfg.AddProfile<ThingProfile3>();
});
var mapper = config.CreateMapper();
var thingA = new ThingA { ValueInt = 42, ValueString = "foo" };
var thingB = mapper.Map<ThingA, ThingB>(thingA);
Assert.AreEqual(42, thingB.ValueInt);
Assert.AreEqual("!!foo!!", thingB.ValueString);
mapper.Map<ThingA, ThingB>(thingA);
mapper.Map<ThingA, ThingB>(thingA);
mapper.Map<ThingA, ThingB>(thingA);
}
// Resolve destination member using a custom value resolver
// void ResolveUsing<TValueResolver>()
// where TValueResolver : IValueResolver<TSource, TDestination, TDestMember>;
// Resolve destination member using a custom value resolver from a source member
// void ResolveUsing<TValueResolver, TSourceMember>(Expression<Func<TSource, TSourceMember>> sourceMember)
// where TValueResolver : IMemberValueResolver<TSource, TDestination, TSourceMember, TMember>;
// void ResolveUsing<TValueResolver, TSourceMember>(string sourceMemberName)
// where TValueResolver : IMemberValueResolver<TSource, TDestination, TSourceMember, TMember>;
// Resolve destination member using a custom value resolver instance
// void ResolveUsing(IValueResolver<TSource, TDestination, TMember> valueResolver);
// void ResolveUsing<TSourceMember>(IMemberValueResolver<TSource, TDestination, TSourceMember, TMember> valueResolver, Expression<Func<TSource, TSourceMember>> sourceMember);
// Resolve destination member using a custom value resolver callback
// void ResolveUsing<TResult>(Func<TSource, TResult> resolver);
// void ResolveUsing<TResult>(Func<TSource, TDestination, TResult> resolver);
// void ResolveUsing<TResult>(Func<TSource, TDestination, TMember, TResult> resolver);
// void ResolveUsing<TResult>(Func<TSource, TDestination, TMember, ResolutionContext, TResult> 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<ThingA, ThingB>()
.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<MemberValueResolver, string>(src => src.ValueString));
break;
case 2:
map
.ForMember(dest => dest.ValueString, opt => opt.MapFrom<ValueResolver>());
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<ThingA, ThingB, string>
{
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<ThingA, ThingB, string, string>
{
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; }
}
}
}

View File

@@ -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<IManifestValueValidator>()),
Composition.Logger)
{
Path = TestHelper.CurrentAssemblyDirectory
};
Composition.RegisterUnique(_ => manifestBuilder);
Func<IEnumerable<Type>> typeListProducerList = Enumerable.Empty<Type>;
Composition.WithCollectionBuilder<DataEditorCollectionBuilder>()
.Clear()
.Add(typeListProducerList);
}
[Test]
public void AssertConfigurationIsValid()
{
var profiles = Factory.GetAllInstances<Profile>().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();
}
}
}

View File

@@ -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<IFileService> _fileService = new Mock<IFileService>();
private Mock<PropertyEditorCollection> _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<IMediaTypeService>(), Mock.Of<ILogger>());
configuration.AddProfile(profile1);
var profile2 = new EntityMapperProfile();
configuration.AddProfile(profile2);
});
}
protected override void Compose()
{
base.Compose();

View File

@@ -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;

View File

@@ -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]

View File

@@ -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()

View File

@@ -12,17 +12,17 @@ namespace Umbraco.Tests.Testing
/// <remarks>
/// <para>Default is false.</para>
/// <para>This is for tests that inherited from TestWithApplicationBase.</para>
/// <para>Implies AutoMapper = true (, ResetPluginManager = false).</para>
/// <para>Implies Mapper = true (, ResetPluginManager = false).</para>
/// </remarks>
public bool WithApplication { get => _withApplication.ValueOrDefault(false); set => _withApplication.Set(value); }
private readonly Settable<bool> _withApplication = new Settable<bool>();
/// <summary>
/// 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.
/// </summary>
/// <remarks>Default is false unless WithApplication is true, in which case default is true.</remarks>
public bool AutoMapper { get => _autoMapper.ValueOrDefault(WithApplication); set => _autoMapper.Set(value); }
private readonly Settable<bool> _autoMapper = new Settable<bool>();
public bool Mapper { get => _mapper.ValueOrDefault(WithApplication); set => _mapper.Set(value); }
private readonly Settable<bool> _mapper = new Settable<bool>();
// FIXME: to be completed
/// <summary>
@@ -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);

View File

@@ -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<IMapperCollection>();
protected Mapper Mapper => Factory.GetInstance<Mapper>();
#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<ContentAppFactoryCollectionBuilder>();
}
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<Profile>();
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();

View File

@@ -163,7 +163,6 @@
<Compile Include="Issues\U9560.cs" />
<Compile Include="Integration\ContentEventsTests.cs" />
<Compile Include="Migrations\AdvancedMigrationTests.cs" />
<Compile Include="Models\Mapping\AutoMapper6Tests.cs" />
<Compile Include="Models\Mapping\UserModelMapperTests.cs" />
<Compile Include="Packaging\PackageExtractionTests.cs" />
<Compile Include="Persistence\NPocoTests\NPocoFetchTests.cs" />
@@ -271,7 +270,6 @@
<Compile Include="Migrations\Stubs\SixZeroMigration2.cs" />
<Compile Include="Testing\TestingTests\MockTests.cs" />
<Compile Include="Models\MacroTests.cs" />
<Compile Include="Models\Mapping\AutoMapperTests.cs" />
<Compile Include="Models\Collections\Item.cs" />
<Compile Include="Models\Collections\OrderItem.cs" />
<Compile Include="Models\Collections\SimpleOrder.cs" />

View File

@@ -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)

View File

@@ -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<IProfile, UserProfile>(profile);
}
public UserProfile GetCreator(IContent source, Mapper mapper)
{
var profile = source.GetWriterProfile(_userService);
return profile == null ? null : mapper.Map<IProfile, UserProfile>(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<IContentTypeComposition, ContentTypeBasic>(contentType);
return contentTypeBasic;
}
//no access
return null;
}
public string GetTreeNodeUrl<TController>(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<TController>(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<MemberTreeController>(controller => controller.GetTreeNode(source.Key.ToString("N"), null));
}
public IEnumerable<ContentApp> 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;
}
}
}

View File

@@ -55,11 +55,18 @@ namespace Umbraco.Web.Models.Mapping
public void SetMaps(Mapper mapper)
{
mapper.Define<IContent, ContentPropertyCollectionDto>((source, context) => new ContentPropertyCollectionDto(), Map);
mapper.Define<IContent, ContentItemDisplay>((source, context) => new ContentItemDisplay(), Map);
mapper.Define<IContent, ContentVariantDisplay>((source, context) => new ContentVariantDisplay(), Map);
mapper.Define<IContent, ContentItemBasic<ContentPropertyBasic>>((source, context) => new ContentItemBasic<ContentPropertyBasic>(), Map);
}
// Umbraco.Code.MapAll
private static void Map(IContent source, ContentPropertyCollectionDto target, MapperContext context)
{
target.Properties = source.Properties.Select(context.Mapper.Map<ContentPropertyDto>);
}
// 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<IEnumerable<ContentPropertyBasic>>(source.Properties);
target.Properties = source.Properties.Select(context.Mapper.Map<ContentPropertyBasic>);
target.SortOrder = source.SortOrder;
target.State = _basicStateMapper.Map(source, context);
target.Trashed = source.Trashed;

View File

@@ -31,7 +31,7 @@ namespace Umbraco.Web.Models.Mapping
/// Assigns the PropertyEditor, Id, Alias and Value to the property
/// </summary>
/// <returns></returns>
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);
}
}
}

View File

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

View File

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

View File

@@ -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

View File

@@ -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<IMedia, ContentPropertyCollectionDto>((source, context) => new ContentPropertyCollectionDto(), Map);
mapper.Define<IMedia, MediaItemDisplay>((source, context) => new MediaItemDisplay(), Map);
mapper.Define<IMedia, ContentItemBasic<ContentPropertyBasic>>((source, context) => new ContentItemBasic<ContentPropertyBasic>(), Map);
}
// Umbraco.Code.MapAll
private static void Map(IMedia source, ContentPropertyCollectionDto target, MapperContext context)
{
target.Properties = source.Properties.Select(context.Mapper.Map<ContentPropertyDto>);
}
// 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<IEnumerable<ContentPropertyBasic>>(source.Properties);
target.Properties = source.Properties.Select(context.Mapper.Map<ContentPropertyBasic>);
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<IProfile, UserProfile>(profile);
}
public UserProfile GetCreator(IContent source, Mapper mapper)
{
var profile = source.GetWriterProfile(_userService);
return profile == null ? null : mapper.Map<IProfile, UserProfile>(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<IContentTypeComposition, ContentTypeBasic>(contentType);
return contentTypeBasic;
}
//no access
return null;
}
public string GetTreeNodeUrl<TController>(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<TController>(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<MemberTreeController>(controller => controller.GetTreeNode(source.Key.ToString("N"), null));
}
public IEnumerable<ContentApp> 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;
}
}
}

View File

@@ -105,14 +105,8 @@ namespace Umbraco.Web.Models.Mapping
/// <returns></returns>
protected virtual List<ContentPropertyDisplay> MapProperties(IContentBase content, List<Property> 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<Property>, IEnumerable<ContentPropertyDisplay>>(
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<ContentPropertyDisplay>(x, context)).ToList();
}
}

View File

@@ -42,7 +42,7 @@ namespace Umbraco.Web.Models.Mapping
{
mapper.Define<UserGroupSave, IUserGroup>((source, context) => new UserGroup { CreateDate = DateTime.UtcNow }, Map);
mapper.Define<UserInvite, IUser>(Map);
mapper.Define<IProfile, ContentEditing.UserProfile>(Map);
mapper.Define<IProfile, ContentEditing.UserProfile>((source, context) => new ContentEditing.UserProfile(), Map);
mapper.Define<IReadOnlyUserGroup, UserGroupBasic>((source, context) => new UserGroupBasic(), Map);
mapper.Define<IUserGroup, UserGroupBasic>((source, context) => new UserGroupBasic(), Map);
mapper.Define<IUserGroup, AssignedUserGroupPermissions>((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<int>();
target.StartMediaIds = source.StartMediaIds ?? Array.Empty<int>();
target.Language = source.Culture;
target.Email = source.Email;
target.Key = source.Key;

View File

@@ -204,6 +204,7 @@
<Compile Include="Models\ContentEditing\LinkDisplay.cs" />
<Compile Include="Models\ContentEditing\MacroDisplay.cs" />
<Compile Include="Models\ContentEditing\MacroParameterDisplay.cs" />
<Compile Include="Models\Mapping\CommonMapper.cs" />
<Compile Include="Models\Mapping\MapperContextExtensions.cs" />
<Compile Include="Models\PublishedContent\HybridVariationContextAccessor.cs" />
<Compile Include="Models\TemplateQuery\QueryConditionExtensions.cs" />