diff --git a/CHANGELOG.md b/CHANGELOG.md index e4098dffd..70369d261 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ Represents the **NuGet** versions. +## v5.3.1 +- *Fixed:* Upgraded `CoreEx` and `UnitTestEx` to latest packages to include all related fixes. + ## v5.3.0 - *Enhancement:* Added new code-generation configuration property `ValidationFramework` that supports either `CoreEx` (default) or `FluentValidation` (uses the `CoreEx.FluentValidation` interop wrapping capabilities) to allow entity validation to be performed using either framework. Supports mix-and-matching where required. The `CoreEx.Validation` framework is still leveraged for `IsMandatory` and `ValidatorCode` logic where specified. - *[Issue 208](https://github.com/Avanade/Beef/issues/208): `ReferenceDataController.GetNamed` now excluded from Swagger output as results in superfluous types/models. diff --git a/Common.targets b/Common.targets index 7a792d67f..8ffe5d2d4 100644 --- a/Common.targets +++ b/Common.targets @@ -1,6 +1,6 @@ - 5.3.0 + 5.3.1 preview Avanade Avanade diff --git a/samples/Cdr.Banking/Cdr.Banking.Business/Cdr.Banking.Business.csproj b/samples/Cdr.Banking/Cdr.Banking.Business/Cdr.Banking.Business.csproj index de3d207c8..8f830406d 100644 --- a/samples/Cdr.Banking/Cdr.Banking.Business/Cdr.Banking.Business.csproj +++ b/samples/Cdr.Banking/Cdr.Banking.Business/Cdr.Banking.Business.csproj @@ -11,7 +11,7 @@ - - + + \ No newline at end of file diff --git a/samples/Cdr.Banking/Cdr.Banking.Common/Cdr.Banking.Common.csproj b/samples/Cdr.Banking/Cdr.Banking.Common/Cdr.Banking.Common.csproj index 4c6d7fb50..42cf6bc15 100644 --- a/samples/Cdr.Banking/Cdr.Banking.Common/Cdr.Banking.Common.csproj +++ b/samples/Cdr.Banking/Cdr.Banking.Common/Cdr.Banking.Common.csproj @@ -8,6 +8,6 @@ - + \ No newline at end of file diff --git a/samples/Cdr.Banking/Cdr.Banking.Test/Cdr.Banking.Test.csproj b/samples/Cdr.Banking/Cdr.Banking.Test/Cdr.Banking.Test.csproj index 8f150e92d..5431f3037 100644 --- a/samples/Cdr.Banking/Cdr.Banking.Test/Cdr.Banking.Test.csproj +++ b/samples/Cdr.Banking/Cdr.Banking.Test/Cdr.Banking.Test.csproj @@ -35,7 +35,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/samples/Demo/Beef.Demo.Business/Beef.Demo.Business.csproj b/samples/Demo/Beef.Demo.Business/Beef.Demo.Business.csproj index 913474932..0f75a8da8 100644 --- a/samples/Demo/Beef.Demo.Business/Beef.Demo.Business.csproj +++ b/samples/Demo/Beef.Demo.Business/Beef.Demo.Business.csproj @@ -15,13 +15,13 @@ - - - - - - - + + + + + + + diff --git a/samples/Demo/Beef.Demo.Common/Beef.Demo.Common.csproj b/samples/Demo/Beef.Demo.Common/Beef.Demo.Common.csproj index f907d7c4d..aadfcc4a3 100644 --- a/samples/Demo/Beef.Demo.Common/Beef.Demo.Common.csproj +++ b/samples/Demo/Beef.Demo.Common/Beef.Demo.Common.csproj @@ -7,7 +7,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/samples/Demo/Beef.Demo.Test/Beef.Demo.Test.csproj b/samples/Demo/Beef.Demo.Test/Beef.Demo.Test.csproj index c1679e986..386e029a3 100644 --- a/samples/Demo/Beef.Demo.Test/Beef.Demo.Test.csproj +++ b/samples/Demo/Beef.Demo.Test/Beef.Demo.Test.csproj @@ -57,7 +57,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/samples/My.Hr/My.Hr.Business/My.Hr.Business.csproj b/samples/My.Hr/My.Hr.Business/My.Hr.Business.csproj index 0a3d0b3c5..ca778f6e3 100644 --- a/samples/My.Hr/My.Hr.Business/My.Hr.Business.csproj +++ b/samples/My.Hr/My.Hr.Business/My.Hr.Business.csproj @@ -5,10 +5,10 @@ true - - - - + + + + \ No newline at end of file diff --git a/samples/My.Hr/My.Hr.Common/My.Hr.Common.csproj b/samples/My.Hr/My.Hr.Common/My.Hr.Common.csproj index fec596da9..85600929f 100644 --- a/samples/My.Hr/My.Hr.Common/My.Hr.Common.csproj +++ b/samples/My.Hr/My.Hr.Common/My.Hr.Common.csproj @@ -4,6 +4,6 @@ enable - + \ No newline at end of file diff --git a/samples/My.Hr/My.Hr.Test/My.Hr.Test.csproj b/samples/My.Hr/My.Hr.Test/My.Hr.Test.csproj index fdc965154..285214ee8 100644 --- a/samples/My.Hr/My.Hr.Test/My.Hr.Test.csproj +++ b/samples/My.Hr/My.Hr.Test/My.Hr.Test.csproj @@ -32,13 +32,13 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/samples/MyEf.Hr/MyEf.Hr.Api/MyEf.Hr.Api.csproj b/samples/MyEf.Hr/MyEf.Hr.Api/MyEf.Hr.Api.csproj index c622ea2bc..94138d17e 100644 --- a/samples/MyEf.Hr/MyEf.Hr.Api/MyEf.Hr.Api.csproj +++ b/samples/MyEf.Hr/MyEf.Hr.Api/MyEf.Hr.Api.csproj @@ -5,7 +5,7 @@ true - + diff --git a/samples/MyEf.Hr/MyEf.Hr.Business/MyEf.Hr.Business.csproj b/samples/MyEf.Hr/MyEf.Hr.Business/MyEf.Hr.Business.csproj index beb9007fa..bfbf8de21 100644 --- a/samples/MyEf.Hr/MyEf.Hr.Business/MyEf.Hr.Business.csproj +++ b/samples/MyEf.Hr/MyEf.Hr.Business/MyEf.Hr.Business.csproj @@ -5,10 +5,10 @@ true - - - - + + + + diff --git a/samples/MyEf.Hr/MyEf.Hr.Common/MyEf.Hr.Common.csproj b/samples/MyEf.Hr/MyEf.Hr.Common/MyEf.Hr.Common.csproj index fec596da9..85600929f 100644 --- a/samples/MyEf.Hr/MyEf.Hr.Common/MyEf.Hr.Common.csproj +++ b/samples/MyEf.Hr/MyEf.Hr.Common/MyEf.Hr.Common.csproj @@ -4,6 +4,6 @@ enable - + \ No newline at end of file diff --git a/samples/MyEf.Hr/MyEf.Hr.Security.Subscriptions/MyEf.Hr.Security.Subscriptions.csproj b/samples/MyEf.Hr/MyEf.Hr.Security.Subscriptions/MyEf.Hr.Security.Subscriptions.csproj index 67a843c99..28e9b9f0b 100644 --- a/samples/MyEf.Hr/MyEf.Hr.Security.Subscriptions/MyEf.Hr.Security.Subscriptions.csproj +++ b/samples/MyEf.Hr/MyEf.Hr.Security.Subscriptions/MyEf.Hr.Security.Subscriptions.csproj @@ -16,8 +16,8 @@ - - + + diff --git a/samples/MyEf.Hr/MyEf.Hr.Security.Subscriptions/OktaHttpClient.cs b/samples/MyEf.Hr/MyEf.Hr.Security.Subscriptions/OktaHttpClient.cs index 21dd55518..454deb894 100644 --- a/samples/MyEf.Hr/MyEf.Hr.Security.Subscriptions/OktaHttpClient.cs +++ b/samples/MyEf.Hr/MyEf.Hr.Security.Subscriptions/OktaHttpClient.cs @@ -15,7 +15,7 @@ public OktaHttpClient(HttpClient client, SecuritySettings settings, IJsonSeriali public async Task GetUser(string email) { var response = await GetAsync>($"/api/v1/users?search=profile.email eq \"{email}\"").ConfigureAwait(false); - return response.Value.SingleOrDefault(); + return response.Value.Count == 1 ? response.Value[0] : null; } /// diff --git a/samples/MyEf.Hr/MyEf.Hr.Security.Subscriptions/SecuritySubscriberFunction.cs b/samples/MyEf.Hr/MyEf.Hr.Security.Subscriptions/SecuritySubscriberFunction.cs index 0646a0940..31a925bfd 100644 --- a/samples/MyEf.Hr/MyEf.Hr.Security.Subscriptions/SecuritySubscriberFunction.cs +++ b/samples/MyEf.Hr/MyEf.Hr.Security.Subscriptions/SecuritySubscriberFunction.cs @@ -8,7 +8,6 @@ public class SecuritySubscriberFunction [Singleton(Mode = SingletonMode.Function)] [FunctionName(nameof(SecuritySubscriberFunction))] - [ExponentialBackoffRetry(3, "00:02:00", "00:30:00")] public Task RunAsync([ServiceBusTrigger("%ServiceBusQueueName%", Connection = "ServiceBusConnectionString")] ServiceBusReceivedMessage message, ServiceBusMessageActions messageActions, CancellationToken cancellationToken) => _subscriber.ReceiveAsync(message, messageActions, cancellationToken); } \ No newline at end of file diff --git a/samples/MyEf.Hr/MyEf.Hr.Security.Subscriptions/Subscribers/EmployeeTerminatedSubcriber.cs b/samples/MyEf.Hr/MyEf.Hr.Security.Subscriptions/Subscribers/EmployeeTerminatedSubcriber.cs index f13f21b28..a11d90014 100644 --- a/samples/MyEf.Hr/MyEf.Hr.Security.Subscriptions/Subscribers/EmployeeTerminatedSubcriber.cs +++ b/samples/MyEf.Hr/MyEf.Hr.Security.Subscriptions/Subscribers/EmployeeTerminatedSubcriber.cs @@ -24,7 +24,7 @@ public EmployeeTerminatedSubcriber(OktaHttpClient okta, ILogger @event, CancellationToken cancellationToken) { - var user = await _okta.GetUser(@event.Value.Email!).ConfigureAwait(false) ?? throw new NotFoundException($"Employee {@event.Value.Id} with email {@event.Value.Email} not found in OKTA."); + var user = await _okta.GetUser(@event.Value.Email!).ConfigureAwait(false) ?? throw new NotFoundException($"Employee {@event.Value.Id} with email {@event.Value.Email} either not found, or multiple exist, within OKTA."); if (!user.IsDeactivatable) _logger.LogWarning("Employee {EmployeeId} with email {Email} has User status of {UserStatus} and is therefore unable to be deactivated.", @event.Value.Id, @event.Value.Email, user.Status); diff --git a/samples/MyEf.Hr/MyEf.Hr.Security.Subscriptions/appsettings.json b/samples/MyEf.Hr/MyEf.Hr.Security.Subscriptions/appsettings.json index 7f7bca8d9..e3f01ec0e 100644 --- a/samples/MyEf.Hr/MyEf.Hr.Security.Subscriptions/appsettings.json +++ b/samples/MyEf.Hr/MyEf.Hr.Security.Subscriptions/appsettings.json @@ -3,5 +3,9 @@ "BaseUri": "https://dev-1234.okta.com", "HttpRetryCount": 2, "HttpTimeoutSeconds": 120 + }, + "ServiceBusOrchestratedSubscriber": { + "AbandonOnTransient": true, + "RetryDelay": "00:00:05" } } \ No newline at end of file diff --git a/samples/MyEf.Hr/MyEf.Hr.Security.Subscriptions/host.json b/samples/MyEf.Hr/MyEf.Hr.Security.Subscriptions/host.json index 141f90b17..84701a454 100644 --- a/samples/MyEf.Hr/MyEf.Hr.Security.Subscriptions/host.json +++ b/samples/MyEf.Hr/MyEf.Hr.Security.Subscriptions/host.json @@ -15,9 +15,9 @@ "serviceBus": { "clientRetryOptions": { "mode": "exponential", - "tryTimeout": "00:05:00", - "delay": "00:00:30.00", - "maxDelay": "00:03:00", + "tryTimeout": "00:01:00", + "delay": "00:00:00.80", + "maxDelay": "00:01:00", "maxRetries": 3 }, "prefetchCount": 0, diff --git a/samples/MyEf.Hr/MyEf.Hr.Security.Test/MyEf.Hr.Security.Test.csproj b/samples/MyEf.Hr/MyEf.Hr.Security.Test/MyEf.Hr.Security.Test.csproj index 91f668ac1..35b6a310a 100644 --- a/samples/MyEf.Hr/MyEf.Hr.Security.Test/MyEf.Hr.Security.Test.csproj +++ b/samples/MyEf.Hr/MyEf.Hr.Security.Test/MyEf.Hr.Security.Test.csproj @@ -28,7 +28,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/samples/MyEf.Hr/MyEf.Hr.Security.Test/SecuritySubscriberFunctionTest.cs b/samples/MyEf.Hr/MyEf.Hr.Security.Test/SecuritySubscriberFunctionTest.cs index 8fe785d82..9cc43a80d 100644 --- a/samples/MyEf.Hr/MyEf.Hr.Security.Test/SecuritySubscriberFunctionTest.cs +++ b/samples/MyEf.Hr/MyEf.Hr.Security.Test/SecuritySubscriberFunctionTest.cs @@ -7,30 +7,27 @@ public class SecuritySubscriberFunctionTest public void InvalidMessage_DeadLetter() { using var test = FunctionTester.Create(); - var actionsMock = new Mock(); + var actions = test.CreateServiceBusMessageActions(); var message = test.CreateServiceBusMessage(null!); test.ServiceBusTrigger() - .ExpectLogContains("[EventDataDeserialization]") - .Run(f => f.RunAsync(message, actionsMock.Object, default)) + .Run(f => f.RunAsync(message, actions, default)) .AssertSuccess(); - actionsMock.Verify(m => m.DeadLetterMessageAsync(message, It.IsAny(), It.IsAny(), default), Times.Once); - actionsMock.VerifyNoOtherCalls(); + actions.AssertDeadLetter("EventDataDeserialization", "Cannot decode JSON element of kind 'Null' as CloudEvent"); } [Test] public void NotSubscribed_CompleteSilent() { using var test = FunctionTester.Create(); - var actionsMock = new Mock(); + var actions = test.CreateServiceBusMessageActions(); var message = test.CreateServiceBusMessage(new EventData { Subject = "myef.hr.employee", Action = "updated", Source = new Uri("test", UriKind.Relative) }); test.ServiceBusTrigger() - .Run(f => f.RunAsync(message, actionsMock.Object, default)) + .Run(f => f.RunAsync(message, actions, default)) .AssertSuccess(); - actionsMock.Verify(m => m.CompleteMessageAsync(message, default), Times.Once); - actionsMock.VerifyNoOtherCalls(); + actions.AssertComplete(); } } \ No newline at end of file diff --git a/samples/MyEf.Hr/MyEf.Hr.Security.Test/Subscribers/EmployeeTerminatedSubscriberTest.cs b/samples/MyEf.Hr/MyEf.Hr.Security.Test/Subscribers/EmployeeTerminatedSubscriberTest.cs index 14d576ab2..6e1286fa8 100644 --- a/samples/MyEf.Hr/MyEf.Hr.Security.Test/Subscribers/EmployeeTerminatedSubscriberTest.cs +++ b/samples/MyEf.Hr/MyEf.Hr.Security.Test/Subscribers/EmployeeTerminatedSubscriberTest.cs @@ -1,6 +1,4 @@ -using UnitTestEx.Expectations; - -namespace MyEf.Hr.Security.Test.Subscribers; +namespace MyEf.Hr.Security.Test.Subscribers; [TestFixture] public class EmployeeTerminatedSubscriberTest @@ -9,32 +7,29 @@ public class EmployeeTerminatedSubscriberTest public void ValueIsRequired_DeadLetter() { using var test = FunctionTester.Create(); - var actionsMock = new Mock(); var message = test.CreateServiceBusMessage(new EventData { Subject = "myef.hr.employee", Action = "terminated", Source = new Uri("test", UriKind.Relative), Value = null! }); + var actions = test.CreateServiceBusMessageActions(); test.ServiceBusTrigger() - .ExpectLogContains("Value is required") - .Run(f => f.RunAsync(message, actionsMock.Object, default)) + .Run(f => f.RunAsync(message, actions, default)) .AssertSuccess(); - actionsMock.Verify(m => m.DeadLetterMessageAsync(message, It.IsAny(), It.IsAny(), default), Times.Once); - actionsMock.VerifyNoOtherCalls(); + actions.AssertDeadLetter("ValidationError", "Value is required"); } [Test] public void ValidationError_DeadLetter() { using var test = FunctionTester.Create(); - var actionsMock = new Mock(); var message = test.CreateServiceBusMessage(new EventData { Subject = "myef.hr.employee", Action = "terminated", Source = new Uri("test", UriKind.Relative), Value = new Employee() }); + var actions = test.CreateServiceBusMessageActions(); test.ServiceBusTrigger() .ExpectLogContains("A data validation error occurred") - .Run(f => f.RunAsync(message, actionsMock.Object, default)) + .Run(f => f.RunAsync(message, actions, default)) .AssertSuccess(); - actionsMock.Verify(m => m.DeadLetterMessageAsync(message, It.IsAny(), It.IsAny(), default), Times.Once); - actionsMock.VerifyNoOtherCalls(); + actions.AssertDeadLetter("ValidationError", "Email is required"); } [Test] @@ -45,18 +40,17 @@ public void EmailNotFound_Complete() mc.Request(HttpMethod.Get, "/api/v1/users?search=profile.email eq \"bob@email.com\"").Respond.WithJson("[]", HttpStatusCode.OK); using var test = FunctionTester.Create(); - var actionsMock = new Mock(); + var actions = test.CreateServiceBusMessageActions(); var message = test.CreateServiceBusMessage( new EventData { Subject = "myef.hr.employee", Action = "terminated", Source = new Uri("test", UriKind.Relative), Value = new Employee { Id = 1.ToGuid(), Email = "bob@email.com", Termination = new() } }); test.ReplaceHttpClientFactory(mcf) .ServiceBusTrigger() - .ExpectLogContains("email bob@email.com not found", "[Source: Subscriber, Handling: CompleteWithWarning]") - .Run(f => f.RunAsync(message, actionsMock.Object, default)) + .ExpectLogContains("email bob@email.com either not found, or multiple exist, within OKTA.", "[Source: Subscriber, Handling: CompleteWithWarning]") + .Run(f => f.RunAsync(message, actions, default)) .AssertSuccess(); - actionsMock.Verify(m => m.CompleteMessageAsync(message, default), Times.Once); - actionsMock.VerifyNoOtherCalls(); + actions.AssertComplete(); mcf.VerifyAll(); } @@ -68,17 +62,17 @@ public void OktaForbidden_Retry() mc.Request(HttpMethod.Get, "/api/v1/users?search=profile.email eq \"bob@email.com\"").Respond.With(HttpStatusCode.Forbidden); using var test = FunctionTester.Create(); - var actionsMock = new Mock(); + var actions = test.CreateServiceBusMessageActions(); var message = test.CreateServiceBusMessage( new EventData { Subject = "myef.hr.employee", Action = "terminated", Source = new Uri("test", UriKind.Relative), Value = new Employee { Id = 1.ToGuid(), Email = "bob@email.com", Termination = new() } }); test.ReplaceHttpClientFactory(mcf) .ServiceBusTrigger() .ExpectLogContains("Retry - Service Bus message", "[AuthenticationError]") - .Run(f => f.RunAsync(message, actionsMock.Object, default)) - .AssertException(); + .Run(f => f.RunAsync(message, actions, default)) + .AssertSuccess(); - actionsMock.VerifyNoOtherCalls(); + actions.AssertRenew(1).AssertAbandon(); mcf.VerifyAll(); } @@ -95,17 +89,17 @@ public void OktaServiceUnavailable_Retry() }); using var test = FunctionTester.Create(); - var actionsMock = new Mock(); + var actions = test.CreateServiceBusMessageActions(); var message = test.CreateServiceBusMessage( new EventData { Subject = "myef.hr.employee", Action = "terminated", Source = new Uri("test", UriKind.Relative), Value = new Employee { Id = 1.ToGuid(), Email = "bob@email.com", Termination = new() } }); test.ReplaceHttpClientFactory(mcf) .ServiceBusTrigger() .ExpectLogContains("Retry - Service Bus message", "[TransientError]") - .Run(f => f.RunAsync(message, actionsMock.Object, default)) - .AssertException(); + .Run(f => f.RunAsync(message, actions, default)) + .AssertSuccess(); - actionsMock.VerifyNoOtherCalls(); + actions.AssertRenew(1).AssertAbandon(); mcf.VerifyAll(); } @@ -118,17 +112,16 @@ public void SuccessDeactivated_Complete() mc.Request(HttpMethod.Post, "/api/v1/users/00ub0oNGTSWTBKOLGLNR/lifecycle/deactivate?sendEmail=true").Respond.With(HttpStatusCode.OK); using var test = FunctionTester.Create(); - var actionsMock = new Mock(); + var actions = test.CreateServiceBusMessageActions(); var message = test.CreateServiceBusMessage( new EventData { Subject = "myef.hr.employee", Action = "terminated", Source = new Uri("test", UriKind.Relative), Value = new Employee { Id = 1.ToGuid(), Email = "bob@email.com", Termination = new() } }); test.ReplaceHttpClientFactory(mcf) .ServiceBusTrigger() - .Run(f => f.RunAsync(message, actionsMock.Object, default)) + .Run(f => f.RunAsync(message, actions, default)) .AssertSuccess(); - actionsMock.Verify(m => m.CompleteMessageAsync(message, default), Times.Once); - actionsMock.VerifyNoOtherCalls(); + actions.AssertComplete(); mcf.VerifyAll(); } @@ -140,18 +133,17 @@ public void SuccessAlreadyDeactivated_Complete() mc.Request(HttpMethod.Get, "/api/v1/users?search=profile.email eq \"bob@email.com\"").Respond.WithJson(new [] { new { id = "00ub0oNGTSWTBKOLGLNR", status = "DEACTIVATED" } }); using var test = FunctionTester.Create(); - var actionsMock = new Mock(); + var actions = test.CreateServiceBusMessageActions(); var message = test.CreateServiceBusMessage( new EventData { Subject = "myef.hr.employee", Action = "terminated", Source = new Uri("test", UriKind.Relative), Value = new Employee { Id = 1.ToGuid(), Email = "bob@email.com", Termination = new() } }); test.ReplaceHttpClientFactory(mcf) .ServiceBusTrigger() .ExpectLogContains("warn: Employee 00000001-0000-0000-0000-000000000000 with email bob@email.com has User status of DEACTIVATED and is therefore unable to be deactivated.") - .Run(f => f.RunAsync(message, actionsMock.Object, default)) + .Run(f => f.RunAsync(message, actions, default)) .AssertSuccess(); - actionsMock.Verify(m => m.CompleteMessageAsync(message, default), Times.Once); - actionsMock.VerifyNoOtherCalls(); + actions.AssertComplete(); mcf.VerifyAll(); } } \ No newline at end of file diff --git a/samples/MyEf.Hr/MyEf.Hr.Security.Test/appsettings.unittest.json b/samples/MyEf.Hr/MyEf.Hr.Security.Test/appsettings.unittest.json index d2ee569b8..521e38059 100644 --- a/samples/MyEf.Hr/MyEf.Hr.Security.Test/appsettings.unittest.json +++ b/samples/MyEf.Hr/MyEf.Hr.Security.Test/appsettings.unittest.json @@ -1,5 +1,9 @@ { "ConnectionStrings": { "OktaApi": "https://test-okta.com" + }, + "ServiceBusOrchestratedSubscriber": { + "AbandonOnTransient": true, + "RetryDelay": "00:00:00.5" } } \ No newline at end of file diff --git a/samples/MyEf.Hr/MyEf.Hr.Test/MyEf.Hr.Test.csproj b/samples/MyEf.Hr/MyEf.Hr.Test/MyEf.Hr.Test.csproj index a1d2245b9..20c02f095 100644 --- a/samples/MyEf.Hr/MyEf.Hr.Test/MyEf.Hr.Test.csproj +++ b/samples/MyEf.Hr/MyEf.Hr.Test/MyEf.Hr.Test.csproj @@ -32,13 +32,13 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/samples/MyEf.Hr/docs/Service-Bus-Subscribe.md b/samples/MyEf.Hr/docs/Service-Bus-Subscribe.md index 8265b1194..0606cac0c 100644 --- a/samples/MyEf.Hr/docs/Service-Bus-Subscribe.md +++ b/samples/MyEf.Hr/docs/Service-Bus-Subscribe.md @@ -87,7 +87,7 @@ Update project dependencies as follows. Open the `host.json` file and replace with the contents from [`host.json`](../MyEf.Hr.Security.Subscriptions/host.json). -The [configuration](https://learn.microsoft.com/en-us/azure/azure-functions/functions-bindings-service-bus) within will ensure that only a single message can be processed at a time, and also configures the basic retry policy. +The [configuration](https://learn.microsoft.com/en-us/azure/azure-functions/functions-bindings-service-bus) within will ensure that _only_ a _single_ message can be processed at a time, and that there is also _no_ automatic completion of messages.
@@ -96,11 +96,15 @@ The [configuration](https://learn.microsoft.com/en-us/azure/azure-functions/func Open the `local.settings.json` file and replace `Values` JSON with the following. ``` json +{ + "IsEncrypted": false, "Values": { "AzureWebJobsStorage": "UseDevelopmentStorage=true", "FUNCTIONS_WORKER_RUNTIME": "dotnet", "ServiceBusConnectionString": "get-your-secret-and-paste-here", "ServiceBusQueueName": "event-stream" + } +} ```
@@ -218,6 +222,8 @@ Setting | Description `OktaHttpClient:BaseUri` | The base [`Uri`](https://learn.microsoft.com/en-us/dotnet/api/system.uri) for the external OKTA API. `OktaHttpClient:HttpRetryCount`* | Specifies the number of times the HTTP request should be retried when a transient error occurs. `OktaHttpClient:HttpTimeoutSeconds`* | Specifies the maximum number of seconds for the HTTP request to complete before timing out. +`ServiceBusOrchestratedSubscriber.AbandonOnTransient`* | Indicates that the message should be explicitly abandoned where transient error occurs. +`ServiceBusOrchestratedSubscriber.RetryDelay`* | The timespan to delay (multiplied by delivery count) after each transient error is encountered; continues to lock message. ``` json { @@ -225,6 +231,10 @@ Setting | Description "BaseUri": "https://dev-1234.okta.com", "HttpRetryCount": 2, "HttpTimeoutSeconds": 120 + }, + "ServiceBusOrchestratedSubscriber": { + "AbandonOnTransient": true, + "RetryDelay": "00:00:30" } } ``` @@ -263,7 +273,7 @@ public class OktaHttpClient : TypedHttpClientBase public async Task GetUser(string email) { var response = await GetAsync>($"/api/v1/users?search=profile.email eq \"{email}\"").ConfigureAwait(false); - return response.Value.SingleOrDefault(); + return response.Value.Count == 1 ? response.Value[0] : null; } /// @@ -335,7 +345,7 @@ public class EmployeeTerminatedSubcriber : SubscriberBase public override async Task ReceiveAsync(EventData @event, CancellationToken cancellationToken) { - var user = await _okta.GetUser(@event.Value.Email!).ConfigureAwait(false) ?? throw new NotFoundException($"Employee {@event.Value.Id} with email {@event.Value.Email} not found in OKTA."); + var user = await _okta.GetUser(@event.Value.Email!).ConfigureAwait(false) ?? throw new NotFoundException($"Employee {@event.Value.Id} with email {@event.Value.Email} either not found, or multiple exist, within OKTA."); if (!user.IsDeactivatable) _logger.LogWarning("Employee {EmployeeId} with email {Email} has User status of {UserStatus} and is therefore unable to be deactivated.", @event.Value.Id, @event.Value.Email, user.Status); diff --git a/templates/Beef.Template.Solution/content/.template.config/template.json b/templates/Beef.Template.Solution/content/.template.config/template.json index 75359907a..dd410b699 100644 --- a/templates/Beef.Template.Solution/content/.template.config/template.json +++ b/templates/Beef.Template.Solution/content/.template.config/template.json @@ -65,7 +65,7 @@ "type": "generated", "generator": "constant", "parameters": { - "value": "2.9.0" + "value": "2.10.0" }, "replaces": "CoreExVersion" }, @@ -73,7 +73,7 @@ "type": "generated", "generator": "constant", "parameters": { - "value": "2.2.1" + "value": "2.2.3" }, "replaces": "UnitTestExVersion" }, @@ -81,7 +81,7 @@ "type": "generated", "generator": "constant", "parameters": { - "value": "5.3.0" + "value": "5.3.1" }, "replaces": "BeefVersion" }, diff --git a/tools/Beef.Database.Core/Beef.Database.Core.csproj b/tools/Beef.Database.Core/Beef.Database.Core.csproj index e3e39d192..a2a464ec8 100644 --- a/tools/Beef.Database.Core/Beef.Database.Core.csproj +++ b/tools/Beef.Database.Core/Beef.Database.Core.csproj @@ -14,7 +14,7 @@ - + diff --git a/tools/Beef.Database.MySql/Beef.Database.MySql.csproj b/tools/Beef.Database.MySql/Beef.Database.MySql.csproj index d04c3ddc2..488a640d0 100644 --- a/tools/Beef.Database.MySql/Beef.Database.MySql.csproj +++ b/tools/Beef.Database.MySql/Beef.Database.MySql.csproj @@ -14,7 +14,7 @@ - + diff --git a/tools/Beef.Database.SqlServer/Beef.Database.SqlServer.csproj b/tools/Beef.Database.SqlServer/Beef.Database.SqlServer.csproj index 4d0da10a7..59645a673 100644 --- a/tools/Beef.Database.SqlServer/Beef.Database.SqlServer.csproj +++ b/tools/Beef.Database.SqlServer/Beef.Database.SqlServer.csproj @@ -14,7 +14,7 @@ - + diff --git a/tools/Beef.Test.NUnit/Beef.Test.NUnit.csproj b/tools/Beef.Test.NUnit/Beef.Test.NUnit.csproj index 058438f18..4db05b92a 100644 --- a/tools/Beef.Test.NUnit/Beef.Test.NUnit.csproj +++ b/tools/Beef.Test.NUnit/Beef.Test.NUnit.csproj @@ -8,8 +8,8 @@ - - + +