Merge branch 'dev' into maliming/Linked-user-enhancements

pull/7953/head
maliming 5 years ago
commit ed4a0caed4

@ -91,6 +91,7 @@
"UserNotFound": "User not found",
"{0}WillBeRemovedFromDevelopers": "{0} Will be removed from developers, do you confirm?",
"{0}WillBeRemovedFromOwners": "{0} Will be removed from owners, do you confirm?",
"{0}WillBeRemovedFromMembers": "{0} Will be removed from members, do you confirm?",
"Computers": "Computers",
"UniqueComputerId": "Unique computer id",
"LastSeenDate": "Last seen date",

@ -1,6 +1,6 @@
# Router Events Simplified
`RouterEvents` is a utility service to provide an easy implementation for one of the most frequent needs in Angular templates: `TrackByFunction`. Please see [this page in Angular docs](https://angular.io/guide/template-syntax#ngfor-with-trackby) for its purpose.
`RouterEvents` is a utility service for filtering specific router events and reacting to them. Please see [this page in Angular docs](https://angular.io/api/router/Event) for available router events.

@ -150,7 +150,7 @@ var people = _personRepository
* 这里异步方法**不是标准LINQ方法**,它们定义在[Microsoft.EntityFrameworkCore](https://www.nuget.org/packages/Microsoft.EntityFrameworkCore)Nuget包中.
* 标准模板应用层与领域层**不引用**EF Core 包以实现数据库提供程序独立.
根据你的需求和开发模式,你可以根据以下选项使用异步方法.s
根据你的需求和开发模式,你可以根据以下选项使用异步方法.
> 强烈建议使用异步方法! 在执行数据库查询时不要使用同步LINQ方法,以便能够开发可伸缩的应用程序.

@ -16,6 +16,7 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Packages.TuiEditor
{
public override void ConfigureBundle(BundleConfigurationContext context)
{
context.Files.AddIfNotContains("/libs/tui-editor/tui-editor-jquery-patch.js");
context.Files.AddIfNotContains("/libs/to-mark/to-mark.min.js");
if (context.FileProvider.GetFileInfo("/libs/tui-code-snippet/tui-code-snippet.min.js").Exists)

@ -1,4 +1,5 @@
using Volo.Abp.Cli.ProjectBuilding.Building.Steps;
using Volo.Abp.Cli.ProjectBuilding.Templates;
namespace Volo.Abp.Cli.ProjectBuilding.Building
{
@ -12,6 +13,7 @@ namespace Volo.Abp.Cli.ProjectBuilding.Building
pipeline.Steps.Add(new ProjectReferenceReplaceStep());
pipeline.Steps.Add(new ReplaceCommonPropsStep());
pipeline.Steps.Add(new ReplaceConfigureAwaitPropsStep());
pipeline.Steps.Add(new UpdateNuGetConfigStep("/NuGet.Config"));
pipeline.Steps.Add(new CreateProjectResultZipStep());
return pipeline;

@ -0,0 +1,78 @@
using System;
using System.IO;
using System.Linq;
using Volo.Abp.Cli.Commands;
namespace Volo.Abp.Cli.ProjectBuilding.Building.Steps
{
public class MicroserviceServiceRandomPortStep : ProjectBuildPipelineStep
{
private readonly string _defaultPort = string.Empty;
private string _tyeFileContent = null;
public MicroserviceServiceRandomPortStep(string defaultPort)
{
_defaultPort = defaultPort;
}
public override void Execute(ProjectBuildContext context)
{
var newPort = GetNewRandomPort(context);
var targetFiles = context.Files.Where(f=> f.Name.EndsWith("launchSettings.json") || f.Name.EndsWith("appsettings.json")).ToList();
foreach (var file in targetFiles)
{
file.SetContent(file.Content.Replace(_defaultPort, newPort));
}
}
private string GetNewRandomPort(ProjectBuildContext context)
{
string newPort;
var rnd = new Random();
var tryCount = 0;
do
{
newPort = rnd.Next(44350, 45350).ToString();
if (tryCount++ > 2000)
{
break;
}
} while (PortExistsForAnotherService(context, newPort));
return newPort;
}
private bool PortExistsForAnotherService(ProjectBuildContext context, string newPort)
{
return ReadTyeFileContent(context).SplitToLines().Any(l => l.Contains("port") && l.Contains(newPort));
}
private string ReadTyeFileContent(ProjectBuildContext context)
{
if (_tyeFileContent != null)
{
return _tyeFileContent;
}
var solutionFolderPath = context.BuildArgs.ExtraProperties[NewCommand.Options.OutputFolder.Short] ??
context.BuildArgs.ExtraProperties[NewCommand.Options.OutputFolder.Long] ??
Directory.GetCurrentDirectory();
var tyeFilePath = Path.Combine(solutionFolderPath, "tye.yaml");
if (!File.Exists(tyeFilePath))
{
return String.Empty;
}
_tyeFileContent = File.ReadAllText(tyeFilePath);
return _tyeFileContent;
}
}
}

@ -9,12 +9,9 @@ namespace Volo.Abp.Cli.ProjectBuilding.Building.Steps
{
var commonFiles = context.Files.Where(f =>
f.Name.EndsWith(".csproj") ||
f.Name.EndsWith("Module.cs") ||
f.Name.EndsWith("MyProjectNameMigrationsDbContext.cs") ||
f.Name.EndsWith("MyProjectNameMigrationsDbContextBase.cs") ||
f.Name.EndsWith("MyProjectNameGlobalFeatureConfigurator.cs") ||
(f.Name.EndsWith(".cshtml") && f.Name.Contains("MyCompanyName.MyProjectName.Web.Public"))
);
f.Name.EndsWith(".cs") ||
f.Name.EndsWith(".json") ||
f.Name.EndsWith(".cshtml"));
foreach (var file in commonFiles)
{

@ -7,8 +7,13 @@ namespace Volo.Abp.Cli.ProjectBuilding.Building.Steps
{
public override void Execute(ProjectBuildContext context)
{
context.Files.FirstOrDefault(f => f.Name.EndsWith("MyCompanyName.MyProjectName.Web.Public.csproj"))?.RemoveTemplateCodeIfNot("EFCORE");
context.Files.FirstOrDefault(f => f.Name.EndsWith("MyProjectNameWebPublicModule.cs"))?.RemoveTemplateCodeIfNot("EFCORE");
foreach (var file in context.Files)
{
if (file.Name.EndsWith(".cs") || file.Name.EndsWith(".csproj") || file.Name.EndsWith(".json"))
{
file.RemoveTemplateCodeIfNot("EFCORE");
}
}
}
}
}

@ -7,8 +7,15 @@ namespace Volo.Abp.Cli.ProjectBuilding.Building.Steps
{
public override void Execute(ProjectBuildContext context)
{
context.Files.FirstOrDefault(f => f.Name.EndsWith("MyCompanyName.MyProjectName.Domain.Shared.csproj"))?.RemoveTemplateCodeIf("CMS-KIT");
context.Files.FirstOrDefault(f => f.Name.EndsWith("MyProjectNameDomainSharedModule.cs"))?.RemoveTemplateCodeIf("CMS-KIT");
var commonFiles = context.Files.Where(f =>
f.Name.EndsWith(".csproj") ||
f.Name.EndsWith(".cs") ||
f.Name.EndsWith(".cshtml"));
foreach (var file in commonFiles)
{
file.RemoveTemplateCodeIf("CMS-KIT");
}
}
}
}

@ -0,0 +1,54 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace Volo.Abp.Cli.ProjectBuilding.Building.Steps
{
public class RemoveProjectFromTyeStep : ProjectBuildPipelineStep
{
private readonly string _name;
public RemoveProjectFromTyeStep(string name)
{
_name = name;
}
public override void Execute(ProjectBuildContext context)
{
var tyeFile = context.Files.FirstOrDefault(f => f.Name == "/tye.yaml");
if (tyeFile == null)
{
return;
}
var lines = tyeFile.GetLines();
var newLines = new List<string>();
var nameLine = $"- name:";
var isOneOfTargetLines = false;
foreach (var line in lines)
{
if (line.Equals($"{nameLine} {_name}"))
{
isOneOfTargetLines = true;
continue;
}
if (line.StartsWith(nameLine))
{
isOneOfTargetLines = false;
}
if (!isOneOfTargetLines)
{
newLines.Add(line);
}
}
tyeFile.SetContent(String.Join(Environment.NewLine, newLines));
}
}
}

@ -8,16 +8,12 @@ namespace Volo.Abp.Cli.ProjectBuilding.Building.Steps
{
public override void Execute(ProjectBuildContext context)
{
context.Files.FirstOrDefault(f => f.Name.EndsWith("MyCompanyName.MyProjectName.Web.csproj"))?.RemoveTemplateCodeIfNot("PUBLIC-REDIS");
context.Files.FirstOrDefault(f => f.Name.EndsWith("MyProjectNameWebModule.cs"))?.RemoveTemplateCodeIfNot("PUBLIC-REDIS");
context.Files.FirstOrDefault(f => f.Name.EndsWith("MyCompanyName.MyProjectName.HttpApi.HostWithIds.csproj"))?.RemoveTemplateCodeIfNot("PUBLIC-REDIS");
context.Files.FirstOrDefault(f => f.Name.EndsWith("MyCompanyName.MyProjectName.HttpApi.Host.csproj"))?.RemoveTemplateCodeIfNot("PUBLIC-REDIS");
context.Files.FirstOrDefault(f => f.Name.EndsWith("MyProjectNameHttpApiHostModule.cs"))?.RemoveTemplateCodeIfNot("PUBLIC-REDIS");
var appSettingsFiles = context.Files.Where(f => f.Name.EndsWith("appSettings.json", StringComparison.InvariantCultureIgnoreCase)).ToList();
foreach (var appSettings in appSettingsFiles)
foreach (var file in context.Files)
{
appSettings.RemoveTemplateCodeIfNot("PUBLIC-REDIS");
if (file.Name.EndsWith(".cs") || file.Name.EndsWith(".csproj") || file.Name.EndsWith(".json"))
{
file.RemoveTemplateCodeIfNot("PUBLIC-REDIS");
}
}
}
}

@ -18,6 +18,14 @@ namespace Volo.Abp.Cli.ProjectBuilding.Building.Steps
context.BuildArgs.SolutionName.CompanyName,
context.BuildArgs.SolutionName.ProjectName
).Run();
new SolutionRenamer(
context.Files,
null,
"MyProjectName",
null,
SolutionName.Parse(context.BuildArgs.SolutionName.CompanyName).ProjectName
).Run();
}
else
{

@ -28,6 +28,11 @@ namespace Volo.Abp.Cli.ProjectBuilding.Files
public static void RemoveTemplateCodeMarkers(this FileEntry file)
{
if (!file.Content.Contains("</TEMPLATE-REMOVE>"))
{
return;
}
file.NormalizeLineEndings();
var lines = file.GetLines();
@ -59,6 +64,11 @@ namespace Volo.Abp.Cli.ProjectBuilding.Files
private static void RemoveMarkedTemplateCode(this FileEntry file, string beginMark)
{
if (!file.Content.Contains("</TEMPLATE-REMOVE>"))
{
return;
}
file.NormalizeLineEndings();
var lines = file.GetLines();

@ -31,6 +31,7 @@ namespace Volo.Abp.Cli.ProjectBuilding.Templates.Microservice
var steps = new List<ProjectBuildPipelineStep>();
DeleteUnrelatedUiProject(context, steps);
SetRandomPortForHostProject(context, steps);
RandomizeStringEncryption(context, steps);
return steps;
@ -54,6 +55,11 @@ namespace Volo.Abp.Cli.ProjectBuilding.Templates.Microservice
}
}
private static void SetRandomPortForHostProject(ProjectBuildContext context, List<ProjectBuildPipelineStep> steps)
{
steps.Add(new MicroserviceServiceRandomPortStep("44371"));
}
private static void RandomizeStringEncryption(ProjectBuildContext context, List<ProjectBuildPipelineStep> steps)
{
steps.Add(new RandomizeStringEncryptionStep());

@ -36,9 +36,13 @@ namespace Volo.Abp.Cli.ProjectBuilding.Templates.Microservice
steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.Web",null,
"/applications/web/src/MyCompanyName.MyProjectName.Web"));
steps.Add(new RemoveFolderStep("/applications/web"));
steps.Add(new RemoveProjectFromTyeStep("web"));
steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.Blazor",null,
"/applications/blazor/src/MyCompanyName.MyProjectName.Blazor"));
steps.Add(new RemoveFolderStep("/applications/blazor"));
steps.Add(new RemoveProjectFromTyeStep("blazor"));
steps.Add(new RemoveFolderStep("/angular"));
break;
@ -46,9 +50,12 @@ namespace Volo.Abp.Cli.ProjectBuilding.Templates.Microservice
steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.Web",null,
"/applications/web/src/MyCompanyName.MyProjectName.Web"));
steps.Add(new RemoveFolderStep("/applications/web"));
steps.Add(new RemoveProjectFromTyeStep("web"));
steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.Blazor",null,
"/applications/blazor/src/MyCompanyName.MyProjectName.Blazor"));
steps.Add(new RemoveFolderStep("/applications/blazor"));
steps.Add(new RemoveProjectFromTyeStep("blazor"));
break;
case UiFramework.Blazor:
@ -56,6 +63,7 @@ namespace Volo.Abp.Cli.ProjectBuilding.Templates.Microservice
"/applications/web/src/MyCompanyName.MyProjectName.Web"));
steps.Add(new RemoveFolderStep("/applications/web"));
steps.Add(new RemoveFolderStep("/angular"));
steps.Add(new RemoveProjectFromTyeStep("web"));
break;
case UiFramework.Mvc:
@ -63,6 +71,8 @@ namespace Volo.Abp.Cli.ProjectBuilding.Templates.Microservice
steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.Blazor",null,
"/applications/blazor/src/MyCompanyName.MyProjectName.Blazor"));
steps.Add(new RemoveFolderStep("/applications/blazor"));
steps.Add(new RemoveProjectFromTyeStep("blazor"));
steps.Add(new RemoveFolderStep("/angular"));
break;
}

