Browse Source

Merge branch 'dev' into berkan/cli-analytics

pull/7988/head
Berkan Sasmaz 5 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": "名称",
"CreationTime": "创建时间",
"IsPro": "是否为专业版",
"IsFreeToActiveLicenseOwners": "许可证所有者免费",
"ShowOnModuleList": "展示模块列表",
"EfCoreConfigureMethodName": "配置方法法",
"IsProFilter": "是否为专业版",
@ -90,6 +91,7 @@
"UserNotFound": "用户不存在",
"{0}WillBeRemovedFromDevelopers": "{0} 将从开发者中移除, 你确定吗?",
"{0}WillBeRemovedFromOwners": "{0} 将从所有者中移除, 你确定吗?",
"{0}WillBeRemovedFromMembers": "{0} 将从成员中删除, 你确定吗?",
"Computers": "计算机",
"UniqueComputerId": "计算机唯一ID",
"LastSeenDate": "上次查看日期",
@ -194,6 +196,27 @@
"CoverImage": "封面图片",
"RemoveCacheConfirmationMessage": "你确定要删除\"{0}\" 文章缓存?",
"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.",
"LinkedinUrlValidationMessage": "Your Linkedin URL can not include whitespace, please be sure your Linkedin URL is correct.",
"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": "视频内容来源",
"GithubPostForm": "Github文章来源",
"ExternalPostForm": "外部文章来源",
"PostSourceTypeChooses": "我们接受文章的三种来源类型;",
"Posts": "文章"
"HowToPost": "如何发布",
"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": "项目名称",
"ProjectType": "项目类型",
"DatabaseProvider": "数据库提供者",
"DatabaseManagementSystem": "数据库管理系统",
"NTier": "N层",
"IncludeUserInterface": "包含用户界面",
"CreateNow": "现在创建",
@ -194,7 +195,7 @@
"MultipleUIOptionsExplanation": "核心框架设计为独立与UI,可以和任何类型的UI系统一起使用. 同时提供了多个开箱即用的预构建集成选项.",
"MultipleDBOptionsExplanation": "该框架可以使用任何数据源,并且以下提供程序已得到正式开发和支持;",
"SelectLanguage": "选择语言",
"LatestArticleOnCommunity": "关于ABP社区的最新文章",
"Register": "寄存器"
"LatestArticleOnCommunity": "关于ABP社区的最新文章",
"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),
(entityBuilder, propertyBuilder) =>
{
propertyBuilder.IsRequired();
propertyBuilder.HasDefaultValue();
propertyBuilder.HasMaxLength(UserConsts.MaxTitleLength);
}
).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
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
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
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
`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包中.
* 标准模板应用层与领域层**不引用**EF Core 包以实现数据库提供程序独立.
根据你的需求和开发模式,你可以根据以下选项使用异步方法.s
根据你的需求和开发模式,你可以根据以下选项使用异步方法.
> 强烈建议使用异步方法! 在执行数据库查询时不要使用同步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)
{
context.Files.AddIfNotContains("/libs/tui-editor/tui-editor-jquery-patch.js");
context.Files.AddIfNotContains("/libs/to-mark/to-mark.min.js");
if (context.FileProvider.GetFileInfo("/libs/tui-code-snippet/tui-code-snippet.min.js").Exists)

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 =>
f.Name.EndsWith(".csproj") ||
f.Name.EndsWith("Module.cs") ||
f.Name.EndsWith("MyProjectNameMigrationsDbContext.cs") ||
f.Name.EndsWith("MyProjectNameMigrationsDbContextBase.cs") ||
f.Name.EndsWith("MyProjectNameGlobalFeatureConfigurator.cs") ||
(f.Name.EndsWith(".cshtml") && f.Name.Contains("MyCompanyName.MyProjectName.Web.Public"))
);
f.Name.EndsWith(".cs") ||
f.Name.EndsWith(".json") ||
f.Name.EndsWith(".cshtml"));
foreach (var file in commonFiles)
{

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)
{
context.Files.FirstOrDefault(f => f.Name.EndsWith("MyCompanyName.MyProjectName.Web.Public.csproj"))?.RemoveTemplateCodeIfNot("EFCORE");
context.Files.FirstOrDefault(f => f.Name.EndsWith("MyProjectNameWebPublicModule.cs"))?.RemoveTemplateCodeIfNot("EFCORE");
foreach (var file in context.Files)
{
if (file.Name.EndsWith(".cs") || file.Name.EndsWith(".csproj") || file.Name.EndsWith(".json"))
{
file.RemoveTemplateCodeIfNot("EFCORE");
}
}
}
}
}

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)
{
context.Files.FirstOrDefault(f => f.Name.EndsWith("MyCompanyName.MyProjectName.Domain.Shared.csproj"))?.RemoveTemplateCodeIf("CMS-KIT");
context.Files.FirstOrDefault(f => f.Name.EndsWith("MyProjectNameDomainSharedModule.cs"))?.RemoveTemplateCodeIf("CMS-KIT");
var commonFiles = context.Files.Where(f =>
f.Name.EndsWith(".csproj") ||
f.Name.EndsWith(".cs") ||
f.Name.EndsWith(".cshtml"));
foreach (var file in commonFiles)
{
file.RemoveTemplateCodeIf("CMS-KIT");
}
}
}
}

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)
{
context.Files.FirstOrDefault(f => f.Name.EndsWith("MyCompanyName.MyProjectName.Web.csproj"))?.RemoveTemplateCodeIfNot("PUBLIC-REDIS");
context.Files.FirstOrDefault(f => f.Name.EndsWith("MyProjectNameWebModule.cs"))?.RemoveTemplateCodeIfNot("PUBLIC-REDIS");
context.Files.FirstOrDefault(f => f.Name.EndsWith("MyCompanyName.MyProjectName.HttpApi.HostWithIds.csproj"))?.RemoveTemplateCodeIfNot("PUBLIC-REDIS");
context.Files.FirstOrDefault(f => f.Name.EndsWith("MyCompanyName.MyProjectName.HttpApi.Host.csproj"))?.RemoveTemplateCodeIfNot("PUBLIC-REDIS");
context.Files.FirstOrDefault(f => f.Name.EndsWith("MyProjectNameHttpApiHostModule.cs"))?.RemoveTemplateCodeIfNot("PUBLIC-REDIS");
var appSettingsFiles = context.Files.Where(f => f.Name.EndsWith("appSettings.json", StringComparison.InvariantCultureIgnoreCase)).ToList();
foreach (var appSettings in appSettingsFiles)
foreach (var file in context.Files)
{
appSettings.RemoveTemplateCodeIfNot("PUBLIC-REDIS");
if (file.Name.EndsWith(".cs") || file.Name.EndsWith(".csproj") || file.Name.EndsWith(".json"))
{
file.RemoveTemplateCodeIfNot("PUBLIC-REDIS");
}
}
}
}

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.ProjectName
).Run();
new SolutionRenamer(
context.Files,
null,
"MyProjectName",
null,
SolutionName.Parse(context.BuildArgs.SolutionName.CompanyName).ProjectName
).Run();
}
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)
{
if (!file.Content.Contains("</TEMPLATE-REMOVE>"))
{
return;
}
file.NormalizeLineEndings();
var lines = file.GetLines();
@ -59,6 +64,11 @@ namespace Volo.Abp.Cli.ProjectBuilding.Files
private static void RemoveMarkedTemplateCode(this FileEntry file, string beginMark)
{
if (!file.Content.Contains("</TEMPLATE-REMOVE>"))
{
return;
}
file.NormalizeLineEndings();
var lines = file.GetLines();

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

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

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

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

@ -3,8 +3,10 @@ using System.Collections.Generic;
using System.Linq;
using JetBrains.Annotations;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Internal;
using Volo.Abp.Logging;
using Volo.Abp.Modularity;
namespace Volo.Abp
@ -60,7 +62,7 @@ namespace Volo.Abp
{
//TODO: Shutdown if not done before?
}
protected virtual void SetServiceProvider(IServiceProvider serviceProvider)
{
ServiceProvider = serviceProvider;
@ -71,12 +73,31 @@ namespace Volo.Abp
{
using (var scope = ServiceProvider.CreateScope())
{
WriteInitLogs(scope.ServiceProvider);
scope.ServiceProvider
.GetRequiredService<IModuleManager>()
.InitializeModules(new ApplicationInitializationContext(scope.ServiceProvider));
}
}
protected virtual void WriteInitLogs(IServiceProvider serviceProvider)
{
var logger = serviceProvider.GetService<ILogger<AbpApplicationBase>>();
if (logger == null)
{
return;
}
var initLogger = serviceProvider.GetRequiredService<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)
{
return services
@ -87,7 +108,7 @@ namespace Volo.Abp
options.PlugInSources
);
}
//TODO: We can extract a new class for this
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.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Volo.Abp.Logging;
using Volo.Abp.Modularity;
using Volo.Abp.Reflection;
@ -16,7 +17,7 @@ namespace Volo.Abp.Internal
}
internal static void AddCoreAbpServices(this IServiceCollection services,
IAbpApplication abpApplication,
IAbpApplication abpApplication,
AbpApplicationCreationOptions applicationCreationOptions)
{
var moduleLoader = new ModuleLoader();
@ -35,6 +36,7 @@ namespace Volo.Abp.Internal
services.TryAddSingleton<IModuleLoader>(moduleLoader);
services.TryAddSingleton<IAssemblyFinder>(assemblyFinder);
services.TryAddSingleton<ITypeFinder>(typeFinder);
services.TryAddSingleton<IInitLoggerFactory>(new DefaultInitLoggerFactory());
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.Linq;
using System.Reflection;
using Microsoft.Extensions.Logging;
namespace Volo.Abp.Modularity
{
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>();
AddModuleAndDependenciesResursively(moduleTypes, startupModuleType);
logger.Log(LogLevel.Information, "Loaded ABP modules:");
AddModuleAndDependenciesResursively(moduleTypes, startupModuleType, logger);
return moduleTypes;
}
@ -35,7 +37,11 @@ namespace Volo.Abp.Modularity
return dependencies;
}
private static void AddModuleAndDependenciesResursively(List<Type> moduleTypes, Type moduleType)
private static void AddModuleAndDependenciesResursively(
List<Type> moduleTypes,
Type moduleType,
ILogger logger,
int depth = 0)
{
AbpModule.CheckAbpModuleType(moduleType);
@ -45,10 +51,11 @@ namespace Volo.Abp.Modularity
}
moduleTypes.Add(moduleType);
logger.Log(LogLevel.Information, $"{new string(' ', depth * 2)}- {moduleType.FullName}");
foreach (var dependedModuleType in FindDependedModuleTypes(moduleType))
{
AddModuleAndDependenciesResursively(moduleTypes, dependedModuleType);
AddModuleAndDependenciesResursively(moduleTypes, dependedModuleType, logger, depth + 1);
}
}
}

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

@ -25,7 +25,7 @@ namespace Volo.Abp.Modularity
}
private List<IAbpModuleDescriptor> GetDescriptors(
IServiceCollection services,
IServiceCollection services,
Type startupModuleType,
PlugInSourceList plugInSources)
{
@ -43,14 +43,16 @@ namespace Volo.Abp.Modularity
Type startupModuleType,
PlugInSourceList plugInSources)
{
var logger = services.GetInitLogger<AbpApplicationBase>();
//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));
}
//Plugin modules
foreach (var moduleType in plugInSources.GetAllModules())
foreach (var moduleType in plugInSources.GetAllModules(logger))
{
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)
{
LogListOfModules();
foreach (var contributor in _lifecycleContributors)
{
foreach (var module in _moduleContainer.Modules)
@ -52,16 +50,6 @@ namespace Volo.Abp.Modularity
_logger.LogInformation("Initialized all ABP modules.");
}
private void LogListOfModules()
{
_logger.LogInformation("Loaded ABP modules:");
foreach (var module in _moduleContainer.Modules)
{
_logger.LogInformation("- " + module.Type.FullName);
}
}
public void ShutdownModules(ApplicationShutdownContext context)
{
var modules = _moduleContainer.Modules.Reverse().ToList();

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

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

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

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

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

@ -18,6 +18,8 @@
"401Message": "未授权",
"403Message": "禁止访问",
"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 Shouldly;
using Volo.Abp.Logging;
using Volo.Abp.Modularity.PlugIns;
using Xunit;
@ -11,7 +12,12 @@ namespace Volo.Abp.Modularity
public void Should_Load_Modules_By_Dependency_Order()
{
var moduleLoader = new ModuleLoader();
var modules = moduleLoader.LoadModules(new ServiceCollection(), typeof(MyStartupModule), new PlugInSourceList());
var modules = moduleLoader.LoadModules(
new ServiceCollection()
.AddSingleton<IInitLoggerFactory>(new DefaultInitLoggerFactory()),
typeof(MyStartupModule),
new PlugInSourceList()
);
modules.Length.ShouldBe(2);
modules[0].Type.ShouldBe(typeof(IndependentEmptyModule));
modules[1].Type.ShouldBe(typeof(MyStartupModule));
@ -22,7 +28,7 @@ namespace Volo.Abp.Modularity
{
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 System.Collections.Generic;
using Microsoft.AspNetCore.Authorization;
using Microsoft.Extensions.Caching.Distributed;
using Volo.Abp.Caching;
using Volo.Blogging.Comments;
using Volo.Blogging.Tagging;
using Volo.Blogging.Tagging.Dtos;
@ -18,71 +20,41 @@ namespace Volo.Blogging.Posts
private readonly IPostRepository _postRepository;
private readonly ITagRepository _tagRepository;
private readonly ICommentRepository _commentRepository;
private readonly IDistributedCache<ListResultDto<PostWithDetailsDto>> _postsCache;
public PostAppService(IPostRepository postRepository, ITagRepository tagRepository, ICommentRepository commentRepository, IBlogUserLookupService userLookupService)
public PostAppService(IPostRepository postRepository, ITagRepository tagRepository, ICommentRepository commentRepository, IBlogUserLookupService userLookupService, IDistributedCache<ListResultDto<PostWithDetailsDto>> postsCache)
{
UserLookupService = userLookupService;
_postRepository = postRepository;
_tagRepository = tagRepository;
_commentRepository = commentRepository;
_postsCache = postsCache;
}
public async Task<ListResultDto<PostWithDetailsDto>> GetListByBlogIdAndTagName(Guid id, string tagName)
{
var posts = await _postRepository.GetPostsByBlogId(id);
var tag = tagName.IsNullOrWhiteSpace() ? null : await _tagRepository.FindByNameAsync(id, tagName);
var userDictionary = new Dictionary<Guid, BlogUserDto>();
var postDtos = new List<PostWithDetailsDto>(ObjectMapper.Map<List<Post>, List<PostWithDetailsDto>>(posts));
foreach (var postDto in postDtos)
{
postDto.Tags = await GetTagsOfPost(postDto.Id);
}
if (tag != null)
{
postDtos = await FilterPostsByTag(postDtos, tag);
}
foreach (var postDto in postDtos)
{
if (postDto.CreatorId.HasValue)
var cacheKey = id.ToString() + "-" + tagName;
return await _postsCache.GetOrAddAsync(
cacheKey,
async () => await GetPostsByBlogIdAndTagName(id, tagName),
() => new DistributedCacheEntryOptions
{
if (!userDictionary.ContainsKey(postDto.CreatorId.Value))
{
var creatorUser = await UserLookupService.FindByIdAsync(postDto.CreatorId.Value);
if (creatorUser != null)
{
userDictionary[creatorUser.Id] = ObjectMapper.Map<BlogUser, BlogUserDto>(creatorUser);
}
}
if (userDictionary.ContainsKey(postDto.CreatorId.Value))
{
postDto.Writer = userDictionary[(Guid)postDto.CreatorId];
}
AbsoluteExpiration = DateTimeOffset.Now.AddHours(6)
}
}
return new ListResultDto<PostWithDetailsDto>(postDtos);
);
}
public async Task<ListResultDto<PostWithDetailsDto>> GetTimeOrderedListAsync(Guid blogId)
{
var posts = await _postRepository.GetOrderedList(blogId);
var postDtos = new List<PostWithDetailsDto>(ObjectMapper.Map<List<Post>, List<PostWithDetailsDto>>(posts));
foreach (var postDto in postDtos)
{
var creatorUser = await UserLookupService.FindByIdAsync(postDto.CreatorId.Value);
if (creatorUser != null)
return await _postsCache.GetOrAddAsync(
blogId.ToString(),
async () => await GetTimeOrderedPostsAsync(blogId),
() => new DistributedCacheEntryOptions
{
postDto.Writer = ObjectMapper.Map<BlogUser, BlogUserDto>(creatorUser);
AbsoluteExpiration = DateTimeOffset.Now.AddHours(6)
}
}
return new ListResultDto<PostWithDetailsDto>(postDtos);
);
}
public async Task<PostWithDetailsDto> GetForReadingAsync(GetPostInput input)
@ -185,6 +157,64 @@ namespace Volo.Blogging.Posts
return ObjectMapper.Map<Post, PostWithDetailsDto>(post);
}
private async Task<ListResultDto<PostWithDetailsDto>> GetPostsByBlogIdAndTagName(Guid id, string tagName)
{
var posts = await _postRepository.GetPostsByBlogId(id);
var tag = tagName.IsNullOrWhiteSpace() ? null : await _tagRepository.FindByNameAsync(id, tagName);
var userDictionary = new Dictionary<Guid, BlogUserDto>();
var postDtos = new List<PostWithDetailsDto>(ObjectMapper.Map<List<Post>, List<PostWithDetailsDto>>(posts));
foreach (var postDto in postDtos)
{
postDto.Tags = await GetTagsOfPost(postDto.Id);
}
if (tag != null)
{
postDtos = await FilterPostsByTag(postDtos, tag);
}
foreach (var postDto in postDtos)
{
if (postDto.CreatorId.HasValue)
{
if (!userDictionary.ContainsKey(postDto.CreatorId.Value))
{
var creatorUser = await UserLookupService.FindByIdAsync(postDto.CreatorId.Value);
if (creatorUser != null)
{
userDictionary[creatorUser.Id] = ObjectMapper.Map<BlogUser, BlogUserDto>(creatorUser);
}
}
if (userDictionary.ContainsKey(postDto.CreatorId.Value))
{
postDto.Writer = userDictionary[(Guid)postDto.CreatorId];
}
}
}
return new ListResultDto<PostWithDetailsDto>(postDtos);
}
private async Task<ListResultDto<PostWithDetailsDto>> GetTimeOrderedPostsAsync(Guid blogId)
{
var posts = await _postRepository.GetOrderedList(blogId);
var postDtos = new List<PostWithDetailsDto>(ObjectMapper.Map<List<Post>, List<PostWithDetailsDto>>(posts));
foreach (var postDto in postDtos)
{
var creatorUser = await UserLookupService.FindByIdAsync(postDto.CreatorId.Value);
if (creatorUser != null)
{
postDto.Writer = ObjectMapper.Map<BlogUser, BlogUserDto>(creatorUser);
}
}
return new ListResultDto<PostWithDetailsDto>(postDtos);
}
private async Task<string> RenameUrlIfItAlreadyExistAsync(Guid blogId, string url, Post existingPost = null)
{
if (await _postRepository.IsPostUrlInUseAsync(blogId, url, existingPost?.Id))

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"));
var blogPostManagement = cmsGroup.AddPermission(CmsKitAdminPermissions.BlogPosts.Default, L("Permission:BlogPostManagement"));
blogManagement.AddChild(CmsKitAdminPermissions.BlogPosts.Create, L("Permission:BlogPostManagement.Create"));
blogManagement.AddChild(CmsKitAdminPermissions.BlogPosts.Update, L("Permission:BlogPostManagement.Update"));
blogManagement.AddChild(CmsKitAdminPermissions.BlogPosts.Delete, L("Permission:BlogPostManagement.Delete"));
blogPostManagement.AddChild(CmsKitAdminPermissions.BlogPosts.Create, L("Permission:BlogPostManagement.Create"));
blogPostManagement.AddChild(CmsKitAdminPermissions.BlogPosts.Update, L("Permission:BlogPostManagement.Update"));
blogPostManagement.AddChild(CmsKitAdminPermissions.BlogPosts.Delete, L("Permission:BlogPostManagement.Delete"));
}
}

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

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);
var newId = GuidGenerator.Create();
using (var stream = inputStream.GetStream())
{
var newEntity = await MediaDescriptorManager.CreateAsync(inputStream.EntityType, inputStream.Name, inputStream.ContentType, inputStream.ContentLength ?? 0);
await MediaContainer.SaveAsync(newId.ToString(), stream);
await MediaContainer.SaveAsync(newEntity.Id.ToString(), stream);
await MediaDescriptorRepository.InsertAsync(newEntity);
return ObjectMapper.Map<MediaDescriptor, MediaDescriptorDto>(newEntity);

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/Update", "/Cms/BlogPosts/Update/{Id}");
options.Conventions.AddPageRoute("/CmsKit/Comments/Index", "/Cms/Comments");
options.Conventions.AddPageRoute("/CmsKit/Comments/Details", "/Cms/Details");
options.Conventions.AddPageRoute("/CmsKit/Comments/Details", "/Cms/Comments/{Id}");
});
Configure<AbpPageToolbarOptions>(options =>

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

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

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

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

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

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

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

@ -18,7 +18,7 @@
success: function (result) {
abp.notify.success(l('SuccessfullySaved'));
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 static class Comments
{
public const string EntityNotCommentable = "CmsKit:Comments:0001";
}
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:Blog:0001": "The given slug ({Slug}) already exists!",
"CmsKit:BlogPost:0001": "The given slug already exists!",
"CmsKit:Comments:0001": "The entity {0} is not commentable.",
"CmsKit:Media:0001": "'{Name}' is not a valid media name.",
"CmsKit:Media:0002": "The entity can't have media.",
"CmsKit:Page:0001": "The given url ({0}) already exists.",
"CmsKit:Tag:0002": "The entity is not taggable!",
"CmsKit:Media:0002": "The entity can't have media.",
"CommentAuthorizationExceptionMessage": "Those comments are not allowed for public display.",
"CommentDeletionConfirmationMessage": "This comment and all replies will be deleted!",
"Comments": "Comments",

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

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

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

@ -1,22 +1,124 @@
{
"culture": "zh-Hans",
"texts": {
"PickYourReaction": "选择你的回应",
"YourComment": "你的评论",
"YourReply": "你的回复",
"BlogDeletionConfirmationMessage": "博客 '{0}' 将被删除. 你确定吗?",
"BlogFeatureNotAvailable": "这个功能目前不可用. 使用 `GlobalFeatureManager` 来启用它.",
"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": "评论",
"Send": "发送",
"ContentDeletionConfirmationMessage": "你确定要删除这个内容吗?",
"Contents": "内容",
"CoverImage": "封面图片",
"CreateBlogPostPage": "新博客帖子",
"CreationTime": "创建时间",
"Delete": "删除",
"Reply": "回复",
"Update": "更新",
"Detail": "详情",
"Details": "详情",
"DoYouPreferAdditionalEmails": "你是否更喜欢额外的邮件?",
"Edit": "修改",
"EndDate": "结束时间",
"EntityId": "实体Id",
"EntityType": "实体类型",
"ExportCSV": "导出CSV",
"Features": "功能",
"GenericDeletionConfirmationMessage": "你确定删除 '{0}' 吗?",
"LastModification": "最后一次修改",
"LoginToAddComment": "登录添加评论",
"LoginToRate": "登录进行评分",
"LoginToReply": "登录进行回复",
"Menu:CMS": "CMS",
"Message": "消息",
"MessageDeletionConfirmationMessage": "这条评论将被完全删除",
"CommentAuthorizationExceptionMessage": "这些评论不允许公开显示",
"Undo": "撤消",
"Name": "名称",
"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": "您的评分将被撤消",
"LoginToRate": "登录进行评分",
"Star": "星"
"Read": "阅读",
"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,
[NotNull] string entityType,
[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>
<ItemGroup>
<ProjectReference Include="..\Volo.CmsKit.Admin.Application\Volo.CmsKit.Admin.Application.csproj" />
<ProjectReference Include="..\Volo.CmsKit.Admin.HttpApi\Volo.CmsKit.Admin.HttpApi.csproj" />
<ProjectReference Include="..\Volo.CmsKit.Application.Contracts\Volo.CmsKit.Application.Contracts.csproj" />
<ProjectReference Include="..\Volo.CmsKit.Public.Application\Volo.CmsKit.Public.Application.csproj" />
<ProjectReference Include="..\Volo.CmsKit.Public.HttpApi\Volo.CmsKit.Public.HttpApi.csproj" />
</ItemGroup>
</Project>

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

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

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

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
<h1>@Model.Title</h1>
<abp-card>
<abp-card-header>
<abp-card-title>@Model.Title</abp-card-title>
<abp-card-subtitle>@Model.Author?.UserName</abp-card-subtitle>
</abp-card-header>

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
@inject IPageLayout PageLayout
@{
PageLayout.Content.Title = Model.Title;
}
<h1>@Model.Title</h1>
<abp-card>
<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
@{
PageLayout.Content.Title = Model.BlogPost.Title;
}
@await Component.InvokeAsync(typeof(DefaultBlogPostViewComponent), new
{
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 BlogManager _blogManager;
private readonly IOptions<CmsKitMediaOptions> _mediaOptions;
private readonly IOptions<CmsKitCommentOptions> _commentsOptions;
public CmsKitDataSeedContributor(
IGuidGenerator guidGenerator,
@ -64,11 +65,12 @@ namespace Volo.CmsKit
IBlogFeatureRepository blogFeatureRepository,
EntityTagManager entityTagManager,
IOptions<CmsKitOptions> options,
IOptions<CmsKitTagOptions> tagOptions,
IMediaDescriptorRepository mediaDescriptorRepository,
IBlobContainer<MediaContainer> mediaBlobContainer,
IOptions<CmsKitTagOptions> tagOptions,
IMediaDescriptorRepository mediaDescriptorRepository,
IBlobContainer<MediaContainer> mediaBlobContainer,
BlogManager blogManager,
IOptions<CmsKitMediaOptions> cmsMediaOptions)
IOptions<CmsKitMediaOptions> cmsMediaOptions,
IOptions<CmsKitCommentOptions> commentsOptions)
{
_guidGenerator = guidGenerator;
_cmsUserRepository = cmsUserRepository;
@ -92,6 +94,7 @@ namespace Volo.CmsKit
_mediaBlobContainer = mediaBlobContainer;
_blogManager = blogManager;
_mediaOptions = cmsMediaOptions;
_commentsOptions = commentsOptions;
}
public async Task SeedAsync(DataSeedContext context)
@ -134,6 +137,9 @@ namespace Volo.CmsKit
createPolicies: new[] { "SomeCreatePolicy" },
deletePolicies: new[] { "SomeDeletePolicy" }));
_commentsOptions.Value.EntityTypes.Add(
new CommentEntityTypeDefinition(_cmsKitTestData.EntityType1));
return Task.CompletedTask;
}

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<IdentityRole, IdentityRoleEto>(typeof(AbpIdentityDomainModule));
options.EtoMappings.Add<OrganizationUnit, OrganizationUnitEto>(typeof(AbpIdentityDomainModule));
options.AutoEventSelectors.Add<IdentityUser>();
options.AutoEventSelectors.Add<IdentityRole>();
});
var identityBuilder = context.Services.AddAbpIdentity(options =>

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

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

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

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

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

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

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

@ -12,13 +12,14 @@ import {
} from '@angular/core';
import { NavigationError, ResolveEnd } from '@angular/router';
import { Actions, ofActionSuccessful } from '@ngxs/store';
import { Observable, Subject } from 'rxjs';
import { filter, map } from 'rxjs/operators';
import { Observable, of, Subject, throwError } from 'rxjs';
import { catchError, filter, map, switchMap } from 'rxjs/operators';
import snq from 'snq';
import { HttpErrorWrapperComponent } from '../components/http-error-wrapper/http-error-wrapper.component';
import { ErrorScreenErrorCodes, HttpErrorConfig } from '../models/common';
import { Confirmation } from '../models/confirmation';
import { ConfirmationService } from '../services/confirmation.service';
import { HTTP_ERROR_HANDLER } from '../tokens/http-error.token';
export const DEFAULT_ERROR_MESSAGES = {
defaultError: {
@ -70,28 +71,32 @@ export const DEFAULT_ERROR_LOCALIZATIONS = {
export class ErrorHandler {
componentRef: ComponentRef<HttpErrorWrapperComponent>;
protected httpErrorHandler = this.injector.get(HTTP_ERROR_HANDLER, (_, err: HttpErrorResponse) =>
throwError(err),
);
constructor(
private actions: Actions,
private routerEvents: RouterEvents,
private confirmationService: ConfirmationService,
private cfRes: ComponentFactoryResolver,
private rendererFactory: RendererFactory2,
private injector: Injector,
@Inject('HTTP_ERROR_CONFIG') private httpErrorConfig: HttpErrorConfig,
protected actions: Actions,
protected routerEvents: RouterEvents,
protected confirmationService: ConfirmationService,
protected cfRes: ComponentFactoryResolver,
protected rendererFactory: RendererFactory2,
protected injector: Injector,
@Inject('HTTP_ERROR_CONFIG') protected httpErrorConfig: HttpErrorConfig,
) {
this.listenToRestError();
this.listenToRouterError();
this.listenToRouterDataResolved();
}
private listenToRouterError() {
protected listenToRouterError() {
this.routerEvents
.getNavigationEvents('Error')
.pipe(filter(this.filterRouteErrors))
.subscribe(() => this.show404Page());
}
private listenToRouterDataResolved() {
protected listenToRouterDataResolved() {
this.routerEvents
.getEvents(ResolveEnd)
.pipe(filter(() => !!this.componentRef))
@ -101,113 +106,127 @@ export class ErrorHandler {
});
}
private listenToRestError() {
protected listenToRestError() {
this.actions
.pipe(
ofActionSuccessful(RestOccurError),
map(action => action.payload),
filter(this.filterRestErrors),
switchMap(this.executeErrorHandler),
)
.subscribe(err => {
const body = snq(() => err.error.error, {
key: DEFAULT_ERROR_LOCALIZATIONS.defaultError.title,
defaultValue: DEFAULT_ERROR_MESSAGES.defaultError.title,
});
.subscribe();
}
if (err instanceof HttpErrorResponse && err.headers.get('_AbpErrorFormat')) {
const confirmation$ = this.showError(null, null, body);
private executeErrorHandler = error => {
const returnValue = this.httpErrorHandler(this.injector, error);
if (err.status === 401) {
confirmation$.subscribe(() => {
this.navigateToLogin();
});
}
} else {
switch (err.status) {
case 401:
this.canCreateCustomError(401)
? this.show401Page()
: this.showError(
{
key: DEFAULT_ERROR_LOCALIZATIONS.defaultError401.title,
defaultValue: DEFAULT_ERROR_MESSAGES.defaultError401.title,
},
{
key: DEFAULT_ERROR_LOCALIZATIONS.defaultError401.details,
defaultValue: DEFAULT_ERROR_MESSAGES.defaultError401.details,
},
).subscribe(() => this.navigateToLogin());
break;
case 403:
this.createErrorComponent({
title: {
key: DEFAULT_ERROR_LOCALIZATIONS.defaultError403.title,
defaultValue: DEFAULT_ERROR_MESSAGES.defaultError403.title,
},
details: {
key: DEFAULT_ERROR_LOCALIZATIONS.defaultError403.details,
defaultValue: DEFAULT_ERROR_MESSAGES.defaultError403.details,
},
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,
return (returnValue instanceof Observable ? returnValue : of(null)).pipe(
catchError(err => {
this.handleError(err);
return of(null);
}),
);
};
private handleError(err: any) {
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')) {
const confirmation$ = this.showError(null, null, body);
if (err.status === 401) {
confirmation$.subscribe(() => {
this.navigateToLogin();
});
}
} else {
switch (err.status) {
case 401:
this.canCreateCustomError(401)
? this.show401Page()
: this.showError(
{
key: DEFAULT_ERROR_LOCALIZATIONS.defaultError401.title,
defaultValue: DEFAULT_ERROR_MESSAGES.defaultError401.title,
},
details: {
key: DEFAULT_ERROR_LOCALIZATIONS.defaultError500.details,
defaultValue: DEFAULT_ERROR_MESSAGES.defaultError500.details,
{
key: DEFAULT_ERROR_LOCALIZATIONS.defaultError401.details,
defaultValue: DEFAULT_ERROR_MESSAGES.defaultError401.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(
).subscribe(() => this.navigateToLogin());
break;
case 403:
this.createErrorComponent({
title: {
key: DEFAULT_ERROR_LOCALIZATIONS.defaultError403.title,
defaultValue: DEFAULT_ERROR_MESSAGES.defaultError403.title,
},
details: {
key: DEFAULT_ERROR_LOCALIZATIONS.defaultError403.details,
defaultValue: DEFAULT_ERROR_MESSAGES.defaultError403.details,
},
status: 403,
});
break;
case 404:
this.canCreateCustomError(404)
? this.show404Page()
: this.showError(
{
key: DEFAULT_ERROR_LOCALIZATIONS.defaultError.details,
defaultValue: DEFAULT_ERROR_MESSAGES.defaultError.details,
key: DEFAULT_ERROR_LOCALIZATIONS.defaultError404.details,
defaultValue: DEFAULT_ERROR_MESSAGES.defaultError404.details,
},
{
key: DEFAULT_ERROR_LOCALIZATIONS.defaultError.title,
defaultValue: DEFAULT_ERROR_MESSAGES.defaultError.title,
key: DEFAULT_ERROR_LOCALIZATIONS.defaultError404.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({
title: {
key: DEFAULT_ERROR_LOCALIZATIONS.defaultError401.title,
@ -217,7 +236,7 @@ export class ErrorHandler {
});
}
private show404Page() {
protected show404Page() {
this.createErrorComponent({
title: {
key: DEFAULT_ERROR_LOCALIZATIONS.defaultError404.title,
@ -227,7 +246,7 @@ export class ErrorHandler {
});
}
private showError(
protected showError(
message?: LocalizationParam,
title?: LocalizationParam,
body?: any,
@ -256,7 +275,7 @@ export class ErrorHandler {
});
}
private navigateToLogin() {
protected navigateToLogin() {
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;
return this.httpErrorConfig.skipHandledErrorCodes.findIndex(code => code === status) < 0;
};
private filterRouteErrors = (navigationError: NavigationError): boolean => {
protected filterRouteErrors = (navigationError: NavigationError): boolean => {
return (
snq(() => navigationError.error.message.indexOf('Cannot match') > -1) &&
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 { Observable } from 'rxjs';
export interface RootParams {
httpErrorConfig: HttpErrorConfig;
@ -17,4 +19,9 @@ export interface HttpErrorConfig {
};
}
export type HttpErrorHandler = (
injector: Injector,
httpError: HttpErrorResponse,
) => Observable<any>;
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 { HttpErrorConfig } from '../models/common';
import { HttpErrorConfig, HttpErrorHandler } from '../models/common';
export function httpErrorConfigFactory(config = {} as HttpErrorConfig) {
if (config.errorScreen && config.errorScreen.component && !config.errorScreen.forWhichErrors) {
@ -13,4 +13,6 @@ export function httpErrorConfigFactory(config = {} 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"
},
"dependencies": {
"@abp/star-rating-svg": "~4.2.1"
"@abp/star-rating-svg": "~4.2.1",
"@abp/tui-editor": "^4.2.1",
"slugify": "^1.4.7"
},
"gitHead": "bb4ea17d5996f01889134c138d00b6c8f858a431"
}

3
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/tui-code-snippet/dist/*.*": "@libs/tui-code-snippet/",
"@node_modules/squire-rte/build/squire.js": "@libs/squire-rte/",
"@node_modules/tui-editor/dist/*.*": "@libs/tui-editor/"
"@node_modules/tui-editor/dist/*.*": "@libs/tui-editor/",
"@node_modules/@abp/tui-editor/src/*.*": "@libs/tui-editor/"
}
}

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

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

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

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

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

Loading…
Cancel
Save