diff --git a/docs/en/Multi-Tenancy.md b/docs/en/Multi-Tenancy.md index 2603cb0436..8b8480669f 100644 --- a/docs/en/Multi-Tenancy.md +++ b/docs/en/Multi-Tenancy.md @@ -32,6 +32,22 @@ Configure(options => > Multi-Tenancy is disabled in the ABP Framework by default. However, it is **enabled by default** when you create a new solution using the [startup template](Startup-Templates/Application.md). `MultiTenancyConsts` class in the solution has a constant to control it in a single place. +### AbpMultiTenancyOptions: Handle inactive and non-existent tenants. + +The `MultiTenancyMiddlewareErrorPageBuilder` of `AbpMultiTenancyOptions` is used to handle inactive and non-existent tenants. + +It will respond to an error page by default, you can change it if you want, eg: only output the error log and continue ASP NET Core's request pipeline. + +```csharp +Configure(options => +{ + options.MultiTenancyMiddlewareErrorPageBuilder = async (context, exception) => + { + // Handle the exception. + }; +}); +``` + ### Database Architecture ABP Framework supports all the following approaches to store the tenant data in the database; diff --git a/docs/zh-Hans/Multi-Tenancy.md b/docs/zh-Hans/Multi-Tenancy.md index 8e92d10673..472dde9eab 100644 --- a/docs/zh-Hans/Multi-Tenancy.md +++ b/docs/zh-Hans/Multi-Tenancy.md @@ -34,6 +34,22 @@ namespace MyCompany.MyProject > 随着"Multi-tenancy ready"的概念,我们打算开发我们的代码和多租户方法兼容.然后它可以被用于多租户和非多租户的程序中,这取决于最终程序的需求. +### AbpMultiTenancyOptions: 处理不活跃或不存在的租户 + +`MultiTenancyMiddlewareErrorPageBuilder` 或 `AbpMultiTenancyOptions` 用于 处理不活跃或不存在的租户. + +默认情况下会响应错误页面, 你可以根据自己的需要更改它, 比如: 只输出错误日志并继续ASP NET Core的请求管道 + +```csharp +Configure(options => +{ + options.MultiTenancyMiddlewareErrorPageBuilder = async (context, exception) => + { + // Handle the exception. + }; +}); +``` + #### 定义实体 你可以在你的实体中实现 **IMultiTenant** 接口来实现多租户,例如: diff --git a/framework/src/Volo.Abp.EventBus.Kafka/Volo/Abp/EventBus/Kafka/KafkaDistributedEventBus.cs b/framework/src/Volo.Abp.EventBus.Kafka/Volo/Abp/EventBus/Kafka/KafkaDistributedEventBus.cs index 4dba728c51..aa79c81f50 100644 --- a/framework/src/Volo.Abp.EventBus.Kafka/Volo/Abp/EventBus/Kafka/KafkaDistributedEventBus.cs +++ b/framework/src/Volo.Abp.EventBus.Kafka/Volo/Abp/EventBus/Kafka/KafkaDistributedEventBus.cs @@ -162,13 +162,13 @@ public class KafkaDistributedEventBus : DistributedEventBusBase, ISingletonDepen protected async override Task PublishToEventBusAsync(Type eventType, object eventData) { await PublishAsync( + AbpKafkaEventBusOptions.TopicName, eventType, eventData, new Headers { - { "messageId", System.Text.Encoding.UTF8.GetBytes(Guid.NewGuid().ToString("N")) } - }, - null + { "messageId", System.Text.Encoding.UTF8.GetBytes(Guid.NewGuid().ToString("N")) } + } ); } @@ -188,42 +188,31 @@ public class KafkaDistributedEventBus : DistributedEventBusBase, ISingletonDepen new Headers { { "messageId", System.Text.Encoding.UTF8.GetBytes(outgoingEvent.Id.ToString("N")) } - }, - null + } ); } public override Task PublishManyFromOutboxAsync(IEnumerable outgoingEvents, OutboxConfig outboxConfig) { - var producer = ProducerPool.Get(); + var producer = ProducerPool.Get(AbpKafkaEventBusOptions.ConnectionName); var outgoingEventArray = outgoingEvents.ToArray(); - producer.BeginTransaction(); - try + + foreach (var outgoingEvent in outgoingEventArray) { - foreach (var outgoingEvent in outgoingEventArray) + var messageId = outgoingEvent.Id.ToString("N"); + var headers = new Headers { - var messageId = outgoingEvent.Id.ToString("N"); - var headers = new Headers - { - { "messageId", System.Text.Encoding.UTF8.GetBytes(messageId)} - }; - - producer.Produce( - AbpKafkaEventBusOptions.TopicName, - new Message - { - Key = outgoingEvent.EventName, - Value = outgoingEvent.EventData, - Headers = headers - }); - } + { "messageId", System.Text.Encoding.UTF8.GetBytes(messageId)} + }; - producer.CommitTransaction(); - } - catch (Exception e) - { - producer.AbortTransaction(); - throw; + producer.Produce( + AbpKafkaEventBusOptions.TopicName, + new Message + { + Key = outgoingEvent.EventName, + Value = outgoingEvent.EventData, + Headers = headers + }); } return Task.CompletedTask; @@ -253,47 +242,22 @@ public class KafkaDistributedEventBus : DistributedEventBusBase, ISingletonDepen return Serializer.Serialize(eventData); } - public virtual async Task PublishAsync(Type eventType, object eventData, Headers headers, Dictionary headersArguments) - { - await PublishAsync( - AbpKafkaEventBusOptions.TopicName, - eventType, - eventData, - headers, - headersArguments - ); - } - - private Task PublishAsync(string topicName, Type eventType, object eventData, Headers headers, Dictionary headersArguments) + private Task PublishAsync(string topicName, Type eventType, object eventData, Headers headers) { var eventName = EventNameAttribute.GetNameOrDefault(eventType); var body = Serializer.Serialize(eventData); - return PublishAsync(topicName, eventName, body, headers, headersArguments); + return PublishAsync(topicName, eventName, body, headers); } private Task> PublishAsync( string topicName, string eventName, byte[] body, - Headers headers, - Dictionary headersArguments) + Headers headers) { var producer = ProducerPool.Get(AbpKafkaEventBusOptions.ConnectionName); - return PublishAsync(producer, topicName, eventName, body, headers, headersArguments); - } - - private Task> PublishAsync( - IProducer producer, - string topicName, - string eventName, - byte[] body, - Headers headers, - Dictionary headersArguments) - { - SetEventMessageHeaders(headers, headersArguments); - return producer.ProduceAsync( topicName, new Message @@ -304,20 +268,6 @@ public class KafkaDistributedEventBus : DistributedEventBusBase, ISingletonDepen }); } - private void SetEventMessageHeaders(Headers headers, Dictionary headersArguments) - { - if (headersArguments == null) - { - return; - } - - foreach (var header in headersArguments) - { - headers.Remove(header.Key); - headers.Add(header.Key, Serializer.Serialize(header.Value)); - } - } - private List GetOrCreateHandlerFactories(Type eventType) { return HandlerFactories.GetOrAdd( diff --git a/framework/src/Volo.Abp.Kafka/Volo/Abp/Kafka/ProducerPool.cs b/framework/src/Volo.Abp.Kafka/Volo/Abp/Kafka/ProducerPool.cs index 0a31a3483a..691a29c1d2 100644 --- a/framework/src/Volo.Abp.Kafka/Volo/Abp/Kafka/ProducerPool.cs +++ b/framework/src/Volo.Abp.Kafka/Volo/Abp/Kafka/ProducerPool.cs @@ -41,16 +41,8 @@ public class ProducerPool : IProducerPool, ISingletonDependency { var producerConfig = new ProducerConfig(Options.Connections.GetOrDefault(connection)); Options.ConfigureProducer?.Invoke(producerConfig); - - if (producerConfig.TransactionalId.IsNullOrWhiteSpace()) - { - producerConfig.TransactionalId = Guid.NewGuid().ToString(); - } - - var producer = new ProducerBuilder(producerConfig).Build(); - producer.InitTransactions(DefaultTransactionsWaitDuration); + return new ProducerBuilder(producerConfig).Build(); - return producer; })).Value; } diff --git a/npm/ng-packs/packages/core/src/lib/core.module.ts b/npm/ng-packs/packages/core/src/lib/core.module.ts index 58c128ac32..bcc071185f 100644 --- a/npm/ng-packs/packages/core/src/lib/core.module.ts +++ b/npm/ng-packs/packages/core/src/lib/core.module.ts @@ -136,6 +136,7 @@ export class CoreModule { return { ngModule: RootCoreModule, providers: [ + OAuthModule.forRoot().providers, LocaleProvider, CookieLanguageProvider, { @@ -190,7 +191,6 @@ export class CoreModule { useValue: localizationContributor(options.localizations), deps: [LocalizationService], }, - OAuthModule.forRoot().providers, ], }; } diff --git a/npm/ng-packs/packages/core/src/lib/directives/permission.directive.ts b/npm/ng-packs/packages/core/src/lib/directives/permission.directive.ts index 2054a0d529..824b013843 100644 --- a/npm/ng-packs/packages/core/src/lib/directives/permission.directive.ts +++ b/npm/ng-packs/packages/core/src/lib/directives/permission.directive.ts @@ -18,6 +18,8 @@ import { PermissionService } from '../services/permission.service'; export class PermissionDirective implements OnDestroy, OnChanges { @Input('abpPermission') condition: string | undefined; + @Input('abpPermissionRunChangeDetection') runChangeDetection = true; + subscription!: Subscription; constructor( @@ -38,7 +40,11 @@ export class PermissionDirective implements OnDestroy, OnChanges { .subscribe(isGranted => { this.vcRef.clear(); if (isGranted) this.vcRef.createEmbeddedView(this.templateRef); - this.cdRef.detectChanges(); + if (this.runChangeDetection) { + this.cdRef.detectChanges(); + } else { + this.cdRef.markForCheck(); + } }); } diff --git a/npm/ng-packs/packages/core/src/lib/tests/permission.directive.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/permission.directive.spec.ts index d465a60fa3..37d792787c 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/permission.directive.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/permission.directive.spec.ts @@ -2,10 +2,12 @@ import { createDirectiveFactory, SpectatorDirective } from '@ngneat/spectator/je import { Subject } from 'rxjs'; import { PermissionDirective } from '../directives/permission.directive'; import { PermissionService } from '../services'; +import { ChangeDetectorRef } from '@angular/core'; describe('PermissionDirective', () => { let spectator: SpectatorDirective; let directive: PermissionDirective; + let cdr: ChangeDetectorRef; const grantedPolicy$ = new Subject(); const createDirective = createDirectiveFactory({ directive: PermissionDirective, @@ -30,7 +32,7 @@ describe('PermissionDirective', () => { grantedPolicy$.next(true); expect(spectator.query('#test-element')).toBeTruthy(); grantedPolicy$.next(false); - // expect(spectator.query('#test-element')).toBeFalsy(); // TODO: change detection problem should be fixed + expect(spectator.query('#test-element')).toBeFalsy(); }); }); @@ -41,6 +43,7 @@ describe('PermissionDirective', () => { { hostProps: { condition: '' } }, ); directive = spectator.directive; + cdr = (directive as any).cdRef as ChangeDetectorRef; }); it('should be created', () => { @@ -55,8 +58,22 @@ describe('PermissionDirective', () => { grantedPolicy$.next(false); expect(spectator.query('#test-element')).toBeFalsy(); grantedPolicy$.next(true); + expect(spectator.queryAll('#test-element')).toHaveLength(1); + }); + + it('should call detect changes method', () => { + const detectChanges = jest.spyOn(cdr, 'detectChanges'); + expect(spectator.query('#test-element')).toBeFalsy(); + spectator.setHostInput({ condition: 'test' }); + grantedPolicy$.next(true); + expect(spectator.query('#test-element')).toBeTruthy(); + expect(detectChanges).toHaveBeenCalled(); + grantedPolicy$.next(false); + expect(spectator.query('#test-element')).toBeFalsy(); + expect(detectChanges).toHaveBeenCalled(); grantedPolicy$.next(true); expect(spectator.queryAll('#test-element')).toHaveLength(1); + expect(detectChanges).toHaveBeenCalled(); }); describe('#subscription', () => { @@ -70,4 +87,35 @@ describe('PermissionDirective', () => { }); }); }); + describe('with runChangeDetection Input', () => { + beforeEach(() => { + spectator = createDirective( + '
Testing Permission Directive
', + { hostProps: { condition: '' } }, + ); + directive = spectator.directive; + cdr = (directive as any).cdRef as ChangeDetectorRef; + }); + it('should not call detectChanges method', () => { + const detectChanges = jest.spyOn(cdr, 'detectChanges'); + const markForCheck = jest.spyOn(cdr, 'markForCheck'); + expect(spectator.query('#test-element')).toBeFalsy(); + spectator.setHostInput({ condition: 'test' }); + + grantedPolicy$.next(true); + expect(spectator.query('#test-element')).toBeTruthy(); + expect(detectChanges).not.toHaveBeenCalled(); + expect(markForCheck).toHaveBeenCalled(); + + grantedPolicy$.next(false); + expect(spectator.query('#test-element')).toBeFalsy(); + expect(detectChanges).not.toHaveBeenCalled(); + expect(markForCheck).toHaveBeenCalled(); + + grantedPolicy$.next(true); + expect(spectator.queryAll('#test-element')).toHaveLength(1); + expect(detectChanges).not.toHaveBeenCalled(); + expect(markForCheck).toHaveBeenCalled(); + }); + }); }); diff --git a/npm/ng-packs/packages/theme-shared/extensions/src/lib/components/extensible-form/extensible-form-prop.component.html b/npm/ng-packs/packages/theme-shared/extensions/src/lib/components/extensible-form/extensible-form-prop.component.html index 24fd4e0f54..4367bd098a 100644 --- a/npm/ng-packs/packages/theme-shared/extensions/src/lib/components/extensible-form/extensible-form-prop.component.html +++ b/npm/ng-packs/packages/theme-shared/extensions/src/lib/components/extensible-form/extensible-form-prop.component.html @@ -1,4 +1,8 @@ -
+
- +