Getting Started
Architecture
NServiceBus
Transports
Persistence
ServiceInsight
ServicePulse
ServiceControl
Monitoring
Modernization
Samples

AsyncAPI with custom message conventions

This sample demonstrates how to generate an AsyncAPI schema document from NServiceBus endpoints using Neuroglia.AsyncApi in two different hosting environments:

It extends the simple AsyncAPI sample by differentiating between event types (i.e. published vs subscribed) and showing how an event can be published by one endpoint and subscribed to by another without using the same concrete class to define the event, therefore decoupling the systems from each other.

Code walk-through

This sample contains four projects:

  • AsyncAPI.Feature - a class library containing shared code required to enable the custom AsyncApiFeature
  • AsyncAPI.Web - a ASP.NET Core WebAPI application that publishes two events and generates an AsyncAPI document schema for its structure, accessible via a URL
  • AsyncAPI.GenericHost - a console application that publishes two events, sends a message and generates an AsyncAPI document schema for its structure and saves it to disk
  • Subscriber - a console application that subscribes to the events published by AsyncAPI.GenericHost and AsyncAPI.Web

Feature project

The project contains all necessary code to support decoupling event types from the publisher and subscriber (contained in the MessageConventions folder), as well as registering a custom AsyncAPI document schema generator.

The EndpointConfigurationExtensions contains the glue that registers the AsyncApiFeature and the endpoint configuration message conventions.

public static void EnableAsyncApiSupport(
    this EndpointConfiguration endpointConfiguration)
{
    endpointConfiguration.DisableFeature<AutoSubscribe>();
    endpointConfiguration.EnableFeature<AsyncApiFeature>();

    var conventions = endpointConfiguration.Conventions();
    conventions.Add(new PublishedEventsConvention());
    conventions.Add(new SubscribedEventsConvention());
}

AsyncAPI feature

The feature creates mappings for physical to logical event types based on the PublishedEvent and SubscribedEvent attributes that decorate the events that are published and subscribed to. These mappings are registered in the container so that they are available for the following pipeline behaviors defined in the feature:

  • ReplaceOutgoingEnclosedMessageTypeHeaderBehavior
  • ReplaceIncomingEnclosedMessageTypeHeaderBehavior
  • ReplaceMulticastRoutingBehavior
context.Services.AddSingleton(new TypeCache
{
    EndpointName = context.Settings.EndpointName(),
    PublishedEventCache = publishedEventCache,
    SubscribedEventCache = subscribedEventCache
});

Finally, the code registers a custom implementation of the Neuroglia IAsyncApiDocumentGenerator which will be used instead of the default implementation to generate the NServiceBus-specific schema document.

context.Services.AddTransient<IAsyncApiDocumentGenerator>(
    provider => new ApiDocumentGenerator(provider));
AsyncAPI document generator

This custom implementation of the Neuroglia IAsyncApiDocumentGenerator creates one AsyncAPI schema document for the NServiceBus endpoint hosted in the application. It demonstrates how channel information with the endpoint's address (queue name) can be generated, containing the publish operation and the event payload.

This code can be extended to include subscriptions as well as sent/received messages.

WebAPI project

Setup AsyncAPI

The project enables AsyncAPI schema generation using two setup calls.

First, by adding the Neuroglia AsyncAPI.

// Configures code-first AsyncAPI document generation.
builder.Services.AddAsyncApiGeneration(asyncApiBuilder =>
    asyncApiBuilder
        .UseDefaultV3DocumentConfiguration(asyncApi =>
        {
            //Setup V3 documents, by configuring servers, for example:
            asyncApi.WithTitle("Web Service");
            asyncApi.WithVersion("1.0.0");
            asyncApi.WithLicense(
                "Apache 2.0",
                new Uri("https://www.apache.org/licenses/LICENSE-2.0"));

            asyncApi.WithServer("amqp", setup =>
            {
                setup
                    .WithProtocol(AsyncApiProtocol.Amqp)
                    .WithHost("sb://example.servicebus.windows.net/")
                    .WithBinding(new AmqpServerBindingDefinition
                    {
                        BindingVersion = "0.1.0",
                    });
            });
        }));

// Adds AsyncAPI UI services. Available at /asyncapi.
builder.Services.AddAsyncApiUI();

Second, by registering the AsyncAPI feature with the NServiceBus endpoint.

endpointConfiguration.EnableAsyncApiSupport();

Define published events

The published events are defined by using the PublishedEvent attribute to tell NServiceBus which event they represent.

The FirstEventThatIsBeingPublished class is marked as representing the SomeNamespace.FirstEvent event.

[PublishedEvent(EventName = "SomeNamespace.FirstEvent", Version = 1)]
public class FirstEventThatIsBeingPublished
{
    public string SomeValue { get; init; }
    public string SomeOtherValue { get; init; }
}

