diff --git a/src/SolutionInfo.cs b/src/SolutionInfo.cs index 59c75192b9..26e46eab0a 100644 --- a/src/SolutionInfo.cs +++ b/src/SolutionInfo.cs @@ -19,4 +19,4 @@ using System.Resources; // these are FYI and changed automatically [assembly: AssemblyFileVersion("0.5.0")] -[assembly: AssemblyInformationalVersion("0.5.0-alpha001")] +[assembly: AssemblyInformationalVersion("0.5.0-alpha002")] diff --git a/src/Umbraco.Core/Composing/TypeFinder.cs b/src/Umbraco.Core/Composing/TypeFinder.cs index 13c5e063be..3427c55731 100644 --- a/src/Umbraco.Core/Composing/TypeFinder.cs +++ b/src/Umbraco.Core/Composing/TypeFinder.cs @@ -156,6 +156,7 @@ namespace Umbraco.Core.Composing "MiniProfiler.", "Owin,", "SQLite", + "ReSharperTestRunner32" // used by resharper testrunner }; /// diff --git a/src/Umbraco.Core/Services/IRuntimeState.cs b/src/Umbraco.Core/Services/IRuntimeState.cs index 4b57908ea7..fd817f1986 100644 --- a/src/Umbraco.Core/Services/IRuntimeState.cs +++ b/src/Umbraco.Core/Services/IRuntimeState.cs @@ -50,5 +50,10 @@ namespace Umbraco.Core /// BootFailedException BootFailedException { get; } + /// + /// Determines the runtime level. + /// + void DetermineRuntimeLevel(); + } } diff --git a/src/Umbraco.Core/SimpleMainDom.cs b/src/Umbraco.Core/SimpleMainDom.cs index e6bdda67d7..a2ef0b8d78 100644 --- a/src/Umbraco.Core/SimpleMainDom.cs +++ b/src/Umbraco.Core/SimpleMainDom.cs @@ -8,11 +8,12 @@ namespace Umbraco.Core /// /// Provides a simple implementation of . /// - public class SimpleMainDom : IMainDom + public class SimpleMainDom : IMainDom, IDisposable { private readonly object _locko = new object(); private readonly List> _callbacks = new List>(); private bool _isStopping; + private bool _disposedValue; /// public bool IsMainDom { get; private set; } = true; @@ -59,5 +60,24 @@ namespace Umbraco.Core IsMainDom = false; } } + + protected virtual void Dispose(bool disposing) + { + if (!_disposedValue) + { + if (disposing) + { + Stop(); + } + _disposedValue = true; + } + } + + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(disposing: true); + GC.SuppressFinalize(this); + } } } diff --git a/src/Umbraco.Examine.Lucene/ExamineLuceneComposer.cs b/src/Umbraco.Examine.Lucene/ExamineLuceneComposer.cs index b7d9cde9d1..b1254a4beb 100644 --- a/src/Umbraco.Examine.Lucene/ExamineLuceneComposer.cs +++ b/src/Umbraco.Examine.Lucene/ExamineLuceneComposer.cs @@ -20,6 +20,7 @@ namespace Umbraco.Examine composition.RegisterUnique(); composition.RegisterUnique(); composition.RegisterUnique(); + composition.RegisterUnique(); } } } diff --git a/src/Umbraco.Examine.Lucene/ILuceneDirectoryFactory.cs b/src/Umbraco.Examine.Lucene/ILuceneDirectoryFactory.cs new file mode 100644 index 0000000000..f4946d491e --- /dev/null +++ b/src/Umbraco.Examine.Lucene/ILuceneDirectoryFactory.cs @@ -0,0 +1,7 @@ +namespace Umbraco.Examine +{ + public interface ILuceneDirectoryFactory + { + Lucene.Net.Store.Directory CreateDirectory(string indexName); + } +} diff --git a/src/Umbraco.Examine.Lucene/LuceneFileSystemDirectoryFactory.cs b/src/Umbraco.Examine.Lucene/LuceneFileSystemDirectoryFactory.cs new file mode 100644 index 0000000000..8f3bf8bf69 --- /dev/null +++ b/src/Umbraco.Examine.Lucene/LuceneFileSystemDirectoryFactory.cs @@ -0,0 +1,69 @@ +using Umbraco.Core.Configuration; +using Umbraco.Core; +using Umbraco.Core.Composing; +using Umbraco.Core.Hosting; +using Lucene.Net.Store; +using System.IO; +using System; +using Examine.LuceneEngine.Directories; + +namespace Umbraco.Examine +{ + + public class LuceneFileSystemDirectoryFactory : ILuceneDirectoryFactory + { + private readonly ITypeFinder _typeFinder; + private readonly IHostingEnvironment _hostingEnvironment; + private readonly IIndexCreatorSettings _settings; + + public LuceneFileSystemDirectoryFactory(ITypeFinder typeFinder, IHostingEnvironment hostingEnvironment, IIndexCreatorSettings settings) + { + _typeFinder = typeFinder; + _hostingEnvironment = hostingEnvironment; + _settings = settings; + } + + public Lucene.Net.Store.Directory CreateDirectory(string indexName) => CreateFileSystemLuceneDirectory(indexName); + + /// + /// Creates a file system based Lucene with the correct locking guidelines for Umbraco + /// + /// + /// The folder name to store the index (single word, not a fully qualified folder) (i.e. Internal) + /// + /// + public virtual Lucene.Net.Store.Directory CreateFileSystemLuceneDirectory(string folderName) + { + + var dirInfo = new DirectoryInfo(Path.Combine(_hostingEnvironment.MapPathContentRoot(Constants.SystemDirectories.TempData), "ExamineIndexes", folderName)); + if (!dirInfo.Exists) + System.IO.Directory.CreateDirectory(dirInfo.FullName); + + //check if there's a configured directory factory, if so create it and use that to create the lucene dir + var configuredDirectoryFactory = _settings.LuceneDirectoryFactory; + + if (!configuredDirectoryFactory.IsNullOrWhiteSpace()) + { + //this should be a fully qualified type + var factoryType = _typeFinder.GetTypeByName(configuredDirectoryFactory); + if (factoryType == null) throw new NullReferenceException("No directory type found for value: " + configuredDirectoryFactory); + var directoryFactory = (IDirectoryFactory)Activator.CreateInstance(factoryType); + return directoryFactory.CreateDirectory(dirInfo); + } + + //no dir factory, just create a normal fs directory + + var luceneDir = new SimpleFSDirectory(dirInfo); + + //we want to tell examine to use a different fs lock instead of the default NativeFSFileLock which could cause problems if the appdomain + //terminates and in some rare cases would only allow unlocking of the file if IIS is forcefully terminated. Instead we'll rely on the simplefslock + //which simply checks the existence of the lock file + // The full syntax of this is: new NoPrefixSimpleFsLockFactory(dirInfo) + // however, we are setting the DefaultLockFactory in startup so we'll use that instead since it can be managed globally. + luceneDir.SetLockFactory(DirectoryFactory.DefaultLockFactory(dirInfo)); + return luceneDir; + + + } + } +} diff --git a/src/Umbraco.Examine.Lucene/LuceneIndexCreator.cs b/src/Umbraco.Examine.Lucene/LuceneIndexCreator.cs index 5c6b111d87..8ecb1b4421 100644 --- a/src/Umbraco.Examine.Lucene/LuceneIndexCreator.cs +++ b/src/Umbraco.Examine.Lucene/LuceneIndexCreator.cs @@ -29,47 +29,6 @@ namespace Umbraco.Examine _settings = settings.Value; } - public abstract IEnumerable Create(); - - /// - /// Creates a file system based Lucene with the correct locking guidelines for Umbraco - /// - /// - /// The folder name to store the index (single word, not a fully qualified folder) (i.e. Internal) - /// - /// - public virtual Lucene.Net.Store.Directory CreateFileSystemLuceneDirectory(string folderName) - { - - var dirInfo = new DirectoryInfo(Path.Combine(_hostingEnvironment.MapPathContentRoot(Constants.SystemDirectories.TempData), "ExamineIndexes", folderName)); - if (!dirInfo.Exists) - System.IO.Directory.CreateDirectory(dirInfo.FullName); - - //check if there's a configured directory factory, if so create it and use that to create the lucene dir - var configuredDirectoryFactory = _settings.LuceneDirectoryFactory; - - if (!configuredDirectoryFactory.IsNullOrWhiteSpace()) - { - //this should be a fully qualified type - var factoryType = _typeFinder.GetTypeByName(configuredDirectoryFactory); - if (factoryType == null) throw new NullReferenceException("No directory type found for value: " + configuredDirectoryFactory); - var directoryFactory = (IDirectoryFactory)Activator.CreateInstance(factoryType); - return directoryFactory.CreateDirectory(dirInfo); - } - - //no dir factory, just create a normal fs directory - - var luceneDir = new SimpleFSDirectory(dirInfo); - - //we want to tell examine to use a different fs lock instead of the default NativeFSFileLock which could cause problems if the appdomain - //terminates and in some rare cases would only allow unlocking of the file if IIS is forcefully terminated. Instead we'll rely on the simplefslock - //which simply checks the existence of the lock file - // The full syntax of this is: new NoPrefixSimpleFsLockFactory(dirInfo) - // however, we are setting the DefaultLockFactory in startup so we'll use that instead since it can be managed globally. - luceneDir.SetLockFactory(DirectoryFactory.DefaultLockFactory(dirInfo)); - return luceneDir; - - - } + public abstract IEnumerable Create(); } } diff --git a/src/Umbraco.Examine.Lucene/LuceneRAMDirectoryFactory.cs b/src/Umbraco.Examine.Lucene/LuceneRAMDirectoryFactory.cs new file mode 100644 index 0000000000..327328390b --- /dev/null +++ b/src/Umbraco.Examine.Lucene/LuceneRAMDirectoryFactory.cs @@ -0,0 +1,24 @@ +using Lucene.Net.Store; +using System; + +namespace Umbraco.Examine +{ + public class LuceneRAMDirectoryFactory : ILuceneDirectoryFactory + { + public LuceneRAMDirectoryFactory() + { + + } + + public Lucene.Net.Store.Directory CreateDirectory(string indexName) => new RandomIdRAMDirectory(); + + private class RandomIdRAMDirectory : RAMDirectory + { + private readonly string _lockId = Guid.NewGuid().ToString(); + public override string GetLockId() + { + return _lockId; + } + } + } +} diff --git a/src/Umbraco.Examine.Lucene/UmbracoContentIndex.cs b/src/Umbraco.Examine.Lucene/UmbracoContentIndex.cs index dc2a5570a6..4cb26d5ae5 100644 --- a/src/Umbraco.Examine.Lucene/UmbracoContentIndex.cs +++ b/src/Umbraco.Examine.Lucene/UmbracoContentIndex.cs @@ -19,9 +19,8 @@ namespace Umbraco.Examine /// /// An indexer for Umbraco content and media /// - public class UmbracoContentIndex : UmbracoExamineIndex, IUmbracoContentIndex + public class UmbracoContentIndex : UmbracoExamineIndex, IUmbracoContentIndex, IDisposable { - protected ILocalizationService LanguageService { get; } #region Constructors diff --git a/src/Umbraco.Examine.Lucene/UmbracoIndexesCreator.cs b/src/Umbraco.Examine.Lucene/UmbracoIndexesCreator.cs index c4eb2249fa..39113b4f50 100644 --- a/src/Umbraco.Examine.Lucene/UmbracoIndexesCreator.cs +++ b/src/Umbraco.Examine.Lucene/UmbracoIndexesCreator.cs @@ -14,6 +14,7 @@ using Microsoft.Extensions.Options; namespace Umbraco.Examine { + /// /// Creates the indexes used by Umbraco /// @@ -30,7 +31,8 @@ namespace Umbraco.Examine IUmbracoIndexConfig umbracoIndexConfig, IHostingEnvironment hostingEnvironment, IRuntimeState runtimeState, - IOptions settings) : base(typeFinder, hostingEnvironment, settings) + IOptions settings, + ILuceneDirectoryFactory directoryFactory) : base(typeFinder, hostingEnvironment, settings) { ProfilingLogger = profilingLogger ?? throw new System.ArgumentNullException(nameof(profilingLogger)); LanguageService = languageService ?? throw new System.ArgumentNullException(nameof(languageService)); @@ -39,11 +41,13 @@ namespace Umbraco.Examine UmbracoIndexConfig = umbracoIndexConfig; HostingEnvironment = hostingEnvironment ?? throw new System.ArgumentNullException(nameof(hostingEnvironment)); RuntimeState = runtimeState ?? throw new System.ArgumentNullException(nameof(runtimeState)); + DirectoryFactory = directoryFactory; } protected IProfilingLogger ProfilingLogger { get; } protected IHostingEnvironment HostingEnvironment { get; } protected IRuntimeState RuntimeState { get; } + protected ILuceneDirectoryFactory DirectoryFactory { get; } protected ILocalizationService LanguageService { get; } protected IPublicAccessService PublicAccessService { get; } protected IMemberService MemberService { get; } @@ -67,7 +71,7 @@ namespace Umbraco.Examine { var index = new UmbracoContentIndex( Constants.UmbracoIndexes.InternalIndexName, - CreateFileSystemLuceneDirectory(Constants.UmbracoIndexes.InternalIndexPath), + DirectoryFactory.CreateDirectory(Constants.UmbracoIndexes.InternalIndexPath), new UmbracoFieldDefinitionCollection(), new CultureInvariantWhitespaceAnalyzer(), ProfilingLogger, @@ -83,7 +87,7 @@ namespace Umbraco.Examine { var index = new UmbracoContentIndex( Constants.UmbracoIndexes.ExternalIndexName, - CreateFileSystemLuceneDirectory(Constants.UmbracoIndexes.ExternalIndexPath), + DirectoryFactory.CreateDirectory(Constants.UmbracoIndexes.ExternalIndexPath), new UmbracoFieldDefinitionCollection(), new StandardAnalyzer(Lucene.Net.Util.Version.LUCENE_30), ProfilingLogger, @@ -99,7 +103,7 @@ namespace Umbraco.Examine var index = new UmbracoMemberIndex( Constants.UmbracoIndexes.MembersIndexName, new UmbracoFieldDefinitionCollection(), - CreateFileSystemLuceneDirectory(Constants.UmbracoIndexes.MembersIndexPath), + DirectoryFactory.CreateDirectory(Constants.UmbracoIndexes.MembersIndexPath), new CultureInvariantWhitespaceAnalyzer(), ProfilingLogger, HostingEnvironment, diff --git a/src/Umbraco.Infrastructure/Compose/NotificationsComponent.cs b/src/Umbraco.Infrastructure/Compose/NotificationsComponent.cs index 15d01bdd12..790cab0913 100644 --- a/src/Umbraco.Infrastructure/Compose/NotificationsComponent.cs +++ b/src/Umbraco.Infrastructure/Compose/NotificationsComponent.cs @@ -5,13 +5,10 @@ using System.Linq; using Microsoft.Extensions.Options; using Umbraco.Core; using Umbraco.Core.Composing; -using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.Models; -using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.Entities; -using Umbraco.Core.Models.Identity; using Umbraco.Core.Models.Membership; using Umbraco.Core.Services; using Umbraco.Core.Services.Implement; @@ -35,42 +32,78 @@ namespace Umbraco.Web.Compose public void Initialize() { //Send notifications for the send to publish action - ContentService.SentToPublish += (sender, args) => _notifier.Notify(_actions.GetAction(), args.Entity); - + ContentService.SentToPublish += ContentService_SentToPublish; //Send notifications for the published action - ContentService.Published += (sender, args) => _notifier.Notify(_actions.GetAction(), args.PublishedEntities.ToArray()); - + ContentService.Published += ContentService_Published; //Send notifications for the saved action - ContentService.Sorted += (sender, args) => ContentServiceSorted(_notifier, sender, args, _actions); - + ContentService.Sorted += ContentService_Sorted; //Send notifications for the update and created actions - ContentService.Saved += (sender, args) => ContentServiceSaved(_notifier, sender, args, _actions); - + ContentService.Saved += ContentService_Saved; //Send notifications for the unpublish action - ContentService.Unpublished += (sender, args) => _notifier.Notify(_actions.GetAction(), args.PublishedEntities.ToArray()); - + ContentService.Unpublished += ContentService_Unpublished; //Send notifications for the move/move to recycle bin and restore actions - ContentService.Moved += (sender, args) => ContentServiceMoved(_notifier, sender, args, _actions); - + ContentService.Moved += ContentService_Moved; //Send notifications for the delete action when content is moved to the recycle bin - ContentService.Trashed += (sender, args) => _notifier.Notify(_actions.GetAction(), args.MoveInfoCollection.Select(m => m.Entity).ToArray()); - + ContentService.Trashed += ContentService_Trashed; //Send notifications for the copy action - ContentService.Copied += (sender, args) => _notifier.Notify(_actions.GetAction(), args.Original); - + ContentService.Copied += ContentService_Copied; //Send notifications for the rollback action - ContentService.RolledBack += (sender, args) => _notifier.Notify(_actions.GetAction(), args.Entity); - + ContentService.RolledBack += ContentService_RolledBack; //Send notifications for the public access changed action - PublicAccessService.Saved += (sender, args) => PublicAccessServiceSaved(_notifier, sender, args, _contentService, _actions); + PublicAccessService.Saved += PublicAccessService_Saved; - UserService.UserGroupPermissionsAssigned += (sender, args) => UserServiceUserGroupPermissionsAssigned(_notifier, sender, args, _contentService, _actions); + UserService.UserGroupPermissionsAssigned += UserService_UserGroupPermissionsAssigned; } public void Terminate() - { } + { + ContentService.SentToPublish -= ContentService_SentToPublish; + ContentService.Published -= ContentService_Published; + ContentService.Sorted -= ContentService_Sorted; + ContentService.Saved -= ContentService_Saved; + ContentService.Unpublished -= ContentService_Unpublished; + ContentService.Moved -= ContentService_Moved; + ContentService.Trashed -= ContentService_Trashed; + ContentService.Copied -= ContentService_Copied; + ContentService.RolledBack -= ContentService_RolledBack; + PublicAccessService.Saved -= PublicAccessService_Saved; + UserService.UserGroupPermissionsAssigned -= UserService_UserGroupPermissionsAssigned; + } - private void ContentServiceSorted(Notifier notifier, IContentService sender, Core.Events.SaveEventArgs args, ActionCollection actions) + private void UserService_UserGroupPermissionsAssigned(IUserService sender, Core.Events.SaveEventArgs args) + => UserServiceUserGroupPermissionsAssigned(args, _contentService); + + private void PublicAccessService_Saved(IPublicAccessService sender, Core.Events.SaveEventArgs args) + => PublicAccessServiceSaved(args, _contentService); + + private void ContentService_RolledBack(IContentService sender, Core.Events.RollbackEventArgs args) + => _notifier.Notify(_actions.GetAction(), args.Entity); + + private void ContentService_Copied(IContentService sender, Core.Events.CopyEventArgs args) + => _notifier.Notify(_actions.GetAction(), args.Original); + + private void ContentService_Trashed(IContentService sender, Core.Events.MoveEventArgs args) + => _notifier.Notify(_actions.GetAction(), args.MoveInfoCollection.Select(m => m.Entity).ToArray()); + + private void ContentService_Moved(IContentService sender, Core.Events.MoveEventArgs args) + => ContentServiceMoved(args); + + private void ContentService_Unpublished(IContentService sender, Core.Events.PublishEventArgs args) + => _notifier.Notify(_actions.GetAction(), args.PublishedEntities.ToArray()); + + private void ContentService_Saved(IContentService sender, Core.Events.ContentSavedEventArgs args) + => ContentServiceSaved(args); + + private void ContentService_Sorted(IContentService sender, Core.Events.SaveEventArgs args) + => ContentServiceSorted(sender, args); + + private void ContentService_Published(IContentService sender, Core.Events.ContentPublishedEventArgs args) + => _notifier.Notify(_actions.GetAction(), args.PublishedEntities.ToArray()); + + private void ContentService_SentToPublish(IContentService sender, Core.Events.SendToPublishEventArgs args) + => _notifier.Notify(_actions.GetAction(), args.Entity); + + private void ContentServiceSorted(IContentService sender, Core.Events.SaveEventArgs args) { var parentId = args.SavedEntities.Select(x => x.ParentId).Distinct().ToList(); if (parentId.Count != 1) return; // this shouldn't happen, for sorting all entities will have the same parent id @@ -82,10 +115,10 @@ namespace Umbraco.Web.Compose var parent = sender.GetById(parentId[0]); if (parent == null) return; // this shouldn't happen - notifier.Notify(actions.GetAction(), new[] { parent }); + _notifier.Notify(_actions.GetAction(), new[] { parent }); } - private void ContentServiceSaved(Notifier notifier, IContentService sender, Core.Events.SaveEventArgs args, ActionCollection actions) + private void ContentServiceSaved(Core.Events.SaveEventArgs args) { var newEntities = new List(); var updatedEntities = new List(); @@ -105,21 +138,21 @@ namespace Umbraco.Web.Compose updatedEntities.Add(entity); } } - notifier.Notify(actions.GetAction(), newEntities.ToArray()); - notifier.Notify(actions.GetAction(), updatedEntities.ToArray()); + _notifier.Notify(_actions.GetAction(), newEntities.ToArray()); + _notifier.Notify(_actions.GetAction(), updatedEntities.ToArray()); } - private void UserServiceUserGroupPermissionsAssigned(Notifier notifier, IUserService sender, Core.Events.SaveEventArgs args, IContentService contentService, ActionCollection actions) + private void UserServiceUserGroupPermissionsAssigned(Core.Events.SaveEventArgs args, IContentService contentService) { var entities = contentService.GetByIds(args.SavedEntities.Select(e => e.EntityId)).ToArray(); if(entities.Any() == false) { return; } - notifier.Notify(actions.GetAction(), entities); + _notifier.Notify(_actions.GetAction(), entities); } - private void ContentServiceMoved(Notifier notifier, IContentService sender, Core.Events.MoveEventArgs args, ActionCollection actions) + private void ContentServiceMoved(Core.Events.MoveEventArgs args) { // notify about the move for all moved items _notifier.Notify(_actions.GetAction(), args.MoveInfoCollection.Select(m => m.Entity).ToArray()); @@ -135,14 +168,14 @@ namespace Umbraco.Web.Compose } } - private void PublicAccessServiceSaved(Notifier notifier, IPublicAccessService sender, Core.Events.SaveEventArgs args, IContentService contentService, ActionCollection actions) + private void PublicAccessServiceSaved(Core.Events.SaveEventArgs args, IContentService contentService) { var entities = contentService.GetByIds(args.SavedEntities.Select(e => e.ProtectedNodeId)).ToArray(); if(entities.Any() == false) { return; } - notifier.Notify(actions.GetAction(), entities); + _notifier.Notify(_actions.GetAction(), entities); } /// diff --git a/src/Umbraco.Infrastructure/Compose/PublicAccessComponent.cs b/src/Umbraco.Infrastructure/Compose/PublicAccessComponent.cs index 37bcfb1ceb..a917cfe0ef 100644 --- a/src/Umbraco.Infrastructure/Compose/PublicAccessComponent.cs +++ b/src/Umbraco.Infrastructure/Compose/PublicAccessComponent.cs @@ -16,13 +16,15 @@ namespace Umbraco.Web.Compose public void Initialize() { - MemberGroupService.Saved += (s, e) => MemberGroupService_Saved(s, e, _publicAccessService); + MemberGroupService.Saved += MemberGroupService_Saved; } public void Terminate() - { } + { + MemberGroupService.Saved -= MemberGroupService_Saved; + } - static void MemberGroupService_Saved(IMemberGroupService sender, Core.Events.SaveEventArgs e, IPublicAccessService publicAccessService) + private void MemberGroupService_Saved(IMemberGroupService sender, Core.Events.SaveEventArgs e) { foreach (var grp in e.SavedEntities) { @@ -32,7 +34,7 @@ namespace Umbraco.Web.Compose && grp.AdditionalData["previousName"].ToString().IsNullOrWhiteSpace() == false && grp.AdditionalData["previousName"].ToString() != grp.Name) { - publicAccessService.RenameMemberGroupRoleRules(grp.AdditionalData["previousName"].ToString(), grp.Name); + _publicAccessService.RenameMemberGroupRoleRules(grp.AdditionalData["previousName"].ToString(), grp.Name); } } } diff --git a/src/Umbraco.Infrastructure/Compose/RelateOnCopyComponent.cs b/src/Umbraco.Infrastructure/Compose/RelateOnCopyComponent.cs index 56a97e4cba..3418dfcfc0 100644 --- a/src/Umbraco.Infrastructure/Compose/RelateOnCopyComponent.cs +++ b/src/Umbraco.Infrastructure/Compose/RelateOnCopyComponent.cs @@ -23,7 +23,9 @@ namespace Umbraco.Core.Compose } public void Terminate() - { } + { + ContentService.Copied -= ContentServiceCopied; + } private void ContentServiceCopied(IContentService sender, Events.CopyEventArgs e) { diff --git a/src/Umbraco.Infrastructure/Compose/RelateOnTrashComponent.cs b/src/Umbraco.Infrastructure/Compose/RelateOnTrashComponent.cs index c81aa2fd7d..aa92972e9c 100644 --- a/src/Umbraco.Infrastructure/Compose/RelateOnTrashComponent.cs +++ b/src/Umbraco.Infrastructure/Compose/RelateOnTrashComponent.cs @@ -24,47 +24,52 @@ namespace Umbraco.Core.Compose public void Initialize() { - ContentService.Moved += (sender, args) => ContentService_Moved(sender, args, _relationService); - ContentService.Trashed += (sender, args) => ContentService_Trashed(sender, args, _relationService, _entityService, _textService, _auditService); - MediaService.Moved += (sender, args) => MediaService_Moved(sender, args, _relationService); - MediaService.Trashed += (sender, args) => MediaService_Trashed(sender, args, _relationService, _entityService, _textService, _auditService); + ContentService.Moved += ContentService_Moved; + ContentService.Trashed += ContentService_Trashed; + MediaService.Moved += MediaService_Moved; + MediaService.Trashed += MediaService_Trashed; } public void Terminate() - { } + { + ContentService.Moved -= ContentService_Moved; + ContentService.Trashed -= ContentService_Trashed; + MediaService.Moved -= MediaService_Moved; + MediaService.Trashed -= MediaService_Trashed; + } - private static void ContentService_Moved(IContentService sender, MoveEventArgs e, IRelationService relationService) + private void ContentService_Moved(IContentService sender, MoveEventArgs e) { foreach (var item in e.MoveInfoCollection.Where(x => x.OriginalPath.Contains(Constants.System.RecycleBinContentString))) { const string relationTypeAlias = Constants.Conventions.RelationTypes.RelateParentDocumentOnDeleteAlias; - var relations = relationService.GetByChildId(item.Entity.Id); + var relations = _relationService.GetByChildId(item.Entity.Id); foreach (var relation in relations.Where(x => x.RelationType.Alias.InvariantEquals(relationTypeAlias))) { - relationService.Delete(relation); + _relationService.Delete(relation); } } } - private static void MediaService_Moved(IMediaService sender, MoveEventArgs e, IRelationService relationService) + private void MediaService_Moved(IMediaService sender, MoveEventArgs e) { foreach (var item in e.MoveInfoCollection.Where(x => x.OriginalPath.Contains(Constants.System.RecycleBinMediaString))) { const string relationTypeAlias = Constants.Conventions.RelationTypes.RelateParentMediaFolderOnDeleteAlias; - var relations = relationService.GetByChildId(item.Entity.Id); + var relations = _relationService.GetByChildId(item.Entity.Id); foreach (var relation in relations.Where(x => x.RelationType.Alias.InvariantEquals(relationTypeAlias))) { - relationService.Delete(relation); + _relationService.Delete(relation); } } } - private static void ContentService_Trashed(IContentService sender, MoveEventArgs e, IRelationService relationService, IEntityService entityService, ILocalizedTextService textService, IAuditService auditService) + private void ContentService_Trashed(IContentService sender, MoveEventArgs e) { const string relationTypeAlias = Constants.Conventions.RelationTypes.RelateParentDocumentOnDeleteAlias; - var relationType = relationService.GetRelationTypeByAlias(relationTypeAlias); + var relationType = _relationService.GetRelationTypeByAlias(relationTypeAlias); // check that the relation-type exists, if not, then recreate it if (relationType == null) @@ -73,7 +78,7 @@ namespace Umbraco.Core.Compose const string relationTypeName = Constants.Conventions.RelationTypes.RelateParentDocumentOnDeleteName; relationType = new RelationType(relationTypeName, relationTypeAlias, false, documentObjectType, documentObjectType); - relationService.Save(relationType); + _relationService.Save(relationType); } foreach (var item in e.MoveInfoCollection) @@ -86,34 +91,34 @@ namespace Umbraco.Core.Compose //before we can create this relation, we need to ensure that the original parent still exists which //may not be the case if the encompassing transaction also deleted it when this item was moved to the bin - if (entityService.Exists(originalParentId)) + if (_entityService.Exists(originalParentId)) { // Add a relation for the item being deleted, so that we can know the original parent for if we need to restore later var relation = new Relation(originalParentId, item.Entity.Id, relationType); - relationService.Save(relation); + _relationService.Save(relation); - auditService.Add(AuditType.Delete, + _auditService.Add(AuditType.Delete, item.Entity.WriterId, item.Entity.Id, ObjectTypes.GetName(UmbracoObjectTypes.Document), - string.Format(textService.Localize( + string.Format(_textService.Localize( "recycleBin/contentTrashed"), item.Entity.Id, originalParentId)); } } } - private static void MediaService_Trashed(IMediaService sender, MoveEventArgs e, IRelationService relationService, IEntityService entityService, ILocalizedTextService textService, IAuditService auditService) + private void MediaService_Trashed(IMediaService sender, MoveEventArgs e) { const string relationTypeAlias = Constants.Conventions.RelationTypes.RelateParentMediaFolderOnDeleteAlias; - var relationType = relationService.GetRelationTypeByAlias(relationTypeAlias); + var relationType = _relationService.GetRelationTypeByAlias(relationTypeAlias); // check that the relation-type exists, if not, then recreate it if (relationType == null) { var documentObjectType = Constants.ObjectTypes.Document; const string relationTypeName = Constants.Conventions.RelationTypes.RelateParentMediaFolderOnDeleteName; relationType = new RelationType(relationTypeName, relationTypeAlias, false, documentObjectType, documentObjectType); - relationService.Save(relationType); + _relationService.Save(relationType); } foreach (var item in e.MoveInfoCollection) { @@ -123,16 +128,16 @@ namespace Umbraco.Core.Compose : Constants.System.Root; //before we can create this relation, we need to ensure that the original parent still exists which //may not be the case if the encompassing transaction also deleted it when this item was moved to the bin - if (entityService.Exists(originalParentId)) + if (_entityService.Exists(originalParentId)) { // Add a relation for the item being deleted, so that we can know the original parent for if we need to restore later var relation = new Relation(originalParentId, item.Entity.Id, relationType); - relationService.Save(relation); - auditService.Add(AuditType.Delete, + _relationService.Save(relation); + _auditService.Add(AuditType.Delete, item.Entity.CreatorId, item.Entity.Id, ObjectTypes.GetName(UmbracoObjectTypes.Media), - string.Format(textService.Localize( + string.Format(_textService.Localize( "recycleBin/mediaTrashed"), item.Entity.Id, originalParentId)); } diff --git a/src/Umbraco.Infrastructure/CompositionExtensions_Essentials.cs b/src/Umbraco.Infrastructure/CompositionExtensions_Essentials.cs index 88ae80bf8b..b2ded07034 100644 --- a/src/Umbraco.Infrastructure/CompositionExtensions_Essentials.cs +++ b/src/Umbraco.Infrastructure/CompositionExtensions_Essentials.cs @@ -31,6 +31,7 @@ namespace Umbraco.Core TypeLoader typeLoader, IRuntimeState state, ITypeFinder typeFinder, + IIOHelper ioHelper, IUmbracoVersion umbracoVersion, IDbProviderFactoryCreator dbProviderFactoryCreator, diff --git a/src/Umbraco.Infrastructure/Install/InstallSteps/DatabaseUpgradeStep.cs b/src/Umbraco.Infrastructure/Install/InstallSteps/DatabaseUpgradeStep.cs index e19fac4028..22ad84df74 100644 --- a/src/Umbraco.Infrastructure/Install/InstallSteps/DatabaseUpgradeStep.cs +++ b/src/Umbraco.Infrastructure/Install/InstallSteps/DatabaseUpgradeStep.cs @@ -22,29 +22,20 @@ namespace Umbraco.Web.Install.InstallSteps private readonly IRuntimeState _runtime; private readonly ILogger _logger; private readonly IUmbracoVersion _umbracoVersion; - private readonly GlobalSettings _globalSettings; private readonly ConnectionStrings _connectionStrings; - private readonly IIOHelper _ioHelper; - private readonly IConfigManipulator _configManipulator; public DatabaseUpgradeStep( DatabaseBuilder databaseBuilder, IRuntimeState runtime, ILogger logger, IUmbracoVersion umbracoVersion, - IOptions globalSettings, - IOptions connectionStrings, - IIOHelper ioHelper, - IConfigManipulator configManipulator) + IOptions connectionStrings) { _databaseBuilder = databaseBuilder; _runtime = runtime; _logger = logger; _umbracoVersion = umbracoVersion; - _globalSettings = globalSettings.Value; _connectionStrings = connectionStrings.Value ?? throw new ArgumentNullException(nameof(connectionStrings)); - _ioHelper = ioHelper; - _configManipulator = configManipulator; } public override Task ExecuteAsync(object model) @@ -57,7 +48,7 @@ namespace Umbraco.Web.Install.InstallSteps { _logger.Info("Running 'Upgrade' service"); - var plan = new UmbracoPlan(_umbracoVersion, _globalSettings); + var plan = new UmbracoPlan(_umbracoVersion); plan.AddPostMigration(); // needed when running installer (back-office) var result = _databaseBuilder.UpgradeSchemaAndData(plan); diff --git a/src/Umbraco.Infrastructure/Migrations/Install/DatabaseBuilder.cs b/src/Umbraco.Infrastructure/Migrations/Install/DatabaseBuilder.cs index c272ebc15b..678860809a 100644 --- a/src/Umbraco.Infrastructure/Migrations/Install/DatabaseBuilder.cs +++ b/src/Umbraco.Infrastructure/Migrations/Install/DatabaseBuilder.cs @@ -23,7 +23,6 @@ namespace Umbraco.Core.Migrations.Install { private readonly IUmbracoDatabaseFactory _databaseFactory; private readonly IScopeProvider _scopeProvider; - private readonly GlobalSettings _globalSettings; private readonly IRuntimeState _runtime; private readonly IMigrationBuilder _migrationBuilder; private readonly IKeyValueService _keyValueService; @@ -40,7 +39,6 @@ namespace Umbraco.Core.Migrations.Install /// public DatabaseBuilder( IScopeProvider scopeProvider, - IOptions globalSettings, IUmbracoDatabaseFactory databaseFactory, IRuntimeState runtime, ILogger logger, @@ -52,7 +50,6 @@ namespace Umbraco.Core.Migrations.Install IConfigManipulator configManipulator) { _scopeProvider = scopeProvider; - _globalSettings = globalSettings.Value; _databaseFactory = databaseFactory; _runtime = runtime; _logger = logger; @@ -321,7 +318,7 @@ namespace Umbraco.Core.Migrations.Install return _databaseSchemaValidationResult; var database = scope.Database; - var dbSchema = new DatabaseSchemaCreator(database, _logger, _umbracoVersion, _globalSettings); + var dbSchema = new DatabaseSchemaCreator(database, _logger, _umbracoVersion); _databaseSchemaValidationResult = dbSchema.ValidateSchema(); return _databaseSchemaValidationResult; } @@ -369,7 +366,7 @@ namespace Umbraco.Core.Migrations.Install if (_runtime.Level == RuntimeLevel.Run) throw new Exception("Umbraco is already configured!"); - var creator = new DatabaseSchemaCreator(database, _logger, _umbracoVersion, _globalSettings); + var creator = new DatabaseSchemaCreator(database, _logger, _umbracoVersion); creator.InitializeDatabaseSchema(); message = message + "

