From b8dab5b6e07f330edf7098b6b84ad837e208c38d Mon Sep 17 00:00:00 2001 From: enisn Date: Thu, 7 Jan 2021 17:20:54 +0300 Subject: [PATCH 01/51] CmsKit - Remove Content value from list items --- .../Volo/CmsKit/Admin/Contents/ContentGetListDto.cs | 12 ++++++++++++ .../CmsKit/Admin/Contents/IContentAdminAppService.cs | 1 + .../Admin/CmsKitAdminApplicationAutoMapperProfile.cs | 1 + .../CmsKit/Admin/Contents/ContentAdminAppService.cs | 3 ++- .../CmsKit/Admin/Contents/ContentAdminController.cs | 2 +- 5 files changed, 17 insertions(+), 2 deletions(-) create mode 100644 modules/cms-kit/src/Volo.CmsKit.Admin.Application.Contracts/Volo/CmsKit/Admin/Contents/ContentGetListDto.cs diff --git a/modules/cms-kit/src/Volo.CmsKit.Admin.Application.Contracts/Volo/CmsKit/Admin/Contents/ContentGetListDto.cs b/modules/cms-kit/src/Volo.CmsKit.Admin.Application.Contracts/Volo/CmsKit/Admin/Contents/ContentGetListDto.cs new file mode 100644 index 0000000000..ce1ca1e86c --- /dev/null +++ b/modules/cms-kit/src/Volo.CmsKit.Admin.Application.Contracts/Volo/CmsKit/Admin/Contents/ContentGetListDto.cs @@ -0,0 +1,12 @@ +using System; +using Volo.Abp.Application.Dtos; + +namespace Volo.CmsKit.Admin.Contents +{ + public class ContentGetListDto : EntityDto + { + public string EntityType { get; set; } + + public string EntityId { get; set; } + } +} diff --git a/modules/cms-kit/src/Volo.CmsKit.Admin.Application.Contracts/Volo/CmsKit/Admin/Contents/IContentAdminAppService.cs b/modules/cms-kit/src/Volo.CmsKit.Admin.Application.Contracts/Volo/CmsKit/Admin/Contents/IContentAdminAppService.cs index 02b25aa16d..e910d42c2f 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Admin.Application.Contracts/Volo/CmsKit/Admin/Contents/IContentAdminAppService.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Admin.Application.Contracts/Volo/CmsKit/Admin/Contents/IContentAdminAppService.cs @@ -7,6 +7,7 @@ namespace Volo.CmsKit.Admin.Contents public interface IContentAdminAppService : ICrudAppService< ContentDto, + ContentGetListDto, Guid, ContentGetListInput, ContentCreateDto, diff --git a/modules/cms-kit/src/Volo.CmsKit.Admin.Application/Volo/CmsKit/Admin/CmsKitAdminApplicationAutoMapperProfile.cs b/modules/cms-kit/src/Volo.CmsKit.Admin.Application/Volo/CmsKit/Admin/CmsKitAdminApplicationAutoMapperProfile.cs index 4b7a6eb58f..1524fe6299 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Admin.Application/Volo/CmsKit/Admin/CmsKitAdminApplicationAutoMapperProfile.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Admin.Application/Volo/CmsKit/Admin/CmsKitAdminApplicationAutoMapperProfile.cs @@ -13,6 +13,7 @@ namespace Volo.CmsKit.Admin CreateMap(); CreateMap(MemberList.Destination); + CreateMap(MemberList.Destination); CreateMap(MemberList.Source); CreateMap(MemberList.Source); } diff --git a/modules/cms-kit/src/Volo.CmsKit.Admin.Application/Volo/CmsKit/Admin/Contents/ContentAdminAppService.cs b/modules/cms-kit/src/Volo.CmsKit.Admin.Application/Volo/CmsKit/Admin/Contents/ContentAdminAppService.cs index 4695339fd7..5c058b5fe8 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Admin.Application/Volo/CmsKit/Admin/Contents/ContentAdminAppService.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Admin.Application/Volo/CmsKit/Admin/Contents/ContentAdminAppService.cs @@ -14,6 +14,7 @@ namespace Volo.CmsKit.Admin.Contents CrudAppService< Content, ContentDto, + ContentGetListDto, Guid, ContentGetListInput, ContentCreateDto, @@ -51,7 +52,7 @@ namespace Volo.CmsKit.Admin.Contents await ContentManager.InsertAsync(entity); - return MapToGetListOutputDto(entity); + return MapToGetOutputDto(entity); } } } diff --git a/modules/cms-kit/src/Volo.CmsKit.Admin.HttpApi/Volo/CmsKit/Admin/Contents/ContentAdminController.cs b/modules/cms-kit/src/Volo.CmsKit.Admin.HttpApi/Volo/CmsKit/Admin/Contents/ContentAdminController.cs index 935606b686..f519109022 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Admin.HttpApi/Volo/CmsKit/Admin/Contents/ContentAdminController.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Admin.HttpApi/Volo/CmsKit/Admin/Contents/ContentAdminController.cs @@ -48,7 +48,7 @@ namespace Volo.CmsKit.Admin.Contents [HttpGet] [Authorize(CmsKitAdminPermissions.Contents.Default)] - public Task> GetListAsync(ContentGetListInput input) + public Task> GetListAsync(ContentGetListInput input) { return ContentAdminAppService.GetListAsync(input); } From f2ee973860b464b37dd50cba0433665182a872b7 Mon Sep 17 00:00:00 2001 From: enisn Date: Fri, 8 Jan 2021 17:35:59 +0300 Subject: [PATCH 02/51] CmsKit - Initial of Tag Definitions --- .../CmsKit/Admin/Tags/ITagAdminAppService.cs | 3 ++ .../CmsKit/Admin/Tags/TagDefinitionDto.cs | 8 ++++ ...CmsKitAdminApplicationAutoMapperProfile.cs | 4 ++ .../CmsKit/Admin/Tags/TagAdminAppService.cs | 9 ++++- .../Volo/CmsKit/CmsKitDomainModule.cs | 18 +++++++++ .../Volo/CmsKit/CmsKitOptions.cs | 9 ++--- .../Volo/CmsKit/PolicySpecifiedDefinition.cs | 37 +++++++++++++++++++ .../CmsKit/Tags/DefaultTagDefinitionStore.cs | 33 +++++++++++++++++ .../Volo/CmsKit/Tags/ITagDefinitionStore.cs | 13 +++++++ .../Volo/CmsKit/Tags/ITagManager.cs | 3 ++ .../CmsKit/Tags/TagDefinitionDictionary.cs | 20 ++++++++++ .../Volo/CmsKit/Tags/TagDefiniton.cs | 27 ++++++++++++++ .../Volo/CmsKit/Tags/TagManager.cs | 14 ++++++- .../CmsKitDomainTestModule.cs | 1 - .../Tags/TagManager_Tests.cs | 10 +++++ .../CmsKitDataSeedContributor.cs | 18 ++++++++- .../Volo.CmsKit.TestBase/CmsKitTestData.cs | 2 + 17 files changed, 218 insertions(+), 11 deletions(-) create mode 100644 modules/cms-kit/src/Volo.CmsKit.Admin.Application.Contracts/Volo/CmsKit/Admin/Tags/TagDefinitionDto.cs create mode 100644 modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/PolicySpecifiedDefinition.cs create mode 100644 modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Tags/DefaultTagDefinitionStore.cs create mode 100644 modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Tags/ITagDefinitionStore.cs create mode 100644 modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Tags/TagDefinitionDictionary.cs create mode 100644 modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Tags/TagDefiniton.cs diff --git a/modules/cms-kit/src/Volo.CmsKit.Admin.Application.Contracts/Volo/CmsKit/Admin/Tags/ITagAdminAppService.cs b/modules/cms-kit/src/Volo.CmsKit.Admin.Application.Contracts/Volo/CmsKit/Admin/Tags/ITagAdminAppService.cs index e677a54c28..a5ae9d85d4 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Admin.Application.Contracts/Volo/CmsKit/Admin/Tags/ITagAdminAppService.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Admin.Application.Contracts/Volo/CmsKit/Admin/Tags/ITagAdminAppService.cs @@ -1,4 +1,6 @@ using System; +using System.Collections.Generic; +using System.Threading.Tasks; using Volo.Abp.Application.Services; using Volo.CmsKit.Tags; @@ -6,5 +8,6 @@ namespace Volo.CmsKit.Admin.Tags { public interface ITagAdminAppService : ICrudAppService { + Task> GetTagDefinitionsAsync(); } } diff --git a/modules/cms-kit/src/Volo.CmsKit.Admin.Application.Contracts/Volo/CmsKit/Admin/Tags/TagDefinitionDto.cs b/modules/cms-kit/src/Volo.CmsKit.Admin.Application.Contracts/Volo/CmsKit/Admin/Tags/TagDefinitionDto.cs new file mode 100644 index 0000000000..b5abf8b69c --- /dev/null +++ b/modules/cms-kit/src/Volo.CmsKit.Admin.Application.Contracts/Volo/CmsKit/Admin/Tags/TagDefinitionDto.cs @@ -0,0 +1,8 @@ +namespace Volo.CmsKit.Admin.Tags +{ + public class TagDefinitionDto + { + public string EntityType { get; set; } + public string DisplayName { get; set; } + } +} diff --git a/modules/cms-kit/src/Volo.CmsKit.Admin.Application/Volo/CmsKit/Admin/CmsKitAdminApplicationAutoMapperProfile.cs b/modules/cms-kit/src/Volo.CmsKit.Admin.Application/Volo/CmsKit/Admin/CmsKitAdminApplicationAutoMapperProfile.cs index 4b7a6eb58f..c36a64ca84 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Admin.Application/Volo/CmsKit/Admin/CmsKitAdminApplicationAutoMapperProfile.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Admin.Application/Volo/CmsKit/Admin/CmsKitAdminApplicationAutoMapperProfile.cs @@ -1,8 +1,10 @@ using AutoMapper; +using Volo.CmsKit.Admin.Application.Contracts.Volo.CmsKit.Admin.Tags; using Volo.CmsKit.Admin.Contents; using Volo.CmsKit.Admin.Pages; using Volo.CmsKit.Contents; using Volo.CmsKit.Pages; +using Volo.CmsKit.Tags; namespace Volo.CmsKit.Admin { @@ -15,6 +17,8 @@ namespace Volo.CmsKit.Admin CreateMap(MemberList.Destination); CreateMap(MemberList.Source); CreateMap(MemberList.Source); + + CreateMap(MemberList.Destination); } } } diff --git a/modules/cms-kit/src/Volo.CmsKit.Admin.Application/Volo/CmsKit/Admin/Tags/TagAdminAppService.cs b/modules/cms-kit/src/Volo.CmsKit.Admin.Application/Volo/CmsKit/Admin/Tags/TagAdminAppService.cs index cda45c23de..29fbb943bb 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Admin.Application/Volo/CmsKit/Admin/Tags/TagAdminAppService.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Admin.Application/Volo/CmsKit/Admin/Tags/TagAdminAppService.cs @@ -2,11 +2,9 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Text; using System.Threading.Tasks; using Volo.Abp.Application.Services; using Volo.Abp.Domain.Repositories; -using Volo.CmsKit.Admin.Tags; using Volo.CmsKit.Permissions; using Volo.CmsKit.Tags; @@ -68,5 +66,12 @@ namespace Volo.CmsKit.Admin.Tags x.Name.ToLower().Contains(input.Filter) || x.EntityType.ToLower().Contains(input.Filter)); } + + public async Task> GetTagDefinitionsAsync() + { + var definitions = await TagManager.GetTagDefinitionsAsync(); + + return ObjectMapper.Map, List>(definitions); + } } } diff --git a/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/CmsKitDomainModule.cs b/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/CmsKitDomainModule.cs index b1937dd6e0..719a5bc215 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/CmsKitDomainModule.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/CmsKitDomainModule.cs @@ -1,6 +1,11 @@ using Volo.Abp.Domain; +using Volo.Abp.GlobalFeatures; +using Volo.Abp.Localization; using Volo.Abp.Modularity; using Volo.Abp.Users; +using Volo.CmsKit.GlobalFeatures; +using Volo.CmsKit.Localization; +using Volo.CmsKit.Pages; using Volo.CmsKit.Reactions; namespace Volo.CmsKit @@ -28,7 +33,20 @@ namespace Volo.CmsKit options.Reactions.AddOrReplace(StandardReactions.HeartBroken); options.Reactions.AddOrReplace(StandardReactions.Rocket); options.Reactions.AddOrReplace(StandardReactions.Pray); + + if (GlobalFeatureManager.Instance.IsEnabled()) + { + if (GlobalFeatureManager.Instance.IsEnabled()) + { + options.Tags.AddOrReplace(typeof(Page).Name, L("Page")); + } + } }); } + + private static LocalizableString L(string name) + { + return LocalizableString.Create(name); + } } } diff --git a/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/CmsKitOptions.cs b/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/CmsKitOptions.cs index cc69f715ce..20fde8704d 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/CmsKitOptions.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/CmsKitOptions.cs @@ -1,16 +1,15 @@ using JetBrains.Annotations; using Volo.CmsKit.Reactions; +using Volo.CmsKit.Tags; namespace Volo.CmsKit { public class CmsKitOptions { [NotNull] - public ReactionDefinitionDictionary Reactions { get; } + public ReactionDefinitionDictionary Reactions { get; } = new ReactionDefinitionDictionary(); - public CmsKitOptions() - { - Reactions = new ReactionDefinitionDictionary(); - } + [NotNull] + public TagDefinitionDictionary Tags { get; } = new TagDefinitionDictionary(); } } diff --git a/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/PolicySpecifiedDefinition.cs b/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/PolicySpecifiedDefinition.cs new file mode 100644 index 0000000000..88fd6eb824 --- /dev/null +++ b/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/PolicySpecifiedDefinition.cs @@ -0,0 +1,37 @@ +using JetBrains.Annotations; +using Volo.Abp; +using Volo.Abp.Localization; + +namespace Volo.CmsKit.Domain.Volo.CmsKit +{ + public abstract class PolicySpecifiedDefinition + { + protected PolicySpecifiedDefinition() + { + } + + public PolicySpecifiedDefinition( + [CanBeNull] ILocalizableString displayName = null, + [CanBeNull] string createPolicy = null, + [CanBeNull] string updatePolicy = null, + [CanBeNull] string deletePolicy = null) + { + DisplayName = displayName; + CreatePolicy = createPolicy; + DeletePolicy = deletePolicy; + UpdatePolicy = updatePolicy; + } + + [CanBeNull] + public virtual ILocalizableString DisplayName { get; } + + [CanBeNull] + public virtual string CreatePolicy { get; set; } + + [CanBeNull] + public virtual string UpdatePolicy { get; set; } + + [CanBeNull] + public virtual string DeletePolicy { get; set; } + } +} diff --git a/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Tags/DefaultTagDefinitionStore.cs b/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Tags/DefaultTagDefinitionStore.cs new file mode 100644 index 0000000000..b851c9af9c --- /dev/null +++ b/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Tags/DefaultTagDefinitionStore.cs @@ -0,0 +1,33 @@ +using JetBrains.Annotations; +using Microsoft.Extensions.Options; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Volo.Abp; +using Volo.Abp.DependencyInjection; + +namespace Volo.CmsKit.Tags +{ + public class DefaultTagDefinitionStore : ITagDefinitionStore, ITransientDependency + { + private readonly CmsKitOptions options; + + public DefaultTagDefinitionStore(IOptions options) + { + this.options = options.Value; + } + + public Task GetTagDefinitionOrNullAsync([NotNull] string entityType) + { + Check.NotNullOrWhiteSpace(entityType, nameof(entityType)); + return Task.FromResult(options.Tags.GetOrDefault(entityType)); + } + + public Task> GetTagDefinitionsAsync() + { + return Task.FromResult(options.Tags.Values.ToList()); + } + } +} diff --git a/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Tags/ITagDefinitionStore.cs b/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Tags/ITagDefinitionStore.cs new file mode 100644 index 0000000000..9fa06f92d4 --- /dev/null +++ b/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Tags/ITagDefinitionStore.cs @@ -0,0 +1,13 @@ +using JetBrains.Annotations; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Volo.CmsKit.Tags +{ + public interface ITagDefinitionStore + { + Task> GetTagDefinitionsAsync(); + + Task GetTagDefinitionOrNullAsync([NotNull] string entityType); + } +} diff --git a/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Tags/ITagManager.cs b/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Tags/ITagManager.cs index 943fd6af00..23d34d6a19 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Tags/ITagManager.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Tags/ITagManager.cs @@ -1,5 +1,6 @@ using JetBrains.Annotations; using System; +using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Volo.Abp.Domain.Services; @@ -26,5 +27,7 @@ namespace Volo.CmsKit.Tags [NotNull] string name, Guid? tenantId = null, CancellationToken cancellationToken = default); + + Task> GetTagDefinitionsAsync(CancellationToken cancellationToken = default); } } diff --git a/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Tags/TagDefinitionDictionary.cs b/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Tags/TagDefinitionDictionary.cs new file mode 100644 index 0000000000..dbbfd29219 --- /dev/null +++ b/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Tags/TagDefinitionDictionary.cs @@ -0,0 +1,20 @@ +using JetBrains.Annotations; +using System.Collections.Generic; +using Volo.Abp.Localization; + +namespace Volo.CmsKit.Tags +{ + public class TagDefinitionDictionary : Dictionary + { + public void AddOrReplace( + [NotNull] string entityType, + [CanBeNull] ILocalizableString displayName = null, + [CanBeNull] string createPolicy = null, + [CanBeNull] string updatePolicy = null, + [CanBeNull] string deletePolicy = null + ) + { + this[entityType] = new TagDefiniton(entityType, displayName, createPolicy, updatePolicy, deletePolicy); + } + } +} diff --git a/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Tags/TagDefiniton.cs b/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Tags/TagDefiniton.cs new file mode 100644 index 0000000000..6ccff22f2c --- /dev/null +++ b/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Tags/TagDefiniton.cs @@ -0,0 +1,27 @@ +using JetBrains.Annotations; +using Volo.Abp; +using Volo.Abp.Localization; +using Volo.CmsKit.Domain.Volo.CmsKit; + +namespace Volo.CmsKit.Tags +{ + public class TagDefiniton : PolicySpecifiedDefinition + { + public string EntityType { get; } + + public TagDefiniton() + { + } + + public TagDefiniton( + [NotNull] string entityType, + [CanBeNull] ILocalizableString displayName = null, + [CanBeNull] string createPolicy = null, + [CanBeNull] string updatePolicy = null, + [CanBeNull] string deletePolicy = null) : base(displayName, createPolicy, updatePolicy, deletePolicy) + { + EntityType = Check.NotNullOrWhiteSpace(entityType, nameof(entityType)); + + } + } +} diff --git a/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Tags/TagManager.cs b/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Tags/TagManager.cs index 042b4e27a9..0c2cbd63ed 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Tags/TagManager.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Tags/TagManager.cs @@ -1,5 +1,8 @@ using JetBrains.Annotations; +using Microsoft.Extensions.Options; using System; +using System.Collections.Generic; +using System.Linq; using System.Threading; using System.Threading.Tasks; using Volo.Abp; @@ -10,10 +13,14 @@ namespace Volo.CmsKit.Tags public class TagManager : DomainService, ITagManager { private readonly ITagRepository _tagRepository; + private readonly ITagDefinitionStore _tagDefinitionStore; - public TagManager(ITagRepository tagRepository) + public TagManager( + ITagRepository tagRepository, + ITagDefinitionStore tagDefinitionStore) { _tagRepository = tagRepository; + _tagDefinitionStore = tagDefinitionStore; } public async Task GetOrAddAsync( @@ -70,5 +77,10 @@ namespace Volo.CmsKit.Tags return await _tagRepository.UpdateAsync(entity, cancellationToken: cancellationToken); } + + public Task> GetTagDefinitionsAsync(CancellationToken cancellationToken = default) + { + return _tagDefinitionStore.GetTagDefinitionsAsync(); + } } } \ No newline at end of file diff --git a/modules/cms-kit/test/Volo.CmsKit.Domain.Tests/CmsKitDomainTestModule.cs b/modules/cms-kit/test/Volo.CmsKit.Domain.Tests/CmsKitDomainTestModule.cs index fc41440757..b5af93fc74 100644 --- a/modules/cms-kit/test/Volo.CmsKit.Domain.Tests/CmsKitDomainTestModule.cs +++ b/modules/cms-kit/test/Volo.CmsKit.Domain.Tests/CmsKitDomainTestModule.cs @@ -12,6 +12,5 @@ namespace Volo.CmsKit )] public class CmsKitDomainTestModule : AbpModule { - } } diff --git a/modules/cms-kit/test/Volo.CmsKit.Domain.Tests/Tags/TagManager_Tests.cs b/modules/cms-kit/test/Volo.CmsKit.Domain.Tests/Tags/TagManager_Tests.cs index aa31b015cc..0fe8d5ddb4 100644 --- a/modules/cms-kit/test/Volo.CmsKit.Domain.Tests/Tags/TagManager_Tests.cs +++ b/modules/cms-kit/test/Volo.CmsKit.Domain.Tests/Tags/TagManager_Tests.cs @@ -100,5 +100,15 @@ namespace Volo.CmsKit.Tags Should.Throw(async () => await _tagManager.UpdateAsync(tag.Id, newName)); } + + [Fact] + public async Task ShouldGetTagDefinitionsProperly_WithoutParameter() + { + var definitions = await _tagManager.GetTagDefinitionsAsync(); + + definitions.ShouldNotBeNull(); + definitions.Count.ShouldBeGreaterThan(1); + definitions.ShouldContain(x => x.EntityType == _cmsKitTestData.TagDefinition_1_EntityType); + } } } \ No newline at end of file diff --git a/modules/cms-kit/test/Volo.CmsKit.TestBase/CmsKitDataSeedContributor.cs b/modules/cms-kit/test/Volo.CmsKit.TestBase/CmsKitDataSeedContributor.cs index 46a552641b..d9f671229d 100644 --- a/modules/cms-kit/test/Volo.CmsKit.TestBase/CmsKitDataSeedContributor.cs +++ b/modules/cms-kit/test/Volo.CmsKit.TestBase/CmsKitDataSeedContributor.cs @@ -1,4 +1,5 @@ -using System; +using Microsoft.Extensions.Options; +using System; using System.Linq; using System.Threading.Tasks; using Volo.Abp.Data; @@ -29,6 +30,8 @@ namespace Volo.CmsKit private readonly IEntityTagRepository _entityTagRepository; private readonly ITagManager _tagManager; private readonly IPageRepository _pageRepository; + private readonly IOptions _options; + public CmsKitDataSeedContributor( IGuidGenerator guidGenerator, ICmsUserRepository cmsUserRepository, @@ -40,7 +43,8 @@ namespace Volo.CmsKit IContentRepository contentRepository, ITagManager tagManager, IEntityTagRepository entityTagRepository, - IPageRepository pageRepository) + IPageRepository pageRepository, + IOptions options) { _guidGenerator = guidGenerator; _cmsUserRepository = cmsUserRepository; @@ -53,6 +57,7 @@ namespace Volo.CmsKit _tagManager = tagManager; _entityTagRepository = entityTagRepository; _pageRepository = pageRepository; + _options = options; } public async Task SeedAsync(DataSeedContext context) @@ -72,9 +77,18 @@ namespace Volo.CmsKit await SeedTagsAsync(); await SeedPagesAsync(); + + await ConfigureCmsKitOptionsAsync(); } } + private Task ConfigureCmsKitOptionsAsync() + { + _options.Value.Tags.AddOrReplace(_cmsKitTestData.TagDefinition_1_EntityType); + + return Task.CompletedTask; + } + private async Task SeedUsersAsync() { await _cmsUserRepository.InsertAsync(new CmsUser(new UserData(_cmsKitTestData.User1Id, "user1", diff --git a/modules/cms-kit/test/Volo.CmsKit.TestBase/CmsKitTestData.cs b/modules/cms-kit/test/Volo.CmsKit.TestBase/CmsKitTestData.cs index 7433759f40..c07b038714 100644 --- a/modules/cms-kit/test/Volo.CmsKit.TestBase/CmsKitTestData.cs +++ b/modules/cms-kit/test/Volo.CmsKit.TestBase/CmsKitTestData.cs @@ -59,5 +59,7 @@ namespace Volo.CmsKit public Guid Page_2_Id { get; } = Guid.NewGuid(); public string Page_2_Content => Content_2; + + public string TagDefinition_1_EntityType => "My.Namespace.CustomType"; } } From 584306b171d385bbae096b6f6d4e8ab58a35a5eb Mon Sep 17 00:00:00 2001 From: enisn Date: Fri, 8 Jan 2021 17:53:33 +0300 Subject: [PATCH 03/51] CmsKit - TagAdminController implementation --- .../CmsKit/Admin/CmsKitAdminApplicationAutoMapperProfile.cs | 2 +- .../Volo/CmsKit/Admin/Tags/TagAdminController.cs | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/modules/cms-kit/src/Volo.CmsKit.Admin.Application/Volo/CmsKit/Admin/CmsKitAdminApplicationAutoMapperProfile.cs b/modules/cms-kit/src/Volo.CmsKit.Admin.Application/Volo/CmsKit/Admin/CmsKitAdminApplicationAutoMapperProfile.cs index c36a64ca84..5edcfd8a04 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Admin.Application/Volo/CmsKit/Admin/CmsKitAdminApplicationAutoMapperProfile.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Admin.Application/Volo/CmsKit/Admin/CmsKitAdminApplicationAutoMapperProfile.cs @@ -1,7 +1,7 @@ using AutoMapper; -using Volo.CmsKit.Admin.Application.Contracts.Volo.CmsKit.Admin.Tags; using Volo.CmsKit.Admin.Contents; using Volo.CmsKit.Admin.Pages; +using Volo.CmsKit.Admin.Tags; using Volo.CmsKit.Contents; using Volo.CmsKit.Pages; using Volo.CmsKit.Tags; diff --git a/modules/cms-kit/src/Volo.CmsKit.Admin.HttpApi/Volo/CmsKit/Admin/Tags/TagAdminController.cs b/modules/cms-kit/src/Volo.CmsKit.Admin.HttpApi/Volo/CmsKit/Admin/Tags/TagAdminController.cs index cc755fa63f..afc9a83fc8 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Admin.HttpApi/Volo/CmsKit/Admin/Tags/TagAdminController.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Admin.HttpApi/Volo/CmsKit/Admin/Tags/TagAdminController.cs @@ -1,6 +1,7 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using System; +using System.Collections.Generic; using System.Threading.Tasks; using Volo.Abp; using Volo.Abp.Application.Dtos; @@ -60,5 +61,10 @@ namespace Volo.CmsKit.Admin.Tags { return TagAdminAppService.UpdateAsync(id, input); } + + public Task> GetTagDefinitionsAsync() + { + return TagAdminAppService.GetTagDefinitionsAsync(); + } } } From 88d990fa9cf809dd5836c314f13b4012dd801b5a Mon Sep 17 00:00:00 2001 From: enisn Date: Mon, 11 Jan 2021 11:38:33 +0300 Subject: [PATCH 04/51] CmsKit - Add EntityNotTaggableException --- .../Volo/CmsKit/CmsKitErrorCodes.cs | 1 + .../CmsKit/Tags/DefaultTagDefinitionStore.cs | 5 +++++ .../CmsKit/Tags/EntityNotTaggableException.cs | 19 +++++++++++++++++++ .../Volo/CmsKit/Tags/ITagDefinitionStore.cs | 2 ++ .../Volo/CmsKit/Tags/TagManager.cs | 5 +++++ 5 files changed, 32 insertions(+) create mode 100644 modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Tags/EntityNotTaggableException.cs diff --git a/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/CmsKitErrorCodes.cs b/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/CmsKitErrorCodes.cs index 4b57a5a52e..59b78259fb 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/CmsKitErrorCodes.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/CmsKitErrorCodes.cs @@ -4,5 +4,6 @@ { public const string TagAlreadyExist = "CmsKit:0001"; public const string ContentAlreadyExist = "CmsKit:0002"; + public const string EntityNotTaggable = "CmsKit:0003"; } } diff --git a/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Tags/DefaultTagDefinitionStore.cs b/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Tags/DefaultTagDefinitionStore.cs index b851c9af9c..5b30ab08b9 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Tags/DefaultTagDefinitionStore.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Tags/DefaultTagDefinitionStore.cs @@ -29,5 +29,10 @@ namespace Volo.CmsKit.Tags { return Task.FromResult(options.Tags.Values.ToList()); } + + public Task IsDefinedAsync([NotNull] string entityType) + { + return Task.FromResult(options.Tags.ContainsKey(entityType)); + } } } diff --git a/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Tags/EntityNotTaggableException.cs b/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Tags/EntityNotTaggableException.cs new file mode 100644 index 0000000000..1987bef869 --- /dev/null +++ b/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Tags/EntityNotTaggableException.cs @@ -0,0 +1,19 @@ +using System; +using Volo.Abp; + +namespace Volo.CmsKit.Tags +{ + [Serializable] + public class EntityNotTaggableException : BusinessException + { + public EntityNotTaggableException(string code = null, string message = null, string details = null, Exception innerException = null, Microsoft.Extensions.Logging.LogLevel logLevel = Microsoft.Extensions.Logging.LogLevel.Warning) : base(code, message, details, innerException, logLevel) + { + } + + public EntityNotTaggableException(string entityType) + { + Code = CmsKitErrorCodes.EntityNotTaggable; + WithData(nameof(Tag.EntityType), entityType); + } + } +} diff --git a/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Tags/ITagDefinitionStore.cs b/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Tags/ITagDefinitionStore.cs index 9fa06f92d4..033c6e6a5f 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Tags/ITagDefinitionStore.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Tags/ITagDefinitionStore.cs @@ -9,5 +9,7 @@ namespace Volo.CmsKit.Tags Task> GetTagDefinitionsAsync(); Task GetTagDefinitionOrNullAsync([NotNull] string entityType); + + Task IsDefinedAsync([NotNull] string entityType); } } diff --git a/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Tags/TagManager.cs b/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Tags/TagManager.cs index 0c2cbd63ed..5318336502 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Tags/TagManager.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Tags/TagManager.cs @@ -51,6 +51,11 @@ namespace Volo.CmsKit.Tags throw new TagAlreadyExistException(entityType, name); } + if (!await _tagDefinitionStore.IsDefinedAsync(entityType)) + { + throw new EntityNotTaggableException(entityType); + } + return await _tagRepository.InsertAsync( new Tag( id, From e95dd13cda292946bc63a7b247e14dca1fbc1f5f Mon Sep 17 00:00:00 2001 From: enisn Date: Mon, 11 Jan 2021 11:42:37 +0300 Subject: [PATCH 05/51] CmsKit - Add resources for CmsKit:0003 --- .../Volo/CmsKit/Localization/Resources/en.json | 1 + .../Volo/CmsKit/Localization/Resources/tr.json | 1 + 2 files changed, 2 insertions(+) diff --git a/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/en.json b/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/en.json index 8ad45af0ff..393097a72d 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/en.json +++ b/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/en.json @@ -2,6 +2,7 @@ "culture": "en", "texts": { "CmsKit:0002": "Content already exists!", + "CmsKit:0003": "The entity {0} is not taggable.", "CommentAuthorizationExceptionMessage": "Those comments are not allowed for public display.", "Comments": "Comments", "Delete": "Delete", diff --git a/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/tr.json b/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/tr.json index c081b86fc7..b469b76968 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/tr.json +++ b/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/tr.json @@ -2,6 +2,7 @@ "culture": "tr", "texts": { "CmsKit:0002": "İçerik zaten mevcut!", + "CmsKit:0003": "{0} ögesi etiketlenebilir değil.", "CommentAuthorizationExceptionMessage": "Bu yorumları görebilmek için yetki gerekir.", "Comments": "Yorumlar", "Delete": "Sil", From f0ae90d8b5fc6ae7305cd902606e28298f03a295 Mon Sep 17 00:00:00 2001 From: enisn Date: Mon, 11 Jan 2021 12:57:07 +0300 Subject: [PATCH 06/51] CmsKit - Implementation of EntityTag Domain logic --- .../Volo/CmsKit/Tags/EntityTag.cs | 3 +- .../Volo/CmsKit/Tags/EntityTagManager.cs | 52 +++++++++++++++++++ .../Volo/CmsKit/Tags/IEntityTagManager.cs | 24 +++++++++ .../Volo/CmsKit/Tags/IEntityTagRepository.cs | 12 ++++- .../CmsKit/Tags/EfCoreEntityTagRepository.cs | 19 ++++++- .../MongoDB/Tags/MongoEntityTagRepository.cs | 19 ++++++- 6 files changed, 123 insertions(+), 6 deletions(-) create mode 100644 modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Tags/EntityTagManager.cs create mode 100644 modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Tags/IEntityTagManager.cs diff --git a/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Tags/EntityTag.cs b/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Tags/EntityTag.cs index 5d27e8b15f..c49a9661ef 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Tags/EntityTag.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Tags/EntityTag.cs @@ -16,7 +16,7 @@ namespace Volo.CmsKit.Tags { } - public EntityTag(Guid tagId, string entityId, Guid? tenantId = null) + internal EntityTag(Guid tagId, string entityId, Guid? tenantId = null) { TagId = tagId; EntityId = entityId; @@ -27,6 +27,5 @@ namespace Volo.CmsKit.Tags { return new object[] { TagId, EntityId }; } - } } diff --git a/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Tags/EntityTagManager.cs b/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Tags/EntityTagManager.cs new file mode 100644 index 0000000000..0de6328775 --- /dev/null +++ b/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Tags/EntityTagManager.cs @@ -0,0 +1,52 @@ +using JetBrains.Annotations; +using System; +using System.Threading; +using System.Threading.Tasks; +using Volo.Abp.Domain.Services; +using Volo.CmsKit.Tags; + +namespace Volo.CmsKit.Tags +{ + public class EntityTagManager : DomainService, IEntityTagManager + { + protected readonly IEntityTagRepository _entityTagRepository; + protected readonly ITagDefinitionStore _tagDefinitionStore; + + public EntityTagManager( + IEntityTagRepository entityTagRepository, + ITagDefinitionStore tagDefinitionStore) + { + _entityTagRepository = entityTagRepository; + _tagDefinitionStore = tagDefinitionStore; + } + + public async Task AddTagToEntityAsync( + [NotNull] Guid tagId, + [NotNull] string entityType, + [NotNull] string entityId, + [CanBeNull] Guid? tenantId = null, + CancellationToken cancellationToken = default) + { + if (!await _tagDefinitionStore.IsDefinedAsync(entityType)) + { + throw new EntityNotTaggableException(entityType); + } + + var entityTag = new EntityTag(tagId, entityId, tenantId); + + return await _entityTagRepository.InsertAsync(entityTag, cancellationToken: cancellationToken); + } + + public async Task RemoveTagFromEntityAsync( + [NotNull] Guid tagId, + [NotNull] string entityType, + [NotNull] string entityId, + [CanBeNull] Guid? tenantId = null, + CancellationToken cancellationToken = default) + { + var entityTag = await _entityTagRepository.FindAsync(tagId, entityId, tenantId, cancellationToken); + + await _entityTagRepository.DeleteAsync(entityTag); + } + } +} diff --git a/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Tags/IEntityTagManager.cs b/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Tags/IEntityTagManager.cs new file mode 100644 index 0000000000..704568619a --- /dev/null +++ b/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Tags/IEntityTagManager.cs @@ -0,0 +1,24 @@ +using JetBrains.Annotations; +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace Volo.CmsKit.Tags +{ + public interface IEntityTagManager + { + Task AddTagToEntityAsync( + [NotNull] Guid tagId, + [NotNull] string entityType, + [NotNull] string entityId, + [CanBeNull] Guid? tenantId = null, + CancellationToken cancellationToken = default); + + Task RemoveTagFromEntityAsync( + [NotNull] Guid tagId, + [NotNull] string entityType, + [NotNull] string entityId, + [CanBeNull] Guid? tenantId = null, + CancellationToken cancellationToken = default); + } +} diff --git a/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Tags/IEntityTagRepository.cs b/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Tags/IEntityTagRepository.cs index 6ec0fd4b44..f2ed160671 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Tags/IEntityTagRepository.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Tags/IEntityTagRepository.cs @@ -1,9 +1,17 @@ -using Volo.Abp.Domain.Repositories; +using JetBrains.Annotations; +using System; +using System.Threading; +using System.Threading.Tasks; +using Volo.Abp.Domain.Repositories; namespace Volo.CmsKit.Tags { public interface IEntityTagRepository : IBasicRepository { - + Task FindAsync( + [NotNull] Guid tagId, + [NotNull] string entityId, + [CanBeNull] Guid? tenantId, + CancellationToken cancellationToken = default); } } \ No newline at end of file diff --git a/modules/cms-kit/src/Volo.CmsKit.EntityFrameworkCore/Volo/CmsKit/Tags/EfCoreEntityTagRepository.cs b/modules/cms-kit/src/Volo.CmsKit.EntityFrameworkCore/Volo/CmsKit/Tags/EfCoreEntityTagRepository.cs index cca61b802b..a82fb7005a 100644 --- a/modules/cms-kit/src/Volo.CmsKit.EntityFrameworkCore/Volo/CmsKit/Tags/EfCoreEntityTagRepository.cs +++ b/modules/cms-kit/src/Volo.CmsKit.EntityFrameworkCore/Volo/CmsKit/Tags/EfCoreEntityTagRepository.cs @@ -1,4 +1,8 @@ -using Volo.Abp.Domain.Repositories.EntityFrameworkCore; +using JetBrains.Annotations; +using System; +using System.Threading; +using System.Threading.Tasks; +using Volo.Abp.Domain.Repositories.EntityFrameworkCore; using Volo.Abp.EntityFrameworkCore; using Volo.CmsKit.EntityFrameworkCore; @@ -9,5 +13,18 @@ namespace Volo.CmsKit.Tags public EfCoreEntityTagRepository(IDbContextProvider dbContextProvider) : base(dbContextProvider) { } + + public Task FindAsync( + [NotNull] Guid tagId, + [NotNull] string entityId, + [CanBeNull] Guid? tenantId, + CancellationToken cancellationToken = default) + { + return base.FindAsync(x => + x.TagId == tagId && + x.EntityId == entityId && + x.TenantId == tenantId, + cancellationToken: cancellationToken); + } } } \ No newline at end of file diff --git a/modules/cms-kit/src/Volo.CmsKit.MongoDB/Volo/CmsKit/MongoDB/Tags/MongoEntityTagRepository.cs b/modules/cms-kit/src/Volo.CmsKit.MongoDB/Volo/CmsKit/MongoDB/Tags/MongoEntityTagRepository.cs index 5ac1a4b69c..3f9e1814fc 100644 --- a/modules/cms-kit/src/Volo.CmsKit.MongoDB/Volo/CmsKit/MongoDB/Tags/MongoEntityTagRepository.cs +++ b/modules/cms-kit/src/Volo.CmsKit.MongoDB/Volo/CmsKit/MongoDB/Tags/MongoEntityTagRepository.cs @@ -1,4 +1,8 @@ -using Volo.Abp.Domain.Repositories.MongoDB; +using JetBrains.Annotations; +using System; +using System.Threading; +using System.Threading.Tasks; +using Volo.Abp.Domain.Repositories.MongoDB; using Volo.Abp.MongoDB; using Volo.CmsKit.Tags; @@ -9,5 +13,18 @@ namespace Volo.CmsKit.MongoDB.Tags public MongoEntityTagRepository(IMongoDbContextProvider dbContextProvider) : base(dbContextProvider) { } + + public Task FindAsync( + [NotNull] Guid tagId, + [NotNull] string entityId, + [CanBeNull] Guid? tenantId, + CancellationToken cancellationToken = default) + { + return base.FindAsync(x => + x.TagId == tagId && + x.EntityId == entityId && + x.TenantId == tenantId, + cancellationToken: cancellationToken); + } } } \ No newline at end of file From dce59dadb7b91a745914ee6471eacb69b1bc5b83 Mon Sep 17 00:00:00 2001 From: enisn Date: Mon, 11 Jan 2021 12:57:25 +0300 Subject: [PATCH 07/51] CmsKit - Update Tests according to EntityTag domain logic --- .../Tags/TagAdminAppService_Tests.cs | 4 +-- .../Tags/EntityTagManager_Tests.cs | 27 +++++++++++++++ .../Tags/TagManager_Tests.cs | 20 ++++++++--- .../CmsKitDataSeedContributor.cs | 34 +++++++++++-------- 4 files changed, 64 insertions(+), 21 deletions(-) create mode 100644 modules/cms-kit/test/Volo.CmsKit.Domain.Tests/Tags/EntityTagManager_Tests.cs diff --git a/modules/cms-kit/test/Volo.CmsKit.Application.Tests/Tags/TagAdminAppService_Tests.cs b/modules/cms-kit/test/Volo.CmsKit.Application.Tests/Tags/TagAdminAppService_Tests.cs index 4ad0d43468..b2128bb385 100644 --- a/modules/cms-kit/test/Volo.CmsKit.Application.Tests/Tags/TagAdminAppService_Tests.cs +++ b/modules/cms-kit/test/Volo.CmsKit.Application.Tests/Tags/TagAdminAppService_Tests.cs @@ -32,8 +32,8 @@ namespace Volo.CmsKit.Tags { var list = await _tagAdminAppService.CreateAsync(new TagCreateDto { - EntityType = "any_new_type", - Name = "1", + EntityType = _cmsKitTestData.EntityType1, + Name = "My First Tag", }); list.Id.ShouldNotBe(Guid.Empty); diff --git a/modules/cms-kit/test/Volo.CmsKit.Domain.Tests/Tags/EntityTagManager_Tests.cs b/modules/cms-kit/test/Volo.CmsKit.Domain.Tests/Tags/EntityTagManager_Tests.cs new file mode 100644 index 0000000000..e259472d96 --- /dev/null +++ b/modules/cms-kit/test/Volo.CmsKit.Domain.Tests/Tags/EntityTagManager_Tests.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Volo.CmsKit.Tags +{ + public class EntityTagManager_Tests : CmsKitDomainTestBase + { + private readonly CmsKitTestData _cmsKitTestData; + private readonly IEntityTagManager _entityTagManager; + private readonly IEntityTagManager _entityTagManager; + + public EntityTagManager_Tests() + { + _cmsKitTestData = GetRequiredService(); + _entityTagManager = GetRequiredService(); + } + + public async Task AddTagToEntityAsync_ShouldAdd_WhenEverythingCorrect() + { + var tag = await + await _entityTagManager.AddTagToEntityAsync(_cmsKitTestData.tag) + } + } +} diff --git a/modules/cms-kit/test/Volo.CmsKit.Domain.Tests/Tags/TagManager_Tests.cs b/modules/cms-kit/test/Volo.CmsKit.Domain.Tests/Tags/TagManager_Tests.cs index 0fe8d5ddb4..7d7416c8ce 100644 --- a/modules/cms-kit/test/Volo.CmsKit.Domain.Tests/Tags/TagManager_Tests.cs +++ b/modules/cms-kit/test/Volo.CmsKit.Domain.Tests/Tags/TagManager_Tests.cs @@ -22,7 +22,7 @@ namespace Volo.CmsKit.Tags [Fact] public async Task ShouldAddWhenGettingAsync() { - var newTagEntityType = "testEntity"; + var newTagEntityType = _cmsKitTestData.EntityType1; var newTagName = "test_tag_2123"; var doesExist = await _tagRepository.AnyAsync(newTagEntityType, newTagName); @@ -53,16 +53,28 @@ namespace Volo.CmsKit.Tags } [Fact] - public async Task ShouldInsert() + public async Task ShouldInsertAsync() { - var tag = await _tagManager.InsertAsync(Guid.NewGuid(), "test", "test"); + var tagName = "Freshly Created New Tag"; + var tag = await _tagManager.InsertAsync(Guid.NewGuid(), _cmsKitTestData.EntityType1, tagName); tag.ShouldNotBeNull(); - var doesExist = await _tagRepository.AnyAsync("test", "test"); + var doesExist = await _tagRepository.AnyAsync(_cmsKitTestData.EntityType1, tagName); doesExist.ShouldBeTrue(); } + [Fact] + public async Task ShouldntInsertWithUnconfiguredEntityTypeAsync() + { + var notConfiguredEntityType = "My.Namespace.SomeEntity"; + + var exception = await Should.ThrowAsync(async () => + await _tagManager.InsertAsync(Guid.NewGuid(), notConfiguredEntityType, "test")); + + exception.ShouldNotBeNull(); + exception.Data[nameof(Tag.EntityType)].ShouldBe(notConfiguredEntityType); + } [Fact] public async Task ShouldNotInsert() diff --git a/modules/cms-kit/test/Volo.CmsKit.TestBase/CmsKitDataSeedContributor.cs b/modules/cms-kit/test/Volo.CmsKit.TestBase/CmsKitDataSeedContributor.cs index d9f671229d..589249a65f 100644 --- a/modules/cms-kit/test/Volo.CmsKit.TestBase/CmsKitDataSeedContributor.cs +++ b/modules/cms-kit/test/Volo.CmsKit.TestBase/CmsKitDataSeedContributor.cs @@ -27,7 +27,7 @@ namespace Volo.CmsKit private readonly IRatingRepository _ratingRepository; private readonly ICurrentTenant _currentTenant; private readonly IContentRepository _contentRepository; - private readonly IEntityTagRepository _entityTagRepository; + private readonly IEntityTagManager _entityTagManager; private readonly ITagManager _tagManager; private readonly IPageRepository _pageRepository; private readonly IOptions _options; @@ -39,10 +39,10 @@ namespace Volo.CmsKit ICommentRepository commentRepository, ReactionManager reactionManager, IRatingRepository ratingRepository, - ICurrentTenant currentTenant, + ICurrentTenant currentTenant, IContentRepository contentRepository, - ITagManager tagManager, - IEntityTagRepository entityTagRepository, + ITagManager tagManager, + IEntityTagManager entityTagManager, IPageRepository pageRepository, IOptions options) { @@ -55,7 +55,7 @@ namespace Volo.CmsKit _currentTenant = currentTenant; _contentRepository = contentRepository; _tagManager = tagManager; - _entityTagRepository = entityTagRepository; + _entityTagManager = entityTagManager; _pageRepository = pageRepository; _options = options; } @@ -64,6 +64,8 @@ namespace Volo.CmsKit { using (_currentTenant.Change(context?.TenantId)) { + await ConfigureCmsKitOptionsAsync(); + await SeedUsersAsync(); await SeedCommentsAsync(); @@ -77,13 +79,15 @@ namespace Volo.CmsKit await SeedTagsAsync(); await SeedPagesAsync(); - - await ConfigureCmsKitOptionsAsync(); } } private Task ConfigureCmsKitOptionsAsync() { + _options.Value.Tags.AddOrReplace(_cmsKitTestData.EntityType1); + _options.Value.Tags.AddOrReplace(_cmsKitTestData.EntityType2); + _options.Value.Tags.AddOrReplace(_cmsKitTestData.Content_1_EntityType); + _options.Value.Tags.AddOrReplace(_cmsKitTestData.Content_2_EntityType); _options.Value.Tags.AddOrReplace(_cmsKitTestData.TagDefinition_1_EntityType); return Task.CompletedTask; @@ -222,14 +226,14 @@ namespace Volo.CmsKit _cmsKitTestData.Content_1_EntityId, _cmsKitTestData.Content_1 ); - + var content2 = new Content( _cmsKitTestData.Content_2_Id, _cmsKitTestData.Content_2_EntityType, _cmsKitTestData.Content_2_EntityId, _cmsKitTestData.Content_2 ); - + var content3 = new Content( Guid.NewGuid(), "deleted_entity_type", @@ -251,14 +255,14 @@ namespace Volo.CmsKit { var tagEntity = await _tagManager.InsertAsync(_guidGenerator.Create(), _cmsKitTestData.Content_1_EntityType, tag); - await _entityTagRepository.InsertAsync(new EntityTag(tagEntity.Id, _cmsKitTestData.Content_1_EntityId)); + await _entityTagManager.AddTagToEntityAsync(tagEntity.Id, _cmsKitTestData.Content_1_EntityType, _cmsKitTestData.Content_1_EntityId); } - + foreach (var tag in _cmsKitTestData.Content_2_Tags) { var tagEntity = await _tagManager.InsertAsync(_guidGenerator.Create(), _cmsKitTestData.Content_2_EntityType, tag); - - await _entityTagRepository.InsertAsync(new EntityTag(tagEntity.Id, _cmsKitTestData.Content_2_EntityId)); + + await _entityTagManager.AddTagToEntityAsync(tagEntity.Id, _cmsKitTestData.Content_2_EntityType, _cmsKitTestData.Content_2_EntityId); } } @@ -266,10 +270,10 @@ namespace Volo.CmsKit { var page1 = new Page(_cmsKitTestData.Page_1_Id, _cmsKitTestData.Page_1_Title, _cmsKitTestData.Page_1_Url, _cmsKitTestData.Page_1_Description); var page1Content = new Content(_guidGenerator.Create(), nameof(Page), page1.Id.ToString(), _cmsKitTestData.Page_1_Content); - + await _pageRepository.InsertAsync(page1); await _contentRepository.InsertAsync(page1Content); - + var page2 = new Page(_cmsKitTestData.Page_2_Id, _cmsKitTestData.Page_2_Title, _cmsKitTestData.Page_2_Url, _cmsKitTestData.Page_2_Description); var page2Content = new Content(_guidGenerator.Create(), nameof(Page), page2.Id.ToString(), _cmsKitTestData.Page_2_Content); From 8c607c4b5a00c5b97a48ae8b9f7b5600251708bd Mon Sep 17 00:00:00 2001 From: enisn Date: Mon, 11 Jan 2021 12:59:32 +0300 Subject: [PATCH 08/51] CmsKit - Remove EntityTagManager tests --- .../Tags/EntityTagManager_Tests.cs | 27 ------------------- 1 file changed, 27 deletions(-) delete mode 100644 modules/cms-kit/test/Volo.CmsKit.Domain.Tests/Tags/EntityTagManager_Tests.cs diff --git a/modules/cms-kit/test/Volo.CmsKit.Domain.Tests/Tags/EntityTagManager_Tests.cs b/modules/cms-kit/test/Volo.CmsKit.Domain.Tests/Tags/EntityTagManager_Tests.cs deleted file mode 100644 index e259472d96..0000000000 --- a/modules/cms-kit/test/Volo.CmsKit.Domain.Tests/Tags/EntityTagManager_Tests.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Volo.CmsKit.Tags -{ - public class EntityTagManager_Tests : CmsKitDomainTestBase - { - private readonly CmsKitTestData _cmsKitTestData; - private readonly IEntityTagManager _entityTagManager; - private readonly IEntityTagManager _entityTagManager; - - public EntityTagManager_Tests() - { - _cmsKitTestData = GetRequiredService(); - _entityTagManager = GetRequiredService(); - } - - public async Task AddTagToEntityAsync_ShouldAdd_WhenEverythingCorrect() - { - var tag = await - await _entityTagManager.AddTagToEntityAsync(_cmsKitTestData.tag) - } - } -} From 56513002753bbf161590d88d9555b27289946b12 Mon Sep 17 00:00:00 2001 From: enisn Date: Mon, 11 Jan 2021 13:43:35 +0300 Subject: [PATCH 09/51] CmsKit - Add EntityTag tests --- .../Tags/EntityTagManager_Tests.cs | 55 +++++++++++++++++++ .../CmsKitDataSeedContributor.cs | 8 +++ .../Volo.CmsKit.TestBase/CmsKitTestData.cs | 9 +++ 3 files changed, 72 insertions(+) create mode 100644 modules/cms-kit/test/Volo.CmsKit.Domain.Tests/Tags/EntityTagManager_Tests.cs diff --git a/modules/cms-kit/test/Volo.CmsKit.Domain.Tests/Tags/EntityTagManager_Tests.cs b/modules/cms-kit/test/Volo.CmsKit.Domain.Tests/Tags/EntityTagManager_Tests.cs new file mode 100644 index 0000000000..2e0d17fadd --- /dev/null +++ b/modules/cms-kit/test/Volo.CmsKit.Domain.Tests/Tags/EntityTagManager_Tests.cs @@ -0,0 +1,55 @@ +using Shouldly; +using System.Linq; +using System.Threading.Tasks; +using Xunit; + +namespace Volo.CmsKit.Tags +{ + public class EntityTagManager_Tests : CmsKitDomainTestBase + { + private readonly CmsKitTestData _cmsKitTestData; + private readonly IEntityTagManager _entityTagManager; + private readonly ITagRepository _tagRepository; + + public EntityTagManager_Tests() + { + _cmsKitTestData = GetRequiredService(); + _entityTagManager = GetRequiredService(); + _tagRepository = GetRequiredService(); + } + + [Fact] + public async Task AddTagToEntityAsync_ShouldAdd_WhenEverythingCorrect() + { + var entityTag = await _entityTagManager.AddTagToEntityAsync(_cmsKitTestData.TagId_1, _cmsKitTestData.EntityType1, _cmsKitTestData.EntityId1); + + entityTag.ShouldNotBeNull(); + } + + [Fact] + public async Task AddTagToEntity_ShouldThrowNotTaggable_WithNotConfiguredEntityType() + { + var entityType = "Not.Configured.EntityType"; + + var exception = Should.Throw(async () => + await _entityTagManager.AddTagToEntityAsync(_cmsKitTestData.TagId_1, entityType, _cmsKitTestData.EntityId1) + ); + + exception.ShouldNotBeNull(); + exception.Data[nameof(Tag.EntityType)].ShouldBe(entityType); + } + + [Fact] + public async Task RemoveTagFromEntityAsync_ShouldRemove_WhenEverythingCorrect() + { + var tagToDelete = (await _tagRepository.GetAllRelatedTagsAsync(_cmsKitTestData.EntityType1, _cmsKitTestData.EntityId1)) + .First(); + + await _entityTagManager.RemoveTagFromEntityAsync(tagToDelete.Id, tagToDelete.EntityType, _cmsKitTestData.EntityId1); + + var tags = await _tagRepository.GetAllRelatedTagsAsync(_cmsKitTestData.EntityType1, _cmsKitTestData.EntityId1); + + tags.ShouldNotContain(x => x.Id == tagToDelete.Id); + } + } +} diff --git a/modules/cms-kit/test/Volo.CmsKit.TestBase/CmsKitDataSeedContributor.cs b/modules/cms-kit/test/Volo.CmsKit.TestBase/CmsKitDataSeedContributor.cs index 589249a65f..9adc4442cb 100644 --- a/modules/cms-kit/test/Volo.CmsKit.TestBase/CmsKitDataSeedContributor.cs +++ b/modules/cms-kit/test/Volo.CmsKit.TestBase/CmsKitDataSeedContributor.cs @@ -251,6 +251,14 @@ namespace Volo.CmsKit private async Task SeedTagsAsync() { + var created1 = await _tagManager.InsertAsync(_cmsKitTestData.TagId_1, _cmsKitTestData.EntityType1, _cmsKitTestData.TagName_1); + + await _entityTagManager.AddTagToEntityAsync(created1.Id, created1.EntityType, _cmsKitTestData.EntityId1); + + var created2 = await _tagManager.InsertAsync(_cmsKitTestData.TagId_2, _cmsKitTestData.EntityType2, _cmsKitTestData.TagName_2); + + await _entityTagManager.AddTagToEntityAsync(created2.Id, created2.EntityType, _cmsKitTestData.EntityId2); + foreach (var tag in _cmsKitTestData.Content_1_Tags) { var tagEntity = await _tagManager.InsertAsync(_guidGenerator.Create(), _cmsKitTestData.Content_1_EntityType, tag); diff --git a/modules/cms-kit/test/Volo.CmsKit.TestBase/CmsKitTestData.cs b/modules/cms-kit/test/Volo.CmsKit.TestBase/CmsKitTestData.cs index c07b038714..4415cd7eb3 100644 --- a/modules/cms-kit/test/Volo.CmsKit.TestBase/CmsKitTestData.cs +++ b/modules/cms-kit/test/Volo.CmsKit.TestBase/CmsKitTestData.cs @@ -12,6 +12,7 @@ namespace Volo.CmsKit public Guid CommentWithChildId { get; } = Guid.NewGuid(); + public string EntityType1 { get; } = "EntityName1"; public string EntityType2 { get; } = "EntityName2"; @@ -61,5 +62,13 @@ namespace Volo.CmsKit public string Page_2_Content => Content_2; public string TagDefinition_1_EntityType => "My.Namespace.CustomType"; + + public Guid TagId_1 { get; } = Guid.NewGuid(); + + public string TagName_1 => "Awesome"; + + public Guid TagId_2 { get; } = Guid.NewGuid(); + + public string TagName_2 => "News"; } } From b28ad6b82e23be39401c65f158c1f1fa70251e00 Mon Sep 17 00:00:00 2001 From: enisn Date: Mon, 11 Jan 2021 15:18:50 +0300 Subject: [PATCH 10/51] CmsKit - Fix EntityTag Tests --- .../Tags/EntityTagManager_Tests.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/modules/cms-kit/test/Volo.CmsKit.Domain.Tests/Tags/EntityTagManager_Tests.cs b/modules/cms-kit/test/Volo.CmsKit.Domain.Tests/Tags/EntityTagManager_Tests.cs index 2e0d17fadd..5a6144733c 100644 --- a/modules/cms-kit/test/Volo.CmsKit.Domain.Tests/Tags/EntityTagManager_Tests.cs +++ b/modules/cms-kit/test/Volo.CmsKit.Domain.Tests/Tags/EntityTagManager_Tests.cs @@ -1,6 +1,7 @@ using Shouldly; using System.Linq; using System.Threading.Tasks; +using Volo.Abp.Guids; using Xunit; namespace Volo.CmsKit.Tags @@ -10,18 +11,22 @@ namespace Volo.CmsKit.Tags private readonly CmsKitTestData _cmsKitTestData; private readonly IEntityTagManager _entityTagManager; private readonly ITagRepository _tagRepository; + private readonly IGuidGenerator _guidGenerator; public EntityTagManager_Tests() { _cmsKitTestData = GetRequiredService(); _entityTagManager = GetRequiredService(); _tagRepository = GetRequiredService(); + _guidGenerator = GetRequiredService(); } [Fact] public async Task AddTagToEntityAsync_ShouldAdd_WhenEverythingCorrect() { - var entityTag = await _entityTagManager.AddTagToEntityAsync(_cmsKitTestData.TagId_1, _cmsKitTestData.EntityType1, _cmsKitTestData.EntityId1); + var tag = await _tagRepository.InsertAsync(new Tag(_guidGenerator.Create(), _cmsKitTestData.EntityType1, "My Test Tag #1")); + + var entityTag = await _entityTagManager.AddTagToEntityAsync(tag.Id, _cmsKitTestData.EntityType1, _cmsKitTestData.EntityId1); entityTag.ShouldNotBeNull(); } From a92a5c98ab95aab4c456d9068c141e6fddba8109 Mon Sep 17 00:00:00 2001 From: enisn Date: Tue, 12 Jan 2021 13:36:35 +0300 Subject: [PATCH 11/51] CmsKit - Fix localization problem for TagDefinitions --- .../Volo/CmsKit/Admin/Tags/TagDefinitionDto.cs | 10 ++++++++++ .../Volo/CmsKit/Admin/Tags/TagAdminAppService.cs | 12 ++++++++++-- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/modules/cms-kit/src/Volo.CmsKit.Admin.Application.Contracts/Volo/CmsKit/Admin/Tags/TagDefinitionDto.cs b/modules/cms-kit/src/Volo.CmsKit.Admin.Application.Contracts/Volo/CmsKit/Admin/Tags/TagDefinitionDto.cs index b5abf8b69c..0fdfe04b00 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Admin.Application.Contracts/Volo/CmsKit/Admin/Tags/TagDefinitionDto.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Admin.Application.Contracts/Volo/CmsKit/Admin/Tags/TagDefinitionDto.cs @@ -2,7 +2,17 @@ { public class TagDefinitionDto { + public TagDefinitionDto() + { + } + public TagDefinitionDto(string entityType, string displayName) + { + EntityType = entityType; + DisplayName = displayName; + } + public string EntityType { get; set; } + public string DisplayName { get; set; } } } diff --git a/modules/cms-kit/src/Volo.CmsKit.Admin.Application/Volo/CmsKit/Admin/Tags/TagAdminAppService.cs b/modules/cms-kit/src/Volo.CmsKit.Admin.Application/Volo/CmsKit/Admin/Tags/TagAdminAppService.cs index 29fbb943bb..54e2a7a530 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Admin.Application/Volo/CmsKit/Admin/Tags/TagAdminAppService.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Admin.Application/Volo/CmsKit/Admin/Tags/TagAdminAppService.cs @@ -1,4 +1,5 @@ using Microsoft.AspNetCore.Authorization; +using Microsoft.Extensions.Localization; using System; using System.Collections.Generic; using System.Linq; @@ -23,11 +24,15 @@ namespace Volo.CmsKit.Admin.Tags { protected ITagManager TagManager { get; } + protected IStringLocalizerFactory StringLocalizerFactory { get; } + public TagAdminAppService( IRepository repository, - ITagManager tagManager) : base(repository) + ITagManager tagManager, + IStringLocalizerFactory stringLocalizerFactory) : base(repository) { TagManager = tagManager; + StringLocalizerFactory = stringLocalizerFactory; GetListPolicyName = CmsKitAdminPermissions.Tags.Default; GetPolicyName = CmsKitAdminPermissions.Tags.Default; @@ -71,7 +76,10 @@ namespace Volo.CmsKit.Admin.Tags { var definitions = await TagManager.GetTagDefinitionsAsync(); - return ObjectMapper.Map, List>(definitions); + return definitions + .Select(s => + new TagDefinitionDto(s.EntityType, s.DisplayName.Localize(StringLocalizerFactory))) + .ToList(); } } } From 02a66342c71dedb484ccf9ca414a3c8066d17bb3 Mon Sep 17 00:00:00 2001 From: enisn Date: Tue, 12 Jan 2021 13:58:31 +0300 Subject: [PATCH 12/51] CmsKit - Refactor DefaultTagDefinitionStore --- .../Volo/CmsKit/Tags/DefaultTagDefinitionStore.cs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Tags/DefaultTagDefinitionStore.cs b/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Tags/DefaultTagDefinitionStore.cs index 5b30ab08b9..bf52474446 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Tags/DefaultTagDefinitionStore.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Tags/DefaultTagDefinitionStore.cs @@ -1,9 +1,7 @@ using JetBrains.Annotations; using Microsoft.Extensions.Options; -using System; using System.Collections.Generic; using System.Linq; -using System.Text; using System.Threading.Tasks; using Volo.Abp; using Volo.Abp.DependencyInjection; @@ -19,19 +17,20 @@ namespace Volo.CmsKit.Tags this.options = options.Value; } - public Task GetTagDefinitionOrNullAsync([NotNull] string entityType) + public virtual Task GetTagDefinitionOrNullAsync([NotNull] string entityType) { Check.NotNullOrWhiteSpace(entityType, nameof(entityType)); return Task.FromResult(options.Tags.GetOrDefault(entityType)); } - public Task> GetTagDefinitionsAsync() + public virtual Task> GetTagDefinitionsAsync() { return Task.FromResult(options.Tags.Values.ToList()); } - public Task IsDefinedAsync([NotNull] string entityType) + public virtual Task IsDefinedAsync([NotNull] string entityType) { + Check.NotNullOrWhiteSpace(entityType, nameof(entityType)); return Task.FromResult(options.Tags.ContainsKey(entityType)); } } From 20db982c20fec04713afc8243ece1522e96cce63 Mon Sep 17 00:00:00 2001 From: enisn Date: Wed, 13 Jan 2021 11:47:03 +0300 Subject: [PATCH 13/51] CmsKit - Move DisplayName to TagDefinition from PolicySpecifiedDefinition --- .../Volo/CmsKit/PolicySpecifiedDefinition.cs | 5 ----- .../src/Volo.CmsKit.Domain/Volo/CmsKit/Tags/TagDefiniton.cs | 6 +++++- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/PolicySpecifiedDefinition.cs b/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/PolicySpecifiedDefinition.cs index 88fd6eb824..6105beab97 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/PolicySpecifiedDefinition.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/PolicySpecifiedDefinition.cs @@ -11,20 +11,15 @@ namespace Volo.CmsKit.Domain.Volo.CmsKit } public PolicySpecifiedDefinition( - [CanBeNull] ILocalizableString displayName = null, [CanBeNull] string createPolicy = null, [CanBeNull] string updatePolicy = null, [CanBeNull] string deletePolicy = null) { - DisplayName = displayName; CreatePolicy = createPolicy; DeletePolicy = deletePolicy; UpdatePolicy = updatePolicy; } - [CanBeNull] - public virtual ILocalizableString DisplayName { get; } - [CanBeNull] public virtual string CreatePolicy { get; set; } diff --git a/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Tags/TagDefiniton.cs b/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Tags/TagDefiniton.cs index 6ccff22f2c..6029ab3946 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Tags/TagDefiniton.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Tags/TagDefiniton.cs @@ -9,6 +9,9 @@ namespace Volo.CmsKit.Tags { public string EntityType { get; } + [CanBeNull] + public virtual ILocalizableString DisplayName { get; } + public TagDefiniton() { } @@ -18,10 +21,11 @@ namespace Volo.CmsKit.Tags [CanBeNull] ILocalizableString displayName = null, [CanBeNull] string createPolicy = null, [CanBeNull] string updatePolicy = null, - [CanBeNull] string deletePolicy = null) : base(displayName, createPolicy, updatePolicy, deletePolicy) + [CanBeNull] string deletePolicy = null) : base(createPolicy, updatePolicy, deletePolicy) { EntityType = Check.NotNullOrWhiteSpace(entityType, nameof(entityType)); + DisplayName = displayName; } } } From fa48c8a4515653a91f8b6bd777df98f39b4084ea Mon Sep 17 00:00:00 2001 From: enisn Date: Wed, 13 Jan 2021 15:16:20 +0300 Subject: [PATCH 14/51] CmsKit - Add EntityTagAppService --- .../CmsKit/Admin/Tags/EntityTagCreateDto.cs | 16 ++++++ .../CmsKit/Admin/Tags/EntityTagRemoveDto.cs | 17 ++++++ .../Admin/Tags/IEntityTagAdminAppService.cs | 11 ++++ .../CmsKit/Admin/Pages/PageAdminAppService.cs | 1 - .../Admin/Tags/EntityTagAdminAppService.cs | 52 +++++++++++++++++++ .../CmsKit/Tags/DefaultTagDefinitionStore.cs | 7 ++- .../Volo/CmsKit/Tags/ITagDefinitionStore.cs | 2 +- 7 files changed, 102 insertions(+), 4 deletions(-) create mode 100644 modules/cms-kit/src/Volo.CmsKit.Admin.Application.Contracts/Volo/CmsKit/Admin/Tags/EntityTagCreateDto.cs create mode 100644 modules/cms-kit/src/Volo.CmsKit.Admin.Application.Contracts/Volo/CmsKit/Admin/Tags/EntityTagRemoveDto.cs create mode 100644 modules/cms-kit/src/Volo.CmsKit.Admin.Application.Contracts/Volo/CmsKit/Admin/Tags/IEntityTagAdminAppService.cs create mode 100644 modules/cms-kit/src/Volo.CmsKit.Admin.Application/Volo/CmsKit/Admin/Tags/EntityTagAdminAppService.cs diff --git a/modules/cms-kit/src/Volo.CmsKit.Admin.Application.Contracts/Volo/CmsKit/Admin/Tags/EntityTagCreateDto.cs b/modules/cms-kit/src/Volo.CmsKit.Admin.Application.Contracts/Volo/CmsKit/Admin/Tags/EntityTagCreateDto.cs new file mode 100644 index 0000000000..92e83fecf4 --- /dev/null +++ b/modules/cms-kit/src/Volo.CmsKit.Admin.Application.Contracts/Volo/CmsKit/Admin/Tags/EntityTagCreateDto.cs @@ -0,0 +1,16 @@ +using System.ComponentModel.DataAnnotations; + +namespace Volo.CmsKit.Admin.Tags +{ + public class EntityTagCreateDto + { + [Required] + public string TagName { get; set; } + + [Required] + public string EntityType { get; set; } + + [Required] + public string EntityId { get; set; } + } +} diff --git a/modules/cms-kit/src/Volo.CmsKit.Admin.Application.Contracts/Volo/CmsKit/Admin/Tags/EntityTagRemoveDto.cs b/modules/cms-kit/src/Volo.CmsKit.Admin.Application.Contracts/Volo/CmsKit/Admin/Tags/EntityTagRemoveDto.cs new file mode 100644 index 0000000000..411d4c7157 --- /dev/null +++ b/modules/cms-kit/src/Volo.CmsKit.Admin.Application.Contracts/Volo/CmsKit/Admin/Tags/EntityTagRemoveDto.cs @@ -0,0 +1,17 @@ +using System; +using System.ComponentModel.DataAnnotations; + +namespace Volo.CmsKit.Admin.Tags +{ + public class EntityTagRemoveDto + { + [Required] + public Guid TagId { get; set; } + + [Required] + public string EntityType { get; set; } + + [Required] + public string EntityId { get; set; } + } +} diff --git a/modules/cms-kit/src/Volo.CmsKit.Admin.Application.Contracts/Volo/CmsKit/Admin/Tags/IEntityTagAdminAppService.cs b/modules/cms-kit/src/Volo.CmsKit.Admin.Application.Contracts/Volo/CmsKit/Admin/Tags/IEntityTagAdminAppService.cs new file mode 100644 index 0000000000..aece4d5c00 --- /dev/null +++ b/modules/cms-kit/src/Volo.CmsKit.Admin.Application.Contracts/Volo/CmsKit/Admin/Tags/IEntityTagAdminAppService.cs @@ -0,0 +1,11 @@ +using System.Threading.Tasks; + +namespace Volo.CmsKit.Admin.Tags +{ + public interface IEntityTagAdminAppService + { + Task AddTagToEntityAsync(EntityTagCreateDto input); + + Task RemoveTagFromEntityAsync(EntityTagRemoveDto input); + } +} diff --git a/modules/cms-kit/src/Volo.CmsKit.Admin.Application/Volo/CmsKit/Admin/Pages/PageAdminAppService.cs b/modules/cms-kit/src/Volo.CmsKit.Admin.Application/Volo/CmsKit/Admin/Pages/PageAdminAppService.cs index 6778dd7916..3934f8a6b2 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Admin.Application/Volo/CmsKit/Admin/Pages/PageAdminAppService.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Admin.Application/Volo/CmsKit/Admin/Pages/PageAdminAppService.cs @@ -21,7 +21,6 @@ namespace Volo.CmsKit.Admin.Pages public virtual async Task GetAsync(Guid id) { var page = await PageRepository.GetAsync(id); - return ObjectMapper.Map(page); } diff --git a/modules/cms-kit/src/Volo.CmsKit.Admin.Application/Volo/CmsKit/Admin/Tags/EntityTagAdminAppService.cs b/modules/cms-kit/src/Volo.CmsKit.Admin.Application/Volo/CmsKit/Admin/Tags/EntityTagAdminAppService.cs new file mode 100644 index 0000000000..848a2ea11e --- /dev/null +++ b/modules/cms-kit/src/Volo.CmsKit.Admin.Application/Volo/CmsKit/Admin/Tags/EntityTagAdminAppService.cs @@ -0,0 +1,52 @@ +using System; +using System.Threading.Tasks; +using Volo.CmsKit.Admin.Tags; +using Volo.CmsKit.Tags; + +namespace Volo.CmsKit.Admin.Application.Volo.CmsKit.Admin.Tags +{ + public class EntityTagAdminAppService : CmsKitAdminAppServiceBase, IEntityTagAdminAppService + { + protected ITagDefinitionStore _tagDefinitionStore; + protected IEntityTagManager _entityTagManager; + protected ITagManager _tagManager; + + public EntityTagAdminAppService( + ITagDefinitionStore tagDefinitionStore, + IEntityTagManager entityTagManager, + ITagManager tagManager) + { + _tagDefinitionStore = tagDefinitionStore; + _entityTagManager = entityTagManager; + _tagManager = tagManager; + } + + public async Task AddTagToEntityAsync(EntityTagCreateDto input) + { + var definition = await _tagDefinitionStore.GetTagDefinitionAsync(input.EntityType); + + await CheckPolicyAsync(definition.CreatePolicy); + + var tag = await _tagManager.GetOrAddAsync(input.EntityType, input.TagName, CurrentTenant?.Id); + + await _entityTagManager.AddTagToEntityAsync( + tag.Id, + input.EntityType, + input.EntityId, + CurrentTenant?.Id); + } + + public async Task RemoveTagFromEntityAsync(EntityTagRemoveDto input) + { + var definition = await _tagDefinitionStore.GetTagDefinitionAsync(input.EntityType); + + await CheckPolicyAsync(definition.DeletePolicy); + + await _entityTagManager.RemoveTagFromEntityAsync( + input.TagId, + input.EntityType, + input.EntityId, + CurrentTenant?.Id); + } + } +} diff --git a/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Tags/DefaultTagDefinitionStore.cs b/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Tags/DefaultTagDefinitionStore.cs index bf52474446..b080720129 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Tags/DefaultTagDefinitionStore.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Tags/DefaultTagDefinitionStore.cs @@ -17,10 +17,13 @@ namespace Volo.CmsKit.Tags this.options = options.Value; } - public virtual Task GetTagDefinitionOrNullAsync([NotNull] string entityType) + public virtual Task GetTagDefinitionAsync([NotNull] string entityType) { Check.NotNullOrWhiteSpace(entityType, nameof(entityType)); - return Task.FromResult(options.Tags.GetOrDefault(entityType)); + + var result = options.Tags.GetOrDefault(entityType) ?? throw new EntityNotTaggableException(entityType); + + return Task.FromResult(result); } public virtual Task> GetTagDefinitionsAsync() diff --git a/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Tags/ITagDefinitionStore.cs b/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Tags/ITagDefinitionStore.cs index 033c6e6a5f..c2e12eb16b 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Tags/ITagDefinitionStore.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Tags/ITagDefinitionStore.cs @@ -8,7 +8,7 @@ namespace Volo.CmsKit.Tags { Task> GetTagDefinitionsAsync(); - Task GetTagDefinitionOrNullAsync([NotNull] string entityType); + Task GetTagDefinitionAsync([NotNull] string entityType); Task IsDefinedAsync([NotNull] string entityType); } From ac7a2423f4178e809824047c862336f8ecf152be Mon Sep 17 00:00:00 2001 From: enisn Date: Mon, 18 Jan 2021 09:55:49 +0300 Subject: [PATCH 15/51] CmsKit - Rename TagDefinition as TagEntityTypeDefinition --- .../CmsKit/Admin/CmsKitAdminApplicationAutoMapperProfile.cs | 2 +- .../Volo/CmsKit/Tags/DefaultTagDefinitionStore.cs | 4 ++-- .../Volo/CmsKit/Tags/ITagDefinitionStore.cs | 4 ++-- .../src/Volo.CmsKit.Domain/Volo/CmsKit/Tags/ITagManager.cs | 2 +- .../Volo/CmsKit/Tags/TagDefinitionDictionary.cs | 4 ++-- .../Tags/{TagDefiniton.cs => TagEntityTypeDefiniton1.cs} | 6 +++--- .../src/Volo.CmsKit.Domain/Volo/CmsKit/Tags/TagManager.cs | 2 +- 7 files changed, 12 insertions(+), 12 deletions(-) rename modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Tags/{TagDefiniton.cs => TagEntityTypeDefiniton1.cs} (83%) diff --git a/modules/cms-kit/src/Volo.CmsKit.Admin.Application/Volo/CmsKit/Admin/CmsKitAdminApplicationAutoMapperProfile.cs b/modules/cms-kit/src/Volo.CmsKit.Admin.Application/Volo/CmsKit/Admin/CmsKitAdminApplicationAutoMapperProfile.cs index 5edcfd8a04..f14efb332d 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Admin.Application/Volo/CmsKit/Admin/CmsKitAdminApplicationAutoMapperProfile.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Admin.Application/Volo/CmsKit/Admin/CmsKitAdminApplicationAutoMapperProfile.cs @@ -18,7 +18,7 @@ namespace Volo.CmsKit.Admin CreateMap(MemberList.Source); CreateMap(MemberList.Source); - CreateMap(MemberList.Destination); + CreateMap(MemberList.Destination); } } } diff --git a/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Tags/DefaultTagDefinitionStore.cs b/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Tags/DefaultTagDefinitionStore.cs index b080720129..b5d02fdd23 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Tags/DefaultTagDefinitionStore.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Tags/DefaultTagDefinitionStore.cs @@ -17,7 +17,7 @@ namespace Volo.CmsKit.Tags this.options = options.Value; } - public virtual Task GetTagDefinitionAsync([NotNull] string entityType) + public virtual Task GetTagDefinitionAsync([NotNull] string entityType) { Check.NotNullOrWhiteSpace(entityType, nameof(entityType)); @@ -26,7 +26,7 @@ namespace Volo.CmsKit.Tags return Task.FromResult(result); } - public virtual Task> GetTagDefinitionsAsync() + public virtual Task> GetTagDefinitionsAsync() { return Task.FromResult(options.Tags.Values.ToList()); } diff --git a/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Tags/ITagDefinitionStore.cs b/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Tags/ITagDefinitionStore.cs index c2e12eb16b..e38b5c9e52 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Tags/ITagDefinitionStore.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Tags/ITagDefinitionStore.cs @@ -6,9 +6,9 @@ namespace Volo.CmsKit.Tags { public interface ITagDefinitionStore { - Task> GetTagDefinitionsAsync(); + Task> GetTagDefinitionsAsync(); - Task GetTagDefinitionAsync([NotNull] string entityType); + Task GetTagDefinitionAsync([NotNull] string entityType); Task IsDefinedAsync([NotNull] string entityType); } diff --git a/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Tags/ITagManager.cs b/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Tags/ITagManager.cs index 23d34d6a19..4b9cef5fb8 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Tags/ITagManager.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Tags/ITagManager.cs @@ -28,6 +28,6 @@ namespace Volo.CmsKit.Tags Guid? tenantId = null, CancellationToken cancellationToken = default); - Task> GetTagDefinitionsAsync(CancellationToken cancellationToken = default); + Task> GetTagDefinitionsAsync(CancellationToken cancellationToken = default); } } diff --git a/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Tags/TagDefinitionDictionary.cs b/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Tags/TagDefinitionDictionary.cs index dbbfd29219..b809abbc8d 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Tags/TagDefinitionDictionary.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Tags/TagDefinitionDictionary.cs @@ -4,7 +4,7 @@ using Volo.Abp.Localization; namespace Volo.CmsKit.Tags { - public class TagDefinitionDictionary : Dictionary + public class TagDefinitionDictionary : Dictionary { public void AddOrReplace( [NotNull] string entityType, @@ -14,7 +14,7 @@ namespace Volo.CmsKit.Tags [CanBeNull] string deletePolicy = null ) { - this[entityType] = new TagDefiniton(entityType, displayName, createPolicy, updatePolicy, deletePolicy); + this[entityType] = new TagEntityTypeDefiniton(entityType, displayName, createPolicy, updatePolicy, deletePolicy); } } } diff --git a/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Tags/TagDefiniton.cs b/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Tags/TagEntityTypeDefiniton1.cs similarity index 83% rename from modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Tags/TagDefiniton.cs rename to modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Tags/TagEntityTypeDefiniton1.cs index 6029ab3946..1193f5e68e 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Tags/TagDefiniton.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Tags/TagEntityTypeDefiniton1.cs @@ -5,18 +5,18 @@ using Volo.CmsKit.Domain.Volo.CmsKit; namespace Volo.CmsKit.Tags { - public class TagDefiniton : PolicySpecifiedDefinition + public class TagEntityTypeDefiniton : PolicySpecifiedDefinition { public string EntityType { get; } [CanBeNull] public virtual ILocalizableString DisplayName { get; } - public TagDefiniton() + public TagEntityTypeDefiniton() { } - public TagDefiniton( + public TagEntityTypeDefiniton( [NotNull] string entityType, [CanBeNull] ILocalizableString displayName = null, [CanBeNull] string createPolicy = null, diff --git a/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Tags/TagManager.cs b/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Tags/TagManager.cs index 5318336502..9c881226fa 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Tags/TagManager.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Tags/TagManager.cs @@ -83,7 +83,7 @@ namespace Volo.CmsKit.Tags return await _tagRepository.UpdateAsync(entity, cancellationToken: cancellationToken); } - public Task> GetTagDefinitionsAsync(CancellationToken cancellationToken = default) + public Task> GetTagDefinitionsAsync(CancellationToken cancellationToken = default) { return _tagDefinitionStore.GetTagDefinitionsAsync(); } From bb630ed577e3ffc4b3f240b649852585f7e6565c Mon Sep 17 00:00:00 2001 From: enisn Date: Mon, 18 Jan 2021 11:02:51 +0300 Subject: [PATCH 16/51] CmsKit - Update content value tests --- .../Contents/ContentAdminAppService_Tests.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/modules/cms-kit/test/Volo.CmsKit.Application.Tests/Contents/ContentAdminAppService_Tests.cs b/modules/cms-kit/test/Volo.CmsKit.Application.Tests/Contents/ContentAdminAppService_Tests.cs index d8c5d62a61..c0abc315dc 100644 --- a/modules/cms-kit/test/Volo.CmsKit.Application.Tests/Contents/ContentAdminAppService_Tests.cs +++ b/modules/cms-kit/test/Volo.CmsKit.Application.Tests/Contents/ContentAdminAppService_Tests.cs @@ -36,6 +36,7 @@ namespace Volo.CmsKit.Contents var result = await _service.GetListAsync(new ContentGetListInput()); result.ShouldNotBeNull(); + result.ShouldBeOfType(); result.Items.ShouldNotBeEmpty(); result.Items.Count.ShouldBe(4); } @@ -46,6 +47,8 @@ namespace Volo.CmsKit.Contents var result = await _service.GetAsync(_data.Content_1_Id); result.ShouldNotBeNull(); + result.ShouldBeOfType(); + result.ShouldNotBeNull(result.Value); } [Fact] From cc787655d1555c3cb768c87d3d9b2d98975da1d2 Mon Sep 17 00:00:00 2001 From: enisn Date: Mon, 18 Jan 2021 11:11:37 +0300 Subject: [PATCH 17/51] CmsKit - Fix type mismatch at Content tests --- .../Contents/ContentAdminAppService_Tests.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/cms-kit/test/Volo.CmsKit.Application.Tests/Contents/ContentAdminAppService_Tests.cs b/modules/cms-kit/test/Volo.CmsKit.Application.Tests/Contents/ContentAdminAppService_Tests.cs index c0abc315dc..34c90cbf6e 100644 --- a/modules/cms-kit/test/Volo.CmsKit.Application.Tests/Contents/ContentAdminAppService_Tests.cs +++ b/modules/cms-kit/test/Volo.CmsKit.Application.Tests/Contents/ContentAdminAppService_Tests.cs @@ -36,9 +36,9 @@ namespace Volo.CmsKit.Contents var result = await _service.GetListAsync(new ContentGetListInput()); result.ShouldNotBeNull(); - result.ShouldBeOfType(); result.Items.ShouldNotBeEmpty(); result.Items.Count.ShouldBe(4); + result.Items.All(x => x.ShouldBeOfType() != null); } [Fact] @@ -47,7 +47,7 @@ namespace Volo.CmsKit.Contents var result = await _service.GetAsync(_data.Content_1_Id); result.ShouldNotBeNull(); - result.ShouldBeOfType(); + result.ShouldBeOfType(); result.ShouldNotBeNull(result.Value); } From 0d17aa39169eadf0539e4501a03d8d4aa03dd9b2 Mon Sep 17 00:00:00 2001 From: Mladen Macanovic Date: Mon, 18 Jan 2021 10:44:09 +0100 Subject: [PATCH 18/51] Move CreateModal.Hide() to created/updated methods --- framework/src/Volo.Abp.BlazoriseUI/AbpCrudPageBase.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/framework/src/Volo.Abp.BlazoriseUI/AbpCrudPageBase.cs b/framework/src/Volo.Abp.BlazoriseUI/AbpCrudPageBase.cs index 4a4d4df722..84ab0099c5 100644 --- a/framework/src/Volo.Abp.BlazoriseUI/AbpCrudPageBase.cs +++ b/framework/src/Volo.Abp.BlazoriseUI/AbpCrudPageBase.cs @@ -365,8 +365,6 @@ namespace Volo.Abp.BlazoriseUI await GetEntitiesAsync(); await OnCreatedEntityAsync(); - - CreateModal.Hide(); } } @@ -377,6 +375,7 @@ namespace Volo.Abp.BlazoriseUI protected virtual Task OnCreatedEntityAsync() { + CreateModal.Hide(); return Task.CompletedTask; } @@ -392,8 +391,6 @@ namespace Volo.Abp.BlazoriseUI await GetEntitiesAsync(); await OnUpdatedEntityAsync(); - - EditModal.Hide(); } } @@ -404,6 +401,7 @@ namespace Volo.Abp.BlazoriseUI protected virtual Task OnUpdatedEntityAsync() { + EditModal.Hide(); return Task.CompletedTask; } From ae0852e670627b9e3c408c52797298dd4078f065 Mon Sep 17 00:00:00 2001 From: enisn Date: Mon, 18 Jan 2021 13:58:49 +0300 Subject: [PATCH 19/51] CmsKit - Seperate CmsKitTagOptions --- .../Admin/Tags/EntityTagAdminAppService.cs | 4 ++-- .../Volo/CmsKit/CmsKitDomainModule.cs | 17 ++++++++++++----- .../Volo/CmsKit/CmsKitOptions.cs | 3 --- .../Volo/CmsKit/Tags/CmsKitTagOptions.cs | 11 +++++++++++ .../CmsKit/Tags/DefaultTagDefinitionStore.cs | 12 ++++++------ .../Volo/CmsKit/Tags/ITagDefinitionStore.cs | 2 +- ....cs => TagEntityTypeDefinitionDictionary.cs} | 2 +- 7 files changed, 33 insertions(+), 18 deletions(-) create mode 100644 modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Tags/CmsKitTagOptions.cs rename modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Tags/{TagDefinitionDictionary.cs => TagEntityTypeDefinitionDictionary.cs} (86%) diff --git a/modules/cms-kit/src/Volo.CmsKit.Admin.Application/Volo/CmsKit/Admin/Tags/EntityTagAdminAppService.cs b/modules/cms-kit/src/Volo.CmsKit.Admin.Application/Volo/CmsKit/Admin/Tags/EntityTagAdminAppService.cs index 848a2ea11e..1ef6a8bdf9 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Admin.Application/Volo/CmsKit/Admin/Tags/EntityTagAdminAppService.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Admin.Application/Volo/CmsKit/Admin/Tags/EntityTagAdminAppService.cs @@ -23,7 +23,7 @@ namespace Volo.CmsKit.Admin.Application.Volo.CmsKit.Admin.Tags public async Task AddTagToEntityAsync(EntityTagCreateDto input) { - var definition = await _tagDefinitionStore.GetTagDefinitionAsync(input.EntityType); + var definition = await _tagDefinitionStore.GetTagEntityTypeDefinitionsAsync(input.EntityType); await CheckPolicyAsync(definition.CreatePolicy); @@ -38,7 +38,7 @@ namespace Volo.CmsKit.Admin.Application.Volo.CmsKit.Admin.Tags public async Task RemoveTagFromEntityAsync(EntityTagRemoveDto input) { - var definition = await _tagDefinitionStore.GetTagDefinitionAsync(input.EntityType); + var definition = await _tagDefinitionStore.GetTagEntityTypeDefinitionsAsync(input.EntityType); await CheckPolicyAsync(definition.DeletePolicy); diff --git a/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/CmsKitDomainModule.cs b/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/CmsKitDomainModule.cs index 719a5bc215..faf258f923 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/CmsKitDomainModule.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/CmsKitDomainModule.cs @@ -1,4 +1,5 @@ -using Volo.Abp.Domain; +using Microsoft.Extensions.Options; +using Volo.Abp.Domain; using Volo.Abp.GlobalFeatures; using Volo.Abp.Localization; using Volo.Abp.Modularity; @@ -7,6 +8,7 @@ using Volo.CmsKit.GlobalFeatures; using Volo.CmsKit.Localization; using Volo.CmsKit.Pages; using Volo.CmsKit.Reactions; +using Volo.CmsKit.Tags; namespace Volo.CmsKit { @@ -34,14 +36,19 @@ namespace Volo.CmsKit options.Reactions.AddOrReplace(StandardReactions.Rocket); options.Reactions.AddOrReplace(StandardReactions.Pray); - if (GlobalFeatureManager.Instance.IsEnabled()) + }); + + + if (GlobalFeatureManager.Instance.IsEnabled()) + { + Configure(options => { if (GlobalFeatureManager.Instance.IsEnabled()) { - options.Tags.AddOrReplace(typeof(Page).Name, L("Page")); + options.EntityTypes.AddOrReplace(typeof(Page).Name, L("Page")); } - } - }); + }); + } } private static LocalizableString L(string name) diff --git a/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/CmsKitOptions.cs b/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/CmsKitOptions.cs index 20fde8704d..8075895184 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/CmsKitOptions.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/CmsKitOptions.cs @@ -8,8 +8,5 @@ namespace Volo.CmsKit { [NotNull] public ReactionDefinitionDictionary Reactions { get; } = new ReactionDefinitionDictionary(); - - [NotNull] - public TagDefinitionDictionary Tags { get; } = new TagDefinitionDictionary(); } } diff --git a/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Tags/CmsKitTagOptions.cs b/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Tags/CmsKitTagOptions.cs new file mode 100644 index 0000000000..8f9b073d6b --- /dev/null +++ b/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Tags/CmsKitTagOptions.cs @@ -0,0 +1,11 @@ +using JetBrains.Annotations; +using Volo.CmsKit.Tags; + +namespace Volo.CmsKit.Tags +{ + public class CmsKitTagOptions + { + [NotNull] + public TagEntityTypeDefinitionDictionary EntityTypes { get; } = new TagEntityTypeDefinitionDictionary(); + } +} diff --git a/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Tags/DefaultTagDefinitionStore.cs b/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Tags/DefaultTagDefinitionStore.cs index b5d02fdd23..6860e8c95c 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Tags/DefaultTagDefinitionStore.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Tags/DefaultTagDefinitionStore.cs @@ -10,31 +10,31 @@ namespace Volo.CmsKit.Tags { public class DefaultTagDefinitionStore : ITagDefinitionStore, ITransientDependency { - private readonly CmsKitOptions options; + private readonly CmsKitTagOptions options; - public DefaultTagDefinitionStore(IOptions options) + public DefaultTagDefinitionStore(IOptions options) { this.options = options.Value; } - public virtual Task GetTagDefinitionAsync([NotNull] string entityType) + public virtual Task GetTagEntityTypeDefinitionsAsync([NotNull] string entityType) { Check.NotNullOrWhiteSpace(entityType, nameof(entityType)); - var result = options.Tags.GetOrDefault(entityType) ?? throw new EntityNotTaggableException(entityType); + var result = options.EntityTypes.GetOrDefault(entityType) ?? throw new EntityNotTaggableException(entityType); return Task.FromResult(result); } public virtual Task> GetTagDefinitionsAsync() { - return Task.FromResult(options.Tags.Values.ToList()); + return Task.FromResult(options.EntityTypes.Values.ToList()); } public virtual Task IsDefinedAsync([NotNull] string entityType) { Check.NotNullOrWhiteSpace(entityType, nameof(entityType)); - return Task.FromResult(options.Tags.ContainsKey(entityType)); + return Task.FromResult(options.EntityTypes.ContainsKey(entityType)); } } } diff --git a/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Tags/ITagDefinitionStore.cs b/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Tags/ITagDefinitionStore.cs index e38b5c9e52..1221e3660d 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Tags/ITagDefinitionStore.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Tags/ITagDefinitionStore.cs @@ -8,7 +8,7 @@ namespace Volo.CmsKit.Tags { Task> GetTagDefinitionsAsync(); - Task GetTagDefinitionAsync([NotNull] string entityType); + Task GetTagEntityTypeDefinitionsAsync([NotNull] string entityType); Task IsDefinedAsync([NotNull] string entityType); } diff --git a/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Tags/TagDefinitionDictionary.cs b/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Tags/TagEntityTypeDefinitionDictionary.cs similarity index 86% rename from modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Tags/TagDefinitionDictionary.cs rename to modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Tags/TagEntityTypeDefinitionDictionary.cs index b809abbc8d..669dbbb315 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Tags/TagDefinitionDictionary.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Tags/TagEntityTypeDefinitionDictionary.cs @@ -4,7 +4,7 @@ using Volo.Abp.Localization; namespace Volo.CmsKit.Tags { - public class TagDefinitionDictionary : Dictionary + public class TagEntityTypeDefinitionDictionary : Dictionary { public void AddOrReplace( [NotNull] string entityType, From c50572e648a0aa876af8d435a23293e0aa038065 Mon Sep 17 00:00:00 2001 From: enisn Date: Mon, 18 Jan 2021 14:40:38 +0300 Subject: [PATCH 20/51] CmsKit - Update tests for CmsKitTagOptions --- .../CmsKitDataSeedContributor.cs | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/modules/cms-kit/test/Volo.CmsKit.TestBase/CmsKitDataSeedContributor.cs b/modules/cms-kit/test/Volo.CmsKit.TestBase/CmsKitDataSeedContributor.cs index 9adc4442cb..3dc77fe1fc 100644 --- a/modules/cms-kit/test/Volo.CmsKit.TestBase/CmsKitDataSeedContributor.cs +++ b/modules/cms-kit/test/Volo.CmsKit.TestBase/CmsKitDataSeedContributor.cs @@ -31,6 +31,7 @@ namespace Volo.CmsKit private readonly ITagManager _tagManager; private readonly IPageRepository _pageRepository; private readonly IOptions _options; + private readonly IOptions _tagOptions; public CmsKitDataSeedContributor( IGuidGenerator guidGenerator, @@ -44,7 +45,8 @@ namespace Volo.CmsKit ITagManager tagManager, IEntityTagManager entityTagManager, IPageRepository pageRepository, - IOptions options) + IOptions options, + IOptions tagOptions) { _guidGenerator = guidGenerator; _cmsUserRepository = cmsUserRepository; @@ -58,6 +60,7 @@ namespace Volo.CmsKit _entityTagManager = entityTagManager; _pageRepository = pageRepository; _options = options; + _tagOptions = tagOptions; } public async Task SeedAsync(DataSeedContext context) @@ -83,12 +86,12 @@ namespace Volo.CmsKit } private Task ConfigureCmsKitOptionsAsync() - { - _options.Value.Tags.AddOrReplace(_cmsKitTestData.EntityType1); - _options.Value.Tags.AddOrReplace(_cmsKitTestData.EntityType2); - _options.Value.Tags.AddOrReplace(_cmsKitTestData.Content_1_EntityType); - _options.Value.Tags.AddOrReplace(_cmsKitTestData.Content_2_EntityType); - _options.Value.Tags.AddOrReplace(_cmsKitTestData.TagDefinition_1_EntityType); + { + _tagOptions.Value.EntityTypes.AddOrReplace(_cmsKitTestData.EntityType1); + _tagOptions.Value.EntityTypes.AddOrReplace(_cmsKitTestData.EntityType2); + _tagOptions.Value.EntityTypes.AddOrReplace(_cmsKitTestData.Content_1_EntityType); + _tagOptions.Value.EntityTypes.AddOrReplace(_cmsKitTestData.Content_2_EntityType); + _tagOptions.Value.EntityTypes.AddOrReplace(_cmsKitTestData.TagDefinition_1_EntityType); return Task.CompletedTask; } From 3f9cdce94b8655d487f3cf73a95890035df1d516 Mon Sep 17 00:00:00 2001 From: Oliver Cooper Date: Mon, 18 Jan 2021 12:17:11 +0000 Subject: [PATCH 21/51] Improve flexibility of email validation in the ValidationHelper * ValidationHelper.EmailRegEx can now be modified to suit the users needs * Regex updated to be more inclusive - see https://html.spec.whatwg.org/#valid-e-mail-address --- .../Volo/Abp/Validation/ValidationHelper.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/ValidationHelper.cs b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/ValidationHelper.cs index d46281d2e9..9f6cec23e5 100644 --- a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/ValidationHelper.cs +++ b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/ValidationHelper.cs @@ -4,7 +4,9 @@ namespace Volo.Abp.Validation { public class ValidationHelper { - private const string EmailRegEx = @"[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?"; + // Taken from W3C as an alternative to the RFC5322 specification: https://html.spec.whatwg.org/#valid-e-mail-address + // The RFC5322 regex can be found here: https://emailregex.com/ + public static string EmailRegEx { get; set; } = @"^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$"; public static bool IsValidEmailAddress(string email) { @@ -13,7 +15,6 @@ namespace Volo.Abp.Validation return false; } - /*RFC 2822 (simplified)*/ return Regex.IsMatch(email, EmailRegEx, RegexOptions.Compiled | RegexOptions.IgnoreCase); } } From 89344d66cd7a116ae70486880cbe63232f9a3ddc Mon Sep 17 00:00:00 2001 From: Oliver Cooper Date: Mon, 18 Jan 2021 13:01:04 +0000 Subject: [PATCH 22/51] Update tests for ValidationHelper --- .../Abp/Validation/ApplicationService_Validation_Tests.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/framework/test/Volo.Abp.Validation.Tests/Volo/Abp/Validation/ApplicationService_Validation_Tests.cs b/framework/test/Volo.Abp.Validation.Tests/Volo/Abp/Validation/ApplicationService_Validation_Tests.cs index de9067e2f7..cd602c07af 100644 --- a/framework/test/Volo.Abp.Validation.Tests/Volo/Abp/Validation/ApplicationService_Validation_Tests.cs +++ b/framework/test/Volo.Abp.Validation.Tests/Volo/Abp/Validation/ApplicationService_Validation_Tests.cs @@ -180,15 +180,19 @@ namespace Volo.Abp.Validation { //Valid ValidationHelper.IsValidEmailAddress("john.doe@domain.com").ShouldBe(true); + ValidationHelper.IsValidEmailAddress("john-doe1@domain.co").ShouldBe(true); ValidationHelper.IsValidEmailAddress("ip@1.2.3.123").ShouldBe(true); ValidationHelper.IsValidEmailAddress("pharaoh@egyptian.museum").ShouldBe(true); ValidationHelper.IsValidEmailAddress("john.doe+regexbuddy@gmail.com").ShouldBe(true); ValidationHelper.IsValidEmailAddress("Mike.O'Dell@ireland.com").ShouldBe(true); + ValidationHelper.IsValidEmailAddress("admin@localhost").ShouldBe(true); + ValidationHelper.IsValidEmailAddress("j@h.c").ShouldBe(true); //Invalid - ValidationHelper.IsValidEmailAddress("1024x768@60Hz").ShouldBe(false); ValidationHelper.IsValidEmailAddress("not.a.valid.email").ShouldBe(false); ValidationHelper.IsValidEmailAddress("john@aol...com").ShouldBe(false); + ValidationHelper.IsValidEmailAddress("john@aol@domain.com").ShouldBe(false); + ValidationHelper.IsValidEmailAddress("jack@domain.").ShouldBe(false); } [DependsOn(typeof(AbpAutofacModule))] From 9e9c315d81c6d18054b43ad4a6409b22c4935017 Mon Sep 17 00:00:00 2001 From: Ahmet Date: Tue, 19 Jan 2021 11:51:46 +0300 Subject: [PATCH 23/51] added routing to page --- .../Volo/CmsKit/Public/Pages/IPageAppService.cs | 2 +- .../Volo/CmsKit/Public/Pages/PageAppService.cs | 4 ++-- .../Volo/CmsKit/Public/Pages/PagesPublicController.cs | 2 +- .../Volo.CmsKit.Public.Web/CmsKitPublicWebModule.cs | 2 +- .../Pages/CmsKit/Pages/Index.cshtml.cs | 11 +++++++++-- .../Pages/PagePublicAppService_Tests.cs | 11 ++++++++--- 6 files changed, 22 insertions(+), 10 deletions(-) diff --git a/modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Public/Pages/IPageAppService.cs b/modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Public/Pages/IPageAppService.cs index d8ea8f1751..7363d32a3c 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Public/Pages/IPageAppService.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Public/Pages/IPageAppService.cs @@ -5,6 +5,6 @@ namespace Volo.CmsKit.Public.Pages { public interface IPageAppService { - Task GetByUrlAsync([NotNull] string url); + Task FindByUrlAsync([NotNull] string url); } } \ No newline at end of file diff --git a/modules/cms-kit/src/Volo.CmsKit.Public.Application/Volo/CmsKit/Public/Pages/PageAppService.cs b/modules/cms-kit/src/Volo.CmsKit.Public.Application/Volo/CmsKit/Public/Pages/PageAppService.cs index 38f4406cab..e78d54a742 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Public.Application/Volo/CmsKit/Public/Pages/PageAppService.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Public.Application/Volo/CmsKit/Public/Pages/PageAppService.cs @@ -12,9 +12,9 @@ namespace Volo.CmsKit.Public.Pages PageRepository = pageRepository; } - public virtual async Task GetByUrlAsync(string url) + public virtual async Task FindByUrlAsync(string url) { - var page = await PageRepository.GetByUrlAsync(url); + var page = await PageRepository.FindByUrlAsync(url); return ObjectMapper.Map(page); } diff --git a/modules/cms-kit/src/Volo.CmsKit.Public.HttpApi/Volo/CmsKit/Public/Pages/PagesPublicController.cs b/modules/cms-kit/src/Volo.CmsKit.Public.HttpApi/Volo/CmsKit/Public/Pages/PagesPublicController.cs index 45dda25ab5..4d1c5391c7 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Public.HttpApi/Volo/CmsKit/Public/Pages/PagesPublicController.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Public.HttpApi/Volo/CmsKit/Public/Pages/PagesPublicController.cs @@ -20,7 +20,7 @@ namespace Volo.CmsKit.Public.Pages [Route("url/{url}")] public Task GetByUrlAsync(string url) { - return PageAppService.GetByUrlAsync(url); + return PageAppService.FindByUrlAsync(url); } } } \ No newline at end of file diff --git a/modules/cms-kit/src/Volo.CmsKit.Public.Web/CmsKitPublicWebModule.cs b/modules/cms-kit/src/Volo.CmsKit.Public.Web/CmsKitPublicWebModule.cs index 0149722671..2dba2f667a 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Public.Web/CmsKitPublicWebModule.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Public.Web/CmsKitPublicWebModule.cs @@ -56,7 +56,7 @@ namespace Volo.CmsKit.Public.Web Configure(options => { - //... + options.Conventions.AddPageRoute("/CmsKit/Pages/Index", "/{pageUrl}"); }); } } diff --git a/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Pages/Index.cshtml.cs b/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Pages/Index.cshtml.cs index 1d5dca0940..5443608845 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Pages/Index.cshtml.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Pages/Index.cshtml.cs @@ -19,9 +19,16 @@ namespace Volo.CmsKit.Public.Web.Pages.CmsKit.Pages PageAppService = pageAppService; } - public async Task OnGetAsync() + public async Task OnGetAsync() { - Page = await PageAppService.GetByUrlAsync(PageUrl); + Page = await PageAppService.FindByUrlAsync(PageUrl); + + if (Page == null) + { + return NotFound(); + } + + return Page(); } } } \ No newline at end of file diff --git a/modules/cms-kit/test/Volo.CmsKit.Application.Tests/Pages/PagePublicAppService_Tests.cs b/modules/cms-kit/test/Volo.CmsKit.Application.Tests/Pages/PagePublicAppService_Tests.cs index 290f02caff..c0943a1279 100644 --- a/modules/cms-kit/test/Volo.CmsKit.Application.Tests/Pages/PagePublicAppService_Tests.cs +++ b/modules/cms-kit/test/Volo.CmsKit.Application.Tests/Pages/PagePublicAppService_Tests.cs @@ -18,15 +18,20 @@ namespace Volo.CmsKit.Pages } [Fact] - public async Task ShouldGetByUrlAsync() + public async Task ShouldFindByUrlAsync() { - await Should.NotThrowAsync(async () => await _pageAppService.GetByUrlAsync(_data.Page_1_Url)); + var page = await _pageAppService.FindByUrlAsync(_data.Page_1_Url); + + page.ShouldNotBeNull(); + page.Title.ShouldBe(_data.Page_1_Title); } [Fact] public async Task ShouldNotGetByUrlAsync() { - await Should.ThrowAsync(async () => await _pageAppService.GetByUrlAsync("not-exist-url")); + var page = await _pageAppService.FindByUrlAsync("not-exist-url"); + + page.ShouldBeNull(); } } } \ No newline at end of file From 08b9ad0d4e0352fd3fbb6f61baa7e2ae8f1b378a Mon Sep 17 00:00:00 2001 From: Ahmet Date: Tue, 19 Jan 2021 12:18:19 +0300 Subject: [PATCH 24/51] replaced the route parameter --- .../src/Volo.CmsKit.Public.Web/CmsKitPublicWebModule.cs | 5 +++-- .../Volo.CmsKit.Public.Web/Pages/CmsKit/Pages/Index.cshtml | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/modules/cms-kit/src/Volo.CmsKit.Public.Web/CmsKitPublicWebModule.cs b/modules/cms-kit/src/Volo.CmsKit.Public.Web/CmsKitPublicWebModule.cs index 2dba2f667a..d44f67afc5 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Public.Web/CmsKitPublicWebModule.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Public.Web/CmsKitPublicWebModule.cs @@ -1,4 +1,5 @@ -using Microsoft.AspNetCore.Mvc.RazorPages; +using System.Linq; +using Microsoft.AspNetCore.Mvc.RazorPages; using Microsoft.Extensions.DependencyInjection; using Volo.Abp.AspNetCore.Mvc.Localization; using Volo.Abp.AutoMapper; @@ -56,7 +57,7 @@ namespace Volo.CmsKit.Public.Web Configure(options => { - options.Conventions.AddPageRoute("/CmsKit/Pages/Index", "/{pageUrl}"); + options.Conventions.AddPageRoute("/CmsKit/Pages/Index", "/{*pageUrl}"); }); } } diff --git a/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Pages/Index.cshtml b/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Pages/Index.cshtml index c31edaaba8..73c7637b01 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Pages/Index.cshtml +++ b/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Pages/Index.cshtml @@ -1,4 +1,4 @@ -@page "{pageUrl}" +@page "{*pageUrl}" @using Microsoft.AspNetCore.Mvc.Localization @using Volo.CmsKit.Localization @using Volo.CmsKit.Public.Web.Pages.CmsKit.Shared.Components.Pages From afcddd5e5d27518fa47012dd35a5d7cd42f8e2e0 Mon Sep 17 00:00:00 2001 From: Ahmet Date: Tue, 19 Jan 2021 12:25:08 +0300 Subject: [PATCH 25/51] check null at app service --- .../Volo/CmsKit/Public/Pages/PageAppService.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/modules/cms-kit/src/Volo.CmsKit.Public.Application/Volo/CmsKit/Public/Pages/PageAppService.cs b/modules/cms-kit/src/Volo.CmsKit.Public.Application/Volo/CmsKit/Public/Pages/PageAppService.cs index e78d54a742..1e1d63bf71 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Public.Application/Volo/CmsKit/Public/Pages/PageAppService.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Public.Application/Volo/CmsKit/Public/Pages/PageAppService.cs @@ -15,6 +15,11 @@ namespace Volo.CmsKit.Public.Pages public virtual async Task FindByUrlAsync(string url) { var page = await PageRepository.FindByUrlAsync(url); + + if (page == null) + { + return null; + } return ObjectMapper.Map(page); } From 0116692bff98a5e0b2a8f9b16c13fbb29cfa3b3d Mon Sep 17 00:00:00 2001 From: enisn Date: Tue, 19 Jan 2021 14:21:24 +0300 Subject: [PATCH 26/51] CmsKit - Fix conflict error --- .../Volo/CmsKit/Tags/EntityNotTaggableException.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Tags/EntityNotTaggableException.cs b/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Tags/EntityNotTaggableException.cs index 1987bef869..d39c81a127 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Tags/EntityNotTaggableException.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Tags/EntityNotTaggableException.cs @@ -12,7 +12,7 @@ namespace Volo.CmsKit.Tags public EntityNotTaggableException(string entityType) { - Code = CmsKitErrorCodes.EntityNotTaggable; + Code = CmsKitErrorCodes.Tags.EntityNotTaggable; WithData(nameof(Tag.EntityType), entityType); } } From 78240039148331da6f856375228daabf5abeb84f Mon Sep 17 00:00:00 2001 From: Arman Ozak Date: Tue, 19 Jan 2021 14:39:30 +0300 Subject: [PATCH 27/51] add link to unit testing video on ABP Community --- docs/en/UI/Angular/Testing.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/en/UI/Angular/Testing.md b/docs/en/UI/Angular/Testing.md index 509fbefe95..bf6d3e1b26 100644 --- a/docs/en/UI/Angular/Testing.md +++ b/docs/en/UI/Angular/Testing.md @@ -374,3 +374,7 @@ Finally, don't forget to run your CI tests with the following command: ```sh npm test -- --prod ``` + +## See Also + +* [ABP Community Video - Unit Testing with the Angular UI](https://community.abp.io/articles/unit-testing-with-the-angular-ui-p4l550q3) From 964a16253b94b52d571c35944d2bb7293538f21a Mon Sep 17 00:00:00 2001 From: enisn Date: Tue, 19 Jan 2021 15:36:07 +0300 Subject: [PATCH 28/51] CmsKit - Conflict fix for TagAlreadyExistException --- .../Volo/CmsKit/Tags/TagAlreadyExistException.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Tags/TagAlreadyExistException.cs b/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Tags/TagAlreadyExistException.cs index 6cae0dde92..2d87668043 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Tags/TagAlreadyExistException.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Tags/TagAlreadyExistException.cs @@ -9,7 +9,7 @@ namespace Volo.CmsKit.Tags { public TagAlreadyExistException([NotNull] string entityType, [NotNull] string name) { - Code = CmsKitErrorCodes.TagAlreadyExist; + Code = CmsKitErrorCodes.Tags.TagAlreadyExist; WithData(nameof(Tag.EntityType), entityType); WithData(nameof(Tag.Name), name); } From a0ce2cc2843d2cf01ef6157aad33e3fa6d88b7c2 Mon Sep 17 00:00:00 2001 From: maliming Date: Wed, 20 Jan 2021 10:21:26 +0800 Subject: [PATCH 29/51] Use the correct localize key on the login page. Resolve #7350 --- .../account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml index 056e69e895..57dec99889 100644 --- a/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml +++ b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml @@ -56,7 +56,7 @@ @foreach (var provider in Model.VisibleExternalProviders) { - + } From 648955798d694f640d573e2b178f09fb41b4150d Mon Sep 17 00:00:00 2001 From: Yunus Emre Kalkan Date: Wed, 20 Jan 2021 09:53:29 +0300 Subject: [PATCH 30/51] Cli DBMS: SQLite connection string espace char resolves https://github.com/abpframework/abp/issues/7339 --- .../src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/NewCommand.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/NewCommand.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/NewCommand.cs index 9caa952528..0c88f238b8 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/NewCommand.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/NewCommand.cs @@ -248,7 +248,7 @@ namespace Volo.Abp.Cli.Commands case DatabaseManagementSystem.OracleDevart: return "Data Source=MyProjectName;Integrated Security=yes;"; case DatabaseManagementSystem.SQLite: - return $"Data Source={Path.Combine(outputFolder,"database\\MyProjectName.db")};Version=3;"; + return $"Data Source={Path.Combine(outputFolder,"database\\MyProjectName.db")};Version=3;".Replace("\\", "\\\\"); default: return null; } From 534419e32130fd6b1f82e70fa562fd940aac0a5d Mon Sep 17 00:00:00 2001 From: Yunus Emre Kalkan Date: Wed, 20 Jan 2021 10:12:10 +0300 Subject: [PATCH 31/51] Cli dbms: remove Version keyword from SqLite connection string --- .../src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/NewCommand.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/NewCommand.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/NewCommand.cs index 0c88f238b8..f80daa4240 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/NewCommand.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/NewCommand.cs @@ -248,7 +248,7 @@ namespace Volo.Abp.Cli.Commands case DatabaseManagementSystem.OracleDevart: return "Data Source=MyProjectName;Integrated Security=yes;"; case DatabaseManagementSystem.SQLite: - return $"Data Source={Path.Combine(outputFolder,"database\\MyProjectName.db")};Version=3;".Replace("\\", "\\\\"); + return $"Data Source={Path.Combine(outputFolder,"database\\MyProjectName.db")};".Replace("\\", "\\\\"); default: return null; } From 30ed87a53abc36765cff7eb56f4d3a90163f6063 Mon Sep 17 00:00:00 2001 From: Yunus Emre Kalkan Date: Wed, 20 Jan 2021 10:16:18 +0300 Subject: [PATCH 32/51] Update NewCommand.cs --- .../src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/NewCommand.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/NewCommand.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/NewCommand.cs index f80daa4240..e0c1a4ffcc 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/NewCommand.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/NewCommand.cs @@ -248,7 +248,7 @@ namespace Volo.Abp.Cli.Commands case DatabaseManagementSystem.OracleDevart: return "Data Source=MyProjectName;Integrated Security=yes;"; case DatabaseManagementSystem.SQLite: - return $"Data Source={Path.Combine(outputFolder,"database\\MyProjectName.db")};".Replace("\\", "\\\\"); + return $"Data Source={Path.Combine(outputFolder,"database" , "MyProjectName.db")};".Replace("\\", "\\\\"); default: return null; } From 4af52a08d0e7c787925caa76cec6b09ff491d2c4 Mon Sep 17 00:00:00 2001 From: enisn Date: Wed, 20 Jan 2021 10:28:08 +0300 Subject: [PATCH 33/51] CmsKit - Refactoring TagsEntityTypes --- .../Volo/CmsKit/CmsKitDomainModule.cs | 11 ++--------- .../Volo/CmsKit/Tags/EntityNotTaggableException.cs | 11 +++++++++-- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/CmsKitDomainModule.cs b/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/CmsKitDomainModule.cs index faf258f923..a60cb0d521 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/CmsKitDomainModule.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/CmsKitDomainModule.cs @@ -38,16 +38,9 @@ namespace Volo.CmsKit }); - if (GlobalFeatureManager.Instance.IsEnabled()) { - Configure(options => - { - if (GlobalFeatureManager.Instance.IsEnabled()) - { - options.EntityTypes.AddOrReplace(typeof(Page).Name, L("Page")); - } - }); + // TODO: Configure TagEntityTypes here... } } @@ -56,4 +49,4 @@ namespace Volo.CmsKit return LocalizableString.Create(name); } } -} +} \ No newline at end of file diff --git a/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Tags/EntityNotTaggableException.cs b/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Tags/EntityNotTaggableException.cs index d39c81a127..8787fb16a5 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Tags/EntityNotTaggableException.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Tags/EntityNotTaggableException.cs @@ -1,4 +1,5 @@ -using System; +using Microsoft.Extensions.Logging; +using System; using Volo.Abp; namespace Volo.CmsKit.Tags @@ -6,7 +7,13 @@ namespace Volo.CmsKit.Tags [Serializable] public class EntityNotTaggableException : BusinessException { - public EntityNotTaggableException(string code = null, string message = null, string details = null, Exception innerException = null, Microsoft.Extensions.Logging.LogLevel logLevel = Microsoft.Extensions.Logging.LogLevel.Warning) : base(code, message, details, innerException, logLevel) + public EntityNotTaggableException( + string code = null, + string message = null, + string details = null, + Exception innerException = null, + LogLevel logLevel = LogLevel.Warning) + : base(code, message, details, innerException, logLevel) { } From a16afa03164e7cbbf084eced7e673c1e82fdf39f Mon Sep 17 00:00:00 2001 From: Yunus Emre Kalkan Date: Wed, 20 Jan 2021 10:34:12 +0300 Subject: [PATCH 34/51] Update NewCommand.cs --- .../src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/NewCommand.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/NewCommand.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/NewCommand.cs index e0c1a4ffcc..96e59152de 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/NewCommand.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/NewCommand.cs @@ -248,7 +248,7 @@ namespace Volo.Abp.Cli.Commands case DatabaseManagementSystem.OracleDevart: return "Data Source=MyProjectName;Integrated Security=yes;"; case DatabaseManagementSystem.SQLite: - return $"Data Source={Path.Combine(outputFolder,"database" , "MyProjectName.db")};".Replace("\\", "\\\\"); + return $"Data Source={Path.Combine(outputFolder , "MyProjectName.db")};".Replace("\\", "\\\\"); default: return null; } From b8e1a969be953097a9a67715ce5f34f76c598a72 Mon Sep 17 00:00:00 2001 From: liangshiwei Date: Wed, 20 Jan 2021 16:46:57 +0800 Subject: [PATCH 35/51] Add CancellationToken optional parameter to the Auditlogging module repository methods --- .../Abp/AuditLogging/IAuditLogRepository.cs | 9 ++--- .../EfCoreAuditLogRepository.cs | 26 +++++++++----- .../MongoDB/MongoAuditLogRepository.cs | 34 ++++++++++++------- 3 files changed, 45 insertions(+), 24 deletions(-) diff --git a/modules/audit-logging/src/Volo.Abp.AuditLogging.Domain/Volo/Abp/AuditLogging/IAuditLogRepository.cs b/modules/audit-logging/src/Volo.Abp.AuditLogging.Domain/Volo/Abp/AuditLogging/IAuditLogRepository.cs index ad0e0d20c2..cd7e22b9a2 100644 --- a/modules/audit-logging/src/Volo.Abp.AuditLogging.Domain/Volo/Abp/AuditLogging/IAuditLogRepository.cs +++ b/modules/audit-logging/src/Volo.Abp.AuditLogging.Domain/Volo/Abp/AuditLogging/IAuditLogRepository.cs @@ -44,9 +44,10 @@ namespace Volo.Abp.AuditLogging Task> GetAverageExecutionDurationPerDayAsync( DateTime startDate, - DateTime endDate); + DateTime endDate, + CancellationToken cancellationToken = default); - Task GetEntityChange(Guid entityChangeId); + Task GetEntityChange(Guid entityChangeId, CancellationToken cancellationToken = default); Task> GetEntityChangeListAsync( string sorting = null, @@ -70,8 +71,8 @@ namespace Volo.Abp.AuditLogging string entityTypeFullName = null, CancellationToken cancellationToken = default); - Task GetEntityChangeWithUsernameAsync(Guid entityChangeId); + Task GetEntityChangeWithUsernameAsync(Guid entityChangeId, CancellationToken cancellationToken = default); - Task> GetEntityChangesWithUsernameAsync(string entityId, string entityTypeFullName); + Task> GetEntityChangesWithUsernameAsync(string entityId, string entityTypeFullName, CancellationToken cancellationToken = default); } } diff --git a/modules/audit-logging/src/Volo.Abp.AuditLogging.EntityFrameworkCore/Volo/Abp/AuditLogging/EntityFrameworkCore/EfCoreAuditLogRepository.cs b/modules/audit-logging/src/Volo.Abp.AuditLogging.EntityFrameworkCore/Volo/Abp/AuditLogging/EntityFrameworkCore/EfCoreAuditLogRepository.cs index edb1f3f599..acd1912925 100644 --- a/modules/audit-logging/src/Volo.Abp.AuditLogging.EntityFrameworkCore/Volo/Abp/AuditLogging/EntityFrameworkCore/EfCoreAuditLogRepository.cs +++ b/modules/audit-logging/src/Volo.Abp.AuditLogging.EntityFrameworkCore/Volo/Abp/AuditLogging/EntityFrameworkCore/EfCoreAuditLogRepository.cs @@ -125,14 +125,17 @@ namespace Volo.Abp.AuditLogging.EntityFrameworkCore .WhereIf(minExecutionDuration != null && minExecutionDuration.Value > 0, auditLog => auditLog.ExecutionDuration >= minExecutionDuration); } - public virtual async Task> GetAverageExecutionDurationPerDayAsync(DateTime startDate, DateTime endDate) + public virtual async Task> GetAverageExecutionDurationPerDayAsync( + DateTime startDate, + DateTime endDate, + CancellationToken cancellationToken = default) { var result = await (await GetDbSetAsync()).AsNoTracking() .Where(a => a.ExecutionTime < endDate.AddDays(1) && a.ExecutionTime > startDate) .OrderBy(t => t.ExecutionTime) .GroupBy(t => new { t.ExecutionTime.Date }) .Select(g => new { Day = g.Min(t => t.ExecutionTime), avgExecutionTime = g.Average(t => t.ExecutionDuration) }) - .ToListAsync(); + .ToListAsync(GetCancellationToken(cancellationToken)); return result.ToDictionary(element => element.Day.ClearTime(), element => element.avgExecutionTime); } @@ -148,14 +151,16 @@ namespace Volo.Abp.AuditLogging.EntityFrameworkCore return (await GetQueryableAsync()).IncludeDetails(); } - public virtual async Task GetEntityChange(Guid entityChangeId) + public virtual async Task GetEntityChange( + Guid entityChangeId, + CancellationToken cancellationToken = default) { var entityChange = await (await GetDbContextAsync()).Set() .AsNoTracking() .IncludeDetails() .Where(x => x.Id == entityChangeId) .OrderBy(x => x.Id) - .FirstOrDefaultAsync(); + .FirstOrDefaultAsync(GetCancellationToken(cancellationToken)); if (entityChange == null) { @@ -201,10 +206,12 @@ namespace Volo.Abp.AuditLogging.EntityFrameworkCore return totalCount; } - public virtual async Task GetEntityChangeWithUsernameAsync(Guid entityChangeId) + public virtual async Task GetEntityChangeWithUsernameAsync( + Guid entityChangeId, + CancellationToken cancellationToken = default) { var auditLog = await (await GetDbSetAsync()).AsNoTracking().IncludeDetails() - .Where(x => x.EntityChanges.Any(y => y.Id == entityChangeId)).FirstAsync(); + .Where(x => x.EntityChanges.Any(y => y.Id == entityChangeId)).FirstAsync(GetCancellationToken(cancellationToken)); return new EntityChangeWithUsername() { @@ -213,7 +220,10 @@ namespace Volo.Abp.AuditLogging.EntityFrameworkCore }; } - public virtual async Task> GetEntityChangesWithUsernameAsync(string entityId, string entityTypeFullName) + public virtual async Task> GetEntityChangesWithUsernameAsync( + string entityId, + string entityTypeFullName, + CancellationToken cancellationToken = default) { var dbContext = await GetDbContextAsync(); @@ -225,7 +235,7 @@ namespace Volo.Abp.AuditLogging.EntityFrameworkCore return await (from e in query join auditLog in dbContext.AuditLogs on e.AuditLogId equals auditLog.Id select new EntityChangeWithUsername {EntityChange = e, UserName = auditLog.UserName}) - .OrderByDescending(x => x.EntityChange.ChangeTime).ToListAsync(); + .OrderByDescending(x => x.EntityChange.ChangeTime).ToListAsync(GetCancellationToken(cancellationToken)); } protected virtual async Task> GetEntityChangeListQueryAsync( diff --git a/modules/audit-logging/src/Volo.Abp.AuditLogging.MongoDB/Volo/Abp/AuditLogging/MongoDB/MongoAuditLogRepository.cs b/modules/audit-logging/src/Volo.Abp.AuditLogging.MongoDB/Volo/Abp/AuditLogging/MongoDB/MongoAuditLogRepository.cs index 788b6995bb..ce3c0e18c8 100644 --- a/modules/audit-logging/src/Volo.Abp.AuditLogging.MongoDB/Volo/Abp/AuditLogging/MongoDB/MongoAuditLogRepository.cs +++ b/modules/audit-logging/src/Volo.Abp.AuditLogging.MongoDB/Volo/Abp/AuditLogging/MongoDB/MongoAuditLogRepository.cs @@ -124,9 +124,12 @@ namespace Volo.Abp.AuditLogging.MongoDB } - public virtual async Task> GetAverageExecutionDurationPerDayAsync(DateTime startDate, DateTime endDate) + public virtual async Task> GetAverageExecutionDurationPerDayAsync( + DateTime startDate, + DateTime endDate, + CancellationToken cancellationToken = default) { - var result = await (await GetMongoQueryableAsync()) + var result = await (await GetMongoQueryableAsync(GetCancellationToken(cancellationToken))) .Where(a => a.ExecutionTime < endDate.AddDays(1) && a.ExecutionTime > startDate) .OrderBy(t => t.ExecutionTime) .GroupBy(t => new @@ -136,17 +139,19 @@ namespace Volo.Abp.AuditLogging.MongoDB t.ExecutionTime.Day }) .Select(g => new { Day = g.Min(t => t.ExecutionTime), avgExecutionTime = g.Average(t => t.ExecutionDuration) }) - .ToListAsync(); + .ToListAsync(GetCancellationToken(cancellationToken)); return result.ToDictionary(element => element.Day.ClearTime(), element => element.avgExecutionTime); } - public virtual async Task GetEntityChange(Guid entityChangeId) + public virtual async Task GetEntityChange( + Guid entityChangeId, + CancellationToken cancellationToken = default) { - var entityChange = (await (await GetMongoQueryableAsync()) + var entityChange = (await (await GetMongoQueryableAsync(GetCancellationToken(cancellationToken))) .Where(x => x.EntityChanges.Any(y => y.Id == entityChangeId)) .OrderBy(x => x.Id) - .FirstAsync()).EntityChanges.FirstOrDefault(x => x.Id == entityChangeId); + .FirstAsync(GetCancellationToken(cancellationToken))).EntityChanges.FirstOrDefault(x => x.Id == entityChangeId); if (entityChange == null) { @@ -194,11 +199,13 @@ namespace Volo.Abp.AuditLogging.MongoDB return count; } - public virtual async Task GetEntityChangeWithUsernameAsync(Guid entityChangeId) + public virtual async Task GetEntityChangeWithUsernameAsync( + Guid entityChangeId, + CancellationToken cancellationToken = default) { - var auditLog = (await (await GetMongoQueryableAsync()) + var auditLog = await (await GetMongoQueryableAsync(GetCancellationToken(cancellationToken))) .Where(x => x.EntityChanges.Any(y => y.Id == entityChangeId)) - .FirstAsync()); + .FirstAsync(GetCancellationToken(cancellationToken)); return new EntityChangeWithUsername() { @@ -207,13 +214,16 @@ namespace Volo.Abp.AuditLogging.MongoDB }; } - public virtual async Task> GetEntityChangesWithUsernameAsync(string entityId, string entityTypeFullName) + public virtual async Task> GetEntityChangesWithUsernameAsync( + string entityId, + string entityTypeFullName, + CancellationToken cancellationToken = default) { - var auditLogs = await (await GetMongoQueryableAsync()) + var auditLogs = await (await GetMongoQueryableAsync(GetCancellationToken(cancellationToken))) .Where(x => x.EntityChanges.Any(y => y.EntityId == entityId && y.EntityTypeFullName == entityTypeFullName)) .As>() .OrderByDescending(x => x.ExecutionTime) - .ToListAsync(); + .ToListAsync(GetCancellationToken(cancellationToken)); var entityChanges = auditLogs.SelectMany(x => x.EntityChanges).ToList(); From 5202d8e7e0533dbd4bce3d346e7e320c21d98c40 Mon Sep 17 00:00:00 2001 From: Erol Arkat Date: Wed, 20 Jan 2021 11:51:17 +0300 Subject: [PATCH 36/51] test --- common.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common.props b/common.props index 5254c91876..11da9b9971 100644 --- a/common.props +++ b/common.props @@ -1,7 +1,7 @@ latest - 4.3.0 + 4.3.0 $(NoWarn);CS1591;CS0436 https://abp.io/assets/abp_nupkg.png https://abp.io/ From 9425585a64815def3b4064363a45fce12e89e7c1 Mon Sep 17 00:00:00 2001 From: Erol Arkat Date: Wed, 20 Jan 2021 11:53:34 +0300 Subject: [PATCH 37/51] test --- common.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common.props b/common.props index 11da9b9971..f4765e5c7d 100644 --- a/common.props +++ b/common.props @@ -1,7 +1,7 @@ latest - 4.3.0 + 4.3.0 $(NoWarn);CS1591;CS0436 https://abp.io/assets/abp_nupkg.png https://abp.io/ From 16056ef6f9dfd8a787ba61239f44dfab76b168b8 Mon Sep 17 00:00:00 2001 From: Mladen Macanovic Date: Wed, 20 Jan 2021 10:03:44 +0100 Subject: [PATCH 38/51] Move GetEntitiesAsync into OnCreatedEntityAsync and OnUpdatedEntityAsync --- .../src/Volo.Abp.BlazoriseUI/AbpCrudPageBase.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/framework/src/Volo.Abp.BlazoriseUI/AbpCrudPageBase.cs b/framework/src/Volo.Abp.BlazoriseUI/AbpCrudPageBase.cs index 84ab0099c5..65bb82feff 100644 --- a/framework/src/Volo.Abp.BlazoriseUI/AbpCrudPageBase.cs +++ b/framework/src/Volo.Abp.BlazoriseUI/AbpCrudPageBase.cs @@ -362,7 +362,6 @@ namespace Volo.Abp.BlazoriseUI await CheckCreatePolicyAsync(); var createInput = MapToCreateInput(NewEntity); await AppService.CreateAsync(createInput); - await GetEntitiesAsync(); await OnCreatedEntityAsync(); } @@ -373,10 +372,11 @@ namespace Volo.Abp.BlazoriseUI return Task.CompletedTask; } - protected virtual Task OnCreatedEntityAsync() + protected virtual async Task OnCreatedEntityAsync() { + await GetEntitiesAsync(); + CreateModal.Hide(); - return Task.CompletedTask; } protected virtual async Task UpdateEntityAsync() @@ -388,7 +388,6 @@ namespace Volo.Abp.BlazoriseUI await CheckUpdatePolicyAsync(); var updateInput = MapToUpdateInput(EditingEntity); await AppService.UpdateAsync(EditingEntityId, updateInput); - await GetEntitiesAsync(); await OnUpdatedEntityAsync(); } @@ -399,10 +398,11 @@ namespace Volo.Abp.BlazoriseUI return Task.CompletedTask; } - protected virtual Task OnUpdatedEntityAsync() + protected virtual async Task OnUpdatedEntityAsync() { + await GetEntitiesAsync(); + EditModal.Hide(); - return Task.CompletedTask; } protected virtual async Task DeleteEntityAsync(TListViewModel entity) From 2bf5b81662204d59dbab8aa8cbe1e61b41eee15d Mon Sep 17 00:00:00 2001 From: Mladen Macanovic Date: Wed, 20 Jan 2021 10:04:09 +0100 Subject: [PATCH 39/51] Optimize StateHasChanged to use InvokeAsync --- framework/src/Volo.Abp.BlazoriseUI/AbpCrudPageBase.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/framework/src/Volo.Abp.BlazoriseUI/AbpCrudPageBase.cs b/framework/src/Volo.Abp.BlazoriseUI/AbpCrudPageBase.cs index 65bb82feff..99d0f2a4c3 100644 --- a/framework/src/Volo.Abp.BlazoriseUI/AbpCrudPageBase.cs +++ b/framework/src/Volo.Abp.BlazoriseUI/AbpCrudPageBase.cs @@ -269,7 +269,7 @@ namespace Volo.Abp.BlazoriseUI await GetEntitiesAsync(); - StateHasChanged(); + await InvokeAsync(StateHasChanged); } protected virtual async Task OnDataGridReadAsync(DataGridReadDataEventArgs e) @@ -282,7 +282,7 @@ namespace Volo.Abp.BlazoriseUI await GetEntitiesAsync(); - StateHasChanged(); + await InvokeAsync(StateHasChanged); } protected virtual async Task OpenCreateModalAsync() @@ -295,7 +295,7 @@ namespace Volo.Abp.BlazoriseUI // Mapper will not notify Blazor that binded values are changed // so we need to notify it manually by calling StateHasChanged - await InvokeAsync(() => StateHasChanged()); + await InvokeAsync(StateHasChanged); CreateModal.Show(); } @@ -317,7 +317,7 @@ namespace Volo.Abp.BlazoriseUI EditingEntityId = entity.Id; EditingEntity = MapToEditingEntity(entityDto); - await InvokeAsync(() => StateHasChanged()); + await InvokeAsync(StateHasChanged); EditModal.Show(); } From d569511af7ad3c07a3d1b504230b862839d6c0cd Mon Sep 17 00:00:00 2001 From: liangshiwei Date: Wed, 20 Jan 2021 17:05:53 +0800 Subject: [PATCH 40/51] Add CancellationToken optional parameter to the BackgroundJobs module repository methods --- .../Volo/Abp/BackgroundJobs/IBackgroundJobRepository.cs | 5 +++-- .../EntityFrameworkCore/EfCoreBackgroundJobRepository.cs | 7 +++++-- .../BackgroundJobs/MongoDB/MongoBackgroundJobRepository.cs | 7 +++++-- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/modules/background-jobs/src/Volo.Abp.BackgroundJobs.Domain/Volo/Abp/BackgroundJobs/IBackgroundJobRepository.cs b/modules/background-jobs/src/Volo.Abp.BackgroundJobs.Domain/Volo/Abp/BackgroundJobs/IBackgroundJobRepository.cs index e12aeff70a..0fdf15de4b 100644 --- a/modules/background-jobs/src/Volo.Abp.BackgroundJobs.Domain/Volo/Abp/BackgroundJobs/IBackgroundJobRepository.cs +++ b/modules/background-jobs/src/Volo.Abp.BackgroundJobs.Domain/Volo/Abp/BackgroundJobs/IBackgroundJobRepository.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Threading; using System.Threading.Tasks; using Volo.Abp.Domain.Repositories; @@ -7,6 +8,6 @@ namespace Volo.Abp.BackgroundJobs { public interface IBackgroundJobRepository : IBasicRepository { - Task> GetWaitingListAsync(int maxResultCount); + Task> GetWaitingListAsync(int maxResultCount, CancellationToken cancellationToken = default); } -} \ No newline at end of file +} diff --git a/modules/background-jobs/src/Volo.Abp.BackgroundJobs.EntityFrameworkCore/Volo/Abp/BackgroundJobs/EntityFrameworkCore/EfCoreBackgroundJobRepository.cs b/modules/background-jobs/src/Volo.Abp.BackgroundJobs.EntityFrameworkCore/Volo/Abp/BackgroundJobs/EntityFrameworkCore/EfCoreBackgroundJobRepository.cs index 1971faf380..a4512a6e16 100644 --- a/modules/background-jobs/src/Volo.Abp.BackgroundJobs.EntityFrameworkCore/Volo/Abp/BackgroundJobs/EntityFrameworkCore/EfCoreBackgroundJobRepository.cs +++ b/modules/background-jobs/src/Volo.Abp.BackgroundJobs.EntityFrameworkCore/Volo/Abp/BackgroundJobs/EntityFrameworkCore/EfCoreBackgroundJobRepository.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading; using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; using Volo.Abp.Domain.Repositories.EntityFrameworkCore; @@ -21,9 +22,11 @@ namespace Volo.Abp.BackgroundJobs.EntityFrameworkCore Clock = clock; } - public virtual async Task> GetWaitingListAsync(int maxResultCount) + public virtual async Task> GetWaitingListAsync( + int maxResultCount, + CancellationToken cancellationToken) { - return await (await GetWaitingListQueryAsync(maxResultCount)).ToListAsync(); + return await (await GetWaitingListQueryAsync(maxResultCount)).ToListAsync(GetCancellationToken(cancellationToken)); } protected virtual async Task> GetWaitingListQueryAsync(int maxResultCount) diff --git a/modules/background-jobs/src/Volo.Abp.BackgroundJobs.MongoDB/Volo/Abp/BackgroundJobs/MongoDB/MongoBackgroundJobRepository.cs b/modules/background-jobs/src/Volo.Abp.BackgroundJobs.MongoDB/Volo/Abp/BackgroundJobs/MongoDB/MongoBackgroundJobRepository.cs index 258c9310e2..88d28616a1 100644 --- a/modules/background-jobs/src/Volo.Abp.BackgroundJobs.MongoDB/Volo/Abp/BackgroundJobs/MongoDB/MongoBackgroundJobRepository.cs +++ b/modules/background-jobs/src/Volo.Abp.BackgroundJobs.MongoDB/Volo/Abp/BackgroundJobs/MongoDB/MongoBackgroundJobRepository.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Threading; using System.Threading.Tasks; using MongoDB.Driver; using MongoDB.Driver.Linq; @@ -21,9 +22,11 @@ namespace Volo.Abp.BackgroundJobs.MongoDB Clock = clock; } - public virtual async Task> GetWaitingListAsync(int maxResultCount) + public virtual async Task> GetWaitingListAsync( + int maxResultCount, + CancellationToken cancellationToken) { - return await (await GetWaitingListQuery(maxResultCount)).ToListAsync(); + return await (await GetWaitingListQuery(maxResultCount)).ToListAsync(GetCancellationToken(cancellationToken)); } protected virtual async Task> GetWaitingListQuery(int maxResultCount) From 3da17ba365c4b9f582abb4b2d1b9a542b0a00f32 Mon Sep 17 00:00:00 2001 From: liangshiwei Date: Wed, 20 Jan 2021 17:32:32 +0800 Subject: [PATCH 41/51] Add CancellationToken optional parameter to the Blogging module repository methods --- .../Volo/Blogging/Blogs/IBlogRepository.cs | 3 ++- .../Blogging/Comments/ICommentRepository.cs | 11 ++++---- .../Volo/Blogging/Posts/IPostRepository.cs | 9 ++++--- .../Volo/Blogging/Tagging/ITagRepository.cs | 8 +++--- .../Blogging/Blogs/EfCoreBlogRepository.cs | 5 ++-- .../Comments/EfCoreCommentRepository.cs | 17 +++++++------ .../Blogging/Posts/EfCorePostRepository.cs | 19 +++++++------- .../Blogging/Tagging/EfCoreTagRepository.cs | 16 ++++++------ .../Blogging/Blogs/MongoBlogRepository.cs | 5 ++-- .../Comments/MongoCommentRepository.cs | 25 ++++++++++--------- .../Blogging/Posts/MongoPostRepository.cs | 23 +++++++++-------- .../Blogging/Tagging/MongoTagRepository.cs | 18 ++++++------- .../Blogging/Users/MongoBlogUserRepository.cs | 6 ++--- 13 files changed, 86 insertions(+), 79 deletions(-) diff --git a/modules/blogging/src/Volo.Blogging.Domain/Volo/Blogging/Blogs/IBlogRepository.cs b/modules/blogging/src/Volo.Blogging.Domain/Volo/Blogging/Blogs/IBlogRepository.cs index ade8d1be0e..37dbf85ddd 100644 --- a/modules/blogging/src/Volo.Blogging.Domain/Volo/Blogging/Blogs/IBlogRepository.cs +++ b/modules/blogging/src/Volo.Blogging.Domain/Volo/Blogging/Blogs/IBlogRepository.cs @@ -1,4 +1,5 @@ using System; +using System.Threading; using System.Threading.Tasks; using Volo.Abp.Domain.Repositories; @@ -6,6 +7,6 @@ namespace Volo.Blogging.Blogs { public interface IBlogRepository : IBasicRepository { - Task FindByShortNameAsync(string shortName); + Task FindByShortNameAsync(string shortName, CancellationToken cancellationToken = default); } } diff --git a/modules/blogging/src/Volo.Blogging.Domain/Volo/Blogging/Comments/ICommentRepository.cs b/modules/blogging/src/Volo.Blogging.Domain/Volo/Blogging/Comments/ICommentRepository.cs index 88ff91d516..65144cf5c2 100644 --- a/modules/blogging/src/Volo.Blogging.Domain/Volo/Blogging/Comments/ICommentRepository.cs +++ b/modules/blogging/src/Volo.Blogging.Domain/Volo/Blogging/Comments/ICommentRepository.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Threading; using System.Threading.Tasks; using Volo.Abp.Domain.Repositories; @@ -7,14 +8,12 @@ namespace Volo.Blogging.Comments { public interface ICommentRepository : IBasicRepository { - Task> GetListOfPostAsync( - Guid postId - ); + Task> GetListOfPostAsync(Guid postId, CancellationToken cancellationToken = default); - Task GetCommentCountOfPostAsync(Guid postId); + Task GetCommentCountOfPostAsync(Guid postId, CancellationToken cancellationToken = default); - Task> GetRepliesOfComment(Guid id); + Task> GetRepliesOfComment(Guid id, CancellationToken cancellationToken = default); - Task DeleteOfPost(Guid id); + Task DeleteOfPost(Guid id, CancellationToken cancellationToken = default); } } diff --git a/modules/blogging/src/Volo.Blogging.Domain/Volo/Blogging/Posts/IPostRepository.cs b/modules/blogging/src/Volo.Blogging.Domain/Volo/Blogging/Posts/IPostRepository.cs index 9a49590015..b5446515bf 100644 --- a/modules/blogging/src/Volo.Blogging.Domain/Volo/Blogging/Posts/IPostRepository.cs +++ b/modules/blogging/src/Volo.Blogging.Domain/Volo/Blogging/Posts/IPostRepository.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Threading; using System.Threading.Tasks; using Volo.Abp.Domain.Repositories; @@ -7,12 +8,12 @@ namespace Volo.Blogging.Posts { public interface IPostRepository : IBasicRepository { - Task> GetPostsByBlogId(Guid id); + Task> GetPostsByBlogId(Guid id, CancellationToken cancellationToken = default); - Task IsPostUrlInUseAsync(Guid blogId, string url, Guid? excludingPostId = null); + Task IsPostUrlInUseAsync(Guid blogId, string url, Guid? excludingPostId = null, CancellationToken cancellationToken = default); - Task GetPostByUrl(Guid blogId, string url); + Task GetPostByUrl(Guid blogId, string url, CancellationToken cancellationToken = default); - Task> GetOrderedList(Guid blogId,bool descending = false); + Task> GetOrderedList(Guid blogId,bool descending = false, CancellationToken cancellationToken = default); } } diff --git a/modules/blogging/src/Volo.Blogging.Domain/Volo/Blogging/Tagging/ITagRepository.cs b/modules/blogging/src/Volo.Blogging.Domain/Volo/Blogging/Tagging/ITagRepository.cs index 8c076c49d7..12aa0031ed 100644 --- a/modules/blogging/src/Volo.Blogging.Domain/Volo/Blogging/Tagging/ITagRepository.cs +++ b/modules/blogging/src/Volo.Blogging.Domain/Volo/Blogging/Tagging/ITagRepository.cs @@ -8,13 +8,13 @@ namespace Volo.Blogging.Tagging { public interface ITagRepository : IBasicRepository { - Task> GetListAsync(Guid blogId); + Task> GetListAsync(Guid blogId, CancellationToken cancellationToken = default); - Task GetByNameAsync(Guid blogId, string name); + Task GetByNameAsync(Guid blogId, string name, CancellationToken cancellationToken = default); - Task FindByNameAsync(Guid blogId, string name); + Task FindByNameAsync(Guid blogId, string name, CancellationToken cancellationToken = default); - Task> GetListAsync(IEnumerable ids); + Task> GetListAsync(IEnumerable ids, CancellationToken cancellationToken = default); Task DecreaseUsageCountOfTagsAsync(List id, CancellationToken cancellationToken = default); } diff --git a/modules/blogging/src/Volo.Blogging.EntityFrameworkCore/Volo/Blogging/Blogs/EfCoreBlogRepository.cs b/modules/blogging/src/Volo.Blogging.EntityFrameworkCore/Volo/Blogging/Blogs/EfCoreBlogRepository.cs index 564d45dc7a..d5397a66f0 100644 --- a/modules/blogging/src/Volo.Blogging.EntityFrameworkCore/Volo/Blogging/Blogs/EfCoreBlogRepository.cs +++ b/modules/blogging/src/Volo.Blogging.EntityFrameworkCore/Volo/Blogging/Blogs/EfCoreBlogRepository.cs @@ -1,4 +1,5 @@ using System; +using System.Threading; using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; using Volo.Abp.Domain.Repositories.EntityFrameworkCore; @@ -15,9 +16,9 @@ namespace Volo.Blogging.Blogs } - public async Task FindByShortNameAsync(string shortName) + public async Task FindByShortNameAsync(string shortName, CancellationToken cancellationToken = default) { - return await (await GetDbSetAsync()).FirstOrDefaultAsync(p => p.ShortName == shortName); + return await (await GetDbSetAsync()).FirstOrDefaultAsync(p => p.ShortName == shortName, GetCancellationToken(cancellationToken)); } } } diff --git a/modules/blogging/src/Volo.Blogging.EntityFrameworkCore/Volo/Blogging/Comments/EfCoreCommentRepository.cs b/modules/blogging/src/Volo.Blogging.EntityFrameworkCore/Volo/Blogging/Comments/EfCoreCommentRepository.cs index 6c28b521aa..801ef07825 100644 --- a/modules/blogging/src/Volo.Blogging.EntityFrameworkCore/Volo/Blogging/Comments/EfCoreCommentRepository.cs +++ b/modules/blogging/src/Volo.Blogging.EntityFrameworkCore/Volo/Blogging/Comments/EfCoreCommentRepository.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading; using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; using Volo.Abp.Domain.Repositories.EntityFrameworkCore; @@ -16,29 +17,29 @@ namespace Volo.Blogging.Comments { } - public async Task> GetListOfPostAsync(Guid postId) + public async Task> GetListOfPostAsync(Guid postId, CancellationToken cancellationToken = default) { return await (await GetDbSetAsync()) .Where(a => a.PostId == postId) .OrderBy(a => a.CreationTime) - .ToListAsync(); + .ToListAsync(GetCancellationToken(cancellationToken)); } - public async Task GetCommentCountOfPostAsync(Guid postId) + public async Task GetCommentCountOfPostAsync(Guid postId, CancellationToken cancellationToken = default) { return await (await GetDbSetAsync()) - .CountAsync(a => a.PostId == postId); + .CountAsync(a => a.PostId == postId, GetCancellationToken(cancellationToken)); } - public async Task> GetRepliesOfComment(Guid id) + public async Task> GetRepliesOfComment(Guid id, CancellationToken cancellationToken = default) { return await (await GetDbSetAsync()) - .Where(a => a.RepliedCommentId == id).ToListAsync(); + .Where(a => a.RepliedCommentId == id).ToListAsync(GetCancellationToken(cancellationToken)); } - public async Task DeleteOfPost(Guid id) + public async Task DeleteOfPost(Guid id, CancellationToken cancellationToken = default) { - var recordsToDelete = (await GetDbSetAsync()).Where(pt => pt.PostId == id); + var recordsToDelete = await (await GetDbSetAsync()).Where(pt => pt.PostId == id).ToListAsync(GetCancellationToken(cancellationToken)); (await GetDbSetAsync()).RemoveRange(recordsToDelete); } } diff --git a/modules/blogging/src/Volo.Blogging.EntityFrameworkCore/Volo/Blogging/Posts/EfCorePostRepository.cs b/modules/blogging/src/Volo.Blogging.EntityFrameworkCore/Volo/Blogging/Posts/EfCorePostRepository.cs index a5a84fd340..a676816dee 100644 --- a/modules/blogging/src/Volo.Blogging.EntityFrameworkCore/Volo/Blogging/Posts/EfCorePostRepository.cs +++ b/modules/blogging/src/Volo.Blogging.EntityFrameworkCore/Volo/Blogging/Posts/EfCorePostRepository.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading; using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; using Volo.Abp.Domain.Entities; @@ -18,12 +19,12 @@ namespace Volo.Blogging.Posts } - public async Task> GetPostsByBlogId(Guid id) + public async Task> GetPostsByBlogId(Guid id, CancellationToken cancellationToken = default) { - return await (await GetDbSetAsync()).Where(p => p.BlogId == id).OrderByDescending(p=>p.CreationTime).ToListAsync(); + return await (await GetDbSetAsync()).Where(p => p.BlogId == id).OrderByDescending(p=>p.CreationTime).ToListAsync(GetCancellationToken(cancellationToken)); } - public async Task IsPostUrlInUseAsync(Guid blogId, string url, Guid? excludingPostId = null) + public async Task IsPostUrlInUseAsync(Guid blogId, string url, Guid? excludingPostId = null, CancellationToken cancellationToken = default) { var query = (await GetDbSetAsync()).Where(p => blogId == p.BlogId && p.Url == url); @@ -32,12 +33,12 @@ namespace Volo.Blogging.Posts query = query.Where(p => excludingPostId != p.Id); } - return await query.AnyAsync(); + return await query.AnyAsync(GetCancellationToken(cancellationToken)); } - public async Task GetPostByUrl(Guid blogId, string url) + public async Task GetPostByUrl(Guid blogId, string url, CancellationToken cancellationToken = default) { - var post = await (await GetDbSetAsync()).FirstOrDefaultAsync(p => p.BlogId == blogId && p.Url == url); + var post = await (await GetDbSetAsync()).FirstOrDefaultAsync(p => p.BlogId == blogId && p.Url == url, GetCancellationToken(cancellationToken)); if (post == null) { @@ -47,15 +48,15 @@ namespace Volo.Blogging.Posts return post; } - public async Task> GetOrderedList(Guid blogId,bool descending = false) + public async Task> GetOrderedList(Guid blogId,bool descending = false, CancellationToken cancellationToken = default) { if (!descending) { - return await (await GetDbSetAsync()).Where(x=>x.BlogId==blogId).OrderByDescending(x => x.CreationTime).ToListAsync(); + return await (await GetDbSetAsync()).Where(x=>x.BlogId==blogId).OrderByDescending(x => x.CreationTime).ToListAsync(GetCancellationToken(cancellationToken)); } else { - return await (await GetDbSetAsync()).Where(x => x.BlogId == blogId).OrderBy(x => x.CreationTime).ToListAsync(); + return await (await GetDbSetAsync()).Where(x => x.BlogId == blogId).OrderBy(x => x.CreationTime).ToListAsync(GetCancellationToken(cancellationToken)); } } diff --git a/modules/blogging/src/Volo.Blogging.EntityFrameworkCore/Volo/Blogging/Tagging/EfCoreTagRepository.cs b/modules/blogging/src/Volo.Blogging.EntityFrameworkCore/Volo/Blogging/Tagging/EfCoreTagRepository.cs index a92ae780da..0a33207374 100644 --- a/modules/blogging/src/Volo.Blogging.EntityFrameworkCore/Volo/Blogging/Tagging/EfCoreTagRepository.cs +++ b/modules/blogging/src/Volo.Blogging.EntityFrameworkCore/Volo/Blogging/Tagging/EfCoreTagRepository.cs @@ -17,24 +17,24 @@ namespace Volo.Blogging.Tagging { } - public async Task> GetListAsync(Guid blogId) + public async Task> GetListAsync(Guid blogId, CancellationToken cancellationToken = default) { - return await (await GetDbSetAsync()).Where(t=>t.BlogId == blogId).ToListAsync(); + return await (await GetDbSetAsync()).Where(t=>t.BlogId == blogId).ToListAsync(GetCancellationToken(cancellationToken)); } - public async Task GetByNameAsync(Guid blogId, string name) + public async Task GetByNameAsync(Guid blogId, string name, CancellationToken cancellationToken = default) { - return await (await GetDbSetAsync()).FirstAsync(t=> t.BlogId == blogId && t.Name == name); + return await (await GetDbSetAsync()).FirstAsync(t=> t.BlogId == blogId && t.Name == name, GetCancellationToken(cancellationToken)); } - public async Task FindByNameAsync(Guid blogId, string name) + public async Task FindByNameAsync(Guid blogId, string name, CancellationToken cancellationToken = default) { - return await (await GetDbSetAsync()).FirstOrDefaultAsync(t => t.BlogId == blogId && t.Name == name); + return await (await GetDbSetAsync()).FirstOrDefaultAsync(t => t.BlogId == blogId && t.Name == name, GetCancellationToken(cancellationToken)); } - public async Task> GetListAsync(IEnumerable ids) + public async Task> GetListAsync(IEnumerable ids, CancellationToken cancellationToken = default) { - return await (await GetDbSetAsync()).Where(t => ids.Contains(t.Id)).ToListAsync(); + return await (await GetDbSetAsync()).Where(t => ids.Contains(t.Id)).ToListAsync(GetCancellationToken(cancellationToken)); } public async Task DecreaseUsageCountOfTagsAsync(List ids, CancellationToken cancellationToken = default) diff --git a/modules/blogging/src/Volo.Blogging.MongoDB/Volo/Blogging/Blogs/MongoBlogRepository.cs b/modules/blogging/src/Volo.Blogging.MongoDB/Volo/Blogging/Blogs/MongoBlogRepository.cs index 6025dfe3ca..0ef6b70a88 100644 --- a/modules/blogging/src/Volo.Blogging.MongoDB/Volo/Blogging/Blogs/MongoBlogRepository.cs +++ b/modules/blogging/src/Volo.Blogging.MongoDB/Volo/Blogging/Blogs/MongoBlogRepository.cs @@ -1,4 +1,5 @@ using System; +using System.Threading; using System.Threading.Tasks; using MongoDB.Driver.Linq; using Volo.Abp.Domain.Repositories.MongoDB; @@ -13,9 +14,9 @@ namespace Volo.Blogging.Blogs { } - public async Task FindByShortNameAsync(string shortName) + public async Task FindByShortNameAsync(string shortName, CancellationToken cancellationToken = default) { - return await (await GetMongoQueryableAsync()).FirstOrDefaultAsync(p => p.ShortName == shortName); + return await (await GetMongoQueryableAsync(GetCancellationToken(cancellationToken))).FirstOrDefaultAsync(p => p.ShortName == shortName, GetCancellationToken(cancellationToken)); } } } diff --git a/modules/blogging/src/Volo.Blogging.MongoDB/Volo/Blogging/Comments/MongoCommentRepository.cs b/modules/blogging/src/Volo.Blogging.MongoDB/Volo/Blogging/Comments/MongoCommentRepository.cs index bbbe60345b..cfeb24475a 100644 --- a/modules/blogging/src/Volo.Blogging.MongoDB/Volo/Blogging/Comments/MongoCommentRepository.cs +++ b/modules/blogging/src/Volo.Blogging.MongoDB/Volo/Blogging/Comments/MongoCommentRepository.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Threading; using System.Threading.Tasks; using MongoDB.Driver; using MongoDB.Driver.Linq; @@ -15,33 +16,33 @@ namespace Volo.Blogging.Comments { } - public async Task> GetListOfPostAsync(Guid postId) + public async Task> GetListOfPostAsync(Guid postId, CancellationToken cancellationToken = default) { - return await (await GetMongoQueryableAsync()) + return await (await GetMongoQueryableAsync(GetCancellationToken(cancellationToken))) .Where(a => a.PostId == postId) .OrderBy(a => a.CreationTime) - .ToListAsync(); + .ToListAsync(GetCancellationToken(cancellationToken)); } - public async Task GetCommentCountOfPostAsync(Guid postId) + public async Task GetCommentCountOfPostAsync(Guid postId, CancellationToken cancellationToken = default) { - return await (await GetMongoQueryableAsync()) - .CountAsync(a => a.PostId == postId); + return await (await GetMongoQueryableAsync(GetCancellationToken(cancellationToken))) + .CountAsync(a => a.PostId == postId, GetCancellationToken(cancellationToken)); } - public async Task> GetRepliesOfComment(Guid id) + public async Task> GetRepliesOfComment(Guid id, CancellationToken cancellationToken = default) { - return await (await GetMongoQueryableAsync()) - .Where(a => a.RepliedCommentId == id).ToListAsync(); + return await (await GetMongoQueryableAsync(GetCancellationToken(cancellationToken))) + .Where(a => a.RepliedCommentId == id).ToListAsync(GetCancellationToken(cancellationToken)); } - public async Task DeleteOfPost(Guid id) + public async Task DeleteOfPost(Guid id, CancellationToken cancellationToken = default) { - var recordsToDelete = (await GetMongoQueryableAsync()).Where(pt => pt.PostId == id); + var recordsToDelete = (await GetMongoQueryableAsync(GetCancellationToken(cancellationToken))).Where(pt => pt.PostId == id); foreach (var record in recordsToDelete) { - await DeleteAsync(record); + await DeleteAsync(record, cancellationToken: GetCancellationToken(cancellationToken)); } } } diff --git a/modules/blogging/src/Volo.Blogging.MongoDB/Volo/Blogging/Posts/MongoPostRepository.cs b/modules/blogging/src/Volo.Blogging.MongoDB/Volo/Blogging/Posts/MongoPostRepository.cs index 27f4ad4348..ec40e8da4a 100644 --- a/modules/blogging/src/Volo.Blogging.MongoDB/Volo/Blogging/Posts/MongoPostRepository.cs +++ b/modules/blogging/src/Volo.Blogging.MongoDB/Volo/Blogging/Posts/MongoPostRepository.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Threading; using System.Threading.Tasks; using MongoDB.Driver; using MongoDB.Driver.Linq; @@ -16,27 +17,27 @@ namespace Volo.Blogging.Posts { } - public async Task> GetPostsByBlogId(Guid id) + public async Task> GetPostsByBlogId(Guid id, CancellationToken cancellationToken = default) { - return await (await GetMongoQueryableAsync()).Where(p => p.BlogId == id).OrderByDescending(p => p.CreationTime).ToListAsync(); + return await (await GetMongoQueryableAsync(GetCancellationToken(cancellationToken))).Where(p => p.BlogId == id).OrderByDescending(p => p.CreationTime).ToListAsync(GetCancellationToken(cancellationToken)); } - public async Task IsPostUrlInUseAsync(Guid blogId, string url, Guid? excludingPostId = null) + public async Task IsPostUrlInUseAsync(Guid blogId, string url, Guid? excludingPostId = null, CancellationToken cancellationToken = default) { - var query = (await GetMongoQueryableAsync()).Where(p => blogId == p.BlogId && p.Url == url); + var query = (await GetMongoQueryableAsync(GetCancellationToken(cancellationToken))).Where(p => blogId == p.BlogId && p.Url == url); if (excludingPostId != null) { query = query.Where(p => excludingPostId != p.Id); } - return await query.AnyAsync(); + return await query.AnyAsync(GetCancellationToken(cancellationToken)); } - public async Task GetPostByUrl(Guid blogId, string url) + public async Task GetPostByUrl(Guid blogId, string url, CancellationToken cancellationToken = default) { - var post = await (await GetMongoQueryableAsync()).FirstOrDefaultAsync(p => p.BlogId == blogId && p.Url == url); + var post = await (await GetMongoQueryableAsync(GetCancellationToken(cancellationToken))).FirstOrDefaultAsync(p => p.BlogId == blogId && p.Url == url, GetCancellationToken(cancellationToken)); if (post == null) { @@ -46,16 +47,16 @@ namespace Volo.Blogging.Posts return post; } - public async Task> GetOrderedList(Guid blogId, bool @descending = false) + public async Task> GetOrderedList(Guid blogId, bool @descending = false, CancellationToken cancellationToken = default) { - var query = (await GetMongoQueryableAsync()).Where(x => x.BlogId == blogId); + var query = (await GetMongoQueryableAsync(GetCancellationToken(cancellationToken))).Where(x => x.BlogId == blogId); if (!descending) { - return await query.OrderBy(x => x.CreationTime).ToListAsync(); + return await query.OrderBy(x => x.CreationTime).ToListAsync(GetCancellationToken(cancellationToken)); } - return await query.OrderByDescending(x => x.CreationTime).ToListAsync(); + return await query.OrderByDescending(x => x.CreationTime).ToListAsync(GetCancellationToken(cancellationToken)); } } } diff --git a/modules/blogging/src/Volo.Blogging.MongoDB/Volo/Blogging/Tagging/MongoTagRepository.cs b/modules/blogging/src/Volo.Blogging.MongoDB/Volo/Blogging/Tagging/MongoTagRepository.cs index f0024b37d7..73999628ce 100644 --- a/modules/blogging/src/Volo.Blogging.MongoDB/Volo/Blogging/Tagging/MongoTagRepository.cs +++ b/modules/blogging/src/Volo.Blogging.MongoDB/Volo/Blogging/Tagging/MongoTagRepository.cs @@ -18,29 +18,29 @@ namespace Volo.Blogging.Tagging { } - public async Task> GetListAsync(Guid blogId) + public async Task> GetListAsync(Guid blogId, CancellationToken cancellationToken = default) { - return await (await GetMongoQueryableAsync()).Where(t => t.BlogId == blogId).ToListAsync(); + return await (await GetMongoQueryableAsync(GetCancellationToken(cancellationToken))).Where(t => t.BlogId == blogId).ToListAsync(GetCancellationToken(cancellationToken)); } - public async Task GetByNameAsync(Guid blogId, string name) + public async Task GetByNameAsync(Guid blogId, string name, CancellationToken cancellationToken = default) { - return await (await GetMongoQueryableAsync()).Where(t => t.BlogId == blogId && t.Name == name).FirstAsync(); + return await (await GetMongoQueryableAsync(GetCancellationToken(cancellationToken))).Where(t => t.BlogId == blogId && t.Name == name).FirstAsync(GetCancellationToken(cancellationToken)); } - public async Task FindByNameAsync(Guid blogId, string name) + public async Task FindByNameAsync(Guid blogId, string name, CancellationToken cancellationToken = default) { - return await (await GetMongoQueryableAsync()).Where(t => t.BlogId == blogId && t.Name == name).FirstOrDefaultAsync(); + return await (await GetMongoQueryableAsync(GetCancellationToken(cancellationToken))).Where(t => t.BlogId == blogId && t.Name == name).FirstOrDefaultAsync(GetCancellationToken(cancellationToken)); } - public async Task> GetListAsync(IEnumerable ids) + public async Task> GetListAsync(IEnumerable ids, CancellationToken cancellationToken = default) { - return await (await GetMongoQueryableAsync()).Where(t => ids.Contains(t.Id)).ToListAsync(); + return await (await GetMongoQueryableAsync(GetCancellationToken(cancellationToken))).Where(t => ids.Contains(t.Id)).ToListAsync(GetCancellationToken(cancellationToken)); } public async Task DecreaseUsageCountOfTagsAsync(List ids, CancellationToken cancellationToken = default) { - var tags = await (await GetMongoQueryableAsync()) + var tags = await (await GetMongoQueryableAsync(GetCancellationToken(cancellationToken))) .Where(t => ids.Contains(t.Id)) .ToListAsync(GetCancellationToken(cancellationToken)); diff --git a/modules/blogging/src/Volo.Blogging.MongoDB/Volo/Blogging/Users/MongoBlogUserRepository.cs b/modules/blogging/src/Volo.Blogging.MongoDB/Volo/Blogging/Users/MongoBlogUserRepository.cs index fdcf4d0097..c558799a86 100644 --- a/modules/blogging/src/Volo.Blogging.MongoDB/Volo/Blogging/Users/MongoBlogUserRepository.cs +++ b/modules/blogging/src/Volo.Blogging.MongoDB/Volo/Blogging/Users/MongoBlogUserRepository.cs @@ -15,16 +15,16 @@ namespace Volo.Blogging.Users { } - public async Task> GetUsersAsync(int maxCount, string filter, CancellationToken cancellationToken) + public async Task> GetUsersAsync(int maxCount, string filter, CancellationToken cancellationToken = default) { - var query = await GetMongoQueryableAsync(cancellationToken); + var query = await GetMongoQueryableAsync(GetCancellationToken(cancellationToken)); if (!string.IsNullOrWhiteSpace(filter)) { query = query.Where(x => x.UserName.Contains(filter)); } - return await query.Take(maxCount).ToListAsync(cancellationToken); + return await query.Take(maxCount).ToListAsync(GetCancellationToken(cancellationToken)); } } } From dd56dd405d2292fded4b39f5459f97042e58f185 Mon Sep 17 00:00:00 2001 From: Ahmet Date: Wed, 20 Jan 2021 12:42:36 +0300 Subject: [PATCH 42/51] Page content converted to nullable --- .../Contents/ContentViewComponent.cs | 32 +++++++++++-------- .../Shared/Components/Contents/Default.cshtml | 2 +- .../Shared/Components/Pages/Default.cshtml | 12 +++---- 3 files changed, 25 insertions(+), 21 deletions(-) diff --git a/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Contents/ContentViewComponent.cs b/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Contents/ContentViewComponent.cs index 129bff61cf..6a2a4e5a13 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Contents/ContentViewComponent.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Contents/ContentViewComponent.cs @@ -2,6 +2,7 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using Volo.Abp.AspNetCore.Mvc; +using Volo.Abp.Domain.Entities; using Volo.CmsKit.Public.Contents; using Volo.CmsKit.Web.Contents; @@ -25,18 +26,26 @@ namespace Volo.CmsKit.Public.Web.Pages.CmsKit.Shared.Components.Contents string entityType, string entityId) { - var content = await contentAppService.GetAsync(new GetContentInput + var content = string.Empty; + + try { - EntityId = entityId, - EntityType = entityType - }); + var contentDto = await contentAppService.GetAsync(new GetContentInput + { + EntityId = entityId, + EntityType = entityType + }); + + content = contentDto.Value; + } + catch (EntityNotFoundException e) + { + // ContentDto can be null, we will render empty content. + } var viewModel = new ContentViewModel { - EntityId = entityId, - EntityType = entityType, - ContentId = content.Id, - Rendered = await contentRenderer.RenderAsync(content.Value) + Value = await contentRenderer.RenderAsync(content) }; return View("~/Pages/CmsKit/Shared/Components/Contents/Default.cshtml", viewModel); @@ -44,12 +53,7 @@ namespace Volo.CmsKit.Public.Web.Pages.CmsKit.Shared.Components.Contents public class ContentViewModel { - public Guid ContentId { get; set; } - public string EntityType { get; set; } - - public string EntityId { get; set; } - - public string Rendered { get; set; } + public string Value { get; set; } } } } diff --git a/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Contents/Default.cshtml b/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Contents/Default.cshtml index 29f7bfa6a3..0f1ddedfe5 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Contents/Default.cshtml +++ b/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Contents/Default.cshtml @@ -1,3 +1,3 @@ @model Volo.CmsKit.Public.Web.Pages.CmsKit.Shared.Components.Contents.ContentViewComponent.ContentViewModel -@Html.Raw(Model.Rendered) +@Html.Raw(Model.Value) diff --git a/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Pages/Default.cshtml b/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Pages/Default.cshtml index 28af196e39..e10fa0754a 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Pages/Default.cshtml +++ b/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Pages/Default.cshtml @@ -1,16 +1,16 @@ @addTagHelper *, Volo.Abp.AspNetCore.Mvc.UI.Bootstrap @using Microsoft.AspNetCore.Mvc.RazorPages +@using Volo.Abp.AspNetCore.Mvc.UI.Layout @using Volo.CmsKit.Public.Web.Pages.CmsKit.Shared.Components.Contents @model Volo.CmsKit.Public.Web.Pages.CmsKit.Shared.Components.Pages.PageViewModel +@inject IPageLayout PageLayout + +@{ + PageLayout.Content.Title = Model.Title; +} - -

- @Model.Title -

-
- @await Component.InvokeAsync(typeof(ContentViewComponent), new From 746dc8a1e03bc314d5c7f9e0380329e404db4446 Mon Sep 17 00:00:00 2001 From: liangshiwei Date: Wed, 20 Jan 2021 17:45:12 +0800 Subject: [PATCH 43/51] Add CancellationToken optional parameter to the Docs module repository methods --- .../Volo/Docs/Projects/IProjectRepository.cs | 9 +++++---- .../Volo/Docs/Projects/EfCoreProjectRepository.cs | 13 +++++++------ .../Volo/Docs/Projects/MongoProjectRepository.cs | 15 ++++++++------- 3 files changed, 20 insertions(+), 17 deletions(-) diff --git a/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Projects/IProjectRepository.cs b/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Projects/IProjectRepository.cs index add77749b4..4893ab4114 100644 --- a/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Projects/IProjectRepository.cs +++ b/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Projects/IProjectRepository.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Threading; using System.Threading.Tasks; using Volo.Abp.Domain.Repositories; @@ -7,10 +8,10 @@ namespace Volo.Docs.Projects { public interface IProjectRepository : IBasicRepository { - Task> GetListAsync(string sorting, int maxResultCount, int skipCount); + Task> GetListAsync(string sorting, int maxResultCount, int skipCount, CancellationToken cancellationToken = default); - Task GetByShortNameAsync(string shortName); + Task GetByShortNameAsync(string shortName, CancellationToken cancellationToken = default); - Task ShortNameExistsAsync(string shortName); + Task ShortNameExistsAsync(string shortName, CancellationToken cancellationToken = default); } -} \ No newline at end of file +} diff --git a/modules/docs/src/Volo.Docs.EntityFrameworkCore/Volo/Docs/Projects/EfCoreProjectRepository.cs b/modules/docs/src/Volo.Docs.EntityFrameworkCore/Volo/Docs/Projects/EfCoreProjectRepository.cs index bf92a8a795..294241c992 100644 --- a/modules/docs/src/Volo.Docs.EntityFrameworkCore/Volo/Docs/Projects/EfCoreProjectRepository.cs +++ b/modules/docs/src/Volo.Docs.EntityFrameworkCore/Volo/Docs/Projects/EfCoreProjectRepository.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Linq.Dynamic.Core; +using System.Threading; using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; using Volo.Abp.Domain.Entities; @@ -18,20 +19,20 @@ namespace Volo.Docs.Projects { } - public async Task> GetListAsync(string sorting, int maxResultCount, int skipCount) + public async Task> GetListAsync(string sorting, int maxResultCount, int skipCount, CancellationToken cancellationToken = default) { var projects = await (await GetDbSetAsync()).OrderBy(sorting ?? "Id desc") .PageBy(skipCount, maxResultCount) - .ToListAsync(); + .ToListAsync(GetCancellationToken(cancellationToken)); return projects; } - public async Task GetByShortNameAsync(string shortName) + public async Task GetByShortNameAsync(string shortName, CancellationToken cancellationToken = default) { var normalizeShortName = NormalizeShortName(shortName); - var project = await (await GetDbSetAsync()).FirstOrDefaultAsync(p => p.ShortName == normalizeShortName); + var project = await (await GetDbSetAsync()).FirstOrDefaultAsync(p => p.ShortName == normalizeShortName, GetCancellationToken(cancellationToken)); if (project == null) { @@ -41,11 +42,11 @@ namespace Volo.Docs.Projects return project; } - public async Task ShortNameExistsAsync(string shortName) + public async Task ShortNameExistsAsync(string shortName, CancellationToken cancellationToken = default) { var normalizeShortName = NormalizeShortName(shortName); - return await (await GetDbSetAsync()).AnyAsync(x => x.ShortName == normalizeShortName); + return await (await GetDbSetAsync()).AnyAsync(x => x.ShortName == normalizeShortName, GetCancellationToken(cancellationToken)); } private string NormalizeShortName(string shortName) diff --git a/modules/docs/src/Volo.Docs.MongoDB/Volo/Docs/Projects/MongoProjectRepository.cs b/modules/docs/src/Volo.Docs.MongoDB/Volo/Docs/Projects/MongoProjectRepository.cs index 91f1af9b2a..1e9fe1be4f 100644 --- a/modules/docs/src/Volo.Docs.MongoDB/Volo/Docs/Projects/MongoProjectRepository.cs +++ b/modules/docs/src/Volo.Docs.MongoDB/Volo/Docs/Projects/MongoProjectRepository.cs @@ -9,6 +9,7 @@ using Volo.Abp.MongoDB; using Volo.Docs.MongoDB; using System.Linq; using System.Linq.Dynamic.Core; +using System.Threading; namespace Volo.Docs.Projects { @@ -19,20 +20,20 @@ namespace Volo.Docs.Projects { } - public async Task> GetListAsync(string sorting, int maxResultCount, int skipCount) + public async Task> GetListAsync(string sorting, int maxResultCount, int skipCount, CancellationToken cancellationToken = default) { - var projects = await (await GetMongoQueryableAsync()).OrderBy(sorting ?? "Id desc").As>() + var projects = await (await GetMongoQueryableAsync(GetCancellationToken(cancellationToken))).OrderBy(sorting ?? "Id desc").As>() .PageBy>(skipCount, maxResultCount) - .ToListAsync(); + .ToListAsync(GetCancellationToken(cancellationToken)); return projects; } - public async Task GetByShortNameAsync(string shortName) + public async Task GetByShortNameAsync(string shortName, CancellationToken cancellationToken = default) { var normalizeShortName = NormalizeShortName(shortName); - var project = await (await GetMongoQueryableAsync()).FirstOrDefaultAsync(p => p.ShortName == normalizeShortName); + var project = await (await GetMongoQueryableAsync(GetCancellationToken(cancellationToken))).FirstOrDefaultAsync(p => p.ShortName == normalizeShortName, GetCancellationToken(cancellationToken)); if (project == null) { @@ -42,11 +43,11 @@ namespace Volo.Docs.Projects return project; } - public async Task ShortNameExistsAsync(string shortName) + public async Task ShortNameExistsAsync(string shortName, CancellationToken cancellationToken = default) { var normalizeShortName = NormalizeShortName(shortName); - return await (await GetMongoQueryableAsync()).AnyAsync(x => x.ShortName == normalizeShortName); + return await (await GetMongoQueryableAsync(GetCancellationToken(cancellationToken))).AnyAsync(x => x.ShortName == normalizeShortName, GetCancellationToken(cancellationToken)); } private string NormalizeShortName(string shortName) From 590176f221b55ef42600dc6aff3d556643fc0f3f Mon Sep 17 00:00:00 2001 From: Ahmet Date: Wed, 20 Jan 2021 12:47:43 +0300 Subject: [PATCH 44/51] Added interface to page public controller --- .../Volo/CmsKit/Public/Pages/PagesPublicController.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/cms-kit/src/Volo.CmsKit.Public.HttpApi/Volo/CmsKit/Public/Pages/PagesPublicController.cs b/modules/cms-kit/src/Volo.CmsKit.Public.HttpApi/Volo/CmsKit/Public/Pages/PagesPublicController.cs index 4d1c5391c7..a9c6cd70e4 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Public.HttpApi/Volo/CmsKit/Public/Pages/PagesPublicController.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Public.HttpApi/Volo/CmsKit/Public/Pages/PagesPublicController.cs @@ -7,7 +7,7 @@ namespace Volo.CmsKit.Public.Pages [RemoteService(Name = CmsKitPublicRemoteServiceConsts.RemoteServiceName)] [Area("cms-kit")] [Route("api/cms-kit-public/comments")] - public class PagesPublicController + public class PagesPublicController : IPageAppService { protected readonly IPageAppService PageAppService; @@ -18,7 +18,7 @@ namespace Volo.CmsKit.Public.Pages [HttpGet] [Route("url/{url}")] - public Task GetByUrlAsync(string url) + public Task FindByUrlAsync(string url) { return PageAppService.FindByUrlAsync(url); } From 630c7610cfbe8f1086cfb6e6c6bc71fb0dd1dc5d Mon Sep 17 00:00:00 2001 From: liangshiwei Date: Wed, 20 Jan 2021 17:50:55 +0800 Subject: [PATCH 45/51] Add CancellationToken optional parameter to the feature-management module repository methods --- .../IFeatureValueRepository.cs | 18 +++++++++-- .../EfCoreFeatureValueRepository.cs | 26 +++++++++++----- .../MongoDB/MongoFeatureValueRepository.cs | 30 +++++++++++++------ 3 files changed, 54 insertions(+), 20 deletions(-) diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/IFeatureValueRepository.cs b/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/IFeatureValueRepository.cs index f5b5c85da4..800424e901 100644 --- a/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/IFeatureValueRepository.cs +++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/IFeatureValueRepository.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Threading; using System.Threading.Tasks; using Volo.Abp.Domain.Repositories; @@ -7,10 +8,21 @@ namespace Volo.Abp.FeatureManagement { public interface IFeatureValueRepository : IBasicRepository { - Task FindAsync(string name, string providerName, string providerKey); + Task FindAsync( + string name, + string providerName, + string providerKey, + CancellationToken cancellationToken = default); - Task> FindAllAsync(string name, string providerName, string providerKey); + Task> FindAllAsync( + string name, + string providerName, + string providerKey, + CancellationToken cancellationToken = default); - Task> GetListAsync(string providerName, string providerKey); + Task> GetListAsync( + string providerName, + string providerKey, + CancellationToken cancellationToken = default); } } diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.EntityFrameworkCore/Volo/Abp/FeatureManagement/EntityFrameworkCore/EfCoreFeatureValueRepository.cs b/modules/feature-management/src/Volo.Abp.FeatureManagement.EntityFrameworkCore/Volo/Abp/FeatureManagement/EntityFrameworkCore/EfCoreFeatureValueRepository.cs index be75e600d8..8256b08c2d 100644 --- a/modules/feature-management/src/Volo.Abp.FeatureManagement.EntityFrameworkCore/Volo/Abp/FeatureManagement/EntityFrameworkCore/EfCoreFeatureValueRepository.cs +++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.EntityFrameworkCore/Volo/Abp/FeatureManagement/EntityFrameworkCore/EfCoreFeatureValueRepository.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading; using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; using Volo.Abp.Domain.Repositories.EntityFrameworkCore; @@ -15,29 +16,38 @@ namespace Volo.Abp.FeatureManagement.EntityFrameworkCore { } - public virtual async Task FindAsync(string name, string providerName, string providerKey) + public virtual async Task FindAsync( + string name, + string providerName, + string providerKey, + CancellationToken cancellationToken = default) { return await (await GetDbSetAsync()) .OrderBy(x => x.Id) - .FirstOrDefaultAsync( - s => s.Name == name && s.ProviderName == providerName && s.ProviderKey == providerKey - ); + .FirstOrDefaultAsync(s => s.Name == name && s.ProviderName == providerName && s.ProviderKey == providerKey, GetCancellationToken(cancellationToken)); } - public async Task> FindAllAsync(string name, string providerName, string providerKey) + public async Task> FindAllAsync( + string name, + string providerName, + string providerKey, + CancellationToken cancellationToken = default) { return await (await GetDbSetAsync()) .Where( s => s.Name == name && s.ProviderName == providerName && s.ProviderKey == providerKey - ).ToListAsync(); + ).ToListAsync(GetCancellationToken(cancellationToken)); } - public virtual async Task> GetListAsync(string providerName, string providerKey) + public virtual async Task> GetListAsync( + string providerName, + string providerKey, + CancellationToken cancellationToken = default) { return await (await GetDbSetAsync()) .Where( s => s.ProviderName == providerName && s.ProviderKey == providerKey - ).ToListAsync(); + ).ToListAsync(GetCancellationToken(cancellationToken)); } } } diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.MongoDB/Volo/Abp/FeatureManagement/MongoDB/MongoFeatureValueRepository.cs b/modules/feature-management/src/Volo.Abp.FeatureManagement.MongoDB/Volo/Abp/FeatureManagement/MongoDB/MongoFeatureValueRepository.cs index 21d4784f6e..5547997251 100644 --- a/modules/feature-management/src/Volo.Abp.FeatureManagement.MongoDB/Volo/Abp/FeatureManagement/MongoDB/MongoFeatureValueRepository.cs +++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.MongoDB/Volo/Abp/FeatureManagement/MongoDB/MongoFeatureValueRepository.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Threading; using System.Threading.Tasks; using MongoDB.Driver; using MongoDB.Driver.Linq; @@ -16,24 +17,35 @@ namespace Volo.Abp.FeatureManagement.MongoDB } - public virtual async Task FindAsync(string name, string providerName, string providerKey) + public virtual async Task FindAsync( + string name, + string providerName, + string providerKey, + CancellationToken cancellationToken = default) { - return await (await GetMongoQueryableAsync()) + return await (await GetMongoQueryableAsync(GetCancellationToken(cancellationToken))) .OrderBy(x => x.Id) - .FirstOrDefaultAsync(s => s.Name == name && s.ProviderName == providerName && s.ProviderKey == providerKey); + .FirstOrDefaultAsync(s => s.Name == name && s.ProviderName == providerName && s.ProviderKey == providerKey, GetCancellationToken(cancellationToken)); } - public async Task> FindAllAsync(string name, string providerName, string providerKey) + public async Task> FindAllAsync( + string name, + string providerName, + string providerKey, + CancellationToken cancellationToken = default) { - return await (await GetMongoQueryableAsync()) - .Where(s => s.Name == name && s.ProviderName == providerName && s.ProviderKey == providerKey).ToListAsync(); + return await (await GetMongoQueryableAsync(GetCancellationToken(cancellationToken))) + .Where(s => s.Name == name && s.ProviderName == providerName && s.ProviderKey == providerKey).ToListAsync(GetCancellationToken(cancellationToken)); } - public virtual async Task> GetListAsync(string providerName, string providerKey) + public virtual async Task> GetListAsync( + string providerName, + string providerKey, + CancellationToken cancellationToken = default) { - return await (await GetMongoQueryableAsync()) + return await (await GetMongoQueryableAsync(GetCancellationToken(cancellationToken))) .Where(s => s.ProviderName == providerName && s.ProviderKey == providerKey) - .ToListAsync(); + .ToListAsync(GetCancellationToken(cancellationToken)); } } } From 07c4d157e8af4e79fe2c2a18536eff8c0a5ad9b9 Mon Sep 17 00:00:00 2001 From: liangshiwei Date: Wed, 20 Jan 2021 18:02:44 +0800 Subject: [PATCH 46/51] Add CancellationToken optional parameter to the setting-management module repository methods --- .../SettingManagement/ISettingRepository.cs | 18 ++++++++-- .../EfCoreSettingRepository.cs | 29 ++++++++++----- .../MongoDB/MongoSettingRepository.cs | 36 +++++++++++++------ 3 files changed, 61 insertions(+), 22 deletions(-) diff --git a/modules/setting-management/src/Volo.Abp.SettingManagement.Domain/Volo/Abp/SettingManagement/ISettingRepository.cs b/modules/setting-management/src/Volo.Abp.SettingManagement.Domain/Volo/Abp/SettingManagement/ISettingRepository.cs index a1e40f3a14..ee8cfa64a8 100644 --- a/modules/setting-management/src/Volo.Abp.SettingManagement.Domain/Volo/Abp/SettingManagement/ISettingRepository.cs +++ b/modules/setting-management/src/Volo.Abp.SettingManagement.Domain/Volo/Abp/SettingManagement/ISettingRepository.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Threading; using System.Threading.Tasks; using Volo.Abp.Domain.Repositories; @@ -7,10 +8,21 @@ namespace Volo.Abp.SettingManagement { public interface ISettingRepository : IBasicRepository { - Task FindAsync(string name, string providerName, string providerKey); + Task FindAsync( + string name, + string providerName, + string providerKey, + CancellationToken cancellationToken = default); - Task> GetListAsync(string providerName, string providerKey); + Task> GetListAsync( + string providerName, + string providerKey, + CancellationToken cancellationToken = default); - Task> GetListAsync(string[] names, string providerName, string providerKey); + Task> GetListAsync( + string[] names, + string providerName, + string providerKey, + CancellationToken cancellationToken = default); } } diff --git a/modules/setting-management/src/Volo.Abp.SettingManagement.EntityFrameworkCore/Volo/Abp/SettingManagement/EntityFrameworkCore/EfCoreSettingRepository.cs b/modules/setting-management/src/Volo.Abp.SettingManagement.EntityFrameworkCore/Volo/Abp/SettingManagement/EntityFrameworkCore/EfCoreSettingRepository.cs index e90d2fc63a..4bab46315a 100644 --- a/modules/setting-management/src/Volo.Abp.SettingManagement.EntityFrameworkCore/Volo/Abp/SettingManagement/EntityFrameworkCore/EfCoreSettingRepository.cs +++ b/modules/setting-management/src/Volo.Abp.SettingManagement.EntityFrameworkCore/Volo/Abp/SettingManagement/EntityFrameworkCore/EfCoreSettingRepository.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading; using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; using Volo.Abp.Domain.Repositories.EntityFrameworkCore; @@ -8,36 +9,48 @@ using Volo.Abp.EntityFrameworkCore; namespace Volo.Abp.SettingManagement.EntityFrameworkCore { - public class EfCoreSettingRepository : EfCoreRepository, ISettingRepository + public class EfCoreSettingRepository : EfCoreRepository, + ISettingRepository { public EfCoreSettingRepository(IDbContextProvider dbContextProvider) : base(dbContextProvider) { } - public virtual async Task FindAsync(string name, string providerName, string providerKey) + public virtual async Task FindAsync( + string name, + string providerName, + string providerKey, + CancellationToken cancellationToken = default) { return await (await GetDbSetAsync()) .OrderBy(x => x.Id) .FirstOrDefaultAsync( - s => s.Name == name && s.ProviderName == providerName && s.ProviderKey == providerKey - ); + s => s.Name == name && s.ProviderName == providerName && s.ProviderKey == providerKey, + GetCancellationToken(cancellationToken)); } - public virtual async Task> GetListAsync(string providerName, string providerKey) + public virtual async Task> GetListAsync( + string providerName, + string providerKey, + CancellationToken cancellationToken = default) { return await (await GetDbSetAsync()) .Where( s => s.ProviderName == providerName && s.ProviderKey == providerKey - ).ToListAsync(); + ).ToListAsync(GetCancellationToken(cancellationToken)); } - public virtual async Task> GetListAsync(string[] names, string providerName, string providerKey) + public virtual async Task> GetListAsync( + string[] names, + string providerName, + string providerKey, + CancellationToken cancellationToken = default) { return await (await GetDbSetAsync()) .Where( s => names.Contains(s.Name) && s.ProviderName == providerName && s.ProviderKey == providerKey - ).ToListAsync(); + ).ToListAsync(GetCancellationToken(cancellationToken)); } } } diff --git a/modules/setting-management/src/Volo.Abp.SettingManagement.MongoDB/Volo/Abp/SettingManagement/MongoDB/MongoSettingRepository.cs b/modules/setting-management/src/Volo.Abp.SettingManagement.MongoDB/Volo/Abp/SettingManagement/MongoDB/MongoSettingRepository.cs index e81e76dfcc..fc01f79132 100644 --- a/modules/setting-management/src/Volo.Abp.SettingManagement.MongoDB/Volo/Abp/SettingManagement/MongoDB/MongoSettingRepository.cs +++ b/modules/setting-management/src/Volo.Abp.SettingManagement.MongoDB/Volo/Abp/SettingManagement/MongoDB/MongoSettingRepository.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading; using System.Threading.Tasks; using MongoDB.Driver; using MongoDB.Driver.Linq; @@ -9,33 +10,46 @@ using Volo.Abp.MongoDB; namespace Volo.Abp.SettingManagement.MongoDB { - public class MongoSettingRepository : MongoDbRepository, ISettingRepository + public class MongoSettingRepository : MongoDbRepository, + ISettingRepository { public MongoSettingRepository(IMongoDbContextProvider dbContextProvider) : base(dbContextProvider) { - } - public virtual async Task FindAsync(string name, string providerName, string providerKey) + public virtual async Task FindAsync( + string name, + string providerName, + string providerKey, + CancellationToken cancellationToken = default) { - return await (await GetMongoQueryableAsync()) + return await (await GetMongoQueryableAsync(GetCancellationToken(cancellationToken))) .OrderBy(x => x.Id) - .FirstOrDefaultAsync(s => s.Name == name && s.ProviderName == providerName && s.ProviderKey == providerKey); + .FirstOrDefaultAsync( + s => s.Name == name && s.ProviderName == providerName && s.ProviderKey == providerKey, + GetCancellationToken(cancellationToken)); } - public virtual async Task> GetListAsync(string providerName, string providerKey) + public virtual async Task> GetListAsync( + string providerName, + string providerKey, + CancellationToken cancellationToken = default) { - return await (await GetMongoQueryableAsync()) + return await (await GetMongoQueryableAsync(GetCancellationToken(cancellationToken))) .Where(s => s.ProviderName == providerName && s.ProviderKey == providerKey) - .ToListAsync(); + .ToListAsync(GetCancellationToken(cancellationToken)); } - public virtual async Task> GetListAsync(string[] names, string providerName, string providerKey) + public virtual async Task> GetListAsync( + string[] names, + string providerName, + string providerKey, + CancellationToken cancellationToken = default) { - return await (await GetMongoQueryableAsync()) + return await (await GetMongoQueryableAsync(GetCancellationToken(cancellationToken))) .Where(s => names.Contains(s.Name) && s.ProviderName == providerName && s.ProviderKey == providerKey) - .ToListAsync(); + .ToListAsync(GetCancellationToken(cancellationToken)); } } } From 3ce47a2525d44ee96b2cbad81ba5f978a198488d Mon Sep 17 00:00:00 2001 From: liangshiwei Date: Wed, 20 Jan 2021 18:29:32 +0800 Subject: [PATCH 47/51] Improve --- .../MongoDB/MongoAuditLogRepository.cs | 28 +++++++++++-------- .../EfCoreBackgroundJobRepository.cs | 2 +- .../Blogging/Blogs/MongoBlogRepository.cs | 2 +- .../Comments/MongoCommentRepository.cs | 8 +++--- .../Blogging/Posts/MongoPostRepository.cs | 8 +++--- .../Blogging/Tagging/MongoTagRepository.cs | 10 +++---- .../Blogging/Users/MongoBlogUserRepository.cs | 2 +- .../MongoDB/Pages/MongoPageRepository.cs | 2 +- .../Docs/Projects/MongoProjectRepository.cs | 6 ++-- .../MongoDB/MongoFeatureValueRepository.cs | 6 ++-- .../MongoDB/MongoSettingRepository.cs | 6 ++-- 11 files changed, 42 insertions(+), 38 deletions(-) diff --git a/modules/audit-logging/src/Volo.Abp.AuditLogging.MongoDB/Volo/Abp/AuditLogging/MongoDB/MongoAuditLogRepository.cs b/modules/audit-logging/src/Volo.Abp.AuditLogging.MongoDB/Volo/Abp/AuditLogging/MongoDB/MongoAuditLogRepository.cs index ce3c0e18c8..e0ce5990b7 100644 --- a/modules/audit-logging/src/Volo.Abp.AuditLogging.MongoDB/Volo/Abp/AuditLogging/MongoDB/MongoAuditLogRepository.cs +++ b/modules/audit-logging/src/Volo.Abp.AuditLogging.MongoDB/Volo/Abp/AuditLogging/MongoDB/MongoAuditLogRepository.cs @@ -52,7 +52,8 @@ namespace Volo.Abp.AuditLogging.MongoDB minDuration, hasException, httpStatusCode, - includeDetails + includeDetails, + cancellationToken ); return await query.OrderBy(sorting ?? "executionTime desc").As>() @@ -85,7 +86,8 @@ namespace Volo.Abp.AuditLogging.MongoDB maxDuration, minDuration, hasException, - httpStatusCode + httpStatusCode, + cancellationToken: cancellationToken ); var count = await query.As>() @@ -106,9 +108,10 @@ namespace Volo.Abp.AuditLogging.MongoDB int? minDuration = null, bool? hasException = null, HttpStatusCode? httpStatusCode = null, - bool includeDetails = false) + bool includeDetails = false, + CancellationToken cancellationToken = default) { - return (await GetMongoQueryableAsync()) + return (await GetMongoQueryableAsync(cancellationToken)) .WhereIf(startTime.HasValue, auditLog => auditLog.ExecutionTime >= startTime) .WhereIf(endTime.HasValue, auditLog => auditLog.ExecutionTime <= endTime) .WhereIf(hasException.HasValue && hasException.Value, auditLog => auditLog.Exceptions != null && auditLog.Exceptions != "") @@ -129,7 +132,7 @@ namespace Volo.Abp.AuditLogging.MongoDB DateTime endDate, CancellationToken cancellationToken = default) { - var result = await (await GetMongoQueryableAsync(GetCancellationToken(cancellationToken))) + var result = await (await GetMongoQueryableAsync(cancellationToken)) .Where(a => a.ExecutionTime < endDate.AddDays(1) && a.ExecutionTime > startDate) .OrderBy(t => t.ExecutionTime) .GroupBy(t => new @@ -148,7 +151,7 @@ namespace Volo.Abp.AuditLogging.MongoDB Guid entityChangeId, CancellationToken cancellationToken = default) { - var entityChange = (await (await GetMongoQueryableAsync(GetCancellationToken(cancellationToken))) + var entityChange = (await (await GetMongoQueryableAsync(cancellationToken)) .Where(x => x.EntityChanges.Any(y => y.Id == entityChangeId)) .OrderBy(x => x.Id) .FirstAsync(GetCancellationToken(cancellationToken))).EntityChanges.FirstOrDefault(x => x.Id == entityChangeId); @@ -174,7 +177,7 @@ namespace Volo.Abp.AuditLogging.MongoDB bool includeDetails = false, CancellationToken cancellationToken = default) { - var query = await GetEntityChangeListQueryAsync(auditLogId, startTime, endTime, changeType, entityId, entityTypeFullName); + var query = await GetEntityChangeListQueryAsync(auditLogId, startTime, endTime, changeType, entityId, entityTypeFullName, cancellationToken); return await query .OrderBy(sorting ?? "changeTime desc") @@ -192,7 +195,7 @@ namespace Volo.Abp.AuditLogging.MongoDB string entityTypeFullName = null, CancellationToken cancellationToken = default) { - var query = await GetEntityChangeListQueryAsync(auditLogId, startTime, endTime, changeType, entityId, entityTypeFullName); + var query = await GetEntityChangeListQueryAsync(auditLogId, startTime, endTime, changeType, entityId, entityTypeFullName, cancellationToken); var count = await query.As>().LongCountAsync(GetCancellationToken(cancellationToken)); @@ -203,7 +206,7 @@ namespace Volo.Abp.AuditLogging.MongoDB Guid entityChangeId, CancellationToken cancellationToken = default) { - var auditLog = await (await GetMongoQueryableAsync(GetCancellationToken(cancellationToken))) + var auditLog = await (await GetMongoQueryableAsync(cancellationToken)) .Where(x => x.EntityChanges.Any(y => y.Id == entityChangeId)) .FirstAsync(GetCancellationToken(cancellationToken)); @@ -219,7 +222,7 @@ namespace Volo.Abp.AuditLogging.MongoDB string entityTypeFullName, CancellationToken cancellationToken = default) { - var auditLogs = await (await GetMongoQueryableAsync(GetCancellationToken(cancellationToken))) + var auditLogs = await (await GetMongoQueryableAsync(cancellationToken)) .Where(x => x.EntityChanges.Any(y => y.EntityId == entityId && y.EntityTypeFullName == entityTypeFullName)) .As>() .OrderByDescending(x => x.ExecutionTime) @@ -239,9 +242,10 @@ namespace Volo.Abp.AuditLogging.MongoDB DateTime? endTime = null, EntityChangeType? changeType = null, string entityId = null, - string entityTypeFullName = null) + string entityTypeFullName = null, + CancellationToken cancellationToken = default) { - return (await GetMongoQueryableAsync()) + return (await GetMongoQueryableAsync(cancellationToken)) .SelectMany(x => x.EntityChanges) .WhereIf(auditLogId.HasValue, e => e.Id == auditLogId) .WhereIf(startTime.HasValue, e => e.ChangeTime >= startTime) diff --git a/modules/background-jobs/src/Volo.Abp.BackgroundJobs.EntityFrameworkCore/Volo/Abp/BackgroundJobs/EntityFrameworkCore/EfCoreBackgroundJobRepository.cs b/modules/background-jobs/src/Volo.Abp.BackgroundJobs.EntityFrameworkCore/Volo/Abp/BackgroundJobs/EntityFrameworkCore/EfCoreBackgroundJobRepository.cs index a4512a6e16..3ae5d2a409 100644 --- a/modules/background-jobs/src/Volo.Abp.BackgroundJobs.EntityFrameworkCore/Volo/Abp/BackgroundJobs/EntityFrameworkCore/EfCoreBackgroundJobRepository.cs +++ b/modules/background-jobs/src/Volo.Abp.BackgroundJobs.EntityFrameworkCore/Volo/Abp/BackgroundJobs/EntityFrameworkCore/EfCoreBackgroundJobRepository.cs @@ -24,7 +24,7 @@ namespace Volo.Abp.BackgroundJobs.EntityFrameworkCore public virtual async Task> GetWaitingListAsync( int maxResultCount, - CancellationToken cancellationToken) + CancellationToken cancellationToken = default) { return await (await GetWaitingListQueryAsync(maxResultCount)).ToListAsync(GetCancellationToken(cancellationToken)); } diff --git a/modules/blogging/src/Volo.Blogging.MongoDB/Volo/Blogging/Blogs/MongoBlogRepository.cs b/modules/blogging/src/Volo.Blogging.MongoDB/Volo/Blogging/Blogs/MongoBlogRepository.cs index 0ef6b70a88..2577b8d3a0 100644 --- a/modules/blogging/src/Volo.Blogging.MongoDB/Volo/Blogging/Blogs/MongoBlogRepository.cs +++ b/modules/blogging/src/Volo.Blogging.MongoDB/Volo/Blogging/Blogs/MongoBlogRepository.cs @@ -16,7 +16,7 @@ namespace Volo.Blogging.Blogs public async Task FindByShortNameAsync(string shortName, CancellationToken cancellationToken = default) { - return await (await GetMongoQueryableAsync(GetCancellationToken(cancellationToken))).FirstOrDefaultAsync(p => p.ShortName == shortName, GetCancellationToken(cancellationToken)); + return await (await GetMongoQueryableAsync(cancellationToken)).FirstOrDefaultAsync(p => p.ShortName == shortName, GetCancellationToken(cancellationToken)); } } } diff --git a/modules/blogging/src/Volo.Blogging.MongoDB/Volo/Blogging/Comments/MongoCommentRepository.cs b/modules/blogging/src/Volo.Blogging.MongoDB/Volo/Blogging/Comments/MongoCommentRepository.cs index cfeb24475a..689e44abbd 100644 --- a/modules/blogging/src/Volo.Blogging.MongoDB/Volo/Blogging/Comments/MongoCommentRepository.cs +++ b/modules/blogging/src/Volo.Blogging.MongoDB/Volo/Blogging/Comments/MongoCommentRepository.cs @@ -18,7 +18,7 @@ namespace Volo.Blogging.Comments public async Task> GetListOfPostAsync(Guid postId, CancellationToken cancellationToken = default) { - return await (await GetMongoQueryableAsync(GetCancellationToken(cancellationToken))) + return await (await GetMongoQueryableAsync(cancellationToken)) .Where(a => a.PostId == postId) .OrderBy(a => a.CreationTime) .ToListAsync(GetCancellationToken(cancellationToken)); @@ -26,19 +26,19 @@ namespace Volo.Blogging.Comments public async Task GetCommentCountOfPostAsync(Guid postId, CancellationToken cancellationToken = default) { - return await (await GetMongoQueryableAsync(GetCancellationToken(cancellationToken))) + return await (await GetMongoQueryableAsync(cancellationToken)) .CountAsync(a => a.PostId == postId, GetCancellationToken(cancellationToken)); } public async Task> GetRepliesOfComment(Guid id, CancellationToken cancellationToken = default) { - return await (await GetMongoQueryableAsync(GetCancellationToken(cancellationToken))) + return await (await GetMongoQueryableAsync(cancellationToken)) .Where(a => a.RepliedCommentId == id).ToListAsync(GetCancellationToken(cancellationToken)); } public async Task DeleteOfPost(Guid id, CancellationToken cancellationToken = default) { - var recordsToDelete = (await GetMongoQueryableAsync(GetCancellationToken(cancellationToken))).Where(pt => pt.PostId == id); + var recordsToDelete = (await GetMongoQueryableAsync(cancellationToken)).Where(pt => pt.PostId == id); foreach (var record in recordsToDelete) { diff --git a/modules/blogging/src/Volo.Blogging.MongoDB/Volo/Blogging/Posts/MongoPostRepository.cs b/modules/blogging/src/Volo.Blogging.MongoDB/Volo/Blogging/Posts/MongoPostRepository.cs index ec40e8da4a..a2d79c9466 100644 --- a/modules/blogging/src/Volo.Blogging.MongoDB/Volo/Blogging/Posts/MongoPostRepository.cs +++ b/modules/blogging/src/Volo.Blogging.MongoDB/Volo/Blogging/Posts/MongoPostRepository.cs @@ -19,13 +19,13 @@ namespace Volo.Blogging.Posts public async Task> GetPostsByBlogId(Guid id, CancellationToken cancellationToken = default) { - return await (await GetMongoQueryableAsync(GetCancellationToken(cancellationToken))).Where(p => p.BlogId == id).OrderByDescending(p => p.CreationTime).ToListAsync(GetCancellationToken(cancellationToken)); + return await (await GetMongoQueryableAsync(cancellationToken)).Where(p => p.BlogId == id).OrderByDescending(p => p.CreationTime).ToListAsync(GetCancellationToken(cancellationToken)); } public async Task IsPostUrlInUseAsync(Guid blogId, string url, Guid? excludingPostId = null, CancellationToken cancellationToken = default) { - var query = (await GetMongoQueryableAsync(GetCancellationToken(cancellationToken))).Where(p => blogId == p.BlogId && p.Url == url); + var query = (await GetMongoQueryableAsync(cancellationToken)).Where(p => blogId == p.BlogId && p.Url == url); if (excludingPostId != null) { @@ -37,7 +37,7 @@ namespace Volo.Blogging.Posts public async Task GetPostByUrl(Guid blogId, string url, CancellationToken cancellationToken = default) { - var post = await (await GetMongoQueryableAsync(GetCancellationToken(cancellationToken))).FirstOrDefaultAsync(p => p.BlogId == blogId && p.Url == url, GetCancellationToken(cancellationToken)); + var post = await (await GetMongoQueryableAsync(cancellationToken)).FirstOrDefaultAsync(p => p.BlogId == blogId && p.Url == url, GetCancellationToken(cancellationToken)); if (post == null) { @@ -49,7 +49,7 @@ namespace Volo.Blogging.Posts public async Task> GetOrderedList(Guid blogId, bool @descending = false, CancellationToken cancellationToken = default) { - var query = (await GetMongoQueryableAsync(GetCancellationToken(cancellationToken))).Where(x => x.BlogId == blogId); + var query = (await GetMongoQueryableAsync(cancellationToken)).Where(x => x.BlogId == blogId); if (!descending) { diff --git a/modules/blogging/src/Volo.Blogging.MongoDB/Volo/Blogging/Tagging/MongoTagRepository.cs b/modules/blogging/src/Volo.Blogging.MongoDB/Volo/Blogging/Tagging/MongoTagRepository.cs index 73999628ce..2030d47f2d 100644 --- a/modules/blogging/src/Volo.Blogging.MongoDB/Volo/Blogging/Tagging/MongoTagRepository.cs +++ b/modules/blogging/src/Volo.Blogging.MongoDB/Volo/Blogging/Tagging/MongoTagRepository.cs @@ -20,27 +20,27 @@ namespace Volo.Blogging.Tagging public async Task> GetListAsync(Guid blogId, CancellationToken cancellationToken = default) { - return await (await GetMongoQueryableAsync(GetCancellationToken(cancellationToken))).Where(t => t.BlogId == blogId).ToListAsync(GetCancellationToken(cancellationToken)); + return await (await GetMongoQueryableAsync(cancellationToken)).Where(t => t.BlogId == blogId).ToListAsync(GetCancellationToken(cancellationToken)); } public async Task GetByNameAsync(Guid blogId, string name, CancellationToken cancellationToken = default) { - return await (await GetMongoQueryableAsync(GetCancellationToken(cancellationToken))).Where(t => t.BlogId == blogId && t.Name == name).FirstAsync(GetCancellationToken(cancellationToken)); + return await (await GetMongoQueryableAsync(cancellationToken)).Where(t => t.BlogId == blogId && t.Name == name).FirstAsync(GetCancellationToken(cancellationToken)); } public async Task FindByNameAsync(Guid blogId, string name, CancellationToken cancellationToken = default) { - return await (await GetMongoQueryableAsync(GetCancellationToken(cancellationToken))).Where(t => t.BlogId == blogId && t.Name == name).FirstOrDefaultAsync(GetCancellationToken(cancellationToken)); + return await (await GetMongoQueryableAsync(cancellationToken)).Where(t => t.BlogId == blogId && t.Name == name).FirstOrDefaultAsync(GetCancellationToken(cancellationToken)); } public async Task> GetListAsync(IEnumerable ids, CancellationToken cancellationToken = default) { - return await (await GetMongoQueryableAsync(GetCancellationToken(cancellationToken))).Where(t => ids.Contains(t.Id)).ToListAsync(GetCancellationToken(cancellationToken)); + return await (await GetMongoQueryableAsync(cancellationToken)).Where(t => ids.Contains(t.Id)).ToListAsync(GetCancellationToken(cancellationToken)); } public async Task DecreaseUsageCountOfTagsAsync(List ids, CancellationToken cancellationToken = default) { - var tags = await (await GetMongoQueryableAsync(GetCancellationToken(cancellationToken))) + var tags = await (await GetMongoQueryableAsync(cancellationToken)) .Where(t => ids.Contains(t.Id)) .ToListAsync(GetCancellationToken(cancellationToken)); diff --git a/modules/blogging/src/Volo.Blogging.MongoDB/Volo/Blogging/Users/MongoBlogUserRepository.cs b/modules/blogging/src/Volo.Blogging.MongoDB/Volo/Blogging/Users/MongoBlogUserRepository.cs index c558799a86..5523af8394 100644 --- a/modules/blogging/src/Volo.Blogging.MongoDB/Volo/Blogging/Users/MongoBlogUserRepository.cs +++ b/modules/blogging/src/Volo.Blogging.MongoDB/Volo/Blogging/Users/MongoBlogUserRepository.cs @@ -17,7 +17,7 @@ namespace Volo.Blogging.Users public async Task> GetUsersAsync(int maxCount, string filter, CancellationToken cancellationToken = default) { - var query = await GetMongoQueryableAsync(GetCancellationToken(cancellationToken)); + var query = await GetMongoQueryableAsync(cancellationToken); if (!string.IsNullOrWhiteSpace(filter)) { diff --git a/modules/cms-kit/src/Volo.CmsKit.MongoDB/Volo/CmsKit/MongoDB/Pages/MongoPageRepository.cs b/modules/cms-kit/src/Volo.CmsKit.MongoDB/Volo/CmsKit/MongoDB/Pages/MongoPageRepository.cs index cc07afcd4b..af72da56c8 100644 --- a/modules/cms-kit/src/Volo.CmsKit.MongoDB/Volo/CmsKit/MongoDB/Pages/MongoPageRepository.cs +++ b/modules/cms-kit/src/Volo.CmsKit.MongoDB/Volo/CmsKit/MongoDB/Pages/MongoPageRepository.cs @@ -63,7 +63,7 @@ namespace Volo.CmsKit.MongoDB.Pages public virtual async Task ExistsAsync(string url, CancellationToken cancellationToken = default) { - return await (await GetMongoQueryableAsync(GetCancellationToken(cancellationToken))).AnyAsync(x => x.Url == url, GetCancellationToken(cancellationToken)); + return await (await GetMongoQueryableAsync(cancellationToken)).AnyAsync(x => x.Url == url, GetCancellationToken(cancellationToken)); } } } diff --git a/modules/docs/src/Volo.Docs.MongoDB/Volo/Docs/Projects/MongoProjectRepository.cs b/modules/docs/src/Volo.Docs.MongoDB/Volo/Docs/Projects/MongoProjectRepository.cs index 1e9fe1be4f..99aa5a99dd 100644 --- a/modules/docs/src/Volo.Docs.MongoDB/Volo/Docs/Projects/MongoProjectRepository.cs +++ b/modules/docs/src/Volo.Docs.MongoDB/Volo/Docs/Projects/MongoProjectRepository.cs @@ -22,7 +22,7 @@ namespace Volo.Docs.Projects public async Task> GetListAsync(string sorting, int maxResultCount, int skipCount, CancellationToken cancellationToken = default) { - var projects = await (await GetMongoQueryableAsync(GetCancellationToken(cancellationToken))).OrderBy(sorting ?? "Id desc").As>() + var projects = await (await GetMongoQueryableAsync(cancellationToken)).OrderBy(sorting ?? "Id desc").As>() .PageBy>(skipCount, maxResultCount) .ToListAsync(GetCancellationToken(cancellationToken)); @@ -33,7 +33,7 @@ namespace Volo.Docs.Projects { var normalizeShortName = NormalizeShortName(shortName); - var project = await (await GetMongoQueryableAsync(GetCancellationToken(cancellationToken))).FirstOrDefaultAsync(p => p.ShortName == normalizeShortName, GetCancellationToken(cancellationToken)); + var project = await (await GetMongoQueryableAsync(cancellationToken)).FirstOrDefaultAsync(p => p.ShortName == normalizeShortName, GetCancellationToken(cancellationToken)); if (project == null) { @@ -47,7 +47,7 @@ namespace Volo.Docs.Projects { var normalizeShortName = NormalizeShortName(shortName); - return await (await GetMongoQueryableAsync(GetCancellationToken(cancellationToken))).AnyAsync(x => x.ShortName == normalizeShortName, GetCancellationToken(cancellationToken)); + return await (await GetMongoQueryableAsync(cancellationToken)).AnyAsync(x => x.ShortName == normalizeShortName, GetCancellationToken(cancellationToken)); } private string NormalizeShortName(string shortName) diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.MongoDB/Volo/Abp/FeatureManagement/MongoDB/MongoFeatureValueRepository.cs b/modules/feature-management/src/Volo.Abp.FeatureManagement.MongoDB/Volo/Abp/FeatureManagement/MongoDB/MongoFeatureValueRepository.cs index 5547997251..8f5a659fd8 100644 --- a/modules/feature-management/src/Volo.Abp.FeatureManagement.MongoDB/Volo/Abp/FeatureManagement/MongoDB/MongoFeatureValueRepository.cs +++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.MongoDB/Volo/Abp/FeatureManagement/MongoDB/MongoFeatureValueRepository.cs @@ -23,7 +23,7 @@ namespace Volo.Abp.FeatureManagement.MongoDB string providerKey, CancellationToken cancellationToken = default) { - return await (await GetMongoQueryableAsync(GetCancellationToken(cancellationToken))) + return await (await GetMongoQueryableAsync(cancellationToken)) .OrderBy(x => x.Id) .FirstOrDefaultAsync(s => s.Name == name && s.ProviderName == providerName && s.ProviderKey == providerKey, GetCancellationToken(cancellationToken)); } @@ -34,7 +34,7 @@ namespace Volo.Abp.FeatureManagement.MongoDB string providerKey, CancellationToken cancellationToken = default) { - return await (await GetMongoQueryableAsync(GetCancellationToken(cancellationToken))) + return await (await GetMongoQueryableAsync(cancellationToken)) .Where(s => s.Name == name && s.ProviderName == providerName && s.ProviderKey == providerKey).ToListAsync(GetCancellationToken(cancellationToken)); } @@ -43,7 +43,7 @@ namespace Volo.Abp.FeatureManagement.MongoDB string providerKey, CancellationToken cancellationToken = default) { - return await (await GetMongoQueryableAsync(GetCancellationToken(cancellationToken))) + return await (await GetMongoQueryableAsync(cancellationToken)) .Where(s => s.ProviderName == providerName && s.ProviderKey == providerKey) .ToListAsync(GetCancellationToken(cancellationToken)); } diff --git a/modules/setting-management/src/Volo.Abp.SettingManagement.MongoDB/Volo/Abp/SettingManagement/MongoDB/MongoSettingRepository.cs b/modules/setting-management/src/Volo.Abp.SettingManagement.MongoDB/Volo/Abp/SettingManagement/MongoDB/MongoSettingRepository.cs index fc01f79132..aef9624a4d 100644 --- a/modules/setting-management/src/Volo.Abp.SettingManagement.MongoDB/Volo/Abp/SettingManagement/MongoDB/MongoSettingRepository.cs +++ b/modules/setting-management/src/Volo.Abp.SettingManagement.MongoDB/Volo/Abp/SettingManagement/MongoDB/MongoSettingRepository.cs @@ -24,7 +24,7 @@ namespace Volo.Abp.SettingManagement.MongoDB string providerKey, CancellationToken cancellationToken = default) { - return await (await GetMongoQueryableAsync(GetCancellationToken(cancellationToken))) + return await (await GetMongoQueryableAsync(cancellationToken)) .OrderBy(x => x.Id) .FirstOrDefaultAsync( s => s.Name == name && s.ProviderName == providerName && s.ProviderKey == providerKey, @@ -36,7 +36,7 @@ namespace Volo.Abp.SettingManagement.MongoDB string providerKey, CancellationToken cancellationToken = default) { - return await (await GetMongoQueryableAsync(GetCancellationToken(cancellationToken))) + return await (await GetMongoQueryableAsync(cancellationToken)) .Where(s => s.ProviderName == providerName && s.ProviderKey == providerKey) .ToListAsync(GetCancellationToken(cancellationToken)); } @@ -47,7 +47,7 @@ namespace Volo.Abp.SettingManagement.MongoDB string providerKey, CancellationToken cancellationToken = default) { - return await (await GetMongoQueryableAsync(GetCancellationToken(cancellationToken))) + return await (await GetMongoQueryableAsync(cancellationToken)) .Where(s => names.Contains(s.Name) && s.ProviderName == providerName && s.ProviderKey == providerKey) .ToListAsync(GetCancellationToken(cancellationToken)); } From 5a6751f10b164e89c0f81fd6760c865fa734ab41 Mon Sep 17 00:00:00 2001 From: liangshiwei Date: Wed, 20 Jan 2021 18:31:51 +0800 Subject: [PATCH 48/51] Improve --- .../MongoDB/MongoBackgroundJobRepository.cs | 6 +++--- .../CmsKit/MongoDB/Contents/MongoContentRepository.cs | 2 +- .../MongoDB/MongoIdentitySecurityLogRepository.cs | 11 +++++++---- .../MongoDB/MongoPersistedGrantRepository.cs | 9 +++++---- 4 files changed, 16 insertions(+), 12 deletions(-) diff --git a/modules/background-jobs/src/Volo.Abp.BackgroundJobs.MongoDB/Volo/Abp/BackgroundJobs/MongoDB/MongoBackgroundJobRepository.cs b/modules/background-jobs/src/Volo.Abp.BackgroundJobs.MongoDB/Volo/Abp/BackgroundJobs/MongoDB/MongoBackgroundJobRepository.cs index 88d28616a1..56d0bfaff1 100644 --- a/modules/background-jobs/src/Volo.Abp.BackgroundJobs.MongoDB/Volo/Abp/BackgroundJobs/MongoDB/MongoBackgroundJobRepository.cs +++ b/modules/background-jobs/src/Volo.Abp.BackgroundJobs.MongoDB/Volo/Abp/BackgroundJobs/MongoDB/MongoBackgroundJobRepository.cs @@ -24,15 +24,15 @@ namespace Volo.Abp.BackgroundJobs.MongoDB public virtual async Task> GetWaitingListAsync( int maxResultCount, - CancellationToken cancellationToken) + CancellationToken cancellationToken = default) { return await (await GetWaitingListQuery(maxResultCount)).ToListAsync(GetCancellationToken(cancellationToken)); } - protected virtual async Task> GetWaitingListQuery(int maxResultCount) + protected virtual async Task> GetWaitingListQuery(int maxResultCount, CancellationToken cancellationToken = default) { var now = Clock.Now; - return (await GetMongoQueryableAsync()) + return (await GetMongoQueryableAsync(cancellationToken)) .Where(t => !t.IsAbandoned && t.NextTryTime <= now) .OrderByDescending(t => t.Priority) .ThenBy(t => t.TryCount) diff --git a/modules/cms-kit/src/Volo.CmsKit.MongoDB/Volo/CmsKit/MongoDB/Contents/MongoContentRepository.cs b/modules/cms-kit/src/Volo.CmsKit.MongoDB/Volo/CmsKit/MongoDB/Contents/MongoContentRepository.cs index f42b0890e7..1068c352d5 100644 --- a/modules/cms-kit/src/Volo.CmsKit.MongoDB/Volo/CmsKit/MongoDB/Contents/MongoContentRepository.cs +++ b/modules/cms-kit/src/Volo.CmsKit.MongoDB/Volo/CmsKit/MongoDB/Contents/MongoContentRepository.cs @@ -54,7 +54,7 @@ namespace Volo.CmsKit.MongoDB.Contents public async Task ExistsAsync([NotNull] string entityType, [NotNull] string entityId, Guid? tenantId = null, CancellationToken cancellationToken = default) { - return await (await GetMongoQueryableAsync()).AnyAsync(x => + return await (await GetMongoQueryableAsync(cancellationToken)).AnyAsync(x => x.EntityType == entityType && x.EntityId == entityId && x.TenantId == tenantId, diff --git a/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/MongoIdentitySecurityLogRepository.cs b/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/MongoIdentitySecurityLogRepository.cs index cc926db139..1ac54cfc82 100644 --- a/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/MongoIdentitySecurityLogRepository.cs +++ b/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/MongoIdentitySecurityLogRepository.cs @@ -44,7 +44,8 @@ namespace Volo.Abp.Identity.MongoDB userId, userName, clientId, - correlationId + correlationId, + cancellationToken ); return await query.OrderBy(sorting ?? nameof(IdentitySecurityLog.CreationTime) + " desc") @@ -74,7 +75,8 @@ namespace Volo.Abp.Identity.MongoDB userId, userName, clientId, - correlationId + correlationId, + cancellationToken ); return await query.As>() @@ -98,9 +100,10 @@ namespace Volo.Abp.Identity.MongoDB Guid? userId = null, string userName = null, string clientId = null, - string correlationId = null) + string correlationId = null, + CancellationToken cancellationToken = default) { - return (await GetMongoQueryableAsync()) + return (await GetMongoQueryableAsync(cancellationToken)) .WhereIf(startTime.HasValue, securityLog => securityLog.CreationTime >= startTime.Value) .WhereIf(endTime.HasValue, securityLog => securityLog.CreationTime < endTime.Value.AddDays(1).Date) .WhereIf(!applicationName.IsNullOrWhiteSpace(), diff --git a/modules/identityserver/src/Volo.Abp.IdentityServer.MongoDB/Volo/Abp/IdentityServer/MongoDB/MongoPersistedGrantRepository.cs b/modules/identityserver/src/Volo.Abp.IdentityServer.MongoDB/Volo/Abp/IdentityServer/MongoDB/MongoPersistedGrantRepository.cs index 2d4728a7e1..9dd4f24af6 100644 --- a/modules/identityserver/src/Volo.Abp.IdentityServer.MongoDB/Volo/Abp/IdentityServer/MongoDB/MongoPersistedGrantRepository.cs +++ b/modules/identityserver/src/Volo.Abp.IdentityServer.MongoDB/Volo/Abp/IdentityServer/MongoDB/MongoPersistedGrantRepository.cs @@ -21,7 +21,7 @@ namespace Volo.Abp.IdentityServer.MongoDB public async Task> GetListAsync(string subjectId, string sessionId, string clientId, string type, bool includeDetails = false, CancellationToken cancellationToken = default) { - return await (await FilterAsync(subjectId, sessionId, clientId, type)) + return await (await FilterAsync(subjectId, sessionId, clientId, type, cancellationToken)) .ToListAsync(GetCancellationToken(cancellationToken)); } @@ -57,7 +57,7 @@ namespace Volo.Abp.IdentityServer.MongoDB string type = null, CancellationToken cancellationToken = default) { - var persistedGrants = await (await FilterAsync(subjectId, sessionId, clientId, type)) + var persistedGrants = await (await FilterAsync(subjectId, sessionId, clientId, type, cancellationToken)) .ToListAsync(GetCancellationToken(cancellationToken)); foreach (var persistedGrant in persistedGrants) @@ -86,9 +86,10 @@ namespace Volo.Abp.IdentityServer.MongoDB string subjectId, string sessionId, string clientId, - string type) + string type, + CancellationToken cancellationToken = default) { - return (await GetMongoQueryableAsync()) + return (await GetMongoQueryableAsync(cancellationToken)) .WhereIf>(!subjectId.IsNullOrWhiteSpace(), x => x.SubjectId == subjectId) .WhereIf>(!sessionId.IsNullOrWhiteSpace(), x => x.SessionId == sessionId) .WhereIf>(!clientId.IsNullOrWhiteSpace(), x => x.ClientId == clientId) From cbc4356e6580d2c50af5e153e1783934268ab46e Mon Sep 17 00:00:00 2001 From: Arman Ozak Date: Wed, 20 Jan 2021 18:56:57 +0300 Subject: [PATCH 49/51] add createTreeNodeFilterCreator util fn --- .../core/src/lib/tests/tree-utils.spec.ts | 75 ++++++++++++++++++- .../packages/core/src/lib/utils/tree-utils.ts | 21 ++++++ 2 files changed, 93 insertions(+), 3 deletions(-) diff --git a/npm/ng-packs/packages/core/src/lib/tests/tree-utils.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/tree-utils.spec.ts index 6115114872..b4f8721f47 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/tree-utils.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/tree-utils.spec.ts @@ -1,4 +1,9 @@ -import { BaseTreeNode, createTreeFromList, TreeNode } from '../utils/tree-utils'; +import { + BaseTreeNode, + createTreeFromList, + createTreeNodeFilterCreator, + TreeNode, +} from '../utils/tree-utils'; const LIST_1 = [ { id: 1, pid: null }, @@ -38,6 +43,42 @@ const TREE_3 = [ ], }, ]; +const SOURCE_TREE: TreeNode[] = [ + { + id: 1, + pid: null, + isLeaf: false, + name: 'foo', + children: [ + { + id: 2, + pid: 1, + name: 'bar', + isLeaf: false, + children: [{ id: 3, pid: 2, name: 'qux', isLeaf: true, children: [] }], + }, + { id: 4, pid: 1, name: 'baz', isLeaf: true, children: [] }, + { id: 5, pid: 1, name: 'quux', isLeaf: true, children: [] }, + ], + }, +]; +const RESULT_TREE_1 = [ + { id: 3, pid: 2, name: 'qux', isLeaf: true, children: [] }, + { id: 5, pid: 1, name: 'quux', isLeaf: true, children: [] }, +]; +const RESULT_TREE_2 = [{ id: 5, pid: 1, name: 'quux', isLeaf: true, children: [] }]; +const RESULT_TREE_3 = [ + { + id: 2, + pid: 1, + name: 'bar', + isLeaf: false, + children: [{ id: 3, pid: 2, name: 'qux', isLeaf: true, children: [] }], + }, + { id: 4, pid: 1, name: 'baz', isLeaf: true, children: [] }, +]; +const RESULT_TREE_4 = [{ id: 4, pid: 1, name: 'baz', isLeaf: true, children: [] }]; + describe('Tree Utils', () => { describe('createTreeFromList', () => { test.each` @@ -56,6 +97,23 @@ describe('Tree Utils', () => { expect(removeParents(tree)).toEqual(expected); }); }); + + describe('createTreeNodeFilterCreator', () => { + test.each` + search | expected + ${'qu'} | ${RESULT_TREE_1} + ${'quu'} | ${RESULT_TREE_2} + ${'ba'} | ${RESULT_TREE_3} + ${'baz'} | ${RESULT_TREE_4} + `( + 'should return $expected when $search is searched', + ({ search, expected }: TestCreateTreeNodeFilter) => { + const filter = createTreeNodeFilterCreator('name', String)(search); + + expect(filter(SOURCE_TREE)).toEqual(expected); + }, + ); + }); }); function removeParents(tree: TreeNode[]) { @@ -72,6 +130,17 @@ interface TestCreateTreeFromList { } interface Model { - id: 1; - pid: null; + id: number; + pid?: number; +} + +interface TestCreateTreeNodeFilter { + search: string; + expected: TreeNode[]; +} + +interface SearchModel { + id: number; + pid?: number; + name: string; } diff --git a/npm/ng-packs/packages/core/src/lib/utils/tree-utils.ts b/npm/ng-packs/packages/core/src/lib/utils/tree-utils.ts index ba6e3a5b90..8339929718 100644 --- a/npm/ng-packs/packages/core/src/lib/utils/tree-utils.ts +++ b/npm/ng-packs/packages/core/src/lib/utils/tree-utils.ts @@ -54,6 +54,27 @@ export function createMapFromList( return map; } +export function createTreeNodeFilterCreator( + key: keyof T, + mapperFn: (value: any) => string, +) { + return (search: string) => { + const regex = new RegExp('.*' + search + '.*', 'i'); + + return function collectNodes(nodes: TreeNode[], matches = []) { + for (let i = 0; i < nodes.length; i++) { + const node = nodes[i]; + + if (regex.test(mapperFn(node[key]))) matches.push(node); + + if (node.children.length) collectNodes(node.children, matches); + } + + return matches; + }; + }; +} + export type TreeNode = { [K in keyof T]: T[K]; } & { From 5fc956ec1edbfe30e7b7cd0f37e5b12a227e7074 Mon Sep 17 00:00:00 2001 From: Arman Ozak Date: Wed, 20 Jan 2021 18:59:39 +0300 Subject: [PATCH 50/51] use for..of instead of for loop --- npm/ng-packs/packages/core/src/lib/utils/tree-utils.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/npm/ng-packs/packages/core/src/lib/utils/tree-utils.ts b/npm/ng-packs/packages/core/src/lib/utils/tree-utils.ts index 8339929718..235820abd6 100644 --- a/npm/ng-packs/packages/core/src/lib/utils/tree-utils.ts +++ b/npm/ng-packs/packages/core/src/lib/utils/tree-utils.ts @@ -62,9 +62,7 @@ export function createTreeNodeFilterCreator( const regex = new RegExp('.*' + search + '.*', 'i'); return function collectNodes(nodes: TreeNode[], matches = []) { - for (let i = 0; i < nodes.length; i++) { - const node = nodes[i]; - + for (const node of nodes) { if (regex.test(mapperFn(node[key]))) matches.push(node); if (node.children.length) collectNodes(node.children, matches); From d495f567e36b80c385eff08a578abfcba4db5fc3 Mon Sep 17 00:00:00 2001 From: Mladen Macanovic Date: Thu, 21 Jan 2021 11:30:46 +0100 Subject: [PATCH 51/51] Community article on how to integrate MatBlazor --- .../POST.md | 103 ++++++++++++++++++ .../example-result.png | Bin 0 -> 63504 bytes .../initial-project.png | Bin 0 -> 11549 bytes .../table-app-contract.png | Bin 0 -> 8686 bytes .../table-application.png | Bin 0 -> 6625 bytes .../table-web.png | Bin 0 -> 14259 bytes 6 files changed, 103 insertions(+) create mode 100644 docs/en/Community-Articles/2021-01-20-How-to-Integrate-the-MatBlazor-Blazor-Component/POST.md create mode 100644 docs/en/Community-Articles/2021-01-20-How-to-Integrate-the-MatBlazor-Blazor-Component/example-result.png create mode 100644 docs/en/Community-Articles/2021-01-20-How-to-Integrate-the-MatBlazor-Blazor-Component/initial-project.png create mode 100644 docs/en/Community-Articles/2021-01-20-How-to-Integrate-the-MatBlazor-Blazor-Component/table-app-contract.png create mode 100644 docs/en/Community-Articles/2021-01-20-How-to-Integrate-the-MatBlazor-Blazor-Component/table-application.png create mode 100644 docs/en/Community-Articles/2021-01-20-How-to-Integrate-the-MatBlazor-Blazor-Component/table-web.png diff --git a/docs/en/Community-Articles/2021-01-20-How-to-Integrate-the-MatBlazor-Blazor-Component/POST.md b/docs/en/Community-Articles/2021-01-20-How-to-Integrate-the-MatBlazor-Blazor-Component/POST.md new file mode 100644 index 0000000000..f30f31d541 --- /dev/null +++ b/docs/en/Community-Articles/2021-01-20-How-to-Integrate-the-MatBlazor-Blazor-Component/POST.md @@ -0,0 +1,103 @@ +## Using MatBlazor UI Components With the ABP Framework + +Hi, in this step by step article, I will show you how to integrate [MatBlazor](https://www.matblazor.com/), a blazor UI components into ABP Framework-based applications. + +![example-result](example-result.png) + +*(A screenshot from the example application developed in this article)* + +## Create the Project + +> First thing is to create the project. ABP Framework offers startup templates to get into business faster. + +In this article, I will create a new startup template with EF Core as a database provider and Blazor for UI framework. But if you already have a project with Blazor UI, you don't need to create a new startup template, you can directly implement the following steps to your existing project. + +> If you already have a project with the Blazor UI, you can skip this section. + +* Before starting the development, we will create a new solution named `MatBlazorSample` (or whatever you want). We will create a new startup template with EF Core as a database provider and Blazor for UI framework by using [ABP CLI](https://docs.abp.io/en/abp/latest/CLI): + +````bash +abp new MatBlazorSample -u blazor +```` + +This will create new project inside of `aspnet-core`, so: + +````bash +cd aspnet-core +```` + +and + +````bash +dotnet restore +```` + +* Our project boilerplate will be ready after the download is finished. Then, we can open the solution in the Visual Studio (or any other IDE) and run the `MatBlazorSample.DbMigrator` to create the database and seed initial data (which creates the admin user, admin role, permissions etc.) + +![initial-project](initial-project.png) + +* After database and initial data created, +* Run the `MatBlazorSample.HttpApi.Host` to see our server side working and +* Run the `MatBlazorSample.Blazor` to see our UI working properly. + +> _Default login credentials for admin: username is **admin** and password is **1q2w3E\***_ + +## Install MatBlazor + +You can follow [this documentation](https://www.matblazor.com/) to install MatBlazor packages into your computer. + +### Adding MatBlazor NuGet Packages + +```bash +Install-Package MatBlazor +``` + +### Register MatBlazor Resources + +1. Add the following line to the HEAD section of the `wwwroot/index.html` file within the `MatBlazorSample.Blazor` project: + + ```Razor + + + + + + ``` + +2. In the `MatBlazorSampleBlazorModule` class, call the `AddMatBlazor()` method from your project's `ConfigureServices()` method: + + ```csharp + public override void ConfigureServices(ServiceConfigurationContext context) + { + var environment = context.Services.GetSingletonInstance(); + var builder = context.Services.GetSingletonInstance(); + // ... + builder.Services.AddMatBlazor(); + } + ``` + +3. Register the **MatBlazorSample.Blazor** namespace in the `_Imports.razor` file: + + ```Razor + @using MatBlazor + ``` + +## The Sample Application + +We have created a sample application with [Table](https://www.matblazor.com/Table) example. + +### The Source Code + +You can download the source code from [here](https://github.com/abpframework/abp-samples/tree/master/MatBlazorSample). + +The related files for this example are marked in the following screenshots. + +![table-app-contract](table-app-contract.png) + +![table-application](table-application.png) + +![table-web](table-web.png) + +## Conclusion + +In this article, I've explained how to use [MatBlazor](https://www.matblazor.com/) components in your application. ABP Framework is designed so that it can work with any UI library/framework. \ No newline at end of file diff --git a/docs/en/Community-Articles/2021-01-20-How-to-Integrate-the-MatBlazor-Blazor-Component/example-result.png b/docs/en/Community-Articles/2021-01-20-How-to-Integrate-the-MatBlazor-Blazor-Component/example-result.png new file mode 100644 index 0000000000000000000000000000000000000000..89a2fecdcecbf08790ea1e549ec1fd25c5194713 GIT binary patch literal 63504 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 literal 0 HcmV?d00001 diff --git a/docs/en/Community-Articles/2021-01-20-How-to-Integrate-the-MatBlazor-Blazor-Component/initial-project.png b/docs/en/Community-Articles/2021-01-20-How-to-Integrate-the-MatBlazor-Blazor-Component/initial-project.png new file mode 100644 index 0000000000000000000000000000000000000000..85e71d56921264d961def41b7aed6558ae86f3b3 GIT binary patch literal 11549 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 literal 0 HcmV?d00001 diff --git a/docs/en/Community-Articles/2021-01-20-How-to-Integrate-the-MatBlazor-Blazor-Component/table-app-contract.png b/docs/en/Community-Articles/2021-01-20-How-to-Integrate-the-MatBlazor-Blazor-Component/table-app-contract.png new file mode 100644 index 0000000000000000000000000000000000000000..8108cdc60616785e5df1ac7f7c10fbe300618bf0 GIT binary patch literal 8686 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 literal 0 HcmV?d00001 diff --git a/docs/en/Community-Articles/2021-01-20-How-to-Integrate-the-MatBlazor-Blazor-Component/table-application.png b/docs/en/Community-Articles/2021-01-20-How-to-Integrate-the-MatBlazor-Blazor-Component/table-application.png new file mode 100644 index 0000000000000000000000000000000000000000..7f07af97e16ed4342836ac181d6018afa02be3c3 GIT binary patch literal 6625 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 E0X0t3Q~&?~ literal 0 HcmV?d00001 diff --git a/docs/en/Community-Articles/2021-01-20-How-to-Integrate-the-MatBlazor-Blazor-Component/table-web.png b/docs/en/Community-Articles/2021-01-20-How-to-Integrate-the-MatBlazor-Blazor-Component/table-web.png new file mode 100644 index 0000000000000000000000000000000000000000..0034aec0950a86942c8e510ae9d7b7595cbfc6a7 GIT binary patch literal 14259 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 literal 0 HcmV?d00001