Browse Source

Merge branch 'dev' into berkan/cli-analytics

pull/7988/head
Berkan Sasmaz 6 years ago
parent
commit
342bf003c3
  1. 25
      abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/zh-Hans.json
  2. 3
      abp_io/AbpIoLocalization/AbpIoLocalization/Community/Localization/Resources/en.json
  3. 31
      abp_io/AbpIoLocalization/AbpIoLocalization/Community/Localization/Resources/zh-Hans.json
  4. 7
      abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/zh-Hans.json
  5. 2
      docs/en/Community-Articles/2020-10-08-How-To-Add-Custom-Property-To-The-User-Entity/How-To-Add-Custom-Property-To-The-User-Entity.md
  6. 101
      docs/en/UI/Angular/HTTP-Requests.md
  7. 2
      docs/en/UI/Angular/Router-Events.md
  8. BIN
      docs/en/UI/Angular/images/custom-error-handler-404-component.jpg
  9. BIN
      docs/en/UI/Angular/images/custom-error-handler-toaster-message.jpg
  10. 2
      docs/zh-Hans/Repositories.md
  11. 1
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/TuiEditor/TuiEditorScriptContributor.cs
  12. 78
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/MicroserviceServiceRandomPortStep.cs
  13. 9
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/RemoveCmsKitStep.cs
  14. 9
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/RemoveEfCoreDependencyFromPublicStep.cs
  15. 11
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/RemoveGlobalFeaturesPackageStep.cs
  16. 54
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/RemoveProjectFromTyeStep.cs
  17. 14
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/RemovePublicRedisStep.cs
  18. 8
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/SolutionRenameStep.cs
  19. 10
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Files/FileEntryExtensions.cs
  20. 6
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/Microservice/MicroserviceServiceTemplateBase.cs
  21. 10
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/Microservice/MicroserviceTemplateBase.cs
  22. 13
      framework/src/Volo.Abp.Core/Microsoft/Extensions/DependencyInjection/ServiceCollectionLoggingExtensions.cs
  23. 21
      framework/src/Volo.Abp.Core/System/AbpStringExtensions.cs
  24. 27
      framework/src/Volo.Abp.Core/Volo/Abp/AbpApplicationBase.cs
  25. 6
      framework/src/Volo.Abp.Core/Volo/Abp/Internal/InternalServiceCollectionExtensions.cs
  26. 20
      framework/src/Volo.Abp.Core/Volo/Abp/Logging/AbpInitLogEntry.cs
  27. 38
      framework/src/Volo.Abp.Core/Volo/Abp/Logging/DefaultInitLogger.cs
  28. 15
      framework/src/Volo.Abp.Core/Volo/Abp/Logging/DefaultInitLoggerFactory.cs
  29. 10
      framework/src/Volo.Abp.Core/Volo/Abp/Logging/IInitLogger.cs
  30. 7
      framework/src/Volo.Abp.Core/Volo/Abp/Logging/IInitLoggerFactory.cs
  31. 15
      framework/src/Volo.Abp.Core/Volo/Abp/Modularity/AbpModuleHelper.cs
  32. 10
      framework/src/Volo.Abp.Core/Volo/Abp/Modularity/ModuleLoader.cs
  33. 12
      framework/src/Volo.Abp.Core/Volo/Abp/Modularity/ModuleManager.cs
  34. 8
      framework/src/Volo.Abp.Core/Volo/Abp/Modularity/PlugIns/PlugInSourceExtensions.cs
  35. 7
      framework/src/Volo.Abp.Core/Volo/Abp/Modularity/PlugIns/PlugInSourceList.cs
  36. 6
      framework/src/Volo.Abp.ExceptionHandling/Volo/Abp/ExceptionHandling/Localization/zh-Hans.json
  37. 10
      framework/test/Volo.Abp.Core.Tests/Volo/Abp/Modularity/ModuleLoader_Tests.cs
  38. 128
      modules/blogging/src/Volo.Blogging.Application/Volo/Blogging/Posts/PostAppService.cs
  39. 6
      modules/cms-kit/src/Volo.CmsKit.Admin.Application.Contracts/Volo/CmsKit/Permissions/CmsKitAdminPermissionDefinitionProvider.cs
  40. 53
      modules/cms-kit/src/Volo.CmsKit.Admin.Application/Volo/CmsKit/Admin/CmsKitAdminApplicationModule.cs
  41. 3
      modules/cms-kit/src/Volo.CmsKit.Admin.Application/Volo/CmsKit/Admin/MediaDescriptors/MediaDescriptorAdminAppService.cs
  42. 2
      modules/cms-kit/src/Volo.CmsKit.Admin.Web/CmsKitAdminWebModule.cs
  43. 2
      modules/cms-kit/src/Volo.CmsKit.Admin.Web/Pages/CmsKit/BlogPosts/create.js
  44. 2
      modules/cms-kit/src/Volo.CmsKit.Admin.Web/Pages/CmsKit/Comments/index.js
  45. 2
      modules/cms-kit/src/Volo.CmsKit.Admin.Web/Pages/CmsKit/Pages/create.js
  46. 4
      modules/cms-kit/src/Volo.CmsKit.Admin.Web/Pages/CmsKit/Pages/index.js
  47. 2
      modules/cms-kit/src/Volo.CmsKit.Admin.Web/Pages/CmsKit/Pages/update.js
  48. 5
      modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/CmsKitErrorCodes.cs
  49. 3
      modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/en.json
  50. 1
      modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/tr.json
  51. 122
      modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/zh-Hans.json
  52. 11
      modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Comments/CmsKitCommentOptions.cs
  53. 2
      modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Comments/Comment.cs
  54. 21
      modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Comments/CommentEntityTypeDefinition.cs
  55. 46
      modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Comments/CommentManager.cs
  56. 41
      modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Comments/DefaultCommentEntityTypeDefinitionStore.cs
  57. 27
      modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Comments/EntityNotCommentableException.cs
  58. 17
      modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Comments/ICommentEntityTypeDefinitionStore.cs
  59. 4
      modules/cms-kit/src/Volo.CmsKit.HttpApi/Volo.CmsKit.HttpApi.csproj
  60. 4
      modules/cms-kit/src/Volo.CmsKit.HttpApi/Volo/CmsKit/CmsKitHttpApiModule.cs
  61. 16
      modules/cms-kit/src/Volo.CmsKit.Public.Application/Volo/CmsKit/Public/Comments/CommentPublicAppService.cs
  62. 2
      modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Blogs/BlogPost/Default.cshtml
  63. 4
      modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Pages/Default.cshtml
  64. 4
      modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/Public/CmsKit/Blogs/BlogPost.cshtml
  65. 59
      modules/cms-kit/test/Volo.CmsKit.Domain.Tests/Comments/CommentManager_Test.cs
  66. 14
      modules/cms-kit/test/Volo.CmsKit.TestBase/CmsKitDataSeedContributor.cs
  67. 16
      modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/IdentityRoleNameChangedEto.cs
  68. 3
      modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/AbpIdentityDomainModule.cs
  69. 12
      modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityRole.cs
  70. 5
      modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityRoleNameChangedEvent.cs
  71. 35
      modules/identity/src/Volo.Abp.PermissionManagement.Domain.Identity/Volo/Abp/PermissionManagement/Identity/RoleDeletedEventHandler.cs
  72. 60
      modules/identity/src/Volo.Abp.PermissionManagement.Domain.Identity/Volo/Abp/PermissionManagement/Identity/RoleUpdateEventHandler.cs
  73. 105
      modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/Distributed_Role_Change_Events_Test.cs
  74. 7
      modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/LocalizationExtensions/zh-Hans.json
  75. 60
      modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/RoleChangingEvents_Test.cs
  76. 4
      modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/PermissionGrantCacheItemInvalidator.cs
  77. 22
      modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/PermissionManager.cs
  78. 229
      npm/ng-packs/packages/theme-shared/src/lib/handlers/error.handler.ts
  79. 9
      npm/ng-packs/packages/theme-shared/src/lib/models/common.ts
  80. 6
      npm/ng-packs/packages/theme-shared/src/lib/tokens/http-error.token.ts
  81. 4
      npm/packs/cms-kit/package.json
  82. 3
      npm/packs/tui-editor/abp.resourcemapping.js
  83. 7
      npm/packs/tui-editor/src/tui-editor-jquery-patch.js
  84. 43
      templates/wpf/src/MyCompanyName.MyProjectName/App.xaml.cs
  85. 12
      templates/wpf/src/MyCompanyName.MyProjectName/MainWindow.xaml.cs
  86. 2
      templates/wpf/src/MyCompanyName.MyProjectName/MyCompanyName.MyProjectName.csproj

