├── .gitignore
├── .nuke
├── Directory.Build.props
├── Eventfully.sln
├── LICENSE
├── README.md
├── analyzers
└── Eventfully.Core.Analyzers
│ ├── Eventfully.Core.Analyzers.Vsix
│ ├── Eventfully.Core.Analyzers.Vsix.csproj
│ └── source.extension.vsixmanifest
│ └── Eventfully.Core.Analyzers
│ ├── Eventfully.Core.Analyzers.csproj
│ ├── MissingHandlerForMapIdForCodeFixProvider.cs
│ ├── MissingMapIdForAnalyzer.cs
│ ├── MissingMapIdForCodeFixProvider.cs
│ ├── Resources.Designer.cs
│ ├── Resources.resx
│ └── tools
│ ├── install.ps1
│ └── uninstall.ps1
├── appveyor.yml
├── build.ps1
├── build.sh
├── build
├── .editorconfig
├── Build.cs
├── _build.csproj
└── _build.csproj.DotSettings
├── samples
└── EFCore
│ ├── Eventfully.Samples.AzureServiceBus.ConsoleApp
│ ├── ApplicationDbContext.cs
│ ├── Entities
│ │ └── Order.cs
│ ├── Eventfully.Samples.AzureServiceBus.ConsoleApp.csproj
│ ├── MessagingProfile.cs
│ ├── Migrations
│ │ ├── 20191203191923_Initial.Designer.cs
│ │ ├── 20191203191923_Initial.cs
│ │ ├── 20200123164022_Orders.Designer.cs
│ │ ├── 20200123164022_Orders.cs
│ │ └── ApplicationDbContextModelSnapshot.cs
│ ├── OrderCreated
│ │ ├── OrderCreated.cs
│ │ └── OrderCreatedHandler.cs
│ ├── PaymentMethodCreated
│ │ ├── PaymentMethodCreated.cs
│ │ └── PaymentMethodCreatedHandler.cs
│ ├── PizzaProcess.cs
│ ├── Program.cs
│ ├── Undependable
│ │ └── FailingHandler.cs
│ └── appsettings.json
│ └── Eventfully.Samples.ConsoleApp
│ ├── ApplicationDbContext.cs
│ ├── Entities
│ └── Order.cs
│ ├── Eventfully.Samples.ConsoleApp.csproj
│ ├── MessagingProfile.cs
│ ├── Migrations
│ ├── 20191203191923_Initial.Designer.cs
│ ├── 20191203191923_Initial.cs
│ ├── 20200123164022_Orders.Designer.cs
│ ├── 20200123164022_Orders.cs
│ └── ApplicationDbContextModelSnapshot.cs
│ ├── OrderCreated
│ ├── OrderCreated.cs
│ └── OrderCreatedHandler.cs
│ ├── PaymentMethodCreated
│ ├── PaymentMethodCreated.cs
│ └── PaymentMethodCreatedHandler.cs
│ ├── Program.cs
│ └── appsettings.json
├── src
├── Eventfully.AzureKeyVault
│ ├── AzureKeyVaultKeyProvider.cs
│ ├── Eventfully.AzureKeyVault.csproj
│ └── README.txt
├── Eventfully.Core
│ ├── Config
│ │ ├── EndpointBindings.cs
│ │ ├── IEndpointFluent.cs
│ │ ├── MessagingConfiguration.cs
│ │ └── MessagingProfile.cs
│ ├── DependencyInjection
│ │ ├── IRegistrar.cs
│ │ └── IServiceFactory.cs
│ ├── Endpoints
│ │ ├── Endpoint.cs
│ │ ├── EndpointSettings.cs
│ │ └── IEndpoint.cs
│ ├── Eventfully.Core.csproj
│ ├── Filters
│ │ ├── AesTransportEncpryptionFilter.cs
│ │ ├── ExpirationCheckStep.cs
│ │ ├── IEncryptionKeyProvider.cs
│ │ ├── IIntegrationMessageFilter.cs
│ │ ├── ITransportMessageFilter.cs
│ │ ├── InboundMessagePipeline.cs
│ │ ├── MessageExtractionStep.cs
│ │ ├── MessageSerializationStep.cs
│ │ ├── MessageTypeIntegrationFilter.cs
│ │ ├── MessageTypeTransportFilter.cs
│ │ ├── OutboundMessagePipeline.cs
│ │ └── Pipeline.cs
│ ├── Handling
│ │ ├── ICustomMessageHandler.cs
│ │ ├── IMessageDispatcher.cs
│ │ ├── IMessageHandler.cs
│ │ ├── MessageContext.cs
│ │ └── MessageDispatcher.cs
│ ├── Messages
│ │ ├── IIntegrationMessage.cs
│ │ ├── IMessageExtractor.cs
│ │ ├── IntegrationCommand.cs
│ │ ├── IntegrationEvent.cs
│ │ ├── IntegrationMessage.cs
│ │ ├── IntegrationReply.cs
│ │ ├── MessageMetaData.cs
│ │ └── WrappedMessage.cs
│ ├── MessagingClient.cs
│ ├── MessagingMap.cs
│ ├── MessagingService.cs
│ ├── Outboxing
│ │ ├── IOutbox.cs
│ │ ├── IOutboxManager.cs
│ │ ├── IOutboxSession.cs
│ │ ├── OutboxDispatchOptions.cs
│ │ ├── OutboxDispatchResult.cs
│ │ └── OutboxManager.cs
│ ├── README.md
│ ├── Saga
│ │ ├── ISaga.cs
│ │ ├── ISagaPersistence.cs
│ │ ├── ITriggeredBy.cs
│ │ └── ProcessManagerMachine.cs
│ ├── Transports
│ │ ├── ITransport.cs
│ │ ├── ITransportFactory.cs
│ │ ├── LocalTransport.cs
│ │ ├── TestTransport.cs
│ │ ├── Transport.cs
│ │ ├── TransportConfigurationExtenstions.cs
│ │ ├── TransportMessage.cs
│ │ └── TransportSettings.cs
│ └── Util
│ │ ├── ConstantRetryStrategy.cs
│ │ ├── DefaultExponentialRetryStrategy.cs
│ │ ├── Enumeration.cs
│ │ ├── Exceptions
│ │ ├── EndpointNotFoundException.cs
│ │ ├── InvalidMessageTypeException.cs
│ │ ├── MessageExpiredException.cs
│ │ ├── NonTransientException.cs
│ │ └── UnknownMessageTypeException.cs
│ │ ├── ICountingSemaphore.cs
│ │ ├── IRetryIntervalStrategy.cs
│ │ └── MessagingLogging.cs
├── Eventfully.EFCoreOutbox
│ ├── Eventfully.EFCoreOutbox.csproj
│ ├── Outbox.cs
│ ├── OutboxMessage.cs
│ ├── OutboxSession.cs
│ ├── README.txt
│ ├── RegistrationExtensions.cs
│ └── SqlConnectionFactory.cs
├── Eventfully.MicrosoftDependencyInjection
│ ├── Eventfully.Extensions.Microsoft.DependencyInjection.csproj
│ ├── Extensions.cs
│ ├── ServiceFactory.cs
│ └── ServiceRegistrar.cs
├── Eventfully.Semaphore.SqlServer
│ ├── CreateSqlSemaphoreTable.sql
│ ├── Eventfully.Semaphore.SqlServer.csproj
│ ├── README.txt
│ ├── SqlConnectionFactory.cs
│ └── SqlServerSemaphore.cs
└── Eventfully.Transports.AzureServiceBus
│ ├── AzureServiceBusClientCache.cs
│ ├── AzureServiceBusConfigurationExtensions.cs
│ ├── AzureServiceBusMessagePump.cs
│ ├── AzureServiceBusMetaDataMapper.cs
│ ├── AzureServiceBusTransport.cs
│ ├── AzureServiceBusTransportFactory.cs
│ ├── Eventfully.Transports.AzureServiceBus.csproj
│ └── Recoverability
│ ├── AzureServiceBusCloneRecoverabilityProvider.cs
│ ├── AzureServiceBusDeferRecoverabilityProvider.cs
│ ├── IAzureServiceBusRecoverabilityProvider.cs
│ └── RecoverabilityControlMessage.cs
└── tests
├── Eventfully.Core.Analyzers.Test
├── Eventfully.Core.Analyzers.Test.csproj
├── Helpers
│ ├── CodeFixVerifier.Helper.cs
│ ├── DiagnosticResult.cs
│ └── DiagnosticVerifier.Helper.cs
├── MissingMapIdForAnalyzerTests.cs
├── PizzaProcess.cs
└── Verifiers
│ ├── CodeFixVerifier.cs
│ └── DiagnosticVerifier.cs
├── Eventfully.Core.UnitTests
├── Eventfully.Core.UnitTests.csproj
├── MessageDispatcherTests.cs
├── MessagingServiceTests.cs
├── MetaDataTests.cs
├── ProcessManagerStateMachineCustomNaming.cs
├── ProcessManagerStateMachineTests.cs
└── Util
│ └── UnitTestFixture.cs
├── Eventfully.EFCoreOutbox.IntegrationTests
├── Eventfully.EFCoreOutbox.IntegrationTests.csproj
├── Migrations
│ ├── 20200106191125_Initial.Designer.cs
│ ├── 20200106191125_Initial.cs
│ └── ApplicationDbContextModelSnapshot.cs
├── OutboxConcurrencyTests.cs
├── OutboxDequeueTests.cs
├── OutboxEnqueueTests.cs
├── OutboxManagementTests.cs
├── OutboxRetryTests.cs
├── TransientDispatchTests.cs
├── Util
│ ├── ApplicationDbContext.cs
│ ├── IntegrationTestBase.cs
│ ├── IntegrationTestFixture.cs
│ └── TestMessage.cs
└── appsettings.json
├── Eventfully.EFCoreOutbox.UnitTests
├── Eventfully.EFCoreOutbox.UnitTests.csproj
├── OutboxMessageTests.cs
└── TestMessage.cs
├── Eventfully.Semaphore.SqlServer.IntegrationTests
├── Eventfully.Semaphore.SqlServer.IntegrationTests.csproj
├── SemaphoreTests.cs
├── Util
│ ├── IntegrationTestBase.cs
│ └── IntegrationTestFixture.cs
└── appsettings.json
├── Eventfully.Transports.AzureServiceBus.IntegrationTests
├── DeferRecoverabilityProviderTests.cs
├── DispatchTests.cs
├── Eventfully.Transports.AzureServiceBus.IntegrationTests.csproj
├── HandleTests.cs
├── Util
│ ├── FakeInterfaces.cs
│ ├── IntegrationTestBase.cs
│ ├── IntegrationTestFixture.cs
│ └── TestMessage.cs
└── appsettings.json
└── Eventfully.Transports.AzureServiceBus.UnitTests.UnitTests
├── Eventfully.Transports.AzureServiceBus.UnitTests.csproj
├── MetaDataMapperApplyTests.cs
└── MetaDataMapperExtractTests.cs
/.nuke:
--------------------------------------------------------------------------------
1 | Eventfully.sln
--------------------------------------------------------------------------------
/Directory.Build.props:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cfrenzel/Eventfully/bf32d3665898fb11b9bc5c25a63ffe6abe4b467b/Directory.Build.props
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 Camron Frenzel
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/analyzers/Eventfully.Core.Analyzers/Eventfully.Core.Analyzers.Vsix/Eventfully.Core.Analyzers.Vsix.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | 14.0
6 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)
7 |
8 |
9 |
10 | Debug
11 | AnyCPU
12 | AnyCPU
13 | 2.0
14 | {82b43b9b-a64c-4715-b499-d71e9ca2bd60};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
15 | {8DE2E3AD-9F5D-454E-86A9-2D68519737A0}
16 | Library
17 | Properties
18 | Eventfully.Core.Analyzers.Vsix
19 | Eventfully.Core.Analyzers
20 | v4.6.1
21 | false
22 | false
23 | false
24 | false
25 | false
26 | false
27 | Roslyn
28 |
29 |
30 | true
31 | full
32 | false
33 | bin\Debug\
34 | DEBUG;TRACE
35 | prompt
36 | 4
37 |
38 |
39 | pdbonly
40 | true
41 | bin\Release\
42 | TRACE
43 | prompt
44 | 4
45 |
46 |
47 | Program
48 | $(DevEnvDir)devenv.exe
49 | /rootsuffix Roslyn
50 |
51 |
52 |
53 | Designer
54 |
55 |
56 |
57 |
58 |
--------------------------------------------------------------------------------
/analyzers/Eventfully.Core.Analyzers/Eventfully.Core.Analyzers.Vsix/source.extension.vsixmanifest:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Eventfully.Core.Analyzers
6 | This is a sample diagnostic extension for the .NET Compiler Platform ("Roslyn").
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/analyzers/Eventfully.Core.Analyzers/Eventfully.Core.Analyzers/Eventfully.Core.Analyzers.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netstandard1.3
5 | false
6 | True
7 |
8 |
9 |
10 | Eventfully.Core.Analyzers
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/analyzers/Eventfully.Core.Analyzers/Eventfully.Core.Analyzers/Resources.Designer.cs:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | //
3 | // This code was generated by a tool.
4 | // Runtime Version:4.0.30319.42000
5 | //
6 | // Changes to this file may cause incorrect behavior and will be lost if
7 | // the code is regenerated.
8 | //
9 | //------------------------------------------------------------------------------
10 |
11 | namespace Eventfully.Core.Analyzers {
12 | using System;
13 | using System.Reflection;
14 |
15 |
16 | ///
17 | /// A strongly-typed resource class, for looking up localized strings, etc.
18 | ///
19 | // This class was auto-generated by the StronglyTypedResourceBuilder
20 | // class via a tool like ResGen or Visual Studio.
21 | // To add or remove a member, edit your .ResX file then rerun ResGen
22 | // with the /str option, or rebuild your VS project.
23 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")]
24 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
25 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
26 | internal class Resources {
27 |
28 | private static global::System.Resources.ResourceManager resourceMan;
29 |
30 | private static global::System.Globalization.CultureInfo resourceCulture;
31 |
32 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
33 | internal Resources() {
34 | }
35 |
36 | ///
37 | /// Returns the cached ResourceManager instance used by this class.
38 | ///
39 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
40 | internal static global::System.Resources.ResourceManager ResourceManager {
41 | get {
42 | if (object.ReferenceEquals(resourceMan, null)) {
43 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Eventfully.Core.Analyzers.Resources", typeof(Resources).GetTypeInfo().Assembly);
44 | resourceMan = temp;
45 | }
46 | return resourceMan;
47 | }
48 | }
49 |
50 | ///
51 | /// Overrides the current thread's CurrentUICulture property for all
52 | /// resource lookups using this strongly typed resource class.
53 | ///
54 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
55 | internal static global::System.Globalization.CultureInfo Culture {
56 | get {
57 | return resourceCulture;
58 | }
59 | set {
60 | resourceCulture = value;
61 | }
62 | }
63 |
64 | ///
65 | /// Looks up a localized string similar to Should have MapIdFor for each event handled.
66 | ///
67 | internal static string AnalyzerDescription {
68 | get {
69 | return ResourceManager.GetString("AnalyzerDescription", resourceCulture);
70 | }
71 | }
72 |
73 | ///
74 | /// Looks up a localized string similar to {0}.
75 | ///
76 | internal static string AnalyzerMessageFormat {
77 | get {
78 | return ResourceManager.GetString("AnalyzerMessageFormat", resourceCulture);
79 | }
80 | }
81 |
82 | ///
83 | /// Looks up a localized string similar to Missing MapIdFor.
84 | ///
85 | internal static string AnalyzerTitle {
86 | get {
87 | return ResourceManager.GetString("AnalyzerTitle", resourceCulture);
88 | }
89 | }
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/analyzers/Eventfully.Core.Analyzers/Eventfully.Core.Analyzers/tools/install.ps1:
--------------------------------------------------------------------------------
1 | param($installPath, $toolsPath, $package, $project)
2 |
3 | if($project.Object.SupportsPackageDependencyResolution)
4 | {
5 | if($project.Object.SupportsPackageDependencyResolution())
6 | {
7 | # Do not install analyzers via install.ps1, instead let the project system handle it.
8 | return
9 | }
10 | }
11 |
12 | $analyzersPaths = Join-Path (Join-Path (Split-Path -Path $toolsPath -Parent) "analyzers") * -Resolve
13 |
14 | foreach($analyzersPath in $analyzersPaths)
15 | {
16 | if (Test-Path $analyzersPath)
17 | {
18 | # Install the language agnostic analyzers.
19 | foreach ($analyzerFilePath in Get-ChildItem -Path "$analyzersPath\*.dll" -Exclude *.resources.dll)
20 | {
21 | if($project.Object.AnalyzerReferences)
22 | {
23 | $project.Object.AnalyzerReferences.Add($analyzerFilePath.FullName)
24 | }
25 | }
26 | }
27 | }
28 |
29 | # $project.Type gives the language name like (C# or VB.NET)
30 | $languageFolder = ""
31 | if($project.Type -eq "C#")
32 | {
33 | $languageFolder = "cs"
34 | }
35 | if($project.Type -eq "VB.NET")
36 | {
37 | $languageFolder = "vb"
38 | }
39 | if($languageFolder -eq "")
40 | {
41 | return
42 | }
43 |
44 | foreach($analyzersPath in $analyzersPaths)
45 | {
46 | # Install language specific analyzers.
47 | $languageAnalyzersPath = join-path $analyzersPath $languageFolder
48 | if (Test-Path $languageAnalyzersPath)
49 | {
50 | foreach ($analyzerFilePath in Get-ChildItem -Path "$languageAnalyzersPath\*.dll" -Exclude *.resources.dll)
51 | {
52 | if($project.Object.AnalyzerReferences)
53 | {
54 | $project.Object.AnalyzerReferences.Add($analyzerFilePath.FullName)
55 | }
56 | }
57 | }
58 | }
--------------------------------------------------------------------------------
/analyzers/Eventfully.Core.Analyzers/Eventfully.Core.Analyzers/tools/uninstall.ps1:
--------------------------------------------------------------------------------
1 | param($installPath, $toolsPath, $package, $project)
2 |
3 | if($project.Object.SupportsPackageDependencyResolution)
4 | {
5 | if($project.Object.SupportsPackageDependencyResolution())
6 | {
7 | # Do not uninstall analyzers via uninstall.ps1, instead let the project system handle it.
8 | return
9 | }
10 | }
11 |
12 | $analyzersPaths = Join-Path (Join-Path (Split-Path -Path $toolsPath -Parent) "analyzers") * -Resolve
13 |
14 | foreach($analyzersPath in $analyzersPaths)
15 | {
16 | # Uninstall the language agnostic analyzers.
17 | if (Test-Path $analyzersPath)
18 | {
19 | foreach ($analyzerFilePath in Get-ChildItem -Path "$analyzersPath\*.dll" -Exclude *.resources.dll)
20 | {
21 | if($project.Object.AnalyzerReferences)
22 | {
23 | $project.Object.AnalyzerReferences.Remove($analyzerFilePath.FullName)
24 | }
25 | }
26 | }
27 | }
28 |
29 | # $project.Type gives the language name like (C# or VB.NET)
30 | $languageFolder = ""
31 | if($project.Type -eq "C#")
32 | {
33 | $languageFolder = "cs"
34 | }
35 | if($project.Type -eq "VB.NET")
36 | {
37 | $languageFolder = "vb"
38 | }
39 | if($languageFolder -eq "")
40 | {
41 | return
42 | }
43 |
44 | foreach($analyzersPath in $analyzersPaths)
45 | {
46 | # Uninstall language specific analyzers.
47 | $languageAnalyzersPath = join-path $analyzersPath $languageFolder
48 | if (Test-Path $languageAnalyzersPath)
49 | {
50 | foreach ($analyzerFilePath in Get-ChildItem -Path "$languageAnalyzersPath\*.dll" -Exclude *.resources.dll)
51 | {
52 | if($project.Object.AnalyzerReferences)
53 | {
54 | try
55 | {
56 | $project.Object.AnalyzerReferences.Remove($analyzerFilePath.FullName)
57 | }
58 | catch
59 | {
60 |
61 | }
62 | }
63 | }
64 | }
65 | }
--------------------------------------------------------------------------------
/appveyor.yml:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cfrenzel/Eventfully/bf32d3665898fb11b9bc5c25a63ffe6abe4b467b/appveyor.yml
--------------------------------------------------------------------------------
/build.ps1:
--------------------------------------------------------------------------------
1 | [CmdletBinding()]
2 | Param(
3 | [Parameter(Position=0,Mandatory=$false,ValueFromRemainingArguments=$true)]
4 | [string[]]$BuildArguments
5 | )
6 |
7 | Write-Output "PowerShell $($PSVersionTable.PSEdition) version $($PSVersionTable.PSVersion)"
8 |
9 | Set-StrictMode -Version 2.0; $ErrorActionPreference = "Stop"; $ConfirmPreference = "None"; trap { exit 1 }
10 | $PSScriptRoot = Split-Path $MyInvocation.MyCommand.Path -Parent
11 |
12 | ###########################################################################
13 | # CONFIGURATION
14 | ###########################################################################
15 |
16 | $BuildProjectFile = "$PSScriptRoot\build\_build.csproj"
17 | $TempDirectory = "$PSScriptRoot\\.tmp"
18 |
19 | $DotNetGlobalFile = "$PSScriptRoot\\global.json"
20 | $DotNetInstallUrl = "https://raw.githubusercontent.com/dotnet/cli/master/scripts/obtain/dotnet-install.ps1"
21 | $DotNetChannel = "Current"
22 |
23 | $env:DOTNET_SKIP_FIRST_TIME_EXPERIENCE = 1
24 | $env:DOTNET_CLI_TELEMETRY_OPTOUT = 1
25 |
26 | ###########################################################################
27 | # EXECUTION
28 | ###########################################################################
29 |
30 | function ExecSafe([scriptblock] $cmd) {
31 | & $cmd
32 | if ($LASTEXITCODE) { exit $LASTEXITCODE }
33 | }
34 |
35 | # If global.json exists, load expected version
36 | if (Test-Path $DotNetGlobalFile) {
37 | $DotNetGlobal = $(Get-Content $DotNetGlobalFile | Out-String | ConvertFrom-Json)
38 | if ($DotNetGlobal.PSObject.Properties["sdk"] -and $DotNetGlobal.sdk.PSObject.Properties["version"]) {
39 | $DotNetVersion = $DotNetGlobal.sdk.version
40 | }
41 | }
42 |
43 | # If dotnet is installed locally, and expected version is not set or installation matches the expected version
44 | if ($null -ne (Get-Command "dotnet" -ErrorAction SilentlyContinue) -and `
45 | (!(Test-Path variable:DotNetVersion) -or $(& dotnet --version) -eq $DotNetVersion)) {
46 | $env:DOTNET_EXE = (Get-Command "dotnet").Path
47 | }
48 | else {
49 | $DotNetDirectory = "$TempDirectory\dotnet-win"
50 | $env:DOTNET_EXE = "$DotNetDirectory\dotnet.exe"
51 |
52 | # Download install script
53 | $DotNetInstallFile = "$TempDirectory\dotnet-install.ps1"
54 | New-Item -ItemType Directory -Path $TempDirectory | Out-Null
55 | (New-Object System.Net.WebClient).DownloadFile($DotNetInstallUrl, $DotNetInstallFile)
56 |
57 | # Install by channel or version
58 | if (!(Test-Path variable:DotNetVersion)) {
59 | ExecSafe { & $DotNetInstallFile -InstallDir $DotNetDirectory -Channel $DotNetChannel -NoPath }
60 | } else {
61 | ExecSafe { & $DotNetInstallFile -InstallDir $DotNetDirectory -Version $DotNetVersion -NoPath }
62 | }
63 | }
64 |
65 | Write-Output "Microsoft (R) .NET Core SDK version $(& $env:DOTNET_EXE --version)"
66 |
67 | ExecSafe { & $env:DOTNET_EXE build $BuildProjectFile /nodeReuse:false }
68 | ExecSafe { & $env:DOTNET_EXE run --project $BuildProjectFile --no-build -- $BuildArguments }
69 |
--------------------------------------------------------------------------------
/build.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | echo $(bash --version 2>&1 | head -n 1)
4 |
5 | set -eo pipefail
6 | SCRIPT_DIR=$(cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd)
7 |
8 | ###########################################################################
9 | # CONFIGURATION
10 | ###########################################################################
11 |
12 | BUILD_PROJECT_FILE="$SCRIPT_DIR/build/_build.csproj"
13 | TEMP_DIRECTORY="$SCRIPT_DIR//.tmp"
14 |
15 | DOTNET_GLOBAL_FILE="$SCRIPT_DIR//global.json"
16 | DOTNET_INSTALL_URL="https://raw.githubusercontent.com/dotnet/cli/master/scripts/obtain/dotnet-install.sh"
17 | DOTNET_CHANNEL="Current"
18 |
19 | export DOTNET_CLI_TELEMETRY_OPTOUT=1
20 | export DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1
21 |
22 | ###########################################################################
23 | # EXECUTION
24 | ###########################################################################
25 |
26 | function FirstJsonValue {
27 | perl -nle 'print $1 if m{"'$1'": "([^"]+)",?}' <<< ${@:2}
28 | }
29 |
30 | # If global.json exists, load expected version
31 | if [ -f "$DOTNET_GLOBAL_FILE" ]; then
32 | DOTNET_VERSION=$(FirstJsonValue "version" $(cat "$DOTNET_GLOBAL_FILE"))
33 | if [ "$DOTNET_VERSION" == "" ]; then
34 | unset DOTNET_VERSION
35 | fi
36 | fi
37 |
38 | # If dotnet is installed locally, and expected version is not set or installation matches the expected version
39 | if [[ -x "$(command -v dotnet)" && (-z ${DOTNET_VERSION+x} || $(dotnet --version 2>&1) == "$DOTNET_VERSION") ]]; then
40 | export DOTNET_EXE="$(command -v dotnet)"
41 | else
42 | DOTNET_DIRECTORY="$TEMP_DIRECTORY/dotnet-unix"
43 | export DOTNET_EXE="$DOTNET_DIRECTORY/dotnet"
44 |
45 | # Download install script
46 | DOTNET_INSTALL_FILE="$TEMP_DIRECTORY/dotnet-install.sh"
47 | mkdir -p "$TEMP_DIRECTORY"
48 | curl -Lsfo "$DOTNET_INSTALL_FILE" "$DOTNET_INSTALL_URL"
49 | chmod +x "$DOTNET_INSTALL_FILE"
50 |
51 | # Install by channel or version
52 | if [ -z ${DOTNET_VERSION+x} ]; then
53 | "$DOTNET_INSTALL_FILE" --install-dir "$DOTNET_DIRECTORY" --channel "$DOTNET_CHANNEL" --no-path
54 | else
55 | "$DOTNET_INSTALL_FILE" --install-dir "$DOTNET_DIRECTORY" --version "$DOTNET_VERSION" --no-path
56 | fi
57 | fi
58 |
59 | echo "Microsoft (R) .NET Core SDK version $("$DOTNET_EXE" --version)"
60 |
61 | "$DOTNET_EXE" build "$BUILD_PROJECT_FILE" /nodeReuse:false
62 | "$DOTNET_EXE" run --project "$BUILD_PROJECT_FILE" --no-build -- "$@"
63 |
--------------------------------------------------------------------------------
/build/.editorconfig:
--------------------------------------------------------------------------------
1 | [*.cs]
2 | dotnet_style_qualification_for_field = false:warning
3 | dotnet_style_qualification_for_property = false:warning
4 | dotnet_style_qualification_for_method = false:warning
5 | dotnet_style_qualification_for_event = false:warning
6 | dotnet_style_require_accessibility_modifiers = never:warning
7 |
8 | csharp_style_expression_bodied_properties = true:warning
9 | csharp_style_expression_bodied_indexers = true:warning
10 | csharp_style_expression_bodied_accessors = true:warning
11 |
--------------------------------------------------------------------------------
/build/_build.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | netcoreapp3.0
6 | false
7 |
8 | False
9 | CS0649;CS0169
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/samples/EFCore/Eventfully.Samples.AzureServiceBus.ConsoleApp/ApplicationDbContext.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading;
6 | using System.Threading.Tasks;
7 | using Microsoft.EntityFrameworkCore;
8 | using Microsoft.EntityFrameworkCore.Migrations;
9 | using Eventfully;
10 | using Eventfully.EFCoreOutbox;
11 | using Microsoft.EntityFrameworkCore.Design;
12 | using Microsoft.Extensions.Configuration;
13 | using System.IO;
14 | using Microsoft.Extensions.DependencyInjection;
15 | using System.Reflection;
16 |
17 | namespace Eventfully.Samples.ConsoleApp
18 | {
19 | public class ApplicationDbContext : DbContext, ISupportTransientDispatch
20 | {
21 | public event EventHandler ChangesPersisted;
22 |
23 | public ApplicationDbContext(DbContextOptions options)
24 | : base(options)
25 | {}
26 |
27 | public DbSet Orders { get; set; }
28 |
29 |
30 |
31 | protected override void OnModelCreating(ModelBuilder builder)
32 | {
33 | base.OnModelCreating(builder);
34 |
35 | /*** OUTBOX Message Entities (see Messaging.EFCoreOutbox) ***/
36 | builder.AddEFCoreOutbox();
37 | }
38 |
39 | public override int SaveChanges()
40 | {
41 | _preSaveChanges();
42 | var res = base.SaveChanges();
43 | _postSaveChanges();
44 | return res;
45 | }
46 |
47 | public override async Task SaveChangesAsync(CancellationToken cancellationToken = default(CancellationToken))
48 | {
49 | _preSaveChanges();
50 | var res = await base.SaveChangesAsync(cancellationToken);
51 | _postSaveChanges();
52 | return res;
53 | }
54 |
55 | private void _preSaveChanges()
56 | {
57 | _addDateTimeStamps();
58 | }
59 | private void _postSaveChanges()
60 | {
61 | this.ChangesPersisted?.Invoke(this, null);
62 | }
63 |
64 |
65 | private void _addDateTimeStamps()
66 | {
67 | foreach (var item in ChangeTracker.Entries().Where(e => e.State == EntityState.Added || e.State == EntityState.Modified))
68 | {
69 | var now = DateTime.UtcNow;
70 |
71 | if (item.State == EntityState.Added && item.Metadata.FindProperty("CreatedAt") != null)
72 | item.Property("CreatedAt").CurrentValue = now;
73 | else if (item.State == EntityState.Added && item.Metadata.FindProperty("CreatedAtUtc") != null)
74 | item.Property("CreatedAtUtc").CurrentValue = now;
75 |
76 | if (item.Metadata.FindProperty("UpdatedAt") != null)
77 | item.Property("UpdatedAt").CurrentValue = now;
78 | else if (item.Metadata.FindProperty("UpdatedAtUtc") != null)
79 | item.Property("UpdatedAtUtc").CurrentValue = now;
80 | }
81 | }
82 |
83 | }
84 |
85 |
86 |
87 |
88 | }
89 |
90 |
91 |
--------------------------------------------------------------------------------
/samples/EFCore/Eventfully.Samples.AzureServiceBus.ConsoleApp/Entities/Order.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.ComponentModel.DataAnnotations;
4 | using System.ComponentModel.DataAnnotations.Schema;
5 | using System.Text;
6 |
7 | namespace Eventfully.Samples.ConsoleApp
8 | {
9 | public class Order
10 | {
11 | public Guid Id { get; set; }
12 |
13 | [Required, StringLength(500)]
14 | public string Number { get; set; }
15 |
16 | [Column(TypeName = "decimal(18,4)")]
17 | public Decimal Total { get; set; }
18 |
19 | [Required, StringLength(3)]
20 | public string CurrencyCode { get; set; }
21 |
22 | public DateTime CreatedAtUtc { get; set; } = DateTime.UtcNow;
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/samples/EFCore/Eventfully.Samples.AzureServiceBus.ConsoleApp/Eventfully.Samples.AzureServiceBus.ConsoleApp.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | netcoreapp3.0
6 | b8751a4b-9e77-466f-9aab-1599e62b5ea1
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | PreserveNewest
16 | true
17 | PreserveNewest
18 |
19 |
20 |
21 |
22 |
23 |
24 | all
25 | runtime; build; native; contentfiles; analyzers; buildtransitive
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 | false
40 | Analyzer
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/samples/EFCore/Eventfully.Samples.AzureServiceBus.ConsoleApp/MessagingProfile.cs:
--------------------------------------------------------------------------------
1 | using Eventfully.Transports.AzureServiceBus;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Threading.Tasks;
6 |
7 | namespace Eventfully.Samples.ConsoleApp
8 | {
9 | public class MessagingProfile : Profile
10 | {
11 |
12 | public MessagingProfile(Microsoft.Extensions.Configuration.IConfiguration config)
13 | {
14 | ConfigureEndpoint("Events")
15 | .AsOutbound()
16 | .AsEventDefault()
17 | .BindEvent()
18 | //see AuzureKeyVaultSample for a better way
19 | .UseAesEncryption(config.GetSection("SampleAESKey").Value)
20 | .UseAzureServiceBusTransport()
21 | ;
22 | ConfigureEndpoint("Events-Sub")
23 | .AsInbound()
24 | .BindEvent()
25 | //see AuzureKeyVaultSample for a better way
26 | .UseAesEncryption(config.GetSection("SampleAESKey").Value)
27 | .UseAzureServiceBusTransport()
28 | ;
29 |
30 | ConfigureEndpoint("Commands")
31 | .AsInboundOutbound()
32 | .UseAzureServiceBusTransport()
33 | ;
34 |
35 | ConfigureEndpoint("Replies")
36 | .AsInboundOutbound()
37 | .AsReplyDefault()
38 | .UseAzureServiceBusTransport()
39 | ;
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/samples/EFCore/Eventfully.Samples.AzureServiceBus.ConsoleApp/Migrations/20191203191923_Initial.Designer.cs:
--------------------------------------------------------------------------------
1 | //
2 | using System;
3 | using Eventfully.Samples.ConsoleApp;
4 | using Microsoft.EntityFrameworkCore;
5 | using Microsoft.EntityFrameworkCore.Infrastructure;
6 | using Microsoft.EntityFrameworkCore.Metadata;
7 | using Microsoft.EntityFrameworkCore.Migrations;
8 | using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
9 |
10 | namespace Eventfully.Samples.ConsoleApp.Migrations
11 | {
12 | [DbContext(typeof(ApplicationDbContext))]
13 | [Migration("20191203191923_Initial")]
14 | partial class Initial
15 | {
16 | protected override void BuildTargetModel(ModelBuilder modelBuilder)
17 | {
18 | #pragma warning disable 612, 618
19 | modelBuilder
20 | .HasAnnotation("ProductVersion", "2.2.6-servicing-10079")
21 | .HasAnnotation("Relational:MaxIdentifierLength", 128)
22 | .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
23 |
24 | modelBuilder.Entity("Messaging.EFCoreOutbox.OutboxMessage", b =>
25 | {
26 | b.Property("Id");
27 |
28 | b.Property("CreatedAtUtc");
29 |
30 | b.Property("Endpoint")
31 | .HasMaxLength(500);
32 |
33 | b.Property("ExpiresAtUtc");
34 |
35 | b.Property("PriorityDateUtc");
36 |
37 | b.Property("Status");
38 |
39 | b.Property("TryCount");
40 |
41 | b.Property("Type")
42 | .IsRequired()
43 | .HasMaxLength(500);
44 |
45 | b.HasKey("Id");
46 |
47 | b.HasIndex("PriorityDateUtc", "Status")
48 | .HasName("IX_PriorityDateUtc")
49 | .HasAnnotation("SqlServer:Include", new[] { "TryCount", "Type", "ExpiresAtUtc", "CreatedAtUtc", "Endpoint" });
50 |
51 | b.ToTable("OutboxMessages");
52 | });
53 |
54 | modelBuilder.Entity("Messaging.EFCoreOutbox.OutboxMessage+OutboxMessageData", b =>
55 | {
56 | b.Property("Id");
57 |
58 | b.Property("Data")
59 | .IsRequired();
60 |
61 | b.Property("MetaData");
62 |
63 | b.HasKey("Id");
64 |
65 | b.ToTable("OutboxMessageData");
66 | });
67 |
68 | modelBuilder.Entity("Messaging.EFCoreOutbox.OutboxMessage+OutboxMessageData", b =>
69 | {
70 | b.HasOne("Messaging.EFCoreOutbox.OutboxMessage")
71 | .WithOne("MessageData")
72 | .HasForeignKey("Messaging.EFCoreOutbox.OutboxMessage+OutboxMessageData", "Id")
73 | .OnDelete(DeleteBehavior.Cascade);
74 | });
75 | #pragma warning restore 612, 618
76 | }
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/samples/EFCore/Eventfully.Samples.AzureServiceBus.ConsoleApp/Migrations/20191203191923_Initial.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Microsoft.EntityFrameworkCore.Migrations;
3 |
4 | namespace Eventfully.Samples.ConsoleApp.Migrations
5 | {
6 | public partial class Initial : Migration
7 | {
8 | protected override void Up(MigrationBuilder migrationBuilder)
9 | {
10 | migrationBuilder.CreateTable(
11 | name: "OutboxMessages",
12 | columns: table => new
13 | {
14 | Id = table.Column(nullable: false),
15 | PriorityDateUtc = table.Column(nullable: false),
16 | TryCount = table.Column(nullable: false),
17 | Type = table.Column(maxLength: 500, nullable: false),
18 | Endpoint = table.Column(maxLength: 500, nullable: true),
19 | Status = table.Column(nullable: false),
20 | CreatedAtUtc = table.Column(nullable: false),
21 | ExpiresAtUtc = table.Column(nullable: true)
22 | },
23 | constraints: table =>
24 | {
25 | table.PrimaryKey("PK_OutboxMessages", x => x.Id);
26 | });
27 |
28 | migrationBuilder.CreateTable(
29 | name: "OutboxMessageData",
30 | columns: table => new
31 | {
32 | Id = table.Column(nullable: false),
33 | Data = table.Column(nullable: false),
34 | MetaData = table.Column(nullable: true)
35 | },
36 | constraints: table =>
37 | {
38 | table.PrimaryKey("PK_OutboxMessageData", x => x.Id);
39 | table.ForeignKey(
40 | name: "FK_OutboxMessageData_OutboxMessages_Id",
41 | column: x => x.Id,
42 | principalTable: "OutboxMessages",
43 | principalColumn: "Id",
44 | onDelete: ReferentialAction.Cascade);
45 | });
46 |
47 | migrationBuilder.CreateIndex(
48 | name: "IX_PriorityDateUtc",
49 | table: "OutboxMessages",
50 | columns: new[] { "PriorityDateUtc", "Status" })
51 | .Annotation("SqlServer:Include", new[] { "TryCount", "Type", "ExpiresAtUtc", "CreatedAtUtc", "Endpoint" });
52 | }
53 |
54 | protected override void Down(MigrationBuilder migrationBuilder)
55 | {
56 | migrationBuilder.DropTable(
57 | name: "OutboxMessageData");
58 |
59 | migrationBuilder.DropTable(
60 | name: "OutboxMessages");
61 | }
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/samples/EFCore/Eventfully.Samples.AzureServiceBus.ConsoleApp/Migrations/20200123164022_Orders.Designer.cs:
--------------------------------------------------------------------------------
1 | //
2 | using System;
3 | using Eventfully.Samples.ConsoleApp;
4 | using Microsoft.EntityFrameworkCore;
5 | using Microsoft.EntityFrameworkCore.Infrastructure;
6 | using Microsoft.EntityFrameworkCore.Metadata;
7 | using Microsoft.EntityFrameworkCore.Migrations;
8 | using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
9 |
10 | namespace Eventfully.Samples.ConsoleApp.Migrations
11 | {
12 | [DbContext(typeof(ApplicationDbContext))]
13 | [Migration("20200123164022_Orders")]
14 | partial class Orders
15 | {
16 | protected override void BuildTargetModel(ModelBuilder modelBuilder)
17 | {
18 | #pragma warning disable 612, 618
19 | modelBuilder
20 | .HasAnnotation("ProductVersion", "2.2.6-servicing-10079")
21 | .HasAnnotation("Relational:MaxIdentifierLength", 128)
22 | .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
23 |
24 | modelBuilder.Entity("Eventfully.EFCoreOutbox.OutboxMessage", b =>
25 | {
26 | b.Property("Id");
27 |
28 | b.Property("CreatedAtUtc");
29 |
30 | b.Property("Endpoint")
31 | .HasMaxLength(500);
32 |
33 | b.Property("ExpiresAtUtc");
34 |
35 | b.Property("PriorityDateUtc");
36 |
37 | b.Property("Status");
38 |
39 | b.Property("TryCount");
40 |
41 | b.Property("Type")
42 | .IsRequired()
43 | .HasMaxLength(500);
44 |
45 | b.HasKey("Id");
46 |
47 | b.HasIndex("PriorityDateUtc", "Status")
48 | .HasName("IX_PriorityDateUtc")
49 | .HasAnnotation("SqlServer:Include", new[] { "TryCount", "Type", "ExpiresAtUtc", "CreatedAtUtc", "Endpoint" });
50 |
51 | b.ToTable("OutboxMessages");
52 | });
53 |
54 | modelBuilder.Entity("Eventfully.EFCoreOutbox.OutboxMessage+OutboxMessageData", b =>
55 | {
56 | b.Property("Id");
57 |
58 | b.Property("Data")
59 | .IsRequired();
60 |
61 | b.Property("MetaData");
62 |
63 | b.HasKey("Id");
64 |
65 | b.ToTable("OutboxMessageData");
66 | });
67 |
68 | modelBuilder.Entity("Eventfully.Samples.ConsoleApp.Order", b =>
69 | {
70 | b.Property("Id")
71 | .ValueGeneratedOnAdd();
72 |
73 | b.Property("CreatedAtUtc");
74 |
75 | b.Property("CurrencyCode")
76 | .IsRequired()
77 | .HasMaxLength(3);
78 |
79 | b.Property("Number")
80 | .IsRequired()
81 | .HasMaxLength(500);
82 |
83 | b.Property("Total")
84 | .HasColumnType("decimal(18,4)");
85 |
86 | b.HasKey("Id");
87 |
88 | b.ToTable("Orders");
89 | });
90 |
91 | modelBuilder.Entity("Eventfully.EFCoreOutbox.OutboxMessage+OutboxMessageData", b =>
92 | {
93 | b.HasOne("Eventfully.EFCoreOutbox.OutboxMessage")
94 | .WithOne("MessageData")
95 | .HasForeignKey("Eventfully.EFCoreOutbox.OutboxMessage+OutboxMessageData", "Id")
96 | .OnDelete(DeleteBehavior.Cascade);
97 | });
98 | #pragma warning restore 612, 618
99 | }
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/samples/EFCore/Eventfully.Samples.AzureServiceBus.ConsoleApp/Migrations/20200123164022_Orders.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Microsoft.EntityFrameworkCore.Migrations;
3 |
4 | namespace Eventfully.Samples.ConsoleApp.Migrations
5 | {
6 | public partial class Orders : Migration
7 | {
8 | protected override void Up(MigrationBuilder migrationBuilder)
9 | {
10 | migrationBuilder.CreateTable(
11 | name: "Orders",
12 | columns: table => new
13 | {
14 | Id = table.Column(nullable: false),
15 | Number = table.Column(maxLength: 500, nullable: false),
16 | Total = table.Column(type: "decimal(18,4)", nullable: false),
17 | CurrencyCode = table.Column(maxLength: 3, nullable: false),
18 | CreatedAtUtc = table.Column(nullable: false)
19 | },
20 | constraints: table =>
21 | {
22 | table.PrimaryKey("PK_Orders", x => x.Id);
23 | });
24 | }
25 |
26 | protected override void Down(MigrationBuilder migrationBuilder)
27 | {
28 | migrationBuilder.DropTable(
29 | name: "Orders");
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/samples/EFCore/Eventfully.Samples.AzureServiceBus.ConsoleApp/Migrations/ApplicationDbContextModelSnapshot.cs:
--------------------------------------------------------------------------------
1 | //
2 | using System;
3 | using Eventfully.Samples.ConsoleApp;
4 | using Microsoft.EntityFrameworkCore;
5 | using Microsoft.EntityFrameworkCore.Infrastructure;
6 | using Microsoft.EntityFrameworkCore.Metadata;
7 | using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
8 |
9 | namespace Eventfully.Samples.ConsoleApp.Migrations
10 | {
11 | [DbContext(typeof(ApplicationDbContext))]
12 | partial class ApplicationDbContextModelSnapshot : ModelSnapshot
13 | {
14 | protected override void BuildModel(ModelBuilder modelBuilder)
15 | {
16 | #pragma warning disable 612, 618
17 | modelBuilder
18 | .HasAnnotation("ProductVersion", "2.2.6-servicing-10079")
19 | .HasAnnotation("Relational:MaxIdentifierLength", 128)
20 | .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
21 |
22 | modelBuilder.Entity("Eventfully.EFCoreOutbox.OutboxMessage", b =>
23 | {
24 | b.Property("Id");
25 |
26 | b.Property("CreatedAtUtc");
27 |
28 | b.Property("Endpoint")
29 | .HasMaxLength(500);
30 |
31 | b.Property("ExpiresAtUtc");
32 |
33 | b.Property("PriorityDateUtc");
34 |
35 | b.Property("Status");
36 |
37 | b.Property("TryCount");
38 |
39 | b.Property("Type")
40 | .IsRequired()
41 | .HasMaxLength(500);
42 |
43 | b.HasKey("Id");
44 |
45 | b.HasIndex("PriorityDateUtc", "Status")
46 | .HasName("IX_PriorityDateUtc")
47 | .HasAnnotation("SqlServer:Include", new[] { "TryCount", "Type", "ExpiresAtUtc", "CreatedAtUtc", "Endpoint" });
48 |
49 | b.ToTable("OutboxMessages");
50 | });
51 |
52 | modelBuilder.Entity("Eventfully.EFCoreOutbox.OutboxMessage+OutboxMessageData", b =>
53 | {
54 | b.Property("Id");
55 |
56 | b.Property("Data")
57 | .IsRequired();
58 |
59 | b.Property("MetaData");
60 |
61 | b.HasKey("Id");
62 |
63 | b.ToTable("OutboxMessageData");
64 | });
65 |
66 | modelBuilder.Entity("Eventfully.Samples.ConsoleApp.Order", b =>
67 | {
68 | b.Property("Id")
69 | .ValueGeneratedOnAdd();
70 |
71 | b.Property("CreatedAtUtc");
72 |
73 | b.Property("CurrencyCode")
74 | .IsRequired()
75 | .HasMaxLength(3);
76 |
77 | b.Property("Number")
78 | .IsRequired()
79 | .HasMaxLength(500);
80 |
81 | b.Property("Total")
82 | .HasColumnType("decimal(18,4)");
83 |
84 | b.HasKey("Id");
85 |
86 | b.ToTable("Orders");
87 | });
88 |
89 | modelBuilder.Entity("Eventfully.EFCoreOutbox.OutboxMessage+OutboxMessageData", b =>
90 | {
91 | b.HasOne("Eventfully.EFCoreOutbox.OutboxMessage")
92 | .WithOne("MessageData")
93 | .HasForeignKey("Eventfully.EFCoreOutbox.OutboxMessage+OutboxMessageData", "Id")
94 | .OnDelete(DeleteBehavior.Cascade);
95 | });
96 | #pragma warning restore 612, 618
97 | }
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/samples/EFCore/Eventfully.Samples.AzureServiceBus.ConsoleApp/OrderCreated/OrderCreated.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 |
5 | namespace Eventfully.Samples.ConsoleApp
6 | {
7 |
8 | public class OrderCreated : IntegrationEvent
9 | {
10 | public override string MessageType => "Sales.OrderCreated";
11 |
12 | public Guid OrderId { get; private set; }
13 | public Decimal TotalDue { get; private set; }
14 | public string CurrencyCode { get; private set; }
15 | public Address ShippingAddress { get; private set; }
16 |
17 | private OrderCreated(){}
18 |
19 | public OrderCreated(Guid orderId, Decimal totalDue, string currencyCode, Address shippignAddr)
20 | {
21 | this.OrderId = orderId;
22 | this.TotalDue = totalDue;
23 | this.CurrencyCode = currencyCode;
24 | this.ShippingAddress = shippignAddr;
25 | }
26 |
27 | public class Address
28 | {
29 | public string Line1 { get; private set; }
30 | public string Line2 { get; private set; }
31 | public string City { get; private set; }
32 | public string StateCode { get; private set; }
33 | public string Zip { get; private set; }
34 |
35 | private Address(){}
36 | public Address(string line1, string line2, string city, string state, string zip)
37 | {
38 | this.Line1 = line1;
39 | this.Line2 = line2;
40 | this.City = city;
41 | this.StateCode = state;
42 | this.Zip = zip;
43 | }
44 |
45 | }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/samples/EFCore/Eventfully.Samples.AzureServiceBus.ConsoleApp/OrderCreated/OrderCreatedHandler.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 | using System.Threading.Tasks;
5 |
6 | namespace Eventfully.Samples.ConsoleApp
7 | {
8 | public class OrderCreatedHandler : IMessageHandler
9 | {
10 | public Task Handle(OrderCreated ev, MessageContext context)
11 | {
12 | Console.WriteLine($"Received OrderCreated Event");
13 | Console.WriteLine($"\tOrderId: {ev.OrderId}");
14 | Console.WriteLine($"\tTotal Due: {ev.TotalDue} {ev.CurrencyCode}");
15 | Console.WriteLine($"\tShipping Address:");
16 | Console.WriteLine($"\t\t{ev.ShippingAddress.Line1}");
17 | if(!String.IsNullOrEmpty(ev.ShippingAddress.Line2))
18 | Console.WriteLine($"\t\t{ev.ShippingAddress.Line2}");
19 | Console.WriteLine($"\t\t{ev.ShippingAddress.City}, {ev.ShippingAddress.StateCode} {ev.ShippingAddress.Zip}");
20 | Console.WriteLine();
21 |
22 | return Task.CompletedTask;
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/samples/EFCore/Eventfully.Samples.AzureServiceBus.ConsoleApp/PaymentMethodCreated/PaymentMethodCreated.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 |
5 | namespace Eventfully.Samples.ConsoleApp
6 | {
7 |
8 | public class PaymentMethodCreated : IntegrationEvent
9 | {
10 | public override string MessageType => "Sales.PaymentMethodCreated";
11 |
12 | public Guid PaymentMethodId { get; private set; }
13 |
14 | public CardInfo MethodInfo { get; private set; }
15 |
16 | private PaymentMethodCreated(){}
17 |
18 | public PaymentMethodCreated(Guid methodId, CardInfo methodInfo)
19 | {
20 | this.PaymentMethodId = methodId;
21 | this.MethodInfo = methodInfo;
22 | }
23 |
24 | public class CardInfo
25 | {
26 | public string Number { get; private set; }
27 | public string NameOnCard { get; set; }
28 | public string ExpirationYear { get; private set; }
29 | public string ExpirationMonth { get; private set; }
30 |
31 | private CardInfo(){}
32 | public CardInfo(string number, string nameOn, string expirationYear, string expirationMonth)
33 | {
34 | this.Number = number;
35 | this.NameOnCard = nameOn;
36 | this.ExpirationYear = expirationYear;
37 | this.ExpirationMonth = expirationMonth;
38 | }
39 |
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/samples/EFCore/Eventfully.Samples.AzureServiceBus.ConsoleApp/PaymentMethodCreated/PaymentMethodCreatedHandler.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 | using System.Threading.Tasks;
5 |
6 | namespace Eventfully.Samples.ConsoleApp
7 | {
8 | public class PaymentMethodCreatedHandler : IMessageHandler
9 | {
10 | public Task Handle(PaymentMethodCreated ev, MessageContext context)
11 | {
12 | Console.WriteLine($"Received PaymentMethodCreated Event");
13 | Console.WriteLine($"\tId: {ev.PaymentMethodId}");
14 | Console.WriteLine($"\tPayment Method Info:");
15 | Console.WriteLine($"\t\t Name: {ev.MethodInfo.NameOnCard}");
16 | Console.WriteLine($"\t\t Number: {ev.MethodInfo.Number}");
17 | Console.WriteLine($"\t\t Expires: {ev.MethodInfo.ExpirationMonth} / {ev.MethodInfo.ExpirationYear}");
18 | Console.WriteLine();
19 |
20 | return Task.CompletedTask;
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/samples/EFCore/Eventfully.Samples.AzureServiceBus.ConsoleApp/Undependable/FailingHandler.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 | using System.Threading.Tasks;
5 |
6 | namespace Eventfully.Samples.ConsoleApp
7 | {
8 | public class FailingHandler
9 | {
10 |
11 | public class Event : IntegrationEvent
12 | {
13 | public override string MessageType => "FailingHandler.Created";
14 | public DateTime FailUntilUtc { get; set; }
15 | public Guid Id { get; set; } = Guid.NewGuid();
16 |
17 | private Event() { }
18 | public Event(DateTime failUntilUtc) => FailUntilUtc = failUntilUtc;
19 |
20 | }
21 |
22 | public class Handler : IMessageHandler
23 | {
24 | public Task Handle(Event ev, MessageContext context)
25 | {
26 | Console.WriteLine($"Received FailingHandler.Created Event");
27 | Console.WriteLine($"\tId: {ev.Id}");
28 | if (DateTime.UtcNow < ev.FailUntilUtc)
29 | {
30 | Console.WriteLine($"\tFailing Until: {ev.FailUntilUtc} - {(ev.FailUntilUtc - DateTime.UtcNow).TotalMinutes :N1} minutes remaining");
31 | Console.WriteLine();
32 | throw new ApplicationException($"Failing until {ev.FailUntilUtc}");
33 | }
34 | Console.WriteLine($"\tSucceeded After: {ev.FailUntilUtc}");
35 | Console.WriteLine();
36 | return Task.CompletedTask;
37 | }
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/samples/EFCore/Eventfully.Samples.AzureServiceBus.ConsoleApp/appsettings.json:
--------------------------------------------------------------------------------
1 | {
2 |
3 | "ConnectionStrings": {
4 | },
5 | "SampleAESKey": "nZr4u7x!A%D*F-JaNdRgUkXp2s5v8y/B",
6 |
7 | "Logging": {
8 | "LogLevel": {
9 | "Default": "Warning",
10 | "System": "Warning",
11 | "Microsoft": "Warning",
12 | "Eventfully": "Information"
13 | }
14 | }
15 |
16 |
17 | }
18 |
--------------------------------------------------------------------------------
/samples/EFCore/Eventfully.Samples.ConsoleApp/ApplicationDbContext.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading;
6 | using System.Threading.Tasks;
7 | using Microsoft.EntityFrameworkCore;
8 | using Microsoft.EntityFrameworkCore.Migrations;
9 | using Eventfully;
10 | using Eventfully.EFCoreOutbox;
11 | using Microsoft.EntityFrameworkCore.Design;
12 | using Microsoft.Extensions.Configuration;
13 | using System.IO;
14 | using Microsoft.Extensions.DependencyInjection;
15 | using System.Reflection;
16 |
17 | namespace Eventfully.Samples.ConsoleApp
18 | {
19 | public class ApplicationDbContext : DbContext, ISupportTransientDispatch
20 | {
21 | public event EventHandler ChangesPersisted;
22 |
23 | public ApplicationDbContext(DbContextOptions options)
24 | : base(options)
25 | {}
26 |
27 | public DbSet Orders { get; set; }
28 |
29 |
30 |
31 | protected override void OnModelCreating(ModelBuilder builder)
32 | {
33 | base.OnModelCreating(builder);
34 |
35 | /*** OUTBOX Message Entities (see Messaging.EFCoreOutbox) ***/
36 | builder.AddEFCoreOutbox();
37 | }
38 |
39 | public override int SaveChanges()
40 | {
41 | _preSaveChanges();
42 | var res = base.SaveChanges();
43 | _postSaveChanges();
44 | return res;
45 | }
46 |
47 | public override async Task SaveChangesAsync(CancellationToken cancellationToken = default(CancellationToken))
48 | {
49 | _preSaveChanges();
50 | var res = await base.SaveChangesAsync(cancellationToken);
51 | _postSaveChanges();
52 | return res;
53 | }
54 |
55 | private void _preSaveChanges()
56 | {
57 | _addDateTimeStamps();
58 | }
59 | private void _postSaveChanges()
60 | {
61 | this.ChangesPersisted?.Invoke(this, null);
62 | }
63 |
64 |
65 | private void _addDateTimeStamps()
66 | {
67 | foreach (var item in ChangeTracker.Entries().Where(e => e.State == EntityState.Added || e.State == EntityState.Modified))
68 | {
69 | var now = DateTime.UtcNow;
70 |
71 | if (item.State == EntityState.Added && item.Metadata.FindProperty("CreatedAt") != null)
72 | item.Property("CreatedAt").CurrentValue = now;
73 | else if (item.State == EntityState.Added && item.Metadata.FindProperty("CreatedAtUtc") != null)
74 | item.Property("CreatedAtUtc").CurrentValue = now;
75 |
76 | if (item.Metadata.FindProperty("UpdatedAt") != null)
77 | item.Property("UpdatedAt").CurrentValue = now;
78 | else if (item.Metadata.FindProperty("UpdatedAtUtc") != null)
79 | item.Property("UpdatedAtUtc").CurrentValue = now;
80 | }
81 | }
82 |
83 | }
84 |
85 |
86 |
87 |
88 | }
89 |
90 |
91 |
--------------------------------------------------------------------------------
/samples/EFCore/Eventfully.Samples.ConsoleApp/Entities/Order.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.ComponentModel.DataAnnotations;
4 | using System.ComponentModel.DataAnnotations.Schema;
5 | using System.Text;
6 |
7 | namespace Eventfully.Samples.ConsoleApp
8 | {
9 | public class Order
10 | {
11 | public Guid Id { get; set; }
12 |
13 | [Required, StringLength(500)]
14 | public string Number { get; set; }
15 |
16 | [Column(TypeName = "decimal(18,4)")]
17 | public Decimal Total { get; set; }
18 |
19 | [Required, StringLength(3)]
20 | public string CurrencyCode { get; set; }
21 |
22 | public DateTime CreatedAtUtc { get; set; } = DateTime.UtcNow;
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/samples/EFCore/Eventfully.Samples.ConsoleApp/Eventfully.Samples.ConsoleApp.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | netcoreapp3.0
6 | b8751a4b-9e77-466f-9aab-1599e62b5ea1
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | PreserveNewest
16 | true
17 | PreserveNewest
18 |
19 |
20 |
21 |
22 |
23 |
24 | all
25 | runtime; build; native; contentfiles; analyzers; buildtransitive
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/samples/EFCore/Eventfully.Samples.ConsoleApp/MessagingProfile.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Threading.Tasks;
5 |
6 | namespace Eventfully.Samples.ConsoleApp
7 | {
8 | public class MessagingProfile : Profile
9 | {
10 |
11 | public MessagingProfile(Microsoft.Extensions.Configuration.IConfiguration config)
12 | {
13 | ConfigureEndpoint("Events")
14 | .AsInboundOutbound()
15 | .BindEvent()
16 | //see AuzureKeyVaultSample for a better way
17 | .UseAesEncryption(config.GetSection("SampleAESKey").Value)
18 | .UseLocalTransport()
19 | ;
20 |
21 | ConfigureEndpoint("Commands")
22 | .AsInboundOutbound()
23 | .UseLocalTransport()
24 | ;
25 |
26 | ConfigureEndpoint("Replies")
27 | .AsInboundOutbound()
28 | .AsReplyDefault()
29 | .UseLocalTransport()
30 | ;
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/samples/EFCore/Eventfully.Samples.ConsoleApp/Migrations/20191203191923_Initial.Designer.cs:
--------------------------------------------------------------------------------
1 | //
2 | using System;
3 | using Eventfully.Samples.ConsoleApp;
4 | using Microsoft.EntityFrameworkCore;
5 | using Microsoft.EntityFrameworkCore.Infrastructure;
6 | using Microsoft.EntityFrameworkCore.Metadata;
7 | using Microsoft.EntityFrameworkCore.Migrations;
8 | using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
9 |
10 | namespace Eventfully.Samples.ConsoleApp.Migrations
11 | {
12 | [DbContext(typeof(ApplicationDbContext))]
13 | [Migration("20191203191923_Initial")]
14 | partial class Initial
15 | {
16 | protected override void BuildTargetModel(ModelBuilder modelBuilder)
17 | {
18 | #pragma warning disable 612, 618
19 | modelBuilder
20 | .HasAnnotation("ProductVersion", "2.2.6-servicing-10079")
21 | .HasAnnotation("Relational:MaxIdentifierLength", 128)
22 | .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
23 |
24 | modelBuilder.Entity("Messaging.EFCoreOutbox.OutboxMessage", b =>
25 | {
26 | b.Property("Id");
27 |
28 | b.Property("CreatedAtUtc");
29 |
30 | b.Property("Endpoint")
31 | .HasMaxLength(500);
32 |
33 | b.Property("ExpiresAtUtc");
34 |
35 | b.Property("PriorityDateUtc");
36 |
37 | b.Property("Status");
38 |
39 | b.Property("TryCount");
40 |
41 | b.Property("Type")
42 | .IsRequired()
43 | .HasMaxLength(500);
44 |
45 | b.HasKey("Id");
46 |
47 | b.HasIndex("PriorityDateUtc", "Status")
48 | .HasName("IX_PriorityDateUtc")
49 | .HasAnnotation("SqlServer:Include", new[] { "TryCount", "Type", "ExpiresAtUtc", "CreatedAtUtc", "Endpoint" });
50 |
51 | b.ToTable("OutboxMessages");
52 | });
53 |
54 | modelBuilder.Entity("Messaging.EFCoreOutbox.OutboxMessage+OutboxMessageData", b =>
55 | {
56 | b.Property("Id");
57 |
58 | b.Property("Data")
59 | .IsRequired();
60 |
61 | b.Property("MetaData");
62 |
63 | b.HasKey("Id");
64 |
65 | b.ToTable("OutboxMessageData");
66 | });
67 |
68 | modelBuilder.Entity("Messaging.EFCoreOutbox.OutboxMessage+OutboxMessageData", b =>
69 | {
70 | b.HasOne("Messaging.EFCoreOutbox.OutboxMessage")
71 | .WithOne("MessageData")
72 | .HasForeignKey("Messaging.EFCoreOutbox.OutboxMessage+OutboxMessageData", "Id")
73 | .OnDelete(DeleteBehavior.Cascade);
74 | });
75 | #pragma warning restore 612, 618
76 | }
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/samples/EFCore/Eventfully.Samples.ConsoleApp/Migrations/20191203191923_Initial.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Microsoft.EntityFrameworkCore.Migrations;
3 |
4 | namespace Eventfully.Samples.ConsoleApp.Migrations
5 | {
6 | public partial class Initial : Migration
7 | {
8 | protected override void Up(MigrationBuilder migrationBuilder)
9 | {
10 | migrationBuilder.CreateTable(
11 | name: "OutboxMessages",
12 | columns: table => new
13 | {
14 | Id = table.Column(nullable: false),
15 | PriorityDateUtc = table.Column(nullable: false),
16 | TryCount = table.Column(nullable: false),
17 | Type = table.Column(maxLength: 500, nullable: false),
18 | Endpoint = table.Column(maxLength: 500, nullable: true),
19 | Status = table.Column(nullable: false),
20 | CreatedAtUtc = table.Column(nullable: false),
21 | ExpiresAtUtc = table.Column(nullable: true)
22 | },
23 | constraints: table =>
24 | {
25 | table.PrimaryKey("PK_OutboxMessages", x => x.Id);
26 | });
27 |
28 | migrationBuilder.CreateTable(
29 | name: "OutboxMessageData",
30 | columns: table => new
31 | {
32 | Id = table.Column(nullable: false),
33 | Data = table.Column(nullable: false),
34 | MetaData = table.Column(nullable: true)
35 | },
36 | constraints: table =>
37 | {
38 | table.PrimaryKey("PK_OutboxMessageData", x => x.Id);
39 | table.ForeignKey(
40 | name: "FK_OutboxMessageData_OutboxMessages_Id",
41 | column: x => x.Id,
42 | principalTable: "OutboxMessages",
43 | principalColumn: "Id",
44 | onDelete: ReferentialAction.Cascade);
45 | });
46 |
47 | migrationBuilder.CreateIndex(
48 | name: "IX_PriorityDateUtc",
49 | table: "OutboxMessages",
50 | columns: new[] { "PriorityDateUtc", "Status" })
51 | .Annotation("SqlServer:Include", new[] { "TryCount", "Type", "ExpiresAtUtc", "CreatedAtUtc", "Endpoint" });
52 | }
53 |
54 | protected override void Down(MigrationBuilder migrationBuilder)
55 | {
56 | migrationBuilder.DropTable(
57 | name: "OutboxMessageData");
58 |
59 | migrationBuilder.DropTable(
60 | name: "OutboxMessages");
61 | }
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/samples/EFCore/Eventfully.Samples.ConsoleApp/Migrations/20200123164022_Orders.Designer.cs:
--------------------------------------------------------------------------------
1 | //
2 | using System;
3 | using Eventfully.Samples.ConsoleApp;
4 | using Microsoft.EntityFrameworkCore;
5 | using Microsoft.EntityFrameworkCore.Infrastructure;
6 | using Microsoft.EntityFrameworkCore.Metadata;
7 | using Microsoft.EntityFrameworkCore.Migrations;
8 | using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
9 |
10 | namespace Eventfully.Samples.ConsoleApp.Migrations
11 | {
12 | [DbContext(typeof(ApplicationDbContext))]
13 | [Migration("20200123164022_Orders")]
14 | partial class Orders
15 | {
16 | protected override void BuildTargetModel(ModelBuilder modelBuilder)
17 | {
18 | #pragma warning disable 612, 618
19 | modelBuilder
20 | .HasAnnotation("ProductVersion", "2.2.6-servicing-10079")
21 | .HasAnnotation("Relational:MaxIdentifierLength", 128)
22 | .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
23 |
24 | modelBuilder.Entity("Eventfully.EFCoreOutbox.OutboxMessage", b =>
25 | {
26 | b.Property("Id");
27 |
28 | b.Property("CreatedAtUtc");
29 |
30 | b.Property("Endpoint")
31 | .HasMaxLength(500);
32 |
33 | b.Property("ExpiresAtUtc");
34 |
35 | b.Property("PriorityDateUtc");
36 |
37 | b.Property("Status");
38 |
39 | b.Property("TryCount");
40 |
41 | b.Property("Type")
42 | .IsRequired()
43 | .HasMaxLength(500);
44 |
45 | b.HasKey("Id");
46 |
47 | b.HasIndex("PriorityDateUtc", "Status")
48 | .HasName("IX_PriorityDateUtc")
49 | .HasAnnotation("SqlServer:Include", new[] { "TryCount", "Type", "ExpiresAtUtc", "CreatedAtUtc", "Endpoint" });
50 |
51 | b.ToTable("OutboxMessages");
52 | });
53 |
54 | modelBuilder.Entity("Eventfully.EFCoreOutbox.OutboxMessage+OutboxMessageData", b =>
55 | {
56 | b.Property("Id");
57 |
58 | b.Property("Data")
59 | .IsRequired();
60 |
61 | b.Property("MetaData");
62 |
63 | b.HasKey("Id");
64 |
65 | b.ToTable("OutboxMessageData");
66 | });
67 |
68 | modelBuilder.Entity("Eventfully.Samples.ConsoleApp.Order", b =>
69 | {
70 | b.Property("Id")
71 | .ValueGeneratedOnAdd();
72 |
73 | b.Property("CreatedAtUtc");
74 |
75 | b.Property("CurrencyCode")
76 | .IsRequired()
77 | .HasMaxLength(3);
78 |
79 | b.Property("Number")
80 | .IsRequired()
81 | .HasMaxLength(500);
82 |
83 | b.Property("Total")
84 | .HasColumnType("decimal(18,4)");
85 |
86 | b.HasKey("Id");
87 |
88 | b.ToTable("Orders");
89 | });
90 |
91 | modelBuilder.Entity("Eventfully.EFCoreOutbox.OutboxMessage+OutboxMessageData", b =>
92 | {
93 | b.HasOne("Eventfully.EFCoreOutbox.OutboxMessage")
94 | .WithOne("MessageData")
95 | .HasForeignKey("Eventfully.EFCoreOutbox.OutboxMessage+OutboxMessageData", "Id")
96 | .OnDelete(DeleteBehavior.Cascade);
97 | });
98 | #pragma warning restore 612, 618
99 | }
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/samples/EFCore/Eventfully.Samples.ConsoleApp/Migrations/20200123164022_Orders.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Microsoft.EntityFrameworkCore.Migrations;
3 |
4 | namespace Eventfully.Samples.ConsoleApp.Migrations
5 | {
6 | public partial class Orders : Migration
7 | {
8 | protected override void Up(MigrationBuilder migrationBuilder)
9 | {
10 | migrationBuilder.CreateTable(
11 | name: "Orders",
12 | columns: table => new
13 | {
14 | Id = table.Column(nullable: false),
15 | Number = table.Column(maxLength: 500, nullable: false),
16 | Total = table.Column(type: "decimal(18,4)", nullable: false),
17 | CurrencyCode = table.Column(maxLength: 3, nullable: false),
18 | CreatedAtUtc = table.Column(nullable: false)
19 | },
20 | constraints: table =>
21 | {
22 | table.PrimaryKey("PK_Orders", x => x.Id);
23 | });
24 | }
25 |
26 | protected override void Down(MigrationBuilder migrationBuilder)
27 | {
28 | migrationBuilder.DropTable(
29 | name: "Orders");
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/samples/EFCore/Eventfully.Samples.ConsoleApp/Migrations/ApplicationDbContextModelSnapshot.cs:
--------------------------------------------------------------------------------
1 | //
2 | using System;
3 | using Eventfully.Samples.ConsoleApp;
4 | using Microsoft.EntityFrameworkCore;
5 | using Microsoft.EntityFrameworkCore.Infrastructure;
6 | using Microsoft.EntityFrameworkCore.Metadata;
7 | using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
8 |
9 | namespace Eventfully.Samples.ConsoleApp.Migrations
10 | {
11 | [DbContext(typeof(ApplicationDbContext))]
12 | partial class ApplicationDbContextModelSnapshot : ModelSnapshot
13 | {
14 | protected override void BuildModel(ModelBuilder modelBuilder)
15 | {
16 | #pragma warning disable 612, 618
17 | modelBuilder
18 | .HasAnnotation("ProductVersion", "2.2.6-servicing-10079")
19 | .HasAnnotation("Relational:MaxIdentifierLength", 128)
20 | .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
21 |
22 | modelBuilder.Entity("Eventfully.EFCoreOutbox.OutboxMessage", b =>
23 | {
24 | b.Property("Id");
25 |
26 | b.Property("CreatedAtUtc");
27 |
28 | b.Property("Endpoint")
29 | .HasMaxLength(500);
30 |
31 | b.Property("ExpiresAtUtc");
32 |
33 | b.Property("PriorityDateUtc");
34 |
35 | b.Property("Status");
36 |
37 | b.Property("TryCount");
38 |
39 | b.Property("Type")
40 | .IsRequired()
41 | .HasMaxLength(500);
42 |
43 | b.HasKey("Id");
44 |
45 | b.HasIndex("PriorityDateUtc", "Status")
46 | .HasName("IX_PriorityDateUtc")
47 | .HasAnnotation("SqlServer:Include", new[] { "TryCount", "Type", "ExpiresAtUtc", "CreatedAtUtc", "Endpoint" });
48 |
49 | b.ToTable("OutboxMessages");
50 | });
51 |
52 | modelBuilder.Entity("Eventfully.EFCoreOutbox.OutboxMessage+OutboxMessageData", b =>
53 | {
54 | b.Property("Id");
55 |
56 | b.Property("Data")
57 | .IsRequired();
58 |
59 | b.Property("MetaData");
60 |
61 | b.HasKey("Id");
62 |
63 | b.ToTable("OutboxMessageData");
64 | });
65 |
66 | modelBuilder.Entity("Eventfully.Samples.ConsoleApp.Order", b =>
67 | {
68 | b.Property("Id")
69 | .ValueGeneratedOnAdd();
70 |
71 | b.Property("CreatedAtUtc");
72 |
73 | b.Property("CurrencyCode")
74 | .IsRequired()
75 | .HasMaxLength(3);
76 |
77 | b.Property("Number")
78 | .IsRequired()
79 | .HasMaxLength(500);
80 |
81 | b.Property("Total")
82 | .HasColumnType("decimal(18,4)");
83 |
84 | b.HasKey("Id");
85 |
86 | b.ToTable("Orders");
87 | });
88 |
89 | modelBuilder.Entity("Eventfully.EFCoreOutbox.OutboxMessage+OutboxMessageData", b =>
90 | {
91 | b.HasOne("Eventfully.EFCoreOutbox.OutboxMessage")
92 | .WithOne("MessageData")
93 | .HasForeignKey("Eventfully.EFCoreOutbox.OutboxMessage+OutboxMessageData", "Id")
94 | .OnDelete(DeleteBehavior.Cascade);
95 | });
96 | #pragma warning restore 612, 618
97 | }
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/samples/EFCore/Eventfully.Samples.ConsoleApp/OrderCreated/OrderCreated.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 |
5 | namespace Eventfully.Samples.ConsoleApp
6 | {
7 |
8 | public class OrderCreated : IntegrationEvent
9 | {
10 | public override string MessageType => "Sales.OrderCreated";
11 |
12 | public Guid OrderId { get; private set; }
13 | public Decimal TotalDue { get; private set; }
14 | public string CurrencyCode { get; private set; }
15 | public Address ShippingAddress { get; private set; }
16 |
17 | private OrderCreated(){}
18 |
19 | public OrderCreated(Guid orderId, Decimal totalDue, string currencyCode, Address shippignAddr)
20 | {
21 | this.OrderId = orderId;
22 | this.TotalDue = totalDue;
23 | this.CurrencyCode = currencyCode;
24 | this.ShippingAddress = shippignAddr;
25 | }
26 |
27 | public class Address
28 | {
29 | public string Line1 { get; private set; }
30 | public string Line2 { get; private set; }
31 | public string City { get; private set; }
32 | public string StateCode { get; private set; }
33 | public string Zip { get; private set; }
34 |
35 | private Address(){}
36 | public Address(string line1, string line2, string city, string state, string zip)
37 | {
38 | this.Line1 = line1;
39 | this.Line2 = line2;
40 | this.City = city;
41 | this.StateCode = state;
42 | this.Zip = zip;
43 | }
44 |
45 | }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/samples/EFCore/Eventfully.Samples.ConsoleApp/OrderCreated/OrderCreatedHandler.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 | using System.Threading.Tasks;
5 |
6 | namespace Eventfully.Samples.ConsoleApp
7 | {
8 | public class OrderCreatedHandler : IMessageHandler
9 | {
10 | public Task Handle(OrderCreated ev, MessageContext context)
11 | {
12 | Console.WriteLine($"Received OrderCreated Event");
13 | Console.WriteLine($"\tOrderId: {ev.OrderId}");
14 | Console.WriteLine($"\tTotal Due: {ev.TotalDue} {ev.CurrencyCode}");
15 | Console.WriteLine($"\tShipping Address:");
16 | Console.WriteLine($"\t\t{ev.ShippingAddress.Line1}");
17 | if(!String.IsNullOrEmpty(ev.ShippingAddress.Line2))
18 | Console.WriteLine($"\t\t{ev.ShippingAddress.Line2}");
19 | Console.WriteLine($"\t\t{ev.ShippingAddress.City}, {ev.ShippingAddress.StateCode} {ev.ShippingAddress.Zip}");
20 | Console.WriteLine();
21 |
22 | return Task.CompletedTask;
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/samples/EFCore/Eventfully.Samples.ConsoleApp/PaymentMethodCreated/PaymentMethodCreated.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 |
5 | namespace Eventfully.Samples.ConsoleApp
6 | {
7 |
8 | public class PaymentMethodCreated : IntegrationEvent
9 | {
10 | public override string MessageType => "Sales.PaymentMethodCreated";
11 |
12 | public Guid PaymentMethodId { get; private set; }
13 |
14 | public CardInfo MethodInfo { get; private set; }
15 |
16 | private PaymentMethodCreated(){}
17 |
18 | public PaymentMethodCreated(Guid methodId, CardInfo methodInfo)
19 | {
20 | this.PaymentMethodId = methodId;
21 | this.MethodInfo = methodInfo;
22 | }
23 |
24 | public class CardInfo
25 | {
26 | public string Number { get; private set; }
27 | public string NameOnCard { get; set; }
28 | public string ExpirationYear { get; private set; }
29 | public string ExpirationMonth { get; private set; }
30 |
31 | private CardInfo(){}
32 | public CardInfo(string number, string nameOn, string expirationYear, string expirationMonth)
33 | {
34 | this.Number = number;
35 | this.NameOnCard = nameOn;
36 | this.ExpirationYear = expirationYear;
37 | this.ExpirationMonth = expirationMonth;
38 | }
39 |
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/samples/EFCore/Eventfully.Samples.ConsoleApp/PaymentMethodCreated/PaymentMethodCreatedHandler.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 | using System.Threading.Tasks;
5 |
6 | namespace Eventfully.Samples.ConsoleApp
7 | {
8 | public class PaymentMethodCreatedHandler : IMessageHandler
9 | {
10 | public Task Handle(PaymentMethodCreated ev, MessageContext context)
11 | {
12 | Console.WriteLine($"Received PaymentMethodCreated Event");
13 | Console.WriteLine($"\tId: {ev.PaymentMethodId}");
14 | Console.WriteLine($"\tPayment Method Info:");
15 | Console.WriteLine($"\t\t Name: {ev.MethodInfo.NameOnCard}");
16 | Console.WriteLine($"\t\t Number: {ev.MethodInfo.Number}");
17 | Console.WriteLine($"\t\t Expires: {ev.MethodInfo.ExpirationMonth} / {ev.MethodInfo.ExpirationYear}");
18 | Console.WriteLine();
19 |
20 | return Task.CompletedTask;
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/samples/EFCore/Eventfully.Samples.ConsoleApp/appsettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "ConnectionStrings": {
3 | "ApplicationConnection": "Server=(localdb)\\mssqllocaldb;Database=eventfully-dev;Trusted_Connection=True;MultipleActiveResultSets=true"
4 | },
5 |
6 | "SampleAESKey": "nZr4u7x!A%D*F-JaNdRgUkXp2s5v8y/B",
7 |
8 | "Logging": {
9 | "LogLevel": {
10 | "Default": "Warning",
11 | "System": "Warning",
12 | "Microsoft": "Warning",
13 | "Eventfully": "Information"
14 | }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/Eventfully.AzureKeyVault/AzureKeyVaultKeyProvider.cs:
--------------------------------------------------------------------------------
1 | using Azure.Identity;
2 | using Azure.Security.KeyVault.Secrets;
3 | using Microsoft.Azure.KeyVault;
4 | using Microsoft.Azure.Services.AppAuthentication;
5 | using System;
6 | using System.Collections.Generic;
7 |
8 | namespace Eventfully
9 | {
10 |
11 | public class AzureKeyVaultKeyProvider : IEncryptionKeyProvider
12 | {
13 | private readonly string _keyVaultUrl;
14 | private static readonly Dictionary _clients = new Dictionary();
15 |
16 | public AzureKeyVaultKeyProvider(string keyVaultuUrl)
17 | {
18 | _keyVaultUrl = keyVaultuUrl;
19 | }
20 |
21 | public string GetKey(string secretName = null)
22 | {
23 | var client = _getClient();
24 | //var secret = client.GetSecret(secretName);
25 | //return secret.Value.Value;
26 | var secret = client.GetSecretAsync(_keyVaultUrl, secretName).GetAwaiter().GetResult();
27 | return secret.Value;
28 | }
29 |
30 | private KeyVaultClient _getClient()
31 | {
32 | if (_clients.ContainsKey(this._keyVaultUrl))
33 | return _clients[_keyVaultUrl];
34 | else
35 | {
36 |
37 | var azureServiceTokenProvider = new AzureServiceTokenProvider();
38 | var client = new KeyVaultClient(
39 | new KeyVaultClient.AuthenticationCallback(
40 | azureServiceTokenProvider.KeyVaultTokenCallback));
41 | return client;
42 | }
43 | }
44 | }
45 |
46 | }
47 |
--------------------------------------------------------------------------------
/src/Eventfully.AzureKeyVault/Eventfully.AzureKeyVault.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netstandard2.0
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/src/Eventfully.AzureKeyVault/README.txt:
--------------------------------------------------------------------------------
1 |
2 | Azure Key Vault Secrets - Managed Service Identity based key provider
3 | https://docs.microsoft.com/en-us/samples/azure-samples/app-service-msi-keyvault-dotnet/keyvault-msi-appservice-sample/
4 |
5 | Configuring Azure Key Vault for use as EncryptionKeyProvider
6 |
7 | 1) Generate/Use existing AES key of length: 128, 192 or 256
8 | - ex: openssl rand 128 > sym_keyfile.key
9 |
10 | 2) Import Key into Key Vault
11 | - Create a New Secret in the KeyVault
12 | - Name:
13 | - Value:
14 | - ContentType: application/octet-stream
15 |
16 | 3) Authenticating with the KeyVault
17 | - By default the provider will Managed Service Identity to transparently authenticate ( make sure you app has permissions to acces the vault).
18 | - This works great while the app is running in azure, but when developing in VS you need to be logged into Azure with an account that has access
19 |
20 |
21 | 4) Using the provider
22 |
23 | ***** Using the provider for a single message type *********
24 |
25 | ConfigureEndpoint("TestEndpoint")
26 | .AsInboundOutbound()
27 | .BindEvent()
28 | .UseEncryption(
29 | "yourkeyname",
30 | new AzureKeyVaultKeyProvider("yourkeyvaulturl")
31 | )
32 | .UseAzureServiceBusTransport()
33 | ;
34 |
35 |
--------------------------------------------------------------------------------
/src/Eventfully.Core/Config/EndpointBindings.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 |
5 | namespace Eventfully
6 | {
7 | public class EndpointBindings
8 | {
9 |
10 | public List Endpoints { get; set; }
11 | public class EndpointBinding
12 | {
13 | public string Name { get; set; }
14 | public string ConnectionString { get; set; }
15 |
16 | }
17 |
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/Eventfully.Core/Config/IEndpointFluent.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 | using Eventfully.Filters;
5 | using Eventfully.Transports;
6 |
7 | namespace Eventfully
8 | {
9 | public interface IEndpointFluent
10 | {
11 | TransportSettings TransportSettings { get; set; }
12 |
13 | EndpointSettings AsEventDefault();
14 | EndpointSettings AsInbound();
15 | EndpointSettings AsInboundOutbound();
16 | EndpointSettings AsOutbound();
17 | EndpointSettings AsReplyDefault();
18 |
19 | BindMessageSettings BindCommand() where T : IIntegrationCommand;
20 | BindMessageSettings BindEvent() where T : IIntegrationEvent;
21 | EndpointSettings BindCommand(string messageTypeIdentifier);
22 |
23 | EndpointSettings WithFilter(params IIntegrationMessageFilter[] filters);
24 | EndpointSettings WithFilter(params ITransportMessageFilter[] filters);
25 |
26 | //EndpointSettings WithOutboundFilter(params IIntegrationMessageFilter[] filters);
27 | //EndpointSettings WithOutboundFilter(params ITransportMessageFilter[] filters);
28 | }
29 |
30 | }
31 |
--------------------------------------------------------------------------------
/src/Eventfully.Core/Config/MessagingConfiguration.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 |
5 | namespace Eventfully
6 | {
7 | public class MessagingConfiguration
8 | {
9 | public ICountingSemaphore OutboxConsumerSemaphore { get; set; }
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/src/Eventfully.Core/Config/MessagingProfile.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 |
6 | namespace Eventfully
7 | {
8 | public class Profile
9 | {
10 | public List EndpointSettings { get; private set; } = new List();
11 |
12 | public EndpointSettings ConfigureEndpoint(string name) => ConfigureEndpoint(name, null);
13 |
14 | public EndpointSettings ConfigureEndpoint(string name, string connectionString)
15 | {
16 | var endpointSettings = new EndpointSettings(name, connectionString);
17 | EndpointSettings.Add(endpointSettings);
18 | return endpointSettings;
19 | }
20 |
21 | public Profile AddBindings(EndpointBindings bindings)
22 | {
23 | if (bindings == null || EndpointSettings.Count < 1)
24 | return this;
25 |
26 | foreach(var binding in bindings.Endpoints)
27 | {
28 | var endpoint = EndpointSettings.SingleOrDefault(x => x.Name.Equals(binding.Name, StringComparison.OrdinalIgnoreCase));
29 | if (endpoint != null)
30 | {
31 | if (String.IsNullOrEmpty(endpoint.ConnectionString))
32 | endpoint.ConnectionString = binding.ConnectionString;
33 | }
34 | }
35 | return this;
36 | }
37 |
38 |
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/Eventfully.Core/DependencyInjection/IRegistrar.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 |
5 | namespace Eventfully
6 | {
7 | public interface IServiceRegistrar
8 | {
9 | void AddTransient();
10 | void AddTransient() where T: I;
11 |
12 | void AddSingleton(object o);
13 | void AddSingleton(object o);
14 | void AddSingleton();
15 | void AddSingleton(bool asBoth=false) where T : I;
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/Eventfully.Core/DependencyInjection/IServiceFactory.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 |
5 | namespace Eventfully
6 | {
7 | public interface IServiceFactory: IDisposable
8 | {
9 | object GetInstance(Type type);
10 | IServiceFactory CreateScope();
11 | }
12 |
13 | public static class ServiceFactoryExtension
14 | {
15 | public static T GetInstance(this IServiceFactory factory)
16 | {
17 | return (T)factory.GetInstance(typeof(T));
18 | }
19 | }
20 | }
--------------------------------------------------------------------------------
/src/Eventfully.Core/Endpoints/Endpoint.cs:
--------------------------------------------------------------------------------
1 | using Eventfully.Transports;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Text;
5 | using System.Threading;
6 | using System.Threading.Tasks;
7 |
8 | namespace Eventfully
9 | {
10 |
11 | public class Endpoint : IEndpoint
12 | {
13 | protected readonly EndpointSettings _settings;
14 | protected readonly ITransport _transport;
15 |
16 | public string Name { get; protected set; }
17 |
18 | public EndpointSettings Settings => _settings;
19 | public ITransport Transport => _transport;
20 |
21 | public HashSet BoundMessageTypes { get; protected set; } = new HashSet();
22 | public HashSet BoundMessageIdentifiers { get; protected set; } = new HashSet();
23 |
24 | public bool IsReader { get; protected set; }
25 | public bool IsWriter { get; protected set; }
26 |
27 | public bool SupportsDelayedDispatch => _transport.SupportsDelayedDispatch;
28 |
29 | public Endpoint(EndpointSettings settings)
30 | :this(settings, null)
31 | {}
32 | public Endpoint(EndpointSettings settings, ITransport transport)
33 | {
34 | _settings = settings;
35 | this.Name = settings.Name;
36 | this.IsReader = settings.IsReader;
37 | this.IsWriter = settings.IsWriter;
38 |
39 | if (settings.MessageTypes != null)
40 | {
41 | foreach (Type messageType in settings.MessageTypes)
42 | this.BoundMessageTypes.Add(messageType);
43 | }
44 |
45 | if (settings.MessageTypeIdentifiers != null)
46 | {
47 | foreach (string messageTypeIdentifier in settings.MessageTypeIdentifiers)
48 | this.BoundMessageIdentifiers.Add(messageTypeIdentifier);
49 | }
50 |
51 | //create the transport from the supplied factory
52 | if (transport != null)
53 | _transport = transport;
54 |
55 | if(_transport == null)
56 | _transport = settings.TransportSettings.Create();
57 | }
58 |
59 | public virtual Task StartAsync(Handler handler, CancellationToken cancellationToken = default(CancellationToken))
60 | {
61 | return _transport.StartAsync(this, handler, cancellationToken);
62 | }
63 |
64 | public virtual Task StopAsync()
65 | {
66 | return _transport.StopAsync();
67 | }
68 |
69 | public virtual Task Dispatch(string messageTypeIdenfifier, byte[] message, MessageMetaData metaData = null)
70 | {
71 | return Transport.Dispatch(messageTypeIdenfifier, message, this, metaData);
72 | }
73 |
74 | public virtual void SetReplyToForCommand(IIntegrationCommand command, MessageMetaData meta)
75 | {
76 | Transport.SetReplyToForCommand(this, command, meta);
77 | }
78 |
79 |
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/src/Eventfully.Core/Endpoints/IEndpoint.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Threading;
4 | using System.Threading.Tasks;
5 | using Eventfully.Transports;
6 |
7 | namespace Eventfully
8 | {
9 | public interface IEndpoint
10 | {
11 | HashSet BoundMessageIdentifiers { get; }
12 | HashSet BoundMessageTypes { get; }
13 | bool IsReader { get; }
14 | bool IsWriter { get; }
15 | string Name { get; }
16 | EndpointSettings Settings { get; }
17 | bool SupportsDelayedDispatch { get; }
18 | ITransport Transport { get; }
19 |
20 | Task Dispatch(string messageTypeIdenfifier, byte[] message, MessageMetaData metaData = null);
21 | void SetReplyToForCommand(IIntegrationCommand command, MessageMetaData meta);
22 | Task StartAsync(Handler handler, CancellationToken cancellationToken = default);
23 | Task StopAsync();
24 | }
25 | }
--------------------------------------------------------------------------------
/src/Eventfully.Core/Eventfully.Core.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netstandard2.0
5 | Eventfully
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/src/Eventfully.Core/Filters/ExpirationCheckStep.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 |
4 | namespace Eventfully.Filters
5 | {
6 | public class ExpirationCheckStep : IPipelineStep
7 | {
8 | public IntegrationMessageFilterContext Process(IntegrationMessageFilterContext context)
9 | {
10 | if (context.Message != null)
11 | {
12 | if (context.MessageMetaData != null && context.MessageMetaData.IsExpired())
13 | throw new MessageExpiredException(context.Message.MessageType);
14 | }
15 | return context;
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/Eventfully.Core/Filters/IEncryptionKeyProvider.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 |
5 | namespace Eventfully
6 | {
7 |
8 | ///
9 | /// Provide a base64 encoded key for encryption
10 | ///
11 | public interface IEncryptionKeyProvider
12 | {
13 | ///
14 | /// Get an encryption key
15 | ///
16 | ///
17 | /// A base64 encoded key
18 | string GetKey(string keyName = null);
19 | }
20 |
21 | public class StringKeyProvider : IEncryptionKeyProvider
22 | {
23 | private readonly string _key;
24 | private readonly bool _isBase64Encoded;
25 | public StringKeyProvider(string key, bool isBase64Encoded = false)
26 | {
27 | _key = key;
28 | _isBase64Encoded = isBase64Encoded;
29 | }
30 |
31 | public string GetKey(string keyName = null)
32 | {
33 | if (!_isBase64Encoded)
34 | {
35 | var bytes = System.Text.Encoding.UTF8.GetBytes(_key);
36 | return System.Convert.ToBase64String(bytes);
37 | }
38 | return _key;
39 | }
40 | }
41 |
42 |
43 | }
44 |
--------------------------------------------------------------------------------
/src/Eventfully.Core/Filters/IIntegrationMessageFilter.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using Newtonsoft.Json;
5 |
6 | namespace Eventfully.Filters
7 | {
8 | ///
9 | /// A filter that acts on messages and meta data in the IIntegrationMessage form
10 | /// Ex. modifying headers based on message content
11 | /// to act on messages while in the byte[] form
12 | ///
13 | public interface IIntegrationMessageFilter : IPipelineStep
14 | {
15 | FilterDirection SupportsDirection { get; }
16 |
17 | new IntegrationMessageFilterContext Process(IntegrationMessageFilterContext context);
18 | }
19 |
20 | public class IntegrationMessageFilterContext
21 | {
22 | public FilterDirection Direction { get; set; }
23 |
24 | public IIntegrationMessage Message { get; set; }
25 | public MessageMetaData MessageMetaData { get; set; }
26 | public IEndpoint Endpoint { get; set; }
27 |
28 | public MessageTypeProperties Props { get; set; }
29 |
30 | public IntegrationMessageFilterContext(IIntegrationMessage message, MessageMetaData meta, IEndpoint endpoint, FilterDirection direction, MessageTypeProperties props)
31 | {
32 | Message = message;
33 | MessageMetaData = meta;
34 | Endpoint = endpoint;
35 | Direction = direction;
36 | Props = props;
37 | }
38 | }
39 |
40 |
41 |
42 | }
43 |
--------------------------------------------------------------------------------
/src/Eventfully.Core/Filters/ITransportMessageFilter.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using Newtonsoft.Json;
5 |
6 | namespace Eventfully.Filters
7 | {
8 | [Flags]
9 | public enum FilterDirection
10 | {
11 | Inbound = 1,
12 | Outbound = 2,
13 | }
14 | ///
15 | /// A filter that acts on messages and meta data while the message content is in
16 | /// its byte[] form. Ex. Encrypting/Decrypting the message context
17 | /// to act on messages while in the IIntegrationMessage format
18 | ///
19 | public interface ITransportMessageFilter : IPipelineStep
20 | {
21 | FilterDirection SupportsDirection { get;}
22 | new TransportMessageFilterContext Process(TransportMessageFilterContext context);
23 | }
24 |
25 | public class TransportMessageFilterContext
26 | {
27 | public FilterDirection Direction { get; set; }
28 | public TransportMessage TransportMessage { get; set; }
29 | public IEndpoint Endpoint { get; set; }
30 |
31 | public TransportMessageFilterContext(TransportMessage message, IEndpoint endpoint, FilterDirection direction)
32 | {
33 | TransportMessage = message;
34 | Endpoint = endpoint;
35 | Direction = direction;
36 | }
37 | }
38 |
39 |
40 |
41 |
42 |
43 | }
44 |
--------------------------------------------------------------------------------
/src/Eventfully.Core/Filters/InboundMessagePipeline.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 |
5 | namespace Eventfully.Filters
6 | {
7 | public class InboundMessagePipeline : Pipeline
8 | {
9 | public InboundMessagePipeline(IEnumerable userTransportFilters, IEnumerable userIntegrationFilters)
10 | {
11 | Pipeline userPrePipeline = null;
12 | if (userTransportFilters != null && userTransportFilters.Count() > 0)
13 | userPrePipeline = new Pipeline(userTransportFilters);
14 |
15 | Pipeline userPostPipeline = null;
16 | if (userIntegrationFilters != null && userIntegrationFilters.Count() > 0)
17 | userPostPipeline = new Pipeline(userIntegrationFilters);
18 |
19 | this.Steps = input =>
20 | {
21 | return input
22 | .Step(userPrePipeline)
23 | .Step(new MessageExtractionStep())
24 | .Step(userPostPipeline)
25 | .Step(new ExpirationCheckStep())
26 | ;
27 | };
28 | }
29 | }
30 |
31 |
32 |
33 | }
34 |
35 |
36 |
--------------------------------------------------------------------------------
/src/Eventfully.Core/Filters/MessageExtractionStep.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Reflection;
4 | using System.Text;
5 | using Newtonsoft.Json;
6 | using Newtonsoft.Json.Serialization;
7 |
8 | namespace Eventfully.Filters
9 | {
10 | public class MessageExtractionStep : IPipelineStep
11 | {
12 | private static readonly Dictionary _messageExtractorCache = new Dictionary();
13 | private static readonly JsonSerializerSettings _serializerSettings = new JsonSerializerSettings()
14 | {
15 | ContractResolver = new PrivateSetterResolver()
16 | };
17 | public IntegrationMessageFilterContext Process(TransportMessageFilterContext context)
18 | {
19 | string messageType = context.TransportMessage.MetaData.MessageType;
20 | if (String.IsNullOrEmpty(messageType))
21 | throw new InvalidMessageTypeException(messageType);
22 |
23 | var metaData = context.TransportMessage.MetaData;
24 | var messageProps = MessagingMap.GetProps(messageType)
25 | ?? throw new UnknownMessageTypeException(messageType);
26 |
27 | IIntegrationMessage message = _extractMessage(context.TransportMessage.Data, messageProps);
28 |
29 | return new IntegrationMessageFilterContext(message, context.TransportMessage.MetaData, context.Endpoint, FilterDirection.Inbound, messageProps);
30 | }
31 |
32 |
33 | ///
34 | /// Convert message off the wire from byte[] into a concrete implementation of IIntegrationMessage
35 | /// delegates to custom extractors and defaults to Json deserialization
36 | ///
37 | private IIntegrationMessage _extractMessage(byte[] messageData, MessageTypeProperties props)
38 | {
39 | IIntegrationMessage message;
40 | if (props.HasCustomExtractor)
41 | {
42 | IMessageExtractor extractor = null;
43 | if (_messageExtractorCache.ContainsKey(props.MessageTypeIdentifier))
44 | extractor = _messageExtractorCache[props.MessageTypeIdentifier];
45 | else
46 | {
47 | extractor = (IMessageExtractor)Activator.CreateInstance(props.Type, true);
48 | _messageExtractorCache.Add(props.MessageTypeIdentifier, extractor);
49 | }
50 | message = extractor.Extract(messageData);
51 | }
52 | else
53 | {
54 | //by default the message is json and will be deserialized to the type with a matching MessageTypeIdentifier property:"MessageType"
55 | var json = Encoding.UTF8.GetString(messageData);
56 | message = (IIntegrationMessage)JsonConvert.DeserializeObject(json, props.Type, _serializerSettings);
57 | }
58 | return message;
59 | }
60 | }
61 | public class PrivateSetterResolver : DefaultContractResolver
62 | {
63 | protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
64 | {
65 | var prop = base.CreateProperty(member, memberSerialization);
66 | if (!prop.Writable)
67 | {
68 | var property = member as PropertyInfo;
69 | prop.Writable = property?.GetSetMethod(true) != null;
70 | }
71 | return prop;
72 | }
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/src/Eventfully.Core/Filters/MessageSerializationStep.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 | using Newtonsoft.Json;
5 |
6 | namespace Eventfully.Filters
7 | {
8 | public class MessageSerializationStep : IPipelineStep
9 | {
10 | private static readonly Dictionary _messageExtractorCache = new Dictionary();
11 |
12 | public TransportMessageFilterContext Process(IntegrationMessageFilterContext context)
13 | {
14 | byte[] messageBytes = null;
15 | if (context.Message is IsSerialized)
16 | messageBytes = (context.Message as IsSerialized).Message;
17 | else
18 | {
19 | string messageBody = JsonConvert.SerializeObject(context.Message);
20 | messageBytes = Encoding.UTF8.GetBytes(messageBody);
21 | }
22 |
23 | return new TransportMessageFilterContext(
24 | new TransportMessage(context.Message.MessageType, messageBytes, context.MessageMetaData),
25 | context.Endpoint,
26 | FilterDirection.Outbound
27 | );
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/Eventfully.Core/Filters/MessageTypeIntegrationFilter.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 |
5 | namespace Eventfully.Filters
6 | {
7 | ///
8 | /// Filter wrapper for IntegrationFilters that should only be applied to
9 | /// Messages of a specific type
10 | ///
11 | ///
12 | public class MessageTypeIntegrationFilter : IIntegrationMessageFilter
13 | where T : IIntegrationMessage
14 | {
15 | private readonly IIntegrationMessageFilter _innerFilter;
16 | public FilterDirection SupportsDirection => _innerFilter.SupportsDirection;
17 |
18 | public MessageTypeIntegrationFilter(IIntegrationMessageFilter innerFilter)
19 | {
20 | _innerFilter = innerFilter;
21 | }
22 |
23 |
24 | public IntegrationMessageFilterContext Process(IntegrationMessageFilterContext context)
25 | {
26 | if (context.Message is T)
27 | return _innerFilter.Process(context);
28 | return context;
29 | }
30 |
31 | }
32 |
33 |
34 | }
35 |
--------------------------------------------------------------------------------
/src/Eventfully.Core/Filters/MessageTypeTransportFilter.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 |
5 | namespace Eventfully.Filters
6 | {
7 | ///
8 | /// Filter wrapper for TransportFilters that should only be applied to
9 | /// Messages of a specific type
10 | ///
11 | ///
12 | public class MessageTypeTransportFilter : ITransportMessageFilter
13 | {
14 | private readonly string _messageTypeIdentifier;
15 | private readonly ITransportMessageFilter _innerFilter;
16 | public FilterDirection SupportsDirection => _innerFilter.SupportsDirection;
17 |
18 | public MessageTypeTransportFilter(string messageTypeIdentifier, ITransportMessageFilter innerFilter)
19 | {
20 | _messageTypeIdentifier = messageTypeIdentifier;
21 | _innerFilter = innerFilter;
22 | }
23 |
24 | public TransportMessageFilterContext Process(TransportMessageFilterContext context)
25 | {
26 | if (context.TransportMessage.MessageTypeIdentifier.Equals(_messageTypeIdentifier, StringComparison.OrdinalIgnoreCase))
27 | return _innerFilter.Process(context);
28 | return context;
29 | }
30 |
31 | }
32 |
33 | public class MessageTypeTransportFilter : ITransportMessageFilter
34 | {
35 | private readonly ITransportMessageFilter _innerFilter;
36 | public FilterDirection SupportsDirection => _innerFilter.SupportsDirection;
37 |
38 | public MessageTypeTransportFilter(ITransportMessageFilter innerFilter)
39 | {
40 | _innerFilter = innerFilter;
41 | }
42 |
43 |
44 | public TransportMessageFilterContext Process(TransportMessageFilterContext context)
45 | {
46 | if (context.TransportMessage.MessageTypeIdentifier.Equals(
47 | MessagingMap.GetMessageTypeIdentifier(typeof(T)),
48 | StringComparison.OrdinalIgnoreCase))
49 | return _innerFilter.Process(context);
50 |
51 | return context;
52 | }
53 |
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/src/Eventfully.Core/Filters/OutboundMessagePipeline.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using Newtonsoft.Json;
6 |
7 | namespace Eventfully.Filters
8 | {
9 | public class OutboundMessagePipeline : Pipeline
10 | {
11 | public OutboundMessagePipeline(IEnumerable userIntegrationFilters, IEnumerable userTransportFilters)
12 | {
13 | Pipeline userPrePipeline = null;
14 | if (userIntegrationFilters != null && userIntegrationFilters.Count() > 0)
15 | userPrePipeline = new Pipeline(userIntegrationFilters);
16 |
17 | Pipeline userPostPipeline = null;
18 | if (userTransportFilters != null && userTransportFilters.Count() > 0)
19 | userPostPipeline = new Pipeline(userTransportFilters);
20 |
21 |
22 | this.Steps = input =>
23 | {
24 | return input
25 | .Step(userPrePipeline)
26 | .Step(new MessageSerializationStep())
27 | .Step(userPostPipeline)
28 | ;
29 | };
30 | }
31 | }
32 |
33 |
34 | }
35 |
--------------------------------------------------------------------------------
/src/Eventfully.Core/Filters/Pipeline.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 |
5 | namespace Eventfully.Filters
6 | {
7 | ///
8 | /// Inspired by Jeremy Davis
9 | /// https://jermdavis.wordpress.com/2016/10/03/an-alternative-approach-to-pipelines/
10 | ///
11 | ///
12 | ///
13 | public interface IPipeline : IPipelineStep
14 | {
15 | new OUTPUT Process(INPUT input);
16 | }
17 |
18 | public interface IPipeline : IPipeline, IPipelineStep
19 | {
20 | new INPUTOUTPUT Process(INPUTOUTPUT input);
21 | }
22 |
23 | public interface IPipelineStep
24 | {
25 | OUTPUT Process(INPUT input);
26 | }
27 |
28 | public interface IPipelineStep : IPipelineStep
29 | {
30 | new INPUTOUTPUT Process(INPUTOUTPUT input);
31 | }
32 |
33 | public class Pipeline : IPipeline
34 | {
35 | protected Func Steps { get; set; }
36 |
37 | public Pipeline() { }
38 |
39 | public OUTPUT Process(INPUT input)
40 | {
41 | return Steps.Invoke(input);
42 | }
43 | }
44 |
45 | public class Pipeline : Pipeline, IPipeline
46 | {
47 | public Pipeline() { }
48 | public Pipeline(IEnumerable> steps)
49 | {
50 | this.Steps = io =>
51 | {
52 | foreach (var step in steps)
53 | io = io.Step(step);
54 | return io;
55 | };
56 | }
57 | }
58 |
59 | public static class PipelineExtensions
60 | {
61 | public static OUTPUT Step(this INPUT input, IPipelineStep step)
62 | {
63 | return step.Process(input);
64 | }
65 | public static INPUTOUTPUT Step(this INPUTOUTPUT input, IPipelineStep step)
66 | {
67 | if (step == null)
68 | return input;
69 | return step.Process(input);
70 | }
71 | }
72 |
73 |
74 | }
75 |
--------------------------------------------------------------------------------
/src/Eventfully.Core/Handling/ICustomMessageHandler.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 | using System.Threading.Tasks;
5 |
6 | namespace Eventfully
7 | {
8 | ///
9 | /// Allows registering as a handler for a message type while
10 | /// processing each message type through a single Handle method
11 | /// used by the ProcessManagerStateMachine
12 | ///
13 | ///
14 | public interface ICustomMessageHandler
15 | {
16 | Task Handle(IIntegrationMessage message, MessageContext context);
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/Eventfully.Core/Handling/IMessageDispatcher.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 | using System.Threading.Tasks;
5 |
6 | namespace Eventfully.Handlers
7 | {
8 | public interface IMessageDispatcher
9 | {
10 | Task Dispatch(IIntegrationMessage message, MessageContext context);
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/src/Eventfully.Core/Handling/IMessageHandler.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Threading.Tasks;
4 |
5 | namespace Eventfully
6 | {
7 | public interface IMessageHandler
8 | {
9 | Task Handle(T message, MessageContext context);
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/src/Eventfully.Core/Handling/MessageContext.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 | using System.Threading.Tasks;
5 | using Eventfully.Outboxing;
6 |
7 | namespace Eventfully
8 | {
9 | public class MessageContext
10 | {
11 | public MessageMetaData MetaData { get; protected set; }
12 |
13 | internal IEndpoint Endpoint { get; set; }
14 |
15 | internal MessagingService MessagingService { get; set; }
16 |
17 | internal MessageTypeProperties Props { get; set; }
18 |
19 | internal IOutboxSession OutboxSession { get; set; }
20 |
21 | public MessageContext(MessageMetaData meta, IEndpoint endpoint, MessagingService messagingService, MessageTypeProperties props = null)
22 | {
23 | MetaData = meta;
24 | Endpoint = endpoint;
25 | MessagingService = messagingService;
26 | Props = props;
27 | }
28 |
29 | public Task Reply(IIntegrationReply reply, MessageMetaData replyMetaData = null)
30 | {
31 | var replyToEndpoint = this.GetEndpointForReply();
32 | return MessagingService.Reply(reply, replyToEndpoint, this.OutboxSession, replyMetaData, this.MetaData);
33 | }
34 |
35 | internal IEndpoint GetEndpointForReply()
36 | {
37 | return Endpoint.Transport.FindEndpointForReply(this);
38 | }
39 |
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/Eventfully.Core/Handling/MessageDispatcher.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Threading.Tasks;
5 | using Eventfully.Outboxing;
6 |
7 | namespace Eventfully.Handlers
8 | {
9 | ///
10 | /// Inspired by Jimmy Bogard's blog and MediatR project
11 | /// https://github.com/jbogard/MediatR/blob/master/LICENSE
12 | ///
13 | public class MessageDispatcher : IMessageDispatcher
14 | {
15 | private readonly IServiceFactory _handlerFactory;
16 |
17 | public MessageDispatcher(IServiceFactory handlerFactory)
18 | {
19 | if (handlerFactory == null) throw new ArgumentNullException(nameof(handlerFactory));
20 | _handlerFactory = handlerFactory;
21 | }
22 |
23 | public async Task Dispatch(IIntegrationMessage message, MessageContext context)
24 | {
25 | var handler = GetHandler(message);
26 | await handler.Handle(message, context, _handlerFactory);
27 | }
28 |
29 | private static IntegrationMessageDispatcherHandler GetHandler(IIntegrationMessage message)
30 | {
31 | var genericDispatcherType = typeof(IntegrationMessageDispatcherHandler<>)
32 | .MakeGenericType(message.GetType());
33 |
34 | return (IntegrationMessageDispatcherHandler)
35 | Activator.CreateInstance(genericDispatcherType);
36 | }
37 |
38 | private abstract class IntegrationMessageDispatcherHandler
39 | {
40 | public abstract Task Handle(IIntegrationMessage message, MessageContext context, IServiceFactory handlerFactory);
41 | }
42 |
43 |
44 | private class IntegrationMessageDispatcherHandler : IntegrationMessageDispatcherHandler
45 | where T : IIntegrationMessage
46 | {
47 | public override Task Handle(IIntegrationMessage message, MessageContext context, IServiceFactory handlerFactory)
48 | {
49 | return HandleCore((T)message, context, handlerFactory);
50 | }
51 |
52 | private static async Task HandleCore(T message, MessageContext context, IServiceFactory handlerFactory)
53 | {
54 | using (var scope = handlerFactory.CreateScope())//for dependency injection
55 | {
56 | context.OutboxSession = scope.GetInstance();
57 |
58 | //is there a processManager/Saga for this message type
59 | if (context.Props != null && context.Props.HasSagaHandler)
60 | {
61 | var sagaProps = MessagingMap.GetSagaProps(context.Props.SagaType) ??
62 | throw new ApplicationException($"Couldn't find saga for Handler: {context.Props.Type}, SagaType: {context.Props.SagaType}");
63 |
64 | var saga = (ISaga)scope.GetInstance(sagaProps.SagaType);
65 |
66 | var persistence = scope.GetInstance(sagaProps.SagaPersistenceType) as ISagaPersistence;
67 | await persistence.LoadOrCreateState(saga, saga.FindKey(message, context.MetaData));
68 |
69 | if (saga is IMessageHandler)//includes ITriggeredBy
70 | await ((IMessageHandler)saga).Handle(message, context);
71 | else if(saga is ICustomMessageHandler)
72 | await ((ICustomMessageHandler)saga).Handle(message, context);
73 |
74 | await persistence.AddOrUpdateState(saga);
75 | }
76 | else // basic handler class
77 | {
78 | var handler = scope.GetInstance>();
79 | await handler.Handle(message, context);
80 | }
81 | }
82 | }
83 |
84 |
85 | }
86 |
87 |
88 |
89 | }
90 | }
91 |
92 |
--------------------------------------------------------------------------------
/src/Eventfully.Core/Messages/IIntegrationMessage.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 |
6 | namespace Eventfully
7 | {
8 | public interface IIntegrationMessage
9 | {
10 | string MessageType { get; }
11 |
12 | }
13 |
14 |
15 | }
16 |
--------------------------------------------------------------------------------
/src/Eventfully.Core/Messages/IMessageExtractor.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 |
5 | namespace Eventfully
6 | {
7 | public interface IMessageExtractor
8 | {
9 | IIntegrationMessage Extract(byte[] data);
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/src/Eventfully.Core/Messages/IntegrationCommand.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 |
5 | namespace Eventfully
6 | {
7 |
8 | public interface IIntegrationCommand : IIntegrationMessage
9 | {
10 | }
11 |
12 | public abstract class IntegrationCommand : IntegrationMessage, IIntegrationCommand
13 | {
14 | public IntegrationCommand()
15 | {
16 | }
17 | }
18 |
19 | }
20 |
--------------------------------------------------------------------------------
/src/Eventfully.Core/Messages/IntegrationEvent.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 |
5 | namespace Eventfully
6 | {
7 | public interface IIntegrationEvent : IIntegrationMessage
8 | {
9 | }
10 |
11 | public abstract class IntegrationEvent : IntegrationMessage, IIntegrationEvent
12 | {
13 | }
14 |
15 | }
16 |
--------------------------------------------------------------------------------
/src/Eventfully.Core/Messages/IntegrationMessage.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Reflection;
5 | using System.Text;
6 |
7 | namespace Eventfully
8 | {
9 | public abstract class IntegrationMessage : IIntegrationMessage
10 | {
11 | public abstract string MessageType { get; }
12 |
13 | public DateTime? CreatedAtUtc { get; set; } = DateTime.UtcNow;
14 |
15 | public IntegrationMessage()
16 | {
17 | }
18 |
19 |
20 | }
21 |
22 | }
23 |
--------------------------------------------------------------------------------
/src/Eventfully.Core/Messages/IntegrationReply.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 |
5 | namespace Eventfully
6 | {
7 |
8 | public interface IIntegrationReply : IIntegrationMessage
9 | {
10 | }
11 |
12 | public abstract class IntegrationReply : IntegrationMessage, IIntegrationReply
13 | {
14 | public IntegrationReply() {}
15 |
16 | }
17 |
18 | }
19 |
--------------------------------------------------------------------------------
/src/Eventfully.Core/Messages/WrappedMessage.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 |
5 | namespace Eventfully
6 | {
7 |
8 | public interface IsSerialized
9 | {
10 | byte[] Message { get; set; }
11 | }
12 |
13 | ///
14 | /// A pre-serialized message whose MessageTypeIdentifier is specified explicitly
15 | /// This is useful for sending pre-serialized messages through the Outbound message
16 | /// pipeline
17 | ///
18 | public class WrappedMessage : IIntegrationMessage, IsSerialized
19 | {
20 | public string MessageType { get; set; }
21 | public byte[] Message { get; set; }
22 |
23 | public WrappedMessage() { }
24 | public WrappedMessage(string messageTypeIdenfifier, byte[] message)
25 | {
26 | this.MessageType = messageTypeIdenfifier;
27 | this.Message = message;
28 | }
29 | }
30 |
31 | public class WrappedCommand : WrappedMessage, IIntegrationCommand {
32 | public WrappedCommand(string messageTypeIdenfifier, byte[] message) : base(messageTypeIdenfifier, message) { }
33 | }
34 | public class WrappedEvent : WrappedMessage, IIntegrationEvent {
35 | public WrappedEvent(string messageTypeIdenfifier, byte[] message) : base(messageTypeIdenfifier, message) { }
36 | }
37 | public class WrappedReply : WrappedMessage, IIntegrationReply {
38 | public WrappedReply(string messageTypeIdenfifier, byte[] message) : base(messageTypeIdenfifier, message) { }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/Eventfully.Core/Outboxing/IOutbox.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 | using System.Threading.Tasks;
5 |
6 |
7 | namespace Eventfully.Outboxing
8 | {
9 |
10 | public interface IOutbox
11 | {
12 | ///
13 | /// Find messages from the outbox that are ready and relay the to their true endpoint
14 | ///
15 | /// method provided to relay the messages
16 | ///
17 | Task Relay(Dispatcher dispatcher);// Func relayCallback);
18 |
19 | ///
20 | /// Do any periodic cleanup necessary on the inbox
21 | /// ex. deleting old processed messages
22 | ///
23 | /// the age of old processed messages that can be deleted
24 | ///
25 | Task CleanUp(TimeSpan cleanupAge);
26 |
27 | ///
28 | /// Do any periodic resets necessary on the inbox
29 | /// ex. resetting messages that began processing but never finished
30 | ///
31 | /// the age of old pending messages that can be reset
32 | ///
33 | Task Reset(TimeSpan resetAge);
34 |
35 | Task StartAsync(Dispatcher dispatcher);
36 |
37 | Task StopAsync();
38 | }
39 |
40 | //public interface IOutboxWithTransient : IOutbox
41 | //{
42 | // Task Relay(Func relayCallback);
43 |
44 | //}
45 |
46 | }
--------------------------------------------------------------------------------
/src/Eventfully.Core/Outboxing/IOutboxManager.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading;
3 | using System.Threading.Tasks;
4 |
5 | namespace Eventfully.Outboxing
6 | {
7 | public interface IOutboxManager : IDisposable
8 | {
9 | Task StartAsync(CancellationToken stoppingToken);
10 |
11 | Task StopAsync();
12 | }
13 | }
--------------------------------------------------------------------------------
/src/Eventfully.Core/Outboxing/IOutboxSession.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 | using System.Threading.Tasks;
5 |
6 |
7 | namespace Eventfully.Outboxing
8 | {
9 |
10 | public interface IOutboxSession
11 | {
12 | ///
13 | /// Send a message to the outbox
14 | ///
15 | Task Dispatch(string messageTypeIdentifier, byte[] message, MessageMetaData meta, IEndpoint endpoint = null, OutboxDispatchOptions options = null);
16 |
17 | /////
18 | ///// Find messages from the outbox that are ready and relay the to their true endpoint
19 | /////
20 | ///// method provided to relay the messages
21 | /////
22 | //Task Relay(Func relayMessage);
23 |
24 | /////
25 | ///// Do any periodic cleanup necessary on the inbox
26 | ///// ex. deleting old processed messages
27 | /////
28 | ///// the age of old processed messages that can be deleted
29 | /////
30 | //Task CleanUp(TimeSpan cleanupAge);
31 |
32 | /////
33 | ///// Do any periodic resets necessary on the inbox
34 | ///// ex. resetting messages that began processing but never finished
35 | /////
36 | ///// the age of old pending messages that can be reset
37 | /////
38 | //Task Reset(TimeSpan resetAge);
39 |
40 | //Task DispatchTransient(Func dispatchMessage);
41 |
42 |
43 | }
44 | }
--------------------------------------------------------------------------------
/src/Eventfully.Core/Outboxing/OutboxDispatchOptions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 |
5 | namespace Eventfully.Outboxing
6 | {
7 | public class OutboxDispatchOptions
8 | {
9 | public bool SkipTransientDispatch { get; set; } = false;
10 |
11 | public TimeSpan? Delay { get; set; }
12 |
13 | public DateTime? ExpiresAtUtc { get; set; }
14 |
15 | public OutboxDispatchOptions()
16 | {
17 | }
18 |
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/Eventfully.Core/Outboxing/OutboxDispatchResult.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 |
5 | namespace Eventfully.Outboxing
6 | {
7 | public class OutboxRelayResult
8 | {
9 | public int MessageCount { get; set; }
10 | public int MaxMessageCount { get; set; }
11 |
12 | public OutboxRelayResult(int messageCount, int maxMessageCount)
13 | {
14 | this.MessageCount = messageCount;
15 | this.MaxMessageCount = maxMessageCount;
16 | }
17 |
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/Eventfully.Core/README.md:
--------------------------------------------------------------------------------
1 |
2 | - reliable messaging framework
3 | - light weight
4 | - no shared message types between apps/services
5 | - easy customization of message deserialization
6 | - message outbox support
7 | - simple sagas
8 | - encryption support (AES)
9 | - azure key vault support
10 | - EFCore Support
11 | - Microsoft Dependency Injection Support
12 | - Azure Service Bus Support
13 | - fluent configurtion
14 |
15 | Extensible
16 | - message pipeline with with easy filters on raw data and messages
17 | - extensible transports, outbox, encryption, dependency injection
18 |
--------------------------------------------------------------------------------
/src/Eventfully.Core/Saga/ISaga.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 | using System.Threading.Tasks;
5 |
6 | namespace Eventfully
7 | {
8 | public interface ISaga
9 | {
10 | object State { get;}
11 | void SetState(object state);
12 | object FindKey(IIntegrationMessage message, MessageMetaData meta);
13 | }
14 |
15 | public interface ISaga : ISaga
16 | {
17 | new S State { get; }
18 | new K FindKey(IIntegrationMessage message, MessageMetaData meta);
19 | void SetState(S state);
20 | }
21 |
22 | public class Saga : ISaga
23 | {
24 | protected Dictionary> _keyMappers = new Dictionary>();
25 |
26 |
27 | public Saga()
28 | {
29 | }
30 |
31 | protected void MapIdFor(Func mapper) where M : IIntegrationMessage
32 | {
33 | Func untyped = (m,md) => mapper((M)m, md);
34 | _keyMappers.Add(typeof(M), untyped);
35 | }
36 |
37 | public K FindKey(IIntegrationMessage message, MessageMetaData meta)
38 | {
39 | return _keyMappers[message.GetType()].Invoke(message, meta);
40 | }
41 |
42 | object ISaga.FindKey(IIntegrationMessage message, MessageMetaData meta) => FindKey(message, meta);
43 |
44 | public S State { get; protected set; }
45 | object ISaga.State { get => State; }
46 | void ISaga.SetState(object state) {
47 | this.SetState((S)state);
48 | }
49 |
50 | public virtual void SetState(S state)
51 | {
52 | if (state == null)
53 | throw new InvalidOperationException("State can not be null");
54 | this.State = state;
55 | }
56 |
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/src/Eventfully.Core/Saga/ISagaPersistence.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 | using System.Threading.Tasks;
5 |
6 | namespace Eventfully
7 | {
8 |
9 | public interface ISagaPersistence
10 | {
11 | Task LoadOrCreateState(ISaga saga, object sagaId);
12 | Task AddOrUpdateState(ISaga saga);
13 | }
14 |
15 | public interface ISagaPersistence : ISagaPersistence
16 | {
17 | Task LoadOrCreateState(ISaga saga, K sagaId);
18 | Task AddOrUpdateState(ISaga saga);
19 | }
20 |
21 | public abstract class SagaPersistence : ISagaPersistence
22 | {
23 | public abstract Task AddOrUpdateState(ISaga saga);
24 |
25 | public abstract Task LoadOrCreateState(ISaga saga, K sagaId);
26 |
27 | Task ISagaPersistence.LoadOrCreateState(ISaga saga, object sagaId) => LoadOrCreateState((ISaga) saga, (K) sagaId);
28 |
29 | Task ISagaPersistence.AddOrUpdateState(ISaga saga) => AddOrUpdateState((ISaga)saga);
30 | }
31 |
32 | }
33 |
--------------------------------------------------------------------------------
/src/Eventfully.Core/Saga/ITriggeredBy.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Threading.Tasks;
4 |
5 | namespace Eventfully
6 | {
7 | ///
8 | /// Alias for IMessageHandler used to set a message that starts a saga/process manager
9 | ///
10 | ///
11 | public interface ITriggeredBy : IMessageHandler { }
12 |
13 | }
14 |
--------------------------------------------------------------------------------
/src/Eventfully.Core/Transports/ITransport.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 | using System.Threading;
5 | using System.Threading.Tasks;
6 |
7 | namespace Eventfully.Transports
8 | {
9 | public interface ITransport
10 | {
11 | bool SupportsDelayedDispatch { get; }
12 |
13 | //Task StartAsync(IEndpoint endpoint, CancellationToken cancellationToken);
14 | Task StartAsync(IEndpoint endpoint, Handler handler, CancellationToken cancellationToken);
15 |
16 | Task StopAsync();
17 |
18 | Task Dispatch(string messageTypeIdenfifier, byte[] message, IEndpoint endpoint, MessageMetaData metaData = null);
19 |
20 | void SetReplyToForCommand(IEndpoint endpoint, IIntegrationCommand command, MessageMetaData meta);
21 |
22 | IEndpoint FindEndpointForReply(MessageContext commandContext);
23 |
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/Eventfully.Core/Transports/ITransportFactory.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 | using Eventfully;
5 |
6 | namespace Eventfully.Transports
7 | {
8 | public interface ITransportFactory
9 | {
10 | ITransport Create(TransportSettings settings);
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/src/Eventfully.Core/Transports/Transport.cs:
--------------------------------------------------------------------------------
1 | //using System;
2 | //using System.Collections.Generic;
3 | //using System.Text;
4 | //using System.Threading;
5 | //using System.Threading.Tasks;
6 |
7 | //namespace Eventfully.Transports
8 | //{
9 | // public abstract class Transport : ITransport
10 | // {
11 |
12 | // public abstract bool SupportsDelayedDispatch { get; }
13 |
14 | // ///
15 | // /// Start sending/receiving for the endpoint
16 | // ///
17 | // ///
18 | // ///
19 | // ///
20 | // public abstract Task StartAsync(IEndpoint endpoint, CancellationToken cancellationToken);
21 |
22 | // ///
23 | // /// Dispatch a pre-serialized message to the supplied endpoint
24 | // ///
25 | // ///
26 | // ///
27 | // ///
28 | // ///
29 | // ///
30 | // public abstract Task Dispatch(string messageTypeIdentifier, byte[] message, IEndpoint endpoint, MessageMetaData metaData = null);
31 |
32 | // ///
33 | // /// Use the ReplyTo information in the meata data
34 | // /// to provide an endpoint
35 | // ///
36 | // ///
37 | // ///
38 | // public abstract IEndpoint FindEndpointForReply(MessageContext commandContext);
39 |
40 | // ///
41 | // /// Populate the ReplyTo meta data for a command using
42 | // /// the transports addressing/routing scheme
43 | // ///
44 | // ///
45 | // ///
46 | // ///
47 | // public abstract void SetReplyToForCommand(IEndpoint endpoint, IIntegrationCommand command, MessageMetaData meta);
48 |
49 | // public abstract Task StopAsync()
50 |
51 | // }
52 | //}
53 |
--------------------------------------------------------------------------------
/src/Eventfully.Core/Transports/TransportConfigurationExtenstions.cs:
--------------------------------------------------------------------------------
1 | using Eventfully.Transports;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Text;
5 |
6 | namespace Eventfully
7 | {
8 | public static class TransportConfigurationExtensions
9 | {
10 | public static LocalTransportSettings UseLocalTransport(this IEndpointFluent endpointSettings)
11 | {
12 | var settings = new LocalTransportSettings();
13 | endpointSettings.TransportSettings = settings;
14 | return settings;
15 | }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/Eventfully.Core/Transports/TransportMessage.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 |
5 | namespace Eventfully
6 | {
7 |
8 | ///
9 | /// The Message meta data and message content byte[]
10 | /// standardized to send/receive from a transport
11 | ///
12 | public class TransportMessage
13 | {
14 | public string MessageTypeIdentifier { get; set; }
15 | public byte[] Data { get; set; }
16 | public MessageMetaData MetaData { get; set; }
17 |
18 | public TransportMessage(string messageTypeIdentifier, byte[] data, MessageMetaData meta)
19 | {
20 | this.MessageTypeIdentifier = messageTypeIdentifier;
21 | this.Data = data;
22 | this.MetaData = meta;
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/Eventfully.Core/Transports/TransportSettings.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 |
5 | namespace Eventfully.Transports
6 | {
7 | public interface ITransportSettings
8 | {
9 | ITransportFactory Factory { get; }
10 |
11 | ITransport Create();
12 | }
13 |
14 |
15 | public abstract class TransportSettings : ITransportSettings
16 | {
17 | public abstract ITransportFactory Factory { get; }
18 |
19 | public ITransport Create()
20 | {
21 | return Factory.Create(this);
22 | }
23 |
24 |
25 | }
26 |
27 |
28 |
29 | }
30 |
--------------------------------------------------------------------------------
/src/Eventfully.Core/Util/ConstantRetryStrategy.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 |
5 | namespace Eventfully
6 | {
7 | public class ConstantRetryStrategy : IRetryIntervalStrategy
8 | {
9 | private readonly double _intervalInSeconds;
10 | public ConstantRetryStrategy(double intervalInSeconds)
11 | {
12 | if (intervalInSeconds <= 0)
13 | throw new InvalidOperationException("interval must be greater than 0");
14 | _intervalInSeconds = intervalInSeconds;
15 | }
16 | public DateTime GetNextDateUtc(int tryCount, DateTime? nowUtc = null)
17 | {
18 | var counter = tryCount > 0 ? tryCount : 1;
19 | nowUtc = nowUtc.HasValue ? nowUtc : DateTime.UtcNow;
20 |
21 | return nowUtc.Value.AddSeconds(_intervalInSeconds);
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/Eventfully.Core/Util/DefaultExponentialRetryStrategy.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 |
5 | namespace Eventfully
6 | {
7 | public class DefaultExponentialRetryStrategy : IRetryIntervalStrategy
8 | {
9 | public DateTime GetNextDateUtc(int tryCount, DateTime? nowUtc = null)
10 | {
11 | var counter = tryCount > 0 ? tryCount : 1;
12 | nowUtc = nowUtc.HasValue ? nowUtc : DateTime.UtcNow;
13 |
14 | //2^3,2^4, 2^5... (8,16,32,64,128 (2min), 256 (4min), 512 (8min), 1024 (17min), 2048 (34min), 4096 (1hr) , 8192 ( 2.2hr) ....)
15 | return nowUtc.Value.AddSeconds(Math.Pow(2, counter + 2));
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/Eventfully.Core/Util/Exceptions/EndpointNotFoundException.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 |
5 | namespace Eventfully
6 | {
7 |
8 | public class EndpointNotFoundException : NonTransientException
9 | {
10 | public EndpointNotFoundException(string messageType) : base($"Endpoint not found for Message Type. MessageType: {messageType}")
11 | {
12 |
13 | }
14 | public EndpointNotFoundException(string endpointName, string additionalMessage) : base($"Endpoint not found for Label :{endpointName}. {additionalMessage}")
15 | {
16 |
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/Eventfully.Core/Util/Exceptions/InvalidMessageTypeException.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 |
5 | namespace Eventfully
6 | {
7 |
8 | public class InvalidMessageTypeException : NonTransientException
9 | {
10 | public InvalidMessageTypeException(string messageType):base($"Invalid Message Type. MessageType: {messageType}")
11 | {
12 |
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/Eventfully.Core/Util/Exceptions/MessageExpiredException.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 |
5 | namespace Eventfully
6 | {
7 |
8 | public class MessageExpiredException : NonTransientException
9 | {
10 | public MessageExpiredException(string messageType):base($"Message Expired. MessageType: {messageType}")
11 | {
12 |
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/Eventfully.Core/Util/Exceptions/NonTransientException.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 |
5 | namespace Eventfully
6 | {
7 |
8 | [Serializable]
9 | public class NonTransientException : ApplicationException
10 | {
11 | public NonTransientException()
12 | {
13 |
14 | }
15 |
16 | public NonTransientException(string message, Exception exc = null)
17 | : base(message, exc)
18 | {
19 | }
20 | }
21 | }
--------------------------------------------------------------------------------
/src/Eventfully.Core/Util/Exceptions/UnknownMessageTypeException.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 |
5 | namespace Eventfully
6 | {
7 |
8 | public class UnknownMessageTypeException : NonTransientException
9 | {
10 | public UnknownMessageTypeException(string messageType):base($"Unknown Message Type. MessageType: {messageType}")
11 | {
12 |
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/Eventfully.Core/Util/ICountingSemaphore.cs:
--------------------------------------------------------------------------------
1 | using System.Threading.Tasks;
2 |
3 | namespace Eventfully
4 | {
5 | public interface ICountingSemaphore
6 | {
7 | int MaxConcurrentOwners { get; }
8 | string Name { get; }
9 | int TimeoutInSeconds { get; }
10 |
11 | Task TryAcquire(string ownerId);
12 | Task TryRelease(string ownerId);
13 | Task TryRenew(string ownerId);
14 | }
15 | }
--------------------------------------------------------------------------------
/src/Eventfully.Core/Util/IRetryIntervalStrategy.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Eventfully
4 | {
5 | public interface IRetryIntervalStrategy
6 | {
7 | DateTime GetNextDateUtc(int tryCount, DateTime? nowUtc = null);
8 | }
9 | }
--------------------------------------------------------------------------------
/src/Eventfully.Core/Util/MessagingLogging.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Extensions.Logging;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Text;
5 |
6 | namespace Eventfully
7 | {
8 | public class Logging
9 | {
10 | public static ILoggerFactory LoggerFactory = null;
11 |
12 | public static ILogger CreateLogger() => LoggerFactory.CreateLogger();
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/Eventfully.EFCoreOutbox/Eventfully.EFCoreOutbox.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netstandard2.0
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/src/Eventfully.EFCoreOutbox/README.txt:
--------------------------------------------------------------------------------
1 | - Add property to your DbContext
2 |
3 | public DbSet OutboxEvents { get; set; }
4 |
5 |
6 | - Add to your DbContext OnModelCreating
7 |
8 | builder.ApplyConfiguration(new Common.Infrastructure.Messaging.EFCore.OutboxEventEntityConfiguration());
9 | builder.ApplyConfiguration(new Common.Infrastructure.Messaging.EFCore.OutboxEventDataEntityConfiguration());
10 |
11 |
12 | - If using migrations you can
13 |
14 | Add-Migration Outbox
15 |
16 |
17 | - Configure Depentcy Injection
18 |
19 |
20 | //Needed for reading/updating/deleting the outbox
21 | services.AddTransient>(x =>
22 | new Common.Infrastructure.SqlHelper.SqlConnectionFactory(_config.GetConnectionString("ApplicationConnection"))
23 | );
24 |
25 | //needed for publishing the events from the outbox
26 | services.AddScoped();
27 |
28 | //needed for injecting the outbox into the DbContext and the OutboxEventPumpService
29 | services.AddScoped();
30 |
31 | //needed for injecting into application layer handlers
32 | services.AddScoped(x =>
33 | x.GetRequiredService()
34 | );
35 |
36 | //needed to start the event pump (can be done anywhere, doesn't have to be in a webapp)
37 | services.AddHostedService();
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/src/Eventfully.EFCoreOutbox/RegistrationExtensions.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.EntityFrameworkCore;
2 | using Microsoft.Extensions.DependencyInjection;
3 | using Eventfully.Outboxing;
4 | using System;
5 | using System.Collections.Generic;
6 | using System.Data;
7 | using System.Text;
8 | using Eventfully.EFCoreOutbox;
9 |
10 | namespace Eventfully
11 | {
12 | public static class RegistrationExtensions
13 | {
14 | public static void WithEFCoreOutbox(this IServiceRegistrar services,
15 | Action config
16 | ) where T : DbContext
17 | {
18 | OutboxSettings settings = new OutboxSettings();
19 | if (config != null)
20 | config.Invoke(settings);
21 | services.AddSingleton(settings);
22 | services.AddSingleton>(true);
23 | services.AddTransient>();
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/Eventfully.EFCoreOutbox/SqlConnectionFactory.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Data;
3 | using System.Data.SqlClient;
4 |
5 | namespace Eventfully.EFCoreOutbox
6 | {
7 | public class SqlConnectionFactory
8 | {
9 | private readonly string _connectionString;
10 | public SqlConnectionFactory()
11 | {
12 | }
13 |
14 | public SqlConnectionFactory(string connectionString)
15 | {
16 | _connectionString = connectionString;
17 | }
18 |
19 | public SqlConnection Get()
20 | {
21 | return new SqlConnection(_connectionString);
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/Eventfully.MicrosoftDependencyInjection/Eventfully.Extensions.Microsoft.DependencyInjection.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netstandard2.0
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/src/Eventfully.MicrosoftDependencyInjection/Extensions.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Extensions.DependencyInjection;
2 | using Microsoft.Extensions.Logging;
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Linq;
6 | using System.Reflection;
7 | using System.Threading.Tasks;
8 | using Eventfully.Handlers;
9 | using Eventfully.Outboxing;
10 |
11 | namespace Eventfully
12 | {
13 |
14 |
15 | public static class DependencyInjectionExtensions
16 | {
17 | public static IServiceRegistrar AddMessaging(this IServiceCollection services,
18 | Profile profile,
19 | params Assembly[] messageAndHandlerAssemblies)
20 | {
21 | return AddMessaging(services, profile, null, null, messageAndHandlerAssemblies);
22 | }
23 |
24 |
25 | public static IServiceRegistrar AddMessaging(this IServiceCollection services,
26 | Profile profile,
27 | EndpointBindings bindings,
28 | params Assembly[] messageAndHandlerAssemblies)
29 | {
30 | return AddMessaging(services, profile, bindings, null, messageAndHandlerAssemblies);
31 | }
32 |
33 | public static IServiceRegistrar AddMessaging(this IServiceCollection services,
34 | Profile profile,
35 | EndpointBindings bindings,
36 | Action config,
37 | params Assembly[] messageAndHandlerAssemblies)
38 | {
39 | MessagingService.InitializeTypes(messageAndHandlerAssemblies);
40 | if(bindings != null)
41 | profile.AddBindings(bindings);
42 | var messagingConfig = new MessagingConfiguration();
43 | if (config != null)
44 | config.Invoke(messagingConfig);
45 |
46 | services.AddSingleton(messagingConfig);
47 | services.AddTransient(x=> new MicrosoftDependencyInjectionServiceFactory(x));
48 | services.AddSingleton(profile);
49 | services.AddSingleton();
50 | services.AddTransient();
51 |
52 | //setup user message handlers
53 | services.Scan(c =>
54 | {
55 | c.FromAssemblies(messageAndHandlerAssemblies)
56 | .AddClasses(t => t.AssignableTo(typeof(IMessageHandler<>)))
57 | .AsImplementedInterfaces()
58 | .WithTransientLifetime()
59 | .AddClasses(t => t.AssignableTo(typeof(ICustomMessageHandler<>)))
60 | .AsImplementedInterfaces()
61 | .WithTransientLifetime()
62 | .AddClasses(t => t.AssignableTo(typeof(ISaga<,>)))
63 | .AsImplementedInterfaces()
64 | .AsSelf()
65 | .WithTransientLifetime()
66 | .AddClasses(t => t.AssignableTo(typeof(ISagaPersistence<,>)))
67 | .AsImplementedInterfaces()
68 | .WithTransientLifetime()
69 | ;
70 | });
71 |
72 | return new ServiceRegistrar(services);
73 | }
74 |
75 | //public static void UseMessagingHost(this IApplicationBuilder builder)
76 | //{
77 | // UseMessagingHost(builder.ApplicationServices);
78 | //}
79 |
80 | ///
81 | /// Helper for applications other than asp.net core where you might not have access to an IApplicationBuilder
82 | ///
83 | ///
84 | public static void UseMessagingHost(this IServiceProvider provider)
85 | {
86 | var messagingService = provider.GetRequiredService();
87 | Logging.LoggerFactory = provider.GetRequiredService();
88 | messagingService.StartAsync().ConfigureAwait(false).GetAwaiter();
89 | }
90 | }
91 |
92 | }
93 |
--------------------------------------------------------------------------------
/src/Eventfully.MicrosoftDependencyInjection/ServiceFactory.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 | using System.Threading;
5 | using Microsoft.Extensions.DependencyInjection;
6 |
7 | namespace Eventfully
8 | {
9 | public class MicrosoftDependencyInjectionServiceFactory : IServiceFactory
10 | {
11 | private readonly IServiceProvider _provider;
12 | private readonly IServiceScope _scope;
13 | public MicrosoftDependencyInjectionServiceFactory(IServiceProvider provider) => _provider = provider;
14 |
15 | private MicrosoftDependencyInjectionServiceFactory(IServiceScope scope) {
16 | _scope = scope;
17 | _provider = _scope.ServiceProvider;
18 | }
19 | public object GetInstance(Type type) => _provider.GetRequiredService(type);
20 |
21 | public IServiceFactory CreateScope() => new MicrosoftDependencyInjectionServiceFactory(_provider.CreateScope());
22 |
23 | public void Dispose()
24 | {
25 | if (_scope != null)
26 | _scope.Dispose();
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/Eventfully.MicrosoftDependencyInjection/ServiceRegistrar.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Extensions.DependencyInjection;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Text;
5 |
6 | namespace Eventfully
7 | {
8 | public class ServiceRegistrar : IServiceRegistrar
9 | {
10 | private IServiceCollection _services;
11 | public ServiceRegistrar(IServiceCollection services) => _services = services;
12 |
13 | public void AddSingleton(object o) => _services.AddSingleton(o);
14 | public void AddSingleton(object o) => _services.AddSingleton(typeof(T), o);
15 | public void AddSingleton() => _services.AddSingleton(typeof(T));
16 | public void AddSingleton(bool asBoth = false) where T : I
17 | {
18 | if(asBoth)
19 | {
20 | _services.AddSingleton(typeof(T));
21 | _services.AddSingleton(typeof(I), x =>
22 | x.GetRequiredService