@ -0,0 +1,12 @@
using Volo.Abp.Logging;
namespace Microsoft.Extensions.DependencyInjection
{
public static class ServiceCollectionLoggingExtensions
{
public static IInitLogger GetInitLogger(this IServiceCollection services)
{
return services.GetSingletonInstance<IInitLogger>();
}
}
}

@ -260,8 +260,9 @@ namespace System
/// </summary>
/// <param name="str">String to convert</param>
/// <param name="useCurrentCulture">set true to use current culture. Otherwise, invariant culture will be used.</param>
/// <param name="handleAbbreviations">set true to if you want to convert 'XYZ' to 'xyz'.</param>
/// <returns>camelCase of the string</returns>
public static string ToCamelCase(this string str, bool useCurrentCulture = false)
public static string ToCamelCase(this string str, bool useCurrentCulture = false, bool handleAbbreviations = false)
{
if (string.IsNullOrWhiteSpace(str))
{
@ -273,6 +274,11 @@ namespace System
return useCurrentCulture ? str.ToLower() : str.ToLowerInvariant();
}
if (handleAbbreviations && IsAllUpperCase(str))
{
return useCurrentCulture ? str.ToLower() : str.ToLowerInvariant();
}
return (useCurrentCulture ? char.ToLower(str[0]) : char.ToLowerInvariant(str[0])) + str.Substring(1);
}
@ -545,5 +551,18 @@ namespace System
return encoding.GetBytes(str);
}
private static bool IsAllUpperCase(string input)
{
for (int i = 0; i < input.Length; i++)
{
if (Char.IsLetter(input[i]) && !Char.IsUpper(input[i]))
{
return false;
}
}
return true;
}
}
}