The SecondEventThatIsBeingPublished class is marked as representing the SomeNamespace.SecondEvent event.

[PublishedEvent(EventName = "SomeNamespace.SecondEvent", Version = 1)]
public class SecondEventThatIsBeingPublished
{
    public string SomeValue { get; init; }
    public string SomeOtherValue { get; init; }
}

Access AsyncAPI schema document

The resulting AsyncAPI schema document can be accessed under /asyncapi. From here it can be downloaded and inspected using AsyncAPI Studio (by pasting in the contents) and incorporated into internal system documentation.

Generic host project

Setup AsyncAPI

The project enables AsyncAPI schema generation using three setup calls.

First, by adding the Neuroglia AsyncAPI.

builder.Services.AddAsyncApi();

Second, by registering the AsyncAPI feature with the NServiceBus endpoint.

endpointConfiguration.EnableAsyncApiSupport();

Lastly, by adding a background service to generate and write the AsyncAPI document schema to disk.

builder.Services.AddHostedService<AsyncAPISchemaWriter>();

Define published events

The published events are defined by using the PublishedEvent attribute to tell NServiceBus which event they represent.

The FirstEventThatIsBeingPublished class is marked as representing the SomeNamespace.FirstEvent event.

[PublishedEvent(EventName = "SomeNamespace.FirstEvent", Version = 1)]
public class FirstEventThatIsBeingPublished
{
    public string SomeValue { get; init; }
    public string SomeOtherValue { get; init; }
}

The SecondEventThatIsBeingPublished class is marked as representing the SomeNamespace.SecondEvent event.

[PublishedEvent(EventName = "SomeNamespace.SecondEvent", Version = 1)]
public class SecondEventThatIsBeingPublished
{
    public string SomeValue { get; init; }
    public string SomeOtherValue { get; init; }
}

One message (MessageBeingSent) is also sent and received locally, demonstrating that standard NServiceBus message processing can run alongside custom Publish/Subscribe translations.

Access AsyncAPI schema document

The AsyncAPISchemaWriter uses the custom document generator injected as part of the AsyncApiFeature to generate the document schema and write it to disk.

var documents = await apiDocumentGenerator.GenerateAsync(
    markupTypes: null, options, cancellationToken);

if (documents is not null && !documents.Any())
{
    logger.LogInformation("No documents generated.");
}
else
{
    logger.LogInformation("Found #{Count} generated document(s).", documents.Count());
    foreach (var document in documents)
    {
        using MemoryStream stream = new();

        await asyncApiDocumentWriter.WriteAsync(
            document, stream, AsyncApiDocumentFormat.Json, cancellationToken);

        var schemaFile = Path.Combine(
            Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),
            "downloads",
            $"{document.Title}.json");

        File.WriteAllBytes(schemaFile, stream.ToArray());
    }
}

The file is saved into the default Downloads folder - it can be viewed using AsyncAPI Studio (by pasting in the contents) and incorporated into internal system documentation.

Subscriber project

Setup AsyncAPI

The project uses the EnableAsyncApiSupport extension method to allow it to subscribe to published events from the WebAPI and Generic host projects using its own implementation of the event classes.

endpointConfiguration.EnableAsyncApiSupport();

Define subscribed to events

It defines its own concrete event classes and uses the SubscribedEvent attribute to tell NServiceBus which event they represent.

The FirstSubscribedToEvent class is marked as representing the SomeNamespace.FirstEvent event.

[SubscribedEvent(EventName = "SomeNamespace.FirstEvent", Version = 1)]
public class FirstSubscribedToEvent
{
    public string SomeOtherValue { get; init; }
}

The SecondSubscribedToEvent class is marked as representing the SomeNamespace.SecondEvent event.

[SubscribedEvent(EventName = "SomeNamespace.SecondEvent", Version = 1)]
public class SecondSubscribedToEvent
{
    public string SomeValue { get; init; }
}

Note that the defined events do not need to match all the properties defined in the published events, but contain only those that they are interested in.

Running the sample

When running the solution with Visual Studio, three applications will start automatically.

AsyncAPI.GenericHost (console)

  • Press s to send a message to itself.
  • Press 1 to publish FirstEventThatIsBeingPublished - received by AsyncAPI.Subscriber.
  • Press 2 to publish SecondEventThatIsBeingPublished - received by AsyncAPI.Subscriber.

AsyncAPI.Web (web)

  • Open /scalar to publish both events - received by AsyncAPI.Subscriber.
  • Open /asyncapi to view the AsyncAPI schema for the application. This can be exported as JSON or YAML.

AsyncAPI.Subscriber (console)

Displays all events published from AsyncAPI.GenericHost and AsyncAPI.Web.

Samples

  • Simple AsyncAPI
    How to generate an AsyncAPI schema document from NServiceBus.