25
abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/zh-Hans.json

@ -35,6 +35,7 @@
"NameFilter": "名称", "NameFilter": "名称",
"CreationTime": "创建时间", "CreationTime": "创建时间",
"IsPro": "是否为专业版", "IsPro": "是否为专业版",
"IsFreeToActiveLicenseOwners": "许可证所有者免费",
"ShowOnModuleList": "展示模块列表", "ShowOnModuleList": "展示模块列表",
"EfCoreConfigureMethodName": "配置方法法", "EfCoreConfigureMethodName": "配置方法法",
"IsProFilter": "是否为专业版", "IsProFilter": "是否为专业版",
@ -90,6 +91,7 @@
"UserNotFound": "用户不存在", "UserNotFound": "用户不存在",
"{0}WillBeRemovedFromDevelopers": "{0} 将从开发者中移除, 你确定吗?", "{0}WillBeRemovedFromDevelopers": "{0} 将从开发者中移除, 你确定吗?",
"{0}WillBeRemovedFromOwners": "{0} 将从所有者中移除, 你确定吗?", "{0}WillBeRemovedFromOwners": "{0} 将从所有者中移除, 你确定吗?",
"{0}WillBeRemovedFromMembers": "{0} 将从成员中删除, 你确定吗?",
"Computers": "计算机", "Computers": "计算机",
"UniqueComputerId": "计算机唯一ID", "UniqueComputerId": "计算机唯一ID",
"LastSeenDate": "上次查看日期", "LastSeenDate": "上次查看日期",
@ -194,6 +196,27 @@
"CoverImage": "封面图片", "CoverImage": "封面图片",
"RemoveCacheConfirmationMessage": "你确定要删除\"{0}\" 文章缓存?", "RemoveCacheConfirmationMessage": "你确定要删除\"{0}\" 文章缓存?",
"SuccessfullyRemoved": "清除成功", "SuccessfullyRemoved": "清除成功",
"RemoveCache": "删除缓存" "RemoveCache": "删除缓存",
"Language": "语言",
"Optional": "可选的",
"CreateArticleLanguageInfo": "发帖所用的语言",
"Enum:ContentSource:2": "视频发布",
"VideoPreview": "视频预览",
"VideoPreviewErrorMessage": "无法从YouTube获取给定的视频网址. 可能是由于视频是私有视频,或者给定的URL不可用.",
"DeleteCoverImage": "删除封面图片",
"DeleteCoverImageConfirmationMessage": "你确定要删除封面图片吗",
"DeleteCoverImageSuccessMessage": "成功删除封面图片",
"PaymentsOf": "付款的",
"ShowPaymentsOfOrganization": "显示付款",
"Date": "日期",
"Products": "商品",
"TotalAmount": "总金额",
"Currency": "货币",
"Gateway": "网关",
"State": "状态",
"FailReason": "失败原因",
"ReIndexAllPosts": "重新索引所有帖子",
"ReIndexAllPostsConfirmationMessage": "你确定要重新索引所有的帖子吗",
"SuccessfullyReIndexAllPosts": "成功索引所有的帖子"
} }
} }

3
abp_io/AbpIoLocalization/AbpIoLocalization/Community/Localization/Resources/en.json

@ -138,6 +138,7 @@
"TwitterUserNameValidationMessage": "Your Twitter username can not include whitespace, please be sure your Twitter username is correct.", "TwitterUserNameValidationMessage": "Your Twitter username can not include whitespace, please be sure your Twitter username is correct.",
"LinkedinUrlValidationMessage": "Your Linkedin URL can not include whitespace, please be sure your Linkedin URL is correct.", "LinkedinUrlValidationMessage": "Your Linkedin URL can not include whitespace, please be sure your Linkedin URL is correct.",
"NoPostsFound": "No posts found!", "NoPostsFound": "No posts found!",
"SearchInPosts": "Search in posts..." "SearchInPosts": "Search in posts...",
"MinimumSearchContent": "You must enter at least 3 characters!"
} }
} }

31
abp_io/AbpIoLocalization/AbpIoLocalization/Community/Localization/Resources/zh-Hans.json

@ -110,7 +110,34 @@
"VideoContentForm": "视频内容来源", "VideoContentForm": "视频内容来源",
"GithubPostForm": "Github文章来源", "GithubPostForm": "Github文章来源",
"ExternalPostForm": "外部文章来源", "ExternalPostForm": "外部文章来源",
"PostSourceTypeChooses": "我们接受文章的三种来源类型;", "HowToPost": "如何发布",
"Posts": "文章" "Posts": "文章",
"VideoUrl": "视频Url",
"GithubArticleUrl": "Github文章Url",
"ExternalArticleUrl": "外部文章Url",
"CreatePostCoverInfo": "要创建一个有效的帖子,需要添加封面图片. 上传16:9的图片获取最佳视觉效果. 文件最大支持: 1MB",
"ThankYouForContribution": "感谢你对ABP社区的贡献",
"GithubArticle": "Github文章",
"GithubArticleSubmitStepOne": "<span class=\"font-weight-bold\">1.</span> 用Markdown格式在GitHub的任何公共存储库上写一篇文章. <a target=\"_blank\" href=\"https://github.com/abpframework/abp/blob/dev/docs/en/Community-Articles/2020-12-04-Event-Organizer/Post.md\">示例</a>",
"GithubArticleSubmitStepTwo": "<span class=\"font-weight-bold\">2.</span> 使用表单提交你的文章URL.",
"GithubArticleSubmitStepThree": "<span class=\"font-weight-bold\">3.</span> 你的文章将在这个网站中呈现.",
"YoutubeVideo": "Youtube视频",
"YoutubeVideoSubmitStepOne": "<span class=\"font-weight-bold\">1.</span> 在YouTube上发布你的视频.",
"YoutubeVideoSubmitStepTwo": "<span class=\"font-weight-bold\">2.</span> 使用表单提交视频URL.",
"YoutubeVideoSubmitStepThree": "<span class=\"font-weight-bold\">3.</span> 访客将能够在这个网站上直接观看你的视频内容.",
"ExternalContent": "外部内容",
"ExternalContentSubmitStepOne": "<span class=\"font-weight-bold\">1.</span> 在任何公开的平台创建内容(medium, 你自己的博客或者任何你喜欢的).",
"ExternalContentSubmitStepTwo": "<span class=\"font-weight-bold\">2.</span> 使用表单提交内容URL.",
"ExternalContentSubmitStepThree": "<span class=\"font-weight-bold\">3.</span> 访客被重定向到原网站的内容.",
"ChooseYourContentType": "请选择你想要添加内容的方式",
"PostContentViaGithub": "我想要使用<span class=\"font-weight-bold\"><i class=\"fa fa-github\"></i> GitHub</span>提交markdown格式的文章",
"PostContentViaYoutube": "我想要分享我在<span class=\"font-weight-bold\"><i class=\"fa fa-youtube\"></i> Youtube</span>的可用视频",
"PostContentViaExternalSource": "我想要添加我在<span class=\"font-weight-bold\">其他平台</span>发布的内容",
"GitHubUserNameValidationMessage": "您的Github用户名不能包含空格, 请确认你的Github用户名是正确的.",
"PersonalSiteUrlValidationMessage": "你的个人网站URL不能包含空格, 请确定你的个人网站URL是正确的.",
"TwitterUserNameValidationMessage": "你的Twitter用户名不能包含空格, 请确认你的Twitter用户名是正确的.",
"LinkedinUrlValidationMessage": "你的领英URL不能包含空格, 请确认你的领英URL是正确的",
"NoPostsFound": "没有发现帖子",
"SearchInPosts": "搜索帖子…"
} }
} }