@ -3,8 +3,10 @@ using System.Collections.Generic;
using System.Linq;
using JetBrains.Annotations;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Internal;
using Volo.Abp.Logging;
using Volo.Abp.Modularity;
namespace Volo.Abp
@ -71,11 +73,30 @@ namespace Volo.Abp
{
using (var scope = ServiceProvider.CreateScope())
{
WriteInitLogs(scope.ServiceProvider);
scope.ServiceProvider
.GetRequiredService<IModuleManager>()
.InitializeModules(new ApplicationInitializationContext(scope.ServiceProvider));
}
}
protected virtual void WriteInitLogs(IServiceProvider serviceProvider)
{
var logger = serviceProvider.GetService<ILogger<AbpApplicationBase>>();
if (logger == null)
{
return;
}
var initLogger = serviceProvider.GetRequiredService<IInitLogger>();
foreach (var entry in initLogger.Entries)
{
logger.LogWithLevel(entry.Level, entry.Message, entry.Exception);
}
initLogger.Entries.Clear();
}
protected virtual IReadOnlyList<IAbpModuleDescriptor> LoadModules(IServiceCollection services, AbpApplicationCreationOptions options)
{

@ -1,6 +1,7 @@
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Volo.Abp.Logging;
using Volo.Abp.Modularity;
using Volo.Abp.Reflection;
@ -35,6 +36,7 @@ namespace Volo.Abp.Internal
services.TryAddSingleton<IModuleLoader>(moduleLoader);
services.TryAddSingleton<IAssemblyFinder>(assemblyFinder);
services.TryAddSingleton<ITypeFinder>(typeFinder);
services.TryAddSingleton<IInitLogger>(new DefaultInitLogger());
services.AddAssemblyOf<IAbpApplication>();

@ -0,0 +1,26 @@
using System;
using JetBrains.Annotations;
using Microsoft.Extensions.Logging;
namespace Volo.Abp.Logging
{
public class AbpInitLogEntry
{
public LogLevel Level { get; }
public string Message { get; }
[CanBeNull]
public Exception Exception { get; }
public AbpInitLogEntry(
LogLevel level,
string message,
Exception exception)
{
Level = level;
Message = message;
Exception = exception;
}
}
}

@ -0,0 +1,24 @@
using System;
using System.Collections.Generic;
using Microsoft.Extensions.Logging;
namespace Volo.Abp.Logging
{
public class DefaultInitLogger : IInitLogger
{
public List<AbpInitLogEntry> Entries { get; }
public DefaultInitLogger()
{
Entries = new List<AbpInitLogEntry>();
}
public void Log(
LogLevel logLevel,
string message,
Exception exception = null)
{
Entries.Add(new AbpInitLogEntry(logLevel, message, exception));
}
}
}

@ -0,0 +1,16 @@
using System;
using System.Collections.Generic;
using Microsoft.Extensions.Logging;
namespace Volo.Abp.Logging
{
public interface IInitLogger
{
public List<AbpInitLogEntry> Entries { get; }
void Log(
LogLevel logLevel,
string message,
Exception exception = null);
}
}

@ -2,15 +2,18 @@
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Microsoft.Extensions.Logging;
using Volo.Abp.Logging;
namespace Volo.Abp.Modularity
{
internal static class AbpModuleHelper
{
public static List<Type> FindAllModuleTypes(Type startupModuleType)
public static List<Type> FindAllModuleTypes(Type startupModuleType, IInitLogger logger)
{
var moduleTypes = new List<Type>();
AddModuleAndDependenciesResursively(moduleTypes, startupModuleType);
logger.Log(LogLevel.Information, "Loaded ABP modules:");
AddModuleAndDependenciesResursively(moduleTypes, startupModuleType, logger);
return moduleTypes;
}
@ -35,7 +38,11 @@ namespace Volo.Abp.Modularity
return dependencies;
}
private static void AddModuleAndDependenciesResursively(List<Type> moduleTypes, Type moduleType)
private static void AddModuleAndDependenciesResursively(
List<Type> moduleTypes,
Type moduleType,
IInitLogger logger,
int depth = 0)
{
AbpModule.CheckAbpModuleType(moduleType);
@ -45,10 +52,11 @@ namespace Volo.Abp.Modularity
}
moduleTypes.Add(moduleType);
logger.Log(LogLevel.Information, $"{new string(' ', depth * 2)}- {moduleType.FullName}");
foreach (var dependedModuleType in FindDependedModuleTypes(moduleType))
{
AddModuleAndDependenciesResursively(moduleTypes, dependedModuleType);
AddModuleAndDependenciesResursively(moduleTypes, dependedModuleType, logger, depth + 1);
}
}
}

