Browse Source

Merge branch 'dev' into cms-kit/menu-management-patch

pull/9354/head
Enis Necipoğlu 5 years ago
parent
commit
a9127447fa
  1. 6
      docs/en/UI/AspNetCore/Customization-User-Interface.md
  2. 2
      docs/en/UI/AspNetCore/Layout-Hooks.md
  3. 4
      docs/en/UI/AspNetCore/Theming.md
  4. 2
      docs/en/UI/Blazor/Basic-Theme.md
  5. 2
      docs/en/UI/Blazor/Theming.md
  6. 6
      docs/zh-Hans/UI/AspNetCore/Customization-User-Interface.md
  7. 4
      framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/CurrentUserDto.cs
  8. 2
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/AbpApplicationConfigurationAppService.cs
  9. 6
      framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/ExceptionHandling/DefaultHttpExceptionStatusCodeFinder.cs
  10. 56
      framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/EntityHistory/EntityHistoryHelper.cs
  11. 62
      framework/src/Volo.Abp.EventBus.Kafka/Volo/Abp/EventBus/Kafka/KafkaDistributedEventBus.cs
  12. 53
      framework/src/Volo.Abp.EventBus.Kafka/Volo/Abp/EventBus/Kafka/KafkaEventErrorHandler.cs
  13. 72
      framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/RabbitMqDistributedEventBus.cs
  14. 47
      framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/RabbitMqEventErrorHandler.cs
  15. 23
      framework/src/Volo.Abp.EventBus.Rebus/Volo/Abp/EventBus/Rebus/AbpEventBusRebusModule.cs
  16. 8
      framework/src/Volo.Abp.EventBus.Rebus/Volo/Abp/EventBus/Rebus/AbpRebusEventBusOptions.cs
  17. 5
      framework/src/Volo.Abp.EventBus.Rebus/Volo/Abp/EventBus/Rebus/RebusDistributedEventBus.cs
  18. 32
      framework/src/Volo.Abp.EventBus.Rebus/Volo/Abp/EventBus/Rebus/RebusEventErrorHandler.cs
  19. 1
      framework/src/Volo.Abp.EventBus/Volo.Abp.EventBus.csproj
  20. 12
      framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/AbpEventBusModule.cs
  21. 22
      framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/AbpEventBusOptions.cs
  22. 9
      framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/AbpEventBusRetryStrategyOptions.cs
  23. 21
      framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/EventBusBase.cs
  24. 76
      framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/EventErrorHandlerBase.cs
  25. 38
      framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/EventExecutionErrorContext.cs
  26. 9
      framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/IEventErrorHandler.cs
  27. 31
      framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Local/LocalEventBus.cs
  28. 60
      framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Local/LocalEventErrorHandler.cs
  29. 20
      framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Local/LocalEventMessage.cs
  30. 16
      framework/src/Volo.Abp.ExceptionHandling/Volo/Abp/AspNetCore/ExceptionHandling/DefaultExceptionToErrorInfoConverter.cs
  31. 1
      framework/src/Volo.Abp.ExceptionHandling/Volo/Abp/ExceptionHandling/Localization/en.json
  32. 1
      framework/src/Volo.Abp.ExceptionHandling/Volo/Abp/ExceptionHandling/Localization/tr.json
  33. 2
      framework/src/Volo.Abp.Kafka/Volo/Abp/Kafka/AbpKafkaOptions.cs
  34. 2
      framework/src/Volo.Abp.Kafka/Volo/Abp/Kafka/IKafkaMessageConsumerFactory.cs
  35. 2
      framework/src/Volo.Abp.Kafka/Volo/Abp/Kafka/IKafkaSerializer.cs
  36. 66
      framework/src/Volo.Abp.Kafka/Volo/Abp/Kafka/KafkaMessageConsumer.cs
  37. 3
      framework/src/Volo.Abp.Kafka/Volo/Abp/Kafka/KafkaMessageConsumerFactory.cs
  38. 5
      framework/src/Volo.Abp.Kafka/Volo/Abp/Kafka/Utf8JsonKafkaSerializer.cs
  39. 8
      framework/src/Volo.Abp.MultiTenancy/Volo/Abp/MultiTenancy/MultiTenantConnectionStringResolver.cs
  40. 14
      framework/src/Volo.Abp.RabbitMQ/Volo/Abp/RabbitMQ/ExchangeDeclareConfiguration.cs
  41. 2
      framework/src/Volo.Abp.RabbitMQ/Volo/Abp/RabbitMQ/IRabbitMqSerializer.cs
  42. 17
      framework/src/Volo.Abp.RabbitMQ/Volo/Abp/RabbitMQ/QueueDeclareConfiguration.cs
  43. 45
      framework/src/Volo.Abp.RabbitMQ/Volo/Abp/RabbitMQ/RabbitMqMessageConsumer.cs
  44. 7
      framework/src/Volo.Abp.RabbitMQ/Volo/Abp/RabbitMQ/Utf8JsonRabbitMqSerializer.cs
  45. 117
      framework/src/Volo.Abp.Security/System/Security/Principal/AbpClaimsIdentityExtensions.cs
  46. 10
      framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/AbpClaimTypes.cs
  47. 33
      framework/src/Volo.Abp.Security/Volo/Abp/Users/CurrentUserExtensions.cs
  48. 34
      framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/Auditing_Tests.cs
  49. 13
      framework/test/Volo.Abp.EventBus.Tests/Volo/Abp/EventBus/EventBusTestModule.cs
  50. 75
      framework/test/Volo.Abp.EventBus.Tests/Volo/Abp/EventBus/Local/EventBus_Exception_Handler_Tests.cs
  51. 12
      framework/test/Volo.Abp.EventBus.Tests/Volo/Abp/EventBus/MyExceptionHandleEventData.cs
  52. 24
      framework/test/Volo.Abp.MultiTenancy.Tests/Volo/Abp/Data/MultiTenancy/MultiTenantConnectionStringResolver_Tests.cs
  53. 8
      modules/audit-logging/src/Volo.Abp.AuditLogging.Domain.Shared/Volo/Abp/AuditLogging/AuditLogConsts.cs
  54. 6
      modules/audit-logging/src/Volo.Abp.AuditLogging.Domain/Volo/Abp/AuditLogging/AbpAuditLoggingDomainModule.cs
  55. 98
      modules/audit-logging/src/Volo.Abp.AuditLogging.Domain/Volo/Abp/AuditLogging/AuditLog.cs
  56. 93
      modules/audit-logging/src/Volo.Abp.AuditLogging.Domain/Volo/Abp/AuditLogging/AuditLogInfoToAuditLogConverter.cs
  57. 15
      modules/audit-logging/src/Volo.Abp.AuditLogging.Domain/Volo/Abp/AuditLogging/AuditingStore.cs
  58. 10
      modules/audit-logging/src/Volo.Abp.AuditLogging.Domain/Volo/Abp/AuditLogging/IAuditLogInfoToAuditLogConverter.cs
  59. 3
      modules/audit-logging/src/Volo.Abp.AuditLogging.EntityFrameworkCore/Volo/Abp/AuditLogging/EntityFrameworkCore/AbpAuditLoggingDbContextModelBuilderExtensions.cs
  60. 43
      modules/audit-logging/test/Volo.Abp.AuditLogging.TestBase/Volo/Abp/AuditLogging/AuditLogRepository_Tests.cs
  61. 7
      modules/docs/src/Volo.Docs.Web/Pages/Documents/Shared/Styles/vs.css
  62. 2
      modules/docs/src/Volo.Docs.Web/Pages/Documents/Shared/Styles/vs.min.css
  63. 8
      modules/docs/src/Volo.Docs.Web/Pages/Documents/Shared/Styles/vs.scss
  64. 33
      modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/AbpSecurityStampValidatorCallback.cs
  65. 2
      modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/AbpClaimsService.cs
  66. 2
      modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/AbpIdentityServerDomainModule.cs
  67. 152
      modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/AspNetIdentity/LinkLoginExtensionGrantValidator.cs
  68. 3
      npm/ng-packs/apps/dev-app/src/environments/environment.ts
  69. 1
      npm/ng-packs/packages/account/src/lib/account-routing.module.ts
  70. 2
      npm/ng-packs/packages/account/src/lib/components/auth-wrapper/auth-wrapper.component.html
  71. 20
      npm/ng-packs/packages/account/src/lib/components/auth-wrapper/auth-wrapper.component.ts
  72. 5
      npm/ng-packs/packages/account/src/lib/components/reset-password/reset-password.component.ts
  73. 2
      npm/ng-packs/packages/account/src/lib/proxy/account/index.ts
  74. 4
      npm/ng-packs/packages/account/src/lib/proxy/account/models.ts
  75. 35
      npm/ng-packs/packages/account/src/lib/proxy/account/web/areas/account/controllers/account.service.ts
  76. 3
      npm/ng-packs/packages/account/src/lib/proxy/account/web/areas/account/controllers/index.ts
  77. 2
      npm/ng-packs/packages/account/src/lib/proxy/account/web/areas/account/controllers/models/index.ts
  78. 11
      npm/ng-packs/packages/account/src/lib/proxy/account/web/areas/account/controllers/models/login-result-type.enum.ts
  79. 12
      npm/ng-packs/packages/account/src/lib/proxy/account/web/areas/account/controllers/models/models.ts
  80. 2
      npm/ng-packs/packages/account/src/lib/proxy/account/web/areas/account/index.ts
  81. 2
      npm/ng-packs/packages/account/src/lib/proxy/account/web/areas/index.ts
  82. 2
      npm/ng-packs/packages/account/src/lib/proxy/account/web/index.ts
  83. 1170
      npm/ng-packs/packages/account/src/lib/proxy/generate-proxy.json
  84. 20
      npm/ng-packs/packages/core/src/lib/services/list.service.ts
  85. 1862
      npm/ng-packs/yarn.lock
  86. 13
      templates/app/angular/.browserslistrc
  87. 3
      templates/app/angular/.editorconfig
  88. 50
      templates/app/angular/.eslintrc.json
  89. 1
      templates/app/angular/.gitignore
  90. 4
      templates/app/angular/README.md
  91. 87
      templates/app/angular/angular.json
  92. 32
      templates/app/angular/e2e/protractor.conf.js
  93. 23
      templates/app/angular/e2e/src/app.e2e-spec.ts
  94. 11
      templates/app/angular/e2e/src/app.po.ts
  95. 13
      templates/app/angular/e2e/tsconfig.json
  96. 22
      templates/app/angular/karma.conf.js
  97. 62
      templates/app/angular/package.json
  98. 12
      templates/app/angular/src/polyfills.ts
  99. 10
      templates/app/angular/src/test.ts
  100. 10
      templates/app/angular/tsconfig.app.json

6
docs/en/UI/AspNetCore/Customization-User-Interface.md