7
abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/zh-Hans.json

@ -8,6 +8,7 @@
"ProjectName": "项目名称", "ProjectName": "项目名称",
"ProjectType": "项目类型", "ProjectType": "项目类型",
"DatabaseProvider": "数据库提供者", "DatabaseProvider": "数据库提供者",
"DatabaseManagementSystem": "数据库管理系统",
"NTier": "N层", "NTier": "N层",
"IncludeUserInterface": "包含用户界面", "IncludeUserInterface": "包含用户界面",
"CreateNow": "现在创建", "CreateNow": "现在创建",
@ -194,7 +195,7 @@
"MultipleUIOptionsExplanation": "核心框架设计为独立与UI,可以和任何类型的UI系统一起使用. 同时提供了多个开箱即用的预构建集成选项.", "MultipleUIOptionsExplanation": "核心框架设计为独立与UI,可以和任何类型的UI系统一起使用. 同时提供了多个开箱即用的预构建集成选项.",
"MultipleDBOptionsExplanation": "该框架可以使用任何数据源,并且以下提供程序已得到正式开发和支持;", "MultipleDBOptionsExplanation": "该框架可以使用任何数据源,并且以下提供程序已得到正式开发和支持;",
"SelectLanguage": "选择语言", "SelectLanguage": "选择语言",
"LatestArticleOnCommunity": "关于ABP社区的最新文章", "LatestArticleOnCommunity": "关于ABP社区的最新文章",
"Register": "寄存器" "Register": "寄存器"
} }
} }

2
docs/en/Community-Articles/2020-10-08-How-To-Add-Custom-Property-To-The-User-Entity/How-To-Add-Custom-Property-To-The-User-Entity.md

@ -78,7 +78,7 @@ public static class CustomizeUserDemoEfCoreEntityExtensionMappings
nameof(AppUser.Title), nameof(AppUser.Title),
(entityBuilder, propertyBuilder) => (entityBuilder, propertyBuilder) =>
{ {
propertyBuilder.IsRequired(); propertyBuilder.HasDefaultValue();
propertyBuilder.HasMaxLength(UserConsts.MaxTitleLength); propertyBuilder.HasMaxLength(UserConsts.MaxTitleLength);
} }
).MapEfCoreProperty<IdentityUser, int>( ).MapEfCoreProperty<IdentityUser, int>(

101
docs/en/UI/Angular/HTTP-Requests.md

@ -27,13 +27,13 @@ An `HttpInterceptor` is able to catch `HttpErrorResponse`  and can be used for
## RestService ## RestService
ABP core module has a utility service for HTTP requests: `RestService`. Unless explicitly configured otherwise, it catches HTTP errors and dispatches a `RestOccurError` action. This action is then captured by the `ErrorHandler` introduced by the `ThemeSharedModule`. Since you should already import this module in your app, when the `RestService` is used, all HTTP errors get automatically handled by deafult. ABP core module has a utility service for HTTP requests: `RestService`. Unless explicitly configured otherwise, it catches HTTP errors and dispatches a `RestOccurError` action. This action is then captured by the `ErrorHandler` introduced by the `ThemeSharedModule`. Since you should already import this module in your app, when the `RestService` is used, all HTTP errors get automatically handled by default.
### Getting Started with RestService ### Getting Started with RestService
In order to use the `RestService`, you must inject it in your class as a dependency. In order to use the `RestService`, you must inject it in your class as a dependency.
```js ```js
import { RestService } from '@abp/ng.core'; import { RestService } from '@abp/ng.core';
@ -200,6 +200,101 @@ getSomeCustomHeaderValue() {
} }
``` ```
You may find `Rest.Observe` enum [here](https://github.com/abpframework/abp/blob/dev/npm/ng-packs/packages/core/src/lib/models/rest.ts#L10).
You may find `Rest.Observe` enum [here](https://github.com/abpframework/abp/blob/dev/npm/ng-packs/packages/core/src/lib/models/rest.ts#L10). ## HTTP Error Handling
When the `RestService` is used, all HTTP errors are automatically handled by `ErrorHandler` which is a service that exposed by the `@abp/ng.theme.shared` package.
### Custom HTTP Error Handler
A custom HTTP error handler can be registered to an injection token named `HTTP_ERROR_HANDLER`. If a custom handler function is registered, the `ErrorHandler` executes that function.
See an example:
```js
// http-error-handler.ts
import { ContentProjectionService, PROJECTION_STRATEGY } from '@abp/ng.core';
import { ToasterService } from '@abp/ng.theme.shared';
import { HttpErrorResponse } from '@angular/common/http';
import { Injector } from '@angular/core';
import { throwError } from 'rxjs';
import { Error404Component } from './error404/error404.component';
export function handleHttpErrors(injector: Injector, httpError: HttpErrorResponse) {
if (httpError.status === 400) {
const toaster = injector.get(ToasterService);
toaster.error(httpError.error?.error?.message || 'Bad request!', '400');
return;
}
if (httpError.status === 404) {
const contentProjection = injector.get(ContentProjectionService);
contentProjection.projectContent(PROJECTION_STRATEGY.AppendComponentToBody(Error404Component));
return;
}
return throwError(httpError);
}
// app.module.ts
import { Error404Component } from './error404/error404.component';
import { handleHttpErrors } from './http-error-handling';
import { HTTP_ERROR_HANDLER, ... } from '@abp/ng.theme.shared';
@NgModule({
// ...
providers: [
// ...
{ provide: HTTP_ERROR_HANDLER, useValue: handleHttpErrors }
],
declarations: [
//...
Error404Component],
})
export class AppModule {}
```
In the example above:
- Created a function named `handleHttpErrors` and defined as value of the `HTTP_ERROR_HANDLER` provider in app.module. After this, the function executes when an HTTP error occurs.
- 400 bad request errors is handled. When a 400 error occurs, backend error message will be displayed as shown below:
![custom-error-handler-toaster-message](images/custom-error-handler-toaster-message.jpg)
- 404 not found errors is handled. When a 404 error occurs, `Error404Component` will be appended to the `<body>` as shown below:
![custom-error-handler-404-component](images/custom-error-handler-404-component.jpg)
- Since `throwError(httpError)` is returned at bottom of the `handleHttpErrors`, the `ErrorHandler` will handle the HTTP errors except 400 and 404 errors.
**Note 1:** If you put `return` to next line of handling an error, default error handling will not work for that error.
```js
export function handleHttpErrors(injector: Injector, httpError: HttpErrorResponse) {
if (httpError.status === 403) {
// handle 403 errors here
return; // put return to skip default error handling
}
}
```
**Note 2:** If you put `return throwError(httpError)`, default error handling will work.
- `throwError` is a function. It can be imported from `rxjs`.
- `httpError` is the second parameter of the error handler function which is registered to the `HTTP_ERROR_HANDLER` provider. Type of the `httpError` is `HttpErrorResponse`.
```js
import { throwError } from 'rxjs';
export function handleHttpErrors(injector: Injector, httpError: HttpErrorResponse) {
if (httpError.status === 500) {
// handle 500 errors here
return;
}
// you can return the throwError(httpError) at bottom of the function to run the default handler of ABP for HTTP errors that you didn't handle above.
return throwError(httpError)
}
```

2
docs/en/UI/Angular/Router-Events.md

@ -1,6 +1,6 @@
# Router Events Simplified # 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.

BIN
docs/en/UI/Angular/images/custom-error-handler-404-component.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

BIN
docs/en/UI/Angular/images/custom-error-handler-toaster-message.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

2
docs/zh-Hans/Repositories.md

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

1
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/TuiEditor/TuiEditorScriptContributor.cs

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

78
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/MicroserviceServiceRandomPortStep.cs

@ -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
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/RemoveCmsKitStep.cs

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

9
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/RemoveEfCoreDependencyFromPublicStep.cs

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

11
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/RemoveGlobalFeaturesPackageStep.cs

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

54
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/RemoveProjectFromTyeStep.cs

@ -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));
}
}
}

14
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/RemovePublicRedisStep.cs

