Browse Source

Merge pull request #23174 from abpframework/DisableAuditingAttribute

Refactor `DisableAuditingAttribute` to allow it to ignore changes to `AuditProperties`.
pull/23746/head
Halil İbrahim Kalkan 11 months ago
committed by GitHub
parent
commit
d3f7e61d7d
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 22
      docs/en/framework/infrastructure/audit-logging.md
  2. 8
      framework/src/Volo.Abp.Auditing.Contracts/Volo/Abp/Auditing/DisableAuditingAttribute.cs
  3. 21
      framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs
  4. 77
      framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/Auditing/Auditing_Tests.cs
  5. 9
      framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Domain/Person.cs

22
docs/en/framework/infrastructure/audit-logging.md

@ -230,6 +230,28 @@ public class MyUser : Entity<Guid>
}
````
#### Ignore Update Audit Properties And Publish Entity Updated Event
The `[DisableAuditing]` attribute supports additional configuration options when applied to **entity properties**.
* **UpdateModificationProps** (default: `true`): When set to `false`, changes to this entity property will not update audit properties (like `LastModificationTime`.
* **PublishEntityEvent** (default: `true`): When set to `false`, changes to this entity property will not publish entity change events (`EntityUpdatedEvent`).
````csharp
public class MyUser : Entity<Guid>
{
public string Name { get; set; }
[DisableAuditing(UpdateModificationProps = false, PublishEntityEvent = false)]
public string ReadCount { get; set; }
}
````
This example will ignore update audit properties and publish entity updated event when the `ReadCount` property is updated.
> The `UpdateModificationProps` and `PublishEntityEvent` only work for [Entity Framework Core](../data/entity-framework-core). It will not work for [MongoDB](../data/mongodb).
## IAuditingStore
`IAuditingStore` is an interface that is used to save the audit log objects (explained below) by the ABP. If you need to save the audit log objects to a custom data store, you can implement the `IAuditingStore` in your own application and replace using the [dependency injection system](../fundamentals/dependency-injection.md).

8
framework/src/Volo.Abp.Auditing.Contracts/Volo/Abp/Auditing/DisableAuditingAttribute.cs

@ -5,5 +5,13 @@ namespace Volo.Abp.Auditing;
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method | AttributeTargets.Property)]
public class DisableAuditingAttribute : Attribute
{
/// <summary>
/// When set to <c>false</c>, changes to this entity property will not update audit properties (like <see cref="AuditLogInfo.LastModificationTime"/>).
/// </summary>
public bool UpdateModificationProps { get; set; } = true;
/// <summary>
/// When set to <c>false</c>, changes to this entity property will not publish entity change events (<see cref="EntityUpdatedEvent"/>).
/// </summary>
public bool PublishEntityEvent { get; set; } = true;
}

21
framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs

@ -436,20 +436,29 @@ public abstract class AbpDbContext<TDbContext> : DbContext, IAbpEfCoreDbContext,
case EntityState.Modified:
if (entry.Properties.Any(x => x.IsModified && (x.Metadata.ValueGenerated == ValueGenerated.Never || x.Metadata.ValueGenerated == ValueGenerated.OnAdd)))
{
if (entry.Properties.Where(x => x.IsModified).All(x => x.Metadata.IsForeignKey()))
var modifiedProperties = entry.Properties.Where(x => x.IsModified).ToList();
if (modifiedProperties.All(x => x.Metadata.IsForeignKey()))
{
// Skip `PublishEntityDeletedEvent/PublishEntityUpdatedEvent` if only foreign keys have changed.
break;
}
ApplyAbpConceptsForModifiedEntity(entry);
if (entry.Entity is ISoftDelete && entry.Entity.As<ISoftDelete>().IsDeleted)
var disableAuditingAttributes = modifiedProperties.Select(x => x.Metadata.PropertyInfo?.GetCustomAttribute<DisableAuditingAttribute>()).ToList();
if (disableAuditingAttributes.Any(x => x == null || x.UpdateModificationProps))
{
EntityChangeEventHelper.PublishEntityDeletedEvent(entry.Entity);
ApplyAbpConceptsForModifiedEntity(entry);
}
else
if (disableAuditingAttributes.Any(x => x == null || x.PublishEntityEvent))
{
EntityChangeEventHelper.PublishEntityUpdatedEvent(entry.Entity);
if (entry.Entity is ISoftDelete && entry.Entity.As<ISoftDelete>().IsDeleted)
{
EntityChangeEventHelper.PublishEntityDeletedEvent(entry.Entity);
}
else
{
EntityChangeEventHelper.PublishEntityUpdatedEvent(entry.Entity);
}
}
}
else if (EntityChangeOptions.Value.PublishEntityUpdatedEventWhenNavigationChanges &&

77
framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/Auditing/Auditing_Tests.cs

@ -1,6 +1,10 @@
using System;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using NSubstitute;
using Shouldly;
using Volo.Abp.Domain.Entities.Events;
using Volo.Abp.TestApp;
using Volo.Abp.TestApp.Testing;
using Xunit;
@ -9,6 +13,16 @@ namespace Volo.Abp.EntityFrameworkCore.Auditing;
public class Auditing_Tests : Auditing_Tests<AbpEntityFrameworkCoreTestModule>
{
protected IEntityChangeEventHelper EntityChangeEventHelper;
protected override void AfterAddApplication(IServiceCollection services)
{
EntityChangeEventHelper = Substitute.For<IEntityChangeEventHelper>();
services.Replace(ServiceDescriptor.Singleton(EntityChangeEventHelper));
base.AfterAddApplication(services);
}
[Fact]
public async Task Should_Not_Set_Modification_If_Properties_Generated_By_Database()
{
@ -24,7 +38,6 @@ public class Auditing_Tests : Auditing_Tests<AbpEntityFrameworkCoreTestModule>
douglas.ShouldNotBeNull();
douglas.LastModificationTime.ShouldBeNull();
douglas.LastModificationTime.ShouldBeNull();
douglas.LastModifierId.ShouldBeNull();
}));
}
@ -71,4 +84,66 @@ public class Auditing_Tests : Auditing_Tests<AbpEntityFrameworkCoreTestModule>
douglas.LastModifierId.ShouldBe(CurrentUserId);
}));
}
[Fact]
public async Task Should_Not_Set_Modification_If_Properties_HasDisableAuditing_UpdateModificationProps()
{
await WithUnitOfWorkAsync((async () =>
{
var douglas = await PersonRepository.GetAsync(TestDataBuilder.UserDouglasId);
douglas.DisableAuditingUpdateModificationPropsProperty = Guid.NewGuid().ToString();
}));
await WithUnitOfWorkAsync((async () =>
{
var douglas = await PersonRepository.FindAsync(TestDataBuilder.UserDouglasId);
douglas.ShouldNotBeNull();
douglas.LastModificationTime.ShouldBeNull();
douglas.LastModifierId.ShouldBeNull();
}));
EntityChangeEventHelper.Received().PublishEntityUpdatedEvent(Arg.Any<object>());
}
[Fact]
public async Task Should_Not_PublishEntityEvent_If_Properties_HasDisableAuditing_PublishEntityEventProperty()
{
await WithUnitOfWorkAsync((async () =>
{
var douglas = await PersonRepository.GetAsync(TestDataBuilder.UserDouglasId);
douglas.DisableAuditingPublishEntityEventProperty = Guid.NewGuid().ToString();
}));
await WithUnitOfWorkAsync((async () =>
{
var douglas = await PersonRepository.FindAsync(TestDataBuilder.UserDouglasId);
douglas.ShouldNotBeNull();
douglas.LastModificationTime.ShouldNotBeNull();
}));
EntityChangeEventHelper.DidNotReceive().PublishEntityUpdatedEvent(Arg.Any<object>());
}
[Fact]
public async Task Should_Set_Modification_And_PublishEntityEvent_If_Properties_HasDisableAuditing()
{
await WithUnitOfWorkAsync((async () =>
{
var douglas = await PersonRepository.GetAsync(TestDataBuilder.UserDouglasId);
douglas.DisableAuditingProperty = Guid.NewGuid().ToString();
}));
await WithUnitOfWorkAsync((async () =>
{
var douglas = await PersonRepository.FindAsync(TestDataBuilder.UserDouglasId);
douglas.ShouldNotBeNull();
douglas.LastModificationTime.ShouldNotBeNull();
}));
EntityChangeEventHelper.Received().PublishEntityUpdatedEvent(Arg.Any<object>());
}
}

9
framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Domain/Person.cs

@ -34,6 +34,15 @@ public class Person : FullAuditedAggregateRoot<Guid>, IMultiTenant, IHasEntityVe
public int EntityVersion { get; set; }
[DisableAuditing(UpdateModificationProps = false)]
public string DisableAuditingUpdateModificationPropsProperty { get; set; }
[DisableAuditing(PublishEntityEvent = false)]
public string DisableAuditingPublishEntityEventProperty { get; set; }
[DisableAuditing]
public string DisableAuditingProperty { get; set; }
private Person()
{
}

Loading…
Cancel
Save