diff --git a/02-DeclareDurableQueues/02-DeclareDurableQueues.csproj b/02-DeclareDurableQueues/02-DeclareDurableQueues.csproj
new file mode 100644
index 0000000..7104473
--- /dev/null
+++ b/02-DeclareDurableQueues/02-DeclareDurableQueues.csproj
@@ -0,0 +1,19 @@
+
+
+
+ Exe
+ netcoreapp2.1
+ _02_DeclareDurableQueues
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/02-DeclareDurableQueues/ExampleMessageController.cs b/02-DeclareDurableQueues/ExampleMessageController.cs
new file mode 100644
index 0000000..ee5266e
--- /dev/null
+++ b/02-DeclareDurableQueues/ExampleMessageController.cs
@@ -0,0 +1,28 @@
+using System;
+using ExampleLib;
+using Messaging.TapetiExample;
+using Tapeti.Annotations;
+
+namespace _02_DeclareDurableQueues
+{
+ [MessageController]
+ [DurableQueue("tapeti.example.02")]
+ public class ExampleMessageController
+ {
+ private readonly IExampleState exampleState;
+
+
+ public ExampleMessageController(IExampleState exampleState)
+ {
+ this.exampleState = exampleState;
+ }
+
+
+ public void HandlePublishSubscribeMessage(PublishSubscribeMessage message)
+ {
+ // Note that if you run example 01 after 02, it's message will also be in this durable queue
+ Console.WriteLine("Received message: " + message.Greeting);
+ exampleState.Done();
+ }
+ }
+}
diff --git a/02-DeclareDurableQueues/Program.cs b/02-DeclareDurableQueues/Program.cs
new file mode 100644
index 0000000..935470a
--- /dev/null
+++ b/02-DeclareDurableQueues/Program.cs
@@ -0,0 +1,48 @@
+using System;
+using System.Threading.Tasks;
+using ExampleLib;
+using Messaging.TapetiExample;
+using SimpleInjector;
+using Tapeti;
+using Tapeti.Default;
+using Tapeti.SimpleInjector;
+
+namespace _02_DeclareDurableQueues
+{
+ public class Program
+ {
+ public static void Main(string[] args)
+ {
+ var container = new Container();
+ var dependencyResolver = new SimpleInjectorDependencyResolver(container);
+
+ container.Register();
+
+ var helper = new ExampleConsoleApp(dependencyResolver);
+ helper.Run(MainAsync);
+ }
+
+
+ internal static async Task MainAsync(IDependencyResolver dependencyResolver, Func waitForDone)
+ {
+ var config = new TapetiConfig(dependencyResolver)
+ .RegisterAllControllers()
+ .EnableDeclareDurableQueues()
+ .Build();
+
+ using (var connection = new TapetiConnection(config))
+ {
+ // This creates or updates the durable queue
+ await connection.Subscribe();
+
+ await dependencyResolver.Resolve().Publish(new PublishSubscribeMessage
+ {
+ Greeting = "Hello durable queue!"
+ });
+
+ // Wait for the controller to signal that the message has been received
+ await waitForDone();
+ }
+ }
+ }
+}
diff --git a/ExampleHelper.cs/ExampleConsoleApp.cs b/ExampleHelper.cs/ExampleConsoleApp.cs
new file mode 100644
index 0000000..59e15f0
--- /dev/null
+++ b/ExampleHelper.cs/ExampleConsoleApp.cs
@@ -0,0 +1,104 @@
+using System;
+using System.Threading.Tasks;
+using Tapeti;
+
+namespace ExampleLib
+{
+ ///
+ /// Callback method for ExampleConsoleApp.Run
+ ///
+ /// A reference to the dependency resolver passed to the ExampleConsoleApp
+ /// Await this function to wait for the Done signal
+ public delegate Task AsyncFunc(IDependencyResolver dependencyResolver, Func waitForDone);
+
+
+ ///
+ /// Since the examples do not run as a service, we need to know when the example has run
+ /// to completion. This helper injects IExampleState into the container which
+ /// can be used to signal that it has finished. It also provides the Wait
+ /// method to wait for this signal.
+ ///
+ public class ExampleConsoleApp
+ {
+ private readonly IDependencyContainer dependencyResolver;
+ private readonly TaskCompletionSource doneSignal = new TaskCompletionSource();
+
+
+ ///
+ public ExampleConsoleApp(IDependencyContainer dependencyResolver)
+ {
+ this.dependencyResolver = dependencyResolver;
+ dependencyResolver.RegisterDefault(() => new ExampleState(this));
+ }
+
+
+ ///
+ /// Runs the specified async method and waits for completion. Handles exceptions and waiting
+ /// for user input when the example application finishes.
+ ///
+ ///
+ public void Run(AsyncFunc asyncFunc)
+ {
+ try
+ {
+ asyncFunc(dependencyResolver, WaitAsync).Wait();
+ }
+ catch (Exception e)
+ {
+ Console.WriteLine(UnwrapException(e));
+ }
+ finally
+ {
+ Console.WriteLine("Press any Enter key to continue...");
+ Console.ReadLine();
+ }
+ }
+
+
+ ///
+ /// Returns a Task which completed when IExampleState.Done is called
+ ///
+ public async Task WaitAsync()
+ {
+ await doneSignal.Task;
+ }
+
+
+ internal Exception UnwrapException(Exception e)
+ {
+ while (true)
+ {
+ if (!(e is AggregateException aggregateException))
+ return e;
+
+ if (aggregateException.InnerExceptions.Count != 1)
+ return e;
+
+ e = aggregateException.InnerExceptions[0];
+ }
+ }
+
+ internal void Done()
+ {
+ doneSignal.TrySetResult(true);
+ }
+
+
+ private class ExampleState : IExampleState
+ {
+ private readonly ExampleConsoleApp owner;
+
+
+ public ExampleState(ExampleConsoleApp owner)
+ {
+ this.owner = owner;
+ }
+
+
+ public void Done()
+ {
+ owner.Done();
+ }
+ }
+ }
+}
diff --git a/ExampleHelper.cs/ExampleLib.csproj b/ExampleHelper.cs/ExampleLib.csproj
new file mode 100644
index 0000000..f3cca6f
--- /dev/null
+++ b/ExampleHelper.cs/ExampleLib.csproj
@@ -0,0 +1,12 @@
+
+
+
+ netstandard2.0
+ true
+
+
+
+
+
+
+
diff --git a/ExampleHelper.cs/IExampleState.cs b/ExampleHelper.cs/IExampleState.cs
new file mode 100644
index 0000000..8f47b22
--- /dev/null
+++ b/ExampleHelper.cs/IExampleState.cs
@@ -0,0 +1,14 @@
+namespace ExampleLib
+{
+ ///
+ /// Since the examples do not run as a service, this interface provides a way
+ /// for the implementation to signal that it has finished and the example can be closed.
+ ///
+ public interface IExampleState
+ {
+ ///
+ /// Signals the Program that the example has finished and the application can be closed.
+ ///
+ void Done();
+ }
+}
diff --git a/Examples/01-PublishSubscribe/01-PublishSubscribe.csproj b/Examples/01-PublishSubscribe/01-PublishSubscribe.csproj
new file mode 100644
index 0000000..245b78a
--- /dev/null
+++ b/Examples/01-PublishSubscribe/01-PublishSubscribe.csproj
@@ -0,0 +1,20 @@
+
+
+
+ Exe
+ netcoreapp2.1
+ _01_PublishSubscribe
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Examples/01-PublishSubscribe/ExampleMessageController.cs b/Examples/01-PublishSubscribe/ExampleMessageController.cs
new file mode 100644
index 0000000..6cce0a1
--- /dev/null
+++ b/Examples/01-PublishSubscribe/ExampleMessageController.cs
@@ -0,0 +1,27 @@
+using System;
+using ExampleLib;
+using Messaging.TapetiExample;
+using Tapeti.Annotations;
+
+namespace _01_PublishSubscribe
+{
+ [MessageController]
+ [DynamicQueue("tapeti.example.01")]
+ public class ExampleMessageController
+ {
+ private readonly IExampleState exampleState;
+
+
+ public ExampleMessageController(IExampleState exampleState)
+ {
+ this.exampleState = exampleState;
+ }
+
+
+ public void HandlePublishSubscribeMessage(PublishSubscribeMessage message)
+ {
+ Console.WriteLine("Received message: " + message.Greeting);
+ exampleState.Done();
+ }
+ }
+}
diff --git a/Examples/01-PublishSubscribe/ExamplePublisher.cs b/Examples/01-PublishSubscribe/ExamplePublisher.cs
new file mode 100644
index 0000000..83347b7
--- /dev/null
+++ b/Examples/01-PublishSubscribe/ExamplePublisher.cs
@@ -0,0 +1,29 @@
+using System.Threading.Tasks;
+using Messaging.TapetiExample;
+using Tapeti;
+
+namespace _01_PublishSubscribe
+{
+ public class ExamplePublisher
+ {
+ private readonly IPublisher publisher;
+
+ ///
+ /// Shows that the IPublisher is registered in the container by Tapeti
+ ///
+ ///
+ public ExamplePublisher(IPublisher publisher)
+ {
+ this.publisher = publisher;
+ }
+
+
+ public async Task SendTestMessage()
+ {
+ await publisher.Publish(new PublishSubscribeMessage
+ {
+ Greeting = "Hello world of messaging!"
+ });
+ }
+ }
+}
diff --git a/Examples/01-PublishSubscribe/Program.cs b/Examples/01-PublishSubscribe/Program.cs
new file mode 100644
index 0000000..a010fc1
--- /dev/null
+++ b/Examples/01-PublishSubscribe/Program.cs
@@ -0,0 +1,64 @@
+using System;
+using System.Threading.Tasks;
+using ExampleLib;
+using SimpleInjector;
+using Tapeti;
+using Tapeti.Default;
+using Tapeti.SimpleInjector;
+
+namespace _01_PublishSubscribe
+{
+ public class Program
+ {
+ public static void Main(string[] args)
+ {
+ var container = new Container();
+ var dependencyResolver = new SimpleInjectorDependencyResolver(container);
+
+ container.Register();
+ container.Register();
+
+
+ // This helper is used because this example is not run as a service. You do not
+ // need it in your own applications.
+ var helper = new ExampleConsoleApp(dependencyResolver);
+ helper.Run(MainAsync);
+ }
+
+
+ internal static async Task MainAsync(IDependencyResolver dependencyResolver, Func waitForDone)
+ {
+ var config = new TapetiConfig(dependencyResolver)
+ .RegisterAllControllers()
+ .Build();
+
+ using (var connection = new TapetiConnection(config)
+ {
+ // Params is optional if you want to use the defaults, but we'll set it
+ // explicitly for this example
+ Params = new TapetiConnectionParams
+ {
+ HostName = "localhost",
+ Username = "guest",
+ Password = "guest"
+ }
+ })
+ {
+ // Create the queues and start consuming immediately.
+ // If you need to do some processing before processing messages, but after the
+ // queues have initialized, pass false as the startConsuming parameter and store
+ // the returned ISubscriber. Then call Resume on it later.
+ await connection.Subscribe();
+
+
+ // We could get an IPublisher from the container directly, but since you'll usually use
+ // it as an injected constructor parameter this shows
+ await dependencyResolver.Resolve().SendTestMessage();
+
+
+ // Wait for the controller to signal that the message has been received
+ await waitForDone();
+ }
+ }
+ }
+}
diff --git a/Messaging.TapetiExample/Messaging.TapetiExample.csproj b/Messaging.TapetiExample/Messaging.TapetiExample.csproj
new file mode 100644
index 0000000..56cdff2
--- /dev/null
+++ b/Messaging.TapetiExample/Messaging.TapetiExample.csproj
@@ -0,0 +1,11 @@
+
+
+
+ netstandard2.0
+
+
+
+
+
+
+
diff --git a/Messaging.TapetiExample/PublishSubscribeMessage.cs b/Messaging.TapetiExample/PublishSubscribeMessage.cs
new file mode 100644
index 0000000..0a14f37
--- /dev/null
+++ b/Messaging.TapetiExample/PublishSubscribeMessage.cs
@@ -0,0 +1,13 @@
+using System.ComponentModel.DataAnnotations;
+
+namespace Messaging.TapetiExample
+{
+ ///
+ /// Example of a simple broadcast message used in the standard publish - subscribe pattern
+ ///
+ public class PublishSubscribeMessage
+ {
+ [Required]
+ public string Greeting { get; set; }
+ }
+}
diff --git a/Tapeti.sln b/Tapeti.sln
index d870188..90289af 100644
--- a/Tapeti.sln
+++ b/Tapeti.sln
@@ -23,7 +23,17 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tapeti.Serilog", "Tapeti.Se
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tapeti.Transient", "Tapeti.Transient\Tapeti.Transient.csproj", "{A6355E63-19AB-47EA-91FA-49B5E9B41F88}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tapeti.DataAnnotations.Extensions", "Tapeti.DataAnnotations.Extensions\Tapeti.DataAnnotations.Extensions.csproj", "{1AAA5A2C-EAA8-4C49-96A6-673EA1EEE831}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tapeti.DataAnnotations.Extensions", "Tapeti.DataAnnotations.Extensions\Tapeti.DataAnnotations.Extensions.csproj", "{1AAA5A2C-EAA8-4C49-96A6-673EA1EEE831}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Examples", "Examples", "{266B9B94-A4D2-41C2-860C-24A7C3B63B56}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "01-PublishSubscribe", "Examples\01-PublishSubscribe\01-PublishSubscribe.csproj", "{8350A0AB-F0EE-48CF-9CA6-6019467101CF}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ExampleLib", "ExampleHelper.cs\ExampleLib.csproj", "{F3B38753-06B4-4932-84B4-A07692AD802D}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Messaging.TapetiExample", "Messaging.TapetiExample\Messaging.TapetiExample.csproj", "{D24120D4-50A2-44B6-A4EA-6ADAAEBABA84}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "02-DeclareDurableQueues", "02-DeclareDurableQueues\02-DeclareDurableQueues.csproj", "{85511282-EF91-4B56-B7DC-9E8706556D6E}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -75,10 +85,32 @@ Global
{1AAA5A2C-EAA8-4C49-96A6-673EA1EEE831}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1AAA5A2C-EAA8-4C49-96A6-673EA1EEE831}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1AAA5A2C-EAA8-4C49-96A6-673EA1EEE831}.Release|Any CPU.Build.0 = Release|Any CPU
+ {8350A0AB-F0EE-48CF-9CA6-6019467101CF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {8350A0AB-F0EE-48CF-9CA6-6019467101CF}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {8350A0AB-F0EE-48CF-9CA6-6019467101CF}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {8350A0AB-F0EE-48CF-9CA6-6019467101CF}.Release|Any CPU.Build.0 = Release|Any CPU
+ {F3B38753-06B4-4932-84B4-A07692AD802D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {F3B38753-06B4-4932-84B4-A07692AD802D}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {F3B38753-06B4-4932-84B4-A07692AD802D}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {F3B38753-06B4-4932-84B4-A07692AD802D}.Release|Any CPU.Build.0 = Release|Any CPU
+ {D24120D4-50A2-44B6-A4EA-6ADAAEBABA84}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {D24120D4-50A2-44B6-A4EA-6ADAAEBABA84}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {D24120D4-50A2-44B6-A4EA-6ADAAEBABA84}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {D24120D4-50A2-44B6-A4EA-6ADAAEBABA84}.Release|Any CPU.Build.0 = Release|Any CPU
+ {85511282-EF91-4B56-B7DC-9E8706556D6E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {85511282-EF91-4B56-B7DC-9E8706556D6E}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {85511282-EF91-4B56-B7DC-9E8706556D6E}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {85511282-EF91-4B56-B7DC-9E8706556D6E}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
+ GlobalSection(NestedProjects) = preSolution
+ {8350A0AB-F0EE-48CF-9CA6-6019467101CF} = {266B9B94-A4D2-41C2-860C-24A7C3B63B56}
+ {F3B38753-06B4-4932-84B4-A07692AD802D} = {266B9B94-A4D2-41C2-860C-24A7C3B63B56}
+ {D24120D4-50A2-44B6-A4EA-6ADAAEBABA84} = {266B9B94-A4D2-41C2-860C-24A7C3B63B56}
+ {85511282-EF91-4B56-B7DC-9E8706556D6E} = {266B9B94-A4D2-41C2-860C-24A7C3B63B56}
+ EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {B09CC2BF-B2AF-4CB6-8728-5D1D8E5C50FA}
EndGlobalSection