@ -8,16 +8,12 @@ namespace Volo.Abp.Cli.ProjectBuilding.Building.Steps
{ {
public override void Execute(ProjectBuildContext context) public override void Execute(ProjectBuildContext context)
{ {
context.Files.FirstOrDefault(f => f.Name.EndsWith("MyCompanyName.MyProjectName.Web.csproj"))?.RemoveTemplateCodeIfNot("PUBLIC-REDIS"); foreach (var file in context.Files)
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)
{ {
appSettings.RemoveTemplateCodeIfNot("PUBLIC-REDIS"); if (file.Name.EndsWith(".cs") || file.Name.EndsWith(".csproj") || file.Name.EndsWith(".json"))
{
file.RemoveTemplateCodeIfNot("PUBLIC-REDIS");
}
} }
} }
} }

8
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/SolutionRenameStep.cs

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

10
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Files/FileEntryExtensions.cs

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

6
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/Microservice/MicroserviceServiceTemplateBase.cs

@ -31,6 +31,7 @@ namespace Volo.Abp.Cli.ProjectBuilding.Templates.Microservice
var steps = new List<ProjectBuildPipelineStep>(); var steps = new List<ProjectBuildPipelineStep>();
DeleteUnrelatedUiProject(context, steps); DeleteUnrelatedUiProject(context, steps);
SetRandomPortForHostProject(context, steps);
RandomizeStringEncryption(context, steps); RandomizeStringEncryption(context, steps);
return 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) private static void RandomizeStringEncryption(ProjectBuildContext context, List<ProjectBuildPipelineStep> steps)
{ {
steps.Add(new RandomizeStringEncryptionStep()); steps.Add(new RandomizeStringEncryptionStep());

10
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/Microservice/MicroserviceTemplateBase.cs

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

13
framework/src/Volo.Abp.Core/Microsoft/Extensions/DependencyInjection/ServiceCollectionLoggingExtensions.cs

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

21
framework/src/Volo.Abp.Core/System/AbpStringExtensions.cs

@ -260,8 +260,9 @@ namespace System
/// </summary> /// </summary>
/// <param name="str">String to convert</param> /// <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="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> /// <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)) if (string.IsNullOrWhiteSpace(str))
{ {
@ -273,6 +274,11 @@ namespace System
return useCurrentCulture ? str.ToLower() : str.ToLowerInvariant(); 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); return (useCurrentCulture ? char.ToLower(str[0]) : char.ToLowerInvariant(str[0])) + str.Substring(1);
} }
@ -545,5 +551,18 @@ namespace System
return encoding.GetBytes(str); 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;
}
} }
} }

27
framework/src/Volo.Abp.Core/Volo/Abp/AbpApplicationBase.cs

@ -3,8 +3,10 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using JetBrains.Annotations; using JetBrains.Annotations;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Volo.Abp.DependencyInjection; using Volo.Abp.DependencyInjection;
using Volo.Abp.Internal; using Volo.Abp.Internal;
using Volo.Abp.Logging;
using Volo.Abp.Modularity; using Volo.Abp.Modularity;
namespace Volo.Abp namespace Volo.Abp
@ -60,7 +62,7 @@ namespace Volo.Abp
{ {
//TODO: Shutdown if not done before? //TODO: Shutdown if not done before?
} }
protected virtual void SetServiceProvider(IServiceProvider serviceProvider) protected virtual void SetServiceProvider(IServiceProvider serviceProvider)
{ {
ServiceProvider = serviceProvider; ServiceProvider = serviceProvider;
@ -71,12 +73,31 @@ namespace Volo.Abp
{ {
using (var scope = ServiceProvider.CreateScope()) using (var scope = ServiceProvider.CreateScope())
{ {
WriteInitLogs(scope.ServiceProvider);
scope.ServiceProvider scope.ServiceProvider
.GetRequiredService<IModuleManager>() .GetRequiredService<IModuleManager>()
.InitializeModules(new ApplicationInitializationContext(scope.ServiceProvider)); .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<IInitLoggerFactory>().Create<AbpApplicationBase>();
foreach (var entry in initLogger.Entries)
{
logger.Log(entry.LogLevel, entry.EventId, entry.State, entry.Exception, entry.Formatter);
}
initLogger.Entries.Clear();
}
protected virtual IReadOnlyList<IAbpModuleDescriptor> LoadModules(IServiceCollection services, AbpApplicationCreationOptions options) protected virtual IReadOnlyList<IAbpModuleDescriptor> LoadModules(IServiceCollection services, AbpApplicationCreationOptions options)
{ {
return services return services
@ -87,7 +108,7 @@ namespace Volo.Abp
options.PlugInSources options.PlugInSources
); );
} }
//TODO: We can extract a new class for this //TODO: We can extract a new class for this
protected virtual void ConfigureServices() protected virtual void ConfigureServices()
{ {
@ -158,4 +179,4 @@ namespace Volo.Abp
} }
} }
} }
} }

6
framework/src/Volo.Abp.Core/Volo/Abp/Internal/InternalServiceCollectionExtensions.cs

@ -1,6 +1,7 @@
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.DependencyInjection.Extensions;
using Volo.Abp.Logging;
using Volo.Abp.Modularity; using Volo.Abp.Modularity;
using Volo.Abp.Reflection; using Volo.Abp.Reflection;
@ -16,7 +17,7 @@ namespace Volo.Abp.Internal
} }
internal static void AddCoreAbpServices(this IServiceCollection services, internal static void AddCoreAbpServices(this IServiceCollection services,
IAbpApplication abpApplication, IAbpApplication abpApplication,
AbpApplicationCreationOptions applicationCreationOptions) AbpApplicationCreationOptions applicationCreationOptions)
{ {
var moduleLoader = new ModuleLoader(); var moduleLoader = new ModuleLoader();
@ -35,6 +36,7 @@ namespace Volo.Abp.Internal
services.TryAddSingleton<IModuleLoader>(moduleLoader); services.TryAddSingleton<IModuleLoader>(moduleLoader);
services.TryAddSingleton<IAssemblyFinder>(assemblyFinder); services.TryAddSingleton<IAssemblyFinder>(assemblyFinder);
services.TryAddSingleton<ITypeFinder>(typeFinder); services.TryAddSingleton<ITypeFinder>(typeFinder);
services.TryAddSingleton<IInitLoggerFactory>(new DefaultInitLoggerFactory());
services.AddAssemblyOf<IAbpApplication>(); services.AddAssemblyOf<IAbpApplication>();
@ -47,4 +49,4 @@ namespace Volo.Abp.Internal
}); });
} }
} }
} }

20
framework/src/Volo.Abp.Core/Volo/Abp/Logging/AbpInitLogEntry.cs

@ -0,0 +1,20 @@
using System;
using Microsoft.Extensions.Logging;
namespace Volo.Abp.Logging
{
public class AbpInitLogEntry
{
public LogLevel LogLevel { get; set; }
public EventId EventId { get; set; }
public object State { get; set; }
public Exception Exception { get; set; }
public Func<object, Exception, string> Formatter { get; set; }
public string Message => Formatter(State, Exception);
}
}

38
framework/src/Volo.Abp.Core/Volo/Abp/Logging/DefaultInitLogger.cs

@ -0,0 +1,38 @@
using System;
using System.Collections.Generic;
using Microsoft.Extensions.Logging;
namespace Volo.Abp.Logging
{
public class DefaultInitLogger<T> : IInitLogger<T>
{
public List<AbpInitLogEntry> Entries { get; }
public DefaultInitLogger()
{
Entries = new List<AbpInitLogEntry>();
}
public virtual void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
{
Entries.Add(new AbpInitLogEntry
{
LogLevel = logLevel,
EventId = eventId,
State = state,
Exception = exception,
Formatter = (s, e) => formatter((TState)s, e),
});
}
public virtual bool IsEnabled(LogLevel logLevel)
{
return logLevel != LogLevel.None;
}
public virtual IDisposable BeginScope<TState>(TState state)
{
return NullDisposable.Instance;
}
}
}

15
framework/src/Volo.Abp.Core/Volo/Abp/Logging/DefaultInitLoggerFactory.cs

@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
namespace Volo.Abp.Logging
{
public class DefaultInitLoggerFactory : IInitLoggerFactory
{
private readonly Dictionary<Type, object> _cache = new Dictionary<Type, object>();
public virtual IInitLogger<T> Create<T>()
{
return (IInitLogger<T>)_cache.GetOrAdd(typeof(T), () => new DefaultInitLogger<T>());;
}
}
}