@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Linq;
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.Logging;
using Volo.Abp.Modularity.PlugIns;
namespace Volo.Abp.Modularity
@ -43,14 +44,16 @@ namespace Volo.Abp.Modularity
Type startupModuleType,
PlugInSourceList plugInSources)
{
var initLogger = services.GetInitLogger();
//All modules starting from the startup module
foreach (var moduleType in AbpModuleHelper.FindAllModuleTypes(startupModuleType))
foreach (var moduleType in AbpModuleHelper.FindAllModuleTypes(startupModuleType, initLogger))
{
modules.Add(CreateModuleDescriptor(services, moduleType));
}
//Plugin modules
foreach (var moduleType in plugInSources.GetAllModules())
foreach (var moduleType in plugInSources.GetAllModules(initLogger))
{
if (modules.Any(m => m.Type == moduleType))
{

@ -32,8 +32,6 @@ namespace Volo.Abp.Modularity
public void InitializeModules(ApplicationInitializationContext context)
{
LogListOfModules();
foreach (var contributor in _lifecycleContributors)
{
foreach (var module in _moduleContainer.Modules)
@ -52,16 +50,6 @@ namespace Volo.Abp.Modularity
_logger.LogInformation("Initialized all ABP modules.");
}
private void LogListOfModules()
{
_logger.LogInformation("Loaded ABP modules:");
foreach (var module in _moduleContainer.Modules)
{
_logger.LogInformation("- " + module.Type.FullName);
}
}
public void ShutdownModules(ApplicationShutdownContext context)
{
var modules = _moduleContainer.Modules.Reverse().ToList();

@ -1,19 +1,20 @@
using System;
using System.Linq;
using JetBrains.Annotations;
using Volo.Abp.Logging;
namespace Volo.Abp.Modularity.PlugIns
{
public static class PlugInSourceExtensions
{
[NotNull]
public static Type[] GetModulesWithAllDependencies([NotNull] this IPlugInSource plugInSource)
public static Type[] GetModulesWithAllDependencies([NotNull] this IPlugInSource plugInSource, IInitLogger logger)
{
Check.NotNull(plugInSource, nameof(plugInSource));
return plugInSource
.GetModules()
.SelectMany(AbpModuleHelper.FindAllModuleTypes)
.SelectMany(type => AbpModuleHelper.FindAllModuleTypes(type, logger))
.Distinct()
.ToArray();
}

@ -2,16 +2,17 @@
using System.Collections.Generic;
using System.Linq;
using JetBrains.Annotations;
using Volo.Abp.Logging;
namespace Volo.Abp.Modularity.PlugIns
{
public class PlugInSourceList : List<IPlugInSource>
{
[NotNull]
internal Type[] GetAllModules()
internal Type[] GetAllModules(IInitLogger logger)
{
return this
.SelectMany(pluginSource => pluginSource.GetModulesWithAllDependencies())
.SelectMany(pluginSource => pluginSource.GetModulesWithAllDependencies(logger))
.Distinct()
.ToArray();
}

@ -1,5 +1,6 @@
using Microsoft.Extensions.DependencyInjection;
using Shouldly;
using Volo.Abp.Logging;
using Volo.Abp.Modularity.PlugIns;
using Xunit;
@ -11,7 +12,12 @@ namespace Volo.Abp.Modularity
public void Should_Load_Modules_By_Dependency_Order()
{
var moduleLoader = new ModuleLoader();
var modules = moduleLoader.LoadModules(new ServiceCollection(), typeof(MyStartupModule), new PlugInSourceList());
var modules = moduleLoader.LoadModules(
new ServiceCollection()
.AddSingleton<IInitLogger>(new DefaultInitLogger()),
typeof(MyStartupModule),
new PlugInSourceList()
);
modules.Length.ShouldBe(2);
modules[0].Type.ShouldBe(typeof(IndependentEmptyModule));
modules[1].Type.ShouldBe(typeof(MyStartupModule));

@ -4,6 +4,8 @@ using System.Threading.Tasks;
using Volo.Abp.Application.Dtos;
using System.Collections.Generic;
using Microsoft.AspNetCore.Authorization;
using Microsoft.Extensions.Caching.Distributed;
using Volo.Abp.Caching;
using Volo.Blogging.Comments;
using Volo.Blogging.Tagging;
using Volo.Blogging.Tagging.Dtos;
@ -18,71 +20,41 @@ namespace Volo.Blogging.Posts
private readonly IPostRepository _postRepository;
private readonly ITagRepository _tagRepository;
private readonly ICommentRepository _commentRepository;
private readonly IDistributedCache<ListResultDto<PostWithDetailsDto>> _postsCache;
public PostAppService(IPostRepository postRepository, ITagRepository tagRepository, ICommentRepository commentRepository, IBlogUserLookupService userLookupService)
public PostAppService(IPostRepository postRepository, ITagRepository tagRepository, ICommentRepository commentRepository, IBlogUserLookupService userLookupService, IDistributedCache<ListResultDto<PostWithDetailsDto>> postsCache)
{
UserLookupService = userLookupService;
_postRepository = postRepository;
_tagRepository = tagRepository;
_commentRepository = commentRepository;
_postsCache = postsCache;
}
public async Task<ListResultDto<PostWithDetailsDto>> GetListByBlogIdAndTagName(Guid id, string tagName)
{
var posts = await _postRepository.GetPostsByBlogId(id);
var tag = tagName.IsNullOrWhiteSpace() ? null : await _tagRepository.FindByNameAsync(id, tagName);
var userDictionary = new Dictionary<Guid, BlogUserDto>();
var postDtos = new List<PostWithDetailsDto>(ObjectMapper.Map<List<Post>, List<PostWithDetailsDto>>(posts));
foreach (var postDto in postDtos)
{
postDto.Tags = await GetTagsOfPost(postDto.Id);
}
if (tag != null)
{
postDtos = await FilterPostsByTag(postDtos, tag);
}
foreach (var postDto in postDtos)
{
if (postDto.CreatorId.HasValue)
var cacheKey = id.ToString() + "-" + tagName;
return await _postsCache.GetOrAddAsync(
cacheKey,
async () => await GetPostsByBlogIdAndTagName(id, tagName),
() => new DistributedCacheEntryOptions
{
if (!userDictionary.ContainsKey(postDto.CreatorId.Value))
{
var creatorUser = await UserLookupService.FindByIdAsync(postDto.CreatorId.Value);
if (creatorUser != null)
{
userDictionary[creatorUser.Id] = ObjectMapper.Map<BlogUser, BlogUserDto>(creatorUser);
}
}
if (userDictionary.ContainsKey(postDto.CreatorId.Value))
{
postDto.Writer = userDictionary[(Guid)postDto.CreatorId];
}
AbsoluteExpiration = DateTimeOffset.Now.AddHours(6)
}
}
return new ListResultDto<PostWithDetailsDto>(postDtos);
);
}
public async Task<ListResultDto<PostWithDetailsDto>> GetTimeOrderedListAsync(Guid blogId)
{
var posts = await _postRepository.GetOrderedList(blogId);
var postDtos = new List<PostWithDetailsDto>(ObjectMapper.Map<List<Post>, List<PostWithDetailsDto>>(posts));
foreach (var postDto in postDtos)
{
var creatorUser = await UserLookupService.FindByIdAsync(postDto.CreatorId.Value);
if (creatorUser != null)
return await _postsCache.GetOrAddAsync(
blogId.ToString(),
async () => await GetTimeOrderedPostsAsync(blogId),
() => new DistributedCacheEntryOptions
{
postDto.Writer = ObjectMapper.Map<BlogUser, BlogUserDto>(creatorUser);
AbsoluteExpiration = DateTimeOffset.Now.AddHours(6)
}
}
return new ListResultDto<PostWithDetailsDto>(postDtos);
);
}
public async Task<PostWithDetailsDto> GetForReadingAsync(GetPostInput input)
@ -185,6 +157,64 @@ namespace Volo.Blogging.Posts
return ObjectMapper.Map<Post, PostWithDetailsDto>(post);
}
private async Task<ListResultDto<PostWithDetailsDto>> GetPostsByBlogIdAndTagName(Guid id, string tagName)
{
var posts = await _postRepository.GetPostsByBlogId(id);
var tag = tagName.IsNullOrWhiteSpace() ? null : await _tagRepository.FindByNameAsync(id, tagName);
var userDictionary = new Dictionary<Guid, BlogUserDto>();
var postDtos = new List<PostWithDetailsDto>(ObjectMapper.Map<List<Post>, List<PostWithDetailsDto>>(posts));
foreach (var postDto in postDtos)
{
postDto.Tags = await GetTagsOfPost(postDto.Id);
}
if (tag != null)
{
postDtos = await FilterPostsByTag(postDtos, tag);
}
foreach (var postDto in postDtos)
{
if (postDto.CreatorId.HasValue)
{
if (!userDictionary.ContainsKey(postDto.CreatorId.Value))
{
var creatorUser = await UserLookupService.FindByIdAsync(postDto.CreatorId.Value);
if (creatorUser != null)
{
userDictionary[creatorUser.Id] = ObjectMapper.Map<BlogUser, BlogUserDto>(creatorUser);
}
}
if (userDictionary.ContainsKey(postDto.CreatorId.Value))
{
postDto.Writer = userDictionary[(Guid)postDto.CreatorId];
}
}
}
return new ListResultDto<PostWithDetailsDto>(postDtos);
}
private async Task<ListResultDto<PostWithDetailsDto>> GetTimeOrderedPostsAsync(Guid blogId)
{
var posts = await _postRepository.GetOrderedList(blogId);
var postDtos = new List<PostWithDetailsDto>(ObjectMapper.Map<List<Post>, List<PostWithDetailsDto>>(posts));
foreach (var postDto in postDtos)
{
var creatorUser = await UserLookupService.FindByIdAsync(postDto.CreatorId.Value);
if (creatorUser != null)
{
postDto.Writer = ObjectMapper.Map<BlogUser, BlogUserDto>(creatorUser);
}
}
return new ListResultDto<PostWithDetailsDto>(postDtos);
}
private async Task<string> RenameUrlIfItAlreadyExistAsync(Guid blogId, string url, Post existingPost = null)
{
if (await _postRepository.IsPostUrlInUseAsync(blogId, url, existingPost?.Id))

@ -52,9 +52,9 @@ namespace Volo.CmsKit.Permissions
blogManagement.AddChild(CmsKitAdminPermissions.Blogs.Features, L("Permission:BlogManagement.Features"));
var blogPostManagement = cmsGroup.AddPermission(CmsKitAdminPermissions.BlogPosts.Default, L("Permission:BlogPostManagement"));
blogManagement.AddChild(CmsKitAdminPermissions.BlogPosts.Create, L("Permission:BlogPostManagement.Create"));
blogManagement.AddChild(CmsKitAdminPermissions.BlogPosts.Update, L("Permission:BlogPostManagement.Update"));
blogManagement.AddChild(CmsKitAdminPermissions.BlogPosts.Delete, L("Permission:BlogPostManagement.Delete"));
blogPostManagement.AddChild(CmsKitAdminPermissions.BlogPosts.Create, L("Permission:BlogPostManagement.Create"));
blogPostManagement.AddChild(CmsKitAdminPermissions.BlogPosts.Update, L("Permission:BlogPostManagement.Update"));
blogPostManagement.AddChild(CmsKitAdminPermissions.BlogPosts.Delete, L("Permission:BlogPostManagement.Delete"));
}
}

@ -5,6 +5,7 @@ using Volo.Abp.GlobalFeatures;
using Volo.Abp.Localization;
using Volo.Abp.Modularity;
using Volo.CmsKit.Blogs;
using Volo.CmsKit.Comments;
using Volo.CmsKit.GlobalFeatures;
using Volo.CmsKit.Localization;
using Volo.CmsKit.MediaDescriptors;
@ -27,6 +28,8 @@ namespace Volo.CmsKit.Admin
ConfigureTagOptions();
ConfigureCommentOptions();
Configure<AbpAutoMapperOptions>(options =>
{
options.AddMaps<CmsKitAdminApplicationModule>(validate: true);
@ -43,20 +46,20 @@ namespace Volo.CmsKit.Admin
new TagEntityTypeDefiniton(
BlogPostConsts.EntityType,
LocalizableString.Create<CmsKitResource>("BlogPost"),
createPolicies: new[]
createPolicies: new[]
{
CmsKitAdminPermissions.BlogPosts.Create,
CmsKitAdminPermissions.BlogPosts.Update
CmsKitAdminPermissions.BlogPosts.Create,
CmsKitAdminPermissions.BlogPosts.Update
},
updatePolicies: new[]
{
CmsKitAdminPermissions.BlogPosts.Create,
CmsKitAdminPermissions.BlogPosts.Update
updatePolicies: new[]
{
CmsKitAdminPermissions.BlogPosts.Create,
CmsKitAdminPermissions.BlogPosts.Update
},
deletePolicies: new[]
{
CmsKitAdminPermissions.BlogPosts.Create,
CmsKitAdminPermissions.BlogPosts.Update
deletePolicies: new[]
{
CmsKitAdminPermissions.BlogPosts.Create,
CmsKitAdminPermissions.BlogPosts.Update
}));
}
});
@ -70,9 +73,9 @@ namespace Volo.CmsKit.Admin
options.EntityTypes.AddIfNotContains(
new MediaDescriptorDefinition(
BlogPostConsts.EntityType,
createPolicies: new[]
{
CmsKitAdminPermissions.BlogPosts.Create,
createPolicies: new[]
{
CmsKitAdminPermissions.BlogPosts.Create,
CmsKitAdminPermissions.BlogPosts.Update
},
deletePolicies: new[]
@ -88,20 +91,36 @@ namespace Volo.CmsKit.Admin
options.EntityTypes.AddIfNotContains(
new MediaDescriptorDefinition(
PageConsts.EntityType,
createPolicies: new[]
createPolicies: new[]
{
CmsKitAdminPermissions.Pages.Create,
CmsKitAdminPermissions.Pages.Update
CmsKitAdminPermissions.Pages.Update
},
deletePolicies: new[]
{
CmsKitAdminPermissions.Pages.Create,
CmsKitAdminPermissions.Pages.Update,
CmsKitAdminPermissions.Pages.Delete
CmsKitAdminPermissions.Pages.Delete
}));
}
});
}
}
private void ConfigureCommentOptions()
{
if (GlobalFeatureManager.Instance.IsEnabled<CommentsFeature>())
{
Configure<CmsKitCommentOptions>(options =>
{
if (GlobalFeatureManager.Instance.IsEnabled<BlogsFeature>())
{
options.EntityTypes.AddIfNotContains(
new CommentEntityTypeDefinition(BlogPostConsts.EntityType));
}
});
}
}
}
}

@ -35,12 +35,11 @@ namespace Volo.CmsKit.Admin.MediaDescriptors
await CheckAnyOfPoliciesAsync(definition.CreatePolicies);
var newId = GuidGenerator.Create();
using (var stream = inputStream.GetStream())
{
var newEntity = await MediaDescriptorManager.CreateAsync(inputStream.EntityType, inputStream.Name, inputStream.ContentType, inputStream.ContentLength ?? 0);
await MediaContainer.SaveAsync(newId.ToString(), stream);
await MediaContainer.SaveAsync(newEntity.Id.ToString(), stream);
await MediaDescriptorRepository.InsertAsync(newEntity);
return ObjectMapper.Map<MediaDescriptor, MediaDescriptorDto>(newEntity);

@ -82,7 +82,7 @@ namespace Volo.CmsKit.Admin.Web
options.Conventions.AddPageRoute("/CmsKit/BlogPosts/Create", "/Cms/BlogPosts/Create");
options.Conventions.AddPageRoute("/CmsKit/BlogPosts/Update", "/Cms/BlogPosts/Update/{Id}");
options.Conventions.AddPageRoute("/CmsKit/Comments/Index", "/Cms/Comments");
options.Conventions.AddPageRoute("/CmsKit/Comments/Details", "/Cms/Details");
options.Conventions.AddPageRoute("/CmsKit/Comments/Details", "/Cms/Comments/{Id}");
});
Configure<AbpPageToolbarOptions>(options =>

@ -8,7 +8,7 @@
var $shortDescription = $('#ViewModel_ShortDescription');
var $url = $('#ViewModel_Slug');
var $buttonSubmit = $('#button-blog-post-create');
var $pageContentInput = $('#ViewModel_Value');
var $pageContentInput = $('#ViewModel_Content');
var $tagsInput = $('.tag-editor-form input[name=tags]');
var $fileInput = $('#BlogPostCoverImage');
var $tagsWrapper = $('#blog-post-tags-wrapper');

@ -60,7 +60,7 @@
{
text: l('Details'),
action: function (data) {
window.location = abp.appPath + 'CmsKit/Comments/Details/' + data.record.id;
location.href = 'Comments/' + data.record.id;
}
},
{

@ -19,7 +19,7 @@
success: function (result) {
abp.notify.success(l('SuccessfullySaved'));
abp.ui.clearBusy();
location.href = "/CmsKit/Pages/";
location.href = "../Pages";
}
});
}

@ -30,7 +30,7 @@
text: l('Edit'),
visible: abp.auth.isGranted('CmsKit.Pages.Update'),
action: function (data) {
location.href = '/CmsKit/Pages/Update/' + data.record.id;
location.href = 'Pages/Update/' + data.record.id;
}
},
{
@ -76,6 +76,6 @@
$('#AbpContentToolbar button[name=CreatePage]').on('click', function (e) {
e.preventDefault();
window.location.href = "/CmsKit/Pages/Create"
window.location.href = "Pages/Create"
});
});

