diff --git a/Framework/Azure/Cqrs.Azure.ServiceBus/AzureServiceBus.cs b/Framework/Azure/Cqrs.Azure.ServiceBus/AzureServiceBus.cs index 16a362be5d..1c40fde6ef 100644 --- a/Framework/Azure/Cqrs.Azure.ServiceBus/AzureServiceBus.cs +++ b/Framework/Azure/Cqrs.Azure.ServiceBus/AzureServiceBus.cs @@ -14,6 +14,7 @@ using System.Reflection; using System.Security.Cryptography; using System.Text; +using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using Chinchilla.Logging; @@ -38,7 +39,6 @@ using Manager = Microsoft.ServiceBus.NamespaceManager; using IMessageReceiver = Microsoft.ServiceBus.Messaging.SubscriptionClient; #endif - #if NET462 using Microsoft.Identity.Client; #endif @@ -234,6 +234,10 @@ public abstract class AzureServiceBus /// protected IList ExclusionNamespaces { get; private set; } + private IList TaskRelatedMethodNames { get; } + + private Regex ContainerNameMatcher { get; } + #if NET462 /// /// Gets an access token from Active Directory when using RBAC based connections. @@ -249,16 +253,25 @@ protected AzureServiceBus(IConfigurationManager configurationManager, IMessageSe { AzureBusHelper = azureBusHelper; BusHelper = busHelper; + Signer = hashAlgorithmFactory; + TelemetryHelper = new NullTelemetryHelper(); PrivateServiceBusReceivers = new Dictionary(); PublicServiceBusReceivers = new Dictionary(); TimeoutOnSendRetryMaximumCount = 1; + string timeoutOnSendRetryMaximumCountValue; short timeoutOnSendRetryMaximumCount; if (ConfigurationManager.TryGetSetting("Cqrs.Azure.Servicebus.TimeoutOnSendRetryMaximumCount", out timeoutOnSendRetryMaximumCountValue) && !string.IsNullOrWhiteSpace(timeoutOnSendRetryMaximumCountValue) && short.TryParse(timeoutOnSendRetryMaximumCountValue, out timeoutOnSendRetryMaximumCount)) TimeoutOnSendRetryMaximumCount = timeoutOnSendRetryMaximumCount; + ExclusionNamespaces = new SynchronizedCollection { "Cqrs", "System" }; - Signer = hashAlgorithmFactory; + TaskRelatedMethodNames = new List + { + "MoveNext", + "Start" + }; + ContainerNameMatcher = new Regex("^(.)+?>", RegexOptions.IgnoreCase); #if NET462 InstantiateActiveDirectoryToken(); @@ -1410,14 +1423,25 @@ BrokeredMessage CreateBrokeredMessage foreach (StackFrame frame in stackFrames) { MethodBase method = frame.GetMethod(); - if (method.ReflectedType == null) + if (method.ReflectedType == null || string.IsNullOrWhiteSpace(method.ReflectedType.FullName)) continue; try { if (ExclusionNamespaces.All(@namespace => !method.ReflectedType.FullName.StartsWith(@namespace))) { - brokeredMessage.AddUserProperty("Source-Method", $"{method.ReflectedType.FullName}.{method.Name}"); + string container; + Match match; + if (method.DeclaringType.IsSealed && method.DeclaringType.IsNestedPrivate && method.DeclaringType.IsAutoLayout && TaskRelatedMethodNames.Contains(method.Name) && (match = ContainerNameMatcher.Match(method.ReflectedType.FullName)).Success) + { + container = match.Value.Substring(0, match.Value.Length - 1).Replace("+<", "\\"); + } + else + { + container = $"{method.ReflectedType.FullName}\\{method.Name}"; + } + + brokeredMessage.AddUserProperty("Source-Method", container); break; } } diff --git a/Framework/Azure/Tests/Cqrs.Azure.Storage.Test.Integration/BlobStorageSnapshotSagaRepositoryTests.cs b/Framework/Azure/Tests/Cqrs.Azure.Storage.Test.Integration/BlobStorageSnapshotSagaRepositoryTests.cs index 7bb3660849..c4fa022937 100644 --- a/Framework/Azure/Tests/Cqrs.Azure.Storage.Test.Integration/BlobStorageSnapshotSagaRepositoryTests.cs +++ b/Framework/Azure/Tests/Cqrs.Azure.Storage.Test.Integration/BlobStorageSnapshotSagaRepositoryTests.cs @@ -29,10 +29,6 @@ using TestCleanup = NUnit.Framework.TearDownAttribute; using TestContext = System.Object; - - - - #if NET472 #else using System.Threading.Tasks; diff --git a/Framework/Azure/Tests/Cqrs.Azure.Storage.Test.Integration/TestSnapshotSaga.cs b/Framework/Azure/Tests/Cqrs.Azure.Storage.Test.Integration/TestSnapshotSaga.cs index 90694e6f19..53ad468af4 100644 --- a/Framework/Azure/Tests/Cqrs.Azure.Storage.Test.Integration/TestSnapshotSaga.cs +++ b/Framework/Azure/Tests/Cqrs.Azure.Storage.Test.Integration/TestSnapshotSaga.cs @@ -34,6 +34,8 @@ public void Apply(TestEvent e) EventCount++; } + #endregion + protected override void SetId(ISagaEvent sagaEvent) { // We set Id as the eventstore is using that and not an IEventWithIdentity @@ -50,8 +52,6 @@ protected override void RestoreFromSnapshot(TestSagaSnapshot snapshot) { EventCount = snapshot.EventCount; } - - #endregion } public class TestSagaSnapshot : Snapshot diff --git a/Framework/Cqrs/Domain/AggregateRepository.cs b/Framework/Cqrs/Domain/AggregateRepository.cs index 46cb4b527e..0d68627ee8 100644 --- a/Framework/Cqrs/Domain/AggregateRepository.cs +++ b/Framework/Cqrs/Domain/AggregateRepository.cs @@ -107,7 +107,7 @@ async Task SaveAsync eventStoreResults = await EventStore.GetAsync(aggregate.GetType(), aggregate.Id, false, expectedVersion.Value); #endif if (eventStoreResults.Any()) - throw new ConcurrencyException(aggregate.Id); + throw new ConcurrencyException(aggregate.Id, expectedVersion.Value, eventStoreResults.First().Version); } var eventsToPublish = new List>(); diff --git a/Framework/Cqrs/Domain/Exceptions/ConcurrencyException.cs b/Framework/Cqrs/Domain/Exceptions/ConcurrencyException.cs index b4f59f865b..07c53310ef 100644 --- a/Framework/Cqrs/Domain/Exceptions/ConcurrencyException.cs +++ b/Framework/Cqrs/Domain/Exceptions/ConcurrencyException.cs @@ -22,10 +22,24 @@ public class ConcurrencyException : Exception /// Instantiate a new instance of with the provided identifier of the that had a concurrency issue. /// /// The identifier of the that wasn't found. - public ConcurrencyException(Guid id) - : base(string.Format("A different version than expected was found in aggregate {0}", id)) + /// The version that was expected. + /// The version that was found. + public ConcurrencyException(Guid id, int? expectedVersion = null, int? foundVersion = null) + : base(GenerateMessage(id, expectedVersion, foundVersion)) { Id = id; + ExpectedVersion = expectedVersion; + FoundVersion = foundVersion; + } + + static string GenerateMessage(Guid id, int? expectedVersion = null, int? foundVersion = null) + { + string pattern = $"A different version than expected was found in aggregate {id}"; + if (expectedVersion != null) + pattern = string.Concat(pattern, $". Expected Version {expectedVersion}"); + if (foundVersion != null) + pattern = string.Concat(pattern, $". Found Version {foundVersion}"); + return pattern; } /// @@ -33,5 +47,17 @@ public ConcurrencyException(Guid id) /// [DataMember] public Guid Id { get; set; } + + /// + /// The version that was expected. + /// + [DataMember] + public int? ExpectedVersion { get; set; } + + /// + /// The version that was found. + /// + [DataMember] + public int? FoundVersion { get; set; } } } \ No newline at end of file diff --git a/Framework/Cqrs/Domain/SagaRepository.cs b/Framework/Cqrs/Domain/SagaRepository.cs index a2235cb5d7..ea2d8fde2b 100644 --- a/Framework/Cqrs/Domain/SagaRepository.cs +++ b/Framework/Cqrs/Domain/SagaRepository.cs @@ -141,7 +141,7 @@ await EventStore.GetAsync #endif (saga.GetType(), saga.Id, false, expectedVersion.Value); if (eventStoreResults.Any()) - throw new ConcurrencyException(saga.Id); + throw new ConcurrencyException(saga.Id, expectedVersion.Value, eventStoreResults.First().Version); } var eventsToPublish = new List>(); diff --git a/Framework/Cqrs/Domain/SagaUnitOfWork.cs b/Framework/Cqrs/Domain/SagaUnitOfWork.cs index 840adeea50..11403ea29e 100644 --- a/Framework/Cqrs/Domain/SagaUnitOfWork.cs +++ b/Framework/Cqrs/Domain/SagaUnitOfWork.cs @@ -78,7 +78,7 @@ async Task AddAsync }; TrackedSagas.Add(saga.Id, sagaDescriptor); } - else if (((TrackedSagas[saga.Id]).Saga) != (ISaga)saga) + else if (TrackedSagas[saga.Id].Saga != (ISaga)saga) throw new ConcurrencyException(saga.Id); #if NET40 #else @@ -102,7 +102,7 @@ async Task GetAsync { var trackedSaga = (TSaga)TrackedSagas[id].Saga; if (expectedVersion != null && trackedSaga.Version != expectedVersion) - throw new ConcurrencyException(trackedSaga.Id); + throw new ConcurrencyException(trackedSaga.Id, expectedVersion.Value, trackedSaga.Version); return trackedSaga; } @@ -114,7 +114,7 @@ async Task GetAsync #endif (id); if (expectedVersion != null && saga.Version != expectedVersion) - throw new ConcurrencyException(id); + throw new ConcurrencyException(id, expectedVersion.Value, saga.Version); #if NET40 Add #else diff --git a/Framework/Cqrs/Domain/UnitOfWork.cs b/Framework/Cqrs/Domain/UnitOfWork.cs index 37b8226ca6..2403f21853 100644 --- a/Framework/Cqrs/Domain/UnitOfWork.cs +++ b/Framework/Cqrs/Domain/UnitOfWork.cs @@ -79,7 +79,7 @@ async Task AddAsync }; TrackedAggregates.Add(aggregate.Id, aggregateDescriptor); } - else if (((TrackedAggregates[aggregate.Id]).Aggregate) != (IAggregateRoot)aggregate) + else if (TrackedAggregates[aggregate.Id].Aggregate != (IAggregateRoot)aggregate) throw new ConcurrencyException(aggregate.Id); #if NET40 #else @@ -103,7 +103,7 @@ async Task GetAsync { var trackedAggregate = (TAggregateRoot)TrackedAggregates[id].Aggregate; if (expectedVersion != null && trackedAggregate.Version != expectedVersion) - throw new ConcurrencyException(trackedAggregate.Id); + throw new ConcurrencyException(trackedAggregate.Id, expectedVersion.Value, trackedAggregate.Version); return trackedAggregate; } @@ -115,7 +115,7 @@ async Task GetAsync #endif (id); if (expectedVersion != null && aggregate.Version != expectedVersion) - throw new ConcurrencyException(id); + throw new ConcurrencyException(id, expectedVersion.Value, aggregate.Version); #if NET40 Add #else