10
framework/src/Volo.Abp.Core/Volo/Abp/Logging/IInitLogger.cs

@ -0,0 +1,10 @@
using System.Collections.Generic;
using Microsoft.Extensions.Logging;
namespace Volo.Abp.Logging
{
public interface IInitLogger<out T> : ILogger<T>
{
public List<AbpInitLogEntry> Entries { get; }
}
}

7
framework/src/Volo.Abp.Core/Volo/Abp/Logging/IInitLoggerFactory.cs

@ -0,0 +1,7 @@
namespace Volo.Abp.Logging
{
public interface IInitLoggerFactory
{
IInitLogger<T> Create<T>();
}
}

15
framework/src/Volo.Abp.Core/Volo/Abp/Modularity/AbpModuleHelper.cs

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

10
framework/src/Volo.Abp.Core/Volo/Abp/Modularity/ModuleLoader.cs

@ -25,7 +25,7 @@ namespace Volo.Abp.Modularity
} }
private List<IAbpModuleDescriptor> GetDescriptors( private List<IAbpModuleDescriptor> GetDescriptors(
IServiceCollection services, IServiceCollection services,
Type startupModuleType, Type startupModuleType,
PlugInSourceList plugInSources) PlugInSourceList plugInSources)
{ {
@ -43,14 +43,16 @@ namespace Volo.Abp.Modularity
Type startupModuleType, Type startupModuleType,
PlugInSourceList plugInSources) PlugInSourceList plugInSources)
{ {
var logger = services.GetInitLogger<AbpApplicationBase>();
//All modules starting from the startup module //All modules starting from the startup module
foreach (var moduleType in AbpModuleHelper.FindAllModuleTypes(startupModuleType)) foreach (var moduleType in AbpModuleHelper.FindAllModuleTypes(startupModuleType, logger))
{ {
modules.Add(CreateModuleDescriptor(services, moduleType)); modules.Add(CreateModuleDescriptor(services, moduleType));
} }
//Plugin modules //Plugin modules
foreach (var moduleType in plugInSources.GetAllModules()) foreach (var moduleType in plugInSources.GetAllModules(logger))
{ {
if (modules.Any(m => m.Type == moduleType)) if (modules.Any(m => m.Type == moduleType))
{ {
@ -102,4 +104,4 @@ namespace Volo.Abp.Modularity
} }
} }
} }
} }

12
framework/src/Volo.Abp.Core/Volo/Abp/Modularity/ModuleManager.cs

@ -32,8 +32,6 @@ namespace Volo.Abp.Modularity
public void InitializeModules(ApplicationInitializationContext context) public void InitializeModules(ApplicationInitializationContext context)
{ {
LogListOfModules();
foreach (var contributor in _lifecycleContributors) foreach (var contributor in _lifecycleContributors)
{ {
foreach (var module in _moduleContainer.Modules) foreach (var module in _moduleContainer.Modules)
@ -52,16 +50,6 @@ namespace Volo.Abp.Modularity
_logger.LogInformation("Initialized all ABP modules."); _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) public void ShutdownModules(ApplicationShutdownContext context)
{ {
var modules = _moduleContainer.Modules.Reverse().ToList(); var modules = _moduleContainer.Modules.Reverse().ToList();

8
framework/src/Volo.Abp.Core/Volo/Abp/Modularity/PlugIns/PlugInSourceExtensions.cs

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

7
framework/src/Volo.Abp.Core/Volo/Abp/Modularity/PlugIns/PlugInSourceList.cs

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

6
framework/src/Volo.Abp.ExceptionHandling/Volo/Abp/ExceptionHandling/Localization/zh-Hans.json

@ -18,6 +18,8 @@
"401Message": "未授权", "401Message": "未授权",
"403Message": "禁止访问", "403Message": "禁止访问",
"404Message": "网页未找到", "404Message": "网页未找到",
"500Message": "内部服务器错误" "500Message": "内部服务器错误",
"403MessageDetail": "您没有权限执行此操作",
"404MessageDetail": "抱歉, 这个地址是空的"
} }
} }

10
framework/test/Volo.Abp.Core.Tests/Volo/Abp/Modularity/ModuleLoader_Tests.cs

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

128
modules/blogging/src/Volo.Blogging.Application/Volo/Blogging/Posts/PostAppService.cs

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

6
modules/cms-kit/src/Volo.CmsKit.Admin.Application.Contracts/Volo/CmsKit/Permissions/CmsKitAdminPermissionDefinitionProvider.cs

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

53
modules/cms-kit/src/Volo.CmsKit.Admin.Application/Volo/CmsKit/Admin/CmsKitAdminApplicationModule.cs

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

3
modules/cms-kit/src/Volo.CmsKit.Admin.Application/Volo/CmsKit/Admin/MediaDescriptors/MediaDescriptorAdminAppService.cs

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

2
modules/cms-kit/src/Volo.CmsKit.Admin.Web/CmsKitAdminWebModule.cs

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

2
modules/cms-kit/src/Volo.CmsKit.Admin.Web/Pages/CmsKit/BlogPosts/create.js

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

2
modules/cms-kit/src/Volo.CmsKit.Admin.Web/Pages/CmsKit/Comments/index.js

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

2
modules/cms-kit/src/Volo.CmsKit.Admin.Web/Pages/CmsKit/Pages/create.js

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

4
modules/cms-kit/src/Volo.CmsKit.Admin.Web/Pages/CmsKit/Pages/index.js

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

2
modules/cms-kit/src/Volo.CmsKit.Admin.Web/Pages/CmsKit/Pages/update.js

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

5
modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/CmsKitErrorCodes.cs

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

3
modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/en.json

@ -17,10 +17,11 @@
"CmsKit:0003": "The entity {0} is not taggable.", "CmsKit:0003": "The entity {0} is not taggable.",
"CmsKit:Blog:0001": "The given slug ({Slug}) already exists!", "CmsKit:Blog:0001": "The given slug ({Slug}) already exists!",
"CmsKit:BlogPost:0001": "The given 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: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:Page:0001": "The given url ({0}) already exists.",
"CmsKit:Tag:0002": "The entity is not taggable!", "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.", "CommentAuthorizationExceptionMessage": "Those comments are not allowed for public display.",
"CommentDeletionConfirmationMessage": "This comment and all replies will be deleted!", "CommentDeletionConfirmationMessage": "This comment and all replies will be deleted!",
"Comments": "Comments", "Comments": "Comments",

1
modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/tr.json

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

122
modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/zh-Hans.json

@ -1,22 +1,124 @@
{ {
"culture": "zh-Hans", "culture": "zh-Hans",
"texts": { "texts": {
"PickYourReaction": "选择你的回应", "BlogDeletionConfirmationMessage": "博客 '{0}' 将被删除. 你确定吗?",
"YourComment": "你的评论", "BlogFeatureNotAvailable": "这个功能目前不可用. 使用 `GlobalFeatureManager` 来启用它.",
"YourReply": "你的回复", "BlogId": "博客",
"BlogPostDeletionConfirmationMessage": "博客帖子 '{0}' 将被删除. 你确定吗?",
"BlogPosts": "博客帖子",
"Blogs": "博客",
"ChoosePreference": "选择首选项...",
"Cms": "Cms",
"CmsKit.Comments": "评论",
"CmsKit.Ratings": "评分",
"CmsKit.Reactions": "反应",
"CmsKit.Tags": "标签",
"CmsKit:0002": "内容已经存在!",
"CmsKit:0003": "实体 {0} 不可标记.",
"CmsKit:Blog:0001": "给定的slug ({Slug}) 已经存在!",
"CmsKit:BlogPost:0001": "给定的slug已经存在!",
"CmsKit:Comments:0001": "实体不可 {0} 不可评论.",
"CmsKit:Media:0001": "'{Name}' 不是有效的媒体名称.",
"CmsKit:Media:0002": "实体不可以含有媒体",
"CmsKit:Page:0001": "给定的url ({0}) 已经存在.",
"CmsKit:Tag:0002": "实体不可标记!",
"CommentAuthorizationExceptionMessage": "这些评论不允许公开显示",
"CommentDeletionConfirmationMessage": "此评论和所有回复将被删除!",
"Comments": "评论", "Comments": "评论",
"Send": "发送", "ContentDeletionConfirmationMessage": "你确定要删除这个内容吗?",
"Contents": "内容",
"CoverImage": "封面图片",
"CreateBlogPostPage": "新博客帖子",
"CreationTime": "创建时间",
"Delete": "删除", "Delete": "删除",
"Reply": "回复", "Detail": "详情",
"Update": "更新", "Details": "详情",
"DoYouPreferAdditionalEmails": "你是否更喜欢额外的邮件?",
"Edit": "修改", "Edit": "修改",
"EndDate": "结束时间",
"EntityId": "实体Id",
"EntityType": "实体类型",
"ExportCSV": "导出CSV",
"Features": "功能",
"GenericDeletionConfirmationMessage": "你确定删除 '{0}' 吗?",
"LastModification": "最后一次修改",
"LoginToAddComment": "登录添加评论", "LoginToAddComment": "登录添加评论",
"LoginToRate": "登录进行评分",
"LoginToReply": "登录进行回复", "LoginToReply": "登录进行回复",
"Menu:CMS": "CMS",
"Message": "消息",
"MessageDeletionConfirmationMessage": "这条评论将被完全删除", "MessageDeletionConfirmationMessage": "这条评论将被完全删除",
"CommentAuthorizationExceptionMessage": "这些评论不允许公开显示", "Name": "名称",
"Undo": "撤消", "New": "新",
"OK": "OK",
"PageDeletionConfirmationMessage": "你确定删除这个页面吗?",
"PageSlugInformation": "Slug用于url. 你的url将是 '/pages/{{slug}}'.",
"Permission:BlogManagement": "博客管理",
"Permission:BlogManagement.Create": "创建",
"Permission:BlogManagement.Delete": "删除",
"Permission:BlogManagement.Features": "删除",
"Permission:BlogManagement.Update": "更新",
"Permission:BlogPostManagement": "博客帖子管理",
"Permission:BlogPostManagement.Create": "创建",
"Permission:BlogPostManagement.Delete": "删除",
"Permission:BlogPostManagement.Update": "更新",
"Permission:CmsKit": "Cms工具包",
"Permission:Comments": "评论管理",
"Permission:Comments.Delete": "删除",
"Permission:Contents": "内容管理",
"Permission:Contents.Create": "创建内容",
"Permission:Contents.Delete": "删除内容",
"Permission:Contents.Update": "更新内容",
"Permission:MediaDescriptorManagement": "媒体管理",
"Permission:MediaDescriptorManagement:Create": "创建",
"Permission:MediaDescriptorManagement:Delete": "删除",
"Permission:PageManagement": "页面管理",
"Permission:PageManagement:Create": "创建",
"Permission:PageManagement:Delete": "删除",
"Permission:PageManagement:Update": "更新",
"Permission:TagManagement": "标签管理",
"Permission:TagManagement.Create": "创建",
"Permission:TagManagement.Delete": "删除",
"Permission:TagManagement.Update": "更新",
"PickYourReaction": "选择你的回应",
"RatingUndoMessage": "您的评分将被撤消", "RatingUndoMessage": "您的评分将被撤消",
"LoginToRate": "登录进行评分", "Read": "阅读",
"Star": "星" "RepliesToThisComment": "回复此评论",
"Reply": "回复",
"ReplyTo": "回复",
"SamplePageMessage": "Pro模块的示例页面",
"SaveChanges": "保存更改",
"SelectAll": "选择所有",
"Send": "发送",
"SendMessage": "发送消息",
"ShortDescription": "简介",
"Slug": "Slug",
"Source": "源",
"SourceUrl": "源URL",
"Star": "星",
"StartDate": "开始时间",
"Subject": "主题",
"SubjectPlaceholder": "请输入主题",
"Submit": "提交",
"Subscribe": "订阅",
"SuccessfullyDeleted": "删除成功!",
"SuccessfullySaved": "保存成功!",
"TagDeletionConfirmationMessage": "你确定删除 '{0}' 标签吗?",
"Tags": "标签",
"Text": "文本",
"ThankYou": "谢谢你",
"Title": "标题",
"Undo": "撤消",
"Update": "更新",
"UpdatePreferenceSuccessMessage": "您的首选项已经保存",
"UpdateYourEmailPreferences": "更新你的邮件首选项",
"UploadFailedMessage": "上传失败",
"UserId": "用户Id",
"Username": "用户名称",
"YourComment": "你的评论",
"YourEmailAddress": "你的邮件地址",
"YourFullName": "你的全称",
"YourMessage": "你的消息",
"YourReply": "你的回复"
} }
} }

11
modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Comments/CmsKitCommentOptions.cs

@ -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>();
}
}