@ -18,7 +18,7 @@
success: function (result) {
abp.notify.success(l('SuccessfullySaved'));
abp.ui.clearBusy();
location.href = "/CmsKit/Pages/";
location.href = "../../Pages";
}
});
}

@ -24,6 +24,11 @@
{
public const string SlugAlreadyExist = "CmsKit:BlogPost:0001";
}
public static class Comments
{
public const string EntityNotCommentable = "CmsKit:Comments:0001";
}
public static class MediaDescriptors
{

@ -17,10 +17,11 @@
"CmsKit:0003": "The entity {0} is not taggable.",
"CmsKit:Blog:0001": "The given slug ({Slug}) already exists!",
"CmsKit:BlogPost:0001": "The given slug already exists!",
"CmsKit:Comments:0001": "The entity {0} is not commentable.",
"CmsKit:Media:0001": "'{Name}' is not a valid media name.",
"CmsKit:Media:0002": "The entity can't have media.",
"CmsKit:Page:0001": "The given url ({0}) already exists.",
"CmsKit:Tag:0002": "The entity is not taggable!",
"CmsKit:Media:0002": "The entity can't have media.",
"CommentAuthorizationExceptionMessage": "Those comments are not allowed for public display.",
"CommentDeletionConfirmationMessage": "This comment and all replies will be deleted!",
"Comments": "Comments",

@ -13,6 +13,7 @@
"CmsKit.Ratings": "Puanlama",
"CmsKit.Reactions": "Tepkiler",
"CmsKit.Tags": "Etiketler",
"CmsKit:Comments:0001": "{0} ögesi yorumlanabilir değil.",
"CmsKit:0002": "İçerik zaten mevcut!",
"CmsKit:0003": "{0} ögesi etiketlenebilir değil.",
"CmsKit:BlogPost:0001": "Aynı url etiketi zaten mevcut.",

@ -0,0 +1,11 @@
using JetBrains.Annotations;
using System.Collections.Generic;
namespace Volo.CmsKit.Comments
{
public class CmsKitCommentOptions
{
[NotNull]
public List<CommentEntityTypeDefinition> EntityTypes { get; } = new List<CommentEntityTypeDefinition>();
}
}

@ -28,7 +28,7 @@ namespace Volo.CmsKit.Comments
}
public Comment(
internal Comment(
Guid id,
[NotNull] string entityType,
[NotNull] string entityId,

@ -0,0 +1,21 @@
using JetBrains.Annotations;
using System;
using Volo.Abp;
namespace Volo.CmsKit.Comments
{
public class CommentEntityTypeDefinition : IEquatable<CommentEntityTypeDefinition>
{
public CommentEntityTypeDefinition([NotNull] string entityType)
{
EntityType = Check.NotNullOrEmpty(entityType, nameof(entityType));
}
public string EntityType { get; }
public bool Equals(CommentEntityTypeDefinition other)
{
return EntityType == other?.EntityType;
}
}
}

@ -0,0 +1,46 @@

using JetBrains.Annotations;
using System;
using System.Threading.Tasks;
using Volo.Abp;
using Volo.Abp.Domain.Services;
using Volo.CmsKit.Users;
namespace Volo.CmsKit.Comments
{
public class CommentManager : DomainService
{
protected ICommentEntityTypeDefinitionStore DefinitionStore { get; }
public CommentManager(ICommentEntityTypeDefinitionStore definitionStore)
{
DefinitionStore = definitionStore;
}
public virtual async Task<Comment> CreateAsync([NotNull] CmsUser creator,
[NotNull] string entityType,
[NotNull] string entityId,
[NotNull] string text,
[CanBeNull] Guid? repliedCommentId = null)
{
Check.NotNull(creator, nameof(creator));
Check.NotNullOrWhiteSpace(entityType, nameof(entityType), CommentConsts.MaxEntityTypeLength);
Check.NotNullOrWhiteSpace(entityId, nameof(entityId), CommentConsts.MaxEntityIdLength);
Check.NotNullOrWhiteSpace(text, nameof(text), CommentConsts.MaxTextLength);
if (!await DefinitionStore.IsDefinedAsync(entityType))
{
throw new EntityNotCommentableException(entityType);
}
return new Comment(
GuidGenerator.Create(),
entityType,
entityId,
text,
repliedCommentId,
creator.Id,
CurrentTenant.Id);
}
}
}

@ -0,0 +1,41 @@
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.Comments
{
public class DefaultCommentEntityTypeDefinitionStore : ICommentEntityTypeDefinitionStore, ITransientDependency
{
protected CmsKitCommentOptions Options { get; }
public DefaultCommentEntityTypeDefinitionStore(IOptions<CmsKitCommentOptions> options)
{
Options = options.Value;
}
public virtual Task<CommentEntityTypeDefinition> GetDefinitionAsync([NotNull] string entityType)
{
Check.NotNullOrWhiteSpace(entityType, nameof(entityType));
var result = Options.EntityTypes.SingleOrDefault(x => x.EntityType.Equals(entityType, StringComparison.InvariantCultureIgnoreCase)) ??
throw new EntityNotCommentableException(entityType);
return Task.FromResult(result);
}
public virtual Task<bool> IsDefinedAsync([NotNull] string entityType)
{
Check.NotNullOrWhiteSpace(entityType, nameof(entityType));
var isDefined = Options.EntityTypes.Any(x => x.EntityType == entityType);
return Task.FromResult(isDefined);
}
}
}

@ -0,0 +1,27 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.Text;
using System.Threading.Tasks;
using Volo.Abp;
namespace Volo.CmsKit.Comments
{
[Serializable]
public class EntityNotCommentableException : BusinessException
{
public EntityNotCommentableException(SerializationInfo serializationInfo, StreamingContext context) : base(serializationInfo, context)
{
}
public EntityNotCommentableException(string entityType)
{
Code = CmsKitErrorCodes.Comments.EntityNotCommentable;
EntityType = entityType;
WithData(nameof(EntityType), EntityType);
}
public string EntityType { get; }
}
}

@ -0,0 +1,17 @@
using JetBrains.Annotations;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Volo.CmsKit.Comments;
namespace Volo.CmsKit.Comments
{
public interface ICommentEntityTypeDefinitionStore
{
Task<CommentEntityTypeDefinition> GetDefinitionAsync([NotNull] string entityType);
Task<bool> IsDefinedAsync([NotNull] string entityType);
}
}

@ -9,9 +9,9 @@
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Volo.CmsKit.Admin.Application\Volo.CmsKit.Admin.Application.csproj" />
<ProjectReference Include="..\Volo.CmsKit.Admin.HttpApi\Volo.CmsKit.Admin.HttpApi.csproj" />
<ProjectReference Include="..\Volo.CmsKit.Application.Contracts\Volo.CmsKit.Application.Contracts.csproj" />
<ProjectReference Include="..\Volo.CmsKit.Public.Application\Volo.CmsKit.Public.Application.csproj" />
<ProjectReference Include="..\Volo.CmsKit.Public.HttpApi\Volo.CmsKit.Public.HttpApi.csproj" />
</ItemGroup>
</Project>

@ -5,8 +5,8 @@ using Volo.CmsKit.Public;
namespace Volo.CmsKit
{
[DependsOn(
typeof(CmsKitAdminApplicationModule),
typeof(CmsKitPublicApplicationModule),
typeof(CmsKitAdminHttpApiModule),
typeof(CmsKitPublicHttpApiModule),
typeof(CmsKitApplicationContractsModule)
)]
public class CmsKitHttpApiModule : AbpModule

@ -21,20 +21,20 @@ namespace Volo.CmsKit.Public.Comments
protected ICommentRepository CommentRepository { get; }
protected ICmsUserLookupService CmsUserLookupService { get; }
public IDistributedEventBus DistributedEventBus { get; }
public IUnitOfWorkManager UnitOfWorkManager { get; }
protected CommentManager CommentManager { get; }
public CommentPublicAppService(
ICommentRepository commentRepository,
ICmsUserLookupService cmsUserLookupService,
IDistributedEventBus distributedEventBus,
IUnitOfWorkManager unitOfWorkManager,
IOptions<CmsKitOptions> cmsKitOptions)
IOptions<CmsKitOptions> cmsKitOptions,
CommentManager commentManager)
{
CmsKitOptions = cmsKitOptions.Value;
CommentRepository = commentRepository;
CmsUserLookupService = cmsUserLookupService;
DistributedEventBus = distributedEventBus;
UnitOfWorkManager = unitOfWorkManager;
CommentManager = commentManager;
}
public virtual async Task<ListResultDto<CommentWithDetailsDto>> GetListAsync(string entityType, string entityId)
@ -58,14 +58,12 @@ namespace Volo.CmsKit.Public.Comments
}
var comment = await CommentRepository.InsertAsync(
new Comment(
GuidGenerator.Create(),
await CommentManager.CreateAsync(
user,
entityType,
entityId,
input.Text,
input.RepliedCommentId,
user.Id,
CurrentTenant.Id
input.RepliedCommentId
)
);

@ -4,10 +4,10 @@
@inject IStringLocalizer<CmsKitResource> L
<h1>@Model.Title</h1>
<abp-card>
<abp-card-header>
<abp-card-title>@Model.Title</abp-card-title>
<abp-card-subtitle>@Model.Author?.UserName</abp-card-subtitle>
</abp-card-header>

@ -3,9 +3,7 @@
@model Volo.CmsKit.Public.Web.Pages.CmsKit.Shared.Components.Pages.PageViewModel
@inject IPageLayout PageLayout
@{
PageLayout.Content.Title = Model.Title;
}
<h1>@Model.Title</h1>
<abp-card>
<abp-card-body>

@ -13,10 +13,6 @@
@model Volo.CmsKit.Public.Web.Pages.Public.CmsKit.Blogs.BlogPostModel
@{
PageLayout.Content.Title = Model.BlogPost.Title;
}
@await Component.InvokeAsync(typeof(DefaultBlogPostViewComponent), new
{
Model.BlogSlug,

@ -0,0 +1,59 @@
using Shouldly;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Volo.CmsKit.Users;
using Xunit;
namespace Volo.CmsKit.Comments
{
public class CommentManager_Test : CmsKitDomainTestBase
{
private readonly CommentManager commentManager;
private readonly CmsKitTestData testData;
private readonly ICmsUserRepository userRepository;
public CommentManager_Test()
{
commentManager = GetRequiredService<CommentManager>();
testData = GetRequiredService<CmsKitTestData>();
userRepository = GetRequiredService<ICmsUserRepository>();
}
[Fact]
public async Task CreateAsync_ShouldWorkProperly_WithCorrectData()
{
var creator = await userRepository.GetAsync(testData.User1Id);
var text = "Thank you for the article. It's awesome";
var comment = await commentManager.CreateAsync(creator, testData.EntityType1, testData.EntityId1, text);
comment.Id.ShouldNotBe(Guid.Empty);
comment.CreatorId.ShouldBe(creator.Id);
comment.EntityType.ShouldBe(testData.EntityType1);
comment.EntityId.ShouldBe(testData.EntityId1);
comment.Text.ShouldBe(text);
}
[Fact]
public async Task CreateAsync_ShouldThrowException_WithNotConfiguredEntityType()
{
var creator = await userRepository.GetAsync(testData.User1Id);
var notConfiguredEntityType = "Some.New.Entity";
var text = "Thank you for the article. It's awesome";
var exception = await Should.ThrowAsync<EntityNotCommentableException>(async () =>
await commentManager.CreateAsync(
creator,
notConfiguredEntityType,
testData.EntityId1,
text));
exception.ShouldNotBeNull();
exception.EntityType.ShouldBe(notConfiguredEntityType);
}
}
}

@ -45,6 +45,7 @@ namespace Volo.CmsKit
private readonly IBlobContainer<MediaContainer> _mediaBlobContainer;
private readonly BlogManager _blogManager;
private readonly IOptions<CmsKitMediaOptions> _mediaOptions;
private readonly IOptions<CmsKitCommentOptions> _commentsOptions;
public CmsKitDataSeedContributor(
IGuidGenerator guidGenerator,
@ -64,11 +65,12 @@ namespace Volo.CmsKit
IBlogFeatureRepository blogFeatureRepository,
EntityTagManager entityTagManager,
IOptions<CmsKitOptions> options,
IOptions<CmsKitTagOptions> tagOptions,
IMediaDescriptorRepository mediaDescriptorRepository,
IBlobContainer<MediaContainer> mediaBlobContainer,
IOptions<CmsKitTagOptions> tagOptions,
IMediaDescriptorRepository mediaDescriptorRepository,
IBlobContainer<MediaContainer> mediaBlobContainer,
BlogManager blogManager,
IOptions<CmsKitMediaOptions> cmsMediaOptions)
IOptions<CmsKitMediaOptions> cmsMediaOptions,
IOptions<CmsKitCommentOptions> commentsOptions)
{
_guidGenerator = guidGenerator;
_cmsUserRepository = cmsUserRepository;
@ -92,6 +94,7 @@ namespace Volo.CmsKit
_mediaBlobContainer = mediaBlobContainer;
_blogManager = blogManager;
_mediaOptions = cmsMediaOptions;
_commentsOptions = commentsOptions;
}
public async Task SeedAsync(DataSeedContext context)
@ -134,6 +137,9 @@ namespace Volo.CmsKit
createPolicies: new[] { "SomeCreatePolicy" },
deletePolicies: new[] { "SomeDeletePolicy" }));
_commentsOptions.Value.EntityTypes.Add(
new CommentEntityTypeDefinition(_cmsKitTestData.EntityType1));
return Task.CompletedTask;
}

