From b43e113cb6fea5bc52e94752d1ba19cc821fc69d Mon Sep 17 00:00:00 2001 From: Stephan Date: Tue, 19 Mar 2019 16:27:08 +0100 Subject: [PATCH] Experiment with mapping (perfs) --- .../CoreMappingProfiles.cs | 6 +- src/Umbraco.Core/Mapping/IMapperProfile.cs | 7 ++ src/Umbraco.Core/Mapping/Mapper.cs | 50 +++++++++ .../Mapping/MapperProfileCollection.cs | 12 +++ .../Mapping/MapperProfileCollectionBuilder.cs | 11 ++ .../Models/Identity/IdentityMapperProfile.cs | 101 +++++++++++------- .../Security/BackOfficeUserStore.cs | 19 +++- src/Umbraco.Core/Umbraco.Core.csproj | 9 ++ .../CheckIfUserTicketDataIsStaleAttribute.cs | 6 +- 9 files changed, 173 insertions(+), 48 deletions(-) create mode 100644 src/Umbraco.Core/Mapping/IMapperProfile.cs create mode 100644 src/Umbraco.Core/Mapping/Mapper.cs create mode 100644 src/Umbraco.Core/Mapping/MapperProfileCollection.cs create mode 100644 src/Umbraco.Core/Mapping/MapperProfileCollectionBuilder.cs diff --git a/src/Umbraco.Core/Composing/CompositionExtensions/CoreMappingProfiles.cs b/src/Umbraco.Core/Composing/CompositionExtensions/CoreMappingProfiles.cs index ed875ec973..0df7571ad9 100644 --- a/src/Umbraco.Core/Composing/CompositionExtensions/CoreMappingProfiles.cs +++ b/src/Umbraco.Core/Composing/CompositionExtensions/CoreMappingProfiles.cs @@ -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(); + composition.RegisterUnique(); + composition.WithCollectionBuilder() + .Append(); return composition; } } diff --git a/src/Umbraco.Core/Mapping/IMapperProfile.cs b/src/Umbraco.Core/Mapping/IMapperProfile.cs new file mode 100644 index 0000000000..90aedbe9fd --- /dev/null +++ b/src/Umbraco.Core/Mapping/IMapperProfile.cs @@ -0,0 +1,7 @@ +namespace Umbraco.Core.Mapping +{ + public interface IMapperProfile + { + void SetMaps(Mapper mapper); + } +} diff --git a/src/Umbraco.Core/Mapping/Mapper.cs b/src/Umbraco.Core/Mapping/Mapper.cs new file mode 100644 index 0000000000..6d6637cf15 --- /dev/null +++ b/src/Umbraco.Core/Mapping/Mapper.cs @@ -0,0 +1,50 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Umbraco.Core.Mapping +{ + public class Mapper + { + private readonly Dictionary>> _maps = new Dictionary>>(); + + public Mapper(MapperProfileCollection profiles) + { + foreach (var profile in profiles) + profile.SetMaps(this); + } + + public void SetMap(Func map) + { + var sourceType = typeof(TSource); + var targetType = typeof(TTarget); + + if (!_maps.TryGetValue(sourceType, out var sourceMap)) + sourceMap = _maps[sourceType] = new Dictionary>(); + + sourceMap[targetType] = o => map((TSource)o); + } + + public TTarget Map(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); + } + } +} diff --git a/src/Umbraco.Core/Mapping/MapperProfileCollection.cs b/src/Umbraco.Core/Mapping/MapperProfileCollection.cs new file mode 100644 index 0000000000..692f96fcc7 --- /dev/null +++ b/src/Umbraco.Core/Mapping/MapperProfileCollection.cs @@ -0,0 +1,12 @@ +using System.Collections.Generic; +using Umbraco.Core.Composing; + +namespace Umbraco.Core.Mapping +{ + public class MapperProfileCollection : BuilderCollectionBase + { + public MapperProfileCollection(IEnumerable items) + : base(items) + { } + } +} diff --git a/src/Umbraco.Core/Mapping/MapperProfileCollectionBuilder.cs b/src/Umbraco.Core/Mapping/MapperProfileCollectionBuilder.cs new file mode 100644 index 0000000000..fc4cee8300 --- /dev/null +++ b/src/Umbraco.Core/Mapping/MapperProfileCollectionBuilder.cs @@ -0,0 +1,11 @@ +using Umbraco.Core.Composing; + +namespace Umbraco.Core.Mapping +{ + public class MapperProfileCollectionBuilder : OrderedCollectionBuilderBase + { + protected override MapperProfileCollectionBuilder This => this; + + protected override Lifetime CollectionLifetime => Lifetime.Transient; + } +} diff --git a/src/Umbraco.Core/Models/Identity/IdentityMapperProfile.cs b/src/Umbraco.Core/Models/Identity/IdentityMapperProfile.cs index 81069bd74c..371331bc49 100644 --- a/src/Umbraco.Core/Models/Identity/IdentityMapperProfile.cs +++ b/src/Umbraco.Core/Models/Identity/IdentityMapperProfile.cs @@ -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() - .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(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) diff --git a/src/Umbraco.Core/Security/BackOfficeUserStore.cs b/src/Umbraco.Core/Security/BackOfficeUserStore.cs index 3d87482d60..0f186b2299 100644 --- a/src/Umbraco.Core/Security/BackOfficeUserStore.cs +++ b/src/Umbraco.Core/Security/BackOfficeUserStore.cs @@ -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(user))); + + var m = Composing.Current.Factory.GetInstance(); + //return await Task.FromResult(AssignLoginsCallback(Mapper.Map(user))); + return await Task.FromResult(AssignLoginsCallback(m.Map(user))); } /// @@ -202,7 +207,9 @@ namespace Umbraco.Core.Security return null; } - var result = AssignLoginsCallback(Mapper.Map(user)); + var m = Composing.Current.Factory.GetInstance(); + //var result = AssignLoginsCallback(Mapper.Map(user)); + var result = AssignLoginsCallback(m.Map(user)); return await Task.FromResult(result); } @@ -311,10 +318,12 @@ namespace Umbraco.Core.Security public Task FindByEmailAsync(string email) { ThrowIfDisposed(); + var m = Composing.Current.Factory.GetInstance(); var user = _userService.GetByEmail(email); var result = user == null ? null - : Mapper.Map(user); + //: Mapper.Map(user); + : m.Map(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(user); + var m = Composing.Current.Factory.GetInstance(); + //output = Mapper.Map(user); + output = m.Map(user); break; } } diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index fa046acd63..2c846c8c67 100755 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -55,6 +55,11 @@ + + 1.0.7017.28472 + runtime; build; native; contentfiles; analyzers + all + @@ -209,6 +214,10 @@ + + + + diff --git a/src/Umbraco.Web/WebApi/Filters/CheckIfUserTicketDataIsStaleAttribute.cs b/src/Umbraco.Web/WebApi/Filters/CheckIfUserTicketDataIsStaleAttribute.cs index 84481868e4..3cfbd5a314 100644 --- a/src/Umbraco.Web/WebApi/Filters/CheckIfUserTicketDataIsStaleAttribute.cs +++ b/src/Umbraco.Web/WebApi/Filters/CheckIfUserTicketDataIsStaleAttribute.cs @@ -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(user); + var m = Composing.Current.Factory.GetInstance(); + //var backOfficeIdentityUser = Mapper.Map(user); + var backOfficeIdentityUser = m.Map(user); await signInManager.SignInAsync(backOfficeIdentityUser, isPersistent: true, rememberBrowser: false); //ensure the remainder of the request has the correct principal set