2
modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Comments/Comment.cs

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

21
modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Comments/CommentEntityTypeDefinition.cs

@ -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;
}
}
}

46
modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Comments/CommentManager.cs

@ -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);
}
}
}

41
modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Comments/DefaultCommentEntityTypeDefinitionStore.cs

@ -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);
}
}
}

27
modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Comments/EntityNotCommentableException.cs

@ -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; }
}
}

17
modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Comments/ICommentEntityTypeDefinitionStore.cs

@ -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);
}
}

4
modules/cms-kit/src/Volo.CmsKit.HttpApi/Volo.CmsKit.HttpApi.csproj

@ -9,9 +9,9 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <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.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> </ItemGroup>
</Project> </Project>

4
modules/cms-kit/src/Volo.CmsKit.HttpApi/Volo/CmsKit/CmsKitHttpApiModule.cs

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

16
modules/cms-kit/src/Volo.CmsKit.Public.Application/Volo/CmsKit/Public/Comments/CommentPublicAppService.cs

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

2
modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Blogs/BlogPost/Default.cshtml

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

4
modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Pages/Default.cshtml

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

4
modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/Public/CmsKit/Blogs/BlogPost.cshtml

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

59
modules/cms-kit/test/Volo.CmsKit.Domain.Tests/Comments/CommentManager_Test.cs

@ -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);
}
}
}

14
modules/cms-kit/test/Volo.CmsKit.TestBase/CmsKitDataSeedContributor.cs

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

16
modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/IdentityRoleNameChangedEto.cs

@ -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; }
}
}

3
modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/AbpIdentityDomainModule.cs

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

12
modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityRole.cs

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

5
modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityRoleNameChangedEvent.cs

@ -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 class IdentityRoleNameChangedEvent
{ {
public IdentityRole IdentityRole { get; set; } public IdentityRole IdentityRole { get; set; }

35
modules/identity/src/Volo.Abp.PermissionManagement.Domain.Identity/Volo/Abp/PermissionManagement/Identity/RoleDeletedEventHandler.cs

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

60
modules/identity/src/Volo.Abp.PermissionManagement.Domain.Identity/Volo/Abp/PermissionManagement/Identity/RoleUpdateEventHandler.cs

@ -1,45 +1,33 @@
using System.Threading.Tasks; using System.Threading.Tasks;
using Volo.Abp.Authorization.Permissions; using Volo.Abp.Authorization.Permissions;
using Volo.Abp.DependencyInjection; using Volo.Abp.DependencyInjection;
using Volo.Abp.Domain.Entities.Events; using Volo.Abp.EventBus.Distributed;
using Volo.Abp.EventBus;
using Volo.Abp.Identity; using Volo.Abp.Identity;
namespace Volo.Abp.PermissionManagement.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 : public async Task HandleEventAsync(IdentityRoleNameChangedEto eventData)
// ILocalEventHandler<IdentityRoleNameChangedEvent>, {
// ITransientDependency var permissionGrantsInRole = await PermissionGrantRepository.GetListAsync(RolePermissionValueProvider.ProviderName, eventData.OldName);
// { foreach (var permissionGrant in permissionGrantsInRole)
// protected IIdentityRoleRepository RoleRepository { get; } {
// protected IPermissionManager PermissionManager { get; } await PermissionManager.UpdateProviderKeyAsync(permissionGrant, eventData.Name);
// 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);
// }
// }
// }
} }

105
modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/Distributed_Role_Change_Events_Test.cs

