Skip to content

Commit

Permalink
Merge pull request #322 from thepirat000/AuditScopeFactory
Browse files Browse the repository at this point in the history
Adding AuditScopeFactory, IAuditScopeFactory and IAuditScope
  • Loading branch information
thepirat000 authored Aug 7, 2020
2 parents 6607a05 + b0c4d11 commit f71bf18
Show file tree
Hide file tree
Showing 76 changed files with 657 additions and 335 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@ 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/).

## [16.0.0] - 2020-08-06
### Modified
- Audit.NET: Moving the AuditScope creation to an `AuditScopeFactory` implementing an interface `IAuditScopeFactory`.
Also added `IAuditScope` interface for unit testing. (#315, #319)
- Audit.NET: Enable disposable async for netstandard2.0 (#318)

## [15.2.4] - 2020-07-22
### Modified
- Audit.EntityFramework.Core: Changing version for Microsoft.EntityFrameworkCore reference when targeting .NET Standard 2.0 or 2.1 (now referencing Microsoft.EntityFrameworkCore 3.1.0) (#310)
Expand Down
85 changes: 54 additions & 31 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,50 @@ The **Audit Scope** is the central object of this framework. It encapsulates an
The **Audit Event** is an extensible information container of an audited operation.
See the [audit scope statechart](#auditscope-statechart).

Create an Audit Scope by calling the static `AuditScope.Create` method.



There are several ways to create an Audit Scope:

- Calling the `Create()` / `CreateAsync()` method of an `AuditScopeFactory` instance, for example:

```c#
var factory = new AuditScopeFactory();
var scope = factory.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 at 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()
{
EventType = "Order:Update",
TargetGetter = () => order,
ExtraFields = new { MyProperty = "value" }
});
```

#### AuditScopeOptions properties

Option field | Type | Description
------------ | ---------------- | ----------------
EventType | `string` | A string representing the type of the event
TargetGetter | `Func<object>` | Target object getter (a func that returns the object to track)
ExtraFields | `obejct` | Anonymous object that contains additional fields to be merged into the audit event
DataProvider | `AuditDataProvider` | The [data provider](#data-providers) to use. Defaults to the DataProvider configured on `Audit.Core.Configuration.DataProvider`
CreationPolicy | `EventCreationPolicy` | The [creation policy](#creation-policy) to use. Default is `InsertOnEnd`
IsCreateAndSave | `bool` | Value indicating whether this scope should be immediately ended and saved after creation. Default is false
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.

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

```c#
Expand All @@ -62,24 +104,6 @@ using (AuditScope.Create("Order:Update", () => order))

> It is not mandatory to use a `using` block, but it simplifies the syntax when the code to audit is on a single block, allowing the detection of exceptions and calculating the duration by implicitly saving the event on disposal.
The first parameter of the `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 at the disposal of the scope. It is not mandatory to supply a target object, pass `null` when you don't want to track a specific object.

There is also a unified overload of the `Create` method that accepts an instance of [`AuditScopeOptions`](https://github.com/thepirat000/Audit.NET/blob/master/src/Audit.NET/AuditScopeOptions.cs). Use this class to configure any of the available options for the scope:

```c#
var options = new AuditScopeOptions()
{
EventType = "MyEvent",
CreationPolicy = EventCreationPolicy.Manual,
ExtraFields = new { Action = this.Action },
AuditEvent = new MyCustomAuditEvent()
};
using (var scope = AuditScope.Create(options))
{
// ...
}
```

> When using the [extensions](#extensions) that logs interactions with different systems, like [Audit.EntityFramework](https://github.com/thepirat000/Audit.NET/blob/master/src/Audit.EntityFramework/README.md), [Audit.WebApi](https://github.com/thepirat000/Audit.NET/blob/master/src/Audit.WebApi/README.md), etc. you don't need to explicitly create the `AuditScope` or `AuditEvent`, they are created internally by the extension.
### Simple logging
Expand All @@ -104,7 +128,12 @@ public class SomethingThatStartsAndEnds
public void Start()
{
// Create a manual scope
auditScope = AuditScope.Create("MyEvent", () => Status, EventCreationPolicy.Manual);
auditScope = AuditScope.Create(new AuditScopeOptions()
{
EventType = "MyEvent",
TargetGetter = () => this.Status,
CreationPolicy = EventCreationPolicy.Manual
});
}

public void End()
Expand Down Expand Up @@ -140,7 +169,7 @@ public async Task SaveOrderAsync(Order order)
}
```

> Note: On older .NET framework versions [the `Dispose` method was always synchronous](https://github.com/dotnet/roslyn/issues/114), so if your audit code is on async methods and you created the scope within a `using` statement, you should explicitly call the `DisposeAsync()` method. For .NET Core starting on version 3.0 and C# 8, you can simply use the `await using` statement, since the `AuditScope` implements the [`IAsyncDisposable` interface](https://docs.microsoft.com/en-us/dotnet/api/system.iasyncdisposable).
> Note: On older .NET framework versions [the `Dispose` method was always synchronous](https://github.com/dotnet/roslyn/issues/114), so if your audit code is on async methods and you created the scope within a `using` statement, you should explicitly call the `DisposeAsync()` method. For projects targeting .NET Standard starting on version 2.0 and C# 8, you can simply use the `await using` statement, since the `AuditScope` implements the [`IAsyncDisposable` interface](https://docs.microsoft.com/en-us/dotnet/api/system.iasyncdisposable).
## Output

Expand Down Expand Up @@ -317,7 +346,7 @@ The `AuditScope` object has a `Discard()` method to allow the user to discard an
For example, if you want to avoid saving the audit event under certain condition:

```c#
using (var scope = AuditScope.Create("SomeEvent", () => someTarget))
using (var scope = AuditScope.Create(new AuditScopeOptions("SomeEvent", () => someTarget)))
{
try
{
Expand Down Expand Up @@ -409,15 +438,9 @@ Audit.Core.Configuration.Setup()

See [Configuration section](#configuration) for more information.

You can also set the data provider per-scope, by using an appropriate overload of the `AuditScope.Create` method. For example:
```c#
AuditScope.Create("Order:Update", () => order, EventCreationPolicy.Manual, new MyCustomDataProvider());
```

Or:

You can also set the data provider per-scope. For example:
```c#
AuditScope.Create(new AuditScopeOptions { DataProvider = new MyCustomDataProvider() });
AuditScope.Create(new AuditScopeOptions { DataProvider = new MyCustomDataProvider(), ... });
```


Expand Down
4 changes: 2 additions & 2 deletions src/Audit.DynamicProxy/Audit.DynamicProxy.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<Description>Generate Audit Logs by intercepting operation calls on any class without changing its code.</Description>
<Copyright>Copyright 2019</Copyright>
<AssemblyTitle>Audit.DynamicProxy</AssemblyTitle>
<VersionPrefix>15.3.0</VersionPrefix>
<VersionPrefix>16.0.0</VersionPrefix>
<Authors>Federico Colombo</Authors>
<TargetFrameworks>netstandard1.5;net45</TargetFrameworks>
<DefineConstants>$(DefineConstants);STRONG_NAME</DefineConstants>
Expand All @@ -31,7 +31,7 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="Castle.Core" Version="4.3.0" />
<PackageReference Include="Castle.Core" Version="4.4.1" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.2" />
</ItemGroup>

Expand Down
11 changes: 6 additions & 5 deletions src/Audit.DynamicProxy/AuditInterceptor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ public class AuditInterceptor : IInterceptor
/// <summary>
/// Intercept an asynchronous operation that returns a Task.
/// </summary>
private static async Task InterceptAsync(Task task, IInvocation invocation, InterceptEvent intEvent, AuditScope scope)
private static async Task InterceptAsync(Task task, IInvocation invocation, InterceptEvent intEvent, IAuditScope scope)
{
try
{
Expand All @@ -53,7 +53,7 @@ private static async Task InterceptAsync(Task task, IInvocation invocation, Inte
/// <summary>
/// Intercept an asynchronous operation that returns a Task Of[T].
/// </summary>
private static async Task<T> InterceptAsync<T>(Task<T> task, IInvocation invocation, InterceptEvent intEvent, AuditScope scope)
private static async Task<T> InterceptAsync<T>(Task<T> task, IInvocation invocation, InterceptEvent intEvent, IAuditScope scope)
{
T result;
try
Expand All @@ -72,7 +72,7 @@ private static async Task<T> InterceptAsync<T>(Task<T> task, IInvocation invocat
/// <summary>
/// Ends the event for asynchronous interceptions.
/// </summary>
private static void EndAsyncAuditInterceptEvent(Task task, IInvocation invocation, InterceptEvent intEvent, AuditScope scope, object result)
private static void EndAsyncAuditInterceptEvent(Task task, IInvocation invocation, InterceptEvent intEvent, IAuditScope scope, object result)
{
intEvent.AsyncStatus = task.Status.ToString();
if (task.Status == TaskStatus.Faulted)
Expand Down Expand Up @@ -220,7 +220,8 @@ public void Intercept(IInvocation invocation)
DataProvider = Settings.AuditDataProvider,
AuditEvent = auditEventIntercept
};
var scope = AuditScope.Create(scopeOptions);
var auditScopeFactory = Settings.AuditScopeFactory ?? Configuration.AuditScopeFactory;
var scope = auditScopeFactory.Create(scopeOptions);
AuditProxy.CurrentScope = scope;
// Call the intercepted method (sync part)
try
Expand All @@ -239,7 +240,7 @@ public void Intercept(IInvocation invocation)
{
if (typeof(Task).IsAssignableFrom(returnType))
{
invocation.ReturnValue = InterceptAsync((dynamic) invocation.ReturnValue, invocation, intEvent, scope);
invocation.ReturnValue = InterceptAsync((dynamic)invocation.ReturnValue, invocation, intEvent, scope);
return;
}
}
Expand Down
4 changes: 2 additions & 2 deletions src/Audit.DynamicProxy/AuditProxy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,15 @@ public class AuditProxy
{
private static readonly ProxyGenerator Generator = new ProxyGenerator();
[ThreadStatic]
private static AuditScope _currentScope;
private static IAuditScope _currentScope;

/// <summary>
/// Gets the scope for the current running thread. Get this property value from the audited methods to customize the Audit.
/// This property should be accessed from the thread that is executing the audited operation.
/// Calling this property from a different thread will return NULL or an unexpected value.
/// </summary>
/// <value>The current scope related to the running thread, or NULL.</value>
public static AuditScope CurrentScope
public static IAuditScope CurrentScope
{
get => _currentScope;
internal set => _currentScope = value;
Expand Down
4 changes: 4 additions & 0 deletions src/Audit.DynamicProxy/InterceptionSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,5 +40,9 @@ public class InterceptionSettings
/// Gets or sets the event creation policy to use for this interception. Default is NULL to use the globally configured creation policy.
/// </summary>
public EventCreationPolicy? EventCreationPolicy { get; set; }
/// <summary>
/// Gets or sets the custom audit scope factory. Default is NULL to use the general AuditScopeFactory.
/// </summary>
public IAuditScopeFactory AuditScopeFactory { get; set; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<Description>Generate Audit Logs from EntityFramework context changes</Description>
<Copyright>Copyright 2016</Copyright>
<AssemblyTitle>Audit.EntityFramework.Core</AssemblyTitle>
<VersionPrefix>15.3.0</VersionPrefix>
<VersionPrefix>16.0.0</VersionPrefix>
<Authors>Federico Colombo</Authors>
<TargetFrameworks>netstandard1.5;netstandard2.0;net461;netstandard2.1;net472</TargetFrameworks>
<DefineConstants>$(DefineConstants);STRONG_NAME;EF_CORE</DefineConstants>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<Description>Generate Audit Logs from EntityFramework identity context changes</Description>
<Copyright>Copyright 2018</Copyright>
<AssemblyTitle>Audit.EntityFramework.Identity.Core</AssemblyTitle>
<VersionPrefix>15.3.0</VersionPrefix>
<VersionPrefix>16.0.0</VersionPrefix>
<Authors>Federico Colombo</Authors>
<TargetFrameworks>netstandard1.5;netstandard2.1;net461</TargetFrameworks>
<DefineConstants>$(DefineConstants);STRONG_NAME</DefineConstants>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<Description>Generate Audit Logs from EntityFramework identity context changes</Description>
<Copyright>Copyright 2018</Copyright>
<AssemblyTitle>Audit.EntityFramework.Identity</AssemblyTitle>
<VersionPrefix>15.3.0</VersionPrefix>
<VersionPrefix>16.0.0</VersionPrefix>
<Authors>Federico Colombo</Authors>
<TargetFrameworks>netstandard1.5;netstandard2.0;net45;netstandard2.1</TargetFrameworks>
<DefineConstants>$(DefineConstants);STRONG_NAME</DefineConstants>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,11 @@ public AuditIdentityDbContext(DbConnection existingConnection, DbCompiledModel m
/// </summary>
public AuditDataProvider AuditDataProvider { get; set; }

/// <summary>
/// To indicate a custom audit scope factory. (Default is NULL to use the Audit.Core.Configuration.DefaultAuditScopeFactory).
/// </summary>
public IAuditScopeFactory AuditScopeFactory { get; set; }

/// <summary>
/// Indicates if the Audit is disabled.
/// Default is false.
Expand Down Expand Up @@ -192,7 +197,7 @@ public AuditIdentityDbContext(DbConnection existingConnection, DbCompiledModel m
/// Override to specify custom logic.
/// </summary>
/// <param name="auditScope">The audit scope.</param>
public virtual void OnScopeCreated(AuditScope auditScope)
public virtual void OnScopeCreated(IAuditScope auditScope)
{
}

Expand All @@ -201,7 +206,7 @@ public virtual void OnScopeCreated(AuditScope auditScope)
/// Override to specify custom logic.
/// </summary>
/// <param name="auditScope">The audit scope.</param>
public virtual void OnScopeSaving(AuditScope auditScope)
public virtual void OnScopeSaving(IAuditScope auditScope)
{
}

Expand Down
9 changes: 7 additions & 2 deletions src/Audit.EntityFramework.Identity/AuditIdentityDbContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,11 @@ public AuditIdentityDbContext(DbConnection existingConnection, DbCompiledModel m
/// </summary>
public AuditDataProvider AuditDataProvider { get; set; }

/// <summary>
/// To indicate a custom audit scope factory. (Default is NULL to use the Audit.Core.Configuration.DefaultAuditScopeFactory).
/// </summary>
public IAuditScopeFactory AuditScopeFactory { get; set; }

/// <summary>
/// Indicates if the Audit is disabled.
/// Default is false.
Expand Down Expand Up @@ -180,7 +185,7 @@ public AuditIdentityDbContext(DbConnection existingConnection, DbCompiledModel m
/// Override to specify custom logic.
/// </summary>
/// <param name="auditScope">The audit scope.</param>
public virtual void OnScopeCreated(AuditScope auditScope)
public virtual void OnScopeCreated(IAuditScope auditScope)
{
}

Expand All @@ -189,7 +194,7 @@ public virtual void OnScopeCreated(AuditScope auditScope)
/// Override to specify custom logic.
/// </summary>
/// <param name="auditScope">The audit scope.</param>
public virtual void OnScopeSaving(AuditScope auditScope)
public virtual void OnScopeSaving(IAuditScope auditScope)
{
}

Expand Down
2 changes: 1 addition & 1 deletion src/Audit.EntityFramework/Audit.EntityFramework.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<Description>Generate Audit Logs from EntityFramework context changes</Description>
<Copyright>Copyright 2016</Copyright>
<AssemblyTitle>Audit.EntityFramework</AssemblyTitle>
<VersionPrefix>15.3.0</VersionPrefix>
<VersionPrefix>16.0.0</VersionPrefix>
<Authors>Federico Colombo</Authors>
<TargetFrameworks>netstandard1.5;netstandard2.0;net45;netstandard2.1;net472</TargetFrameworks>
<DefineConstants>$(DefineConstants);STRONG_NAME</DefineConstants>
Expand Down
9 changes: 7 additions & 2 deletions src/Audit.EntityFramework/AuditDbContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,11 @@ protected AuditDbContext() : base()
/// </summary>
public virtual AuditDataProvider AuditDataProvider { get; set; }

/// <summary>
/// To indicate a custom audit scope factory. (Default is NULL to use the Audit.Core.Configuration.DefaultAuditScopeFactory).
/// </summary>
public virtual IAuditScopeFactory AuditScopeFactory { get; set; }

/// <summary>
/// Optional custom fields added to the audit event
/// </summary>
Expand Down Expand Up @@ -149,15 +154,15 @@ protected AuditDbContext() : base()
/// Override to specify custom logic.
/// </summary>
/// <param name="auditScope">The audit scope.</param>
public virtual void OnScopeCreated(AuditScope auditScope)
public virtual void OnScopeCreated(IAuditScope auditScope)
{
}
/// <summary>
/// Called after the EF operation execution and before the AuditScope saving.
/// Override to specify custom logic.
/// </summary>
/// <param name="auditScope">The audit scope.</param>
public virtual void OnScopeSaving(AuditScope auditScope)
public virtual void OnScopeSaving(IAuditScope auditScope)
{
}

Expand Down
2 changes: 1 addition & 1 deletion src/Audit.EntityFramework/AuditEventExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ public static class AuditEventExtensions
/// Gets the Entity Framework Event portion of the Audit Event on the given scope.
/// </summary>
/// <param name="auditScope">The audit scope.</param>
public static EntityFrameworkEvent GetEntityFrameworkEvent(this AuditScope auditScope)
public static EntityFrameworkEvent GetEntityFrameworkEvent(this IAuditScope auditScope)
{
return auditScope?.Event.GetEntityFrameworkEvent();
}
Expand Down
Loading

0 comments on commit f71bf18

Please sign in to comment.