Skip to content

Commit

Permalink
V27 (#693)
Browse files Browse the repository at this point in the history
* Changes for the version 27
  • Loading branch information
thepirat000 authored Sep 3, 2024
1 parent 0847315 commit a84b559
Show file tree
Hide file tree
Showing 50 changed files with 1,283 additions and 348 deletions.
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,18 @@ All notable changes to Audit.NET and its extensions will be documented in this f

The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/).

## [27.0.0] - 2024-09-03:
- Audit.NET: Introducing an `Items` collection in the `AuditScope` to store custom data accessible throughout the audit scope's lifecycle.
- Audit.NET: Refactoring `AuditScopeOptions` to eliminate the dependency on the static `Audit.Core.Configuration`.
- Audit.NET: Adding virtual methods `OnConfiguring`/`OnScopeCreated` method to the `AuditScopeFactory` to enable custom configuration of the audit scope.
- Audit.NET: Refactoring the `ISystemClock` interface to use a method instead of a property to get the current time.
- Audit.NET: Enabling Custom Actions to optionally return a boolean value, signaling whether to proceed with or halt the processing of subsequent actions of the same type.
- Audit.NET: Adding `ExcludeEnvironmentInfo` configuration to the `Audit.Core.Configuration` and `AuditScopeOptions` to allow excluding the environment information from the audit event.
- Audit.EntityFramework.Core: Enabling configuration of the `IAuditScopeFactory` and `AuditDataProvider` through dependency injection.
- Audit.WebApi.Core: Enabling configuration of the `IAuditScopeFactory` and `AuditDataProvider` through dependency injection.
- Audit.Mvc.Core: Enabling configuration of the `IAuditScopeFactory` and `AuditDataProvider` through dependency injection.
- Audit.SignalR: Enabling configuration of the `IAuditScopeFactory` and `AuditDataProvider` through dependency injection.

