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.
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)
@ -150,7 +150,7 @@ Now, you can run the application to see the result:
![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
@ -445,7 +445,7 @@ These names are defined in the `StandardLayouts` class as constants. You can def
#### 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

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
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

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 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
@ -410,7 +410,7 @@ See the [Page Header](Page-Header.md) document for more.
#### 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

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

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

@ -63,7 +63,7 @@ A theme is simply a Razor Class Library.
### 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

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

@ -133,7 +133,7 @@ public class MyLoginModel : LoginModel
[基本主题](../../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)
@ -149,7 +149,7 @@ public class MyLoginModel : LoginModel
![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

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? ImpersonatorUserId { get; set; }
public Guid? ImpersonatorTenantId { get; set; }
public string UserName { 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,
Id = _currentUser.Id,
TenantId = _currentUser.TenantId,
ImpersonatorUserId = _currentUser.FindImpersonatorUserId(),
ImpersonatorTenantId = _currentUser.FindImpersonatorTenantId(),
UserName = _currentUser.UserName,
SurName = _currentUser.SurName,
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.Extensions.Options;
using Volo.Abp.Authorization;
using Volo.Abp.Data;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Domain.Entities;
using Volo.Abp.ExceptionHandling;
@ -55,6 +56,11 @@ namespace Volo.Abp.AspNetCore.ExceptionHandling
{
return HttpStatusCode.NotFound;
}
if (exception is AbpDbConcurrencyException)
{
return HttpStatusCode.Conflict;
}
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.Options;
using Volo.Abp.Auditing;
using Volo.Abp.Data;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Domain.Entities;
using Volo.Abp.Json;
@ -26,8 +27,7 @@ namespace Volo.Abp.EntityFrameworkCore.EntityHistory
protected IJsonSerializer JsonSerializer { get; }
protected AbpAuditingOptions Options { get; }
protected IAuditingHelper AuditingHelper { get; }
private readonly IClock _clock;
protected IClock Clock{ get; }
public EntityHistoryHelper(
IAuditingStore auditingStore,
@ -36,7 +36,7 @@ namespace Volo.Abp.EntityFrameworkCore.EntityHistory
IJsonSerializer jsonSerializer,
IAuditingHelper auditingHelper)
{
_clock = clock;
Clock = clock;
AuditingStore = auditingStore;
JsonSerializer = jsonSerializer;
AuditingHelper = auditingHelper;
@ -69,7 +69,7 @@ namespace Volo.Abp.EntityFrameworkCore.EntityHistory
}
[CanBeNull]
private EntityChangeInfo CreateEntityChangeOrNull(EntityEntry entityEntry)
protected virtual EntityChangeInfo CreateEntityChangeOrNull(EntityEntry entityEntry)
{
var entity = entityEntry.Entity;
@ -121,23 +121,23 @@ namespace Volo.Abp.EntityFrameworkCore.EntityHistory
return multiTenantEntity.TenantId;
}
private DateTime GetChangeTime(EntityChangeInfo entityChange)
protected virtual DateTime GetChangeTime(EntityChangeInfo entityChange)
{
var entity = entityChange.EntityEntry.As<EntityEntry>().Entity;
switch (entityChange.ChangeType)
{
case EntityChangeType.Created:
return (entity as IHasCreationTime)?.CreationTime ?? _clock.Now;
return (entity as IHasCreationTime)?.CreationTime ?? Clock.Now;
case EntityChangeType.Deleted:
return (entity as IHasDeletionTime)?.DeletionTime ?? _clock.Now;
return (entity as IHasDeletionTime)?.DeletionTime ?? Clock.Now;
case EntityChangeType.Updated:
return (entity as IHasModificationTime)?.LastModificationTime ?? _clock.Now;
return (entity as IHasModificationTime)?.LastModificationTime ?? Clock.Now;
default:
throw new AbpException($"Unknown {nameof(EntityChangeInfo)}: {entityChange}");
}
}
private string GetEntityId(object entityAsObj)
protected virtual string GetEntityId(object entityAsObj)
{
if (!(entityAsObj is IEntity entity))
{
@ -156,7 +156,7 @@ namespace Volo.Abp.EntityFrameworkCore.EntityHistory
/// <summary>
/// Gets the property changes for this entry.
/// </summary>
private List<EntityPropertyChangeInfo> GetPropertyChanges(EntityEntry entityEntry)
protected virtual List<EntityPropertyChangeInfo> GetPropertyChanges(EntityEntry entityEntry)
{
var propertyChanges = new List<EntityPropertyChangeInfo>();
var properties = entityEntry.Metadata.GetProperties();
@ -181,12 +181,12 @@ namespace Volo.Abp.EntityFrameworkCore.EntityHistory
return propertyChanges;
}
private bool IsCreated(EntityEntry entityEntry)
protected virtual bool IsCreated(EntityEntry entityEntry)
{
return entityEntry.State == EntityState.Added;
}
private bool IsDeleted(EntityEntry entityEntry)
protected virtual bool IsDeleted(EntityEntry entityEntry)
{
if (entityEntry.State == EntityState.Deleted)
{
@ -197,7 +197,7 @@ namespace Volo.Abp.EntityFrameworkCore.EntityHistory
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 ||
entityEntry.State == EntityState.Unchanged)
@ -220,7 +220,7 @@ namespace Volo.Abp.EntityFrameworkCore.EntityHistory
return defaultValue;
}
private bool ShouldSavePropertyHistory(PropertyEntry propertyEntry, bool defaultValue)
protected virtual bool ShouldSavePropertyHistory(PropertyEntry propertyEntry, bool defaultValue)
{
if (propertyEntry.Metadata.Name == "Id")
{
@ -247,6 +247,26 @@ namespace Volo.Abp.EntityFrameworkCore.EntityHistory
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);
if (isModified)
{
@ -256,7 +276,7 @@ namespace Volo.Abp.EntityFrameworkCore.EntityHistory
return defaultValue;
}
private bool IsBaseAuditProperty(PropertyInfo propertyInfo, Type entityType)
protected virtual bool IsBaseAuditProperty(PropertyInfo propertyInfo, Type entityType)
{
if (entityType.IsAssignableTo<IHasCreationTime>()
&& propertyInfo.Name == nameof(IHasCreationTime.CreationTime))
@ -308,11 +328,11 @@ namespace Volo.Abp.EntityFrameworkCore.EntityHistory
return false;
}
/// <summary>
/// Updates change time, entity id and foreign keys after SaveChanges is called.
/// </summary>
public void UpdateChangeList(List<EntityChangeInfo> entityChanges)
public virtual void UpdateChangeList(List<EntityChangeInfo> 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 Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Volo.Abp.Data;
using Volo.Abp.DependencyInjection;
using Volo.Abp.EventBus.Distributed;
using Volo.Abp.Kafka;
@ -18,6 +19,7 @@ namespace Volo.Abp.EventBus.Kafka
[ExposeServices(typeof(IDistributedEventBus), typeof(KafkaDistributedEventBus))]
public class KafkaDistributedEventBus : EventBusBase, IDistributedEventBus, ISingletonDependency
{
protected AbpEventBusOptions AbpEventBusOptions { get; }
protected AbpKafkaEventBusOptions AbpKafkaEventBusOptions { get; }
protected AbpDistributedEventBusOptions AbpDistributedEventBusOptions { get; }
protected IKafkaMessageConsumerFactory MessageConsumerFactory { get; }
@ -26,6 +28,7 @@ namespace Volo.Abp.EventBus.Kafka
protected ConcurrentDictionary<Type, List<IEventHandlerFactory>> HandlerFactories { get; }
protected ConcurrentDictionary<string, Type> EventTypes { get; }
protected IKafkaMessageConsumer Consumer { get; private set; }
protected string DeadLetterTopicName { get; }
public KafkaDistributedEventBus(
IServiceScopeFactory serviceScopeFactory,
@ -34,14 +37,19 @@ namespace Volo.Abp.EventBus.Kafka
IKafkaMessageConsumerFactory messageConsumerFactory,
IOptions<AbpDistributedEventBusOptions> abpDistributedEventBusOptions,
IKafkaSerializer serializer,
IProducerPool producerPool)
: base(serviceScopeFactory, currentTenant)
IProducerPool producerPool,
IEventErrorHandler errorHandler,
IOptions<AbpEventBusOptions> abpEventBusOptions)
: base(serviceScopeFactory, currentTenant, errorHandler)
{
AbpKafkaEventBusOptions = abpKafkaEventBusOptions.Value;
AbpDistributedEventBusOptions = abpDistributedEventBusOptions.Value;
AbpEventBusOptions = abpEventBusOptions.Value;
MessageConsumerFactory = messageConsumerFactory;
Serializer = serializer;
ProducerPool = producerPool;
DeadLetterTopicName =
AbpEventBusOptions.DeadLetterName ?? AbpKafkaEventBusOptions.TopicName + "_dead_letter";
HandlerFactories = new ConcurrentDictionary<Type, List<IEventHandlerFactory>>();
EventTypes = new ConcurrentDictionary<string, Type>();
@ -51,9 +59,9 @@ namespace Volo.Abp.EventBus.Kafka
{
Consumer = MessageConsumerFactory.Create(
AbpKafkaEventBusOptions.TopicName,
DeadLetterTopicName,
AbpKafkaEventBusOptions.GroupId,
AbpKafkaEventBusOptions.ConnectionName);
Consumer.OnMessageReceived(ProcessEventAsync);
SubscribeHandlers(AbpDistributedEventBusOptions.Handlers);
@ -70,7 +78,18 @@ namespace Volo.Abp.EventBus.Kafka
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
@ -147,20 +166,51 @@ namespace Volo.Abp.EventBus.Kafka
}
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 body = Serializer.Serialize(eventData);
var producer = ProducerPool.Get(AbpKafkaEventBusOptions.ConnectionName);
SetEventMessageHeaders(headers, headersArguments);
await producer.ProduceAsync(
AbpKafkaEventBusOptions.TopicName,
topicName,
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)
{
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 RabbitMQ.Client;
using RabbitMQ.Client.Events;
using Volo.Abp.Data;
using Volo.Abp.DependencyInjection;
using Volo.Abp.EventBus.Distributed;
using Volo.Abp.MultiTenancy;
@ -25,6 +26,7 @@ namespace Volo.Abp.EventBus.RabbitMq
{
protected AbpRabbitMqEventBusOptions AbpRabbitMqEventBusOptions { get; }
protected AbpDistributedEventBusOptions AbpDistributedEventBusOptions { get; }
protected AbpEventBusOptions AbpEventBusOptions { get; }
protected IConnectionPool ConnectionPool { get; }
protected IRabbitMqSerializer Serializer { get; }
@ -41,12 +43,15 @@ namespace Volo.Abp.EventBus.RabbitMq
IServiceScopeFactory serviceScopeFactory,
IOptions<AbpDistributedEventBusOptions> distributedEventBusOptions,
IRabbitMqMessageConsumerFactory messageConsumerFactory,
ICurrentTenant currentTenant)
: base(serviceScopeFactory, currentTenant)
ICurrentTenant currentTenant,
IEventErrorHandler errorHandler,
IOptions<AbpEventBusOptions> abpEventBusOptions)
: base(serviceScopeFactory, currentTenant, errorHandler)
{
ConnectionPool = connectionPool;
Serializer = serializer;
MessageConsumerFactory = messageConsumerFactory;
AbpEventBusOptions = abpEventBusOptions.Value;
AbpDistributedEventBusOptions = distributedEventBusOptions.Value;
AbpRabbitMqEventBusOptions = options.Value;
@ -56,17 +61,21 @@ namespace Volo.Abp.EventBus.RabbitMq
public void Initialize()
{
const string suffix = "_dead_letter";
Consumer = MessageConsumerFactory.Create(
new ExchangeDeclareConfiguration(
AbpRabbitMqEventBusOptions.ExchangeName,
type: "direct",
durable: true
durable: true,
deadLetterExchangeName: AbpRabbitMqEventBusOptions.ExchangeName + suffix
),
new QueueDeclareConfiguration(
AbpRabbitMqEventBusOptions.ClientName,
durable: true,
exclusive: false,
autoDelete: false
autoDelete: false,
AbpEventBusOptions.DeadLetterName ?? AbpRabbitMqEventBusOptions.ClientName + suffix
),
AbpRabbitMqEventBusOptions.ConnectionName
);
@ -87,7 +96,19 @@ namespace Volo.Abp.EventBus.RabbitMq
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
@ -168,8 +189,14 @@ namespace Volo.Abp.EventBus.RabbitMq
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 body = Serializer.Serialize(eventData);
@ -181,11 +208,17 @@ namespace Volo.Abp.EventBus.RabbitMq
durable: true
);
var properties = channel.CreateBasicProperties();
properties.DeliveryMode = RabbitMqConsts.DeliveryModes.Persistent;
if (properties == null)
{
properties = channel.CreateBasicProperties();
properties.DeliveryMode = RabbitMqConsts.DeliveryModes.Persistent;
properties.MessageId = Guid.NewGuid().ToString("N");
}
SetEventMessageHeaders(properties, headersArguments);
channel.BasicPublish(
exchange: AbpRabbitMqEventBusOptions.ExchangeName,
exchange: AbpRabbitMqEventBusOptions.ExchangeName,
routingKey: eventName,
mandatory: true,
basicProperties: properties,
@ -196,6 +229,21 @@ namespace Volo.Abp.EventBus.RabbitMq
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)
{
return HandlerFactories.GetOrAdd(
@ -213,9 +261,11 @@ namespace Volo.Abp.EventBus.RabbitMq
{
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();

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 Rebus.Handlers;
using Rebus.Retry.Simple;
using Rebus.ServiceProvider;
using Volo.Abp.Modularity;
@ -11,23 +12,29 @@ namespace Volo.Abp.EventBus.Rebus
{
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<>));
Configure<AbpRebusEventBusOptions>(rebusOptions =>
{
rebusOptions.Configurer = options.Configurer;
rebusOptions.Publish = options.Publish;
rebusOptions.InputQueueName = options.InputQueueName;
context.Services.ExecutePreConfiguredActions(rebusOptions);
});
context.Services.AddRebus(configurer =>
context.Services.AddRebus(configure =>
{
options.Configurer?.Invoke(configurer);
return configurer;
});
if (abpEventBusOptions.RetryStrategyOptions != null)
{
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)

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

@ -32,7 +32,7 @@ namespace Volo.Abp.EventBus.Rebus
public AbpRebusEventBusOptions()
{
_publish = DefaultPublish;
_configurer = DefaultConfigurer;
_configurer = DefaultConfigure;
}
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);
}
private void DefaultConfigurer(RebusConfigurer configurer)
private void DefaultConfigure(RebusConfigurer configure)
{
configurer.Subscriptions(s => s.StoreInMemory());
configurer.Transport(t => t.UseInMemoryTransport(new InMemNetwork(), InputQueueName));
configure.Subscriptions(s => s.StoreInMemory());
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,
IBus rebus,
IOptions<AbpDistributedEventBusOptions> abpDistributedEventBusOptions,
IOptions<AbpRebusEventBusOptions> abpEventBusRebusOptions) :
base(serviceScopeFactory, currentTenant)
IOptions<AbpRebusEventBusOptions> abpEventBusRebusOptions,
IEventErrorHandler errorHandler) :
base(serviceScopeFactory, currentTenant, errorHandler)
{
Rebus = rebus;
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>
<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" />
</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.Distributed;
using Volo.Abp.EventBus.Local;
using Volo.Abp.Json;
using Volo.Abp.Modularity;
using Volo.Abp.MultiTenancy;
using Volo.Abp.Reflection;
@ -12,7 +13,8 @@ namespace Volo.Abp.EventBus
{
[DependsOn(
typeof(AbpEventBusAbstractionsModule),
typeof(AbpMultiTenancyModule))]
typeof(AbpMultiTenancyModule),
typeof(AbpJsonModule))]
public class AbpEventBusModule : AbpModule
{
public override void PreConfigureServices(ServiceConfigurationContext context)
@ -20,6 +22,14 @@ namespace Volo.Abp.EventBus
AddEventHandlers(context.Services);
}
public override void ConfigureServices(ServiceConfigurationContext context)
{
Configure<AbpEventBusOptions>(options =>
{
context.Services.ExecutePreConfiguredActions(options);
});
}
private static void AddEventHandlers(IServiceCollection services)
{
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 EventBusBase(IServiceScopeFactory serviceScopeFactory, ICurrentTenant currentTenant)
protected IEventErrorHandler ErrorHandler { get; }
protected EventBusBase(
IServiceScopeFactory serviceScopeFactory,
ICurrentTenant currentTenant,
IEventErrorHandler errorHandler)
{
ServiceScopeFactory = serviceScopeFactory;
CurrentTenant = currentTenant;
ErrorHandler = errorHandler;
}
/// <inheritdoc/>
@ -89,7 +95,7 @@ namespace Volo.Abp.EventBus
/// <inheritdoc/>
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>();
@ -97,16 +103,13 @@ namespace Volo.Abp.EventBus
if (exceptions.Any())
{
if (exceptions.Count == 1)
{
exceptions[0].ReThrow();
}
throw new AggregateException("More than one error has occurred while triggering the event: " + eventType, exceptions);
var context = new EventExecutionErrorContext(exceptions, eventType, this);
onErrorAction?.Invoke(context);
await ErrorHandler.HandleAsync(context);
}
}
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();

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.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.Data;
using Volo.Abp.DependencyInjection;
using Volo.Abp.MultiTenancy;
using Volo.Abp.Threading;
using Volo.Abp.Json;
namespace Volo.Abp.EventBus.Local
{
@ -28,12 +30,17 @@ namespace Volo.Abp.EventBus.Local
protected ConcurrentDictionary<Type, List<IEventHandlerFactory>> HandlerFactories { get; }
protected IJsonSerializer Serializer { get; }
public LocalEventBus(
IOptions<AbpLocalEventBusOptions> options,
IServiceScopeFactory serviceScopeFactory,
ICurrentTenant currentTenant)
: base(serviceScopeFactory, currentTenant)
ICurrentTenant currentTenant,
IEventErrorHandler errorHandler,
IJsonSerializer serializer)
: base(serviceScopeFactory, currentTenant, errorHandler)
{
Serializer = serializer;
Options = options.Value;
Logger = NullLogger<LocalEventBus>.Instance;
@ -119,19 +126,17 @@ namespace Volo.Abp.EventBus.Local
public override async Task PublishAsync(Type eventType, object eventData)
{
var exceptions = new List<Exception>();
await TriggerHandlersAsync(eventType, eventData, exceptions);
await PublishAsync(new LocalEventMessage(Guid.NewGuid(), eventData, eventType));
}
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)
{
exceptions[0].ReThrow();
}
throw new AggregateException("More than one error has occurred while triggering the event: " + eventType, exceptions);
}
errorContext.EventData = Serializer.Deserialize(localEventMessage.EventType, rawEventData);
errorContext.SetProperty(nameof(LocalEventMessage.MessageId), localEventMessage.MessageId);
});
}
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.Options;
using Volo.Abp.Authorization;
using Volo.Abp.Data;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Domain.Entities;
using Volo.Abp.ExceptionHandling;
@ -58,14 +59,19 @@ namespace Volo.Abp.AspNetCore.ExceptionHandling
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();
@ -169,7 +175,7 @@ namespace Volo.Abp.AspNetCore.ExceptionHandling
return new RemoteServiceErrorInfo(exception.Message);
}
protected virtual Exception TryToGetActualException(Exception exception)
{
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!",
"DefaultErrorMessage404Detail": "The resource requested could not found on the server!",
"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",
"UnhandledException": "Unhandled exception!",
"401Message": "Unauthorized",

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

@ -13,6 +13,7 @@
"DefaultErrorMessage404": "Kaynak bulunamadı!",
"DefaultErrorMessage404Detail": "İstenilen kaynak sunucuda 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",
"UnhandledException": "Yakalanmamış hata!",
"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 bool ReQueue { get; set; } = true;
public AbpKafkaOptions()
{
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.
/// </summary>
/// <param name="topicName"></param>
/// <param name="deadLetterTopicName"></param>
/// <param name="groupId"></param>
/// <param name="connectionName"></param>
/// <returns></returns>
IKafkaMessageConsumer Create(
string topicName,
string deadLetterTopicName,
string groupId,
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);
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.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Confluent.Kafka;
@ -26,6 +27,8 @@ namespace Volo.Abp.Kafka
protected AbpKafkaOptions Options { get; }
protected AbpAsyncTimer Timer { get; }
protected ConcurrentBag<Func<Message<string, byte[]>, Task>> Callbacks { get; }
protected IConsumer<string, byte[]> Consumer { get; private set; }
@ -36,34 +39,43 @@ namespace Volo.Abp.Kafka
protected string TopicName { get; private set; }
protected string DeadLetterTopicName { get; private set; }
public KafkaMessageConsumer(
IConsumerPool consumerPool,
IExceptionNotifier exceptionNotifier,
IOptions<AbpKafkaOptions> options,
IProducerPool producerPool)
IProducerPool producerPool,
AbpAsyncTimer timer)
{
ConsumerPool = consumerPool;
ExceptionNotifier = exceptionNotifier;
ProducerPool = producerPool;
Timer = timer;
Options = options.Value;
Logger = NullLogger<KafkaMessageConsumer>.Instance;
Callbacks = new ConcurrentBag<Func<Message<string, byte[]>, Task>>();
Timer.Period = 5000; //5 sec.
Timer.Elapsed = Timer_Elapsed;
Timer.RunOnStart = true;
}
public virtual void Initialize(
[NotNull] string topicName,
[NotNull] string deadLetterTopicName,
[NotNull] string groupId,
string connectionName = null)
{
Check.NotNull(topicName, nameof(topicName));
Check.NotNull(deadLetterTopicName, nameof(deadLetterTopicName));
Check.NotNull(groupId, nameof(groupId));
TopicName = topicName;
DeadLetterTopicName = deadLetterTopicName;
ConnectionName = connectionName ?? KafkaConnections.DefaultConnectionName;
GroupId = groupId;
AsyncHelper.RunSync(CreateTopicAsync);
Consume();
Timer.Start();
}
public virtual void OnMessageReceived(Func<Message<string, byte[]>, Task> callback)
@ -71,26 +83,45 @@ namespace Volo.Abp.Kafka
Callbacks.Add(callback);
}
protected virtual async Task Timer_Elapsed(AbpAsyncTimer timer)
{
await CreateTopicAsync();
Consume();
Timer.Stop();
}
protected virtual async Task CreateTopicAsync()
{
using (var adminClient = new AdminClientBuilder(Options.Connections.GetOrDefault(ConnectionName)).Build())
{
var topic = new TopicSpecification
var topics = new List<TopicSpecification>
{
Name = TopicName,
NumPartitions = 1,
ReplicationFactor = 1
new()
{
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
{
await adminClient.CreateTopicsAsync(new[] {topic});
await adminClient.CreateTopicsAsync(topics);
}
catch (CreateTopicsException e)
{
if(e.Results.First().Error.Code != ErrorCode.TopicAlreadyExists)
if(e.Results.Any(x => x.Error.Code != ErrorCode.TopicAlreadyExists))
{
throw;
}
@ -139,8 +170,6 @@ namespace Volo.Abp.Kafka
}
catch (Exception ex)
{
await RequeueAsync(consumeResult);
Logger.LogException(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()
{
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(
string topicName,
string deadLetterTopicName,
string groupId,
string connectionName = null)
{
var consumer = ServiceScope.ServiceProvider.GetRequiredService<KafkaMessageConsumer>();
consumer.Initialize(topicName, groupId, connectionName);
consumer.Initialize(topicName, deadLetterTopicName, groupId, connectionName);
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));
}
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;
//Requesting default connection string...
if (connectionStringName == null ||
connectionStringName == ConnectionStrings.DefaultConnectionStringName)
@ -59,10 +59,10 @@ namespace Volo.Abp.MultiTenancy
//Found for the tenant
return connString;
}
//Fallback to the mapped database for the specific connection string
var database = Options.Databases.GetMappedDatabaseOrNull(connectionStringName);
if (database != null)
if (database != null && database.IsUsedByTenants)
{
connString = tenant.ConnectionStrings.GetOrDefault(database.DatabaseName);
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 DeadLetterExchangeName { get; set; }
public string Type { get; }
public bool Durable { get; set; }
@ -15,16 +17,18 @@ namespace Volo.Abp.RabbitMQ
public IDictionary<string, object> Arguments { get; }
public ExchangeDeclareConfiguration(
string exchangeName,
string type,
bool durable = false,
bool autoDelete = false)
string exchangeName,
string type,
bool durable = false,
bool autoDelete = false,
string deadLetterExchangeName = null)
{
ExchangeName = exchangeName;
DeadLetterExchangeName = deadLetterExchangeName;
Type = type;
Durable = durable;
AutoDelete = autoDelete;
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);
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
{
[NotNull]
public string QueueName { get; }
[NotNull] public string QueueName { get; }
public string DeadLetterQueueName { get; set; }
public bool Durable { get; set; }
@ -18,12 +19,14 @@ namespace Volo.Abp.RabbitMQ
public IDictionary<string, object> Arguments { get; }
public QueueDeclareConfiguration(
[NotNull] string queueName,
bool durable = true,
bool exclusive = false,
bool autoDelete = false)
[NotNull] string queueName,
bool durable = true,
bool exclusive = false,
bool autoDelete = false,
string deadLetterQueueName = null)
{
QueueName = queueName;
DeadLetterQueueName = deadLetterQueueName;
Durable = durable;
Exclusive = exclusive;
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.Collections.Concurrent;
using System.Threading.Tasks;
using RabbitMQ.Client.Exceptions;
using Volo.Abp.DependencyInjection;
using Volo.Abp.ExceptionHandling;
using Volo.Abp.Threading;
@ -147,6 +148,7 @@ namespace Volo.Abp.RabbitMQ
Channel = ConnectionPool
.Get(ConnectionName)
.CreateModel();
Channel.ExchangeDeclare(
exchange: Exchange.ExchangeName,
type: Exchange.Type,
@ -155,7 +157,29 @@ namespace Volo.Abp.RabbitMQ
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,
durable: Queue.Durable,
exclusive: Queue.Exclusive,
@ -174,6 +198,17 @@ namespace Volo.Abp.RabbitMQ
}
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);
await ExceptionNotifier.NotifyAsync(ex, logLevel: LogLevel.Warning);
}
@ -194,14 +229,10 @@ namespace Volo.Abp.RabbitMQ
{
try
{
Channel.BasicNack(
basicDeliverEventArgs.DeliveryTag,
multiple: false,
requeue: true
);
Channel.BasicReject(basicDeliverEventArgs.DeliveryTag, false);
}
catch { }
Logger.LogException(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));
}
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;
}
if (Guid.TryParse(userIdOrNull.Value, out Guid result))
if (Guid.TryParse(userIdOrNull.Value, out Guid guid))
{
return result;
return guid;
}
return null;
}
@ -36,7 +38,12 @@ namespace System.Security.Principal
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)
@ -49,7 +56,12 @@ namespace System.Security.Principal
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)
@ -64,7 +76,12 @@ namespace System.Security.Principal
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)
@ -105,7 +122,12 @@ namespace System.Security.Principal
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)
@ -120,7 +142,88 @@ namespace System.Security.Principal
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)

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