@ -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();
}
}
}
}

7
modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/LocalizationExtensions/zh-Hans.json

@ -0,0 +1,7 @@
{
"culture": "zh-Hans",
"texts": {
"Volo.Abp.Identity:PasswordTooShort": "密码长度必须大于{0}字符. ",
"Volo.Abp.Identity:PasswordRequiresNonAlphanumeric": "密码必须至少包含一个非字母数字字符."
}
}

60
modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/RoleChangingEvents_Test.cs

@ -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);
// }
// }
}

4
modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/PermissionGrantCacheItemInvalidator.cs

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

22
modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/PermissionManager.cs

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

229
npm/ng-packs/packages/theme-shared/src/lib/handlers/error.handler.ts

@ -12,13 +12,14 @@ import {
} from '@angular/core'; } from '@angular/core';
import { NavigationError, ResolveEnd } from '@angular/router'; import { NavigationError, ResolveEnd } from '@angular/router';
import { Actions, ofActionSuccessful } from '@ngxs/store'; import { Actions, ofActionSuccessful } from '@ngxs/store';
import { Observable, Subject } from 'rxjs'; import { Observable, of, Subject, throwError } from 'rxjs';
import { filter, map } from 'rxjs/operators'; import { catchError, filter, map, switchMap } from 'rxjs/operators';
import snq from 'snq'; import snq from 'snq';
import { HttpErrorWrapperComponent } from '../components/http-error-wrapper/http-error-wrapper.component'; import { HttpErrorWrapperComponent } from '../components/http-error-wrapper/http-error-wrapper.component';
import { ErrorScreenErrorCodes, HttpErrorConfig } from '../models/common'; import { ErrorScreenErrorCodes, HttpErrorConfig } from '../models/common';
import { Confirmation } from '../models/confirmation'; import { Confirmation } from '../models/confirmation';
import { ConfirmationService } from '../services/confirmation.service'; import { ConfirmationService } from '../services/confirmation.service';
import { HTTP_ERROR_HANDLER } from '../tokens/http-error.token';
export const DEFAULT_ERROR_MESSAGES = { export const DEFAULT_ERROR_MESSAGES = {
defaultError: { defaultError: {
@ -70,28 +71,32 @@ export const DEFAULT_ERROR_LOCALIZATIONS = {
export class ErrorHandler { export class ErrorHandler {
componentRef: ComponentRef<HttpErrorWrapperComponent>; componentRef: ComponentRef<HttpErrorWrapperComponent>;
protected httpErrorHandler = this.injector.get(HTTP_ERROR_HANDLER, (_, err: HttpErrorResponse) =>
throwError(err),
);
constructor( constructor(
private actions: Actions, protected actions: Actions,
private routerEvents: RouterEvents, protected routerEvents: RouterEvents,
private confirmationService: ConfirmationService, protected confirmationService: ConfirmationService,
private cfRes: ComponentFactoryResolver, protected cfRes: ComponentFactoryResolver,
private rendererFactory: RendererFactory2, protected rendererFactory: RendererFactory2,
private injector: Injector, protected injector: Injector,
@Inject('HTTP_ERROR_CONFIG') private httpErrorConfig: HttpErrorConfig, @Inject('HTTP_ERROR_CONFIG') protected httpErrorConfig: HttpErrorConfig,
) { ) {
this.listenToRestError(); this.listenToRestError();
this.listenToRouterError(); this.listenToRouterError();
this.listenToRouterDataResolved(); this.listenToRouterDataResolved();
} }
private listenToRouterError() { protected listenToRouterError() {
this.routerEvents this.routerEvents
.getNavigationEvents('Error') .getNavigationEvents('Error')
.pipe(filter(this.filterRouteErrors)) .pipe(filter(this.filterRouteErrors))
.subscribe(() => this.show404Page()); .subscribe(() => this.show404Page());
} }
private listenToRouterDataResolved() { protected listenToRouterDataResolved() {
this.routerEvents this.routerEvents
.getEvents(ResolveEnd) .getEvents(ResolveEnd)
.pipe(filter(() => !!this.componentRef)) .pipe(filter(() => !!this.componentRef))
@ -101,113 +106,127 @@ export class ErrorHandler {
}); });
} }
private listenToRestError() { protected listenToRestError() {
this.actions this.actions
.pipe( .pipe(
ofActionSuccessful(RestOccurError), ofActionSuccessful(RestOccurError),
map(action => action.payload), map(action => action.payload),
filter(this.filterRestErrors), filter(this.filterRestErrors),
switchMap(this.executeErrorHandler),
) )
.subscribe(err => { .subscribe();
const body = snq(() => err.error.error, { }
key: DEFAULT_ERROR_LOCALIZATIONS.defaultError.title,
defaultValue: DEFAULT_ERROR_MESSAGES.defaultError.title,
});
if (err instanceof HttpErrorResponse && err.headers.get('_AbpErrorFormat')) { private executeErrorHandler = error => {
const confirmation$ = this.showError(null, null, body); const returnValue = this.httpErrorHandler(this.injector, error);
if (err.status === 401) { return (returnValue instanceof Observable ? returnValue : of(null)).pipe(
confirmation$.subscribe(() => { catchError(err => {
this.navigateToLogin(); this.handleError(err);
}); return of(null);
} }),
} else { );
switch (err.status) { };
case 401:
this.canCreateCustomError(401) private handleError(err: any) {
? this.show401Page() const body = snq(() => err.error.error, {
: this.showError( key: DEFAULT_ERROR_LOCALIZATIONS.defaultError.title,
{ defaultValue: DEFAULT_ERROR_MESSAGES.defaultError.title,
key: DEFAULT_ERROR_LOCALIZATIONS.defaultError401.title, });
defaultValue: DEFAULT_ERROR_MESSAGES.defaultError401.title,
}, if (err instanceof HttpErrorResponse && err.headers.get('_AbpErrorFormat')) {
{ const confirmation$ = this.showError(null, null, body);
key: DEFAULT_ERROR_LOCALIZATIONS.defaultError401.details,
defaultValue: DEFAULT_ERROR_MESSAGES.defaultError401.details, if (err.status === 401) {
}, confirmation$.subscribe(() => {
).subscribe(() => this.navigateToLogin()); this.navigateToLogin();
break; });
case 403: }
this.createErrorComponent({ } else {
title: { switch (err.status) {
key: DEFAULT_ERROR_LOCALIZATIONS.defaultError403.title, case 401:
defaultValue: DEFAULT_ERROR_MESSAGES.defaultError403.title, this.canCreateCustomError(401)
}, ? this.show401Page()
details: { : this.showError(
key: DEFAULT_ERROR_LOCALIZATIONS.defaultError403.details, {
defaultValue: DEFAULT_ERROR_MESSAGES.defaultError403.details, key: DEFAULT_ERROR_LOCALIZATIONS.defaultError401.title,
}, defaultValue: DEFAULT_ERROR_MESSAGES.defaultError401.title,
status: 403,
});
break;
case 404:
this.canCreateCustomError(404)
? this.show404Page()
: this.showError(
{
key: DEFAULT_ERROR_LOCALIZATIONS.defaultError404.details,
defaultValue: DEFAULT_ERROR_MESSAGES.defaultError404.details,
},
{
key: DEFAULT_ERROR_LOCALIZATIONS.defaultError404.title,
defaultValue: DEFAULT_ERROR_MESSAGES.defaultError404.title,
},
);
break;
case 500:
this.createErrorComponent({
title: {
key: DEFAULT_ERROR_LOCALIZATIONS.defaultError500.title,
defaultValue: DEFAULT_ERROR_MESSAGES.defaultError500.title,
}, },
details: { {
key: DEFAULT_ERROR_LOCALIZATIONS.defaultError500.details, key: DEFAULT_ERROR_LOCALIZATIONS.defaultError401.details,
defaultValue: DEFAULT_ERROR_MESSAGES.defaultError500.details, defaultValue: DEFAULT_ERROR_MESSAGES.defaultError401.details,
}, },
status: 500, ).subscribe(() => this.navigateToLogin());
}); break;
break; case 403:
case 0: this.createErrorComponent({
if (err.statusText === 'Unknown Error') { title: {
this.createErrorComponent({ key: DEFAULT_ERROR_LOCALIZATIONS.defaultError403.title,
title: { defaultValue: DEFAULT_ERROR_MESSAGES.defaultError403.title,
key: DEFAULT_ERROR_LOCALIZATIONS.defaultError.title, },
defaultValue: DEFAULT_ERROR_MESSAGES.defaultError.title, details: {
}, key: DEFAULT_ERROR_LOCALIZATIONS.defaultError403.details,
details: err.message, defaultValue: DEFAULT_ERROR_MESSAGES.defaultError403.details,
isHomeShow: false, },
}); status: 403,
} });
break; break;
default: case 404:
this.showError( this.canCreateCustomError(404)
? this.show404Page()
: this.showError(
{ {
key: DEFAULT_ERROR_LOCALIZATIONS.defaultError.details, key: DEFAULT_ERROR_LOCALIZATIONS.defaultError404.details,
defaultValue: DEFAULT_ERROR_MESSAGES.defaultError.details, defaultValue: DEFAULT_ERROR_MESSAGES.defaultError404.details,
}, },
{ {
key: DEFAULT_ERROR_LOCALIZATIONS.defaultError.title, key: DEFAULT_ERROR_LOCALIZATIONS.defaultError404.title,
defaultValue: DEFAULT_ERROR_MESSAGES.defaultError.title, defaultValue: DEFAULT_ERROR_MESSAGES.defaultError404.title,
}, },
); );
break; break;
case 500:
this.createErrorComponent({
title: {
key: DEFAULT_ERROR_LOCALIZATIONS.defaultError500.title,
defaultValue: DEFAULT_ERROR_MESSAGES.defaultError500.title,
},
details: {
key: DEFAULT_ERROR_LOCALIZATIONS.defaultError500.details,
defaultValue: DEFAULT_ERROR_MESSAGES.defaultError500.details,
},
status: 500,
});
break;
case 0:
if (err.statusText === 'Unknown Error') {
this.createErrorComponent({
title: {
key: DEFAULT_ERROR_LOCALIZATIONS.defaultError.title,
defaultValue: DEFAULT_ERROR_MESSAGES.defaultError.title,
},
details: err.message,
isHomeShow: false,
});
} }
} break;
}); default:
this.showError(
{
key: DEFAULT_ERROR_LOCALIZATIONS.defaultError.details,
defaultValue: DEFAULT_ERROR_MESSAGES.defaultError.details,
},
{
key: DEFAULT_ERROR_LOCALIZATIONS.defaultError.title,
defaultValue: DEFAULT_ERROR_MESSAGES.defaultError.title,
},
);
break;
}
}
} }
private show401Page() { protected show401Page() {
this.createErrorComponent({ this.createErrorComponent({
title: { title: {
key: DEFAULT_ERROR_LOCALIZATIONS.defaultError401.title, key: DEFAULT_ERROR_LOCALIZATIONS.defaultError401.title,
@ -217,7 +236,7 @@ export class ErrorHandler {
}); });
} }
private show404Page() { protected show404Page() {
this.createErrorComponent({ this.createErrorComponent({
title: { title: {
key: DEFAULT_ERROR_LOCALIZATIONS.defaultError404.title, key: DEFAULT_ERROR_LOCALIZATIONS.defaultError404.title,
@ -227,7 +246,7 @@ export class ErrorHandler {
}); });
} }
private showError( protected showError(
message?: LocalizationParam, message?: LocalizationParam,
title?: LocalizationParam, title?: LocalizationParam,
body?: any, body?: any,
@ -256,7 +275,7 @@ export class ErrorHandler {
}); });
} }
private navigateToLogin() { protected navigateToLogin() {
this.injector.get(AuthService).initLogin(); this.injector.get(AuthService).initLogin();
} }
@ -304,13 +323,13 @@ export class ErrorHandler {
); );
} }
private filterRestErrors = ({ status }: HttpErrorResponse): boolean => { protected filterRestErrors = ({ status }: HttpErrorResponse): boolean => {
if (typeof status !== 'number') return false; if (typeof status !== 'number') return false;
return this.httpErrorConfig.skipHandledErrorCodes.findIndex(code => code === status) < 0; return this.httpErrorConfig.skipHandledErrorCodes.findIndex(code => code === status) < 0;
}; };
private filterRouteErrors = (navigationError: NavigationError): boolean => { protected filterRouteErrors = (navigationError: NavigationError): boolean => {
return ( return (
snq(() => navigationError.error.message.indexOf('Cannot match') > -1) && snq(() => navigationError.error.message.indexOf('Cannot match') > -1) &&
this.httpErrorConfig.skipHandledErrorCodes.findIndex(code => code === 404) < 0 this.httpErrorConfig.skipHandledErrorCodes.findIndex(code => code === 404) < 0

9
npm/ng-packs/packages/theme-shared/src/lib/models/common.ts

@ -1,5 +1,7 @@
import { Type } from '@angular/core'; import { HttpErrorResponse } from '@angular/common/http';
import { Injector, Type } from '@angular/core';
import { Validation } from '@ngx-validate/core'; import { Validation } from '@ngx-validate/core';
import { Observable } from 'rxjs';
export interface RootParams { export interface RootParams {
httpErrorConfig: HttpErrorConfig; httpErrorConfig: HttpErrorConfig;
@ -17,4 +19,9 @@ export interface HttpErrorConfig {
}; };
} }
export type HttpErrorHandler = (
injector: Injector,
httpError: HttpErrorResponse,
) => Observable<any>;
export type LocaleDirection = 'ltr' | 'rtl'; export type LocaleDirection = 'ltr' | 'rtl';

6
npm/ng-packs/packages/theme-shared/src/lib/tokens/http-error.token.ts

@ -1,5 +1,5 @@
import { InjectionToken } from '@angular/core'; import { InjectionToken } from '@angular/core';
import { HttpErrorConfig } from '../models/common'; import { HttpErrorConfig, HttpErrorHandler } from '../models/common';
export function httpErrorConfigFactory(config = {} as HttpErrorConfig) { export function httpErrorConfigFactory(config = {} as HttpErrorConfig) {
if (config.errorScreen && config.errorScreen.component && !config.errorScreen.forWhichErrors) { if (config.errorScreen && config.errorScreen.component && !config.errorScreen.forWhichErrors) {
@ -13,4 +13,6 @@ export function httpErrorConfigFactory(config = {} as HttpErrorConfig) {
} as HttpErrorConfig; } as HttpErrorConfig;
} }
export const HTTP_ERROR_CONFIG = new InjectionToken('HTTP_ERROR_CONFIG'); export const HTTP_ERROR_CONFIG = new InjectionToken<HttpErrorConfig>('HTTP_ERROR_CONFIG');
export const HTTP_ERROR_HANDLER = new InjectionToken<HttpErrorHandler>('HTTP_ERROR_HANDLER');

4
npm/packs/cms-kit/package.json

@ -5,7 +5,9 @@
"access": "public" "access": "public"
}, },
"dependencies": { "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" "gitHead": "bb4ea17d5996f01889134c138d00b6c8f858a431"
} }

3
npm/packs/tui-editor/abp.resourcemapping.js

@ -3,6 +3,7 @@
"@node_modules/to-mark/dist/to-mark.min.js": "@libs/to-mark/", "@node_modules/to-mark/dist/to-mark.min.js": "@libs/to-mark/",
"@node_modules/tui-code-snippet/dist/*.*": "@libs/tui-code-snippet/", "@node_modules/tui-code-snippet/dist/*.*": "@libs/tui-code-snippet/",
"@node_modules/squire-rte/build/squire.js": "@libs/squire-rte/", "@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/"
} }
} }

7
npm/packs/tui-editor/src/tui-editor-jquery-patch.js

@ -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>" );
};

43
templates/wpf/src/MyCompanyName.MyProjectName/App.xaml.cs

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

12
templates/wpf/src/MyCompanyName.MyProjectName/MainWindow.xaml.cs

@ -1,17 +1,5 @@
using System; using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows; 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 namespace MyCompanyName.MyProjectName
{ {

2
templates/wpf/src/MyCompanyName.MyProjectName/MyCompanyName.MyProjectName.csproj

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

Loading…
Cancel
Save