## [26.0.1] - 2024-08-22:
- Audit.WebApi.Core: Adding new SkipResponseBodyContent() configuration to allow deciding whether to skip or include the response body content **after** the action is executed (#690)

Expand Down
2 changes: 1 addition & 1 deletion Directory.Build.props
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<Project>
<PropertyGroup>
<Version>26.0.1</Version>
<Version>27.0.0</Version>
<PackageReleaseNotes></PackageReleaseNotes>
<CheckEolTargetFramework>false</CheckEolTargetFramework>
</PropertyGroup>
Expand Down
112 changes: 82 additions & 30 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,74 @@ The `AuditEvent` is typically serialized into a format suitable for storage or t

The audit events are stored using a **Data Provider**. You can use one of the [available data providers](https://github.com/thepirat000/Audit.NET?tab=readme-ov-file#storage-providers) or [implement your own](https://github.com/thepirat000/Audit.NET?tab=readme-ov-file#data-providers).

# SUPPORT FOR OLDER .NET FRAMEWORKS
### Audit Scope Factory

The preferred method for creating audit scopes is by using an `IAuditScopeFactory` instance.
This approach ensures a centralized and consistent configuration for all audit scopes.

```c#
var factory = new AuditScopeFactory();
using var scope = factory.Create(new AuditScopeOptions(...));
...
```

If you're using a DI container, you can register the `IAuditScopeFactory` as a service and inject it into your classes.
The default implementation of `IAuditScopeFactory` is provided by the `AuditScopeFactory` class.

```c#
services.AddSingleton<IAuditScopeFactory, AuditScopeFactory>();
```

Then you can inject the IAuditScopeFactory into your classes to create audit scopes:

```c#
public class MyService
{
private readonly IAuditScopeFactory _auditScopeFactory;

public MyService(IAuditScopeFactory auditScopeFactory)
{
_auditScopeFactory = auditScopeFactory;
}

public void MyMethod()
{
using var scope = _auditScopeFactory.Create(new AuditScopeOptions(...));
...
}
}
```

You can also implement your own `IAuditScopeFactory` to customize the creation of audit scopes.
The recommended approach is to inherit from the `AuditScopeFactory` class.
By overriding the `OnConfiguring` and `OnScopeCreated` methods, you can configure the audit scope options before creation and customize the audit scope after creation, respectively.

For example:

```c#
public class MyAuditScopeFactory : AuditScopeFactory
{
private readonly IMyService _myService;
public MyAuditScopeFactory(IMyService myService)
{
_myService = myService;
}

public override void OnConfiguring(AuditScopeOptions options)
{
// Set the data provider to use
options.DataProvider = new SqlDataProvider(...);
}

public override void OnScopeCreated(AuditScope auditScope)
{
// Add a custom field to the audit scope
auditScope.SetCustomField("CustomId", _myService.GetId());
}
}
```

# Support for older .NET frameworks

Beginning with version 23.0.0, this library and its extensions have discontinued support for older .NET Framework and Entity Framework (versions that lost Microsoft support before 2023).

Expand All @@ -96,46 +163,22 @@ This library and its extensions will maintain support for the following **minimu
- .NET Standard 2.0 (netstandard2.0)
- .NET 6 (net6.0)

The following frameworks were **deprecated and removed** from the list of target frameworks:

- net45, net451, net452, net461
- netstandard1.3, netstandard1.4, netstandard1.5, netstandard1.6
- netcoreapp2.1, netcoreapp3.0
- net5.0

This discontinuation led to the following modifications:

- All library versions will now use `System.Text.Json` as the default (Newtonsoft.Json will be deprecated but can still be used through the JsonAdapter).
- Support for EF Core versions 3 and earlier has been discontinued in the `Audit.EntityFramework.Core` libraries. The minimum supported version is now EF Core 5 (`Audit.EntityFramework` will continue to support .NET Entity Framework 6).
- The libraries `Audit.EntityFramework.Core.v3` and `Audit.EntityFramework.Identity.Core.v3` has been deprecated.
- `Audit.NET.JsonSystemAdapter` has been deprecated.


## Usage

The **Audit Scope** is the central object of this framework. It encapsulates an audit event, controlling its life cycle.
The **Audit Event** is an extensible information container of an audited operation.


There are several ways to create an Audit Scope:

- Calling the `Create()` / `CreateAsync()` method of an `AuditScopeFactory` instance, for example:
- Calling the `Create()` / `CreateAsync()` methods of an `AuditScopeFactory` instance.
This is the recommended approach. For example:

```c#
var factory = new AuditScopeFactory();
var scope = factory.Create(new AuditScopeOptions(...));
var scope = auditScopeFactory.Create(new AuditScopeOptions(...));
```

- Using the overloads of the static methods `Create()` / `CreateAsync()` on `AuditScope`, for example:

```c#
var scope = AuditScope.Create("Order:Update", () => order, new { MyProperty = "value" });
```

The first parameter of the `AuditScope.Create` method is an _event type name_ intended to identify and group the events. The second is the delegate to obtain the object to track (target object). This object is passed as a `Func<object>` to allow the library to inspect the value at the beginning and the disposal of the scope. It is not mandatory to supply a target object.

You can use the overload that accepts an `AuditScopeOptions` instance to configure any of the available options for the scope:

```c#
var scope = AuditScope.Create(new AuditScopeOptions()
{
Expand Down Expand Up @@ -167,6 +210,7 @@ IsCreateAndSave | `bool` | Value indicating whether this scope should be immedia
AuditEvent | `AuditEvent` | Custom initial audit event to use. By default it will create a new instance of basic `AuditEvent`
SkipExtraFrames | `int` | Value used to indicate how many frames in the stack should be skipped to determine the calling method. Default is 0
CallingMethod | `MethodBase` | Specific calling method to store on the event. Default is to use the calling stack to determine the calling method.
Items | `Dictionary<string, object>` | A dictionary of items that can be used to store additional information on the scope, accessible from the `AuditScope` instance.

Suppose you have the following code to _cancel an order_ that you want to audit:

Expand Down Expand Up @@ -732,21 +776,29 @@ Audit.Core.Configuration.CreationPolicy = EventCreationPolicy.Manual;
> If you don't specify a Creation Policy, the default `Insert on End` will be used.
### Custom Actions
You can configure Custom Actions that are executed for all the Audit Scopes in your application. This allows to globally change the behavior and data, intercepting the scopes after they are created or before they are saved.
You can configure Custom Actions to be executed across all Audit Scopes in your application.
This allows you to globally modify behavior and data, intercepting scopes after they are created, before they are saved, or during disposal

Call the static `AddCustomAction()` method on `Audit.Core.Configuration` class to attach a custom action.

For example, to globally discard the events under a certain condition:
```c#
Audit.Core.Configuration.AddCustomAction(ActionType.OnScopeCreated, scope =>
{
if (DateTime.Now.Hour == 17) // Tea time
if (DateTime.Now.Hour >= 17)
{
scope.Discard();
return false;
}
return true;
});
```

> **Note**
>
> The custom actions can return a boolean value to indicate if subsequent actions of the same type should be executed.

Or to add custom fields/comments globally to all scopes:
```c#
Audit.Core.Configuration.AddCustomAction(ActionType.OnEventSaving, scope =>
Expand Down
51 changes: 47 additions & 4 deletions src/Audit.EntityFramework/DbContextHelper.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
#if EF_CORE
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.ChangeTracking;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.Extensions.DependencyInjection;
#else
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
Expand Down Expand Up @@ -310,12 +312,15 @@ public IAuditScope CreateAuditScope(IAuditDbContext context, EntityFrameworkEven
{
auditEfEvent.CustomFields = new Dictionary<string, object>(context.ExtraFields);
}
var factory = context.AuditScopeFactory ?? Core.Configuration.AuditScopeFactory;

var factory = GetAuditScopeFactory(context.DbContext);
var dataProvider = GetDataProvider(context.DbContext);

var options = new AuditScopeOptions()
{
EventType = eventType,
CreationPolicy = EventCreationPolicy.Manual,
DataProvider = context.AuditDataProvider,
DataProvider = dataProvider,
AuditEvent = auditEfEvent,
#if NETSTANDARD2_1_OR_GREATER || NET6_0_OR_GREATER
SkipExtraFrames = 5
Expand Down Expand Up @@ -345,12 +350,15 @@ public async Task<IAuditScope> CreateAuditScopeAsync(IAuditDbContext context, En
{
auditEfEvent.CustomFields = new Dictionary<string, object>(context.ExtraFields);
}
var factory = context.AuditScopeFactory ?? Core.Configuration.AuditScopeFactory;

var factory = GetAuditScopeFactory(context.DbContext);
var dataProvider = GetDataProvider(context.DbContext);

var options = new AuditScopeOptions()
{
EventType = eventType,
CreationPolicy = EventCreationPolicy.Manual,
DataProvider = context.AuditDataProvider,
DataProvider = dataProvider,
AuditEvent = auditEfEvent,
SkipExtraFrames = 3
};
Expand All @@ -359,6 +367,41 @@ public async Task<IAuditScope> CreateAuditScopeAsync(IAuditDbContext context, En
return scope;
}

internal IAuditScopeFactory GetAuditScopeFactory(DbContext dbContext)
{
var auditDbContext = dbContext as IAuditDbContext;
#if EF_CORE
return auditDbContext?.AuditScopeFactory ?? TryGetService<IAuditScopeFactory>(dbContext) ?? Core.Configuration.AuditScopeFactory;
#else
return auditDbContext?.AuditScopeFactory ?? Core.Configuration.AuditScopeFactory;
#endif
}

internal AuditDataProvider GetDataProvider(DbContext dbContext)
{
var auditDbContext = dbContext as IAuditDbContext;
#if EF_CORE
return auditDbContext?.AuditDataProvider ?? TryGetService<AuditDataProvider>(dbContext);
#else
return auditDbContext?.AuditDataProvider;
#endif
}

#if EF_CORE
private T TryGetService<T>(DbContext dbContext) where T : class
{
var infrastructure = dbContext?.GetInfrastructure();

// Based on EF Core code from: https://github.com/dotnet/efcore/blob/ecfee78eb1fa2b2eaa0dbf945f1d4f8fa571be74/src/EFCore/Infrastructure/Internal/InfrastructureExtensions.cs#L32
var service =
infrastructure?.GetService(typeof(T)) ??
infrastructure?.GetService<IDbContextOptions>()?.Extensions.OfType<CoreOptionsExtension>()
.FirstOrDefault()?.ApplicationServiceProvider?.GetService(typeof(T));

return service as T;
}
#endif

/// <summary>
/// Gets the modified entries to process.
/// </summary>
Expand Down
14 changes: 10 additions & 4 deletions src/Audit.EntityFramework/Interceptors/AuditCommandInterceptor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
using System.Threading;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Internal;

namespace Audit.EntityFramework.Interceptors
{
Expand Down Expand Up @@ -363,13 +365,15 @@ private IAuditScope CreateAuditScope(AuditEventCommandEntityFramework cmdEvent)
cmdEvent.CustomFields = new Dictionary<string, object>(context.ExtraFields);
}

var factory = context?.AuditScopeFactory ?? Core.Configuration.AuditScopeFactory;
var factory = _dbContextHelper.GetAuditScopeFactory(cmdEvent.CommandEvent.DbContext);
var dataProvider = _dbContextHelper.GetDataProvider(cmdEvent.CommandEvent.DbContext);

var options = new AuditScopeOptions()
{
EventType = eventType,
AuditEvent = cmdEvent,
SkipExtraFrames = 3,
DataProvider = context?.AuditDataProvider
DataProvider = dataProvider
};

var scope = factory.Create(options);
Expand All @@ -396,14 +400,16 @@ private async Task<IAuditScope> CreateAuditScopeAsync(AuditEventCommandEntityFra
{
cmdEvent.CustomFields = new Dictionary<string, object>(context.ExtraFields);
}

var factory = _dbContextHelper.GetAuditScopeFactory(cmdEvent.CommandEvent.DbContext);
var dataProvider = _dbContextHelper.GetDataProvider(cmdEvent.CommandEvent.DbContext);

var factory = context?.AuditScopeFactory ?? Core.Configuration.AuditScopeFactory;
var options = new AuditScopeOptions()
{
EventType = eventType,
AuditEvent = cmdEvent,
SkipExtraFrames = 3,
DataProvider = context?.AuditDataProvider
DataProvider = dataProvider
};

var scope = await factory.CreateAsync(options, cancellationToken);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using System.Data.Common;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore.Infrastructure;

namespace Audit.EntityFramework.Interceptors
{
Expand Down Expand Up @@ -310,13 +311,15 @@ private IAuditScope CreateAuditScope(AuditEventTransactionEntityFramework tranEv
tranEvent.CustomFields = new Dictionary<string, object>(context.ExtraFields);
}

var factory = context?.AuditScopeFactory ?? Core.Configuration.AuditScopeFactory;
var factory = _dbContextHelper.GetAuditScopeFactory(tranEvent.TransactionEvent.DbContext);
var dataProvider = _dbContextHelper.GetDataProvider(tranEvent.TransactionEvent.DbContext);

var options = new AuditScopeOptions()
{
EventType = eventType,
AuditEvent = tranEvent,
SkipExtraFrames = 3,
DataProvider = context?.AuditDataProvider
DataProvider = dataProvider
};

var scope = factory.Create(options);
Expand All @@ -339,13 +342,15 @@ private async Task<IAuditScope> CreateAuditScopeAsync(AuditEventTransactionEntit
tranEvent.CustomFields = new Dictionary<string, object>(context.ExtraFields);
}

var factory = context?.AuditScopeFactory ?? Core.Configuration.AuditScopeFactory;
var factory = _dbContextHelper.GetAuditScopeFactory(tranEvent.TransactionEvent.DbContext);
var dataProvider = _dbContextHelper.GetDataProvider(tranEvent.TransactionEvent.DbContext);

var options = new AuditScopeOptions()
{
EventType = eventType,
AuditEvent = tranEvent,
SkipExtraFrames = 3,
DataProvider = context?.AuditDataProvider
DataProvider = dataProvider
};

var scope = await factory.CreateAsync(options, cancellationToken);
Expand Down
Loading

0 comments on commit a84b559

Please sign in to comment.