tl;dr - There is a complete example here Examples/HelloWorldNetCore
Prerequisites
This walk through will be done using .NET Core 2.1, if you’d rather an example in the full .NET Framework take a look here Hello World - .Net Framework
We will also need access to an azure subscription in which we are able to create service bus namespaces and topics. If you’re not sure how to create an azure service bus, take a look at Create a Service Bus namespace using the Azure portal.
Objective
Our Hello World sample will have two applications
- The publisher - this will publish the events to the Topic
- The subscriber.
The two applications will have shared knowledge of an event via a third project.
Setup the solution
We need to create a new solution and 3 projects.
The first should be a class library called
Contract
, the other two should be console applications called Publisher
and Subscriber
. Both
Publisher
and Subscriber
should reference the Contract
project.
You can do this in Visual Studio or follow the command line steps below.
Configure Solution via CLI
First create the projects:
dotnet new classlib -o Contracts
dotnet new console -o Publisher
dotnet new console -o Subscriber
Publisher
and Subscriber
projects both need to add a reference to Contracts
:
dotnet add Publisher/Publisher.csproj reference Contracts/Contracts.csproj
dotnet add Subscriber/Subscriber.csproj reference Contracts/Contracts.csproj
Next, create the solution and add references to the projects:
dotnet new sln -n HelloWorldNetCore
dotnet sln add **/*.csproj
Now open the solution in your IDE/Edititor of choice.
Create the contract
Delete Class1.cs
from the Contract
project.
Add a new class to the Contract
project, call it Foo
, for now this doesn’t need any properties. For a
real event we would have something a bit more meaningful.
Building the Subscriber
Now that we have the skeleton projects in place we need to build a subscriber. This is the more complex of the two applications we need to build.
We need to install the Pat.Sender Nuget packages. Fortunately we just need to install the latest versions of
Pat.Subscriber.NetCoreDependencyResolution
which holds a reference to the latest Pat.Subscriber
.
We can do this by running following command from the Subscriber
directory:
dotnet add package Pat.Subscriber.NetCoreDependencyResolution
To handle a message in a Pat subscriber a class must implement the IHandleEvent<T>
interface. The interface
has a single method with the signature Task HandleAsync(Foo @event)
. The simplest implementation of an event
handler for our Foo
contract is:
public class FooHandler : IHandleEvent<Foo>
{
public async Task HandleAsync(Foo @event)
{
await Task.CompletedTask;
}
}
A handler is designed to require as little knowledge of the service bus infrastructure as is possible. The key
thing to know at this point is that if the HandleAsync
method completes successfully then the message is
marked as Complete,
if it throws an exception then the message is simply dropped by Pat. The azure service bus will hold that
message until the peek lock expires and the message will be received again by Pat. This will repeat until
the max delivery count has exceeded, at which point the message is moved onto the dead letter queue.
To show that our handler is receiving messages let’s add some logging. Pat already has a dependency on .NET Core Logging so we don’t need to install (Microsoft.Extensions.Logging
).
Add a new constructor with a parameter ILogger<FooHandler> log
to our handler, assign that to an instance variable called _log
. Now update our HandleAsync
method to the following.
public Task HandleAsync(Foo @event)
{
_log.LogInformation("Handling: {event}", @event);
await Task.CompletedTask;
}
Configuring the Subscribers Dependency Resolution
Now that we have a fully functional handler in place, we need to hook up the infrastructure to support it.
Note: Setting up a new subscriber without dependency resolution is entirely possible, but not recommended.
In Program.cs
create a new method private static ServiceProvider InitialiseIoC()
in this we need to create the dependency configuration for our subscriber. For this example we can use:
var subscriberConfiguration = new SubscriberConfiguration
{
ConnectionStrings = new[]
{
// Use your own service bus connection string here
"Endpoint=sb://namespace.servicebus.windows.net/;SharedAccessKeyName=RootManageSharedAccessKey;SharedAccessKey=YOURKEY"
},
TopicName = "pat",
SubscriberName = "PatExampleSubscriber",
UseDevelopmentTopic = false
};
For our service collection Pat provides the dependency resolution helper .AddPatLite
this sets some
sensible defaults for us. The simplest setup available setup is:
var serviceProvider = new ServiceCollection()
.AddPatLite(subscriberConfiguration)
.AddLogging(b => b.AddConsole())
.AddTransient<IStatisticsReporter, StatisticsReporter>()
.AddSingleton(new StatisticsReporterConfiguration())
.AddHandlersFromAssemblyContainingType<FooHandler>()
.BuildServiceProvider();
return serviceProvider;
You will need to add following nuget packages to resolve the logging dependencies:
dotnet add package Microsoft.Extensions.Logging.Console
The two lines for the StatisticsReporter
are part of Pat’s telemetry reporting and these defaults won’t be
required in the future. Details on the statistics reporter are provided in the telemetry
documentation.
The .AddPatLite
helper method configures the service collection for the Pat subscriber and its dependencies,
the .AddHandlersFromAssemblyContainingType
tells Pat where to find the handlers in our app. If we have
handlers split across multiple projects we’ll need to call this method multiple times.
Configuring Logging
Pat uses .NET Core Logging for its internal logging, to help visualize what’s happening it’s useful to add a console provider.
In the above IoC setup, the .AddLogging(b => b.AddConsole())
line is configuring .NET Core Logging to provide a Console log.
Bringing it all together.
First we need to convert our Main method’s signature to async Task Main()
, for this we’ll need to enable C#
7.1. This can be done by adding <LangVersion>7.1</LangVersion>
to the <PropertyGroup>
section in our
subscriber’s csproj file.
The main method needs to perform 3 actions:
- Initialise the dependency resolution
- Provide a way to shut down Pat
- Start Pat.
Initialising the dependency resolution is done by calling InitialiseIoC
. This is done by adding the
following to our main method.
var serviceProvider = InitialiseIoC();
For a console app a nice way to enable shutdown is to listen for Ctrl+C and then gracefully shutdown. Since Pat might be processing a message we will trigger the cancellation of a continuation token and allow Pat to shutdown when it’s ready. Pat can take up to a minute to shutdown, but if it’s waiting on our handler this may take longer. This is done by adding the following to our main method:
var tokenSource = new CancellationTokenSource();
Console.CancelKeyPress += (sender, args) =>
{
var log = serviceProvider.GetService<ILogger<Program>>();
log.LogInformation("Subscriber Shutdown Requested");
args.Cancel = true;
tokenSource.Cancel();
};
Starting Pat is a case of creating an instance of the Pat Subscriber, initialising it and then listening for messages. This is done by adding the following to our main method:
var subscriber = serviceProvider.GetService<Pat.Subscriber.Subscriber>();
await subscriber.Initialise(new[] {Assembly.GetExecutingAssembly()});
await subscriber.ListenForMessages(tokenSource);
Next we need to create the topic.
Creating the Subscription
Now that our subscriber is complete we need to create a subscription on our service bus. This can be done manually or via a tool. The pat global tool does this for us. The tool requires that minimum of .Net Core 2.1.300 is installed.
To install the Pat tooling run:
dotnet tool install -g Pat.Subscriber.Tools
Then in our prompt run (note some prompts need reopening before pat
is available):
pat create -n namespace -s PatExampleSubscriber -t pat
We need to replace namespace
with the service bus namespace from our connection string.
Building the Publisher
Publishing a message with Pat is somewhat simpler than subscribing to messages.
We need to start by installing the latest versions of
- Pat.Sender.NetCoreLogAdapter
- Microsoft.Extensions.Logging.Console
- Microsoft.Extensions.DependencyInjection
Navigate to the Publisher project and run:
dotnet add package Pat.Sender.NetCoreLog
dotnet add package Microsoft.Extensions.Logging.Console
dotnet add package Microsoft.Extensions.DependencyInjection
Note:
Pat.Sender.NetCoreLogAdapter
brings in the dependency Pat.Sender meaning we don’t have to explicitly add this package.
Configuring the Publishers Dependency Resolution
Note: Setting up a new publisher without dependency resolution is entirely possible, but not recommended.
In program.cs
create a
new method private static ServiceProvider InitialiseIoC()
in this we need to create the configuration for
our publisher. For this example we can use:
var sender = new PatSenderSettings
{
TopicName = "pat",
// Use your own service bus connection string here
PrimaryConnection = ""Endpoint=sb://namespace.servicebus.windows.net/;SharedAccessKeyName=RootManageSharedAccessKey;SharedAccessKey=YOURKEY";",
UseDevelopmentTopic = false
};
For our service collection Pat requires a few classes to be configured. The IMessagePublisher
is the
interface which we will use in our application. The concrete implementation of this requires a
MessageSender, a MessageGenerator,
MessageProperties and correlation
ids. The details of which are explained on their specific
documentation pages. The simplest setup available is below:
var serviceProvider = new ServiceCollection()
.AddLogging(b => b.AddConsole())
.AddPatSender(settings)
.BuildServiceProvider();
return serviceProvider;
Then add helper function:
private static IServiceCollection AddPatSender(this IServiceCollection services, PatSenderSettings settings)
=> services
.AddPatSenderNetCoreLogAdapter()
.AddSingleton(settings)
.AddTransient<IMessagePublisher, MessagePublisher>()
.AddTransient<IMessageSender, MessageSender>()
.AddTransient<IMessageGenerator, MessageGenerator>()
.AddTransient(s => new MessageProperties(new LiteralCorrelationIdProvider($"{Guid.NewGuid()}")));
Using .NET Core Logging in the Publisher
Because Pat.Sender supports multiple logging frameworks, we use the NetCore Log Adapter (as shown above).
For more information on logging in dotnet core see https://docs.microsoft.com/en-us/aspnet/core/fundamentals/logging/?view=aspnetcore-2.1.
See our Hello World - Full Framework example for example using Log4Net
Bringing it all together
First we need to convert our Main method’s signature to static async Task Main()
, for this we’ll need to enable C#
7.1. This can be done by adding <LangVersion>7.1</LangVersion>
to the <PropertyGroup>
section in our
subscriber’s csproj file.
The main method needs to perform 2 actions:
- Initialise the dependency resolution
- Publish an event
Initialising the dependency resolution is a case of calling InitialiseIoC
. This is done by adding the
following to our main method.
var serviceProvider = InitialiseIoC();
Publishing a message is done by getting an instance of IMessagePublisher
and calling its PublishEvent
method.
var publisher = serviceProvider.GetService<IMessagePublisher>();
await publisher.PublishEvent(new Foo());
Running Our Example
Start by running the subscriber, the first time this starts up it will ensure that the filter on the subscription which it is subscribing to matches the messages it can handle.
Our output should look something like this:
info: Pat.Subscriber.SubscriptionBuilder[0]
Building subscription 1 on service bus namespace.servicebus.windows.net/...
info: Pat.Subscriber.SubscriptionBuilder[0]
Validating subscription 'PatExampleSubscriber' rules on topic 'pat'...
info: Pat.Subscriber.SubscriberRules.RuleApplier[0]
Creating rule 1_v_1_0_0 for subscriber PatExampleSubscriber
info: Pat.Subscriber.SubscriberRules.RuleApplier[0]
Deleting rule $Default for subscriber PatExampleSubscriber, as it has been superceded by a newer version
info: Pat.Subscriber.AzureServiceBusMessageReceiverFactory[0]
Adding on subscription client 1 to list of source subscriptions
info: Pat.Subscriber.Subscriber[0]
Listening for messages...
Hitting Ctrl+C results in
info: Subscriber.Program[0]
Subscriber Shutdown Requested
Followed by the subscriber shutting down, this may take up to 60 seconds.
We can now run the publisher, this will publish our event and exit with nothing displayed on the console.
Running the subscriber again results in:
info: Pat.Subscriber.SubscriptionBuilder[0]
Building subscription 1 on service bus mailmachinegun-ns.servicebus.windows.net/...
info: Pat.Subscriber.SubscriptionBuilder[0]
Validating subscription 'PatExampleSubscriber' rules on topic 'patDESKTOP-N2B8ALM'...
info: Pat.Subscriber.AzureServiceBusMessageReceiverFactory[0]
Adding on subscription client 1 to list of source subscriptions
info: Pat.Subscriber.Subscriber[0]
Listening for messages...
info: Subscriber.FooHandler[0]
Handling: Contracts.Foo
From this we can see that the subscriber has successfully received and processed our message. Yeay!
Next Steps
Now we have a working publisher and subscriber. It’s worth reading through the other topics on the getting started guide to help you build out a production ready subscriber.
Troubleshooting
Common issues and trouble shooting guides are over on the troubleshooting page