@ -0,0 +1,16 @@
using System;
namespace Volo.Abp.Identity
{
[Serializable]
public class IdentityRoleNameChangedEto
{
public Guid Id { get; set; }
public Guid? TenantId { get; set; }
public string Name { get; set; }
public string OldName { get; set; }
}
}

@ -40,6 +40,9 @@ namespace Volo.Abp.Identity
options.EtoMappings.Add<IdentityClaimType, IdentityClaimTypeEto>(typeof(AbpIdentityDomainModule));
options.EtoMappings.Add<IdentityRole, IdentityRoleEto>(typeof(AbpIdentityDomainModule));
options.EtoMappings.Add<OrganizationUnit, OrganizationUnitEto>(typeof(AbpIdentityDomainModule));
options.AutoEventSelectors.Add<IdentityUser>();
options.AutoEventSelectors.Add<IdentityRole>();
});
var identityBuilder = context.Services.AddAbpIdentity(options =>

@ -108,12 +108,24 @@ namespace Volo.Abp.Identity
Name = name;
AddLocalEvent(
#pragma warning disable 618
new IdentityRoleNameChangedEvent
#pragma warning restore 618
{
IdentityRole = this,
OldName = oldName
}
);
AddDistributedEvent(
new IdentityRoleNameChangedEto
{
Id = Id,
Name = Name,
OldName = oldName,
TenantId = TenantId
}
);
}
public override string ToString()