@ -67,5 +67,15 @@ namespace Volo.Abp.Security.Claims
/// Default: "client_id".
/// </summary>
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.Diagnostics;
using JetBrains.Annotations;
using Volo.Abp.Security.Claims;
namespace Volo.Abp.Users
{
@ -30,5 +31,35 @@ namespace Volo.Abp.Users
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 NSubstitute;
using Volo.Abp.Auditing.App.Entities;
using Volo.Abp.Data;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Domain.Repositories;
using Volo.Abp.Uow;
@ -44,7 +45,7 @@ namespace Volo.Abp.Auditing
await _auditingStore.Received().SaveAsync(Arg.Any<AuditLogInfo>());
}
[Fact]
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
.ExtraProperties))
.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
}
}

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

@ -5,6 +5,17 @@ namespace Volo.Abp.EventBus
[DependsOn(typeof(AbpEventBusModule))]
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["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 =>
@ -41,7 +56,8 @@ namespace Volo.Abp.Data.MultiTenancy
ConnectionStrings =
{
{ ConnectionStrings.DefaultConnectionStringName, "tenant1-default-value"},
{"db1", "tenant1-db1-value"}
{"db1", "tenant1-db1-value"},
{"Admin", "tenant1-Admin-value"}
}
},
new TenantConfiguration(_tenant2Id, "tenant2")
@ -55,12 +71,16 @@ namespace Volo.Abp.Data.MultiTenancy
//No tenant in current context
(await _connectionResolver.ResolveAsync()).ShouldBe("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
using (_currentTenant.Change(_tenant1Id))
{
(await _connectionResolver.ResolveAsync()).ShouldBe("tenant1-default-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
@ -72,6 +92,8 @@ namespace Volo.Abp.Data.MultiTenancy
{
(await _connectionResolver.ResolveAsync()).ShouldBe("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>
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>
/// Default value: 256
/// </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.Domain;
using Volo.Abp.ExceptionHandling;
using Volo.Abp.Json;
using Volo.Abp.Modularity;
using Volo.Abp.ObjectExtending;
using Volo.Abp.ObjectExtending.Modularity;
@ -10,13 +12,15 @@ namespace Volo.Abp.AuditLogging
[DependsOn(typeof(AbpAuditingModule))]
[DependsOn(typeof(AbpDddDomainModule))]
[DependsOn(typeof(AbpAuditLoggingDomainSharedModule))]
[DependsOn(typeof(AbpExceptionHandlingModule))]
[DependsOn(typeof(AbpJsonModule))]
public class AbpAuditLoggingDomainModule : AbpModule
{
private static readonly OneTimeRunner OneTimeRunner = new OneTimeRunner();
public override void PostConfigureServices(ServiceConfigurationContext context)
{
OneTimeRunner.Run(() =>
OneTimeRunner.Run(() =>
{
ModuleExtensionConfigurationHelper.ApplyEntityConfigurationToEntity(
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)
: base(guidGenerator.Create())
public AuditLog(
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);
TenantId = auditInfo.TenantId;
TenantName = auditInfo.TenantName.Truncate(AuditLogConsts.MaxTenantNameLength);
UserId = auditInfo.UserId;
UserName = auditInfo.UserName.Truncate(AuditLogConsts.MaxUserNameLength);
ExecutionTime = auditInfo.ExecutionTime;
ExecutionDuration = auditInfo.ExecutionDuration;
ClientIpAddress = auditInfo.ClientIpAddress.Truncate(AuditLogConsts.MaxClientIpAddressLength);
ClientName = auditInfo.ClientName.Truncate(AuditLogConsts.MaxClientNameLength);
ClientId = auditInfo.ClientId.Truncate(AuditLogConsts.MaxClientIdLength);
CorrelationId = auditInfo.CorrelationId.Truncate(AuditLogConsts.MaxCorrelationIdLength);
BrowserInfo = auditInfo.BrowserInfo.Truncate(AuditLogConsts.MaxBrowserInfoLength);
HttpMethod = auditInfo.HttpMethod.Truncate(AuditLogConsts.MaxHttpMethodLength);
Url = auditInfo.Url.Truncate(AuditLogConsts.MaxUrlLength);
HttpStatusCode = auditInfo.HttpStatusCode;
ImpersonatorUserId = auditInfo.ImpersonatorUserId;
ImpersonatorTenantId = auditInfo.ImpersonatorTenantId;
ExtraProperties = new ExtraPropertyDictionary();
if (auditInfo.ExtraProperties != null)
{
foreach (var pair in auditInfo.ExtraProperties)
{
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);
ApplicationName = applicationName.Truncate(AuditLogConsts.MaxApplicationNameLength);
TenantId = tenantId;
TenantName = tenantName.Truncate(AuditLogConsts.MaxTenantNameLength);
UserId = userId;
UserName = userName.Truncate(AuditLogConsts.MaxUserNameLength);
ExecutionTime = executionTime;
ExecutionDuration = executionDuration;
ClientIpAddress = clientIpAddress.Truncate(AuditLogConsts.MaxClientIpAddressLength);
ClientName = clientName.Truncate(AuditLogConsts.MaxClientNameLength);
ClientId = clientId.Truncate(AuditLogConsts.MaxClientIdLength);
CorrelationId = correlationId.Truncate(AuditLogConsts.MaxCorrelationIdLength);
BrowserInfo = browserInfo.Truncate(AuditLogConsts.MaxBrowserInfoLength);
HttpMethod = httpMethod.Truncate(AuditLogConsts.MaxHttpMethodLength);
Url = url.Truncate(AuditLogConsts.MaxUrlLength);
HttpStatusCode = httpStatusCode;
ImpersonatorUserId = impersonatorUserId;
ImpersonatorTenantId = impersonatorTenantId;
ExtraProperties = extraPropertyDictionary;
EntityChanges = entityChanges;
Actions = actions;
Exceptions = exceptions;
Comments = comments.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 Volo.Abp.Auditing;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Guids;
using Volo.Abp.Uow;
namespace Volo.Abp.AuditLogging
@ -13,21 +12,19 @@ namespace Volo.Abp.AuditLogging
public class AuditingStore : IAuditingStore, ITransientDependency
{
public ILogger<AuditingStore> Logger { get; set; }
protected IAuditLogRepository AuditLogRepository { get; }
protected IGuidGenerator GuidGenerator { get; }
protected IUnitOfWorkManager UnitOfWorkManager { get; }
protected AbpAuditingOptions Options { get; }
protected IAuditLogInfoToAuditLogConverter Converter { get; }
public AuditingStore(
IAuditLogRepository auditLogRepository,
IGuidGenerator guidGenerator,
IUnitOfWorkManager unitOfWorkManager,
IOptions<AbpAuditingOptions> options)
IOptions<AbpAuditingOptions> options,
IAuditLogInfoToAuditLogConverter converter)
{
AuditLogRepository = auditLogRepository;
GuidGenerator = guidGenerator;
UnitOfWorkManager = unitOfWorkManager;
Converter = converter;
Options = options.Value;
Logger = NullLogger<AuditingStore>.Instance;
@ -56,9 +53,9 @@ namespace Volo.Abp.AuditLogging
{
using (var uow = UnitOfWorkManager.Begin(true))
{
await AuditLogRepository.InsertAsync(new AuditLog(GuidGenerator, auditInfo));
await AuditLogRepository.InsertAsync(await Converter.ConvertAsync(auditInfo));
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.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.ExecutionDuration).HasColumnName(nameof(AuditLog.ExecutionDuration));
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.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
using Microsoft.VisualBasic;
using Shouldly;
using Volo.Abp.Auditing;
using Volo.Abp.Guids;
using Volo.Abp.Modularity;
using Xunit;
@ -17,12 +14,12 @@ namespace Volo.Abp.AuditLogging
where TStartupModule : IAbpModule
{
protected IAuditLogRepository AuditLogRepository { get; }
protected IGuidGenerator GuidGenerator { get; }
protected IAuditLogInfoToAuditLogConverter AuditLogInfoToAuditLogConverter { get; }
protected AuditLogRepository_Tests()
{
AuditLogRepository = GetRequiredService<IAuditLogRepository>();
GuidGenerator = GetRequiredService<IGuidGenerator>();
AuditLogInfoToAuditLogConverter = GetRequiredService<IAuditLogInfoToAuditLogConverter>();
}
[Fact]
@ -119,8 +116,8 @@ namespace Volo.Abp.AuditLogging
}
};
await AuditLogRepository.InsertAsync(new AuditLog(GuidGenerator, log1));
await AuditLogRepository.InsertAsync(new AuditLog(GuidGenerator, log2));
await AuditLogRepository.InsertAsync(await AuditLogInfoToAuditLogConverter.ConvertAsync(log1));
await AuditLogRepository.InsertAsync(await AuditLogInfoToAuditLogConverter.ConvertAsync(log2));
//Assert
var logs = await AuditLogRepository.GetListAsync();
@ -223,8 +220,8 @@ namespace Volo.Abp.AuditLogging
}
};
await AuditLogRepository.InsertAsync(new AuditLog(GuidGenerator, log1));
await AuditLogRepository.InsertAsync(new AuditLog(GuidGenerator, log2));
await AuditLogRepository.InsertAsync(await AuditLogInfoToAuditLogConverter.ConvertAsync(log1));
await AuditLogRepository.InsertAsync(await AuditLogInfoToAuditLogConverter.ConvertAsync(log2));
//Assert
var logs = await AuditLogRepository.GetCountAsync();
@ -325,8 +322,8 @@ namespace Volo.Abp.AuditLogging
}
};
await AuditLogRepository.InsertAsync(new AuditLog(GuidGenerator, log1));
await AuditLogRepository.InsertAsync(new AuditLog(GuidGenerator, log2));
await AuditLogRepository.InsertAsync(await AuditLogInfoToAuditLogConverter.ConvertAsync(log1));
await AuditLogRepository.InsertAsync(await AuditLogInfoToAuditLogConverter.ConvertAsync(log2));
//Assert
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(new AuditLog(GuidGenerator, log2));
await AuditLogRepository.InsertAsync(await AuditLogInfoToAuditLogConverter.ConvertAsync(log1));
await AuditLogRepository.InsertAsync(await AuditLogInfoToAuditLogConverter.ConvertAsync(log2));
//Assert
var entityChanges = await AuditLogRepository.GetEntityChangeListAsync();
@ -534,8 +531,8 @@ namespace Volo.Abp.AuditLogging
}
};
await AuditLogRepository.InsertAsync(new AuditLog(GuidGenerator, log1));
await AuditLogRepository.InsertAsync(new AuditLog(GuidGenerator, log2));
await AuditLogRepository.InsertAsync(await AuditLogInfoToAuditLogConverter.ConvertAsync(log1));
await AuditLogRepository.InsertAsync(await AuditLogInfoToAuditLogConverter.ConvertAsync(log2));
var entityChanges = await AuditLogRepository.GetEntityChangeListAsync();
var entityChange =
@ -641,8 +638,8 @@ namespace Volo.Abp.AuditLogging
}
};
await AuditLogRepository.InsertAsync(new AuditLog(GuidGenerator, log1));
await AuditLogRepository.InsertAsync(new AuditLog(GuidGenerator, log2));
await AuditLogRepository.InsertAsync(await AuditLogInfoToAuditLogConverter.ConvertAsync(log1));
await AuditLogRepository.InsertAsync(await AuditLogInfoToAuditLogConverter.ConvertAsync(log2));
//Assert
var entityChangesDesc = await AuditLogRepository.GetEntityChangeListAsync();
@ -754,8 +751,8 @@ namespace Volo.Abp.AuditLogging
}
};
await AuditLogRepository.InsertAsync(new AuditLog(GuidGenerator, log1));
await AuditLogRepository.InsertAsync(new AuditLog(GuidGenerator, log2));
await AuditLogRepository.InsertAsync(await AuditLogInfoToAuditLogConverter.ConvertAsync(log1));
await AuditLogRepository.InsertAsync(await AuditLogInfoToAuditLogConverter.ConvertAsync(log2));
//Assert
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(new AuditLog(GuidGenerator, log2));
await AuditLogRepository.InsertAsync(await AuditLogInfoToAuditLogConverter.ConvertAsync(log1));
await AuditLogRepository.InsertAsync(await AuditLogInfoToAuditLogConverter.ConvertAsync(log2));
//Assert
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(new AuditLog(GuidGenerator, log2));
await AuditLogRepository.InsertAsync(await AuditLogInfoToAuditLogConverter.ConvertAsync(log1));
await AuditLogRepository.InsertAsync(await AuditLogInfoToAuditLogConverter.ConvertAsync(log2));
var entityChanges = await AuditLogRepository.GetEntityChangeListAsync();
var entityHistory =

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

@ -22,7 +22,7 @@
border-radius: 5px;
overflow: hidden; }
.docs-page .docs-sidebar .docs-sidebar-wrapper {
width: 270px;
width: 300px;
float: right; }
.docs-page .docs-sidebar .docs-sidebar-wrapper input.form-control {
border: 0; }
@ -70,7 +70,7 @@
margin-right: 12px !important;
margin-top: 20px !important; }
.docs-page .docs-sidebar .docs-sidebar-wrapper .docs-tree-list ul {
font-size: .9em;
font-size: 14px;
list-style: none;
padding: 0 1rem;
margin: 0; }
@ -555,6 +555,9 @@ div.code-toolbar > .toolbar span {
div.code-toolbar > .toolbar a {
cursor: copy; }
.logo-nav ul {
width: 300px !important; }
@media (max-width: 767px) {
body {
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 {
width: 270px;
width: 300px;
float: right;
input.form-control {
@ -110,7 +110,7 @@ body {
}
ul {
font-size: .9em;
font-size: 14px;
list-style: none;
padding: 0 1rem;
margin: 0;
@ -943,6 +943,10 @@ div.code-toolbar > .toolbar a {
cursor: copy;
}
.logo-nav ul {
width: 300px !important;
}
@media (max-width: 767px) {
body {
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 =
{
AbpClaimTypes.TenantId,
AbpClaimTypes.ImpersonatorTenantId,
AbpClaimTypes.ImpersonatorUserId,
AbpClaimTypes.Name,
AbpClaimTypes.SurName,
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.AddInMemoryIdentityResources(configuration.GetSection("IdentityServer:IdentityResources"));
}
identityServerBuilder.AddExtensionGrantValidator<LinkLoginExtensionGrantValidator>();
}
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',
rootNamespace: 'MyCompanyName.MyProjectName',
},
AbpAccount: {
rootNamespace: 'Volo.Abp',
},
AbpFeatureManagement: {
rootNamespace: 'Volo.Abp',
},

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

@ -69,6 +69,7 @@ const routes: Routes = [
component: ReplaceableRouteContainerComponent,
canActivate: [AuthenticationFlowGuard],
data: {
tenantBoxVisible: false,
replaceableComponent: {
key: eAccountComponents.ResetPassword,
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="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>
</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 { Component } from '@angular/core';
import { Component, Injector } from '@angular/core';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { eAccountComponents } from '../../enums/components';
import { ActivatedRoute } from '@angular/router';
@Component({
selector: 'abp-auth-wrapper',
@ -20,9 +21,24 @@ export class AuthWrapperComponent {
}
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(
public readonly multiTenancy: MultiTenancyService,
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;
isPasswordReset = false;
tenantId = '';
mapErrorsFn: Validation.MapErrorsFn = (errors, groupErrors, control) => {
if (PASSWORD_FIELDS.indexOf(String(control.name)) < 0) return errors;
@ -34,7 +35,8 @@ export class ResetPasswordComponent implements OnInit {
) {}
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');
this.form = this.fb.group(
@ -61,6 +63,7 @@ export class ResetPasswordComponent implements OnInit {
userId: this.form.get('userId').value,
resetToken: this.form.get('resetToken').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)))
.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 './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;
emailAddress: string;
password: string;
@ -8,6 +9,7 @@ export interface RegisterDto {
export interface ResetPasswordDto {
userId?: string;
tenantId?: string;
resetToken: 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 = () => {
this.resetPageWhenUnchanged();
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);
this.next();
};
getWithoutPageReset = () => {
this.next();
};
private delay: MonoTypeOperatorFunction<QueryParamsType>;
@ -130,6 +129,15 @@ export class ListService<QueryParamsType = ABP.PageQueryParams> implements OnDes
this._skipCount = 0;
} 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> = (

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:
# 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:
# npx browserslist
> 0.5%
last 2 versions
last 1 Chrome version
last 1 Firefox version
last 2 Edge major versions
last 2 Safari major versions
last 2 iOS major versions
Firefox ESR
not dead
not IE 9-11 # For IE 9-11 support, remove 'not'.
not IE 11 # Angular supports IE 11 only as an opt-in. To opt-in, remove the 'not' prefix on this line.

3
templates/app/angular/.editorconfig

@ -8,6 +8,9 @@ indent_size = 2
insert_final_newline = true
trim_trailing_whitespace = true
[*.ts]
quote_type = single
[*.md]
max_line_length = off
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
chrome-profiler-events*.json
speed-measure-plugin*.json
# IDEs and editors
/.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
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
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",
"cli": {
"analytics": false,
"defaultCollection": "@angular-eslint/schematics"
},
"version": 1,
"newProjectRoot": "projects",
"projects": {
@ -22,7 +26,7 @@
"main": "src/main.ts",
"polyfills": "src/polyfills.ts",
"tsConfig": "tsconfig.app.json",
"aot": true,
"inlineStyleLanguage": "scss",
"allowedCommonJsDependencies": ["chart.js", "js-sha256"],
"assets": ["src/favicon.ico", "src/assets"],
"styles": [
@ -67,44 +71,48 @@
},
"configurations": {
"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": [
{
"type": "initial",
"maximumWarning": "2mb",
"maximumError": "5mb"
"maximumError": "2.5mb"
},
{
"type": "anyComponentStyle",
"maximumWarning": "6kb",
"maximumError": "10kb"
"maximumWarning": "2kb",
"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": {
"builder": "@angular-devkit/build-angular:dev-server",
"options": {
"browserTarget": "MyProjectName:build"
},
"configurations": {
"production": {
"browserTarget": "MyProjectName:build:production"
},
"development": {
"browserTarget": "MyProjectName:build:development"
}
}
},
"defaultConfiguration": "development"
},
"extract-i18n": {
"builder": "@angular-devkit/build-angular:extract-i18n",
@ -119,43 +127,20 @@
"polyfills": "src/polyfills.ts",
"tsConfig": "tsconfig.spec.json",
"karmaConfig": "karma.conf.js",
"inlineStyleLanguage": "scss",
"assets": ["src/favicon.ico", "src/assets"],
"styles": [
"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"
],
"styles": ["src/styles.scss"],
"scripts": []
}
},
"lint": {
"builder": "@angular-devkit/build-angular:tslint",
"builder": "@angular-eslint/builder:lint",
"options": {
"tsConfig": ["tsconfig.app.json", "tsconfig.spec.json", "e2e/tsconfig.json"],
"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"
}
"lintFilePatterns": ["src/**/*.ts", "src/**/*.html"]
}
}
}
}
},
"defaultProject": "MyProjectName",
"cli": {
"analytics": false
}
"defaultProject": "MyProjectName"
}

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-chrome-launcher'),
require('karma-jasmine-html-reporter'),
require('karma-coverage-istanbul-reporter'),
require('karma-coverage'),
require('@angular-devkit/build-angular/plugins/karma')
],
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
},
coverageIstanbulReporter: {
dir: require('path').join(__dirname, './coverage/ng9-abp'),
reports: ['html', 'lcovonly', 'text-summary'],
fixWebpackSourcePaths: true
jasmineHtmlReporter: {
suppressAll: true // removes the duplicated traces
},
coverageReporter: {
dir: require('path').join(__dirname, './coverage/MyProjectName'),
subdir: '.',
reporters: [
{ type: 'html' },
{ type: 'text-summary' }
]
},
reporters: ['progress', 'kjhtml'],
port: 9876,

62
templates/app/angular/package.json

@ -5,10 +5,10 @@
"ng": "ng",
"start": "ng serve --open",
"build": "ng build",
"build:prod": "ng build --prod",
"build:prod": "ng build --configuration production",
"watch": "ng build --watch --configuration development",
"test": "ng test",
"lint": "ng lint",
"e2e": "ng e2e"
"lint": "ng lint"
},
"private": true,
"dependencies": {
@ -20,39 +20,41 @@
"@abp/ng.tenant-management": "~4.3.2",
"@abp/ng.theme.basic": "~4.3.2",
"@abp/ng.theme.shared": "~4.3.2",
"@angular/animations": "~11.1.0",
"@angular/common": "~11.1.0",
"@angular/compiler": "~11.1.0",
"@angular/core": "~11.1.0",
"@angular/forms": "~11.1.0",
"@angular/platform-browser": "~11.1.0",
"@angular/platform-browser-dynamic": "~11.1.0",
"@angular/router": "~11.1.0",
"@angular/animations": "~12.0.0",
"@angular/common": "~12.0.0",
"@angular/compiler": "~12.0.0",
"@angular/core": "~12.0.0",
"@angular/forms": "~12.0.0",
"@angular/platform-browser-dynamic": "~12.0.0",
"@angular/platform-browser": "~12.0.0",
"@angular/router": "~12.0.0",
"rxjs": "~6.6.0",
"tslib": "^2.0.0",
"zone.js": "~0.10.2"
"tslib": "^2.1.0",
"zone.js": "~0.11.4"
},
"devDependencies": {
"@abp/ng.schematics": "~4.3.2",
"@angular-devkit/build-angular": "~0.1101.0",
"@angular/cli": "~11.1.0",
"@angular/compiler-cli": "~11.1.0",
"@angular/language-service": "~11.1.0",
"@types/jasmine": "~3.5.0",
"@types/jasminewd2": "~2.0.3",
"@angular-devkit/build-angular": "~12.0.0",
"@angular-eslint/builder": "12.1.0",
"@angular-eslint/eslint-plugin-template": "12.1.0",
"@angular-eslint/eslint-plugin": "12.1.0",
"@angular-eslint/schematics": "12.1.0",
"@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",
"codelyzer": "^6.0.1",
"jasmine-core": "~3.6.0",
"jasmine-spec-reporter": "~5.0.0",
"karma": "~5.2.3",
"@typescript-eslint/eslint-plugin": "4.23.0",
"@typescript-eslint/parser": "4.23.0",
"eslint": "^7.26.0",
"jasmine-core": "~3.7.0",
"karma-chrome-launcher": "~3.1.0",
"karma-coverage-istanbul-reporter": "~3.0.2",
"karma-jasmine": "~4.0.0",
"karma-coverage": "~2.0.3",
"karma-jasmine-html-reporter": "^1.5.0",
"ng-packagr": "^11.0.1",
"protractor": "~7.0.0",
"ts-node": "~8.3.0",
"tslint": "~6.1.0",
"typescript": "~4.1.3"
"karma-jasmine": "~4.0.0",
"karma": "~6.3.0",
"ng-packagr": "^12.0.5",
"typescript": "~4.2.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
*/
/***************************************************************************************************
* 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`.
/**
* Web Animations `@angular/platform-browser/animations`
* 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).
*/
// import 'web-animations-js'; // Run `npm install --save web-animations-js`.
/**
* By default, zone.js will patch all possible macroTask and DomEvents
* 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;
*
*/
/***************************************************************************************************
* 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

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
import 'zone.js/dist/zone-testing';
import { getTestBed } from '@angular/core/testing';
import {
BrowserDynamicTestingModule,
platformBrowserDynamicTesting
platformBrowserDynamicTesting,
} from '@angular/platform-browser-dynamic/testing';
import 'zone.js/testing';
declare const require: {
context(path: string, deep?: boolean, filter?: RegExp): {
context(
path: string,
deep?: boolean,
filter?: RegExp
): {
keys(): string[];
<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",
"compilerOptions": {
"outDir": "./out-tsc/app",
"types": []
},
"files": ["src/main.ts", "src/polyfills.ts"],
"include": ["src/**/*.d.ts"]
"files": [
"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