Experiment with mapping (perfs)

This commit is contained in:
Stephan
2019-03-19 16:27:08 +01:00
parent edb8340b32
commit b43e113cb6
9 changed files with 173 additions and 48 deletions

View File

@@ -1,4 +1,4 @@
using AutoMapper;
using Umbraco.Core.Mapping;
using Umbraco.Core.Models.Identity;
namespace Umbraco.Core.Composing.CompositionExtensions
@@ -8,7 +8,9 @@ namespace Umbraco.Core.Composing.CompositionExtensions
{
public static Composition ComposeCoreMappingProfiles(this Composition composition)
{
composition.Register<Profile, IdentityMapperProfile>();
composition.RegisterUnique<Mapper>();
composition.WithCollectionBuilder<MapperProfileCollectionBuilder>()
.Append<IdentityMapper>();
return composition;
}
}

View File

@@ -0,0 +1,7 @@
namespace Umbraco.Core.Mapping
{
public interface IMapperProfile
{
void SetMaps(Mapper mapper);
}
}

View File

@@ -0,0 +1,50 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace Umbraco.Core.Mapping
{
public class Mapper
{
private readonly Dictionary<Type, Dictionary<Type, Func<object, object>>> _maps = new Dictionary<Type, Dictionary<Type, Func<object, object>>>();
public Mapper(MapperProfileCollection profiles)
{
foreach (var profile in profiles)
profile.SetMaps(this);
}
public void SetMap<TSource, TTarget>(Func<TSource, TTarget> map)
{
var sourceType = typeof(TSource);
var targetType = typeof(TTarget);
if (!_maps.TryGetValue(sourceType, out var sourceMap))
sourceMap = _maps[sourceType] = new Dictionary<Type, Func<object, object>>();
sourceMap[targetType] = o => map((TSource)o);
}
public TTarget Map<TTarget>(object source)
{
if (source == null)
throw new ArgumentNullException(nameof(source));
var sourceType = source.GetType();
var targetType = typeof(TTarget);
if (!_maps.TryGetValue(sourceType, out var sourceMap))
{
var type = _maps.Keys.FirstOrDefault(x => x.IsAssignableFrom(sourceType));
if (type == null)
throw new InvalidOperationException($"Don't know how to map {sourceType.FullName} to {targetType.FullName}.");
sourceMap = _maps[sourceType] = _maps[type];
}
if (!sourceMap.TryGetValue(targetType, out var map))
throw new InvalidOperationException($"Don't know how to map {sourceType.FullName} to {targetType.FullName}.");
return (TTarget) map(source);
}
}
}

View File

@@ -0,0 +1,12 @@
using System.Collections.Generic;
using Umbraco.Core.Composing;
namespace Umbraco.Core.Mapping
{
public class MapperProfileCollection : BuilderCollectionBase<IMapperProfile>
{
public MapperProfileCollection(IEnumerable<IMapperProfile> items)
: base(items)
{ }
}
}

View File

@@ -0,0 +1,11 @@
using Umbraco.Core.Composing;
namespace Umbraco.Core.Mapping
{
public class MapperProfileCollectionBuilder : OrderedCollectionBuilderBase<MapperProfileCollectionBuilder, MapperProfileCollection, IMapperProfile>
{
protected override MapperProfileCollectionBuilder This => this;
protected override Lifetime CollectionLifetime => Lifetime.Transient;
}
}

View File

