diff --git a/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AuditingDisabledState.cs b/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AuditingDisabledState.cs new file mode 100644 index 0000000000..c4a2384316 --- /dev/null +++ b/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AuditingDisabledState.cs @@ -0,0 +1,11 @@ +namespace Volo.Abp.Auditing; + +public class AuditingDisabledState +{ + public bool IsDisabled { get; private set; } + + public AuditingDisabledState(bool isDisabled) + { + IsDisabled = isDisabled; + } +} diff --git a/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AuditingHelper.cs b/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AuditingHelper.cs index aadbeccabb..028096d0ac 100644 --- a/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AuditingHelper.cs +++ b/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AuditingHelper.cs @@ -8,6 +8,7 @@ using Microsoft.Extensions.Options; using Volo.Abp.Clients; using Volo.Abp.DependencyInjection; using Volo.Abp.MultiTenancy; +using Volo.Abp.Threading; using Volo.Abp.Timing; using Volo.Abp.Tracing; using Volo.Abp.Users; @@ -26,6 +27,7 @@ public class AuditingHelper : IAuditingHelper, ITransientDependency protected IAuditSerializer AuditSerializer; protected IServiceProvider ServiceProvider; protected ICorrelationIdProvider CorrelationIdProvider { get; } + protected IAmbientScopeProvider AuditingDisabledState { get; } public AuditingHelper( IAuditSerializer auditSerializer, @@ -37,7 +39,8 @@ public class AuditingHelper : IAuditingHelper, ITransientDependency IAuditingStore auditingStore, ILogger logger, IServiceProvider serviceProvider, - ICorrelationIdProvider correlationIdProvider) + ICorrelationIdProvider correlationIdProvider, + IAmbientScopeProvider auditingDisabledState) { Options = options.Value; AuditSerializer = auditSerializer; @@ -50,6 +53,7 @@ public class AuditingHelper : IAuditingHelper, ITransientDependency Logger = logger; ServiceProvider = serviceProvider; CorrelationIdProvider = correlationIdProvider; + AuditingDisabledState = auditingDisabledState; } public virtual bool ShouldSaveAudit(MethodInfo? methodInfo, bool defaultValue = false, bool ignoreIntegrationServiceAttribute = false) @@ -64,6 +68,11 @@ public class AuditingHelper : IAuditingHelper, ITransientDependency return false; } + if (!IsAuditingEnabled()) + { + return false; + } + if (methodInfo.IsDefined(typeof(AuditedAttribute), true)) { return true; @@ -178,6 +187,19 @@ public class AuditingHelper : IAuditingHelper, ITransientDependency return actionInfo; } + private const string AuditingDisabledScopeKey = "Volo.Abp.Auditing.DisabledScope"; + + public virtual IDisposable DisableAuditing() + { + return AuditingDisabledState.BeginScope(AuditingDisabledScopeKey, new AuditingDisabledState(true)); + } + + public virtual bool IsAuditingEnabled() + { + var state = AuditingDisabledState.GetValue(AuditingDisabledScopeKey); + return state == null || !state.IsDisabled; + } + protected virtual void ExecutePreContributors(AuditLogInfo auditLogInfo) { using (var scope = ServiceProvider.CreateScope()) diff --git a/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/IAuditingHelper.cs b/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/IAuditingHelper.cs index e6fa20bc90..f0a36da5b4 100644 --- a/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/IAuditingHelper.cs +++ b/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/IAuditingHelper.cs @@ -26,4 +26,23 @@ public interface IAuditingHelper MethodInfo method, IDictionary arguments ); + + /// + /// Creates a scope in which auditing is temporarily disabled. + /// + /// + /// An that restores the previous auditing state + /// when disposed. This method supports nested scopes; disposing a scope + /// restores the auditing state that was active before that scope was created. + /// + IDisposable DisableAuditing(); + + /// + /// Determines whether auditing is currently enabled. + /// + /// + /// true if auditing is enabled in the current context; otherwise, false. + /// This reflects any active scopes created by . + /// + bool IsAuditingEnabled(); } diff --git a/framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/AuditingHelper_Tests.cs b/framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/AuditingHelper_Tests.cs new file mode 100644 index 0000000000..f1f82a4459 --- /dev/null +++ b/framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/AuditingHelper_Tests.cs @@ -0,0 +1,139 @@ +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using NSubstitute; +using Volo.Abp.DependencyInjection; +using Xunit; + +namespace Volo.Abp.Auditing; + +public class AuditingHelper_Tests : AbpAuditingTestBase +{ + private readonly IAuditingHelper _auditingHelper; + protected IAuditingStore AuditingStore; + + public AuditingHelper_Tests() + { + _auditingHelper = GetRequiredService(); + } + + protected override void AfterAddApplication(IServiceCollection services) + { + AuditingStore = Substitute.For(); + services.Replace(ServiceDescriptor.Singleton(AuditingStore)); + } + + [Fact] + public async Task Should_Write_AuditLog_Without_DisableAuditing() + { + var myAuditedObject = GetRequiredService(); + + await myAuditedObject.DoItAsync(); + + await AuditingStore.Received().SaveAsync(Arg.Any()); + } + + [Fact] + public async Task Should_Not_Write_AuditLog_With_DisableAuditing() + { + var myAuditedObject = GetRequiredService(); + + using (_auditingHelper.DisableAuditing()) + { + await myAuditedObject.DoItAsync(); + } + + await AuditingStore.DidNotReceive().SaveAsync(Arg.Any()); + } + + [Fact] + public async Task Should_Write_AuditLog_After_DisableAuditing_Scope_Disposed() + { + var myAuditedObject = GetRequiredService(); + + using (_auditingHelper.DisableAuditing()) + { + await myAuditedObject.DoItAsync(); + } + + AuditingStore.ClearReceivedCalls(); + + await myAuditedObject.DoItAsync(); + + await AuditingStore.Received().SaveAsync(Arg.Any()); + } + + [Fact] + public async Task Should_Not_Write_AuditLog_With_Nested_DisableAuditing() + { + var myAuditedObject = GetRequiredService(); + + using (_auditingHelper.DisableAuditing()) + { + await myAuditedObject.DoItAsync(); + + using (_auditingHelper.DisableAuditing()) + { + await myAuditedObject.DoItAsync(); + } + + await myAuditedObject.DoItAsync(); + } + + await AuditingStore.DidNotReceive().SaveAsync(Arg.Any()); + } + + [Fact] + public void Should_Return_True_When_Auditing_Is_Enabled() + { + Assert.True(_auditingHelper.IsAuditingEnabled()); + } + + [Fact] + public void Should_Return_False_When_Auditing_Is_Disabled() + { + using (_auditingHelper.DisableAuditing()) + { + Assert.False(_auditingHelper.IsAuditingEnabled()); + } + } + + [Fact] + public void Should_Return_True_After_DisableAuditing_Scope_Disposed() + { + using (_auditingHelper.DisableAuditing()) + { + Assert.False(_auditingHelper.IsAuditingEnabled()); + } + + Assert.True(_auditingHelper.IsAuditingEnabled()); + } + + [Fact] + public void Should_Return_False_With_Nested_DisableAuditing() + { + using (_auditingHelper.DisableAuditing()) + { + Assert.False(_auditingHelper.IsAuditingEnabled()); + + using (_auditingHelper.DisableAuditing()) + { + Assert.False(_auditingHelper.IsAuditingEnabled()); + } + + Assert.False(_auditingHelper.IsAuditingEnabled()); + } + } + + public interface IMyAuditedObject : ITransientDependency, IAuditingEnabled + { + } + + public class MyAuditedObject : IMyAuditedObject + { + public virtual Task DoItAsync() + { + return Task.CompletedTask; + } + } +} diff --git a/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKitPageRouteValueTransformer.cs b/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKitPageRouteValueTransformer.cs index 996a449489..82750943c0 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKitPageRouteValueTransformer.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKitPageRouteValueTransformer.cs @@ -1,6 +1,7 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Routing; +using Volo.Abp.Auditing; using Volo.Abp.Caching; using Volo.Abp.Features; using Volo.Abp.MultiTenancy; @@ -16,18 +17,21 @@ public class CmsKitPageRouteValueTransformer : CmsKitDynamicRouteValueTransforme protected IFeatureChecker FeatureChecker { get; } protected IPagePublicAppService PagePublicAppService { get; } protected IDistributedCache PageCache { get; } + protected IAuditingHelper AuditingHelper { get; } public CmsKitPageRouteValueTransformer( ICurrentTenant currentTenant, ITenantConfigurationProvider tenantConfigurationProvider, IFeatureChecker featureChecker, IPagePublicAppService pagePublicAppService, - IDistributedCache pageCache) + IDistributedCache pageCache, + IAuditingHelper auditingHelper) : base(currentTenant, tenantConfigurationProvider) { FeatureChecker = featureChecker; PagePublicAppService = pagePublicAppService; PageCache = pageCache; + AuditingHelper = auditingHelper; } protected async override ValueTask DoTransformAsync(HttpContext httpContext, RouteValueDictionary values) @@ -44,7 +48,10 @@ public class CmsKitPageRouteValueTransformer : CmsKitDynamicRouteValueTransforme var exist = await PageCache.GetAsync(PageCacheItem.GetKey(slug)) != null; if (!exist) { - exist = await PagePublicAppService.DoesSlugExistAsync(slug); + using (AuditingHelper.DisableAuditing()) + { + exist = await PagePublicAppService.DoesSlugExistAsync(slug); + } } if (exist)