@ -1,5 +1,8 @@
namespace Volo.Abp.Identity
using System;
namespace Volo.Abp.Identity
{
[Obsolete("Use the distributed event (IdentityRoleNameChangedEto) instead.")]
public class IdentityRoleNameChangedEvent
{
public IdentityRole IdentityRole { get; set; }

@ -1,26 +1,27 @@
using System.Threading.Tasks;
using Volo.Abp.Authorization.Permissions;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Domain.Entities.Events;
using Volo.Abp.Domain.Entities.Events.Distributed;
using Volo.Abp.EventBus;
using Volo.Abp.EventBus.Distributed;
using Volo.Abp.Identity;
namespace Volo.Abp.PermissionManagement.Identity
{
// public class RoleDeletedEventHandler :
// ILocalEventHandler<EntityDeletedEventData<IdentityRole>>,
// ITransientDependency
// {
// protected IPermissionManager PermissionManager { get; }
//
// public RoleDeletedEventHandler(IPermissionManager permissionManager)
// {
// PermissionManager = permissionManager;
// }
//
// public virtual async Task HandleEventAsync(EntityDeletedEventData<IdentityRole> eventData)
// {
// await PermissionManager.DeleteAsync(RolePermissionValueProvider.ProviderName, eventData.Entity.Name);
// }
// }
public class RoleDeletedEventHandler :
IDistributedEventHandler<EntityDeletedEto<IdentityRoleEto>>,
ITransientDependency
{
protected IPermissionManager PermissionManager { get; }
public RoleDeletedEventHandler(IPermissionManager permissionManager)
{
PermissionManager = permissionManager;
}
public async Task HandleEventAsync(EntityDeletedEto<IdentityRoleEto> eventData)
{
await PermissionManager.DeleteAsync(RolePermissionValueProvider.ProviderName, eventData.Entity.Name);
}
}
}

@ -1,45 +1,33 @@
using System.Threading.Tasks;
using Volo.Abp.Authorization.Permissions;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Domain.Entities.Events;
using Volo.Abp.EventBus;
using Volo.Abp.EventBus.Distributed;
using Volo.Abp.Identity;
namespace Volo.Abp.PermissionManagement.Identity
{
//TODO: This code can not be here!
public class RoleUpdateEventHandler :
IDistributedEventHandler<IdentityRoleNameChangedEto>,
ITransientDependency
{
protected IPermissionManager PermissionManager { get; }
protected IPermissionGrantRepository PermissionGrantRepository { get; }
public RoleUpdateEventHandler(
IPermissionManager permissionManager,
IPermissionGrantRepository permissionGrantRepository)
{
PermissionManager = permissionManager;
PermissionGrantRepository = permissionGrantRepository;
}
// public class RoleUpdateEventHandler :
// ILocalEventHandler<IdentityRoleNameChangedEvent>,
// ITransientDependency
// {
// protected IIdentityRoleRepository RoleRepository { get; }
// protected IPermissionManager PermissionManager { get; }
// protected IPermissionGrantRepository PermissionGrantRepository { get; }
//
// public RoleUpdateEventHandler(
// IIdentityRoleRepository roleRepository,
// IPermissionManager permissionManager,
// IPermissionGrantRepository permissionGrantRepository)
// {
// RoleRepository = roleRepository;
// PermissionManager = permissionManager;
// PermissionGrantRepository = permissionGrantRepository;
// }
//
// public virtual async Task HandleEventAsync(IdentityRoleNameChangedEvent eventData)
// {
// var role = await RoleRepository.FindAsync(eventData.IdentityRole.Id, false);
// if (role == null)
// {
// return;
// }
//
// var permissionGrantsInRole = await PermissionGrantRepository.GetListAsync(RolePermissionValueProvider.ProviderName, eventData.OldName);
// foreach (var permissionGrant in permissionGrantsInRole)
// {
// await PermissionManager.UpdateProviderKeyAsync(permissionGrant, eventData.IdentityRole.Name);
// }
// }
// }
public async Task HandleEventAsync(IdentityRoleNameChangedEto eventData)
{
var permissionGrantsInRole = await PermissionGrantRepository.GetListAsync(RolePermissionValueProvider.ProviderName, eventData.OldName);
foreach (var permissionGrant in permissionGrantsInRole)
{
await PermissionManager.UpdateProviderKeyAsync(permissionGrant, eventData.Name);
}
}
}
}

@ -0,0 +1,105 @@
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Identity;
using Shouldly;
using System.Threading.Tasks;
using Microsoft.Extensions.Options;
using Volo.Abp.Caching;
using Volo.Abp.Domain.Entities.Events.Distributed;
using Volo.Abp.EventBus.Distributed;
using Volo.Abp.PermissionManagement;
using Volo.Abp.PermissionManagement.Identity;
using Volo.Abp.Uow;
using Xunit;
namespace Volo.Abp.Identity
{
public class Distributed_Role_Change_Events_Test : AbpIdentityDomainTestBase
{
protected readonly IIdentityRoleRepository RoleRepository;
protected readonly IPermissionGrantRepository PermissionGrantRepository;
protected readonly IdentityRoleManager RoleManager;
protected readonly ILookupNormalizer LookupNormalizer;
protected readonly IUnitOfWorkManager UowManager;
protected readonly IDistributedCache<PermissionGrantCacheItem> Cache;
public Distributed_Role_Change_Events_Test()
{
RoleRepository = GetRequiredService<IIdentityRoleRepository>();
;
PermissionGrantRepository = GetRequiredService<IPermissionGrantRepository>();
;
RoleManager = GetRequiredService<IdentityRoleManager>();
;
LookupNormalizer = GetRequiredService<ILookupNormalizer>();
;
UowManager = GetRequiredService<IUnitOfWorkManager>();
Cache = GetRequiredService<IDistributedCache<PermissionGrantCacheItem>>();
}
[Fact]
public void Should_Register_Handler()
{
var x = GetRequiredService<IOptions<AbpDistributedEntityEventOptions>>();
GetRequiredService<IOptions<AbpDistributedEntityEventOptions>>()
.Value
.AutoEventSelectors
.ShouldContain(m => m.Name == "Entity:" + typeof(IdentityRole).FullName);
GetRequiredService<IOptions<AbpDistributedEventBusOptions>>()
.Value
.Handlers
.ShouldContain(h => h == typeof(RoleUpdateEventHandler) || h == typeof(RoleDeletedEventHandler));
}
[Fact]
public async Task Role_Updated_Distributed_Event_Test()
{
var role = await RoleRepository.FindByNormalizedNameAsync(LookupNormalizer.NormalizeName("moderator"));
var permissionGrantsInRole = await PermissionGrantRepository.GetListAsync("R", role.Name);
permissionGrantsInRole.ShouldNotBeNull();
permissionGrantsInRole.Count.ShouldBeGreaterThan(0);
var count = permissionGrantsInRole.Count;
using (var uow = UowManager.Begin())
{
var identityResult = await RoleManager.SetRoleNameAsync(role, "TestModerator");
identityResult.Succeeded.ShouldBeTrue();
await RoleRepository.UpdateAsync(role);
await uow.CompleteAsync();
}
role = await RoleRepository.GetAsync(role.Id);
role.Name.ShouldBe("TestModerator");
permissionGrantsInRole = await PermissionGrantRepository.GetListAsync("R", role.Name);
permissionGrantsInRole.Count.ShouldBe(count);
}
[Fact]
public async Task Role_Deleted_Distributed_Event_Test()
{
var role = await RoleRepository.FindByNormalizedNameAsync(LookupNormalizer.NormalizeName("moderator"));
var permissionGrantsInRole = await PermissionGrantRepository.GetListAsync("R", role.Name);
var caches = permissionGrantsInRole.Select(x => new KeyValuePair<string, PermissionGrantCacheItem>(
PermissionGrantCacheItem.CalculateCacheKey(x.Name, x.ProviderName, x.ProviderKey),
new PermissionGrantCacheItem(true))).ToList();
await Cache.SetManyAsync(caches);
using (var uow = UowManager.Begin())
{
await RoleRepository.DeleteAsync(role);
await uow.CompleteAsync();
}
var permissionGrantCaches = await Cache.GetManyAsync(caches.Select(x=>x.Key));
foreach (var cache in permissionGrantCaches)
{
cache.Value.ShouldBeNull();
}
}
}
}

@ -1,60 +0,0 @@
using Microsoft.AspNetCore.Identity;
using Shouldly;
using System.Threading.Tasks;
using Volo.Abp.EventBus.Distributed;
using Volo.Abp.Guids;
using Volo.Abp.PermissionManagement;
using Volo.Abp.Uow;
using Xunit;
namespace Volo.Abp.Identity
{
//TODO: This code can not be here!
//https://github.com/abpframework/abp/commit/847f526041145b62376b760776829d5ce257da1c
// public class RoleChangingEvents_Test : AbpIdentityDomainTestBase
// {
// protected readonly IIdentityRoleRepository RoleRepository;
// protected readonly IPermissionGrantRepository PermissionGrantRepository;
// protected readonly IdentityRoleManager RoleManager;
// protected readonly ILookupNormalizer LookupNormalizer;
// protected readonly IGuidGenerator GuidGenerator;
// protected readonly IUnitOfWorkManager UowManager;
//
// public RoleChangingEvents_Test()
// {
// RoleRepository = GetRequiredService<IIdentityRoleRepository>(); ;
// PermissionGrantRepository = GetRequiredService<IPermissionGrantRepository>(); ;
// RoleManager = GetRequiredService<IdentityRoleManager>(); ;
// LookupNormalizer = GetRequiredService<ILookupNormalizer>(); ;
// GuidGenerator = GetRequiredService<IGuidGenerator>();
// UowManager = GetRequiredService<IUnitOfWorkManager>();
// }
//
// [Fact(Skip = "https://github.com/abpframework/abp/actions/runs/454248191")]
// public async Task Role_Update_Event_Test()
// {
// var role = await RoleRepository
// .FindByNormalizedNameAsync(LookupNormalizer.NormalizeName("moderator"))
// ;
//
// var permissionGrantsInRole = await PermissionGrantRepository.GetListAsync("R", role.Name);
// permissionGrantsInRole.ShouldNotBeNull();
// permissionGrantsInRole.Count.ShouldBeGreaterThan(0);
// var count = permissionGrantsInRole.Count;
//
// using (var uow = UowManager.Begin())
// {
// var identityResult = await RoleManager.SetRoleNameAsync(role, "TestModerator");
// identityResult.Succeeded.ShouldBeTrue();
// var xx = await RoleRepository.UpdateAsync(role);
// await uow.CompleteAsync();
// }
//
// role = await RoleRepository.GetAsync(role.Id);
// role.Name.ShouldBe("TestModerator");
//
// permissionGrantsInRole = await PermissionGrantRepository.GetListAsync("R", role.Name);
// permissionGrantsInRole.Count.ShouldBe(count);
// }
// }
}

@ -7,7 +7,9 @@ using Volo.Abp.MultiTenancy;
namespace Volo.Abp.PermissionManagement
{
public class PermissionGrantCacheItemInvalidator : ILocalEventHandler<EntityChangedEventData<PermissionGrant>>, ITransientDependency
public class PermissionGrantCacheItemInvalidator :
ILocalEventHandler<EntityChangedEventData<PermissionGrant>>,
ITransientDependency
{
protected ICurrentTenant CurrentTenant { get; }

@ -5,6 +5,7 @@ using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Volo.Abp.Authorization.Permissions;
using Volo.Abp.Caching;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Guids;
using Volo.Abp.MultiTenancy;
@ -24,6 +25,8 @@ namespace Volo.Abp.PermissionManagement
protected IReadOnlyList<IPermissionManagementProvider> ManagementProviders => _lazyProviders.Value;
protected PermissionManagementOptions Options { get; }
protected IDistributedCache<PermissionGrantCacheItem> Cache { get; }
private readonly Lazy<List<IPermissionManagementProvider>> _lazyProviders;
@ -33,10 +36,12 @@ namespace Volo.Abp.PermissionManagement
IServiceProvider serviceProvider,
IGuidGenerator guidGenerator,
IOptions<PermissionManagementOptions> options,
ICurrentTenant currentTenant)
ICurrentTenant currentTenant,
IDistributedCache<PermissionGrantCacheItem> cache)
{
GuidGenerator = guidGenerator;
CurrentTenant = currentTenant;
Cache = cache;
PermissionGrantRepository = permissionGrantRepository;
PermissionDefinitionManager = permissionDefinitionManager;
Options = options.Value;
@ -104,9 +109,21 @@ namespace Volo.Abp.PermissionManagement
await provider.SetAsync(permissionName, providerKey, isGranted);
}
public virtual async Task<PermissionGrant> UpdateProviderKeyAsync(PermissionGrant permissionGrant, string providerKey)
{
using (CurrentTenant.Change(permissionGrant.TenantId))
{
//Invalidating the cache for the old key
await Cache.RemoveAsync(
PermissionGrantCacheItem.CalculateCacheKey(
permissionGrant.Name,
permissionGrant.ProviderName,
permissionGrant.ProviderKey
)
);
}
permissionGrant.ProviderKey = providerKey;
return await PermissionGrantRepository.UpdateAsync(permissionGrant);
}
@ -114,7 +131,6 @@ namespace Volo.Abp.PermissionManagement
public virtual async Task DeleteAsync(string providerName, string providerKey)
{
var permissionGrants = await PermissionGrantRepository.GetListAsync(providerName, providerKey);
//TODO: Use DeleteManyAsync method
foreach (var permissionGrant in permissionGrants)
{
await PermissionGrantRepository.DeleteAsync(permissionGrant);

@ -5,7 +5,9 @@
"access": "public"
},
"dependencies": {
"@abp/star-rating-svg": "~4.2.1"
"@abp/star-rating-svg": "~4.2.1",
"@abp/tui-editor": "^4.2.1",
"slugify": "^1.4.7"
},
"gitHead": "bb4ea17d5996f01889134c138d00b6c8f858a431"
}

@ -3,6 +3,7 @@
"@node_modules/to-mark/dist/to-mark.min.js": "@libs/to-mark/",
"@node_modules/tui-code-snippet/dist/*.*": "@libs/tui-code-snippet/",
"@node_modules/squire-rte/build/squire.js": "@libs/squire-rte/",
"@node_modules/tui-editor/dist/*.*": "@libs/tui-editor/"
"@node_modules/tui-editor/dist/*.*": "@libs/tui-editor/",
"@node_modules/@abp/tui-editor/src/*.*": "@libs/tui-editor/"
}
}

