Home

Awesome

Saunter

CI NuGet Badge

Saunter is an AsyncAPI documentation generator for dotnet.

ℹ Note that pre version 1.0.0, the API is regarded as unstable and breaking changes may be introduced.

Getting Started

See examples/StreetlightsAPI.

  1. Install the Saunter package

    dotnet add package Saunter
    
  2. In the ConfigureServices method of Startup.cs, configure Saunter.

    // Add Saunter to the application services. 
    services.AddAsyncApiSchemaGeneration(options =>
    {
        // Specify example type(s) from assemblies to scan.
        options.AssemblyMarkerTypes = new[] { typeof(StreetlightMessageBus) };
        
        // Build as much (or as little) of the AsyncApi document as you like.
        // Saunter will generate Channels, Operations, Messages, etc, but you
        // may want to specify Info here.
        options.Middleware.UiTitle = "Streetlights API";
        options.AsyncApi = new AsyncApiDocument
        {
            Info = new AsyncApiInfo()
            {
                Title = "Streetlights API",
                Version = "1.0.0",
                Description = "The Smartylighting Streetlights API allows you to remotely manage the city lights.",
                License = new AsyncApiLicense()
                {
                    Name = "Apache 2.0",
                    Url = new("https://www.apache.org/licenses/LICENSE-2.0"),
                }
            },
            Servers =
            {
                ["mosquitto"] = new AsyncApiServer(){ Url = "test.mosquitto.org",  Protocol = "mqtt"},
                ["webapi"] = new AsyncApiServer(){ Url = "localhost:5000",  Protocol = "http"},
            },
        };
    });
    
  3. Add attributes to your classes which publish or subscribe to messages.

    [AsyncApi] // Tells Saunter to scan this class.
    public class StreetlightMessageBus : IStreetlightMessageBus
    {
        private const string SubscribeLightMeasuredTopic = "subscribe/light/measured";
        
        [Channel(SubscribeLightMeasuredTopic, Servers = new[] { "mosquitto" })]
        [SubscribeOperation(typeof(LightMeasuredEvent), "Light", Summary = "Subscribe to environmental lighting conditions for a particular streetlight.")]
         public void PublishLightMeasuredEvent(Streetlight streetlight, int lumens) {}
    
  4. Add saunter middleware to host the AsyncApi json document. In the Configure method of Startup.cs:

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapAsyncApiDocuments();
        endpoints.MapAsyncApiUi();
    
        endpoints.MapControllers();
    });
    
  5. Use the published AsyncApi document:

    // HTTP GET /asyncapi/asyncapi.json
    {
        // Properties from Startup.cs
        "asyncapi": "2.1.0",
        "info": {
            "title": "Streetlights API",
            "version": "1.0.0",
            "description": "The Smartylighting Streetlights API allows you\nto remotely manage the city lights.",
           // ...
        },
        // Properties generated from Attributes
        "channels": {
            "light/measured": {
            "publish": {
                "operationId": "PublishLightMeasuredEvent",
                "summary": "Inform about environmental lighting conditions for a particular streetlight.",
            //...
    }
    
  6. Use the published AsyncAPI UI:

    AsyncAPI UI

Configuration

See the options source code for detailed info.

Common options are below:

services.AddAsyncApiSchemaGeneration(options =>
{
    options.AssemblyMarkerTypes = new[] { typeof(Startup) };   // Tell Saunter where to scan for your classes.
    
    options.AddChannelFilter<MyAsyncApiChannelFilter>();       // Dynamically update ChanelItems
    options.AddOperationFilter<MyOperationFilter>();           // Dynamically update Operations
    
    options.Middleware.Route = "/asyncapi/asyncapi.json";      // AsyncAPI JSON document URL
    options.Middleware.UiBaseRoute = "/asyncapi/ui/";          // AsyncAPI UI URL
    options.Middleware.UiTitle = "My AsyncAPI Documentation";  // AsyncAPI UI page title
}

Bindings

Bindings are used to describe protocol specific information. These can be added to the AsyncAPI document and then applied to different components by setting the BindingsRef property in the relevant attributes [OperationAttribute], [MessageAttribute], [ChannelAttribute]

// Startup.cs
services.AddAsyncApiSchemaGeneration(options =>
{
    options.AsyncApi = new AsyncApiDocument
    {
        Components = 
        {
            ChannelBindings =
            {
                ["amqpDev"] = new()
                {
                    new AMQPChannelBinding
                    {
                        Is = ChannelType.Queue,
                        Exchange = new()
                        {
                            Name = "example-exchange",
                            Vhost = "/development"
                        }
                    }
                }
            },
            OperationBindings =
            {
                {
                    "postBind",
                    new()
                    {
                        new HttpOperationBinding
                        {
                            Method = "POST",
                            Type = HttpOperationType.Response,
                        }
                    }
                }
            }
        }
    }
});
[Channel("light.measured", BindingsRef = "amqpDev")] // Set the BindingsRef property
public void PublishLightMeasuredEvent(Streetlight streetlight, int lumens) {}
[PublishOperation(typeof(LightMeasuredEvent), "Light", BindingsRef = "postBind")]
public void MeasureLight([FromBody] LightMeasuredEvent lightMeasuredEvent)

Available bindings: https://www.nuget.org/packages/AsyncAPI.NET.Bindings/

Multiple AsyncAPI documents

You can generate multiple AsyncAPI documents by using the ConfigureNamedAsyncApi extension method.

// Startup.cs

// Add Saunter to the application services. 
services.AddAsyncApiSchemaGeneration(options =>
{
    // Specify example type(s) from assemblies to scan.
    options.AssemblyMarkerTypes = new[] {typeof(FooMessageBus)};
}

// Configure one or more named AsyncAPI documents
services.ConfigureNamedAsyncApi("Foo", asyncApi => 
{
    asyncApi.Info = new Info("Foo API", "1.0.0");
    // ...
});

services.ConfigureNamedAsyncApi("Bar", asyncApi => 
{
    asyncApi.Info = new Info("Bar API", "1.0.0");
    // ...
});

Classes need to be decorated with the AsyncApiAttribute specifying the name of the AsyncAPI document.

[AsyncApi("Foo")]
public class FooMessageBus 
{
    // Any channels defined in this class will be added to the "Foo" document
}


[AsyncApi("Bar")]
public class BarMessageBus 
{
    // Any channels defined in this class will be added to the "Bar" document
}

Each document can be accessed by specifying the name in the URL

// GET /asyncapi/foo/asyncapi.json
{
    "info": {
        "title": "Foo API"
    }
}

// GET /asyncapi/bar/asyncapi.json
{
    "info": {
        "title": "Bar API"
    }
}

Migration to LEGO AsyncApi.Net

When switching to the LEGO AsyncApi.Net, we broke the public API.

To simplify the transition to new versions of the library, this note was created.

What was broken:

There was no more significant changes on public API.

Keep this in mind when planning the migration process.

Contributing

See our contributing guide.

Feel free to get involved in the project by opening issues, or submitting pull requests.

You can also find me on the AsyncAPI community slack.

Thanks