[skip appveyor] #9 Documentation and examples

Implemented two examples. More needed to get rid of the mess that is the current "Test" project.
This commit is contained in:
Mark van Renswoude 2019-08-16 10:51:35 +02:00
parent 93fa25c163
commit 8e0edabeed
13 changed files with 422 additions and 1 deletions

View File

@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.1</TargetFramework>
<RootNamespace>_02_DeclareDurableQueues</RootNamespace>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="SimpleInjector" Version="4.6.2" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\ExampleHelper.cs\ExampleLib.csproj" />
<ProjectReference Include="..\Messaging.TapetiExample\Messaging.TapetiExample.csproj" />
<ProjectReference Include="..\Tapeti.SimpleInjector\Tapeti.SimpleInjector.csproj" />
</ItemGroup>
</Project>

View File

@ -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();
}
}
}

View File

@ -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<ILogger, ConsoleLogger>();
var helper = new ExampleConsoleApp(dependencyResolver);
helper.Run(MainAsync);
}
internal static async Task MainAsync(IDependencyResolver dependencyResolver, Func<Task> 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<IPublisher>().Publish(new PublishSubscribeMessage
{
Greeting = "Hello durable queue!"
});
// Wait for the controller to signal that the message has been received
await waitForDone();
}
}
}
}

View File

@ -0,0 +1,104 @@
using System;
using System.Threading.Tasks;
using Tapeti;
namespace ExampleLib
{
/// <summary>
/// Callback method for ExampleConsoleApp.Run
/// </summary>
/// <param name="dependencyResolver">A reference to the dependency resolver passed to the ExampleConsoleApp</param>
/// <param name="waitForDone">Await this function to wait for the Done signal</param>
public delegate Task AsyncFunc(IDependencyResolver dependencyResolver, Func<Task> waitForDone);
/// <summary>
/// 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.
/// </summary>
public class ExampleConsoleApp
{
private readonly IDependencyContainer dependencyResolver;
private readonly TaskCompletionSource<bool> doneSignal = new TaskCompletionSource<bool>();
/// <inheritdoc />
public ExampleConsoleApp(IDependencyContainer dependencyResolver)
{
this.dependencyResolver = dependencyResolver;
dependencyResolver.RegisterDefault<IExampleState>(() => new ExampleState(this));
}
/// <summary>
/// Runs the specified async method and waits for completion. Handles exceptions and waiting
/// for user input when the example application finishes.
/// </summary>
/// <param name="asyncFunc"></param>
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();
}
}
/// <summary>
/// Returns a Task which completed when IExampleState.Done is called
/// </summary>
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();
}
}
}
}

View File

@ -0,0 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Tapeti\Tapeti.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,14 @@
namespace ExampleLib
{
/// <summary>
/// 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.
/// </summary>
public interface IExampleState
{
/// <summary>
/// Signals the Program that the example has finished and the application can be closed.
/// </summary>
void Done();
}
}

View File

@ -0,0 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.1</TargetFramework>
<RootNamespace>_01_PublishSubscribe</RootNamespace>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="SimpleInjector" Version="4.6.2" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\ExampleHelper.cs\ExampleLib.csproj" />
<ProjectReference Include="..\..\Messaging.TapetiExample\Messaging.TapetiExample.csproj" />
<ProjectReference Include="..\..\Tapeti.SimpleInjector\Tapeti.SimpleInjector.csproj" />
<ProjectReference Include="..\..\Tapeti\Tapeti.csproj" />
</ItemGroup>
</Project>

View File

@ -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();
}
}
}

View File

@ -0,0 +1,29 @@
using System.Threading.Tasks;
using Messaging.TapetiExample;
using Tapeti;
namespace _01_PublishSubscribe
{
public class ExamplePublisher
{
private readonly IPublisher publisher;
/// <summary>
/// Shows that the IPublisher is registered in the container by Tapeti
/// </summary>
/// <param name="publisher"></param>
public ExamplePublisher(IPublisher publisher)
{
this.publisher = publisher;
}
public async Task SendTestMessage()
{
await publisher.Publish(new PublishSubscribeMessage
{
Greeting = "Hello world of messaging!"
});
}
}
}

View File

@ -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<ILogger, ConsoleLogger>();
container.Register<ExamplePublisher>();
// 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<Task> 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<ExamplePublisher>().SendTestMessage();
// Wait for the controller to signal that the message has been received
await waitForDone();
}
}
}
}

View File

@ -0,0 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="System.ComponentModel.Annotations" Version="4.5.0" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,13 @@
using System.ComponentModel.DataAnnotations;
namespace Messaging.TapetiExample
{
/// <summary>
/// Example of a simple broadcast message used in the standard publish - subscribe pattern
/// </summary>
public class PublishSubscribeMessage
{
[Required]
public string Greeting { get; set; }
}
}

View File

@ -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