@@ -1,53 +1,72 @@
using System;
using System.Linq;
using AutoMapper;
using Umbraco.Core.Configuration;
using Umbraco.Core.Mapping;
using Umbraco.Core.Models.Membership;
using Umbraco.Core.Security;
using Umbraco.Core.Services;
namespace Umbraco.Core.Models.Identity
{
public class IdentityMapperProfile : Profile
public class IdentityMapper : IMapperProfile
{
public IdentityMapperProfile(ILocalizedTextService textService, IEntityService entityService, IGlobalSettings globalSettings)
private readonly ILocalizedTextService _textService;
private readonly IEntityService _entityService;
private readonly IGlobalSettings _globalSettings;
public IdentityMapper(ILocalizedTextService textService, IEntityService entityService, IGlobalSettings globalSettings)
{
CreateMap<IUser, BackOfficeIdentityUser>()
.BeforeMap((src, dest) =>
{
dest.DisableChangeTracking();
})
.ConstructUsing(src => new BackOfficeIdentityUser(src.Id, src.Groups))
.ForMember(dest => dest.LastLoginDateUtc, opt => opt.MapFrom(src => src.LastLoginDate.ToUniversalTime()))
.ForMember(user => user.LastPasswordChangeDateUtc, expression => expression.MapFrom(user => user.LastPasswordChangeDate.ToUniversalTime()))
.ForMember(dest => dest.Email, opt => opt.MapFrom(src => src.Email))
.ForMember(dest => dest.EmailConfirmed, opt => opt.MapFrom(src => src.EmailConfirmedDate.HasValue))
.ForMember(dest => dest.Id, opt => opt.MapFrom(src => src.Id))
.ForMember(dest => dest.LockoutEndDateUtc, opt => opt.MapFrom(src => src.IsLockedOut ? DateTime.MaxValue.ToUniversalTime() : (DateTime?) null))
.ForMember(dest => dest.IsApproved, opt => opt.MapFrom(src => src.IsApproved))
.ForMember(dest => dest.UserName, opt => opt.MapFrom(src => src.Username))
.ForMember(dest => dest.PasswordHash, opt => opt.MapFrom(user => GetPasswordHash(user.RawPasswordValue)))
.ForMember(dest => dest.Culture, opt => opt.MapFrom(src => src.GetUserCulture(textService, globalSettings)))
.ForMember(dest => dest.Name, opt => opt.MapFrom(src => src.Name))
.ForMember(dest => dest.StartMediaIds, opt => opt.MapFrom(src => src.StartMediaIds))
.ForMember(dest => dest.StartContentIds, opt => opt.MapFrom(src => src.StartContentIds))
.ForMember(dest => dest.AccessFailedCount, opt => opt.MapFrom(src => src.FailedPasswordAttempts))
.ForMember(dest => dest.CalculatedContentStartNodeIds, opt => opt.MapFrom(src => src.CalculateContentStartNodeIds(entityService)))
.ForMember(dest => dest.CalculatedMediaStartNodeIds, opt => opt.MapFrom(src => src.CalculateMediaStartNodeIds(entityService)))
.ForMember(dest => dest.AllowedSections, opt => opt.MapFrom(src => src.AllowedSections.ToArray()))
.ForMember(dest => dest.LockoutEnabled, opt => opt.Ignore())
.ForMember(dest => dest.Logins, opt => opt.Ignore())
.ForMember(dest => dest.EmailConfirmed, opt => opt.Ignore())
.ForMember(dest => dest.PhoneNumber, opt => opt.Ignore())
.ForMember(dest => dest.PhoneNumberConfirmed, opt => opt.Ignore())
.ForMember(dest => dest.TwoFactorEnabled, opt => opt.Ignore())
.ForMember(dest => dest.Roles, opt => opt.Ignore())
.ForMember(dest => dest.Claims, opt => opt.Ignore())
.AfterMap((src, dest) =>
{
dest.ResetDirtyProperties(true);
dest.EnableChangeTracking();
});
_textService = textService;
_entityService = entityService;
_globalSettings = globalSettings;
}
public void SetMaps(Mapper mapper)
{
mapper.SetMap<IUser, BackOfficeIdentityUser>(Map);
}
public BackOfficeIdentityUser Map(IUser source)
{
// AssignAll enable
var target = new BackOfficeIdentityUser(source.Id, source.Groups)
{
// ignored
//Groups = ,
//LockoutEnabled = ,
//PhoneNumber = ,
//PhoneNumberConfirmed = ,
//TwoFactorEnabled = ,
Id = source.Id, // also in ctor but required, BackOfficeIdentityUser is weird
CalculatedMediaStartNodeIds = source.CalculateMediaStartNodeIds(_entityService),
CalculatedContentStartNodeIds = source.CalculateContentStartNodeIds(_entityService),
Email = source.Email,
UserName = source.Username,
LastPasswordChangeDateUtc = source.LastPasswordChangeDate.ToUniversalTime(),
LastLoginDateUtc = source.LastLoginDate.ToUniversalTime(),
EmailConfirmed = source.EmailConfirmedDate.HasValue,
Name = source.Name,
AccessFailedCount = source.FailedPasswordAttempts,
PasswordHash = GetPasswordHash(source.RawPasswordValue),
StartContentIds = source.StartContentIds,
StartMediaIds = source.StartMediaIds,
Culture = source.GetUserCulture(_textService, _globalSettings).ToString(), // project CultureInfo to string
IsApproved = source.IsApproved,
SecurityStamp = source.SecurityStamp,
LockoutEndDateUtc = source.IsLockedOut ? DateTime.MaxValue.ToUniversalTime() : (DateTime?)null,
// this was in AutoMapper but does not have a setter anyways
//AllowedSections = source.AllowedSections.ToArray(),
// these were marked as ignored for AutoMapper but don't have a setter anyways
//Logins =,
//Claims =,
//Roles =,
};
target.ResetDirtyProperties(true);
target.EnableChangeTracking(); // fixme but how can we disable it?
return target;
}
private static string GetPasswordHash(string storedPass)

View File

@@ -10,11 +10,13 @@ using Microsoft.AspNet.Identity;
using Microsoft.Owin;
using Umbraco.Core.Configuration;
using Umbraco.Core.Exceptions;
using Umbraco.Core.Mapping;
using Umbraco.Core.Models;
using Umbraco.Core.Models.Identity;
using Umbraco.Core.Models.Membership;
using Umbraco.Core.Services;
using IUser = Umbraco.Core.Models.Membership.IUser;
using Mapper = Umbraco.Core.Mapping.Mapper;
using Task = System.Threading.Tasks.Task;
namespace Umbraco.Core.Security
@@ -185,7 +187,10 @@ namespace Umbraco.Core.Security
{
return null;
}
return await Task.FromResult(AssignLoginsCallback(Mapper.Map<BackOfficeIdentityUser>(user)));
var m = Composing.Current.Factory.GetInstance<Mapper>();
//return await Task.FromResult(AssignLoginsCallback(Mapper.Map<BackOfficeIdentityUser>(user)));
return await Task.FromResult(AssignLoginsCallback(m.Map<BackOfficeIdentityUser>(user)));
}
/// <summary>
@@ -202,7 +207,9 @@ namespace Umbraco.Core.Security
return null;
}
var result = AssignLoginsCallback(Mapper.Map<BackOfficeIdentityUser>(user));
var m = Composing.Current.Factory.GetInstance<Mapper>();
//var result = AssignLoginsCallback(Mapper.Map<BackOfficeIdentityUser>(user));
var result = AssignLoginsCallback(m.Map<BackOfficeIdentityUser>(user));
return await Task.FromResult(result);
}
@@ -311,10 +318,12 @@ namespace Umbraco.Core.Security
public Task<BackOfficeIdentityUser> FindByEmailAsync(string email)
{
ThrowIfDisposed();
var m = Composing.Current.Factory.GetInstance<Mapper>();
var user = _userService.GetByEmail(email);
var result = user == null
? null
: Mapper.Map<BackOfficeIdentityUser>(user);
//: Mapper.Map<BackOfficeIdentityUser>(user);
: m.Map<BackOfficeIdentityUser>(user);
return Task.FromResult(AssignLoginsCallback(result));
}
@@ -391,7 +400,9 @@ namespace Umbraco.Core.Security
var user = _userService.GetUserById(l.UserId);
if (user != null)
{
output = Mapper.Map<BackOfficeIdentityUser>(user);
var m = Composing.Current.Factory.GetInstance<Mapper>();
//output = Mapper.Map<BackOfficeIdentityUser>(user);
output = m.Map<BackOfficeIdentityUser>(user);
break;
}
}

