From b5f900e789fb2e40cc166e5eee56e84f717b2319 Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 28 Jul 2014 19:37:44 -0700 Subject: [PATCH 1/6] added a couple of tests for member names --- .../Services/MemberServiceTests.cs | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/src/Umbraco.Tests/Services/MemberServiceTests.cs b/src/Umbraco.Tests/Services/MemberServiceTests.cs index e8bc946f6e..8091923ca0 100644 --- a/src/Umbraco.Tests/Services/MemberServiceTests.cs +++ b/src/Umbraco.Tests/Services/MemberServiceTests.cs @@ -2,6 +2,7 @@ using System; using System.Linq; using NUnit.Framework; using Umbraco.Core; +using Umbraco.Core.Events; using Umbraco.Core.Models; using Umbraco.Core.Models.Membership; using Umbraco.Core.Models.Rdbms; @@ -352,6 +353,34 @@ namespace Umbraco.Tests.Services Assert.IsNull(ServiceContext.MemberService.GetByEmail("do@not.find")); } + [Test] + public void Get_Member_Name() + { + IMemberType memberType = MockedContentTypes.CreateSimpleMemberType(); + ServiceContext.MemberTypeService.Save(memberType); + IMember member = MockedMember.CreateSimpleMember(memberType, "Test Real Name", "test@test.com", "pass", "testUsername"); + ServiceContext.MemberService.Save(member); + + + Assert.AreEqual("Test Real Name", member.Name); + } + + [Test] + public void Get_Member_Name_In_Created_Event() + { + IMemberType memberType = MockedContentTypes.CreateSimpleMemberType(); + ServiceContext.MemberTypeService.Save(memberType); + + TypedEventHandler> callback = (sender, args) => + { + Assert.AreEqual("Test Real Name", args.Entity.Name); + }; + + MemberService.Created += callback; + var member = ServiceContext.MemberService.CreateMember("testUsername", "test@test.com", "Test Real Name", memberType); + MemberService.Created -= callback; + } + [Test] public void Get_By_Username() { From 7261162d8a686f97d6124a89194bf8e0a8e4fabd Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 4 Aug 2014 17:50:19 -0600 Subject: [PATCH 2/6] Fixes some of U4-2633 Bundle all cache refresher transmissions into a single call per request for much better performance. See last note on issue. --- .../Sync/DefaultServerMessenger.cs | 48 +++++++---------- src/Umbraco.Core/Sync/IServerAddress.cs | 2 + src/Umbraco.Web/BatchedServerMessenger.cs | 53 ++++++++++++++++--- 3 files changed, 67 insertions(+), 36 deletions(-) diff --git a/src/Umbraco.Core/Sync/DefaultServerMessenger.cs b/src/Umbraco.Core/Sync/DefaultServerMessenger.cs index f6704dd413..d6839bc848 100644 --- a/src/Umbraco.Core/Sync/DefaultServerMessenger.cs +++ b/src/Umbraco.Core/Sync/DefaultServerMessenger.cs @@ -20,18 +20,19 @@ namespace Umbraco.Core.Sync { private readonly Func> _getUserNamePasswordDelegate; private volatile bool _hasResolvedDelegate = false; - private readonly object _locker = new object(); - private bool _useDistributedCalls; + private readonly object _locker = new object(); protected string Login { get; private set; } protected string Password{ get; private set; } + protected bool UseDistributedCalls { get; private set; } + /// /// Without a username/password all distribuion will be disabled /// internal DefaultServerMessenger() { - _useDistributedCalls = false; + UseDistributedCalls = false; } /// @@ -55,7 +56,7 @@ namespace Umbraco.Core.Sync if (login == null) throw new ArgumentNullException("login"); if (password == null) throw new ArgumentNullException("password"); - _useDistributedCalls = useDistributedCalls; + UseDistributedCalls = useDistributedCalls; Login = login; Password = password; } @@ -221,13 +222,13 @@ namespace Umbraco.Core.Sync { Login = null; Password = null; - _useDistributedCalls = false; + UseDistributedCalls = false; } else { Login = result.Item1; Password = result.Item2; - _useDistributedCalls = UmbracoSettings.UseDistributedCalls; + UseDistributedCalls = UmbracoSettings.UseDistributedCalls; } } catch (Exception ex) @@ -235,7 +236,7 @@ namespace Umbraco.Core.Sync LogHelper.Error("Could not resolve username/password delegate, server distribution will be disabled", ex); Login = null; Password = null; - _useDistributedCalls = false; + UseDistributedCalls = false; } } } @@ -314,7 +315,7 @@ namespace Umbraco.Core.Sync //Now, check if we are using Distrubuted calls. If there are no servers in the list then we // can definitely not distribute. - if (!_useDistributedCalls || !servers.Any()) + if (!UseDistributedCalls || !servers.Any()) { //if we are not, then just invoke the call on the cache refresher InvokeMethodOnRefresherInstance(refresher, dispatchType, getId, instances); @@ -325,7 +326,7 @@ namespace Umbraco.Core.Sync MessageSeversForIdsOrJson(servers, refresher, dispatchType, instances.Select(getId)); } - private void MessageSeversForIdsOrJson( + protected virtual void MessageSeversForIdsOrJson( IEnumerable servers, ICacheRefresher refresher, MessageType dispatchType, @@ -345,7 +346,7 @@ namespace Umbraco.Core.Sync //Now, check if we are using Distrubuted calls. If there are no servers in the list then we // can definitely not distribute. - if (!_useDistributedCalls || !servers.Any()) + if (!UseDistributedCalls || !servers.Any()) { //if we are not, then just invoke the call on the cache refresher InvokeMethodOnRefresherInstance(refresher, dispatchType, ids, jsonPayload); @@ -456,16 +457,16 @@ namespace Umbraco.Core.Sync } } - List waitHandlesList; - var asyncResults = GetAsyncResults(asyncResultsList, out waitHandlesList); - + var waitHandlesList = asyncResultsList.Select(x => x.AsyncWaitHandle).ToArray(); + var errorCount = 0; - // Once for each WaitHandle that we have, wait for a response and log it - // We're previously submitted all these requests effectively in parallel and will now retrieve responses on a FIFO basis - foreach (var t in asyncResults) + //Wait for all requests to complete + WaitHandle.WaitAll(waitHandlesList.ToArray()); + + foreach (var t in asyncResultsList) { - var handleIndex = WaitHandle.WaitAny(waitHandlesList.ToArray(), TimeSpan.FromSeconds(15)); + //var handleIndex = WaitHandle.WaitAny(waitHandlesList.ToArray(), TimeSpan.FromSeconds(15)); try { @@ -520,18 +521,7 @@ namespace Umbraco.Core.Sync LogDispatchBatchError(ee); } } - - internal IEnumerable GetAsyncResults(List asyncResultsList, out List waitHandlesList) - { - var asyncResults = asyncResultsList.ToArray(); - waitHandlesList = new List(); - foreach (var asyncResult in asyncResults) - { - waitHandlesList.Add(asyncResult.AsyncWaitHandle); - } - return asyncResults; - } - + private void LogDispatchBatchError(Exception ee) { LogHelper.Error("Error refreshing distributed list", ee); diff --git a/src/Umbraco.Core/Sync/IServerAddress.cs b/src/Umbraco.Core/Sync/IServerAddress.cs index 8463c66c61..08333846a7 100644 --- a/src/Umbraco.Core/Sync/IServerAddress.cs +++ b/src/Umbraco.Core/Sync/IServerAddress.cs @@ -8,5 +8,7 @@ namespace Umbraco.Core.Sync internal interface IServerAddress { string ServerAddress { get; } + + //TODO : Should probably add things like port, protocol, server name, app id } } \ No newline at end of file diff --git a/src/Umbraco.Web/BatchedServerMessenger.cs b/src/Umbraco.Web/BatchedServerMessenger.cs index 669c1de817..8163bed4a1 100644 --- a/src/Umbraco.Web/BatchedServerMessenger.cs +++ b/src/Umbraco.Web/BatchedServerMessenger.cs @@ -67,6 +67,43 @@ namespace Umbraco.Web public string JsonPayload { get; set; } } + /// + /// We need to check if distributed calls are enabled, if they are we also want to make sure + /// that the current server's cache is updated internally in real time instead of at the end of + /// the call. This is because things like the URL cache, etc... might need to be updated during + /// the request that is making these calls. + /// + /// + /// + /// + /// + /// + /// + /// See: http://issues.umbraco.org/issue/U4-2633#comment=67-15604 + /// + protected override void MessageSeversForIdsOrJson(IEnumerable servers, ICacheRefresher refresher, MessageType dispatchType, IEnumerable ids = null, string jsonPayload = null) + { + //do all the normal stuff + base.MessageSeversForIdsOrJson(servers, refresher, dispatchType, ids, jsonPayload); + + //Now, check if we are using Distrubuted calls + if (UseDistributedCalls && servers.Any()) + { + //invoke on the current server - we will basically be double cache refreshing for the calling + // server but that just needs to be done currently, see the link above for details. + InvokeMethodOnRefresherInstance(refresher, dispatchType, ids, jsonPayload); + } + } + + /// + /// This adds the call to batched list + /// + /// + /// + /// + /// + /// + /// protected override void PerformDistributedCall( IEnumerable servers, ICacheRefresher refresher, @@ -75,6 +112,7 @@ namespace Umbraco.Web Type idArrayType = null, string jsonPayload = null) { + //NOTE: we use UmbracoContext instead of HttpContext.Current because when some web methods run async, the // HttpContext.Current is null but the UmbracoContext.Current won't be since we manually assign it. if (UmbracoContext.Current == null || UmbracoContext.Current.HttpContext == null) @@ -208,16 +246,17 @@ namespace Umbraco.Web instructions, Login, Password, null, null)); } - List waitHandlesList; - var asyncResults = GetAsyncResults(asyncResultsList, out waitHandlesList); - + var waitHandlesList = asyncResultsList.Select(x => x.AsyncWaitHandle).ToArray(); + var errorCount = 0; - // Once for each WaitHandle that we have, wait for a response and log it - // We're previously submitted all these requests effectively in parallel and will now retrieve responses on a FIFO basis - foreach (var t in asyncResults) + //Wait for all requests to complete + WaitHandle.WaitAll(waitHandlesList.ToArray()); + + foreach (var t in asyncResultsList) { - var handleIndex = WaitHandle.WaitAny(waitHandlesList.ToArray(), TimeSpan.FromSeconds(15)); + //var handleIndex = WaitHandle.WaitAny(waitHandlesList.ToArray(), TimeSpan.FromSeconds(15)); + try { cacheRefresher.EndBulkRefresh(t); From ce29f5466369da6800d3e19ccd2f7b9852139c3a Mon Sep 17 00:00:00 2001 From: Stephan Date: Wed, 7 May 2014 19:09:08 +0200 Subject: [PATCH 3/6] U4-4837 - bugfix Umbraco.Core.EnumerableExtensions.InGroupsOf --- src/Umbraco.Core/EnumerableExtensions.cs | 42 ++++-------------------- 1 file changed, 6 insertions(+), 36 deletions(-) diff --git a/src/Umbraco.Core/EnumerableExtensions.cs b/src/Umbraco.Core/EnumerableExtensions.cs index 525f75e709..8055b33ab8 100644 --- a/src/Umbraco.Core/EnumerableExtensions.cs +++ b/src/Umbraco.Core/EnumerableExtensions.cs @@ -15,43 +15,13 @@ namespace Umbraco.Core public static IEnumerable> InGroupsOf(this IEnumerable source, int groupSize) { if (source == null) - throw new NullReferenceException("source"); + throw new ArgumentNullException("source"); + if (groupSize <= 0) + throw new ArgumentException("Must be greater than zero.", "groupSize"); - // enumerate the source only once! - return new InGroupsEnumerator(source, groupSize).Groups(); - } - - // this class makes sure that the source is enumerated only ONCE - // which means that when it is enumerated, the actual groups content - // has to be evaluated at the same time, and stored in an array. - private class InGroupsEnumerator - { - private readonly IEnumerator _source; - private readonly int _count; - private bool _mightHaveNext; - - public InGroupsEnumerator(IEnumerable source, int count) - { - _source = source.GetEnumerator(); - _count = count; - _mightHaveNext = true; - } - - public IEnumerable> Groups() - { - while (_mightHaveNext && _source.MoveNext()) - yield return Group().ToArray(); // see note above - } - - private IEnumerable Group() - { - var c = 0; - do - { - yield return _source.Current; - } while (++c < _count && _source.MoveNext()); - _mightHaveNext = c == _count; - } + return source + .Select((x, i) => Tuple.Create(i / groupSize, x)) + .GroupBy(t => t.Item1, t => t.Item2); } /// The distinct by. From 3374f9e02301941fe45d14568976adfbf65ff5b0 Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 5 Aug 2014 10:32:07 -0600 Subject: [PATCH 4/6] Ensures that the batched distributed calls doesn't cache refresh the server executing the distributed calls since tht will already be done locally. --- .../Sync/ServerSyncWebServiceClient.cs | 6 ++++-- src/Umbraco.Web/BatchedServerMessenger.cs | 4 +++- .../umbraco/webservices/CacheRefresher.asmx.cs | 15 ++++++++++++++- 3 files changed, 21 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Core/Sync/ServerSyncWebServiceClient.cs b/src/Umbraco.Core/Sync/ServerSyncWebServiceClient.cs index 923ed3c670..6a620bc4c3 100644 --- a/src/Umbraco.Core/Sync/ServerSyncWebServiceClient.cs +++ b/src/Umbraco.Core/Sync/ServerSyncWebServiceClient.cs @@ -22,19 +22,21 @@ namespace Umbraco.Core.Sync /// [System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://umbraco.org/webservices/BulkRefresh", RequestNamespace = "http://umbraco.org/webservices/", ResponseNamespace = "http://umbraco.org/webservices/", Use = System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle = System.Web.Services.Protocols.SoapParameterStyle.Wrapped)] - public void BulkRefresh(RefreshInstruction[] instructions, string login, string password) + public void BulkRefresh(RefreshInstruction[] instructions, string appId, string login, string password) { this.Invoke("BulkRefresh", new object[] { instructions, + appId, login, password}); } /// - public System.IAsyncResult BeginBulkRefresh(RefreshInstruction[] instructions, string login, string password, System.AsyncCallback callback, object asyncState) + public System.IAsyncResult BeginBulkRefresh(RefreshInstruction[] instructions, string appId, string login, string password, System.AsyncCallback callback, object asyncState) { return this.BeginInvoke("BulkRefresh", new object[] { instructions, + appId, login, password}, callback, asyncState); } diff --git a/src/Umbraco.Web/BatchedServerMessenger.cs b/src/Umbraco.Web/BatchedServerMessenger.cs index 8163bed4a1..9ed2e3fd2c 100644 --- a/src/Umbraco.Web/BatchedServerMessenger.cs +++ b/src/Umbraco.Web/BatchedServerMessenger.cs @@ -243,7 +243,9 @@ namespace Umbraco.Web asyncResultsList.Add( cacheRefresher.BeginBulkRefresh( - instructions, Login, Password, null, null)); + instructions, + HttpRuntime.AppDomainAppId, + Login, Password, null, null)); } var waitHandlesList = asyncResultsList.Select(x => x.AsyncWaitHandle).ToArray(); diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/CacheRefresher.asmx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/CacheRefresher.asmx.cs index cd992fc71f..3a519dfea1 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/CacheRefresher.asmx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/CacheRefresher.asmx.cs @@ -24,13 +24,26 @@ namespace umbraco.presentation.webservices { [WebMethod] - public void BulkRefresh(RefreshInstruction[] instructions, string login, string password) + public void BulkRefresh(RefreshInstruction[] instructions, string appId, string login, string password) { if (BusinessLogic.User.validateCredentials(login, password) == false) { return; } + //check if this is the same app id as the one passed in, if it is, then we will ignore + // the request - we will have to assume that the cache refeshing has already been applied to the server + // that executed the request. + if (SystemUtilities.GetCurrentTrustLevel() == AspNetHostingPermissionLevel.Unrestricted) + { + //we can only check this in full trust. if it's in medium trust we'll just end up with + // the server refreshing it's cache twice. + if (HttpRuntime.AppDomainAppId == appId) + { + return; + } + } + //only execute distinct instructions - no sense in running the same one. foreach (var instruction in instructions.Distinct()) { From 62d3dc78fa21e7438531eb7134692d70ff59c5fb Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 5 Aug 2014 10:59:54 -0600 Subject: [PATCH 5/6] Ensures all caching is disabled for editing templates (master pages and views) --- src/Umbraco.Web.UI/umbraco/settings/editTemplate.aspx | 3 +++ src/Umbraco.Web.UI/umbraco/settings/views/EditView.aspx | 2 ++ 2 files changed, 5 insertions(+) diff --git a/src/Umbraco.Web.UI/umbraco/settings/editTemplate.aspx b/src/Umbraco.Web.UI/umbraco/settings/editTemplate.aspx index a32fbf32bf..5abcd446f5 100644 --- a/src/Umbraco.Web.UI/umbraco/settings/editTemplate.aspx +++ b/src/Umbraco.Web.UI/umbraco/settings/editTemplate.aspx @@ -1,5 +1,8 @@ <%@ Page MasterPageFile="../masterpages/umbracoPage.Master" Language="c#" CodeBehind="EditTemplate.aspx.cs" ValidateRequest="false" AutoEventWireup="True" Inherits="Umbraco.Web.UI.Umbraco.Settings.EditTemplate" %> + +<%@ OutputCache Location="None" %> + <%@ Import Namespace="Umbraco.Core" %> <%@ Import Namespace="Umbraco.Core.IO" %> <%@ Register TagPrefix="cc1" Namespace="umbraco.uicontrols" Assembly="controls" %> diff --git a/src/Umbraco.Web.UI/umbraco/settings/views/EditView.aspx b/src/Umbraco.Web.UI/umbraco/settings/views/EditView.aspx index 2253dae9cc..31e14cef31 100644 --- a/src/Umbraco.Web.UI/umbraco/settings/views/EditView.aspx +++ b/src/Umbraco.Web.UI/umbraco/settings/views/EditView.aspx @@ -2,6 +2,8 @@ CodeBehind="EditView.aspx.cs" Inherits="Umbraco.Web.UI.Umbraco.Settings.Views.EditView" ValidateRequest="False" %> +<%@ OutputCache Location="None" %> + <%@ Import Namespace="Umbraco.Core" %> <%@ Import Namespace="Umbraco.Core.IO" %> <%@ Register TagPrefix="cc1" Namespace="umbraco.uicontrols" Assembly="controls" %> From 6ce303a918227084a282261cb594e1d243df60b8 Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 5 Aug 2014 12:37:20 -0600 Subject: [PATCH 6/6] Completes: U4-5264 Add Count methods to IContentService, IMediaService, etc... --- .../Interfaces/IRepositoryVersionable.cs | 11 +++ .../Repositories/VersionableRepositoryBase.cs | 84 +++++++++++++++++++ src/Umbraco.Core/Services/ContentService.cs | 27 ++++++ src/Umbraco.Core/Services/IContentService.cs | 4 + src/Umbraco.Core/Services/IMediaService.cs | 4 + src/Umbraco.Core/Services/IMemberService.cs | 2 + src/Umbraco.Core/Services/MediaService.cs | 27 ++++++ src/Umbraco.Core/Services/MemberService.cs | 9 ++ .../Services/ContentServiceTests.cs | 78 ++++++++++++++++- 9 files changed, 245 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IRepositoryVersionable.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IRepositoryVersionable.cs index c8b6f79f7d..81783ccfbd 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IRepositoryVersionable.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IRepositoryVersionable.cs @@ -12,6 +12,17 @@ namespace Umbraco.Core.Persistence.Repositories public interface IRepositoryVersionable : IRepositoryQueryable where TEntity : IAggregateRoot { + /// + /// Get the total count of entities + /// + /// + /// + int Count(string contentTypeAlias = null); + + int CountChildren(int parentId, string contentTypeAlias = null); + + int CountDescendants(int parentId, string contentTypeAlias = null); + /// /// Gets a list of all versions for an . /// diff --git a/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs index a9bc878195..22e3970e1c 100644 --- a/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs +++ b/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs @@ -93,6 +93,90 @@ namespace Umbraco.Core.Persistence.Repositories #endregion + public int CountDescendants(int parentId, string contentTypeAlias = null) + { + var pathMatch = parentId == -1 + ? "-1," + : "," + parentId + ","; + var sql = new Sql(); + if (contentTypeAlias.IsNullOrWhiteSpace()) + { + sql.Select("COUNT(*)") + .From() + .Where(x => x.NodeObjectType == NodeObjectTypeId) + .Where(x => x.Path.Contains(pathMatch)); + } + else + { + sql.Select("COUNT(*)") + .From() + .InnerJoin() + .On(left => left.NodeId, right => right.NodeId) + .InnerJoin() + .On(left => left.NodeId, right => right.ContentTypeId) + .Where(x => x.NodeObjectType == NodeObjectTypeId) + .Where(x => x.Path.Contains(pathMatch)) + .Where(x => x.Alias == contentTypeAlias); + } + + return Database.ExecuteScalar(sql); + } + + public int CountChildren(int parentId, string contentTypeAlias = null) + { + var sql = new Sql(); + if (contentTypeAlias.IsNullOrWhiteSpace()) + { + sql.Select("COUNT(*)") + .From() + .Where(x => x.NodeObjectType == NodeObjectTypeId) + .Where(x => x.ParentId == parentId); + } + else + { + sql.Select("COUNT(*)") + .From() + .InnerJoin() + .On(left => left.NodeId, right => right.NodeId) + .InnerJoin() + .On(left => left.NodeId, right => right.ContentTypeId) + .Where(x => x.NodeObjectType == NodeObjectTypeId) + .Where(x => x.ParentId == parentId) + .Where(x => x.Alias == contentTypeAlias); + } + + return Database.ExecuteScalar(sql); + } + + /// + /// Get the total count of entities + /// + /// + /// + public int Count(string contentTypeAlias = null) + { + var sql = new Sql(); + if (contentTypeAlias.IsNullOrWhiteSpace()) + { + sql.Select("COUNT(*)") + .From() + .Where(x => x.NodeObjectType == NodeObjectTypeId); + } + else + { + sql.Select("COUNT(*)") + .From() + .InnerJoin() + .On(left => left.NodeId, right => right.NodeId) + .InnerJoin() + .On(left => left.NodeId, right => right.ContentTypeId) + .Where(x => x.NodeObjectType == NodeObjectTypeId) + .Where(x => x.Alias == contentTypeAlias); + } + + return Database.ExecuteScalar(sql); + } + /// /// This is a fix for U4-1407 - when property types are added to a content type - the property of the entity are not actually created /// and we get YSODs diff --git a/src/Umbraco.Core/Services/ContentService.cs b/src/Umbraco.Core/Services/ContentService.cs index 2d2481d385..d24f39d726 100644 --- a/src/Umbraco.Core/Services/ContentService.cs +++ b/src/Umbraco.Core/Services/ContentService.cs @@ -74,6 +74,33 @@ namespace Umbraco.Core.Services _dataTypeService = dataTypeService; } + public int Count(string contentTypeAlias = null) + { + var uow = _uowProvider.GetUnitOfWork(); + using (var repository = _repositoryFactory.CreateContentRepository(uow)) + { + return repository.Count(contentTypeAlias); + } + } + + public int CountChildren(int parentId, string contentTypeAlias = null) + { + var uow = _uowProvider.GetUnitOfWork(); + using (var repository = _repositoryFactory.CreateContentRepository(uow)) + { + return repository.CountChildren(parentId, contentTypeAlias); + } + } + + public int CountDescendants(int parentId, string contentTypeAlias = null) + { + var uow = _uowProvider.GetUnitOfWork(); + using (var repository = _repositoryFactory.CreateContentRepository(uow)) + { + return repository.CountDescendants(parentId, contentTypeAlias); + } + } + /// /// Assigns a single permission to the current content item for the specified user ids /// diff --git a/src/Umbraco.Core/Services/IContentService.cs b/src/Umbraco.Core/Services/IContentService.cs index f26ec77b53..3f3ac28cdf 100644 --- a/src/Umbraco.Core/Services/IContentService.cs +++ b/src/Umbraco.Core/Services/IContentService.cs @@ -11,6 +11,10 @@ namespace Umbraco.Core.Services /// public interface IContentService : IService { + int Count(string contentTypeAlias = null); + int CountChildren(int parentId, string contentTypeAlias = null); + int CountDescendants(int parentId, string contentTypeAlias = null); + /// /// Assigns a single permission to the current content item for the specified user ids /// diff --git a/src/Umbraco.Core/Services/IMediaService.cs b/src/Umbraco.Core/Services/IMediaService.cs index 63248b3974..51541e138b 100644 --- a/src/Umbraco.Core/Services/IMediaService.cs +++ b/src/Umbraco.Core/Services/IMediaService.cs @@ -9,6 +9,10 @@ namespace Umbraco.Core.Services /// public interface IMediaService : IService { + int Count(string contentTypeAlias = null); + int CountChildren(int parentId, string contentTypeAlias = null); + int CountDescendants(int parentId, string contentTypeAlias = null); + IEnumerable GetByIds(IEnumerable ids); /// diff --git a/src/Umbraco.Core/Services/IMemberService.cs b/src/Umbraco.Core/Services/IMemberService.cs index f7918b2d8d..5d78f6df86 100644 --- a/src/Umbraco.Core/Services/IMemberService.cs +++ b/src/Umbraco.Core/Services/IMemberService.cs @@ -11,6 +11,8 @@ namespace Umbraco.Core.Services /// public interface IMemberService : IMembershipMemberService { + int Count(string contentTypeAlias = null); + IMember CreateMember(string username, string email, string name, string memberTypeAlias); IMember CreateMember(string username, string email, string name, IMemberType memberType); IMember CreateMemberWithIdentity(string username, string email, string name, string memberTypeAlias); diff --git a/src/Umbraco.Core/Services/MediaService.cs b/src/Umbraco.Core/Services/MediaService.cs index 935d6ffb07..6b237e652d 100644 --- a/src/Umbraco.Core/Services/MediaService.cs +++ b/src/Umbraco.Core/Services/MediaService.cs @@ -251,6 +251,33 @@ namespace Umbraco.Core.Services } } + public int Count(string contentTypeAlias = null) + { + var uow = _uowProvider.GetUnitOfWork(); + using (var repository = _repositoryFactory.CreateMediaRepository(uow)) + { + return repository.Count(contentTypeAlias); + } + } + + public int CountChildren(int parentId, string contentTypeAlias = null) + { + var uow = _uowProvider.GetUnitOfWork(); + using (var repository = _repositoryFactory.CreateMediaRepository(uow)) + { + return repository.CountChildren(parentId, contentTypeAlias); + } + } + + public int CountDescendants(int parentId, string contentTypeAlias = null) + { + var uow = _uowProvider.GetUnitOfWork(); + using (var repository = _repositoryFactory.CreateMediaRepository(uow)) + { + return repository.CountDescendants(parentId, contentTypeAlias); + } + } + /// /// Gets an object by Id /// diff --git a/src/Umbraco.Core/Services/MemberService.cs b/src/Umbraco.Core/Services/MemberService.cs index 3998ecc1ce..4760ef5204 100644 --- a/src/Umbraco.Core/Services/MemberService.cs +++ b/src/Umbraco.Core/Services/MemberService.cs @@ -642,6 +642,15 @@ namespace Umbraco.Core.Services } } + public int Count(string contentTypeAlias = null) + { + var uow = _uowProvider.GetUnitOfWork(); + using (var repository = _repositoryFactory.CreateMemberRepository(uow)) + { + return repository.Count(contentTypeAlias); + } + } + /// /// Creates a member object /// diff --git a/src/Umbraco.Tests/Services/ContentServiceTests.cs b/src/Umbraco.Tests/Services/ContentServiceTests.cs index 86d34374e3..ab904a9e51 100644 --- a/src/Umbraco.Tests/Services/ContentServiceTests.cs +++ b/src/Umbraco.Tests/Services/ContentServiceTests.cs @@ -38,7 +38,83 @@ namespace Umbraco.Tests.Services //TODO Add test to verify there is only ONE newest document/content in cmsDocument table after updating. //TODO Add test to delete specific version (with and without deleting prior versions) and versions by date. - + + [Test] + public void Count_All() + { + // Arrange + var contentService = ServiceContext.ContentService; + + // Act + for (int i = 0; i < 20; i++) + { + contentService.CreateContentWithIdentity("Test", -1, "umbTextpage", 0); + } + + // Assert + Assert.AreEqual(24, contentService.Count()); + } + + [Test] + public void Count_By_Content_Type() + { + // Arrange + var contentService = ServiceContext.ContentService; + var contentTypeService = ServiceContext.ContentTypeService; + var contentType = MockedContentTypes.CreateSimpleContentType("umbBlah", "test Doc Type"); + contentTypeService.Save(contentType); + + // Act + for (int i = 0; i < 20; i++) + { + contentService.CreateContentWithIdentity("Test", -1, "umbBlah", 0); + } + + // Assert + Assert.AreEqual(20, contentService.Count(contentTypeAlias: "umbBlah")); + } + + [Test] + public void Count_Children() + { + // Arrange + var contentService = ServiceContext.ContentService; + var contentTypeService = ServiceContext.ContentTypeService; + var contentType = MockedContentTypes.CreateSimpleContentType("umbBlah", "test Doc Type"); + contentTypeService.Save(contentType); + var parent = contentService.CreateContentWithIdentity("Test", -1, "umbBlah", 0); + + // Act + for (int i = 0; i < 20; i++) + { + contentService.CreateContentWithIdentity("Test", parent, "umbBlah"); + } + + // Assert + Assert.AreEqual(20, contentService.CountChildren(parent.Id)); + } + + [Test] + public void Count_Descendants() + { + // Arrange + var contentService = ServiceContext.ContentService; + var contentTypeService = ServiceContext.ContentTypeService; + var contentType = MockedContentTypes.CreateSimpleContentType("umbBlah", "test Doc Type"); + contentTypeService.Save(contentType); + var parent = contentService.CreateContentWithIdentity("Test", -1, "umbBlah", 0); + + // Act + IContent current = parent; + for (int i = 0; i < 20; i++) + { + current = contentService.CreateContentWithIdentity("Test", current, "umbBlah"); + } + + // Assert + Assert.AreEqual(20, contentService.CountDescendants(parent.Id)); + } + [Test] public void Can_Remove_Property_Type() {