diff --git a/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Auditing/AbpAuditingMiddleware.cs b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Auditing/AbpAuditingMiddleware.cs
index 78ae28e63f..be84495336 100644
--- a/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Auditing/AbpAuditingMiddleware.cs
+++ b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Auditing/AbpAuditingMiddleware.cs
@@ -28,38 +28,46 @@ namespace Volo.Abp.AspNetCore.Auditing
public async Task InvokeAsync(HttpContext context, RequestDelegate next)
{
- if (!ShouldWriteAuditLog(context))
- {
- await next(context);
- return;
- }
-
+ bool hasError = false;
using (var scope = _auditingManager.BeginScope())
{
try
{
await next(context);
}
+ catch (Exception)
+ {
+ hasError = true;
+ throw;
+ }
finally
{
- await scope.SaveAsync();
+ if (ShouldWriteAuditLog(context, hasError))
+ {
+ await scope.SaveAsync();
+ }
}
}
}
- private bool ShouldWriteAuditLog(HttpContext httpContext)
+ private bool ShouldWriteAuditLog(HttpContext httpContext, bool hasError = false)
{
if (!Options.IsEnabled)
{
return false;
}
+ if (Options.AlwaysLogOnException && hasError)
+ {
+ return true;
+ }
+
if (!Options.IsEnabledForAnonymousUsers && !CurrentUser.IsAuthenticated)
{
return false;
}
- if (!Options.IsEnabledForGetRequests &&
+ if (!Options.IsEnabledForGetRequests &&
string.Equals(httpContext.Request.Method, HttpMethods.Get, StringComparison.OrdinalIgnoreCase))
{
return false;
diff --git a/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AbpAuditingOptions.cs b/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AbpAuditingOptions.cs
index da406fa490..db13bdaf74 100644
--- a/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AbpAuditingOptions.cs
+++ b/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AbpAuditingOptions.cs
@@ -30,6 +30,12 @@ namespace Volo.Abp.Auditing
/// Default: true.
///
public bool IsEnabledForAnonymousUsers { get; set; }
+
+ ///
+ /// Audit log on exceptions.
+ /// Default: true.
+ ///
+ public bool AlwaysLogOnException { get; set; }
public List Contributors { get; }
@@ -48,6 +54,7 @@ namespace Volo.Abp.Auditing
IsEnabled = true;
IsEnabledForAnonymousUsers = true;
HideErrors = true;
+ AlwaysLogOnException = true;
Contributors = new List();
diff --git a/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AuditingManager.cs b/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AuditingManager.cs
index 070684f48a..2a9dd8bf5a 100644
--- a/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AuditingManager.cs
+++ b/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AuditingManager.cs
@@ -23,8 +23,8 @@ namespace Volo.Abp.Auditing
private readonly IAuditingStore _auditingStore;
public AuditingManager(
- IAmbientScopeProvider ambientScopeProvider,
- IAuditingHelper auditingHelper,
+ IAmbientScopeProvider ambientScopeProvider,
+ IAuditingHelper auditingHelper,
IAuditingStore auditingStore,
IServiceProvider serviceProvider,
IOptions options)
@@ -84,7 +84,7 @@ namespace Volo.Abp.Auditing
{
var changeGroups = auditLog.EntityChanges
.Where(e => e.ChangeType == EntityChangeType.Updated)
- .GroupBy(e => new {e.EntityTypeFullName, e.EntityId})
+ .GroupBy(e => new { e.EntityTypeFullName, e.EntityId })
.ToList();
foreach (var changeGroup in changeGroups)
@@ -141,7 +141,7 @@ namespace Volo.Abp.Auditing
public DisposableSaveHandle(
AuditingManager auditingManager,
IDisposable scope,
- AuditLogInfo auditLog,
+ AuditLogInfo auditLog,
Stopwatch stopWatch)
{
_auditingManager = auditingManager;
diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Auditing/AuditTestController.cs b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Auditing/AuditTestController.cs
new file mode 100644
index 0000000000..9b4892cea4
--- /dev/null
+++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Auditing/AuditTestController.cs
@@ -0,0 +1,30 @@
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Options;
+using Volo.Abp.Auditing;
+
+namespace Volo.Abp.AspNetCore.Mvc.Auditing
+{
+ [Route("api/audit-test")]
+ [Audited]
+ public class AuditTestController : AbpController
+ {
+ private readonly AbpAuditingOptions _options;
+
+ public AuditTestController(IOptions options)
+ {
+ _options = options.Value;
+ }
+
+ [Route("audit-success")]
+ public IActionResult AuditSuccessForGetRequests()
+ {
+ return Ok();
+ }
+
+ [Route("audit-fail")]
+ public IActionResult AuditFailForGetRequests()
+ {
+ throw new UserFriendlyException("Exception occurred!");
+ }
+ }
+}
diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Auditing/AuditTestController_Tests.cs b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Auditing/AuditTestController_Tests.cs
new file mode 100644
index 0000000000..733fab3f49
--- /dev/null
+++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Auditing/AuditTestController_Tests.cs
@@ -0,0 +1,55 @@
+using System;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.DependencyInjection.Extensions;
+using Microsoft.Extensions.Hosting;
+using Microsoft.Extensions.Options;
+using NSubstitute;
+using System.Threading.Tasks;
+using Volo.Abp.Auditing;
+using Xunit;
+
+namespace Volo.Abp.AspNetCore.Mvc.Auditing
+{
+ public class AuditTestController_Tests : AspNetCoreMvcTestBase
+ {
+ private readonly AbpAuditingOptions _options;
+ private IAuditingStore _auditingStore;
+
+ public AuditTestController_Tests()
+ {
+ _options = ServiceProvider.GetRequiredService>().Value;
+ _auditingStore = ServiceProvider.GetRequiredService();
+ }
+
+ protected override void ConfigureServices(HostBuilderContext context, IServiceCollection services)
+ {
+ _auditingStore = Substitute.For();
+ services.Replace(ServiceDescriptor.Singleton(_auditingStore));
+ base.ConfigureServices(context, services);
+ }
+
+ [Fact]
+ public async Task Should_Trigger_Middleware_And_AuditLog_Success_For_GetRequests()
+ {
+ _options.IsEnabledForGetRequests = true;
+ _options.AlwaysLogOnException = false;
+ await GetResponseAsync("api/audit-test/audit-success");
+ await _auditingStore.Received().SaveAsync(Arg.Any()); //Won't work, save happens out of scope
+ }
+
+ [Fact]
+ public async Task Should_Trigger_Middleware_And_AuditLog_Exception_Always()
+ {
+ _options.IsEnabled = true;
+ _options.AlwaysLogOnException = true;
+
+ try
+ {
+ await GetResponseAsync("api/audit-test/audit-fail", System.Net.HttpStatusCode.Forbidden);
+ }
+ catch { }
+
+ await _auditingStore.Received().SaveAsync(Arg.Any()); //Won't work, save happens out of scope
+ }
+ }
+}
diff --git a/npm/ng-packs/packages/core/src/lib/actions/config.actions.ts b/npm/ng-packs/packages/core/src/lib/actions/config.actions.ts
index 56d0874ebd..8fd11bd2cb 100644
--- a/npm/ng-packs/packages/core/src/lib/actions/config.actions.ts
+++ b/npm/ng-packs/packages/core/src/lib/actions/config.actions.ts
@@ -1,4 +1,5 @@
import { ABP } from '../models/common';
+import { Config } from '../models/config';
export class PatchRouteByName {
static readonly type = '[Config] Patch Route By Name';
@@ -16,3 +17,8 @@ export class AddRoute {
static readonly type = '[Config] Add Route';
constructor(public payload: Omit) {}
}
+
+export class SetEnvironment {
+ static readonly type = '[Config] Set Environment';
+ constructor(public environment: Config.Environment) {}
+}
diff --git a/npm/ng-packs/packages/core/src/lib/services/config-state.service.ts b/npm/ng-packs/packages/core/src/lib/services/config-state.service.ts
index 506b278634..cf97e42b0f 100644
--- a/npm/ng-packs/packages/core/src/lib/services/config-state.service.ts
+++ b/npm/ng-packs/packages/core/src/lib/services/config-state.service.ts
@@ -1,8 +1,12 @@
import { Injectable } from '@angular/core';
import { Store } from '@ngxs/store';
+import {
+ AddRoute,
+ GetAppConfiguration,
+ PatchRouteByName,
+ SetEnvironment,
+} from '../actions/config.actions';
import { ConfigState } from '../states';
-import { GetAppConfiguration, PatchRouteByName, AddRoute } from '../actions/config.actions';
-import { ABP } from '../models';
@Injectable({
providedIn: 'root',
@@ -61,4 +65,8 @@ export class ConfigStateService {
dispatchAddRoute(...args: ConstructorParameters) {
return this.store.dispatch(new AddRoute(...args));
}
+
+ dispatchSetEnvironment(...args: ConstructorParameters) {
+ return this.store.dispatch(new SetEnvironment(...args));
+ }
}
diff --git a/npm/ng-packs/packages/core/src/lib/states/config.state.ts b/npm/ng-packs/packages/core/src/lib/states/config.state.ts
index debbc9b8a0..ff964cfe92 100644
--- a/npm/ng-packs/packages/core/src/lib/states/config.state.ts
+++ b/npm/ng-packs/packages/core/src/lib/states/config.state.ts
@@ -2,7 +2,12 @@ import { Action, createSelector, Selector, State, StateContext, Store } from '@n
import { of } from 'rxjs';
import { switchMap, tap } from 'rxjs/operators';
import snq from 'snq';
-import { GetAppConfiguration, PatchRouteByName, AddRoute } from '../actions/config.actions';
+import {
+ GetAppConfiguration,
+ PatchRouteByName,
+ AddRoute,
+ SetEnvironment,
+} from '../actions/config.actions';
import { SetLanguage } from '../actions/session.actions';
import { ABP } from '../models/common';
import { Config } from '../models/config';
@@ -291,6 +296,13 @@ export class ConfigState {
flattedRoutes,
});
}
+
+ @Action(SetEnvironment)
+ setEnvironment({ patchState }: StateContext, environment: Config.Environment) {
+ return patchState({
+ environment,
+ });
+ }
}
function patchRouteDeep(
diff --git a/npm/ng-packs/packages/theme-shared/src/lib/tests/table-sort.directive.spec.ts b/npm/ng-packs/packages/theme-shared/src/lib/tests/table-sort.directive.spec.ts
index f359518099..231f627fa1 100644
--- a/npm/ng-packs/packages/theme-shared/src/lib/tests/table-sort.directive.spec.ts
+++ b/npm/ng-packs/packages/theme-shared/src/lib/tests/table-sort.directive.spec.ts
@@ -1,16 +1,20 @@
import { SpectatorDirective, createDirectiveFactory } from '@ngneat/spectator/jest';
import { TableSortDirective } from '../directives/table-sort.directive';
+import { TableComponent } from '../components/table/table.component';
+import { DummyLocalizationPipe } from './table.component.spec';
+import { PaginationComponent } from '../components';
describe('TableSortDirective', () => {
let spectator: SpectatorDirective;
let directive: TableSortDirective;
const createDirective = createDirectiveFactory({
directive: TableSortDirective,
+ declarations: [TableComponent, DummyLocalizationPipe, PaginationComponent],
});
beforeEach(() => {
spectator = createDirective(
- ``,
+ ``,
);
directive = spectator.directive;
});
@@ -21,7 +25,7 @@ describe('TableSortDirective', () => {
test('should change table value', () => {
expect(directive.value).toEqual([1, 4, 2]);
- const table = spectator.query(Table);
+ const table = spectator.query(TableComponent);
expect(table.value).toEqual([1, 2, 4]);
});
});
diff --git a/npm/preview-publish.ps1 b/npm/preview-publish.ps1
index f10173226b..d448378750 100644
--- a/npm/preview-publish.ps1
+++ b/npm/preview-publish.ps1
@@ -4,7 +4,7 @@ param(
npm install
-$NextVersion = $(node get-version.js) + '-preview' + (Get-Date).tostring(“yyyyMMdd”)
+$NextVersion = $(node get-version.js) + '-preview' + (Get-Date).tostring(“yyyyMMdd”) + '-1'
$rootFolder = (Get-Item -Path "./" -Verbose).FullName
if(-Not $Version) {
@@ -16,7 +16,6 @@ $commands = (
"npm install",
"npm run publish-packages -- --nextVersion $Version --preview",
"cd ../../",
- "yarn",
"yarn lerna publish $Version --no-push --yes --no-git-reset --no-commit-hooks --no-git-tag-version --force-publish --dist-tag preview"
)
diff --git a/npm/publish.ps1 b/npm/publish.ps1
index 69f9ece982..a510527401 100644
--- a/npm/publish.ps1
+++ b/npm/publish.ps1
@@ -16,7 +16,6 @@ $commands = (
"npm install",
"npm run publish-packages -- --nextVersion $Version",
"cd ../../",
- "yarn",
"yarn lerna publish $Version --no-push --yes --no-git-reset --no-commit-hooks --no-git-tag-version --force-publish",
"yarn update:templates",
"yarn gulp:app",