diff --git a/Source/Csla.TestHelpers/TestDIContextFactory.cs b/Source/Csla.TestHelpers/TestDIContextFactory.cs
index 6a7f9b6084..7300b9af68 100644
--- a/Source/Csla.TestHelpers/TestDIContextFactory.cs
+++ b/Source/Csla.TestHelpers/TestDIContextFactory.cs
@@ -25,7 +25,7 @@ public static class TestDIContextFactory
/// Create a test DI context for testing with a default authenticated user
///
/// A TestDIContext that can be used to perform testing dependent upon DI
- public static TestDIContext CreateDefaultContext()
+ public static TestDIContext CreateDefaultContext(Action configureServices = null)
{
ClaimsPrincipal principal;
@@ -33,7 +33,7 @@ public static TestDIContext CreateDefaultContext()
principal = CreateDefaultClaimsPrincipal();
// Delegate to the other overload to create the context
- return CreateContext(principal);
+ return CreateContext(principal, configureServices);
}
///
@@ -41,9 +41,9 @@ public static TestDIContext CreateDefaultContext()
///
/// The principal which is to be set as the security context for Csla operations
/// A TestDIContext that can be used to perform testing dependent upon DI
- public static TestDIContext CreateContext(ClaimsPrincipal principal)
+ public static TestDIContext CreateContext(ClaimsPrincipal principal, Action configureServices = null)
{
- return CreateContext(null, principal);
+ return CreateContext(null, principal, configureServices);
}
///
@@ -65,7 +65,7 @@ public static TestDIContext CreateContext(Action customCslaOptions)
/// The options action that is used by the consumer to configure Csla
/// The principal which is to be set as the security context for Csla operations
/// A TestDIContext that can be used to perform testing dependent upon DI
- public static TestDIContext CreateContext(Action customCslaOptions, ClaimsPrincipal principal)
+ public static TestDIContext CreateContext(Action customCslaOptions, ClaimsPrincipal principal, Action configureServices = null)
{
IServiceProvider serviceProvider;
ApplicationContext context;
@@ -77,6 +77,7 @@ public static TestDIContext CreateContext(Action customCslaOptions,
services.TryAddSingleton();
services.AddSingleton();
services.AddCsla(customCslaOptions);
+ configureServices?.Invoke(services);
serviceProvider = services.BuildServiceProvider();
diff --git a/Source/Csla/Configuration/Fluent/DataPortalConfigurationExtensions.cs b/Source/Csla/Configuration/Fluent/DataPortalConfigurationExtensions.cs
index 8a97e0277f..ded8577f32 100644
--- a/Source/Csla/Configuration/Fluent/DataPortalConfigurationExtensions.cs
+++ b/Source/Csla/Configuration/Fluent/DataPortalConfigurationExtensions.cs
@@ -8,6 +8,7 @@
using Csla.DataPortalClient;
using Csla.Server;
+using Csla.Server.Interceptors.ServerSide;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
@@ -100,6 +101,7 @@ internal static void AddRequiredDataPortalServices(this CslaOptions config, ISer
services.TryAddTransient(typeof(IChildDataPortal<>), typeof(DataPortal<>));
services.TryAddTransient();
services.TryAddTransient();
+ services.AddOptions();
services.TryAddScoped(typeof(IAuthorizeDataPortal), config.DataPortalOptions.DataPortalServerOptions.AuthorizerProviderType);
foreach (Type interceptorType in config.DataPortalOptions.DataPortalServerOptions.InterceptorProviders)
diff --git a/Source/Csla/Csla.csproj b/Source/Csla/Csla.csproj
index 6a08769fda..b93a0b0d12 100644
--- a/Source/Csla/Csla.csproj
+++ b/Source/Csla/Csla.csproj
@@ -61,6 +61,7 @@
+
@@ -85,6 +86,7 @@
+
@@ -94,6 +96,7 @@
+
@@ -103,6 +106,7 @@
+
all
diff --git a/Source/Csla/Server/Interceptors/ServerSide/RevalidatingInterceptor.cs b/Source/Csla/Server/Interceptors/ServerSide/RevalidatingInterceptor.cs
index f63614090b..60edace5ea 100644
--- a/Source/Csla/Server/Interceptors/ServerSide/RevalidatingInterceptor.cs
+++ b/Source/Csla/Server/Interceptors/ServerSide/RevalidatingInterceptor.cs
@@ -9,6 +9,7 @@
using System.Collections;
using Csla.Core;
using Csla.Properties;
+using Microsoft.Extensions.Options;
namespace Csla.Server.Interceptors.ServerSide
{
@@ -18,15 +19,18 @@ namespace Csla.Server.Interceptors.ServerSide
public class RevalidatingInterceptor : IInterceptDataPortal
{
private readonly ApplicationContext _applicationContext;
+ private readonly RevalidatingInterceptorOptions _options;
///
/// Public constructor, intended to be executed by DI
///
/// The context under which the DataPortal operation is executing
- /// is .
- public RevalidatingInterceptor(ApplicationContext applicationContext)
+ /// The options.
+ /// or is .
+ public RevalidatingInterceptor(ApplicationContext applicationContext, IOptions options)
{
_applicationContext = applicationContext ?? throw new ArgumentNullException(nameof(applicationContext));
+ _options = options?.Value ?? throw new ArgumentNullException(nameof(options));
}
///
@@ -46,11 +50,11 @@ public async Task InitializeAsync(InterceptArgs e)
if (e is null)
throw new ArgumentNullException(nameof(e));
- if (e.Parameter is not ITrackStatus checkableObject)
+ if (e.Parameter is not ITrackStatus checkableObject || (_options.IgnoreDeleteOperation && e.Operation == DataPortalOperations.Delete))
{
return;
}
-
+
await RevalidateObjectAsync(checkableObject);
if (!checkableObject.IsValid)
{
@@ -95,7 +99,7 @@ private async Task RevalidateObjectAsync(object? parameter)
if (parameter is IUseFieldManager fieldHolder)
{
var properties = fieldHolder.FieldManager.GetRegisteredProperties();
- foreach (var property in properties.Where(r=>r.IsChild && fieldHolder.FieldManager.FieldExists(r)))
+ foreach (var property in properties.Where(r => r.IsChild && fieldHolder.FieldManager.FieldExists(r)))
{
var fieldData = fieldHolder.FieldManager.GetFieldData(property);
if (fieldData is null)
diff --git a/Source/Csla/Server/Interceptors/ServerSide/RevalidatingInterceptorOptions.cs b/Source/Csla/Server/Interceptors/ServerSide/RevalidatingInterceptorOptions.cs
new file mode 100644
index 0000000000..cbee74b514
--- /dev/null
+++ b/Source/Csla/Server/Interceptors/ServerSide/RevalidatingInterceptorOptions.cs
@@ -0,0 +1,21 @@
+//-----------------------------------------------------------------------
+//
+// Copyright (c) Marimer LLC. All rights reserved.
+// Website: https://cslanet.com
+//
+// Initiates revalidation on business objects during data portal operations
+//-----------------------------------------------------------------------
+
+namespace Csla.Server.Interceptors.ServerSide
+{
+ ///
+ /// Options for .
+ ///
+ public class RevalidatingInterceptorOptions
+ {
+ ///
+ /// Indicates whether the should not re-validate business objects during a operation.
+ ///
+ public bool IgnoreDeleteOperation { get; set; }
+ }
+}
diff --git a/Source/csla.netcore.test/Server/Interceptors/ServerSide/RevalidatingInterceptorTests.cs b/Source/csla.netcore.test/Server/Interceptors/ServerSide/RevalidatingInterceptorTests.cs
index 2b70e0fa8e..00237b3dd6 100644
--- a/Source/csla.netcore.test/Server/Interceptors/ServerSide/RevalidatingInterceptorTests.cs
+++ b/Source/csla.netcore.test/Server/Interceptors/ServerSide/RevalidatingInterceptorTests.cs
@@ -2,6 +2,8 @@
using Csla.Server;
using Csla.Server.Interceptors.ServerSide;
using Csla.TestHelpers;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Options;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace Csla.Test.Server.Interceptors.ServerSide
@@ -10,6 +12,8 @@ namespace Csla.Test.Server.Interceptors.ServerSide
public class RevalidatingInterceptorTests
{
private static TestDIContext _testDIContext;
+ private ApplicationContext _applicationContext;
+ private RevalidatingInterceptor _systemUnderTest;
[ClassInitialize]
public static void ClassInitialize(TestContext context)
@@ -17,19 +21,31 @@ public static void ClassInitialize(TestContext context)
_testDIContext = TestDIContextFactory.CreateDefaultContext();
}
+ [TestInitialize]
+ public void TestSetup()
+ {
+ _applicationContext = _testDIContext.CreateTestApplicationContext();
+ _systemUnderTest = new RevalidatingInterceptor(_applicationContext, _testDIContext.ServiceProvider.GetRequiredService>());
+
+ PrepareApplicationContext(_applicationContext);
+ }
+
+ private static void PrepareApplicationContext(ApplicationContext applicationContext)
+ {
+ applicationContext.SetExecutionLocation(ApplicationContext.ExecutionLocations.Server);
+ applicationContext.LocalContext["__logicalExecutionLocation"] = ApplicationContext.LogicalExecutionLocations.Server;
+ }
+
+
[TestMethod]
public async Task Initialize_PrimitiveCriteria_NoExceptionRaised()
{
// Arrange
var criteria = new PrimitiveCriteria(1);
- ApplicationContext applicationContext = _testDIContext.CreateTestApplicationContext();
- var sut = new RevalidatingInterceptor(applicationContext);
var args = CreateUpdateArgsOfRoot(criteria);
- applicationContext.SetExecutionLocation(ApplicationContext.ExecutionLocations.Server);
- applicationContext.LocalContext["__logicalExecutionLocation"] = ApplicationContext.LogicalExecutionLocations.Server;
// Act
- await sut.InitializeAsync(args);
+ await _systemUnderTest.InitializeAsync(args);
}
[TestMethod]
@@ -38,14 +54,10 @@ public async Task Initialize_ValidRootObjectNoChildren_NoExceptionRaised()
// Arrange
IDataPortal dataPortal = _testDIContext.CreateDataPortal();
Root rootObject = dataPortal.Fetch(new Root.Criteria("Test Data"));
- ApplicationContext applicationContext = _testDIContext.CreateTestApplicationContext();
- RevalidatingInterceptor sut = new RevalidatingInterceptor(applicationContext);
var args = CreateUpdateArgsOfRoot(rootObject);
- applicationContext.SetExecutionLocation(ApplicationContext.ExecutionLocations.Server);
- applicationContext.LocalContext["__logicalExecutionLocation"] = ApplicationContext.LogicalExecutionLocations.Server;
// Act
- await sut.InitializeAsync(args);
+ await _systemUnderTest.InitializeAsync(args);
}
[TestMethod]
@@ -56,14 +68,10 @@ public async Task Initialize_ValidRootObjectWithChild_NoExceptionRaised()
Root rootObject = dataPortal.Fetch(new Root.Criteria("Test Data"));
Child childObject = rootObject.Children.AddNew();
childObject.Data = "Test child data";
- ApplicationContext applicationContext = _testDIContext.CreateTestApplicationContext();
- var sut = new RevalidatingInterceptor(applicationContext);
var args = CreateUpdateArgsOfRoot(rootObject);
- applicationContext.SetExecutionLocation(ApplicationContext.ExecutionLocations.Server);
- applicationContext.LocalContext["__logicalExecutionLocation"] = ApplicationContext.LogicalExecutionLocations.Server;
// Act
- await sut.InitializeAsync(args);
+ await _systemUnderTest.InitializeAsync(args);
}
[TestMethod]
@@ -76,14 +84,10 @@ public async Task Initialize_ValidRootObjectWithChildAndGrandChild_NoExceptionRa
childObject.Data = "Test child data";
GrandChild grandChildObject = childObject.GrandChildren.AddNew();
grandChildObject.Data = "Test grandchild data";
- ApplicationContext applicationContext = _testDIContext.CreateTestApplicationContext();
- var sut = new RevalidatingInterceptor(applicationContext);
var args = CreateUpdateArgsOfRoot(rootObject);
- applicationContext.SetExecutionLocation(ApplicationContext.ExecutionLocations.Server);
- applicationContext.LocalContext["__logicalExecutionLocation"] = ApplicationContext.LogicalExecutionLocations.Server;
// Act
- await sut.InitializeAsync(args);
+ await _systemUnderTest.InitializeAsync(args);
}
[TestMethod]
@@ -92,14 +96,10 @@ public async Task Initialize_InvalidRootObject_ExceptionRaised()
// Arrange
IDataPortal dataPortal = _testDIContext.CreateDataPortal();
Root rootObject = dataPortal.Create(new Root.Criteria(""));
- ApplicationContext applicationContext = _testDIContext.CreateTestApplicationContext();
- var sut = new RevalidatingInterceptor(applicationContext);
var args = CreateUpdateArgsOfRoot(rootObject);
- applicationContext.SetExecutionLocation(ApplicationContext.ExecutionLocations.Server);
- applicationContext.LocalContext["__logicalExecutionLocation"] = ApplicationContext.LogicalExecutionLocations.Server;
// Act and Assert
- await Assert.ThrowsExceptionAsync(async () => await sut.InitializeAsync(args));
+ await Assert.ThrowsExceptionAsync(async () => await _systemUnderTest.InitializeAsync(args));
}
[TestMethod]
@@ -109,14 +109,10 @@ public async Task Initialize_InvalidChildObject_ExceptionRaised()
IDataPortal dataPortal = _testDIContext.CreateDataPortal();
Root rootObject = dataPortal.Create(new Root.Criteria("Test Data"));
rootObject.Children.AddNew();
- ApplicationContext applicationContext = _testDIContext.CreateTestApplicationContext();
- var sut = new RevalidatingInterceptor(applicationContext);
var args = CreateUpdateArgsOfRoot(rootObject);
- applicationContext.SetExecutionLocation(ApplicationContext.ExecutionLocations.Server);
- applicationContext.LocalContext["__logicalExecutionLocation"] = ApplicationContext.LogicalExecutionLocations.Server;
// Act and Assert
- await Assert.ThrowsExceptionAsync(async () => await sut.InitializeAsync(args));
+ await Assert.ThrowsExceptionAsync(async () => await _systemUnderTest.InitializeAsync(args));
}
[TestMethod]
@@ -128,14 +124,29 @@ public async Task Initialize_InvalidGrandChildObject_ExceptionRaised()
Child childObject = rootObject.Children.AddNew();
childObject.Data = "Test child data";
childObject.GrandChildren.AddNew();
- ApplicationContext applicationContext = _testDIContext.CreateTestApplicationContext();
- var sut = new RevalidatingInterceptor(applicationContext);
var args = CreateUpdateArgsOfRoot(rootObject);
- applicationContext.SetExecutionLocation(ApplicationContext.ExecutionLocations.Server);
- applicationContext.LocalContext["__logicalExecutionLocation"] = ApplicationContext.LogicalExecutionLocations.Server;
// Act and Assert
- await Assert.ThrowsExceptionAsync(async () => await sut.InitializeAsync(args));
+ await Assert.ThrowsExceptionAsync(async () => await _systemUnderTest.InitializeAsync(args));
+ }
+
+ [TestMethod]
+ public async Task Initialize_DeletingAnInvalidObjectDoesNotThrowWhenRevalidationForDeleteIsDisabled()
+ {
+ IDataPortal dataPortal = _testDIContext.CreateDataPortal();
+ Root rootObject = dataPortal.Create(new Root.Criteria(""));
+ var args = new InterceptArgs(rootObject.GetType(), rootObject, DataPortalOperations.Delete, true);
+
+ var diContext = TestDIContextFactory.CreateDefaultContext(services =>
+ {
+ services.Configure(opts => opts.IgnoreDeleteOperation = true);
+ });
+ var appContext = diContext.CreateTestApplicationContext();
+ PrepareApplicationContext(appContext);
+
+ var sut = new RevalidatingInterceptor(appContext, diContext.ServiceProvider.GetRequiredService>());
+
+ await sut.InitializeAsync(args);
}
private static InterceptArgs CreateUpdateArgsOfRoot(object parameter) => new InterceptArgs(typeof(Root), parameter, DataPortalOperations.Update, true);
diff --git a/docs/Upgrading to CSLA 10.md b/docs/Upgrading to CSLA 10.md
index 3fe3542aa5..a99db8c690 100644
--- a/docs/Upgrading to CSLA 10.md
+++ b/docs/Upgrading to CSLA 10.md
@@ -10,6 +10,18 @@ If you are upgrading from a version of CSLA prior to 8, you should review the [U
TBD
+## RevalidatingInterceptor
+
+The constructor has changed and now expects an `IOptions` instance. With this new options object it is now possible to skip the revalidation of business rules during a `Delete` operation.
+To configure the new options we are using the .Net [Options pattern](https://learn.microsoft.com/en-us/dotnet/core/extensions/options).
+```csharp
+services.Configure(opts =>
+{
+ opts.IgnoreDeleteOperation = true;
+});
+```
+
+
## Nullable Reference Types
CSLA 10 supports the use of nullable reference types in your code. This means that you can use the `#nullable enable` directive in your code and the compiler will now tell you where CSLA does not expect any `null` values or returns `null`.