View File

@@ -55,6 +55,11 @@
</ItemGroup>
<ItemGroup>
<!-- note: NuGet deals with transitive references now -->
<PackageReference Include="AssignAll">
<Version>1.0.7017.28472</Version>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="AutoMapper" Version="8.0.0" />
<PackageReference Include="LightInject" Version="5.4.0" />
<PackageReference Include="LightInject.Annotation" Version="1.1.0" />
@@ -209,6 +214,10 @@
<Compile Include="IO\MediaPathSchemes\UniqueMediaPathScheme.cs" />
<Compile Include="Migrations\Upgrade\V_8_0_0\MergeDateAndDateTimePropertyEditor.cs" />
<Compile Include="Migrations\Upgrade\V_8_0_1\ChangeNuCacheJsonFormat.cs" />
<Compile Include="Mapping\MapperProfileCollection.cs" />
<Compile Include="Mapping\MapperProfileCollectionBuilder.cs" />
<Compile Include="Mapping\IMapperProfile.cs" />
<Compile Include="Mapping\Mapper.cs" />
<Compile Include="Models\PublishedContent\ILivePublishedModelFactory.cs" />
<Compile Include="PropertyEditors\DateTimeConfiguration.cs" />
<Compile Include="Migrations\Upgrade\V_8_0_0\RenameLabelAndRichTextPropertyEditorAliases.cs" />

View File

@@ -8,10 +8,12 @@ using AutoMapper;
using Umbraco.Core;
using Umbraco.Core.Composing;
using Umbraco.Core.Configuration;
using Umbraco.Core.Mapping;
using Umbraco.Core.Models.Identity;
using Umbraco.Core.Models.Membership;
using Umbraco.Core.Security;
using Umbraco.Web.Security;
using Mapper = Umbraco.Core.Mapping.Mapper;
using UserExtensions = Umbraco.Core.Models.UserExtensions;
namespace Umbraco.Web.WebApi.Filters
@@ -112,7 +114,9 @@ namespace Umbraco.Web.WebApi.Filters
{
var signInManager = owinCtx.Result.GetBackOfficeSignInManager();
var backOfficeIdentityUser = Mapper.Map<BackOfficeIdentityUser>(user);
var m = Composing.Current.Factory.GetInstance<Mapper>();
//var backOfficeIdentityUser = Mapper.Map<BackOfficeIdentityUser>(user);
var backOfficeIdentityUser = m.Map<BackOfficeIdentityUser>(user);
await signInManager.SignInAsync(backOfficeIdentityUser, isPersistent: true, rememberBrowser: false);
//ensure the remainder of the request has the correct principal set