Installation completed!

"; diff --git a/src/Umbraco.Infrastructure/Migrations/Install/DatabaseDataCreator.cs b/src/Umbraco.Infrastructure/Migrations/Install/DatabaseDataCreator.cs index 6f0f32c37d..866f5230b0 100644 --- a/src/Umbraco.Infrastructure/Migrations/Install/DatabaseDataCreator.cs +++ b/src/Umbraco.Infrastructure/Migrations/Install/DatabaseDataCreator.cs @@ -17,14 +17,12 @@ namespace Umbraco.Core.Migrations.Install private readonly IDatabase _database; private readonly ILogger _logger; private readonly IUmbracoVersion _umbracoVersion; - private readonly GlobalSettings _globalSettings; - public DatabaseDataCreator(IDatabase database, ILogger logger, IUmbracoVersion umbracoVersion, GlobalSettings globalSettings) + public DatabaseDataCreator(IDatabase database, ILogger logger, IUmbracoVersion umbracoVersion) { _database = database; _logger = logger; _umbracoVersion = umbracoVersion; - _globalSettings = globalSettings; } /// @@ -341,7 +339,7 @@ namespace Umbraco.Core.Migrations.Install { // on install, initialize the umbraco migration plan with the final state - var upgrader = new Upgrader(new UmbracoPlan(_umbracoVersion, _globalSettings)); + var upgrader = new Upgrader(new UmbracoPlan(_umbracoVersion)); var stateValueKey = upgrader.StateValueKey; var finalState = upgrader.Plan.FinalState; diff --git a/src/Umbraco.Infrastructure/Migrations/Install/DatabaseSchemaCreator.cs b/src/Umbraco.Infrastructure/Migrations/Install/DatabaseSchemaCreator.cs index 97bd8bc2d6..bf30a520ca 100644 --- a/src/Umbraco.Infrastructure/Migrations/Install/DatabaseSchemaCreator.cs +++ b/src/Umbraco.Infrastructure/Migrations/Install/DatabaseSchemaCreator.cs @@ -21,14 +21,12 @@ namespace Umbraco.Core.Migrations.Install private readonly IUmbracoDatabase _database; private readonly ILogger _logger; private readonly IUmbracoVersion _umbracoVersion; - private readonly GlobalSettings _globalSettings; - public DatabaseSchemaCreator(IUmbracoDatabase database, ILogger logger, IUmbracoVersion umbracoVersion, GlobalSettings globalSettings) + public DatabaseSchemaCreator(IUmbracoDatabase database, ILogger logger, IUmbracoVersion umbracoVersion) { _database = database; _logger = logger; _umbracoVersion = umbracoVersion; - _globalSettings = globalSettings; } private ISqlSyntaxProvider SqlSyntax => _database.SqlContext.SqlSyntax; @@ -131,7 +129,7 @@ namespace Umbraco.Core.Migrations.Install if (e.Cancel == false) { - var dataCreation = new DatabaseDataCreator(_database, _logger, _umbracoVersion, _globalSettings); + var dataCreation = new DatabaseDataCreator(_database, _logger, _umbracoVersion); foreach (var table in OrderedTables) CreateTable(false, table, dataCreation); } @@ -401,7 +399,7 @@ namespace Umbraco.Core.Migrations.Install where T : new() { var tableType = typeof(T); - CreateTable(overwrite, tableType, new DatabaseDataCreator(_database, _logger, _umbracoVersion, _globalSettings)); + CreateTable(overwrite, tableType, new DatabaseDataCreator(_database, _logger, _umbracoVersion)); } /// diff --git a/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs b/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs index 3b555c89b1..7f8c47f92f 100644 --- a/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs +++ b/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs @@ -17,17 +17,15 @@ namespace Umbraco.Core.Migrations.Upgrade public class UmbracoPlan : MigrationPlan { private readonly IUmbracoVersion _umbracoVersion; - private readonly GlobalSettings _globalSettings; private const string InitPrefix = "{init-"; private const string InitSuffix = "}"; /// /// Initializes a new instance of the class. /// - public UmbracoPlan(IUmbracoVersion umbracoVersion, GlobalSettings globalSettings) + public UmbracoPlan(IUmbracoVersion umbracoVersion) : base(Constants.System.UmbracoUpgradePlanName) { _umbracoVersion = umbracoVersion; - _globalSettings = globalSettings; DefinePlan(); } @@ -192,7 +190,7 @@ namespace Umbraco.Core.Migrations.Upgrade // to 8.7.0... To("{a78e3369-8ea3-40ec-ad3f-5f76929d2b20}"); - + //FINAL } } diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentTypeRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentTypeRepository.cs index 61aabdc1b5..868e088d93 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentTypeRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentTypeRepository.cs @@ -54,11 +54,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement protected override bool PerformExists(Guid id) => GetMany().FirstOrDefault(x => x.Key == id) != null; - protected override IEnumerable PerformGetAll(params int[] ids) + protected override IEnumerable GetAllWithFullCachePolicy() { - // the cache policy will always want everything - // even GetMany(ids) gets everything and filters afterwards - if (ids.Any()) throw new PanicException("There can be no ids specified"); return CommonRepository.GetAllTypes().OfType(); } diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs index b2a83e83fd..50ba0a698b 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs @@ -90,6 +90,24 @@ namespace Umbraco.Core.Persistence.Repositories.Implement return moveInfo; } + protected override IEnumerable PerformGetAll(params int[] ids) + { + var result = GetAllWithFullCachePolicy(); + + // By default the cache policy will always want everything + // even GetMany(ids) gets everything and filters afterwards, + // however if we are using No Cache, we must still be able to support + // collections of Ids, so this is to work around that: + if (ids.Any()) + { + return result.Where(x => ids.Contains(x.Id)); + } + + return result; + } + + protected abstract IEnumerable GetAllWithFullCachePolicy(); + protected virtual PropertyType CreatePropertyType(string propertyEditorAlias, ValueStorageType storageType, string propertyTypeAlias) { return new PropertyType(_shortStringHelper, propertyEditorAlias, storageType, propertyTypeAlias); diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/LanguageRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/LanguageRepository.cs index 6b6ab37001..80a83ce909 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/LanguageRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/LanguageRepository.cs @@ -42,7 +42,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement protected override ILanguage PerformGet(int id) { - throw new NotSupportedException(); // not required since policy is full dataset + return PerformGetAll(id).FirstOrDefault(); } protected override IEnumerable PerformGetAll(params int[] ids) diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MediaTypeRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MediaTypeRepository.cs index c6caa40750..818b5c9f21 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MediaTypeRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MediaTypeRepository.cs @@ -48,11 +48,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement protected override IMediaType PerformGet(string alias) => GetMany().FirstOrDefault(x => x.Alias.InvariantEquals(alias)); - protected override IEnumerable PerformGetAll(params int[] ids) + protected override IEnumerable GetAllWithFullCachePolicy() { - // the cache policy will always want everything - // even GetMany(ids) gets everything and filters afterwards - if (ids.Any()) throw new PanicException("There can be no ids specified"); return CommonRepository.GetAllTypes().OfType(); } diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MemberTypeRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MemberTypeRepository.cs index b9c40eebc9..059035a9bc 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MemberTypeRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MemberTypeRepository.cs @@ -59,11 +59,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement protected override IMemberType PerformGet(string alias) => GetMany().FirstOrDefault(x => x.Alias.InvariantEquals(alias)); - protected override IEnumerable PerformGetAll(params int[] ids) + protected override IEnumerable GetAllWithFullCachePolicy() { - // the cache policy will always want everything - // even GetMany(ids) gets everything and filters afterwards - if (ids.Any()) throw new PanicException("There can be no ids specified"); return CommonRepository.GetAllTypes().OfType(); } diff --git a/src/Umbraco.Infrastructure/PropertyEditors/PropertyEditorsComponent.cs b/src/Umbraco.Infrastructure/PropertyEditors/PropertyEditorsComponent.cs index de071d1a1e..cd7b7a1f39 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/PropertyEditorsComponent.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/PropertyEditorsComponent.cs @@ -1,7 +1,11 @@ -using System.Linq; +using System; +using System.Collections.Generic; +using System.Linq; using Umbraco.Core.Composing; +using Umbraco.Core.Events; using Umbraco.Core.Models; using Umbraco.Core.PropertyEditors; +using Umbraco.Core.Services; using Umbraco.Core.Services.Implement; namespace Umbraco.Web.PropertyEditors @@ -9,6 +13,7 @@ namespace Umbraco.Web.PropertyEditors public sealed class PropertyEditorsComponent : IComponent { private readonly PropertyEditorCollection _propertyEditors; + private readonly List _terminate = new List(); public PropertyEditorsComponent(PropertyEditorCollection propertyEditors) { @@ -27,32 +32,48 @@ namespace Umbraco.Web.PropertyEditors } public void Terminate() - { } - - private static void Initialize(FileUploadPropertyEditor fileUpload) { - MediaService.Saving += fileUpload.MediaServiceSaving; - ContentService.Copied += fileUpload.ContentServiceCopied; - - MediaService.Deleted += (sender, args) - => args.MediaFilesToDelete.AddRange(fileUpload.ServiceDeleted(args.DeletedEntities.Cast())); - ContentService.Deleted += (sender, args) - => args.MediaFilesToDelete.AddRange(fileUpload.ServiceDeleted(args.DeletedEntities.Cast())); - MemberService.Deleted += (sender, args) - => args.MediaFilesToDelete.AddRange(fileUpload.ServiceDeleted(args.DeletedEntities.Cast())); + foreach (var t in _terminate) t(); } - private static void Initialize(ImageCropperPropertyEditor imageCropper) + private void Initialize(FileUploadPropertyEditor fileUpload) + { + MediaService.Saving += fileUpload.MediaServiceSaving; + _terminate.Add(() => MediaService.Saving -= fileUpload.MediaServiceSaving); + ContentService.Copied += fileUpload.ContentServiceCopied; + _terminate.Add(() => ContentService.Copied -= fileUpload.ContentServiceCopied); + + void mediaServiceDeleted(IMediaService sender, DeleteEventArgs args) => args.MediaFilesToDelete.AddRange(fileUpload.ServiceDeleted(args.DeletedEntities.Cast())); + MediaService.Deleted += mediaServiceDeleted; + _terminate.Add(() => MediaService.Deleted -= mediaServiceDeleted); + + void contentServiceDeleted(IContentService sender, DeleteEventArgs args) => args.MediaFilesToDelete.AddRange(fileUpload.ServiceDeleted(args.DeletedEntities.Cast())); + ContentService.Deleted += contentServiceDeleted; + _terminate.Add(() => ContentService.Deleted -= contentServiceDeleted); + + void memberServiceDeleted(IMemberService sender, DeleteEventArgs args) => args.MediaFilesToDelete.AddRange(fileUpload.ServiceDeleted(args.DeletedEntities.Cast())); + MemberService.Deleted += memberServiceDeleted; + _terminate.Add(() => MemberService.Deleted -= memberServiceDeleted); + } + + private void Initialize(ImageCropperPropertyEditor imageCropper) { MediaService.Saving += imageCropper.MediaServiceSaving; + _terminate.Add(() => MediaService.Saving -= imageCropper.MediaServiceSaving); ContentService.Copied += imageCropper.ContentServiceCopied; + _terminate.Add(() => ContentService.Copied -= imageCropper.ContentServiceCopied); - MediaService.Deleted += (sender, args) - => args.MediaFilesToDelete.AddRange(imageCropper.ServiceDeleted(args.DeletedEntities.Cast())); - ContentService.Deleted += (sender, args) - => args.MediaFilesToDelete.AddRange(imageCropper.ServiceDeleted(args.DeletedEntities.Cast())); - MemberService.Deleted += (sender, args) - => args.MediaFilesToDelete.AddRange(imageCropper.ServiceDeleted(args.DeletedEntities.Cast())); + void mediaServiceDeleted(IMediaService sender, DeleteEventArgs args) => args.MediaFilesToDelete.AddRange(imageCropper.ServiceDeleted(args.DeletedEntities.Cast())); + MediaService.Deleted += mediaServiceDeleted; + _terminate.Add(() => MediaService.Deleted -= mediaServiceDeleted); + + void contentServiceDeleted(IContentService sender, DeleteEventArgs args) => args.MediaFilesToDelete.AddRange(imageCropper.ServiceDeleted(args.DeletedEntities.Cast())); + ContentService.Deleted += contentServiceDeleted; + _terminate.Add(() => ContentService.Deleted -= contentServiceDeleted); + + void memberServiceDeleted(IMemberService sender, DeleteEventArgs args) => args.MediaFilesToDelete.AddRange(imageCropper.ServiceDeleted(args.DeletedEntities.Cast())); + MemberService.Deleted += memberServiceDeleted; + _terminate.Add(() => MemberService.Deleted -= memberServiceDeleted); } } } diff --git a/src/Umbraco.Infrastructure/Routing/RedirectTrackingComponent.cs b/src/Umbraco.Infrastructure/Routing/RedirectTrackingComponent.cs index cd623b585a..0273b3d14a 100644 --- a/src/Umbraco.Infrastructure/Routing/RedirectTrackingComponent.cs +++ b/src/Umbraco.Infrastructure/Routing/RedirectTrackingComponent.cs @@ -58,7 +58,12 @@ namespace Umbraco.Web.Routing } public void Terminate() - { } + { + ContentService.Publishing -= ContentService_Publishing; + ContentService.Published -= ContentService_Published; + ContentService.Moving -= ContentService_Moving; + ContentService.Moved -= ContentService_Moved; + } private void ContentService_Publishing(IContentService sender, PublishEventArgs args) { diff --git a/src/Umbraco.Infrastructure/Runtime/CoreRuntime.cs b/src/Umbraco.Infrastructure/Runtime/CoreRuntime.cs index 9e1910d938..7d02152a57 100644 --- a/src/Umbraco.Infrastructure/Runtime/CoreRuntime.cs +++ b/src/Umbraco.Infrastructure/Runtime/CoreRuntime.cs @@ -7,6 +7,7 @@ using Umbraco.Core.Cache; using Umbraco.Core.Composing; using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.Models; +using Umbraco.Core.Events; using Umbraco.Core.Exceptions; using Umbraco.Core.Hosting; using Umbraco.Core.IO; @@ -25,12 +26,12 @@ namespace Umbraco.Core.Runtime { private ComponentCollection _components; private IFactory _factory; - private readonly RuntimeState _state; + // runtime state, this instance will get replaced again once the essential services are available to run the check + private RuntimeState _state = RuntimeState.Booting(); private readonly IUmbracoBootPermissionChecker _umbracoBootPermissionChecker; - private readonly IRequestCache _requestCache; + private readonly Configs _configs; private readonly GlobalSettings _globalSettings; private readonly ConnectionStrings _connectionStrings; - private readonly Configs _configs; public CoreRuntime( Configs configs, // TODO: remove this parameter with legacy configuraiton no longer needed in Umbraco.Web and Umbraco.Tests @@ -46,13 +47,14 @@ namespace Umbraco.Core.Runtime IDbProviderFactoryCreator dbProviderFactoryCreator, IMainDom mainDom, ITypeFinder typeFinder, - IRequestCache requestCache) + AppCaches appCaches) { _configs = configs; _globalSettings = globalSettings; _connectionStrings = connectionStrings; IOHelper = ioHelper; + AppCaches = appCaches; UmbracoVersion = umbracoVersion; Profiler = profiler; HostingEnvironment = hostingEnvironment; @@ -60,19 +62,14 @@ namespace Umbraco.Core.Runtime DbProviderFactoryCreator = dbProviderFactoryCreator; _umbracoBootPermissionChecker = umbracoBootPermissionChecker; - _requestCache = requestCache; Logger = logger; MainDom = mainDom; TypeFinder = typeFinder; - // runtime state - // beware! must use '() => _factory.GetInstance()' and NOT '_factory.GetInstance' - // as the second one captures the current value (null) and therefore fails - _state = new RuntimeState(_globalSettings, UmbracoVersion) - { - Level = RuntimeLevel.Boot - }; + _globalSettings = globalSettings; + _connectionStrings = connectionStrings; + } /// @@ -92,7 +89,7 @@ namespace Umbraco.Core.Runtime /// /// Gets the profiling logger. /// - protected IProfilingLogger ProfilingLogger { get; private set; } + public IProfilingLogger ProfilingLogger { get; private set; } /// /// Gets the @@ -105,7 +102,8 @@ namespace Umbraco.Core.Runtime protected IIOHelper IOHelper { get; } protected IHostingEnvironment HostingEnvironment { get; } - protected IUmbracoVersion UmbracoVersion { get; } + public AppCaches AppCaches { get; } + public IUmbracoVersion UmbracoVersion { get; } /// public IRuntimeState State => _state; @@ -168,31 +166,33 @@ namespace Umbraco.Core.Runtime try { - - // run handlers - RuntimeOptions.DoRuntimeBoot(ProfilingLogger); - - // application caches - var appCaches = GetAppCaches(); + OnRuntimeBoot(); // database factory - var databaseFactory = GetDatabaseFactory(); + var databaseFactory = CreateDatabaseFactory(); // type finder/loader - var typeLoader = new TypeLoader(TypeFinder, appCaches.RuntimeCache, new DirectoryInfo(HostingEnvironment.LocalTempPath), ProfilingLogger); + var typeLoader = new TypeLoader(TypeFinder, AppCaches.RuntimeCache, new DirectoryInfo(HostingEnvironment.LocalTempPath), ProfilingLogger); + + // re-create the state object with the essential services + _state = new RuntimeState(_globalSettings, UmbracoVersion, databaseFactory, Logger); // create the composition - composition = new Composition(register, typeLoader, ProfilingLogger, _state, _configs, IOHelper, appCaches); - composition.RegisterEssentials(Logger, Profiler, ProfilingLogger, MainDom, appCaches, databaseFactory, typeLoader, _state, TypeFinder, IOHelper, UmbracoVersion, DbProviderFactoryCreator, HostingEnvironment, BackOfficeInfo); + composition = new Composition(register, typeLoader, ProfilingLogger, _state, _configs, IOHelper, AppCaches); + + composition.RegisterEssentials(Logger, Profiler, ProfilingLogger, MainDom, AppCaches, databaseFactory, typeLoader, _state, TypeFinder, IOHelper, UmbracoVersion, DbProviderFactoryCreator, HostingEnvironment, BackOfficeInfo); // register ourselves (TODO: Should we put this in RegisterEssentials?) composition.Register(_ => this, Lifetime.Singleton); + // run handlers + OnRuntimeEssentials(composition, AppCaches, typeLoader, databaseFactory); + try { // determine our runtime level - DetermineRuntimeLevel(databaseFactory, ProfilingLogger); + DetermineRuntimeLevel(databaseFactory); } finally { @@ -247,9 +247,6 @@ namespace Umbraco.Core.Runtime // throws if not full-trust _umbracoBootPermissionChecker.ThrowIfNotPermissions(); - // run handlers - RuntimeOptions.DoRuntimeEssentials(_factory); - var hostingEnvironmentLifetime = _factory.TryGetInstance(); if (hostingEnvironmentLifetime == null) throw new InvalidOperationException($"An instance of {typeof(IApplicationShutdownRegistry)} could not be resolved from the container, ensure that one if registered in your runtime before calling {nameof(IRuntime)}.{nameof(Start)}"); @@ -261,7 +258,6 @@ namespace Umbraco.Core.Runtime _components = _factory.GetInstance(); _components.Initialize(); - // now (and only now) is the time to switch over to perWebRequest scopes. // up until that point we may not have a request, and scoped services would // fail to resolve - but we run Initialize within a factory scope - and then, @@ -317,31 +313,29 @@ namespace Umbraco.Core.Runtime } } - // internal for tests - internal void DetermineRuntimeLevel(IUmbracoDatabaseFactory databaseFactory, IProfilingLogger profilingLogger) + private void DetermineRuntimeLevel(IUmbracoDatabaseFactory databaseFactory) { - using (var timer = profilingLogger.DebugDuration("Determining runtime level.", "Determined.")) + using var timer = ProfilingLogger.DebugDuration("Determining runtime level.", "Determined."); + + try { - try - { - _state.DetermineRuntimeLevel(databaseFactory, profilingLogger); + _state.DetermineRuntimeLevel(); - profilingLogger.Debug("Runtime level: {RuntimeLevel} - {RuntimeLevelReason}", _state.Level, _state.Reason); + ProfilingLogger.Debug("Runtime level: {RuntimeLevel} - {RuntimeLevelReason}", _state.Level, _state.Reason); - if (_state.Level == RuntimeLevel.Upgrade) - { - profilingLogger.Debug("Configure database factory for upgrades."); - databaseFactory.ConfigureForUpgrade(); - } - } - catch + if (_state.Level == RuntimeLevel.Upgrade) { - _state.Level = RuntimeLevel.BootFailed; - _state.Reason = RuntimeLevelReason.BootFailedOnException; - timer?.Fail(); - throw; + ProfilingLogger.Debug("Configure database factory for upgrades."); + databaseFactory.ConfigureForUpgrade(); } } + catch + { + _state.Level = RuntimeLevel.BootFailed; + _state.Reason = RuntimeLevelReason.BootFailedOnException; + timer?.Fail(); + throw; + } } private IEnumerable ResolveComposerTypes(TypeLoader typeLoader) @@ -377,21 +371,6 @@ namespace Umbraco.Core.Runtime protected virtual IEnumerable GetComposerTypes(TypeLoader typeLoader) => typeLoader.GetTypes(); - /// - /// Gets the application caches. - /// - protected virtual AppCaches GetAppCaches() - { - // need the deep clone runtime cache provider to ensure entities are cached properly, ie - // are cloned in and cloned out - no request-based cache here since no web-based context, - // is overridden by the web runtime - - return new AppCaches( - new DeepCloneAppCache(new ObjectCacheAppCache()), - _requestCache, - new IsolatedCaches(type => new DeepCloneAppCache(new ObjectCacheAppCache()))); - } - /// /// Returns the application path of the site/solution /// @@ -404,14 +383,33 @@ namespace Umbraco.Core.Runtime => null; /// - /// Gets the database factory. + /// Creates the database factory. /// /// This is strictly internal, for tests only. - protected internal virtual IUmbracoDatabaseFactory GetDatabaseFactory() + protected internal virtual IUmbracoDatabaseFactory CreateDatabaseFactory() => new UmbracoDatabaseFactory(Logger, Options.Create(_globalSettings), Options.Create(_connectionStrings), new Lazy(() => _factory.GetInstance()), DbProviderFactoryCreator); #endregion + #region Events + + protected void OnRuntimeBoot() + { + RuntimeOptions.DoRuntimeBoot(ProfilingLogger); + RuntimeBooting?.Invoke(this, ProfilingLogger); + } + + protected void OnRuntimeEssentials(Composition composition, AppCaches appCaches, TypeLoader typeLoader, IUmbracoDatabaseFactory databaseFactory) + { + RuntimeOptions.DoRuntimeEssentials(composition, appCaches, typeLoader, databaseFactory); + RuntimeEssentials?.Invoke(this, new RuntimeEssentialsEventArgs(composition, appCaches, typeLoader, databaseFactory)); + } + + public event TypedEventHandler RuntimeBooting; + public event TypedEventHandler RuntimeEssentials; + + #endregion + } } diff --git a/src/Umbraco.Infrastructure/Runtime/RuntimeEssentialsEventArgs.cs b/src/Umbraco.Infrastructure/Runtime/RuntimeEssentialsEventArgs.cs new file mode 100644 index 0000000000..78d068cb9c --- /dev/null +++ b/src/Umbraco.Infrastructure/Runtime/RuntimeEssentialsEventArgs.cs @@ -0,0 +1,23 @@ +using System; +using Umbraco.Core.Cache; +using Umbraco.Core.Composing; +using Umbraco.Core.Persistence; + +namespace Umbraco.Core.Runtime +{ + public class RuntimeEssentialsEventArgs : EventArgs + { + public RuntimeEssentialsEventArgs(Composition composition, AppCaches appCaches, TypeLoader typeLoader, IUmbracoDatabaseFactory databaseFactory) + { + Composition = composition; + AppCaches = appCaches; + TypeLoader = typeLoader; + DatabaseFactory = databaseFactory; + } + + public Composition Composition { get; } + public AppCaches AppCaches { get; } + public TypeLoader TypeLoader { get; } + public IUmbracoDatabaseFactory DatabaseFactory { get; } + } +} diff --git a/src/Umbraco.Infrastructure/RuntimeOptions.cs b/src/Umbraco.Infrastructure/RuntimeOptions.cs index 562a7e3c5c..23abd474a4 100644 --- a/src/Umbraco.Infrastructure/RuntimeOptions.cs +++ b/src/Umbraco.Infrastructure/RuntimeOptions.cs @@ -16,7 +16,7 @@ namespace Umbraco.Core public static class RuntimeOptions { private static List> _onBoot; - private static List> _onEssentials; + private static List> _onEssentials; /// /// Executes the RuntimeBoot handlers. @@ -33,13 +33,13 @@ namespace Umbraco.Core /// /// Executes the RuntimeEssentials handlers. /// - internal static void DoRuntimeEssentials(IFactory factory) + internal static void DoRuntimeEssentials(Composition composition, AppCaches appCaches, TypeLoader typeLoader, IUmbracoDatabaseFactory databaseFactory) { if (_onEssentials== null) return; foreach (var action in _onEssentials) - action(factory); + action(composition, appCaches, typeLoader, databaseFactory); } /// @@ -64,10 +64,10 @@ namespace Umbraco.Core /// essential things (AppCaches, a TypeLoader, and a database factory) but /// before anything else. /// - public static void OnRuntimeEssentials(Action action) + public static void OnRuntimeEssentials(Action action) { if (_onEssentials == null) - _onEssentials = new List>(); + _onEssentials = new List>(); _onEssentials.Add(action); } } diff --git a/src/Umbraco.Infrastructure/RuntimeState.cs b/src/Umbraco.Infrastructure/RuntimeState.cs index 832cdc7605..4adf9fdf7c 100644 --- a/src/Umbraco.Infrastructure/RuntimeState.cs +++ b/src/Umbraco.Infrastructure/RuntimeState.cs @@ -1,11 +1,9 @@ using System; using System.Threading; using Semver; -using Umbraco.Core.Collections; using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.Models; using Umbraco.Core.Exceptions; -using Umbraco.Core.Hosting; using Umbraco.Core.Logging; using Umbraco.Core.Migrations.Upgrade; using Umbraco.Core.Persistence; @@ -19,14 +17,27 @@ namespace Umbraco.Core { private readonly GlobalSettings _globalSettings; private readonly IUmbracoVersion _umbracoVersion; + private readonly IUmbracoDatabaseFactory _databaseFactory; + private readonly ILogger _logger; + + /// + /// The initial + /// + public static RuntimeState Booting() => new RuntimeState() { Level = RuntimeLevel.Boot }; + + private RuntimeState() + { + } /// /// Initializes a new instance of the class. /// - public RuntimeState(GlobalSettings globalSettings, IUmbracoVersion umbracoVersion) + public RuntimeState(GlobalSettings globalSettings, IUmbracoVersion umbracoVersion, IUmbracoDatabaseFactory databaseFactory, ILogger logger) { _globalSettings = globalSettings; _umbracoVersion = umbracoVersion; + _databaseFactory = databaseFactory; + _logger = logger; } @@ -54,16 +65,14 @@ namespace Umbraco.Core /// public BootFailedException BootFailedException { get; internal set; } - /// - /// Determines the runtime level. - /// - public void DetermineRuntimeLevel(IUmbracoDatabaseFactory databaseFactory, ILogger logger) + /// + public void DetermineRuntimeLevel() { - if (databaseFactory.Configured == false) + if (_databaseFactory.Configured == false) { // local version *does* match code version, but the database is not configured // install - may happen with Deploy/Cloud/etc - logger.Debug("Database is not configured, need to install Umbraco."); + _logger.Debug("Database is not configured, need to install Umbraco."); Level = RuntimeLevel.Install; Reason = RuntimeLevelReason.InstallNoDatabase; return; @@ -76,16 +85,16 @@ namespace Umbraco.Core var tries = _globalSettings.InstallMissingDatabase ? 2 : 5; for (var i = 0;;) { - connect = databaseFactory.CanConnect; + connect = _databaseFactory.CanConnect; if (connect || ++i == tries) break; - logger.Debug("Could not immediately connect to database, trying again."); + _logger.Debug("Could not immediately connect to database, trying again."); Thread.Sleep(1000); } if (connect == false) { // cannot connect to configured database, this is bad, fail - logger.Debug("Could not connect to database."); + _logger.Debug("Could not connect to database."); if (_globalSettings.InstallMissingDatabase) { @@ -109,12 +118,12 @@ namespace Umbraco.Core bool noUpgrade; try { - noUpgrade = EnsureUmbracoUpgradeState(databaseFactory, logger); + noUpgrade = EnsureUmbracoUpgradeState(_databaseFactory, _logger); } catch (Exception e) { // can connect to the database but cannot check the upgrade state... oops - logger.Warn(e, "Could not check the upgrade state."); + _logger.Warn(e, "Could not check the upgrade state."); if (_globalSettings.InstallEmptyDatabase) { @@ -146,14 +155,14 @@ namespace Umbraco.Core // although the files version matches the code version, the database version does not // which means the local files have been upgraded but not the database - need to upgrade - logger.Debug("Has not reached the final upgrade step, need to upgrade Umbraco."); + _logger.Debug("Has not reached the final upgrade step, need to upgrade Umbraco."); Level = RuntimeLevel.Upgrade; Reason = RuntimeLevelReason.UpgradeMigrations; } private bool EnsureUmbracoUpgradeState(IUmbracoDatabaseFactory databaseFactory, ILogger logger) { - var upgrader = new Upgrader(new UmbracoPlan(_umbracoVersion, _globalSettings)); + var upgrader = new Upgrader(new UmbracoPlan(_umbracoVersion)); var stateValueKey = upgrader.StateValueKey; // no scope, no service - just directly accessing the database diff --git a/src/Umbraco.Infrastructure/Scheduling/SchedulerComponent.cs b/src/Umbraco.Infrastructure/Scheduling/SchedulerComponent.cs index 8aabaf0b57..cbf1df197d 100644 --- a/src/Umbraco.Infrastructure/Scheduling/SchedulerComponent.cs +++ b/src/Umbraco.Infrastructure/Scheduling/SchedulerComponent.cs @@ -18,7 +18,7 @@ using Umbraco.Web.Routing; namespace Umbraco.Web.Scheduling { - internal sealed class SchedulerComponent : IComponent + public sealed class SchedulerComponent : IComponent { private const int DefaultDelayMilliseconds = 180000; // 3 mins private const int OneMinuteMilliseconds = 60000; diff --git a/src/Umbraco.Infrastructure/Search/BackgroundIndexRebuilder.cs b/src/Umbraco.Infrastructure/Search/BackgroundIndexRebuilder.cs index 1946e2041b..d18480eb82 100644 --- a/src/Umbraco.Infrastructure/Search/BackgroundIndexRebuilder.cs +++ b/src/Umbraco.Infrastructure/Search/BackgroundIndexRebuilder.cs @@ -12,7 +12,7 @@ namespace Umbraco.Web.Search /// /// Utility to rebuild all indexes on a background thread /// - public sealed class BackgroundIndexRebuilder + public class BackgroundIndexRebuilder { private static readonly object RebuildLocker = new object(); private readonly IndexRebuilder _indexRebuilder; @@ -34,7 +34,7 @@ namespace Umbraco.Web.Search /// /// /// - public void RebuildIndexes(bool onlyEmptyIndexes, int waitMilliseconds = 0) + public virtual void RebuildIndexes(bool onlyEmptyIndexes, int waitMilliseconds = 0) { // TODO: need a way to disable rebuilding on startup diff --git a/src/Umbraco.Infrastructure/Search/ExamineComponent.cs b/src/Umbraco.Infrastructure/Search/ExamineComponent.cs index 35a8804ecb..ae5bfd628b 100644 --- a/src/Umbraco.Infrastructure/Search/ExamineComponent.cs +++ b/src/Umbraco.Infrastructure/Search/ExamineComponent.cs @@ -24,7 +24,6 @@ namespace Umbraco.Web.Search private readonly IValueSetBuilder _mediaValueSetBuilder; private readonly IValueSetBuilder _memberValueSetBuilder; private readonly BackgroundIndexRebuilder _backgroundIndexRebuilder; - private static object _isConfiguredLocker = new object(); private readonly IScopeProvider _scopeProvider; private readonly ServiceContext _services; private readonly IMainDom _mainDom; @@ -104,7 +103,13 @@ namespace Umbraco.Web.Search } public void Terminate() - { } + { + ContentCacheRefresher.CacheUpdated -= ContentCacheRefresherUpdated; + ContentTypeCacheRefresher.CacheUpdated -= ContentTypeCacheRefresherUpdated; + MediaCacheRefresher.CacheUpdated -= MediaCacheRefresherUpdated; + MemberCacheRefresher.CacheUpdated -= MemberCacheRefresherUpdated; + LanguageCacheRefresher.CacheUpdated -= LanguageCacheRefresherUpdated; + } #region Cache refresher updated event handlers diff --git a/src/Umbraco.ModelsBuilder.Embedded/Compose/ModelsBuilderComponent.cs b/src/Umbraco.ModelsBuilder.Embedded/Compose/ModelsBuilderComponent.cs index 32cfd3057e..30754d3a7f 100644 --- a/src/Umbraco.ModelsBuilder.Embedded/Compose/ModelsBuilderComponent.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/Compose/ModelsBuilderComponent.cs @@ -53,32 +53,38 @@ namespace Umbraco.ModelsBuilder.Embedded.Compose } public void Terminate() - { } + { + ServerVariablesParser.Parsing -= ServerVariablesParser_Parsing; + ContentModelBinder.ModelBindingException -= ContentModelBinder_ModelBindingException; + FileService.SavingTemplate -= FileService_SavingTemplate; + } private void InstallServerVars() { // register our url - for the backoffice api - ServerVariablesParser.Parsing += (sender, serverVars) => - { - if (!serverVars.ContainsKey("umbracoUrls")) - throw new ArgumentException("Missing umbracoUrls."); - var umbracoUrlsObject = serverVars["umbracoUrls"]; - if (umbracoUrlsObject == null) - throw new ArgumentException("Null umbracoUrls"); - if (!(umbracoUrlsObject is Dictionary umbracoUrls)) - throw new ArgumentException("Invalid umbracoUrls"); + ServerVariablesParser.Parsing += ServerVariablesParser_Parsing; + } - if (!serverVars.ContainsKey("umbracoPlugins")) - throw new ArgumentException("Missing umbracoPlugins."); - if (!(serverVars["umbracoPlugins"] is Dictionary umbracoPlugins)) - throw new ArgumentException("Invalid umbracoPlugins"); + private void ServerVariablesParser_Parsing(object sender, Dictionary serverVars) + { + if (!serverVars.ContainsKey("umbracoUrls")) + throw new ArgumentException("Missing umbracoUrls."); + var umbracoUrlsObject = serverVars["umbracoUrls"]; + if (umbracoUrlsObject == null) + throw new ArgumentException("Null umbracoUrls"); + if (!(umbracoUrlsObject is Dictionary umbracoUrls)) + throw new ArgumentException("Invalid umbracoUrls"); - if (HttpContext.Current == null) throw new InvalidOperationException("HttpContext is null"); - var urlHelper = new UrlHelper(new RequestContext(new HttpContextWrapper(HttpContext.Current), new RouteData())); + if (!serverVars.ContainsKey("umbracoPlugins")) + throw new ArgumentException("Missing umbracoPlugins."); + if (!(serverVars["umbracoPlugins"] is Dictionary umbracoPlugins)) + throw new ArgumentException("Invalid umbracoPlugins"); - umbracoUrls["modelsBuilderBaseUrl"] = urlHelper.GetUmbracoApiServiceBaseUrl(controller => controller.BuildModels()); - umbracoPlugins["modelsBuilder"] = GetModelsBuilderSettings(); - }; + if (HttpContext.Current == null) throw new InvalidOperationException("HttpContext is null"); + var urlHelper = new UrlHelper(new RequestContext(new HttpContextWrapper(HttpContext.Current), new RouteData())); + + umbracoUrls["modelsBuilderBaseUrl"] = urlHelper.GetUmbracoApiServiceBaseUrl(controller => controller.BuildModels()); + umbracoPlugins["modelsBuilder"] = GetModelsBuilderSettings(); } private Dictionary GetModelsBuilderSettings() diff --git a/src/Umbraco.Tests.Common/TestHelperBase.cs b/src/Umbraco.Tests.Common/TestHelperBase.cs index df5f67a206..803b6c0cae 100644 --- a/src/Umbraco.Tests.Common/TestHelperBase.cs +++ b/src/Umbraco.Tests.Common/TestHelperBase.cs @@ -48,14 +48,6 @@ namespace Umbraco.Tests.Common public Configs GetConfigs() => GetConfigsFactory().Create(); - public IRuntimeState GetRuntimeState() - { - var globalSettings = new GlobalSettingsBuilder().Build(); - return new RuntimeState( - globalSettings, - GetUmbracoVersion()); - } - public abstract IBackOfficeInfo GetBackOfficeInfo(); public IConfigsFactory GetConfigsFactory() => new ConfigsFactory(); diff --git a/src/Umbraco.Tests.Integration/ContainerTests.cs b/src/Umbraco.Tests.Integration/ContainerTests.cs index 2098b7241e..3ba3c31d03 100644 --- a/src/Umbraco.Tests.Integration/ContainerTests.cs +++ b/src/Umbraco.Tests.Integration/ContainerTests.cs @@ -83,7 +83,7 @@ namespace Umbraco.Tests.Integration // it means the container won't be disposed, and maybe other services? not sure. // In cases where we use it can we use IConfigureOptions? https://andrewlock.net/access-services-inside-options-and-startup-using-configureoptions/ - var umbracoContainer = UmbracoIntegrationTest.GetUmbracoContainer(out var serviceProviderFactory); + var umbracoContainer = UmbracoIntegrationTest.CreateUmbracoContainer(out var serviceProviderFactory); IHostApplicationLifetime lifetime1 = null; diff --git a/src/Umbraco.Tests.Integration/Extensions/ApplicationBuilderExtensions.cs b/src/Umbraco.Tests.Integration/Extensions/ApplicationBuilderExtensions.cs deleted file mode 100644 index 96ac3d603c..0000000000 --- a/src/Umbraco.Tests.Integration/Extensions/ApplicationBuilderExtensions.cs +++ /dev/null @@ -1,128 +0,0 @@ -using System; -using System.Data.Common; -using System.Data.SqlClient; -using System.IO; -using Microsoft.AspNetCore.Builder; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Options; -using NUnit.Framework; -using Umbraco.Core; -using Umbraco.Core.Configuration; -using Umbraco.Core.Configuration.Models; -using Umbraco.Core.Logging; -using Umbraco.Core.Persistence; -using Umbraco.Tests.Integration.Testing; -using Umbraco.Tests.Testing; - -namespace Umbraco.Tests.Integration.Extensions -{ - public static class ApplicationBuilderExtensions - { - /// - /// Creates a LocalDb instance to use for the test - /// - /// - /// - /// - /// - /// - public static IApplicationBuilder UseTestLocalDb(this IApplicationBuilder app, - string workingDirectory, - UmbracoIntegrationTest integrationTest, out string connectionString) - { - connectionString = null; - var dbFilePath = Path.Combine(workingDirectory, "LocalDb"); - - // get the currently set db options - var testOptions = TestOptionAttributeBase.GetTestOptions(); - - if (testOptions.Database == UmbracoTestOptions.Database.None) - return app; - - // need to manually register this factory - DbProviderFactories.RegisterFactory(Constants.DbProviderNames.SqlServer, SqlClientFactory.Instance); - - if (!Directory.Exists(dbFilePath)) - Directory.CreateDirectory(dbFilePath); - - var db = UmbracoIntegrationTest.GetOrCreate(dbFilePath, - app.ApplicationServices.GetRequiredService(), - app.ApplicationServices.GetRequiredService>().Value, - app.ApplicationServices.GetRequiredService()); - - - switch (testOptions.Database) - { - case UmbracoTestOptions.Database.NewSchemaPerTest: - - // New DB + Schema - var newSchemaDbId = db.AttachSchema(); - - // Add teardown callback - integrationTest.OnTestTearDown(() => db.Detach(newSchemaDbId)); - - // We must re-configure our current factory since attaching a new LocalDb from the pool changes connection strings - var dbFactory = app.ApplicationServices.GetRequiredService(); - if (!dbFactory.Configured) - { - dbFactory.Configure(db.ConnectionString, Constants.DatabaseProviders.SqlServer); - } - - // In the case that we've initialized the schema, it means that we are installed so we'll want to ensure that - // the runtime state is configured correctly so we'll force update the configuration flag and re-run the - // runtime state checker. - // TODO: This wouldn't be required if we don't store the Umbraco version in config - - // right now we are an an 'Install' state - var runtimeState = (RuntimeState)app.ApplicationServices.GetRequiredService(); - Assert.AreEqual(RuntimeLevel.Install, runtimeState.Level); - - // dynamically change the config status - var umbVersion = app.ApplicationServices.GetRequiredService(); - var config = app.ApplicationServices.GetRequiredService(); - config[Constants.Configuration.ConfigGlobalPrefix + "ConfigurationStatus"] = umbVersion.SemanticVersion.ToString(); - - // re-run the runtime level check - var profilingLogger = app.ApplicationServices.GetRequiredService(); - runtimeState.DetermineRuntimeLevel(dbFactory, profilingLogger); - - Assert.AreEqual(RuntimeLevel.Run, runtimeState.Level); - - break; - case UmbracoTestOptions.Database.NewEmptyPerTest: - - var newEmptyDbId = db.AttachEmpty(); - - // Add teardown callback - integrationTest.OnTestTearDown(() => db.Detach(newEmptyDbId)); - - - break; - case UmbracoTestOptions.Database.NewSchemaPerFixture: - - // New DB + Schema - var newSchemaFixtureDbId = db.AttachSchema(); - - // Add teardown callback - integrationTest.OnFixtureTearDown(() => db.Detach(newSchemaFixtureDbId)); - - break; - case UmbracoTestOptions.Database.NewEmptyPerFixture: - - throw new NotImplementedException(); - - //// Add teardown callback - //integrationTest.OnFixtureTearDown(() => db.Detach()); - - break; - default: - throw new ArgumentOutOfRangeException(nameof(testOptions), testOptions, null); - } - connectionString = db.ConnectionString; - return app; - } - } - - -} diff --git a/src/Umbraco.Tests.Integration/Implementations/TestHelper.cs b/src/Umbraco.Tests.Integration/Implementations/TestHelper.cs index fc7974f8f3..0e46941904 100644 --- a/src/Umbraco.Tests.Integration/Implementations/TestHelper.cs +++ b/src/Umbraco.Tests.Integration/Implementations/TestHelper.cs @@ -21,6 +21,7 @@ using Umbraco.Tests.Common; using Umbraco.Tests.Common.Builders; using Umbraco.Web.Common.AspNetCore; using IHostingEnvironment = Umbraco.Core.Hosting.IHostingEnvironment; +using Microsoft.Extensions.FileProviders; namespace Umbraco.Tests.Integration.Implementations { @@ -41,11 +42,17 @@ namespace Umbraco.Tests.Integration.Implementations _httpContextAccessor = Mock.Of(x => x.HttpContext == httpContext); _ipResolver = new AspNetCoreIpResolver(_httpContextAccessor); + var contentRoot = Assembly.GetExecutingAssembly().GetRootDirectorySafe(); var hostEnvironment = new Mock(); - hostEnvironment.Setup(x => x.ApplicationName).Returns("UmbracoIntegrationTests"); - hostEnvironment.Setup(x => x.ContentRootPath) - .Returns(() => Assembly.GetExecutingAssembly().GetRootDirectorySafe()); + // this must be the assembly name for the WebApplicationFactory to work + hostEnvironment.Setup(x => x.ApplicationName).Returns(GetType().Assembly.GetName().Name); + hostEnvironment.Setup(x => x.ContentRootPath).Returns(() => contentRoot); + hostEnvironment.Setup(x => x.ContentRootFileProvider).Returns(() => new PhysicalFileProvider(contentRoot)); hostEnvironment.Setup(x => x.WebRootPath).Returns(() => WorkingDirectory); + hostEnvironment.Setup(x => x.WebRootFileProvider).Returns(() => new PhysicalFileProvider(WorkingDirectory)); + // we also need to expose it as the obsolete interface since netcore's WebApplicationFactory casts it + hostEnvironment.As(); + _hostEnvironment = hostEnvironment.Object; _hostingLifetime = new AspNetCoreApplicationShutdownRegistry(Mock.Of()); diff --git a/src/Umbraco.Tests.Integration/RuntimeTests.cs b/src/Umbraco.Tests.Integration/RuntimeTests.cs index 2766f4b044..c081caedc1 100644 --- a/src/Umbraco.Tests.Integration/RuntimeTests.cs +++ b/src/Umbraco.Tests.Integration/RuntimeTests.cs @@ -64,7 +64,7 @@ namespace Umbraco.Tests.Integration var coreRuntime = new CoreRuntime(configs, globalSettings, connectionStrings, testHelper.GetUmbracoVersion(), testHelper.IOHelper, testHelper.Logger, testHelper.Profiler, testHelper.UmbracoBootPermissionChecker, testHelper.GetHostingEnvironment(), testHelper.GetBackOfficeInfo(), testHelper.DbProviderFactoryCreator, - testHelper.MainDom, testHelper.GetTypeFinder(), NoAppCache.Instance); + testHelper.MainDom, testHelper.GetTypeFinder(), AppCaches.NoCache); // boot it! var factory = coreRuntime.Configure(umbracoContainer); @@ -111,7 +111,7 @@ namespace Umbraco.Tests.Integration [Test] public async Task AddUmbracoCore() { - var umbracoContainer = UmbracoIntegrationTest.GetUmbracoContainer(out var serviceProviderFactory); + var umbracoContainer = UmbracoIntegrationTest.CreateUmbracoContainer(out var serviceProviderFactory); var testHelper = new TestHelper(); var hostBuilder = new HostBuilder() @@ -124,7 +124,7 @@ namespace Umbraco.Tests.Integration // Add it! services.AddUmbracoConfiguration(hostContext.Configuration); - services.AddUmbracoCore(webHostEnvironment, umbracoContainer, GetType().Assembly, NoAppCache.Instance, testHelper.GetLoggingConfiguration(), out _); + services.AddUmbracoCore(webHostEnvironment, umbracoContainer, GetType().Assembly, AppCaches.NoCache, testHelper.GetLoggingConfiguration(), out _); }); var host = await hostBuilder.StartAsync(); @@ -151,7 +151,7 @@ namespace Umbraco.Tests.Integration [Test] public async Task UseUmbracoCore() { - var umbracoContainer = UmbracoIntegrationTest.GetUmbracoContainer(out var serviceProviderFactory); + var umbracoContainer = UmbracoIntegrationTest.CreateUmbracoContainer(out var serviceProviderFactory); var testHelper = new TestHelper(); var hostBuilder = new HostBuilder() @@ -164,7 +164,7 @@ namespace Umbraco.Tests.Integration // Add it! services.AddUmbracoConfiguration(hostContext.Configuration); - services.AddUmbracoCore(webHostEnvironment, umbracoContainer, GetType().Assembly, NoAppCache.Instance, testHelper.GetLoggingConfiguration(), out _); + services.AddUmbracoCore(webHostEnvironment, umbracoContainer, GetType().Assembly, AppCaches.NoCache, testHelper.GetLoggingConfiguration(), out _); }); var host = await hostBuilder.StartAsync(); diff --git a/src/Umbraco.Tests.Integration/TestServerTest/Controllers/ContentControllerTests.cs b/src/Umbraco.Tests.Integration/TestServerTest/Controllers/ContentControllerTests.cs index a9a272ac59..9a1b335c62 100644 --- a/src/Umbraco.Tests.Integration/TestServerTest/Controllers/ContentControllerTests.cs +++ b/src/Umbraco.Tests.Integration/TestServerTest/Controllers/ContentControllerTests.cs @@ -18,7 +18,6 @@ using Umbraco.Web.Models.ContentEditing; namespace Umbraco.Tests.Integration.TestServerTest.Controllers { [TestFixture] - [Explicit("Need to figure out whats wrong with these tests when executed all in one run.")] public class ContentControllerTests : UmbracoTestServerTestBase { diff --git a/src/Umbraco.Tests.Integration/TestServerTest/Controllers/UsersControllerTests.cs b/src/Umbraco.Tests.Integration/TestServerTest/Controllers/UsersControllerTests.cs index e2d66373ec..c001eb2d92 100644 --- a/src/Umbraco.Tests.Integration/TestServerTest/Controllers/UsersControllerTests.cs +++ b/src/Umbraco.Tests.Integration/TestServerTest/Controllers/UsersControllerTests.cs @@ -149,7 +149,7 @@ namespace Umbraco.Tests.Integration.TestServerTest.Controllers } [Test] - public async Task PostUnlockUsers_When_User_Does_Not_Exist_Expect_InvalidOperationException() + public async Task PostUnlockUsers_When_User_Does_Not_Exist_Expect_Zero_Users_Message() { var userId = 42; // Must not exist var url = PrepareUrl(x => x.PostUnlockUsers(new []{userId})); @@ -158,18 +158,15 @@ namespace Umbraco.Tests.Integration.TestServerTest.Controllers var response = await Client.PostAsync(url, new StringContent(string.Empty)); var body = await response.Content.ReadAsStringAsync(); body = body.TrimStart(AngularJsonMediaTypeFormatter.XsrfPrefix); - Assert.AreEqual(HttpStatusCode.InternalServerError, response.StatusCode); + Assert.AreEqual(HttpStatusCode.OK, response.StatusCode); - var actual = JsonConvert.DeserializeObject(body, new JsonSerializerSettings + var actual = JsonConvert.DeserializeObject(body, new JsonSerializerSettings { ContractResolver = new IgnoreRequiredAttributesResolver() }); Assert.Multiple(() => { - var expected = new InvalidOperationException(); - Assert.IsNotNull(actual); - Assert.AreEqual(expected.GetType(), actual.ExceptionType); - Assert.AreEqual(expected.Message, actual.ExceptionMessage); + Assert.AreEqual($"Unlocked 0 users", actual.Message); }); } @@ -230,8 +227,6 @@ namespace Umbraco.Tests.Integration.TestServerTest.Controllers userService.Save(user); } - - var url = PrepareUrl(x => x.PostUnlockUsers(users.Select(x=>x.Id).ToArray())); // Act diff --git a/src/Umbraco.Tests.Integration/TestServerTest/TestAuthHandler.cs b/src/Umbraco.Tests.Integration/TestServerTest/TestAuthHandler.cs new file mode 100644 index 0000000000..08cd49bd81 --- /dev/null +++ b/src/Umbraco.Tests.Integration/TestServerTest/TestAuthHandler.cs @@ -0,0 +1,40 @@ +using System.Text.Encodings.Web; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authentication; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Umbraco.Core; +using Umbraco.Core.BackOffice; +using Umbraco.Core.Mapping; +using Umbraco.Core.Models.Membership; +using Umbraco.Core.Services; +using Umbraco.Web.Common.Security; + +namespace Umbraco.Tests.Integration.TestServerTest +{ + public class TestAuthHandler : AuthenticationHandler + { + private readonly BackOfficeSignInManager _backOfficeSignInManager; + + private readonly BackOfficeIdentityUser _fakeUser; + public TestAuthHandler(IOptionsMonitor options, + ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock, BackOfficeSignInManager backOfficeSignInManager, IUserService userService, UmbracoMapper umbracoMapper) + : base(options, logger, encoder, clock) + { + _backOfficeSignInManager = backOfficeSignInManager; + + var user = userService.GetUserById(Constants.Security.SuperUserId); + _fakeUser = umbracoMapper.Map(user); + _fakeUser.SecurityStamp = "Needed"; + } + + protected override async Task HandleAuthenticateAsync() + { + + var principal = await _backOfficeSignInManager.CreateUserPrincipalAsync(_fakeUser); + var ticket = new AuthenticationTicket(principal, "Test"); + + return AuthenticateResult.Success(ticket); + } + } +} diff --git a/src/Umbraco.Tests.Integration/TestServerTest/UmbracoBuilderExtensions.cs b/src/Umbraco.Tests.Integration/TestServerTest/UmbracoBuilderExtensions.cs new file mode 100644 index 0000000000..49c4f224ee --- /dev/null +++ b/src/Umbraco.Tests.Integration/TestServerTest/UmbracoBuilderExtensions.cs @@ -0,0 +1,57 @@ +using System; +using Umbraco.Core.Cache; +using Umbraco.Core.Composing.LightInject; +using Umbraco.Core.Configuration.Models; +using Umbraco.Core.Runtime; +using Umbraco.Extensions; +using Umbraco.Tests.Integration.Implementations; +using Umbraco.Tests.Integration.Testing; +using Umbraco.Web.Common.Builder; + +namespace Umbraco.Tests.Integration.TestServerTest +{ + public static class UmbracoBuilderExtensions + { + /// + /// Uses a test version of Umbraco Core with a test IRuntime + /// + /// + /// + public static IUmbracoBuilder WithTestCore(this IUmbracoBuilder builder, TestHelper testHelper, LightInjectContainer container, + Action dbInstallEventHandler) + { + return builder.AddWith(nameof(global::Umbraco.Web.Common.Builder.UmbracoBuilderExtensions.WithCore), + () => + { + builder.Services.AddUmbracoCore( + builder.WebHostEnvironment, + container, + typeof(UmbracoBuilderExtensions).Assembly, + AppCaches.NoCache, // Disable caches in integration tests + testHelper.GetLoggingConfiguration(), + // TODO: Yep that's extremely ugly + (configs, globalSettings, connectionStrings, umbVersion, ioHelper, logger, profiler, hostingEnv, backOfficeInfo, typeFinder, appCaches, dbProviderFactoryCreator) => + { + var runtime = UmbracoIntegrationTest.CreateTestRuntime( + configs, + globalSettings, + connectionStrings, + umbVersion, + ioHelper, + logger, + profiler, + hostingEnv, + backOfficeInfo, + typeFinder, + appCaches, + dbProviderFactoryCreator, + testHelper.MainDom, // SimpleMainDom + dbInstallEventHandler); // DB Installation event handler + + return runtime; + }, + out _); + }); + } + } +} diff --git a/src/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs b/src/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs index b666cc40f2..08be22c07b 100644 --- a/src/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs +++ b/src/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs @@ -2,20 +2,26 @@ using System; using System.Linq.Expressions; using System.Net.Http; -using System.Reflection; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.Testing; using Microsoft.AspNetCore.Routing; +using Microsoft.AspNetCore.TestHost; using Microsoft.Extensions.DependencyInjection; using NUnit.Framework; using Umbraco.Composing; +using Umbraco.Core; using Umbraco.Extensions; using Umbraco.Tests.Integration.Testing; using Umbraco.Tests.Testing; using Umbraco.Web; +using Umbraco.Web.Common.Builder; using Umbraco.Web.Common.Controllers; using Umbraco.Web.Editors; - +using Microsoft.Extensions.Hosting; namespace Umbraco.Tests.Integration.TestServerTest { @@ -24,19 +30,55 @@ namespace Umbraco.Tests.Integration.TestServerTest public abstract class UmbracoTestServerTestBase : UmbracoIntegrationTest { [SetUp] - public void SetUp() + public override Task Setup() { - Factory = new UmbracoWebApplicationFactory(TestDBConnectionString); - Client = Factory.CreateClient(new WebApplicationFactoryClientOptions(){ + InMemoryConfiguration["ConnectionStrings:" + Constants.System.UmbracoConnectionName] = null; + InMemoryConfiguration["Umbraco:CMS:Hosting:Debug"] = "true"; + + // create new WebApplicationFactory specifying 'this' as the IStartup instance + var factory = new UmbracoWebApplicationFactory(CreateHostBuilder); + + // additional host configuration for web server integration tests + Factory = factory.WithWebHostBuilder(builder => + { + // Executes after the standard ConfigureServices method + builder.ConfigureTestServices(services => + { + services.AddAuthentication("Test").AddScheme("Test", options => { }); + }); + }); + + Client = Factory.CreateClient(new WebApplicationFactoryClientOptions + { AllowAutoRedirect = false }); + LinkGenerator = Factory.Services.GetRequiredService(); + + return Task.CompletedTask; } - /// - /// Get the service from the underlying container that is also used by the . - /// - protected T GetRequiredService() => Factory.Services.GetRequiredService(); + public override IHostBuilder CreateHostBuilder() + { + var builder = base.CreateHostBuilder(); + builder.ConfigureWebHost(builder => + { + // need to configure the IWebHostEnvironment too + builder.ConfigureServices((c, s) => + { + c.HostingEnvironment = TestHelper.GetWebHostEnvironment(); + }); + + // call startup + builder.Configure(app => + { + Services = app.ApplicationServices; + Configure(app); + }); + }).UseEnvironment(Environments.Development); + + return builder; + } /// /// Prepare a url before using . @@ -67,13 +109,14 @@ namespace Umbraco.Tests.Integration.TestServerTest return url; } - protected HttpClient Client { get; set; } - protected LinkGenerator LinkGenerator { get; set; } - protected UmbracoWebApplicationFactory Factory { get; set; } + protected HttpClient Client { get; private set; } + protected LinkGenerator LinkGenerator { get; private set; } + protected WebApplicationFactory Factory { get; private set; } [TearDown] - public void TearDown() + public override void TearDown() { + base.TearDown(); Factory.Dispose(); @@ -81,7 +124,36 @@ namespace Umbraco.Tests.Integration.TestServerTest { Current.IsInitialized = false; } - } + + #region IStartup + + public override void ConfigureServices(IServiceCollection services) + { + var umbracoBuilder = services.AddUmbraco(TestHelper.GetWebHostEnvironment(), Configuration); + umbracoBuilder + .WithConfiguration() + .WithTestCore(TestHelper, UmbracoContainer, UseTestLocalDb) // This is the important one! + .WithWebComponents() + .WithRuntimeMinifier() + .WithBackOffice() + .WithBackOfficeIdentity() + //.WithMiniProfiler() // we don't want this running in tests + .WithMvcAndRazor(mvcBuilding: mvcBuilder => + { + mvcBuilder.AddApplicationPart(typeof(ContentController).Assembly); + }) + .WithWebServer() + .Build(); + } + + public override void Configure(IApplicationBuilder app) + { + app.UseUmbraco(); + } + + #endregion + + } } diff --git a/src/Umbraco.Tests.Integration/TestServerTest/UmbracoWebApplicationFactory.cs b/src/Umbraco.Tests.Integration/TestServerTest/UmbracoWebApplicationFactory.cs index ea2b64c790..251f155d2c 100644 --- a/src/Umbraco.Tests.Integration/TestServerTest/UmbracoWebApplicationFactory.cs +++ b/src/Umbraco.Tests.Integration/TestServerTest/UmbracoWebApplicationFactory.cs @@ -1,97 +1,23 @@ -using System.Collections.Generic; -using System.Net.Http; -using System.Security.Claims; -using System.Text.Encodings.Web; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Authentication; -using Microsoft.AspNetCore.Hosting; +using System; using Microsoft.AspNetCore.Mvc.Testing; -using Microsoft.AspNetCore.TestHost; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; -using Umbraco.Core; -using Umbraco.Core.BackOffice; -using Umbraco.Core.Composing; -using Umbraco.Core.Configuration; -using Umbraco.Core.Mapping; -using Umbraco.Core.Models.Membership; -using Umbraco.Core.Services; -using Umbraco.Web.Common.Security; -using Umbraco.Web.UI.NetCore; namespace Umbraco.Tests.Integration.TestServerTest { - public class UmbracoWebApplicationFactory : CustomWebApplicationFactory + + public class UmbracoWebApplicationFactory : WebApplicationFactory where TStartup : class { - public UmbracoWebApplicationFactory(string testDbConnectionString) :base(testDbConnectionString) - { - } - } + private readonly Func _createHostBuilder; - public abstract class CustomWebApplicationFactory : WebApplicationFactory where TStartup : class - { - private readonly string _testDbConnectionString; - - protected CustomWebApplicationFactory(string testDbConnectionString) + /// + /// Constructor to create a new WebApplicationFactory + /// + /// Method to create the IHostBuilder + public UmbracoWebApplicationFactory(Func createHostBuilder) { - _testDbConnectionString = testDbConnectionString; + _createHostBuilder = createHostBuilder; } - protected override void ConfigureWebHost(IWebHostBuilder builder) - { - base.ConfigureWebHost(builder); - - builder.ConfigureTestServices(services => - { - services.AddAuthentication("Test").AddScheme("Test", options => {}); - }); - - builder.ConfigureAppConfiguration(x => - { - x.AddInMemoryCollection(new Dictionary() - { - ["ConnectionStrings:"+ Constants.System.UmbracoConnectionName] = _testDbConnectionString, - ["Umbraco:CMS:Hosting:Debug"] = "true", - }); - }); - - } - - protected override IHostBuilder CreateHostBuilder() - { - - var builder = base.CreateHostBuilder(); - builder.UseUmbraco(); - return builder; - } - } - - public class TestAuthHandler : AuthenticationHandler - { - private readonly BackOfficeSignInManager _backOfficeSignInManager; - - private readonly BackOfficeIdentityUser _fakeUser; - public TestAuthHandler(IOptionsMonitor options, - ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock, BackOfficeSignInManager backOfficeSignInManager, IUserService userService, UmbracoMapper umbracoMapper) - : base(options, logger, encoder, clock) - { - _backOfficeSignInManager = backOfficeSignInManager; - - var user = userService.GetUserById(Constants.Security.SuperUserId); - _fakeUser = umbracoMapper.Map(user); - _fakeUser.SecurityStamp = "Needed"; - } - - protected override async Task HandleAuthenticateAsync() - { - - var principal = await _backOfficeSignInManager.CreateUserPrincipalAsync(_fakeUser); - var ticket = new AuthenticationTicket(principal, "Test"); - - return AuthenticateResult.Success(ticket); - } + protected override IHostBuilder CreateHostBuilder() => _createHostBuilder(); } } diff --git a/src/Umbraco.Tests.Integration/Testing/IntegrationTestComponent.cs b/src/Umbraco.Tests.Integration/Testing/IntegrationTestComponent.cs new file mode 100644 index 0000000000..69819c9bef --- /dev/null +++ b/src/Umbraco.Tests.Integration/Testing/IntegrationTestComponent.cs @@ -0,0 +1,43 @@ +using Examine; +using Examine.LuceneEngine.Providers; +using Umbraco.Core.Composing; +using Umbraco.Examine; + +namespace Umbraco.Tests.Integration.Testing +{ + /// + /// A component to customize some services to work nicely with integration tests + /// + public class IntegrationTestComponent : IComponent + { + private readonly IExamineManager _examineManager; + + public IntegrationTestComponent(IExamineManager examineManager) + { + _examineManager = examineManager; + } + + public void Initialize() + { + ConfigureExamineIndexes(); + } + + public void Terminate() + { + } + + /// + /// Configure all indexes to run sync (non-backbround threads) and to use RAMDirectory + /// + private void ConfigureExamineIndexes() + { + foreach (var index in _examineManager.Indexes) + { + if (index is LuceneIndex luceneIndex) + { + luceneIndex.ProcessNonAsync(); + } + } + } + } +} diff --git a/src/Umbraco.Tests.Integration/Testing/IntegrationTestComposer.cs b/src/Umbraco.Tests.Integration/Testing/IntegrationTestComposer.cs index 844877b1fd..e9b38741e2 100644 --- a/src/Umbraco.Tests.Integration/Testing/IntegrationTestComposer.cs +++ b/src/Umbraco.Tests.Integration/Testing/IntegrationTestComposer.cs @@ -1,7 +1,23 @@ using Moq; +using NUnit.Framework; +using System; +using System.IO; +using System.Linq; using Umbraco.Core; +using Umbraco.Core.Cache; using Umbraco.Core.Composing; +using Umbraco.Core.Configuration; +using Umbraco.Core.Hosting; +using Umbraco.Core.IO; +using Umbraco.Core.Logging; +using Umbraco.Core.Services; +using Umbraco.Core.Services.Implement; using Umbraco.Core.WebAssets; +using Umbraco.Examine; +using Umbraco.Web.Compose; +using Umbraco.Web.PublishedCache.NuCache; +using Umbraco.Web.Scheduling; +using Umbraco.Web.Search; namespace Umbraco.Tests.Integration.Testing { @@ -13,11 +29,74 @@ namespace Umbraco.Tests.Integration.Testing /// This is a IUserComposer so that it runs after all core composers /// [RuntimeLevel(MinLevel = RuntimeLevel.Boot)] - public class IntegrationTestComposer : IUserComposer + public class IntegrationTestComposer : ComponentComposer { - public void Compose(Composition composition) + public override void Compose(Composition composition) { + base.Compose(composition); + + composition.Components().Remove(); + composition.Components().Remove(); + composition.RegisterUnique(); composition.RegisterUnique(factory => Mock.Of()); + + // we don't want persisted nucache files in tests + composition.Register(factory => new PublishedSnapshotServiceOptions { IgnoreLocalDb = true }); + + // ensure all lucene indexes are using RAM directory (no file system) + composition.RegisterUnique(); + + // replace this service so that it can lookup the correct file locations + composition.RegisterUnique(GetLocalizedTextService); } + + /// + /// Used to register a replacement for where the file sources are the ones within the netcore project so + /// we don't need to copy files + /// + /// + private ILocalizedTextService GetLocalizedTextService(IFactory factory) + { + var configs = factory.GetInstance(); + var logger = factory.GetInstance(); + var appCaches = factory.GetInstance(); + + var localizedTextService = new LocalizedTextService( + new Lazy(() => + { + // get the src folder + var currFolder = new DirectoryInfo(TestContext.CurrentContext.TestDirectory); + while(!currFolder.Name.Equals("src", StringComparison.InvariantCultureIgnoreCase)) + { + currFolder = currFolder.Parent; + } + var netcoreUI = currFolder.GetDirectories("Umbraco.Web.UI.NetCore", SearchOption.TopDirectoryOnly).First(); + var mainLangFolder = new DirectoryInfo(Path.Combine(netcoreUI.FullName, configs.Global().UmbracoPath.TrimStart("~/"), "config", "lang")); + + return new LocalizedTextServiceFileSources( + logger, + appCaches, + mainLangFolder); + + }), + logger); + + return localizedTextService; + } + + // replace the default so there is no background index rebuilder + private class TestBackgroundIndexRebuilder : BackgroundIndexRebuilder + { + public TestBackgroundIndexRebuilder(IMainDom mainDom, IProfilingLogger logger, IApplicationShutdownRegistry hostingEnvironment, IndexRebuilder indexRebuilder) + : base(mainDom, logger, hostingEnvironment, indexRebuilder) + { + } + + public override void RebuildIndexes(bool onlyEmptyIndexes, int waitMilliseconds = 0) + { + // noop + } + } + } } diff --git a/src/Umbraco.Tests.Integration/Testing/LocalDbTestDatabase.cs b/src/Umbraco.Tests.Integration/Testing/LocalDbTestDatabase.cs index f628437507..01abe90b32 100644 --- a/src/Umbraco.Tests.Integration/Testing/LocalDbTestDatabase.cs +++ b/src/Umbraco.Tests.Integration/Testing/LocalDbTestDatabase.cs @@ -26,7 +26,6 @@ namespace Umbraco.Tests.Integration.Testing public const string DatabaseName = "UmbracoTests"; private readonly ILogger _logger; - private readonly GlobalSettings _globalSettings; private readonly LocalDb _localDb; private readonly IUmbracoVersion _umbracoVersion; private static LocalDb.Instance _instance; @@ -39,11 +38,10 @@ namespace Umbraco.Tests.Integration.Testing private DatabasePool _currentPool; //It's internal because `Umbraco.Core.Persistence.LocalDb` is internal - internal LocalDbTestDatabase(ILogger logger, GlobalSettings globalSettings, LocalDb localDb, string filesPath, IUmbracoDatabaseFactory dbFactory) + internal LocalDbTestDatabase(ILogger logger, LocalDb localDb, string filesPath, IUmbracoDatabaseFactory dbFactory) { _umbracoVersion = new UmbracoVersion(); _logger = logger; - _globalSettings = globalSettings; _localDb = localDb; _filesPath = filesPath; _dbFactory = dbFactory; @@ -131,7 +129,7 @@ namespace Umbraco.Tests.Integration.Testing using var trans = database.GetTransaction(); - var creator = new DatabaseSchemaCreator(database, _logger, _umbracoVersion, _globalSettings); + var creator = new DatabaseSchemaCreator(database, _logger, _umbracoVersion); creator.InitializeDatabaseSchema(); trans.Complete(); // commit it diff --git a/src/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs b/src/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs index 562480df83..975710f460 100644 --- a/src/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs +++ b/src/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs @@ -3,7 +3,6 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; using NUnit.Framework; using Umbraco.Core.Cache; using Umbraco.Core.Composing; @@ -22,11 +21,21 @@ using Umbraco.Extensions; using Umbraco.Tests.Testing; using Umbraco.Web; using ILogger = Umbraco.Core.Logging.ILogger; +using Umbraco.Core.Runtime; +using Umbraco.Core; +using Moq; +using System.Collections.Generic; +using Microsoft.Extensions.Configuration; +using System.Data.SqlClient; +using System.Data.Common; +using System.IO; using Umbraco.Core.Configuration.Models; using Microsoft.Extensions.Options; +using ConnectionStrings = Umbraco.Core.Configuration.Models.ConnectionStrings; namespace Umbraco.Tests.Integration.Testing { + /// /// Abstract class for integration tests /// @@ -37,7 +46,7 @@ namespace Umbraco.Tests.Integration.Testing [NonParallelizable] public abstract class UmbracoIntegrationTest { - public static LightInjectContainer GetUmbracoContainer(out UmbracoServiceProviderFactory serviceProviderFactory) + public static LightInjectContainer CreateUmbracoContainer(out UmbracoServiceProviderFactory serviceProviderFactory) { var container = UmbracoServiceProviderFactory.CreateServiceContainer(); serviceProviderFactory = new UmbracoServiceProviderFactory(container, false); @@ -45,6 +54,232 @@ namespace Umbraco.Tests.Integration.Testing return umbracoContainer; } + private List _testTeardown = null; + private List _fixtureTeardown = new List(); + + public void OnTestTearDown(Action tearDown) + { + if (_testTeardown == null) + _testTeardown = new List(); + _testTeardown.Add(tearDown); + } + + public void OnFixtureTearDown(Action tearDown) => _fixtureTeardown.Add(tearDown); + + [OneTimeTearDown] + public void FixtureTearDown() + { + foreach (var a in _fixtureTeardown) a(); + } + + [TearDown] + public virtual void TearDown() + { + foreach (var a in _testTeardown) a(); + _testTeardown = null; + } + + [SetUp] + public virtual async Task Setup() + { + var hostBuilder = CreateHostBuilder(); + var host = await hostBuilder.StartAsync(); + Services = host.Services; + var app = new ApplicationBuilder(host.Services); + Configure(app); + } + + #region Generic Host Builder and Runtime + + /// + /// Create the Generic Host and execute startup ConfigureServices/Configure calls + /// + /// + public virtual IHostBuilder CreateHostBuilder() + { + UmbracoContainer = CreateUmbracoContainer(out var serviceProviderFactory); + _serviceProviderFactory = serviceProviderFactory; + + var hostBuilder = Host.CreateDefaultBuilder() + // IMPORTANT: We Cannot use UseStartup, there's all sorts of threads about this with testing. Although this can work + // if you want to setup your tests this way, it is a bit annoying to do that as the WebApplicationFactory will + // create separate Host instances. So instead of UseStartup, we just call ConfigureServices/Configure ourselves, + // and in the case of the UmbracoTestServerTestBase it will use the ConfigureWebHost to Configure the IApplicationBuilder directly. + //.ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup(GetType()); }) + .UseUmbraco(_serviceProviderFactory) + .ConfigureAppConfiguration((context, configBuilder) => + { + context.HostingEnvironment = TestHelper.GetWebHostEnvironment(); + Configuration = context.Configuration; + configBuilder.AddInMemoryCollection(InMemoryConfiguration); + }) + .ConfigureServices((hostContext, services) => + { + ConfigureServices(services); + }); + return hostBuilder; + } + + /// + /// Creates a instance for testing and registers an event handler for database install + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + public CoreRuntime CreateTestRuntime( + Configs configs, + GlobalSettings globalSettings, + ConnectionStrings connectionStrings, + IUmbracoVersion umbracoVersion, IIOHelper ioHelper, + ILogger logger, IProfiler profiler, Core.Hosting.IHostingEnvironment hostingEnvironment, IBackOfficeInfo backOfficeInfo, + ITypeFinder typeFinder, AppCaches appCaches, IDbProviderFactoryCreator dbProviderFactoryCreator) + { + var runtime = CreateTestRuntime(configs, + globalSettings, + connectionStrings, + umbracoVersion, + ioHelper, + logger, + profiler, + hostingEnvironment, + backOfficeInfo, + typeFinder, + appCaches, + dbProviderFactoryCreator, + TestHelper.MainDom, // SimpleMainDom + UseTestLocalDb // DB Installation event handler + ); + + return runtime; + } + + /// + /// Creates a instance for testing and registers an event handler for database install + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// The event handler used for DB installation + /// + /// + public static CoreRuntime CreateTestRuntime( + Configs configs, + GlobalSettings globalSettings, + ConnectionStrings connectionStrings, + IUmbracoVersion umbracoVersion, IIOHelper ioHelper, + ILogger logger, IProfiler profiler, Core.Hosting.IHostingEnvironment hostingEnvironment, IBackOfficeInfo backOfficeInfo, + ITypeFinder typeFinder, AppCaches appCaches, IDbProviderFactoryCreator dbProviderFactoryCreator, + IMainDom mainDom, Action eventHandler) + { + var runtime = new CoreRuntime( + configs, + globalSettings, + connectionStrings, + umbracoVersion, + ioHelper, + logger, + profiler, + Mock.Of(), + hostingEnvironment, + backOfficeInfo, + dbProviderFactoryCreator, + mainDom, + typeFinder, + appCaches); + + runtime.RuntimeEssentials += (sender, args) => eventHandler(sender, args); + + return runtime; + } + + #endregion + + #region IStartup + + public virtual void ConfigureServices(IServiceCollection services) + { + services.AddSingleton(TestHelper.DbProviderFactoryCreator); + var webHostEnvironment = TestHelper.GetWebHostEnvironment(); + services.AddRequiredNetCoreServices(TestHelper, webHostEnvironment); + + // Add it! + services.AddUmbracoConfiguration(Configuration); + services.AddUmbracoCore( + webHostEnvironment, + UmbracoContainer, + GetType().Assembly, + AppCaches.NoCache, // Disable caches for integration tests + TestHelper.GetLoggingConfiguration(), + CreateTestRuntime, + out _); + + services.AddUmbracoWebComponents(); + services.AddUmbracoRuntimeMinifier(Configuration); + services.AddUmbracoBackOffice(); + services.AddUmbracoBackOfficeIdentity(); + + services.AddMvc(); + + services.AddSingleton(new ConsoleLogger(new MessageTemplates())); + + CustomTestSetup(services); + } + + public virtual void Configure(IApplicationBuilder app) + { + Services.GetRequiredService().EnsureUmbracoContext(); + + // get the currently set ptions + var testOptions = TestOptionAttributeBase.GetTestOptions(); + if (testOptions.Boot) + { + app.UseUmbracoCore(); + } + } + + #endregion + + #region LocalDb + + + private static readonly object _dbLocker = new object(); + private static LocalDbTestDatabase _dbInstance; + + /// + /// Event handler for the to install the database and register the to Terminate + /// + /// + /// + protected void UseTestLocalDb(CoreRuntime runtime, RuntimeEssentialsEventArgs args) + { + // MUST be terminated on teardown + OnTestTearDown(() => runtime.Terminate()); + + // This will create a db, install the schema and ensure the app is configured to run + InstallTestLocalDb(args.DatabaseFactory, runtime.ProfilingLogger, runtime.State, TestHelper.WorkingDirectory, out var connectionString); + TestDBConnectionString = connectionString; + InMemoryConfiguration["ConnectionStrings:" + Constants.System.UmbracoConnectionName] = TestDBConnectionString; + } + /// /// Get or create an instance of /// @@ -56,7 +291,7 @@ namespace Umbraco.Tests.Integration.Testing /// /// There must only be ONE instance shared between all tests in a session /// - public static LocalDbTestDatabase GetOrCreate(string filesPath, ILogger logger, GlobalSettings globalSettings, IUmbracoDatabaseFactory dbFactory) + private static LocalDbTestDatabase GetOrCreateDatabase(string filesPath, ILogger logger, IUmbracoDatabaseFactory dbFactory) { lock (_dbLocker) { @@ -65,91 +300,109 @@ namespace Umbraco.Tests.Integration.Testing var localDb = new LocalDb(); if (localDb.IsAvailable == false) throw new InvalidOperationException("LocalDB is not available."); - _dbInstance = new LocalDbTestDatabase(logger, globalSettings, localDb, filesPath, dbFactory); + _dbInstance = new LocalDbTestDatabase(logger, localDb, filesPath, dbFactory); return _dbInstance; } } - private static readonly object _dbLocker = new object(); - private static LocalDbTestDatabase _dbInstance; - - private Action _testTeardown = null; - private Action _fixtureTeardown = null; - - public void OnTestTearDown(Action tearDown) + /// + /// Creates a LocalDb instance to use for the test + /// + /// + /// + /// + /// + /// + private void InstallTestLocalDb( + IUmbracoDatabaseFactory databaseFactory, IProfilingLogger logger, + IRuntimeState runtimeState, string workingDirectory, out string connectionString) { - _testTeardown = tearDown; - } + connectionString = null; + var dbFilePath = Path.Combine(workingDirectory, "LocalDb"); - public void OnFixtureTearDown(Action tearDown) - { - _fixtureTeardown = tearDown; - } - - [OneTimeTearDown] - public void FixtureTearDown() - { - _fixtureTeardown?.Invoke(); - } - - [TearDown] - public void TearDown() - { - _testTeardown?.Invoke(); - } - - [SetUp] - public async Task Setup() - { - var umbracoContainer = GetUmbracoContainer(out var serviceProviderFactory); - var testHelper = new TestHelper(); // get the currently set db options var testOptions = TestOptionAttributeBase.GetTestOptions(); - var hostBuilder = new HostBuilder() - .UseUmbraco(serviceProviderFactory) - .ConfigureServices((hostContext, services) => - { - services.AddSingleton(testHelper.DbProviderFactoryCreator); - var webHostEnvironment = testHelper.GetWebHostEnvironment(); - services.AddRequiredNetCoreServices(testHelper, webHostEnvironment); + if (testOptions.Database == UmbracoTestOptions.Database.None) + return; - // Add it! - services.AddUmbracoConfiguration(hostContext.Configuration); - services.AddUmbracoCore(webHostEnvironment, umbracoContainer, GetType().Assembly, NoAppCache.Instance, testHelper.GetLoggingConfiguration(), out _); - services.AddUmbracoWebComponents(); - services.AddUmbracoRuntimeMinifier(hostContext.Configuration); - services.AddUmbracoBackOffice(); - services.AddUmbracoBackOfficeIdentity(); + // need to manually register this factory + DbProviderFactories.RegisterFactory(Constants.DbProviderNames.SqlServer, SqlClientFactory.Instance); - services.AddMvc(); + if (!Directory.Exists(dbFilePath)) + Directory.CreateDirectory(dbFilePath); + var db = GetOrCreateDatabase(dbFilePath, logger, databaseFactory); - services.AddSingleton(new ConsoleLogger(new MessageTemplates())); - - CustomTestSetup(services); - }); - - var host = await hostBuilder.StartAsync(); - var app = new ApplicationBuilder(host.Services); - Services = app.ApplicationServices; - - Services.GetRequiredService().EnsureUmbracoContext(); - // This will create a db, install the schema and ensure the app is configured to run - app.UseTestLocalDb(testHelper.WorkingDirectory, this, out var connectionString); - TestDBConnectionString = connectionString; - - if (testOptions.Boot) + switch (testOptions.Database) { - app.UseUmbracoCore(); - } + case UmbracoTestOptions.Database.NewSchemaPerTest: + // New DB + Schema + var newSchemaDbId = db.AttachSchema(); + + // Add teardown callback + OnTestTearDown(() => db.Detach(newSchemaDbId)); + + // We must re-configure our current factory since attaching a new LocalDb from the pool changes connection strings + if (!databaseFactory.Configured) + { + databaseFactory.Configure(db.ConnectionString, Constants.DatabaseProviders.SqlServer); + } + + // re-run the runtime level check + runtimeState.DetermineRuntimeLevel(); + + Assert.AreEqual(RuntimeLevel.Run, runtimeState.Level); + + break; + case UmbracoTestOptions.Database.NewEmptyPerTest: + + var newEmptyDbId = db.AttachEmpty(); + + // Add teardown callback + OnTestTearDown(() => db.Detach(newEmptyDbId)); + + + break; + case UmbracoTestOptions.Database.NewSchemaPerFixture: + + // New DB + Schema + var newSchemaFixtureDbId = db.AttachSchema(); + + // Add teardown callback + OnFixtureTearDown(() => db.Detach(newSchemaFixtureDbId)); + + break; + case UmbracoTestOptions.Database.NewEmptyPerFixture: + + throw new NotImplementedException(); + + //// Add teardown callback + //integrationTest.OnFixtureTearDown(() => db.Detach()); + + break; + default: + throw new ArgumentOutOfRangeException(nameof(testOptions), testOptions, null); + } + connectionString = db.ConnectionString; } - protected T GetRequiredService() => Services.GetRequiredService(); + #endregion #region Common services + protected LightInjectContainer UmbracoContainer { get; private set; } + private UmbracoServiceProviderFactory _serviceProviderFactory; + + protected virtual T GetRequiredService() => Services.GetRequiredService(); + + public Dictionary InMemoryConfiguration { get; } = new Dictionary(); + + public IConfiguration Configuration { get; protected set; } + + public TestHelper TestHelper = new TestHelper(); + protected string TestDBConnectionString { get; private set; } protected virtual Action CustomTestSetup => services => { }; @@ -157,7 +410,7 @@ namespace Umbraco.Tests.Integration.Testing /// /// Returns the DI container /// - protected IServiceProvider Services { get; private set; } + protected IServiceProvider Services { get; set; } /// /// Returns the diff --git a/src/Umbraco.Tests.Integration/Umbraco.Tests.Integration.csproj b/src/Umbraco.Tests.Integration/Umbraco.Tests.Integration.csproj index ba986ebd86..072f15bcd1 100644 --- a/src/Umbraco.Tests.Integration/Umbraco.Tests.Integration.csproj +++ b/src/Umbraco.Tests.Integration/Umbraco.Tests.Integration.csproj @@ -14,6 +14,7 @@ + diff --git a/src/Umbraco.Tests/Migrations/AdvancedMigrationTests.cs b/src/Umbraco.Tests/Migrations/AdvancedMigrationTests.cs index bba2649af3..90b2da5500 100644 --- a/src/Umbraco.Tests/Migrations/AdvancedMigrationTests.cs +++ b/src/Umbraco.Tests/Migrations/AdvancedMigrationTests.cs @@ -43,8 +43,7 @@ namespace Umbraco.Tests.Migrations upgrader.Execute(ScopeProvider, builder, Mock.Of(), logger); - var globalSettings = new GlobalSettingsBuilder().Build(); - var helper = new DatabaseSchemaCreator(scope.Database, logger, UmbracoVersion, globalSettings); + var helper = new DatabaseSchemaCreator(scope.Database, logger, UmbracoVersion); var exists = helper.TableExists("umbracoUser"); Assert.IsTrue(exists); diff --git a/src/Umbraco.Tests/Migrations/MigrationPlanTests.cs b/src/Umbraco.Tests/Migrations/MigrationPlanTests.cs index 6fc06d0618..31ceb1bdbf 100644 --- a/src/Umbraco.Tests/Migrations/MigrationPlanTests.cs +++ b/src/Umbraco.Tests/Migrations/MigrationPlanTests.cs @@ -139,8 +139,7 @@ namespace Umbraco.Tests.Migrations [Test] public void ValidateUmbracoPlan() { - var globalSettings = new GlobalSettingsBuilder().Build(); - var plan = new UmbracoPlan(TestHelper.GetUmbracoVersion(), globalSettings); + var plan = new UmbracoPlan(TestHelper.GetUmbracoVersion()); plan.Validate(); Console.WriteLine(plan.FinalState); Assert.IsFalse(plan.FinalState.IsNullOrWhiteSpace()); diff --git a/src/Umbraco.Tests/Persistence/DatabaseContextTests.cs b/src/Umbraco.Tests/Persistence/DatabaseContextTests.cs index d1ee5ac690..7483b5c611 100644 --- a/src/Umbraco.Tests/Persistence/DatabaseContextTests.cs +++ b/src/Umbraco.Tests/Persistence/DatabaseContextTests.cs @@ -96,7 +96,7 @@ namespace Umbraco.Tests.Persistence using (var database = _databaseFactory.CreateDatabase()) using (var transaction = database.GetTransaction()) { - schemaHelper = new DatabaseSchemaCreator(database, _logger, _umbracoVersion, new GlobalSettingsBuilder().Build()); + schemaHelper = new DatabaseSchemaCreator(database, _logger, _umbracoVersion); schemaHelper.InitializeDatabaseSchema(); transaction.Complete(); } diff --git a/src/Umbraco.Tests/Persistence/SchemaValidationTest.cs b/src/Umbraco.Tests/Persistence/SchemaValidationTest.cs index 27bab47968..704d64823c 100644 --- a/src/Umbraco.Tests/Persistence/SchemaValidationTest.cs +++ b/src/Umbraco.Tests/Persistence/SchemaValidationTest.cs @@ -23,8 +23,7 @@ namespace Umbraco.Tests.Persistence using (var scope = ScopeProvider.CreateScope()) { - var globalSettings = new GlobalSettingsBuilder().Build(); - var schema = new DatabaseSchemaCreator(scope.Database, Logger, UmbracoVersion, globalSettings); + var schema = new DatabaseSchemaCreator(scope.Database, Logger, UmbracoVersion); result = schema.ValidateSchema( //TODO: When we remove the xml cache from tests we can remove this too DatabaseSchemaCreator.OrderedTables.Concat(new []{typeof(ContentXmlDto), typeof(PreviewXmlDto)})); diff --git a/src/Umbraco.Tests/Persistence/SqlCeTableByTableTest.cs b/src/Umbraco.Tests/Persistence/SqlCeTableByTableTest.cs index 337f1a7289..1fe7153dd0 100644 --- a/src/Umbraco.Tests/Persistence/SqlCeTableByTableTest.cs +++ b/src/Umbraco.Tests/Persistence/SqlCeTableByTableTest.cs @@ -25,7 +25,7 @@ namespace Umbraco.Tests.Persistence { using (var scope = ScopeProvider.CreateScope()) { - var helper = new DatabaseSchemaCreator(scope.Database, Mock.Of(), UmbracoVersion, GlobalSettings); + var helper = new DatabaseSchemaCreator(scope.Database, Mock.Of(), UmbracoVersion); helper.CreateTable(); @@ -38,7 +38,7 @@ namespace Umbraco.Tests.Persistence { using (var scope = ScopeProvider.CreateScope()) { - var helper = new DatabaseSchemaCreator(scope.Database, Mock.Of(), UmbracoVersion, GlobalSettings); + var helper = new DatabaseSchemaCreator(scope.Database, Mock.Of(), UmbracoVersion); helper.CreateTable(); helper.CreateTable(); @@ -52,7 +52,7 @@ namespace Umbraco.Tests.Persistence { using (var scope = ScopeProvider.CreateScope()) { - var helper = new DatabaseSchemaCreator(scope.Database, Mock.Of(), UmbracoVersion, GlobalSettings); + var helper = new DatabaseSchemaCreator(scope.Database, Mock.Of(), UmbracoVersion); helper.CreateTable(); helper.CreateTable(); @@ -67,7 +67,7 @@ namespace Umbraco.Tests.Persistence { using (var scope = ScopeProvider.CreateScope()) { - var helper = new DatabaseSchemaCreator(scope.Database, Mock.Of(), UmbracoVersion, GlobalSettings); + var helper = new DatabaseSchemaCreator(scope.Database, Mock.Of(), UmbracoVersion); helper.CreateTable(); helper.CreateTable(); @@ -81,7 +81,7 @@ namespace Umbraco.Tests.Persistence { using (var scope = ScopeProvider.CreateScope()) { - var helper = new DatabaseSchemaCreator(scope.Database, Mock.Of(), UmbracoVersion, GlobalSettings); + var helper = new DatabaseSchemaCreator(scope.Database, Mock.Of(), UmbracoVersion); helper.CreateTable(); helper.CreateTable(); @@ -96,7 +96,7 @@ namespace Umbraco.Tests.Persistence { using (var scope = ScopeProvider.CreateScope()) { - var helper = new DatabaseSchemaCreator(scope.Database, Mock.Of(), UmbracoVersion, GlobalSettings); + var helper = new DatabaseSchemaCreator(scope.Database, Mock.Of(), UmbracoVersion); helper.CreateTable(); helper.CreateTable(); @@ -110,7 +110,7 @@ namespace Umbraco.Tests.Persistence { using (var scope = ScopeProvider.CreateScope()) { - var helper = new DatabaseSchemaCreator(scope.Database, Mock.Of(), UmbracoVersion, GlobalSettings); + var helper = new DatabaseSchemaCreator(scope.Database, Mock.Of(), UmbracoVersion); helper.CreateTable(); helper.CreateTable(); @@ -126,7 +126,7 @@ namespace Umbraco.Tests.Persistence { using (var scope = ScopeProvider.CreateScope()) { - var helper = new DatabaseSchemaCreator(scope.Database, Mock.Of(), UmbracoVersion, GlobalSettings); + var helper = new DatabaseSchemaCreator(scope.Database, Mock.Of(), UmbracoVersion); helper.CreateTable(); helper.CreateTable(); @@ -142,7 +142,7 @@ namespace Umbraco.Tests.Persistence { using (var scope = ScopeProvider.CreateScope()) { - var helper = new DatabaseSchemaCreator(scope.Database, Mock.Of(), UmbracoVersion, GlobalSettings); + var helper = new DatabaseSchemaCreator(scope.Database, Mock.Of(), UmbracoVersion); helper.CreateTable(); helper.CreateTable(); @@ -156,7 +156,7 @@ namespace Umbraco.Tests.Persistence { using (var scope = ScopeProvider.CreateScope()) { - var helper = new DatabaseSchemaCreator(scope.Database, Mock.Of(), UmbracoVersion, GlobalSettings); + var helper = new DatabaseSchemaCreator(scope.Database, Mock.Of(), UmbracoVersion); helper.CreateTable(); @@ -169,7 +169,7 @@ namespace Umbraco.Tests.Persistence { using (var scope = ScopeProvider.CreateScope()) { - var helper = new DatabaseSchemaCreator(scope.Database, Mock.Of(), UmbracoVersion, GlobalSettings); + var helper = new DatabaseSchemaCreator(scope.Database, Mock.Of(), UmbracoVersion); helper.CreateTable(); helper.CreateTable(); @@ -184,7 +184,7 @@ namespace Umbraco.Tests.Persistence { using (var scope = ScopeProvider.CreateScope()) { - var helper = new DatabaseSchemaCreator(scope.Database, Mock.Of(), UmbracoVersion, GlobalSettings); + var helper = new DatabaseSchemaCreator(scope.Database, Mock.Of(), UmbracoVersion); helper.CreateTable(); helper.CreateTable(); @@ -198,7 +198,7 @@ namespace Umbraco.Tests.Persistence { using (var scope = ScopeProvider.CreateScope()) { - var helper = new DatabaseSchemaCreator(scope.Database, Mock.Of(), UmbracoVersion, GlobalSettings); + var helper = new DatabaseSchemaCreator(scope.Database, Mock.Of(), UmbracoVersion); helper.CreateTable(); helper.CreateTable(); @@ -215,7 +215,7 @@ namespace Umbraco.Tests.Persistence { using (var scope = ScopeProvider.CreateScope()) { - var helper = new DatabaseSchemaCreator(scope.Database, Mock.Of(), UmbracoVersion, GlobalSettings); + var helper = new DatabaseSchemaCreator(scope.Database, Mock.Of(), UmbracoVersion); helper.CreateTable(); helper.CreateTable(); @@ -231,7 +231,7 @@ namespace Umbraco.Tests.Persistence { using (var scope = ScopeProvider.CreateScope()) { - var helper = new DatabaseSchemaCreator(scope.Database, Mock.Of(), UmbracoVersion, GlobalSettings); + var helper = new DatabaseSchemaCreator(scope.Database, Mock.Of(), UmbracoVersion); helper.CreateTable(); helper.CreateTable(); @@ -245,7 +245,7 @@ namespace Umbraco.Tests.Persistence { using (var scope = ScopeProvider.CreateScope()) { - var helper = new DatabaseSchemaCreator(scope.Database, Mock.Of(), UmbracoVersion, GlobalSettings); + var helper = new DatabaseSchemaCreator(scope.Database, Mock.Of(), UmbracoVersion); helper.CreateTable(); @@ -258,7 +258,7 @@ namespace Umbraco.Tests.Persistence { using (var scope = ScopeProvider.CreateScope()) { - var helper = new DatabaseSchemaCreator(scope.Database, Mock.Of(), UmbracoVersion, GlobalSettings); + var helper = new DatabaseSchemaCreator(scope.Database, Mock.Of(), UmbracoVersion); helper.CreateTable(); @@ -271,7 +271,7 @@ namespace Umbraco.Tests.Persistence { using (var scope = ScopeProvider.CreateScope()) { - var helper = new DatabaseSchemaCreator(scope.Database, Mock.Of(), UmbracoVersion, GlobalSettings); + var helper = new DatabaseSchemaCreator(scope.Database, Mock.Of(), UmbracoVersion); helper.CreateTable(); @@ -284,7 +284,7 @@ namespace Umbraco.Tests.Persistence { using (var scope = ScopeProvider.CreateScope()) { - var helper = new DatabaseSchemaCreator(scope.Database, Mock.Of(), UmbracoVersion, GlobalSettings); + var helper = new DatabaseSchemaCreator(scope.Database, Mock.Of(), UmbracoVersion); helper.CreateTable(); helper.CreateTable(); @@ -300,7 +300,7 @@ namespace Umbraco.Tests.Persistence { using (var scope = ScopeProvider.CreateScope()) { - var helper = new DatabaseSchemaCreator(scope.Database, Mock.Of(), UmbracoVersion, GlobalSettings); + var helper = new DatabaseSchemaCreator(scope.Database, Mock.Of(), UmbracoVersion); helper.CreateTable(); helper.CreateTable(); @@ -317,7 +317,7 @@ namespace Umbraco.Tests.Persistence { using (var scope = ScopeProvider.CreateScope()) { - var helper = new DatabaseSchemaCreator(scope.Database, Mock.Of(), UmbracoVersion, GlobalSettings); + var helper = new DatabaseSchemaCreator(scope.Database, Mock.Of(), UmbracoVersion); helper.CreateTable(); helper.CreateTable(); @@ -332,7 +332,7 @@ namespace Umbraco.Tests.Persistence { using (var scope = ScopeProvider.CreateScope()) { - var helper = new DatabaseSchemaCreator(scope.Database, Mock.Of(), UmbracoVersion, GlobalSettings); + var helper = new DatabaseSchemaCreator(scope.Database, Mock.Of(), UmbracoVersion); helper.CreateTable(); helper.CreateTable(); @@ -349,7 +349,7 @@ namespace Umbraco.Tests.Persistence { using (var scope = ScopeProvider.CreateScope()) { - var helper = new DatabaseSchemaCreator(scope.Database, Mock.Of(), UmbracoVersion, GlobalSettings); + var helper = new DatabaseSchemaCreator(scope.Database, Mock.Of(), UmbracoVersion); helper.CreateTable(); helper.CreateTable(); @@ -367,7 +367,7 @@ namespace Umbraco.Tests.Persistence { using (var scope = ScopeProvider.CreateScope()) { - var helper = new DatabaseSchemaCreator(scope.Database, Mock.Of(), UmbracoVersion, GlobalSettings); + var helper = new DatabaseSchemaCreator(scope.Database, Mock.Of(), UmbracoVersion); helper.CreateTable(); helper.CreateTable(); @@ -384,7 +384,7 @@ namespace Umbraco.Tests.Persistence { using (var scope = ScopeProvider.CreateScope()) { - var helper = new DatabaseSchemaCreator(scope.Database, Mock.Of(), UmbracoVersion, GlobalSettings); + var helper = new DatabaseSchemaCreator(scope.Database, Mock.Of(), UmbracoVersion); helper.CreateTable(); helper.CreateTable(); @@ -399,7 +399,7 @@ namespace Umbraco.Tests.Persistence { using (var scope = ScopeProvider.CreateScope()) { - var helper = new DatabaseSchemaCreator(scope.Database, Mock.Of(), UmbracoVersion, GlobalSettings); + var helper = new DatabaseSchemaCreator(scope.Database, Mock.Of(), UmbracoVersion); helper.CreateTable(); helper.CreateTable(); @@ -414,7 +414,7 @@ namespace Umbraco.Tests.Persistence { using (var scope = ScopeProvider.CreateScope()) { - var helper = new DatabaseSchemaCreator(scope.Database, Mock.Of(), UmbracoVersion, GlobalSettings); + var helper = new DatabaseSchemaCreator(scope.Database, Mock.Of(), UmbracoVersion); helper.CreateTable(); @@ -427,7 +427,7 @@ namespace Umbraco.Tests.Persistence { using (var scope = ScopeProvider.CreateScope()) { - var helper = new DatabaseSchemaCreator(scope.Database, Mock.Of(), UmbracoVersion, GlobalSettings); + var helper = new DatabaseSchemaCreator(scope.Database, Mock.Of(), UmbracoVersion); helper.CreateTable(); @@ -440,7 +440,7 @@ namespace Umbraco.Tests.Persistence { using (var scope = ScopeProvider.CreateScope()) { - var helper = new DatabaseSchemaCreator(scope.Database, Mock.Of(), UmbracoVersion, GlobalSettings); + var helper = new DatabaseSchemaCreator(scope.Database, Mock.Of(), UmbracoVersion); helper.CreateTable(); helper.CreateTable(); @@ -461,7 +461,7 @@ namespace Umbraco.Tests.Persistence { using (var scope = ScopeProvider.CreateScope()) { - var helper = new DatabaseSchemaCreator(scope.Database, Mock.Of(), UmbracoVersion, GlobalSettings); + var helper = new DatabaseSchemaCreator(scope.Database, Mock.Of(), UmbracoVersion); helper.CreateTable(); @@ -474,7 +474,7 @@ namespace Umbraco.Tests.Persistence { using (var scope = ScopeProvider.CreateScope()) { - var helper = new DatabaseSchemaCreator(scope.Database, Mock.Of(), UmbracoVersion, GlobalSettings); + var helper = new DatabaseSchemaCreator(scope.Database, Mock.Of(), UmbracoVersion); helper.CreateTable(); helper.CreateTable(); @@ -488,7 +488,7 @@ namespace Umbraco.Tests.Persistence { using (var scope = ScopeProvider.CreateScope()) { - var helper = new DatabaseSchemaCreator(scope.Database, Mock.Of(), UmbracoVersion, GlobalSettings); + var helper = new DatabaseSchemaCreator(scope.Database, Mock.Of(), UmbracoVersion); helper.CreateTable(); helper.CreateTable(); @@ -502,7 +502,7 @@ namespace Umbraco.Tests.Persistence { using (var scope = ScopeProvider.CreateScope()) { - var helper = new DatabaseSchemaCreator(scope.Database, Mock.Of(), UmbracoVersion, GlobalSettings); + var helper = new DatabaseSchemaCreator(scope.Database, Mock.Of(), UmbracoVersion); helper.CreateTable(); helper.CreateTable(); @@ -517,7 +517,7 @@ namespace Umbraco.Tests.Persistence { using (var scope = ScopeProvider.CreateScope()) { - var helper = new DatabaseSchemaCreator(scope.Database, Mock.Of(), UmbracoVersion, GlobalSettings); + var helper = new DatabaseSchemaCreator(scope.Database, Mock.Of(), UmbracoVersion); helper.CreateTable(); helper.CreateTable(); diff --git a/src/Umbraco.Tests/Routing/RenderRouteHandlerTests.cs b/src/Umbraco.Tests/Routing/RenderRouteHandlerTests.cs index 668507b497..3f97f1a4a3 100644 --- a/src/Umbraco.Tests/Routing/RenderRouteHandlerTests.cs +++ b/src/Umbraco.Tests/Routing/RenderRouteHandlerTests.cs @@ -53,7 +53,7 @@ namespace Umbraco.Tests.Routing public class TestRuntime : CoreRuntime { public TestRuntime(Configs configs, GlobalSettings globalSettings, ConnectionStrings connectionStrings, IUmbracoVersion umbracoVersion, IIOHelper ioHelper, ILogger logger, IHostingEnvironment hostingEnvironment, IBackOfficeInfo backOfficeInfo) - : base(configs, globalSettings, connectionStrings, umbracoVersion, ioHelper, Mock.Of(), Mock.Of(), new AspNetUmbracoBootPermissionChecker(), hostingEnvironment, backOfficeInfo, TestHelper.DbProviderFactoryCreator, TestHelper.MainDom, TestHelper.GetTypeFinder(), NoAppCache.Instance) + : base(configs, globalSettings, connectionStrings,umbracoVersion, ioHelper, Mock.Of(), Mock.Of(), new AspNetUmbracoBootPermissionChecker(), hostingEnvironment, backOfficeInfo, TestHelper.DbProviderFactoryCreator, TestHelper.MainDom, TestHelper.GetTypeFinder(), AppCaches.NoCache) { } diff --git a/src/Umbraco.Tests/Routing/UmbracoModuleTests.cs b/src/Umbraco.Tests/Routing/UmbracoModuleTests.cs index bc2511fe41..993b0a112b 100644 --- a/src/Umbraco.Tests/Routing/UmbracoModuleTests.cs +++ b/src/Umbraco.Tests/Routing/UmbracoModuleTests.cs @@ -28,7 +28,7 @@ namespace Umbraco.Tests.Routing var globalSettings = new GlobalSettingsBuilder() .WithReservedPaths((GlobalSettings.StaticReservedPaths + "~/umbraco")) .Build(); - var runtime = new RuntimeState(globalSettings, UmbracoVersion); + var runtime = Umbraco.Core.RuntimeState.Booting(); _module = new UmbracoInjectedModule ( diff --git a/src/Umbraco.Tests/Runtimes/CoreRuntimeTests.cs b/src/Umbraco.Tests/Runtimes/CoreRuntimeTests.cs index 67b3be0293..531102ae59 100644 --- a/src/Umbraco.Tests/Runtimes/CoreRuntimeTests.cs +++ b/src/Umbraco.Tests/Runtimes/CoreRuntimeTests.cs @@ -116,14 +116,14 @@ namespace Umbraco.Tests.Runtimes public class TestRuntime : CoreRuntime { public TestRuntime(Configs configs, IUmbracoVersion umbracoVersion, IIOHelper ioHelper, ILogger logger, IProfiler profiler, IHostingEnvironment hostingEnvironment, IBackOfficeInfo backOfficeInfo) - : base(configs, ConfigModelConversionsFromLegacy.ConvertGlobalSettings(configs.Global()), ConfigModelConversionsFromLegacy.ConvertConnectionStrings(configs.ConnectionStrings()), umbracoVersion, ioHelper, logger, profiler, new AspNetUmbracoBootPermissionChecker(), hostingEnvironment, backOfficeInfo, TestHelper.DbProviderFactoryCreator, TestHelper.MainDom, TestHelper.GetTypeFinder(), NoAppCache.Instance) + :base(configs, ConfigModelConversionsFromLegacy.ConvertGlobalSettings(configs.Global()), ConfigModelConversionsFromLegacy.ConvertConnectionStrings(configs.ConnectionStrings()),umbracoVersion, ioHelper, logger, profiler, new AspNetUmbracoBootPermissionChecker(), hostingEnvironment, backOfficeInfo, TestHelper.DbProviderFactoryCreator, TestHelper.MainDom, TestHelper.GetTypeFinder(), AppCaches.NoCache) { } // must override the database factory // else BootFailedException because U cannot connect to the configured db - protected internal override IUmbracoDatabaseFactory GetDatabaseFactory() + protected internal override IUmbracoDatabaseFactory CreateDatabaseFactory() { var mock = new Mock(); mock.Setup(x => x.Configured).Returns(true); diff --git a/src/Umbraco.Tests/Runtimes/StandaloneTests.cs b/src/Umbraco.Tests/Runtimes/StandaloneTests.cs index 6ad675ed88..2231633a10 100644 --- a/src/Umbraco.Tests/Runtimes/StandaloneTests.cs +++ b/src/Umbraco.Tests/Runtimes/StandaloneTests.cs @@ -75,7 +75,7 @@ namespace Umbraco.Tests.Runtimes var mainDom = new SimpleMainDom(); var umbracoVersion = TestHelper.GetUmbracoVersion(); var backOfficeInfo = TestHelper.GetBackOfficeInfo(); - var runtimeState = new RuntimeState(null, umbracoVersion); + var runtimeState = new RuntimeState(globalSettings, umbracoVersion, databaseFactory, logger); var configs = TestHelper.GetConfigs(); var variationContextAccessor = TestHelper.VariationContextAccessor; @@ -85,10 +85,10 @@ namespace Umbraco.Tests.Runtimes composition.RegisterEssentials(logger, profiler, profilingLogger, mainDom, appCaches, databaseFactory, typeLoader, runtimeState, typeFinder, ioHelper, umbracoVersion, TestHelper.DbProviderFactoryCreator, hostingEnvironment, backOfficeInfo); // create the core runtime and have it compose itself - var coreRuntime = new CoreRuntime(configs, globalSettings, connectionStrings, umbracoVersion, ioHelper, logger, profiler, new AspNetUmbracoBootPermissionChecker(), hostingEnvironment, backOfficeInfo, TestHelper.DbProviderFactoryCreator, TestHelper.MainDom, typeFinder, NoAppCache.Instance); + var coreRuntime = new CoreRuntime(configs, globalSettings, connectionStrings, umbracoVersion, ioHelper, logger, profiler, new AspNetUmbracoBootPermissionChecker(), hostingEnvironment, backOfficeInfo, TestHelper.DbProviderFactoryCreator, TestHelper.MainDom, typeFinder, AppCaches.NoCache); // determine actual runtime level - runtimeState.DetermineRuntimeLevel(databaseFactory, logger); + runtimeState.DetermineRuntimeLevel(); Console.WriteLine(runtimeState.Level); // going to be Install BUT we want to force components to be there (nucache etc) runtimeState.Level = RuntimeLevel.Run; @@ -179,7 +179,7 @@ namespace Umbraco.Tests.Runtimes var scopeProvider = factory.GetInstance(); using (var scope = scopeProvider.CreateScope()) { - var creator = new DatabaseSchemaCreator(scope.Database, logger, umbracoVersion, globalSettings); + var creator = new DatabaseSchemaCreator(scope.Database, logger, umbracoVersion); creator.InitializeDatabaseSchema(); scope.Complete(); } @@ -296,7 +296,8 @@ namespace Umbraco.Tests.Runtimes // create the core runtime and have it compose itself var globalSettings = new GlobalSettingsBuilder().Build(); var connectionStrings = new ConnectionStringsBuilder().Build(); - var coreRuntime = new CoreRuntime(configs, globalSettings, connectionStrings, umbracoVersion, ioHelper, logger, profiler, new AspNetUmbracoBootPermissionChecker(), hostingEnvironment, backOfficeInfo, TestHelper.DbProviderFactoryCreator, TestHelper.MainDom, typeFinder, NoAppCache.Instance); + + var coreRuntime = new CoreRuntime(configs, globalSettings, connectionStrings, umbracoVersion, ioHelper, logger, profiler, new AspNetUmbracoBootPermissionChecker(), hostingEnvironment, backOfficeInfo, TestHelper.DbProviderFactoryCreator, TestHelper.MainDom, typeFinder, AppCaches.NoCache); // get the components // all of them? diff --git a/src/Umbraco.Tests/TestHelpers/TestHelper.cs b/src/Umbraco.Tests/TestHelpers/TestHelper.cs index 0eb96e753b..2131393b85 100644 --- a/src/Umbraco.Tests/TestHelpers/TestHelper.cs +++ b/src/Umbraco.Tests/TestHelpers/TestHelper.cs @@ -80,8 +80,6 @@ namespace Umbraco.Tests.TestHelpers public static Configs GetConfigs() => _testHelperInternal.GetConfigs(); - public static IRuntimeState GetRuntimeState() => _testHelperInternal.GetRuntimeState(); - public static IBackOfficeInfo GetBackOfficeInfo() => _testHelperInternal.GetBackOfficeInfo(); public static IConfigsFactory GetConfigsFactory() => _testHelperInternal.GetConfigsFactory(); diff --git a/src/Umbraco.Tests/TestHelpers/TestObjects.cs b/src/Umbraco.Tests/TestHelpers/TestObjects.cs index 73d258a5ed..b3f576b06d 100644 --- a/src/Umbraco.Tests/TestHelpers/TestObjects.cs +++ b/src/Umbraco.Tests/TestHelpers/TestObjects.cs @@ -15,16 +15,10 @@ using Umbraco.Core.Events; using Umbraco.Core.Hosting; using Umbraco.Core.IO; using Umbraco.Core.Logging; -using Umbraco.Core.Packaging; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.Mappers; -using Umbraco.Core.Persistence.Repositories; using Umbraco.Core.Persistence.SqlSyntax; -using Umbraco.Core.PropertyEditors; using Umbraco.Core.Scoping; -using Umbraco.Core.Services; -using Umbraco.Core.Services.Implement; -using Umbraco.Core.Strings; using Umbraco.Persistance.SqlCe; using Umbraco.Tests.Common.Builders; using Umbraco.Tests.TestHelpers.Stubs; @@ -74,163 +68,6 @@ namespace Umbraco.Tests.TestHelpers return new UmbracoDatabase(connection, sqlContext, logger, TestHelper.BulkSqlInsertProvider); } - public void RegisterServices(IRegister register) - { } - - /// - /// Gets a ServiceContext. - /// - /// - /// - /// A cache. - /// A logger. - /// An io helper. - /// - /// - /// An event messages factory. - /// Some url segment providers. - /// An Umbraco Version. - /// A container. - /// A ServiceContext. - /// Should be used sparingly for integration tests only - for unit tests - /// just mock the services to be passed to the ctor of the ServiceContext. - public ServiceContext GetServiceContext(IScopeProvider scopeProvider, IScopeAccessor scopeAccessor, - AppCaches cache, - ILogger logger, - IIOHelper ioHelper, - GlobalSettings globalSettings, - ContentSettings contentSettings, - IEventMessagesFactory eventMessagesFactory, - UrlSegmentProviderCollection urlSegmentProviders, - IUmbracoVersion umbracoVersion, - IHostingEnvironment hostingEnvironment, - IFactory factory = null) - { - if (scopeProvider == null) throw new ArgumentNullException(nameof(scopeProvider)); - if (scopeAccessor == null) throw new ArgumentNullException(nameof(scopeAccessor)); - if (cache == null) throw new ArgumentNullException(nameof(cache)); - if (logger == null) throw new ArgumentNullException(nameof(logger)); - if (eventMessagesFactory == null) throw new ArgumentNullException(nameof(eventMessagesFactory)); - - var scheme = Mock.Of(); - - var shortStringHelper = Mock.Of(); - - var mediaFileSystem = new MediaFileSystem(Mock.Of(), scheme, logger, shortStringHelper); - - var externalLoginService = GetLazyService(factory, c => new ExternalLoginService(scopeProvider, logger, eventMessagesFactory, GetRepo(c))); - var publicAccessService = GetLazyService(factory, c => new PublicAccessService(scopeProvider, logger, eventMessagesFactory, GetRepo(c))); - var domainService = GetLazyService(factory, c => new DomainService(scopeProvider, logger, eventMessagesFactory, GetRepo(c))); - var auditService = GetLazyService(factory, c => new AuditService(scopeProvider, logger, eventMessagesFactory, GetRepo(c), GetRepo(c))); - - var localizedTextService = GetLazyService(factory, c => new LocalizedTextService( - new Lazy(() => - { - var mainLangFolder = new DirectoryInfo(ioHelper.MapPath(Current.Configs.Global().UmbracoPath + "/config/lang/")); - var appPlugins = new DirectoryInfo(ioHelper.MapPath(Constants.SystemDirectories.AppPlugins)); - var configLangFolder = new DirectoryInfo(ioHelper.MapPath(Constants.SystemDirectories.Config + "/lang/")); - - var pluginLangFolders = appPlugins.Exists == false - ? Enumerable.Empty() - : appPlugins.GetDirectories() - .SelectMany(x => x.GetDirectories("Lang")) - .SelectMany(x => x.GetFiles("*.xml", SearchOption.TopDirectoryOnly)) - .Where(x => Path.GetFileNameWithoutExtension(x.FullName).Length == 5) - .Select(x => new LocalizedTextServiceSupplementaryFileSource(x, false)); - - //user defined langs that overwrite the default, these should not be used by plugin creators - var userLangFolders = configLangFolder.Exists == false - ? Enumerable.Empty() - : configLangFolder - .GetFiles("*.user.xml", SearchOption.TopDirectoryOnly) - .Where(x => Path.GetFileNameWithoutExtension(x.FullName).Length == 10) - .Select(x => new LocalizedTextServiceSupplementaryFileSource(x, true)); - - return new LocalizedTextServiceFileSources( - logger, - cache, - mainLangFolder, - pluginLangFolders.Concat(userLangFolders)); - - }), - logger)); - - var runtimeState = Mock.Of(); - var idkMap = new IdKeyMap(scopeProvider); - - var propertyEditorCollection = new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty())); - - var localizationService = GetLazyService(factory, c => new LocalizationService(scopeProvider, logger, eventMessagesFactory, GetRepo(c), GetRepo(c), GetRepo(c))); - var userService = GetLazyService(factory, c => new UserService(scopeProvider, logger, eventMessagesFactory, runtimeState, GetRepo(c), GetRepo(c), Options.Create(globalSettings))); - var dataTypeService = GetLazyService(factory, c => new DataTypeService(scopeProvider, logger, eventMessagesFactory, GetRepo(c), GetRepo(c), GetRepo(c), GetRepo(c), GetRepo(c), ioHelper, localizedTextService.Value, localizationService.Value, TestHelper.ShortStringHelper)); - var propertyValidationService = new Lazy(() => new PropertyValidationService(propertyEditorCollection, dataTypeService.Value, localizedTextService.Value)); - var contentService = GetLazyService(factory, c => new ContentService(scopeProvider, logger, eventMessagesFactory, GetRepo(c), GetRepo(c), GetRepo(c), GetRepo(c), GetRepo(c), GetRepo(c), propertyValidationService, TestHelper.ShortStringHelper)); - var notificationService = GetLazyService(factory, c => new NotificationService(scopeProvider, userService.Value, contentService.Value, localizationService.Value, logger, ioHelper, GetRepo(c), Options.Create(globalSettings), Options.Create(contentSettings), TestHelper.EmailSender)); - var serverRegistrationService = GetLazyService(factory, c => new ServerRegistrationService(scopeProvider, logger, eventMessagesFactory, GetRepo(c), TestHelper.GetHostingEnvironment())); - var memberGroupService = GetLazyService(factory, c => new MemberGroupService(scopeProvider, logger, eventMessagesFactory, GetRepo(c))); - var memberService = GetLazyService(factory, c => new MemberService(scopeProvider, logger, eventMessagesFactory, memberGroupService.Value, GetRepo(c), GetRepo(c), GetRepo(c), GetRepo(c))); - var mediaService = GetLazyService(factory, c => new MediaService(scopeProvider, mediaFileSystem, logger, eventMessagesFactory, GetRepo(c), GetRepo(c), GetRepo(c), GetRepo(c), TestHelper.ShortStringHelper)); - var contentTypeService = GetLazyService(factory, c => new ContentTypeService(scopeProvider, logger, eventMessagesFactory, contentService.Value, GetRepo(c), GetRepo(c), GetRepo(c), GetRepo(c))); - var mediaTypeService = GetLazyService(factory, c => new MediaTypeService(scopeProvider, logger, eventMessagesFactory, mediaService.Value, GetRepo(c), GetRepo(c), GetRepo(c), GetRepo(c))); - var fileService = GetLazyService(factory, c => new FileService(scopeProvider, logger, eventMessagesFactory, GetRepo(c), GetRepo(c), GetRepo(c), GetRepo(c), GetRepo(c), GetRepo(c), TestHelper.ShortStringHelper, Options.Create(globalSettings), hostingEnvironment)); - - var memberTypeService = GetLazyService(factory, c => new MemberTypeService(scopeProvider, logger, eventMessagesFactory, memberService.Value, GetRepo(c), GetRepo(c), GetRepo(c))); - var entityService = GetLazyService(factory, c => new EntityService(scopeProvider, logger, eventMessagesFactory, idkMap, GetRepo(c))); - - var macroService = GetLazyService(factory, c => new MacroService(scopeProvider, logger, eventMessagesFactory, GetRepo(c), GetRepo(c))); - var packagingService = GetLazyService(factory, c => - { - var compiledPackageXmlParser = new CompiledPackageXmlParser(new ConflictingPackageData(macroService.Value, fileService.Value), Options.Create(globalSettings)); - return new PackagingService( - auditService.Value, - new PackagesRepository(contentService.Value, contentTypeService.Value, dataTypeService.Value, fileService.Value, macroService.Value, localizationService.Value, hostingEnvironment, - new EntityXmlSerializer(contentService.Value, mediaService.Value, dataTypeService.Value, userService.Value, localizationService.Value, contentTypeService.Value, urlSegmentProviders, TestHelper.ShortStringHelper, propertyEditorCollection), logger, umbracoVersion, Options.Create(globalSettings), "createdPackages.config"), - new PackagesRepository(contentService.Value, contentTypeService.Value, dataTypeService.Value, fileService.Value, macroService.Value, localizationService.Value, hostingEnvironment, - new EntityXmlSerializer(contentService.Value, mediaService.Value, dataTypeService.Value, userService.Value, localizationService.Value, contentTypeService.Value, urlSegmentProviders, TestHelper.ShortStringHelper, propertyEditorCollection), logger, umbracoVersion, Options.Create(globalSettings), "installedPackages.config"), - new PackageInstallation( - new PackageDataInstallation(logger, fileService.Value, macroService.Value, localizationService.Value, dataTypeService.Value, entityService.Value, contentTypeService.Value, contentService.Value, propertyEditorCollection, scopeProvider, shortStringHelper, Options.Create(globalSettings), localizedTextService.Value), - new PackageFileInstallation(compiledPackageXmlParser, ioHelper, new ProfilingLogger(logger, new TestProfiler())), - compiledPackageXmlParser, Mock.Of(), - Mock.Of(x => x.ApplicationPhysicalPath == ioHelper.MapPath("~"))), - ioHelper); - }); - var relationService = GetLazyService(factory, c => new RelationService(scopeProvider, logger, eventMessagesFactory, entityService.Value, GetRepo(c), GetRepo(c), GetRepo(c))); - 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))); - var contentTypeServiceBaseFactory = GetLazyService(factory, c => new ContentTypeBaseServiceProvider(factory.GetInstance(),factory.GetInstance(),factory.GetInstance())); - - return new ServiceContext( - publicAccessService, - domainService, - auditService, - localizedTextService, - tagService, - contentService, - userService, - memberService, - mediaService, - contentTypeService, - mediaTypeService, - dataTypeService, - fileService, - localizationService, - packagingService, - serverRegistrationService, - entityService, - relationService, - macroService, - memberTypeService, - memberGroupService, - notificationService, - externalLoginService, - redirectUrlService, - consentService, - keyValueService, - contentTypeServiceBaseFactory); - } - private Lazy GetLazyService(IFactory container, Func ctor) where T : class { diff --git a/src/Umbraco.Tests/TestHelpers/TestWithDatabaseBase.cs b/src/Umbraco.Tests/TestHelpers/TestWithDatabaseBase.cs index ea53d36498..3c88f710bc 100644 --- a/src/Umbraco.Tests/TestHelpers/TestWithDatabaseBase.cs +++ b/src/Umbraco.Tests/TestHelpers/TestWithDatabaseBase.cs @@ -302,8 +302,7 @@ namespace Umbraco.Tests.TestHelpers { using (var scope = ScopeProvider.CreateScope()) { - var globalSettings = new GlobalSettingsBuilder().Build(); - var schemaHelper = new DatabaseSchemaCreator(scope.Database, Logger, UmbracoVersion, globalSettings); + var schemaHelper = new DatabaseSchemaCreator(scope.Database, Logger, UmbracoVersion); //Create the umbraco database and its base data schemaHelper.InitializeDatabaseSchema(); diff --git a/src/Umbraco.Web.BackOffice/Controllers/EntityController.cs b/src/Umbraco.Web.BackOffice/Controllers/EntityController.cs index 433cf6f345..1afe9bccf4 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/EntityController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/EntityController.cs @@ -248,6 +248,7 @@ namespace Umbraco.Web.BackOffice.Controllers /// UDI of the entity to fetch URL for /// The culture to fetch the URL for /// The URL or path to the item + [DetermineAmbiguousActionByPassingParameters] public HttpResponseMessage GetUrl(Udi udi, string culture = "*") { var intId = _entityService.GetId(udi); @@ -281,6 +282,7 @@ namespace Umbraco.Web.BackOffice.Controllers /// /// We are not restricting this with security because there is no sensitive data /// + [DetermineAmbiguousActionByPassingParameters] public HttpResponseMessage GetUrl(int id, UmbracoEntityTypes type, string culture = null) { culture = culture ?? ClientCulture(); diff --git a/src/Umbraco.Web.BackOffice/Controllers/UsersController.cs b/src/Umbraco.Web.BackOffice/Controllers/UsersController.cs index 66c998b8a8..3cffa307eb 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/UsersController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/UsersController.cs @@ -739,11 +739,16 @@ namespace Umbraco.Web.BackOffice.Controllers public async Task PostUnlockUsers([FromQuery]int[] userIds) { if (userIds.Length <= 0) return Ok(); + var notFound = new List(); foreach (var u in userIds) { var user = await _backOfficeUserManager.FindByIdAsync(u.ToString()); - if (user == null) throw new InvalidOperationException(); + if (user == null) + { + notFound.Add(u); + continue; + } var unlockResult = await _backOfficeUserManager.SetLockoutEndDateAsync(user, DateTimeOffset.Now); if (unlockResult.Succeeded == false) @@ -760,7 +765,7 @@ namespace Umbraco.Web.BackOffice.Controllers } return new UmbracoNotificationSuccessResponse( - _localizedTextService.Localize("speechBubbles/unlockUsersSuccess", new[] {userIds.Length.ToString()})); + _localizedTextService.Localize("speechBubbles/unlockUsersSuccess", new[] {(userIds.Length - notFound.Count).ToString()})); } [AdminUsersAuthorize("userIds")] diff --git a/src/Umbraco.Web.BackOffice/Extensions/UmbracoBackOfficeApplicationBuilderExtensions.cs b/src/Umbraco.Web.BackOffice/Extensions/BackOfficeApplicationBuilderExtensions.cs similarity index 79% rename from src/Umbraco.Web.BackOffice/Extensions/UmbracoBackOfficeApplicationBuilderExtensions.cs rename to src/Umbraco.Web.BackOffice/Extensions/BackOfficeApplicationBuilderExtensions.cs index 282668e4e6..3cba66812e 100644 --- a/src/Umbraco.Web.BackOffice/Extensions/UmbracoBackOfficeApplicationBuilderExtensions.cs +++ b/src/Umbraco.Web.BackOffice/Extensions/BackOfficeApplicationBuilderExtensions.cs @@ -7,7 +7,7 @@ using Umbraco.Web.BackOffice.Security; namespace Umbraco.Extensions { - public static class UmbracoBackOfficeApplicationBuilderExtensions + public static class BackOfficeApplicationBuilderExtensions { public static IApplicationBuilder UseUmbraco(this IApplicationBuilder app) { @@ -44,11 +44,6 @@ namespace Umbraco.Extensions app.UseUmbracoRuntimeMinification(); - // Important we handle image manipulations before the static files, otherwise the querystring is just ignored. - // TODO: Since we are dependent on these we need to register them but what happens when we call this multiple times since we are dependent on this for UseUmbracoWebsite too? - app.UseImageSharp(); - app.UseStaticFiles(); - app.UseMiddleware(); return app; diff --git a/src/Umbraco.Web.BackOffice/Extensions/UmbracoBackOfficeServiceCollectionExtensions.cs b/src/Umbraco.Web.BackOffice/Extensions/BackOfficeServiceCollectionExtensions.cs similarity index 64% rename from src/Umbraco.Web.BackOffice/Extensions/UmbracoBackOfficeServiceCollectionExtensions.cs rename to src/Umbraco.Web.BackOffice/Extensions/BackOfficeServiceCollectionExtensions.cs index 7be2306b3d..d9eb0987d0 100644 --- a/src/Umbraco.Web.BackOffice/Extensions/UmbracoBackOfficeServiceCollectionExtensions.cs +++ b/src/Umbraco.Web.BackOffice/Extensions/BackOfficeServiceCollectionExtensions.cs @@ -21,51 +21,9 @@ using Umbraco.Web.Common.Security; namespace Umbraco.Extensions { - public static class UmbracoBackOfficeServiceCollectionExtensions + + public static class BackOfficeServiceCollectionExtensions { - public static IServiceCollection AddUmbraco(this IServiceCollection services, IWebHostEnvironment webHostEnvironment, IConfiguration config) - { - if (services == null) throw new ArgumentNullException(nameof(services)); - - // TODO: We will need to decide on if we want to use the ServiceBasedControllerActivator to create our controllers - // or use the default IControllerActivator: DefaultControllerActivator (which doesn't directly use the container to resolve controllers) - // This will affect whether we need to explicitly register controllers in the container like we do today in v8. - // What we absolutely must do though is make sure we explicitly opt-in to using one or the other *always* for our controllers instead of - // relying on a global configuration set by a user since if a custom IControllerActivator is used for our own controllers we may not - // guarantee it will work. And then... is that even possible? - - // TODO: we will need to simplify this and prob just have a one or 2 main method that devs call which call all other required methods, - // but for now we'll just be explicit with all of them - services.AddUmbracoConfiguration(config); - services.AddUmbracoCore(webHostEnvironment, out var factory); - services.AddUmbracoWebComponents(); - services.AddUmbracoRuntimeMinifier(config); - services.AddUmbracoBackOffice(); - services.AddUmbracoBackOfficeIdentity(); - services.AddMiniProfiler(options => - { - options.ShouldProfile = request => false; // WebProfiler determine and start profiling. We should not use the MiniProfilerMiddleware to also profile - }); - - //We need to have runtime compilation of views when using umbraco. We could consider having only this when a specific config is set. - //But as far as I can see, there are still precompiled views, even when this is activated, so maybe it is okay. - services.AddControllersWithViews().AddRazorRuntimeCompilation(); - - - // If using Kestrel: https://stackoverflow.com/a/55196057 - services.Configure(options => - { - options.AllowSynchronousIO = true; - }); - - services.Configure(options => - { - options.AllowSynchronousIO = true; - }); - - return services; - } - /// /// Adds the services required for running the Umbraco back office /// @@ -106,7 +64,7 @@ namespace Umbraco.Extensions // Configure the options specifically for the UmbracoBackOfficeIdentityOptions instance services.ConfigureOptions(); services.ConfigureOptions(); - } + } private static IdentityBuilder BuildUmbracoBackOfficeIdentity(this IServiceCollection services) { diff --git a/src/Umbraco.Web.BackOffice/Extensions/UmbracoBuilderExtensions.cs b/src/Umbraco.Web.BackOffice/Extensions/UmbracoBuilderExtensions.cs new file mode 100644 index 0000000000..71325d70d4 --- /dev/null +++ b/src/Umbraco.Web.BackOffice/Extensions/UmbracoBuilderExtensions.cs @@ -0,0 +1,28 @@ +using Umbraco.Web.Common.Builder; + +namespace Umbraco.Extensions +{ + public static class UmbracoBuilderExtensions + { + public static void BuildWithAllBackOfficeComponents(this IUmbracoBuilder builder) + { + builder + .WithConfiguration() + .WithCore() + .WithWebComponents() + .WithRuntimeMinifier() + .WithBackOffice() + .WithBackOfficeIdentity() + .WithMiniProfiler() + .WithMvcAndRazor() + .WithWebServer() + .Build(); + } + + public static IUmbracoBuilder WithBackOffice(this IUmbracoBuilder builder) + => builder.AddWith(nameof(WithBackOffice), () => builder.Services.AddUmbracoBackOffice()); + + public static IUmbracoBuilder WithBackOfficeIdentity(this IUmbracoBuilder builder) + => builder.AddWith(nameof(WithBackOfficeIdentity), () => builder.Services.AddUmbracoBackOfficeIdentity()); + } +} diff --git a/src/Umbraco.Web.BackOffice/Umbraco.Web.BackOffice.csproj b/src/Umbraco.Web.BackOffice/Umbraco.Web.BackOffice.csproj index 2547117531..1c66edc2ce 100644 --- a/src/Umbraco.Web.BackOffice/Umbraco.Web.BackOffice.csproj +++ b/src/Umbraco.Web.BackOffice/Umbraco.Web.BackOffice.csproj @@ -16,7 +16,7 @@ - + diff --git a/src/Umbraco.Web.Common/Builder/IUmbracoBuilder.cs b/src/Umbraco.Web.Common/Builder/IUmbracoBuilder.cs new file mode 100644 index 0000000000..2de7d2d285 --- /dev/null +++ b/src/Umbraco.Web.Common/Builder/IUmbracoBuilder.cs @@ -0,0 +1,18 @@ +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using System; +using System.Text; + +namespace Umbraco.Web.Common.Builder +{ + + public interface IUmbracoBuilder + { + IServiceCollection Services { get; } + IWebHostEnvironment WebHostEnvironment { get; } + IConfiguration Config { get; } + IUmbracoBuilder AddWith(string key, Action add); + void Build(); + } +} diff --git a/src/Umbraco.Web.Common/Builder/UmbracoBuilder.cs b/src/Umbraco.Web.Common/Builder/UmbracoBuilder.cs new file mode 100644 index 0000000000..3efb1e74f5 --- /dev/null +++ b/src/Umbraco.Web.Common/Builder/UmbracoBuilder.cs @@ -0,0 +1,37 @@ +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using System; +using System.Collections.Generic; + +namespace Umbraco.Web.Common.Builder +{ + public class UmbracoBuilder : IUmbracoBuilder + { + private readonly Dictionary _registrations = new Dictionary(); + + public UmbracoBuilder(IServiceCollection services, IWebHostEnvironment webHostEnvironment, IConfiguration config) + { + Services = services; + WebHostEnvironment = webHostEnvironment; + Config = config; + } + + public IServiceCollection Services { get; } + public IWebHostEnvironment WebHostEnvironment { get; } + public IConfiguration Config { get; } + + public IUmbracoBuilder AddWith(string key, Action add) + { + if (_registrations.ContainsKey(key)) return this; + _registrations[key] = add; + return this; + } + + public void Build() + { + foreach (var a in _registrations) + a.Value(); + } + } +} diff --git a/src/Umbraco.Web.Common/Builder/UmbracoBuilderExtensions.cs b/src/Umbraco.Web.Common/Builder/UmbracoBuilderExtensions.cs new file mode 100644 index 0000000000..dd91a2cca9 --- /dev/null +++ b/src/Umbraco.Web.Common/Builder/UmbracoBuilderExtensions.cs @@ -0,0 +1,70 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Server.Kestrel.Core; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using System; +using Umbraco.Extensions; + +namespace Umbraco.Web.Common.Builder +{ + // TODO: We could add parameters to configure each of these for flexibility + public static class UmbracoBuilderExtensions + { + public static IUmbracoBuilder AddUmbraco(this IServiceCollection services, IWebHostEnvironment webHostEnvironment, IConfiguration config) + { + if (services is null) throw new ArgumentNullException(nameof(services)); + if (webHostEnvironment is null) throw new ArgumentNullException(nameof(webHostEnvironment)); + if (config is null) throw new ArgumentNullException(nameof(config)); + + var builder = new UmbracoBuilder(services, webHostEnvironment, config); + return builder; + } + + public static IUmbracoBuilder WithConfiguration(this IUmbracoBuilder builder) + => builder.AddWith(nameof(WithConfiguration), () => builder.Services.AddUmbracoConfiguration(builder.Config)); + + public static IUmbracoBuilder WithCore(this IUmbracoBuilder builder) + => builder.AddWith(nameof(WithCore), () => builder.Services.AddUmbracoCore(builder.WebHostEnvironment, out _)); + + public static IUmbracoBuilder WithMiniProfiler(this IUmbracoBuilder builder) + => builder.AddWith(nameof(WithMiniProfiler), () => + builder.Services.AddMiniProfiler(options => + { + options.ShouldProfile = request => false; // WebProfiler determine and start profiling. We should not use the MiniProfilerMiddleware to also profile + })); + + public static IUmbracoBuilder WithMvcAndRazor(this IUmbracoBuilder builder, Action mvcOptions = null, Action mvcBuilding = null) + => builder.AddWith(nameof(WithMvcAndRazor), () => + { + // TODO: We need to figure out if we can work around this because calling AddControllersWithViews modifies the global app and order is very important + // this will directly affect developers who need to call that themselves. + //We need to have runtime compilation of views when using umbraco. We could consider having only this when a specific config is set. + //But as far as I can see, there are still precompiled views, even when this is activated, so maybe it is okay. + var mvcBuilder = builder.Services.AddControllersWithViews(mvcOptions).AddRazorRuntimeCompilation(); + mvcBuilding?.Invoke(mvcBuilder); + }); + + public static IUmbracoBuilder WithRuntimeMinifier(this IUmbracoBuilder builder) + => builder.AddWith(nameof(WithRuntimeMinifier), () => builder.Services.AddUmbracoRuntimeMinifier(builder.Config)); + + public static IUmbracoBuilder WithWebComponents(this IUmbracoBuilder builder) + => builder.AddWith(nameof(WithWebComponents), () => builder.Services.AddUmbracoWebComponents()); + + public static IUmbracoBuilder WithWebServer(this IUmbracoBuilder builder) + => builder.AddWith(nameof(WithWebServer), () => + { + // TODO: We need to figure out why thsi is needed and fix those endpoints to not need them, we don't want to change global things + // If using Kestrel: https://stackoverflow.com/a/55196057 + builder.Services.Configure(options => + { + options.AllowSynchronousIO = true; + }); + builder.Services.Configure(options => + { + options.AllowSynchronousIO = true; + }); + }); + } +} diff --git a/src/Umbraco.Web.Common/Extensions/LinkGeneratorExtensions.cs b/src/Umbraco.Web.Common/Extensions/LinkGeneratorExtensions.cs index e3f335150e..f2babdb07c 100644 --- a/src/Umbraco.Web.Common/Extensions/LinkGeneratorExtensions.cs +++ b/src/Umbraco.Web.Common/Extensions/LinkGeneratorExtensions.cs @@ -56,7 +56,7 @@ namespace Umbraco.Extensions { return linkGenerator.GetPathByAction(nameof(InstallApiController.GetSetup), ControllerExtensions.GetControllerName(), - new { area = Constants.Web.Mvc.InstallArea }); + new { area = Constants.Web.Mvc.InstallArea }).TrimEnd(nameof(InstallApiController.GetSetup)); } /// diff --git a/src/Umbraco.Web.Common/Extensions/UmbracoCoreServiceCollectionExtensions.cs b/src/Umbraco.Web.Common/Extensions/UmbracoCoreServiceCollectionExtensions.cs index 2245784c44..7afa4adf68 100644 --- a/src/Umbraco.Web.Common/Extensions/UmbracoCoreServiceCollectionExtensions.cs +++ b/src/Umbraco.Web.Common/Extensions/UmbracoCoreServiceCollectionExtensions.cs @@ -172,12 +172,17 @@ namespace Umbraco.Extensions IHttpContextAccessor httpContextAccessor = new HttpContextAccessor(); services.AddSingleton(httpContextAccessor); + var requestCache = new GenericDictionaryRequestAppCache(() => httpContextAccessor.HttpContext?.Items); + var appCaches = new AppCaches( + new DeepCloneAppCache(new ObjectCacheAppCache()), + requestCache, + new IsolatedCaches(type => new DeepCloneAppCache(new ObjectCacheAppCache()))); services.AddUmbracoCore(webHostEnvironment, umbContainer, Assembly.GetEntryAssembly(), - requestCache, + appCaches, loggingConfig, out factory); @@ -191,7 +196,7 @@ namespace Umbraco.Extensions /// /// /// - /// + /// /// /// /// @@ -201,9 +206,35 @@ namespace Umbraco.Extensions IWebHostEnvironment webHostEnvironment, IRegister umbContainer, Assembly entryAssembly, - IRequestCache requestCache, + AppCaches appCaches, ILoggingConfiguration loggingConfiguration, out IFactory factory) + => services.AddUmbracoCore(webHostEnvironment, umbContainer, entryAssembly, appCaches, loggingConfiguration, GetCoreRuntime, out factory); + + + /// + /// Adds the Umbraco Back Core requirements + /// + /// + /// + /// + /// + /// + /// + /// + /// Delegate to create an + /// + /// + public static IServiceCollection AddUmbracoCore( + this IServiceCollection services, + IWebHostEnvironment webHostEnvironment, + IRegister umbContainer, + Assembly entryAssembly, + AppCaches appCaches, + ILoggingConfiguration loggingConfiguration, + // TODO: Yep that's extremely ugly + Func getRuntime, + out IFactory factory) { if (services is null) throw new ArgumentNullException(nameof(services)); var container = umbContainer; @@ -236,6 +267,7 @@ namespace Umbraco.Extensions var typeFinderSettings = serviceProvider.GetService>(); var dbProviderFactoryCreator = serviceProvider.GetRequiredService(); + var configs = serviceProvider.GetRequiredService(); CreateCompositionRoot(services, globalSettings, @@ -247,8 +279,7 @@ namespace Umbraco.Extensions var umbracoVersion = new UmbracoVersion(); var typeFinder = CreateTypeFinder(logger, profiler, webHostEnvironment, entryAssembly, typeFinderSettings); - var configs = serviceProvider.GetService(); - var coreRuntime = GetCoreRuntime( + var runtime = getRuntime( configs, globalSettings.CurrentValue, connectionStrings.Value, @@ -259,10 +290,10 @@ namespace Umbraco.Extensions hostingEnvironment, backOfficeInfo, typeFinder, - requestCache, + appCaches, dbProviderFactoryCreator); - factory = coreRuntime.Configure(container); + factory = runtime.Configure(container); return services; } @@ -278,7 +309,7 @@ namespace Umbraco.Extensions private static IRuntime GetCoreRuntime( Configs configs, GlobalSettings globalSettings, ConnectionStrings connectionStrings, IUmbracoVersion umbracoVersion, IIOHelper ioHelper, Core.Logging.ILogger logger, IProfiler profiler, Core.Hosting.IHostingEnvironment hostingEnvironment, IBackOfficeInfo backOfficeInfo, - ITypeFinder typeFinder, IRequestCache requestCache, IDbProviderFactoryCreator dbProviderFactoryCreator) + ITypeFinder typeFinder, AppCaches appCaches, IDbProviderFactoryCreator dbProviderFactoryCreator) { // Determine if we should use the sql main dom or the default var appSettingMainDomLock = globalSettings.MainDomLock; @@ -304,7 +335,7 @@ namespace Umbraco.Extensions dbProviderFactoryCreator, mainDom, typeFinder, - requestCache); + appCaches); return coreRuntime; } diff --git a/src/Umbraco.Web.Common/Profiler/WebProfilerComponent.cs b/src/Umbraco.Web.Common/Profiler/WebProfilerComponent.cs index bc5cce9df1..dfd13ff101 100644 --- a/src/Umbraco.Web.Common/Profiler/WebProfilerComponent.cs +++ b/src/Umbraco.Web.Common/Profiler/WebProfilerComponent.cs @@ -1,4 +1,6 @@ -using System; +using Microsoft.AspNetCore.Http; +using System; +using System.Collections.Generic; using Umbraco.Core.Composing; using Umbraco.Core.Logging; using Umbraco.Net; @@ -13,6 +15,7 @@ namespace Umbraco.Web.Common.Profiler private readonly WebProfiler _profiler; private readonly IUmbracoApplicationLifetime _umbracoApplicationLifetime; private readonly IUmbracoRequestLifetime _umbracoRequestLifetime; + private readonly List _terminate = new List(); public WebProfilerComponent(IProfiler profiler, ILogger logger, IUmbracoRequestLifetime umbracoRequestLifetime, IUmbracoApplicationLifetime umbracoApplicationLifetime) @@ -44,13 +47,19 @@ namespace Umbraco.Web.Common.Profiler public void Terminate() { + _umbracoApplicationLifetime.ApplicationInit -= InitializeApplication; + foreach (var t in _terminate) t(); } private void InitializeApplication(object sender, EventArgs args) { - _umbracoRequestLifetime.RequestStart += - (sender, context) => _profiler.UmbracoApplicationBeginRequest(context); - _umbracoRequestLifetime.RequestEnd += (sender, context) => _profiler.UmbracoApplicationEndRequest(context); + void requestStart(object sender, HttpContext context) => _profiler.UmbracoApplicationBeginRequest(context); + _umbracoRequestLifetime.RequestStart += requestStart; + _terminate.Add(() => _umbracoRequestLifetime.RequestStart -= requestStart); + + void requestEnd(object sender, HttpContext context) => _profiler.UmbracoApplicationEndRequest(context); + _umbracoRequestLifetime.RequestEnd += requestEnd; + _terminate.Add(() => _umbracoRequestLifetime.RequestEnd -= requestEnd); // Stop the profiling of the booting process _profiler.StopBoot(); diff --git a/src/Umbraco.Web.Common/Umbraco.Web.Common.csproj b/src/Umbraco.Web.Common/Umbraco.Web.Common.csproj index 2c8ce61d9a..ee657e82bb 100644 --- a/src/Umbraco.Web.Common/Umbraco.Web.Common.csproj +++ b/src/Umbraco.Web.Common/Umbraco.Web.Common.csproj @@ -22,6 +22,7 @@ + diff --git a/src/Umbraco.Web.UI.NetCore/Startup.cs b/src/Umbraco.Web.UI.NetCore/Startup.cs index 673ac02013..72e0d792f3 100644 --- a/src/Umbraco.Web.UI.NetCore/Startup.cs +++ b/src/Umbraco.Web.UI.NetCore/Startup.cs @@ -5,6 +5,7 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Umbraco.Extensions; +using Umbraco.Web.Common.Builder; namespace Umbraco.Web.UI.NetCore { @@ -31,7 +32,8 @@ namespace Umbraco.Web.UI.NetCore // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940 public void ConfigureServices(IServiceCollection services) { - services.AddUmbraco(_env, _config); + var umbracoBuilder = services.AddUmbraco(_env, _config); + umbracoBuilder.BuildWithAllBackOfficeComponents(); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. diff --git a/src/Umbraco.Web/Compose/AuditEventsComponent.cs b/src/Umbraco.Web/Compose/AuditEventsComponent.cs index 9aabfd0a66..d3667ef93c 100644 --- a/src/Umbraco.Web/Compose/AuditEventsComponent.cs +++ b/src/Umbraco.Web/Compose/AuditEventsComponent.cs @@ -48,7 +48,19 @@ namespace Umbraco.Core.Compose } public void Terminate() - { } + { + UserService.SavedUserGroup -= OnSavedUserGroupWithUsers; + + UserService.SavedUser -= OnSavedUser; + UserService.DeletedUser -= OnDeletedUser; + UserService.UserGroupPermissionsAssigned -= UserGroupPermissionAssigned; + + MemberService.Saved -= OnSavedMember; + MemberService.Deleted -= OnDeletedMember; + MemberService.AssignedRoles -= OnAssignedRoles; + MemberService.RemovedRoles -= OnRemovedRoles; + MemberService.Exported -= OnMemberExported; + } public static IUser UnknownUser(IGlobalSettings globalSettings) => new User(ConfigModelConversionsFromLegacy.ConvertGlobalSettings(globalSettings)) { Id = Constants.Security.UnknownUserId, Name = Constants.Security.UnknownUserName, Email = "" }; diff --git a/src/Umbraco.Web/Compose/BackOfficeUserAuditEventsComponent.cs b/src/Umbraco.Web/Compose/BackOfficeUserAuditEventsComponent.cs index dcb5fac32d..e4f9679f4c 100644 --- a/src/Umbraco.Web/Compose/BackOfficeUserAuditEventsComponent.cs +++ b/src/Umbraco.Web/Compose/BackOfficeUserAuditEventsComponent.cs @@ -38,7 +38,19 @@ namespace Umbraco.Web.Compose } public void Terminate() - { } + { + //BackOfficeUserManager.AccountLocked -= ; + //BackOfficeUserManager.AccountUnlocked -= ; + BackOfficeOwinUserManager.ForgotPasswordRequested -= OnForgotPasswordRequest; + BackOfficeOwinUserManager.ForgotPasswordChangedSuccess -= OnForgotPasswordChange; + BackOfficeOwinUserManager.LoginFailed -= OnLoginFailed; + //BackOfficeUserManager.LoginRequiresVerification -= ; + BackOfficeOwinUserManager.LoginSuccess -= OnLoginSuccess; + BackOfficeOwinUserManager.LogoutSuccess -= OnLogoutSuccess; + BackOfficeOwinUserManager.PasswordChanged -= OnPasswordChanged; + BackOfficeOwinUserManager.PasswordReset -= OnPasswordReset; + //BackOfficeUserManager.ResetAccessFailedCount -= ; + } private IUser GetPerformingUser(int userId) { diff --git a/src/Umbraco.Web/Logging/WebProfilerComponent.cs b/src/Umbraco.Web/Logging/WebProfilerComponent.cs index 2959e12ad7..2edeea6a1b 100755 --- a/src/Umbraco.Web/Logging/WebProfilerComponent.cs +++ b/src/Umbraco.Web/Logging/WebProfilerComponent.cs @@ -35,7 +35,9 @@ namespace Umbraco.Web.Logging } public void Terminate() - { } + { + UmbracoApplicationBase.ApplicationInit -= InitializeApplication; + } private void InitializeApplication(object sender, EventArgs args) { diff --git a/src/Umbraco.Web/UmbracoApplication.cs b/src/Umbraco.Web/UmbracoApplication.cs index 1ee5231ec7..37e2522d43 100644 --- a/src/Umbraco.Web/UmbracoApplication.cs +++ b/src/Umbraco.Web/UmbracoApplication.cs @@ -37,9 +37,14 @@ namespace Umbraco.Web var mainDom = new MainDom(logger, mainDomLock); var requestCache = new HttpRequestAppCache(() => HttpContext.Current != null ? HttpContext.Current.Items : null); + var appCaches = new AppCaches( + new DeepCloneAppCache(new ObjectCacheAppCache()), + requestCache, + new IsolatedCaches(type => new DeepCloneAppCache(new ObjectCacheAppCache()))); + var umbracoBootPermissionChecker = new AspNetUmbracoBootPermissionChecker(); - return new CoreRuntime(configs, globalSettings, connectionStrings, umbracoVersion, ioHelper, logger, profiler, umbracoBootPermissionChecker, hostingEnvironment, backOfficeInfo, dbProviderFactoryCreator, mainDom, - GetTypeFinder(hostingEnvironment, logger, profiler), requestCache); + return new CoreRuntime(configs, globalSettings, connectionStrings,umbracoVersion, ioHelper, logger, profiler, umbracoBootPermissionChecker, hostingEnvironment, backOfficeInfo, dbProviderFactoryCreator, mainDom, + GetTypeFinder(hostingEnvironment, logger, profiler), appCaches); }