@ -0,0 +1,7 @@
/*
https://jquery.com/upgrade-guide/3.5/#jquery-htmlprefilter-changes
*/
var rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([a-z][^\/\0>\x20\t\r\n\f]*)[^>]*)\/>/gi;
jQuery.htmlPrefilter = function( html ) {
return html.replace( rxhtmlTag, "<$1></$2>" );
};

@ -1,9 +1,4 @@
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
@ -22,19 +17,6 @@ namespace MyCompanyName.MyProjectName
private readonly IAbpApplicationWithExternalServiceProvider _application;
public App()
{
_host = Host
.CreateDefaultBuilder(null)
.UseAutofac()
.UseSerilog()
.ConfigureServices((hostContext, services) =>
{
services.AddApplication<MyProjectNameModule>();
}).Build();
_application = _host.Services.GetService<IAbpApplicationWithExternalServiceProvider>();
}
protected async override void OnStartup(StartupEventArgs e)
{
Log.Logger = new LoggerConfiguration()
#if DEBUG
@ -47,6 +29,12 @@ namespace MyCompanyName.MyProjectName
.WriteTo.Async(c => c.File("Logs/logs.txt"))
.CreateLogger();
_host = CreateHostBuilder();
_application = _host.Services.GetService<IAbpApplicationWithExternalServiceProvider>();
}
protected override async void OnStartup(StartupEventArgs e)
{
try
{
Log.Information("Starting WPF host.");
@ -60,22 +48,31 @@ namespace MyCompanyName.MyProjectName
{
Log.Fatal(ex, "Host terminated unexpectedly!");
}
finally
{
Log.CloseAndFlush();
}
}
protected async override void OnExit(ExitEventArgs e)
protected override async void OnExit(ExitEventArgs e)
{
_application.Shutdown();
await _host.StopAsync();
_host.Dispose();
Log.CloseAndFlush();
}
private void Initialize(IServiceProvider serviceProvider)
{
_application.Initialize(serviceProvider);
}
private IHost CreateHostBuilder()
{
return Host
.CreateDefaultBuilder(null)
.UseAutofac()
.UseSerilog()
.ConfigureServices((hostContext, services) =>
{
services.AddApplication<MyProjectNameModule>();
}).Build();
}
}
}

@ -1,17 +1,5 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace MyCompanyName.MyProjectName
{

@ -14,7 +14,7 @@
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Hosting" Version="5.0.*" />
<PackageReference Include="Serilog.Extensions.Hosting" Version="3.1.0" />
<PackageReference Include="Serilog.Extensions.Hosting" Version="4.1.2" />
<PackageReference Include="Serilog.Sinks.Async" Version="1.4.0" />
<PackageReference Include="Serilog.Sinks.File" Version="4.1.0" />
</ItemGroup>

Loading…
Cancel
Save