diff --git a/Brighter.sln b/Brighter.sln
index 8757596dca..786253ea6a 100644
--- a/Brighter.sln
+++ b/Brighter.sln
@@ -1,9 +1,12 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
-VisualStudioVersion = 17.0.32112.339
-MinimumVisualStudioVersion = 10.0.40219.1
+VisualStudioVersion = 17.8.0.0
+MinimumVisualStudioVersion = 17.8.0.0
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{235DE1F1-E71B-4817-8E27-3B34FF006E4C}"
+ ProjectSection(SolutionItems) = preProject
+ samples\AspireSetup.md = samples\AspireSetup.md
+ EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{329736D2-BF92-4D06-A7BF-19F4B6B64EDD}"
ProjectSection(SolutionItems) = preProject
@@ -337,6 +340,14 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Paramore.Brighter.ServiceAc
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Paramore.Brighter.Sqlite.Dapper", "src\Paramore.Brighter.Sqlite.Dapper\Paramore.Brighter.Sqlite.Dapper.csproj", "{3384FBF0-5DCB-452D-8288-FAD1D0023089}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AppHost", "samples\RMQTaskQueue\AppHost\AppHost.csproj", "{995A09D0-C440-4E88-A0C6-7AF93D82CA82}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ServiceDefaults", "samples\RMQTaskQueue\ServiceDefaults\ServiceDefaults.csproj", "{69AF4ECC-FABB-431F-89CF-0AF972BF197F}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebAPI_EFCore.AppHost", "samples\WebAPI_EFCore\WebAPI_EFCore.AppHost\WebAPI_EFCore.AppHost.csproj", "{F457820A-6B15-4697-B2B3-E3E9FCAABFDE}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebAPI_EFCore.ServiceDefaults", "samples\WebAPI_EFCore\WebAPI_EFCore.ServiceDefaults\WebAPI_EFCore.ServiceDefaults.csproj", "{0D35AAF9-54AE-43CB-8E29-D9605ECA5EDF}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -1931,6 +1942,54 @@ Global
{3384FBF0-5DCB-452D-8288-FAD1D0023089}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{3384FBF0-5DCB-452D-8288-FAD1D0023089}.Release|x86.ActiveCfg = Release|Any CPU
{3384FBF0-5DCB-452D-8288-FAD1D0023089}.Release|x86.Build.0 = Release|Any CPU
+ {995A09D0-C440-4E88-A0C6-7AF93D82CA82}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {995A09D0-C440-4E88-A0C6-7AF93D82CA82}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {995A09D0-C440-4E88-A0C6-7AF93D82CA82}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
+ {995A09D0-C440-4E88-A0C6-7AF93D82CA82}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
+ {995A09D0-C440-4E88-A0C6-7AF93D82CA82}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {995A09D0-C440-4E88-A0C6-7AF93D82CA82}.Debug|x86.Build.0 = Debug|Any CPU
+ {995A09D0-C440-4E88-A0C6-7AF93D82CA82}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {995A09D0-C440-4E88-A0C6-7AF93D82CA82}.Release|Any CPU.Build.0 = Release|Any CPU
+ {995A09D0-C440-4E88-A0C6-7AF93D82CA82}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
+ {995A09D0-C440-4E88-A0C6-7AF93D82CA82}.Release|Mixed Platforms.Build.0 = Release|Any CPU
+ {995A09D0-C440-4E88-A0C6-7AF93D82CA82}.Release|x86.ActiveCfg = Release|Any CPU
+ {995A09D0-C440-4E88-A0C6-7AF93D82CA82}.Release|x86.Build.0 = Release|Any CPU
+ {69AF4ECC-FABB-431F-89CF-0AF972BF197F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {69AF4ECC-FABB-431F-89CF-0AF972BF197F}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {69AF4ECC-FABB-431F-89CF-0AF972BF197F}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
+ {69AF4ECC-FABB-431F-89CF-0AF972BF197F}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
+ {69AF4ECC-FABB-431F-89CF-0AF972BF197F}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {69AF4ECC-FABB-431F-89CF-0AF972BF197F}.Debug|x86.Build.0 = Debug|Any CPU
+ {69AF4ECC-FABB-431F-89CF-0AF972BF197F}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {69AF4ECC-FABB-431F-89CF-0AF972BF197F}.Release|Any CPU.Build.0 = Release|Any CPU
+ {69AF4ECC-FABB-431F-89CF-0AF972BF197F}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
+ {69AF4ECC-FABB-431F-89CF-0AF972BF197F}.Release|Mixed Platforms.Build.0 = Release|Any CPU
+ {69AF4ECC-FABB-431F-89CF-0AF972BF197F}.Release|x86.ActiveCfg = Release|Any CPU
+ {69AF4ECC-FABB-431F-89CF-0AF972BF197F}.Release|x86.Build.0 = Release|Any CPU
+ {F457820A-6B15-4697-B2B3-E3E9FCAABFDE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {F457820A-6B15-4697-B2B3-E3E9FCAABFDE}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {F457820A-6B15-4697-B2B3-E3E9FCAABFDE}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
+ {F457820A-6B15-4697-B2B3-E3E9FCAABFDE}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
+ {F457820A-6B15-4697-B2B3-E3E9FCAABFDE}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {F457820A-6B15-4697-B2B3-E3E9FCAABFDE}.Debug|x86.Build.0 = Debug|Any CPU
+ {F457820A-6B15-4697-B2B3-E3E9FCAABFDE}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {F457820A-6B15-4697-B2B3-E3E9FCAABFDE}.Release|Any CPU.Build.0 = Release|Any CPU
+ {F457820A-6B15-4697-B2B3-E3E9FCAABFDE}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
+ {F457820A-6B15-4697-B2B3-E3E9FCAABFDE}.Release|Mixed Platforms.Build.0 = Release|Any CPU
+ {F457820A-6B15-4697-B2B3-E3E9FCAABFDE}.Release|x86.ActiveCfg = Release|Any CPU
+ {F457820A-6B15-4697-B2B3-E3E9FCAABFDE}.Release|x86.Build.0 = Release|Any CPU
+ {0D35AAF9-54AE-43CB-8E29-D9605ECA5EDF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {0D35AAF9-54AE-43CB-8E29-D9605ECA5EDF}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {0D35AAF9-54AE-43CB-8E29-D9605ECA5EDF}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
+ {0D35AAF9-54AE-43CB-8E29-D9605ECA5EDF}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
+ {0D35AAF9-54AE-43CB-8E29-D9605ECA5EDF}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {0D35AAF9-54AE-43CB-8E29-D9605ECA5EDF}.Debug|x86.Build.0 = Debug|Any CPU
+ {0D35AAF9-54AE-43CB-8E29-D9605ECA5EDF}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {0D35AAF9-54AE-43CB-8E29-D9605ECA5EDF}.Release|Any CPU.Build.0 = Release|Any CPU
+ {0D35AAF9-54AE-43CB-8E29-D9605ECA5EDF}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
+ {0D35AAF9-54AE-43CB-8E29-D9605ECA5EDF}.Release|Mixed Platforms.Build.0 = Release|Any CPU
+ {0D35AAF9-54AE-43CB-8E29-D9605ECA5EDF}.Release|x86.ActiveCfg = Release|Any CPU
+ {0D35AAF9-54AE-43CB-8E29-D9605ECA5EDF}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -2044,6 +2103,10 @@ Global
{D7361C21-AB4D-4E82-8094-FB86C2ED1800} = {65F8C2DE-0CB6-4102-8187-A247F1D5D3D7}
{18742337-075A-40D6-B67F-91F5894A50C3} = {65F8C2DE-0CB6-4102-8187-A247F1D5D3D7}
{AA2AA086-9B8A-4910-A793-E92B1E352351} = {329736D2-BF92-4D06-A7BF-19F4B6B64EDD}
+ {995A09D0-C440-4E88-A0C6-7AF93D82CA82} = {E9748DC0-6E72-4634-AF49-428F806E03B0}
+ {69AF4ECC-FABB-431F-89CF-0AF972BF197F} = {E9748DC0-6E72-4634-AF49-428F806E03B0}
+ {F457820A-6B15-4697-B2B3-E3E9FCAABFDE} = {C6B17EFD-4F05-4D45-AF3E-C4F3F790B994}
+ {0D35AAF9-54AE-43CB-8E29-D9605ECA5EDF} = {C6B17EFD-4F05-4D45-AF3E-C4F3F790B994}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {8B7C7E31-2E32-4E0D-9426-BC9AF22E9F4C}
diff --git a/Directory.Packages.props b/Directory.Packages.props
index 90ecb0bce8..cd120aa7bf 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -4,6 +4,8 @@
false
+
+
@@ -27,6 +29,7 @@
+
@@ -38,8 +41,10 @@
+
+
@@ -55,9 +60,13 @@
+
-
+
+
+
+
diff --git a/samples/AspireSetup.md b/samples/AspireSetup.md
new file mode 100644
index 0000000000..1b73187ab7
--- /dev/null
+++ b/samples/AspireSetup.md
@@ -0,0 +1,44 @@
+Aspire documentation
+https://learn.microsoft.com/en-us/dotnet/aspire/
+
+Visual Studio:
+Visual Studio 2022 Preview version 17.9 or higher (Optional)
+
+Rider:
+plugin is https://plugins.jetbrains.com/plugin/23289--net-aspire
+
+
+To install the aspire workload
+```shell
+dotnet workload update
+dotnet workload install aspire
+```
+To check your workload
+```shell
+dotnet workload list
+```
+To update your workloads
+```shell
+dotnet workload update
+```
+
+Aspire template
+```shell
+dotnet new list aspire
+```
+
+Try the sample-starter in a new directory
+```shell
+mkdir TestAspire
+cd TestAspire
+dotnet new aspire-starter
+dotnet run --project TestAspire.AppHost
+```
+
+To run sample from the commandline
+
+```shell
+dotnet run --project samples/RMQTaskQueue/AppHost
+```
+
+
diff --git a/samples/RMQTaskQueue/AppHost/AppHost.csproj b/samples/RMQTaskQueue/AppHost/AppHost.csproj
new file mode 100644
index 0000000000..f35acd51cc
--- /dev/null
+++ b/samples/RMQTaskQueue/AppHost/AppHost.csproj
@@ -0,0 +1,20 @@
+
+
+
+ Exe
+ net8.0
+ enable
+ enable
+ true
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/RMQTaskQueue/AppHost/Program.cs b/samples/RMQTaskQueue/AppHost/Program.cs
new file mode 100644
index 0000000000..f9e1bc0c11
--- /dev/null
+++ b/samples/RMQTaskQueue/AppHost/Program.cs
@@ -0,0 +1,10 @@
+var builder = DistributedApplication.CreateBuilder(args);
+
+var rabbit = builder.AddRabbitMQ("messaging");
+
+var receiver = builder.AddProject("receiver")
+ .WithReference(rabbit);
+var sender = builder.AddProject("sender")
+ .WithReference(rabbit);
+
+builder.Build().Run();
diff --git a/samples/RMQTaskQueue/AppHost/Properties/launchSettings.json b/samples/RMQTaskQueue/AppHost/Properties/launchSettings.json
new file mode 100644
index 0000000000..38f25fcdf1
--- /dev/null
+++ b/samples/RMQTaskQueue/AppHost/Properties/launchSettings.json
@@ -0,0 +1,16 @@
+{
+ "$schema": "http://json.schemastore.org/launchsettings.json",
+ "profiles": {
+ "http": {
+ "commandName": "Project",
+ "dotnetRunMessages": true,
+ "launchBrowser": true,
+ "applicationUrl": "http://localhost:15281",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development",
+ "DOTNET_ENVIRONMENT": "Development",
+ "DOTNET_DASHBOARD_OTLP_ENDPOINT_URL": "http://localhost:16176"
+ }
+ }
+ }
+}
diff --git a/samples/RMQTaskQueue/AppHost/appsettings.Development.json b/samples/RMQTaskQueue/AppHost/appsettings.Development.json
new file mode 100644
index 0000000000..0c208ae918
--- /dev/null
+++ b/samples/RMQTaskQueue/AppHost/appsettings.Development.json
@@ -0,0 +1,8 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft.AspNetCore": "Warning"
+ }
+ }
+}
diff --git a/samples/RMQTaskQueue/AppHost/appsettings.json b/samples/RMQTaskQueue/AppHost/appsettings.json
new file mode 100644
index 0000000000..31c092aa45
--- /dev/null
+++ b/samples/RMQTaskQueue/AppHost/appsettings.json
@@ -0,0 +1,9 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft.AspNetCore": "Warning",
+ "Aspire.Hosting.Dcp": "Warning"
+ }
+ }
+}
diff --git a/samples/RMQTaskQueue/GreetingsReceiverConsole/GreetingsReceiverConsole.csproj b/samples/RMQTaskQueue/GreetingsReceiverConsole/GreetingsReceiverConsole.csproj
index 0bca6766d1..8cc64c1389 100644
--- a/samples/RMQTaskQueue/GreetingsReceiverConsole/GreetingsReceiverConsole.csproj
+++ b/samples/RMQTaskQueue/GreetingsReceiverConsole/GreetingsReceiverConsole.csproj
@@ -5,8 +5,10 @@
+
+
diff --git a/samples/RMQTaskQueue/GreetingsReceiverConsole/Program.cs b/samples/RMQTaskQueue/GreetingsReceiverConsole/Program.cs
index 135c7faf38..b1f35e58fd 100644
--- a/samples/RMQTaskQueue/GreetingsReceiverConsole/Program.cs
+++ b/samples/RMQTaskQueue/GreetingsReceiverConsole/Program.cs
@@ -22,6 +22,7 @@ THE SOFTWARE. */
#endregion
+using Aspire.RabbitMQ.Client;
using System;
using System.Threading.Tasks;
using Greetings.Ports.Commands;
@@ -32,6 +33,7 @@ THE SOFTWARE. */
using Paramore.Brighter.MessagingGateway.RMQ;
using Paramore.Brighter.ServiceActivator.Extensions.DependencyInjection;
using Paramore.Brighter.ServiceActivator.Extensions.Hosting;
+using RabbitMQ.Client;
using Serilog;
namespace GreetingsReceiverConsole
@@ -46,52 +48,56 @@ public static async Task Main(string[] args)
.WriteTo.Console()
.CreateLogger();
- var host = new HostBuilder()
- .ConfigureServices((hostContext, services) =>
+ var subscriptions = new Subscription[]
+ {
+ new RmqSubscription(
+ new SubscriptionName("paramore.example.greeting"),
+ new ChannelName("greeting.event"),
+ new RoutingKey("greeting.event"),
+ timeoutInMilliseconds: 200,
+ isDurable: true,
+ highAvailability: true,
+ makeChannels: OnMissingChannel
+ .Create), //change to OnMissingChannel.Validate if you have infrastructure declared elsewhere
+ new RmqSubscription(
+ new SubscriptionName(
+ "paramore.example.farewell"), //change to OnMissingChannel.Validate if you have infrastructure declared elsewhere
+ new ChannelName("farewell.event"),
+ new RoutingKey("farewell.event"),
+ timeoutInMilliseconds: 200,
+ isDurable: true,
+ highAvailability: true,
+ makeChannels: OnMissingChannel.Create)
+ };
+ var builder = new HostApplicationBuilder();
+
+
+ builder.AddServiceDefaults();
+ builder.AddRabbitMQ("messaging");
+ builder.Logging.AddSerilog();
+
+ var rmqConnection = new RmqMessagingGatewayConnection
+ {
+ AmpqUri = new AmqpUriSpecification(builder.Services.BuildServiceProvider()
+ .GetService().Uri),
+ Exchange = new Exchange("paramore.brighter.exchange")
+ };
+
+ var rmqMessageConsumerFactory = new RmqMessageConsumerFactory(rmqConnection);
+
+ builder.Services.AddServiceActivator(options =>
{
- var subscriptions = new Subscription[]
- {
- new RmqSubscription(
- new SubscriptionName("paramore.example.greeting"),
- new ChannelName("greeting.event"),
- new RoutingKey("greeting.event"),
- timeoutInMilliseconds: 200,
- isDurable: true,
- highAvailability: true,
- makeChannels: OnMissingChannel.Create), //change to OnMissingChannel.Validate if you have infrastructure declared elsewhere
- new RmqSubscription(
- new SubscriptionName("paramore.example.farewell"), //change to OnMissingChannel.Validate if you have infrastructure declared elsewhere
- new ChannelName("farewell.event"),
- new RoutingKey("farewell.event"),
- timeoutInMilliseconds: 200,
- isDurable: true,
- highAvailability: true,
- makeChannels: OnMissingChannel.Create)
- };
-
- var rmqConnection = new RmqMessagingGatewayConnection
- {
- AmpqUri = new AmqpUriSpecification(new Uri("amqp://guest:guest@localhost:5672")),
- Exchange = new Exchange("paramore.brighter.exchange")
- };
-
- var rmqMessageConsumerFactory = new RmqMessageConsumerFactory(rmqConnection);
-
- services.AddServiceActivator(options =>
- {
- options.Subscriptions = subscriptions;
- options.ChannelFactory = new ChannelFactory(rmqMessageConsumerFactory);
- })
- .AutoFromAssemblies();
-
-
- services.AddHostedService();
+ options.Subscriptions = subscriptions;
+ options.ChannelFactory = new ChannelFactory(rmqMessageConsumerFactory);
+
+
})
- .UseConsoleLifetime()
- .UseSerilog()
- .Build();
+ .AutoFromAssemblies();
+
+ builder.Services.AddHostedService();
+ using var host = builder.Build();
await host.RunAsync();
}
}
diff --git a/samples/RMQTaskQueue/GreetingsReceiverConsole/Properties/launchSettings.json b/samples/RMQTaskQueue/GreetingsReceiverConsole/Properties/launchSettings.json
new file mode 100644
index 0000000000..eedf326775
--- /dev/null
+++ b/samples/RMQTaskQueue/GreetingsReceiverConsole/Properties/launchSettings.json
@@ -0,0 +1,12 @@
+{
+ "$schema": "http://json.schemastore.org/launchsettings.json",
+ "profiles": {
+ "GreetingsReceiverConsole": {
+ "commandName": "Project",
+ "dotnetRunMessages": true,
+ "environmentVariables": {
+ "DOTNET_ENVIRONMENT": "Development"
+ }
+ }
+ }
+}
diff --git a/samples/RMQTaskQueue/GreetingsSender/GreetingsSender.csproj b/samples/RMQTaskQueue/GreetingsSender/GreetingsSender.csproj
index faef444721..bc4e023146 100644
--- a/samples/RMQTaskQueue/GreetingsSender/GreetingsSender.csproj
+++ b/samples/RMQTaskQueue/GreetingsSender/GreetingsSender.csproj
@@ -4,6 +4,7 @@
net8.0
+
@@ -14,6 +15,7 @@
+
diff --git a/samples/RMQTaskQueue/GreetingsSender/Program.cs b/samples/RMQTaskQueue/GreetingsSender/Program.cs
index 59fb8d000e..bfdbcadddd 100644
--- a/samples/RMQTaskQueue/GreetingsSender/Program.cs
+++ b/samples/RMQTaskQueue/GreetingsSender/Program.cs
@@ -1,4 +1,5 @@
#region Licence
+
/* The MIT License (MIT)
Copyright © 2017 Ian Cooper
@@ -23,72 +24,71 @@ THE SOFTWARE. */
#endregion
using System;
-using System.Transactions;
using Greetings.Ports.Commands;
using Microsoft.Extensions.DependencyInjection;
-using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Hosting;
using Paramore.Brighter;
using Paramore.Brighter.Extensions.DependencyInjection;
using Paramore.Brighter.MessagingGateway.RMQ;
-using Serilog;
-using Serilog.Extensions.Logging;
+using RabbitMQ.Client;
+
+
+//private static ActivitySource source = new ActivitySource("GreetingsSender", "1.0.0");
+
-namespace GreetingsSender
+HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);
+
+builder.AddServiceDefaults();
+builder.AddRabbitMQ("messaging");
+
+RmqMessagingGatewayConnection rmqConnection = new()
{
- class Program
+ AmpqUri = new AmqpUriSpecification(builder.Services.BuildServiceProvider().GetService().Uri),
+ Exchange = new Exchange("paramore.brighter.exchange")
+};
+
+IAmAProducerRegistry producerRegistry = new RmqProducerRegistryFactory(
+ rmqConnection,
+ new RmqPublication[]
{
- static void Main(string[] args)
+ new()
+ {
+ MaxOutStandingMessages = 5,
+ MaxOutStandingCheckIntervalMilliSeconds = 500,
+ WaitForConfirmsTimeOutInMilliseconds = 1000,
+ MakeChannels = OnMissingChannel.Create,
+ Topic = new RoutingKey("greeting.event")
+ },
+ new()
{
- Log.Logger = new LoggerConfiguration()
- .MinimumLevel.Debug()
- .Enrich.FromLogContext()
- .WriteTo.Console()
- .CreateLogger();
-
- var serviceCollection = new ServiceCollection();
- serviceCollection.AddSingleton(new SerilogLoggerFactory());
-
- var rmqConnection = new RmqMessagingGatewayConnection
- {
- AmpqUri = new AmqpUriSpecification(new Uri("amqp://guest:guest@localhost:5672")),
- Exchange = new Exchange("paramore.brighter.exchange"),
- };
-
- var producerRegistry = new RmqProducerRegistryFactory(
- rmqConnection,
- new RmqPublication[]
- {
- new()
- {
- MaxOutStandingMessages = 5,
- MaxOutStandingCheckIntervalMilliSeconds = 500,
- WaitForConfirmsTimeOutInMilliseconds = 1000,
- MakeChannels =OnMissingChannel.Create,
- Topic = new RoutingKey("greeting.event")
- },
- new()
- {
- MaxOutStandingMessages = 5,
- MaxOutStandingCheckIntervalMilliSeconds = 500,
- WaitForConfirmsTimeOutInMilliseconds = 1000,
- MakeChannels =OnMissingChannel.Create,
- Topic = new RoutingKey("farewell.event")
- }
- }).Create();
-
- serviceCollection.AddBrighter()
- .UseExternalBus((configure) =>
- {
- configure.ProducerRegistry = producerRegistry;
- })
- .AutoFromAssemblies();
-
- var serviceProvider = serviceCollection.BuildServiceProvider();
-
- var commandProcessor = serviceProvider.GetService();
-
- commandProcessor.Post(new GreetingEvent("Ian says: Hi there!"));
- commandProcessor.Post(new FarewellEvent("Ian says: See you later!"));
+ MaxOutStandingMessages = 5,
+ MaxOutStandingCheckIntervalMilliSeconds = 500,
+ WaitForConfirmsTimeOutInMilliseconds = 1000,
+ MakeChannels = OnMissingChannel.Create,
+ Topic = new RoutingKey("farewell.event")
}
- }
-}
+ }).Create();
+
+builder.Services.AddBrighter()
+ .UseExternalBus(configure =>
+ {
+ configure.ProducerRegistry = producerRegistry;
+ })
+ .AutoFromAssemblies();
+
+IHost host = builder.Build();
+
+IAmACommandProcessor commandProcessor = host.Services.GetService();
+
+Console.ReadKey();
+// using (var activity = source.StartActivity("Post"))
+//{
+commandProcessor.Post(new GreetingEvent("Ian says: Hi there!"));
+//}
+
+//using (var activity = source.StartActivity("Post"))
+//{
+commandProcessor.Post(new FarewellEvent("Ian says: See you later!"));
+//}
+
+host.Run();
diff --git a/samples/RMQTaskQueue/GreetingsSender/Properties/launchSettings.json b/samples/RMQTaskQueue/GreetingsSender/Properties/launchSettings.json
new file mode 100644
index 0000000000..c5ab01ab1b
--- /dev/null
+++ b/samples/RMQTaskQueue/GreetingsSender/Properties/launchSettings.json
@@ -0,0 +1,12 @@
+{
+ "$schema": "http://json.schemastore.org/launchsettings.json",
+ "profiles": {
+ "GreetingsSender": {
+ "commandName": "Project",
+ "dotnetRunMessages": true,
+ "environmentVariables": {
+ "DOTNET_ENVIRONMENT": "Development"
+ }
+ }
+ }
+}
diff --git a/samples/RMQTaskQueue/ServiceDefaults/Extensions.cs b/samples/RMQTaskQueue/ServiceDefaults/Extensions.cs
new file mode 100644
index 0000000000..bb7ebd68b7
--- /dev/null
+++ b/samples/RMQTaskQueue/ServiceDefaults/Extensions.cs
@@ -0,0 +1,125 @@
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Diagnostics.HealthChecks;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Diagnostics.HealthChecks;
+using Microsoft.Extensions.Logging;
+using OpenTelemetry.Logs;
+using OpenTelemetry.Metrics;
+using OpenTelemetry.Trace;
+
+namespace Microsoft.Extensions.Hosting;
+
+public static class Extensions
+{
+ public static IHostApplicationBuilder AddServiceDefaults(this IHostApplicationBuilder builder)
+ {
+ builder.ConfigureOpenTelemetry();
+
+ builder.AddDefaultHealthChecks();
+
+ builder.Services.AddServiceDiscovery();
+
+ builder.Services.ConfigureHttpClientDefaults(http =>
+ {
+ // Turn on resilience by default
+ http.AddStandardResilienceHandler();
+
+ // Turn on service discovery by default
+ http.UseServiceDiscovery();
+ });
+
+ return builder;
+ }
+
+ public static IHostApplicationBuilder ConfigureOpenTelemetry(this IHostApplicationBuilder builder)
+ {
+ builder.Logging.AddOpenTelemetry(logging =>
+ {
+ logging.IncludeFormattedMessage = true;
+ logging.IncludeScopes = true;
+ });
+
+ builder.Services.AddOpenTelemetry()
+ .WithMetrics(metrics =>
+ {
+ metrics.AddRuntimeInstrumentation()
+ .AddBuiltInMeters();
+ })
+ .WithTracing(tracing =>
+ {
+ if (builder.Environment.IsDevelopment())
+ {
+ // We want to view all traces in development
+ tracing.SetSampler(new AlwaysOnSampler());
+ }
+
+ tracing.AddAspNetCoreInstrumentation()
+ .AddGrpcClientInstrumentation()
+ .AddHttpClientInstrumentation()
+ .AddSource(
+ "Paramore.Brighter",
+ "Paramore.Brighter.ServiceActivator",
+ "Brighter",
+ "Aspire.RabbitMQ.Client");
+ });
+
+ builder.AddOpenTelemetryExporters();
+
+ return builder;
+ }
+
+ private static IHostApplicationBuilder AddOpenTelemetryExporters(this IHostApplicationBuilder builder)
+ {
+ var useOtlpExporter = !string.IsNullOrWhiteSpace(builder.Configuration["OTEL_EXPORTER_OTLP_ENDPOINT"]);
+
+ if (useOtlpExporter)
+ {
+ builder.Services.Configure(logging => logging.AddOtlpExporter());
+ builder.Services.ConfigureOpenTelemetryMeterProvider(metrics => metrics.AddOtlpExporter());
+ builder.Services.ConfigureOpenTelemetryTracerProvider(tracing => tracing.AddOtlpExporter());
+ }
+
+ // Uncomment the following lines to enable the Prometheus exporter (requires the OpenTelemetry.Exporter.Prometheus.AspNetCore package)
+ // builder.Services.AddOpenTelemetry()
+ // .WithMetrics(metrics => metrics.AddPrometheusExporter());
+
+ // Uncomment the following lines to enable the Azure Monitor exporter (requires the Azure.Monitor.OpenTelemetry.AspNetCore package)
+ // builder.Services.AddOpenTelemetry()
+ // .UseAzureMonitor();
+
+ return builder;
+ }
+
+ public static IHostApplicationBuilder AddDefaultHealthChecks(this IHostApplicationBuilder builder)
+ {
+ builder.Services.AddHealthChecks()
+ // Add a default liveness check to ensure app is responsive
+ .AddCheck("self", () => HealthCheckResult.Healthy(), ["live"]);
+
+ return builder;
+ }
+
+ public static WebApplication MapDefaultEndpoints(this WebApplication app)
+ {
+ // Uncomment the following line to enable the Prometheus endpoint (requires the OpenTelemetry.Exporter.Prometheus.AspNetCore package)
+ // app.MapPrometheusScrapingEndpoint();
+
+ // All health checks must pass for app to be considered ready to accept traffic after starting
+ app.MapHealthChecks("/health");
+
+ // Only health checks tagged with the "live" tag must pass for app to be considered alive
+ app.MapHealthChecks("/alive", new HealthCheckOptions { Predicate = r => r.Tags.Contains("live") });
+
+ return app;
+ }
+
+ private static MeterProviderBuilder AddBuiltInMeters(this MeterProviderBuilder meterProviderBuilder) =>
+ meterProviderBuilder.AddMeter(
+ "Microsoft.AspNetCore.Hosting",
+ "Microsoft.AspNetCore.Server.Kestrel",
+ "System.Net.Http",
+ "Paramore.Brighter",
+ "Paramore.Brighter.ServiceActivator",
+ "Brighter",
+ "Aspire.RabbitMQ.Client");
+}
diff --git a/samples/RMQTaskQueue/ServiceDefaults/ServiceDefaults.csproj b/samples/RMQTaskQueue/ServiceDefaults/ServiceDefaults.csproj
new file mode 100644
index 0000000000..2e29dc1eda
--- /dev/null
+++ b/samples/RMQTaskQueue/ServiceDefaults/ServiceDefaults.csproj
@@ -0,0 +1,23 @@
+
+
+
+ Library
+ net8.0
+ enable
+ enable
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/WebAPI_EFCore/GreetingsWeb/Controllers/GreetingsController.cs b/samples/WebAPI_EFCore/GreetingsWeb/Controllers/GreetingsController.cs
deleted file mode 100644
index 10bbb3ad1e..0000000000
--- a/samples/WebAPI_EFCore/GreetingsWeb/Controllers/GreetingsController.cs
+++ /dev/null
@@ -1,48 +0,0 @@
-using System.Threading.Tasks;
-using GreetingsPorts.Requests;
-using GreetingsPorts.Responses;
-using GreetingsWeb.Models;
-using Microsoft.AspNetCore.Mvc;
-using Paramore.Brighter;
-using Paramore.Darker;
-
-namespace GreetingsWeb.Controllers
-{
- [ApiController]
- [Route("[controller]")]
- public class GreetingsController : Controller
- {
- private readonly IAmACommandProcessor _commandProcessor;
- private readonly IQueryProcessor _queryProcessor;
-
- public GreetingsController(IAmACommandProcessor commandProcessor, IQueryProcessor queryProcessor)
- {
- _commandProcessor = commandProcessor;
- _queryProcessor = queryProcessor;
- }
-
- [Route("{name}")]
- [HttpGet]
- public async Task Get(string name)
- {
- var personsGreetings = await _queryProcessor.ExecuteAsync(new FindGreetingsForPerson(name));
-
- if (personsGreetings == null) return new NotFoundResult();
-
- return Ok(personsGreetings);
- }
-
- [Route("{name}/new")]
- [HttpPost]
- public async Task> Post(string name, NewGreeting newGreeting)
- {
- await _commandProcessor.SendAsync(new AddGreeting(name, newGreeting.Greeting));
-
- var personsGreetings = await _queryProcessor.ExecuteAsync(new FindGreetingsForPerson(name));
-
- if (personsGreetings == null) return new NotFoundResult();
-
- return Ok(personsGreetings);
- }
- }
-}
diff --git a/samples/WebAPI_EFCore/GreetingsWeb/Controllers/PeopleController.cs b/samples/WebAPI_EFCore/GreetingsWeb/Controllers/PeopleController.cs
deleted file mode 100644
index 0ce06da254..0000000000
--- a/samples/WebAPI_EFCore/GreetingsWeb/Controllers/PeopleController.cs
+++ /dev/null
@@ -1,60 +0,0 @@
-using System;
-using System.Threading.Tasks;
-using GreetingsPorts.Requests;
-using GreetingsPorts.Responses;
-using GreetingsWeb.Models;
-using Microsoft.AspNetCore.Mvc;
-using Paramore.Brighter;
-using Paramore.Darker;
-
-namespace GreetingsWeb.Controllers
-{
- [ApiController]
- [Route("[controller]")]
- public class PeopleController : Controller
- {
- private readonly IAmACommandProcessor _commandProcessor;
- private readonly IQueryProcessor _queryProcessor;
-
- public PeopleController(IAmACommandProcessor commandProcessor, IQueryProcessor queryProcessor)
- {
- _commandProcessor = commandProcessor;
- _queryProcessor = queryProcessor;
- }
-
-
- [Route("{name}")]
- [HttpGet]
- public async Task> Get(string name)
- {
- var foundPerson = await _queryProcessor.ExecuteAsync(new FindPersonByName(name));
-
- if (foundPerson == null) return new NotFoundResult();
-
- return Ok(foundPerson);
- }
-
- [Route("{name}")]
- [HttpDelete]
- public async Task Delete(string name)
- {
- await _commandProcessor.SendAsync(new DeletePerson(name));
-
- return Ok();
- }
-
- [Route("new")]
- [HttpPost]
- public async Task> Post(NewPerson newPerson)
- {
- await _commandProcessor.SendAsync(new AddPerson(newPerson.Name));
-
- var addedPerson = await _queryProcessor.ExecuteAsync(new FindPersonByName(newPerson.Name));
-
- if (addedPerson == null) return new NotFoundResult();
-
- return Ok(addedPerson);
- }
-
- }
-}
diff --git a/samples/WebAPI_EFCore/GreetingsWeb/GreetingsWeb.csproj b/samples/WebAPI_EFCore/GreetingsWeb/GreetingsWeb.csproj
index bcd78a44ec..585fb21c4d 100644
--- a/samples/WebAPI_EFCore/GreetingsWeb/GreetingsWeb.csproj
+++ b/samples/WebAPI_EFCore/GreetingsWeb/GreetingsWeb.csproj
@@ -5,6 +5,7 @@
+
@@ -25,6 +26,7 @@
true
+
diff --git a/samples/WebAPI_EFCore/GreetingsWeb/Program.cs b/samples/WebAPI_EFCore/GreetingsWeb/Program.cs
index 5c3320abab..9b86e17d06 100644
--- a/samples/WebAPI_EFCore/GreetingsWeb/Program.cs
+++ b/samples/WebAPI_EFCore/GreetingsWeb/Program.cs
@@ -1,45 +1,251 @@
-using System.IO;
-using GreetingsWeb;
-using GreetingsWeb.Database;
-using Microsoft.AspNetCore.Hosting;
+using System;
+using GreetingsPorts.EntityGateway;
+using GreetingsPorts.Handlers;
+using GreetingsPorts.Policies;
+using GreetingsPorts.Requests;
+using GreetingsWeb.Models;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Http;
+using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
-using Microsoft.Extensions.Logging;
+using Microsoft.OpenApi.Models;
+using MySqlConnector;
+using Paramore.Brighter;
+using Paramore.Brighter.Extensions.DependencyInjection;
+using Paramore.Brighter.Extensions.Hosting;
+using Paramore.Brighter.MessagingGateway.RMQ;
+using Paramore.Brighter.MySql;
+using Paramore.Brighter.MySql.EntityFrameworkCore;
+using Paramore.Brighter.Outbox.MySql;
+using Paramore.Brighter.Outbox.Sqlite;
+using Paramore.Brighter.Sqlite;
+using Paramore.Brighter.Sqlite.EntityFrameworkCore;
+using Paramore.Darker;
+using Paramore.Darker.AspNetCore;
+using Paramore.Darker.Policies;
+using Paramore.Darker.QueryLogging;
+using Polly;
-var host = CreateHostBuilder(args).Build();
+const string _outBoxTableName = "Outbox";
-host.CheckDbIsUp();
-host.MigrateDatabase();
-host.CreateOutbox();
+var builder = WebApplication.CreateBuilder(args);
+builder.Services.AddProblemDetails();
-host.Run();
+// Add service defaults & Aspire components.
+builder.AddServiceDefaults();
+
+builder.Services.AddEndpointsApiExplorer();
+builder.Services.AddSwaggerGen(options => options.SwaggerDoc("v1", new OpenApiInfo { Title = "GreetingsAPI", Version = "v1" }));
+
+if (builder.Environment.IsDevelopment())
+{
+ //NOTE: Sqlite needs to use a shared cache to allow Db writes to the Outbox as well as entities
+ builder.Services.AddDbContext(
+ options =>
+ {
+ options.UseSqlite(DbConnectionString(),
+ optionsBuilder =>
+ {
+ optionsBuilder.MigrationsAssembly("Greetings_SqliteMigrations");
+ })
+ .EnableDetailedErrors()
+ .EnableSensitiveDataLogging();
+ });
+}
+else
+{
+ builder.Services.AddDbContextPool(options =>
+ {
+ options
+ .UseMySql(DbConnectionString(), ServerVersion.AutoDetect(DbConnectionString()), optionsBuilder =>
+ {
+ optionsBuilder.MigrationsAssembly("Greetings_MySqlMigrations");
+ })
+ .EnableDetailedErrors()
+ .EnableSensitiveDataLogging();
+ });
+}
+
+string DbConnectionString()
+{
+ if (builder.Environment.IsDevelopment())
+ {
+ return "Filename=Greetings.db;Cache=Shared";
+ }
+
+ return builder.Configuration.GetConnectionString("Greetings");
+}
+
+(IAmAnOutbox outbox, Type transactionProvider, Type connectionProvider) = MakeOutbox();
+var outboxConfiguration = new RelationalDatabaseConfiguration(DbConnectionString());
+
+builder.Services.AddSingleton(outboxConfiguration);
+
+IAmAProducerRegistry producerRegistry = ConfigureProducerRegistry();
+
+builder.Services.AddBrighter(options =>
+ {
+ //we want to use scoped, so make sure everything understands that which needs to
+ options.HandlerLifetime = ServiceLifetime.Scoped;
+ options.CommandProcessorLifetime = ServiceLifetime.Scoped;
+ options.MapperLifetime = ServiceLifetime.Singleton;
+ options.PolicyRegistry = new GreetingsPolicy();
+ })
+ .UseExternalBus((configure) =>
+ {
+ configure.ProducerRegistry = producerRegistry;
+ configure.Outbox = outbox;
+ configure.TransactionProvider = transactionProvider;
+ configure.ConnectionProvider = connectionProvider;
+ }
+ )
+ .UseOutboxSweeper(options =>
+ {
+ options.TimerInterval = 5;
+ options.MinimumMessageAge = 5000;
+ })
+ .UseOutboxSweeper()
+ .AutoFromAssemblies();
+
+
+builder.Services.AddDarker(options =>
+ {
+ options.HandlerLifetime = ServiceLifetime.Scoped;
+ options.QueryProcessorLifetime = ServiceLifetime.Scoped;
+ })
+ .AddHandlersFromAssemblies(typeof(FindPersonByNameHandlerAsync).Assembly)
+ .AddJsonQueryLogging()
+ .AddPolicies(new GreetingsPolicy());
+
+
+var app = builder.Build();
+
+if (app.Environment.IsDevelopment())
+{
+ app.UseSwagger();
+ app.UseSwaggerUI();
+}
+else
+{
+ app.UseExceptionHandler("/Error", createScopeForErrors: true);
+}
+
+app.UseHttpsRedirection();
+
+//host.CheckDbIsUp();
+string connectionString = DbConnectionString();
+
+var policy = Policy.Handle().WaitAndRetryForever(
+ retryAttempt => TimeSpan.FromSeconds(2),
+ (exception, timespan) =>
+ {
+ Console.WriteLine($"Healthcheck: Waiting for the database {connectionString} to come online - {exception.Message}");
+ });
+
+policy.Execute(() =>
+{
+ //don't check this for SQlite in development
+ if (!app.Environment.IsDevelopment())
+ {
+ using var conn = new MySqlConnection(connectionString);
+ conn.Open();
+ }
+});
+
+
+//host.CreateOutbox();
+
+
+//host.MigrateDatabase();
+using (var scope = app.Services.CreateScope())
+{
+ var dbContext = scope.ServiceProvider.GetRequiredService();
+ dbContext.Database.Migrate();
+}
+
+// Greetings
+app.MapGet("greetings", async (string name, IQueryProcessor queryProcessor) =>
+{
+ var personsGreetings = await queryProcessor.ExecuteAsync(new FindGreetingsForPerson(name));
+ return personsGreetings == null ? Results.NotFound() : Results.Ok(personsGreetings);
+}).WithName("GetGreetings").WithOpenApi();
+
+app.MapPost("greetings/new", async (string name, NewGreeting newGreeting, IAmACommandProcessor commandProcessor, IQueryProcessor queryProcessor) =>
+{
+ await commandProcessor.SendAsync(new AddGreeting(name, newGreeting.Greeting));
+
+ var personsGreetings = await queryProcessor.ExecuteAsync(new FindGreetingsForPerson(name));
+
+ return personsGreetings == null ? Results.NotFound() : Results.Ok(personsGreetings);
+});
+
+// People
+app.MapGet("people", async (string name, IQueryProcessor queryProcessor) =>
+{
+ var foundPerson = await queryProcessor.ExecuteAsync(new FindPersonByName(name));
+
+ return foundPerson == null ? Results.NotFound() : Results.Ok(foundPerson);
+});
+
+app.MapDelete("people", async (string name, IAmACommandProcessor commandProcessor) =>
+{
+ await commandProcessor.SendAsync(new DeletePerson(name));
+
+ return Results.Ok();
+});
+
+app.MapPost("people/new", async (NewPerson newPerson, IAmACommandProcessor commandProcessor, IQueryProcessor queryProcessor) =>
+{
+ await commandProcessor.SendAsync(new AddPerson(newPerson.Name));
+
+ var addedPerson = await queryProcessor.ExecuteAsync(new FindPersonByName(newPerson.Name));
+
+ return addedPerson == null ? Results.NotFound() : Results.Ok(addedPerson);
+});
+
+app.Run();
return;
-static IHostBuilder CreateHostBuilder(string[] args) =>
- Host.CreateDefaultBuilder(args)
- .ConfigureAppConfiguration((context, configBuilder) =>
+
+(IAmAnOutbox outbox, Type transactionProvider, Type connectionProvider) MakeOutbox()
+{
+ if (builder.Environment.IsDevelopment())
+ {
+ var outbox = new SqliteOutbox(new RelationalDatabaseConfiguration(DbConnectionString(), _outBoxTableName));
+ var transactionProvider = typeof(SqliteEntityFrameworkConnectionProvider);
+ var connectionProvider = typeof(SqliteConnectionProvider);
+ return (outbox, transactionProvider, connectionProvider);
+ }
+ else
+ {
+ var outbox = new MySqlOutbox(new RelationalDatabaseConfiguration(DbConnectionString(), _outBoxTableName));
+ var transactionProvider = typeof(MySqlEntityFrameworkConnectionProvider);
+ var connectionProvider = typeof(MySqlConnectionProvider);
+ return (outbox, transactionProvider, connectionProvider);
+ }
+}
+
+static IAmAProducerRegistry ConfigureProducerRegistry()
+{
+ var producerRegistry = new RmqProducerRegistryFactory(
+ new RmqMessagingGatewayConnection
{
- var env = context.HostingEnvironment;
- configBuilder.AddJsonFile("appsettings.json", optional: false);
- configBuilder.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true);
- configBuilder.AddEnvironmentVariables(prefix: "BRIGHTER_");
- })
- .ConfigureWebHostDefaults(webBuilder =>
+ AmpqUri = new AmqpUriSpecification(new Uri("amqp://guest:guest@localhost:5672")),
+ Exchange = new Exchange("paramore.brighter.exchange"),
+ },
+ new RmqPublication[]
{
- webBuilder.UseKestrel();
- webBuilder.UseContentRoot(Directory.GetCurrentDirectory());
- webBuilder.CaptureStartupErrors(true);
- webBuilder.UseSetting("detailedErrors", "true");
- webBuilder.ConfigureLogging((hostingContext, logging) =>
+ new RmqPublication
{
- logging.AddConsole();
- logging.AddDebug();
- });
- webBuilder.UseDefaultServiceProvider((context, options) =>
- {
- var isDevelopment = context.HostingEnvironment.IsDevelopment();
- options.ValidateScopes = isDevelopment;
- options.ValidateOnBuild = isDevelopment;
- });
- webBuilder.UseStartup();
- });
+ Topic = new RoutingKey("GreetingMade"),
+ MaxOutStandingMessages = 5,
+ MaxOutStandingCheckIntervalMilliSeconds = 500,
+ WaitForConfirmsTimeOutInMilliseconds = 1000,
+ MakeChannels = OnMissingChannel.Create
+ }
+ }
+ ).Create();
+ return producerRegistry;
+}
diff --git a/samples/WebAPI_EFCore/GreetingsWeb/Startup.cs b/samples/WebAPI_EFCore/GreetingsWeb/Startup.cs
deleted file mode 100644
index 60376552e2..0000000000
--- a/samples/WebAPI_EFCore/GreetingsWeb/Startup.cs
+++ /dev/null
@@ -1,233 +0,0 @@
-using System;
-using GreetingsPorts.EntityGateway;
-using GreetingsPorts.Handlers;
-using GreetingsPorts.Policies;
-using Microsoft.AspNetCore.Builder;
-using Microsoft.AspNetCore.Hosting;
-using Microsoft.EntityFrameworkCore;
-using Microsoft.Extensions.Configuration;
-using Microsoft.Extensions.DependencyInjection;
-using Microsoft.Extensions.Hosting;
-using Microsoft.OpenApi.Models;
-using MySqlConnector;
-using Paramore.Brighter;
-using Paramore.Brighter.Extensions.DependencyInjection;
-using Paramore.Brighter.Extensions.Hosting;
-using Paramore.Brighter.MessagingGateway.RMQ;
-using Paramore.Brighter.MySql;
-using Paramore.Brighter.MySql.EntityFrameworkCore;
-using Paramore.Brighter.Outbox.MySql;
-using Paramore.Brighter.Outbox.Sqlite;
-using Paramore.Brighter.Sqlite;
-using Paramore.Brighter.Sqlite.EntityFrameworkCore;
-using Paramore.Darker.AspNetCore;
-using Paramore.Darker.Policies;
-using Paramore.Darker.QueryLogging;
-using Polly;
-
-namespace GreetingsWeb
-{
- public class Startup
- {
- private const string _outBoxTableName = "Outbox";
- private IWebHostEnvironment _env;
-
- public Startup(IConfiguration configuration, IWebHostEnvironment env)
- {
- Configuration = configuration;
- _env = env;
- }
-
- public IConfiguration Configuration { get; }
-
- // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
- public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
- {
- if (env.IsDevelopment())
- {
- app.UseSwagger();
- app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "GreetingsAPI v1"));
- }
-
- app.UseHttpsRedirection();
- app.UseRouting();
-
- app.UseEndpoints(endpoints => { endpoints.MapControllers(); });
- }
-
-
- // This method gets called by the runtime. Use this method to add services to the container.
- public void ConfigureServices(IServiceCollection services)
- {
- services.AddMvcCore().AddApiExplorer();
- services.AddControllers(options =>
- {
- options.RespectBrowserAcceptHeader = true;
- })
- .AddXmlSerializerFormatters();
- services.AddProblemDetails();
- services.AddSwaggerGen(c =>
- {
- c.SwaggerDoc("v1", new OpenApiInfo { Title = "GreetingsAPI", Version = "v1" });
- });
-
- ConfigureEFCore(services);
- ConfigureBrighter(services);
- ConfigureDarker(services);
- }
-
- private void CheckDbIsUp()
- {
- string connectionString = DbConnectionString();
-
- var policy = Policy.Handle().WaitAndRetryForever(
- retryAttempt => TimeSpan.FromSeconds(2),
- (exception, timespan) =>
- {
- Console.WriteLine(
- $"Healthcheck: Waiting for the database {connectionString} to come online - {exception.Message}");
- });
-
- policy.Execute(() =>
- {
- //don't check this for SQlite in development
- if (!_env.IsDevelopment())
- {
- using (var conn = new MySqlConnection(connectionString))
- {
- conn.Open();
- }
- }
- });
- }
-
- private void ConfigureBrighter(IServiceCollection services)
- {
- (IAmAnOutbox outbox, Type transactionProvider, Type connectionProvider) = MakeOutbox();
- var outboxConfiguration = new RelationalDatabaseConfiguration(DbConnectionString());
- services.AddSingleton(outboxConfiguration);
-
- IAmAProducerRegistry producerRegistry = ConfigureProducerRegistry();
-
- services.AddBrighter(options =>
- {
- //we want to use scoped, so make sure everything understands that which needs to
- options.HandlerLifetime = ServiceLifetime.Scoped;
- options.CommandProcessorLifetime = ServiceLifetime.Scoped;
- options.MapperLifetime = ServiceLifetime.Singleton;
- options.PolicyRegistry = new GreetingsPolicy();
- })
- .UseExternalBus((configure) =>
- {
- configure.ProducerRegistry = producerRegistry;
- configure.Outbox = outbox;
- configure.TransactionProvider = transactionProvider;
- configure.ConnectionProvider = connectionProvider;
- }
- )
- .UseOutboxSweeper(options =>
- {
- options.TimerInterval = 5;
- options.MinimumMessageAge = 5000;
- })
- .UseOutboxSweeper()
- .AutoFromAssemblies();
- }
-
- private void ConfigureDarker(IServiceCollection services)
- {
- services.AddDarker(options =>
- {
- options.HandlerLifetime = ServiceLifetime.Scoped;
- options.QueryProcessorLifetime = ServiceLifetime.Scoped;
- })
- .AddHandlersFromAssemblies(typeof(FindPersonByNameHandlerAsync).Assembly)
- .AddJsonQueryLogging()
- .AddPolicies(new GreetingsPolicy());
- }
-
- private void ConfigureEFCore(IServiceCollection services)
- {
- string connectionString = DbConnectionString();
-
- if (_env.IsDevelopment())
- {
- services.AddDbContext(
- builder =>
- {
- builder.UseSqlite(connectionString,
- optionsBuilder =>
- {
- optionsBuilder.MigrationsAssembly("Greetings_SqliteMigrations");
- })
- .EnableDetailedErrors()
- .EnableSensitiveDataLogging();
- });
- }
- else
- {
- services.AddDbContextPool(builder =>
- {
- builder
- .UseMySql(connectionString, ServerVersion.AutoDetect(connectionString), optionsBuilder =>
- {
- optionsBuilder.MigrationsAssembly("Greetings_MySqlMigrations");
- })
- .EnableDetailedErrors()
- .EnableSensitiveDataLogging();
- });
- }
- }
-
- private static IAmAProducerRegistry ConfigureProducerRegistry()
- {
- var producerRegistry = new RmqProducerRegistryFactory(
- new RmqMessagingGatewayConnection
- {
- AmpqUri = new AmqpUriSpecification(new Uri("amqp://guest:guest@localhost:5672")),
- Exchange = new Exchange("paramore.brighter.exchange"),
- },
- new RmqPublication[]
- {
- new RmqPublication
- {
- Topic = new RoutingKey("GreetingMade"),
- MaxOutStandingMessages = 5,
- MaxOutStandingCheckIntervalMilliSeconds = 500,
- WaitForConfirmsTimeOutInMilliseconds = 1000,
- MakeChannels = OnMissingChannel.Create
- }
- }
- ).Create();
- return producerRegistry;
- }
-
- private string DbConnectionString()
- {
- //NOTE: Sqlite needs to use a shared cache to allow Db writes to the Outbox as well as entities
- return _env.IsDevelopment()
- ? "Filename=Greetings.db;Cache=Shared"
- : Configuration.GetConnectionString("Greetings");
- }
-
- private (IAmAnOutbox outbox, Type transactionProvider, Type connectionProvider) MakeOutbox()
- {
- if (_env.IsDevelopment())
- {
- var outbox = new SqliteOutbox(
- new RelationalDatabaseConfiguration(DbConnectionString(), _outBoxTableName));
- var transactionProvider = typeof(SqliteEntityFrameworkConnectionProvider);
- var connectionProvider = typeof(SqliteConnectionProvider);
- return (outbox, transactionProvider, connectionProvider);
- }
- else
- {
- var outbox = new MySqlOutbox(
- new RelationalDatabaseConfiguration(DbConnectionString(), _outBoxTableName));
- var transactionProvider = typeof(MySqlEntityFrameworkConnectionProvider);
- var connectionProvider = typeof(MySqlConnectionProvider);
- return (outbox, transactionProvider, connectionProvider);
- }
- }
- }
-}
diff --git a/samples/WebAPI_EFCore/WebAPI_EFCore.AppHost/Program.cs b/samples/WebAPI_EFCore/WebAPI_EFCore.AppHost/Program.cs
new file mode 100644
index 0000000000..9ed54abcb8
--- /dev/null
+++ b/samples/WebAPI_EFCore/WebAPI_EFCore.AppHost/Program.cs
@@ -0,0 +1,10 @@
+var builder = DistributedApplication.CreateBuilder(args);
+
+var rabbit = builder.AddRabbitMQ("messaging");
+
+var greetingsWeb = builder.AddProject("greetings_web")
+ .WithReference(rabbit);
+var salutation_analytics = builder.AddProject("salutation_analytics")
+ .WithReference(rabbit);
+
+builder.Build().Run();
diff --git a/samples/WebAPI_EFCore/WebAPI_EFCore.AppHost/Properties/launchSettings.json b/samples/WebAPI_EFCore/WebAPI_EFCore.AppHost/Properties/launchSettings.json
new file mode 100644
index 0000000000..2713ccd572
--- /dev/null
+++ b/samples/WebAPI_EFCore/WebAPI_EFCore.AppHost/Properties/launchSettings.json
@@ -0,0 +1,16 @@
+{
+ "$schema": "http://json.schemastore.org/launchsettings.json",
+ "profiles": {
+ "http": {
+ "commandName": "Project",
+ "dotnetRunMessages": true,
+ "launchBrowser": true,
+ "applicationUrl": "http://localhost:15080",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development",
+ "DOTNET_ENVIRONMENT": "Development",
+ "DOTNET_DASHBOARD_OTLP_ENDPOINT_URL": "http://localhost:16016"
+ }
+ }
+ }
+}
diff --git a/samples/WebAPI_EFCore/WebAPI_EFCore.AppHost/WebAPI_EFCore.AppHost.csproj b/samples/WebAPI_EFCore/WebAPI_EFCore.AppHost/WebAPI_EFCore.AppHost.csproj
new file mode 100644
index 0000000000..440b4cd3df
--- /dev/null
+++ b/samples/WebAPI_EFCore/WebAPI_EFCore.AppHost/WebAPI_EFCore.AppHost.csproj
@@ -0,0 +1,20 @@
+
+
+
+ Exe
+ net8.0
+ enable
+ enable
+ true
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/WebAPI_EFCore/WebAPI_EFCore.AppHost/appsettings.Development.json b/samples/WebAPI_EFCore/WebAPI_EFCore.AppHost/appsettings.Development.json
new file mode 100644
index 0000000000..0c208ae918
--- /dev/null
+++ b/samples/WebAPI_EFCore/WebAPI_EFCore.AppHost/appsettings.Development.json
@@ -0,0 +1,8 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft.AspNetCore": "Warning"
+ }
+ }
+}
diff --git a/samples/WebAPI_EFCore/WebAPI_EFCore.AppHost/appsettings.json b/samples/WebAPI_EFCore/WebAPI_EFCore.AppHost/appsettings.json
new file mode 100644
index 0000000000..31c092aa45
--- /dev/null
+++ b/samples/WebAPI_EFCore/WebAPI_EFCore.AppHost/appsettings.json
@@ -0,0 +1,9 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft.AspNetCore": "Warning",
+ "Aspire.Hosting.Dcp": "Warning"
+ }
+ }
+}
diff --git a/samples/WebAPI_EFCore/WebAPI_EFCore.ServiceDefaults/Extensions.cs b/samples/WebAPI_EFCore/WebAPI_EFCore.ServiceDefaults/Extensions.cs
new file mode 100644
index 0000000000..4921efee9a
--- /dev/null
+++ b/samples/WebAPI_EFCore/WebAPI_EFCore.ServiceDefaults/Extensions.cs
@@ -0,0 +1,119 @@
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Diagnostics.HealthChecks;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Diagnostics.HealthChecks;
+using Microsoft.Extensions.Logging;
+using OpenTelemetry.Logs;
+using OpenTelemetry.Metrics;
+using OpenTelemetry.Trace;
+
+namespace Microsoft.Extensions.Hosting;
+
+public static class Extensions
+{
+ public static IHostApplicationBuilder AddServiceDefaults(this IHostApplicationBuilder builder)
+ {
+ builder.ConfigureOpenTelemetry();
+
+ builder.AddDefaultHealthChecks();
+
+ builder.Services.AddServiceDiscovery();
+
+ builder.Services.ConfigureHttpClientDefaults(http =>
+ {
+ // Turn on resilience by default
+ http.AddStandardResilienceHandler();
+
+ // Turn on service discovery by default
+ http.UseServiceDiscovery();
+ });
+
+ return builder;
+ }
+
+ public static IHostApplicationBuilder ConfigureOpenTelemetry(this IHostApplicationBuilder builder)
+ {
+ builder.Logging.AddOpenTelemetry(logging =>
+ {
+ logging.IncludeFormattedMessage = true;
+ logging.IncludeScopes = true;
+ });
+
+ builder.Services.AddOpenTelemetry()
+ .WithMetrics(metrics =>
+ {
+ metrics.AddRuntimeInstrumentation()
+ .AddBuiltInMeters();
+ })
+ .WithTracing(tracing =>
+ {
+ if (builder.Environment.IsDevelopment())
+ {
+ // We want to view all traces in development
+ tracing.SetSampler(new AlwaysOnSampler());
+ }
+
+ tracing.AddAspNetCoreInstrumentation()
+ .AddGrpcClientInstrumentation()
+ .AddHttpClientInstrumentation();
+ });
+
+ builder.AddOpenTelemetryExporters();
+
+ return builder;
+ }
+
+ private static IHostApplicationBuilder AddOpenTelemetryExporters(this IHostApplicationBuilder builder)
+ {
+ var useOtlpExporter = !string.IsNullOrWhiteSpace(builder.Configuration["OTEL_EXPORTER_OTLP_ENDPOINT"]);
+
+ if (useOtlpExporter)
+ {
+ builder.Services.Configure(logging => logging.AddOtlpExporter());
+ builder.Services.ConfigureOpenTelemetryMeterProvider(metrics => metrics.AddOtlpExporter());
+ builder.Services.ConfigureOpenTelemetryTracerProvider(tracing => tracing.AddOtlpExporter());
+ }
+
+ // Uncomment the following lines to enable the Prometheus exporter (requires the OpenTelemetry.Exporter.Prometheus.AspNetCore package)
+ // builder.Services.AddOpenTelemetry()
+ // .WithMetrics(metrics => metrics.AddPrometheusExporter());
+
+ // Uncomment the following lines to enable the Azure Monitor exporter (requires the Azure.Monitor.OpenTelemetry.AspNetCore package)
+ // builder.Services.AddOpenTelemetry()
+ // .UseAzureMonitor();
+
+ return builder;
+ }
+
+ public static IHostApplicationBuilder AddDefaultHealthChecks(this IHostApplicationBuilder builder)
+ {
+ builder.Services.AddHealthChecks()
+ // Add a default liveness check to ensure app is responsive
+ .AddCheck("self", () => HealthCheckResult.Healthy(), ["live"]);
+
+ return builder;
+ }
+
+ public static WebApplication MapDefaultEndpoints(this WebApplication app)
+ {
+ // Uncomment the following line to enable the Prometheus endpoint (requires the OpenTelemetry.Exporter.Prometheus.AspNetCore package)
+ // app.MapPrometheusScrapingEndpoint();
+
+ // All health checks must pass for app to be considered ready to accept traffic after starting
+ app.MapHealthChecks("/health");
+
+ // Only health checks tagged with the "live" tag must pass for app to be considered alive
+ app.MapHealthChecks("/alive", new HealthCheckOptions
+ {
+ Predicate = r => r.Tags.Contains("live")
+ });
+
+ return app;
+ }
+
+ private static MeterProviderBuilder AddBuiltInMeters(this MeterProviderBuilder meterProviderBuilder) =>
+ meterProviderBuilder.AddMeter(
+ "Microsoft.AspNetCore.Hosting",
+ "Microsoft.AspNetCore.Server.Kestrel",
+ "System.Net.Http");
+}
diff --git a/samples/WebAPI_EFCore/WebAPI_EFCore.ServiceDefaults/WebAPI_EFCore.ServiceDefaults.csproj b/samples/WebAPI_EFCore/WebAPI_EFCore.ServiceDefaults/WebAPI_EFCore.ServiceDefaults.csproj
new file mode 100644
index 0000000000..30d5c1fb7c
--- /dev/null
+++ b/samples/WebAPI_EFCore/WebAPI_EFCore.ServiceDefaults/WebAPI_EFCore.ServiceDefaults.csproj
@@ -0,0 +1,24 @@
+
+
+
+ Library
+ net8.0
+ enable
+ enable
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Paramore.Brighter.ServiceActivator/MessagePump.cs b/src/Paramore.Brighter.ServiceActivator/MessagePump.cs
index 15f5e4ad9d..db7c0a979b 100644
--- a/src/Paramore.Brighter.ServiceActivator/MessagePump.cs
+++ b/src/Paramore.Brighter.ServiceActivator/MessagePump.cs
@@ -53,7 +53,7 @@ public abstract class MessagePump : IAmAMessagePump where TRequest : c
internal static readonly ILogger s_logger = ApplicationLogging.CreateLogger>();
private static readonly ActivitySource _activitySource = new ActivitySource("Paramore.Brighter.ServiceActivator",
- Assembly.GetAssembly(typeof(CommandProcessor)).GetName().Version.ToString());
+ Assembly.GetAssembly(typeof(CommandProcessor))?.GetName().Version?.ToString());
protected readonly IAmACommandProcessorProvider CommandProcessorProvider;
private int _unacceptableMessageCount = 0;