@ -134,7 +134,7 @@ The screenshot below was taken from the [Basic Theme](Basic-Theme.md) comes with
The [Basic Theme](Basic-Theme.md) defines some view components for the layout. For example, the highlighted area with the red rectangle above is called **Brand component**. You probably want to customize this component by adding your **own application logo**. Let's see how to do it. The [Basic Theme](Basic-Theme.md) defines some view components for the layout. For example, the highlighted area with the red rectangle above is called **Brand component**. You probably want to customize this component by adding your **own application logo**. Let's see how to do it.
First, create your logo and place under a folder in your web application. We used `wwwroot/logos/bookstore-logo.png` path. Then copy the Brand component's view ([from here](https://github.com/abpframework/abp/blob/dev/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Components/Brand/Default.cshtml)) from the basic theme files under the `Themes/Basic/Components/Brand` folder. The result should be similar the picture below: First, create your logo and place under a folder in your web application. We used `wwwroot/logos/bookstore-logo.png` path. Then copy the Brand component's view ([from here](https://github.com/abpframework/abp/blob/dev/modules/basic-theme/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Components/Brand/Default.cshtml)) from the basic theme files under the `Themes/Basic/Components/Brand` folder. The result should be similar the picture below:
![bookstore-added-brand-files](../../images/bookstore-added-brand-files.png) ![bookstore-added-brand-files](../../images/bookstore-added-brand-files.png)
@ -150,7 +150,7 @@ Now, you can run the application to see the result:
![bookstore-added-logo](../../images/bookstore-added-logo.png) ![bookstore-added-logo](../../images/bookstore-added-logo.png)
If you need, you can also replace [the code behind c# class](https://github.com/abpframework/abp/blob/dev/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Components/Brand/MainNavbarBrandViewComponent.cs) of the component just using the dependency injection system. If you need, you can also replace [the code behind c# class](https://github.com/abpframework/abp/blob/dev/modules/basic-theme/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Components/Brand/MainNavbarBrandViewComponent.cs) of the component just using the dependency injection system.
### Overriding the Theme ### Overriding the Theme
@ -445,7 +445,7 @@ These names are defined in the `StandardLayouts` class as constants. You can def
#### Layout Location #### Layout Location
You can find the layout files [here](https://github.com/abpframework/abp/tree/dev/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Layouts) for the basic theme. You can take them as references to build your own layouts or you can override them if necessary. You can find the layout files [here](https://github.com/abpframework/abp/blob/dev/modules/basic-theme/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Layouts) for the basic theme. You can take them as references to build your own layouts or you can override them if necessary.
#### ITheme #### ITheme

2
docs/en/UI/AspNetCore/Layout-Hooks.md

@ -98,7 +98,7 @@ These names are defined in the `StandardLayouts` class as constants. You can def
### Layout Location ### Layout Location
You can find the layout files [here](https://github.com/abpframework/abp/tree/dev/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Layouts) for the basic theme. You can take them as references to build your own layouts or you can override them if necessary. You can find the layout files [here](https://github.com/abpframework/abp/blob/dev/modules/basic-theme/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Layouts) for the basic theme. You can take them as references to build your own layouts or you can override them if necessary.
## See Also ## See Also

4
docs/en/UI/AspNetCore/Theming.md

@ -124,7 +124,7 @@ The empty layout provides an empty page. It typically includes the following par
### The Easy Way ### The Easy Way
The easiest way to create a new theme is to copy the [Basic Theme Source Code](https://github.com/abpframework/abp/tree/dev/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic) and customize it. Once you get a copy of the theme in your solution, remove the `Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic` NuGet package and reference to the local project. The easiest way to create a new theme is to copy the [Basic Theme Source Code](https://github.com/abpframework/abp/blob/dev/modules/basic-theme/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic) and customize it. Once you get a copy of the theme in your solution, remove the `Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic` NuGet package and reference to the local project.
### The ITheme Interface ### The ITheme Interface
@ -410,7 +410,7 @@ See the [Page Header](Page-Header.md) document for more.
#### Tenant Switch #### Tenant Switch
The Account Layout should allow the user to switch the current tenant if the application is multi-tenant and the tenant was resolved from the cookies. See the [Basic Theme Account Layout](https://github.com/abpframework/abp/blob/dev/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Layouts/Account.cshtml) as an example implementation. The Account Layout should allow the user to switch the current tenant if the application is multi-tenant and the tenant was resolved from the cookies. See the [Basic Theme Account Layout](https://github.com/abpframework/abp/blob/dev/modules/basic-theme/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Layouts/Account.cshtml) as an example implementation.
### Layout Classes ### Layout Classes

2
docs/en/UI/Blazor/Basic-Theme.md

@ -60,7 +60,7 @@ Then, navigate to downloaded `Volo.Abp.AspNetCore.Components.WebAssembly.BasicTh
---- ----
Or, you can download the [source code](https://github.com/abpframework/abp/tree/dev/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme) of the Basic Theme, manually copy the project content into your solution, re-arrange the package/module dependencies (see the Installation section above to understand how it was installed to the project) and freely customize the theme based on your application requirements. Or, you can download the [source code](https://github.com/abpframework/abp/blob/dev/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme) of the Basic Theme, manually copy the project content into your solution, re-arrange the package/module dependencies (see the Installation section above to understand how it was installed to the project) and freely customize the theme based on your application requirements.
## See Also ## See Also

2
docs/en/UI/Blazor/Theming.md

@ -63,7 +63,7 @@ A theme is simply a Razor Class Library.
### The Easy Way ### The Easy Way
The easiest way to create a new theme is to copy the [Basic Theme Source Code](https://github.com/abpframework/abp/tree/dev/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme) and customize it. Once you get a copy of the theme in your solution, remove the `Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme` NuGet package and reference to the local project. The easiest way to create a new theme is to copy the [Basic Theme Source Code](https://github.com/abpframework/abp/blob/dev/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme) and customize it. Once you get a copy of the theme in your solution, remove the `Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme` NuGet package and reference to the local project.
### Global Styles / Scripts ### Global Styles / Scripts

6
docs/zh-Hans/UI/AspNetCore/Customization-User-Interface.md

@ -133,7 +133,7 @@ public class MyLoginModel : LoginModel
[基本主题](../../Themes/Basic.md) 为layout定义了一些视图组件. 例如上面带有红色矩形的突出显示区域称为 **Brand组件**, 你可能想添加自己的**自己的应用程序logo**来自定义此组件. 让我们来看看如何去做. [基本主题](../../Themes/Basic.md) 为layout定义了一些视图组件. 例如上面带有红色矩形的突出显示区域称为 **Brand组件**, 你可能想添加自己的**自己的应用程序logo**来自定义此组件. 让我们来看看如何去做.
首先创建你的logo并且放到你的web应用程序文件夹中,我们使用 `wwwroot/logos/bookstore-logo.png` 路径. 然后在 `Themes/Basic/Components/Brand` 文件夹下复制[Brand组件视图](https://github.com/abpframework/abp/blob/dev/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Components/Brand/Default.cshtml). 结果应该是类似下面的图片: 首先创建你的logo并且放到你的web应用程序文件夹中,我们使用 `wwwroot/logos/bookstore-logo.png` 路径. 然后在 `Themes/Basic/Components/Brand` 文件夹下复制[Brand组件视图](https://github.com/abpframework/abp/blob/dev/modules/basic-theme/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Components/Brand/Default.cshtml). 结果应该是类似下面的图片:
![bookstore-added-brand-files](../../images/bookstore-added-brand-files.png) ![bookstore-added-brand-files](../../images/bookstore-added-brand-files.png)
@ -149,7 +149,7 @@ public class MyLoginModel : LoginModel
![bookstore-added-logo](../../images/bookstore-added-logo.png) ![bookstore-added-logo](../../images/bookstore-added-logo.png)
如果你需要,你也可以仅使用依赖注入系统替换组件[背后的C#类代码](https://github.com/abpframework/abp/blob/dev/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Components/Brand/MainNavbarBrandViewComponent.cs) 如果你需要,你也可以仅使用依赖注入系统替换组件[背后的C#类代码](https://github.com/abpframework/abp/blob/dev/modules/basic-theme/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Components/Brand/MainNavbarBrandViewComponent.cs)
### 重写主题 ### 重写主题
@ -444,7 +444,7 @@ Configure<AbpLayoutHookOptions>(options =>
#### 布局位置 #### 布局位置
你可以在[这里](https://github.com/abpframework/abp/tree/dev/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Layouts)找到基本主题的布局文件. 你可以将它们作用构建自己的布局的参考,也可以在必要时覆盖它们. 你可以在[这里](https://github.com/abpframework/abp/blob/dev/modules/basic-theme/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Layouts)找到基本主题的布局文件. 你可以将它们作用构建自己的布局的参考,也可以在必要时覆盖它们.
#### ITheme #### ITheme

4
framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/CurrentUserDto.cs

@ -11,6 +11,10 @@ namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations
public Guid? TenantId { get; set; } public Guid? TenantId { get; set; }
public Guid? ImpersonatorUserId { get; set; }
public Guid? ImpersonatorTenantId { get; set; }
public string UserName { get; set; } public string UserName { get; set; }
public string Name { get; set; } public string Name { get; set; }

2
framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/AbpApplicationConfigurationAppService.cs

@ -127,6 +127,8 @@ namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations
IsAuthenticated = _currentUser.IsAuthenticated, IsAuthenticated = _currentUser.IsAuthenticated,
Id = _currentUser.Id, Id = _currentUser.Id,
TenantId = _currentUser.TenantId, TenantId = _currentUser.TenantId,
ImpersonatorUserId = _currentUser.FindImpersonatorUserId(),
ImpersonatorTenantId = _currentUser.FindImpersonatorTenantId(),
UserName = _currentUser.UserName, UserName = _currentUser.UserName,
SurName = _currentUser.SurName, SurName = _currentUser.SurName,
Name = _currentUser.Name, Name = _currentUser.Name,

6
framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/ExceptionHandling/DefaultHttpExceptionStatusCodeFinder.cs

@ -3,6 +3,7 @@ using System.Net;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using Volo.Abp.Authorization; using Volo.Abp.Authorization;
using Volo.Abp.Data;
using Volo.Abp.DependencyInjection; using Volo.Abp.DependencyInjection;
using Volo.Abp.Domain.Entities; using Volo.Abp.Domain.Entities;
using Volo.Abp.ExceptionHandling; using Volo.Abp.ExceptionHandling;
@ -55,6 +56,11 @@ namespace Volo.Abp.AspNetCore.ExceptionHandling
{ {
return HttpStatusCode.NotFound; return HttpStatusCode.NotFound;
} }
if (exception is AbpDbConcurrencyException)
{
return HttpStatusCode.Conflict;
}
if (exception is NotImplementedException) if (exception is NotImplementedException)
{ {

56
framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/EntityHistory/EntityHistoryHelper.cs

@ -9,6 +9,7 @@ using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using Volo.Abp.Auditing; using Volo.Abp.Auditing;
using Volo.Abp.Data;
using Volo.Abp.DependencyInjection; using Volo.Abp.DependencyInjection;
using Volo.Abp.Domain.Entities; using Volo.Abp.Domain.Entities;
using Volo.Abp.Json; using Volo.Abp.Json;
@ -26,8 +27,7 @@ namespace Volo.Abp.EntityFrameworkCore.EntityHistory
protected IJsonSerializer JsonSerializer { get; } protected IJsonSerializer JsonSerializer { get; }
protected AbpAuditingOptions Options { get; } protected AbpAuditingOptions Options { get; }
protected IAuditingHelper AuditingHelper { get; } protected IAuditingHelper AuditingHelper { get; }
protected IClock Clock{ get; }
private readonly IClock _clock;
public EntityHistoryHelper( public EntityHistoryHelper(
IAuditingStore auditingStore, IAuditingStore auditingStore,
@ -36,7 +36,7 @@ namespace Volo.Abp.EntityFrameworkCore.EntityHistory
IJsonSerializer jsonSerializer, IJsonSerializer jsonSerializer,
IAuditingHelper auditingHelper) IAuditingHelper auditingHelper)
{ {
_clock = clock; Clock = clock;
AuditingStore = auditingStore; AuditingStore = auditingStore;
JsonSerializer = jsonSerializer; JsonSerializer = jsonSerializer;
AuditingHelper = auditingHelper; AuditingHelper = auditingHelper;
@ -69,7 +69,7 @@ namespace Volo.Abp.EntityFrameworkCore.EntityHistory
} }
[CanBeNull] [CanBeNull]
private EntityChangeInfo CreateEntityChangeOrNull(EntityEntry entityEntry) protected virtual EntityChangeInfo CreateEntityChangeOrNull(EntityEntry entityEntry)
{ {
var entity = entityEntry.Entity; var entity = entityEntry.Entity;
@ -121,23 +121,23 @@ namespace Volo.Abp.EntityFrameworkCore.EntityHistory
return multiTenantEntity.TenantId; return multiTenantEntity.TenantId;
} }
private DateTime GetChangeTime(EntityChangeInfo entityChange) protected virtual DateTime GetChangeTime(EntityChangeInfo entityChange)
{ {
var entity = entityChange.EntityEntry.As<EntityEntry>().Entity; var entity = entityChange.EntityEntry.As<EntityEntry>().Entity;
switch (entityChange.ChangeType) switch (entityChange.ChangeType)
{ {
case EntityChangeType.Created: case EntityChangeType.Created:
return (entity as IHasCreationTime)?.CreationTime ?? _clock.Now; return (entity as IHasCreationTime)?.CreationTime ?? Clock.Now;
case EntityChangeType.Deleted: case EntityChangeType.Deleted:
return (entity as IHasDeletionTime)?.DeletionTime ?? _clock.Now; return (entity as IHasDeletionTime)?.DeletionTime ?? Clock.Now;
case EntityChangeType.Updated: case EntityChangeType.Updated:
return (entity as IHasModificationTime)?.LastModificationTime ?? _clock.Now; return (entity as IHasModificationTime)?.LastModificationTime ?? Clock.Now;
default: default:
throw new AbpException($"Unknown {nameof(EntityChangeInfo)}: {entityChange}"); throw new AbpException($"Unknown {nameof(EntityChangeInfo)}: {entityChange}");
} }
} }
private string GetEntityId(object entityAsObj) protected virtual string GetEntityId(object entityAsObj)
{ {
if (!(entityAsObj is IEntity entity)) if (!(entityAsObj is IEntity entity))
{ {
@ -156,7 +156,7 @@ namespace Volo.Abp.EntityFrameworkCore.EntityHistory
/// <summary> /// <summary>
/// Gets the property changes for this entry. /// Gets the property changes for this entry.
/// </summary> /// </summary>
private List<EntityPropertyChangeInfo> GetPropertyChanges(EntityEntry entityEntry) protected virtual List<EntityPropertyChangeInfo> GetPropertyChanges(EntityEntry entityEntry)
{ {
var propertyChanges = new List<EntityPropertyChangeInfo>(); var propertyChanges = new List<EntityPropertyChangeInfo>();
var properties = entityEntry.Metadata.GetProperties(); var properties = entityEntry.Metadata.GetProperties();
@ -181,12 +181,12 @@ namespace Volo.Abp.EntityFrameworkCore.EntityHistory
return propertyChanges; return propertyChanges;
} }
private bool IsCreated(EntityEntry entityEntry) protected virtual bool IsCreated(EntityEntry entityEntry)
{ {
return entityEntry.State == EntityState.Added; return entityEntry.State == EntityState.Added;
} }
private bool IsDeleted(EntityEntry entityEntry) protected virtual bool IsDeleted(EntityEntry entityEntry)
{ {
if (entityEntry.State == EntityState.Deleted) if (entityEntry.State == EntityState.Deleted)
{ {
@ -197,7 +197,7 @@ namespace Volo.Abp.EntityFrameworkCore.EntityHistory
return entity is ISoftDelete && entity.As<ISoftDelete>().IsDeleted; return entity is ISoftDelete && entity.As<ISoftDelete>().IsDeleted;
} }
private bool ShouldSaveEntityHistory(EntityEntry entityEntry, bool defaultValue = false) protected virtual bool ShouldSaveEntityHistory(EntityEntry entityEntry, bool defaultValue = false)
{ {
if (entityEntry.State == EntityState.Detached || if (entityEntry.State == EntityState.Detached ||
entityEntry.State == EntityState.Unchanged) entityEntry.State == EntityState.Unchanged)
@ -220,7 +220,7 @@ namespace Volo.Abp.EntityFrameworkCore.EntityHistory
return defaultValue; return defaultValue;
} }
private bool ShouldSavePropertyHistory(PropertyEntry propertyEntry, bool defaultValue) protected virtual bool ShouldSavePropertyHistory(PropertyEntry propertyEntry, bool defaultValue)
{ {
if (propertyEntry.Metadata.Name == "Id") if (propertyEntry.Metadata.Name == "Id")
{ {
@ -247,6 +247,26 @@ namespace Volo.Abp.EntityFrameworkCore.EntityHistory
return false; return false;
} }
if (propertyEntry.OriginalValue is ExtraPropertyDictionary originalValue && propertyEntry.CurrentValue is ExtraPropertyDictionary currentValue)
{
if (originalValue.IsNullOrEmpty() && currentValue.IsNullOrEmpty())
{
return false;
}
if (!originalValue.Select(x => x.Key).SequenceEqual(currentValue.Select(x => x.Key)))
{
return true;
}
if (!originalValue.Select(x => x.Value).SequenceEqual(currentValue.Select(x => x.Value)))
{
return true;
}
return defaultValue;
}
var isModified = !(propertyEntry.OriginalValue?.Equals(propertyEntry.CurrentValue) ?? propertyEntry.CurrentValue == null); var isModified = !(propertyEntry.OriginalValue?.Equals(propertyEntry.CurrentValue) ?? propertyEntry.CurrentValue == null);
if (isModified) if (isModified)
{ {
@ -256,7 +276,7 @@ namespace Volo.Abp.EntityFrameworkCore.EntityHistory
return defaultValue; return defaultValue;
} }
private bool IsBaseAuditProperty(PropertyInfo propertyInfo, Type entityType) protected virtual bool IsBaseAuditProperty(PropertyInfo propertyInfo, Type entityType)
{ {
if (entityType.IsAssignableTo<IHasCreationTime>() if (entityType.IsAssignableTo<IHasCreationTime>()
&& propertyInfo.Name == nameof(IHasCreationTime.CreationTime)) && propertyInfo.Name == nameof(IHasCreationTime.CreationTime))
@ -308,11 +328,11 @@ namespace Volo.Abp.EntityFrameworkCore.EntityHistory
return false; return false;
} }
/// <summary> /// <summary>
/// Updates change time, entity id and foreign keys after SaveChanges is called. /// Updates change time, entity id and foreign keys after SaveChanges is called.
/// </summary> /// </summary>
public void UpdateChangeList(List<EntityChangeInfo> entityChanges) public virtual void UpdateChangeList(List<EntityChangeInfo> entityChanges)
{ {
foreach (var entityChange in entityChanges) foreach (var entityChange in entityChanges)
{ {
@ -372,4 +392,4 @@ namespace Volo.Abp.EntityFrameworkCore.EntityHistory
} }
} }
} }
} }

62
framework/src/Volo.Abp.EventBus.Kafka/Volo/Abp/EventBus/Kafka/KafkaDistributedEventBus.cs

@ -6,6 +6,7 @@ using System.Threading.Tasks;
using Confluent.Kafka; using Confluent.Kafka;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using Volo.Abp.Data;
using Volo.Abp.DependencyInjection; using Volo.Abp.DependencyInjection;
using Volo.Abp.EventBus.Distributed; using Volo.Abp.EventBus.Distributed;
using Volo.Abp.Kafka; using Volo.Abp.Kafka;
@ -18,6 +19,7 @@ namespace Volo.Abp.EventBus.Kafka
[ExposeServices(typeof(IDistributedEventBus), typeof(KafkaDistributedEventBus))] [ExposeServices(typeof(IDistributedEventBus), typeof(KafkaDistributedEventBus))]
public class KafkaDistributedEventBus : EventBusBase, IDistributedEventBus, ISingletonDependency public class KafkaDistributedEventBus : EventBusBase, IDistributedEventBus, ISingletonDependency
{ {
protected AbpEventBusOptions AbpEventBusOptions { get; }
protected AbpKafkaEventBusOptions AbpKafkaEventBusOptions { get; } protected AbpKafkaEventBusOptions AbpKafkaEventBusOptions { get; }
protected AbpDistributedEventBusOptions AbpDistributedEventBusOptions { get; } protected AbpDistributedEventBusOptions AbpDistributedEventBusOptions { get; }
protected IKafkaMessageConsumerFactory MessageConsumerFactory { get; } protected IKafkaMessageConsumerFactory MessageConsumerFactory { get; }
@ -26,6 +28,7 @@ namespace Volo.Abp.EventBus.Kafka
protected ConcurrentDictionary<Type, List<IEventHandlerFactory>> HandlerFactories { get; } protected ConcurrentDictionary<Type, List<IEventHandlerFactory>> HandlerFactories { get; }
protected ConcurrentDictionary<string, Type> EventTypes { get; } protected ConcurrentDictionary<string, Type> EventTypes { get; }
protected IKafkaMessageConsumer Consumer { get; private set; } protected IKafkaMessageConsumer Consumer { get; private set; }
protected string DeadLetterTopicName { get; }
public KafkaDistributedEventBus( public KafkaDistributedEventBus(
IServiceScopeFactory serviceScopeFactory, IServiceScopeFactory serviceScopeFactory,
@ -34,14 +37,19 @@ namespace Volo.Abp.EventBus.Kafka
IKafkaMessageConsumerFactory messageConsumerFactory, IKafkaMessageConsumerFactory messageConsumerFactory,
IOptions<AbpDistributedEventBusOptions> abpDistributedEventBusOptions, IOptions<AbpDistributedEventBusOptions> abpDistributedEventBusOptions,
IKafkaSerializer serializer, IKafkaSerializer serializer,
IProducerPool producerPool) IProducerPool producerPool,
: base(serviceScopeFactory, currentTenant) IEventErrorHandler errorHandler,
IOptions<AbpEventBusOptions> abpEventBusOptions)
: base(serviceScopeFactory, currentTenant, errorHandler)
{ {
AbpKafkaEventBusOptions = abpKafkaEventBusOptions.Value; AbpKafkaEventBusOptions = abpKafkaEventBusOptions.Value;
AbpDistributedEventBusOptions = abpDistributedEventBusOptions.Value; AbpDistributedEventBusOptions = abpDistributedEventBusOptions.Value;
AbpEventBusOptions = abpEventBusOptions.Value;
MessageConsumerFactory = messageConsumerFactory; MessageConsumerFactory = messageConsumerFactory;
Serializer = serializer; Serializer = serializer;
ProducerPool = producerPool; ProducerPool = producerPool;
DeadLetterTopicName =
AbpEventBusOptions.DeadLetterName ?? AbpKafkaEventBusOptions.TopicName + "_dead_letter";
HandlerFactories = new ConcurrentDictionary<Type, List<IEventHandlerFactory>>(); HandlerFactories = new ConcurrentDictionary<Type, List<IEventHandlerFactory>>();
EventTypes = new ConcurrentDictionary<string, Type>(); EventTypes = new ConcurrentDictionary<string, Type>();
@ -51,9 +59,9 @@ namespace Volo.Abp.EventBus.Kafka
{ {
Consumer = MessageConsumerFactory.Create( Consumer = MessageConsumerFactory.Create(
AbpKafkaEventBusOptions.TopicName, AbpKafkaEventBusOptions.TopicName,
DeadLetterTopicName,
AbpKafkaEventBusOptions.GroupId, AbpKafkaEventBusOptions.GroupId,
AbpKafkaEventBusOptions.ConnectionName); AbpKafkaEventBusOptions.ConnectionName);
Consumer.OnMessageReceived(ProcessEventAsync); Consumer.OnMessageReceived(ProcessEventAsync);
SubscribeHandlers(AbpDistributedEventBusOptions.Handlers); SubscribeHandlers(AbpDistributedEventBusOptions.Handlers);
@ -70,7 +78,18 @@ namespace Volo.Abp.EventBus.Kafka
var eventData = Serializer.Deserialize(message.Value, eventType); var eventData = Serializer.Deserialize(message.Value, eventType);
await TriggerHandlersAsync(eventType, eventData); await TriggerHandlersAsync(eventType, eventData, errorContext =>
{
var retryAttempt = 0;
if (message.Headers.TryGetLastBytes(EventErrorHandlerBase.RetryAttemptKey, out var retryAttemptBytes))
{
retryAttempt = Serializer.Deserialize<int>(retryAttemptBytes);
}
errorContext.EventData = Serializer.Deserialize(message.Value, eventType);
errorContext.SetProperty(EventErrorHandlerBase.HeadersKey, message.Headers);
errorContext.SetProperty(EventErrorHandlerBase.RetryAttemptKey, retryAttempt);
});
} }
public IDisposable Subscribe<TEvent>(IDistributedEventHandler<TEvent> handler) where TEvent : class public IDisposable Subscribe<TEvent>(IDistributedEventHandler<TEvent> handler) where TEvent : class
@ -147,20 +166,51 @@ namespace Volo.Abp.EventBus.Kafka
} }
public override async Task PublishAsync(Type eventType, object eventData) public override async Task PublishAsync(Type eventType, object eventData)
{
await PublishAsync(eventType, eventData, new Headers {{"messageId", Serializer.Serialize(Guid.NewGuid())}}, null);
}
public virtual async Task PublishAsync(Type eventType, object eventData, Headers headers, Dictionary<string, object> headersArguments)
{
await PublishAsync(AbpKafkaEventBusOptions.TopicName, eventType, eventData, headers, headersArguments);
}
public virtual async Task PublishToDeadLetterAsync(Type eventType, object eventData, Headers headers, Dictionary<string, object> headersArguments)
{
await PublishAsync(DeadLetterTopicName, eventType, eventData, headers, headersArguments);
}
private async Task PublishAsync(string topicName, Type eventType, object eventData, Headers headers, Dictionary<string, object> headersArguments)
{ {
var eventName = EventNameAttribute.GetNameOrDefault(eventType); var eventName = EventNameAttribute.GetNameOrDefault(eventType);
var body = Serializer.Serialize(eventData); var body = Serializer.Serialize(eventData);
var producer = ProducerPool.Get(AbpKafkaEventBusOptions.ConnectionName); var producer = ProducerPool.Get(AbpKafkaEventBusOptions.ConnectionName);
SetEventMessageHeaders(headers, headersArguments);
await producer.ProduceAsync( await producer.ProduceAsync(
AbpKafkaEventBusOptions.TopicName, topicName,
new Message<string, byte[]> new Message<string, byte[]>
{ {
Key = eventName, Value = body Key = eventName, Value = body, Headers = headers
}); });
} }
private void SetEventMessageHeaders(Headers headers, Dictionary<string, object> headersArguments)
{
if (headersArguments == null)
{
return;
}
foreach (var header in headersArguments)
{
headers.Remove(header.Key);
headers.Add(header.Key, Serializer.Serialize(header.Value));
}
}
private List<IEventHandlerFactory> GetOrCreateHandlerFactories(Type eventType) private List<IEventHandlerFactory> GetOrCreateHandlerFactories(Type eventType)
{ {
return HandlerFactories.GetOrAdd( return HandlerFactories.GetOrAdd(

53
framework/src/Volo.Abp.EventBus.Kafka/Volo/Abp/EventBus/Kafka/KafkaEventErrorHandler.cs

@ -0,0 +1,53 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Confluent.Kafka;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using Volo.Abp.Data;
using Volo.Abp.DependencyInjection;
namespace Volo.Abp.EventBus.Kafka
{
public class KafkaEventErrorHandler : EventErrorHandlerBase, ISingletonDependency
{
protected ILogger<KafkaEventErrorHandler> Logger { get; set; }
public KafkaEventErrorHandler(
IOptions<AbpEventBusOptions> options) : base(options)
{
Logger = NullLogger<KafkaEventErrorHandler>.Instance;
}
protected override async Task RetryAsync(EventExecutionErrorContext context)
{
if (Options.RetryStrategyOptions.IntervalMillisecond > 0)
{
await Task.Delay(Options.RetryStrategyOptions.IntervalMillisecond);
}
context.TryGetRetryAttempt(out var retryAttempt);
await context.EventBus.As<KafkaDistributedEventBus>().PublishAsync(
context.EventType,
context.EventData,
context.GetProperty(HeadersKey).As<Headers>(),
new Dictionary<string, object> {{RetryAttemptKey, ++retryAttempt}});
}
protected override async Task MoveToDeadLetterAsync(EventExecutionErrorContext context)
{
Logger.LogException(
context.Exceptions.Count == 1 ? context.Exceptions.First() : new AggregateException(context.Exceptions),
LogLevel.Error);
await context.EventBus.As<KafkaDistributedEventBus>().PublishToDeadLetterAsync(
context.EventType,
context.EventData,
context.GetProperty(HeadersKey).As<Headers>(),
new Dictionary<string, object> {{"exceptions", context.Exceptions.Select(x => x.ToString()).ToList()}});
}
}
}

72
framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/RabbitMqDistributedEventBus.cs

@ -7,6 +7,7 @@ using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using RabbitMQ.Client; using RabbitMQ.Client;
using RabbitMQ.Client.Events; using RabbitMQ.Client.Events;
using Volo.Abp.Data;
using Volo.Abp.DependencyInjection; using Volo.Abp.DependencyInjection;
using Volo.Abp.EventBus.Distributed; using Volo.Abp.EventBus.Distributed;
using Volo.Abp.MultiTenancy; using Volo.Abp.MultiTenancy;
@ -25,6 +26,7 @@ namespace Volo.Abp.EventBus.RabbitMq
{ {
protected AbpRabbitMqEventBusOptions AbpRabbitMqEventBusOptions { get; } protected AbpRabbitMqEventBusOptions AbpRabbitMqEventBusOptions { get; }
protected AbpDistributedEventBusOptions AbpDistributedEventBusOptions { get; } protected AbpDistributedEventBusOptions AbpDistributedEventBusOptions { get; }
protected AbpEventBusOptions AbpEventBusOptions { get; }
protected IConnectionPool ConnectionPool { get; } protected IConnectionPool ConnectionPool { get; }
protected IRabbitMqSerializer Serializer { get; } protected IRabbitMqSerializer Serializer { get; }
@ -41,12 +43,15 @@ namespace Volo.Abp.EventBus.RabbitMq
IServiceScopeFactory serviceScopeFactory, IServiceScopeFactory serviceScopeFactory,
IOptions<AbpDistributedEventBusOptions> distributedEventBusOptions, IOptions<AbpDistributedEventBusOptions> distributedEventBusOptions,
IRabbitMqMessageConsumerFactory messageConsumerFactory, IRabbitMqMessageConsumerFactory messageConsumerFactory,
ICurrentTenant currentTenant) ICurrentTenant currentTenant,
: base(serviceScopeFactory, currentTenant) IEventErrorHandler errorHandler,
IOptions<AbpEventBusOptions> abpEventBusOptions)
: base(serviceScopeFactory, currentTenant, errorHandler)
{ {
ConnectionPool = connectionPool; ConnectionPool = connectionPool;
Serializer = serializer; Serializer = serializer;
MessageConsumerFactory = messageConsumerFactory; MessageConsumerFactory = messageConsumerFactory;
AbpEventBusOptions = abpEventBusOptions.Value;
AbpDistributedEventBusOptions = distributedEventBusOptions.Value; AbpDistributedEventBusOptions = distributedEventBusOptions.Value;
AbpRabbitMqEventBusOptions = options.Value; AbpRabbitMqEventBusOptions = options.Value;
@ -56,17 +61,21 @@ namespace Volo.Abp.EventBus.RabbitMq
public void Initialize() public void Initialize()
{ {
const string suffix = "_dead_letter";
Consumer = MessageConsumerFactory.Create( Consumer = MessageConsumerFactory.Create(
new ExchangeDeclareConfiguration( new ExchangeDeclareConfiguration(
AbpRabbitMqEventBusOptions.ExchangeName, AbpRabbitMqEventBusOptions.ExchangeName,
type: "direct", type: "direct",
durable: true durable: true,
deadLetterExchangeName: AbpRabbitMqEventBusOptions.ExchangeName + suffix
), ),
new QueueDeclareConfiguration( new QueueDeclareConfiguration(
AbpRabbitMqEventBusOptions.ClientName, AbpRabbitMqEventBusOptions.ClientName,
durable: true, durable: true,
exclusive: false, exclusive: false,
autoDelete: false autoDelete: false,
AbpEventBusOptions.DeadLetterName ?? AbpRabbitMqEventBusOptions.ClientName + suffix
), ),
AbpRabbitMqEventBusOptions.ConnectionName AbpRabbitMqEventBusOptions.ConnectionName
); );
@ -87,7 +96,19 @@ namespace Volo.Abp.EventBus.RabbitMq
var eventData = Serializer.Deserialize(ea.Body.ToArray(), eventType); var eventData = Serializer.Deserialize(ea.Body.ToArray(), eventType);
await TriggerHandlersAsync(eventType, eventData); await TriggerHandlersAsync(eventType, eventData, errorContext =>
{
var retryAttempt = 0;
if (ea.BasicProperties.Headers != null &&
ea.BasicProperties.Headers.ContainsKey(EventErrorHandlerBase.RetryAttemptKey))
{
retryAttempt = (int)ea.BasicProperties.Headers[EventErrorHandlerBase.RetryAttemptKey];
}
errorContext.EventData = Serializer.Deserialize(ea.Body.ToArray(), eventType);
errorContext.SetProperty(EventErrorHandlerBase.HeadersKey, ea.BasicProperties);
errorContext.SetProperty(EventErrorHandlerBase.RetryAttemptKey, retryAttempt);
});
} }
public IDisposable Subscribe<TEvent>(IDistributedEventHandler<TEvent> handler) where TEvent : class public IDisposable Subscribe<TEvent>(IDistributedEventHandler<TEvent> handler) where TEvent : class
@ -168,8 +189,14 @@ namespace Volo.Abp.EventBus.RabbitMq
GetOrCreateHandlerFactories(eventType).Locking(factories => factories.Clear()); GetOrCreateHandlerFactories(eventType).Locking(factories => factories.Clear());
} }
public override Task PublishAsync(Type eventType, object eventData) public override async Task PublishAsync(Type eventType, object eventData)
{
await PublishAsync(eventType, eventData, null);
}
public Task PublishAsync(Type eventType, object eventData, IBasicProperties properties, Dictionary<string, object> headersArguments = null)
{ {
var eventName = EventNameAttribute.GetNameOrDefault(eventType); var eventName = EventNameAttribute.GetNameOrDefault(eventType);
var body = Serializer.Serialize(eventData); var body = Serializer.Serialize(eventData);
@ -181,11 +208,17 @@ namespace Volo.Abp.EventBus.RabbitMq
durable: true durable: true
); );
var properties = channel.CreateBasicProperties(); if (properties == null)
properties.DeliveryMode = RabbitMqConsts.DeliveryModes.Persistent; {
properties = channel.CreateBasicProperties();
properties.DeliveryMode = RabbitMqConsts.DeliveryModes.Persistent;
properties.MessageId = Guid.NewGuid().ToString("N");
}
SetEventMessageHeaders(properties, headersArguments);
channel.BasicPublish( channel.BasicPublish(
exchange: AbpRabbitMqEventBusOptions.ExchangeName, exchange: AbpRabbitMqEventBusOptions.ExchangeName,
routingKey: eventName, routingKey: eventName,
mandatory: true, mandatory: true,
basicProperties: properties, basicProperties: properties,
@ -196,6 +229,21 @@ namespace Volo.Abp.EventBus.RabbitMq
return Task.CompletedTask; return Task.CompletedTask;
} }
private void SetEventMessageHeaders(IBasicProperties properties, Dictionary<string, object> headersArguments)
{
if (headersArguments == null)
{
return;
}
properties.Headers ??= new Dictionary<string, object>();
foreach (var header in headersArguments)
{
properties.Headers[header.Key] = header.Value;
}
}
private List<IEventHandlerFactory> GetOrCreateHandlerFactories(Type eventType) private List<IEventHandlerFactory> GetOrCreateHandlerFactories(Type eventType)
{ {
return HandlerFactories.GetOrAdd( return HandlerFactories.GetOrAdd(
@ -213,9 +261,11 @@ namespace Volo.Abp.EventBus.RabbitMq
{ {
var handlerFactoryList = new List<EventTypeWithEventHandlerFactories>(); var handlerFactoryList = new List<EventTypeWithEventHandlerFactories>();
foreach (var handlerFactory in HandlerFactories.Where(hf => ShouldTriggerEventForHandler(eventType, hf.Key))) foreach (var handlerFactory in
HandlerFactories.Where(hf => ShouldTriggerEventForHandler(eventType, hf.Key)))
{ {
handlerFactoryList.Add(new EventTypeWithEventHandlerFactories(handlerFactory.Key, handlerFactory.Value)); handlerFactoryList.Add(
new EventTypeWithEventHandlerFactories(handlerFactory.Key, handlerFactory.Value));
} }
return handlerFactoryList.ToArray(); return handlerFactoryList.ToArray();

47
framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/RabbitMqEventErrorHandler.cs

@ -0,0 +1,47 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Extensions.Options;
using RabbitMQ.Client;
using Volo.Abp.Data;
using Volo.Abp.DependencyInjection;
namespace Volo.Abp.EventBus.RabbitMq
{
public class RabbitMqEventErrorHandler : EventErrorHandlerBase, ISingletonDependency
{
public RabbitMqEventErrorHandler(
IOptions<AbpEventBusOptions> options)
: base(options)
{
}
protected override async Task RetryAsync(EventExecutionErrorContext context)
{
if (Options.RetryStrategyOptions.IntervalMillisecond > 0)
{
await Task.Delay(Options.RetryStrategyOptions.IntervalMillisecond);
}
context.TryGetRetryAttempt(out var retryAttempt);
await context.EventBus.As<RabbitMqDistributedEventBus>().PublishAsync(
context.EventType,
context.EventData,
context.GetProperty(HeadersKey).As<IBasicProperties>(),
new Dictionary<string, object>
{
{RetryAttemptKey, ++retryAttempt},
{"exceptions", context.Exceptions.Select(x => x.ToString()).ToList()}
});
}
protected override Task MoveToDeadLetterAsync(EventExecutionErrorContext context)
{
ThrowOriginalExceptions(context);
return Task.CompletedTask;
}
}
}

23
framework/src/Volo.Abp.EventBus.Rebus/Volo/Abp/EventBus/Rebus/AbpEventBusRebusModule.cs

@ -1,5 +1,6 @@
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Rebus.Handlers; using Rebus.Handlers;
using Rebus.Retry.Simple;
using Rebus.ServiceProvider; using Rebus.ServiceProvider;
using Volo.Abp.Modularity; using Volo.Abp.Modularity;
@ -11,23 +12,29 @@ namespace Volo.Abp.EventBus.Rebus
{ {
public override void ConfigureServices(ServiceConfigurationContext context) public override void ConfigureServices(ServiceConfigurationContext context)
{ {
var options = context.Services.ExecutePreConfiguredActions<AbpRebusEventBusOptions>(); var abpEventBusOptions = context.Services.ExecutePreConfiguredActions<AbpEventBusOptions>();
var options = context.Services.ExecutePreConfiguredActions<AbpRebusEventBusOptions>();;
context.Services.AddTransient(typeof(IHandleMessages<>), typeof(RebusDistributedEventHandlerAdapter<>)); context.Services.AddTransient(typeof(IHandleMessages<>), typeof(RebusDistributedEventHandlerAdapter<>));
Configure<AbpRebusEventBusOptions>(rebusOptions => Configure<AbpRebusEventBusOptions>(rebusOptions =>
{ {
rebusOptions.Configurer = options.Configurer; context.Services.ExecutePreConfiguredActions(rebusOptions);
rebusOptions.Publish = options.Publish;
rebusOptions.InputQueueName = options.InputQueueName;
}); });
context.Services.AddRebus(configurer => context.Services.AddRebus(configure =>
{ {
options.Configurer?.Invoke(configurer); if (abpEventBusOptions.RetryStrategyOptions != null)
return configurer; {
}); configure.Options(b =>
b.SimpleRetryStrategy(
errorQueueAddress: abpEventBusOptions.DeadLetterName ?? options.InputQueueName + "_dead_letter",
maxDeliveryAttempts: abpEventBusOptions.RetryStrategyOptions.MaxRetryAttempts));
}
options.Configurer?.Invoke(configure);
return configure;
});
} }
public override void OnApplicationInitialization(ApplicationInitializationContext context) public override void OnApplicationInitialization(ApplicationInitializationContext context)

8
framework/src/Volo.Abp.EventBus.Rebus/Volo/Abp/EventBus/Rebus/AbpRebusEventBusOptions.cs

@ -32,7 +32,7 @@ namespace Volo.Abp.EventBus.Rebus
public AbpRebusEventBusOptions() public AbpRebusEventBusOptions()
{ {
_publish = DefaultPublish; _publish = DefaultPublish;
_configurer = DefaultConfigurer; _configurer = DefaultConfigure;
} }
private async Task DefaultPublish(IBus bus, Type eventType, object eventData) private async Task DefaultPublish(IBus bus, Type eventType, object eventData)
@ -40,10 +40,10 @@ namespace Volo.Abp.EventBus.Rebus
await bus.Advanced.Routing.Send(InputQueueName, eventData); await bus.Advanced.Routing.Send(InputQueueName, eventData);
} }
private void DefaultConfigurer(RebusConfigurer configurer) private void DefaultConfigure(RebusConfigurer configure)
{ {
configurer.Subscriptions(s => s.StoreInMemory()); configure.Subscriptions(s => s.StoreInMemory());
configurer.Transport(t => t.UseInMemoryTransport(new InMemNetwork(), InputQueueName)); configure.Transport(t => t.UseInMemoryTransport(new InMemNetwork(), InputQueueName));
} }
} }
} }

5
framework/src/Volo.Abp.EventBus.Rebus/Volo/Abp/EventBus/Rebus/RebusDistributedEventBus.cs

@ -30,8 +30,9 @@ namespace Volo.Abp.EventBus.Rebus
ICurrentTenant currentTenant, ICurrentTenant currentTenant,
IBus rebus, IBus rebus,
IOptions<AbpDistributedEventBusOptions> abpDistributedEventBusOptions, IOptions<AbpDistributedEventBusOptions> abpDistributedEventBusOptions,
IOptions<AbpRebusEventBusOptions> abpEventBusRebusOptions) : IOptions<AbpRebusEventBusOptions> abpEventBusRebusOptions,
base(serviceScopeFactory, currentTenant) IEventErrorHandler errorHandler) :
base(serviceScopeFactory, currentTenant, errorHandler)
{ {
Rebus = rebus; Rebus = rebus;
AbpRebusEventBusOptions = abpEventBusRebusOptions.Value; AbpRebusEventBusOptions = abpEventBusRebusOptions.Value;

32
framework/src/Volo.Abp.EventBus.Rebus/Volo/Abp/EventBus/Rebus/RebusEventErrorHandler.cs

@ -0,0 +1,32 @@
using System.Threading.Tasks;
using Microsoft.Extensions.Options;
using Volo.Abp.DependencyInjection;
namespace Volo.Abp.EventBus.Rebus
{
/// <summary>
/// Rebus will automatic retries and error handling: https://github.com/rebus-org/Rebus/wiki/Automatic-retries-and-error-handling
/// </summary>
public class RebusEventErrorHandler : EventErrorHandlerBase, ISingletonDependency
{
public RebusEventErrorHandler(
IOptions<AbpEventBusOptions> options)
: base(options)
{
}
protected override Task RetryAsync(EventExecutionErrorContext context)
{
ThrowOriginalExceptions(context);
return Task.CompletedTask;
}
protected override Task MoveToDeadLetterAsync(EventExecutionErrorContext context)
{
ThrowOriginalExceptions(context);
return Task.CompletedTask;
}
}
}

1
framework/src/Volo.Abp.EventBus/Volo.Abp.EventBus.csproj

@ -16,6 +16,7 @@
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Volo.Abp.EventBus.Abstractions\Volo.Abp.EventBus.Abstractions.csproj" /> <ProjectReference Include="..\Volo.Abp.EventBus.Abstractions\Volo.Abp.EventBus.Abstractions.csproj" />
<ProjectReference Include="..\Volo.Abp.Json\Volo.Abp.Json.csproj" />
<ProjectReference Include="..\Volo.Abp.MultiTenancy\Volo.Abp.MultiTenancy.csproj" /> <ProjectReference Include="..\Volo.Abp.MultiTenancy\Volo.Abp.MultiTenancy.csproj" />
</ItemGroup> </ItemGroup>

12
framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/AbpEventBusModule.cs

@ -4,6 +4,7 @@ using System.Collections.Generic;
using Volo.Abp.EventBus.Abstractions; using Volo.Abp.EventBus.Abstractions;
using Volo.Abp.EventBus.Distributed; using Volo.Abp.EventBus.Distributed;
using Volo.Abp.EventBus.Local; using Volo.Abp.EventBus.Local;
using Volo.Abp.Json;
using Volo.Abp.Modularity; using Volo.Abp.Modularity;
using Volo.Abp.MultiTenancy; using Volo.Abp.MultiTenancy;
using Volo.Abp.Reflection; using Volo.Abp.Reflection;
@ -12,7 +13,8 @@ namespace Volo.Abp.EventBus
{ {
[DependsOn( [DependsOn(
typeof(AbpEventBusAbstractionsModule), typeof(AbpEventBusAbstractionsModule),
typeof(AbpMultiTenancyModule))] typeof(AbpMultiTenancyModule),
typeof(AbpJsonModule))]
public class AbpEventBusModule : AbpModule public class AbpEventBusModule : AbpModule
{ {
public override void PreConfigureServices(ServiceConfigurationContext context) public override void PreConfigureServices(ServiceConfigurationContext context)
@ -20,6 +22,14 @@ namespace Volo.Abp.EventBus
AddEventHandlers(context.Services); AddEventHandlers(context.Services);
} }
public override void ConfigureServices(ServiceConfigurationContext context)
{
Configure<AbpEventBusOptions>(options =>
{
context.Services.ExecutePreConfiguredActions(options);
});
}
private static void AddEventHandlers(IServiceCollection services) private static void AddEventHandlers(IServiceCollection services)
{ {
var localHandlers = new List<Type>(); var localHandlers = new List<Type>();

22
framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/AbpEventBusOptions.cs

@ -0,0 +1,22 @@
using System;
namespace Volo.Abp.EventBus
{
public class AbpEventBusOptions
{
public bool EnabledErrorHandle { get; set; }
public Func<Type, bool> ErrorHandleSelector { get; set; }
public string DeadLetterName { get; set; }
public AbpEventBusRetryStrategyOptions RetryStrategyOptions { get; set; }
public void UseRetryStrategy(Action<AbpEventBusRetryStrategyOptions> action = null)
{
EnabledErrorHandle = true;
RetryStrategyOptions = new AbpEventBusRetryStrategyOptions();
action?.Invoke(RetryStrategyOptions);
}
}
}

9
framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/AbpEventBusRetryStrategyOptions.cs

@ -0,0 +1,9 @@
namespace Volo.Abp.EventBus
{
public class AbpEventBusRetryStrategyOptions
{
public int IntervalMillisecond { get; set; } = 3000;
public int MaxRetryAttempts { get; set; } = 3;
}
}

21
framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/EventBusBase.cs

@ -19,10 +19,16 @@ namespace Volo.Abp.EventBus
protected ICurrentTenant CurrentTenant { get; } protected ICurrentTenant CurrentTenant { get; }
protected EventBusBase(IServiceScopeFactory serviceScopeFactory, ICurrentTenant currentTenant) protected IEventErrorHandler ErrorHandler { get; }
protected EventBusBase(
IServiceScopeFactory serviceScopeFactory,
ICurrentTenant currentTenant,
IEventErrorHandler errorHandler)
{ {
ServiceScopeFactory = serviceScopeFactory; ServiceScopeFactory = serviceScopeFactory;
CurrentTenant = currentTenant; CurrentTenant = currentTenant;
ErrorHandler = errorHandler;
} }
/// <inheritdoc/> /// <inheritdoc/>
@ -89,7 +95,7 @@ namespace Volo.Abp.EventBus
/// <inheritdoc/> /// <inheritdoc/>
public abstract Task PublishAsync(Type eventType, object eventData); public abstract Task PublishAsync(Type eventType, object eventData);
public virtual async Task TriggerHandlersAsync(Type eventType, object eventData) public virtual async Task TriggerHandlersAsync(Type eventType, object eventData, Action<EventExecutionErrorContext> onErrorAction = null)
{ {
var exceptions = new List<Exception>(); var exceptions = new List<Exception>();
@ -97,16 +103,13 @@ namespace Volo.Abp.EventBus
if (exceptions.Any()) if (exceptions.Any())
{ {
if (exceptions.Count == 1) var context = new EventExecutionErrorContext(exceptions, eventType, this);
{ onErrorAction?.Invoke(context);
exceptions[0].ReThrow(); await ErrorHandler.HandleAsync(context);
}
throw new AggregateException("More than one error has occurred while triggering the event: " + eventType, exceptions);
} }
} }
protected virtual async Task TriggerHandlersAsync(Type eventType, object eventData, List<Exception> exceptions) protected virtual async Task TriggerHandlersAsync(Type eventType, object eventData , List<Exception> exceptions)
{ {
await new SynchronizationContextRemover(); await new SynchronizationContextRemover();

76
framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/EventErrorHandlerBase.cs

@ -0,0 +1,76 @@
using System;
using System.Threading.Tasks;
using Microsoft.Extensions.Options;
namespace Volo.Abp.EventBus
{
public abstract class EventErrorHandlerBase : IEventErrorHandler
{
public const string HeadersKey = "headers";
public const string RetryAttemptKey = "retryAttempt";
protected AbpEventBusOptions Options { get; }
protected EventErrorHandlerBase(IOptions<AbpEventBusOptions> options)
{
Options = options.Value;
}
public virtual async Task HandleAsync(EventExecutionErrorContext context)
{
if (!await ShouldHandleAsync(context))
{
ThrowOriginalExceptions(context);
}
if (await ShouldRetryAsync(context))
{
await RetryAsync(context);
return;
}
await MoveToDeadLetterAsync(context);
}
protected abstract Task RetryAsync(EventExecutionErrorContext context);
protected abstract Task MoveToDeadLetterAsync(EventExecutionErrorContext context);
protected virtual Task<bool> ShouldHandleAsync(EventExecutionErrorContext context)
{
if (!Options.EnabledErrorHandle)
{
return Task.FromResult(false);
}
return Task.FromResult(Options.ErrorHandleSelector == null || Options.ErrorHandleSelector.Invoke(context.EventType));
}
protected virtual Task<bool> ShouldRetryAsync(EventExecutionErrorContext context)
{
if (Options.RetryStrategyOptions == null)
{
return Task.FromResult(false);
}
if (!context.TryGetRetryAttempt(out var retryAttempt))
{
return Task.FromResult(false);
}
return Task.FromResult(Options.RetryStrategyOptions.MaxRetryAttempts > retryAttempt);
}
protected virtual void ThrowOriginalExceptions(EventExecutionErrorContext context)
{
if (context.Exceptions.Count == 1)
{
context.Exceptions[0].ReThrow();
}
throw new AggregateException(
"More than one error has occurred while triggering the event: " + context.EventType,
context.Exceptions);
}
}
}

38
framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/EventExecutionErrorContext.cs

@ -0,0 +1,38 @@
using System;
using System.Collections.Generic;
using Volo.Abp.Data;
using Volo.Abp.ObjectExtending;
namespace Volo.Abp.EventBus
{
public class EventExecutionErrorContext : ExtensibleObject
{
public IReadOnlyList<Exception> Exceptions { get; }
public object EventData { get; set; }
public Type EventType { get; }
public IEventBus EventBus { get; }
public EventExecutionErrorContext(List<Exception> exceptions, Type eventType, IEventBus eventBus)
{
Exceptions = exceptions;
EventType = eventType;
EventBus = eventBus;
}
public bool TryGetRetryAttempt(out int retryAttempt)
{
retryAttempt = 0;
if (!this.HasProperty(EventErrorHandlerBase.RetryAttemptKey))
{
return false;
}
retryAttempt = this.GetProperty<int>(EventErrorHandlerBase.RetryAttemptKey);
return true;
}
}
}

9
framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/IEventErrorHandler.cs

@ -0,0 +1,9 @@
using System.Threading.Tasks;
namespace Volo.Abp.EventBus
{
public interface IEventErrorHandler
{
Task HandleAsync(EventExecutionErrorContext context);
}
}

31
framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Local/LocalEventBus.cs

@ -7,9 +7,11 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.Data;
using Volo.Abp.DependencyInjection; using Volo.Abp.DependencyInjection;
using Volo.Abp.MultiTenancy; using Volo.Abp.MultiTenancy;
using Volo.Abp.Threading; using Volo.Abp.Threading;
using Volo.Abp.Json;
namespace Volo.Abp.EventBus.Local namespace Volo.Abp.EventBus.Local
{ {
@ -28,12 +30,17 @@ namespace Volo.Abp.EventBus.Local
protected ConcurrentDictionary<Type, List<IEventHandlerFactory>> HandlerFactories { get; } protected ConcurrentDictionary<Type, List<IEventHandlerFactory>> HandlerFactories { get; }
protected IJsonSerializer Serializer { get; }
public LocalEventBus( public LocalEventBus(
IOptions<AbpLocalEventBusOptions> options, IOptions<AbpLocalEventBusOptions> options,
IServiceScopeFactory serviceScopeFactory, IServiceScopeFactory serviceScopeFactory,
ICurrentTenant currentTenant) ICurrentTenant currentTenant,
: base(serviceScopeFactory, currentTenant) IEventErrorHandler errorHandler,
IJsonSerializer serializer)
: base(serviceScopeFactory, currentTenant, errorHandler)
{ {
Serializer = serializer;
Options = options.Value; Options = options.Value;
Logger = NullLogger<LocalEventBus>.Instance; Logger = NullLogger<LocalEventBus>.Instance;
@ -119,19 +126,17 @@ namespace Volo.Abp.EventBus.Local
public override async Task PublishAsync(Type eventType, object eventData) public override async Task PublishAsync(Type eventType, object eventData)
{ {
var exceptions = new List<Exception>(); await PublishAsync(new LocalEventMessage(Guid.NewGuid(), eventData, eventType));
}
await TriggerHandlersAsync(eventType, eventData, exceptions);
if (exceptions.Any()) public virtual async Task PublishAsync(LocalEventMessage localEventMessage)
{
var rawEventData = Serializer.Serialize(localEventMessage.EventData);
await TriggerHandlersAsync(localEventMessage.EventType, localEventMessage.EventData, errorContext =>
{ {
if (exceptions.Count == 1) errorContext.EventData = Serializer.Deserialize(localEventMessage.EventType, rawEventData);
{ errorContext.SetProperty(nameof(LocalEventMessage.MessageId), localEventMessage.MessageId);
exceptions[0].ReThrow(); });
}
throw new AggregateException("More than one error has occurred while triggering the event: " + eventType, exceptions);
}
} }
protected override IEnumerable<EventTypeWithEventHandlerFactories> GetHandlerFactories(Type eventType) protected override IEnumerable<EventTypeWithEventHandlerFactories> GetHandlerFactories(Type eventType)

60
framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Local/LocalEventErrorHandler.cs

@ -0,0 +1,60 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.Extensions.Options;
using Volo.Abp.Data;
using Volo.Abp.DependencyInjection;
namespace Volo.Abp.EventBus.Local
{
[ExposeServices(typeof(LocalEventErrorHandler), typeof(IEventErrorHandler))]
public class LocalEventErrorHandler : EventErrorHandlerBase, ISingletonDependency
{
protected Dictionary<Guid, int> RetryTracking { get; }
public LocalEventErrorHandler(
IOptions<AbpEventBusOptions> options)
: base(options)
{
RetryTracking = new Dictionary<Guid, int>();
}
protected override async Task RetryAsync(EventExecutionErrorContext context)
{
if (Options.RetryStrategyOptions.IntervalMillisecond > 0)
{
await Task.Delay(Options.RetryStrategyOptions.IntervalMillisecond);
}
var messageId = context.GetProperty<Guid>(nameof(LocalEventMessage.MessageId));
context.TryGetRetryAttempt(out var retryAttempt);
RetryTracking[messageId] = ++retryAttempt;
await context.EventBus.As<LocalEventBus>().PublishAsync(new LocalEventMessage(messageId, context.EventData, context.EventType));
RetryTracking.Remove(messageId);
}
protected override Task MoveToDeadLetterAsync(EventExecutionErrorContext context)
{
ThrowOriginalExceptions(context);
return Task.CompletedTask;
}
protected override async Task<bool> ShouldRetryAsync(EventExecutionErrorContext context)
{
var messageId = context.GetProperty<Guid>(nameof(LocalEventMessage.MessageId));
context.SetProperty(RetryAttemptKey, RetryTracking.GetOrDefault(messageId));
if (await base.ShouldRetryAsync(context))
{
return true;
}
RetryTracking.Remove(messageId);
return false;
}
}
}

20
framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Local/LocalEventMessage.cs

@ -0,0 +1,20 @@
using System;
namespace Volo.Abp.EventBus.Local
{
public class LocalEventMessage
{
public Guid MessageId { get; }
public object EventData { get; }
public Type EventType { get; }
public LocalEventMessage(Guid messageId, object eventData, Type eventType)
{
MessageId = messageId;
EventData = eventData;
EventType = eventType;
}
}
}

16
framework/src/Volo.Abp.ExceptionHandling/Volo/Abp/AspNetCore/ExceptionHandling/DefaultExceptionToErrorInfoConverter.cs

@ -6,6 +6,7 @@ using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Localization; using Microsoft.Extensions.Localization;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using Volo.Abp.Authorization; using Volo.Abp.Authorization;
using Volo.Abp.Data;
using Volo.Abp.DependencyInjection; using Volo.Abp.DependencyInjection;
using Volo.Abp.Domain.Entities; using Volo.Abp.Domain.Entities;
using Volo.Abp.ExceptionHandling; using Volo.Abp.ExceptionHandling;
@ -58,14 +59,19 @@ namespace Volo.Abp.AspNetCore.ExceptionHandling
exception = TryToGetActualException(exception); exception = TryToGetActualException(exception);
if (exception is EntityNotFoundException) if (exception is AbpRemoteCallException remoteCallException)
{ {
return CreateEntityNotFoundError(exception as EntityNotFoundException); return remoteCallException.Error;
} }
if (exception is AbpRemoteCallException remoteCallException) if (exception is AbpDbConcurrencyException)
{ {
return remoteCallException.Error; return new RemoteServiceErrorInfo(L["AbpDbConcurrencyErrorMessage"]);
}
if (exception is EntityNotFoundException)
{
return CreateEntityNotFoundError(exception as EntityNotFoundException);
} }
var errorInfo = new RemoteServiceErrorInfo(); var errorInfo = new RemoteServiceErrorInfo();
@ -169,7 +175,7 @@ namespace Volo.Abp.AspNetCore.ExceptionHandling
return new RemoteServiceErrorInfo(exception.Message); return new RemoteServiceErrorInfo(exception.Message);
} }
protected virtual Exception TryToGetActualException(Exception exception) protected virtual Exception TryToGetActualException(Exception exception)
{ {
if (exception is AggregateException && exception.InnerException != null) if (exception is AggregateException && exception.InnerException != null)

1
framework/src/Volo.Abp.ExceptionHandling/Volo/Abp/ExceptionHandling/Localization/en.json

@ -13,6 +13,7 @@
"DefaultErrorMessage404": "Resource not found!", "DefaultErrorMessage404": "Resource not found!",
"DefaultErrorMessage404Detail": "The resource requested could not found on the server!", "DefaultErrorMessage404Detail": "The resource requested could not found on the server!",
"EntityNotFoundErrorMessage": "There is no entity {0} with id = {1}!", "EntityNotFoundErrorMessage": "There is no entity {0} with id = {1}!",
"AbpDbConcurrencyErrorMessage": "The data you have submitted has already changed by another user/client. Please discard the changes you've done and try from the beginning.",
"Error": "Error", "Error": "Error",
"UnhandledException": "Unhandled exception!", "UnhandledException": "Unhandled exception!",
"401Message": "Unauthorized", "401Message": "Unauthorized",

1
framework/src/Volo.Abp.ExceptionHandling/Volo/Abp/ExceptionHandling/Localization/tr.json

@ -13,6 +13,7 @@
"DefaultErrorMessage404": "Kaynak bulunamadı!", "DefaultErrorMessage404": "Kaynak bulunamadı!",
"DefaultErrorMessage404Detail": "İstenilen kaynak sunucuda bulunamadı.", "DefaultErrorMessage404Detail": "İstenilen kaynak sunucuda bulunamadı.",
"EntityNotFoundErrorMessage": "Id değeri {1} olan {0} türünden bir nesne bulunamadı!", "EntityNotFoundErrorMessage": "Id değeri {1} olan {0} türünden bir nesne bulunamadı!",
"AbpDbConcurrencyErrorMessage": "Gönderdiğiniz veri başka bir kullanıcı/istemci tarafından değiştirilmiş. Lütfen işlemi iptal edip baştan deneyin.",
"Error": "Hata", "Error": "Hata",
"UnhandledException": "Yakalanmamış hata!", "UnhandledException": "Yakalanmamış hata!",
"401Message": "Yetkisiz İşlem", "401Message": "Yetkisiz İşlem",

2
framework/src/Volo.Abp.Kafka/Volo/Abp/Kafka/AbpKafkaOptions.cs

@ -14,8 +14,6 @@ namespace Volo.Abp.Kafka
public Action<TopicSpecification> ConfigureTopic { get; set; } public Action<TopicSpecification> ConfigureTopic { get; set; }
public bool ReQueue { get; set; } = true;
public AbpKafkaOptions() public AbpKafkaOptions()
{ {
Connections = new KafkaConnections(); Connections = new KafkaConnections();

2
framework/src/Volo.Abp.Kafka/Volo/Abp/Kafka/IKafkaMessageConsumerFactory.cs

@ -8,11 +8,13 @@
/// not disposed until end of the application. /// not disposed until end of the application.
/// </summary> /// </summary>
/// <param name="topicName"></param> /// <param name="topicName"></param>
/// <param name="deadLetterTopicName"></param>
/// <param name="groupId"></param> /// <param name="groupId"></param>
/// <param name="connectionName"></param> /// <param name="connectionName"></param>
/// <returns></returns> /// <returns></returns>
IKafkaMessageConsumer Create( IKafkaMessageConsumer Create(
string topicName, string topicName,
string deadLetterTopicName,
string groupId, string groupId,
string connectionName = null); string connectionName = null);
} }

2
framework/src/Volo.Abp.Kafka/Volo/Abp/Kafka/IKafkaSerializer.cs

@ -7,5 +7,7 @@ namespace Volo.Abp.Kafka
byte[] Serialize(object obj); byte[] Serialize(object obj);
object Deserialize(byte[] value, Type type); object Deserialize(byte[] value, Type type);
T Deserialize<T>(byte[] value);
} }
} }

66
framework/src/Volo.Abp.Kafka/Volo/Abp/Kafka/KafkaMessageConsumer.cs

@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Confluent.Kafka; using Confluent.Kafka;
@ -26,6 +27,8 @@ namespace Volo.Abp.Kafka
protected AbpKafkaOptions Options { get; } protected AbpKafkaOptions Options { get; }
protected AbpAsyncTimer Timer { get; }
protected ConcurrentBag<Func<Message<string, byte[]>, Task>> Callbacks { get; } protected ConcurrentBag<Func<Message<string, byte[]>, Task>> Callbacks { get; }
protected IConsumer<string, byte[]> Consumer { get; private set; } protected IConsumer<string, byte[]> Consumer { get; private set; }
@ -36,34 +39,43 @@ namespace Volo.Abp.Kafka
protected string TopicName { get; private set; } protected string TopicName { get; private set; }
protected string DeadLetterTopicName { get; private set; }
public KafkaMessageConsumer( public KafkaMessageConsumer(
IConsumerPool consumerPool, IConsumerPool consumerPool,
IExceptionNotifier exceptionNotifier, IExceptionNotifier exceptionNotifier,
IOptions<AbpKafkaOptions> options, IOptions<AbpKafkaOptions> options,
IProducerPool producerPool) IProducerPool producerPool,
AbpAsyncTimer timer)
{ {
ConsumerPool = consumerPool; ConsumerPool = consumerPool;
ExceptionNotifier = exceptionNotifier; ExceptionNotifier = exceptionNotifier;
ProducerPool = producerPool; ProducerPool = producerPool;
Timer = timer;
Options = options.Value; Options = options.Value;
Logger = NullLogger<KafkaMessageConsumer>.Instance; Logger = NullLogger<KafkaMessageConsumer>.Instance;
Callbacks = new ConcurrentBag<Func<Message<string, byte[]>, Task>>(); Callbacks = new ConcurrentBag<Func<Message<string, byte[]>, Task>>();
Timer.Period = 5000; //5 sec.
Timer.Elapsed = Timer_Elapsed;
Timer.RunOnStart = true;
} }
public virtual void Initialize( public virtual void Initialize(
[NotNull] string topicName, [NotNull] string topicName,
[NotNull] string deadLetterTopicName,
[NotNull] string groupId, [NotNull] string groupId,
string connectionName = null) string connectionName = null)
{ {
Check.NotNull(topicName, nameof(topicName)); Check.NotNull(topicName, nameof(topicName));
Check.NotNull(deadLetterTopicName, nameof(deadLetterTopicName));
Check.NotNull(groupId, nameof(groupId)); Check.NotNull(groupId, nameof(groupId));
TopicName = topicName; TopicName = topicName;
DeadLetterTopicName = deadLetterTopicName;
ConnectionName = connectionName ?? KafkaConnections.DefaultConnectionName; ConnectionName = connectionName ?? KafkaConnections.DefaultConnectionName;
GroupId = groupId; GroupId = groupId;
Timer.Start();
AsyncHelper.RunSync(CreateTopicAsync);
Consume();
} }
public virtual void OnMessageReceived(Func<Message<string, byte[]>, Task> callback) public virtual void OnMessageReceived(Func<Message<string, byte[]>, Task> callback)
@ -71,26 +83,45 @@ namespace Volo.Abp.Kafka
Callbacks.Add(callback); Callbacks.Add(callback);
} }
protected virtual async Task Timer_Elapsed(AbpAsyncTimer timer)
{
await CreateTopicAsync();
Consume();
Timer.Stop();
}
protected virtual async Task CreateTopicAsync() protected virtual async Task CreateTopicAsync()
{ {
using (var adminClient = new AdminClientBuilder(Options.Connections.GetOrDefault(ConnectionName)).Build()) using (var adminClient = new AdminClientBuilder(Options.Connections.GetOrDefault(ConnectionName)).Build())
{ {
var topic = new TopicSpecification var topics = new List<TopicSpecification>
{ {
Name = TopicName, new()
NumPartitions = 1, {
ReplicationFactor = 1 Name = TopicName,
NumPartitions = 1,
ReplicationFactor = 1
},
new()
{
Name = DeadLetterTopicName,
NumPartitions = 1,
ReplicationFactor = 1
}
}; };
Options.ConfigureTopic?.Invoke(topic); topics.ForEach(topic =>
{
Options.ConfigureTopic?.Invoke(topic);
});
try try
{ {
await adminClient.CreateTopicsAsync(new[] {topic}); await adminClient.CreateTopicsAsync(topics);
} }
catch (CreateTopicsException e) catch (CreateTopicsException e)
{ {
if(e.Results.First().Error.Code != ErrorCode.TopicAlreadyExists) if(e.Results.Any(x => x.Error.Code != ErrorCode.TopicAlreadyExists))
{ {
throw; throw;
} }
@ -139,8 +170,6 @@ namespace Volo.Abp.Kafka
} }
catch (Exception ex) catch (Exception ex)
{ {
await RequeueAsync(consumeResult);
Logger.LogException(ex); Logger.LogException(ex);
await ExceptionNotifier.NotifyAsync(ex); await ExceptionNotifier.NotifyAsync(ex);
} }
@ -150,17 +179,6 @@ namespace Volo.Abp.Kafka
} }
} }
protected virtual async Task RequeueAsync(ConsumeResult<string, byte[]> consumeResult)
{
if (!Options.ReQueue)
{
return;
}
var producer = ProducerPool.Get(ConnectionName);
await producer.ProduceAsync(consumeResult.Topic, consumeResult.Message);
}
public virtual void Dispose() public virtual void Dispose()
{ {
if (Consumer == null) if (Consumer == null)

3
framework/src/Volo.Abp.Kafka/Volo/Abp/Kafka/KafkaMessageConsumerFactory.cs

@ -16,11 +16,12 @@ namespace Volo.Abp.Kafka
public IKafkaMessageConsumer Create( public IKafkaMessageConsumer Create(
string topicName, string topicName,
string deadLetterTopicName,
string groupId, string groupId,
string connectionName = null) string connectionName = null)
{ {
var consumer = ServiceScope.ServiceProvider.GetRequiredService<KafkaMessageConsumer>(); var consumer = ServiceScope.ServiceProvider.GetRequiredService<KafkaMessageConsumer>();
consumer.Initialize(topicName, groupId, connectionName); consumer.Initialize(topicName, deadLetterTopicName, groupId, connectionName);
return consumer; return consumer;
} }

5
framework/src/Volo.Abp.Kafka/Volo/Abp/Kafka/Utf8JsonKafkaSerializer.cs

@ -23,5 +23,10 @@ namespace Volo.Abp.Kafka
{ {
return _jsonSerializer.Deserialize(type, Encoding.UTF8.GetString(value)); return _jsonSerializer.Deserialize(type, Encoding.UTF8.GetString(value));
} }
public T Deserialize<T>(byte[] value)
{
return _jsonSerializer.Deserialize<T>(Encoding.UTF8.GetString(value));
}
} }
} }

8
framework/src/Volo.Abp.MultiTenancy/Volo/Abp/MultiTenancy/MultiTenantConnectionStringResolver.cs

@ -41,7 +41,7 @@ namespace Volo.Abp.MultiTenancy
} }
var tenantDefaultConnectionString = tenant.ConnectionStrings.Default; var tenantDefaultConnectionString = tenant.ConnectionStrings.Default;
//Requesting default connection string... //Requesting default connection string...
if (connectionStringName == null || if (connectionStringName == null ||
connectionStringName == ConnectionStrings.DefaultConnectionStringName) connectionStringName == ConnectionStrings.DefaultConnectionStringName)
@ -59,10 +59,10 @@ namespace Volo.Abp.MultiTenancy
//Found for the tenant //Found for the tenant
return connString; return connString;
} }
//Fallback to the mapped database for the specific connection string //Fallback to the mapped database for the specific connection string
var database = Options.Databases.GetMappedDatabaseOrNull(connectionStringName); var database = Options.Databases.GetMappedDatabaseOrNull(connectionStringName);
if (database != null) if (database != null && database.IsUsedByTenants)
{ {
connString = tenant.ConnectionStrings.GetOrDefault(database.DatabaseName); connString = tenant.ConnectionStrings.GetOrDefault(database.DatabaseName);
if (!connString.IsNullOrWhiteSpace()) if (!connString.IsNullOrWhiteSpace())
@ -166,4 +166,4 @@ namespace Volo.Abp.MultiTenancy
} }
} }
} }
} }

14
framework/src/Volo.Abp.RabbitMQ/Volo/Abp/RabbitMQ/ExchangeDeclareConfiguration.cs

@ -6,6 +6,8 @@ namespace Volo.Abp.RabbitMQ
{ {
public string ExchangeName { get; } public string ExchangeName { get; }
public string DeadLetterExchangeName { get; set; }
public string Type { get; } public string Type { get; }
public bool Durable { get; set; } public bool Durable { get; set; }
@ -15,16 +17,18 @@ namespace Volo.Abp.RabbitMQ
public IDictionary<string, object> Arguments { get; } public IDictionary<string, object> Arguments { get; }
public ExchangeDeclareConfiguration( public ExchangeDeclareConfiguration(
string exchangeName, string exchangeName,
string type, string type,
bool durable = false, bool durable = false,
bool autoDelete = false) bool autoDelete = false,
string deadLetterExchangeName = null)
{ {
ExchangeName = exchangeName; ExchangeName = exchangeName;
DeadLetterExchangeName = deadLetterExchangeName;
Type = type; Type = type;
Durable = durable; Durable = durable;
AutoDelete = autoDelete; AutoDelete = autoDelete;
Arguments = new Dictionary<string, object>(); Arguments = new Dictionary<string, object>();
} }
} }
} }

2
framework/src/Volo.Abp.RabbitMQ/Volo/Abp/RabbitMQ/IRabbitMqSerializer.cs

@ -7,5 +7,7 @@ namespace Volo.Abp.RabbitMQ
byte[] Serialize(object obj); byte[] Serialize(object obj);
object Deserialize(byte[] value, Type type); object Deserialize(byte[] value, Type type);
T Deserialize<T>(byte[] value);
} }
} }

17
framework/src/Volo.Abp.RabbitMQ/Volo/Abp/RabbitMQ/QueueDeclareConfiguration.cs

@ -6,8 +6,9 @@ namespace Volo.Abp.RabbitMQ
{ {
public class QueueDeclareConfiguration public class QueueDeclareConfiguration
{ {
[NotNull] [NotNull] public string QueueName { get; }
public string QueueName { get; }
public string DeadLetterQueueName { get; set; }
public bool Durable { get; set; } public bool Durable { get; set; }
@ -18,12 +19,14 @@ namespace Volo.Abp.RabbitMQ
public IDictionary<string, object> Arguments { get; } public IDictionary<string, object> Arguments { get; }
public QueueDeclareConfiguration( public QueueDeclareConfiguration(
[NotNull] string queueName, [NotNull] string queueName,
bool durable = true, bool durable = true,
bool exclusive = false, bool exclusive = false,
bool autoDelete = false) bool autoDelete = false,
string deadLetterQueueName = null)
{ {
QueueName = queueName; QueueName = queueName;
DeadLetterQueueName = deadLetterQueueName;
Durable = durable; Durable = durable;
Exclusive = exclusive; Exclusive = exclusive;
AutoDelete = autoDelete; AutoDelete = autoDelete;
@ -41,4 +44,4 @@ namespace Volo.Abp.RabbitMQ
); );
} }
} }
} }

45
framework/src/Volo.Abp.RabbitMQ/Volo/Abp/RabbitMQ/RabbitMqMessageConsumer.cs

@ -6,6 +6,7 @@ using RabbitMQ.Client.Events;
using System; using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Threading.Tasks; using System.Threading.Tasks;
using RabbitMQ.Client.Exceptions;
using Volo.Abp.DependencyInjection; using Volo.Abp.DependencyInjection;
using Volo.Abp.ExceptionHandling; using Volo.Abp.ExceptionHandling;
using Volo.Abp.Threading; using Volo.Abp.Threading;
@ -147,6 +148,7 @@ namespace Volo.Abp.RabbitMQ
Channel = ConnectionPool Channel = ConnectionPool
.Get(ConnectionName) .Get(ConnectionName)
.CreateModel(); .CreateModel();
Channel.ExchangeDeclare( Channel.ExchangeDeclare(
exchange: Exchange.ExchangeName, exchange: Exchange.ExchangeName,
type: Exchange.Type, type: Exchange.Type,
@ -155,7 +157,29 @@ namespace Volo.Abp.RabbitMQ
arguments: Exchange.Arguments arguments: Exchange.Arguments
); );
Channel.QueueDeclare( if (!Exchange.DeadLetterExchangeName.IsNullOrWhiteSpace() &&
!Queue.DeadLetterQueueName.IsNullOrWhiteSpace())
{
Channel.ExchangeDeclare(
Exchange.DeadLetterExchangeName,
Exchange.Type,
Exchange.Durable,
Exchange.AutoDelete
);
Channel.QueueDeclare(
Queue.DeadLetterQueueName,
Queue.Durable,
Queue.Exclusive,
Queue.AutoDelete);
Queue.Arguments["x-dead-letter-exchange"] = Exchange.DeadLetterExchangeName;
Queue.Arguments["x-dead-letter-routing-key"] = Queue.DeadLetterQueueName;
Channel.QueueBind(Queue.DeadLetterQueueName, Exchange.DeadLetterExchangeName, Queue.DeadLetterQueueName);
}
var result = Channel.QueueDeclare(
queue: Queue.QueueName, queue: Queue.QueueName,
durable: Queue.Durable, durable: Queue.Durable,
exclusive: Queue.Exclusive, exclusive: Queue.Exclusive,
@ -174,6 +198,17 @@ namespace Volo.Abp.RabbitMQ
} }
catch (Exception ex) catch (Exception ex)
{ {
if (ex is OperationInterruptedException operationInterruptedException &&
operationInterruptedException.ShutdownReason.ReplyCode == 406 &&
operationInterruptedException.Message.Contains("arg 'x-dead-letter-exchange'"))
{
Exchange.DeadLetterExchangeName = null;
Queue.DeadLetterQueueName = null;
Queue.Arguments.Remove("x-dead-letter-exchange");
Queue.Arguments.Remove("x-dead-letter-routing-key");
Logger.LogWarning("Unable to bind the dead letter queue to an existing queue. You can delete the queue or add policy. See: https://www.rabbitmq.com/parameters.html");
}
Logger.LogException(ex, LogLevel.Warning); Logger.LogException(ex, LogLevel.Warning);
await ExceptionNotifier.NotifyAsync(ex, logLevel: LogLevel.Warning); await ExceptionNotifier.NotifyAsync(ex, logLevel: LogLevel.Warning);
} }
@ -194,14 +229,10 @@ namespace Volo.Abp.RabbitMQ
{ {
try try
{ {
Channel.BasicNack( Channel.BasicReject(basicDeliverEventArgs.DeliveryTag, false);
basicDeliverEventArgs.DeliveryTag,
multiple: false,
requeue: true
);
} }
catch { } catch { }
Logger.LogException(ex); Logger.LogException(ex);
await ExceptionNotifier.NotifyAsync(ex); await ExceptionNotifier.NotifyAsync(ex);
} }

7
framework/src/Volo.Abp.RabbitMQ/Volo/Abp/RabbitMQ/Utf8JsonRabbitMqSerializer.cs

@ -23,5 +23,10 @@ namespace Volo.Abp.RabbitMQ
{ {
return _jsonSerializer.Deserialize(type, Encoding.UTF8.GetString(value)); return _jsonSerializer.Deserialize(type, Encoding.UTF8.GetString(value));
} }
public T Deserialize<T>(byte[] value)
{
return _jsonSerializer.Deserialize<T>(Encoding.UTF8.GetString(value));
}
} }
} }

117
framework/src/Volo.Abp.Security/System/Security/Principal/AbpClaimsIdentityExtensions.cs

@ -17,10 +17,12 @@ namespace System.Security.Principal
{ {
return null; return null;
} }
if (Guid.TryParse(userIdOrNull.Value, out Guid result))
if (Guid.TryParse(userIdOrNull.Value, out Guid guid))
{ {
return result; return guid;
} }
return null; return null;
} }
@ -36,7 +38,12 @@ namespace System.Security.Principal
return null; return null;
} }
return Guid.Parse(userIdOrNull.Value); if (Guid.TryParse(userIdOrNull.Value, out var guid))
{
return guid;
}
return null;
} }
public static Guid? FindTenantId([NotNull] this ClaimsPrincipal principal) public static Guid? FindTenantId([NotNull] this ClaimsPrincipal principal)
@ -49,7 +56,12 @@ namespace System.Security.Principal
return null; return null;
} }
return Guid.Parse(tenantIdOrNull.Value); if (Guid.TryParse(tenantIdOrNull.Value, out var guid))
{
return guid;
}
return null;
} }
public static Guid? FindTenantId([NotNull] this IIdentity identity) public static Guid? FindTenantId([NotNull] this IIdentity identity)
@ -64,7 +76,12 @@ namespace System.Security.Principal
return null; return null;
} }
return Guid.Parse(tenantIdOrNull.Value); if (Guid.TryParse(tenantIdOrNull.Value, out var guid))
{
return guid;
}
return null;
} }
public static string FindClientId([NotNull] this ClaimsPrincipal principal) public static string FindClientId([NotNull] this ClaimsPrincipal principal)
@ -105,7 +122,12 @@ namespace System.Security.Principal
return null; return null;
} }
return Guid.Parse(editionIdOrNull.Value); if (Guid.TryParse(editionIdOrNull.Value, out var guid))
{
return guid;
}
return null;
} }
public static Guid? FindEditionId([NotNull] this IIdentity identity) public static Guid? FindEditionId([NotNull] this IIdentity identity)
@ -120,7 +142,88 @@ namespace System.Security.Principal
return null; return null;
} }
return Guid.Parse(editionIdOrNull.Value); if (Guid.TryParse(editionIdOrNull.Value, out var guid))
{
return guid;
}
return null;
}
public static Guid? FindImpersonatorTenantId([NotNull] this ClaimsPrincipal principal)
{
Check.NotNull(principal, nameof(principal));
var impersonatorTenantIdOrNull = principal.Claims?.FirstOrDefault(c => c.Type == AbpClaimTypes.ImpersonatorTenantId);
if (impersonatorTenantIdOrNull == null || impersonatorTenantIdOrNull.Value.IsNullOrWhiteSpace())
{
return null;
}
if (Guid.TryParse(impersonatorTenantIdOrNull.Value, out var guid))
{
return guid;
}
return null;
}
public static Guid? FindImpersonatorTenantId([NotNull] this IIdentity identity)
{
Check.NotNull(identity, nameof(identity));
var claimsIdentity = identity as ClaimsIdentity;
var impersonatorTenantIdOrNull = claimsIdentity?.Claims?.FirstOrDefault(c => c.Type == AbpClaimTypes.ImpersonatorTenantId);
if (impersonatorTenantIdOrNull == null || impersonatorTenantIdOrNull.Value.IsNullOrWhiteSpace())
{
return null;
}
if (Guid.TryParse(impersonatorTenantIdOrNull.Value, out var guid))
{
return guid;
}
return null;
}
public static Guid? FindImpersonatorUserId([NotNull] this ClaimsPrincipal principal)
{
Check.NotNull(principal, nameof(principal));
var impersonatorUserIdOrNull = principal.Claims?.FirstOrDefault(c => c.Type == AbpClaimTypes.ImpersonatorUserId);
if (impersonatorUserIdOrNull == null || impersonatorUserIdOrNull.Value.IsNullOrWhiteSpace())
{
return null;
}
if (Guid.TryParse(impersonatorUserIdOrNull.Value, out var guid))
{
return guid;
}
return null;
}
public static Guid? FindImpersonatorUserId([NotNull] this IIdentity identity)
{
Check.NotNull(identity, nameof(identity));
var claimsIdentity = identity as ClaimsIdentity;
var impersonatorUserIdOrNull = claimsIdentity?.Claims?.FirstOrDefault(c => c.Type == AbpClaimTypes.ImpersonatorUserId);
if (impersonatorUserIdOrNull == null || impersonatorUserIdOrNull.Value.IsNullOrWhiteSpace())
{
return null;
}
if (Guid.TryParse(impersonatorUserIdOrNull.Value, out var guid))
{
return guid;
}
return null;
} }
public static ClaimsIdentity AddIfNotContains(this ClaimsIdentity claimsIdentity, Claim claim) public static ClaimsIdentity AddIfNotContains(this ClaimsIdentity claimsIdentity, Claim claim)

10
framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/AbpClaimTypes.cs

@ -67,5 +67,15 @@ namespace Volo.Abp.Security.Claims
/// Default: "client_id". /// Default: "client_id".
/// </summary> /// </summary>
public static string ClientId { get; set; } = "client_id"; public static string ClientId { get; set; } = "client_id";
/// <summary>
/// Default: "impersonator_tenantid".
/// </summary>
public static string ImpersonatorTenantId { get; set; } = "impersonator_tenantid";
/// <summary>
/// Default: "impersonator_userid".
/// </summary>
public static string ImpersonatorUserId { get; set; } = "impersonator_userid";
} }
} }

33
framework/src/Volo.Abp.Security/Volo/Abp/Users/CurrentUserExtensions.cs

@ -1,6 +1,7 @@
using System; using System;
using System.Diagnostics; using System.Diagnostics;
using JetBrains.Annotations; using JetBrains.Annotations;
using Volo.Abp.Security.Claims;
namespace Volo.Abp.Users namespace Volo.Abp.Users
{ {
@ -30,5 +31,35 @@ namespace Volo.Abp.Users
return currentUser.Id.Value; return currentUser.Id.Value;
} }
public static Guid? FindImpersonatorTenantId([NotNull] this ICurrentUser currentUser)
{
var impersonatorTenantId = currentUser.FindClaimValue(AbpClaimTypes.ImpersonatorTenantId);
if (impersonatorTenantId == null || impersonatorTenantId.IsNullOrWhiteSpace())
{
return null;
}
if (Guid.TryParse(impersonatorTenantId, out var guid))
{
return guid;
}
return null;
}
public static Guid? FindImpersonatorUserId([NotNull] this ICurrentUser currentUser)
{
var impersonatorUserId = currentUser.FindClaimValue(AbpClaimTypes.ImpersonatorUserId);
if (impersonatorUserId == null || impersonatorUserId.IsNullOrWhiteSpace())
{
return null;
}
if (Guid.TryParse(impersonatorUserId, out var guid))
{
return guid;
}
return null;
}
} }
} }

34
framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/Auditing_Tests.cs

@ -6,6 +6,7 @@ using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.DependencyInjection.Extensions;
using NSubstitute; using NSubstitute;
using Volo.Abp.Auditing.App.Entities; using Volo.Abp.Auditing.App.Entities;
using Volo.Abp.Data;
using Volo.Abp.DependencyInjection; using Volo.Abp.DependencyInjection;
using Volo.Abp.Domain.Repositories; using Volo.Abp.Domain.Repositories;
using Volo.Abp.Uow; using Volo.Abp.Uow;
@ -44,7 +45,7 @@ namespace Volo.Abp.Auditing
await _auditingStore.Received().SaveAsync(Arg.Any<AuditLogInfo>()); await _auditingStore.Received().SaveAsync(Arg.Any<AuditLogInfo>());
} }
[Fact] [Fact]
public async Task Should_Write_AuditLog_For_Classes_That_Implement_IAuditingEnabled_Without_An_Explicit_Scope() public async Task Should_Write_AuditLog_For_Classes_That_Implement_IAuditingEnabled_Without_An_Explicit_Scope()
{ {
@ -280,6 +281,37 @@ namespace Volo.Abp.Auditing
.Where(y => y.PropertyName != nameof(AppEntityWithAuditedAndHasCustomAuditingProperties .Where(y => y.PropertyName != nameof(AppEntityWithAuditedAndHasCustomAuditingProperties
.ExtraProperties)) .ExtraProperties))
.All(y => GetBaseAuditPropertyNames().Contains(y.PropertyName)))); .All(y => GetBaseAuditPropertyNames().Contains(y.PropertyName))));
#pragma warning restore 4014
}
[Fact]
public virtual async Task Should_Write_AuditLog_Without_ExtraPropertyDictionary()
{
var entityId = Guid.NewGuid();
var repository = ServiceProvider.GetRequiredService<IBasicRepository<AppEntityWithAudited, Guid>>();
var appEntityWithAudited = new AppEntityWithAudited(entityId, "test name");
appEntityWithAudited.SetProperty("No", 123456);
await repository.InsertAsync(appEntityWithAudited);
using (var scope = _auditingManager.BeginScope())
{
using (var uow = _unitOfWorkManager.Begin())
{
var entity = await repository.GetAsync(entityId);
entity.Name = "new test name";
await repository.UpdateAsync(entity);
await uow.CompleteAsync();
}
await scope.SaveAsync();
}
#pragma warning disable 4014
_auditingStore.Received().SaveAsync(Arg.Is<AuditLogInfo>(x => x.EntityChanges.Count == 1
&& x.EntityChanges[0].PropertyChanges.Count == 1
&& x.EntityChanges[0].PropertyChanges[0].PropertyName == nameof(AppEntityWithAudited.Name)));
#pragma warning restore 4014 #pragma warning restore 4014
} }
} }

13
framework/test/Volo.Abp.EventBus.Tests/Volo/Abp/EventBus/EventBusTestModule.cs

@ -5,6 +5,17 @@ namespace Volo.Abp.EventBus
[DependsOn(typeof(AbpEventBusModule))] [DependsOn(typeof(AbpEventBusModule))]
public class EventBusTestModule : AbpModule public class EventBusTestModule : AbpModule
{ {
public override void PreConfigureServices(ServiceConfigurationContext context)
{
PreConfigure<AbpEventBusOptions>(options =>
{
options.UseRetryStrategy(retryStrategyOptions =>
{
retryStrategyOptions.IntervalMillisecond = 0;
});
options.ErrorHandleSelector = type => type == typeof(MyExceptionHandleEventData);
});
}
} }
} }

75
framework/test/Volo.Abp.EventBus.Tests/Volo/Abp/EventBus/Local/EventBus_Exception_Handler_Tests.cs

@ -0,0 +1,75 @@
using System;
using System.Threading.Tasks;
using Shouldly;
using Xunit;
namespace Volo.Abp.EventBus.Local
{
public class EventBus_Exception_Handler_Tests : EventBusTestBase
{
[Fact]
public async Task Should_Not_Handle_Exception()
{
var retryAttempt = 0;
LocalEventBus.Subscribe<MySimpleEventData>(eventData =>
{
retryAttempt++;
throw new Exception("This exception is intentionally thrown!");
});
var appException = await Assert.ThrowsAsync<Exception>(async () =>
{
await LocalEventBus.PublishAsync(new MySimpleEventData(1));
});
retryAttempt.ShouldBe(1);
appException.Message.ShouldBe("This exception is intentionally thrown!");
}
[Fact]
public async Task Should_Handle_Exception()
{
var retryAttempt = 0;
LocalEventBus.Subscribe<MyExceptionHandleEventData>(eventData =>
{
eventData.Value.ShouldBe(0);
retryAttempt++;
eventData.Value++;
if (retryAttempt < 2)
{
throw new Exception("This exception is intentionally thrown!");
}
return Task.CompletedTask;
});
await LocalEventBus.PublishAsync(new MyExceptionHandleEventData(0));
retryAttempt.ShouldBe(2);
}
[Fact]
public async Task Should_Throw_Exception_After_Error_Handle()
{
var retryAttempt = 0;
LocalEventBus.Subscribe<MyExceptionHandleEventData>(eventData =>
{
eventData.Value.ShouldBe(0);
retryAttempt++;
eventData.Value++;
throw new Exception("This exception is intentionally thrown!");
});
var appException = await Assert.ThrowsAsync<Exception>(async () =>
{
await LocalEventBus.PublishAsync(new MyExceptionHandleEventData(0));
});
retryAttempt.ShouldBe(4);
appException.Message.ShouldBe("This exception is intentionally thrown!");
}
}
}

12
framework/test/Volo.Abp.EventBus.Tests/Volo/Abp/EventBus/MyExceptionHandleEventData.cs

@ -0,0 +1,12 @@
namespace Volo.Abp.EventBus
{
public class MyExceptionHandleEventData
{
public int Value { get; set; }
public MyExceptionHandleEventData(int value)
{
Value = value;
}
}
}

24
framework/test/Volo.Abp.MultiTenancy.Tests/Volo/Abp/Data/MultiTenancy/MultiTenantConnectionStringResolver_Tests.cs

@ -30,6 +30,21 @@ namespace Volo.Abp.Data.MultiTenancy
{ {
options.ConnectionStrings.Default = "default-value"; options.ConnectionStrings.Default = "default-value";
options.ConnectionStrings["db1"] = "db1-default-value"; options.ConnectionStrings["db1"] = "db1-default-value";
options.ConnectionStrings["Saas"] = "Saas-default-value";
options.ConnectionStrings["Admin"] = "Admin-default-value";
options.Databases.Configure("Saas", database =>
{
database.MappedConnections.Add("Saas1");
database.MappedConnections.Add("Saas2");
database.IsUsedByTenants = false;
});
options.Databases.Configure("Admin", database =>
{
database.MappedConnections.Add("Admin1");
database.MappedConnections.Add("Admin2");
});
}); });
services.Configure<AbpDefaultTenantStoreOptions>(options => services.Configure<AbpDefaultTenantStoreOptions>(options =>
@ -41,7 +56,8 @@ namespace Volo.Abp.Data.MultiTenancy
ConnectionStrings = ConnectionStrings =
{ {
{ ConnectionStrings.DefaultConnectionStringName, "tenant1-default-value"}, { ConnectionStrings.DefaultConnectionStringName, "tenant1-default-value"},
{"db1", "tenant1-db1-value"} {"db1", "tenant1-db1-value"},
{"Admin", "tenant1-Admin-value"}
} }
}, },
new TenantConfiguration(_tenant2Id, "tenant2") new TenantConfiguration(_tenant2Id, "tenant2")
@ -55,12 +71,16 @@ namespace Volo.Abp.Data.MultiTenancy
//No tenant in current context //No tenant in current context
(await _connectionResolver.ResolveAsync()).ShouldBe("default-value"); (await _connectionResolver.ResolveAsync()).ShouldBe("default-value");
(await _connectionResolver.ResolveAsync("db1")).ShouldBe("db1-default-value"); (await _connectionResolver.ResolveAsync("db1")).ShouldBe("db1-default-value");
(await _connectionResolver.ResolveAsync("Saas1")).ShouldBe("Saas-default-value");
(await _connectionResolver.ResolveAsync("Admin2")).ShouldBe("Admin-default-value");
//Overriden connection strings for tenant1 //Overriden connection strings for tenant1
using (_currentTenant.Change(_tenant1Id)) using (_currentTenant.Change(_tenant1Id))
{ {
(await _connectionResolver.ResolveAsync()).ShouldBe("tenant1-default-value"); (await _connectionResolver.ResolveAsync()).ShouldBe("tenant1-default-value");
(await _connectionResolver.ResolveAsync("db1")).ShouldBe("tenant1-db1-value"); (await _connectionResolver.ResolveAsync("db1")).ShouldBe("tenant1-db1-value");
(await _connectionResolver.ResolveAsync("Saas1")).ShouldBe("tenant1-default-value");
(await _connectionResolver.ResolveAsync("Admin2")).ShouldBe("tenant1-Admin-value");
} }
//No tenant in current context //No tenant in current context
@ -72,6 +92,8 @@ namespace Volo.Abp.Data.MultiTenancy
{ {
(await _connectionResolver.ResolveAsync()).ShouldBe("default-value"); (await _connectionResolver.ResolveAsync()).ShouldBe("default-value");
(await _connectionResolver.ResolveAsync("db1")).ShouldBe("db1-default-value"); (await _connectionResolver.ResolveAsync("db1")).ShouldBe("db1-default-value");
(await _connectionResolver.ResolveAsync("Saas1")).ShouldBe("Saas-default-value");
(await _connectionResolver.ResolveAsync("Admin2")).ShouldBe("Admin-default-value");
} }
} }
} }

8
modules/audit-logging/src/Volo.Abp.AuditLogging.Domain.Shared/Volo/Abp/AuditLogging/AuditLogConsts.cs

@ -32,14 +32,6 @@
/// </summary> /// </summary>
public static int MaxBrowserInfoLength { get; set; } = 512; public static int MaxBrowserInfoLength { get; set; } = 512;
/// <summary>
/// Default value: 4000
/// </summary>
public static int MaxExceptionsLength { get; set; } = 4000;
//TODO: Replace with MaxExceptionsLength in v3.0
public static int MaxExceptionsLengthValue { get; set; } = MaxExceptionsLength;
/// <summary> /// <summary>
/// Default value: 256 /// Default value: 256
/// </summary> /// </summary>

6
modules/audit-logging/src/Volo.Abp.AuditLogging.Domain/Volo/Abp/AuditLogging/AbpAuditLoggingDomainModule.cs

@ -1,5 +1,7 @@
using Volo.Abp.Auditing; using Volo.Abp.Auditing;
using Volo.Abp.Domain; using Volo.Abp.Domain;
using Volo.Abp.ExceptionHandling;
using Volo.Abp.Json;
using Volo.Abp.Modularity; using Volo.Abp.Modularity;
using Volo.Abp.ObjectExtending; using Volo.Abp.ObjectExtending;
using Volo.Abp.ObjectExtending.Modularity; using Volo.Abp.ObjectExtending.Modularity;
@ -10,13 +12,15 @@ namespace Volo.Abp.AuditLogging
[DependsOn(typeof(AbpAuditingModule))] [DependsOn(typeof(AbpAuditingModule))]
[DependsOn(typeof(AbpDddDomainModule))] [DependsOn(typeof(AbpDddDomainModule))]
[DependsOn(typeof(AbpAuditLoggingDomainSharedModule))] [DependsOn(typeof(AbpAuditLoggingDomainSharedModule))]
[DependsOn(typeof(AbpExceptionHandlingModule))]
[DependsOn(typeof(AbpJsonModule))]
public class AbpAuditLoggingDomainModule : AbpModule public class AbpAuditLoggingDomainModule : AbpModule
{ {
private static readonly OneTimeRunner OneTimeRunner = new OneTimeRunner(); private static readonly OneTimeRunner OneTimeRunner = new OneTimeRunner();
public override void PostConfigureServices(ServiceConfigurationContext context) public override void PostConfigureServices(ServiceConfigurationContext context)
{ {
OneTimeRunner.Run(() => OneTimeRunner.Run(() =>
{ {
ModuleExtensionConfigurationHelper.ApplyEntityConfigurationToEntity( ModuleExtensionConfigurationHelper.ApplyEntityConfigurationToEntity(
AuditLoggingModuleExtensionConsts.ModuleName, AuditLoggingModuleExtensionConsts.ModuleName,

98
modules/audit-logging/src/Volo.Abp.AuditLogging.Domain/Volo/Abp/AuditLogging/AuditLog.cs

@ -59,57 +59,55 @@ namespace Volo.Abp.AuditLogging
} }
public AuditLog(IGuidGenerator guidGenerator, AuditLogInfo auditInfo) public AuditLog(
: base(guidGenerator.Create()) Guid id,
string applicationName,
Guid? tenantId,
string tenantName,
Guid? userId,
string userName,
DateTime executionTime,
int executionDuration,
string clientIpAddress,
string clientName,
string clientId,
string correlationId,
string browserInfo,
string httpMethod,
string url,
int? httpStatusCode,
Guid? impersonatorUserId,
Guid? impersonatorTenantId,
ExtraPropertyDictionary extraPropertyDictionary,
List<EntityChange> entityChanges,
List<AuditLogAction> actions,
string exceptions,
string comments)
: base(id)
{ {
ApplicationName = auditInfo.ApplicationName.Truncate(AuditLogConsts.MaxApplicationNameLength); ApplicationName = applicationName.Truncate(AuditLogConsts.MaxApplicationNameLength);
TenantId = auditInfo.TenantId; TenantId = tenantId;
TenantName = auditInfo.TenantName.Truncate(AuditLogConsts.MaxTenantNameLength); TenantName = tenantName.Truncate(AuditLogConsts.MaxTenantNameLength);
UserId = auditInfo.UserId; UserId = userId;
UserName = auditInfo.UserName.Truncate(AuditLogConsts.MaxUserNameLength); UserName = userName.Truncate(AuditLogConsts.MaxUserNameLength);
ExecutionTime = auditInfo.ExecutionTime; ExecutionTime = executionTime;
ExecutionDuration = auditInfo.ExecutionDuration; ExecutionDuration = executionDuration;
ClientIpAddress = auditInfo.ClientIpAddress.Truncate(AuditLogConsts.MaxClientIpAddressLength); ClientIpAddress = clientIpAddress.Truncate(AuditLogConsts.MaxClientIpAddressLength);
ClientName = auditInfo.ClientName.Truncate(AuditLogConsts.MaxClientNameLength); ClientName = clientName.Truncate(AuditLogConsts.MaxClientNameLength);
ClientId = auditInfo.ClientId.Truncate(AuditLogConsts.MaxClientIdLength); ClientId = clientId.Truncate(AuditLogConsts.MaxClientIdLength);
CorrelationId = auditInfo.CorrelationId.Truncate(AuditLogConsts.MaxCorrelationIdLength); CorrelationId = correlationId.Truncate(AuditLogConsts.MaxCorrelationIdLength);
BrowserInfo = auditInfo.BrowserInfo.Truncate(AuditLogConsts.MaxBrowserInfoLength); BrowserInfo = browserInfo.Truncate(AuditLogConsts.MaxBrowserInfoLength);
HttpMethod = auditInfo.HttpMethod.Truncate(AuditLogConsts.MaxHttpMethodLength); HttpMethod = httpMethod.Truncate(AuditLogConsts.MaxHttpMethodLength);
Url = auditInfo.Url.Truncate(AuditLogConsts.MaxUrlLength); Url = url.Truncate(AuditLogConsts.MaxUrlLength);
HttpStatusCode = auditInfo.HttpStatusCode; HttpStatusCode = httpStatusCode;
ImpersonatorUserId = auditInfo.ImpersonatorUserId; ImpersonatorUserId = impersonatorUserId;
ImpersonatorTenantId = auditInfo.ImpersonatorTenantId; ImpersonatorTenantId = impersonatorTenantId;
ExtraProperties = new ExtraPropertyDictionary(); ExtraProperties = extraPropertyDictionary;
if (auditInfo.ExtraProperties != null) EntityChanges = entityChanges;
{ Actions = actions;
foreach (var pair in auditInfo.ExtraProperties) Exceptions = exceptions;
{ Comments = comments.Truncate(AuditLogConsts.MaxCommentsLength);
ExtraProperties.Add(pair.Key, pair.Value);
}
}
EntityChanges = auditInfo
.EntityChanges?
.Select(entityChangeInfo => new EntityChange(guidGenerator, Id, entityChangeInfo, tenantId: auditInfo.TenantId))
.ToList()
?? new List<EntityChange>();
Actions = auditInfo
.Actions?
.Select(auditLogActionInfo => new AuditLogAction(guidGenerator.Create(), Id, auditLogActionInfo, tenantId: auditInfo.TenantId))
.ToList()
?? new List<AuditLogAction>();
Exceptions = auditInfo
.Exceptions?
.JoinAsString(Environment.NewLine)
.Truncate(AuditLogConsts.MaxExceptionsLengthValue);
Comments = auditInfo
.Comments?
.JoinAsString(Environment.NewLine)
.Truncate(AuditLogConsts.MaxCommentsLength);
} }
} }
} }

93
modules/audit-logging/src/Volo.Abp.AuditLogging.Domain/Volo/Abp/AuditLogging/AuditLogInfoToAuditLogConverter.cs

@ -0,0 +1,93 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Volo.Abp.AspNetCore.ExceptionHandling;
using Volo.Abp.Auditing;
using Volo.Abp.Data;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Guids;
using Volo.Abp.Http;
using Volo.Abp.Json;
namespace Volo.Abp.AuditLogging
{
public class AuditLogInfoToAuditLogConverter : IAuditLogInfoToAuditLogConverter, ITransientDependency
{
protected IGuidGenerator GuidGenerator { get; }
protected IExceptionToErrorInfoConverter ExceptionToErrorInfoConverter { get; }
protected IJsonSerializer JsonSerializer { get; }
public AuditLogInfoToAuditLogConverter(IGuidGenerator guidGenerator, IExceptionToErrorInfoConverter exceptionToErrorInfoConverter, IJsonSerializer jsonSerializer)
{
GuidGenerator = guidGenerator;
ExceptionToErrorInfoConverter = exceptionToErrorInfoConverter;
JsonSerializer = jsonSerializer;
}
public virtual Task<AuditLog> ConvertAsync(AuditLogInfo auditLogInfo)
{
var auditLogId = GuidGenerator.Create();
var extraProperties = new ExtraPropertyDictionary();
if (auditLogInfo.ExtraProperties != null)
{
foreach (var pair in auditLogInfo.ExtraProperties)
{
extraProperties.Add(pair.Key, pair.Value);
}
}
var entityChanges = auditLogInfo
.EntityChanges?
.Select(entityChangeInfo => new EntityChange(GuidGenerator, auditLogId, entityChangeInfo, tenantId: auditLogInfo.TenantId))
.ToList()
?? new List<EntityChange>();
var actions = auditLogInfo
.Actions?
.Select(auditLogActionInfo => new AuditLogAction(GuidGenerator.Create(), auditLogId, auditLogActionInfo, tenantId: auditLogInfo.TenantId))
.ToList()
?? new List<AuditLogAction>();
var remoteServiceErrorInfos = auditLogInfo.Exceptions?.Select(exception => ExceptionToErrorInfoConverter.Convert(exception, true))
?? new List<RemoteServiceErrorInfo>();
var exceptions = remoteServiceErrorInfos.Any()
? JsonSerializer.Serialize(remoteServiceErrorInfos, indented: true)
: null;
var comments = auditLogInfo
.Comments?
.JoinAsString(Environment.NewLine);
var auditLog = new AuditLog(
auditLogId,
auditLogInfo.ApplicationName,
auditLogInfo.TenantId,
auditLogInfo.TenantName,
auditLogInfo.UserId,
auditLogInfo.UserName,
auditLogInfo.ExecutionTime,
auditLogInfo.ExecutionDuration,
auditLogInfo.ClientIpAddress,
auditLogInfo.ClientName,
auditLogInfo.ClientId,
auditLogInfo.CorrelationId,
auditLogInfo.BrowserInfo,
auditLogInfo.HttpMethod,
auditLogInfo.Url,
auditLogInfo.HttpStatusCode,
auditLogInfo.ImpersonatorUserId,
auditLogInfo.ImpersonatorTenantId,
extraProperties,
entityChanges,
actions,
exceptions,
comments
);
return Task.FromResult(auditLog);
}
}
}

15
modules/audit-logging/src/Volo.Abp.AuditLogging.Domain/Volo/Abp/AuditLogging/AuditingStore.cs

@ -5,7 +5,6 @@ using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using Volo.Abp.Auditing; using Volo.Abp.Auditing;
using Volo.Abp.DependencyInjection; using Volo.Abp.DependencyInjection;
using Volo.Abp.Guids;
using Volo.Abp.Uow; using Volo.Abp.Uow;
namespace Volo.Abp.AuditLogging namespace Volo.Abp.AuditLogging
@ -13,21 +12,19 @@ namespace Volo.Abp.AuditLogging
public class AuditingStore : IAuditingStore, ITransientDependency public class AuditingStore : IAuditingStore, ITransientDependency
{ {
public ILogger<AuditingStore> Logger { get; set; } public ILogger<AuditingStore> Logger { get; set; }
protected IAuditLogRepository AuditLogRepository { get; } protected IAuditLogRepository AuditLogRepository { get; }
protected IGuidGenerator GuidGenerator { get; }
protected IUnitOfWorkManager UnitOfWorkManager { get; } protected IUnitOfWorkManager UnitOfWorkManager { get; }
protected AbpAuditingOptions Options { get; } protected AbpAuditingOptions Options { get; }
protected IAuditLogInfoToAuditLogConverter Converter { get; }
public AuditingStore( public AuditingStore(
IAuditLogRepository auditLogRepository, IAuditLogRepository auditLogRepository,
IGuidGenerator guidGenerator,
IUnitOfWorkManager unitOfWorkManager, IUnitOfWorkManager unitOfWorkManager,
IOptions<AbpAuditingOptions> options) IOptions<AbpAuditingOptions> options,
IAuditLogInfoToAuditLogConverter converter)
{ {
AuditLogRepository = auditLogRepository; AuditLogRepository = auditLogRepository;
GuidGenerator = guidGenerator;
UnitOfWorkManager = unitOfWorkManager; UnitOfWorkManager = unitOfWorkManager;
Converter = converter;
Options = options.Value; Options = options.Value;
Logger = NullLogger<AuditingStore>.Instance; Logger = NullLogger<AuditingStore>.Instance;
@ -56,9 +53,9 @@ namespace Volo.Abp.AuditLogging
{ {
using (var uow = UnitOfWorkManager.Begin(true)) using (var uow = UnitOfWorkManager.Begin(true))
{ {
await AuditLogRepository.InsertAsync(new AuditLog(GuidGenerator, auditInfo)); await AuditLogRepository.InsertAsync(await Converter.ConvertAsync(auditInfo));
await uow.CompleteAsync(); await uow.CompleteAsync();
} }
} }
} }
} }

10
modules/audit-logging/src/Volo.Abp.AuditLogging.Domain/Volo/Abp/AuditLogging/IAuditLogInfoToAuditLogConverter.cs

@ -0,0 +1,10 @@
using System.Threading.Tasks;
using Volo.Abp.Auditing;
namespace Volo.Abp.AuditLogging
{
public interface IAuditLogInfoToAuditLogConverter
{
Task<AuditLog> ConvertAsync(AuditLogInfo auditLogInfo);
}
}

3
modules/audit-logging/src/Volo.Abp.AuditLogging.EntityFrameworkCore/Volo/Abp/AuditLogging/EntityFrameworkCore/AbpAuditLoggingDbContextModelBuilderExtensions.cs

@ -37,9 +37,6 @@ namespace Volo.Abp.AuditLogging.EntityFrameworkCore
b.Property(x => x.Url).HasMaxLength(AuditLogConsts.MaxUrlLength).HasColumnName(nameof(AuditLog.Url)); b.Property(x => x.Url).HasMaxLength(AuditLogConsts.MaxUrlLength).HasColumnName(nameof(AuditLog.Url));
b.Property(x => x.HttpStatusCode).HasColumnName(nameof(AuditLog.HttpStatusCode)); b.Property(x => x.HttpStatusCode).HasColumnName(nameof(AuditLog.HttpStatusCode));
if (builder.IsUsingOracle()) { AuditLogConsts.MaxExceptionsLengthValue = 2000; }
b.Property(x => x.Exceptions).HasMaxLength(AuditLogConsts.MaxExceptionsLengthValue).HasColumnName(nameof(AuditLog.Exceptions));
b.Property(x => x.Comments).HasMaxLength(AuditLogConsts.MaxCommentsLength).HasColumnName(nameof(AuditLog.Comments)); b.Property(x => x.Comments).HasMaxLength(AuditLogConsts.MaxCommentsLength).HasColumnName(nameof(AuditLog.Comments));
b.Property(x => x.ExecutionDuration).HasColumnName(nameof(AuditLog.ExecutionDuration)); b.Property(x => x.ExecutionDuration).HasColumnName(nameof(AuditLog.ExecutionDuration));
b.Property(x => x.ImpersonatorTenantId).HasColumnName(nameof(AuditLog.ImpersonatorTenantId)); b.Property(x => x.ImpersonatorTenantId).HasColumnName(nameof(AuditLog.ImpersonatorTenantId));

43
modules/audit-logging/test/Volo.Abp.AuditLogging.TestBase/Volo/Abp/AuditLogging/AuditLogRepository_Tests.cs

@ -1,13 +1,10 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization;
using System.Linq; using System.Linq;
using System.Net; using System.Net;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.VisualBasic;
using Shouldly; using Shouldly;
using Volo.Abp.Auditing; using Volo.Abp.Auditing;
using Volo.Abp.Guids;
using Volo.Abp.Modularity; using Volo.Abp.Modularity;
using Xunit; using Xunit;
@ -17,12 +14,12 @@ namespace Volo.Abp.AuditLogging
where TStartupModule : IAbpModule where TStartupModule : IAbpModule
{ {
protected IAuditLogRepository AuditLogRepository { get; } protected IAuditLogRepository AuditLogRepository { get; }
protected IGuidGenerator GuidGenerator { get; } protected IAuditLogInfoToAuditLogConverter AuditLogInfoToAuditLogConverter { get; }
protected AuditLogRepository_Tests() protected AuditLogRepository_Tests()
{ {
AuditLogRepository = GetRequiredService<IAuditLogRepository>(); AuditLogRepository = GetRequiredService<IAuditLogRepository>();
GuidGenerator = GetRequiredService<IGuidGenerator>(); AuditLogInfoToAuditLogConverter = GetRequiredService<IAuditLogInfoToAuditLogConverter>();
} }
[Fact] [Fact]
@ -119,8 +116,8 @@ namespace Volo.Abp.AuditLogging
} }
}; };
await AuditLogRepository.InsertAsync(new AuditLog(GuidGenerator, log1)); await AuditLogRepository.InsertAsync(await AuditLogInfoToAuditLogConverter.ConvertAsync(log1));
await AuditLogRepository.InsertAsync(new AuditLog(GuidGenerator, log2)); await AuditLogRepository.InsertAsync(await AuditLogInfoToAuditLogConverter.ConvertAsync(log2));
//Assert //Assert
var logs = await AuditLogRepository.GetListAsync(); var logs = await AuditLogRepository.GetListAsync();
@ -223,8 +220,8 @@ namespace Volo.Abp.AuditLogging
} }
}; };
await AuditLogRepository.InsertAsync(new AuditLog(GuidGenerator, log1)); await AuditLogRepository.InsertAsync(await AuditLogInfoToAuditLogConverter.ConvertAsync(log1));
await AuditLogRepository.InsertAsync(new AuditLog(GuidGenerator, log2)); await AuditLogRepository.InsertAsync(await AuditLogInfoToAuditLogConverter.ConvertAsync(log2));
//Assert //Assert
var logs = await AuditLogRepository.GetCountAsync(); var logs = await AuditLogRepository.GetCountAsync();
@ -325,8 +322,8 @@ namespace Volo.Abp.AuditLogging
} }
}; };
await AuditLogRepository.InsertAsync(new AuditLog(GuidGenerator, log1)); await AuditLogRepository.InsertAsync(await AuditLogInfoToAuditLogConverter.ConvertAsync(log1));
await AuditLogRepository.InsertAsync(new AuditLog(GuidGenerator, log2)); await AuditLogRepository.InsertAsync(await AuditLogInfoToAuditLogConverter.ConvertAsync(log2));
//Assert //Assert
var date = DateTime.Parse("2020-01-01"); var date = DateTime.Parse("2020-01-01");
@ -428,8 +425,8 @@ namespace Volo.Abp.AuditLogging
} }
}; };
await AuditLogRepository.InsertAsync(new AuditLog(GuidGenerator, log1)); await AuditLogRepository.InsertAsync(await AuditLogInfoToAuditLogConverter.ConvertAsync(log1));
await AuditLogRepository.InsertAsync(new AuditLog(GuidGenerator, log2)); await AuditLogRepository.InsertAsync(await AuditLogInfoToAuditLogConverter.ConvertAsync(log2));
//Assert //Assert
var entityChanges = await AuditLogRepository.GetEntityChangeListAsync(); var entityChanges = await AuditLogRepository.GetEntityChangeListAsync();
@ -534,8 +531,8 @@ namespace Volo.Abp.AuditLogging
} }
}; };
await AuditLogRepository.InsertAsync(new AuditLog(GuidGenerator, log1)); await AuditLogRepository.InsertAsync(await AuditLogInfoToAuditLogConverter.ConvertAsync(log1));
await AuditLogRepository.InsertAsync(new AuditLog(GuidGenerator, log2)); await AuditLogRepository.InsertAsync(await AuditLogInfoToAuditLogConverter.ConvertAsync(log2));
var entityChanges = await AuditLogRepository.GetEntityChangeListAsync(); var entityChanges = await AuditLogRepository.GetEntityChangeListAsync();
var entityChange = var entityChange =
@ -641,8 +638,8 @@ namespace Volo.Abp.AuditLogging
} }
}; };
await AuditLogRepository.InsertAsync(new AuditLog(GuidGenerator, log1)); await AuditLogRepository.InsertAsync(await AuditLogInfoToAuditLogConverter.ConvertAsync(log1));
await AuditLogRepository.InsertAsync(new AuditLog(GuidGenerator, log2)); await AuditLogRepository.InsertAsync(await AuditLogInfoToAuditLogConverter.ConvertAsync(log2));
//Assert //Assert
var entityChangesDesc = await AuditLogRepository.GetEntityChangeListAsync(); var entityChangesDesc = await AuditLogRepository.GetEntityChangeListAsync();
@ -754,8 +751,8 @@ namespace Volo.Abp.AuditLogging
} }
}; };
await AuditLogRepository.InsertAsync(new AuditLog(GuidGenerator, log1)); await AuditLogRepository.InsertAsync(await AuditLogInfoToAuditLogConverter.ConvertAsync(log1));
await AuditLogRepository.InsertAsync(new AuditLog(GuidGenerator, log2)); await AuditLogRepository.InsertAsync(await AuditLogInfoToAuditLogConverter.ConvertAsync(log2));
//Assert //Assert
var entityChanges = await AuditLogRepository.GetEntityChangeListAsync(changeType: EntityChangeType.Created); var entityChanges = await AuditLogRepository.GetEntityChangeListAsync(changeType: EntityChangeType.Created);
@ -864,8 +861,8 @@ namespace Volo.Abp.AuditLogging
} }
}; };
await AuditLogRepository.InsertAsync(new AuditLog(GuidGenerator, log1)); await AuditLogRepository.InsertAsync(await AuditLogInfoToAuditLogConverter.ConvertAsync(log1));
await AuditLogRepository.InsertAsync(new AuditLog(GuidGenerator, log2)); await AuditLogRepository.InsertAsync(await AuditLogInfoToAuditLogConverter.ConvertAsync(log2));
//Assert //Assert
var entityHistory = await AuditLogRepository.GetEntityChangesWithUsernameAsync(entityId, entityType); var entityHistory = await AuditLogRepository.GetEntityChangesWithUsernameAsync(entityId, entityType);
@ -976,8 +973,8 @@ namespace Volo.Abp.AuditLogging
} }
}; };
await AuditLogRepository.InsertAsync(new AuditLog(GuidGenerator, log1)); await AuditLogRepository.InsertAsync(await AuditLogInfoToAuditLogConverter.ConvertAsync(log1));
await AuditLogRepository.InsertAsync(new AuditLog(GuidGenerator, log2)); await AuditLogRepository.InsertAsync(await AuditLogInfoToAuditLogConverter.ConvertAsync(log2));
var entityChanges = await AuditLogRepository.GetEntityChangeListAsync(); var entityChanges = await AuditLogRepository.GetEntityChangeListAsync();
var entityHistory = var entityHistory =

7
modules/docs/src/Volo.Docs.Web/Pages/Documents/Shared/Styles/vs.css

@ -22,7 +22,7 @@
border-radius: 5px; border-radius: 5px;
overflow: hidden; } overflow: hidden; }
.docs-page .docs-sidebar .docs-sidebar-wrapper { .docs-page .docs-sidebar .docs-sidebar-wrapper {
width: 270px; width: 300px;
float: right; } float: right; }
.docs-page .docs-sidebar .docs-sidebar-wrapper input.form-control { .docs-page .docs-sidebar .docs-sidebar-wrapper input.form-control {
border: 0; } border: 0; }
@ -70,7 +70,7 @@
margin-right: 12px !important; margin-right: 12px !important;
margin-top: 20px !important; } margin-top: 20px !important; }
.docs-page .docs-sidebar .docs-sidebar-wrapper .docs-tree-list ul { .docs-page .docs-sidebar .docs-sidebar-wrapper .docs-tree-list ul {
font-size: .9em; font-size: 14px;
list-style: none; list-style: none;
padding: 0 1rem; padding: 0 1rem;
margin: 0; } margin: 0; }
@ -555,6 +555,9 @@ div.code-toolbar > .toolbar span {
div.code-toolbar > .toolbar a { div.code-toolbar > .toolbar a {
cursor: copy; } cursor: copy; }
.logo-nav ul {
width: 300px !important; }
@media (max-width: 767px) { @media (max-width: 767px) {
body { body {
font-size: 14px; } font-size: 14px; }

2
modules/docs/src/Volo.Docs.Web/Pages/Documents/Shared/Styles/vs.min.css

File diff suppressed because one or more lines are too long

8
modules/docs/src/Volo.Docs.Web/Pages/Documents/Shared/Styles/vs.scss

@ -33,7 +33,7 @@ body {
} }
.docs-sidebar-wrapper { .docs-sidebar-wrapper {
width: 270px; width: 300px;
float: right; float: right;
input.form-control { input.form-control {
@ -110,7 +110,7 @@ body {
} }
ul { ul {
font-size: .9em; font-size: 14px;
list-style: none; list-style: none;
padding: 0 1rem; padding: 0 1rem;
margin: 0; margin: 0;
@ -943,6 +943,10 @@ div.code-toolbar > .toolbar a {
cursor: copy; cursor: copy;
} }
.logo-nav ul {
width: 300px !important;
}
@media (max-width: 767px) { @media (max-width: 767px) {
body { body {
font-size: 14px; font-size: 14px;

33
modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/AbpSecurityStampValidatorCallback.cs

@ -0,0 +1,33 @@
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
namespace Volo.Abp.Identity.AspNetCore
{
public class AbpSecurityStampValidatorCallback
{
/// <summary>
/// Implements callback for SecurityStampValidator's OnRefreshingPrincipal event.
/// https://github.com/IdentityServer/IdentityServer4/blob/main/src/AspNetIdentity/src/SecurityStampValidatorCallback.cs
/// </summary>
public class SecurityStampValidatorCallback
{
/// <summary>
/// Maintains the claims captured at login time that are not being created by ASP.NET Identity.
/// This is needed to preserve claims such as idp, auth_time, amr.
/// </summary>
/// <param name="context">The context.</param>
/// <returns></returns>
public static Task UpdatePrincipal(SecurityStampRefreshingPrincipalContext context)
{
var newClaimTypes = context.NewPrincipal.Claims.Select(x => x.Type).ToArray();
var currentClaimsToKeep = context.CurrentPrincipal.Claims.Where(x => !newClaimTypes.Contains(x.Type)).ToArray();
var id = context.NewPrincipal.Identities.First();
id.AddClaims(currentClaimsToKeep);
return Task.CompletedTask;
}
}
}
}

2
modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/AbpClaimsService.cs

@ -16,6 +16,8 @@ namespace Volo.Abp.IdentityServer
private static readonly string[] AdditionalOptionalClaimNames = private static readonly string[] AdditionalOptionalClaimNames =
{ {
AbpClaimTypes.TenantId, AbpClaimTypes.TenantId,
AbpClaimTypes.ImpersonatorTenantId,
AbpClaimTypes.ImpersonatorUserId,
AbpClaimTypes.Name, AbpClaimTypes.Name,
AbpClaimTypes.SurName, AbpClaimTypes.SurName,
JwtClaimTypes.PreferredUserName, JwtClaimTypes.PreferredUserName,

2
modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/AbpIdentityServerDomainModule.cs

@ -107,8 +107,6 @@ namespace Volo.Abp.IdentityServer
identityServerBuilder.AddInMemoryApiResources(configuration.GetSection("IdentityServer:ApiResources")); identityServerBuilder.AddInMemoryApiResources(configuration.GetSection("IdentityServer:ApiResources"));
identityServerBuilder.AddInMemoryIdentityResources(configuration.GetSection("IdentityServer:IdentityResources")); identityServerBuilder.AddInMemoryIdentityResources(configuration.GetSection("IdentityServer:IdentityResources"));
} }
identityServerBuilder.AddExtensionGrantValidator<LinkLoginExtensionGrantValidator>();
} }
public override void PostConfigureServices(ServiceConfigurationContext context) public override void PostConfigureServices(ServiceConfigurationContext context)

152
modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/AspNetIdentity/LinkLoginExtensionGrantValidator.cs

@ -1,152 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
using IdentityServer4.Validation;
using Microsoft.Extensions.Localization;
using Microsoft.Extensions.Logging;
using Volo.Abp.Identity;
using Volo.Abp.IdentityServer.Localization;
using Volo.Abp.MultiTenancy;
using Volo.Abp.Security.Claims;
using Volo.Abp.Users;
using IdentityUser = Volo.Abp.Identity.IdentityUser;
namespace Volo.Abp.IdentityServer.AspNetIdentity
{
public class LinkLoginExtensionGrantValidator : IExtensionGrantValidator
{
public const string ExtensionGrantType = "LinkLogin";
public string GrantType => ExtensionGrantType;
protected ITokenValidator TokenValidator { get; }
protected IdentityLinkUserManager IdentityLinkUserManager { get; }
protected ICurrentTenant CurrentTenant { get; }
protected ICurrentUser CurrentUser { get; }
protected ICurrentPrincipalAccessor CurrentPrincipalAccessor { get; }
protected IdentityUserManager UserManager { get; }
protected IdentitySecurityLogManager IdentitySecurityLogManager { get; }
protected ILogger<LinkLoginExtensionGrantValidator> Logger { get; }
protected IStringLocalizer<AbpIdentityServerResource> Localizer { get; }
public LinkLoginExtensionGrantValidator(
ITokenValidator tokenValidator,
IdentityLinkUserManager identityLinkUserManager,
ICurrentTenant currentTenant,
ICurrentUser currentUser,
IdentityUserManager userManager,
ICurrentPrincipalAccessor currentPrincipalAccessor,
IdentitySecurityLogManager identitySecurityLogManager,
ILogger<LinkLoginExtensionGrantValidator> logger,
IStringLocalizer<AbpIdentityServerResource> localizer)
{
TokenValidator = tokenValidator;
IdentityLinkUserManager = identityLinkUserManager;
CurrentTenant = currentTenant;
CurrentUser = currentUser;
UserManager = userManager;
CurrentPrincipalAccessor = currentPrincipalAccessor;
IdentitySecurityLogManager = identitySecurityLogManager;
Logger = logger;
Localizer = localizer;
}
public virtual async Task ValidateAsync(ExtensionGrantValidationContext context)
{
var accessToken = context.Request.Raw["access_token"];
if (accessToken.IsNullOrWhiteSpace())
{
context.Result = new GrantValidationResult
{
IsError = true,
Error = "invalid_access_token"
};
return;
}
var result = await TokenValidator.ValidateAccessTokenAsync(accessToken);
if (result.IsError)
{
context.Result = new GrantValidationResult
{
IsError = true,
Error = result.Error,
ErrorDescription = result.ErrorDescription
};
return;
}
using (CurrentPrincipalAccessor.Change(result.Claims))
{
if (!Guid.TryParse(context.Request.Raw["LinkUserId"], out var linkUserId))
{
context.Result = new GrantValidationResult
{
IsError = true,
Error = "invalid_link_user_id"
};
return;
}
Guid? linkTenantId = null;
if (!context.Request.Raw["LinkTenantId"].IsNullOrWhiteSpace())
{
if (!Guid.TryParse(context.Request.Raw["LinkTenantId"], out var parsedGuid))
{
context.Result = new GrantValidationResult
{
IsError = true,
Error = "invalid_link_tenant_id"
};
return;
}
linkTenantId = parsedGuid;
}
var isLinked = await IdentityLinkUserManager.IsLinkedAsync(
new IdentityLinkUserInfo(CurrentUser.GetId(), CurrentTenant.Id),
new IdentityLinkUserInfo(linkUserId, linkTenantId),
true);
if (isLinked)
{
using (CurrentTenant.Change(linkTenantId))
{
var user = await UserManager.GetByIdAsync(linkUserId);
var sub = await UserManager.GetUserIdAsync(user);
var additionalClaims = new List<Claim>();
await AddCustomClaimsAsync(additionalClaims, user, context);
context.Result = new GrantValidationResult(
sub,
GrantType,
additionalClaims.ToArray()
);
}
}
else
{
context.Result = new GrantValidationResult
{
IsError = true,
Error = Localizer["TheTargetUserIsNotLinkedToYou"]
};
}
}
}
protected virtual Task AddCustomClaimsAsync(List<Claim> customClaims, IdentityUser user, ExtensionGrantValidationContext context)
{
if (user.TenantId.HasValue)
{
customClaims.Add(new Claim(AbpClaimTypes.TenantId, user.TenantId?.ToString()));
}
return Task.CompletedTask;
}
}
}

3
npm/ng-packs/apps/dev-app/src/environments/environment.ts

@ -23,6 +23,9 @@ export const environment = {
url: 'https://localhost:44305', url: 'https://localhost:44305',
rootNamespace: 'MyCompanyName.MyProjectName', rootNamespace: 'MyCompanyName.MyProjectName',
}, },
AbpAccount: {
rootNamespace: 'Volo.Abp',
},
AbpFeatureManagement: { AbpFeatureManagement: {
rootNamespace: 'Volo.Abp', rootNamespace: 'Volo.Abp',
}, },

1
npm/ng-packs/packages/account/src/lib/account-routing.module.ts

@ -69,6 +69,7 @@ const routes: Routes = [
component: ReplaceableRouteContainerComponent, component: ReplaceableRouteContainerComponent,
canActivate: [AuthenticationFlowGuard], canActivate: [AuthenticationFlowGuard],
data: { data: {
tenantBoxVisible: false,
replaceableComponent: { replaceableComponent: {
key: eAccountComponents.ResetPassword, key: eAccountComponents.ResetPassword,
defaultComponent: ResetPasswordComponent, defaultComponent: ResetPasswordComponent,

2
npm/ng-packs/packages/account/src/lib/components/auth-wrapper/auth-wrapper.component.html

@ -1,6 +1,6 @@
<div class="row"> <div class="row">
<div class="mx-auto col col-md-5"> <div class="mx-auto col col-md-5">
<ng-container *ngIf="(isMultiTenancyEnabled$ | async) && multiTenancy.isTenantBoxVisible"> <ng-container *ngIf="(isMultiTenancyEnabled$ | async) && isTenantBoxVisible">
<abp-tenant-box *abpReplaceableTemplate="{ componentKey: tenantBoxKey }"></abp-tenant-box> <abp-tenant-box *abpReplaceableTemplate="{ componentKey: tenantBoxKey }"></abp-tenant-box>
</ng-container> </ng-container>

20
npm/ng-packs/packages/account/src/lib/components/auth-wrapper/auth-wrapper.component.ts

@ -1,8 +1,9 @@
import { ConfigStateService, MultiTenancyService, SubscriptionService } from '@abp/ng.core'; import { ConfigStateService, MultiTenancyService, SubscriptionService } from '@abp/ng.core';
import { Component } from '@angular/core'; import { Component, Injector } from '@angular/core';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { map } from 'rxjs/operators'; import { map } from 'rxjs/operators';
import { eAccountComponents } from '../../enums/components'; import { eAccountComponents } from '../../enums/components';
import { ActivatedRoute } from '@angular/router';
@Component({ @Component({
selector: 'abp-auth-wrapper', selector: 'abp-auth-wrapper',
@ -20,9 +21,24 @@ export class AuthWrapperComponent {
} }
tenantBoxKey = eAccountComponents.TenantBox; tenantBoxKey = eAccountComponents.TenantBox;
route: ActivatedRoute;
private _tenantBoxVisible = true;
private setTenantBoxVisibility = () => {
this._tenantBoxVisible = this.route.snapshot.firstChild.data.tenantBoxVisible ?? true;
};
get isTenantBoxVisible() {
return this._tenantBoxVisible && this.multiTenancy.isTenantBoxVisible;
}
constructor( constructor(
public readonly multiTenancy: MultiTenancyService, public readonly multiTenancy: MultiTenancyService,
private configState: ConfigStateService, private configState: ConfigStateService,
) {} injector: Injector,
) {
this.route = injector.get(ActivatedRoute);
this.setTenantBoxVisibility();
}
} }

5
npm/ng-packs/packages/account/src/lib/components/reset-password/reset-password.component.ts

@ -18,6 +18,7 @@ export class ResetPasswordComponent implements OnInit {
inProgress = false; inProgress = false;
isPasswordReset = false; isPasswordReset = false;
tenantId = '';
mapErrorsFn: Validation.MapErrorsFn = (errors, groupErrors, control) => { mapErrorsFn: Validation.MapErrorsFn = (errors, groupErrors, control) => {
if (PASSWORD_FIELDS.indexOf(String(control.name)) < 0) return errors; if (PASSWORD_FIELDS.indexOf(String(control.name)) < 0) return errors;
@ -34,7 +35,8 @@ export class ResetPasswordComponent implements OnInit {
) {} ) {}
ngOnInit(): void { ngOnInit(): void {
this.route.queryParams.subscribe(({ userId, resetToken }) => { this.route.queryParams.subscribe(({ userId, resetToken, tenantId }) => {
this.tenantId = tenantId;
if (!userId || !resetToken) this.router.navigateByUrl('/account/login'); if (!userId || !resetToken) this.router.navigateByUrl('/account/login');
this.form = this.fb.group( this.form = this.fb.group(
@ -61,6 +63,7 @@ export class ResetPasswordComponent implements OnInit {
userId: this.form.get('userId').value, userId: this.form.get('userId').value,
resetToken: this.form.get('resetToken').value, resetToken: this.form.get('resetToken').value,
password: this.form.get('password').value, password: this.form.get('password').value,
tenantId: this.tenantId || undefined, // if this.tenantId is empty, we should not send it at all
}) })
.pipe(finalize(() => (this.inProgress = false))) .pipe(finalize(() => (this.inProgress = false)))
.subscribe(() => { .subscribe(() => {

2
npm/ng-packs/packages/account/src/lib/proxy/account/index.ts

@ -1,2 +1,4 @@
import * as Web from './web';
export * from './account.service'; export * from './account.service';
export * from './models'; export * from './models';
export { Web };

4
npm/ng-packs/packages/account/src/lib/proxy/account/models.ts

@ -1,5 +1,6 @@
import type { ExtensibleObject } from '@abp/ng.core';
export interface RegisterDto { export interface RegisterDto extends ExtensibleObject {
userName: string; userName: string;
emailAddress: string; emailAddress: string;
password: string; password: string;
@ -8,6 +9,7 @@ export interface RegisterDto {
export interface ResetPasswordDto { export interface ResetPasswordDto {
userId?: string; userId?: string;
tenantId?: string;
resetToken: string; resetToken: string;
password: string; password: string;
} }

35
npm/ng-packs/packages/account/src/lib/proxy/account/web/areas/account/controllers/account.service.ts

@ -0,0 +1,35 @@
import type { AbpLoginResult, UserLoginInfo } from './models/models';
import { RestService } from '@abp/ng.core';
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root',
})
export class AccountService {
apiName = 'AbpAccount';
checkPasswordByLogin = (login: UserLoginInfo) =>
this.restService.request<any, AbpLoginResult>({
method: 'POST',
url: '/api/account/check-password',
body: login,
},
{ apiName: this.apiName });
loginByLogin = (login: UserLoginInfo) =>
this.restService.request<any, AbpLoginResult>({
method: 'POST',
url: '/api/account/login',
body: login,
},
{ apiName: this.apiName });
logout = () =>
this.restService.request<any, void>({
method: 'GET',
url: '/api/account/logout',
},
{ apiName: this.apiName });
constructor(private restService: RestService) {}
}

3
npm/ng-packs/packages/account/src/lib/proxy/account/web/areas/account/controllers/index.ts

@ -0,0 +1,3 @@
import * as Models from './models';
export * from './account.service';
export { Models };

2
npm/ng-packs/packages/account/src/lib/proxy/account/web/areas/account/controllers/models/index.ts

@ -0,0 +1,2 @@
export * from './login-result-type.enum';
export * from './models';

11
npm/ng-packs/packages/account/src/lib/proxy/account/web/areas/account/controllers/models/login-result-type.enum.ts

@ -0,0 +1,11 @@
import { mapEnumToOptions } from '@abp/ng.core';
export enum LoginResultType {
Success = 1,
InvalidUserNameOrPassword = 2,
NotAllowed = 3,
LockedOut = 4,
RequiresTwoFactor = 5,
}
export const loginResultTypeOptions = mapEnumToOptions(LoginResultType);

12
npm/ng-packs/packages/account/src/lib/proxy/account/web/areas/account/controllers/models/models.ts

@ -0,0 +1,12 @@
import type { LoginResultType } from './login-result-type.enum';
export interface AbpLoginResult {
result: LoginResultType;
description?: string;
}
export interface UserLoginInfo {
userNameOrEmailAddress: string;
password: string;
rememberMe: boolean;
}

2
npm/ng-packs/packages/account/src/lib/proxy/account/web/areas/account/index.ts

@ -0,0 +1,2 @@
import * as Controllers from './controllers';
export { Controllers };

2
npm/ng-packs/packages/account/src/lib/proxy/account/web/areas/index.ts

@ -0,0 +1,2 @@
import * as Account from './account';
export { Account };

2
npm/ng-packs/packages/account/src/lib/proxy/account/web/index.ts

@ -0,0 +1,2 @@
import * as Areas from './areas';
export { Areas };

1170
npm/ng-packs/packages/account/src/lib/proxy/generate-proxy.json

File diff suppressed because it is too large

20
npm/ng-packs/packages/core/src/lib/services/list.service.ts

@ -88,12 +88,11 @@ export class ListService<QueryParamsType = ABP.PageQueryParams> implements OnDes
get = () => { get = () => {
this.resetPageWhenUnchanged(); this.resetPageWhenUnchanged();
this._query$.next(({ this.next();
filter: this._filter || undefined, };
maxResultCount: this._maxResultCount,
skipCount: this._page * this._maxResultCount, getWithoutPageReset = () => {
sorting: this._sortOrder ? `${this._sortKey} ${this._sortOrder}` : undefined, this.next();
} as any) as QueryParamsType);
}; };
private delay: MonoTypeOperatorFunction<QueryParamsType>; private delay: MonoTypeOperatorFunction<QueryParamsType>;
@ -130,6 +129,15 @@ export class ListService<QueryParamsType = ABP.PageQueryParams> implements OnDes
this._skipCount = 0; this._skipCount = 0;
} else this._skipCount = skipCount; } else this._skipCount = skipCount;
} }
private next() {
this._query$.next(({
filter: this._filter || undefined,
maxResultCount: this._maxResultCount,
skipCount: this._page * this._maxResultCount,
sorting: this._sortOrder ? `${this._sortKey} ${this._sortOrder}` : undefined,
} as any) as QueryParamsType);
}
} }
export type QueryStreamCreatorCallback<T, QueryParamsType = ABP.PageQueryParams> = ( export type QueryStreamCreatorCallback<T, QueryParamsType = ABP.PageQueryParams> = (

1862
npm/ng-packs/yarn.lock

File diff suppressed because it is too large

13
templates/app/angular/.browserslistrc

@ -2,11 +2,16 @@
# For additional information regarding the format and rule options, please see: # For additional information regarding the format and rule options, please see:
# https://github.com/browserslist/browserslist#queries # https://github.com/browserslist/browserslist#queries
# For the full list of supported browsers by the Angular framework, please see:
# https://angular.io/guide/browser-support
# You can see what browsers were selected by your queries by running: # You can see what browsers were selected by your queries by running:
# npx browserslist # npx browserslist
> 0.5% last 1 Chrome version
last 2 versions last 1 Firefox version
last 2 Edge major versions
last 2 Safari major versions
last 2 iOS major versions
Firefox ESR Firefox ESR
not dead not IE 11 # Angular supports IE 11 only as an opt-in. To opt-in, remove the 'not' prefix on this line.
not IE 9-11 # For IE 9-11 support, remove 'not'.

3
templates/app/angular/.editorconfig

@ -8,6 +8,9 @@ indent_size = 2
insert_final_newline = true insert_final_newline = true
trim_trailing_whitespace = true trim_trailing_whitespace = true
[*.ts]
quote_type = single
[*.md] [*.md]
max_line_length = off max_line_length = off
trim_trailing_whitespace = false trim_trailing_whitespace = false

50
templates/app/angular/.eslintrc.json

@ -0,0 +1,50 @@
{
"root": true,
"ignorePatterns": [
"projects/**/*"
],
"overrides": [
{
"files": [
"*.ts"
],
"parserOptions": {
"project": [
"tsconfig.json"
],
"createDefaultProgram": true
},
"extends": [
"plugin:@angular-eslint/recommended",
"plugin:@angular-eslint/template/process-inline-templates"
],
"rules": {
"@angular-eslint/directive-selector": [
"error",
{
"type": "attribute",
"prefix": "app",
"style": "camelCase"
}
],
"@angular-eslint/component-selector": [
"error",
{
"type": "element",
"prefix": "app",
"style": "kebab-case"
}
]
}
},
{
"files": [
"*.html"
],
"extends": [
"plugin:@angular-eslint/template/recommended"
],
"rules": {}
}
]
}

1
templates/app/angular/.gitignore

@ -12,7 +12,6 @@
# profiling files # profiling files
chrome-profiler-events*.json chrome-profiler-events*.json
speed-measure-plugin*.json
# IDEs and editors # IDEs and editors
/.idea /.idea

4
templates/app/angular/README.md

@ -20,8 +20,8 @@ Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.
## Running end-to-end tests ## Running end-to-end tests
Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/). Run `ng e2e` to execute the end-to-end tests via a platform of your choice.
## Further help ## Further help
To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md). To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page.

87
templates/app/angular/angular.json

@ -1,5 +1,9 @@
{ {
"$schema": "./node_modules/@angular/cli/lib/config/schema.json", "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"cli": {
"analytics": false,
"defaultCollection": "@angular-eslint/schematics"
},
"version": 1, "version": 1,
"newProjectRoot": "projects", "newProjectRoot": "projects",
"projects": { "projects": {
@ -22,7 +26,7 @@
"main": "src/main.ts", "main": "src/main.ts",
"polyfills": "src/polyfills.ts", "polyfills": "src/polyfills.ts",
"tsConfig": "tsconfig.app.json", "tsConfig": "tsconfig.app.json",
"aot": true, "inlineStyleLanguage": "scss",
"allowedCommonJsDependencies": ["chart.js", "js-sha256"], "allowedCommonJsDependencies": ["chart.js", "js-sha256"],
"assets": ["src/favicon.ico", "src/assets"], "assets": ["src/favicon.ico", "src/assets"],
"styles": [ "styles": [
@ -67,44 +71,48 @@
}, },
"configurations": { "configurations": {
"production": { "production": {
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.prod.ts"
}
],
"optimization": true,
"outputHashing": "all",
"sourceMap": false,
"namedChunks": false,
"extractLicenses": true,
"vendorChunk": false,
"buildOptimizer": true,
"budgets": [ "budgets": [
{ {
"type": "initial", "type": "initial",
"maximumWarning": "2mb", "maximumWarning": "2mb",
"maximumError": "5mb" "maximumError": "2.5mb"
}, },
{ {
"type": "anyComponentStyle", "type": "anyComponentStyle",
"maximumWarning": "6kb", "maximumWarning": "2kb",
"maximumError": "10kb" "maximumError": "4kb"
} }
] ],
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.prod.ts"
}
],
"outputHashing": "all"
},
"development": {
"buildOptimizer": false,
"optimization": false,
"vendorChunk": true,
"extractLicenses": false,
"sourceMap": true,
"namedChunks": true
} }
} },
"defaultConfiguration": "production"
}, },
"serve": { "serve": {
"builder": "@angular-devkit/build-angular:dev-server", "builder": "@angular-devkit/build-angular:dev-server",
"options": {
"browserTarget": "MyProjectName:build"
},
"configurations": { "configurations": {
"production": { "production": {
"browserTarget": "MyProjectName:build:production" "browserTarget": "MyProjectName:build:production"
},
"development": {
"browserTarget": "MyProjectName:build:development"
} }
} },
"defaultConfiguration": "development"
}, },
"extract-i18n": { "extract-i18n": {
"builder": "@angular-devkit/build-angular:extract-i18n", "builder": "@angular-devkit/build-angular:extract-i18n",
@ -119,43 +127,20 @@
"polyfills": "src/polyfills.ts", "polyfills": "src/polyfills.ts",
"tsConfig": "tsconfig.spec.json", "tsConfig": "tsconfig.spec.json",
"karmaConfig": "karma.conf.js", "karmaConfig": "karma.conf.js",
"inlineStyleLanguage": "scss",
"assets": ["src/favicon.ico", "src/assets"], "assets": ["src/favicon.ico", "src/assets"],
"styles": [ "styles": ["src/styles.scss"],
"node_modules/bootstrap/dist/css/bootstrap.min.css",
"node_modules/@fortawesome/fontawesome-free/css/all.min.css",
"node_modules/@fortawesome/fontawesome-free/css/v4-shims.min.css",
"node_modules/@swimlane/ngx-datatable/index.css",
"node_modules/@swimlane/ngx-datatable/assets/icons.css",
"node_modules/@swimlane/ngx-datatable/themes/material.css",
"src/styles.scss"
],
"scripts": [] "scripts": []
} }
}, },
"lint": { "lint": {
"builder": "@angular-devkit/build-angular:tslint", "builder": "@angular-eslint/builder:lint",
"options": { "options": {
"tsConfig": ["tsconfig.app.json", "tsconfig.spec.json", "e2e/tsconfig.json"], "lintFilePatterns": ["src/**/*.ts", "src/**/*.html"]
"exclude": ["**/node_modules/**"]
}
},
"e2e": {
"builder": "@angular-devkit/build-angular:protractor",
"options": {
"protractorConfig": "e2e/protractor.conf.js",
"devServerTarget": "MyProjectName:serve"
},
"configurations": {
"production": {
"devServerTarget": "MyProjectName:serve:production"
}
} }
} }
} }
} }
}, },
"defaultProject": "MyProjectName", "defaultProject": "MyProjectName"
"cli": {
"analytics": false
}
} }

32
templates/app/angular/e2e/protractor.conf.js

@ -1,32 +0,0 @@
// @ts-check
// Protractor configuration file, see link for more information
// https://github.com/angular/protractor/blob/master/lib/config.ts
const { SpecReporter } = require('jasmine-spec-reporter');
/**
* @type { import("protractor").Config }
*/
exports.config = {
allScriptsTimeout: 11000,
specs: [
'./src/**/*.e2e-spec.ts'
],
capabilities: {
browserName: 'chrome'
},
directConnect: true,
baseUrl: 'http://localhost:4200/',
framework: 'jasmine',
jasmineNodeOpts: {
showColors: true,
defaultTimeoutInterval: 30000,
print: function() {}
},
onPrepare() {
require('ts-node').register({
project: require('path').join(__dirname, './tsconfig.json')
});
jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } }));
}
};

23
templates/app/angular/e2e/src/app.e2e-spec.ts

@ -1,23 +0,0 @@
import { AppPage } from './app.po';
import { browser, logging } from 'protractor';
describe('workspace-project App', () => {
let page: AppPage;
beforeEach(() => {
page = new AppPage();
});
it('should display welcome message', () => {
page.navigateTo();
expect(page.getTitleText()).toEqual('ng9-abp app is running!');
});
afterEach(async () => {
// Assert that there are no errors emitted from the browser
const logs = await browser.manage().logs().get(logging.Type.BROWSER);
expect(logs).not.toContain(jasmine.objectContaining({
level: logging.Level.SEVERE,
} as logging.Entry));
});
});

11
templates/app/angular/e2e/src/app.po.ts

@ -1,11 +0,0 @@
import { browser, by, element } from 'protractor';
export class AppPage {
navigateTo(): Promise<unknown> {
return browser.get(browser.baseUrl) as Promise<unknown>;
}
getTitleText(): Promise<string> {
return element(by.css('app-root .content span')).getText() as Promise<string>;
}
}

13
templates/app/angular/e2e/tsconfig.json

@ -1,13 +0,0 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
"outDir": "../out-tsc/e2e",
"module": "commonjs",
"target": "es2018",
"types": [
"jasmine",
"jasminewd2",
"node"
]
}
}

22
templates/app/angular/karma.conf.js

@ -9,16 +9,28 @@ module.exports = function (config) {
require('karma-jasmine'), require('karma-jasmine'),
require('karma-chrome-launcher'), require('karma-chrome-launcher'),
require('karma-jasmine-html-reporter'), require('karma-jasmine-html-reporter'),
require('karma-coverage-istanbul-reporter'), require('karma-coverage'),
require('@angular-devkit/build-angular/plugins/karma') require('@angular-devkit/build-angular/plugins/karma')
], ],
client: { client: {
jasmine: {
// you can add configuration options for Jasmine here
// the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html
// for example, you can disable the random execution with `random: false`
// or set a specific seed with `seed: 4321`
},
clearContext: false // leave Jasmine Spec Runner output visible in browser clearContext: false // leave Jasmine Spec Runner output visible in browser
}, },
coverageIstanbulReporter: { jasmineHtmlReporter: {
dir: require('path').join(__dirname, './coverage/ng9-abp'), suppressAll: true // removes the duplicated traces
reports: ['html', 'lcovonly', 'text-summary'], },
fixWebpackSourcePaths: true coverageReporter: {
dir: require('path').join(__dirname, './coverage/MyProjectName'),
subdir: '.',
reporters: [
{ type: 'html' },
{ type: 'text-summary' }
]
}, },
reporters: ['progress', 'kjhtml'], reporters: ['progress', 'kjhtml'],
port: 9876, port: 9876,

62
templates/app/angular/package.json

@ -5,10 +5,10 @@
"ng": "ng", "ng": "ng",
"start": "ng serve --open", "start": "ng serve --open",
"build": "ng build", "build": "ng build",
"build:prod": "ng build --prod", "build:prod": "ng build --configuration production",
"watch": "ng build --watch --configuration development",
"test": "ng test", "test": "ng test",
"lint": "ng lint", "lint": "ng lint"
"e2e": "ng e2e"
}, },
"private": true, "private": true,
"dependencies": { "dependencies": {
@ -20,39 +20,41 @@
"@abp/ng.tenant-management": "~4.3.2", "@abp/ng.tenant-management": "~4.3.2",
"@abp/ng.theme.basic": "~4.3.2", "@abp/ng.theme.basic": "~4.3.2",
"@abp/ng.theme.shared": "~4.3.2", "@abp/ng.theme.shared": "~4.3.2",
"@angular/animations": "~11.1.0", "@angular/animations": "~12.0.0",
"@angular/common": "~11.1.0", "@angular/common": "~12.0.0",
"@angular/compiler": "~11.1.0", "@angular/compiler": "~12.0.0",
"@angular/core": "~11.1.0", "@angular/core": "~12.0.0",
"@angular/forms": "~11.1.0", "@angular/forms": "~12.0.0",
"@angular/platform-browser": "~11.1.0", "@angular/platform-browser-dynamic": "~12.0.0",
"@angular/platform-browser-dynamic": "~11.1.0", "@angular/platform-browser": "~12.0.0",
"@angular/router": "~11.1.0", "@angular/router": "~12.0.0",
"rxjs": "~6.6.0", "rxjs": "~6.6.0",
"tslib": "^2.0.0", "tslib": "^2.1.0",
"zone.js": "~0.10.2" "zone.js": "~0.11.4"
}, },
"devDependencies": { "devDependencies": {
"@abp/ng.schematics": "~4.3.2", "@abp/ng.schematics": "~4.3.2",
"@angular-devkit/build-angular": "~0.1101.0", "@angular-devkit/build-angular": "~12.0.0",
"@angular/cli": "~11.1.0", "@angular-eslint/builder": "12.1.0",
"@angular/compiler-cli": "~11.1.0", "@angular-eslint/eslint-plugin-template": "12.1.0",
"@angular/language-service": "~11.1.0", "@angular-eslint/eslint-plugin": "12.1.0",
"@types/jasmine": "~3.5.0", "@angular-eslint/schematics": "12.1.0",
"@types/jasminewd2": "~2.0.3", "@angular-eslint/template-parser": "12.1.0",
"@angular/cli": "~12.0.0",
"@angular/compiler-cli": "~12.0.0",
"@angular/language-service": "~12.0.4",
"@types/jasmine": "~3.6.0",
"@types/node": "^12.11.1", "@types/node": "^12.11.1",
"codelyzer": "^6.0.1", "@typescript-eslint/eslint-plugin": "4.23.0",
"jasmine-core": "~3.6.0", "@typescript-eslint/parser": "4.23.0",
"jasmine-spec-reporter": "~5.0.0", "eslint": "^7.26.0",
"karma": "~5.2.3", "jasmine-core": "~3.7.0",
"karma-chrome-launcher": "~3.1.0", "karma-chrome-launcher": "~3.1.0",
"karma-coverage-istanbul-reporter": "~3.0.2", "karma-coverage": "~2.0.3",
"karma-jasmine": "~4.0.0",
"karma-jasmine-html-reporter": "^1.5.0", "karma-jasmine-html-reporter": "^1.5.0",
"ng-packagr": "^11.0.1", "karma-jasmine": "~4.0.0",
"protractor": "~7.0.0", "karma": "~6.3.0",
"ts-node": "~8.3.0", "ng-packagr": "^12.0.5",
"tslint": "~6.1.0", "typescript": "~4.2.3"
"typescript": "~4.1.3"
} }
} }

12
templates/app/angular/src/polyfills.ts

@ -17,21 +17,19 @@ import '@angular/localize/init';
* *
* Learn more in https://angular.io/guide/browser-support * Learn more in https://angular.io/guide/browser-support
*/ */
/*************************************************************************************************** /***************************************************************************************************
* BROWSER POLYFILLS * BROWSER POLYFILLS
*/ */
/**
/** IE10 and IE11 requires the following for NgClass support on SVG elements */ * IE11 requires the following for NgClass support on SVG elements
*/
// import 'classlist.js'; // Run `npm install --save classlist.js`. // import 'classlist.js'; // Run `npm install --save classlist.js`.
/** /**
* Web Animations `@angular/platform-browser/animations` * Web Animations `@angular/platform-browser/animations`
* Only required if AnimationBuilder is used within the application and using IE/Edge or Safari. * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari.
* Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0). * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0).
*/ */
// import 'web-animations-js'; // Run `npm install --save web-animations-js`. // import 'web-animations-js'; // Run `npm install --save web-animations-js`.
/** /**
* By default, zone.js will patch all possible macroTask and DomEvents * By default, zone.js will patch all possible macroTask and DomEvents
* user can disable parts of macroTask/DomEvents patch by setting following flags * user can disable parts of macroTask/DomEvents patch by setting following flags
@ -55,12 +53,10 @@ import '@angular/localize/init';
* (window as any).__Zone_enable_cross_context_check = true; * (window as any).__Zone_enable_cross_context_check = true;
* *
*/ */
/*************************************************************************************************** /***************************************************************************************************
* Zone JS is required by default for Angular itself. * Zone JS is required by default for Angular itself.
*/ */
import 'zone.js/dist/zone'; // Included with Angular CLI. import 'zone.js'; // Included with Angular CLI.
/*************************************************************************************************** /***************************************************************************************************
* APPLICATION IMPORTS * APPLICATION IMPORTS

10
templates/app/angular/src/test.ts

@ -1,14 +1,18 @@
// This file is required by karma.conf.js and loads recursively all the .spec and framework files // This file is required by karma.conf.js and loads recursively all the .spec and framework files
import 'zone.js/dist/zone-testing';
import { getTestBed } from '@angular/core/testing'; import { getTestBed } from '@angular/core/testing';
import { import {
BrowserDynamicTestingModule, BrowserDynamicTestingModule,
platformBrowserDynamicTesting platformBrowserDynamicTesting,
} from '@angular/platform-browser-dynamic/testing'; } from '@angular/platform-browser-dynamic/testing';
import 'zone.js/testing';
declare const require: { declare const require: {
context(path: string, deep?: boolean, filter?: RegExp): { context(
path: string,
deep?: boolean,
filter?: RegExp
): {
keys(): string[]; keys(): string[];
<T>(id: string): T; <T>(id: string): T;
}; };

10
templates/app/angular/tsconfig.app.json

@ -1,9 +1,15 @@
/* To learn more about this file see: https://angular.io/config/tsconfig. */
{ {
"extends": "./tsconfig.json", "extends": "./tsconfig.json",
"compilerOptions": { "compilerOptions": {
"outDir": "./out-tsc/app", "outDir": "./out-tsc/app",
"types": [] "types": []
}, },
"files": ["src/main.ts", "src/polyfills.ts"], "files": [
"include": ["src/**/*.d.ts"] "src/main.ts",
"src/polyfills.ts"
],
"include": [
"src/**/*.d.ts"
]
} }

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save