From 1f8d4b1db7226c2ced9e944f0e4648773e35c77e Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Sat, 15 Feb 2020 09:20:59 +0100 Subject: [PATCH 01/73] Moved implementation of check for existance of username on creating user from user service to repository. --- .../Persistence/Repositories/IUserRepository.cs | 16 ++++++++++++++++ .../Repositories/Implement/UserRepository.cs | 15 +++++++++++++++ .../Services/Implement/UserService.cs | 4 ++-- 3 files changed, 33 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Abstractions/Persistence/Repositories/IUserRepository.cs b/src/Umbraco.Abstractions/Persistence/Repositories/IUserRepository.cs index 29ae581459..0d1a8764ae 100644 --- a/src/Umbraco.Abstractions/Persistence/Repositories/IUserRepository.cs +++ b/src/Umbraco.Abstractions/Persistence/Repositories/IUserRepository.cs @@ -20,8 +20,24 @@ namespace Umbraco.Core.Persistence.Repositories /// /// /// + [Obsolete("This method will be removed in future versions. Please use ExistsByUserName instead.")] bool Exists(string username); + /// + /// Checks if a user with the username exists + /// + /// + /// + bool ExistsByUserName(string username); + + + /// + /// Checks if a user with the login exists + /// + /// + /// + bool ExistsByLogin(string login); + /// /// Gets a list of objects associated with a given group /// diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/UserRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/UserRepository.cs index f3db5c5906..f35e820042 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/UserRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/UserRepository.cs @@ -626,6 +626,11 @@ ORDER BY colName"; } public bool Exists(string username) + { + return ExistsByUserName(username); + } + + public bool ExistsByUserName(string username) { var sql = SqlContext.Sql() .SelectCount() @@ -635,6 +640,16 @@ ORDER BY colName"; return Database.ExecuteScalar(sql) > 0; } + public bool ExistsByLogin(string login) + { + var sql = SqlContext.Sql() + .SelectCount() + .From() + .Where(x => x.Login == login); + + return Database.ExecuteScalar(sql) > 0; + } + /// /// Gets a list of objects associated with a given group /// diff --git a/src/Umbraco.Infrastructure/Services/Implement/UserService.cs b/src/Umbraco.Infrastructure/Services/Implement/UserService.cs index f09e917e94..4dbc87dd4f 100644 --- a/src/Umbraco.Infrastructure/Services/Implement/UserService.cs +++ b/src/Umbraco.Infrastructure/Services/Implement/UserService.cs @@ -109,9 +109,9 @@ namespace Umbraco.Core.Services.Implement User user; using (var scope = ScopeProvider.CreateScope()) { - var loginExists = scope.Database.ExecuteScalar("SELECT COUNT(id) FROM umbracoUser WHERE userLogin = @Login", new { Login = username }) != 0; + var loginExists = _userRepository.ExistsByLogin(username); if (loginExists) - throw new ArgumentException("Login already exists"); // causes rollback // causes rollback + throw new ArgumentException("Login already exists"); // causes rollback user = new User(_globalSettings) { From 0b49c2d84e812adf9fd3a0092d38014df3d74204 Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Sat, 15 Feb 2020 09:24:13 +0100 Subject: [PATCH 02/73] Removed a few duplicated comments. --- src/Umbraco.Infrastructure/Services/Implement/MemberService.cs | 2 -- .../Services/Implement/PublicAccessService.cs | 1 - .../Services/Implement/ServerRegistrationService.cs | 1 - src/Umbraco.Infrastructure/Services/Implement/UserService.cs | 2 -- 4 files changed, 6 deletions(-) diff --git a/src/Umbraco.Infrastructure/Services/Implement/MemberService.cs b/src/Umbraco.Infrastructure/Services/Implement/MemberService.cs index 4876772c86..fc0dd8a554 100644 --- a/src/Umbraco.Infrastructure/Services/Implement/MemberService.cs +++ b/src/Umbraco.Infrastructure/Services/Implement/MemberService.cs @@ -756,7 +756,6 @@ namespace Umbraco.Core.Services.Implement throw new ArgumentOutOfRangeException(nameof(matchType)); // causes rollback // causes rollback } - // TODO: Since this is by property value, we need a GetByPropertyQuery on the repo! // TODO: Since this is by property value, we need a GetByPropertyQuery on the repo! return _memberRepository.Get(query); } @@ -1196,7 +1195,6 @@ namespace Umbraco.Core.Services.Implement { scope.WriteLock(Constants.Locks.MemberTree); - // TODO: What about content that has the contenttype as part of its composition? // TODO: What about content that has the contenttype as part of its composition? var query = Query().Where(x => x.ContentTypeId == memberTypeId); diff --git a/src/Umbraco.Infrastructure/Services/Implement/PublicAccessService.cs b/src/Umbraco.Infrastructure/Services/Implement/PublicAccessService.cs index ab9ea64292..f2af6cf424 100644 --- a/src/Umbraco.Infrastructure/Services/Implement/PublicAccessService.cs +++ b/src/Umbraco.Infrastructure/Services/Implement/PublicAccessService.cs @@ -124,7 +124,6 @@ namespace Umbraco.Core.Services.Implement } else { - //If they are both the same already then there's nothing to update, exit //If they are both the same already then there's nothing to update, exit return OperationResult.Attempt.Succeed(evtMsgs, entry); } diff --git a/src/Umbraco.Infrastructure/Services/Implement/ServerRegistrationService.cs b/src/Umbraco.Infrastructure/Services/Implement/ServerRegistrationService.cs index 97bf76e672..6a092b159f 100644 --- a/src/Umbraco.Infrastructure/Services/Implement/ServerRegistrationService.cs +++ b/src/Umbraco.Infrastructure/Services/Implement/ServerRegistrationService.cs @@ -72,7 +72,6 @@ namespace Umbraco.Core.Services.Implement _serverRegistrationRepository.Save(server); _serverRegistrationRepository.DeactiveStaleServers(staleTimeout); // triggers a cache reload - // reload - cheap, cached // reload - cheap, cached // default role is single server, but if registrations contain more diff --git a/src/Umbraco.Infrastructure/Services/Implement/UserService.cs b/src/Umbraco.Infrastructure/Services/Implement/UserService.cs index 4dbc87dd4f..7b7c36d2ea 100644 --- a/src/Umbraco.Infrastructure/Services/Implement/UserService.cs +++ b/src/Umbraco.Infrastructure/Services/Implement/UserService.cs @@ -340,7 +340,6 @@ namespace Umbraco.Core.Services.Implement _userRepository.Save(user); - //Now we have to check for backwards compat hacks //Now we have to check for backwards compat hacks var explicitUser = user as User; if (explicitUser != null && explicitUser.GroupsToSave.Count > 0) @@ -358,7 +357,6 @@ namespace Umbraco.Core.Services.Implement scope.Events.Dispatch(SavedUser, this, saveEventArgs); } - //commit the whole lot in one go //commit the whole lot in one go scope.Complete(); } From 40d7baca7909038f809da936289892f70b0e95a0 Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Sat, 15 Feb 2020 11:08:32 +0100 Subject: [PATCH 03/73] Migrated database concerns from KeyValueService to new repository. --- src/Umbraco.Abstractions/Models/IKeyValue.cs | 14 ++ src/Umbraco.Abstractions/Models/KeyValue.cs | 39 +++ .../Repositories/IKeyValueRepository.cs | 13 + .../Migrations/Install/DatabaseDataCreator.cs | 2 +- .../Persistence/Dtos/KeyValueDto.cs | 2 +- .../Implement/KeyValueRepository.cs | 233 ++++++++++++++++++ .../Runtime/SqlMainDomLock.cs | 2 +- src/Umbraco.Infrastructure/RuntimeState.cs | 4 +- .../Services/Implement/KeyValueService.cs | 125 +--------- 9 files changed, 316 insertions(+), 118 deletions(-) create mode 100644 src/Umbraco.Abstractions/Models/IKeyValue.cs create mode 100644 src/Umbraco.Abstractions/Models/KeyValue.cs create mode 100644 src/Umbraco.Abstractions/Persistence/Repositories/IKeyValueRepository.cs create mode 100644 src/Umbraco.Infrastructure/Persistence/Repositories/Implement/KeyValueRepository.cs diff --git a/src/Umbraco.Abstractions/Models/IKeyValue.cs b/src/Umbraco.Abstractions/Models/IKeyValue.cs new file mode 100644 index 0000000000..7396ae1b68 --- /dev/null +++ b/src/Umbraco.Abstractions/Models/IKeyValue.cs @@ -0,0 +1,14 @@ +using System; +using Umbraco.Core.Models.Entities; + +namespace Umbraco.Core.Models +{ + public interface IKeyValue : IEntity + { + string Identifier { get; set; } + + string Value { get; set; } + + DateTime UpdateDate { get; set; } + } +} diff --git a/src/Umbraco.Abstractions/Models/KeyValue.cs b/src/Umbraco.Abstractions/Models/KeyValue.cs new file mode 100644 index 0000000000..4ad906d91e --- /dev/null +++ b/src/Umbraco.Abstractions/Models/KeyValue.cs @@ -0,0 +1,39 @@ +using System; +using System.Runtime.Serialization; +using Umbraco.Core.Models.Entities; + +namespace Umbraco.Core.Models +{ + /// + /// Implements . + /// + [Serializable] + [DataContract(IsReference = true)] + public class KeyValue : EntityBase, IKeyValue + { + private string _identifier; + private string _value; + private DateTime _updateDate; + + /// + public string Identifier + { + get => _identifier; + set => SetPropertyValueAndDetectChanges(value, ref _identifier, nameof(Identifier)); + } + + /// + public string Value + { + get => _value; + set => SetPropertyValueAndDetectChanges(value, ref _value, nameof(Value)); + } + + /// + public DateTime UpdateDate + { + get => _updateDate; + set => SetPropertyValueAndDetectChanges(value, ref _updateDate, nameof(UpdateDate)); + } + } +} diff --git a/src/Umbraco.Abstractions/Persistence/Repositories/IKeyValueRepository.cs b/src/Umbraco.Abstractions/Persistence/Repositories/IKeyValueRepository.cs new file mode 100644 index 0000000000..baae2a1724 --- /dev/null +++ b/src/Umbraco.Abstractions/Persistence/Repositories/IKeyValueRepository.cs @@ -0,0 +1,13 @@ +namespace Umbraco.Infrastructure.Persistence.Repositories +{ + public interface IKeyValueRepository + { + void Initialize(); + + string GetValue(string key); + + void SetValue(string key, string value); + + bool TrySetValue(string key, string originalValue, string newValue); + } +} diff --git a/src/Umbraco.Infrastructure/Migrations/Install/DatabaseDataCreator.cs b/src/Umbraco.Infrastructure/Migrations/Install/DatabaseDataCreator.cs index da6574670b..b4328c973d 100644 --- a/src/Umbraco.Infrastructure/Migrations/Install/DatabaseDataCreator.cs +++ b/src/Umbraco.Infrastructure/Migrations/Install/DatabaseDataCreator.cs @@ -344,7 +344,7 @@ namespace Umbraco.Core.Migrations.Install var stateValueKey = upgrader.StateValueKey; var finalState = upgrader.Plan.FinalState; - _database.Insert(Constants.DatabaseSchema.Tables.KeyValue, "key", false, new KeyValueDto { Key = stateValueKey, Value = finalState, Updated = DateTime.Now }); + _database.Insert(Constants.DatabaseSchema.Tables.KeyValue, "key", false, new KeyValueDto { Key = stateValueKey, Value = finalState, UpdateDate = DateTime.Now }); } } } diff --git a/src/Umbraco.Infrastructure/Persistence/Dtos/KeyValueDto.cs b/src/Umbraco.Infrastructure/Persistence/Dtos/KeyValueDto.cs index b74039c388..5ead6d0d26 100644 --- a/src/Umbraco.Infrastructure/Persistence/Dtos/KeyValueDto.cs +++ b/src/Umbraco.Infrastructure/Persistence/Dtos/KeyValueDto.cs @@ -21,6 +21,6 @@ namespace Umbraco.Core.Persistence.Dtos [Column("updated")] [Constraint(Default = SystemMethods.CurrentDateTime)] - public DateTime Updated { get; set; } + public DateTime UpdateDate { get; set; } } } diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/KeyValueRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/KeyValueRepository.cs new file mode 100644 index 0000000000..ebe101bbee --- /dev/null +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/KeyValueRepository.cs @@ -0,0 +1,233 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using NPoco; +using Umbraco.Core.Cache; +using Umbraco.Core.Logging; +using Umbraco.Core.Migrations; +using Umbraco.Core.Models; +using Umbraco.Core.Persistence.Dtos; +using Umbraco.Core.Persistence.Querying; +using Umbraco.Core.Scoping; +using Umbraco.Infrastructure.Persistence.Repositories; + +namespace Umbraco.Core.Persistence.Repositories.Implement +{ + internal class KeyValueRepository : NPocoRepositoryBase, IKeyValueRepository + { + public KeyValueRepository(IScopeAccessor scopeAccessor, ILogger logger) + : base(scopeAccessor, AppCaches.NoCache, logger) + { } + + public void Initialize() + { + var context = new MigrationContext(Database, Logger); + var initMigration = new InitialMigration(context); + initMigration.Migrate(); + } + + public string GetValue(string key) + { + return GetDtoByKey(key)?.Value; + } + + public void SetValue(string key, string value) + { + var dto = GetDtoByKey(key); + if (dto == null) + { + dto = new KeyValueDto + { + Key = key, + Value = value, + UpdateDate = DateTime.Now + }; + + Database.Insert(dto); + } + else + { + UpdateDtoValue(dto, value); + } + } + + public bool TrySetValue(string key, string originalValue, string newValue) + { + var dto = GetDtoByKey(key); + + if (dto == null || dto.Value != originalValue) + return false; + + UpdateDtoValue(dto, newValue); + return true; + } + + private void UpdateDtoValue(KeyValueDto dto, string value) + { + dto.Value = value; + dto.UpdateDate = DateTime.Now; + Database.Update(dto); + } + + /// + /// Gets a value directly from the database, no scope, nothing. + /// + /// Used by to determine the runtime state. + internal static string GetValue(IUmbracoDatabase database, string key) + { + if (database is null) return null; + + var sql = database.SqlContext.Sql() + .Select() + .From() + .Where(x => x.Key == key); + return database.FirstOrDefault(sql)?.Value; + } + + #region Overrides of NPocoRepositoryBase + + protected override Guid NodeObjectTypeId => throw new NotImplementedException(); + + protected override Sql GetBaseQuery(bool isCount) + { + var sql = SqlContext.Sql(); + + sql = isCount + ? sql.SelectCount() + : sql.Select(); + + sql + .From(); + + return sql; + } + + protected override string GetBaseWhereClause() + { + return Constants.DatabaseSchema.Tables.KeyValue + ".key = @id"; + } + + protected override IEnumerable GetDeleteClauses() + { + return new List(); + } + + protected override IKeyValue PerformGet(string id) + { + var dto = GetDtoByKey(id); + return dto == null ? null : Map(dto); + } + + private KeyValueDto GetDtoByKey(string key) + { + var sql = GetBaseQuery(false).Where(x => x.Key == key); + return Database.Fetch(sql).FirstOrDefault(); + } + + protected override IEnumerable PerformGetAll(params string[] ids) + { + var sql = GetBaseQuery(false).WhereIn(x => x.Key, ids); + var dtos = Database.Fetch(sql); + return dtos.WhereNotNull().Select(Map); + } + + protected override IEnumerable PerformGetByQuery(IQuery query) + { + throw new NotImplementedException(); + } + + protected override void PersistNewItem(IKeyValue entity) + { + var dto = Map(entity); + Database.Insert(dto); + } + + protected override void PersistUpdatedItem(IKeyValue entity) + { + var dto = Map(entity); + Database.Update(dto); + } + + private static KeyValueDto Map(IKeyValue keyValue) + { + if (keyValue == null) return null; + + return new KeyValueDto + { + Key = keyValue.Identifier, + Value = keyValue.Value, + UpdateDate = keyValue.UpdateDate, + }; + } + + private static IKeyValue Map(KeyValueDto dto) + { + if (dto == null) return null; + + return new KeyValue + { + Identifier = dto.Key, + Value = dto.Value, + UpdateDate = dto.UpdateDate, + }; + } + + #endregion + + + /// + /// A custom migration that executes standalone during the Initialize phase of the KeyValueService. + /// + internal class InitialMigration : MigrationBase + { + public InitialMigration(IMigrationContext context) + : base(context) + { } + + public override void Migrate() + { + // as long as we are still running 7 this migration will be invoked, + // but due to multiple restarts during upgrades, maybe the table + // exists already + if (TableExists(Constants.DatabaseSchema.Tables.KeyValue)) + return; + + Logger.Info("Creating KeyValue structure."); + + // the locks table was initially created with an identity (auto-increment) primary key, + // but we don't want this, especially as we are about to insert a new row into the table, + // so here we drop that identity + DropLockTableIdentity(); + + // insert the lock object for key/value + Insert.IntoTable(Constants.DatabaseSchema.Tables.Lock).Row(new { id = Constants.Locks.KeyValues, name = "KeyValues", value = 1 }).Do(); + + // create the key-value table + Create.Table().Do(); + } + + private void DropLockTableIdentity() + { + // one cannot simply drop an identity, that requires a bit of work + + // create a temp. id column and copy values + Alter.Table(Constants.DatabaseSchema.Tables.Lock).AddColumn("nid").AsInt32().Nullable().Do(); + Execute.Sql("update umbracoLock set nid = id").Do(); + + // drop the id column entirely (cannot just drop identity) + Delete.PrimaryKey("PK_umbracoLock").FromTable(Constants.DatabaseSchema.Tables.Lock).Do(); + Delete.Column("id").FromTable(Constants.DatabaseSchema.Tables.Lock).Do(); + + // recreate the id column without identity and copy values + Alter.Table(Constants.DatabaseSchema.Tables.Lock).AddColumn("id").AsInt32().Nullable().Do(); + Execute.Sql("update umbracoLock set id = nid").Do(); + + // drop the temp. id column + Delete.Column("nid").FromTable(Constants.DatabaseSchema.Tables.Lock).Do(); + + // complete the primary key + Alter.Table(Constants.DatabaseSchema.Tables.Lock).AlterColumn("id").AsInt32().NotNullable().PrimaryKey("PK_umbracoLock").Do(); + } + } + } +} diff --git a/src/Umbraco.Infrastructure/Runtime/SqlMainDomLock.cs b/src/Umbraco.Infrastructure/Runtime/SqlMainDomLock.cs index 4e1feb221a..d8a66b4198 100644 --- a/src/Umbraco.Infrastructure/Runtime/SqlMainDomLock.cs +++ b/src/Umbraco.Infrastructure/Runtime/SqlMainDomLock.cs @@ -324,7 +324,7 @@ namespace Umbraco.Core.Runtime { Key = MainDomKey, Value = id, - Updated = DateTime.Now + UpdateDate = DateTime.Now }); } diff --git a/src/Umbraco.Infrastructure/RuntimeState.cs b/src/Umbraco.Infrastructure/RuntimeState.cs index 5952e73e62..b6ffecbb0d 100644 --- a/src/Umbraco.Infrastructure/RuntimeState.cs +++ b/src/Umbraco.Infrastructure/RuntimeState.cs @@ -9,7 +9,7 @@ using Umbraco.Core.Hosting; using Umbraco.Core.Logging; using Umbraco.Core.Migrations.Upgrade; using Umbraco.Core.Persistence; -using Umbraco.Core.Services.Implement; +using Umbraco.Core.Persistence.Repositories.Implement; using Umbraco.Core.Sync; namespace Umbraco.Core @@ -263,7 +263,7 @@ namespace Umbraco.Core // no scope, no service - just directly accessing the database using (var database = databaseFactory.CreateDatabase()) { - CurrentMigrationState = KeyValueService.GetValue(database, stateValueKey); + CurrentMigrationState = KeyValueRepository.GetValue(database, stateValueKey); FinalMigrationState = upgrader.Plan.FinalState; } diff --git a/src/Umbraco.Infrastructure/Services/Implement/KeyValueService.cs b/src/Umbraco.Infrastructure/Services/Implement/KeyValueService.cs index d5d8e66525..bdb65091de 100644 --- a/src/Umbraco.Infrastructure/Services/Implement/KeyValueService.cs +++ b/src/Umbraco.Infrastructure/Services/Implement/KeyValueService.cs @@ -1,12 +1,8 @@ using System; -using System.Linq; -using Umbraco.Core.Composing; using Umbraco.Core.Configuration; using Umbraco.Core.Logging; -using Umbraco.Core.Migrations; using Umbraco.Core.Scoping; -using Umbraco.Core.Persistence; -using Umbraco.Core.Persistence.Dtos; +using Umbraco.Infrastructure.Persistence.Repositories; namespace Umbraco.Core.Services.Implement { @@ -14,13 +10,15 @@ namespace Umbraco.Core.Services.Implement { private readonly object _initialock = new object(); private readonly IScopeProvider _scopeProvider; + private readonly IKeyValueRepository _repository; private readonly ILogger _logger; private readonly IUmbracoVersion _umbracoVersion; private bool _initialized; - public KeyValueService(IScopeProvider scopeProvider, ILogger logger, IUmbracoVersion umbracoVersion) + public KeyValueService(IScopeProvider scopeProvider, IKeyValueRepository repository, ILogger logger, IUmbracoVersion umbracoVersion) { _scopeProvider = scopeProvider; + _repository = repository; _logger = logger; _umbracoVersion = umbracoVersion; } @@ -53,71 +51,14 @@ namespace Umbraco.Core.Services.Implement using (var scope = _scopeProvider.CreateScope()) { - var context = new MigrationContext(scope.Database, _logger); - var initMigration = new InitializeMigration(context); - initMigration.Migrate(); + _repository.Initialize(); scope.Complete(); } // but don't assume we are initializing // we are upgrading from v7 and if anything goes wrong, // the table and everything will be rolled back - } - - /// - /// A custom migration that executes standalone during the Initialize phase of this service. - /// - internal class InitializeMigration : MigrationBase - { - public InitializeMigration(IMigrationContext context) - : base(context) - { } - - public override void Migrate() - { - // as long as we are still running 7 this migration will be invoked, - // but due to multiple restarts during upgrades, maybe the table - // exists already - if (TableExists(Constants.DatabaseSchema.Tables.KeyValue)) - return; - - Logger.Info("Creating KeyValue structure."); - - // the locks table was initially created with an identity (auto-increment) primary key, - // but we don't want this, especially as we are about to insert a new row into the table, - // so here we drop that identity - DropLockTableIdentity(); - - // insert the lock object for key/value - Insert.IntoTable(Constants.DatabaseSchema.Tables.Lock).Row(new {id = Constants.Locks.KeyValues, name = "KeyValues", value = 1}).Do(); - - // create the key-value table - Create.Table().Do(); - } - - private void DropLockTableIdentity() - { - // one cannot simply drop an identity, that requires a bit of work - - // create a temp. id column and copy values - Alter.Table(Constants.DatabaseSchema.Tables.Lock).AddColumn("nid").AsInt32().Nullable().Do(); - Execute.Sql("update umbracoLock set nid = id").Do(); - - // drop the id column entirely (cannot just drop identity) - Delete.PrimaryKey("PK_umbracoLock").FromTable(Constants.DatabaseSchema.Tables.Lock).Do(); - Delete.Column("id").FromTable(Constants.DatabaseSchema.Tables.Lock).Do(); - - // recreate the id column without identity and copy values - Alter.Table(Constants.DatabaseSchema.Tables.Lock).AddColumn("id").AsInt32().Nullable().Do(); - Execute.Sql("update umbracoLock set id = nid").Do(); - - // drop the temp. id column - Delete.Column("nid").FromTable(Constants.DatabaseSchema.Tables.Lock).Do(); - - // complete the primary key - Alter.Table(Constants.DatabaseSchema.Tables.Lock).AlterColumn("id").AsInt32().NotNullable().PrimaryKey("PK_umbracoLock").Do(); - } - } + } /// public string GetValue(string key) @@ -126,10 +67,7 @@ namespace Umbraco.Core.Services.Implement using (var scope = _scopeProvider.CreateScope()) { - var sql = scope.SqlContext.Sql().Select().From().Where(x => x.Key == key); - var dto = scope.Database.Fetch(sql).FirstOrDefault(); - scope.Complete(); - return dto?.Value; + return _repository.GetValue(key); } } @@ -142,26 +80,7 @@ namespace Umbraco.Core.Services.Implement { scope.WriteLock(Constants.Locks.KeyValues); - var sql = scope.SqlContext.Sql().Select().From().Where(x => x.Key == key); - var dto = scope.Database.Fetch(sql).FirstOrDefault(); - - if (dto == null) - { - dto = new KeyValueDto - { - Key = key, - Value = value, - Updated = DateTime.Now - }; - - scope.Database.Insert(dto); - } - else - { - dto.Value = value; - dto.Updated = DateTime.Now; - scope.Database.Update(dto); - } + _repository.SetValue(key, value); scope.Complete(); } @@ -175,7 +94,7 @@ namespace Umbraco.Core.Services.Implement } /// - public bool TrySetValue(string key, string originValue, string newValue) + public bool TrySetValue(string key, string originalValue, string newValue) { EnsureInitialized(); @@ -183,35 +102,15 @@ namespace Umbraco.Core.Services.Implement { scope.WriteLock(Constants.Locks.KeyValues); - var sql = scope.SqlContext.Sql().Select().From().Where(x => x.Key == key); - var dto = scope.Database.Fetch(sql).FirstOrDefault(); - - if (dto == null || dto.Value != originValue) + if (!_repository.TrySetValue(key, originalValue, newValue)) + { return false; - - dto.Value = newValue; - dto.Updated = DateTime.Now; - scope.Database.Update(dto); + } scope.Complete(); } return true; } - - /// - /// Gets a value directly from the database, no scope, nothing. - /// - /// Used by to determine the runtime state. - internal static string GetValue(IUmbracoDatabase database, string key) - { - if (database is null) return null; - - var sql = database.SqlContext.Sql() - .Select() - .From() - .Where(x => x.Key == key); - return database.FirstOrDefault(sql)?.Value; - } } } From ac91b9ef5e1e587272613bf033969cfe7b7d7240 Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Sat, 15 Feb 2020 11:10:25 +0100 Subject: [PATCH 04/73] Removed usage of obsoleteted method in UserService. --- src/Umbraco.Infrastructure/Services/Implement/UserService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Infrastructure/Services/Implement/UserService.cs b/src/Umbraco.Infrastructure/Services/Implement/UserService.cs index 7b7c36d2ea..c6cf6c01c3 100644 --- a/src/Umbraco.Infrastructure/Services/Implement/UserService.cs +++ b/src/Umbraco.Infrastructure/Services/Implement/UserService.cs @@ -46,7 +46,7 @@ namespace Umbraco.Core.Services.Implement { using (var scope = ScopeProvider.CreateScope(autoComplete: true)) { - return _userRepository.Exists(username); + return _userRepository.ExistsByUserName(username); } } From f92b43260af235a39e5fce9f3a153cc9b6dca532 Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Sat, 15 Feb 2020 12:17:48 +0100 Subject: [PATCH 05/73] Added unit tests for new KeyValueRepository. --- .../Repositories/KeyValueRepositoryTests.cs | 61 +++++++++++++++++++ src/Umbraco.Tests/Umbraco.Tests.csproj | 1 + 2 files changed, 62 insertions(+) create mode 100644 src/Umbraco.Tests/Persistence/Repositories/KeyValueRepositoryTests.cs diff --git a/src/Umbraco.Tests/Persistence/Repositories/KeyValueRepositoryTests.cs b/src/Umbraco.Tests/Persistence/Repositories/KeyValueRepositoryTests.cs new file mode 100644 index 0000000000..92ebb03a32 --- /dev/null +++ b/src/Umbraco.Tests/Persistence/Repositories/KeyValueRepositoryTests.cs @@ -0,0 +1,61 @@ +using NUnit.Framework; +using Umbraco.Core.Persistence.Repositories.Implement; +using Umbraco.Core.Scoping; +using Umbraco.Infrastructure.Persistence.Repositories; +using Umbraco.Tests.TestHelpers; +using Umbraco.Tests.Testing; + +namespace Umbraco.Tests.Persistence.Repositories +{ + [TestFixture] + [UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest)] + public class KeyValueRepositoryTests : TestWithDatabaseBase + { + [Test] + public void CanSetAndGet() + { + var provider = TestObjects.GetScopeProvider(Logger); + + // Insert new key/value + using (var scope = provider.CreateScope()) + { + var repo = CreateRepository(provider); + repo.SetValue("foo", "bar"); + scope.Complete(); + } + + // Retrieve key/value + using (var scope = provider.CreateScope()) + { + var repo = CreateRepository(provider); + var value = repo.GetValue("foo"); + scope.Complete(); + + Assert.AreEqual("bar", value); + } + + // Update new key/value + using (var scope = provider.CreateScope()) + { + var repo = CreateRepository(provider); + repo.SetValue("foo", "buzz"); + scope.Complete(); + } + + // Retrieve key/value again + using (var scope = provider.CreateScope()) + { + var repo = CreateRepository(provider); + var value = repo.GetValue("foo"); + scope.Complete(); + + Assert.AreEqual("buzz", value); + } + } + + private IKeyValueRepository CreateRepository(IScopeProvider provider) + { + return new KeyValueRepository((IScopeAccessor) provider, Logger); + } + } +} diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index 16e4ca2c11..1527a8d4cc 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -148,6 +148,7 @@ + From 0bba4755339ae186ef194cde9799ff933d629579 Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Mon, 17 Feb 2020 22:12:42 +0100 Subject: [PATCH 06/73] Amends to key value service/repository to better match existing repository patterns. --- src/Umbraco.Abstractions/Models/KeyValue.cs | 7 - .../Repositories/IKeyValueRepository.cs | 13 +- .../Services/ServiceContext.cs | 11 +- .../CompositionExtensions/Repositories.cs | 1 + .../Custom/IKeyValueServiceInitialization.cs | 9 ++ .../Custom/KeyValueServiceInitialization.cs | 80 +++++++++++ .../Implement/KeyValueRepository.cs | 134 +++--------------- .../Implement/RepositoryBaseOfTIdTEntity.cs | 2 +- .../Runtime/CoreInitialComposer.cs | 2 + .../Services/Implement/KeyValueService.cs | 37 ++++- .../Repositories/KeyValueRepositoryTests.cs | 29 ++-- .../Services/KeyValueServiceTests.cs | 93 ++++++++++++ src/Umbraco.Tests/TestHelpers/TestObjects.cs | 3 + src/Umbraco.Tests/Umbraco.Tests.csproj | 1 + 14 files changed, 272 insertions(+), 150 deletions(-) create mode 100644 src/Umbraco.Infrastructure/Migrations/Custom/IKeyValueServiceInitialization.cs create mode 100644 src/Umbraco.Infrastructure/Migrations/Custom/KeyValueServiceInitialization.cs create mode 100644 src/Umbraco.Tests/Services/KeyValueServiceTests.cs diff --git a/src/Umbraco.Abstractions/Models/KeyValue.cs b/src/Umbraco.Abstractions/Models/KeyValue.cs index 4ad906d91e..ba98b577a4 100644 --- a/src/Umbraco.Abstractions/Models/KeyValue.cs +++ b/src/Umbraco.Abstractions/Models/KeyValue.cs @@ -28,12 +28,5 @@ namespace Umbraco.Core.Models get => _value; set => SetPropertyValueAndDetectChanges(value, ref _value, nameof(Value)); } - - /// - public DateTime UpdateDate - { - get => _updateDate; - set => SetPropertyValueAndDetectChanges(value, ref _updateDate, nameof(UpdateDate)); - } } } diff --git a/src/Umbraco.Abstractions/Persistence/Repositories/IKeyValueRepository.cs b/src/Umbraco.Abstractions/Persistence/Repositories/IKeyValueRepository.cs index baae2a1724..0eb2b27eb0 100644 --- a/src/Umbraco.Abstractions/Persistence/Repositories/IKeyValueRepository.cs +++ b/src/Umbraco.Abstractions/Persistence/Repositories/IKeyValueRepository.cs @@ -1,13 +1,8 @@ -namespace Umbraco.Infrastructure.Persistence.Repositories +using Umbraco.Core.Models; + +namespace Umbraco.Core.Persistence.Repositories { - public interface IKeyValueRepository + public interface IKeyValueRepository : IReadRepository, IWriteRepository { - void Initialize(); - - string GetValue(string key); - - void SetValue(string key, string value); - - bool TrySetValue(string key, string originalValue, string newValue); } } diff --git a/src/Umbraco.Abstractions/Services/ServiceContext.cs b/src/Umbraco.Abstractions/Services/ServiceContext.cs index f3c95b07f9..c9bdc6d924 100644 --- a/src/Umbraco.Abstractions/Services/ServiceContext.cs +++ b/src/Umbraco.Abstractions/Services/ServiceContext.cs @@ -32,12 +32,13 @@ namespace Umbraco.Core.Services private readonly Lazy _externalLoginService; private readonly Lazy _redirectUrlService; private readonly Lazy _consentService; + private readonly Lazy _keyValueService; private readonly Lazy _contentTypeBaseServiceProvider; /// /// Initializes a new instance of the class with lazy services. /// - public ServiceContext(Lazy publicAccessService, Lazy domainService, Lazy auditService, Lazy localizedTextService, Lazy tagService, Lazy contentService, Lazy userService, Lazy memberService, Lazy mediaService, Lazy contentTypeService, Lazy mediaTypeService, Lazy dataTypeService, Lazy fileService, Lazy localizationService, Lazy packagingService, Lazy serverRegistrationService, Lazy entityService, Lazy relationService, Lazy macroService, Lazy memberTypeService, Lazy memberGroupService, Lazy notificationService, Lazy externalLoginService, Lazy redirectUrlService, Lazy consentService, Lazy contentTypeBaseServiceProvider) + public ServiceContext(Lazy publicAccessService, Lazy domainService, Lazy auditService, Lazy localizedTextService, Lazy tagService, Lazy contentService, Lazy userService, Lazy memberService, Lazy mediaService, Lazy contentTypeService, Lazy mediaTypeService, Lazy dataTypeService, Lazy fileService, Lazy localizationService, Lazy packagingService, Lazy serverRegistrationService, Lazy entityService, Lazy relationService, Lazy macroService, Lazy memberTypeService, Lazy memberGroupService, Lazy notificationService, Lazy externalLoginService, Lazy redirectUrlService, Lazy consentService, Lazy keyValueService, Lazy contentTypeBaseServiceProvider) { _publicAccessService = publicAccessService; _domainService = domainService; @@ -64,6 +65,7 @@ namespace Umbraco.Core.Services _externalLoginService = externalLoginService; _redirectUrlService = redirectUrlService; _consentService = consentService; + _keyValueService = keyValueService; _contentTypeBaseServiceProvider = contentTypeBaseServiceProvider; } @@ -99,6 +101,7 @@ namespace Umbraco.Core.Services IServerRegistrationService serverRegistrationService = null, IRedirectUrlService redirectUrlService = null, IConsentService consentService = null, + IKeyValueService keyValueService = null, IContentTypeBaseServiceProvider contentTypeBaseServiceProvider = null) { Lazy Lazy(T service) => service == null ? null : new Lazy(() => service); @@ -129,6 +132,7 @@ namespace Umbraco.Core.Services Lazy(externalLoginService), Lazy(redirectUrlService), Lazy(consentService), + Lazy(keyValueService), Lazy(contentTypeBaseServiceProvider) ); } @@ -258,6 +262,11 @@ namespace Umbraco.Core.Services /// public IConsentService ConsentService => _consentService.Value; + /// + /// Gets the KeyValueService. + /// + public IKeyValueService KeyValueService => _keyValueService.Value; + /// /// Gets the ContentTypeServiceBaseFactory. /// diff --git a/src/Umbraco.Infrastructure/Composing/CompositionExtensions/Repositories.cs b/src/Umbraco.Infrastructure/Composing/CompositionExtensions/Repositories.cs index 0939dd0f71..5318c47a40 100644 --- a/src/Umbraco.Infrastructure/Composing/CompositionExtensions/Repositories.cs +++ b/src/Umbraco.Infrastructure/Composing/CompositionExtensions/Repositories.cs @@ -47,6 +47,7 @@ namespace Umbraco.Core.Composing.CompositionExtensions composition.RegisterUnique(); composition.RegisterUnique(); composition.RegisterUnique(); + composition.RegisterUnique(); return composition; } diff --git a/src/Umbraco.Infrastructure/Migrations/Custom/IKeyValueServiceInitialization.cs b/src/Umbraco.Infrastructure/Migrations/Custom/IKeyValueServiceInitialization.cs new file mode 100644 index 0000000000..a863905dc7 --- /dev/null +++ b/src/Umbraco.Infrastructure/Migrations/Custom/IKeyValueServiceInitialization.cs @@ -0,0 +1,9 @@ +using Umbraco.Core.Persistence; + +namespace Umbraco.Infrastructure.Migrations.Custom +{ + public interface IKeyValueServiceInitialization + { + void PerformInitialMigration(IUmbracoDatabase database); + } +} diff --git a/src/Umbraco.Infrastructure/Migrations/Custom/KeyValueServiceInitialization.cs b/src/Umbraco.Infrastructure/Migrations/Custom/KeyValueServiceInitialization.cs new file mode 100644 index 0000000000..488acf507a --- /dev/null +++ b/src/Umbraco.Infrastructure/Migrations/Custom/KeyValueServiceInitialization.cs @@ -0,0 +1,80 @@ +using Umbraco.Core; +using Umbraco.Core.Logging; +using Umbraco.Core.Migrations; +using Umbraco.Core.Persistence; +using Umbraco.Core.Persistence.Dtos; + +namespace Umbraco.Infrastructure.Migrations.Custom +{ + public class KeyValueServiceInitialization : IKeyValueServiceInitialization + { + private readonly ILogger _logger; + + public KeyValueServiceInitialization(ILogger logger) + { + _logger = logger; + } + + public void PerformInitialMigration(IUmbracoDatabase database) + { + var context = new MigrationContext(database, _logger); + var initMigration = new InitialMigration(context); + initMigration.Migrate(); + } + + /// + /// A custom migration that executes standalone during the Initialize phase of the KeyValueService. + /// + internal class InitialMigration : MigrationBase + { + public InitialMigration(IMigrationContext context) + : base(context) + { } + + public override void Migrate() + { + // as long as we are still running 7 this migration will be invoked, + // but due to multiple restarts during upgrades, maybe the table + // exists already + if (TableExists(Constants.DatabaseSchema.Tables.KeyValue)) + return; + + Logger.Info("Creating KeyValue structure."); + + // the locks table was initially created with an identity (auto-increment) primary key, + // but we don't want this, especially as we are about to insert a new row into the table, + // so here we drop that identity + DropLockTableIdentity(); + + // insert the lock object for key/value + Insert.IntoTable(Constants.DatabaseSchema.Tables.Lock).Row(new { id = Constants.Locks.KeyValues, name = "KeyValues", value = 1 }).Do(); + + // create the key-value table + Create.Table().Do(); + } + + private void DropLockTableIdentity() + { + // one cannot simply drop an identity, that requires a bit of work + + // create a temp. id column and copy values + Alter.Table(Constants.DatabaseSchema.Tables.Lock).AddColumn("nid").AsInt32().Nullable().Do(); + Execute.Sql("update umbracoLock set nid = id").Do(); + + // drop the id column entirely (cannot just drop identity) + Delete.PrimaryKey("PK_umbracoLock").FromTable(Constants.DatabaseSchema.Tables.Lock).Do(); + Delete.Column("id").FromTable(Constants.DatabaseSchema.Tables.Lock).Do(); + + // recreate the id column without identity and copy values + Alter.Table(Constants.DatabaseSchema.Tables.Lock).AddColumn("id").AsInt32().Nullable().Do(); + Execute.Sql("update umbracoLock set id = nid").Do(); + + // drop the temp. id column + Delete.Column("nid").FromTable(Constants.DatabaseSchema.Tables.Lock).Do(); + + // complete the primary key + Alter.Table(Constants.DatabaseSchema.Tables.Lock).AlterColumn("id").AsInt32().NotNullable().PrimaryKey("PK_umbracoLock").Do(); + } + } + } +} diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/KeyValueRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/KeyValueRepository.cs index ebe101bbee..eab4049ec9 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/KeyValueRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/KeyValueRepository.cs @@ -4,12 +4,11 @@ using System.Linq; using NPoco; using Umbraco.Core.Cache; using Umbraco.Core.Logging; -using Umbraco.Core.Migrations; using Umbraco.Core.Models; +using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.Dtos; using Umbraco.Core.Persistence.Querying; using Umbraco.Core.Scoping; -using Umbraco.Infrastructure.Persistence.Repositories; namespace Umbraco.Core.Persistence.Repositories.Implement { @@ -19,56 +18,6 @@ namespace Umbraco.Core.Persistence.Repositories.Implement : base(scopeAccessor, AppCaches.NoCache, logger) { } - public void Initialize() - { - var context = new MigrationContext(Database, Logger); - var initMigration = new InitialMigration(context); - initMigration.Migrate(); - } - - public string GetValue(string key) - { - return GetDtoByKey(key)?.Value; - } - - public void SetValue(string key, string value) - { - var dto = GetDtoByKey(key); - if (dto == null) - { - dto = new KeyValueDto - { - Key = key, - Value = value, - UpdateDate = DateTime.Now - }; - - Database.Insert(dto); - } - else - { - UpdateDtoValue(dto, value); - } - } - - public bool TrySetValue(string key, string originalValue, string newValue) - { - var dto = GetDtoByKey(key); - - if (dto == null || dto.Value != originalValue) - return false; - - UpdateDtoValue(dto, newValue); - return true; - } - - private void UpdateDtoValue(KeyValueDto dto, string value) - { - dto.Value = value; - dto.UpdateDate = DateTime.Now; - Database.Update(dto); - } - /// /// Gets a value directly from the database, no scope, nothing. /// @@ -84,6 +33,18 @@ namespace Umbraco.Core.Persistence.Repositories.Implement return database.FirstOrDefault(sql)?.Value; } + #region Overrides of IReadWriteQueryRepository + + public override void Save(IKeyValue entity) + { + if (Get(entity.Identifier) == null) + PersistNewItem(entity); + else + PersistUpdatedItem(entity); + } + + #endregion + #region Overrides of NPocoRepositoryBase protected override Guid NodeObjectTypeId => throw new NotImplementedException(); @@ -114,16 +75,11 @@ namespace Umbraco.Core.Persistence.Repositories.Implement protected override IKeyValue PerformGet(string id) { - var dto = GetDtoByKey(id); + var sql = GetBaseQuery(false).Where(x => x.Key == id); + var dto = Database.Fetch(sql).FirstOrDefault(); return dto == null ? null : Map(dto); } - private KeyValueDto GetDtoByKey(string key) - { - var sql = GetBaseQuery(false).Where(x => x.Key == key); - return Database.Fetch(sql).FirstOrDefault(); - } - protected override IEnumerable PerformGetAll(params string[] ids) { var sql = GetBaseQuery(false).WhereIn(x => x.Key, ids); @@ -170,64 +126,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement Value = dto.Value, UpdateDate = dto.UpdateDate, }; - } + } - #endregion - - - /// - /// A custom migration that executes standalone during the Initialize phase of the KeyValueService. - /// - internal class InitialMigration : MigrationBase - { - public InitialMigration(IMigrationContext context) - : base(context) - { } - - public override void Migrate() - { - // as long as we are still running 7 this migration will be invoked, - // but due to multiple restarts during upgrades, maybe the table - // exists already - if (TableExists(Constants.DatabaseSchema.Tables.KeyValue)) - return; - - Logger.Info("Creating KeyValue structure."); - - // the locks table was initially created with an identity (auto-increment) primary key, - // but we don't want this, especially as we are about to insert a new row into the table, - // so here we drop that identity - DropLockTableIdentity(); - - // insert the lock object for key/value - Insert.IntoTable(Constants.DatabaseSchema.Tables.Lock).Row(new { id = Constants.Locks.KeyValues, name = "KeyValues", value = 1 }).Do(); - - // create the key-value table - Create.Table().Do(); - } - - private void DropLockTableIdentity() - { - // one cannot simply drop an identity, that requires a bit of work - - // create a temp. id column and copy values - Alter.Table(Constants.DatabaseSchema.Tables.Lock).AddColumn("nid").AsInt32().Nullable().Do(); - Execute.Sql("update umbracoLock set nid = id").Do(); - - // drop the id column entirely (cannot just drop identity) - Delete.PrimaryKey("PK_umbracoLock").FromTable(Constants.DatabaseSchema.Tables.Lock).Do(); - Delete.Column("id").FromTable(Constants.DatabaseSchema.Tables.Lock).Do(); - - // recreate the id column without identity and copy values - Alter.Table(Constants.DatabaseSchema.Tables.Lock).AddColumn("id").AsInt32().Nullable().Do(); - Execute.Sql("update umbracoLock set id = nid").Do(); - - // drop the temp. id column - Delete.Column("nid").FromTable(Constants.DatabaseSchema.Tables.Lock).Do(); - - // complete the primary key - Alter.Table(Constants.DatabaseSchema.Tables.Lock).AlterColumn("id").AsInt32().NotNullable().PrimaryKey("PK_umbracoLock").Do(); - } - } + #endregion } } diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RepositoryBaseOfTIdTEntity.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RepositoryBaseOfTIdTEntity.cs index e8397ba22a..6985bf78da 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RepositoryBaseOfTIdTEntity.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RepositoryBaseOfTIdTEntity.cs @@ -132,7 +132,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement /// /// This method is backed by an cache /// - public void Save(TEntity entity) + public virtual void Save(TEntity entity) { if (entity.HasIdentity == false) CachePolicy.Create(entity, PersistNewItem); diff --git a/src/Umbraco.Infrastructure/Runtime/CoreInitialComposer.cs b/src/Umbraco.Infrastructure/Runtime/CoreInitialComposer.cs index dbecd56d18..109f307c98 100644 --- a/src/Umbraco.Infrastructure/Runtime/CoreInitialComposer.cs +++ b/src/Umbraco.Infrastructure/Runtime/CoreInitialComposer.cs @@ -21,6 +21,7 @@ using Umbraco.Core.Services; using Umbraco.Core.Strings; using Umbraco.Core.Sync; using IntegerValidator = Umbraco.Core.PropertyEditors.Validators.IntegerValidator; +using Umbraco.Infrastructure.Migrations.Custom; namespace Umbraco.Core.Runtime { @@ -128,6 +129,7 @@ namespace Umbraco.Core.Runtime .Append(); composition.RegisterUnique(factory => new MigrationBuilder(factory)); + composition.RegisterUnique(); // by default, register a noop factory composition.RegisterUnique(); diff --git a/src/Umbraco.Infrastructure/Services/Implement/KeyValueService.cs b/src/Umbraco.Infrastructure/Services/Implement/KeyValueService.cs index bdb65091de..6835457f64 100644 --- a/src/Umbraco.Infrastructure/Services/Implement/KeyValueService.cs +++ b/src/Umbraco.Infrastructure/Services/Implement/KeyValueService.cs @@ -1,8 +1,10 @@ using System; using Umbraco.Core.Configuration; using Umbraco.Core.Logging; +using Umbraco.Core.Models; +using Umbraco.Core.Persistence.Repositories; using Umbraco.Core.Scoping; -using Umbraco.Infrastructure.Persistence.Repositories; +using Umbraco.Infrastructure.Migrations.Custom; namespace Umbraco.Core.Services.Implement { @@ -11,14 +13,16 @@ namespace Umbraco.Core.Services.Implement private readonly object _initialock = new object(); private readonly IScopeProvider _scopeProvider; private readonly IKeyValueRepository _repository; + private readonly IKeyValueServiceInitialization _initialization; private readonly ILogger _logger; private readonly IUmbracoVersion _umbracoVersion; private bool _initialized; - public KeyValueService(IScopeProvider scopeProvider, IKeyValueRepository repository, ILogger logger, IUmbracoVersion umbracoVersion) + public KeyValueService(IScopeProvider scopeProvider, IKeyValueRepository repository, IKeyValueServiceInitialization initialization, ILogger logger, IUmbracoVersion umbracoVersion) { _scopeProvider = scopeProvider; _repository = repository; + _initialization = initialization; _logger = logger; _umbracoVersion = umbracoVersion; } @@ -51,7 +55,7 @@ namespace Umbraco.Core.Services.Implement using (var scope = _scopeProvider.CreateScope()) { - _repository.Initialize(); + _initialization.PerformInitialMigration(scope.Database); scope.Complete(); } @@ -67,7 +71,7 @@ namespace Umbraco.Core.Services.Implement using (var scope = _scopeProvider.CreateScope()) { - return _repository.GetValue(key); + return _repository.Get(key)?.Value; } } @@ -80,7 +84,23 @@ namespace Umbraco.Core.Services.Implement { scope.WriteLock(Constants.Locks.KeyValues); - _repository.SetValue(key, value); + var keyValue = _repository.Get(key); + if (keyValue == null) + { + keyValue = new KeyValue + { + Identifier = key, + Value = value, + UpdateDate = DateTime.Now, + }; + } + else + { + keyValue.Value = value; + keyValue.UpdateDate = DateTime.Now; + } + + _repository.Save(keyValue); scope.Complete(); } @@ -102,11 +122,16 @@ namespace Umbraco.Core.Services.Implement { scope.WriteLock(Constants.Locks.KeyValues); - if (!_repository.TrySetValue(key, originalValue, newValue)) + var keyValue = _repository.Get(key); + if (keyValue == null || keyValue.Value != originalValue) { return false; } + keyValue.Value = newValue; + keyValue.UpdateDate = DateTime.Now; + _repository.Save(keyValue); + scope.Complete(); } diff --git a/src/Umbraco.Tests/Persistence/Repositories/KeyValueRepositoryTests.cs b/src/Umbraco.Tests/Persistence/Repositories/KeyValueRepositoryTests.cs index 92ebb03a32..50ebc7ff5d 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/KeyValueRepositoryTests.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/KeyValueRepositoryTests.cs @@ -1,7 +1,9 @@ -using NUnit.Framework; +using System; +using NUnit.Framework; +using Umbraco.Core.Models; +using Umbraco.Core.Persistence.Repositories; using Umbraco.Core.Persistence.Repositories.Implement; using Umbraco.Core.Scoping; -using Umbraco.Infrastructure.Persistence.Repositories; using Umbraco.Tests.TestHelpers; using Umbraco.Tests.Testing; @@ -19,8 +21,14 @@ namespace Umbraco.Tests.Persistence.Repositories // Insert new key/value using (var scope = provider.CreateScope()) { + var keyValue = new KeyValue + { + Identifier = "foo", + Value = "bar", + UpdateDate = DateTime.Now, + }; var repo = CreateRepository(provider); - repo.SetValue("foo", "bar"); + repo.Save(keyValue); scope.Complete(); } @@ -28,17 +36,20 @@ namespace Umbraco.Tests.Persistence.Repositories using (var scope = provider.CreateScope()) { var repo = CreateRepository(provider); - var value = repo.GetValue("foo"); + var keyValue = repo.Get("foo"); scope.Complete(); - Assert.AreEqual("bar", value); + Assert.AreEqual("bar", keyValue.Value); } - // Update new key/value + // Update value using (var scope = provider.CreateScope()) { var repo = CreateRepository(provider); - repo.SetValue("foo", "buzz"); + var keyValue = repo.Get("foo"); + keyValue.Value = "buzz"; + keyValue.UpdateDate = DateTime.Now; + repo.Save(keyValue); scope.Complete(); } @@ -46,10 +57,10 @@ namespace Umbraco.Tests.Persistence.Repositories using (var scope = provider.CreateScope()) { var repo = CreateRepository(provider); - var value = repo.GetValue("foo"); + var keyValue = repo.Get("foo"); scope.Complete(); - Assert.AreEqual("buzz", value); + Assert.AreEqual("buzz", keyValue.Value); } } diff --git a/src/Umbraco.Tests/Services/KeyValueServiceTests.cs b/src/Umbraco.Tests/Services/KeyValueServiceTests.cs new file mode 100644 index 0000000000..5ab29258f5 --- /dev/null +++ b/src/Umbraco.Tests/Services/KeyValueServiceTests.cs @@ -0,0 +1,93 @@ +using System.Threading; +using NUnit.Framework; +using NUnit.Framework.Internal; +using Umbraco.Core.Services; +using Umbraco.Tests.Testing; + +namespace Umbraco.Tests.Services +{ + /// + /// Tests covering methods in the KeyValueService class. + /// This is more of an integration test as it involves multiple layers + /// as well as configuration. + /// + [TestFixture] + [Apartment(ApartmentState.STA)] + [UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest)] + public class KeyValueServiceTests : TestWithSomeContentBase + { + [Test] + public void GetValue_ForMissingKey_ReturnsNull() + { + // Arrange + var keyValueService = ServiceContext.KeyValueService; + + // Act + var value = keyValueService.GetValue("foo"); + + // Assert + Assert.IsNull(value); + } + + [Test] + public void GetValue_ForExistingKey_ReturnsValue() + { + // Arrange + var keyValueService = ServiceContext.KeyValueService; + keyValueService.SetValue("foo", "bar"); + + // Act + var value = keyValueService.GetValue("foo"); + + // Assert + Assert.AreEqual("bar", value); + } + + [Test] + public void SetValue_ForExistingKey_SavesValue() + { + // Arrange + var keyValueService = ServiceContext.KeyValueService; + keyValueService.SetValue("foo", "bar"); + + // Act + keyValueService.SetValue("foo", "buzz"); + var value = keyValueService.GetValue("foo"); + + // Assert + Assert.AreEqual("buzz", value); + } + + [Test] + public void TrySetValue_ForExistingKeyWithProvidedValue_ReturnsTrueAndSetsValue() + { + // Arrange + var keyValueService = ServiceContext.KeyValueService; + keyValueService.SetValue("foo", "bar"); + + // Act + var result = keyValueService.TrySetValue("foo", "bar", "buzz"); + var value = keyValueService.GetValue("foo"); + + // Assert + Assert.IsTrue(result); + Assert.AreEqual("buzz", value); + } + + [Test] + public void TrySetValue_ForExistingKeyWithoutProvidedValue_ReturnsFalseAndDoesNotSetValue() + { + // Arrange + var keyValueService = ServiceContext.KeyValueService; + keyValueService.SetValue("foo", "bar"); + + // Act + var result = keyValueService.TrySetValue("foo", "bang", "buzz"); + var value = keyValueService.GetValue("foo"); + + // Assert + Assert.IsFalse(result); + Assert.AreEqual("bar", value); + } + } +} diff --git a/src/Umbraco.Tests/TestHelpers/TestObjects.cs b/src/Umbraco.Tests/TestHelpers/TestObjects.cs index 92b9dd0ad2..8e884a5937 100644 --- a/src/Umbraco.Tests/TestHelpers/TestObjects.cs +++ b/src/Umbraco.Tests/TestHelpers/TestObjects.cs @@ -21,6 +21,7 @@ using Umbraco.Core.Scoping; using Umbraco.Core.Services; using Umbraco.Core.Services.Implement; using Umbraco.Core.Strings; +using Umbraco.Infrastructure.Migrations.Custom; using Umbraco.Tests.TestHelpers.Stubs; using Current = Umbraco.Web.Composing.Current; @@ -193,6 +194,7 @@ namespace Umbraco.Tests.TestHelpers var tagService = GetLazyService(factory, c => new TagService(scopeProvider, logger, eventMessagesFactory, GetRepo(c))); var redirectUrlService = GetLazyService(factory, c => new RedirectUrlService(scopeProvider, logger, eventMessagesFactory, GetRepo(c))); var consentService = GetLazyService(factory, c => new ConsentService(scopeProvider, logger, eventMessagesFactory, GetRepo(c))); + var keyValueService = GetLazyService(factory, c => new KeyValueService(scopeProvider, GetRepo(c), Mock.Of(), logger, umbracoVersion)); var contentTypeServiceBaseFactory = GetLazyService(factory, c => new ContentTypeBaseServiceProvider(factory.GetInstance(),factory.GetInstance(),factory.GetInstance())); return new ServiceContext( @@ -221,6 +223,7 @@ namespace Umbraco.Tests.TestHelpers externalLoginService, redirectUrlService, consentService, + keyValueService, contentTypeServiceBaseFactory); } diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index 1527a8d4cc..46baf94644 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -149,6 +149,7 @@ + From baacd6f27fc9bc276fc374f7efd32122757fe38f Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Mon, 24 Feb 2020 15:50:13 +0000 Subject: [PATCH 07/73] Merge pull request #7654 from kjac/v8/fix/recycle-bin-without-mculture V8: Ensure that the recycle bin doesn't delete content at root (cherry picked from commit 4275401c4b3f5695c6fa454a9f428775df0558c1) --- .../src/views/dashboard/dashboard.tabs.controller.js | 4 +++- .../views/propertyeditors/listview/listview.controller.js | 7 +++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/dashboard/dashboard.tabs.controller.js b/src/Umbraco.Web.UI.Client/src/views/dashboard/dashboard.tabs.controller.js index a35a404c24..cdabb05fd4 100644 --- a/src/Umbraco.Web.UI.Client/src/views/dashboard/dashboard.tabs.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/dashboard/dashboard.tabs.controller.js @@ -220,7 +220,7 @@ function startupLatestEditsController($scope) { } angular.module("umbraco").controller("Umbraco.Dashboard.StartupLatestEditsController", startupLatestEditsController); -function MediaFolderBrowserDashboardController($rootScope, $scope, $location, contentTypeResource, userService) { +function MediaFolderBrowserDashboardController($scope, $routeParams, $location, contentTypeResource, userService) { var currentUser = {}; @@ -251,6 +251,8 @@ function MediaFolderBrowserDashboardController($rootScope, $scope, $location, co view: dt.view }; + // tell the list view to list content at root + $routeParams.id = -1; }); } else if (currentUser.startMediaIds.length > 0){ diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.controller.js index 3c954b9316..175199379a 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.controller.js @@ -276,6 +276,9 @@ function listViewController($scope, $interpolate, $routeParams, $injector, $time } $scope.reloadView = function (id, reloadActiveNode) { + if (!id) { + return; + } $scope.viewLoaded = false; $scope.folders = []; @@ -713,10 +716,10 @@ function listViewController($scope, $interpolate, $routeParams, $injector, $time } function initView() { - //default to root id if the id is undefined var id = $routeParams.id; if (id === undefined) { - id = -1; + // no ID found in route params - don't list anything as we don't know for sure where we are + return; } getContentTypesCallback(id).then(function (listViewAllowedTypes) { From 78a46677e5b0937a210d786c5703b74a6935d0e2 Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Tue, 25 Feb 2020 09:43:56 +0000 Subject: [PATCH 08/73] Merge pull request #7630 from kjac/v8/fix/nested-content-single-mode-add-more-than-one V8: Nested Content can't add multiple items (cherry picked from commit a65bf08f1a488a0c38962fe45165c09b50445ea4) --- .../propertyeditors/nestedcontent/nestedcontent.controller.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.controller.js index 7de3a5b567..1c6d305c6b 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.controller.js @@ -221,6 +221,7 @@ if (vm.overlayMenu.availableItems.length === 1 && vm.overlayMenu.pasteItems.length === 0) { // only one scaffold type - no need to display the picker addNode(vm.scaffolds[0].contentTypeAlias); + vm.overlayMenu = null; return; } From e2f54c3f3a2a5c290a24de5f4aacd30e78ed1fef Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Tue, 25 Feb 2020 13:07:34 +0000 Subject: [PATCH 09/73] Merge pull request #7696 from kjac/v8/fix/login-culture-unset-2 V8: Make sure mculture is set after logout and login (cherry picked from commit 4acddb288a423ecfe09ba44040a2a041f55307c5) --- .../src/navigation.controller.js | 61 +++++++++++++------ 1 file changed, 43 insertions(+), 18 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/navigation.controller.js b/src/Umbraco.Web.UI.Client/src/navigation.controller.js index 194c45afe6..281be2d331 100644 --- a/src/Umbraco.Web.UI.Client/src/navigation.controller.js +++ b/src/Umbraco.Web.UI.Client/src/navigation.controller.js @@ -257,6 +257,7 @@ function NavigationController($scope, $rootScope, $location, $log, $q, $routePar evts.push(eventsService.on("app.ready", function (evt, data) { $scope.authenticated = true; ensureInit(); + ensureMainCulture(); })); // event for infinite editors @@ -279,8 +280,22 @@ function NavigationController($scope, $rootScope, $location, $log, $q, $routePar } })); - - + /** + * For multi language sites, this ensures that mculture is set to either the last selected language or the default one + */ + function ensureMainCulture() { + if ($location.search().mculture) { + return; + } + var language = lastLanguageOrDefault(); + if (!language) { + return; + } + // trigger a language selection in the next digest cycle + $timeout(function () { + $scope.selectLanguage(language); + }); + } /** * Based on the current state of the application, this configures the scope variables that control the main tree and language drop down @@ -385,28 +400,19 @@ function NavigationController($scope, $rootScope, $location, $log, $q, $routePar if ($scope.languages.length > 1) { //if there's already one set, check if it exists - var currCulture = null; + var language = null; var mainCulture = $location.search().mculture; if (mainCulture) { - currCulture = _.find($scope.languages, function (l) { + language = _.find($scope.languages, function (l) { return l.culture.toLowerCase() === mainCulture.toLowerCase(); }); } - if (!currCulture) { - // no culture in the request, let's look for one in the cookie that's set when changing language - var defaultCulture = $cookies.get("UMB_MCULTURE"); - if (!defaultCulture || !_.find($scope.languages, function (l) { - return l.culture.toLowerCase() === defaultCulture.toLowerCase(); - })) { - // no luck either, look for the default language - var defaultLang = _.find($scope.languages, function (l) { - return l.isDefault; - }); - if (defaultLang) { - defaultCulture = defaultLang.culture; - } + if (!language) { + language = lastLanguageOrDefault(); + + if (language) { + $location.search("mculture", language.culture); } - $location.search("mculture", defaultCulture ? defaultCulture : null); } } @@ -431,6 +437,25 @@ function NavigationController($scope, $rootScope, $location, $log, $q, $routePar }); }); } + + function lastLanguageOrDefault() { + if (!$scope.languages || $scope.languages.length <= 1) { + return null; + } + // see if we can find a culture in the cookie set when changing language + var lastCulture = $cookies.get("UMB_MCULTURE"); + var language = lastCulture ? _.find($scope.languages, function (l) { + return l.culture.toLowerCase() === lastCulture.toLowerCase(); + }) : null; + if (!language) { + // no luck, look for the default language + language = _.find($scope.languages, function (l) { + return l.isDefault; + }); + } + return language; + } + function nodeExpandedHandler(args) { //store the reference to the expanded node path if (args.node) { From 5a35ba60c1fa3506eb6aa6467de1be846df5b6ca Mon Sep 17 00:00:00 2001 From: ksingercoffey <48767938+ksingercoffey@users.noreply.github.com> Date: Wed, 12 Feb 2020 22:41:19 -0800 Subject: [PATCH 10/73] #7643: add type="button" to prevent default clicks by pressing Enter (#7651) (cherry picked from commit 5307a8cf13975a07f987b93b8a45d73eb88ea5df) --- .../src/views/propertyeditors/mediapicker/mediapicker.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker/mediapicker.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker/mediapicker.html index b17906272d..c4dba4d373 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker/mediapicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker/mediapicker.html @@ -36,16 +36,16 @@
- -
  • -
  • From b48e288d717cf51dedfe80623fff27b5e5e36a43 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Fri, 10 Jan 2020 14:28:37 +0100 Subject: [PATCH 11/73] Fix JS errors for Nested Content in single mode (cherry picked from commit f4cd1c2f0cdaada66b28c60c9a3dd8742dcd8a7e) --- .../nestedcontent/nestedcontent.controller.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.controller.js index 1c6d305c6b..19547c38e4 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.controller.js @@ -277,6 +277,9 @@ }; vm.getName = function (idx) { + if (!model.value || !model.value.length) { + return ""; + } var name = ""; @@ -326,6 +329,10 @@ }; vm.getIcon = function (idx) { + if (!model.value || !model.value.length) { + return ""; + } + var scaffold = getScaffold(model.value[idx].ncContentTypeAlias); return scaffold && scaffold.icon ? iconHelper.convertFromLegacyIcon(scaffold.icon) : "icon-folder"; } From 05354b2ce9846ec86211534774935706ad34f30b Mon Sep 17 00:00:00 2001 From: Poornima Nayar Date: Tue, 25 Feb 2020 22:26:18 +0000 Subject: [PATCH 12/73] Merge pull request #7140 from kjac/v8/fix/media-picker-logout-login V8: Make sure the media picker can survive a logout and subsequent login (cherry picked from commit e36df809c05283cc3fdc79cc4ff1f207bfc96742) --- .../infiniteeditors/mediapicker/mediapicker.controller.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediapicker/mediapicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediapicker/mediapicker.controller.js index 01f61a16ae..35c68c55bf 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediapicker/mediapicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediapicker/mediapicker.controller.js @@ -318,7 +318,7 @@ angular.module("umbraco") // also make sure the node is not trashed if (nodePath.indexOf($scope.startNodeId.toString()) !== -1 && node.trashed === false) { - gotoFolder({ id: $scope.lastOpenedNode, name: "Media", icon: "icon-folder", path: node.path }); + gotoFolder({ id: $scope.lastOpenedNode || $scope.startNodeId, name: "Media", icon: "icon-folder", path: node.path }); return true; } else { gotoFolder({ id: $scope.startNodeId, name: "Media", icon: "icon-folder" }); From 2e40b11cd189687697995263f42bd9c8423971c0 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Sun, 1 Mar 2020 20:07:49 +0100 Subject: [PATCH 13/73] Make sure entity media path is mapped for DTO types of GenericContentEntityDto (cherry picked from commit fbe963a542b938bc678570372061667caffdc133) --- .../Repositories/Implement/EntityRepository.cs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/EntityRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/EntityRepository.cs index 0eafebbfde..24040bf393 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/EntityRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/EntityRepository.cs @@ -654,10 +654,14 @@ namespace Umbraco.Core.Persistence.Repositories.Implement var entity = new MediaEntitySlim(); BuildContentEntity(entity, dto); - if (dto is MediaEntityDto contentDto) + // fill in the media info + if (dto is MediaEntityDto mediaEntityDto) { - // fill in the media info - entity.MediaPath = contentDto.MediaPath; + entity.MediaPath = mediaEntityDto.MediaPath; + } + else if (dto is GenericContentEntityDto genericContentEntityDto) + { + entity.MediaPath = genericContentEntityDto.MediaPath; } return entity; From cbd4746c761108e8db68111f2bb0f5ff8576865d Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Wed, 4 Mar 2020 10:04:00 +0100 Subject: [PATCH 14/73] Cherry pick of 7bfeae7329bd4b94baa3cacd5b33a1c8fc33b5c8 Merge pull request #7745 from kjac/v8/fix/media-picker-upload-rte V8: Fix broken "upload and insert media" in RTE --- .../infiniteeditors/mediapicker/mediapicker.controller.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediapicker/mediapicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediapicker/mediapicker.controller.js index 35c68c55bf..ba4c982b5e 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediapicker/mediapicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediapicker/mediapicker.controller.js @@ -300,9 +300,7 @@ angular.module("umbraco") }); } else { var image = $scope.images[$scope.images.length - 1]; - $scope.target = image; - $scope.target.url = mediaHelper.resolveFile(image); - selectMedia(image); + clickHandler(image); } }); }); From 3d206533101a5f23c04327dc5bb70128e8749938 Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Thu, 5 Mar 2020 07:53:27 +0100 Subject: [PATCH 15/73] Aligned files with new project structure and fixed failing unit tests. --- src/{Umbraco.Abstractions => Umbraco.Core}/Models/IKeyValue.cs | 0 src/{Umbraco.Abstractions => Umbraco.Core}/Models/KeyValue.cs | 0 .../Persistence/Repositories/IKeyValueRepository.cs | 0 .../Composing/CompositionExtensions/Repositories.cs | 2 ++ 4 files changed, 2 insertions(+) rename src/{Umbraco.Abstractions => Umbraco.Core}/Models/IKeyValue.cs (100%) rename src/{Umbraco.Abstractions => Umbraco.Core}/Models/KeyValue.cs (100%) rename src/{Umbraco.Abstractions => Umbraco.Core}/Persistence/Repositories/IKeyValueRepository.cs (100%) diff --git a/src/Umbraco.Abstractions/Models/IKeyValue.cs b/src/Umbraco.Core/Models/IKeyValue.cs similarity index 100% rename from src/Umbraco.Abstractions/Models/IKeyValue.cs rename to src/Umbraco.Core/Models/IKeyValue.cs diff --git a/src/Umbraco.Abstractions/Models/KeyValue.cs b/src/Umbraco.Core/Models/KeyValue.cs similarity index 100% rename from src/Umbraco.Abstractions/Models/KeyValue.cs rename to src/Umbraco.Core/Models/KeyValue.cs diff --git a/src/Umbraco.Abstractions/Persistence/Repositories/IKeyValueRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IKeyValueRepository.cs similarity index 100% rename from src/Umbraco.Abstractions/Persistence/Repositories/IKeyValueRepository.cs rename to src/Umbraco.Core/Persistence/Repositories/IKeyValueRepository.cs diff --git a/src/Umbraco.Infrastructure/Composing/CompositionExtensions/Repositories.cs b/src/Umbraco.Infrastructure/Composing/CompositionExtensions/Repositories.cs index 9a23255cc4..b8231241a1 100644 --- a/src/Umbraco.Infrastructure/Composing/CompositionExtensions/Repositories.cs +++ b/src/Umbraco.Infrastructure/Composing/CompositionExtensions/Repositories.cs @@ -1,5 +1,6 @@ using Umbraco.Core.Persistence.Repositories; using Umbraco.Core.Persistence.Repositories.Implement; +using Umbraco.Infrastructure.Migrations.Custom; namespace Umbraco.Core.Composing.CompositionExtensions { @@ -48,6 +49,7 @@ namespace Umbraco.Core.Composing.CompositionExtensions composition.RegisterUnique(); composition.RegisterUnique(); composition.RegisterUnique(); + composition.RegisterUnique(); composition.RegisterUnique(); composition.RegisterUnique(); From 69483d03b665520cc344a6dbb443757af9aea691 Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Mon, 9 Mar 2020 09:57:22 +0000 Subject: [PATCH 16/73] Set version to 8.6.0 .\build SetUmbracoVersion 8.6.0 --- src/SolutionInfo.cs | 2 +- src/Umbraco.Web.UI/Umbraco.Web.UI.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/SolutionInfo.cs b/src/SolutionInfo.cs index 641c06bcfc..1bb53004a0 100644 --- a/src/SolutionInfo.cs +++ b/src/SolutionInfo.cs @@ -19,4 +19,4 @@ using System.Resources; // these are FYI and changed automatically [assembly: AssemblyFileVersion("8.6.0")] -[assembly: AssemblyInformationalVersion("8.6.0-rc")] +[assembly: AssemblyInformationalVersion("8.6.0")] diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index a2dfa0ffd2..8f0db76c1d 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -429,4 +429,4 @@ - + \ No newline at end of file From 3eb4c642c39709840ce117a00a64c63f2526c7a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20Knippers?= Date: Mon, 9 Mar 2020 17:07:56 +0100 Subject: [PATCH 17/73] Added GetSegment() method to VariationContext and uses it when contentId is available --- .../PublishedContent/VariationContext.cs | 7 +++++++ .../VariationContextAccessorExtensions.cs | 21 ++++++++++++++++++- .../PublishedValueFallback.cs | 4 ++-- .../PublishedCache/NuCache/Property.cs | 8 +++---- 4 files changed, 33 insertions(+), 7 deletions(-) diff --git a/src/Umbraco.Core/Models/PublishedContent/VariationContext.cs b/src/Umbraco.Core/Models/PublishedContent/VariationContext.cs index b83002fdce..424fc7c4a8 100644 --- a/src/Umbraco.Core/Models/PublishedContent/VariationContext.cs +++ b/src/Umbraco.Core/Models/PublishedContent/VariationContext.cs @@ -23,5 +23,12 @@ /// Gets the segment. /// public string Segment { get; } + + /// + /// Gets the segment for the content item + /// + /// + /// + public virtual string GetSegment(int contentId) => Segment; } } diff --git a/src/Umbraco.Core/Models/PublishedContent/VariationContextAccessorExtensions.cs b/src/Umbraco.Core/Models/PublishedContent/VariationContextAccessorExtensions.cs index 6710d79cc6..a387ea87b8 100644 --- a/src/Umbraco.Core/Models/PublishedContent/VariationContextAccessorExtensions.cs +++ b/src/Umbraco.Core/Models/PublishedContent/VariationContextAccessorExtensions.cs @@ -3,13 +3,32 @@ public static class VariationContextAccessorExtensions { public static void ContextualizeVariation(this IVariationContextAccessor variationContextAccessor, ContentVariation variations, ref string culture, ref string segment) + => variationContextAccessor.ContextualizeVariation(variations, null, ref culture, ref segment); + + public static void ContextualizeVariation(this IVariationContextAccessor variationContextAccessor, ContentVariation variations, int contentId, ref string culture, ref string segment) + => variationContextAccessor.ContextualizeVariation(variations, (int?)contentId, ref culture, ref segment); + + private static void ContextualizeVariation(this IVariationContextAccessor variationContextAccessor, ContentVariation variations, int? contentId, ref string culture, ref string segment) { if (culture != null && segment != null) return; // use context values var publishedVariationContext = variationContextAccessor?.VariationContext; if (culture == null) culture = variations.VariesByCulture() ? publishedVariationContext?.Culture : ""; - if (segment == null) segment = variations.VariesBySegment() ? publishedVariationContext?.Segment : ""; + + if (segment == null) + { + if (variations.VariesBySegment()) + { + segment = contentId == null + ? publishedVariationContext?.Segment + : publishedVariationContext?.GetSegment(contentId.Value); + } + else + { + segment = ""; + } + } } } } diff --git a/src/Umbraco.Web/Models/PublishedContent/PublishedValueFallback.cs b/src/Umbraco.Web/Models/PublishedContent/PublishedValueFallback.cs index 7b467b6d15..57c3094eaf 100644 --- a/src/Umbraco.Web/Models/PublishedContent/PublishedValueFallback.cs +++ b/src/Umbraco.Web/Models/PublishedContent/PublishedValueFallback.cs @@ -111,7 +111,7 @@ namespace Umbraco.Web.Models.PublishedContent var propertyType = content.ContentType.GetPropertyType(alias); if (propertyType != null) { - _variationContextAccessor.ContextualizeVariation(propertyType.Variations, ref culture, ref segment); + _variationContextAccessor.ContextualizeVariation(propertyType.Variations, content.Id, ref culture, ref segment); noValueProperty = content.GetProperty(alias); } @@ -168,7 +168,7 @@ namespace Umbraco.Web.Models.PublishedContent { culture = null; segment = null; - _variationContextAccessor.ContextualizeVariation(propertyType.Variations, ref culture, ref segment); + _variationContextAccessor.ContextualizeVariation(propertyType.Variations, content.Id, ref culture, ref segment); } property = content?.GetProperty(alias); diff --git a/src/Umbraco.Web/PublishedCache/NuCache/Property.cs b/src/Umbraco.Web/PublishedCache/NuCache/Property.cs index 0254b815c1..86023bb302 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/Property.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/Property.cs @@ -90,7 +90,7 @@ namespace Umbraco.Web.PublishedCache.NuCache // determines whether a property has value public override bool HasValue(string culture = null, string segment = null) { - _content.VariationContextAccessor.ContextualizeVariation(_variations, ref culture, ref segment); + _content.VariationContextAccessor.ContextualizeVariation(_variations, _content.Id, ref culture, ref segment); var value = GetSourceValue(culture, segment); var hasValue = PropertyType.IsValue(value, PropertyValueLevel.Source); @@ -194,7 +194,7 @@ namespace Umbraco.Web.PublishedCache.NuCache public override object GetSourceValue(string culture = null, string segment = null) { - _content.VariationContextAccessor.ContextualizeVariation(_variations, ref culture, ref segment); + _content.VariationContextAccessor.ContextualizeVariation(_variations, _content.Id, ref culture, ref segment); if (culture == "" && segment == "") return _sourceValue; @@ -208,7 +208,7 @@ namespace Umbraco.Web.PublishedCache.NuCache public override object GetValue(string culture = null, string segment = null) { - _content.VariationContextAccessor.ContextualizeVariation(_variations, ref culture, ref segment); + _content.VariationContextAccessor.ContextualizeVariation(_variations, _content.Id, ref culture, ref segment); object value; lock (_locko) @@ -229,7 +229,7 @@ namespace Umbraco.Web.PublishedCache.NuCache public override object GetXPathValue(string culture = null, string segment = null) { - _content.VariationContextAccessor.ContextualizeVariation(_variations, ref culture, ref segment); + _content.VariationContextAccessor.ContextualizeVariation(_variations, _content.Id, ref culture, ref segment); lock (_locko) { From 5531c7300dbca4ec3253795bfa19786d2f52697a Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Thu, 19 Mar 2020 14:02:01 +0100 Subject: [PATCH 18/73] Update dependency for 8.5.5 (cherry picked from commit 2d3388d6d3ff6882c9e7b9e92f717ece03834fbe) # Conflicts: # src/SolutionInfo.cs --- build/NuSpecs/UmbracoCms.Web.nuspec | 2 +- src/Umbraco.Web.UI/Umbraco.Web.UI.csproj | 2 +- src/Umbraco.Web/Umbraco.Web.csproj | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/build/NuSpecs/UmbracoCms.Web.nuspec b/build/NuSpecs/UmbracoCms.Web.nuspec index 347bde139e..72619db02e 100644 --- a/build/NuSpecs/UmbracoCms.Web.nuspec +++ b/build/NuSpecs/UmbracoCms.Web.nuspec @@ -25,7 +25,7 @@ not want this to happen as the alpha of the next major is, really, the next major already. --> - + diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index 8f0db76c1d..cc6200cea7 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -86,7 +86,7 @@ - + diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 0111a36993..5173972f42 100755 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -61,7 +61,7 @@ - + From 8752ce871aecab986aeb1aa54b992620786d94ad Mon Sep 17 00:00:00 2001 From: SimonCropp Date: Fri, 20 Mar 2020 23:25:32 +1100 Subject: [PATCH 19/73] fix assert order of expted V actual --- src/Umbraco.Tests/Cache/AppCacheTests.cs | 2 +- .../UmbracoSettings/ContentElementTests.cs | 44 +++++++++---------- .../CoreThings/XmlExtensionsTests.cs | 6 +-- .../Persistence/BulkDataReaderTests.cs | 4 +- .../AngularAntiForgeryTests.cs | 4 +- 5 files changed, 30 insertions(+), 30 deletions(-) diff --git a/src/Umbraco.Tests/Cache/AppCacheTests.cs b/src/Umbraco.Tests/Cache/AppCacheTests.cs index 3a86feb90a..f0ac9a2b0a 100644 --- a/src/Umbraco.Tests/Cache/AppCacheTests.cs +++ b/src/Umbraco.Tests/Cache/AppCacheTests.cs @@ -96,7 +96,7 @@ namespace Umbraco.Tests.Cache return ""; }); - Assert.AreEqual(counter, 1); + Assert.AreEqual(1, counter); } diff --git a/src/Umbraco.Tests/Configurations/UmbracoSettings/ContentElementTests.cs b/src/Umbraco.Tests/Configurations/UmbracoSettings/ContentElementTests.cs index 0245159c6e..33df3caaad 100644 --- a/src/Umbraco.Tests/Configurations/UmbracoSettings/ContentElementTests.cs +++ b/src/Umbraco.Tests/Configurations/UmbracoSettings/ContentElementTests.cs @@ -16,7 +16,7 @@ namespace Umbraco.Tests.Configurations.UmbracoSettings [Test] public void EmailAddress() { - Assert.AreEqual(SettingsSection.Content.NotificationEmailAddress, "robot@umbraco.dk"); + Assert.AreEqual("robot@umbraco.dk", SettingsSection.Content.NotificationEmailAddress); } [Test] public virtual void DisableHtmlEmail() @@ -27,17 +27,17 @@ namespace Umbraco.Tests.Configurations.UmbracoSettings [Test] public virtual void Can_Set_Multiple() { - Assert.AreEqual(SettingsSection.Content.Error404Collection.Count(), 3); - Assert.AreEqual(SettingsSection.Content.Error404Collection.ElementAt(0).Culture, "default"); - Assert.AreEqual(SettingsSection.Content.Error404Collection.ElementAt(0).ContentId, 1047); + Assert.AreEqual(3, SettingsSection.Content.Error404Collection.Count()); + Assert.AreEqual("default", SettingsSection.Content.Error404Collection.ElementAt(0).Culture); + Assert.AreEqual(1047, SettingsSection.Content.Error404Collection.ElementAt(0).ContentId); Assert.IsTrue(SettingsSection.Content.Error404Collection.ElementAt(0).HasContentId); Assert.IsFalse(SettingsSection.Content.Error404Collection.ElementAt(0).HasContentKey); - Assert.AreEqual(SettingsSection.Content.Error404Collection.ElementAt(1).Culture, "en-US"); - Assert.AreEqual(SettingsSection.Content.Error404Collection.ElementAt(1).ContentXPath, "$site/error [@name = 'error']"); + Assert.AreEqual("en-US", SettingsSection.Content.Error404Collection.ElementAt(1).Culture); + Assert.AreEqual("$site/error [@name = 'error']", SettingsSection.Content.Error404Collection.ElementAt(1).ContentXPath); Assert.IsFalse(SettingsSection.Content.Error404Collection.ElementAt(1).HasContentId); Assert.IsFalse(SettingsSection.Content.Error404Collection.ElementAt(1).HasContentKey); - Assert.AreEqual(SettingsSection.Content.Error404Collection.ElementAt(2).Culture, "en-UK"); - Assert.AreEqual(SettingsSection.Content.Error404Collection.ElementAt(2).ContentKey, new Guid("8560867F-B88F-4C74-A9A4-679D8E5B3BFC")); + Assert.AreEqual("en-UK", SettingsSection.Content.Error404Collection.ElementAt(2).Culture); + Assert.AreEqual(new Guid("8560867F-B88F-4C74-A9A4-679D8E5B3BFC"), SettingsSection.Content.Error404Collection.ElementAt(2).ContentKey); Assert.IsTrue(SettingsSection.Content.Error404Collection.ElementAt(2).HasContentKey); Assert.IsFalse(SettingsSection.Content.Error404Collection.ElementAt(2).HasContentId); } @@ -47,23 +47,23 @@ namespace Umbraco.Tests.Configurations.UmbracoSettings { Assert.IsTrue(SettingsSection.Content.ImageFileTypes.All(x => "jpeg,jpg,gif,bmp,png,tiff,tif".Split(',').Contains(x))); } - + [Test] public virtual void ImageAutoFillProperties() { - Assert.AreEqual(SettingsSection.Content.ImageAutoFillProperties.Count(), 2); - Assert.AreEqual(SettingsSection.Content.ImageAutoFillProperties.ElementAt(0).Alias, "umbracoFile"); - Assert.AreEqual(SettingsSection.Content.ImageAutoFillProperties.ElementAt(0).WidthFieldAlias, "umbracoWidth"); - Assert.AreEqual(SettingsSection.Content.ImageAutoFillProperties.ElementAt(0).HeightFieldAlias, "umbracoHeight"); - Assert.AreEqual(SettingsSection.Content.ImageAutoFillProperties.ElementAt(0).LengthFieldAlias, "umbracoBytes"); - Assert.AreEqual(SettingsSection.Content.ImageAutoFillProperties.ElementAt(0).ExtensionFieldAlias, "umbracoExtension"); - Assert.AreEqual(SettingsSection.Content.ImageAutoFillProperties.ElementAt(1).Alias, "umbracoFile2"); - Assert.AreEqual(SettingsSection.Content.ImageAutoFillProperties.ElementAt(1).WidthFieldAlias, "umbracoWidth2"); - Assert.AreEqual(SettingsSection.Content.ImageAutoFillProperties.ElementAt(1).HeightFieldAlias, "umbracoHeight2"); - Assert.AreEqual(SettingsSection.Content.ImageAutoFillProperties.ElementAt(1).LengthFieldAlias, "umbracoBytes2"); - Assert.AreEqual(SettingsSection.Content.ImageAutoFillProperties.ElementAt(1).ExtensionFieldAlias, "umbracoExtension2"); + Assert.AreEqual(2, SettingsSection.Content.ImageAutoFillProperties.Count()); + Assert.AreEqual("umbracoFile", SettingsSection.Content.ImageAutoFillProperties.ElementAt(0).Alias); + Assert.AreEqual("umbracoWidth", SettingsSection.Content.ImageAutoFillProperties.ElementAt(0).WidthFieldAlias); + Assert.AreEqual("umbracoHeight", SettingsSection.Content.ImageAutoFillProperties.ElementAt(0).HeightFieldAlias); + Assert.AreEqual("umbracoBytes", SettingsSection.Content.ImageAutoFillProperties.ElementAt(0).LengthFieldAlias); + Assert.AreEqual("umbracoExtension", SettingsSection.Content.ImageAutoFillProperties.ElementAt(0).ExtensionFieldAlias); + Assert.AreEqual("umbracoFile2", SettingsSection.Content.ImageAutoFillProperties.ElementAt(1).Alias); + Assert.AreEqual("umbracoWidth2", SettingsSection.Content.ImageAutoFillProperties.ElementAt(1).WidthFieldAlias); + Assert.AreEqual("umbracoHeight2", SettingsSection.Content.ImageAutoFillProperties.ElementAt(1).HeightFieldAlias); + Assert.AreEqual("umbracoBytes2", SettingsSection.Content.ImageAutoFillProperties.ElementAt(1).LengthFieldAlias); + Assert.AreEqual("umbracoExtension2", SettingsSection.Content.ImageAutoFillProperties.ElementAt(1).ExtensionFieldAlias); } - + [Test] public void PreviewBadge() { @@ -116,7 +116,7 @@ namespace Umbraco.Tests.Configurations.UmbracoSettings Debug.WriteLine("AllowedContainsExtension: {0}", allowedContainsExtension); Debug.WriteLine("DisallowedContainsExtension: {0}", disallowedContainsExtension); - Assert.AreEqual(SettingsSection.Content.IsFileAllowedForUpload(extension), expected); + Assert.AreEqual(expected, SettingsSection.Content.IsFileAllowedForUpload(extension)); } } } diff --git a/src/Umbraco.Tests/CoreThings/XmlExtensionsTests.cs b/src/Umbraco.Tests/CoreThings/XmlExtensionsTests.cs index 42d8f92a5d..62e385beff 100644 --- a/src/Umbraco.Tests/CoreThings/XmlExtensionsTests.cs +++ b/src/Umbraco.Tests/CoreThings/XmlExtensionsTests.cs @@ -16,7 +16,7 @@ namespace Umbraco.Tests.CoreThings var xmlNode = cdata.GetXmlNode(xdoc); - Assert.AreEqual(xmlNode.InnerText, "hello world"); + Assert.AreEqual("hello world", xmlNode.InnerText); } [Test] @@ -27,7 +27,7 @@ namespace Umbraco.Tests.CoreThings var xmlNode = cdata.GetXmlNode(xdoc); - Assert.AreEqual(xmlNode.InnerText, "hello world"); + Assert.AreEqual("hello world", xmlNode.InnerText); } [Test] @@ -41,7 +41,7 @@ namespace Umbraco.Tests.CoreThings var xmlNode = cdata.GetXmlNode(xdoc); - Assert.AreEqual(xmlNode.InnerText, "hello world"); + Assert.AreEqual("hello world", xmlNode.InnerText); Assert.AreEqual(xml, xdoc.OuterXml); } } diff --git a/src/Umbraco.Tests/Persistence/BulkDataReaderTests.cs b/src/Umbraco.Tests/Persistence/BulkDataReaderTests.cs index 3ada9fcceb..c157d66b2c 100644 --- a/src/Umbraco.Tests/Persistence/BulkDataReaderTests.cs +++ b/src/Umbraco.Tests/Persistence/BulkDataReaderTests.cs @@ -1693,7 +1693,7 @@ namespace Umbraco.Tests.Persistence { Assert.IsTrue(testReader.Read()); - Assert.AreEqual(testReader.Depth, 0); + Assert.AreEqual(0, testReader.Depth); } } @@ -2067,7 +2067,7 @@ namespace Umbraco.Tests.Persistence { Assert.IsTrue(testReader.Read()); - Assert.AreEqual(testReader.RecordsAffected, -1); + Assert.AreEqual(-1, testReader.RecordsAffected); } } diff --git a/src/Umbraco.Tests/Web/AngularIntegration/AngularAntiForgeryTests.cs b/src/Umbraco.Tests/Web/AngularIntegration/AngularAntiForgeryTests.cs index 780a3e90fd..fd30f1b5ec 100644 --- a/src/Umbraco.Tests/Web/AngularIntegration/AngularAntiForgeryTests.cs +++ b/src/Umbraco.Tests/Web/AngularIntegration/AngularAntiForgeryTests.cs @@ -27,7 +27,7 @@ namespace Umbraco.Tests.Web.AngularIntegration string cookieToken, headerToken; AngularAntiForgeryHelper.GetTokens(out cookieToken, out headerToken); - Assert.AreEqual(true, AngularAntiForgeryHelper.ValidateTokens(cookieToken, headerToken)); + Assert.IsTrue(AngularAntiForgeryHelper.ValidateTokens(cookieToken, headerToken)); } } @@ -45,7 +45,7 @@ namespace Umbraco.Tests.Web.AngularIntegration string cookieToken, headerToken; AngularAntiForgeryHelper.GetTokens(out cookieToken, out headerToken); - Assert.AreEqual(true, AngularAntiForgeryHelper.ValidateTokens(cookieToken, headerToken)); + Assert.IsTrue(AngularAntiForgeryHelper.ValidateTokens(cookieToken, headerToken)); } } From ff2027df0731ad3a3d1b56e0c0b897bf1b31bf01 Mon Sep 17 00:00:00 2001 From: Olivier Bossaer Date: Sat, 21 Mar 2020 16:49:14 +0100 Subject: [PATCH 20/73] Fixes #7798 : Invalid implementation of IEnumerable in Content Cache --- .../PublishedContent/NuCacheChildrenTests.cs | 15 ++++++++++++--- .../PublishedCache/NuCache/ContentCache.cs | 1 + 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Tests/PublishedContent/NuCacheChildrenTests.cs b/src/Umbraco.Tests/PublishedContent/NuCacheChildrenTests.cs index cc9ffdba3c..4221968429 100644 --- a/src/Umbraco.Tests/PublishedContent/NuCacheChildrenTests.cs +++ b/src/Umbraco.Tests/PublishedContent/NuCacheChildrenTests.cs @@ -1,9 +1,7 @@ using System; -using System.Collections.Concurrent; using System.Collections.Generic; using System.Data; using System.Linq; -using System.Reflection; using Moq; using NUnit.Framework; using Umbraco.Core; @@ -27,7 +25,6 @@ using Umbraco.Web.Cache; using Umbraco.Web.PublishedCache; using Umbraco.Web.PublishedCache.NuCache; using Umbraco.Web.PublishedCache.NuCache.DataSource; -using Umbraco.Web.PublishedCache.NuCache.Snap; namespace Umbraco.Tests.PublishedContent { @@ -1312,6 +1309,18 @@ namespace Umbraco.Tests.PublishedContent AssertLinkedNode(child3.contentNode, 1, 3, -1, -1, -1); } + [Test] + public void MultipleCacheIteration() + { + //see https://github.com/umbraco/Umbraco-CMS/issues/7798 + this.Init(this.GetInvariantKits()); + var snapshot = this._snapshotService.CreatePublishedSnapshot(previewToken: null); + this._snapshotAccessor.PublishedSnapshot = snapshot; + + var items = snapshot.Content.GetByXPath("/root/itype"); + Assert.AreEqual(items.Count(), items.Count()); + } + private void AssertLinkedNode(ContentNode node, int parent, int prevSibling, int nextSibling, int firstChild, int lastChild) { Assert.AreEqual(parent, node.ParentContentId); diff --git a/src/Umbraco.Web/PublishedCache/NuCache/ContentCache.cs b/src/Umbraco.Web/PublishedCache/NuCache/ContentCache.cs index 84edb9113c..24c6a7018b 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/ContentCache.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/ContentCache.cs @@ -355,6 +355,7 @@ namespace Umbraco.Web.PublishedCache.NuCache private static IEnumerable GetByXPath(XPathNodeIterator iterator) { + iterator = iterator.Clone(); while (iterator.MoveNext()) { var xnav = iterator.Current as NavigableNavigator; From 3edfc42623cc283f75c3b46bad35f0cdfb54734d Mon Sep 17 00:00:00 2001 From: Chris Oliver Date: Fri, 20 Mar 2020 17:42:10 +0000 Subject: [PATCH 21/73] Collapse navigation items based on container size - Previously a maximum of 8 items were shown. - This change calculates the width of each item and displays as many as possible in the container. - Any overflowing items will be moved to the ... menu --- .../application/umbsections.directive.js | 37 +++++++------------ .../components/application/umb-sections.html | 8 ++-- 2 files changed, 18 insertions(+), 27 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbsections.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbsections.directive.js index a33796ab6d..190da7da0d 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbsections.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbsections.directive.js @@ -12,26 +12,25 @@ function sectionsDirective($timeout, $window, navigationService, treeService, se var sectionItemsWidth = []; var evts = []; - var maxSections = 8; //setup scope vars - scope.maxSections = maxSections; - scope.overflowingSections = 0; scope.sections = []; + scope.visibleSections = 0; scope.currentSection = appState.getSectionState("currentSection"); - scope.showTray = false; //appState.getGlobalState("showTray"); + scope.showTray = false; scope.stickyNavigation = appState.getGlobalState("stickyNavigation"); - scope.needTray = false; function loadSections() { sectionService.getSectionsForUser() .then(function (result) { scope.sections = result; + scope.visibleSections = scope.sections.length; + // store the width of each section so we can hide/show them based on browser width // we store them because the sections get removed from the dom and then we // can't tell when to show them gain $timeout(function () { - $("#applications .sections li").each(function (index) { + $("#applications .sections li:not(:last)").each(function (index) { sectionItemsWidth.push($(this).outerWidth()); }); }); @@ -42,25 +41,22 @@ function sectionsDirective($timeout, $window, navigationService, treeService, se function calculateWidth() { $timeout(function () { //total width minus room for avatar, search, and help icon - var windowWidth = $(window).width() - 150; + var containerWidth = $(".umb-app-header").outerWidth() - $(".umb-app-header__actions").outerWidth(); + var trayToggleWidth = $("#applications .sections li.expand").outerWidth(); var sectionsWidth = 0; - scope.totalSections = scope.sections.length; - scope.maxSections = maxSections; - scope.overflowingSections = scope.maxSections - scope.totalSections; - scope.needTray = scope.sections.length > scope.maxSections; - + // detect how many sections we can show on the screen for (var i = 0; i < sectionItemsWidth.length; i++) { var sectionItemWidth = sectionItemsWidth[i]; sectionsWidth += sectionItemWidth; - if (sectionsWidth > windowWidth) { - scope.needTray = true; - scope.maxSections = i - 1; - scope.overflowingSections = scope.maxSections - scope.totalSections; - break; + if (sectionsWidth + trayToggleWidth > containerWidth) { + scope.visibleSections = i; + return; } } + + scope.visibleSections = scope.sections.length; }); } @@ -134,14 +130,9 @@ function sectionsDirective($timeout, $window, navigationService, treeService, se }; scope.currentSectionInOverflow = function () { - if (scope.overflowingSections === 0) { - return false; - } - var currentSection = scope.sections.filter(s => s.alias === scope.currentSection); - return (scope.sections.indexOf(currentSection[0]) >= scope.maxSections); - + return currentSection.length > 0 && scope.sections.indexOf(currentSection[0]) > scope.visibleSections - 1; }; loadSections(); diff --git a/src/Umbraco.Web.UI.Client/src/views/components/application/umb-sections.html b/src/Umbraco.Web.UI.Client/src/views/components/application/umb-sections.html index 0dac7176ee..d435c6ead7 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/application/umb-sections.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/application/umb-sections.html @@ -2,7 +2,7 @@
    - - - From b082d40921d3ccc939c28f1adda7973729b63b16 Mon Sep 17 00:00:00 2001 From: Nathan Woulfe Date: Wed, 25 Mar 2020 02:48:14 +1000 Subject: [PATCH 31/73] =?UTF-8?q?adds=20utility.js=20as=20facade=20to=20ge?= =?UTF-8?q?neric=20javascript=20utility=20functio=E2=80=A6=20(#7738)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Umbraco.Web.UI.Client/.eslintrc | 4 +- src/Umbraco.Web.UI.Client/gulp/config.js | 4 +- .../common/services/angularhelper.service.js | 10 +-- .../src/main.controller.js | 11 ++- src/Umbraco.Web.UI.Client/src/utilities.js | 86 +++++++++++++++++++ .../views/documenttypes/edit.controller.js | 2 +- .../packages/views/created.controller.js | 2 +- .../partialviewmacros/edit.controller.js | 2 +- .../src/views/users/user.controller.js | 2 +- .../users/views/groups/groups.controller.js | 2 +- .../users/views/users/users.controller.js | 2 +- 11 files changed, 106 insertions(+), 21 deletions(-) create mode 100644 src/Umbraco.Web.UI.Client/src/utilities.js diff --git a/src/Umbraco.Web.UI.Client/.eslintrc b/src/Umbraco.Web.UI.Client/.eslintrc index 1bfa081b9a..a4010917fb 100644 --- a/src/Umbraco.Web.UI.Client/.eslintrc +++ b/src/Umbraco.Web.UI.Client/.eslintrc @@ -19,10 +19,10 @@ "tinyMCE": false, "FileReader": false, "Umbraco": false, + "Utilities": false, "window": false, "LazyLoad": false, "ActiveXObject": false, "Bloodhound": false } - -} \ No newline at end of file +} diff --git a/src/Umbraco.Web.UI.Client/gulp/config.js b/src/Umbraco.Web.UI.Client/gulp/config.js index 59e8bf6c05..39dc9bb2a4 100755 --- a/src/Umbraco.Web.UI.Client/gulp/config.js +++ b/src/Umbraco.Web.UI.Client/gulp/config.js @@ -28,9 +28,9 @@ module.exports = { installer: { files: "./src/installer/**/*.js", out: "umbraco.installer.js" }, filters: { files: "./src/common/filters/**/*.js", out: "umbraco.filters.js" }, resources: { files: "./src/common/resources/**/*.js", out: "umbraco.resources.js" }, - services: { files: "./src/common/services/**/*.js", out: "umbraco.services.js" }, + services: { files: ["./src/common/services/**/*.js", "./src/utilities.js"], out: "umbraco.services.js" }, security: { files: "./src/common/interceptors/**/*.js", out: "umbraco.interceptors.js" }, - + //the controllers for views controllers: { files: [ diff --git a/src/Umbraco.Web.UI.Client/src/common/services/angularhelper.service.js b/src/Umbraco.Web.UI.Client/src/common/services/angularhelper.service.js index 455857c1e1..8cba83328f 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/angularhelper.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/angularhelper.service.js @@ -178,11 +178,11 @@ function angularHelper($q) { $valid: true, $submitted: false, $pending: undefined, - $addControl: angular.noop, - $removeControl: angular.noop, - $setValidity: angular.noop, - $setDirty: angular.noop, - $setPristine: angular.noop, + $addControl: Utilities.noop, + $removeControl: Utilities.noop, + $setValidity: Utilities.noop, + $setDirty: Utilities.noop, + $setPristine: Utilities.noop, $name: formName }; } diff --git a/src/Umbraco.Web.UI.Client/src/main.controller.js b/src/Umbraco.Web.UI.Client/src/main.controller.js index 883907d1dc..198ac132c1 100644 --- a/src/Umbraco.Web.UI.Client/src/main.controller.js +++ b/src/Umbraco.Web.UI.Client/src/main.controller.js @@ -1,19 +1,18 @@ - -/** +/** * @ngdoc controller - * @name Umbraco.MainController + * @name Umbraco.MainController * @function * - * @description + * @description * The main application controller * */ function MainController($scope, $location, appState, treeService, notificationsService, userService, historyService, updateChecker, navigationService, eventsService, tmhDynamicLocale, localStorageService, editorService, overlayService, assetsService, tinyMceAssets) { - + //the null is important because we do an explicit bool check on this in the view - $scope.authenticated = null; + $scope.authenticated = null; $scope.touchDevice = appState.getGlobalState("touchDevice"); $scope.infiniteMode = false; $scope.overlay = {}; diff --git a/src/Umbraco.Web.UI.Client/src/utilities.js b/src/Umbraco.Web.UI.Client/src/utilities.js new file mode 100644 index 0000000000..bd12506358 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/utilities.js @@ -0,0 +1,86 @@ +/** + * A friendly utility collection to replace AngularJs' ng-functions + * If it doesn't exist here, it's probably available as vanilla JS + * + * Still carries a dependency on underscore, but if usages of underscore from + * elsewhere in the codebase can instead use these methods, the underscore + * dependency will be nicely abstracted and can be removed/swapped later + * + * This collection is open to extension... + */ +(function (window) { + + /** + * Equivalent to angular.noop + */ + const noop = () => { }; + + /** + * Facade to angular.copy + */ + const copy = val => angular.copy(val); + + /** + * Equivalent to angular.isArray + */ + const isArray = val => Array.isArray(val) || val instanceof Array; + + /** + * Facade to angular.equals + */ + const equals = (a, b) => angular.equals(a, b); + + /** + * Facade to angular.extend + * Use this with Angular objects, for vanilla JS objects, use Object.assign() + */ + const extend = (dst, src) => angular.extend(dst, src); + + /** + * Equivalent to angular.isFunction + */ + const isFunction = val => typeof val === 'function'; + + /** + * Equivalent to angular.isUndefined + */ + const isUndefined = val => typeof val === 'undefined'; + + /** + * Equivalent to angular.isDefined. Inverts result of const isUndefined + */ + const isDefined = val => !isUndefined(val); + + /** + * Equivalent to angular.isString + */ + const isString = val => typeof val === 'string'; + + /** + * Equivalent to angular.isNumber + */ + const isNumber = val => typeof val === 'number'; + + /** + * Equivalent to angular.isObject + */ + const isObject = val => val !== null && typeof val === 'object'; + + let _utilities = { + noop: noop, + copy: copy, + isArray: isArray, + equals: equals, + extend: extend, + isFunction: isFunction, + isUndefined: isUndefined, + isDefined: isDefined, + isString: isString, + isNumber: isNumber, + isObject: isObject + }; + + if (typeof (window.Utilities) === 'undefined') { + window.Utilities = _utilities; + } +})(window); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/views/documenttypes/edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/documenttypes/edit.controller.js index b04cccbc0d..21eb77b1e9 100644 --- a/src/Umbraco.Web.UI.Client/src/views/documenttypes/edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/documenttypes/edit.controller.js @@ -325,7 +325,7 @@ /* ---------- SAVE ---------- */ function save() { - saveInternal().then(angular.noop, angular.noop); + saveInternal().then(Utilities.noop, Utilities.noop); } /** This internal save method performs the actual saving and returns a promise, not to be bound to any buttons but used by other bound methods */ diff --git a/src/Umbraco.Web.UI.Client/src/views/packages/views/created.controller.js b/src/Umbraco.Web.UI.Client/src/views/packages/views/created.controller.js index 295057e609..503d85fe8d 100644 --- a/src/Umbraco.Web.UI.Client/src/views/packages/views/created.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/packages/views/created.controller.js @@ -15,7 +15,7 @@ packageResource.getAllCreated().then(createdPackages => { vm.createdPackages = createdPackages; - }, angular.noop); + }, Utilities.noop); } diff --git a/src/Umbraco.Web.UI.Client/src/views/partialviewmacros/edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/partialviewmacros/edit.controller.js index aa37a91919..2f7d879607 100644 --- a/src/Umbraco.Web.UI.Client/src/views/partialviewmacros/edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/partialviewmacros/edit.controller.js @@ -80,7 +80,7 @@ activate: false }); completeSave(saved); - }, angular.noop); + }, Utilities.noop); } else { diff --git a/src/Umbraco.Web.UI.Client/src/views/users/user.controller.js b/src/Umbraco.Web.UI.Client/src/views/users/user.controller.js index 9534eea9fd..56d01a8604 100644 --- a/src/Umbraco.Web.UI.Client/src/views/users/user.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/users/user.controller.js @@ -168,7 +168,7 @@ extendedSave(saved).then(function(result) { //if all is good, then reset the form formHelper.resetForm({ scope: $scope }); - }, angular.noop); + }, Utilities.noop); vm.user = _.omit(saved, "navigation"); //restore diff --git a/src/Umbraco.Web.UI.Client/src/views/users/views/groups/groups.controller.js b/src/Umbraco.Web.UI.Client/src/views/users/views/groups/groups.controller.js index 984866dca1..31c52a344b 100644 --- a/src/Umbraco.Web.UI.Client/src/views/users/views/groups/groups.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/users/views/groups/groups.controller.js @@ -112,7 +112,7 @@ userGroupsResource.deleteUserGroups(_.pluck(vm.selection, "id")).then(function (data) { clearSelection(); onInit(); - }, angular.noop); + }, Utilities.noop); overlayService.close(); } }; diff --git a/src/Umbraco.Web.UI.Client/src/views/users/views/users/users.controller.js b/src/Umbraco.Web.UI.Client/src/views/users/views/users/users.controller.js index f3c28fdb9d..4ac385921e 100644 --- a/src/Umbraco.Web.UI.Client/src/views/users/views/users/users.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/users/views/users/users.controller.js @@ -386,7 +386,7 @@ vm.selectedBulkUserGroups = []; editorService.close(); clearSelection(); - }, angular.noop); + }, Utilities.noop); }, close: function () { vm.selectedBulkUserGroups = []; From 994c379a946fa7e36eed669e429ab26fa977b00c Mon Sep 17 00:00:00 2001 From: Matthew Wise <6782865+Matthew-Wise@users.noreply.github.com> Date: Wed, 4 Mar 2020 10:36:49 +0000 Subject: [PATCH 32/73] Removed duplications of tree routing --- .../src/common/services/navigation.service.js | 49 +++++++++++-------- src/Umbraco.Web.UI.Client/src/routes.js | 44 ++--------------- 2 files changed, 33 insertions(+), 60 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js b/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js index 8d1caab850..ab9cfb63d2 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js @@ -578,30 +578,12 @@ function navigationService($routeParams, $location, $q, $injector, eventsService if (args.action.metaData["actionView"]) { templateUrl = args.action.metaData["actionView"]; } - else { - - //by convention we will look into the /views/{treetype}/{action}.html - // for example: /views/content/create.html - - //we will also check for a 'packageName' for the current tree, if it exists then the convention will be: - // for example: /App_Plugins/{mypackage}/backoffice/{treetype}/create.html - + else { var treeAlias = treeService.getTreeAlias(args.node); - var packageTreeFolder = treeService.getTreePackageFolder(treeAlias); - if (!treeAlias) { throw "Could not get tree alias for node " + args.node.id; - } - - if (packageTreeFolder) { - templateUrl = Umbraco.Sys.ServerVariables.umbracoSettings.appPluginsPath + - "/" + packageTreeFolder + - "/backoffice/" + treeAlias + "/" + args.action.alias + ".html"; - } - else { - templateUrl = "views/" + treeAlias + "/" + args.action.alias + ".html"; - } - + } + templateUrl = this.getTreeTemplateUrl(treeAlias, args.action.alias); } setMode("dialog"); @@ -611,6 +593,31 @@ function navigationService($routeParams, $location, $q, $injector, eventsService } }, + /** + * @ngdoc method + * @name umbraco.services.navigationService#getTreeTemplateUrl + * @methodOf umbraco.services.navigationService + * + * @param {string} treeAlias the alias of the tree to look up + * @param {string} action the view file name + * @description + * creates the templateUrl based on treeAlias and action + * by convention we will look into the /views/{treetype}/{action}.html + * for example: /views/content/create.html + * we will also check for a 'packageName' for the current tree, if it exists then the convention will be: + * for example: /App_Plugins/{mypackage}/backoffice/{treetype}/create.html + */ + getTreeTemplateUrl: function(treeAlias, action) { + var packageTreeFolder = treeService.getTreePackageFolder(treeAlias); + if (packageTreeFolder) { + return Umbraco.Sys.ServerVariables.umbracoSettings.appPluginsPath + + "/" + packageTreeFolder + + "/backoffice/" + treeAlias + "/" + action + ".html"; + } + else { + return "views/" + treeAlias + "/" + action + ".html"; + } + }, /** * @ngdoc method diff --git a/src/Umbraco.Web.UI.Client/src/routes.js b/src/Umbraco.Web.UI.Client/src/routes.js index 556a4d6aef..93122792d3 100644 --- a/src/Umbraco.Web.UI.Client/src/routes.js +++ b/src/Umbraco.Web.UI.Client/src/routes.js @@ -154,7 +154,7 @@ app.config(function ($routeProvider) { //This allows us to dynamically change the template for this route since you cannot inject services into the templateUrl method. template: "
    ", //This controller will execute for this route, then we replace the template dynamically based on the current tree. - controller: function ($scope, $routeParams, treeService) { + controller: function ($scope, $routeParams, navigationService) { if (!$routeParams.method) { $scope.templateUrl = "views/common/dashboard.html"; @@ -176,24 +176,7 @@ app.config(function ($routeProvider) { $scope.templateUrl = "views/users/overview.html"; return; } - - // Here we need to figure out if this route is for a user's package tree and if so then we need - // to change it's convention view path to: - // /App_Plugins/{mypackage}/backoffice/{treetype}/{method}.html - - // otherwise if it is a core tree we use the core paths: - // views/{treetype}/{method}.html - - var packageTreeFolder = treeService.getTreePackageFolder($routeParams.tree); - - if (packageTreeFolder) { - $scope.templateUrl = (Umbraco.Sys.ServerVariables.umbracoSettings.appPluginsPath + - "/" + packageTreeFolder + - "/backoffice/" + $routeParams.tree + "/" + $routeParams.method + ".html"); - } - else { - $scope.templateUrl = ('views/' + $routeParams.tree + '/' + $routeParams.method + '.html'); - } + $scope.templateUrl = navigationService.getTreeTemplateUrl($routeParams.tree, $routeParams.method); }, reloadOnSearch: false, resolve: canRoute(true) @@ -202,30 +185,13 @@ app.config(function ($routeProvider) { //This allows us to dynamically change the template for this route since you cannot inject services into the templateUrl method. template: "
    ", //This controller will execute for this route, then we replace the template dynamically based on the current tree. - controller: function ($scope, $route, $routeParams, treeService) { + controller: function ($scope, $routeParams, navigationService) { if (!$routeParams.tree || !$routeParams.method) { $scope.templateUrl = "views/common/dashboard.html"; + return; } - - // Here we need to figure out if this route is for a package tree and if so then we need - // to change it's convention view path to: - // /App_Plugins/{mypackage}/backoffice/{treetype}/{method}.html - - // otherwise if it is a core tree we use the core paths: - // views/{treetype}/{method}.html - - var packageTreeFolder = treeService.getTreePackageFolder($routeParams.tree); - - if (packageTreeFolder) { - $scope.templateUrl = (Umbraco.Sys.ServerVariables.umbracoSettings.appPluginsPath + - "/" + packageTreeFolder + - "/backoffice/" + $routeParams.tree + "/" + $routeParams.method + ".html"); - } - else { - $scope.templateUrl = ('views/' + $routeParams.tree + '/' + $routeParams.method + '.html'); - } - + $scope.templateUrl = navigationService.getTreeTemplateUrl($routeParams.tree, $routeParams.method); }, reloadOnSearch: false, reloadOnUrl: false, From 7c841eb6ac627ca4c632ce365b76af36dce98eab Mon Sep 17 00:00:00 2001 From: Matt Barlow Date: Thu, 27 Feb 2020 12:59:34 +0100 Subject: [PATCH 33/73] 7728 - show elipsis only when > length Fixed the elipsis issue, only adds an elipisis if the text length exceeds the length that is being truncated too. --- src/Umbraco.Web/HtmlStringUtilities.cs | 28 ++++++++++++++++---------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/src/Umbraco.Web/HtmlStringUtilities.cs b/src/Umbraco.Web/HtmlStringUtilities.cs index 4606a58a3a..a8cbb70019 100644 --- a/src/Umbraco.Web/HtmlStringUtilities.cs +++ b/src/Umbraco.Web/HtmlStringUtilities.cs @@ -243,21 +243,27 @@ namespace Umbraco.Web } } - if (!lengthReached && currentTextLength >= length) + if (!lengthReached) { - // if the last character added was the first of a two character unicode pair, add the second character - if (Char.IsHighSurrogate((char)ic)) + if (currentTextLength == length) { - var lowSurrogate = tr.Read(); - outputtw.Write((char)lowSurrogate); - } + // if the last character added was the first of a two character unicode pair, add the second character + if (char.IsHighSurrogate((char)ic)) + { + var lowSurrogate = tr.Read(); + outputtw.Write((char)lowSurrogate); + } - // Reached truncate limit. - if (addElipsis) - { - outputtw.Write(hellip); } - lengthReached = true; + // only add elipsis if current length greater than original length + if (currentTextLength > length) + { + if (addElipsis) + { + outputtw.Write(hellip); + } + lengthReached = true; + } } } From ffef6ed2a1e9dbad6f8237668ea7fd8e4c373672 Mon Sep 17 00:00:00 2001 From: Nathan Woulfe Date: Wed, 25 Mar 2020 03:04:38 +1000 Subject: [PATCH 34/73] 7713 - changing CSS directory breaks RTE (#7726) --- .../Implement/StylesheetRepository.cs | 11 ++++++++++- .../Repositories/StylesheetRepositoryTest.cs | 16 +++++++--------- .../rte/rte.prevalues.controller.js | 10 ++++++++-- 3 files changed, 25 insertions(+), 12 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/StylesheetRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/StylesheetRepository.cs index 4c02a8f4b5..698a9f4364 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/StylesheetRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/StylesheetRepository.cs @@ -25,8 +25,17 @@ namespace Umbraco.Core.Persistence.Repositories.Implement path = path.EnsureEndsWith(".css"); - if (FileSystem.FileExists(path) == false) + // if the css directory is changed, references to the old path can still exist (ie in RTE config) + // these old references will throw an error, which breaks the RTE + // try-catch here makes the request fail silently, and allows RTE to load correctly + try + { + if (FileSystem.FileExists(path) == false) + return null; + } catch + { return null; + } // content will be lazy-loaded when required var created = FileSystem.GetCreated(path).UtcDateTime; diff --git a/src/Umbraco.Tests/Persistence/Repositories/StylesheetRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/StylesheetRepositoryTest.cs index f427f22796..304422d1e2 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/StylesheetRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/StylesheetRepositoryTest.cs @@ -294,15 +294,13 @@ namespace Umbraco.Tests.Persistence.Repositories stylesheet = repository.Get("missing.css"); Assert.IsNull(stylesheet); - // fixed in 7.3 - 7.2.8 used to... - Assert.Throws(() => - { - stylesheet = repository.Get("\\test-path-4.css"); // outside the filesystem, does not exist - }); - Assert.Throws(() => - { - stylesheet = repository.Get("../packages.config"); // outside the filesystem, exists - }); + // #7713 changes behaviour to return null when outside the filesystem + // to accomodate changing the CSS path and not flooding the backoffice with errors + stylesheet = repository.Get("\\test-path-4.css"); // outside the filesystem, does not exist + Assert.IsNull(stylesheet); + + stylesheet = repository.Get("../packages.config"); // outside the filesystem, exists + Assert.IsNull(stylesheet); } } diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.prevalues.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.prevalues.controller.js index 41097f9e9a..47b0215dac 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.prevalues.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.prevalues.controller.js @@ -39,8 +39,14 @@ angular.module("umbraco").controller("Umbraco.PrevalueEditors.RteController", stylesheetResource.getAll().then(function(stylesheets){ $scope.stylesheets = stylesheets; - - _.each($scope.stylesheets, function (stylesheet) { + + // if the CSS directory changes, previously assigned stylesheets are retained, but will not be visible + // and will throw a 404 when loading the RTE. Remove them here. Still needs to be saved... + let cssPath = Umbraco.Sys.ServerVariables.umbracoSettings.cssPath; + $scope.model.value.stylesheets = $scope.model.value.stylesheets + .filter(sheet => sheet.startsWith(cssPath)); + + $scope.stylesheets.forEach(stylesheet => { // support both current format (full stylesheet path) and legacy format (stylesheet name only) stylesheet.selected = $scope.model.value.stylesheets.indexOf(stylesheet.path) >= 0 ||$scope.model.value.stylesheets.indexOf(stylesheet.name) >= 0; }); From d577452aff647006e01cd8742cfb60357afcbd08 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Tue, 24 Mar 2020 18:18:25 +0100 Subject: [PATCH 35/73] AB#5820 - Introduced WebProfiler for asp.net core --- .../Net/IUmbracoApplicationLifetime.cs | 4 + src/Umbraco.Core/UriExtensions.cs | 2 +- src/Umbraco.Tests/Umbraco.Tests.csproj | 2 +- .../AspNetCoreUmbracoApplicationLifetime.cs | 8 ++ ...racoRequestApplicationBuilderExtensions.cs | 21 +++ ...bracoRequestServiceCollectionExtensions.cs | 20 +++ .../Middleware/UmbracoRequestMiddleware.cs | 52 ++++++++ .../Runtime/Profiler/WebProfiler.cs | 83 ++++++++++++ .../Runtime/Profiler/WebProfilerComponent.cs | 55 ++++++++ .../Runtime/Profiler/WebProfilerComposer.cs | 8 ++ .../Runtime/Profiler/WebProfilerProvider.cs | 122 ++++++++++++++++++ .../Umbraco.Web.Common.csproj | 22 ++++ src/Umbraco.Web.UI.NetCore/Startup.cs | 4 + .../Umbraco.Web.UI.NetCore.csproj | 1 + src/Umbraco.Web.UI/Umbraco.Web.UI.csproj | 2 +- .../AspNetUmbracoApplicationLifetime.cs | 5 + .../Install/InstallSteps/NewInstallStep.cs | 35 +++-- src/Umbraco.Web/Umbraco.Web.csproj | 2 +- src/umbraco.sln | 6 + 19 files changed, 436 insertions(+), 18 deletions(-) create mode 100644 src/Umbraco.Web.Common/Extensions/UmbracoRequestApplicationBuilderExtensions.cs create mode 100644 src/Umbraco.Web.Common/Extensions/UmbracoRequestServiceCollectionExtensions.cs create mode 100644 src/Umbraco.Web.Common/Middleware/UmbracoRequestMiddleware.cs create mode 100644 src/Umbraco.Web.Common/Runtime/Profiler/WebProfiler.cs create mode 100644 src/Umbraco.Web.Common/Runtime/Profiler/WebProfilerComponent.cs create mode 100644 src/Umbraco.Web.Common/Runtime/Profiler/WebProfilerComposer.cs create mode 100644 src/Umbraco.Web.Common/Runtime/Profiler/WebProfilerProvider.cs create mode 100644 src/Umbraco.Web.Common/Umbraco.Web.Common.csproj diff --git a/src/Umbraco.Core/Net/IUmbracoApplicationLifetime.cs b/src/Umbraco.Core/Net/IUmbracoApplicationLifetime.cs index 10d5b10955..4010252571 100644 --- a/src/Umbraco.Core/Net/IUmbracoApplicationLifetime.cs +++ b/src/Umbraco.Core/Net/IUmbracoApplicationLifetime.cs @@ -1,3 +1,5 @@ +using System; + namespace Umbraco.Net { public interface IUmbracoApplicationLifetime @@ -10,5 +12,7 @@ namespace Umbraco.Net /// Terminates the current application. The application restarts the next time a request is received for it. /// void Restart(); + + event EventHandler ApplicationInit; } } diff --git a/src/Umbraco.Core/UriExtensions.cs b/src/Umbraco.Core/UriExtensions.cs index 4321aefd7f..e7be0b6500 100644 --- a/src/Umbraco.Core/UriExtensions.cs +++ b/src/Umbraco.Core/UriExtensions.cs @@ -146,7 +146,7 @@ namespace Umbraco.Core /// /// /// - internal static bool IsClientSideRequest(this Uri url) + public static bool IsClientSideRequest(this Uri url) { try { diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index 29d69db0d2..7dd0d6592b 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -99,7 +99,7 @@ - + diff --git a/src/Umbraco.Web.BackOffice/AspNetCore/AspNetCoreUmbracoApplicationLifetime.cs b/src/Umbraco.Web.BackOffice/AspNetCore/AspNetCoreUmbracoApplicationLifetime.cs index 20cfef352d..f597c40252 100644 --- a/src/Umbraco.Web.BackOffice/AspNetCore/AspNetCoreUmbracoApplicationLifetime.cs +++ b/src/Umbraco.Web.BackOffice/AspNetCore/AspNetCoreUmbracoApplicationLifetime.cs @@ -1,3 +1,4 @@ +using System; using System.Threading; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Hosting; @@ -14,6 +15,11 @@ namespace Umbraco.Web.AspNet { _httpContextAccessor = httpContextAccessor; _hostApplicationLifetime = hostApplicationLifetime; + + hostApplicationLifetime.ApplicationStarted.Register(() => + { + ApplicationInit?.Invoke(this, EventArgs.Empty); + }); } public bool IsRestarting { get; set; } @@ -32,5 +38,7 @@ namespace Umbraco.Web.AspNet Thread.CurrentPrincipal = null; _hostApplicationLifetime.StopApplication(); } + + public event EventHandler ApplicationInit; } } diff --git a/src/Umbraco.Web.Common/Extensions/UmbracoRequestApplicationBuilderExtensions.cs b/src/Umbraco.Web.Common/Extensions/UmbracoRequestApplicationBuilderExtensions.cs new file mode 100644 index 0000000000..110c09e1a9 --- /dev/null +++ b/src/Umbraco.Web.Common/Extensions/UmbracoRequestApplicationBuilderExtensions.cs @@ -0,0 +1,21 @@ +using System; +using Microsoft.AspNetCore.Builder; +using Umbraco.Web.Common.Middleware; + +namespace Umbraco.Web.Common.Extensions +{ + public static class UmbracoRequestApplicationBuilderExtensions + { + public static IApplicationBuilder UseUmbracoRequest(this IApplicationBuilder app) + { + if (app == null) + { + throw new ArgumentNullException(nameof(app)); + } + + app.UseMiddleware(); + + return app; + } + } +} diff --git a/src/Umbraco.Web.Common/Extensions/UmbracoRequestServiceCollectionExtensions.cs b/src/Umbraco.Web.Common/Extensions/UmbracoRequestServiceCollectionExtensions.cs new file mode 100644 index 0000000000..2a16b8b4f9 --- /dev/null +++ b/src/Umbraco.Web.Common/Extensions/UmbracoRequestServiceCollectionExtensions.cs @@ -0,0 +1,20 @@ +using Microsoft.Extensions.DependencyInjection; +using Umbraco.Web.Common.Middleware; + +namespace Umbraco.Web.Common.Extensions +{ + public static class UmbracoRequestServiceCollectionExtensions + { + public static IServiceCollection AddUmbracoRequest(this IServiceCollection services) + { + var umbracoRequestLifetime = new UmbracoRequestLifetime(); + + services.AddSingleton(umbracoRequestLifetime); + services.AddSingleton(umbracoRequestLifetime); + + return services; + } + + } + +} diff --git a/src/Umbraco.Web.Common/Middleware/UmbracoRequestMiddleware.cs b/src/Umbraco.Web.Common/Middleware/UmbracoRequestMiddleware.cs new file mode 100644 index 0000000000..60b6463152 --- /dev/null +++ b/src/Umbraco.Web.Common/Middleware/UmbracoRequestMiddleware.cs @@ -0,0 +1,52 @@ +using System; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; + +namespace Umbraco.Web.Common.Middleware +{ + public class UmbracoRequestMiddleware + { + private readonly RequestDelegate _next; + private readonly IUmbracoRequestLifetimeManager _umbracoRequestLifetimeManager; + public UmbracoRequestMiddleware(RequestDelegate next, IUmbracoRequestLifetimeManager umbracoRequestLifetimeManager) + { + _next = next; + _umbracoRequestLifetimeManager = umbracoRequestLifetimeManager; + } + + public async Task InvokeAsync(HttpContext context) + { + _umbracoRequestLifetimeManager.InitRequest(context); + await _next(context); + _umbracoRequestLifetimeManager.EndRequest(context); + } + } + + public interface IUmbracoRequestLifetime + { + event EventHandler RequestStart; + event EventHandler RequestEnd; + } + + public class UmbracoRequestLifetime : IUmbracoRequestLifetime, IUmbracoRequestLifetimeManager + { + public event EventHandler RequestStart; + public event EventHandler RequestEnd; + + public void InitRequest(HttpContext context) + { + RequestStart?.Invoke(this, context); + } + + public void EndRequest(HttpContext context) + { + RequestEnd?.Invoke(this, context); + } + } + + public interface IUmbracoRequestLifetimeManager + { + void InitRequest(HttpContext context); + void EndRequest(HttpContext context); + } +} diff --git a/src/Umbraco.Web.Common/Runtime/Profiler/WebProfiler.cs b/src/Umbraco.Web.Common/Runtime/Profiler/WebProfiler.cs new file mode 100644 index 0000000000..e9fe7ea332 --- /dev/null +++ b/src/Umbraco.Web.Common/Runtime/Profiler/WebProfiler.cs @@ -0,0 +1,83 @@ +using System; +using System.Threading; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Extensions; +using StackExchange.Profiling; +using Umbraco.Core; +using Umbraco.Core.Cache; +using Umbraco.Core.Logging; +using Umbraco.Web.Common.Middleware; + +namespace Umbraco.Web.Common.Runtime.Profiler +{ + public class WebProfiler : IProfiler + { + private const string BootRequestItemKey = "Umbraco.Core.Logging.WebProfiler__isBootRequest"; + private readonly IHttpContextAccessor _httpContextAccessor; + private readonly WebProfilerProvider _provider; + private readonly IUmbracoRequestLifetime _umbracoRequestLifetime; + private int _first; + + //TODO register and inject WebProfilerProvider? + public WebProfiler(IHttpContextAccessor httpContextAccessor, IRequestCache requestCache) + { + // create our own provider, which can provide a profiler even during boot + _provider = new WebProfilerProvider(requestCache); + _httpContextAccessor = httpContextAccessor; + } + + public string Render() => MiniProfiler.Current + .RenderIncludes(_httpContextAccessor.HttpContext, RenderPosition.Right).ToString(); + + public IDisposable Step(string name) => MiniProfiler.Current?.Step(name); + + public void Start() + { + MiniProfiler.StartNew(); + } + + public void Stop(bool discardResults = false) + { + MiniProfiler.Current?.Stop(discardResults); + } + + public void UmbracoApplicationBeginRequest(HttpContext context) + { + // if this is the first request, notify our own provider that this request is the boot request + var first = Interlocked.Exchange(ref _first, 1) == 0; + if (first) + { + _provider.BeginBootRequest(); + context.Items[BootRequestItemKey] = true; + // and no need to start anything, profiler is already there + } + // else start a profiler, the normal way + else if (ShouldProfile(context.Request)) + Start(); + } + + public void UmbracoApplicationEndRequest(HttpContext context) + { + // if this is the boot request, or if we should profile this request, stop + // (the boot request is always profiled, no matter what) + if (context.Items.TryGetValue(BootRequestItemKey, out var isBoot) && isBoot.Equals(true)) + { + _provider.EndBootRequest(); + Stop(); + } + else if (ShouldProfile(context.Request)) + { + Stop(); + } + } + + private static bool ShouldProfile(HttpRequest request) + { + if (new Uri(request.GetEncodedUrl(), UriKind.RelativeOrAbsolute).IsClientSideRequest()) return false; + if (bool.TryParse(request.Query["umbDebug"], out var umbDebug)) return umbDebug; + if (bool.TryParse(request.Headers["X-UMB-DEBUG"], out var xUmbDebug)) return xUmbDebug; + if (bool.TryParse(request.Cookies["UMB-DEBUG"], out var cUmbDebug)) return cUmbDebug; + return false; + } + } +} diff --git a/src/Umbraco.Web.Common/Runtime/Profiler/WebProfilerComponent.cs b/src/Umbraco.Web.Common/Runtime/Profiler/WebProfilerComponent.cs new file mode 100644 index 0000000000..425996a765 --- /dev/null +++ b/src/Umbraco.Web.Common/Runtime/Profiler/WebProfilerComponent.cs @@ -0,0 +1,55 @@ +using System; +using Umbraco.Core.Composing; +using Umbraco.Core.Logging; +using Umbraco.Net; +using Umbraco.Web.Common.Middleware; + +namespace Umbraco.Web.Common.Runtime.Profiler +{ + internal sealed class WebProfilerComponent : IComponent + { + private readonly bool _profile; + private readonly WebProfiler _profiler; + private readonly IUmbracoApplicationLifetime _umbracoApplicationLifetime; + private readonly IUmbracoRequestLifetime _umbracoRequestLifetime; + + public WebProfilerComponent(IProfiler profiler, ILogger logger, IUmbracoRequestLifetime umbracoRequestLifetime, + IUmbracoApplicationLifetime umbracoApplicationLifetime) + { + _umbracoRequestLifetime = umbracoRequestLifetime; + _umbracoApplicationLifetime = umbracoApplicationLifetime; + _profile = true; + + // although registered in WebRuntime.Compose, ensure that we have not + // been replaced by another component, and we are still "the" profiler + _profiler = profiler as WebProfiler; + if (_profiler != null) return; + + // if VoidProfiler was registered, let it be known + if (profiler is VoidProfiler) + logger.Info( + "Profiler is VoidProfiler, not profiling (must run debug mode to profile)."); + _profile = false; + } + + public void Initialize() + { + if (!_profile) return; + + // bind to ApplicationInit - ie execute the application initialization for *each* application + // it would be a mistake to try and bind to the current application events + _umbracoApplicationLifetime.ApplicationInit += InitializeApplication; + } + + public void Terminate() + { + } + + private void InitializeApplication(object sender, EventArgs args) + { + _umbracoRequestLifetime.RequestStart += + (sender, context) => _profiler.UmbracoApplicationBeginRequest(context); + _umbracoRequestLifetime.RequestEnd += (sender, context) => _profiler.UmbracoApplicationEndRequest(context); + } + } +} diff --git a/src/Umbraco.Web.Common/Runtime/Profiler/WebProfilerComposer.cs b/src/Umbraco.Web.Common/Runtime/Profiler/WebProfilerComposer.cs new file mode 100644 index 0000000000..688a3e5c28 --- /dev/null +++ b/src/Umbraco.Web.Common/Runtime/Profiler/WebProfilerComposer.cs @@ -0,0 +1,8 @@ +using Umbraco.Core.Composing; + +namespace Umbraco.Web.Common.Runtime.Profiler +{ + internal class WebProfilerComposer : ComponentComposer, ICoreComposer + { + } +} diff --git a/src/Umbraco.Web.Common/Runtime/Profiler/WebProfilerProvider.cs b/src/Umbraco.Web.Common/Runtime/Profiler/WebProfilerProvider.cs new file mode 100644 index 0000000000..3ba5f762fe --- /dev/null +++ b/src/Umbraco.Web.Common/Runtime/Profiler/WebProfilerProvider.cs @@ -0,0 +1,122 @@ +using System; +using System.Threading; +using StackExchange.Profiling; +using StackExchange.Profiling.Internal; +using Umbraco.Core.Cache; + +namespace Umbraco.Web.Common.Runtime.Profiler +{ + public class WebProfilerProvider : DefaultProfilerProvider + { + private readonly ReaderWriterLockSlim _locker = new ReaderWriterLockSlim(); + private readonly IRequestCache _requestCache; + private volatile BootPhase _bootPhase; + private int _first; + private MiniProfiler _startupProfiler; + + public WebProfilerProvider(IRequestCache requestCache) + { + _requestCache = requestCache; + // booting... + _bootPhase = BootPhase.Boot; + } + + + /// + /// Gets the current profiler. + /// + /// + /// If the boot phase is not Booted, then this will return the startup profiler (this), otherwise + /// returns the base class + /// + public override MiniProfiler CurrentProfiler + { + get + { + // if not booting then just use base (fast) + // no lock, _bootPhase is volatile + if (_bootPhase == BootPhase.Booted) + return base.CurrentProfiler; + + // else + try + { + var current = base.CurrentProfiler; + return current ?? _startupProfiler; + } + catch + { + return _startupProfiler; + } + } + } + + public void BeginBootRequest() + { + _locker.EnterWriteLock(); + try + { + if (_bootPhase != BootPhase.Boot) + throw new InvalidOperationException("Invalid boot phase."); + _bootPhase = BootPhase.BootRequest; + + //TODO is this necessary? :mini-profiler: seems like a magic string + // assign the profiler to be the current MiniProfiler for the request + // is's already active, starting and all + _requestCache.Set(":mini-profiler:", _startupProfiler); + } + finally + { + _locker.ExitWriteLock(); + } + } + + public void EndBootRequest() + { + _locker.EnterWriteLock(); + try + { + if (_bootPhase != BootPhase.BootRequest) + throw new InvalidOperationException("Invalid boot phase."); + _bootPhase = BootPhase.Booted; + + _startupProfiler = null; + } + finally + { + _locker.ExitWriteLock(); + } + } + + /// + /// Starts a new MiniProfiler. + /// + /// + /// + /// This is called when WebProfiler calls MiniProfiler.Start() so, + /// - as a result of WebRuntime starting the WebProfiler, and + /// - assuming profiling is enabled, on every BeginRequest that should be profiled, + /// - except for the very first one which is the boot request. + /// + /// + public override MiniProfiler Start(string profilerName, MiniProfilerBaseOptions options) + { + var first = Interlocked.Exchange(ref _first, 1) == 0; + if (first == false) return base.Start(profilerName, options); + + _startupProfiler = new MiniProfiler("StartupProfiler", options); + CurrentProfiler = _startupProfiler; + return _startupProfiler; + } + + /// + /// Indicates the boot phase. + /// + private enum BootPhase + { + Boot = 0, // boot phase (before the 1st request even begins) + BootRequest = 1, // request boot phase (during the 1st request) + Booted = 2 // done booting + } + } +} diff --git a/src/Umbraco.Web.Common/Umbraco.Web.Common.csproj b/src/Umbraco.Web.Common/Umbraco.Web.Common.csproj new file mode 100644 index 0000000000..cb9ca9156a --- /dev/null +++ b/src/Umbraco.Web.Common/Umbraco.Web.Common.csproj @@ -0,0 +1,22 @@ + + + + netcoreapp3.1 + Library + 8 + + + + + + + + + + + + + + + + diff --git a/src/Umbraco.Web.UI.NetCore/Startup.cs b/src/Umbraco.Web.UI.NetCore/Startup.cs index 9ef4985aea..be81261d2a 100644 --- a/src/Umbraco.Web.UI.NetCore/Startup.cs +++ b/src/Umbraco.Web.UI.NetCore/Startup.cs @@ -9,6 +9,7 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Umbraco.Web.BackOffice.AspNetCore; +using Umbraco.Web.Common.Extensions; using Umbraco.Web.Website.AspNetCore; @@ -21,7 +22,9 @@ namespace Umbraco.Web.UI.BackOffice // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940 public void ConfigureServices(IServiceCollection services) { + services.AddUmbracoConfiguration(); + services.AddUmbracoRequest(); services.AddUmbracoWebsite(); services.AddUmbracoBackOffice(); } @@ -29,6 +32,7 @@ namespace Umbraco.Web.UI.BackOffice // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { + app.UseUmbracoRequest(); if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); diff --git a/src/Umbraco.Web.UI.NetCore/Umbraco.Web.UI.NetCore.csproj b/src/Umbraco.Web.UI.NetCore/Umbraco.Web.UI.NetCore.csproj index ca7c3e26fa..abfb172763 100644 --- a/src/Umbraco.Web.UI.NetCore/Umbraco.Web.UI.NetCore.csproj +++ b/src/Umbraco.Web.UI.NetCore/Umbraco.Web.UI.NetCore.csproj @@ -7,6 +7,7 @@ + diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index fe11a8d9aa..0c0e6aee50 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -101,7 +101,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive all - + 3.4.0 diff --git a/src/Umbraco.Web/AspNet/AspNetUmbracoApplicationLifetime.cs b/src/Umbraco.Web/AspNet/AspNetUmbracoApplicationLifetime.cs index 245e8ea374..f5ebc03cd0 100644 --- a/src/Umbraco.Web/AspNet/AspNetUmbracoApplicationLifetime.cs +++ b/src/Umbraco.Web/AspNet/AspNetUmbracoApplicationLifetime.cs @@ -1,3 +1,4 @@ +using System; using System.Threading; using System.Web; using Umbraco.Net; @@ -11,6 +12,8 @@ namespace Umbraco.Web.AspNet public AspNetUmbracoApplicationLifetime(IHttpContextAccessor httpContextAccessor) { _httpContextAccessor = httpContextAccessor; + + UmbracoApplicationBase.ApplicationInit += ApplicationInit; } public bool IsRestarting { get; set; } @@ -30,5 +33,7 @@ namespace Umbraco.Web.AspNet Thread.CurrentPrincipal = null; HttpRuntime.UnloadAppDomain(); } + + public event EventHandler ApplicationInit; } } diff --git a/src/Umbraco.Web/Install/InstallSteps/NewInstallStep.cs b/src/Umbraco.Web/Install/InstallSteps/NewInstallStep.cs index f5a2ca8ac8..3f81c0d589 100644 --- a/src/Umbraco.Web/Install/InstallSteps/NewInstallStep.cs +++ b/src/Umbraco.Web/Install/InstallSteps/NewInstallStep.cs @@ -56,20 +56,7 @@ namespace Umbraco.Web.Install.InstallSteps throw new InvalidOperationException("Could not find the super user!"); } - var userManager = _httpContextAccessor.GetRequiredHttpContext().GetOwinContext().GetBackOfficeUserManager(); - var membershipUser = await userManager.FindByIdAsync(Constants.Security.SuperUserId); - if (membershipUser == null) - { - throw new InvalidOperationException($"No user found in membership provider with id of {Constants.Security.SuperUserId}."); - } - - //To change the password here we actually need to reset it since we don't have an old one to use to change - var resetToken = await userManager.GeneratePasswordResetTokenAsync(membershipUser.Id); - var resetResult = await userManager.ChangePasswordWithResetAsync(membershipUser.Id, resetToken, user.Password.Trim()); - if (!resetResult.Succeeded) - { - throw new InvalidOperationException("Could not reset password: " + string.Join(", ", resetResult.Errors)); - } + await ResetAdminPassword(user.Password); admin.Email = user.Email.Trim(); admin.Name = user.Name.Trim(); @@ -95,6 +82,26 @@ namespace Umbraco.Web.Install.InstallSteps return null; } + private async Task ResetAdminPassword(string clearTextPassword) + { + var userManager = _httpContextAccessor.GetRequiredHttpContext().GetOwinContext().GetBackOfficeUserManager(); + var membershipUser = await userManager.FindByIdAsync(Constants.Security.SuperUserId); + if (membershipUser == null) + { + throw new InvalidOperationException( + $"No user found in membership provider with id of {Constants.Security.SuperUserId}."); + } + + //To change the password here we actually need to reset it since we don't have an old one to use to change + var resetToken = await userManager.GeneratePasswordResetTokenAsync(membershipUser.Id); + var resetResult = + await userManager.ChangePasswordWithResetAsync(membershipUser.Id, resetToken, clearTextPassword.Trim()); + if (!resetResult.Succeeded) + { + throw new InvalidOperationException("Could not reset password: " + string.Join(", ", resetResult.Errors)); + } + } + /// /// Return a custom view model for this step /// diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index e8aaecdf4d..b9892e1c31 100755 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -92,7 +92,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive all - + diff --git a/src/umbraco.sln b/src/umbraco.sln index be363ef2e6..11098abdec 100644 --- a/src/umbraco.sln +++ b/src/umbraco.sln @@ -123,6 +123,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Umbraco.Web.Website", "Umbr EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Umbraco.Tests.Common", "Umbraco.Tests.Common\Umbraco.Tests.Common.csproj", "{A499779C-1B3B-48A8-B551-458E582E6E96}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Umbraco.Web.Common", "Umbraco.Web.Common\Umbraco.Web.Common.csproj", "{839D3048-9718-4907-BDE0-7CD63D364383}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -195,6 +197,10 @@ Global {A499779C-1B3B-48A8-B551-458E582E6E96}.Debug|Any CPU.Build.0 = Debug|Any CPU {A499779C-1B3B-48A8-B551-458E582E6E96}.Release|Any CPU.ActiveCfg = Release|Any CPU {A499779C-1B3B-48A8-B551-458E582E6E96}.Release|Any CPU.Build.0 = Release|Any CPU + {839D3048-9718-4907-BDE0-7CD63D364383}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {839D3048-9718-4907-BDE0-7CD63D364383}.Debug|Any CPU.Build.0 = Debug|Any CPU + {839D3048-9718-4907-BDE0-7CD63D364383}.Release|Any CPU.ActiveCfg = Release|Any CPU + {839D3048-9718-4907-BDE0-7CD63D364383}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE From 2ec08610518ea2cdc9a03d8c84d95530eba0365c Mon Sep 17 00:00:00 2001 From: Joe Delly Date: Tue, 24 Mar 2020 13:35:11 -0400 Subject: [PATCH 36/73] Fix for exception throw when validating URLs (#7701) --- src/Umbraco.Core/StringExtensions.cs | 15 +++++++++++++++ .../Strings/StringExtensionsTests.cs | 18 ++++++++++++++++++ .../PropertyEditors/UploadFileTypeValidator.cs | 2 +- 3 files changed, 34 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Core/StringExtensions.cs b/src/Umbraco.Core/StringExtensions.cs index 4451fdbba7..c37f8bdf35 100644 --- a/src/Umbraco.Core/StringExtensions.cs +++ b/src/Umbraco.Core/StringExtensions.cs @@ -79,6 +79,21 @@ namespace Umbraco.Core } + /// + /// Determines the extension of the path or URL + /// + /// + /// Extension of the file + public static string GetFileExtension(this string file) + { + //Find any characters between the last . and the start of a query string or the end of the string + const string pattern = @"(?\.[^\.\?]+)(\?.*|$)"; + var match = Regex.Match(file, pattern); + return match.Success + ? match.Groups["extension"].Value + : string.Empty; + } + /// /// Based on the input string, this will detect if the string is a JS path or a JS snippet. /// If a path cannot be determined, then it is assumed to be a snippet the original text is returned diff --git a/src/Umbraco.Tests/Strings/StringExtensionsTests.cs b/src/Umbraco.Tests/Strings/StringExtensionsTests.cs index 4c365d733f..39ffeed205 100644 --- a/src/Umbraco.Tests/Strings/StringExtensionsTests.cs +++ b/src/Umbraco.Tests/Strings/StringExtensionsTests.cs @@ -70,6 +70,24 @@ namespace Umbraco.Tests.Strings Assert.AreEqual(stripped, result); } + [TestCase("../wtf.js?x=wtf", ".js")] + [TestCase(".htaccess", ".htaccess")] + [TestCase("path/to/file/image.png", ".png")] + [TestCase("c:\\abc\\def\\ghi.jkl", ".jkl")] + [TestCase("/root/folder.name/file.ext", ".ext")] + [TestCase("http://www.domain.com/folder/name/file.txt", ".txt")] + [TestCase("i/don't\\have\\an/extension", "")] + [TestCase("https://some.where/path/to/file.ext?query=this&more=that", ".ext")] + [TestCase("double_query.string/file.ext?query=abc?something.else", ".ext")] + [TestCase("test.tar.gz", ".gz")] + [TestCase("wierd.file,but._legal", "._legal")] + [TestCase("one_char.x", ".x")] + public void Get_File_Extension(string input, string result) + { + var extension = input.GetFileExtension(); + Assert.AreEqual(result, extension); + } + [TestCase("'+alert(1234)+'", "+alert1234+")] [TestCase("'+alert(56+78)+'", "+alert56+78+")] [TestCase("{{file}}", "file")] diff --git a/src/Umbraco.Web/PropertyEditors/UploadFileTypeValidator.cs b/src/Umbraco.Web/PropertyEditors/UploadFileTypeValidator.cs index 6855ab3bb8..8d7bd29889 100644 --- a/src/Umbraco.Web/PropertyEditors/UploadFileTypeValidator.cs +++ b/src/Umbraco.Web/PropertyEditors/UploadFileTypeValidator.cs @@ -48,7 +48,7 @@ namespace Umbraco.Web.PropertyEditors internal static bool IsValidFileExtension(string fileName) { if (fileName.IndexOf('.') <= 0) return false; - var extension = new FileInfo(fileName).Extension.TrimStart("."); + var extension = fileName.GetFileExtension().TrimStart("."); return Current.Configs.Settings().Content.IsFileAllowedForUpload(extension); } } From 9339e1d999e00221925a8b8514ca21bb1d37c6e7 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Tue, 24 Mar 2020 18:18:25 +0100 Subject: [PATCH 37/73] AB#5820 - Introduced WebProfiler for asp.net core --- .../Net/IUmbracoApplicationLifetime.cs | 4 + src/Umbraco.Core/UriExtensions.cs | 2 +- src/Umbraco.Tests/Umbraco.Tests.csproj | 2 +- .../AspNetCoreUmbracoApplicationLifetime.cs | 8 ++ ...racoRequestApplicationBuilderExtensions.cs | 21 ++++ ...bracoRequestServiceCollectionExtensions.cs | 20 +++ .../Middleware/UmbracoRequestMiddleware.cs | 52 ++++++++ .../Runtime/Profiler/WebProfiler.cs | 81 ++++++++++++ .../Runtime/Profiler/WebProfilerComponent.cs | 55 ++++++++ .../Runtime/Profiler/WebProfilerComposer.cs | 8 ++ .../Runtime/Profiler/WebProfilerProvider.cs | 119 ++++++++++++++++++ .../Umbraco.Web.Common.csproj | 22 ++++ src/Umbraco.Web.UI.NetCore/Startup.cs | 4 + .../Umbraco.Web.UI.NetCore.csproj | 1 + src/Umbraco.Web.UI/Umbraco.Web.UI.csproj | 2 +- .../AspNetUmbracoApplicationLifetime.cs | 5 + .../Install/InstallSteps/NewInstallStep.cs | 35 +++--- src/Umbraco.Web/Umbraco.Web.csproj | 2 +- src/umbraco.sln | 6 + 19 files changed, 431 insertions(+), 18 deletions(-) create mode 100644 src/Umbraco.Web.Common/Extensions/UmbracoRequestApplicationBuilderExtensions.cs create mode 100644 src/Umbraco.Web.Common/Extensions/UmbracoRequestServiceCollectionExtensions.cs create mode 100644 src/Umbraco.Web.Common/Middleware/UmbracoRequestMiddleware.cs create mode 100644 src/Umbraco.Web.Common/Runtime/Profiler/WebProfiler.cs create mode 100644 src/Umbraco.Web.Common/Runtime/Profiler/WebProfilerComponent.cs create mode 100644 src/Umbraco.Web.Common/Runtime/Profiler/WebProfilerComposer.cs create mode 100644 src/Umbraco.Web.Common/Runtime/Profiler/WebProfilerProvider.cs create mode 100644 src/Umbraco.Web.Common/Umbraco.Web.Common.csproj diff --git a/src/Umbraco.Core/Net/IUmbracoApplicationLifetime.cs b/src/Umbraco.Core/Net/IUmbracoApplicationLifetime.cs index 10d5b10955..4010252571 100644 --- a/src/Umbraco.Core/Net/IUmbracoApplicationLifetime.cs +++ b/src/Umbraco.Core/Net/IUmbracoApplicationLifetime.cs @@ -1,3 +1,5 @@ +using System; + namespace Umbraco.Net { public interface IUmbracoApplicationLifetime @@ -10,5 +12,7 @@ namespace Umbraco.Net /// Terminates the current application. The application restarts the next time a request is received for it. /// void Restart(); + + event EventHandler ApplicationInit; } } diff --git a/src/Umbraco.Core/UriExtensions.cs b/src/Umbraco.Core/UriExtensions.cs index 4321aefd7f..e7be0b6500 100644 --- a/src/Umbraco.Core/UriExtensions.cs +++ b/src/Umbraco.Core/UriExtensions.cs @@ -146,7 +146,7 @@ namespace Umbraco.Core /// /// /// - internal static bool IsClientSideRequest(this Uri url) + public static bool IsClientSideRequest(this Uri url) { try { diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index 29d69db0d2..7dd0d6592b 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -99,7 +99,7 @@ - + diff --git a/src/Umbraco.Web.BackOffice/AspNetCore/AspNetCoreUmbracoApplicationLifetime.cs b/src/Umbraco.Web.BackOffice/AspNetCore/AspNetCoreUmbracoApplicationLifetime.cs index 20cfef352d..f597c40252 100644 --- a/src/Umbraco.Web.BackOffice/AspNetCore/AspNetCoreUmbracoApplicationLifetime.cs +++ b/src/Umbraco.Web.BackOffice/AspNetCore/AspNetCoreUmbracoApplicationLifetime.cs @@ -1,3 +1,4 @@ +using System; using System.Threading; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Hosting; @@ -14,6 +15,11 @@ namespace Umbraco.Web.AspNet { _httpContextAccessor = httpContextAccessor; _hostApplicationLifetime = hostApplicationLifetime; + + hostApplicationLifetime.ApplicationStarted.Register(() => + { + ApplicationInit?.Invoke(this, EventArgs.Empty); + }); } public bool IsRestarting { get; set; } @@ -32,5 +38,7 @@ namespace Umbraco.Web.AspNet Thread.CurrentPrincipal = null; _hostApplicationLifetime.StopApplication(); } + + public event EventHandler ApplicationInit; } } diff --git a/src/Umbraco.Web.Common/Extensions/UmbracoRequestApplicationBuilderExtensions.cs b/src/Umbraco.Web.Common/Extensions/UmbracoRequestApplicationBuilderExtensions.cs new file mode 100644 index 0000000000..110c09e1a9 --- /dev/null +++ b/src/Umbraco.Web.Common/Extensions/UmbracoRequestApplicationBuilderExtensions.cs @@ -0,0 +1,21 @@ +using System; +using Microsoft.AspNetCore.Builder; +using Umbraco.Web.Common.Middleware; + +namespace Umbraco.Web.Common.Extensions +{ + public static class UmbracoRequestApplicationBuilderExtensions + { + public static IApplicationBuilder UseUmbracoRequest(this IApplicationBuilder app) + { + if (app == null) + { + throw new ArgumentNullException(nameof(app)); + } + + app.UseMiddleware(); + + return app; + } + } +} diff --git a/src/Umbraco.Web.Common/Extensions/UmbracoRequestServiceCollectionExtensions.cs b/src/Umbraco.Web.Common/Extensions/UmbracoRequestServiceCollectionExtensions.cs new file mode 100644 index 0000000000..2a16b8b4f9 --- /dev/null +++ b/src/Umbraco.Web.Common/Extensions/UmbracoRequestServiceCollectionExtensions.cs @@ -0,0 +1,20 @@ +using Microsoft.Extensions.DependencyInjection; +using Umbraco.Web.Common.Middleware; + +namespace Umbraco.Web.Common.Extensions +{ + public static class UmbracoRequestServiceCollectionExtensions + { + public static IServiceCollection AddUmbracoRequest(this IServiceCollection services) + { + var umbracoRequestLifetime = new UmbracoRequestLifetime(); + + services.AddSingleton(umbracoRequestLifetime); + services.AddSingleton(umbracoRequestLifetime); + + return services; + } + + } + +} diff --git a/src/Umbraco.Web.Common/Middleware/UmbracoRequestMiddleware.cs b/src/Umbraco.Web.Common/Middleware/UmbracoRequestMiddleware.cs new file mode 100644 index 0000000000..60b6463152 --- /dev/null +++ b/src/Umbraco.Web.Common/Middleware/UmbracoRequestMiddleware.cs @@ -0,0 +1,52 @@ +using System; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; + +namespace Umbraco.Web.Common.Middleware +{ + public class UmbracoRequestMiddleware + { + private readonly RequestDelegate _next; + private readonly IUmbracoRequestLifetimeManager _umbracoRequestLifetimeManager; + public UmbracoRequestMiddleware(RequestDelegate next, IUmbracoRequestLifetimeManager umbracoRequestLifetimeManager) + { + _next = next; + _umbracoRequestLifetimeManager = umbracoRequestLifetimeManager; + } + + public async Task InvokeAsync(HttpContext context) + { + _umbracoRequestLifetimeManager.InitRequest(context); + await _next(context); + _umbracoRequestLifetimeManager.EndRequest(context); + } + } + + public interface IUmbracoRequestLifetime + { + event EventHandler RequestStart; + event EventHandler RequestEnd; + } + + public class UmbracoRequestLifetime : IUmbracoRequestLifetime, IUmbracoRequestLifetimeManager + { + public event EventHandler RequestStart; + public event EventHandler RequestEnd; + + public void InitRequest(HttpContext context) + { + RequestStart?.Invoke(this, context); + } + + public void EndRequest(HttpContext context) + { + RequestEnd?.Invoke(this, context); + } + } + + public interface IUmbracoRequestLifetimeManager + { + void InitRequest(HttpContext context); + void EndRequest(HttpContext context); + } +} diff --git a/src/Umbraco.Web.Common/Runtime/Profiler/WebProfiler.cs b/src/Umbraco.Web.Common/Runtime/Profiler/WebProfiler.cs new file mode 100644 index 0000000000..4e1a7c7d84 --- /dev/null +++ b/src/Umbraco.Web.Common/Runtime/Profiler/WebProfiler.cs @@ -0,0 +1,81 @@ +using System; +using System.Threading; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Extensions; +using StackExchange.Profiling; +using Umbraco.Core; +using Umbraco.Core.Cache; +using Umbraco.Core.Logging; +using Umbraco.Web.Common.Middleware; + +namespace Umbraco.Web.Common.Runtime.Profiler +{ + public class WebProfiler : IProfiler + { + private const string BootRequestItemKey = "Umbraco.Core.Logging.WebProfiler__isBootRequest"; + private readonly IHttpContextAccessor _httpContextAccessor; + private readonly WebProfilerProvider _provider; + private int _first; + + public WebProfiler(IHttpContextAccessor httpContextAccessor) + { + // create our own provider, which can provide a profiler even during boot + _provider = new WebProfilerProvider(); + _httpContextAccessor = httpContextAccessor; + } + + public string Render() => MiniProfiler.Current + .RenderIncludes(_httpContextAccessor.HttpContext, RenderPosition.Right).ToString(); + + public IDisposable Step(string name) => MiniProfiler.Current?.Step(name); + + public void Start() + { + MiniProfiler.StartNew(); + } + + public void Stop(bool discardResults = false) + { + MiniProfiler.Current?.Stop(discardResults); + } + + public void UmbracoApplicationBeginRequest(HttpContext context) + { + // if this is the first request, notify our own provider that this request is the boot request + var first = Interlocked.Exchange(ref _first, 1) == 0; + if (first) + { + _provider.BeginBootRequest(); + context.Items[BootRequestItemKey] = true; + // and no need to start anything, profiler is already there + } + // else start a profiler, the normal way + else if (ShouldProfile(context.Request)) + Start(); + } + + public void UmbracoApplicationEndRequest(HttpContext context) + { + // if this is the boot request, or if we should profile this request, stop + // (the boot request is always profiled, no matter what) + if (context.Items.TryGetValue(BootRequestItemKey, out var isBoot) && isBoot.Equals(true)) + { + _provider.EndBootRequest(); + Stop(); + } + else if (ShouldProfile(context.Request)) + { + Stop(); + } + } + + private static bool ShouldProfile(HttpRequest request) + { + if (new Uri(request.GetEncodedUrl(), UriKind.RelativeOrAbsolute).IsClientSideRequest()) return false; + if (bool.TryParse(request.Query["umbDebug"], out var umbDebug)) return umbDebug; + if (bool.TryParse(request.Headers["X-UMB-DEBUG"], out var xUmbDebug)) return xUmbDebug; + if (bool.TryParse(request.Cookies["UMB-DEBUG"], out var cUmbDebug)) return cUmbDebug; + return false; + } + } +} diff --git a/src/Umbraco.Web.Common/Runtime/Profiler/WebProfilerComponent.cs b/src/Umbraco.Web.Common/Runtime/Profiler/WebProfilerComponent.cs new file mode 100644 index 0000000000..425996a765 --- /dev/null +++ b/src/Umbraco.Web.Common/Runtime/Profiler/WebProfilerComponent.cs @@ -0,0 +1,55 @@ +using System; +using Umbraco.Core.Composing; +using Umbraco.Core.Logging; +using Umbraco.Net; +using Umbraco.Web.Common.Middleware; + +namespace Umbraco.Web.Common.Runtime.Profiler +{ + internal sealed class WebProfilerComponent : IComponent + { + private readonly bool _profile; + private readonly WebProfiler _profiler; + private readonly IUmbracoApplicationLifetime _umbracoApplicationLifetime; + private readonly IUmbracoRequestLifetime _umbracoRequestLifetime; + + public WebProfilerComponent(IProfiler profiler, ILogger logger, IUmbracoRequestLifetime umbracoRequestLifetime, + IUmbracoApplicationLifetime umbracoApplicationLifetime) + { + _umbracoRequestLifetime = umbracoRequestLifetime; + _umbracoApplicationLifetime = umbracoApplicationLifetime; + _profile = true; + + // although registered in WebRuntime.Compose, ensure that we have not + // been replaced by another component, and we are still "the" profiler + _profiler = profiler as WebProfiler; + if (_profiler != null) return; + + // if VoidProfiler was registered, let it be known + if (profiler is VoidProfiler) + logger.Info( + "Profiler is VoidProfiler, not profiling (must run debug mode to profile)."); + _profile = false; + } + + public void Initialize() + { + if (!_profile) return; + + // bind to ApplicationInit - ie execute the application initialization for *each* application + // it would be a mistake to try and bind to the current application events + _umbracoApplicationLifetime.ApplicationInit += InitializeApplication; + } + + public void Terminate() + { + } + + private void InitializeApplication(object sender, EventArgs args) + { + _umbracoRequestLifetime.RequestStart += + (sender, context) => _profiler.UmbracoApplicationBeginRequest(context); + _umbracoRequestLifetime.RequestEnd += (sender, context) => _profiler.UmbracoApplicationEndRequest(context); + } + } +} diff --git a/src/Umbraco.Web.Common/Runtime/Profiler/WebProfilerComposer.cs b/src/Umbraco.Web.Common/Runtime/Profiler/WebProfilerComposer.cs new file mode 100644 index 0000000000..688a3e5c28 --- /dev/null +++ b/src/Umbraco.Web.Common/Runtime/Profiler/WebProfilerComposer.cs @@ -0,0 +1,8 @@ +using Umbraco.Core.Composing; + +namespace Umbraco.Web.Common.Runtime.Profiler +{ + internal class WebProfilerComposer : ComponentComposer, ICoreComposer + { + } +} diff --git a/src/Umbraco.Web.Common/Runtime/Profiler/WebProfilerProvider.cs b/src/Umbraco.Web.Common/Runtime/Profiler/WebProfilerProvider.cs new file mode 100644 index 0000000000..99ad6f724c --- /dev/null +++ b/src/Umbraco.Web.Common/Runtime/Profiler/WebProfilerProvider.cs @@ -0,0 +1,119 @@ +using System; +using System.Threading; +using StackExchange.Profiling; +using StackExchange.Profiling.Internal; +using Umbraco.Core.Cache; + +namespace Umbraco.Web.Common.Runtime.Profiler +{ + public class WebProfilerProvider : DefaultProfilerProvider + { + private readonly ReaderWriterLockSlim _locker = new ReaderWriterLockSlim(); + private volatile BootPhase _bootPhase; + private int _first; + private MiniProfiler _startupProfiler; + + public WebProfilerProvider() + { + // booting... + _bootPhase = BootPhase.Boot; + } + + + /// + /// Gets the current profiler. + /// + /// + /// If the boot phase is not Booted, then this will return the startup profiler (this), otherwise + /// returns the base class + /// + public override MiniProfiler CurrentProfiler + { + get + { + // if not booting then just use base (fast) + // no lock, _bootPhase is volatile + if (_bootPhase == BootPhase.Booted) + return base.CurrentProfiler; + + // else + try + { + var current = base.CurrentProfiler; + return current ?? _startupProfiler; + } + catch + { + return _startupProfiler; + } + } + } + + public void BeginBootRequest() + { + _locker.EnterWriteLock(); + try + { + if (_bootPhase != BootPhase.Boot) + throw new InvalidOperationException("Invalid boot phase."); + _bootPhase = BootPhase.BootRequest; + + // assign the profiler to be the current MiniProfiler for the request + // is's already active, starting and all + CurrentProfiler = _startupProfiler; + } + finally + { + _locker.ExitWriteLock(); + } + } + + public void EndBootRequest() + { + _locker.EnterWriteLock(); + try + { + if (_bootPhase != BootPhase.BootRequest) + throw new InvalidOperationException("Invalid boot phase."); + _bootPhase = BootPhase.Booted; + + _startupProfiler = null; + } + finally + { + _locker.ExitWriteLock(); + } + } + + /// + /// Starts a new MiniProfiler. + /// + /// + /// + /// This is called when WebProfiler calls MiniProfiler.Start() so, + /// - as a result of WebRuntime starting the WebProfiler, and + /// - assuming profiling is enabled, on every BeginRequest that should be profiled, + /// - except for the very first one which is the boot request. + /// + /// + public override MiniProfiler Start(string profilerName, MiniProfilerBaseOptions options) + { + var first = Interlocked.Exchange(ref _first, 1) == 0; + if (first == false) return base.Start(profilerName, options); + + _startupProfiler = new MiniProfiler("StartupProfiler", options); + CurrentProfiler = _startupProfiler; + return _startupProfiler; + } + + /// + /// Indicates the boot phase. + /// + private enum BootPhase + { + Boot = 0, // boot phase (before the 1st request even begins) + BootRequest = 1, // request boot phase (during the 1st request) + Booted = 2 // done booting + } + } +} diff --git a/src/Umbraco.Web.Common/Umbraco.Web.Common.csproj b/src/Umbraco.Web.Common/Umbraco.Web.Common.csproj new file mode 100644 index 0000000000..cb9ca9156a --- /dev/null +++ b/src/Umbraco.Web.Common/Umbraco.Web.Common.csproj @@ -0,0 +1,22 @@ + + + + netcoreapp3.1 + Library + 8 + + + + + + + + + + + + + + + + diff --git a/src/Umbraco.Web.UI.NetCore/Startup.cs b/src/Umbraco.Web.UI.NetCore/Startup.cs index 9ef4985aea..be81261d2a 100644 --- a/src/Umbraco.Web.UI.NetCore/Startup.cs +++ b/src/Umbraco.Web.UI.NetCore/Startup.cs @@ -9,6 +9,7 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Umbraco.Web.BackOffice.AspNetCore; +using Umbraco.Web.Common.Extensions; using Umbraco.Web.Website.AspNetCore; @@ -21,7 +22,9 @@ namespace Umbraco.Web.UI.BackOffice // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940 public void ConfigureServices(IServiceCollection services) { + services.AddUmbracoConfiguration(); + services.AddUmbracoRequest(); services.AddUmbracoWebsite(); services.AddUmbracoBackOffice(); } @@ -29,6 +32,7 @@ namespace Umbraco.Web.UI.BackOffice // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { + app.UseUmbracoRequest(); if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); diff --git a/src/Umbraco.Web.UI.NetCore/Umbraco.Web.UI.NetCore.csproj b/src/Umbraco.Web.UI.NetCore/Umbraco.Web.UI.NetCore.csproj index ca7c3e26fa..abfb172763 100644 --- a/src/Umbraco.Web.UI.NetCore/Umbraco.Web.UI.NetCore.csproj +++ b/src/Umbraco.Web.UI.NetCore/Umbraco.Web.UI.NetCore.csproj @@ -7,6 +7,7 @@ + diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index fe11a8d9aa..0c0e6aee50 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -101,7 +101,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive all - + 3.4.0 diff --git a/src/Umbraco.Web/AspNet/AspNetUmbracoApplicationLifetime.cs b/src/Umbraco.Web/AspNet/AspNetUmbracoApplicationLifetime.cs index 245e8ea374..f5ebc03cd0 100644 --- a/src/Umbraco.Web/AspNet/AspNetUmbracoApplicationLifetime.cs +++ b/src/Umbraco.Web/AspNet/AspNetUmbracoApplicationLifetime.cs @@ -1,3 +1,4 @@ +using System; using System.Threading; using System.Web; using Umbraco.Net; @@ -11,6 +12,8 @@ namespace Umbraco.Web.AspNet public AspNetUmbracoApplicationLifetime(IHttpContextAccessor httpContextAccessor) { _httpContextAccessor = httpContextAccessor; + + UmbracoApplicationBase.ApplicationInit += ApplicationInit; } public bool IsRestarting { get; set; } @@ -30,5 +33,7 @@ namespace Umbraco.Web.AspNet Thread.CurrentPrincipal = null; HttpRuntime.UnloadAppDomain(); } + + public event EventHandler ApplicationInit; } } diff --git a/src/Umbraco.Web/Install/InstallSteps/NewInstallStep.cs b/src/Umbraco.Web/Install/InstallSteps/NewInstallStep.cs index f5a2ca8ac8..3f81c0d589 100644 --- a/src/Umbraco.Web/Install/InstallSteps/NewInstallStep.cs +++ b/src/Umbraco.Web/Install/InstallSteps/NewInstallStep.cs @@ -56,20 +56,7 @@ namespace Umbraco.Web.Install.InstallSteps throw new InvalidOperationException("Could not find the super user!"); } - var userManager = _httpContextAccessor.GetRequiredHttpContext().GetOwinContext().GetBackOfficeUserManager(); - var membershipUser = await userManager.FindByIdAsync(Constants.Security.SuperUserId); - if (membershipUser == null) - { - throw new InvalidOperationException($"No user found in membership provider with id of {Constants.Security.SuperUserId}."); - } - - //To change the password here we actually need to reset it since we don't have an old one to use to change - var resetToken = await userManager.GeneratePasswordResetTokenAsync(membershipUser.Id); - var resetResult = await userManager.ChangePasswordWithResetAsync(membershipUser.Id, resetToken, user.Password.Trim()); - if (!resetResult.Succeeded) - { - throw new InvalidOperationException("Could not reset password: " + string.Join(", ", resetResult.Errors)); - } + await ResetAdminPassword(user.Password); admin.Email = user.Email.Trim(); admin.Name = user.Name.Trim(); @@ -95,6 +82,26 @@ namespace Umbraco.Web.Install.InstallSteps return null; } + private async Task ResetAdminPassword(string clearTextPassword) + { + var userManager = _httpContextAccessor.GetRequiredHttpContext().GetOwinContext().GetBackOfficeUserManager(); + var membershipUser = await userManager.FindByIdAsync(Constants.Security.SuperUserId); + if (membershipUser == null) + { + throw new InvalidOperationException( + $"No user found in membership provider with id of {Constants.Security.SuperUserId}."); + } + + //To change the password here we actually need to reset it since we don't have an old one to use to change + var resetToken = await userManager.GeneratePasswordResetTokenAsync(membershipUser.Id); + var resetResult = + await userManager.ChangePasswordWithResetAsync(membershipUser.Id, resetToken, clearTextPassword.Trim()); + if (!resetResult.Succeeded) + { + throw new InvalidOperationException("Could not reset password: " + string.Join(", ", resetResult.Errors)); + } + } + /// /// Return a custom view model for this step /// diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index e8aaecdf4d..b9892e1c31 100755 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -92,7 +92,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive all - + diff --git a/src/umbraco.sln b/src/umbraco.sln index be363ef2e6..11098abdec 100644 --- a/src/umbraco.sln +++ b/src/umbraco.sln @@ -123,6 +123,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Umbraco.Web.Website", "Umbr EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Umbraco.Tests.Common", "Umbraco.Tests.Common\Umbraco.Tests.Common.csproj", "{A499779C-1B3B-48A8-B551-458E582E6E96}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Umbraco.Web.Common", "Umbraco.Web.Common\Umbraco.Web.Common.csproj", "{839D3048-9718-4907-BDE0-7CD63D364383}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -195,6 +197,10 @@ Global {A499779C-1B3B-48A8-B551-458E582E6E96}.Debug|Any CPU.Build.0 = Debug|Any CPU {A499779C-1B3B-48A8-B551-458E582E6E96}.Release|Any CPU.ActiveCfg = Release|Any CPU {A499779C-1B3B-48A8-B551-458E582E6E96}.Release|Any CPU.Build.0 = Release|Any CPU + {839D3048-9718-4907-BDE0-7CD63D364383}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {839D3048-9718-4907-BDE0-7CD63D364383}.Debug|Any CPU.Build.0 = Debug|Any CPU + {839D3048-9718-4907-BDE0-7CD63D364383}.Release|Any CPU.ActiveCfg = Release|Any CPU + {839D3048-9718-4907-BDE0-7CD63D364383}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE From 9cca16d31b574604b57814a0eb9233ed3967056a Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Tue, 24 Mar 2020 19:37:04 +0100 Subject: [PATCH 38/73] Rollback of changes not related to this issue --- .../Install/InstallSteps/NewInstallStep.cs | 37 ++++++++----------- 1 file changed, 16 insertions(+), 21 deletions(-) diff --git a/src/Umbraco.Web/Install/InstallSteps/NewInstallStep.cs b/src/Umbraco.Web/Install/InstallSteps/NewInstallStep.cs index 3f81c0d589..fdec5bfa2e 100644 --- a/src/Umbraco.Web/Install/InstallSteps/NewInstallStep.cs +++ b/src/Umbraco.Web/Install/InstallSteps/NewInstallStep.cs @@ -56,7 +56,22 @@ namespace Umbraco.Web.Install.InstallSteps throw new InvalidOperationException("Could not find the super user!"); } - await ResetAdminPassword(user.Password); + var userManager = _httpContextAccessor.GetRequiredHttpContext().GetOwinContext().GetBackOfficeUserManager(); + var membershipUser = await userManager.FindByIdAsync(Constants.Security.SuperUserId); + if (membershipUser == null) + { + throw new InvalidOperationException( + $"No user found in membership provider with id of {Constants.Security.SuperUserId}."); + } + + //To change the password here we actually need to reset it since we don't have an old one to use to change + var resetToken = await userManager.GeneratePasswordResetTokenAsync(membershipUser.Id); + var resetResult = + await userManager.ChangePasswordWithResetAsync(membershipUser.Id, resetToken, user.Password.Trim()); + if (!resetResult.Succeeded) + { + throw new InvalidOperationException("Could not reset password: " + string.Join(", ", resetResult.Errors)); + } admin.Email = user.Email.Trim(); admin.Name = user.Name.Trim(); @@ -82,26 +97,6 @@ namespace Umbraco.Web.Install.InstallSteps return null; } - private async Task ResetAdminPassword(string clearTextPassword) - { - var userManager = _httpContextAccessor.GetRequiredHttpContext().GetOwinContext().GetBackOfficeUserManager(); - var membershipUser = await userManager.FindByIdAsync(Constants.Security.SuperUserId); - if (membershipUser == null) - { - throw new InvalidOperationException( - $"No user found in membership provider with id of {Constants.Security.SuperUserId}."); - } - - //To change the password here we actually need to reset it since we don't have an old one to use to change - var resetToken = await userManager.GeneratePasswordResetTokenAsync(membershipUser.Id); - var resetResult = - await userManager.ChangePasswordWithResetAsync(membershipUser.Id, resetToken, clearTextPassword.Trim()); - if (!resetResult.Succeeded) - { - throw new InvalidOperationException("Could not reset password: " + string.Join(", ", resetResult.Errors)); - } - } - /// /// Return a custom view model for this step /// From 92afbfc6fc7fc8e95569222e23022903f098354c Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Wed, 25 Mar 2020 05:39:25 +0100 Subject: [PATCH 39/73] AB#5820 - Initialize the webprofiler in asp.net core --- .../Models/HostingSettings.cs | 2 +- .../DefaultUmbracoAssemblyProvider.cs | 3 +++ ...coBackOfficeServiceCollectionExtensions.cs | 20 ++++++++++++++-- .../Umbraco.Web.BackOffice.csproj | 1 + .../AspNetCoreUmbracoApplicationLifetime.cs | 2 +- ...bracoRequestServiceCollectionExtensions.cs | 20 ---------------- .../Runtime/Profiler/WebInitialComponent.cs | 18 ++++++++++++++ .../Runtime/Profiler/WebInitialComposer.cs | 24 +++++++++++++++++++ src/Umbraco.Web.UI.NetCore/Startup.cs | 1 - src/Umbraco.Web.UI.NetCore/appsettings.json | 3 +++ 10 files changed, 69 insertions(+), 25 deletions(-) rename src/{Umbraco.Web.BackOffice => Umbraco.Web.Common}/AspNetCore/AspNetCoreUmbracoApplicationLifetime.cs (97%) delete mode 100644 src/Umbraco.Web.Common/Extensions/UmbracoRequestServiceCollectionExtensions.cs create mode 100644 src/Umbraco.Web.Common/Runtime/Profiler/WebInitialComponent.cs create mode 100644 src/Umbraco.Web.Common/Runtime/Profiler/WebInitialComposer.cs diff --git a/src/Umbraco.Configuration/Models/HostingSettings.cs b/src/Umbraco.Configuration/Models/HostingSettings.cs index 4a156cee6a..7cfc08d2d4 100644 --- a/src/Umbraco.Configuration/Models/HostingSettings.cs +++ b/src/Umbraco.Configuration/Models/HostingSettings.cs @@ -22,6 +22,6 @@ namespace Umbraco.Configuration.Models /// Gets a value indicating whether umbraco is running in [debug mode]. /// /// true if [debug mode]; otherwise, false. - public bool DebugMode => _configuration.GetValue(Prefix+":Debug", false); + public bool DebugMode => _configuration.GetValue(Prefix+"Debug", false); } } diff --git a/src/Umbraco.Core/Composing/DefaultUmbracoAssemblyProvider.cs b/src/Umbraco.Core/Composing/DefaultUmbracoAssemblyProvider.cs index 61d7cff240..4d153d8922 100644 --- a/src/Umbraco.Core/Composing/DefaultUmbracoAssemblyProvider.cs +++ b/src/Umbraco.Core/Composing/DefaultUmbracoAssemblyProvider.cs @@ -22,6 +22,9 @@ namespace Umbraco.Core.Composing "Umbraco.PublishedCache.NuCache", "Umbraco.ModelsBuilder.Embedded", "Umbraco.Examine.Lucene", + "Umbraco.Web.Common", + "Umbraco.Web.BackOffice", + "Umbraco.Web.Website", }; public DefaultUmbracoAssemblyProvider(Assembly entryPointAssembly) diff --git a/src/Umbraco.Web.BackOffice/AspNetCore/UmbracoBackOfficeServiceCollectionExtensions.cs b/src/Umbraco.Web.BackOffice/AspNetCore/UmbracoBackOfficeServiceCollectionExtensions.cs index 135ba90b97..b63988d4e5 100644 --- a/src/Umbraco.Web.BackOffice/AspNetCore/UmbracoBackOfficeServiceCollectionExtensions.cs +++ b/src/Umbraco.Web.BackOffice/AspNetCore/UmbracoBackOfficeServiceCollectionExtensions.cs @@ -18,6 +18,7 @@ using Umbraco.Core.Logging; using Umbraco.Core.Logging.Serilog; using Umbraco.Core.Persistence; using Umbraco.Core.Runtime; +using Umbraco.Web.Common.Runtime.Profiler; namespace Umbraco.Web.BackOffice.AspNetCore { @@ -118,7 +119,7 @@ namespace Umbraco.Web.BackOffice.AspNetCore private static void CreateCompositionRoot(IServiceCollection services) { // TODO: This isn't the best to have to resolve the services now but to avoid this will - // require quite a lot of re-work. + // require quite a lot of re-work. var serviceProvider = services.BuildServiceProvider(); var httpContextAccessor = serviceProvider.GetRequiredService(); @@ -142,11 +143,26 @@ namespace Umbraco.Web.BackOffice.AspNetCore new AspNetCoreMarchal()); var backOfficeInfo = new AspNetCoreBackOfficeInfo(globalSettings); - var profiler = new LogProfiler(logger); + var profiler = GetWebProfiler(hostingEnvironment, httpContextAccessor); Current.Initialize(logger, configs, ioHelper, hostingEnvironment, backOfficeInfo, profiler); } + private static IProfiler GetWebProfiler(Umbraco.Core.Hosting.IHostingEnvironment hostingEnvironment, IHttpContextAccessor httpContextAccessor) + { + // create and start asap to profile boot + if (!hostingEnvironment.IsDebugMode) + { + // should let it be null, that's how MiniProfiler is meant to work, + // but our own IProfiler expects an instance so let's get one + return new VoidProfiler(); + } + + var webProfiler = new WebProfiler(httpContextAccessor); + webProfiler.Start(); + + return webProfiler; + } private class AspNetCoreBootPermissionsChecker : IUmbracoBootPermissionChecker { public void ThrowIfNotPermissions() diff --git a/src/Umbraco.Web.BackOffice/Umbraco.Web.BackOffice.csproj b/src/Umbraco.Web.BackOffice/Umbraco.Web.BackOffice.csproj index bd20769d45..5b031c095e 100644 --- a/src/Umbraco.Web.BackOffice/Umbraco.Web.BackOffice.csproj +++ b/src/Umbraco.Web.BackOffice/Umbraco.Web.BackOffice.csproj @@ -18,6 +18,7 @@ +
    diff --git a/src/Umbraco.Web.BackOffice/AspNetCore/AspNetCoreUmbracoApplicationLifetime.cs b/src/Umbraco.Web.Common/AspNetCore/AspNetCoreUmbracoApplicationLifetime.cs similarity index 97% rename from src/Umbraco.Web.BackOffice/AspNetCore/AspNetCoreUmbracoApplicationLifetime.cs rename to src/Umbraco.Web.Common/AspNetCore/AspNetCoreUmbracoApplicationLifetime.cs index f597c40252..45cfb7d570 100644 --- a/src/Umbraco.Web.BackOffice/AspNetCore/AspNetCoreUmbracoApplicationLifetime.cs +++ b/src/Umbraco.Web.Common/AspNetCore/AspNetCoreUmbracoApplicationLifetime.cs @@ -4,7 +4,7 @@ using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Hosting; using Umbraco.Net; -namespace Umbraco.Web.AspNet +namespace Umbraco.Web.Common.AspNetCore { public class AspNetCoreUmbracoApplicationLifetime : IUmbracoApplicationLifetime { diff --git a/src/Umbraco.Web.Common/Extensions/UmbracoRequestServiceCollectionExtensions.cs b/src/Umbraco.Web.Common/Extensions/UmbracoRequestServiceCollectionExtensions.cs deleted file mode 100644 index 2a16b8b4f9..0000000000 --- a/src/Umbraco.Web.Common/Extensions/UmbracoRequestServiceCollectionExtensions.cs +++ /dev/null @@ -1,20 +0,0 @@ -using Microsoft.Extensions.DependencyInjection; -using Umbraco.Web.Common.Middleware; - -namespace Umbraco.Web.Common.Extensions -{ - public static class UmbracoRequestServiceCollectionExtensions - { - public static IServiceCollection AddUmbracoRequest(this IServiceCollection services) - { - var umbracoRequestLifetime = new UmbracoRequestLifetime(); - - services.AddSingleton(umbracoRequestLifetime); - services.AddSingleton(umbracoRequestLifetime); - - return services; - } - - } - -} diff --git a/src/Umbraco.Web.Common/Runtime/Profiler/WebInitialComponent.cs b/src/Umbraco.Web.Common/Runtime/Profiler/WebInitialComponent.cs new file mode 100644 index 0000000000..2194b3c038 --- /dev/null +++ b/src/Umbraco.Web.Common/Runtime/Profiler/WebInitialComponent.cs @@ -0,0 +1,18 @@ +using Umbraco.Core.Composing; + +namespace Umbraco.Web.Common.Runtime.Profiler +{ + public sealed class WebInitialComponent : IComponent + { + + + public void Initialize() + { + } + + public void Terminate() + { + } + + } +} diff --git a/src/Umbraco.Web.Common/Runtime/Profiler/WebInitialComposer.cs b/src/Umbraco.Web.Common/Runtime/Profiler/WebInitialComposer.cs new file mode 100644 index 0000000000..472d8bf8e9 --- /dev/null +++ b/src/Umbraco.Web.Common/Runtime/Profiler/WebInitialComposer.cs @@ -0,0 +1,24 @@ +using Microsoft.AspNetCore.Http; +using Umbraco.Core; +using Umbraco.Core.Composing; +using Umbraco.Net; +using Umbraco.Web.Common.AspNetCore; +using Umbraco.Web.Common.Middleware; + +namespace Umbraco.Web.Common.Runtime.Profiler +{ + public class WebInitialComposer : ComponentComposer, ICoreComposer + { + + public override void Compose(Composition composition) + { + base.Compose(composition); + + var umbracoRequestLifetime = new UmbracoRequestLifetime(); + + composition.RegisterUnique(factory => umbracoRequestLifetime); + composition.RegisterUnique(factory => umbracoRequestLifetime); + composition.RegisterUnique(); + } + } +} diff --git a/src/Umbraco.Web.UI.NetCore/Startup.cs b/src/Umbraco.Web.UI.NetCore/Startup.cs index 444b9a835d..8a04b0b17b 100644 --- a/src/Umbraco.Web.UI.NetCore/Startup.cs +++ b/src/Umbraco.Web.UI.NetCore/Startup.cs @@ -24,7 +24,6 @@ namespace Umbraco.Web.UI.BackOffice { services.AddUmbracoConfiguration(); - services.AddUmbracoRequest(); services.AddUmbracoCore(); services.AddUmbracoWebsite(); } diff --git a/src/Umbraco.Web.UI.NetCore/appsettings.json b/src/Umbraco.Web.UI.NetCore/appsettings.json index 1c89647efa..2448ae0aa9 100644 --- a/src/Umbraco.Web.UI.NetCore/appsettings.json +++ b/src/Umbraco.Web.UI.NetCore/appsettings.json @@ -12,6 +12,9 @@ "AllowedHosts": "*", "Umbraco": { "CMS": { + "Hosting": { + "Debug": true + }, "Imaging": { "Resize": { "MaxWidth": 5000, From 17d91da8d1444c8f2ce4c48380ead0c89bf9e65a Mon Sep 17 00:00:00 2001 From: Chris Oliver Date: Wed, 25 Mar 2020 16:29:42 +0000 Subject: [PATCH 40/73] Remove button border radius when used in a button group (#7834) --- .../src/less/components/buttons/umb-button-group.less | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/less/components/buttons/umb-button-group.less b/src/Umbraco.Web.UI.Client/src/less/components/buttons/umb-button-group.less index 0465881387..02b67460f6 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/buttons/umb-button-group.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/buttons/umb-button-group.less @@ -17,15 +17,17 @@ .umb-button-group { .umb-button__button { - border-radius: @baseBorderRadius; + border-radius: @baseBorderRadius 0 0 @baseBorderRadius; + + &:hover { + z-index: 2; + } } .umb-button-group__toggle { border-radius: 0 @baseBorderRadius @baseBorderRadius 0; - border-left: 1px solid rgba(0,0,0,0.09); - margin-left: -2px; + margin-left: -1px; padding-left: 10px; padding-right: 10px; } - } From ec64da080460d85176960289dd7c57d75511a570 Mon Sep 17 00:00:00 2001 From: Steve Megson Date: Tue, 11 Feb 2020 16:39:18 +0000 Subject: [PATCH 41/73] Grant access to Packages section for any user groups with access to the Developer section --- .../Migrations/Upgrade/UmbracoPlan.cs | 1 + .../V_8_0_0/AddPackagesSectionAccess.cs | 25 +++++++++++++++++++ src/Umbraco.Core/Umbraco.Core.csproj | 1 + 3 files changed, 27 insertions(+) create mode 100644 src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/AddPackagesSectionAccess.cs diff --git a/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs b/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs index 7143be2b90..f0cfc08281 100644 --- a/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs +++ b/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs @@ -166,6 +166,7 @@ namespace Umbraco.Core.Migrations.Upgrade .As("{0576E786-5C30-4000-B969-302B61E90CA3}"); To("{48AD6CCD-C7A4-4305-A8AB-38728AD23FC5}"); + To("{DF470D86-E5CA-42AC-9780-9D28070E25F9}"); // finish migrating from v7 - recreate all keys and indexes To("{3F9764F5-73D0-4D45-8804-1240A66E43A2}"); diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/AddPackagesSectionAccess.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/AddPackagesSectionAccess.cs new file mode 100644 index 0000000000..d44e637a2c --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/AddPackagesSectionAccess.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 +{ + public class AddPackagesSectionAccess : MigrationBase + { + public AddPackagesSectionAccess(IMigrationContext context) + : base(context) + { } + + public override void Migrate() + { + // Any user group which had access to the Developer section should have access to Packages + Database.Execute($@" + insert into {Constants.DatabaseSchema.Tables.UserGroup2App} + select userGroupId, '{Constants.Applications.Packages}' + from {Constants.DatabaseSchema.Tables.UserGroup2App} + where app='developer'"); + } + } +} diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 30247bed8a..24954e316c 100755 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -256,6 +256,7 @@ + From 935690b727c395d7a451d6f23890c45d14c6ecbd Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Wed, 25 Mar 2020 18:09:03 +0100 Subject: [PATCH 42/73] Bump `caniuse-lite` dependency --- src/Umbraco.Web.UI.Client/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/package.json b/src/Umbraco.Web.UI.Client/package.json index c150af79de..438052367e 100644 --- a/src/Umbraco.Web.UI.Client/package.json +++ b/src/Umbraco.Web.UI.Client/package.json @@ -50,7 +50,7 @@ "@babel/core": "7.6.4", "@babel/preset-env": "7.6.3", "autoprefixer": "9.6.5", - "caniuse-lite": "^1.0.30001002", + "caniuse-lite": "^1.0.30001037", "cssnano": "4.1.10", "fs": "0.0.2", "gulp": "4.0.2", From 972acc6718ce418e648ed4ac9ef2527f47c1aa33 Mon Sep 17 00:00:00 2001 From: Bjarne Fyrstenborg Date: Thu, 26 Mar 2020 08:39:38 +0100 Subject: [PATCH 43/73] Installer adjustments (#7592) --- .../src/installer/installer.service.js | 25 +++++++++---------- .../installer/steps/database.controller.js | 17 +++++++------ .../installer/steps/machinekey.controller.js | 3 +-- .../src/installer/steps/starterkit.html | 7 +++--- .../src/installer/steps/user.controller.js | 12 ++++----- 5 files changed, 32 insertions(+), 32 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/installer/installer.service.js b/src/Umbraco.Web.UI.Client/src/installer/installer.service.js index f6f162f04f..05c391c3e7 100644 --- a/src/Umbraco.Web.UI.Client/src/installer/installer.service.js +++ b/src/Umbraco.Web.UI.Client/src/installer/installer.service.js @@ -2,17 +2,16 @@ angular.module("umbraco.install").factory('installerService', function ($rootSco var _status = { index: 0, - current: undefined, - steps: undefined, + current: null, + steps: null, loading: true, progress: "100%" }; - var factTimer = undefined; + var factTimer; var _installerModel = { - installId: undefined, - instructions: { - } + installId: null, + instructions: {} }; //add to umbraco installer facts here @@ -304,7 +303,7 @@ angular.module("umbraco.install").factory('installerService', function ($rootSco }, switchToFeedback : function(){ - service.status.current = undefined; + service.status.current = null; service.status.loading = true; service.status.configuring = false; @@ -320,11 +319,11 @@ angular.module("umbraco.install").factory('installerService', function ($rootSco switchToConfiguration : function(){ service.status.loading = false; service.status.configuring = true; - service.status.feedback = undefined; - service.status.fact = undefined; + service.status.feedback = null; + service.status.fact = null; - if(factTimer){ - clearInterval(factTimer); + if (factTimer) { + clearInterval(factTimer); } }, @@ -335,8 +334,8 @@ angular.module("umbraco.install").factory('installerService', function ($rootSco service.status.feedback = "Redirecting you to Umbraco, please wait"; service.status.loading = false; - if(factTimer){ - clearInterval(factTimer); + if (factTimer) { + clearInterval(factTimer); } $timeout(function(){ diff --git a/src/Umbraco.Web.UI.Client/src/installer/steps/database.controller.js b/src/Umbraco.Web.UI.Client/src/installer/steps/database.controller.js index e83c5114fc..687fce95ae 100644 --- a/src/Umbraco.Web.UI.Client/src/installer/steps/database.controller.js +++ b/src/Umbraco.Web.UI.Client/src/installer/steps/database.controller.js @@ -4,18 +4,19 @@ angular.module("umbraco.install").controller("Umbraco.Installer.DataBaseControll $scope.invalidDbDns = false; $scope.dbs = [ - { name: 'Microsoft SQL Server Compact (SQL CE)', id: 0}, - { name: 'Microsoft SQL Server', id: 1}, + { name: 'Microsoft SQL Server Compact (SQL CE)', id: 0 }, + { name: 'Microsoft SQL Server', id: 1 }, { name: 'Microsoft SQL Azure', id: 3 }, - { name: 'Custom connection string', id: -1} + { name: 'Custom connection string', id: -1 } ]; - if ( installerService.status.current.model.dbType === undefined ) { + if (angular.isUndefined(installerService.status.current.model.dbType) || installerService.status.current.model.dbType === null) { installerService.status.current.model.dbType = 0; } - $scope.validateAndForward = function(){ - if ( !$scope.checking && this.myForm.$valid ) { + $scope.validateAndForward = function() { + if (!$scope.checking && this.myForm.$valid) + { $scope.checking = true; $scope.invalidDbDns = false; @@ -23,9 +24,9 @@ angular.module("umbraco.install").controller("Umbraco.Installer.DataBaseControll $http.post( Umbraco.Sys.ServerVariables.installApiBaseUrl + "PostValidateDatabaseConnection", - model ).then( function( response ) { + model).then(function(response) { - if ( response.data === true ) { + if (response.data === true) { installerService.forward(); } else { diff --git a/src/Umbraco.Web.UI.Client/src/installer/steps/machinekey.controller.js b/src/Umbraco.Web.UI.Client/src/installer/steps/machinekey.controller.js index bdcef63d95..fdd2c65c1c 100644 --- a/src/Umbraco.Web.UI.Client/src/installer/steps/machinekey.controller.js +++ b/src/Umbraco.Web.UI.Client/src/installer/steps/machinekey.controller.js @@ -1,5 +1,4 @@ angular.module("umbraco.install").controller("Umbraco.Installer.MachineKeyController", function ($scope, installerService) { - $scope.continue = function () { installerService.status.current.model = true; @@ -11,4 +10,4 @@ installerService.forward(); }; -}); \ No newline at end of file +}); diff --git a/src/Umbraco.Web.UI.Client/src/installer/steps/starterkit.html b/src/Umbraco.Web.UI.Client/src/installer/steps/starterkit.html index cfb08744b2..92f5cc8d9d 100644 --- a/src/Umbraco.Web.UI.Client/src/installer/steps/starterkit.html +++ b/src/Umbraco.Web.UI.Client/src/installer/steps/starterkit.html @@ -20,8 +20,9 @@ - + diff --git a/src/Umbraco.Web.UI.Client/src/installer/steps/user.controller.js b/src/Umbraco.Web.UI.Client/src/installer/steps/user.controller.js index 10c0d596eb..9f0108853f 100644 --- a/src/Umbraco.Web.UI.Client/src/installer/steps/user.controller.js +++ b/src/Umbraco.Web.UI.Client/src/installer/steps/user.controller.js @@ -13,14 +13,14 @@ angular.module("umbraco.install").controller("Umbraco.Install.UserController", f $scope.passwordPattern = new RegExp(exp); } - $scope.validateAndInstall = function(){ - installerService.install(); + $scope.validateAndInstall = function() { + installerService.install(); }; $scope.validateAndForward = function(){ - if(this.myForm.$valid){ - installerService.forward(); - } + if (this.myForm.$valid) { + installerService.forward(); + } }; -}); \ No newline at end of file +}); From a0e5b839141dd3d63a063656ac5f3ebc0f4aca92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B8ren=20Gregersen?= Date: Wed, 25 Mar 2020 19:23:29 +0100 Subject: [PATCH 44/73] Add document to downloadFile-method Simple copy/paste to document a useful method --- .../common/services/umbrequesthelper.service.js | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/services/umbrequesthelper.service.js b/src/Umbraco.Web.UI.Client/src/common/services/umbrequesthelper.service.js index 6c765ee135..0d766dc7d8 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/umbrequesthelper.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/umbrequesthelper.service.js @@ -365,12 +365,23 @@ function umbRequestHelper($http, $q, notificationsService, eventsService, formHe }, /** + * @ngdoc method + * @name umbraco.resources.contentResource#downloadFile + * @methodOf umbraco.resources.contentResource + * + * @description * Downloads a file to the client using AJAX/XHR - * Based on an implementation here: web.student.tuwien.ac.at/~e0427417/jsdownload.html - * See https://stackoverflow.com/a/24129082/694494 + * + * @param {string} httpPath the path (url) to the resource being downloaded + * @returns {Promise} http promise object. */ downloadFile : function (httpPath) { + /** + * Based on an implementation here: web.student.tuwien.ac.at/~e0427417/jsdownload.html + * See https://stackoverflow.com/a/24129082/694494 + */ + // Use an arraybuffer return $http.get(httpPath, { responseType: 'arraybuffer' }) .then(function (response) { From 6fef85aafde5eb6614c9831034ac1a9cc11eabf6 Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 26 Mar 2020 22:03:26 +1100 Subject: [PATCH 45/73] fixes Memory Leak in GetCacheItem cache dependency #7773 --- src/Umbraco.Core/Cache/WebCachingAppCache.cs | 37 +++++++++++--------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/src/Umbraco.Core/Cache/WebCachingAppCache.cs b/src/Umbraco.Core/Cache/WebCachingAppCache.cs index 044aab4c42..87f0e75845 100644 --- a/src/Umbraco.Core/Cache/WebCachingAppCache.cs +++ b/src/Umbraco.Core/Cache/WebCachingAppCache.cs @@ -37,23 +37,28 @@ namespace Umbraco.Core.Cache /// public object Get(string key, Func factory, TimeSpan? timeout, bool isSliding = false, CacheItemPriority priority = CacheItemPriority.Normal, CacheItemRemovedCallback removedCallback = null, string[] dependentFiles = null) { - CacheDependency dependency = null; - if (dependentFiles != null && dependentFiles.Any()) - { - dependency = new CacheDependency(dependentFiles); - } - return Get(key, factory, timeout, isSliding, priority, removedCallback, dependency); + return Get(key, factory, timeout, isSliding, priority, removedCallback, + // NOTE: We don't want to allocate an object if it isn't going to be used so we create the CacheDependency + // in a callback if it's needed ... but more importantly and we didn't anticipate this, just constructing + // a CacheDependency object is expensive and allocates a bunch of stuff, just check the code out for it: + // https://referencesource.microsoft.com/#system.web/Cache/CacheDependency.cs,304 + // Init is called as part of the ctor!! yikes. + // This change fixes https://github.com/umbraco/Umbraco-CMS/issues/7773 + () => dependentFiles != null && dependentFiles.Length > 0 ? new CacheDependency(dependentFiles) : null); } /// public void Insert(string key, Func factory, TimeSpan? timeout = null, bool isSliding = false, CacheItemPriority priority = CacheItemPriority.Normal, CacheItemRemovedCallback removedCallback = null, string[] dependentFiles = null) { - CacheDependency dependency = null; - if (dependentFiles != null && dependentFiles.Any()) - { - dependency = new CacheDependency(dependentFiles); - } - Insert(key, factory, timeout, isSliding, priority, removedCallback, dependency); + + Insert(key, factory, timeout, isSliding, priority, removedCallback, + // NOTE: We don't want to allocate an object if it isn't going to be used so we create the CacheDependency + // in a callback if it's needed ... but more importantly and we didn't anticipate this, just constructing + // a CacheDependency object is expensive and allocates a bunch of stuff, just check the code out for it: + // https://referencesource.microsoft.com/#system.web/Cache/CacheDependency.cs,304 + // Init is called as part of the ctor!! yikes. + // This change fixes https://github.com/umbraco/Umbraco-CMS/issues/7773 + () => dependentFiles != null && dependentFiles.Length > 0 ? new CacheDependency(dependentFiles) : null); } #region Dictionary @@ -103,7 +108,7 @@ namespace Umbraco.Core.Cache #endregion - private object Get(string key, Func factory, TimeSpan? timeout, bool isSliding = false, CacheItemPriority priority = CacheItemPriority.Normal, CacheItemRemovedCallback removedCallback = null, CacheDependency dependency = null) + private object Get(string key, Func factory, TimeSpan? timeout, bool isSliding = false, CacheItemPriority priority = CacheItemPriority.Normal, CacheItemRemovedCallback removedCallback = null, Func dependency = null) { key = GetCacheKey(key); @@ -164,7 +169,7 @@ namespace Umbraco.Core.Cache lck.UpgradeToWriteLock(); //NOTE: 'Insert' on System.Web.Caching.Cache actually does an add or update! - _cache.Insert(key, result, dependency, absolute, sliding, priority, removedCallback); + _cache.Insert(key, result, dependency(), absolute, sliding, priority, removedCallback); } } @@ -180,7 +185,7 @@ namespace Umbraco.Core.Cache return value; } - private void Insert(string cacheKey, Func getCacheItem, TimeSpan? timeout = null, bool isSliding = false, CacheItemPriority priority = CacheItemPriority.Normal, CacheItemRemovedCallback removedCallback = null, CacheDependency dependency = null) + private void Insert(string cacheKey, Func getCacheItem, TimeSpan? timeout = null, bool isSliding = false, CacheItemPriority priority = CacheItemPriority.Normal, CacheItemRemovedCallback removedCallback = null, Func dependency = null) { // NOTE - here also we must insert a Lazy but we can evaluate it right now // and make sure we don't store a null value. @@ -198,7 +203,7 @@ namespace Umbraco.Core.Cache { _locker.EnterWriteLock(); //NOTE: 'Insert' on System.Web.Caching.Cache actually does an add or update! - _cache.Insert(cacheKey, result, dependency, absolute, sliding, priority, removedCallback); + _cache.Insert(cacheKey, result, dependency(), absolute, sliding, priority, removedCallback); } finally { From a855a90a8a5b06eff0a91008e64d446379fb94ca Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 26 Mar 2020 22:09:49 +1100 Subject: [PATCH 46/73] cleaner fix --- src/Umbraco.Core/Cache/WebCachingAppCache.cs | 35 ++++++++------------ 1 file changed, 14 insertions(+), 21 deletions(-) diff --git a/src/Umbraco.Core/Cache/WebCachingAppCache.cs b/src/Umbraco.Core/Cache/WebCachingAppCache.cs index 87f0e75845..bec596b129 100644 --- a/src/Umbraco.Core/Cache/WebCachingAppCache.cs +++ b/src/Umbraco.Core/Cache/WebCachingAppCache.cs @@ -37,28 +37,13 @@ namespace Umbraco.Core.Cache /// public object Get(string key, Func factory, TimeSpan? timeout, bool isSliding = false, CacheItemPriority priority = CacheItemPriority.Normal, CacheItemRemovedCallback removedCallback = null, string[] dependentFiles = null) { - return Get(key, factory, timeout, isSliding, priority, removedCallback, - // NOTE: We don't want to allocate an object if it isn't going to be used so we create the CacheDependency - // in a callback if it's needed ... but more importantly and we didn't anticipate this, just constructing - // a CacheDependency object is expensive and allocates a bunch of stuff, just check the code out for it: - // https://referencesource.microsoft.com/#system.web/Cache/CacheDependency.cs,304 - // Init is called as part of the ctor!! yikes. - // This change fixes https://github.com/umbraco/Umbraco-CMS/issues/7773 - () => dependentFiles != null && dependentFiles.Length > 0 ? new CacheDependency(dependentFiles) : null); + return GetInternal(key, factory, timeout, isSliding, priority, removedCallback, dependentFiles); } /// public void Insert(string key, Func factory, TimeSpan? timeout = null, bool isSliding = false, CacheItemPriority priority = CacheItemPriority.Normal, CacheItemRemovedCallback removedCallback = null, string[] dependentFiles = null) { - - Insert(key, factory, timeout, isSliding, priority, removedCallback, - // NOTE: We don't want to allocate an object if it isn't going to be used so we create the CacheDependency - // in a callback if it's needed ... but more importantly and we didn't anticipate this, just constructing - // a CacheDependency object is expensive and allocates a bunch of stuff, just check the code out for it: - // https://referencesource.microsoft.com/#system.web/Cache/CacheDependency.cs,304 - // Init is called as part of the ctor!! yikes. - // This change fixes https://github.com/umbraco/Umbraco-CMS/issues/7773 - () => dependentFiles != null && dependentFiles.Length > 0 ? new CacheDependency(dependentFiles) : null); + InsertInternal(key, factory, timeout, isSliding, priority, removedCallback, dependentFiles); } #region Dictionary @@ -108,7 +93,7 @@ namespace Umbraco.Core.Cache #endregion - private object Get(string key, Func factory, TimeSpan? timeout, bool isSliding = false, CacheItemPriority priority = CacheItemPriority.Normal, CacheItemRemovedCallback removedCallback = null, Func dependency = null) + private object GetInternal(string key, Func factory, TimeSpan? timeout, bool isSliding = false, CacheItemPriority priority = CacheItemPriority.Normal, CacheItemRemovedCallback removedCallback = null, string[] dependentFiles = null) { key = GetCacheKey(key); @@ -168,8 +153,12 @@ namespace Umbraco.Core.Cache var sliding = isSliding == false ? System.Web.Caching.Cache.NoSlidingExpiration : (timeout ?? System.Web.Caching.Cache.NoSlidingExpiration); lck.UpgradeToWriteLock(); + + // create a cache dependency if one is needed. + var dependency = dependentFiles != null && dependentFiles.Length > 0 ? new CacheDependency(dependentFiles) : null; + //NOTE: 'Insert' on System.Web.Caching.Cache actually does an add or update! - _cache.Insert(key, result, dependency(), absolute, sliding, priority, removedCallback); + _cache.Insert(key, result, dependency, absolute, sliding, priority, removedCallback); } } @@ -185,7 +174,7 @@ namespace Umbraco.Core.Cache return value; } - private void Insert(string cacheKey, Func getCacheItem, TimeSpan? timeout = null, bool isSliding = false, CacheItemPriority priority = CacheItemPriority.Normal, CacheItemRemovedCallback removedCallback = null, Func dependency = null) + private void InsertInternal(string cacheKey, Func getCacheItem, TimeSpan? timeout = null, bool isSliding = false, CacheItemPriority priority = CacheItemPriority.Normal, CacheItemRemovedCallback removedCallback = null, string[] dependentFiles = null) { // NOTE - here also we must insert a Lazy but we can evaluate it right now // and make sure we don't store a null value. @@ -202,8 +191,12 @@ namespace Umbraco.Core.Cache try { _locker.EnterWriteLock(); + + // create a cache dependency if one is needed. + var dependency = dependentFiles != null && dependentFiles.Length > 0 ? new CacheDependency(dependentFiles) : null; + //NOTE: 'Insert' on System.Web.Caching.Cache actually does an add or update! - _cache.Insert(cacheKey, result, dependency(), absolute, sliding, priority, removedCallback); + _cache.Insert(cacheKey, result, dependency, absolute, sliding, priority, removedCallback); } finally { From 42ddd90fd71883a3ae6c01bf2c6bbda17b610f25 Mon Sep 17 00:00:00 2001 From: Marc Goodson Date: Thu, 26 Mar 2020 15:57:15 +0000 Subject: [PATCH 47/73] Allow Macro's to be cached differently across variants (#7555) --- src/Umbraco.Web/Macros/MacroRenderer.cs | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/src/Umbraco.Web/Macros/MacroRenderer.cs b/src/Umbraco.Web/Macros/MacroRenderer.cs index 8d7d3bc013..b4fd8c0d86 100755 --- a/src/Umbraco.Web/Macros/MacroRenderer.cs +++ b/src/Umbraco.Web/Macros/MacroRenderer.cs @@ -40,12 +40,18 @@ namespace Umbraco.Web.Macros #region MacroContent cache // gets this macro content cache identifier - private string GetContentCacheIdentifier(MacroModel model, int pageId) + private string GetContentCacheIdentifier(MacroModel model, int pageId, string cultureName) { var id = new StringBuilder(); var alias = model.Alias; id.AppendFormat("{0}-", alias); + //always add current culture to the key to allow variants to have different cache results + if (!string.IsNullOrEmpty(cultureName)) + { + // are there any unusual culture formats we'd need to handle? + id.AppendFormat("{0}-", cultureName); + } if (model.CacheByPage) id.AppendFormat("{0}-", pageId); @@ -190,7 +196,7 @@ namespace Umbraco.Web.Macros ? macroParams[key]?.ToString() ?? string.Empty : string.Empty; } - } + } #endregion #region Render/Execute @@ -221,7 +227,8 @@ namespace Umbraco.Web.Macros foreach (var prop in macro.Properties) prop.Value = ParseAttribute(pageElements, prop.Value); - macro.CacheIdentifier = GetContentCacheIdentifier(macro, content.Id); + var cultureName = System.Threading.Thread.CurrentThread.CurrentUICulture.Name; + macro.CacheIdentifier = GetContentCacheIdentifier(macro, content.Id, cultureName); // get the macro from cache if it is there var macroContent = GetMacroContentFromCache(macro); @@ -317,7 +324,7 @@ namespace Umbraco.Web.Macros private Attempt ExecuteMacroOfType(MacroModel model, IPublishedContent content) { if (model == null) throw new ArgumentNullException(nameof(model)); - + // ensure that we are running against a published node (ie available in XML) // that may not be the case if the macro is embedded in a RTE of an unpublished document @@ -453,9 +460,9 @@ namespace Umbraco.Web.Macros return value; } - + #endregion - + } } From 681a25b861f632aae867c496de54ee35c0b774ea Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Fri, 27 Mar 2020 11:39:17 +0100 Subject: [PATCH 48/73] Moved files from backoffice into Common + Introduced AspNetCoreComponent to invoke the Umbraco Application Init event --- .../Net/IUmbracoApplicationLifetime.cs | 2 ++ .../Implementations/TestHelper.cs | 1 + .../Implementations/TestHostingEnvironment.cs | 4 +-- src/Umbraco.Tests.Integration/RuntimeTests.cs | 11 +++---- .../AspNetCoreApplicationShutdownRegistry.cs | 2 +- .../AspNetCore/AspNetCoreBackOfficeInfo.cs | 2 +- .../AspNetCoreHostingEnvironment.cs | 8 ++--- .../AspNetCore/AspNetCoreIpResolver.cs | 2 +- .../AspNetCore/AspNetCoreMarchal.cs | 2 +- .../AspNetCore/AspNetCoreSessionIdResolver.cs | 4 +-- .../AspNetCoreUmbracoApplicationLifetime.cs | 9 +++--- .../UmbracoCoreServiceCollectionExtensions.cs | 8 ++--- .../Lifetime/IUmbracoRequestLifetime.cs | 11 +++++++ .../IUmbracoRequestLifetimeManager.cs | 10 +++++++ .../Lifetime/UmbracoRequestLifetime.cs | 21 ++++++++++++++ .../Middleware/UmbracoRequestMiddleware.cs | 28 +----------------- .../Runtime/AspNetCoreComponent.cs | 29 +++++++++++++++++++ .../Runtime}/AspNetCoreComposer.cs | 15 ++++++++-- .../Runtime/Profiler/WebInitialComponent.cs | 18 ------------ .../Runtime/Profiler/WebInitialComposer.cs | 24 --------------- .../Runtime/Profiler/WebProfiler.cs | 9 ++++-- .../Runtime/Profiler/WebProfilerComponent.cs | 1 + .../Umbraco.Web.Common.csproj | 1 + src/Umbraco.Web.UI.NetCore/Startup.cs | 8 ++++- .../AspNetUmbracoApplicationLifetime.cs | 4 +++ src/Umbraco.Web/Logging/WebProfiler.cs | 2 +- 26 files changed, 134 insertions(+), 102 deletions(-) rename src/{Umbraco.Web.BackOffice => Umbraco.Web.Common}/AspNetCore/AspNetCoreApplicationShutdownRegistry.cs (97%) rename src/{Umbraco.Web.BackOffice => Umbraco.Web.Common}/AspNetCore/AspNetCoreBackOfficeInfo.cs (88%) rename src/{Umbraco.Web.BackOffice => Umbraco.Web.Common}/AspNetCore/AspNetCoreHostingEnvironment.cs (98%) rename src/{Umbraco.Web.BackOffice => Umbraco.Web.Common}/AspNetCore/AspNetCoreIpResolver.cs (91%) rename src/{Umbraco.Web.BackOffice => Umbraco.Web.Common}/AspNetCore/AspNetCoreMarchal.cs (88%) rename src/{Umbraco.Web.BackOffice => Umbraco.Web.Common}/AspNetCore/AspNetCoreSessionIdResolver.cs (94%) rename src/{Umbraco.Web.BackOffice => Umbraco.Web.Common}/AspNetCore/UmbracoCoreServiceCollectionExtensions.cs (97%) create mode 100644 src/Umbraco.Web.Common/Lifetime/IUmbracoRequestLifetime.cs create mode 100644 src/Umbraco.Web.Common/Lifetime/IUmbracoRequestLifetimeManager.cs create mode 100644 src/Umbraco.Web.Common/Lifetime/UmbracoRequestLifetime.cs create mode 100644 src/Umbraco.Web.Common/Runtime/AspNetCoreComponent.cs rename src/{Umbraco.Web.BackOffice/AspNetCore => Umbraco.Web.Common/Runtime}/AspNetCoreComposer.cs (54%) delete mode 100644 src/Umbraco.Web.Common/Runtime/Profiler/WebInitialComponent.cs delete mode 100644 src/Umbraco.Web.Common/Runtime/Profiler/WebInitialComposer.cs diff --git a/src/Umbraco.Core/Net/IUmbracoApplicationLifetime.cs b/src/Umbraco.Core/Net/IUmbracoApplicationLifetime.cs index 8bf3f115cd..c84d475e90 100644 --- a/src/Umbraco.Core/Net/IUmbracoApplicationLifetime.cs +++ b/src/Umbraco.Core/Net/IUmbracoApplicationLifetime.cs @@ -15,5 +15,7 @@ namespace Umbraco.Net void Restart(); event EventHandler ApplicationInit; + + void InvokeApplicationInit(); } } diff --git a/src/Umbraco.Tests.Integration/Implementations/TestHelper.cs b/src/Umbraco.Tests.Integration/Implementations/TestHelper.cs index 4a986fa35a..f10586b94b 100644 --- a/src/Umbraco.Tests.Integration/Implementations/TestHelper.cs +++ b/src/Umbraco.Tests.Integration/Implementations/TestHelper.cs @@ -16,6 +16,7 @@ using Umbraco.Core.Runtime; using Umbraco.Tests.Common; using Umbraco.Web.BackOffice; using Umbraco.Web.BackOffice.AspNetCore; +using Umbraco.Web.Common.AspNetCore; using IHostingEnvironment = Umbraco.Core.Hosting.IHostingEnvironment; namespace Umbraco.Tests.Integration.Implementations diff --git a/src/Umbraco.Tests.Integration/Implementations/TestHostingEnvironment.cs b/src/Umbraco.Tests.Integration/Implementations/TestHostingEnvironment.cs index 491b7e5480..652aad8928 100644 --- a/src/Umbraco.Tests.Integration/Implementations/TestHostingEnvironment.cs +++ b/src/Umbraco.Tests.Integration/Implementations/TestHostingEnvironment.cs @@ -1,11 +1,11 @@ using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Umbraco.Core.Configuration; -using Umbraco.Web.BackOffice.AspNetCore; +using Umbraco.Web.Common.AspNetCore; namespace Umbraco.Tests.Integration.Implementations { - + public class TestHostingEnvironment : AspNetCoreHostingEnvironment, Umbraco.Core.Hosting.IHostingEnvironment { public TestHostingEnvironment(IHostingSettings hostingSettings, IWebHostEnvironment webHostEnvironment, IHttpContextAccessor httpContextAccessor) : base(hostingSettings, webHostEnvironment, httpContextAccessor) diff --git a/src/Umbraco.Tests.Integration/RuntimeTests.cs b/src/Umbraco.Tests.Integration/RuntimeTests.cs index 0e11a29b95..c7bb22f135 100644 --- a/src/Umbraco.Tests.Integration/RuntimeTests.cs +++ b/src/Umbraco.Tests.Integration/RuntimeTests.cs @@ -22,6 +22,7 @@ using Umbraco.Tests.Common; using Umbraco.Tests.Integration.Implementations; using Umbraco.Tests.Integration.Testing; using Umbraco.Web.BackOffice.AspNetCore; +using Umbraco.Web.Common.AspNetCore; using static Umbraco.Core.Migrations.Install.DatabaseBuilder; namespace Umbraco.Tests.Integration @@ -52,11 +53,11 @@ namespace Umbraco.Tests.Integration // LightInject / Umbraco var container = UmbracoServiceProviderFactory.CreateServiceContainer(); var serviceProviderFactory = new UmbracoServiceProviderFactory(container); - var umbracoContainer = serviceProviderFactory.GetContainer(); + var umbracoContainer = serviceProviderFactory.GetContainer(); // Special case since we are not using the Generic Host, we need to manually add an AspNetCore service to the container umbracoContainer.Register(x => Mock.Of()); - + var testHelper = new TestHelper(); // Create the core runtime @@ -70,7 +71,7 @@ namespace Umbraco.Tests.Integration Assert.IsTrue(coreRuntime.MainDom.IsMainDom); Assert.IsNull(coreRuntime.State.BootFailedException); - Assert.AreEqual(RuntimeLevel.Install, coreRuntime.State.Level); + Assert.AreEqual(RuntimeLevel.Install, coreRuntime.State.Level); Assert.IsTrue(MyComposer.IsComposed); Assert.IsFalse(MyComponent.IsInit); Assert.IsFalse(MyComponent.IsTerminated); @@ -88,7 +89,7 @@ namespace Umbraco.Tests.Integration } /// - /// Calling AddUmbracoCore to configure the container + /// Calling AddUmbracoCore to configure the container /// [Test] public async Task AddUmbracoCore() @@ -280,5 +281,5 @@ namespace Umbraco.Tests.Integration } } - + } diff --git a/src/Umbraco.Web.BackOffice/AspNetCore/AspNetCoreApplicationShutdownRegistry.cs b/src/Umbraco.Web.Common/AspNetCore/AspNetCoreApplicationShutdownRegistry.cs similarity index 97% rename from src/Umbraco.Web.BackOffice/AspNetCore/AspNetCoreApplicationShutdownRegistry.cs rename to src/Umbraco.Web.Common/AspNetCore/AspNetCoreApplicationShutdownRegistry.cs index 92af822836..57ad83d4ba 100644 --- a/src/Umbraco.Web.BackOffice/AspNetCore/AspNetCoreApplicationShutdownRegistry.cs +++ b/src/Umbraco.Web.Common/AspNetCore/AspNetCoreApplicationShutdownRegistry.cs @@ -5,7 +5,7 @@ using Microsoft.Extensions.Hosting; using Umbraco.Core; using Umbraco.Core.Hosting; -namespace Umbraco.Web.BackOffice.AspNetCore +namespace Umbraco.Web.Common.AspNetCore { public class AspNetCoreApplicationShutdownRegistry : IApplicationShutdownRegistry { diff --git a/src/Umbraco.Web.BackOffice/AspNetCore/AspNetCoreBackOfficeInfo.cs b/src/Umbraco.Web.Common/AspNetCore/AspNetCoreBackOfficeInfo.cs similarity index 88% rename from src/Umbraco.Web.BackOffice/AspNetCore/AspNetCoreBackOfficeInfo.cs rename to src/Umbraco.Web.Common/AspNetCore/AspNetCoreBackOfficeInfo.cs index ba12b64dfe..375028132e 100644 --- a/src/Umbraco.Web.BackOffice/AspNetCore/AspNetCoreBackOfficeInfo.cs +++ b/src/Umbraco.Web.Common/AspNetCore/AspNetCoreBackOfficeInfo.cs @@ -1,7 +1,7 @@ using Umbraco.Core; using Umbraco.Core.Configuration; -namespace Umbraco.Web.BackOffice.AspNetCore +namespace Umbraco.Web.Common.AspNetCore { public class AspNetCoreBackOfficeInfo : IBackOfficeInfo { diff --git a/src/Umbraco.Web.BackOffice/AspNetCore/AspNetCoreHostingEnvironment.cs b/src/Umbraco.Web.Common/AspNetCore/AspNetCoreHostingEnvironment.cs similarity index 98% rename from src/Umbraco.Web.BackOffice/AspNetCore/AspNetCoreHostingEnvironment.cs rename to src/Umbraco.Web.Common/AspNetCore/AspNetCoreHostingEnvironment.cs index 6f1298918d..c24c2c4698 100644 --- a/src/Umbraco.Web.BackOffice/AspNetCore/AspNetCoreHostingEnvironment.cs +++ b/src/Umbraco.Web.Common/AspNetCore/AspNetCoreHostingEnvironment.cs @@ -6,16 +6,16 @@ using Microsoft.AspNetCore.Http; using Umbraco.Core; using Umbraco.Core.Configuration; -namespace Umbraco.Web.BackOffice.AspNetCore +namespace Umbraco.Web.Common.AspNetCore { public class AspNetCoreHostingEnvironment : Umbraco.Core.Hosting.IHostingEnvironment { - + private readonly IHostingSettings _hostingSettings; private readonly IWebHostEnvironment _webHostEnvironment; private readonly IHttpContextAccessor _httpContextAccessor; - + private string _localTempPath; public AspNetCoreHostingEnvironment(IHostingSettings hostingSettings, IWebHostEnvironment webHostEnvironment, IHttpContextAccessor httpContextAccessor) @@ -103,7 +103,7 @@ namespace Umbraco.Web.BackOffice.AspNetCore return applicationPath.Add(segment).Value; } - + } diff --git a/src/Umbraco.Web.BackOffice/AspNetCore/AspNetCoreIpResolver.cs b/src/Umbraco.Web.Common/AspNetCore/AspNetCoreIpResolver.cs similarity index 91% rename from src/Umbraco.Web.BackOffice/AspNetCore/AspNetCoreIpResolver.cs rename to src/Umbraco.Web.Common/AspNetCore/AspNetCoreIpResolver.cs index cee43757d8..863d545066 100644 --- a/src/Umbraco.Web.BackOffice/AspNetCore/AspNetCoreIpResolver.cs +++ b/src/Umbraco.Web.Common/AspNetCore/AspNetCoreIpResolver.cs @@ -1,7 +1,7 @@ using Microsoft.AspNetCore.Http; using Umbraco.Net; -namespace Umbraco.Web.BackOffice.AspNetCore +namespace Umbraco.Web.Common.AspNetCore { public class AspNetIpResolver : IIpResolver { diff --git a/src/Umbraco.Web.BackOffice/AspNetCore/AspNetCoreMarchal.cs b/src/Umbraco.Web.Common/AspNetCore/AspNetCoreMarchal.cs similarity index 88% rename from src/Umbraco.Web.BackOffice/AspNetCore/AspNetCoreMarchal.cs rename to src/Umbraco.Web.Common/AspNetCore/AspNetCoreMarchal.cs index 247666090e..af23d092e9 100644 --- a/src/Umbraco.Web.BackOffice/AspNetCore/AspNetCoreMarchal.cs +++ b/src/Umbraco.Web.Common/AspNetCore/AspNetCoreMarchal.cs @@ -2,7 +2,7 @@ using System; using System.Runtime.InteropServices; using Umbraco.Core.Diagnostics; -namespace Umbraco.Web.BackOffice.AspNetCore +namespace Umbraco.Web.Common.AspNetCore { public class AspNetCoreMarchal : IMarchal diff --git a/src/Umbraco.Web.BackOffice/AspNetCore/AspNetCoreSessionIdResolver.cs b/src/Umbraco.Web.Common/AspNetCore/AspNetCoreSessionIdResolver.cs similarity index 94% rename from src/Umbraco.Web.BackOffice/AspNetCore/AspNetCoreSessionIdResolver.cs rename to src/Umbraco.Web.Common/AspNetCore/AspNetCoreSessionIdResolver.cs index cafb02d367..818a39fac5 100644 --- a/src/Umbraco.Web.BackOffice/AspNetCore/AspNetCoreSessionIdResolver.cs +++ b/src/Umbraco.Web.Common/AspNetCore/AspNetCoreSessionIdResolver.cs @@ -2,7 +2,7 @@ using Microsoft.AspNetCore.Http.Features; using Umbraco.Net; -namespace Umbraco.Web.BackOffice.AspNetCore +namespace Umbraco.Web.Common.AspNetCore { internal class AspNetCoreSessionIdResolver : ISessionIdResolver { @@ -13,7 +13,7 @@ namespace Umbraco.Web.BackOffice.AspNetCore _httpContextAccessor = httpContextAccessor; } - + public string SessionId { get diff --git a/src/Umbraco.Web.Common/AspNetCore/AspNetCoreUmbracoApplicationLifetime.cs b/src/Umbraco.Web.Common/AspNetCore/AspNetCoreUmbracoApplicationLifetime.cs index 45cfb7d570..d73b9bebd4 100644 --- a/src/Umbraco.Web.Common/AspNetCore/AspNetCoreUmbracoApplicationLifetime.cs +++ b/src/Umbraco.Web.Common/AspNetCore/AspNetCoreUmbracoApplicationLifetime.cs @@ -15,11 +15,6 @@ namespace Umbraco.Web.Common.AspNetCore { _httpContextAccessor = httpContextAccessor; _hostApplicationLifetime = hostApplicationLifetime; - - hostApplicationLifetime.ApplicationStarted.Register(() => - { - ApplicationInit?.Invoke(this, EventArgs.Empty); - }); } public bool IsRestarting { get; set; } @@ -39,6 +34,10 @@ namespace Umbraco.Web.Common.AspNetCore _hostApplicationLifetime.StopApplication(); } + public void InvokeApplicationInit() + { + ApplicationInit?.Invoke(this, EventArgs.Empty); + } public event EventHandler ApplicationInit; } } diff --git a/src/Umbraco.Web.BackOffice/AspNetCore/UmbracoCoreServiceCollectionExtensions.cs b/src/Umbraco.Web.Common/AspNetCore/UmbracoCoreServiceCollectionExtensions.cs similarity index 97% rename from src/Umbraco.Web.BackOffice/AspNetCore/UmbracoCoreServiceCollectionExtensions.cs rename to src/Umbraco.Web.Common/AspNetCore/UmbracoCoreServiceCollectionExtensions.cs index c14b164e9c..eb320c459f 100644 --- a/src/Umbraco.Web.BackOffice/AspNetCore/UmbracoCoreServiceCollectionExtensions.cs +++ b/src/Umbraco.Web.Common/AspNetCore/UmbracoCoreServiceCollectionExtensions.cs @@ -19,9 +19,8 @@ using Umbraco.Core.Persistence; using Umbraco.Core.Runtime; using Umbraco.Web.Common.Runtime.Profiler; -namespace Umbraco.Web.BackOffice.AspNetCore +namespace Umbraco.Web.Common.AspNetCore { - // TODO: Move to Umbraco.Web.Common public static class UmbracoCoreServiceCollectionExtensions { /// @@ -71,7 +70,8 @@ namespace Umbraco.Web.BackOffice.AspNetCore public static IServiceCollection AddUmbracoCore(this IServiceCollection services, IWebHostEnvironment webHostEnvironment, IRegister umbContainer, Assembly entryAssembly) { if (services is null) throw new ArgumentNullException(nameof(services)); - if (umbContainer is null) throw new ArgumentNullException(nameof(umbContainer)); + var container = umbContainer; + if (container is null) throw new ArgumentNullException(nameof(container)); if (entryAssembly is null) throw new ArgumentNullException(nameof(entryAssembly)); // Special case! The generic host adds a few default services but we need to manually add this one here NOW because @@ -97,7 +97,7 @@ namespace Umbraco.Web.BackOffice.AspNetCore backOfficeInfo, typeFinder); - var factory = coreRuntime.Configure(umbContainer); + var factory = coreRuntime.Configure(container); return services; } diff --git a/src/Umbraco.Web.Common/Lifetime/IUmbracoRequestLifetime.cs b/src/Umbraco.Web.Common/Lifetime/IUmbracoRequestLifetime.cs new file mode 100644 index 0000000000..616a75bfe7 --- /dev/null +++ b/src/Umbraco.Web.Common/Lifetime/IUmbracoRequestLifetime.cs @@ -0,0 +1,11 @@ +using System; +using Microsoft.AspNetCore.Http; + +namespace Umbraco.Web.Common.Lifetime +{ + public interface IUmbracoRequestLifetime + { + event EventHandler RequestStart; + event EventHandler RequestEnd; + } +} diff --git a/src/Umbraco.Web.Common/Lifetime/IUmbracoRequestLifetimeManager.cs b/src/Umbraco.Web.Common/Lifetime/IUmbracoRequestLifetimeManager.cs new file mode 100644 index 0000000000..e4c671c2d3 --- /dev/null +++ b/src/Umbraco.Web.Common/Lifetime/IUmbracoRequestLifetimeManager.cs @@ -0,0 +1,10 @@ +using Microsoft.AspNetCore.Http; + +namespace Umbraco.Web.Common.Lifetime +{ + public interface IUmbracoRequestLifetimeManager + { + void InitRequest(HttpContext context); + void EndRequest(HttpContext context); + } +} diff --git a/src/Umbraco.Web.Common/Lifetime/UmbracoRequestLifetime.cs b/src/Umbraco.Web.Common/Lifetime/UmbracoRequestLifetime.cs new file mode 100644 index 0000000000..43810a9e66 --- /dev/null +++ b/src/Umbraco.Web.Common/Lifetime/UmbracoRequestLifetime.cs @@ -0,0 +1,21 @@ +using System; +using Microsoft.AspNetCore.Http; + +namespace Umbraco.Web.Common.Lifetime +{ + public class UmbracoRequestLifetime : IUmbracoRequestLifetime, IUmbracoRequestLifetimeManager + { + public event EventHandler RequestStart; + public event EventHandler RequestEnd; + + public void InitRequest(HttpContext context) + { + RequestStart?.Invoke(this, context); + } + + public void EndRequest(HttpContext context) + { + RequestEnd?.Invoke(this, context); + } + } +} diff --git a/src/Umbraco.Web.Common/Middleware/UmbracoRequestMiddleware.cs b/src/Umbraco.Web.Common/Middleware/UmbracoRequestMiddleware.cs index 60b6463152..93461fc1d5 100644 --- a/src/Umbraco.Web.Common/Middleware/UmbracoRequestMiddleware.cs +++ b/src/Umbraco.Web.Common/Middleware/UmbracoRequestMiddleware.cs @@ -1,6 +1,7 @@ using System; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; +using Umbraco.Web.Common.Lifetime; namespace Umbraco.Web.Common.Middleware { @@ -22,31 +23,4 @@ namespace Umbraco.Web.Common.Middleware } } - public interface IUmbracoRequestLifetime - { - event EventHandler RequestStart; - event EventHandler RequestEnd; - } - - public class UmbracoRequestLifetime : IUmbracoRequestLifetime, IUmbracoRequestLifetimeManager - { - public event EventHandler RequestStart; - public event EventHandler RequestEnd; - - public void InitRequest(HttpContext context) - { - RequestStart?.Invoke(this, context); - } - - public void EndRequest(HttpContext context) - { - RequestEnd?.Invoke(this, context); - } - } - - public interface IUmbracoRequestLifetimeManager - { - void InitRequest(HttpContext context); - void EndRequest(HttpContext context); - } } diff --git a/src/Umbraco.Web.Common/Runtime/AspNetCoreComponent.cs b/src/Umbraco.Web.Common/Runtime/AspNetCoreComponent.cs new file mode 100644 index 0000000000..04ae022fa5 --- /dev/null +++ b/src/Umbraco.Web.Common/Runtime/AspNetCoreComponent.cs @@ -0,0 +1,29 @@ +using Microsoft.Extensions.Hosting; +using Umbraco.Core.Composing; +using Umbraco.Net; + +namespace Umbraco.Web.Common.Runtime +{ + public sealed class AspNetCoreComponent : IComponent + { + private readonly IHostApplicationLifetime _hostApplicationLifetime; + private readonly IUmbracoApplicationLifetime _umbracoApplicationLifetime; + + public AspNetCoreComponent(IHostApplicationLifetime hostApplicationLifetime, IUmbracoApplicationLifetime umbracoApplicationLifetime) + { + _hostApplicationLifetime = hostApplicationLifetime; + _umbracoApplicationLifetime = umbracoApplicationLifetime; + } + + public void Initialize() + { + _hostApplicationLifetime.ApplicationStarted.Register(() => { + _umbracoApplicationLifetime.InvokeApplicationInit(); + }); + } + + public void Terminate() + { + } + } +} diff --git a/src/Umbraco.Web.BackOffice/AspNetCore/AspNetCoreComposer.cs b/src/Umbraco.Web.Common/Runtime/AspNetCoreComposer.cs similarity index 54% rename from src/Umbraco.Web.BackOffice/AspNetCore/AspNetCoreComposer.cs rename to src/Umbraco.Web.Common/Runtime/AspNetCoreComposer.cs index 463af87849..f9a6a770e2 100644 --- a/src/Umbraco.Web.BackOffice/AspNetCore/AspNetCoreComposer.cs +++ b/src/Umbraco.Web.Common/Runtime/AspNetCoreComposer.cs @@ -5,24 +5,33 @@ using Umbraco.Core.Hosting; using Umbraco.Net; using Umbraco.Core.Runtime; using Umbraco.Web.Common.AspNetCore; +using Umbraco.Web.Common.Lifetime; -namespace Umbraco.Web.BackOffice.AspNetCore +namespace Umbraco.Web.Common.Runtime { /// /// Adds/replaces AspNetCore specific services /// [ComposeBefore(typeof(ICoreComposer))] [ComposeAfter(typeof(CoreInitialComposer))] - public class AspNetCoreComposer : IComposer + public class AspNetCoreComposer : ComponentComposer, IComposer { - public void Compose(Composition composition) + public new void Compose(Composition composition) { + base.Compose(composition); + // AspNetCore specific services composition.RegisterUnique(); // Our own netcore implementations composition.RegisterUnique(); composition.RegisterUnique(); + + // The umbraco request lifetime + var umbracoRequestLifetime = new UmbracoRequestLifetime(); + composition.RegisterUnique(factory => umbracoRequestLifetime); + composition.RegisterUnique(factory => umbracoRequestLifetime); + composition.RegisterUnique(); } } } diff --git a/src/Umbraco.Web.Common/Runtime/Profiler/WebInitialComponent.cs b/src/Umbraco.Web.Common/Runtime/Profiler/WebInitialComponent.cs deleted file mode 100644 index 2194b3c038..0000000000 --- a/src/Umbraco.Web.Common/Runtime/Profiler/WebInitialComponent.cs +++ /dev/null @@ -1,18 +0,0 @@ -using Umbraco.Core.Composing; - -namespace Umbraco.Web.Common.Runtime.Profiler -{ - public sealed class WebInitialComponent : IComponent - { - - - public void Initialize() - { - } - - public void Terminate() - { - } - - } -} diff --git a/src/Umbraco.Web.Common/Runtime/Profiler/WebInitialComposer.cs b/src/Umbraco.Web.Common/Runtime/Profiler/WebInitialComposer.cs deleted file mode 100644 index 472d8bf8e9..0000000000 --- a/src/Umbraco.Web.Common/Runtime/Profiler/WebInitialComposer.cs +++ /dev/null @@ -1,24 +0,0 @@ -using Microsoft.AspNetCore.Http; -using Umbraco.Core; -using Umbraco.Core.Composing; -using Umbraco.Net; -using Umbraco.Web.Common.AspNetCore; -using Umbraco.Web.Common.Middleware; - -namespace Umbraco.Web.Common.Runtime.Profiler -{ - public class WebInitialComposer : ComponentComposer, ICoreComposer - { - - public override void Compose(Composition composition) - { - base.Compose(composition); - - var umbracoRequestLifetime = new UmbracoRequestLifetime(); - - composition.RegisterUnique(factory => umbracoRequestLifetime); - composition.RegisterUnique(factory => umbracoRequestLifetime); - composition.RegisterUnique(); - } - } -} diff --git a/src/Umbraco.Web.Common/Runtime/Profiler/WebProfiler.cs b/src/Umbraco.Web.Common/Runtime/Profiler/WebProfiler.cs index 4e1a7c7d84..8baf8790c3 100644 --- a/src/Umbraco.Web.Common/Runtime/Profiler/WebProfiler.cs +++ b/src/Umbraco.Web.Common/Runtime/Profiler/WebProfiler.cs @@ -22,10 +22,15 @@ namespace Umbraco.Web.Common.Runtime.Profiler // create our own provider, which can provide a profiler even during boot _provider = new WebProfilerProvider(); _httpContextAccessor = httpContextAccessor; + + MiniProfiler.DefaultOptions.ProfilerProvider = _provider; } - public string Render() => MiniProfiler.Current - .RenderIncludes(_httpContextAccessor.HttpContext, RenderPosition.Right).ToString(); + public string Render() + { + return MiniProfiler.Current + .RenderIncludes(_httpContextAccessor.HttpContext, RenderPosition.Right).ToString(); + } public IDisposable Step(string name) => MiniProfiler.Current?.Step(name); diff --git a/src/Umbraco.Web.Common/Runtime/Profiler/WebProfilerComponent.cs b/src/Umbraco.Web.Common/Runtime/Profiler/WebProfilerComponent.cs index 425996a765..9a656ae4f3 100644 --- a/src/Umbraco.Web.Common/Runtime/Profiler/WebProfilerComponent.cs +++ b/src/Umbraco.Web.Common/Runtime/Profiler/WebProfilerComponent.cs @@ -2,6 +2,7 @@ using Umbraco.Core.Composing; using Umbraco.Core.Logging; using Umbraco.Net; +using Umbraco.Web.Common.Lifetime; using Umbraco.Web.Common.Middleware; namespace Umbraco.Web.Common.Runtime.Profiler diff --git a/src/Umbraco.Web.Common/Umbraco.Web.Common.csproj b/src/Umbraco.Web.Common/Umbraco.Web.Common.csproj index cb9ca9156a..2cf6790484 100644 --- a/src/Umbraco.Web.Common/Umbraco.Web.Common.csproj +++ b/src/Umbraco.Web.Common/Umbraco.Web.Common.csproj @@ -11,6 +11,7 @@ + diff --git a/src/Umbraco.Web.UI.NetCore/Startup.cs b/src/Umbraco.Web.UI.NetCore/Startup.cs index fe77dfabda..b38333bb9a 100644 --- a/src/Umbraco.Web.UI.NetCore/Startup.cs +++ b/src/Umbraco.Web.UI.NetCore/Startup.cs @@ -8,7 +8,9 @@ using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; +using Umbraco.Composing; using Umbraco.Web.BackOffice.AspNetCore; +using Umbraco.Web.Common.AspNetCore; using Umbraco.Web.Common.Extensions; using Umbraco.Web.Website.AspNetCore; @@ -52,6 +54,7 @@ namespace Umbraco.Web.UI.BackOffice app.UseDeveloperExceptionPage(); } + app.UseUmbracoCore(); app.UseUmbracoWebsite(); app.UseUmbracoBackOffice(); @@ -59,7 +62,10 @@ namespace Umbraco.Web.UI.BackOffice app.UseEndpoints(endpoints => { - endpoints.MapGet("/", async context => { await context.Response.WriteAsync("Hello World!"); }); + endpoints.MapGet("/", async context => + { + await context.Response.WriteAsync($"Hello World!{Current.Profiler.Render()}"); + }); }); } } diff --git a/src/Umbraco.Web/AspNet/AspNetUmbracoApplicationLifetime.cs b/src/Umbraco.Web/AspNet/AspNetUmbracoApplicationLifetime.cs index f5ebc03cd0..9818a510b2 100644 --- a/src/Umbraco.Web/AspNet/AspNetUmbracoApplicationLifetime.cs +++ b/src/Umbraco.Web/AspNet/AspNetUmbracoApplicationLifetime.cs @@ -35,5 +35,9 @@ namespace Umbraco.Web.AspNet } public event EventHandler ApplicationInit; + public void InvokeApplicationInit() + { + + } } } diff --git a/src/Umbraco.Web/Logging/WebProfiler.cs b/src/Umbraco.Web/Logging/WebProfiler.cs index 512edb2296..e390950c0b 100755 --- a/src/Umbraco.Web/Logging/WebProfiler.cs +++ b/src/Umbraco.Web/Logging/WebProfiler.cs @@ -29,7 +29,7 @@ namespace Umbraco.Web.Logging MiniProfiler.Configure(new MiniProfilerOptions { SqlFormatter = new SqlServerFormatter(), - StackMaxLength = 5000, + StackMaxLength = 5000, ProfilerProvider = _provider }); } From 93fd2eb954a5bb8ba49ea91e80d75c07e9b690d2 Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Sat, 28 Mar 2020 13:59:52 +0100 Subject: [PATCH 49/73] Fixed creation of image processing options in grid media controller. --- .../grid/editors/media.controller.js | 32 ++++++++----------- 1 file changed, 14 insertions(+), 18 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/media.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/media.controller.js index 1fb8cbe2f2..7c98fbfc5e 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/media.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/media.controller.js @@ -2,7 +2,7 @@ angular.module("umbraco") .controller("Umbraco.PropertyEditors.Grid.MediaController", function ($scope, userService, editorService, mediaHelper) { - $scope.thumbnailUrl = getThumbnailUrl(); + setThumbnailUrl(); if (!$scope.model.config.startNodeId) { if ($scope.model.config.ignoreUserStartNodes === true) { @@ -55,26 +55,24 @@ angular.module("umbraco") return; // simply skip that } - $scope.thumbnailUrl = getThumbnailUrl(); + setThumbnailUrl(); }, true); - function getThumbnailUrl() { + function setThumbnailUrl() { if ($scope.control.value && $scope.control.value.image) { - - var imageOptions; + var url = $scope.control.value.image; + var imageOptions = {}; if (url.indexOf('?') == -1) { // set default size if no crop present (moved from the view) - imageOptions = { - animationprocessmode : false, - width: 800 - } + imageOptions.animationprocessmode = false; + imageOptions.width = 800; } else { - imageOptions = { - animationprocessmode: "first", - height: $scope.control.editor.config.size.height, - width: $scope.control.editor.config.size.width - }; + if ($scope.control.editor.config && $scope.control.editor.config.size) { + imageOptions.animationprocessmode = "first"; + imageOptions.height = $scope.control.editor.config.size.height; + imageOptions.width = $scope.control.editor.config.size.width; + } if ($scope.control.value.focalPoint) { imageOptions.focalPoint = { @@ -85,13 +83,11 @@ angular.module("umbraco") } } - return mediaHelper.getProcessedImageUrl($scope.control.value.image, imageOptions) + mediaHelper.getProcessedImageUrl($scope.control.value.image, imageOptions) .then(function (url) { - return url; + $scope.thumbnailUrl = url; }); } - - return null; }; }); From 3e40389819c57a5edc98f592bf250ed0a53d10bc Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Sat, 28 Mar 2020 14:55:03 +0100 Subject: [PATCH 50/73] PR review amends on key value model and repository classes. --- src/Umbraco.Core/Models/IKeyValue.cs | 2 -- src/Umbraco.Core/Models/KeyValue.cs | 5 +++-- .../Implement/KeyValueRepository.cs | 18 +++--------------- .../Implement/RepositoryBaseOfTIdTEntity.cs | 2 +- 4 files changed, 7 insertions(+), 20 deletions(-) diff --git a/src/Umbraco.Core/Models/IKeyValue.cs b/src/Umbraco.Core/Models/IKeyValue.cs index 7396ae1b68..6025d4d37b 100644 --- a/src/Umbraco.Core/Models/IKeyValue.cs +++ b/src/Umbraco.Core/Models/IKeyValue.cs @@ -8,7 +8,5 @@ namespace Umbraco.Core.Models string Identifier { get; set; } string Value { get; set; } - - DateTime UpdateDate { get; set; } } } diff --git a/src/Umbraco.Core/Models/KeyValue.cs b/src/Umbraco.Core/Models/KeyValue.cs index ba98b577a4..2d47fcbfb3 100644 --- a/src/Umbraco.Core/Models/KeyValue.cs +++ b/src/Umbraco.Core/Models/KeyValue.cs @@ -9,11 +9,10 @@ namespace Umbraco.Core.Models /// [Serializable] [DataContract(IsReference = true)] - public class KeyValue : EntityBase, IKeyValue + public class KeyValue : EntityBase, IKeyValue, IEntity { private string _identifier; private string _value; - private DateTime _updateDate; /// public string Identifier @@ -28,5 +27,7 @@ namespace Umbraco.Core.Models get => _value; set => SetPropertyValueAndDetectChanges(value, ref _value, nameof(Value)); } + + bool IEntity.HasIdentity => !string.IsNullOrEmpty(Identifier); } } diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/KeyValueRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/KeyValueRepository.cs index eab4049ec9..094c83b05f 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/KeyValueRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/KeyValueRepository.cs @@ -33,21 +33,9 @@ namespace Umbraco.Core.Persistence.Repositories.Implement return database.FirstOrDefault(sql)?.Value; } - #region Overrides of IReadWriteQueryRepository - - public override void Save(IKeyValue entity) - { - if (Get(entity.Identifier) == null) - PersistNewItem(entity); - else - PersistUpdatedItem(entity); - } - - #endregion - #region Overrides of NPocoRepositoryBase - protected override Guid NodeObjectTypeId => throw new NotImplementedException(); + protected override Guid NodeObjectTypeId => throw new NotSupportedException(); protected override Sql GetBaseQuery(bool isCount) { @@ -70,7 +58,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement protected override IEnumerable GetDeleteClauses() { - return new List(); + return Enumerable.Empty(); } protected override IKeyValue PerformGet(string id) @@ -89,7 +77,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement protected override IEnumerable PerformGetByQuery(IQuery query) { - throw new NotImplementedException(); + throw new NotSupportedException(); } protected override void PersistNewItem(IKeyValue entity) diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RepositoryBaseOfTIdTEntity.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RepositoryBaseOfTIdTEntity.cs index 6985bf78da..e8397ba22a 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RepositoryBaseOfTIdTEntity.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RepositoryBaseOfTIdTEntity.cs @@ -132,7 +132,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement /// /// This method is backed by an cache /// - public virtual void Save(TEntity entity) + public void Save(TEntity entity) { if (entity.HasIdentity == false) CachePolicy.Create(entity, PersistNewItem); From f01ccb49dd8d181a4ba33293163f7f83e6485da8 Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Sat, 28 Mar 2020 15:04:36 +0100 Subject: [PATCH 51/73] Removed key value service initialization logic. --- .../CompositionExtensions/Repositories.cs | 2 - .../Custom/IKeyValueServiceInitialization.cs | 9 --- .../Custom/KeyValueServiceInitialization.cs | 80 ------------------- .../Runtime/CoreInitialComposer.cs | 14 ++-- .../Services/Implement/KeyValueService.cs | 56 +------------ src/Umbraco.Tests/TestHelpers/TestObjects.cs | 3 +- 6 files changed, 7 insertions(+), 157 deletions(-) delete mode 100644 src/Umbraco.Infrastructure/Migrations/Custom/IKeyValueServiceInitialization.cs delete mode 100644 src/Umbraco.Infrastructure/Migrations/Custom/KeyValueServiceInitialization.cs diff --git a/src/Umbraco.Infrastructure/Composing/CompositionExtensions/Repositories.cs b/src/Umbraco.Infrastructure/Composing/CompositionExtensions/Repositories.cs index b8231241a1..9a23255cc4 100644 --- a/src/Umbraco.Infrastructure/Composing/CompositionExtensions/Repositories.cs +++ b/src/Umbraco.Infrastructure/Composing/CompositionExtensions/Repositories.cs @@ -1,6 +1,5 @@ using Umbraco.Core.Persistence.Repositories; using Umbraco.Core.Persistence.Repositories.Implement; -using Umbraco.Infrastructure.Migrations.Custom; namespace Umbraco.Core.Composing.CompositionExtensions { @@ -49,7 +48,6 @@ namespace Umbraco.Core.Composing.CompositionExtensions composition.RegisterUnique(); composition.RegisterUnique(); composition.RegisterUnique(); - composition.RegisterUnique(); composition.RegisterUnique(); composition.RegisterUnique(); diff --git a/src/Umbraco.Infrastructure/Migrations/Custom/IKeyValueServiceInitialization.cs b/src/Umbraco.Infrastructure/Migrations/Custom/IKeyValueServiceInitialization.cs deleted file mode 100644 index a863905dc7..0000000000 --- a/src/Umbraco.Infrastructure/Migrations/Custom/IKeyValueServiceInitialization.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Umbraco.Core.Persistence; - -namespace Umbraco.Infrastructure.Migrations.Custom -{ - public interface IKeyValueServiceInitialization - { - void PerformInitialMigration(IUmbracoDatabase database); - } -} diff --git a/src/Umbraco.Infrastructure/Migrations/Custom/KeyValueServiceInitialization.cs b/src/Umbraco.Infrastructure/Migrations/Custom/KeyValueServiceInitialization.cs deleted file mode 100644 index 488acf507a..0000000000 --- a/src/Umbraco.Infrastructure/Migrations/Custom/KeyValueServiceInitialization.cs +++ /dev/null @@ -1,80 +0,0 @@ -using Umbraco.Core; -using Umbraco.Core.Logging; -using Umbraco.Core.Migrations; -using Umbraco.Core.Persistence; -using Umbraco.Core.Persistence.Dtos; - -namespace Umbraco.Infrastructure.Migrations.Custom -{ - public class KeyValueServiceInitialization : IKeyValueServiceInitialization - { - private readonly ILogger _logger; - - public KeyValueServiceInitialization(ILogger logger) - { - _logger = logger; - } - - public void PerformInitialMigration(IUmbracoDatabase database) - { - var context = new MigrationContext(database, _logger); - var initMigration = new InitialMigration(context); - initMigration.Migrate(); - } - - /// - /// A custom migration that executes standalone during the Initialize phase of the KeyValueService. - /// - internal class InitialMigration : MigrationBase - { - public InitialMigration(IMigrationContext context) - : base(context) - { } - - public override void Migrate() - { - // as long as we are still running 7 this migration will be invoked, - // but due to multiple restarts during upgrades, maybe the table - // exists already - if (TableExists(Constants.DatabaseSchema.Tables.KeyValue)) - return; - - Logger.Info("Creating KeyValue structure."); - - // the locks table was initially created with an identity (auto-increment) primary key, - // but we don't want this, especially as we are about to insert a new row into the table, - // so here we drop that identity - DropLockTableIdentity(); - - // insert the lock object for key/value - Insert.IntoTable(Constants.DatabaseSchema.Tables.Lock).Row(new { id = Constants.Locks.KeyValues, name = "KeyValues", value = 1 }).Do(); - - // create the key-value table - Create.Table().Do(); - } - - private void DropLockTableIdentity() - { - // one cannot simply drop an identity, that requires a bit of work - - // create a temp. id column and copy values - Alter.Table(Constants.DatabaseSchema.Tables.Lock).AddColumn("nid").AsInt32().Nullable().Do(); - Execute.Sql("update umbracoLock set nid = id").Do(); - - // drop the id column entirely (cannot just drop identity) - Delete.PrimaryKey("PK_umbracoLock").FromTable(Constants.DatabaseSchema.Tables.Lock).Do(); - Delete.Column("id").FromTable(Constants.DatabaseSchema.Tables.Lock).Do(); - - // recreate the id column without identity and copy values - Alter.Table(Constants.DatabaseSchema.Tables.Lock).AddColumn("id").AsInt32().Nullable().Do(); - Execute.Sql("update umbracoLock set id = nid").Do(); - - // drop the temp. id column - Delete.Column("nid").FromTable(Constants.DatabaseSchema.Tables.Lock).Do(); - - // complete the primary key - Alter.Table(Constants.DatabaseSchema.Tables.Lock).AlterColumn("id").AsInt32().NotNullable().PrimaryKey("PK_umbracoLock").Do(); - } - } - } -} diff --git a/src/Umbraco.Infrastructure/Runtime/CoreInitialComposer.cs b/src/Umbraco.Infrastructure/Runtime/CoreInitialComposer.cs index f1f96a9f4a..aa9ccb958f 100644 --- a/src/Umbraco.Infrastructure/Runtime/CoreInitialComposer.cs +++ b/src/Umbraco.Infrastructure/Runtime/CoreInitialComposer.cs @@ -1,5 +1,4 @@ using System; -using System.IO; using Umbraco.Core.Cache; using Umbraco.Core.Composing; using Umbraco.Core.Composing.CompositionExtensions; @@ -7,9 +6,8 @@ using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.Grid; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Dashboards; -using Umbraco.Core.Hosting; using Umbraco.Core.Dictionary; -using Umbraco.Core.IO; +using Umbraco.Core.Hosting; using Umbraco.Core.Logging; using Umbraco.Core.Manifest; using Umbraco.Core.Migrations; @@ -25,16 +23,15 @@ using Umbraco.Core.Services; using Umbraco.Core.Services.Implement; using Umbraco.Core.Strings; using Umbraco.Core.Sync; -using Umbraco.Web.Models.PublishedContent; -using Umbraco.Web.PublishedCache; using Umbraco.Web; -using Umbraco.Web.Migrations.PostMigrations; using Umbraco.Web.Install; -using Umbraco.Web.Trees; +using Umbraco.Web.Migrations.PostMigrations; +using Umbraco.Web.Models.PublishedContent; using Umbraco.Web.PropertyEditors; +using Umbraco.Web.PublishedCache; using Umbraco.Web.Services; +using Umbraco.Web.Trees; using IntegerValidator = Umbraco.Core.PropertyEditors.Validators.IntegerValidator; -using Umbraco.Infrastructure.Migrations.Custom; namespace Umbraco.Core.Runtime { @@ -149,7 +146,6 @@ namespace Umbraco.Core.Runtime .Append(); composition.RegisterUnique(factory => new MigrationBuilder(factory)); - composition.RegisterUnique(); // by default, register a noop factory composition.RegisterUnique(); diff --git a/src/Umbraco.Infrastructure/Services/Implement/KeyValueService.cs b/src/Umbraco.Infrastructure/Services/Implement/KeyValueService.cs index 6835457f64..12ca34b020 100644 --- a/src/Umbraco.Infrastructure/Services/Implement/KeyValueService.cs +++ b/src/Umbraco.Infrastructure/Services/Implement/KeyValueService.cs @@ -1,74 +1,24 @@ using System; -using Umbraco.Core.Configuration; -using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Persistence.Repositories; using Umbraco.Core.Scoping; -using Umbraco.Infrastructure.Migrations.Custom; namespace Umbraco.Core.Services.Implement { internal class KeyValueService : IKeyValueService { - private readonly object _initialock = new object(); private readonly IScopeProvider _scopeProvider; private readonly IKeyValueRepository _repository; - private readonly IKeyValueServiceInitialization _initialization; - private readonly ILogger _logger; - private readonly IUmbracoVersion _umbracoVersion; - private bool _initialized; - public KeyValueService(IScopeProvider scopeProvider, IKeyValueRepository repository, IKeyValueServiceInitialization initialization, ILogger logger, IUmbracoVersion umbracoVersion) + public KeyValueService(IScopeProvider scopeProvider, IKeyValueRepository repository) { _scopeProvider = scopeProvider; _repository = repository; - _initialization = initialization; - _logger = logger; - _umbracoVersion = umbracoVersion; } - private void EnsureInitialized() - { - lock (_initialock) - { - if (_initialized) return; - Initialize(); - } - } - - private void Initialize() - { - // the key/value service is entirely self-managed, because it is used by the - // upgrader and anything we might change need to happen before everything else - - // if already running 8, either following an upgrade or an install, - // then everything should be ok (the table should exist, etc) - - if (_umbracoVersion.LocalVersion != null && _umbracoVersion.LocalVersion.Major >= 8) - { - _initialized = true; - return; - } - - // else we are upgrading from 7, we can assume that the locks table - // exists, but we need to create everything for key/value - - using (var scope = _scopeProvider.CreateScope()) - { - _initialization.PerformInitialMigration(scope.Database); - scope.Complete(); - } - - // but don't assume we are initializing - // we are upgrading from v7 and if anything goes wrong, - // the table and everything will be rolled back - } - /// public string GetValue(string key) { - EnsureInitialized(); - using (var scope = _scopeProvider.CreateScope()) { return _repository.Get(key)?.Value; @@ -78,8 +28,6 @@ namespace Umbraco.Core.Services.Implement /// public void SetValue(string key, string value) { - EnsureInitialized(); - using (var scope = _scopeProvider.CreateScope()) { scope.WriteLock(Constants.Locks.KeyValues); @@ -116,8 +64,6 @@ namespace Umbraco.Core.Services.Implement /// public bool TrySetValue(string key, string originalValue, string newValue) { - EnsureInitialized(); - using (var scope = _scopeProvider.CreateScope()) { scope.WriteLock(Constants.Locks.KeyValues); diff --git a/src/Umbraco.Tests/TestHelpers/TestObjects.cs b/src/Umbraco.Tests/TestHelpers/TestObjects.cs index ccbea01d0b..13d5cc0328 100644 --- a/src/Umbraco.Tests/TestHelpers/TestObjects.cs +++ b/src/Umbraco.Tests/TestHelpers/TestObjects.cs @@ -21,7 +21,6 @@ using Umbraco.Core.Scoping; using Umbraco.Core.Services; using Umbraco.Core.Services.Implement; using Umbraco.Core.Strings; -using Umbraco.Infrastructure.Migrations.Custom; using Umbraco.Tests.TestHelpers.Stubs; using Current = Umbraco.Web.Composing.Current; @@ -194,7 +193,7 @@ namespace Umbraco.Tests.TestHelpers var tagService = GetLazyService(factory, c => new TagService(scopeProvider, logger, eventMessagesFactory, GetRepo(c))); var redirectUrlService = GetLazyService(factory, c => new RedirectUrlService(scopeProvider, logger, eventMessagesFactory, GetRepo(c))); var consentService = GetLazyService(factory, c => new ConsentService(scopeProvider, logger, eventMessagesFactory, GetRepo(c))); - var keyValueService = GetLazyService(factory, c => new KeyValueService(scopeProvider, GetRepo(c), Mock.Of(), logger, umbracoVersion)); + var keyValueService = GetLazyService(factory, c => new KeyValueService(scopeProvider, GetRepo(c))); var contentTypeServiceBaseFactory = GetLazyService(factory, c => new ContentTypeBaseServiceProvider(factory.GetInstance(),factory.GetInstance(),factory.GetInstance())); return new ServiceContext( From 8958cf90b978cbebc72a7f523ad61308dc31e189 Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Sat, 28 Mar 2020 15:13:50 +0100 Subject: [PATCH 52/73] Moved non-scoped retrieval of key value into an extension method in IUmbracoDatabase. --- .../Repositories/Implement/KeyValueRepository.cs | 15 --------------- .../Persistence/UmbracoDatabaseExtensions.cs | 16 ++++++++++++++++ src/Umbraco.Infrastructure/RuntimeState.cs | 2 +- 3 files changed, 17 insertions(+), 16 deletions(-) diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/KeyValueRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/KeyValueRepository.cs index 094c83b05f..2b318b2056 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/KeyValueRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/KeyValueRepository.cs @@ -18,21 +18,6 @@ namespace Umbraco.Core.Persistence.Repositories.Implement : base(scopeAccessor, AppCaches.NoCache, logger) { } - /// - /// Gets a value directly from the database, no scope, nothing. - /// - /// Used by to determine the runtime state. - internal static string GetValue(IUmbracoDatabase database, string key) - { - if (database is null) return null; - - var sql = database.SqlContext.Sql() - .Select() - .From() - .Where(x => x.Key == key); - return database.FirstOrDefault(sql)?.Value; - } - #region Overrides of NPocoRepositoryBase protected override Guid NodeObjectTypeId => throw new NotSupportedException(); diff --git a/src/Umbraco.Infrastructure/Persistence/UmbracoDatabaseExtensions.cs b/src/Umbraco.Infrastructure/Persistence/UmbracoDatabaseExtensions.cs index 249dd3dc73..4e5c3a113a 100644 --- a/src/Umbraco.Infrastructure/Persistence/UmbracoDatabaseExtensions.cs +++ b/src/Umbraco.Infrastructure/Persistence/UmbracoDatabaseExtensions.cs @@ -1,4 +1,5 @@ using System; +using Umbraco.Core.Persistence.Dtos; namespace Umbraco.Core.Persistence { @@ -10,5 +11,20 @@ namespace Umbraco.Core.Persistence if (asDatabase == null) throw new Exception("oops: database."); return asDatabase; } + + /// + /// Gets a key/value directly from the database, no scope, nothing. + /// + /// Used by to determine the runtime state. + public static string GetFromKeyValueTable(this IUmbracoDatabase database, string key) + { + if (database is null) return null; + + var sql = database.SqlContext.Sql() + .Select() + .From() + .Where(x => x.Key == key); + return database.FirstOrDefault(sql)?.Value; + } } } diff --git a/src/Umbraco.Infrastructure/RuntimeState.cs b/src/Umbraco.Infrastructure/RuntimeState.cs index 9e5cf96906..d6f674f028 100644 --- a/src/Umbraco.Infrastructure/RuntimeState.cs +++ b/src/Umbraco.Infrastructure/RuntimeState.cs @@ -225,7 +225,7 @@ namespace Umbraco.Core // no scope, no service - just directly accessing the database using (var database = databaseFactory.CreateDatabase()) { - CurrentMigrationState = KeyValueRepository.GetValue(database, stateValueKey); + CurrentMigrationState = database.GetFromKeyValueTable(stateValueKey); FinalMigrationState = upgrader.Plan.FinalState; } From 5a29365063d1bd783137690edb86f6a7d96260bc Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Sat, 28 Mar 2020 15:20:17 +0100 Subject: [PATCH 53/73] Revert change to overridden save method on key value repository. --- .../Repositories/Implement/KeyValueRepository.cs | 12 ++++++++++++ .../Implement/RepositoryBaseOfTIdTEntity.cs | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/KeyValueRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/KeyValueRepository.cs index 2b318b2056..3c2da4d5b7 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/KeyValueRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/KeyValueRepository.cs @@ -18,6 +18,18 @@ namespace Umbraco.Core.Persistence.Repositories.Implement : base(scopeAccessor, AppCaches.NoCache, logger) { } + #region Overrides of IReadWriteQueryRepository + + public override void Save(IKeyValue entity) + { + if (Get(entity.Identifier) == null) + PersistNewItem(entity); + else + PersistUpdatedItem(entity); + } + + #endregion + #region Overrides of NPocoRepositoryBase protected override Guid NodeObjectTypeId => throw new NotSupportedException(); diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RepositoryBaseOfTIdTEntity.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RepositoryBaseOfTIdTEntity.cs index e8397ba22a..6985bf78da 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RepositoryBaseOfTIdTEntity.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RepositoryBaseOfTIdTEntity.cs @@ -132,7 +132,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement /// /// This method is backed by an cache /// - public void Save(TEntity entity) + public virtual void Save(TEntity entity) { if (entity.HasIdentity == false) CachePolicy.Create(entity, PersistNewItem); From 50631c2b2894168915c7851ce2c5cc4d8ae59360 Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Sat, 28 Mar 2020 20:54:10 +0100 Subject: [PATCH 54/73] Migrated member group unit tests to new unit test project using builder pattern. --- .../Builders/ChildBuilderBase.cs | 1 - .../Builders/DataTypeBuilder.cs | 7 ++ .../Builders/Extensions/BuilderExtensions.cs | 7 ++ .../Builders/GenericDictionaryBuilder.cs | 26 ++++++ .../Interfaces/IWithCreatorIdBuilder.cs | 9 ++ .../Builders/MemberGroupBuilder.cs | 90 +++++++++++++++++++ .../Models/DataTypeTests.cs | 2 +- .../Models/MemberGroupTests.cs | 48 +++++----- src/Umbraco.Tests/Umbraco.Tests.csproj | 1 - 9 files changed, 162 insertions(+), 29 deletions(-) create mode 100644 src/Umbraco.Tests.Common/Builders/GenericDictionaryBuilder.cs create mode 100644 src/Umbraco.Tests.Common/Builders/Interfaces/IWithCreatorIdBuilder.cs create mode 100644 src/Umbraco.Tests.Common/Builders/MemberGroupBuilder.cs rename src/{Umbraco.Tests => Umbraco.Tests.UnitTests/Umbraco.Infrastructure}/Models/MemberGroupTests.cs (61%) diff --git a/src/Umbraco.Tests.Common/Builders/ChildBuilderBase.cs b/src/Umbraco.Tests.Common/Builders/ChildBuilderBase.cs index e1436ac1fe..fcd9691e1f 100644 --- a/src/Umbraco.Tests.Common/Builders/ChildBuilderBase.cs +++ b/src/Umbraco.Tests.Common/Builders/ChildBuilderBase.cs @@ -9,7 +9,6 @@ namespace Umbraco.Tests.Common.Builders _parentBuilder = parentBuilder; } - public TParent Done() { return _parentBuilder; diff --git a/src/Umbraco.Tests.Common/Builders/DataTypeBuilder.cs b/src/Umbraco.Tests.Common/Builders/DataTypeBuilder.cs index 20dc1bab81..04a464e5bd 100644 --- a/src/Umbraco.Tests.Common/Builders/DataTypeBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/DataTypeBuilder.cs @@ -8,6 +8,7 @@ namespace Umbraco.Tests.Common.Builders : BuilderBase, IWithIdBuilder, IWithKeyBuilder, + IWithCreatorIdBuilder, IWithCreateDateBuilder, IWithUpdateDateBuilder, IWithDeleteDateBuilder, @@ -133,6 +134,12 @@ namespace Umbraco.Tests.Common.Builders set => _key = value; } + int? IWithCreatorIdBuilder.CreatorId + { + get => _creatorId; + set => _creatorId = value; + } + DateTime? IWithCreateDateBuilder.CreateDate { get => _createDate; diff --git a/src/Umbraco.Tests.Common/Builders/Extensions/BuilderExtensions.cs b/src/Umbraco.Tests.Common/Builders/Extensions/BuilderExtensions.cs index c8f3f80bf1..6a2407ae53 100644 --- a/src/Umbraco.Tests.Common/Builders/Extensions/BuilderExtensions.cs +++ b/src/Umbraco.Tests.Common/Builders/Extensions/BuilderExtensions.cs @@ -12,6 +12,13 @@ namespace Umbraco.Tests.Common.Builders.Extensions return builder; } + public static T WithCreatorId(this T builder, int creatorId) + where T : IWithCreatorIdBuilder + { + builder.CreatorId = creatorId; + return builder; + } + public static T WithCreateDate(this T builder, DateTime createDate) where T : IWithCreateDateBuilder { diff --git a/src/Umbraco.Tests.Common/Builders/GenericDictionaryBuilder.cs b/src/Umbraco.Tests.Common/Builders/GenericDictionaryBuilder.cs new file mode 100644 index 0000000000..0cf2284a49 --- /dev/null +++ b/src/Umbraco.Tests.Common/Builders/GenericDictionaryBuilder.cs @@ -0,0 +1,26 @@ +using System.Collections.Generic; + +namespace Umbraco.Tests.Common.Builders +{ + public class GenericDictionaryBuilder + : ChildBuilderBase> + { + private readonly IDictionary _dictionary; + + public GenericDictionaryBuilder(TBuilder parentBuilder) : base(parentBuilder) + { + _dictionary = new Dictionary(); + } + + public override IDictionary Build() + { + return new Dictionary(); + } + + public GenericDictionaryBuilder AddKeyValue(TKey key, TValue value) + { + _dictionary.Add(key, value); + return this; + } + } +} diff --git a/src/Umbraco.Tests.Common/Builders/Interfaces/IWithCreatorIdBuilder.cs b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithCreatorIdBuilder.cs new file mode 100644 index 0000000000..ae7712cf9e --- /dev/null +++ b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithCreatorIdBuilder.cs @@ -0,0 +1,9 @@ +using System; + +namespace Umbraco.Tests.Common.Builders.Interfaces +{ + public interface IWithCreatorIdBuilder + { + int? CreatorId { get; set; } + } +} diff --git a/src/Umbraco.Tests.Common/Builders/MemberGroupBuilder.cs b/src/Umbraco.Tests.Common/Builders/MemberGroupBuilder.cs new file mode 100644 index 0000000000..6092c79abb --- /dev/null +++ b/src/Umbraco.Tests.Common/Builders/MemberGroupBuilder.cs @@ -0,0 +1,90 @@ +using System; +using System.Collections.Generic; +using Umbraco.Core.Models; +using Umbraco.Tests.Common.Builders.Interfaces; + +namespace Umbraco.Tests.Common.Builders +{ + public class MemberGroupBuilder + : BuilderBase, + IWithIdBuilder, + IWithKeyBuilder, + IWithCreatorIdBuilder, + IWithCreateDateBuilder, + IWithUpdateDateBuilder, + IWithNameBuilder + { + private int? _id; + private Guid? _key; + private DateTime? _createDate; + private DateTime? _updateDate; + private string _name; + private int? _creatorId; + private IDictionary _additionalData = new Dictionary(); + + private GenericDictionaryBuilder _additionalDataBuilder; + + public GenericDictionaryBuilder AddAdditionalData() + { + var builder = new GenericDictionaryBuilder(this); + _additionalDataBuilder = builder; + return builder; + } + + public override MemberGroup Build() + { + var id = _id ?? 1; + var key = _key ?? Guid.NewGuid(); + var createDate = _createDate ?? DateTime.Now; + var updateDate = _updateDate ?? DateTime.Now; + var name = _name ?? Guid.NewGuid().ToString(); + var creatorId = _creatorId ?? 1; + + return new MemberGroup + { + Id = id, + Key = key, + CreateDate = createDate, + UpdateDate = updateDate, + Name = name, + CreatorId = creatorId, + }; + } + + int? IWithIdBuilder.Id + { + get => _id; + set => _id = value; + } + + Guid? IWithKeyBuilder.Key + { + get => _key; + set => _key = value; + } + + int? IWithCreatorIdBuilder.CreatorId + { + get => _creatorId; + set => _creatorId = value; + } + + DateTime? IWithCreateDateBuilder.CreateDate + { + get => _createDate; + set => _createDate = value; + } + + DateTime? IWithUpdateDateBuilder.UpdateDate + { + get => _updateDate; + set => _updateDate = value; + } + + string IWithNameBuilder.Name + { + get => _name; + set => _name = value; + } + } +} diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Models/DataTypeTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Models/DataTypeTests.cs index a097661a93..4be32a9b4f 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Models/DataTypeTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Models/DataTypeTests.cs @@ -10,8 +10,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Models [TestFixture] public class DataTypeTests { - private readonly DataTypeBuilder _builder = new DataTypeBuilder(); + [Test] public void Can_Deep_Clone() { diff --git a/src/Umbraco.Tests/Models/MemberGroupTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Models/MemberGroupTests.cs similarity index 61% rename from src/Umbraco.Tests/Models/MemberGroupTests.cs rename to src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Models/MemberGroupTests.cs index c3cf1c8c76..158a70598c 100644 --- a/src/Umbraco.Tests/Models/MemberGroupTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Models/MemberGroupTests.cs @@ -3,28 +3,21 @@ using System.Diagnostics; using Newtonsoft.Json; using NUnit.Framework; using Umbraco.Core.Models; -using Umbraco.Core.Serialization; +using Umbraco.Tests.Common.Builders; +using Umbraco.Tests.Common.Builders.Extensions; -namespace Umbraco.Tests.Models +namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Models { [TestFixture] public class MemberGroupTests { + private readonly MemberGroupBuilder _builder = new MemberGroupBuilder(); + [Test] public void Can_Deep_Clone() { // Arrange - var group = new MemberGroup() - { - CreateDate = DateTime.Now, - CreatorId = 4, - Id = 6, - Key = Guid.NewGuid(), - UpdateDate = DateTime.Now, - Name = "asdf" - }; - group.AdditionalData.Add("test1", 123); - group.AdditionalData.Add("test2", "hello"); + var group = BuildMemberGroup(); // Act var clone = (MemberGroup)group.DeepClone(); @@ -44,29 +37,32 @@ namespace Umbraco.Tests.Models //This double verifies by reflection var allProps = clone.GetType().GetProperties(); foreach (var propertyInfo in allProps) - { Assert.AreEqual(propertyInfo.GetValue(clone, null), propertyInfo.GetValue(group, null)); - } } [Test] public void Can_Serialize_Without_Error() { - var group = new MemberGroup() - { - CreateDate = DateTime.Now, - CreatorId = 4, - Id = 6, - Key = Guid.NewGuid(), - UpdateDate = DateTime.Now, - Name = "asdf" - }; - group.AdditionalData.Add("test1", 123); - group.AdditionalData.Add("test2", "hello"); + var group = BuildMemberGroup(); var json = JsonConvert.SerializeObject(group); Debug.Print(json); } + private MemberGroup BuildMemberGroup() + { + return _builder + .WithId(6) + .WithKey(Guid.NewGuid()) + .WithName("asdf") + .WithCreatorId(4) + .WithCreateDate(DateTime.Now) + .WithUpdateDate(DateTime.Now) + .AddAdditionalData() + .AddKeyValue("test1", 123) + .AddKeyValue("test2", "hello") + .Done() + .Build(); + } } } diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index 06f6a98573..eb500bf7e3 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -298,7 +298,6 @@ - From dface90de7f9a56a95a80f29d9eabd08571dbfd6 Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Sun, 29 Mar 2020 16:17:06 +0200 Subject: [PATCH 55/73] Migrated member unit tests to new unit test project using builder pattern. --- .../Builders/ConfigurationEditorBuilder.cs | 2 +- .../Builders/DataTypeBuilder.cs | 73 +++-- .../Builders/DictionaryTranslationBuilder.cs | 1 - .../Builders/Extensions/BuilderExtensions.cs | 56 ++++ .../Builders/Extensions/StringExtensions.cs | 20 ++ .../Builders/GenericCollectionBuilder.cs | 26 ++ .../Builders/GenericDictionaryBuilder.cs | 4 +- .../Interfaces/IWithDescriptionBuilder.cs | 7 + .../Builders/Interfaces/IWithIconBuilder.cs | 7 + .../Builders/Interfaces/IWithLevelBuilder.cs | 7 + .../Interfaces/IWithParentIdBuilder.cs | 7 + .../Builders/Interfaces/IWithPathBuilder.cs | 7 + .../Interfaces/IWithSortOrderBuilder.cs | 7 + .../Interfaces/IWithThumbnailBuilder.cs | 7 + .../Interfaces/IWithTrashedBuilder.cs | 7 + .../Builders/MemberBuilder.cs | 280 ++++++++++++++++++ .../Builders/MemberGroupBuilder.cs | 19 +- .../Builders/MemberTypeBuilder.cs | 198 +++++++++++++ .../Builders/PropertyGroupBuilder.cs | 60 ++++ .../Builders/PropertyTypeBuilder.cs | 93 ++++++ .../Models/MemberGroupTests.cs | 4 +- .../Models/MemberTests.cs | 154 ++++++++++ src/Umbraco.Tests/Models/MemberTests.cs | 134 --------- src/Umbraco.Tests/Umbraco.Tests.csproj | 1 - 24 files changed, 998 insertions(+), 183 deletions(-) create mode 100644 src/Umbraco.Tests.Common/Builders/Extensions/StringExtensions.cs create mode 100644 src/Umbraco.Tests.Common/Builders/GenericCollectionBuilder.cs create mode 100644 src/Umbraco.Tests.Common/Builders/Interfaces/IWithDescriptionBuilder.cs create mode 100644 src/Umbraco.Tests.Common/Builders/Interfaces/IWithIconBuilder.cs create mode 100644 src/Umbraco.Tests.Common/Builders/Interfaces/IWithLevelBuilder.cs create mode 100644 src/Umbraco.Tests.Common/Builders/Interfaces/IWithParentIdBuilder.cs create mode 100644 src/Umbraco.Tests.Common/Builders/Interfaces/IWithPathBuilder.cs create mode 100644 src/Umbraco.Tests.Common/Builders/Interfaces/IWithSortOrderBuilder.cs create mode 100644 src/Umbraco.Tests.Common/Builders/Interfaces/IWithThumbnailBuilder.cs create mode 100644 src/Umbraco.Tests.Common/Builders/Interfaces/IWithTrashedBuilder.cs create mode 100644 src/Umbraco.Tests.Common/Builders/MemberBuilder.cs create mode 100644 src/Umbraco.Tests.Common/Builders/MemberTypeBuilder.cs create mode 100644 src/Umbraco.Tests.Common/Builders/PropertyGroupBuilder.cs create mode 100644 src/Umbraco.Tests.Common/Builders/PropertyTypeBuilder.cs create mode 100644 src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Models/MemberTests.cs delete mode 100644 src/Umbraco.Tests/Models/MemberTests.cs diff --git a/src/Umbraco.Tests.Common/Builders/ConfigurationEditorBuilder.cs b/src/Umbraco.Tests.Common/Builders/ConfigurationEditorBuilder.cs index 7789090d16..3e0b9b96a9 100644 --- a/src/Umbraco.Tests.Common/Builders/ConfigurationEditorBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/ConfigurationEditorBuilder.cs @@ -21,7 +21,7 @@ namespace Umbraco.Tests.Common.Builders public override IConfigurationEditor Build() { - var defaultConfiguration = _defaultConfiguration ?? new Dictionary(); + var defaultConfiguration = _defaultConfiguration ?? new Dictionary(); return new ConfigurationEditor() { diff --git a/src/Umbraco.Tests.Common/Builders/DataTypeBuilder.cs b/src/Umbraco.Tests.Common/Builders/DataTypeBuilder.cs index 04a464e5bd..de7b8d9a97 100644 --- a/src/Umbraco.Tests.Common/Builders/DataTypeBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/DataTypeBuilder.cs @@ -12,7 +12,12 @@ namespace Umbraco.Tests.Common.Builders IWithCreateDateBuilder, IWithUpdateDateBuilder, IWithDeleteDateBuilder, - IWithNameBuilder + IWithNameBuilder, + IWithParentIdBuilder, + IWithTrashedBuilder, + IWithLevelBuilder, + IWithPathBuilder, + IWithSortOrderBuilder { private readonly DataEditorBuilder _dataEditorBuilder; private int? _id; @@ -35,54 +40,18 @@ namespace Umbraco.Tests.Common.Builders _dataEditorBuilder = new DataEditorBuilder(this); } - public DataTypeBuilder WithParentId(int parentId) - { - _parentId = parentId; - return this; - } - - public DataTypeBuilder WithTrashed(bool trashed) - { - _trashed = trashed; - return this; - } - // public DataTypeBuilder WithConfiguration(object configuration) // { // _configuration = configuration; // return this; // } - public DataTypeBuilder WithLevel(int level) - { - _level = level; - return this; - } - - public DataTypeBuilder WithPath(string path) - { - _path = path; - return this; - } - - public DataTypeBuilder WithCreatorId(int creatorId) - { - _creatorId = creatorId; - return this; - } - public DataTypeBuilder WithDatabaseType(ValueStorageType databaseType) { _databaseType = databaseType; return this; } - public DataTypeBuilder WithSortOrder(int sortOrder) - { - _sortOrder = sortOrder; - return this; - } - public DataEditorBuilder AddEditor() { return _dataEditorBuilder; @@ -163,5 +132,35 @@ namespace Umbraco.Tests.Common.Builders get => _name; set => _name = value; } + + int? IWithParentIdBuilder.ParentId + { + get => _parentId; + set => _parentId = value; + } + + bool? IWithTrashedBuilder.Trashed + { + get => _trashed; + set => _trashed = value; + } + + int? IWithLevelBuilder.Level + { + get => _level; + set => _level = value; + } + + string IWithPathBuilder.Path + { + get => _path; + set => _path = value; + } + + int? IWithSortOrderBuilder.SortOrder + { + get => _sortOrder; + set => _sortOrder = value; + } } } diff --git a/src/Umbraco.Tests.Common/Builders/DictionaryTranslationBuilder.cs b/src/Umbraco.Tests.Common/Builders/DictionaryTranslationBuilder.cs index 37fb7c5b07..d2f9d4bf02 100644 --- a/src/Umbraco.Tests.Common/Builders/DictionaryTranslationBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/DictionaryTranslationBuilder.cs @@ -21,7 +21,6 @@ namespace Umbraco.Tests.Common.Builders private DateTime? _updateDate; private string _value; - public DictionaryTranslationBuilder(DictionaryItemBuilder parentBuilder) : base(parentBuilder) { _languageBuilder = new LanguageBuilder(this); diff --git a/src/Umbraco.Tests.Common/Builders/Extensions/BuilderExtensions.cs b/src/Umbraco.Tests.Common/Builders/Extensions/BuilderExtensions.cs index 6a2407ae53..cce3b88470 100644 --- a/src/Umbraco.Tests.Common/Builders/Extensions/BuilderExtensions.cs +++ b/src/Umbraco.Tests.Common/Builders/Extensions/BuilderExtensions.cs @@ -53,5 +53,61 @@ namespace Umbraco.Tests.Common.Builders.Extensions builder.Key = key; return builder; } + + public static T WithParentId(this T builder, int parentId) + where T : IWithParentIdBuilder + { + builder.ParentId = parentId; + return builder; + } + + public static T WithTrashed(this T builder, bool trashed) + where T : IWithTrashedBuilder + { + builder.Trashed = trashed; + return builder; + } + + public static T WithLevel(this T builder, int level) + where T : IWithLevelBuilder + { + builder.Level = level; + return builder; + } + + public static T WithPath(this T builder, string path) + where T : IWithPathBuilder + { + builder.Path = path; + return builder; + } + + public static T WithSortOrder(this T builder, int sortOrder) + where T : IWithSortOrderBuilder + { + builder.SortOrder = sortOrder; + return builder; + } + + public static T WithDescription(this T builder, string description) + where T : IWithDescriptionBuilder + { + builder.Description = description; + return builder; + } + + public static T WithIcon(this T builder, string icon) + where T : IWithIconBuilder + { + builder.Icon = icon; + return builder; + } + + public static T WithThumbnail(this T builder, string thumbnail) + where T : IWithThumbnailBuilder + { + builder.Thumbnail = thumbnail; + return builder; + } } } diff --git a/src/Umbraco.Tests.Common/Builders/Extensions/StringExtensions.cs b/src/Umbraco.Tests.Common/Builders/Extensions/StringExtensions.cs new file mode 100644 index 0000000000..b426cabaa6 --- /dev/null +++ b/src/Umbraco.Tests.Common/Builders/Extensions/StringExtensions.cs @@ -0,0 +1,20 @@ +namespace Umbraco.Tests.Common.Builders.Extensions +{ + public static class StringExtensions + { + public static string ToCamelCase(this string s) + { + if (string.IsNullOrWhiteSpace(s)) + { + return string.Empty; + } + + if (s.Length == 1) + { + return s.ToLowerInvariant(); + } + + return char.ToLowerInvariant(s[0]) + s.Substring(1); + } + } +} diff --git a/src/Umbraco.Tests.Common/Builders/GenericCollectionBuilder.cs b/src/Umbraco.Tests.Common/Builders/GenericCollectionBuilder.cs new file mode 100644 index 0000000000..c7e176e9b0 --- /dev/null +++ b/src/Umbraco.Tests.Common/Builders/GenericCollectionBuilder.cs @@ -0,0 +1,26 @@ +using System.Collections.Generic; + +namespace Umbraco.Tests.Common.Builders +{ + public class GenericCollectionBuilder + : ChildBuilderBase> + { + private readonly IList _collection; + + public GenericCollectionBuilder(TBuilder parentBuilder) : base(parentBuilder) + { + _collection = new List(); + } + + public override IEnumerable Build() + { + return _collection; + } + + public GenericCollectionBuilder WithValue(T value) + { + _collection.Add(value); + return this; + } + } +} diff --git a/src/Umbraco.Tests.Common/Builders/GenericDictionaryBuilder.cs b/src/Umbraco.Tests.Common/Builders/GenericDictionaryBuilder.cs index 0cf2284a49..8f6aedcf43 100644 --- a/src/Umbraco.Tests.Common/Builders/GenericDictionaryBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/GenericDictionaryBuilder.cs @@ -14,10 +14,10 @@ namespace Umbraco.Tests.Common.Builders public override IDictionary Build() { - return new Dictionary(); + return _dictionary; } - public GenericDictionaryBuilder AddKeyValue(TKey key, TValue value) + public GenericDictionaryBuilder WithKeyValue(TKey key, TValue value) { _dictionary.Add(key, value); return this; diff --git a/src/Umbraco.Tests.Common/Builders/Interfaces/IWithDescriptionBuilder.cs b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithDescriptionBuilder.cs new file mode 100644 index 0000000000..1a155073b3 --- /dev/null +++ b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithDescriptionBuilder.cs @@ -0,0 +1,7 @@ +namespace Umbraco.Tests.Common.Builders.Interfaces +{ + public interface IWithDescriptionBuilder + { + string Description { get; set; } + } +} diff --git a/src/Umbraco.Tests.Common/Builders/Interfaces/IWithIconBuilder.cs b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithIconBuilder.cs new file mode 100644 index 0000000000..5de5224e18 --- /dev/null +++ b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithIconBuilder.cs @@ -0,0 +1,7 @@ +namespace Umbraco.Tests.Common.Builders.Interfaces +{ + public interface IWithIconBuilder + { + string Icon { get; set; } + } +} diff --git a/src/Umbraco.Tests.Common/Builders/Interfaces/IWithLevelBuilder.cs b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithLevelBuilder.cs new file mode 100644 index 0000000000..dc6ee239ab --- /dev/null +++ b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithLevelBuilder.cs @@ -0,0 +1,7 @@ +namespace Umbraco.Tests.Common.Builders.Interfaces +{ + public interface IWithLevelBuilder + { + int? Level { get; set; } + } +} diff --git a/src/Umbraco.Tests.Common/Builders/Interfaces/IWithParentIdBuilder.cs b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithParentIdBuilder.cs new file mode 100644 index 0000000000..33d13b7ef1 --- /dev/null +++ b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithParentIdBuilder.cs @@ -0,0 +1,7 @@ +namespace Umbraco.Tests.Common.Builders.Interfaces +{ + public interface IWithParentIdBuilder + { + int? ParentId { get; set; } + } +} diff --git a/src/Umbraco.Tests.Common/Builders/Interfaces/IWithPathBuilder.cs b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithPathBuilder.cs new file mode 100644 index 0000000000..ed632c4e7d --- /dev/null +++ b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithPathBuilder.cs @@ -0,0 +1,7 @@ +namespace Umbraco.Tests.Common.Builders.Interfaces +{ + public interface IWithPathBuilder + { + string Path { get; set; } + } +} diff --git a/src/Umbraco.Tests.Common/Builders/Interfaces/IWithSortOrderBuilder.cs b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithSortOrderBuilder.cs new file mode 100644 index 0000000000..3202c243fb --- /dev/null +++ b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithSortOrderBuilder.cs @@ -0,0 +1,7 @@ +namespace Umbraco.Tests.Common.Builders.Interfaces +{ + public interface IWithSortOrderBuilder + { + int? SortOrder { get; set; } + } +} diff --git a/src/Umbraco.Tests.Common/Builders/Interfaces/IWithThumbnailBuilder.cs b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithThumbnailBuilder.cs new file mode 100644 index 0000000000..ce5b10e274 --- /dev/null +++ b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithThumbnailBuilder.cs @@ -0,0 +1,7 @@ +namespace Umbraco.Tests.Common.Builders.Interfaces +{ + public interface IWithThumbnailBuilder + { + string Thumbnail { get; set; } + } +} diff --git a/src/Umbraco.Tests.Common/Builders/Interfaces/IWithTrashedBuilder.cs b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithTrashedBuilder.cs new file mode 100644 index 0000000000..119e6a6e52 --- /dev/null +++ b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithTrashedBuilder.cs @@ -0,0 +1,7 @@ +namespace Umbraco.Tests.Common.Builders.Interfaces +{ + public interface IWithTrashedBuilder + { + bool? Trashed { get; set; } + } +} diff --git a/src/Umbraco.Tests.Common/Builders/MemberBuilder.cs b/src/Umbraco.Tests.Common/Builders/MemberBuilder.cs new file mode 100644 index 0000000000..cef4a35524 --- /dev/null +++ b/src/Umbraco.Tests.Common/Builders/MemberBuilder.cs @@ -0,0 +1,280 @@ +using System; +using Umbraco.Core.Models; +using Umbraco.Tests.Common.Builders.Interfaces; + +namespace Umbraco.Tests.Common.Builders +{ + public class MemberBuilder + : BuilderBase, + IWithIdBuilder, + IWithKeyBuilder, + IWithCreatorIdBuilder, + IWithCreateDateBuilder, + IWithUpdateDateBuilder, + IWithNameBuilder, + IWithTrashedBuilder, + IWithLevelBuilder, + IWithPathBuilder, + IWithSortOrderBuilder + { + private MemberTypeBuilder _memberTypeBuilder; + private GenericCollectionBuilder _memberGroupsBuilder; + private GenericDictionaryBuilder _additionalDataBuilder; + private GenericDictionaryBuilder _propertyDataBuilder; + + private int? _id; + private Guid? _key; + private DateTime? _createDate; + private DateTime? _updateDate; + private string _name; + private int? _creatorId; + private string _username; + private string _rawPasswordValue; + private string _email; + private int? _failedPasswordAttempts; + private int? _level; + private string _path; + private bool? _isApproved; + private bool? _isLockedOut; + private DateTime? _lastLockoutDate; + private DateTime? _lastLoginDate; + private DateTime? _lastPasswordChangeDate; + private int? _sortOrder; + private bool? _trashed; + private int? _propertyIdsIncrementingFrom; + + public MemberBuilder WithUserName(string username) + { + _username = username; + return this; + } + + public MemberBuilder WithEmail(string email) + { + _email = email; + return this; + } + + public MemberBuilder WithRawPasswordValue(string rawPasswordValue) + { + _rawPasswordValue = rawPasswordValue; + return this; + } + + public MemberBuilder WithFailedPasswordAttempts(int failedPasswordAttempts) + { + _failedPasswordAttempts = failedPasswordAttempts; + return this; + } + + public MemberBuilder WithIsApproved(bool isApproved) + { + _isApproved = isApproved; + return this; + } + + public MemberBuilder WithIsLockedOut(bool isLockedOut) + { + _isLockedOut = isLockedOut; + return this; + } + + public MemberBuilder WithLastLockoutDate(DateTime lastLockoutDate) + { + _lastLockoutDate = lastLockoutDate; + return this; + } + + public MemberBuilder WithLastLoginDate(DateTime lastLoginDate) + { + _lastLoginDate = lastLoginDate; + return this; + } + + public MemberBuilder WithLastPasswordChangeDate(DateTime lastPasswordChangeDate) + { + _lastPasswordChangeDate = lastPasswordChangeDate; + return this; + } + + public MemberBuilder WithPropertyIdsIncrementingFrom(int propertyIdsIncrementingFrom) + { + _propertyIdsIncrementingFrom = propertyIdsIncrementingFrom; + return this; + } + + public MemberTypeBuilder AddMemberType() + { + var builder = new MemberTypeBuilder(this); + _memberTypeBuilder = builder; + return builder; + } + + public GenericCollectionBuilder AddMemberGroups() + { + var builder = new GenericCollectionBuilder(this); + _memberGroupsBuilder = builder; + return builder; + } + + public GenericDictionaryBuilder AddAdditionalData() + { + var builder = new GenericDictionaryBuilder(this); + _additionalDataBuilder = builder; + return builder; + } + + public GenericDictionaryBuilder AddPropertyData() + { + var builder = new GenericDictionaryBuilder(this); + _propertyDataBuilder = builder; + return builder; + } + + public override Member Build() + { + var id = _id ?? 1; + var key = _key ?? Guid.NewGuid(); + var createDate = _createDate ?? DateTime.Now; + var updateDate = _updateDate ?? DateTime.Now; + var name = _name ?? Guid.NewGuid().ToString(); + var creatorId = _creatorId ?? 1; + var username = _username ?? string.Empty; + var email = _email ?? string.Empty; + var rawPasswordValue = _rawPasswordValue ?? string.Empty; + var failedPasswordAttempts = _failedPasswordAttempts ?? 0; + var level = _level ?? 1; + var path = _path ?? "-1"; + var isApproved = _isApproved ?? false; + var isLockedOut = _isLockedOut ?? false; + var lastLockoutDate = _lastLockoutDate ?? DateTime.Now; + var lastLoginDate = _lastLoginDate ?? DateTime.Now; + var lastPasswordChangeDate = _lastPasswordChangeDate ?? DateTime.Now; + var sortOrder = _sortOrder ?? 0; + var trashed = _trashed ?? false; + + if (_memberTypeBuilder == null) + { + throw new InvalidOperationException("A member cannot be constructed without providing a member type (use AddMemberType)."); + } + + var memberType = _memberTypeBuilder.Build(); + + var member = new Member(name, email, username, rawPasswordValue, memberType) + { + Id = id, + Key = key, + CreateDate = createDate, + UpdateDate = updateDate, + CreatorId = creatorId, + Level = level, + Path = path, + SortOrder = sortOrder, + Trashed = trashed, + }; + + if (_propertyIdsIncrementingFrom.HasValue) + { + var i = _propertyIdsIncrementingFrom.Value; + foreach (var property in member.Properties) + { + property.Id = ++i; + } + } + + member.FailedPasswordAttempts = failedPasswordAttempts; + member.IsApproved = isApproved; + member.IsLockedOut = isLockedOut; + member.LastLockoutDate = lastLockoutDate; + member.LastLoginDate = lastLoginDate; + member.LastPasswordChangeDate = lastPasswordChangeDate; + + if (_memberGroupsBuilder != null) + { + member.Groups = _memberGroupsBuilder.Build(); + } + + if (_additionalDataBuilder != null) + { + var additionalData = _additionalDataBuilder.Build(); + foreach (var kvp in additionalData) + { + member.AdditionalData.Add(kvp.Key, kvp.Value); + } + } + + if (_propertyDataBuilder != null) + { + var propertyData = _propertyDataBuilder.Build(); + foreach (var kvp in propertyData) + { + member.SetValue(kvp.Key, kvp.Value); + } + + member.ResetDirtyProperties(false); + } + + return member; + } + + int? IWithIdBuilder.Id + { + get => _id; + set => _id = value; + } + + Guid? IWithKeyBuilder.Key + { + get => _key; + set => _key = value; + } + + int? IWithCreatorIdBuilder.CreatorId + { + get => _creatorId; + set => _creatorId = value; + } + + DateTime? IWithCreateDateBuilder.CreateDate + { + get => _createDate; + set => _createDate = value; + } + + DateTime? IWithUpdateDateBuilder.UpdateDate + { + get => _updateDate; + set => _updateDate = value; + } + + string IWithNameBuilder.Name + { + get => _name; + set => _name = value; + } + + bool? IWithTrashedBuilder.Trashed + { + get => _trashed; + set => _trashed = value; + } + + int? IWithLevelBuilder.Level + { + get => _level; + set => _level = value; + } + + string IWithPathBuilder.Path + { + get => _path; + set => _path = value; + } + + int? IWithSortOrderBuilder.SortOrder + { + get => _sortOrder; + set => _sortOrder = value; + } + } +} diff --git a/src/Umbraco.Tests.Common/Builders/MemberGroupBuilder.cs b/src/Umbraco.Tests.Common/Builders/MemberGroupBuilder.cs index 6092c79abb..bfd7f30a14 100644 --- a/src/Umbraco.Tests.Common/Builders/MemberGroupBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/MemberGroupBuilder.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using Umbraco.Core.Models; using Umbraco.Tests.Common.Builders.Interfaces; @@ -14,15 +13,14 @@ namespace Umbraco.Tests.Common.Builders IWithUpdateDateBuilder, IWithNameBuilder { + private GenericDictionaryBuilder _additionalDataBuilder; + private int? _id; private Guid? _key; private DateTime? _createDate; private DateTime? _updateDate; private string _name; private int? _creatorId; - private IDictionary _additionalData = new Dictionary(); - - private GenericDictionaryBuilder _additionalDataBuilder; public GenericDictionaryBuilder AddAdditionalData() { @@ -40,7 +38,7 @@ namespace Umbraco.Tests.Common.Builders var name = _name ?? Guid.NewGuid().ToString(); var creatorId = _creatorId ?? 1; - return new MemberGroup + var memberGroup = new MemberGroup { Id = id, Key = key, @@ -49,6 +47,17 @@ namespace Umbraco.Tests.Common.Builders Name = name, CreatorId = creatorId, }; + + if (_additionalDataBuilder != null) + { + var additionalData = _additionalDataBuilder.Build(); + foreach (var kvp in additionalData) + { + memberGroup.AdditionalData.Add(kvp.Key, kvp.Value); + } + } + + return memberGroup; } int? IWithIdBuilder.Id diff --git a/src/Umbraco.Tests.Common/Builders/MemberTypeBuilder.cs b/src/Umbraco.Tests.Common/Builders/MemberTypeBuilder.cs new file mode 100644 index 0000000000..b01b8a1680 --- /dev/null +++ b/src/Umbraco.Tests.Common/Builders/MemberTypeBuilder.cs @@ -0,0 +1,198 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Umbraco.Core; +using Umbraco.Core.Models; +using Umbraco.Core.Strings; +using Umbraco.Tests.Common.Builders.Extensions; +using Umbraco.Tests.Common.Builders.Interfaces; + +namespace Umbraco.Tests.Common.Builders +{ + public class MemberTypeBuilder + : ChildBuilderBase, + IWithIdBuilder, + IWithAliasBuilder, + IWithNameBuilder, + IWithParentIdBuilder, + IWithSortOrderBuilder, + IWithCreatorIdBuilder, + IWithDescriptionBuilder, + IWithIconBuilder, + IWithThumbnailBuilder, + IWithTrashedBuilder + { + private readonly List _propertyGroupBuilders = new List(); + + private int? _id; + private string _alias; + private string _name; + private int? _parentId; + private int? _sortOrder; + private int? _creatorId; + private string _description; + private string _icon; + private string _thumbnail; + private bool? _trashed; + + public MemberTypeBuilder(MemberBuilder parentBuilder) : base(parentBuilder) + { + } + + public MemberTypeBuilder WithMembershipPropertyGroup() + { + var builder = new PropertyGroupBuilder(this) + .WithName(Constants.Conventions.Member.StandardPropertiesGroupName) + .WithSortOrder(1) + .AddPropertyType() + .WithPropertyEditorAlias(Constants.PropertyEditors.Aliases.TextArea) + .WithValueStorageType(ValueStorageType.Ntext) + .WithAlias(Constants.Conventions.Member.Comments) + .WithName(Constants.Conventions.Member.CommentsLabel) + .Done() + .AddPropertyType() + .WithPropertyEditorAlias(Constants.PropertyEditors.Aliases.Boolean) + .WithValueStorageType(ValueStorageType.Integer) + .WithAlias(Constants.Conventions.Member.IsApproved) + .WithName(Constants.Conventions.Member.IsApprovedLabel) + .Done() + .AddPropertyType() + .WithPropertyEditorAlias(Constants.PropertyEditors.Aliases.Boolean) + .WithValueStorageType(ValueStorageType.Integer) + .WithAlias(Constants.Conventions.Member.IsLockedOut) + .WithName(Constants.Conventions.Member.IsLockedOutLabel) + .Done() + .AddPropertyType() + .WithPropertyEditorAlias(Constants.PropertyEditors.Aliases.Label) + .WithValueStorageType(ValueStorageType.Date) + .WithAlias(Constants.Conventions.Member.LastLoginDate) + .WithName(Constants.Conventions.Member.LastLoginDateLabel) + .Done() + .AddPropertyType() + .WithPropertyEditorAlias(Constants.PropertyEditors.Aliases.Label) + .WithValueStorageType(ValueStorageType.Date) + .WithAlias(Constants.Conventions.Member.LastPasswordChangeDate) + .WithName(Constants.Conventions.Member.LastPasswordChangeDateLabel) + .Done() + .AddPropertyType() + .WithPropertyEditorAlias(Constants.PropertyEditors.Aliases.Label) + .WithValueStorageType(ValueStorageType.Date) + .WithAlias(Constants.Conventions.Member.LastLockoutDate) + .WithName(Constants.Conventions.Member.LastLockoutDateLabel) + .Done() + .AddPropertyType() + .WithPropertyEditorAlias(Constants.PropertyEditors.Aliases.Label) + .WithValueStorageType(ValueStorageType.Integer) + .WithAlias(Constants.Conventions.Member.FailedPasswordAttempts) + .WithName(Constants.Conventions.Member.FailedPasswordAttemptsLabel) + .Done(); + _propertyGroupBuilders.Add(builder); + return this; + } + + public PropertyGroupBuilder AddPropertyGroup() + { + var builder = new PropertyGroupBuilder(this); + _propertyGroupBuilders.Add(builder); + return builder; + } + + public override MemberType Build() + { + var id = _id ?? 1; + var name = _name ?? Guid.NewGuid().ToString(); + var alias = _alias ?? name.ToCamelCase(); + var parentId = _parentId ?? -1; + var sortOrder = _sortOrder ?? 0; + var description = _description ?? string.Empty; + var icon = _icon ?? string.Empty; + var thumbnail = _thumbnail ?? string.Empty; + var creatorId = _creatorId ?? 0; + var trashed = _trashed ?? false; + + var shortStringHelper = new DefaultShortStringHelper(new DefaultShortStringHelperConfig()); + + var memberType = new MemberType(shortStringHelper, parentId) + { + Id = id, + Alias = alias, + Name = name, + SortOrder = sortOrder, + Description = description, + Icon = icon, + Thumbnail = thumbnail, + CreatorId = creatorId, + Trashed = trashed, + }; + + foreach (var propertyGroup in _propertyGroupBuilders.Select(x => x.Build())) + { + memberType.PropertyGroups.Add(propertyGroup); + } + + memberType.ResetDirtyProperties(false); + + return memberType; + } + + int? IWithIdBuilder.Id + { + get => _id; + set => _id = value; + } + + string IWithAliasBuilder.Alias + { + get => _alias; + set => _alias = value; + } + + string IWithNameBuilder.Name + { + get => _name; + set => _name = value; + } + + int? IWithParentIdBuilder.ParentId + { + get => _parentId; + set => _parentId = value; + } + + int? IWithSortOrderBuilder.SortOrder + { + get => _sortOrder; + set => _sortOrder = value; + } + + int? IWithCreatorIdBuilder.CreatorId + { + get => _creatorId; + set => _creatorId = value; + } + + string IWithDescriptionBuilder.Description + { + get => _description; + set => _description = value; + } + + string IWithIconBuilder.Icon + { + get => _icon; + set => _icon = value; + } + + string IWithThumbnailBuilder.Thumbnail + { + get => _thumbnail; + set => _thumbnail = value; + } + + bool? IWithTrashedBuilder.Trashed + { + get => _trashed; + set => _trashed = value; + } + } +} diff --git a/src/Umbraco.Tests.Common/Builders/PropertyGroupBuilder.cs b/src/Umbraco.Tests.Common/Builders/PropertyGroupBuilder.cs new file mode 100644 index 0000000000..4b349a7e8a --- /dev/null +++ b/src/Umbraco.Tests.Common/Builders/PropertyGroupBuilder.cs @@ -0,0 +1,60 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Umbraco.Core.Models; +using Umbraco.Tests.Common.Builders.Interfaces; + +namespace Umbraco.Tests.Common.Builders +{ + public class PropertyGroupBuilder + : ChildBuilderBase, // TODO: likely want to genericise this, so can use for document and media types too. + IWithNameBuilder, + IWithSortOrderBuilder + { + private readonly List _propertyTypeBuilders = new List(); + + private string _name; + private int? _sortOrder; + + public PropertyGroupBuilder(MemberTypeBuilder parentBuilder) : base(parentBuilder) + { + } + + public PropertyTypeBuilder AddPropertyType() + { + var builder = new PropertyTypeBuilder(this); + _propertyTypeBuilders.Add(builder); + return builder; + } + + public override PropertyGroup Build() + { + var name = _name ?? Guid.NewGuid().ToString(); + var sortOrder = _sortOrder ?? 0; + + var properties = new PropertyTypeCollection(false); + foreach (var propertyType in _propertyTypeBuilders.Select(x => x.Build())) + { + properties.Add(propertyType); + } + + return new PropertyGroup(properties) + { + Name = name, + SortOrder = sortOrder, + }; + } + + string IWithNameBuilder.Name + { + get => _name; + set => _name = value; + } + + int? IWithSortOrderBuilder.SortOrder + { + get => _sortOrder; + set => _sortOrder = value; + } + } +} diff --git a/src/Umbraco.Tests.Common/Builders/PropertyTypeBuilder.cs b/src/Umbraco.Tests.Common/Builders/PropertyTypeBuilder.cs new file mode 100644 index 0000000000..955e2fca4c --- /dev/null +++ b/src/Umbraco.Tests.Common/Builders/PropertyTypeBuilder.cs @@ -0,0 +1,93 @@ +using System; +using Moq; +using Umbraco.Core.Models; +using Umbraco.Core.Strings; +using Umbraco.Tests.Common.Builders.Extensions; +using Umbraco.Tests.Common.Builders.Interfaces; + +namespace Umbraco.Tests.Common.Builders +{ + public class PropertyTypeBuilder + : ChildBuilderBase, + IWithAliasBuilder, + IWithNameBuilder, + IWithSortOrderBuilder, + IWithDescriptionBuilder + { + private string _propertyEditorAlias; + private ValueStorageType? _valueStorageType; + private string _alias; + private string _name; + private int? _sortOrder; + private string _description; + private int? _dataTypeId; + + public PropertyTypeBuilder(PropertyGroupBuilder parentBuilder) : base(parentBuilder) + { + } + + public PropertyTypeBuilder WithPropertyEditorAlias(string propertyEditorAlias) + { + _propertyEditorAlias = propertyEditorAlias; + return this; + } + + public PropertyTypeBuilder WithValueStorageType(ValueStorageType valueStorageType) + { + _valueStorageType = valueStorageType; + return this; + } + + public PropertyTypeBuilder WithDataTypeId(int dataTypeId) + { + _dataTypeId = dataTypeId; + return this; + } + + public override PropertyType Build() + { + var propertyEditorAlias = _propertyEditorAlias ?? Guid.NewGuid().ToString().ToCamelCase(); + var valueStorageType = _valueStorageType ?? ValueStorageType.Ntext; + var name = _name ?? Guid.NewGuid().ToString(); + var alias = _alias ?? name.ToCamelCase(); + var sortOrder = _sortOrder ?? 0; + var dataTypeId = _dataTypeId ?? 0; + var description = _description ?? string.Empty; + + var shortStringHelper = new DefaultShortStringHelper(new DefaultShortStringHelperConfig()); + + return new PropertyType(shortStringHelper, propertyEditorAlias, valueStorageType) + { + Alias = alias, + Name = name, + SortOrder = sortOrder, + DataTypeId = dataTypeId, + Description = description, + }; + } + + string IWithAliasBuilder.Alias + { + get => _alias; + set => _alias = value; + } + + string IWithNameBuilder.Name + { + get => _name; + set => _name = value; + } + + int? IWithSortOrderBuilder.SortOrder + { + get => _sortOrder; + set => _sortOrder = value; + } + + string IWithDescriptionBuilder.Description + { + get => _description; + set => _description = value; + } + } +} diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Models/MemberGroupTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Models/MemberGroupTests.cs index 158a70598c..f2b17cc23f 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Models/MemberGroupTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Models/MemberGroupTests.cs @@ -59,8 +59,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Models .WithCreateDate(DateTime.Now) .WithUpdateDate(DateTime.Now) .AddAdditionalData() - .AddKeyValue("test1", 123) - .AddKeyValue("test2", "hello") + .WithKeyValue("test1", 123) + .WithKeyValue("test2", "hello") .Done() .Build(); } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Models/MemberTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Models/MemberTests.cs new file mode 100644 index 0000000000..cda7641e8e --- /dev/null +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Models/MemberTests.cs @@ -0,0 +1,154 @@ +using System; +using System.Diagnostics; +using System.Linq; +using Newtonsoft.Json; +using NUnit.Framework; +using Umbraco.Core; +using Umbraco.Core.Models; +using Umbraco.Tests.Common.Builders; +using Umbraco.Tests.Common.Builders.Extensions; + +namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Models +{ + [TestFixture] + public class MemberTests + { + private readonly MemberBuilder _builder = new MemberBuilder(); + + [Test] + public void Can_Deep_Clone() + { + // Arrange + var member = BuildMember(); + + // Act + var clone = (Member)member.DeepClone(); + + // Assert + Assert.AreNotSame(clone, member); + Assert.AreEqual(clone, member); + Assert.AreEqual(clone.Id, member.Id); + Assert.AreEqual(clone.VersionId, member.VersionId); + Assert.AreEqual(clone.AdditionalData, member.AdditionalData); + Assert.AreEqual(clone.ContentType, member.ContentType); + Assert.AreEqual(clone.ContentTypeId, member.ContentTypeId); + Assert.AreEqual(clone.CreateDate, member.CreateDate); + Assert.AreEqual(clone.CreatorId, member.CreatorId); + Assert.AreEqual(clone.Comments, member.Comments); + Assert.AreEqual(clone.Key, member.Key); + Assert.AreEqual(clone.FailedPasswordAttempts, member.FailedPasswordAttempts); + Assert.AreEqual(clone.Level, member.Level); + Assert.AreEqual(clone.Path, member.Path); + Assert.AreEqual(clone.Groups, member.Groups); + Assert.AreEqual(clone.Groups.Count(), member.Groups.Count()); + Assert.AreEqual(clone.IsApproved, member.IsApproved); + Assert.AreEqual(clone.IsLockedOut, member.IsLockedOut); + Assert.AreEqual(clone.SortOrder, member.SortOrder); + Assert.AreEqual(clone.LastLockoutDate, member.LastLockoutDate); + Assert.AreNotSame(clone.LastLoginDate, member.LastLoginDate); + Assert.AreEqual(clone.LastPasswordChangeDate, member.LastPasswordChangeDate); + Assert.AreEqual(clone.Trashed, member.Trashed); + Assert.AreEqual(clone.UpdateDate, member.UpdateDate); + Assert.AreEqual(clone.VersionId, member.VersionId); + Assert.AreEqual(clone.RawPasswordValue, member.RawPasswordValue); + Assert.AreNotSame(clone.Properties, member.Properties); + Assert.AreEqual(clone.Properties.Count(), member.Properties.Count()); + for (var index = 0; index < member.Properties.Count; index++) + { + Assert.AreNotSame(clone.Properties[index], member.Properties[index]); + Assert.AreEqual(clone.Properties[index], member.Properties[index]); + } + + // this can be the same, it is immutable + Assert.AreSame(clone.ContentType, member.ContentType); + + //This double verifies by reflection + var allProps = clone.GetType().GetProperties(); + foreach (var propertyInfo in allProps) + Assert.AreEqual(propertyInfo.GetValue(clone, null), propertyInfo.GetValue(member, null)); + } + + [Test] + public void Can_Serialize_Without_Error() + { + var member = BuildMember(); + + var json = JsonConvert.SerializeObject(member); + Debug.Print(json); + } + + private Member BuildMember() + { + return _builder + .AddMemberType() + .WithId(99) + .WithAlias("memberType") + .WithName("Member Type") + .WithMembershipPropertyGroup() + .AddPropertyGroup() + .WithName("Content") + .WithSortOrder(1) + .AddPropertyType() + .WithPropertyEditorAlias(Constants.PropertyEditors.Aliases.TextBox) + .WithValueStorageType(ValueStorageType.Nvarchar) + .WithAlias("title") + .WithName("Title") + .WithSortOrder(1) + .WithDataTypeId(-88) + .Done() + .AddPropertyType() + .WithPropertyEditorAlias(Constants.PropertyEditors.Aliases.TextBox) + .WithValueStorageType(ValueStorageType.Ntext) + .WithAlias("bodyText") + .WithName("Body Text") + .WithSortOrder(2) + .WithDataTypeId(-87) + .Done() + .AddPropertyType() + .WithPropertyEditorAlias(Constants.PropertyEditors.Aliases.TextBox) + .WithValueStorageType(ValueStorageType.Nvarchar) + .WithAlias("author") + .WithName("Author") + .WithDescription("Name of the author") + .WithSortOrder(3) + .WithDataTypeId(-88) + .Done() + .Done() + .Done() + .WithId(10) + .WithKey(Guid.NewGuid()) + .WithName("Name") + .WithUserName("user") + .WithRawPasswordValue("raw pass") + .WithEmail("email@email.com") + .WithCreatorId(22) + .WithCreateDate(DateTime.Now) + .WithUpdateDate(DateTime.Now) + .WithFailedPasswordAttempts(22) + .WithLevel(3) + .WithPath("-1, 4, 10") + .WithIsApproved(true) + .WithIsLockedOut(true) + .WithLastLockoutDate(DateTime.Now) + .WithLastLoginDate(DateTime.Now) + .WithLastPasswordChangeDate(DateTime.Now) + .WithSortOrder(5) + .WithTrashed(false) + .AddMemberGroups() + .WithValue("group1") + .WithValue("group2") + .Done() + .AddAdditionalData() + .WithKeyValue("test1", 123) + .WithKeyValue("test2", "hello") + .Done() + .WithPropertyIdsIncrementingFrom(200) + .AddPropertyData() + .WithKeyValue("title", "Name member") + .WithKeyValue("bodyText", "This is a subpage") + .WithKeyValue("author", "John Doe") + .Done() + .Build(); + } + } +} diff --git a/src/Umbraco.Tests/Models/MemberTests.cs b/src/Umbraco.Tests/Models/MemberTests.cs deleted file mode 100644 index e0cd826536..0000000000 --- a/src/Umbraco.Tests/Models/MemberTests.cs +++ /dev/null @@ -1,134 +0,0 @@ -using System; -using System.Diagnostics; -using System.Linq; -using Newtonsoft.Json; -using NUnit.Framework; -using Umbraco.Core.Composing; -using Umbraco.Core.Models; -using Umbraco.Tests.TestHelpers; -using Umbraco.Tests.TestHelpers.Entities; - -namespace Umbraco.Tests.Models -{ - [TestFixture] - public class MemberTests - { - [Test] - public void Can_Deep_Clone() - { - // Arrange - var memberType = MockedContentTypes.CreateSimpleMemberType("memberType", "Member Type"); - memberType.Id = 99; - var member = MockedMember.CreateSimpleMember(memberType, "Name", "email@email.com", "pass", "user", Guid.NewGuid()); - var i = 200; - foreach (var property in member.Properties) - { - property.Id = ++i; - } - member.Id = 10; - member.CreateDate = DateTime.Now; - member.CreatorId = 22; - member.Comments = "comments"; - member.Key = Guid.NewGuid(); - member.FailedPasswordAttempts = 22; - member.Level = 3; - member.Path = "-1,4,10"; - member.Groups = new[] {"group1", "group2"}; - member.IsApproved = true; - member.IsLockedOut = true; - member.LastLockoutDate = DateTime.Now; - member.LastLoginDate = DateTime.Now; - member.LastPasswordChangeDate = DateTime.Now; - member.RawPasswordValue = "raw pass"; - member.SortOrder = 5; - member.Trashed = false; - member.UpdateDate = DateTime.Now; - member.AdditionalData.Add("test1", 123); - member.AdditionalData.Add("test2", "hello"); - - // Act - var clone = (Member)member.DeepClone(); - - // Assert - Assert.AreNotSame(clone, member); - Assert.AreEqual(clone, member); - Assert.AreEqual(clone.Id, member.Id); - Assert.AreEqual(clone.VersionId, member.VersionId); - Assert.AreEqual(clone.AdditionalData, member.AdditionalData); - Assert.AreEqual(clone.ContentType, member.ContentType); - Assert.AreEqual(clone.ContentTypeId, member.ContentTypeId); - Assert.AreEqual(clone.CreateDate, member.CreateDate); - Assert.AreEqual(clone.CreatorId, member.CreatorId); - Assert.AreEqual(clone.Comments, member.Comments); - Assert.AreEqual(clone.Key, member.Key); - Assert.AreEqual(clone.FailedPasswordAttempts, member.FailedPasswordAttempts); - Assert.AreEqual(clone.Level, member.Level); - Assert.AreEqual(clone.Path, member.Path); - Assert.AreEqual(clone.Groups, member.Groups); - Assert.AreEqual(clone.Groups.Count(), member.Groups.Count()); - Assert.AreEqual(clone.IsApproved, member.IsApproved); - Assert.AreEqual(clone.IsLockedOut, member.IsLockedOut); - Assert.AreEqual(clone.SortOrder, member.SortOrder); - Assert.AreEqual(clone.LastLockoutDate, member.LastLockoutDate); - Assert.AreNotSame(clone.LastLoginDate, member.LastLoginDate); - Assert.AreEqual(clone.LastPasswordChangeDate, member.LastPasswordChangeDate); - Assert.AreEqual(clone.Trashed, member.Trashed); - Assert.AreEqual(clone.UpdateDate, member.UpdateDate); - Assert.AreEqual(clone.VersionId, member.VersionId); - Assert.AreEqual(clone.RawPasswordValue, member.RawPasswordValue); - Assert.AreNotSame(clone.Properties, member.Properties); - Assert.AreEqual(clone.Properties.Count(), member.Properties.Count()); - for (var index = 0; index < member.Properties.Count; index++) - { - Assert.AreNotSame(clone.Properties[index], member.Properties[index]); - Assert.AreEqual(clone.Properties[index], member.Properties[index]); - } - - // this can be the same, it is immutable - Assert.AreSame(clone.ContentType, member.ContentType); - - //This double verifies by reflection - var allProps = clone.GetType().GetProperties(); - foreach (var propertyInfo in allProps) - { - Assert.AreEqual(propertyInfo.GetValue(clone, null), propertyInfo.GetValue(member, null)); - } - } - - [Test] - public void Can_Serialize_Without_Error() - { - var memberType = MockedContentTypes.CreateSimpleMemberType("memberType", "Member Type"); - memberType.Id = 99; - var member = MockedMember.CreateSimpleMember(memberType, "Name", "email@email.com", "pass", "user", Guid.NewGuid()); - var i = 200; - foreach (var property in member.Properties) - { - property.Id = ++i; - } - member.Id = 10; - member.CreateDate = DateTime.Now; - member.CreatorId = 22; - member.Comments = "comments"; - member.Key = Guid.NewGuid(); - member.FailedPasswordAttempts = 22; - member.Level = 3; - member.Path = "-1,4,10"; - member.Groups = new[] { "group1", "group2" }; - member.IsApproved = true; - member.IsLockedOut = true; - member.LastLockoutDate = DateTime.Now; - member.LastLoginDate = DateTime.Now; - member.LastPasswordChangeDate = DateTime.Now; - member.RawPasswordValue = "raw pass"; - member.SortOrder = 5; - member.Trashed = false; - member.UpdateDate = DateTime.Now; - member.AdditionalData.Add("test1", 123); - member.AdditionalData.Add("test2", "hello"); - - var json = JsonConvert.SerializeObject(member); - Debug.Print(json); - } - } -} diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index eb500bf7e3..79bd8cfc3d 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -298,7 +298,6 @@ - From b2799ef90147f125061919bc3d462750bfcbfa4e Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Sun, 29 Mar 2020 22:35:52 +0200 Subject: [PATCH 56/73] AB#5820 - Added simple controller and view to test the miniprofiler --- .../Controllers/BackOfficeController.cs | 14 +++ .../AspNetCore/AspNetCoreBackOfficeInfo.cs | 2 +- .../UmbracoCoreServiceCollectionExtensions.cs | 2 +- ...racoRequestApplicationBuilderExtensions.cs | 5 +- .../Runtime/Profiler/WebProfiler.cs | 104 +++++++++++---- .../Runtime/Profiler/WebProfilerComponent.cs | 3 + .../Runtime/Profiler/WebProfilerProvider.cs | 119 ------------------ .../Properties/launchSettings.json | 11 +- src/Umbraco.Web.UI.NetCore/Startup.cs | 45 ++++++- .../Views/BackOffice/Index.cshtml | 20 +++ .../Editors/BackOfficeController.cs | 7 +- 11 files changed, 176 insertions(+), 156 deletions(-) create mode 100644 src/Umbraco.Web.BackOffice/Controllers/BackOfficeController.cs delete mode 100644 src/Umbraco.Web.Common/Runtime/Profiler/WebProfilerProvider.cs create mode 100644 src/Umbraco.Web.UI.NetCore/Views/BackOffice/Index.cshtml diff --git a/src/Umbraco.Web.BackOffice/Controllers/BackOfficeController.cs b/src/Umbraco.Web.BackOffice/Controllers/BackOfficeController.cs new file mode 100644 index 0000000000..83f6eda24a --- /dev/null +++ b/src/Umbraco.Web.BackOffice/Controllers/BackOfficeController.cs @@ -0,0 +1,14 @@ +using Microsoft.AspNetCore.Mvc; +using Umbraco.Composing; + +namespace Umbraco.Web.BackOffice.Controllers +{ + public class BackOfficeController : Controller + { + // GET + public IActionResult Index() + { + return View(); + } + } +} diff --git a/src/Umbraco.Web.Common/AspNetCore/AspNetCoreBackOfficeInfo.cs b/src/Umbraco.Web.Common/AspNetCore/AspNetCoreBackOfficeInfo.cs index 375028132e..39b1d7ff4e 100644 --- a/src/Umbraco.Web.Common/AspNetCore/AspNetCoreBackOfficeInfo.cs +++ b/src/Umbraco.Web.Common/AspNetCore/AspNetCoreBackOfficeInfo.cs @@ -10,7 +10,7 @@ namespace Umbraco.Web.Common.AspNetCore GetAbsoluteUrl = globalSettings.UmbracoPath; } - public string GetAbsoluteUrl { get; } + public string GetAbsoluteUrl { get; } // TODO make absolute } } diff --git a/src/Umbraco.Web.Common/AspNetCore/UmbracoCoreServiceCollectionExtensions.cs b/src/Umbraco.Web.Common/AspNetCore/UmbracoCoreServiceCollectionExtensions.cs index eb320c459f..99a274364b 100644 --- a/src/Umbraco.Web.Common/AspNetCore/UmbracoCoreServiceCollectionExtensions.cs +++ b/src/Umbraco.Web.Common/AspNetCore/UmbracoCoreServiceCollectionExtensions.cs @@ -169,7 +169,7 @@ namespace Umbraco.Web.Common.AspNetCore } var webProfiler = new WebProfiler(httpContextAccessor); - webProfiler.Start(); + webProfiler.StartBoot(); return webProfiler; } diff --git a/src/Umbraco.Web.Common/Extensions/UmbracoRequestApplicationBuilderExtensions.cs b/src/Umbraco.Web.Common/Extensions/UmbracoRequestApplicationBuilderExtensions.cs index 110c09e1a9..8278c99bf5 100644 --- a/src/Umbraco.Web.Common/Extensions/UmbracoRequestApplicationBuilderExtensions.cs +++ b/src/Umbraco.Web.Common/Extensions/UmbracoRequestApplicationBuilderExtensions.cs @@ -1,5 +1,6 @@ using System; using Microsoft.AspNetCore.Builder; +using StackExchange.Profiling; using Umbraco.Web.Common.Middleware; namespace Umbraco.Web.Common.Extensions @@ -13,9 +14,11 @@ namespace Umbraco.Web.Common.Extensions throw new ArgumentNullException(nameof(app)); } - app.UseMiddleware(); + app.UseMiddleware(); + app.UseMiddleware(); return app; } } + } diff --git a/src/Umbraco.Web.Common/Runtime/Profiler/WebProfiler.cs b/src/Umbraco.Web.Common/Runtime/Profiler/WebProfiler.cs index 8baf8790c3..a05483633d 100644 --- a/src/Umbraco.Web.Common/Runtime/Profiler/WebProfiler.cs +++ b/src/Umbraco.Web.Common/Runtime/Profiler/WebProfiler.cs @@ -1,76 +1,118 @@ using System; +using System.Collections.Generic; +using System.Linq; using System.Threading; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Extensions; using StackExchange.Profiling; +using StackExchange.Profiling.Internal; using Umbraco.Core; -using Umbraco.Core.Cache; using Umbraco.Core.Logging; -using Umbraco.Web.Common.Middleware; namespace Umbraco.Web.Common.Runtime.Profiler { public class WebProfiler : IProfiler { + private readonly ReaderWriterLockSlim _locker = new ReaderWriterLockSlim(); + private volatile BootPhase _bootPhase; + private MiniProfiler _startupProfiler; + + private const string BootRequestItemKey = "Umbraco.Core.Logging.WebProfiler__isBootRequest"; private readonly IHttpContextAccessor _httpContextAccessor; - private readonly WebProfilerProvider _provider; private int _first; public WebProfiler(IHttpContextAccessor httpContextAccessor) { // create our own provider, which can provide a profiler even during boot - _provider = new WebProfilerProvider(); _httpContextAccessor = httpContextAccessor; - - MiniProfiler.DefaultOptions.ProfilerProvider = _provider; + _bootPhase = BootPhase.Boot; } + /// + /// + /// + /// + /// Normally we would call MiniProfiler.Current.RenderIncludes(...), but because the requeststate is not set, this method does not work. + /// We fake the requestIds from the RequestState here. + /// public string Render() { - return MiniProfiler.Current - .RenderIncludes(_httpContextAccessor.HttpContext, RenderPosition.Right).ToString(); + + var profiler = MiniProfiler.Current; + if (profiler == null) return string.Empty; + + var context = _httpContextAccessor.HttpContext; + + var path = (profiler.Options as MiniProfilerOptions)?.RouteBasePath.Value.EnsureTrailingSlash(); + + var result = StackExchange.Profiling.Internal.Render.Includes( + profiler, + path: context.Request.PathBase + path, + isAuthorized: true, + requestIDs: new List{ profiler.Id }, + position: RenderPosition.Right, + showTrivial: profiler.Options.PopupShowTrivial, + showTimeWithChildren: profiler.Options.PopupShowTimeWithChildren, + maxTracesToShow: profiler.Options.PopupMaxTracesToShow, + showControls:profiler.Options.ShowControls, + startHidden: profiler.Options.PopupStartHidden); + + return result; } - public IDisposable Step(string name) => MiniProfiler.Current?.Step(name); + public IDisposable Step(string name) + { + return MiniProfiler.Current?.Step(name); + } public void Start() { MiniProfiler.StartNew(); } + public void StartBoot() + { + _startupProfiler = MiniProfiler.StartNew("Startup Profiler"); + } + + public void StopBoot() + { + _startupProfiler.Stop(); + } + public void Stop(bool discardResults = false) { MiniProfiler.Current?.Stop(discardResults); } + public void UmbracoApplicationBeginRequest(HttpContext context) { - // if this is the first request, notify our own provider that this request is the boot request - var first = Interlocked.Exchange(ref _first, 1) == 0; - if (first) + if (ShouldProfile(context.Request)) { - _provider.BeginBootRequest(); - context.Items[BootRequestItemKey] = true; - // and no need to start anything, profiler is already there - } - // else start a profiler, the normal way - else if (ShouldProfile(context.Request)) Start(); + } } public void UmbracoApplicationEndRequest(HttpContext context) { - // if this is the boot request, or if we should profile this request, stop - // (the boot request is always profiled, no matter what) - if (context.Items.TryGetValue(BootRequestItemKey, out var isBoot) && isBoot.Equals(true)) - { - _provider.EndBootRequest(); - Stop(); - } - else if (ShouldProfile(context.Request)) + if (ShouldProfile(context.Request)) { Stop(); + + // if this is the first request, append the startup profiler + var first = Interlocked.Exchange(ref _first, 1) == 0; + if (first) + { + + var startupDuration = _startupProfiler.Root.DurationMilliseconds.GetValueOrDefault(); + MiniProfiler.Current.DurationMilliseconds += startupDuration; + MiniProfiler.Current.GetTimingHierarchy().First().DurationMilliseconds += startupDuration; + MiniProfiler.Current.Root.AddChild(_startupProfiler.Root); + + _startupProfiler = null; + } } } @@ -82,5 +124,15 @@ namespace Umbraco.Web.Common.Runtime.Profiler if (bool.TryParse(request.Cookies["UMB-DEBUG"], out var cUmbDebug)) return cUmbDebug; return false; } + + /// + /// Indicates the boot phase. + /// + private enum BootPhase + { + Boot = 0, // boot phase (before the 1st request even begins) + BootRequest = 1, // request boot phase (during the 1st request) + Booted = 2 // done booting + } } } diff --git a/src/Umbraco.Web.Common/Runtime/Profiler/WebProfilerComponent.cs b/src/Umbraco.Web.Common/Runtime/Profiler/WebProfilerComponent.cs index 9a656ae4f3..a36753e634 100644 --- a/src/Umbraco.Web.Common/Runtime/Profiler/WebProfilerComponent.cs +++ b/src/Umbraco.Web.Common/Runtime/Profiler/WebProfilerComponent.cs @@ -51,6 +51,9 @@ namespace Umbraco.Web.Common.Runtime.Profiler _umbracoRequestLifetime.RequestStart += (sender, context) => _profiler.UmbracoApplicationBeginRequest(context); _umbracoRequestLifetime.RequestEnd += (sender, context) => _profiler.UmbracoApplicationEndRequest(context); + + // Stop the profiling of the booting process + _profiler.StopBoot(); } } } diff --git a/src/Umbraco.Web.Common/Runtime/Profiler/WebProfilerProvider.cs b/src/Umbraco.Web.Common/Runtime/Profiler/WebProfilerProvider.cs deleted file mode 100644 index 99ad6f724c..0000000000 --- a/src/Umbraco.Web.Common/Runtime/Profiler/WebProfilerProvider.cs +++ /dev/null @@ -1,119 +0,0 @@ -using System; -using System.Threading; -using StackExchange.Profiling; -using StackExchange.Profiling.Internal; -using Umbraco.Core.Cache; - -namespace Umbraco.Web.Common.Runtime.Profiler -{ - public class WebProfilerProvider : DefaultProfilerProvider - { - private readonly ReaderWriterLockSlim _locker = new ReaderWriterLockSlim(); - private volatile BootPhase _bootPhase; - private int _first; - private MiniProfiler _startupProfiler; - - public WebProfilerProvider() - { - // booting... - _bootPhase = BootPhase.Boot; - } - - - /// - /// Gets the current profiler. - /// - /// - /// If the boot phase is not Booted, then this will return the startup profiler (this), otherwise - /// returns the base class - /// - public override MiniProfiler CurrentProfiler - { - get - { - // if not booting then just use base (fast) - // no lock, _bootPhase is volatile - if (_bootPhase == BootPhase.Booted) - return base.CurrentProfiler; - - // else - try - { - var current = base.CurrentProfiler; - return current ?? _startupProfiler; - } - catch - { - return _startupProfiler; - } - } - } - - public void BeginBootRequest() - { - _locker.EnterWriteLock(); - try - { - if (_bootPhase != BootPhase.Boot) - throw new InvalidOperationException("Invalid boot phase."); - _bootPhase = BootPhase.BootRequest; - - // assign the profiler to be the current MiniProfiler for the request - // is's already active, starting and all - CurrentProfiler = _startupProfiler; - } - finally - { - _locker.ExitWriteLock(); - } - } - - public void EndBootRequest() - { - _locker.EnterWriteLock(); - try - { - if (_bootPhase != BootPhase.BootRequest) - throw new InvalidOperationException("Invalid boot phase."); - _bootPhase = BootPhase.Booted; - - _startupProfiler = null; - } - finally - { - _locker.ExitWriteLock(); - } - } - - /// - /// Starts a new MiniProfiler. - /// - /// - /// - /// This is called when WebProfiler calls MiniProfiler.Start() so, - /// - as a result of WebRuntime starting the WebProfiler, and - /// - assuming profiling is enabled, on every BeginRequest that should be profiled, - /// - except for the very first one which is the boot request. - /// - /// - public override MiniProfiler Start(string profilerName, MiniProfilerBaseOptions options) - { - var first = Interlocked.Exchange(ref _first, 1) == 0; - if (first == false) return base.Start(profilerName, options); - - _startupProfiler = new MiniProfiler("StartupProfiler", options); - CurrentProfiler = _startupProfiler; - return _startupProfiler; - } - - /// - /// Indicates the boot phase. - /// - private enum BootPhase - { - Boot = 0, // boot phase (before the 1st request even begins) - BootRequest = 1, // request boot phase (during the 1st request) - Booted = 2 // done booting - } - } -} diff --git a/src/Umbraco.Web.UI.NetCore/Properties/launchSettings.json b/src/Umbraco.Web.UI.NetCore/Properties/launchSettings.json index b145249bb5..27f36121ac 100644 --- a/src/Umbraco.Web.UI.NetCore/Properties/launchSettings.json +++ b/src/Umbraco.Web.UI.NetCore/Properties/launchSettings.json @@ -1,7 +1,7 @@ { "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, + "windowsAuthentication": false, + "anonymousAuthentication": true, "iisExpress": { "applicationUrl": "http://localhost:36804", "sslPort": 44354 @@ -15,10 +15,11 @@ "ASPNETCORE_ENVIRONMENT": "Development" } }, - "Umbraco.Web.UI.BackOffice": { + "Umbraco.Web.UI.NetCore": { "commandName": "Project", - "launchBrowser": true, - "applicationUrl": "https://localhost:5001;http://localhost:5000", + "launchBrowser": false, + "applicationUrl": "https://localhost:44354;http://localhost:9000", + "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } diff --git a/src/Umbraco.Web.UI.NetCore/Startup.cs b/src/Umbraco.Web.UI.NetCore/Startup.cs index b38333bb9a..e0440e39ea 100644 --- a/src/Umbraco.Web.UI.NetCore/Startup.cs +++ b/src/Umbraco.Web.UI.NetCore/Startup.cs @@ -5,13 +5,19 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Extensions; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; +using StackExchange.Profiling; using Umbraco.Composing; +using Umbraco.Core; +using Umbraco.Core.Configuration; +using Umbraco.Core.IO; using Umbraco.Web.BackOffice.AspNetCore; using Umbraco.Web.Common.AspNetCore; using Umbraco.Web.Common.Extensions; +using Umbraco.Web.Common.Runtime.Profiler; using Umbraco.Web.Website.AspNetCore; @@ -43,11 +49,19 @@ namespace Umbraco.Web.UI.BackOffice services.AddUmbracoConfiguration(_config); services.AddUmbracoCore(_webHostEnvironment); services.AddUmbracoWebsite(); + + services.AddMvc(); + services.AddMiniProfiler(options => + { + options.ShouldProfile = request => false; // WebProfiler determine and start profiling. We should not use the MiniProfilerMiddleware to also profile + }); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { + + // app.UseMiniProfiler(); app.UseUmbracoRequest(); if (env.IsDevelopment()) { @@ -59,9 +73,17 @@ namespace Umbraco.Web.UI.BackOffice app.UseUmbracoBackOffice(); app.UseRouting(); - app.UseEndpoints(endpoints => { + endpoints.MapControllerRoute( + "backoffice-spa", + "/umbraco", + new + { + Controller = "BackOffice", + Action = "Index" + } + ); endpoints.MapGet("/", async context => { await context.Response.WriteAsync($"Hello World!{Current.Profiler.Render()}"); @@ -69,4 +91,25 @@ namespace Umbraco.Web.UI.BackOffice }); } } + + public class UmbracoMiniProfilerOptions : MiniProfilerOptions + { + public UmbracoMiniProfilerOptions() + { + } + + public new MiniProfiler StartProfiler(string profilerName = null) + { + return base.StartProfiler(); + } + + public new Func ShouldProfile => (request) => + { + if (new Uri(request.GetEncodedUrl(), UriKind.RelativeOrAbsolute).IsClientSideRequest()) return false; + if (bool.TryParse(request.Query["umbDebug"], out var umbDebug)) return umbDebug; + if (bool.TryParse(request.Headers["X-UMB-DEBUG"], out var xUmbDebug)) return xUmbDebug; + if (bool.TryParse(request.Cookies["UMB-DEBUG"], out var cUmbDebug)) return cUmbDebug; + return false; + }; + } } diff --git a/src/Umbraco.Web.UI.NetCore/Views/BackOffice/Index.cshtml b/src/Umbraco.Web.UI.NetCore/Views/BackOffice/Index.cshtml new file mode 100644 index 0000000000..1b5007c005 --- /dev/null +++ b/src/Umbraco.Web.UI.NetCore/Views/BackOffice/Index.cshtml @@ -0,0 +1,20 @@ +@using Umbraco.Composing + +@{ + Layout = null; +} + + + + + + title + + +
    + Hello World + @Html.Raw(Current.Profiler.Render()) +
    + + + diff --git a/src/Umbraco.Web/Editors/BackOfficeController.cs b/src/Umbraco.Web/Editors/BackOfficeController.cs index e39d1658a2..09076400de 100644 --- a/src/Umbraco.Web/Editors/BackOfficeController.cs +++ b/src/Umbraco.Web/Editors/BackOfficeController.cs @@ -104,8 +104,11 @@ namespace Umbraco.Web.Editors public async Task Default() { return await RenderDefaultOrProcessExternalLoginAsync( - () => View(_ioHelper.BackOfficePath.EnsureEndsWith('/') + "Views/Default.cshtml", new BackOfficeModel(_features, GlobalSettings, _umbracoVersion, _contentSettings,_ioHelper, _treeCollection, _httpContextAccessor, _hostingEnvironment, _runtimeSettings, _securitySettings)), - () => View(_ioHelper.BackOfficePath.EnsureEndsWith('/') + "Views/Default.cshtml", new BackOfficeModel(_features, GlobalSettings, _umbracoVersion, _contentSettings, _ioHelper, _treeCollection, _httpContextAccessor, _hostingEnvironment, _runtimeSettings, _securitySettings))); + () => + View(_ioHelper.BackOfficePath.EnsureEndsWith('/') + "Views/Default.cshtml", new BackOfficeModel(_features, GlobalSettings, _umbracoVersion, _contentSettings,_ioHelper, _treeCollection, _httpContextAccessor, _hostingEnvironment, _runtimeSettings, _securitySettings)), + () => + View(_ioHelper.BackOfficePath.EnsureEndsWith('/') + "Views/Default.cshtml", new BackOfficeModel(_features, GlobalSettings, _umbracoVersion, _contentSettings, _ioHelper, _treeCollection, _httpContextAccessor, _hostingEnvironment, _runtimeSettings, _securitySettings)) + ); } [HttpGet] From ff5d6770d929e52e7fb2d756f213b8610f1e9902 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Mon, 30 Mar 2020 06:34:56 +0200 Subject: [PATCH 57/73] AB#5820 - Clean up and remove unnecessary stuff for this task --- .../Controllers/BackOfficeController.cs | 14 ------------- src/Umbraco.Web.UI.NetCore/Startup.cs | 9 --------- .../Umbraco.Web.UI.NetCore.csproj | 1 + .../Views/BackOffice/Index.cshtml | 20 ------------------- 4 files changed, 1 insertion(+), 43 deletions(-) delete mode 100644 src/Umbraco.Web.BackOffice/Controllers/BackOfficeController.cs delete mode 100644 src/Umbraco.Web.UI.NetCore/Views/BackOffice/Index.cshtml diff --git a/src/Umbraco.Web.BackOffice/Controllers/BackOfficeController.cs b/src/Umbraco.Web.BackOffice/Controllers/BackOfficeController.cs deleted file mode 100644 index 83f6eda24a..0000000000 --- a/src/Umbraco.Web.BackOffice/Controllers/BackOfficeController.cs +++ /dev/null @@ -1,14 +0,0 @@ -using Microsoft.AspNetCore.Mvc; -using Umbraco.Composing; - -namespace Umbraco.Web.BackOffice.Controllers -{ - public class BackOfficeController : Controller - { - // GET - public IActionResult Index() - { - return View(); - } - } -} diff --git a/src/Umbraco.Web.UI.NetCore/Startup.cs b/src/Umbraco.Web.UI.NetCore/Startup.cs index e0440e39ea..f674832952 100644 --- a/src/Umbraco.Web.UI.NetCore/Startup.cs +++ b/src/Umbraco.Web.UI.NetCore/Startup.cs @@ -75,15 +75,6 @@ namespace Umbraco.Web.UI.BackOffice app.UseRouting(); app.UseEndpoints(endpoints => { - endpoints.MapControllerRoute( - "backoffice-spa", - "/umbraco", - new - { - Controller = "BackOffice", - Action = "Index" - } - ); endpoints.MapGet("/", async context => { await context.Response.WriteAsync($"Hello World!{Current.Profiler.Render()}"); diff --git a/src/Umbraco.Web.UI.NetCore/Umbraco.Web.UI.NetCore.csproj b/src/Umbraco.Web.UI.NetCore/Umbraco.Web.UI.NetCore.csproj index abfb172763..0e6f2cf6b0 100644 --- a/src/Umbraco.Web.UI.NetCore/Umbraco.Web.UI.NetCore.csproj +++ b/src/Umbraco.Web.UI.NetCore/Umbraco.Web.UI.NetCore.csproj @@ -18,6 +18,7 @@ <_ContentIncludedByDefault Remove="wwwroot\~\App_Data\TEMP\TypesCache\umbraco-types.DESKTOP-2016.hash" /> <_ContentIncludedByDefault Remove="wwwroot\~\App_Data\TEMP\TypesCache\umbraco-types.DESKTOP-2016.list" /> + <_ContentIncludedByDefault Remove="Views\BackOffice\Index.cshtml" /> diff --git a/src/Umbraco.Web.UI.NetCore/Views/BackOffice/Index.cshtml b/src/Umbraco.Web.UI.NetCore/Views/BackOffice/Index.cshtml deleted file mode 100644 index 1b5007c005..0000000000 --- a/src/Umbraco.Web.UI.NetCore/Views/BackOffice/Index.cshtml +++ /dev/null @@ -1,20 +0,0 @@ -@using Umbraco.Composing - -@{ - Layout = null; -} - - - - - - title - - -
    - Hello World - @Html.Raw(Current.Profiler.Render()) -
    - - - From 8ed7ae470ad541986c9dff64ede3c099f4d19701 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Mon, 30 Mar 2020 06:38:39 +0200 Subject: [PATCH 58/73] AB#5820 - Clean up --- src/Umbraco.Web.Common/Runtime/Profiler/WebProfiler.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/Umbraco.Web.Common/Runtime/Profiler/WebProfiler.cs b/src/Umbraco.Web.Common/Runtime/Profiler/WebProfiler.cs index a05483633d..431c195d90 100644 --- a/src/Umbraco.Web.Common/Runtime/Profiler/WebProfiler.cs +++ b/src/Umbraco.Web.Common/Runtime/Profiler/WebProfiler.cs @@ -13,12 +13,8 @@ namespace Umbraco.Web.Common.Runtime.Profiler { public class WebProfiler : IProfiler { - private readonly ReaderWriterLockSlim _locker = new ReaderWriterLockSlim(); - private volatile BootPhase _bootPhase; private MiniProfiler _startupProfiler; - - private const string BootRequestItemKey = "Umbraco.Core.Logging.WebProfiler__isBootRequest"; private readonly IHttpContextAccessor _httpContextAccessor; private int _first; @@ -26,7 +22,6 @@ namespace Umbraco.Web.Common.Runtime.Profiler { // create our own provider, which can provide a profiler even during boot _httpContextAccessor = httpContextAccessor; - _bootPhase = BootPhase.Boot; } /// From 2fc21667672d66ad755e8291af058deecaca3a97 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Mon, 30 Mar 2020 07:14:22 +0200 Subject: [PATCH 59/73] AB#5820 - Clean up --- src/Umbraco.Core/Net/IUmbracoApplicationLifetime.cs | 4 ++++ .../AspNetCoreUmbracoApplicationLifetime.cs | 2 +- .../Lifetime/IUmbracoRequestLifetimeManager.cs | 2 +- .../Runtime/AspNetCoreComponent.cs | 8 ++++---- .../Runtime/AspNetCoreComposer.cs | 13 ++++++++----- .../Runtime/Profiler/WebProfilerComponent.cs | 4 ++-- .../AspNet/AspNetUmbracoApplicationLifetime.cs | 4 ++-- 7 files changed, 22 insertions(+), 15 deletions(-) diff --git a/src/Umbraco.Core/Net/IUmbracoApplicationLifetime.cs b/src/Umbraco.Core/Net/IUmbracoApplicationLifetime.cs index c84d475e90..f594e45652 100644 --- a/src/Umbraco.Core/Net/IUmbracoApplicationLifetime.cs +++ b/src/Umbraco.Core/Net/IUmbracoApplicationLifetime.cs @@ -15,7 +15,11 @@ namespace Umbraco.Net void Restart(); event EventHandler ApplicationInit; + } + + public interface IUmbracoApplicationLifetimeManager: IUmbracoApplicationLifetime + { void InvokeApplicationInit(); } } diff --git a/src/Umbraco.Web.Common/AspNetCore/AspNetCoreUmbracoApplicationLifetime.cs b/src/Umbraco.Web.Common/AspNetCore/AspNetCoreUmbracoApplicationLifetime.cs index d73b9bebd4..38d78860b0 100644 --- a/src/Umbraco.Web.Common/AspNetCore/AspNetCoreUmbracoApplicationLifetime.cs +++ b/src/Umbraco.Web.Common/AspNetCore/AspNetCoreUmbracoApplicationLifetime.cs @@ -6,7 +6,7 @@ using Umbraco.Net; namespace Umbraco.Web.Common.AspNetCore { - public class AspNetCoreUmbracoApplicationLifetime : IUmbracoApplicationLifetime + public class AspNetCoreUmbracoApplicationLifetime : IUmbracoApplicationLifetime, IUmbracoApplicationLifetimeManager { private readonly IHttpContextAccessor _httpContextAccessor; private readonly IHostApplicationLifetime _hostApplicationLifetime; diff --git a/src/Umbraco.Web.Common/Lifetime/IUmbracoRequestLifetimeManager.cs b/src/Umbraco.Web.Common/Lifetime/IUmbracoRequestLifetimeManager.cs index e4c671c2d3..7ee8fac1e9 100644 --- a/src/Umbraco.Web.Common/Lifetime/IUmbracoRequestLifetimeManager.cs +++ b/src/Umbraco.Web.Common/Lifetime/IUmbracoRequestLifetimeManager.cs @@ -2,7 +2,7 @@ namespace Umbraco.Web.Common.Lifetime { - public interface IUmbracoRequestLifetimeManager + public interface IUmbracoRequestLifetimeManager : IUmbracoRequestLifetime { void InitRequest(HttpContext context); void EndRequest(HttpContext context); diff --git a/src/Umbraco.Web.Common/Runtime/AspNetCoreComponent.cs b/src/Umbraco.Web.Common/Runtime/AspNetCoreComponent.cs index 04ae022fa5..991dce55e6 100644 --- a/src/Umbraco.Web.Common/Runtime/AspNetCoreComponent.cs +++ b/src/Umbraco.Web.Common/Runtime/AspNetCoreComponent.cs @@ -7,18 +7,18 @@ namespace Umbraco.Web.Common.Runtime public sealed class AspNetCoreComponent : IComponent { private readonly IHostApplicationLifetime _hostApplicationLifetime; - private readonly IUmbracoApplicationLifetime _umbracoApplicationLifetime; + private readonly IUmbracoApplicationLifetimeManager _umbracoApplicationLifetimeManager; - public AspNetCoreComponent(IHostApplicationLifetime hostApplicationLifetime, IUmbracoApplicationLifetime umbracoApplicationLifetime) + public AspNetCoreComponent(IHostApplicationLifetime hostApplicationLifetime, IUmbracoApplicationLifetimeManager umbracoApplicationLifetimeManager) { _hostApplicationLifetime = hostApplicationLifetime; - _umbracoApplicationLifetime = umbracoApplicationLifetime; + _umbracoApplicationLifetimeManager = umbracoApplicationLifetimeManager; } public void Initialize() { _hostApplicationLifetime.ApplicationStarted.Register(() => { - _umbracoApplicationLifetime.InvokeApplicationInit(); + _umbracoApplicationLifetimeManager.InvokeApplicationInit(); }); } diff --git a/src/Umbraco.Web.Common/Runtime/AspNetCoreComposer.cs b/src/Umbraco.Web.Common/Runtime/AspNetCoreComposer.cs index f9a6a770e2..a0b2fb9dd8 100644 --- a/src/Umbraco.Web.Common/Runtime/AspNetCoreComposer.cs +++ b/src/Umbraco.Web.Common/Runtime/AspNetCoreComposer.cs @@ -24,14 +24,17 @@ namespace Umbraco.Web.Common.Runtime composition.RegisterUnique(); // Our own netcore implementations - composition.RegisterUnique(); + composition.RegisterUnique(); + composition.RegisterUnique(factory => factory.GetInstance()); + composition.RegisterUnique(factory => factory.GetInstance()); + composition.RegisterUnique(); // The umbraco request lifetime - var umbracoRequestLifetime = new UmbracoRequestLifetime(); - composition.RegisterUnique(factory => umbracoRequestLifetime); - composition.RegisterUnique(factory => umbracoRequestLifetime); - composition.RegisterUnique(); + composition.RegisterUnique(); + composition.RegisterUnique(factory => factory.GetInstance()); + composition.RegisterUnique(factory => factory.GetInstance()); + composition.RegisterUnique(); } } } diff --git a/src/Umbraco.Web.Common/Runtime/Profiler/WebProfilerComponent.cs b/src/Umbraco.Web.Common/Runtime/Profiler/WebProfilerComponent.cs index a36753e634..7f0b5071c5 100644 --- a/src/Umbraco.Web.Common/Runtime/Profiler/WebProfilerComponent.cs +++ b/src/Umbraco.Web.Common/Runtime/Profiler/WebProfilerComponent.cs @@ -11,11 +11,11 @@ namespace Umbraco.Web.Common.Runtime.Profiler { private readonly bool _profile; private readonly WebProfiler _profiler; - private readonly IUmbracoApplicationLifetime _umbracoApplicationLifetime; + private readonly IUmbracoApplicationLifetimeManager _umbracoApplicationLifetime; private readonly IUmbracoRequestLifetime _umbracoRequestLifetime; public WebProfilerComponent(IProfiler profiler, ILogger logger, IUmbracoRequestLifetime umbracoRequestLifetime, - IUmbracoApplicationLifetime umbracoApplicationLifetime) + IUmbracoApplicationLifetimeManager umbracoApplicationLifetime) { _umbracoRequestLifetime = umbracoRequestLifetime; _umbracoApplicationLifetime = umbracoApplicationLifetime; diff --git a/src/Umbraco.Web/AspNet/AspNetUmbracoApplicationLifetime.cs b/src/Umbraco.Web/AspNet/AspNetUmbracoApplicationLifetime.cs index 9818a510b2..f0ff6e3cad 100644 --- a/src/Umbraco.Web/AspNet/AspNetUmbracoApplicationLifetime.cs +++ b/src/Umbraco.Web/AspNet/AspNetUmbracoApplicationLifetime.cs @@ -5,7 +5,7 @@ using Umbraco.Net; namespace Umbraco.Web.AspNet { - public class AspNetUmbracoApplicationLifetime : IUmbracoApplicationLifetime + public class AspNetUmbracoApplicationLifetime : IUmbracoApplicationLifetime, IUmbracoApplicationLifetimeManager { private readonly IHttpContextAccessor _httpContextAccessor; @@ -37,7 +37,7 @@ namespace Umbraco.Web.AspNet public event EventHandler ApplicationInit; public void InvokeApplicationInit() { - + ApplicationInit?.Invoke(this, EventArgs.Empty); } } } From 49da01c65579e1aec575a30bd27c81b8e937c59c Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Mon, 30 Mar 2020 07:16:55 +0200 Subject: [PATCH 60/73] AB#5820 - Clean up --- src/Umbraco.Web.UI.NetCore/Startup.cs | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/src/Umbraco.Web.UI.NetCore/Startup.cs b/src/Umbraco.Web.UI.NetCore/Startup.cs index f674832952..3906f1c8e9 100644 --- a/src/Umbraco.Web.UI.NetCore/Startup.cs +++ b/src/Umbraco.Web.UI.NetCore/Startup.cs @@ -82,25 +82,4 @@ namespace Umbraco.Web.UI.BackOffice }); } } - - public class UmbracoMiniProfilerOptions : MiniProfilerOptions - { - public UmbracoMiniProfilerOptions() - { - } - - public new MiniProfiler StartProfiler(string profilerName = null) - { - return base.StartProfiler(); - } - - public new Func ShouldProfile => (request) => - { - if (new Uri(request.GetEncodedUrl(), UriKind.RelativeOrAbsolute).IsClientSideRequest()) return false; - if (bool.TryParse(request.Query["umbDebug"], out var umbDebug)) return umbDebug; - if (bool.TryParse(request.Headers["X-UMB-DEBUG"], out var xUmbDebug)) return xUmbDebug; - if (bool.TryParse(request.Cookies["UMB-DEBUG"], out var cUmbDebug)) return cUmbDebug; - return false; - }; - } } From 1e33b3df7a5558d8f9cfb253afc71631b3a6a33f Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Mon, 30 Mar 2020 07:17:56 +0200 Subject: [PATCH 61/73] AB#5820 - Clean up --- src/Umbraco.Web.UI.NetCore/Umbraco.Web.UI.NetCore.csproj | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Umbraco.Web.UI.NetCore/Umbraco.Web.UI.NetCore.csproj b/src/Umbraco.Web.UI.NetCore/Umbraco.Web.UI.NetCore.csproj index 0e6f2cf6b0..abfb172763 100644 --- a/src/Umbraco.Web.UI.NetCore/Umbraco.Web.UI.NetCore.csproj +++ b/src/Umbraco.Web.UI.NetCore/Umbraco.Web.UI.NetCore.csproj @@ -18,7 +18,6 @@ <_ContentIncludedByDefault Remove="wwwroot\~\App_Data\TEMP\TypesCache\umbraco-types.DESKTOP-2016.hash" /> <_ContentIncludedByDefault Remove="wwwroot\~\App_Data\TEMP\TypesCache\umbraco-types.DESKTOP-2016.list" /> - <_ContentIncludedByDefault Remove="Views\BackOffice\Index.cshtml" /> From f6af286e0bc312243495636974f59788ed268d34 Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Mon, 30 Mar 2020 07:42:02 +0200 Subject: [PATCH 62/73] Added tests confirming correct building of unit test objects. --- .../Models/DataTypeTests.cs | 18 ++- .../Models/MemberGroupTests.cs | 45 +++++- .../Models/MemberTests.cs | 144 +++++++++++++----- 3 files changed, 155 insertions(+), 52 deletions(-) diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Models/DataTypeTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Models/DataTypeTests.cs index 4be32a9b4f..53431bfdea 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Models/DataTypeTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Models/DataTypeTests.cs @@ -4,7 +4,6 @@ using Umbraco.Core.Models; using Umbraco.Tests.Common.Builders; using Umbraco.Tests.Common.Builders.Extensions; - namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Models { [TestFixture] @@ -12,11 +11,26 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Models { private readonly DataTypeBuilder _builder = new DataTypeBuilder(); + private const int _testId = 3123; + + [Test] + public void Is_Built_Correctly() + { + // Arrange + // Act + var dtd = _builder + .WithId(_testId) + .Build(); + + // Assert + Assert.AreEqual(_testId, dtd.Id); + } + [Test] public void Can_Deep_Clone() { var dtd = _builder - .WithId(3123) + .WithId(_testId) .Build(); var clone = (DataType) dtd.DeepClone(); diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Models/MemberGroupTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Models/MemberGroupTests.cs index f2b17cc23f..a6718660dd 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Models/MemberGroupTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Models/MemberGroupTests.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Diagnostics; using Newtonsoft.Json; using NUnit.Framework; @@ -13,6 +14,34 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Models { private readonly MemberGroupBuilder _builder = new MemberGroupBuilder(); + private const int _testId = 6; + private const string _testName = "Test Group"; + private const int _testCreatorId = 4; + private readonly Guid _testKey = Guid.NewGuid(); + private readonly DateTime _testCreateDate = DateTime.Now.AddHours(-1); + private readonly DateTime _testUpdateDate = DateTime.Now; + private readonly KeyValuePair _testAdditionalData1 = new KeyValuePair("test1", 123); + private readonly KeyValuePair _testAdditionalData2 = new KeyValuePair("test2", "hello"); + + [Test] + public void Is_Built_Correctly() + { + // Arrange + // Act + var group = BuildMemberGroup(); + + // Assert + Assert.AreEqual(_testId, group.Id); + Assert.AreEqual(_testKey, group.Key); + Assert.AreEqual(_testName, group.Name); + Assert.AreEqual(_testCreateDate, group.CreateDate); + Assert.AreEqual(_testUpdateDate, group.UpdateDate); + Assert.AreEqual(_testCreatorId, group.CreatorId); + Assert.AreEqual(2, group.AdditionalData.Count); + Assert.AreEqual(_testAdditionalData1.Value, group.AdditionalData[_testAdditionalData1.Key]); + Assert.AreEqual(_testAdditionalData2.Value, group.AdditionalData[_testAdditionalData2.Key]); + } + [Test] public void Can_Deep_Clone() { @@ -52,15 +81,15 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Models private MemberGroup BuildMemberGroup() { return _builder - .WithId(6) - .WithKey(Guid.NewGuid()) - .WithName("asdf") - .WithCreatorId(4) - .WithCreateDate(DateTime.Now) - .WithUpdateDate(DateTime.Now) + .WithId(_testId) + .WithKey(_testKey) + .WithName(_testName) + .WithCreatorId(_testCreatorId) + .WithCreateDate(_testCreateDate) + .WithUpdateDate(_testUpdateDate) .AddAdditionalData() - .WithKeyValue("test1", 123) - .WithKeyValue("test2", "hello") + .WithKeyValue(_testAdditionalData1.Key, _testAdditionalData1.Value) + .WithKeyValue(_testAdditionalData2.Key, _testAdditionalData2.Value) .Done() .Build(); } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Models/MemberTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Models/MemberTests.cs index cda7641e8e..9420d03c8f 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Models/MemberTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Models/MemberTests.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Diagnostics; using System.Linq; using Newtonsoft.Json; @@ -15,6 +16,65 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Models { private readonly MemberBuilder _builder = new MemberBuilder(); + private const int _testMemberTypeId = 99; + private const string _testMemberTypeAlias = "memberType"; + private const string _testMemberTypeName = "Member Type"; + private const string _testMemberTypePropertyGroupName = "Content"; + private const int _testId = 6; + private const string _testName = "Fred"; + private const string _testUsername = "fred"; + private const string _testRawPasswordValue = "raw pass"; + private const string _testEmail = "email@email.com"; + private const int _testCreatorId = 22; + private const int _testLevel = 3; + private const string _testPath = "-1, 4, 10"; + private const bool _testIsApproved = true; + private const bool _testIsLockedOut = true; + private const int _testSortOrder = 5; + private const bool _testTrashed = false; + private readonly Guid _testKey = Guid.NewGuid(); + private readonly DateTime _testCreateDate = DateTime.Now.AddHours(-1); + private readonly DateTime _testUpdateDate = DateTime.Now; + private readonly DateTime _testLastLockoutDate = DateTime.Now.AddHours(-2); + private readonly DateTime _testLastLoginDate = DateTime.Now.AddHours(-3); + private readonly DateTime _testLastPasswordChangeDate = DateTime.Now.AddHours(-4); + private readonly (string alias, string name, string description, int sortOrder, int dataTypeId) _testPropertyType1 = ("title", "Title", string.Empty, 1, -88); + private readonly (string alias, string name, string description, int sortOrder, int dataTypeId) _testPropertyType2 = ("bodyText", "Body Text", string.Empty, 2, -87); + private readonly (string alias, string name, string description, int sortOrder, int dataTypeId) _testPropertyType3 = ("author", "Author", "Writer of the article", 1, -88); + private readonly string[] _testGroups = new string[] { "group1", "group2" }; + private readonly KeyValuePair _testPropertyData1 = new KeyValuePair("title", "Name member"); + private readonly KeyValuePair _testPropertyData2 = new KeyValuePair("bodyText", "This is a subpage"); + private readonly KeyValuePair _testPropertyData3 = new KeyValuePair("author", "John Doe"); + private readonly KeyValuePair _testAdditionalData1 = new KeyValuePair("test1", 123); + private readonly KeyValuePair _testAdditionalData2 = new KeyValuePair("test2", "hello"); + + [Test] + public void Is_Built_Correctly() + { + // Arrange + // Act + var member = BuildMember(); + + // Assert + Assert.AreEqual(_testMemberTypeId, member.ContentTypeId); + Assert.AreEqual(_testMemberTypeAlias, member.ContentType.Alias); + Assert.AreEqual(_testMemberTypeName, member.ContentType.Name); + Assert.AreEqual(_testId, member.Id); + Assert.AreEqual(_testKey, member.Key); + Assert.AreEqual(_testName, member.Name); + Assert.AreEqual(_testCreateDate, member.CreateDate); + Assert.AreEqual(_testUpdateDate, member.UpdateDate); + Assert.AreEqual(_testCreatorId, member.CreatorId); + Assert.AreEqual(_testGroups, member.Groups.ToArray()); + Assert.AreEqual(10, member.Properties.Count); // 7 from membership properties group, 3 custom + Assert.AreEqual(_testPropertyData1.Value, member.GetValue(_testPropertyData1.Key)); + Assert.AreEqual(_testPropertyData2.Value, member.GetValue(_testPropertyData2.Key)); + Assert.AreEqual(_testPropertyData3.Value, member.GetValue(_testPropertyData3.Key)); + Assert.AreEqual(2, member.AdditionalData.Count); + Assert.AreEqual(_testAdditionalData1.Value, member.AdditionalData[_testAdditionalData1.Key]); + Assert.AreEqual(_testAdditionalData2.Value, member.AdditionalData[_testAdditionalData2.Key]); + } + [Test] public void Can_Deep_Clone() { @@ -81,72 +141,72 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Models { return _builder .AddMemberType() - .WithId(99) - .WithAlias("memberType") - .WithName("Member Type") + .WithId(_testMemberTypeId) + .WithAlias(_testMemberTypeAlias) + .WithName(_testMemberTypeName) .WithMembershipPropertyGroup() .AddPropertyGroup() - .WithName("Content") + .WithName(_testMemberTypePropertyGroupName) .WithSortOrder(1) .AddPropertyType() .WithPropertyEditorAlias(Constants.PropertyEditors.Aliases.TextBox) .WithValueStorageType(ValueStorageType.Nvarchar) - .WithAlias("title") - .WithName("Title") - .WithSortOrder(1) - .WithDataTypeId(-88) + .WithAlias(_testPropertyType1.alias) + .WithName(_testPropertyType1.name) + .WithSortOrder(_testPropertyType1.sortOrder) + .WithDataTypeId(_testPropertyType1.dataTypeId) .Done() .AddPropertyType() .WithPropertyEditorAlias(Constants.PropertyEditors.Aliases.TextBox) .WithValueStorageType(ValueStorageType.Ntext) - .WithAlias("bodyText") - .WithName("Body Text") - .WithSortOrder(2) - .WithDataTypeId(-87) + .WithAlias(_testPropertyType2.alias) + .WithName(_testPropertyType2.name) + .WithSortOrder(_testPropertyType2.sortOrder) + .WithDataTypeId(_testPropertyType2.dataTypeId) .Done() .AddPropertyType() .WithPropertyEditorAlias(Constants.PropertyEditors.Aliases.TextBox) .WithValueStorageType(ValueStorageType.Nvarchar) - .WithAlias("author") - .WithName("Author") - .WithDescription("Name of the author") - .WithSortOrder(3) - .WithDataTypeId(-88) + .WithAlias(_testPropertyType3.alias) + .WithName(_testPropertyType3.name) + .WithDescription(_testPropertyType3.description) + .WithSortOrder(_testPropertyType3.sortOrder) + .WithDataTypeId(_testPropertyType3.dataTypeId) .Done() .Done() .Done() - .WithId(10) - .WithKey(Guid.NewGuid()) - .WithName("Name") - .WithUserName("user") - .WithRawPasswordValue("raw pass") - .WithEmail("email@email.com") - .WithCreatorId(22) - .WithCreateDate(DateTime.Now) - .WithUpdateDate(DateTime.Now) + .WithId(_testId) + .WithKey(_testKey) + .WithName(_testName) + .WithUserName(_testUsername) + .WithRawPasswordValue(_testRawPasswordValue) + .WithEmail(_testEmail) + .WithCreatorId(_testCreatorId) + .WithCreateDate(_testCreateDate) + .WithUpdateDate(_testUpdateDate) .WithFailedPasswordAttempts(22) - .WithLevel(3) - .WithPath("-1, 4, 10") - .WithIsApproved(true) - .WithIsLockedOut(true) - .WithLastLockoutDate(DateTime.Now) - .WithLastLoginDate(DateTime.Now) - .WithLastPasswordChangeDate(DateTime.Now) - .WithSortOrder(5) - .WithTrashed(false) + .WithLevel(_testLevel) + .WithPath(_testPath) + .WithIsApproved(_testIsApproved) + .WithIsLockedOut(_testIsLockedOut) + .WithLastLockoutDate(_testLastLockoutDate) + .WithLastLoginDate(_testLastLoginDate) + .WithLastPasswordChangeDate(_testLastPasswordChangeDate) + .WithSortOrder(_testSortOrder) + .WithTrashed(_testTrashed) .AddMemberGroups() - .WithValue("group1") - .WithValue("group2") + .WithValue(_testGroups[0]) + .WithValue(_testGroups[1]) .Done() .AddAdditionalData() - .WithKeyValue("test1", 123) - .WithKeyValue("test2", "hello") + .WithKeyValue(_testAdditionalData1.Key, _testAdditionalData1.Value) + .WithKeyValue(_testAdditionalData2.Key, _testAdditionalData2.Value) .Done() .WithPropertyIdsIncrementingFrom(200) .AddPropertyData() - .WithKeyValue("title", "Name member") - .WithKeyValue("bodyText", "This is a subpage") - .WithKeyValue("author", "John Doe") + .WithKeyValue(_testPropertyData1.Key, _testPropertyData1.Value) + .WithKeyValue(_testPropertyData2.Key, _testPropertyData2.Value) + .WithKeyValue(_testPropertyData3.Key, _testPropertyData3.Value) .Done() .Build(); } From 5fad35d0eedbfae2a09fc29045f88ea321dc787a Mon Sep 17 00:00:00 2001 From: Olivier Bossaer Date: Sat, 28 Mar 2020 14:37:31 +0100 Subject: [PATCH 63/73] Fix #7521 - Runtime error if removing email notificationMethod --- .../HealthCheck/NotificationMethods/EmailNotificationMethod.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web/HealthCheck/NotificationMethods/EmailNotificationMethod.cs b/src/Umbraco.Web/HealthCheck/NotificationMethods/EmailNotificationMethod.cs index 87c0e4f46d..873b356214 100644 --- a/src/Umbraco.Web/HealthCheck/NotificationMethods/EmailNotificationMethod.cs +++ b/src/Umbraco.Web/HealthCheck/NotificationMethods/EmailNotificationMethod.cs @@ -20,7 +20,7 @@ namespace Umbraco.Web.HealthCheck.NotificationMethods public EmailNotificationMethod(ILocalizedTextService textService, IRuntimeState runtimeState, ILogger logger) { - var recipientEmail = Settings["recipientEmail"]?.Value; + var recipientEmail = Settings?["recipientEmail"]?.Value; if (string.IsNullOrWhiteSpace(recipientEmail)) { Enabled = false; From 8cba80913a46628778b477b39d1e87bb4b5e1fca Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Mon, 30 Mar 2020 13:36:41 +0200 Subject: [PATCH 64/73] Remove generated files from git and ignore them --- .gitignore | 3 + .../Umbraco/js/main.controller.js | 220 ------- .../Umbraco/js/navigation.controller.js | 544 ------------------ 3 files changed, 3 insertions(+), 764 deletions(-) delete mode 100644 src/Umbraco.Web.UI/Umbraco/js/main.controller.js delete mode 100644 src/Umbraco.Web.UI/Umbraco/js/navigation.controller.js diff --git a/.gitignore b/.gitignore index 12ad3299ad..870d24c648 100644 --- a/.gitignore +++ b/.gitignore @@ -100,6 +100,9 @@ src/Umbraco.Web.UI/[Uu]mbraco/[Jj]s/loader.dev.js src/Umbraco.Web.UI/[Uu]mbraco/[Jj]s/main.js src/Umbraco.Web.UI/[Uu]mbraco/[Jj]s/app.js src/Umbraco.Web.UI/[Uu]mbraco/[Jj]s/canvasdesigner.*.js +src/Umbraco.Web.UI/[Uu]mbraco/[Jj]s/navigation.controller.js +src/Umbraco.Web.UI/[Uu]mbraco/[Jj]s/main.controller.js +src/Umbraco.Web.UI/[Uu]mbraco/[Jj]s/utilities.js src/Umbraco.Web.UI/[Uu]mbraco/[Vv]iews/ src/Umbraco.Web.UI/[Uu]mbraco/[Vv]iews/**/*.js diff --git a/src/Umbraco.Web.UI/Umbraco/js/main.controller.js b/src/Umbraco.Web.UI/Umbraco/js/main.controller.js deleted file mode 100644 index 883907d1dc..0000000000 --- a/src/Umbraco.Web.UI/Umbraco/js/main.controller.js +++ /dev/null @@ -1,220 +0,0 @@ - -/** - * @ngdoc controller - * @name Umbraco.MainController - * @function - * - * @description - * The main application controller - * - */ -function MainController($scope, $location, appState, treeService, notificationsService, - userService, historyService, updateChecker, navigationService, eventsService, - tmhDynamicLocale, localStorageService, editorService, overlayService, assetsService, tinyMceAssets) { - - //the null is important because we do an explicit bool check on this in the view - $scope.authenticated = null; - $scope.touchDevice = appState.getGlobalState("touchDevice"); - $scope.infiniteMode = false; - $scope.overlay = {}; - $scope.drawer = {}; - $scope.search = {}; - $scope.login = {}; - $scope.tabbingActive = false; - - // Load TinyMCE assets ahead of time in the background for the user - // To help with first load of the RTE - tinyMceAssets.forEach(function (tinyJsAsset) { - assetsService.loadJs(tinyJsAsset, $scope); - }); - - // There are a number of ways to detect when a focus state should be shown when using the tab key and this seems to be the simplest solution. - // For more information about this approach, see https://hackernoon.com/removing-that-ugly-focus-ring-and-keeping-it-too-6c8727fefcd2 - function handleFirstTab(evt) { - if (evt.keyCode === 9) { - $scope.tabbingActive = true; - $scope.$digest(); - window.removeEventListener('keydown', handleFirstTab); - window.addEventListener('mousedown', disableTabbingActive); - } - } - - function disableTabbingActive(evt) { - $scope.tabbingActive = false; - $scope.$digest(); - window.removeEventListener('mousedown', disableTabbingActive); - window.addEventListener("keydown", handleFirstTab); - } - - window.addEventListener("keydown", handleFirstTab); - - - $scope.removeNotification = function (index) { - notificationsService.remove(index); - }; - - $scope.closeSearch = function() { - appState.setSearchState("show", false); - }; - - $scope.showLoginScreen = function(isTimedOut) { - $scope.login.isTimedOut = isTimedOut; - $scope.login.show = true; - }; - - $scope.hideLoginScreen = function() { - $scope.login.show = false; - }; - - var evts = []; - - //when a user logs out or timesout - evts.push(eventsService.on("app.notAuthenticated", function (evt, data) { - $scope.authenticated = null; - $scope.user = null; - const isTimedOut = data && data.isTimedOut ? true : false; - $scope.showLoginScreen(isTimedOut); - - // Remove the localstorage items for tours shown - // Means that when next logged in they can be re-shown if not already dismissed etc - localStorageService.remove("emailMarketingTourShown"); - localStorageService.remove("introTourShown"); - })); - - evts.push(eventsService.on("app.userRefresh", function(evt) { - userService.refreshCurrentUser().then(function(data) { - $scope.user = data; - - //Load locale file - if ($scope.user.locale) { - tmhDynamicLocale.set($scope.user.locale); - } - }); - })); - - //when the app is ready/user is logged in, setup the data - evts.push(eventsService.on("app.ready", function (evt, data) { - - $scope.authenticated = data.authenticated; - $scope.user = data.user; - - updateChecker.check().then(function (update) { - if (update && update !== "null") { - if (update.type !== "None") { - var notification = { - headline: "Update available", - message: "Click to download", - sticky: true, - type: "info", - url: update.url - }; - notificationsService.add(notification); - } - } - }); - - //if the user has changed we need to redirect to the root so they don't try to continue editing the - //last item in the URL (NOTE: the user id can equal zero, so we cannot just do !data.lastUserId since that will resolve to true) - if (data.lastUserId !== undefined && data.lastUserId !== null && data.lastUserId !== data.user.id) { - - var section = appState.getSectionState("currentSection"); - if (section) { - //if there's a section already assigned, reload it so the tree is cleared - navigationService.reloadSection(section); - } - - $location.path("/").search(""); - historyService.removeAll(); - treeService.clearCache(); - editorService.closeAll(); - overlayService.close(); - - //if the user changed, clearout local storage too - could contain sensitive data - localStorageService.clearAll(); - } - - //if this is a new login (i.e. the user entered credentials), then clear out local storage - could contain sensitive data - if (data.loginType === "credentials") { - localStorageService.clearAll(); - } - - //Load locale file - if ($scope.user.locale) { - tmhDynamicLocale.set($scope.user.locale); - } - - })); - - // events for search - evts.push(eventsService.on("appState.searchState.changed", function (e, args) { - if (args.key === "show") { - $scope.search.show = args.value; - } - })); - - // events for drawer - // manage the help dialog by subscribing to the showHelp appState - evts.push(eventsService.on("appState.drawerState.changed", function (e, args) { - // set view - if (args.key === "view") { - $scope.drawer.view = args.value; - } - // set custom model - if (args.key === "model") { - $scope.drawer.model = args.value; - } - // show / hide drawer - if (args.key === "showDrawer") { - $scope.drawer.show = args.value; - } - })); - - // events for overlays - evts.push(eventsService.on("appState.overlay", function (name, args) { - $scope.overlay = args; - })); - - // events for tours - evts.push(eventsService.on("appState.tour.start", function (name, args) { - $scope.tour = args; - $scope.tour.show = true; - })); - - evts.push(eventsService.on("appState.tour.end", function () { - $scope.tour = null; - })); - - evts.push(eventsService.on("appState.tour.complete", function () { - $scope.tour = null; - })); - - // events for backdrop - evts.push(eventsService.on("appState.backdrop", function (name, args) { - $scope.backdrop = args; - })); - - // event for infinite editors - evts.push(eventsService.on("appState.editors.open", function (name, args) { - $scope.infiniteMode = args && args.editors.length > 0 ? true : false; - })); - - evts.push(eventsService.on("appState.editors.close", function (name, args) { - $scope.infiniteMode = args && args.editors.length > 0 ? true : false; - })); - - //ensure to unregister from all events! - $scope.$on('$destroy', function () { - for (var e in evts) { - eventsService.unsubscribe(evts[e]); - } - }); - -} - - -//register it -angular.module('umbraco').controller("Umbraco.MainController", MainController). - config(function (tmhDynamicLocaleProvider) { - //Set url for locale files - tmhDynamicLocaleProvider.localeLocationPattern('lib/angular-i18n/angular-locale_{{locale}}.js'); - }); diff --git a/src/Umbraco.Web.UI/Umbraco/js/navigation.controller.js b/src/Umbraco.Web.UI/Umbraco/js/navigation.controller.js deleted file mode 100644 index 194c45afe6..0000000000 --- a/src/Umbraco.Web.UI/Umbraco/js/navigation.controller.js +++ /dev/null @@ -1,544 +0,0 @@ - -/** - * @ngdoc controller - * @name Umbraco.NavigationController - * @function - * - * @description - * Handles the section area of the app - * - * @param {navigationService} navigationService A reference to the navigationService - */ -function NavigationController($scope, $rootScope, $location, $log, $q, $routeParams, $timeout, $cookies, treeService, appState, navigationService, keyboardService, historyService, eventsService, angularHelper, languageResource, contentResource, editorState) { - - //this is used to trigger the tree to start loading once everything is ready - var treeInitPromise = $q.defer(); - - $scope.treeApi = {}; - - //Bind to the main tree events - $scope.onTreeInit = function () { - - $scope.treeApi.callbacks.treeNodeExpanded(nodeExpandedHandler); - - //when a tree is loaded into a section, we need to put it into appState - $scope.treeApi.callbacks.treeLoaded(function (args) { - appState.setTreeState("currentRootNode", args.tree); - }); - - //when a tree node is synced this event will fire, this allows us to set the currentNode - $scope.treeApi.callbacks.treeSynced(function (args) { - - if (args.activate === undefined || args.activate === true) { - //set the current selected node - appState.setTreeState("selectedNode", args.node); - //when a node is activated, this is the same as clicking it and we need to set the - //current menu item to be this node as well. - //appState.setMenuState("currentNode", args.node);// Niels: No, we are setting it from the dialog. - } - }); - - //this reacts to the options item in the tree - $scope.treeApi.callbacks.treeOptionsClick(function (args) { - args.event.stopPropagation(); - args.event.preventDefault(); - - //Set the current action node (this is not the same as the current selected node!) - //appState.setMenuState("currentNode", args.node);// Niels: No, we are setting it from the dialog. - - if (args.event && args.event.altKey) { - args.skipDefault = true; - } - - navigationService.showMenu(args); - }); - - $scope.treeApi.callbacks.treeNodeAltSelect(function (args) { - args.event.stopPropagation(); - args.event.preventDefault(); - - args.skipDefault = true; - navigationService.showMenu(args); - }); - - //this reacts to tree items themselves being clicked - //the tree directive should not contain any handling, simply just bubble events - $scope.treeApi.callbacks.treeNodeSelect(function (args) { - var n = args.node; - args.event.stopPropagation(); - args.event.preventDefault(); - - if (n.metaData && n.metaData["jsClickCallback"] && angular.isString(n.metaData["jsClickCallback"]) && n.metaData["jsClickCallback"] !== "") { - //this is a legacy tree node! - var jsPrefix = "javascript:"; - var js; - if (n.metaData["jsClickCallback"].startsWith(jsPrefix)) { - js = n.metaData["jsClickCallback"].substr(jsPrefix.length); - } - else { - js = n.metaData["jsClickCallback"]; - } - try { - var func = eval(js); - //this is normally not necessary since the eval above should execute the method and will return nothing. - if (func != null && (typeof func === "function")) { - func.call(); - } - } - catch (ex) { - $log.error("Error evaluating js callback from legacy tree node: " + ex); - } - } - else if (n.routePath) { - //add action to the history service - historyService.add({ name: n.name, link: n.routePath, icon: n.icon }); - - //put this node into the tree state - appState.setTreeState("selectedNode", args.node); - //when a node is clicked we also need to set the active menu node to this node - //appState.setMenuState("currentNode", args.node); - - //not legacy, lets just set the route value and clear the query string if there is one. - $location.path(n.routePath); - navigationService.clearSearch(); - } - else if (n.section) { - $location.path(n.section); - navigationService.clearSearch(); - } - - navigationService.hideNavigation(); - }); - - return treeInitPromise.promise; - } - - //set up our scope vars - $scope.showContextMenuDialog = false; - $scope.showContextMenu = false; - $scope.showSearchResults = false; - $scope.menuDialogTitle = null; - $scope.menuActions = []; - $scope.menuNode = null; - $scope.languages = []; - $scope.selectedLanguage = {}; - $scope.page = {}; - $scope.page.languageSelectorIsOpen = false; - - $scope.currentSection = null; - $scope.customTreeParams = null; - $scope.treeCacheKey = "_"; - $scope.showNavigation = appState.getGlobalState("showNavigation"); - // tracks all expanded paths so when the language is switched we can resync it with the already loaded paths - var expandedPaths = []; - - //trigger search with a hotkey: - keyboardService.bind("ctrl+shift+s", function () { - navigationService.showSearch(); - }); - - //// TODO: remove this it's not a thing - //$scope.selectedId = navigationService.currentId; - - var isInit = false; - var evts = []; - - //Listen for global state changes - evts.push(eventsService.on("appState.globalState.changed", function (e, args) { - if (args.key === "showNavigation") { - $scope.showNavigation = args.value; - } - })); - - //Listen for menu state changes - evts.push(eventsService.on("appState.menuState.changed", function (e, args) { - if (args.key === "showMenuDialog") { - $scope.showContextMenuDialog = args.value; - } - if (args.key === "dialogTemplateUrl") { - $scope.dialogTemplateUrl = args.value; - } - if (args.key === "showMenu") { - $scope.showContextMenu = args.value; - } - if (args.key === "dialogTitle") { - $scope.menuDialogTitle = args.value; - } - if (args.key === "menuActions") { - $scope.menuActions = args.value; - } - if (args.key === "currentNode") { - $scope.menuNode = args.value; - } - })); - - //Listen for tree state changes - evts.push(eventsService.on("appState.treeState.changed", function (e, args) { - if (args.key === "currentRootNode") { - - //if the changed state is the currentRootNode, determine if this is a full screen app - if (args.value.root && args.value.root.containsTrees === false) { - $rootScope.emptySection = true; - } - else { - $rootScope.emptySection = false; - } - } - - })); - - //Listen for section state changes - evts.push(eventsService.on("appState.sectionState.changed", function (e, args) { - - //section changed - if (args.key === "currentSection" && $scope.currentSection != args.value) { - //before loading the main tree we need to ensure that the nav is ready - navigationService.waitForNavReady().then(() => { - $scope.currentSection = args.value; - //load the tree - configureTreeAndLanguages(); - $scope.treeApi.load({ section: $scope.currentSection, customTreeParams: $scope.customTreeParams, cacheKey: $scope.treeCacheKey }); - }); - } - - //show/hide search results - if (args.key === "showSearchResults") { - $scope.showSearchResults = args.value; - } - - })); - - // Listen for language updates - evts.push(eventsService.on("editors.languages.languageDeleted", function (e, args) { - loadLanguages().then(function (languages) { - $scope.languages = languages; - const defaultCulture = $scope.languages[0].culture; - - if (args.language.culture === $scope.selectedLanguage.culture) { - $scope.selectedLanguage = defaultCulture; - - if ($scope.languages.length > 1) { - $location.search("mculture", defaultCulture); - } else { - $location.search("mculture", null); - } - - var currentEditorState = editorState.getCurrent(); - if (currentEditorState && currentEditorState.path) { - $scope.treeApi.syncTree({ path: currentEditorState.path, activate: true }); - } - } - }); - })); - - //Emitted when a language is created or an existing one saved/edited - evts.push(eventsService.on("editors.languages.languageSaved", function (e, args) { - if(args.isNew){ - //A new language has been created - reload languages for tree - loadLanguages().then(function (languages) { - $scope.languages = languages; - }); - } - else if(args.language.isDefault){ - //A language was saved and was set to be the new default (refresh the tree, so its at the top) - loadLanguages().then(function (languages) { - $scope.languages = languages; - }); - } - })); - - //when a user logs out or timesout - evts.push(eventsService.on("app.notAuthenticated", function () { - $scope.authenticated = false; - })); - - //when the application is ready and the user is authorized, setup the data - //this will occur anytime a new user logs in! - evts.push(eventsService.on("app.ready", function (evt, data) { - $scope.authenticated = true; - ensureInit(); - })); - - // event for infinite editors - evts.push(eventsService.on("appState.editors.open", function (name, args) { - $scope.infiniteMode = args && args.editors.length > 0 ? true : false; - })); - - evts.push(eventsService.on("appState.editors.close", function (name, args) { - $scope.infiniteMode = args && args.editors.length > 0 ? true : false; - })); - - evts.push(eventsService.on("treeService.removeNode", function (e, args) { - //check to see if the current page has been removed - - var currentEditorState = editorState.getCurrent(); - if (currentEditorState && currentEditorState.id.toString() === args.node.id.toString()) { - //current page is loaded, so navigate to root - var section = appState.getSectionState("currentSection"); - $location.path("/" + section); - } - })); - - - - - /** - * Based on the current state of the application, this configures the scope variables that control the main tree and language drop down - */ - function configureTreeAndLanguages() { - - //create the custom query string param for this tree, this is currently only relevant for content - if ($scope.currentSection === "content") { - - //must use $location here because $routeParams isn't available until after the route change - var mainCulture = $location.search().mculture; - //select the current language if set in the query string - if (mainCulture && $scope.languages && $scope.languages.length > 1) { - var found = _.find($scope.languages, function (l) { - if (mainCulture === true) { - return false; - } - return l.culture.toLowerCase() === mainCulture.toLowerCase(); - }); - if (found) { - //set the route param - found.active = true; - $scope.selectedLanguage = found; - } - } - - var queryParams = {}; - if ($scope.selectedLanguage && $scope.selectedLanguage.culture) { - queryParams["culture"] = $scope.selectedLanguage.culture; - } - var queryString = $.param(queryParams); //create the query string from the params object - } - - if (queryString) { - $scope.customTreeParams = queryString; - $scope.treeCacheKey = queryString; // this tree uses caching but we need to change it's cache key per lang - } - else { - $scope.treeCacheKey = "_"; // this tree uses caching, there's no lang selected so use the default - } - - } - - /** - * Called when the app is ready and sets up the navigation (should only be called once) - */ - function ensureInit() { - - //only run once ever! - if (isInit) { - return; - } - - isInit = true; - - var navInit = false; - - //$routeParams will be populated after $routeChangeSuccess since this controller is used outside ng-view, - //* we listen for the first route change with a section to setup the navigation. - //* we listen for all route changes to track the current section. - $rootScope.$on('$routeChangeSuccess', function () { - - //only continue if there's a section available - if ($routeParams.section) { - - if (!navInit) { - navInit = true; - initNav(); - } - - //keep track of the current section when it changes - if ($scope.currentSection != $routeParams.section) { - appState.setSectionState("currentSection", $routeParams.section); - } - - } - }); - } - - /** - * This loads the language data, if the are no variant content types configured this will return no languages - */ - function loadLanguages() { - - return contentResource.allowsCultureVariation().then(function (b) { - if (b === true) { - return languageResource.getAll(); - } else { - return $q.when([]); //resolve an empty collection - } - }); - } - - /** - * Called once during init to initialize the navigation/tree/languages - */ - function initNav() { - // load languages - loadLanguages().then(function (languages) { - - $scope.languages = languages; - - if ($scope.languages.length > 1) { - //if there's already one set, check if it exists - var currCulture = null; - var mainCulture = $location.search().mculture; - if (mainCulture) { - currCulture = _.find($scope.languages, function (l) { - return l.culture.toLowerCase() === mainCulture.toLowerCase(); - }); - } - if (!currCulture) { - // no culture in the request, let's look for one in the cookie that's set when changing language - var defaultCulture = $cookies.get("UMB_MCULTURE"); - if (!defaultCulture || !_.find($scope.languages, function (l) { - return l.culture.toLowerCase() === defaultCulture.toLowerCase(); - })) { - // no luck either, look for the default language - var defaultLang = _.find($scope.languages, function (l) { - return l.isDefault; - }); - if (defaultLang) { - defaultCulture = defaultLang.culture; - } - } - $location.search("mculture", defaultCulture ? defaultCulture : null); - } - } - - $scope.currentSection = $routeParams.section; - - configureTreeAndLanguages(); - - //resolve the tree promise, set it's property values for loading the tree which will make the tree load - treeInitPromise.resolve({ - section: $scope.currentSection, - customTreeParams: $scope.customTreeParams, - cacheKey: $scope.treeCacheKey, - - //because angular doesn't return a promise for the resolve method, we need to resort to some hackery, else - //like normal JS promises we could do resolve(...).then() - onLoaded: function () { - - //the nav is ready, let the app know - eventsService.emit("app.navigationReady", { treeApi: $scope.treeApi }); - - } - }); - }); - } - function nodeExpandedHandler(args) { - //store the reference to the expanded node path - if (args.node) { - treeService._trackExpandedPaths(args.node, expandedPaths); - } - } - - $scope.selectLanguage = function (language) { - - $location.search("mculture", language.culture); - // add the selected culture to a cookie so the user will log back into the same culture later on (cookie lifetime = one year) - var expireDate = new Date(); - expireDate.setDate(expireDate.getDate() + 365); - $cookies.put("UMB_MCULTURE", language.culture, {path: "/", expires: expireDate}); - - // close the language selector - $scope.page.languageSelectorIsOpen = false; - - configureTreeAndLanguages(); //re-bind language to the query string and update the tree params - - //reload the tree with it's updated querystring args - $scope.treeApi.load({ section: $scope.currentSection, customTreeParams: $scope.customTreeParams, cacheKey: $scope.treeCacheKey }).then(function () { - - //re-sync to currently edited node - var currNode = appState.getTreeState("selectedNode"); - //create the list of promises - var promises = []; - //starting with syncing to the currently selected node if there is one - if (currNode) { - var path = treeService.getPath(currNode); - promises.push($scope.treeApi.syncTree({ path: path, activate: true })); - } - // TODO: If we want to keep all paths expanded ... but we need more testing since we need to deal with unexpanding - //for (var i = 0; i < expandedPaths.length; i++) { - // promises.push($scope.treeApi.syncTree({ path: expandedPaths[i], activate: false, forceReload: true })); - //} - //execute them sequentially - - // set selected language to active - angular.forEach($scope.languages, function(language){ - language.active = false; - }); - language.active = true; - - angularHelper.executeSequentialPromises(promises); - }); - - }; - - //this reacts to the options item in the tree - // TODO: migrate to nav service - // TODO: is this used? - $scope.searchShowMenu = function (ev, args) { - //always skip default - args.skipDefault = true; - navigationService.showMenu(args); - }; - - // TODO: migrate to nav service - // TODO: is this used? - $scope.searchHide = function () { - navigationService.hideSearch(); - }; - - //the below assists with hiding/showing the tree - var treeActive = false; - - //Sets a service variable as soon as the user hovers the navigation with the mouse - //used by the leaveTree method to delay hiding - $scope.enterTree = function (event) { - treeActive = true; - }; - - // Hides navigation tree, with a short delay, is cancelled if the user moves the mouse over the tree again - $scope.leaveTree = function (event) { - //this is a hack to handle IE touch events which freaks out due to no mouse events so the tree instantly shuts down - if (!event) { - return; - } - closeTree(); - }; - - $scope.onOutsideClick = function() { - closeTree(); - }; - - function closeTree() { - if (!appState.getGlobalState("touchDevice")) { - treeActive = false; - $timeout(function () { - if (!treeActive) { - navigationService.hideTree(); - } - }, 300); - } - } - - $scope.toggleLanguageSelector = function () { - $scope.page.languageSelectorIsOpen = !$scope.page.languageSelectorIsOpen; - }; - - //ensure to unregister from all events! - $scope.$on('$destroy', function () { - for (var e in evts) { - eventsService.unsubscribe(evts[e]); - } - }); -} - -//register it -angular.module('umbraco').controller("Umbraco.NavigationController", NavigationController); From 75b216bca569ed4d9498dac2fd39d801870d54e5 Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Mon, 30 Mar 2020 14:30:50 +0200 Subject: [PATCH 65/73] Removed registerFileResolver methods on image cropper and file upload property editors. --- .../fileupload/fileupload.controller.js | 34 +------------ .../imagecropper/imagecropper.controller.js | 51 ------------------- 2 files changed, 1 insertion(+), 84 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/fileupload/fileupload.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/fileupload/fileupload.controller.js index ee71a0d2f2..15f8dbf11c 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/fileupload/fileupload.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/fileupload/fileupload.controller.js @@ -41,37 +41,5 @@ }; angular.module("umbraco") - .controller('Umbraco.PropertyEditors.FileUploadController', fileUploadController) - .run(function (mediaHelper, umbRequestHelper, assetsService) { - if (mediaHelper && mediaHelper.registerFileResolver) { - // NOTE: The 'entity' can be either a normal media entity or an "entity" returned from the entityResource - // they contain different data structures so if we need to query against it we need to be aware of this. - mediaHelper.registerFileResolver("Umbraco.UploadField", function (property, entity, thumbnail) { - if (thumbnail) { - if (mediaHelper.detectIfImageByExtension(property.value)) { - - // Get default big thumbnail from image processor - return mediaHelper.getProcessedImageUrl(property.value, - { - animationprocessmode: "first", - cacheBusterValue: moment(entity.updateDate).format("YYYYMMDDHHmmss"), - width: 500 - }) - .then(function (url) { - return url; - }); - } - else { - return null; - } - } - else { - return property.value; - } - }); - - } - }); - - + .controller('Umbraco.PropertyEditors.FileUploadController', fileUploadController); })(); diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/imagecropper/imagecropper.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/imagecropper/imagecropper.controller.js index c483e83845..cfb15dc8b7 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/imagecropper/imagecropper.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/imagecropper/imagecropper.controller.js @@ -214,55 +214,4 @@ angular.module('umbraco') $scope.$on('$destroy', function () { unsubscribe(); }); - }) - .run(function (mediaHelper, umbRequestHelper) { - if (mediaHelper && mediaHelper.registerFileResolver) { - - //NOTE: The 'entity' can be either a normal media entity or an "entity" returned from the entityResource - // they contain different data structures so if we need to query against it we need to be aware of this. - mediaHelper.registerFileResolver("Umbraco.ImageCropper", function (property, entity, thumbnail) { - if (property.value && property.value.src) { - if (thumbnail === true) { - return mediaHelper.getProcessedImageUrl(property.value.src, - { - animationprocessmode: "first", - mode: "max", - width: 500 - }) - .then(function (url) { - return url; - }); - - } - else { - return property.value.src; - } - - //this is a fallback in case the cropper has been asssigned a upload field - } - else if (angular.isString(property.value)) { - if (thumbnail) { - - if (mediaHelper.detectIfImageByExtension(property.value)) { - - var thumbnailUrl = umbRequestHelper.getApiUrl( - "imagesApiBaseUrl", - "GetBigThumbnail", - [{ originalImagePath: property.value }]); - - return thumbnailUrl; - } - else { - return null; - } - - } - else { - return property.value; - } - } - - return null; - }); - } }); From 4ac8ae3b1520acd0f7281239d8dc7296ca3b3892 Mon Sep 17 00:00:00 2001 From: Elitsa Marinovska Date: Mon, 30 Mar 2020 15:29:53 +0200 Subject: [PATCH 66/73] Removing specific to each machine file paths from project --- src/Umbraco.Web.UI.NetCore/Umbraco.Web.UI.NetCore.csproj | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/Umbraco.Web.UI.NetCore/Umbraco.Web.UI.NetCore.csproj b/src/Umbraco.Web.UI.NetCore/Umbraco.Web.UI.NetCore.csproj index abfb172763..dc148d8d1f 100644 --- a/src/Umbraco.Web.UI.NetCore/Umbraco.Web.UI.NetCore.csproj +++ b/src/Umbraco.Web.UI.NetCore/Umbraco.Web.UI.NetCore.csproj @@ -15,9 +15,4 @@ - - <_ContentIncludedByDefault Remove="wwwroot\~\App_Data\TEMP\TypesCache\umbraco-types.DESKTOP-2016.hash" /> - <_ContentIncludedByDefault Remove="wwwroot\~\App_Data\TEMP\TypesCache\umbraco-types.DESKTOP-2016.list" /> - - From 2a2903f932645256ad36fdaa90fec547a8f9a8b1 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Tue, 31 Mar 2020 12:22:11 +0200 Subject: [PATCH 67/73] AB#5820 - Review fixes / Clean up --- src/Umbraco.Tests.Integration/RuntimeTests.cs | 7 ++----- .../Testing/UmbracoIntegrationTest.cs | 3 +-- .../Umbraco.Tests.Integration.csproj | 1 + .../AspNetCore/AspNetCoreUmbracoApplicationLifetime.cs | 2 +- .../UmbracoCoreServiceCollectionExtensions.cs | 3 ++- .../Lifetime/UmbracoRequestLifetime.cs | 2 +- src/Umbraco.Web.Common/Runtime/Profiler/WebProfiler.cs | 10 ---------- .../AspNet/AspNetUmbracoApplicationLifetime.cs | 2 +- 8 files changed, 9 insertions(+), 21 deletions(-) rename src/Umbraco.Web.Common/{AspNetCore => Extensions}/UmbracoCoreServiceCollectionExtensions.cs (99%) diff --git a/src/Umbraco.Tests.Integration/RuntimeTests.cs b/src/Umbraco.Tests.Integration/RuntimeTests.cs index 52a4ac1062..2974a01669 100644 --- a/src/Umbraco.Tests.Integration/RuntimeTests.cs +++ b/src/Umbraco.Tests.Integration/RuntimeTests.cs @@ -1,10 +1,7 @@ -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Moq; using NUnit.Framework; -using System.IO; using System.Threading.Tasks; using Microsoft.AspNetCore.Builder; using Umbraco.Core; @@ -16,7 +13,7 @@ using Umbraco.Tests.Integration.Extensions; using Umbraco.Tests.Integration.Implementations; using Umbraco.Tests.Integration.Testing; using Umbraco.Web.BackOffice.AspNetCore; -using Umbraco.Web.Common.AspNetCore; +using Umbraco.Web.Common.Extensions; namespace Umbraco.Tests.Integration { diff --git a/src/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs b/src/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs index 3d94e52860..43504e908a 100644 --- a/src/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs +++ b/src/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Data.Common; using System.IO; using System.Threading.Tasks; using Microsoft.AspNetCore.Builder; @@ -20,8 +19,8 @@ using Umbraco.Core.Strings; using Umbraco.Tests.Common.Builders; using Umbraco.Tests.Integration.Extensions; using Umbraco.Tests.Integration.Implementations; -using Umbraco.Tests.Testing; using Umbraco.Web.BackOffice.AspNetCore; +using Umbraco.Web.Common.Extensions; namespace Umbraco.Tests.Integration.Testing { diff --git a/src/Umbraco.Tests.Integration/Umbraco.Tests.Integration.csproj b/src/Umbraco.Tests.Integration/Umbraco.Tests.Integration.csproj index 320db33568..59b7de4cf4 100644 --- a/src/Umbraco.Tests.Integration/Umbraco.Tests.Integration.csproj +++ b/src/Umbraco.Tests.Integration/Umbraco.Tests.Integration.csproj @@ -4,6 +4,7 @@ Exe netcoreapp3.1 false + 8 diff --git a/src/Umbraco.Web.Common/AspNetCore/AspNetCoreUmbracoApplicationLifetime.cs b/src/Umbraco.Web.Common/AspNetCore/AspNetCoreUmbracoApplicationLifetime.cs index 38d78860b0..c8d8f40a00 100644 --- a/src/Umbraco.Web.Common/AspNetCore/AspNetCoreUmbracoApplicationLifetime.cs +++ b/src/Umbraco.Web.Common/AspNetCore/AspNetCoreUmbracoApplicationLifetime.cs @@ -6,7 +6,7 @@ using Umbraco.Net; namespace Umbraco.Web.Common.AspNetCore { - public class AspNetCoreUmbracoApplicationLifetime : IUmbracoApplicationLifetime, IUmbracoApplicationLifetimeManager + public class AspNetCoreUmbracoApplicationLifetime : IUmbracoApplicationLifetimeManager { private readonly IHttpContextAccessor _httpContextAccessor; private readonly IHostApplicationLifetime _hostApplicationLifetime; diff --git a/src/Umbraco.Web.Common/AspNetCore/UmbracoCoreServiceCollectionExtensions.cs b/src/Umbraco.Web.Common/Extensions/UmbracoCoreServiceCollectionExtensions.cs similarity index 99% rename from src/Umbraco.Web.Common/AspNetCore/UmbracoCoreServiceCollectionExtensions.cs rename to src/Umbraco.Web.Common/Extensions/UmbracoCoreServiceCollectionExtensions.cs index 99a274364b..6f2b31bac1 100644 --- a/src/Umbraco.Web.Common/AspNetCore/UmbracoCoreServiceCollectionExtensions.cs +++ b/src/Umbraco.Web.Common/Extensions/UmbracoCoreServiceCollectionExtensions.cs @@ -17,9 +17,10 @@ using Umbraco.Core.Logging; using Umbraco.Core.Logging.Serilog; using Umbraco.Core.Persistence; using Umbraco.Core.Runtime; +using Umbraco.Web.Common.AspNetCore; using Umbraco.Web.Common.Runtime.Profiler; -namespace Umbraco.Web.Common.AspNetCore +namespace Umbraco.Web.Common.Extensions { public static class UmbracoCoreServiceCollectionExtensions { diff --git a/src/Umbraco.Web.Common/Lifetime/UmbracoRequestLifetime.cs b/src/Umbraco.Web.Common/Lifetime/UmbracoRequestLifetime.cs index 43810a9e66..0d858679a6 100644 --- a/src/Umbraco.Web.Common/Lifetime/UmbracoRequestLifetime.cs +++ b/src/Umbraco.Web.Common/Lifetime/UmbracoRequestLifetime.cs @@ -3,7 +3,7 @@ using Microsoft.AspNetCore.Http; namespace Umbraco.Web.Common.Lifetime { - public class UmbracoRequestLifetime : IUmbracoRequestLifetime, IUmbracoRequestLifetimeManager + public class UmbracoRequestLifetime : IUmbracoRequestLifetimeManager { public event EventHandler RequestStart; public event EventHandler RequestEnd; diff --git a/src/Umbraco.Web.Common/Runtime/Profiler/WebProfiler.cs b/src/Umbraco.Web.Common/Runtime/Profiler/WebProfiler.cs index 431c195d90..bdbc6f164d 100644 --- a/src/Umbraco.Web.Common/Runtime/Profiler/WebProfiler.cs +++ b/src/Umbraco.Web.Common/Runtime/Profiler/WebProfiler.cs @@ -119,15 +119,5 @@ namespace Umbraco.Web.Common.Runtime.Profiler if (bool.TryParse(request.Cookies["UMB-DEBUG"], out var cUmbDebug)) return cUmbDebug; return false; } - - /// - /// Indicates the boot phase. - /// - private enum BootPhase - { - Boot = 0, // boot phase (before the 1st request even begins) - BootRequest = 1, // request boot phase (during the 1st request) - Booted = 2 // done booting - } } } diff --git a/src/Umbraco.Web/AspNet/AspNetUmbracoApplicationLifetime.cs b/src/Umbraco.Web/AspNet/AspNetUmbracoApplicationLifetime.cs index f0ff6e3cad..8c43293ad7 100644 --- a/src/Umbraco.Web/AspNet/AspNetUmbracoApplicationLifetime.cs +++ b/src/Umbraco.Web/AspNet/AspNetUmbracoApplicationLifetime.cs @@ -5,7 +5,7 @@ using Umbraco.Net; namespace Umbraco.Web.AspNet { - public class AspNetUmbracoApplicationLifetime : IUmbracoApplicationLifetime, IUmbracoApplicationLifetimeManager + public class AspNetUmbracoApplicationLifetime : IUmbracoApplicationLifetimeManager { private readonly IHttpContextAccessor _httpContextAccessor; From b9ad6a0b7c386bdeac51c9aabd7bf6a01bb8f1a4 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Tue, 31 Mar 2020 15:02:02 +0200 Subject: [PATCH 68/73] AB#5820 - Cleanup --- src/Umbraco.Core/Net/IUmbracoApplicationLifetime.cs | 2 +- .../AspNetCore/AspNetCoreUmbracoApplicationLifetime.cs | 2 +- .../Lifetime/IUmbracoRequestLifetimeManager.cs | 2 +- src/Umbraco.Web.Common/Lifetime/UmbracoRequestLifetime.cs | 2 +- src/Umbraco.Web.Common/Runtime/AspNetCoreComposer.cs | 1 - .../Runtime/Profiler/WebProfilerComponent.cs | 4 ++-- src/Umbraco.Web/Logging/WebProfilerComposer.cs | 2 +- 7 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/Umbraco.Core/Net/IUmbracoApplicationLifetime.cs b/src/Umbraco.Core/Net/IUmbracoApplicationLifetime.cs index f594e45652..a032720d46 100644 --- a/src/Umbraco.Core/Net/IUmbracoApplicationLifetime.cs +++ b/src/Umbraco.Core/Net/IUmbracoApplicationLifetime.cs @@ -18,7 +18,7 @@ namespace Umbraco.Net } - public interface IUmbracoApplicationLifetimeManager: IUmbracoApplicationLifetime + public interface IUmbracoApplicationLifetimeManager { void InvokeApplicationInit(); } diff --git a/src/Umbraco.Web.Common/AspNetCore/AspNetCoreUmbracoApplicationLifetime.cs b/src/Umbraco.Web.Common/AspNetCore/AspNetCoreUmbracoApplicationLifetime.cs index c8d8f40a00..38d78860b0 100644 --- a/src/Umbraco.Web.Common/AspNetCore/AspNetCoreUmbracoApplicationLifetime.cs +++ b/src/Umbraco.Web.Common/AspNetCore/AspNetCoreUmbracoApplicationLifetime.cs @@ -6,7 +6,7 @@ using Umbraco.Net; namespace Umbraco.Web.Common.AspNetCore { - public class AspNetCoreUmbracoApplicationLifetime : IUmbracoApplicationLifetimeManager + public class AspNetCoreUmbracoApplicationLifetime : IUmbracoApplicationLifetime, IUmbracoApplicationLifetimeManager { private readonly IHttpContextAccessor _httpContextAccessor; private readonly IHostApplicationLifetime _hostApplicationLifetime; diff --git a/src/Umbraco.Web.Common/Lifetime/IUmbracoRequestLifetimeManager.cs b/src/Umbraco.Web.Common/Lifetime/IUmbracoRequestLifetimeManager.cs index 7ee8fac1e9..e4c671c2d3 100644 --- a/src/Umbraco.Web.Common/Lifetime/IUmbracoRequestLifetimeManager.cs +++ b/src/Umbraco.Web.Common/Lifetime/IUmbracoRequestLifetimeManager.cs @@ -2,7 +2,7 @@ namespace Umbraco.Web.Common.Lifetime { - public interface IUmbracoRequestLifetimeManager : IUmbracoRequestLifetime + public interface IUmbracoRequestLifetimeManager { void InitRequest(HttpContext context); void EndRequest(HttpContext context); diff --git a/src/Umbraco.Web.Common/Lifetime/UmbracoRequestLifetime.cs b/src/Umbraco.Web.Common/Lifetime/UmbracoRequestLifetime.cs index 0d858679a6..43810a9e66 100644 --- a/src/Umbraco.Web.Common/Lifetime/UmbracoRequestLifetime.cs +++ b/src/Umbraco.Web.Common/Lifetime/UmbracoRequestLifetime.cs @@ -3,7 +3,7 @@ using Microsoft.AspNetCore.Http; namespace Umbraco.Web.Common.Lifetime { - public class UmbracoRequestLifetime : IUmbracoRequestLifetimeManager + public class UmbracoRequestLifetime : IUmbracoRequestLifetime, IUmbracoRequestLifetimeManager { public event EventHandler RequestStart; public event EventHandler RequestEnd; diff --git a/src/Umbraco.Web.Common/Runtime/AspNetCoreComposer.cs b/src/Umbraco.Web.Common/Runtime/AspNetCoreComposer.cs index a0b2fb9dd8..6c5aeb1b56 100644 --- a/src/Umbraco.Web.Common/Runtime/AspNetCoreComposer.cs +++ b/src/Umbraco.Web.Common/Runtime/AspNetCoreComposer.cs @@ -34,7 +34,6 @@ namespace Umbraco.Web.Common.Runtime composition.RegisterUnique(); composition.RegisterUnique(factory => factory.GetInstance()); composition.RegisterUnique(factory => factory.GetInstance()); - composition.RegisterUnique(); } } } diff --git a/src/Umbraco.Web.Common/Runtime/Profiler/WebProfilerComponent.cs b/src/Umbraco.Web.Common/Runtime/Profiler/WebProfilerComponent.cs index 7f0b5071c5..a36753e634 100644 --- a/src/Umbraco.Web.Common/Runtime/Profiler/WebProfilerComponent.cs +++ b/src/Umbraco.Web.Common/Runtime/Profiler/WebProfilerComponent.cs @@ -11,11 +11,11 @@ namespace Umbraco.Web.Common.Runtime.Profiler { private readonly bool _profile; private readonly WebProfiler _profiler; - private readonly IUmbracoApplicationLifetimeManager _umbracoApplicationLifetime; + private readonly IUmbracoApplicationLifetime _umbracoApplicationLifetime; private readonly IUmbracoRequestLifetime _umbracoRequestLifetime; public WebProfilerComponent(IProfiler profiler, ILogger logger, IUmbracoRequestLifetime umbracoRequestLifetime, - IUmbracoApplicationLifetimeManager umbracoApplicationLifetime) + IUmbracoApplicationLifetime umbracoApplicationLifetime) { _umbracoRequestLifetime = umbracoRequestLifetime; _umbracoApplicationLifetime = umbracoApplicationLifetime; diff --git a/src/Umbraco.Web/Logging/WebProfilerComposer.cs b/src/Umbraco.Web/Logging/WebProfilerComposer.cs index 5834dd9dd4..283c519b44 100644 --- a/src/Umbraco.Web/Logging/WebProfilerComposer.cs +++ b/src/Umbraco.Web/Logging/WebProfilerComposer.cs @@ -1,4 +1,4 @@ -using Umbraco.Core.Composing; + using Umbraco.Core.Composing; namespace Umbraco.Web.Logging { From 919861bf361115ca2d473fce7ef9787ee6937615 Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Tue, 31 Mar 2020 17:37:34 +0200 Subject: [PATCH 69/73] Separated member and member group builder and model unit tests, and other minor updates from PR review. --- .../Builders/PropertyGroupBuilder.cs | 2 +- .../Models/DataTypeTests.cs | 19 +-- .../Models/MemberGroupTests.cs | 45 +---- .../Models/MemberTests.cs | 141 +++++----------- .../Builders/DataTypeBuilderTests.cs | 27 +++ .../Builders/MemberBuilderTests.cs | 158 ++++++++++++++++++ .../Builders/MemberGroupBuilderTests.cs | 53 ++++++ 7 files changed, 288 insertions(+), 157 deletions(-) create mode 100644 src/Umbraco.Tests.UnitTests/Umbraco.Tests.Common/Builders/DataTypeBuilderTests.cs create mode 100644 src/Umbraco.Tests.UnitTests/Umbraco.Tests.Common/Builders/MemberBuilderTests.cs create mode 100644 src/Umbraco.Tests.UnitTests/Umbraco.Tests.Common/Builders/MemberGroupBuilderTests.cs diff --git a/src/Umbraco.Tests.Common/Builders/PropertyGroupBuilder.cs b/src/Umbraco.Tests.Common/Builders/PropertyGroupBuilder.cs index 4b349a7e8a..5f6fe12dff 100644 --- a/src/Umbraco.Tests.Common/Builders/PropertyGroupBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/PropertyGroupBuilder.cs @@ -7,7 +7,7 @@ using Umbraco.Tests.Common.Builders.Interfaces; namespace Umbraco.Tests.Common.Builders { public class PropertyGroupBuilder - : ChildBuilderBase, // TODO: likely want to genericise this, so can use for document and media types too. + : ChildBuilderBase, // TODO: likely want to generalise this, so can use for document and media types too. IWithNameBuilder, IWithSortOrderBuilder { diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Models/DataTypeTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Models/DataTypeTests.cs index 53431bfdea..ba1e500d5a 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Models/DataTypeTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Models/DataTypeTests.cs @@ -11,26 +11,11 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Models { private readonly DataTypeBuilder _builder = new DataTypeBuilder(); - private const int _testId = 3123; - - [Test] - public void Is_Built_Correctly() - { - // Arrange - // Act - var dtd = _builder - .WithId(_testId) - .Build(); - - // Assert - Assert.AreEqual(_testId, dtd.Id); - } - [Test] public void Can_Deep_Clone() { - var dtd = _builder - .WithId(_testId) + var dtd = _builder + .WithId(3123) .Build(); var clone = (DataType) dtd.DeepClone(); diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Models/MemberGroupTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Models/MemberGroupTests.cs index a6718660dd..91ccd56b9e 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Models/MemberGroupTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Models/MemberGroupTests.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.Diagnostics; using Newtonsoft.Json; using NUnit.Framework; @@ -14,34 +13,6 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Models { private readonly MemberGroupBuilder _builder = new MemberGroupBuilder(); - private const int _testId = 6; - private const string _testName = "Test Group"; - private const int _testCreatorId = 4; - private readonly Guid _testKey = Guid.NewGuid(); - private readonly DateTime _testCreateDate = DateTime.Now.AddHours(-1); - private readonly DateTime _testUpdateDate = DateTime.Now; - private readonly KeyValuePair _testAdditionalData1 = new KeyValuePair("test1", 123); - private readonly KeyValuePair _testAdditionalData2 = new KeyValuePair("test2", "hello"); - - [Test] - public void Is_Built_Correctly() - { - // Arrange - // Act - var group = BuildMemberGroup(); - - // Assert - Assert.AreEqual(_testId, group.Id); - Assert.AreEqual(_testKey, group.Key); - Assert.AreEqual(_testName, group.Name); - Assert.AreEqual(_testCreateDate, group.CreateDate); - Assert.AreEqual(_testUpdateDate, group.UpdateDate); - Assert.AreEqual(_testCreatorId, group.CreatorId); - Assert.AreEqual(2, group.AdditionalData.Count); - Assert.AreEqual(_testAdditionalData1.Value, group.AdditionalData[_testAdditionalData1.Key]); - Assert.AreEqual(_testAdditionalData2.Value, group.AdditionalData[_testAdditionalData2.Key]); - } - [Test] public void Can_Deep_Clone() { @@ -81,15 +52,15 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Models private MemberGroup BuildMemberGroup() { return _builder - .WithId(_testId) - .WithKey(_testKey) - .WithName(_testName) - .WithCreatorId(_testCreatorId) - .WithCreateDate(_testCreateDate) - .WithUpdateDate(_testUpdateDate) + .WithId(6) + .WithKey(Guid.NewGuid()) + .WithName("Test Group") + .WithCreatorId(4) + .WithCreateDate(DateTime.Now) + .WithUpdateDate(DateTime.Now) .AddAdditionalData() - .WithKeyValue(_testAdditionalData1.Key, _testAdditionalData1.Value) - .WithKeyValue(_testAdditionalData2.Key, _testAdditionalData2.Value) + .WithKeyValue("test1", 123) + .WithKeyValue("test2", "hello") .Done() .Build(); } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Models/MemberTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Models/MemberTests.cs index 9420d03c8f..1ac255db39 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Models/MemberTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Models/MemberTests.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.Diagnostics; using System.Linq; using Newtonsoft.Json; @@ -16,65 +15,6 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Models { private readonly MemberBuilder _builder = new MemberBuilder(); - private const int _testMemberTypeId = 99; - private const string _testMemberTypeAlias = "memberType"; - private const string _testMemberTypeName = "Member Type"; - private const string _testMemberTypePropertyGroupName = "Content"; - private const int _testId = 6; - private const string _testName = "Fred"; - private const string _testUsername = "fred"; - private const string _testRawPasswordValue = "raw pass"; - private const string _testEmail = "email@email.com"; - private const int _testCreatorId = 22; - private const int _testLevel = 3; - private const string _testPath = "-1, 4, 10"; - private const bool _testIsApproved = true; - private const bool _testIsLockedOut = true; - private const int _testSortOrder = 5; - private const bool _testTrashed = false; - private readonly Guid _testKey = Guid.NewGuid(); - private readonly DateTime _testCreateDate = DateTime.Now.AddHours(-1); - private readonly DateTime _testUpdateDate = DateTime.Now; - private readonly DateTime _testLastLockoutDate = DateTime.Now.AddHours(-2); - private readonly DateTime _testLastLoginDate = DateTime.Now.AddHours(-3); - private readonly DateTime _testLastPasswordChangeDate = DateTime.Now.AddHours(-4); - private readonly (string alias, string name, string description, int sortOrder, int dataTypeId) _testPropertyType1 = ("title", "Title", string.Empty, 1, -88); - private readonly (string alias, string name, string description, int sortOrder, int dataTypeId) _testPropertyType2 = ("bodyText", "Body Text", string.Empty, 2, -87); - private readonly (string alias, string name, string description, int sortOrder, int dataTypeId) _testPropertyType3 = ("author", "Author", "Writer of the article", 1, -88); - private readonly string[] _testGroups = new string[] { "group1", "group2" }; - private readonly KeyValuePair _testPropertyData1 = new KeyValuePair("title", "Name member"); - private readonly KeyValuePair _testPropertyData2 = new KeyValuePair("bodyText", "This is a subpage"); - private readonly KeyValuePair _testPropertyData3 = new KeyValuePair("author", "John Doe"); - private readonly KeyValuePair _testAdditionalData1 = new KeyValuePair("test1", 123); - private readonly KeyValuePair _testAdditionalData2 = new KeyValuePair("test2", "hello"); - - [Test] - public void Is_Built_Correctly() - { - // Arrange - // Act - var member = BuildMember(); - - // Assert - Assert.AreEqual(_testMemberTypeId, member.ContentTypeId); - Assert.AreEqual(_testMemberTypeAlias, member.ContentType.Alias); - Assert.AreEqual(_testMemberTypeName, member.ContentType.Name); - Assert.AreEqual(_testId, member.Id); - Assert.AreEqual(_testKey, member.Key); - Assert.AreEqual(_testName, member.Name); - Assert.AreEqual(_testCreateDate, member.CreateDate); - Assert.AreEqual(_testUpdateDate, member.UpdateDate); - Assert.AreEqual(_testCreatorId, member.CreatorId); - Assert.AreEqual(_testGroups, member.Groups.ToArray()); - Assert.AreEqual(10, member.Properties.Count); // 7 from membership properties group, 3 custom - Assert.AreEqual(_testPropertyData1.Value, member.GetValue(_testPropertyData1.Key)); - Assert.AreEqual(_testPropertyData2.Value, member.GetValue(_testPropertyData2.Key)); - Assert.AreEqual(_testPropertyData3.Value, member.GetValue(_testPropertyData3.Key)); - Assert.AreEqual(2, member.AdditionalData.Count); - Assert.AreEqual(_testAdditionalData1.Value, member.AdditionalData[_testAdditionalData1.Key]); - Assert.AreEqual(_testAdditionalData2.Value, member.AdditionalData[_testAdditionalData2.Key]); - } - [Test] public void Can_Deep_Clone() { @@ -141,72 +81,69 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Models { return _builder .AddMemberType() - .WithId(_testMemberTypeId) - .WithAlias(_testMemberTypeAlias) - .WithName(_testMemberTypeName) + .WithId(99) + .WithAlias("memberType") + .WithName("Member Type") .WithMembershipPropertyGroup() .AddPropertyGroup() - .WithName(_testMemberTypePropertyGroupName) + .WithName("Content") .WithSortOrder(1) .AddPropertyType() .WithPropertyEditorAlias(Constants.PropertyEditors.Aliases.TextBox) .WithValueStorageType(ValueStorageType.Nvarchar) - .WithAlias(_testPropertyType1.alias) - .WithName(_testPropertyType1.name) - .WithSortOrder(_testPropertyType1.sortOrder) - .WithDataTypeId(_testPropertyType1.dataTypeId) + .WithAlias("title") + .WithName("Title") + .WithSortOrder(1) + .WithDataTypeId(-88) .Done() .AddPropertyType() .WithPropertyEditorAlias(Constants.PropertyEditors.Aliases.TextBox) .WithValueStorageType(ValueStorageType.Ntext) - .WithAlias(_testPropertyType2.alias) - .WithName(_testPropertyType2.name) - .WithSortOrder(_testPropertyType2.sortOrder) - .WithDataTypeId(_testPropertyType2.dataTypeId) + .WithAlias("bodyText") + .WithName("Body text") + .WithSortOrder(2) + .WithDataTypeId(-87) .Done() .AddPropertyType() .WithPropertyEditorAlias(Constants.PropertyEditors.Aliases.TextBox) .WithValueStorageType(ValueStorageType.Nvarchar) - .WithAlias(_testPropertyType3.alias) - .WithName(_testPropertyType3.name) - .WithDescription(_testPropertyType3.description) - .WithSortOrder(_testPropertyType3.sortOrder) - .WithDataTypeId(_testPropertyType3.dataTypeId) + .WithAlias("author") + .WithName("Author") + .WithDescription("Name of the author") + .WithSortOrder(3) + .WithDataTypeId(-88) .Done() .Done() .Done() - .WithId(_testId) - .WithKey(_testKey) - .WithName(_testName) - .WithUserName(_testUsername) - .WithRawPasswordValue(_testRawPasswordValue) - .WithEmail(_testEmail) - .WithCreatorId(_testCreatorId) - .WithCreateDate(_testCreateDate) - .WithUpdateDate(_testUpdateDate) + .WithId(10) + .WithKey(Guid.NewGuid()) + .WithName("Fred") + .WithUserName("fred") + .WithRawPasswordValue("raw pass") + .WithEmail("email@email.com") + .WithCreatorId(22) + .WithCreateDate(DateTime.Now) + .WithUpdateDate(DateTime.Now) .WithFailedPasswordAttempts(22) - .WithLevel(_testLevel) - .WithPath(_testPath) - .WithIsApproved(_testIsApproved) - .WithIsLockedOut(_testIsLockedOut) - .WithLastLockoutDate(_testLastLockoutDate) - .WithLastLoginDate(_testLastLoginDate) - .WithLastPasswordChangeDate(_testLastPasswordChangeDate) - .WithSortOrder(_testSortOrder) - .WithTrashed(_testTrashed) + .WithLevel(3) + .WithPath("-1, 4, 10") + .WithIsApproved(true) + .WithIsLockedOut(true) + .WithSortOrder(5) + .WithTrashed(false) .AddMemberGroups() - .WithValue(_testGroups[0]) - .WithValue(_testGroups[1]) + .WithValue("Group 1") + .WithValue("Group 2") .Done() .AddAdditionalData() - .WithKeyValue(_testAdditionalData1.Key, _testAdditionalData1.Value) - .WithKeyValue(_testAdditionalData2.Key, _testAdditionalData2.Value) + .WithKeyValue("test1", 123) + .WithKeyValue("test2", "hello") .Done() .WithPropertyIdsIncrementingFrom(200) .AddPropertyData() - .WithKeyValue(_testPropertyData1.Key, _testPropertyData1.Value) - .WithKeyValue(_testPropertyData2.Key, _testPropertyData2.Value) - .WithKeyValue(_testPropertyData3.Key, _testPropertyData3.Value) + .WithKeyValue("title", "Name member") + .WithKeyValue("bodyText", "This is a subpage") + .WithKeyValue("author", "John Doe") .Done() .Build(); } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Tests.Common/Builders/DataTypeBuilderTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Tests.Common/Builders/DataTypeBuilderTests.cs new file mode 100644 index 0000000000..ce47c69763 --- /dev/null +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Tests.Common/Builders/DataTypeBuilderTests.cs @@ -0,0 +1,27 @@ +using NUnit.Framework; +using Umbraco.Tests.Common.Builders; +using Umbraco.Tests.Common.Builders.Extensions; + +namespace Umbraco.Tests.UnitTests.Umbraco.Tests.Common.Builders +{ + [TestFixture] + public class DataTypeBuilderTests + { + [Test] + public void Is_Built_Correctly() + { + // Arrange + const int testId = 3123; + + var builder = new DataTypeBuilder(); + + // Act + var dtd = builder + .WithId(testId) + .Build(); + + // Assert + Assert.AreEqual(testId, dtd.Id); + } + } +} diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Tests.Common/Builders/MemberBuilderTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Tests.Common/Builders/MemberBuilderTests.cs new file mode 100644 index 0000000000..e1df867642 --- /dev/null +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Tests.Common/Builders/MemberBuilderTests.cs @@ -0,0 +1,158 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using Umbraco.Core; +using Umbraco.Core.Models; +using Umbraco.Tests.Common.Builders; +using Umbraco.Tests.Common.Builders.Extensions; + +namespace Umbraco.Tests.UnitTests.Umbraco.Tests.Common.Builders +{ + [TestFixture] + public class MemberBuilderTests + { + private class PropertyTypeDetail + { + public string Alias { get; set; } + + public string Name { get; set; } + + public string Description { get; set; } = string.Empty; + + public int SortOrder { get; set; } + + public int DataTypeId { get; set; } + } + + [Test] + public void Is_Built_Correctly() + { + // Arrange + const int testMemberTypeId = 99; + const string testMemberTypeAlias = "memberType"; + const string testMemberTypeName = "Member Type"; + const string testMemberTypePropertyGroupName = "Content"; + const int testId = 10; + const string testName = "Fred"; + const string testUsername = "fred"; + const string testRawPasswordValue = "raw pass"; + const string testEmail = "email@email.com"; + const int testCreatorId = 22; + const int testLevel = 3; + const string testPath = "-1, 4, 10"; + const bool testIsApproved = true; + const bool testIsLockedOut = true; + const int testSortOrder = 5; + const bool testTrashed = false; + var testKey = Guid.NewGuid(); + var testCreateDate = DateTime.Now.AddHours(-1); + var testUpdateDate = DateTime.Now; + var testLastLockoutDate = DateTime.Now.AddHours(-2); + var testLastLoginDate = DateTime.Now.AddHours(-3); + var testLastPasswordChangeDate = DateTime.Now.AddHours(-4); + var testPropertyType1 = new PropertyTypeDetail { Alias = "title", Name = "Title", SortOrder = 1, DataTypeId = -88 }; + var testPropertyType2 = new PropertyTypeDetail { Alias = "bodyText", Name = "Body Text", SortOrder = 2, DataTypeId = -87 }; + var testPropertyType3 = new PropertyTypeDetail { Alias = "author", Name = "Author", Description = "Writer of the article", SortOrder = 1, DataTypeId = -88 }; + var testGroups = new string[] { "group1", "group2" }; + var testPropertyData1 = new KeyValuePair("title", "Name member"); + var testPropertyData2 = new KeyValuePair("bodyText", "This is a subpage"); + var testPropertyData3 = new KeyValuePair("author", "John Doe"); + var testAdditionalData1 = new KeyValuePair("test1", 123); + var testAdditionalData2 = new KeyValuePair("test2", "hello"); + + var builder = new MemberBuilder(); + + // Act + var member = builder + .AddMemberType() + .WithId(testMemberTypeId) + .WithAlias(testMemberTypeAlias) + .WithName(testMemberTypeName) + .WithMembershipPropertyGroup() + .AddPropertyGroup() + .WithName(testMemberTypePropertyGroupName) + .WithSortOrder(1) + .AddPropertyType() + .WithPropertyEditorAlias(Constants.PropertyEditors.Aliases.TextBox) + .WithValueStorageType(ValueStorageType.Nvarchar) + .WithAlias(testPropertyType1.Alias) + .WithName(testPropertyType1.Name) + .WithSortOrder(testPropertyType1.SortOrder) + .WithDataTypeId(testPropertyType1.DataTypeId) + .Done() + .AddPropertyType() + .WithPropertyEditorAlias(Constants.PropertyEditors.Aliases.TextBox) + .WithValueStorageType(ValueStorageType.Ntext) + .WithAlias(testPropertyType2.Alias) + .WithName(testPropertyType2.Name) + .WithSortOrder(testPropertyType2.SortOrder) + .WithDataTypeId(testPropertyType2.DataTypeId) + .Done() + .AddPropertyType() + .WithPropertyEditorAlias(Constants.PropertyEditors.Aliases.TextBox) + .WithValueStorageType(ValueStorageType.Nvarchar) + .WithAlias(testPropertyType3.Alias) + .WithName(testPropertyType3.Name) + .WithDescription(testPropertyType3.Description) + .WithSortOrder(testPropertyType3.SortOrder) + .WithDataTypeId(testPropertyType3.DataTypeId) + .Done() + .Done() + .Done() + .WithId(testId) + .WithKey(testKey) + .WithName(testName) + .WithUserName(testUsername) + .WithRawPasswordValue(testRawPasswordValue) + .WithEmail(testEmail) + .WithCreatorId(testCreatorId) + .WithCreateDate(testCreateDate) + .WithUpdateDate(testUpdateDate) + .WithFailedPasswordAttempts(22) + .WithLevel(testLevel) + .WithPath(testPath) + .WithIsApproved(testIsApproved) + .WithIsLockedOut(testIsLockedOut) + .WithLastLockoutDate(testLastLockoutDate) + .WithLastLoginDate(testLastLoginDate) + .WithLastPasswordChangeDate(testLastPasswordChangeDate) + .WithSortOrder(testSortOrder) + .WithTrashed(testTrashed) + .AddMemberGroups() + .WithValue(testGroups[0]) + .WithValue(testGroups[1]) + .Done() + .AddAdditionalData() + .WithKeyValue(testAdditionalData1.Key, testAdditionalData1.Value) + .WithKeyValue(testAdditionalData2.Key, testAdditionalData2.Value) + .Done() + .WithPropertyIdsIncrementingFrom(200) + .AddPropertyData() + .WithKeyValue(testPropertyData1.Key, testPropertyData1.Value) + .WithKeyValue(testPropertyData2.Key, testPropertyData2.Value) + .WithKeyValue(testPropertyData3.Key, testPropertyData3.Value) + .Done() + .Build(); + + // Assert + Assert.AreEqual(testMemberTypeId, member.ContentTypeId); + Assert.AreEqual(testMemberTypeAlias, member.ContentType.Alias); + Assert.AreEqual(testMemberTypeName, member.ContentType.Name); + Assert.AreEqual(testId, member.Id); + Assert.AreEqual(testKey, member.Key); + Assert.AreEqual(testName, member.Name); + Assert.AreEqual(testCreateDate, member.CreateDate); + Assert.AreEqual(testUpdateDate, member.UpdateDate); + Assert.AreEqual(testCreatorId, member.CreatorId); + Assert.AreEqual(testGroups, member.Groups.ToArray()); + Assert.AreEqual(10, member.Properties.Count); // 7 from membership properties group, 3 custom + Assert.AreEqual(testPropertyData1.Value, member.GetValue(testPropertyData1.Key)); + Assert.AreEqual(testPropertyData2.Value, member.GetValue(testPropertyData2.Key)); + Assert.AreEqual(testPropertyData3.Value, member.GetValue(testPropertyData3.Key)); + Assert.AreEqual(2, member.AdditionalData.Count); + Assert.AreEqual(testAdditionalData1.Value, member.AdditionalData[testAdditionalData1.Key]); + Assert.AreEqual(testAdditionalData2.Value, member.AdditionalData[testAdditionalData2.Key]); + } + } +} diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Tests.Common/Builders/MemberGroupBuilderTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Tests.Common/Builders/MemberGroupBuilderTests.cs new file mode 100644 index 0000000000..4cb47de052 --- /dev/null +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Tests.Common/Builders/MemberGroupBuilderTests.cs @@ -0,0 +1,53 @@ +using System; +using System.Collections.Generic; +using NUnit.Framework; +using Umbraco.Tests.Common.Builders; +using Umbraco.Tests.Common.Builders.Extensions; + +namespace Umbraco.Tests.UnitTests.Umbraco.Tests.Common.Builders +{ + [TestFixture] + public class MemberGroupBuilderTests + { + [Test] + public void Is_Built_Correctly() + { + // Arrange + const int testId = 6; + const string testName = "Test Group"; + const int testCreatorId = 4; + var testKey = Guid.NewGuid(); + var testCreateDate = DateTime.Now.AddHours(-1); + var testUpdateDate = DateTime.Now; + var testAdditionalData1 = new KeyValuePair("test1", 123); + var testAdditionalData2 = new KeyValuePair("test2", "hello"); + + var builder = new MemberGroupBuilder(); + + // Act + var group = builder + .WithId(testId) + .WithKey(testKey) + .WithName(testName) + .WithCreatorId(testCreatorId) + .WithCreateDate(testCreateDate) + .WithUpdateDate(testUpdateDate) + .AddAdditionalData() + .WithKeyValue(testAdditionalData1.Key, testAdditionalData1.Value) + .WithKeyValue(testAdditionalData2.Key, testAdditionalData2.Value) + .Done() + .Build(); + + // Assert + Assert.AreEqual(testId, group.Id); + Assert.AreEqual(testKey, group.Key); + Assert.AreEqual(testName, group.Name); + Assert.AreEqual(testCreateDate, group.CreateDate); + Assert.AreEqual(testUpdateDate, group.UpdateDate); + Assert.AreEqual(testCreatorId, group.CreatorId); + Assert.AreEqual(3, group.AdditionalData.Count); // previousName is added as part of the MemberGroup construction, plus the 2 we've added. + Assert.AreEqual(testAdditionalData1.Value, group.AdditionalData[testAdditionalData1.Key]); + Assert.AreEqual(testAdditionalData2.Value, group.AdditionalData[testAdditionalData2.Key]); + } + } +} From 9e38ed045fc708b1cf51ca0875f730111152c9dc Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Wed, 1 Apr 2020 08:18:56 +0200 Subject: [PATCH 70/73] Aligned builders --- .../Models/GlobalSettings.cs | 2 +- src/Umbraco.Core/Constants-Configuration.cs | 1 + .../Interfaces/IWithApprovedBuilder.cs | 7 ++ .../Builders/UserBuilder.cs | 94 ++++++++++++------- .../ApplicationBuilderExtensions.cs | 2 +- .../UmbracoCoreServiceCollectionExtensions.cs | 2 - 6 files changed, 68 insertions(+), 40 deletions(-) create mode 100644 src/Umbraco.Tests.Common/Builders/Interfaces/IWithApprovedBuilder.cs diff --git a/src/Umbraco.Configuration/Models/GlobalSettings.cs b/src/Umbraco.Configuration/Models/GlobalSettings.cs index ed1b4e58a2..68d293d104 100644 --- a/src/Umbraco.Configuration/Models/GlobalSettings.cs +++ b/src/Umbraco.Configuration/Models/GlobalSettings.cs @@ -12,7 +12,7 @@ namespace Umbraco.Configuration.Models /// internal class GlobalSettings : IGlobalSettings { - public const string Prefix = Constants.Configuration.ConfigPrefix + "Global:"; + private const string Prefix = Constants.Configuration.ConfigGlobalPrefix; internal const string StaticReservedPaths = "~/app_plugins/,~/install/,~/mini-profiler-resources/,"; //must end with a comma! diff --git a/src/Umbraco.Core/Constants-Configuration.cs b/src/Umbraco.Core/Constants-Configuration.cs index c45229d918..a34ad8294f 100644 --- a/src/Umbraco.Core/Constants-Configuration.cs +++ b/src/Umbraco.Core/Constants-Configuration.cs @@ -12,6 +12,7 @@ /// public const string ConfigPrefix = "Umbraco:CMS:"; public const string ConfigSecurityPrefix = ConfigPrefix+"Security:"; + public const string ConfigGlobalPrefix = ConfigPrefix + "Global:"; public const string ConfigModelsBuilderPrefix = ConfigPrefix+"ModelsBuilder:"; } } diff --git a/src/Umbraco.Tests.Common/Builders/Interfaces/IWithApprovedBuilder.cs b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithApprovedBuilder.cs new file mode 100644 index 0000000000..48d41e7a84 --- /dev/null +++ b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithApprovedBuilder.cs @@ -0,0 +1,7 @@ +namespace Umbraco.Tests.Common.Builders.Interfaces +{ + public interface IWithApprovedBuilder + { + bool? Approved { get; set; } + } +} diff --git a/src/Umbraco.Tests.Common/Builders/UserBuilder.cs b/src/Umbraco.Tests.Common/Builders/UserBuilder.cs index 07ab8ef3f7..dd11156bf0 100644 --- a/src/Umbraco.Tests.Common/Builders/UserBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/UserBuilder.cs @@ -1,16 +1,21 @@ -using System; -using System.Linq; -using Moq; -using Umbraco.Core; -using Umbraco.Core.Configuration; using Umbraco.Core.Models.Membership; using Umbraco.Tests.Common.Builders.Interfaces; namespace Umbraco.Tests.Common.Builders { - public class UserBuilder - : BuilderBase, - IWithIdBuilder + + public class UserBuilder : UserBuilder + { + public UserBuilder() : base(null) + { + } + } + + public class UserBuilder + : ChildBuilderBase, + IWithIdBuilder, + IWithNameBuilder, + IWithApprovedBuilder { private int? _id; private string _language; @@ -22,61 +27,57 @@ namespace Umbraco.Tests.Common.Builders private string _username; private string _defaultLang; private string _suffix = string.Empty; + private GlobalSettingsBuilder> _globalSettingsBuilder; - public UserBuilder WithDefaultUILanguage(string defaultLang) + + public UserBuilder(TParent parentBuilder) : base(parentBuilder) + { + _globalSettingsBuilder = new GlobalSettingsBuilder>(this); + } + + public GlobalSettingsBuilder> AddGlobalSettings() => _globalSettingsBuilder; + public UserBuilder WithDefaultUILanguage(string defaultLang) { _defaultLang = defaultLang; return this; } - public UserBuilder WithLanguage(string language) + public UserBuilder WithLanguage(string language) { _language = language; return this; } - public UserBuilder WithApproved(bool approved) - { - _approved = approved; - return this; - } - - public UserBuilder WithRawPassword(string rawPassword) + public UserBuilder WithRawPassword(string rawPassword) { _rawPassword = rawPassword; return this; } - public UserBuilder WithEmail(string email) + public UserBuilder WithEmail(string email) { _email = email; return this; } - public UserBuilder WithUsername(string username) + public UserBuilder WithUsername(string username) { _username = username; return this; } - public UserBuilder WithLockedOut(bool isLockedOut) + public UserBuilder WithLockedOut(bool isLockedOut) { _isLockedOut = isLockedOut; return this; } - public UserBuilder WithName(string name) - { - _name = name; - return this; - } - /// /// Will suffix the name, email and username for testing /// /// /// - public UserBuilder WithSuffix(string suffix) + public UserBuilder WithSuffix(string suffix) { _suffix = suffix; return this; @@ -84,16 +85,25 @@ namespace Umbraco.Tests.Common.Builders public override User Build() { - var globalSettings = Mock.Of(x => x.DefaultUILanguage == (_defaultLang ?? "en-US")); - return new User(globalSettings, - _name ?? "TestUser" + _suffix, - _email ?? "test" + _suffix + "@test.com", - _username ?? "TestUser" + _suffix, - _rawPassword ?? "abcdefghijklmnopqrstuvwxyz") + var globalSettings = _globalSettingsBuilder.Build(); + var name = _name ?? "TestUser" + _suffix; + var email = _email ?? "test" + _suffix + "@test.com"; + var username = _username ?? "TestUser" + _suffix; + var rawPassword = _rawPassword ?? "abcdefghijklmnopqrstuvwxyz"; + var language = _language ?? globalSettings.DefaultUILanguage; + var isLockedOut = _isLockedOut ?? false; + var approved = _approved ?? true; + + return new User( + globalSettings, + name, + email, + username, + rawPassword) { - Language = _language ?? _defaultLang ?? "en-US", - IsLockedOut = _isLockedOut ?? false, - IsApproved = _approved ?? true + Language = language, + IsLockedOut = isLockedOut, + IsApproved = approved }; } @@ -102,5 +112,17 @@ namespace Umbraco.Tests.Common.Builders get => _id; set => _id = value; } + + string IWithNameBuilder.Name + { + get => _name; + set => _name = value; + } + + bool? IWithApprovedBuilder.Approved + { + get => _approved; + set => _approved = value; + } } } diff --git a/src/Umbraco.Tests.Integration/Extensions/ApplicationBuilderExtensions.cs b/src/Umbraco.Tests.Integration/Extensions/ApplicationBuilderExtensions.cs index 553aa4fe85..a162845200 100644 --- a/src/Umbraco.Tests.Integration/Extensions/ApplicationBuilderExtensions.cs +++ b/src/Umbraco.Tests.Integration/Extensions/ApplicationBuilderExtensions.cs @@ -75,7 +75,7 @@ namespace Umbraco.Tests.Integration.Extensions // dynamically change the config status var umbVersion = app.ApplicationServices.GetRequiredService(); var config = app.ApplicationServices.GetRequiredService(); - config[GlobalSettings.Prefix + "ConfigurationStatus"] = umbVersion.SemanticVersion.ToString(); + config[Constants.Configuration.ConfigGlobalPrefix + "ConfigurationStatus"] = umbVersion.SemanticVersion.ToString(); // re-run the runtime level check var profilingLogger = app.ApplicationServices.GetRequiredService(); diff --git a/src/Umbraco.Web.Common/Extensions/UmbracoCoreServiceCollectionExtensions.cs b/src/Umbraco.Web.Common/Extensions/UmbracoCoreServiceCollectionExtensions.cs index 6f2b31bac1..a0dd1f8e63 100644 --- a/src/Umbraco.Web.Common/Extensions/UmbracoCoreServiceCollectionExtensions.cs +++ b/src/Umbraco.Web.Common/Extensions/UmbracoCoreServiceCollectionExtensions.cs @@ -155,8 +155,6 @@ namespace Umbraco.Web.Common.Extensions backOfficeInfo = new AspNetCoreBackOfficeInfo(globalSettings); profiler = GetWebProfiler(hostingEnvironment, httpContextAccessor); - - Current.Initialize(logger, configs, ioHelper, hostingEnvironment, backOfficeInfo, profiler); } private static IProfiler GetWebProfiler(Umbraco.Core.Hosting.IHostingEnvironment hostingEnvironment, IHttpContextAccessor httpContextAccessor) From 6012d6749e00ed75b03b754515557316516d9403 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Wed, 1 Apr 2020 11:31:21 +0200 Subject: [PATCH 71/73] Fix views --- .../Umbraco/PartialViewMacros/Templates/Gallery.cshtml | 2 +- src/Umbraco.Web.UI/Views/Partials/Grid/Editors/Media.cshtml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/Gallery.cshtml b/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/Gallery.cshtml index 9462b3b638..8388c7a90d 100755 --- a/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/Gallery.cshtml +++ b/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/Gallery.cshtml @@ -1,7 +1,7 @@ @using Umbraco.Core.Models.PublishedContent @using Umbraco.Web @using Umbraco.Core -@using Umbraco.Core.Models +@using Umbraco.Core.Media @using Umbraco.Web.Composing @inherits Umbraco.Web.Macros.PartialViewMacroPage diff --git a/src/Umbraco.Web.UI/Views/Partials/Grid/Editors/Media.cshtml b/src/Umbraco.Web.UI/Views/Partials/Grid/Editors/Media.cshtml index 0ed54ab958..9d91abc34a 100644 --- a/src/Umbraco.Web.UI/Views/Partials/Grid/Editors/Media.cshtml +++ b/src/Umbraco.Web.UI/Views/Partials/Grid/Editors/Media.cshtml @@ -1,5 +1,5 @@ @model dynamic -@using Umbraco.Web.Templates +@using Umbraco.Core.Media @if (Model.value != null) { @@ -21,7 +21,7 @@ var altText = Model.value.altText ?? Model.value.caption ?? string.Empty; @altText - + if (Model.value.caption != null) {

    @Model.value.caption

    From 944042ad614b7803d47c9e34dfb7ee0b815f22a6 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Wed, 1 Apr 2020 12:07:54 +0200 Subject: [PATCH 72/73] Updated builders --- .../Builders/UserBuilder.cs | 11 +++-- .../Builders/UserGroupBuilder.cs | 42 +++++++++++++++++-- 2 files changed, 43 insertions(+), 10 deletions(-) diff --git a/src/Umbraco.Tests.Common/Builders/UserBuilder.cs b/src/Umbraco.Tests.Common/Builders/UserBuilder.cs index dd11156bf0..cb24e0e8dc 100644 --- a/src/Umbraco.Tests.Common/Builders/UserBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/UserBuilder.cs @@ -1,3 +1,4 @@ +using Umbraco.Configuration.Models; using Umbraco.Core.Models.Membership; using Umbraco.Tests.Common.Builders.Interfaces; @@ -25,18 +26,16 @@ namespace Umbraco.Tests.Common.Builders private bool? _isLockedOut; private string _email; private string _username; - private string _defaultLang; private string _suffix = string.Empty; - private GlobalSettingsBuilder> _globalSettingsBuilder; + private string _defaultLang; public UserBuilder(TParent parentBuilder) : base(parentBuilder) { - _globalSettingsBuilder = new GlobalSettingsBuilder>(this); + } - public GlobalSettingsBuilder> AddGlobalSettings() => _globalSettingsBuilder; - public UserBuilder WithDefaultUILanguage(string defaultLang) + public UserBuilder WithDefaultUILanguage(string defaultLang) { _defaultLang = defaultLang; return this; @@ -85,7 +84,7 @@ namespace Umbraco.Tests.Common.Builders public override User Build() { - var globalSettings = _globalSettingsBuilder.Build(); + var globalSettings = new GlobalSettingsBuilder().WithDefaultUiLanguage(_defaultLang).Build(); var name = _name ?? "TestUser" + _suffix; var email = _email ?? "test" + _suffix + "@test.com"; var username = _username ?? "TestUser" + _suffix; diff --git a/src/Umbraco.Tests.Common/Builders/UserGroupBuilder.cs b/src/Umbraco.Tests.Common/Builders/UserGroupBuilder.cs index d3ce5e71a8..4c6bb7a74c 100644 --- a/src/Umbraco.Tests.Common/Builders/UserGroupBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/UserGroupBuilder.cs @@ -6,9 +6,20 @@ using Umbraco.Tests.Common.Builders.Interfaces; namespace Umbraco.Tests.Common.Builders { - public class UserGroupBuilder - : BuilderBase, - IWithIdBuilder + + public class UserGroupBuilder : UserGroupBuilder + { + public UserGroupBuilder() : base(null) + { + } + } + + public class UserGroupBuilder + : ChildBuilderBase, + IWithIdBuilder, + IWithIconBuilder, + IWithAliasBuilder, + IWithNameBuilder { private int? _startContentId; private int? _startMediaId; @@ -20,12 +31,16 @@ namespace Umbraco.Tests.Common.Builders private string _suffix; private int? _id; + public UserGroupBuilder(TParent parentBuilder) : base(parentBuilder) + { + } + /// /// Will suffix the name and alias for testing /// /// /// - public UserGroupBuilder WithSuffix(string suffix) + public UserGroupBuilder WithSuffix(string suffix) { _suffix = suffix; return this; @@ -61,5 +76,24 @@ namespace Umbraco.Tests.Common.Builders get => _id; set => _id = value; } + + + string IWithIconBuilder.Icon + { + get => _icon; + set => _icon = value; + } + + string IWithAliasBuilder.Alias + { + get => _alias; + set => _alias = value; + } + + string IWithNameBuilder.Name + { + get => _name; + set => _name = value; + } } } From 400fa1ccd40a573ec3f3956d11ae35d8329d6cfe Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Wed, 1 Apr 2020 14:19:41 +0200 Subject: [PATCH 73/73] Initialize Current --- src/Umbraco.Tests.Integration/RuntimeTests.cs | 4 ++-- .../Testing/UmbracoIntegrationTest.cs | 2 +- .../UmbracoCoreServiceCollectionExtensions.cs | 19 ++++++++++++++++--- src/Umbraco.Web.UI.NetCore/Startup.cs | 15 ++++++++++++++- 4 files changed, 33 insertions(+), 7 deletions(-) diff --git a/src/Umbraco.Tests.Integration/RuntimeTests.cs b/src/Umbraco.Tests.Integration/RuntimeTests.cs index 2974a01669..5968ee89fb 100644 --- a/src/Umbraco.Tests.Integration/RuntimeTests.cs +++ b/src/Umbraco.Tests.Integration/RuntimeTests.cs @@ -97,7 +97,7 @@ namespace Umbraco.Tests.Integration // Add it! services.AddUmbracoConfiguration(hostContext.Configuration); - services.AddUmbracoCore(webHostEnvironment, umbracoContainer, GetType().Assembly); + services.AddUmbracoCore(webHostEnvironment, umbracoContainer, GetType().Assembly, out _); }); var host = await hostBuilder.StartAsync(); @@ -136,7 +136,7 @@ namespace Umbraco.Tests.Integration // Add it! services.AddUmbracoConfiguration(hostContext.Configuration); - services.AddUmbracoCore(webHostEnvironment, umbracoContainer, GetType().Assembly); + services.AddUmbracoCore(webHostEnvironment, umbracoContainer, GetType().Assembly, out _); }); var host = await hostBuilder.StartAsync(); diff --git a/src/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs b/src/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs index 43504e908a..d822d687d9 100644 --- a/src/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs +++ b/src/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs @@ -118,7 +118,7 @@ namespace Umbraco.Tests.Integration.Testing // Add it! services.AddUmbracoConfiguration(hostContext.Configuration); - services.AddUmbracoCore(webHostEnvironment, umbracoContainer, GetType().Assembly); + services.AddUmbracoCore(webHostEnvironment, umbracoContainer, GetType().Assembly, out _); }); var host = await hostBuilder.StartAsync(); diff --git a/src/Umbraco.Web.Common/Extensions/UmbracoCoreServiceCollectionExtensions.cs b/src/Umbraco.Web.Common/Extensions/UmbracoCoreServiceCollectionExtensions.cs index a0dd1f8e63..41581523bb 100644 --- a/src/Umbraco.Web.Common/Extensions/UmbracoCoreServiceCollectionExtensions.cs +++ b/src/Umbraco.Web.Common/Extensions/UmbracoCoreServiceCollectionExtensions.cs @@ -51,13 +51,25 @@ namespace Umbraco.Web.Common.Extensions /// /// public static IServiceCollection AddUmbracoCore(this IServiceCollection services, IWebHostEnvironment webHostEnvironment) + { + return services.AddUmbracoCore(webHostEnvironment,out _); + } + + /// + /// Adds the Umbraco Back Core requirements + /// + /// + /// + /// + /// + public static IServiceCollection AddUmbracoCore(this IServiceCollection services, IWebHostEnvironment webHostEnvironment, out IFactory factory) { if (!UmbracoServiceProviderFactory.IsActive) throw new InvalidOperationException("Ensure to add UseUmbraco() in your Program.cs after ConfigureWebHostDefaults to enable Umbraco's service provider factory"); var umbContainer = UmbracoServiceProviderFactory.UmbracoContainer; - return services.AddUmbracoCore(webHostEnvironment, umbContainer, Assembly.GetEntryAssembly()); + return services.AddUmbracoCore(webHostEnvironment, umbContainer, Assembly.GetEntryAssembly(), out factory); } /// @@ -67,8 +79,9 @@ namespace Umbraco.Web.Common.Extensions /// /// /// + /// /// - public static IServiceCollection AddUmbracoCore(this IServiceCollection services, IWebHostEnvironment webHostEnvironment, IRegister umbContainer, Assembly entryAssembly) + public static IServiceCollection AddUmbracoCore(this IServiceCollection services, IWebHostEnvironment webHostEnvironment, IRegister umbContainer, Assembly entryAssembly, out IFactory factory) { if (services is null) throw new ArgumentNullException(nameof(services)); var container = umbContainer; @@ -98,7 +111,7 @@ namespace Umbraco.Web.Common.Extensions backOfficeInfo, typeFinder); - var factory = coreRuntime.Configure(container); + factory = coreRuntime.Configure(container); return services; } diff --git a/src/Umbraco.Web.UI.NetCore/Startup.cs b/src/Umbraco.Web.UI.NetCore/Startup.cs index 3906f1c8e9..da6e45086f 100644 --- a/src/Umbraco.Web.UI.NetCore/Startup.cs +++ b/src/Umbraco.Web.UI.NetCore/Startup.cs @@ -14,11 +14,13 @@ using Umbraco.Composing; using Umbraco.Core; using Umbraco.Core.Configuration; using Umbraco.Core.IO; +using Umbraco.Core.Logging; using Umbraco.Web.BackOffice.AspNetCore; using Umbraco.Web.Common.AspNetCore; using Umbraco.Web.Common.Extensions; using Umbraco.Web.Common.Runtime.Profiler; using Umbraco.Web.Website.AspNetCore; +using IHostingEnvironment = Umbraco.Core.Hosting.IHostingEnvironment; namespace Umbraco.Web.UI.BackOffice @@ -47,7 +49,7 @@ namespace Umbraco.Web.UI.BackOffice public void ConfigureServices(IServiceCollection services) { services.AddUmbracoConfiguration(_config); - services.AddUmbracoCore(_webHostEnvironment); + services.AddUmbracoCore(_webHostEnvironment, out var factory); services.AddUmbracoWebsite(); services.AddMvc(); @@ -55,6 +57,17 @@ namespace Umbraco.Web.UI.BackOffice { options.ShouldProfile = request => false; // WebProfiler determine and start profiling. We should not use the MiniProfilerMiddleware to also profile }); + + //Finally initialize Current + Current.Initialize( + factory.GetInstance (), + factory.GetInstance(), + factory.GetInstance(), + factory.GetInstance(), + factory.GetInstance(), + factory.GetInstance() + ); + } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.