A .NET used to visualize and interact with AsyncAPI documents. The UI is built using Razor Pages and Boostrap
dotnet add package Neuroglia.AsyncApi.Core
Fluent validation library
dotnet add package Neuroglia.AsyncApi.Validation
dotnet add package Neuroglia.AsyncApi.FluentBuilders
dotnet add package Neuroglia.AsyncApi.Validation
Code-first generation library
dotnet add package Neuroglia.AsyncApi.Generation
Dependency inject extensions library
dotnet add package Neuroglia.AsyncApi.DependencyInjectionExtensions
Cloud event extensions library
dotnet add package Neuroglia.AsyncApi.CloudEvents
AsyncAPI document serving library
dotnet add package Neuroglia.AsyncApi.AspNetCore
Attention, please note that projects serving the UI MUST use the Microsoft.NET.Sdk.Web
dotnet add package Neuroglia.AsyncApi.AspNetCore.UI
Attention, please note that projects serving the UI MUST use the Microsoft.NET.Sdk.Web
dotnet add package Neuroglia.AsyncApi.Client
Building an AsyncAPI document
var services = new ServiceCollection();
services.AddAsyncApi();
var serviceProvider = services.BuildServiceProvider();
var builder = serviceProvider.GetRequiredService<IAsyncApiDocumentBuilder>();
var document = builder
.UsingAsyncApiV2()
.WithTitle("Cloud Event API")
.WithVersion("1.0.0")
.WithServer("StreetLightsApi", server => server
.WithUrl(new("https://streetlights.fake.com"))
.WithProtocol(AsyncApiProtocol.Http, "2.0")
.WithBinding(new HttpServerBindingDefinition())
.WithSecurityRequirement("oauth2"))
.WithChannel("/events", channel => channel
.WithDescription("The endpoint used to publish and subscribe to cloud events")
.WithBinding(new HttpChannelBindingDefinition())
.WithSubscribeOperation(operation => operation
.WithOperationId("ObserveCloudEvents")
.WithDescription("Observes cloud events published by the StreetLightsApi")
.WithBinding(new HttpOperationBindingDefinition() { Method = Neuroglia.AsyncApi.v2.Bindings.Http.HttpMethod.POST, Type = HttpBindingOperationType.Response })
.WithMessages
(
message => message
.WithName("LightMeasuredEvent")
.WithDescription("The event fired whenever the luminosity of a light has been measured")
.WithContentType("application/cloudevents+json")
.WithTraitReference("cloud-event")
.WithPayloadSchema(lightMeasuredEventSchema)
.WithCorrelationId("$message.payload#/subject")
.WithTag(tag => tag
.WithName("light")),
message => message
.WithName("MovementDetectedEvent")
.WithDescription("The event fired whenever a movement has been detected by a sensor")
.WithContentType("application/cloudevents+json")
.WithTraitReference("cloud-event")
.WithPayloadSchema(movementDetectedEventSchema)
.WithCorrelationId("$message.payload#/subject")
.WithTag(tag => tag
.WithName("movement"))
)))
.WithMessageTraitComponent("cloud-event", message => message
.WithBinding(new HttpMessageBindingDefinition())
.WithContentType("application/cloudevents+json"))
.WithSecurityScheme("oauth2", scheme => scheme
.WithType(SecuritySchemeType.OAuth2)
.WithDescription("The security scheme used to authorize application requests")
.WithAuthorizationScheme("Bearer")
.WithOAuthFlows(oauth => oauth
.WithClientCredentialsFlow(flow => flow
.WithAuthorizationUrl(new("https://fake.idp.com/token"))
.WithScope("api:read", "The scope used to read data")))))
.Build();
var services = new ServiceCollection();
services.AddAsyncApi();
var serviceProvider = services.BuildServiceProvider();
var builder = serviceProvider.GetRequiredService<IAsyncApiDocumentBuilder>();
var document = builder
.UsingAsyncApiV3()
.WithTitle("Cloud Event API")
.WithVersion("1.0.0")
.WithServer("StreetLightsApi", server => server
.WithHost("https://streetlights.fake.com")
.WithProtocol(AsyncApiProtocol.Http, "2.0")
.WithBinding(new HttpServerBindingDefinition())
.WithSecurityRequirement(security => security
.Use("#/components/securitySchemes/oauth2")))
.WithChannel("events", channel => channel
.WithServer("#/servers/StreetLightsApi")
.WithDescription("The endpoint used to publish and subscribe to cloud events")
.WithBinding(new HttpChannelBindingDefinition()))
.WithOperation("observeCloudEvents", operation => operation
.WithAction(Neuroglia.AsyncApi.v3.V3OperationAction.Send)
.WithChannel("#/channels/events")
.WithTitle("ObserveCloudEvents")
.WithDescription("Observes cloud events published by the StreetLightsApi")
.WithBinding(new HttpOperationBindingDefinition()
{
Method = Neuroglia.AsyncApi.Bindings.Http.HttpMethod.POST,
Type = HttpBindingOperationType.Response
})
.WithMessage("#/components/messages/lightMeasuredEvent"))
.WithMessageComponent("lightMeasuredEvent", message => message
.WithName("LightMeasuredEvent")
.WithDescription("The event fired whenever the luminosity of a light has been measured")
.WithContentType("application/cloudevents+json")
.WithTrait(trait => trait
.Use("#/components/messageTraits/cloud-event"))
.WithPayloadSchema(schema => schema
.WithFormat("application/schema+json")
.WithSchema(lightMeasuredEventSchema))
.WithCorrelationId(setup => setup
.WithLocation("$message.payload#/subject"))
.WithTag(tag => tag
.WithName("light")))
.WithMessageComponent("movementDetectedEvent", message => message
.WithName("MovementDetectedEvent")
.WithDescription("The event fired whenever a movement has been detected by a sensor")
.WithContentType("application/cloudevents+json")
.WithTrait(trait => trait
.Use("#/components/messageTraits/cloud-event"))
.WithPayloadSchema(schema => schema
.WithFormat("application/schema+json")
.WithSchema(movementDetectedEventSchema))
.WithCorrelationId(setup => setup
.WithLocation("$message.payload#/subject"))
.WithTag(tag => tag
.WithName("movement")))
.WithMessageTraitComponent("cloud-event", message => message
.WithBinding(new HttpMessageBindingDefinition())
.WithContentType("application/cloudevents+json"))
.WithSecuritySchemeComponent("oauth2", scheme => scheme
.WithType(SecuritySchemeType.OAuth2)
.WithDescription("The security scheme used to authorize application requests")
.WithAuthorizationScheme("Bearer")
.WithOAuthFlows(oauth => oauth
.WithClientCredentialsFlow(flow => flow
.WithAuthorizationUrl(new("https://fake.idp.com/token"))
.WithScope("api:read", "The scope used to read data")))));
var writer = serviceProvider.GetRequiredService<IAsyncApiDocumentWriter>();
using MemoryStream stream = new();
await writer.WriteAsync(document, stream, AsyncApiDocumentFormat.Yaml, cancellationToken);
var reader = serviceProvider.GetRequiredService<IAsyncApiDocumentReader>();
var asyncApi = await reader.ReadAsync(stream, cancellationToken);
Generate code-first AsyncAPI documents
[AsyncApi("Streetlights API", "1.0.0", Description = "The Smartylighting Streetlights API allows you to remotely manage the city lights.", LicenseName = "Apache 2.0", LicenseUrl = "https://www.apache.org/licenses/LICENSE-2.0")]
public class StreetLightsService
: BackgroundService
{
... //Omitted for brevity
[Channel("light/measured"), PublishOperation(OperationId = "onLightMeasured", Summary = "Inform about environmental lighting conditions for a particular streetlight")]
public async Task PublishLightMeasured(LightMeasuredEvent e)
{
...
}
[Channel("light/measured"), SubscribeOperation(OperationId = "lightMeasuredEvent", Summary = "Inform about environmental lighting conditions for a particular streetlight")]
protected async Task OnLightMeasured(LightMeasuredEvent e)
{
...
}
...
}
Note the usage of the following attributes:
- AsyncApiV2: Marks a class for code-first AsyncAPI document generation. Used to provide information about the API (licensing, contact, ...)
- ChannelV2: Marks a method or class for code-first AsyncAPI channel generation. Used to provide information about the channel marked methods belong to.
- OperationV2: Marks a method for code-first AsyncAPI operation generation. Use to provide information about the AsyncAPI operation.
[AsyncApi("Streetlights API", "1.0.0", Description = "The **Smartylighting Streetlights API** allows you to remotely manage the city lights.", LicenseName = "Apache 2.0", LicenseUrl = "https://www.apache.org/licenses/LICENSE-2.0")]
[Server("http", "http://fake-http-server.com", AsyncApiProtocol.Http, PathName = "/{environment}", Description = "A sample **HTTP** server declared using attributes", Bindings = "#/components/serverBindings/http")]
[ServerVariable("http", "environment", Description = "The **environment** to use.", Enum = ["dev", "stg", "prod"])]
[HttpServerBinding("http")]
[Channel("lightingMeasuredMQTT", Address = "streets.{streetName}", Description = "This channel is used to exchange messages about lightning measurements.", Servers = ["#/servers/mosquitto"], Bindings = "#/components/channelBindings/mqtt")]
[MqttChannelBinding("mqtt")]
[ChannelParameter("lightingMeasured", "streetName", Description = "The name of the **street** the lights to get measurements for are located in")]
public class StreetLightsService
: BackgroundService
{
[Operation("sendLightMeasurement", V3OperationAction.Send, "#/channels/lightingMeasuredMQTT", Description = "Notifies remote **consumers** about environmental lighting conditions for a particular **streetlight**."), Neuroglia.AsyncApi.v3.Tag(Reference = "#/components/tags/measurement")]
public async Task PublishLightMeasured(LightMeasuredEvent e, CancellationToken cancellationToken = default)
{
...
}
[Operation("receiveLightMeasurement", V3OperationAction.Receive, "#/channels/lightingMeasuredMQTT"), Neuroglia.AsyncApi.v3.Tag(Reference = "#/components/tags/measurement")]
protected Task OnLightMeasured(LightMeasuredEvent e)
{
...
}
}
Generate documents explicitly
var generator = serviceProvider.GetRequiredService<IAsyncApiDocumentGenerator>();
var options = new AsyncApiDocumentGenerationOptions()
{
DefaultV2Configuration = builder =>
{
//Setup V2 documents, by configuring servers, for example
};
DefaultV3Configuration = builder =>
{
//Setup V3 documents, by configuring servers, for example
}
};
IEnumerable<AsyncApiDocument> documents = generator.GenerateAsync(typeof(StreetLightsService), options);
Generate documents implicitly
services.AddAsyncApiGeneration(builder =>
builder
.WithMarkupType<StreetLightsService>()
.UseDefaultV2Configuration(asyncApi =>
{
//Setup V2 documents, by configuring servers, for example
})
.UseDefaultV3Configuration(asyncApi =>
{
//Setup V3 documents, by configuring servers, for example
}));
...
builder.Services.AddAsyncApiUI();
...
...
var app = builder.Build();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.MapAsyncApiDocuments();
app.MapRazorPages();
...
Note: Since Razor Pages are used to render the UI, make sure to configure its services and map the pages:
...
builder.Services.AddRazorPages();
...
var app = builder.Build();
...
app.MapRazorPages();
...
You will also need to register an IJsonSchemaResolver and an HttpClient:
services.AddSingleton<IJsonSchemaResolver, JsonSchemaResolver>();
services.AddHttpClient();
For reference, please refer to the sample.
Launch your application, then navigate to /asyncapi. You should see something like this:
Interact with AsyncAPI applications
- ✅ AMQP
- ✅ AMQP1
- ❌ AnypointMQ
- ❌ GooglePubSub
- ✅ HTTP/HTTPS
- ❌ IBMMQ
- ❌ JMS
- ✅ Kafka
- ❌ Mercure
- ✅ MQTT
- ✅ MQTT5
- ✅ NATS
- ✅ Pulsar
- ✅ Redis
- ❌ SNS
- ✅ Solace
- ❌ SQS
- ✅ Stomp
- ✅ WS/WSS
⚠️ Warning: Security requirements have not yet been implemented.
We welcome community contributions to help implement and improve additional binding handlers!
Configuring an AsyncAPI Client:
Configure an AsyncAPI client with all binding handlers:
services.AddAsyncApiClient(options => options.AddAllBindingHandlers());
Configure an AsyncAPI client using specific binding handlers:
services.AddAsyncApiClient(options =>
{
options.AddHttpBindingHandler();
options.AddWebSocketBindingHandler();
});
These configurations ensure that your application is set up to handle various communication protocols as defined in your AsyncAPI document.
Creating an AsyncAPI Client:
Once the services are configured, you can create an AsyncAPI client using the client factory:
var clientFactory = ServiceProvider.GetRequiredService<IAsyncApiClientFactory>();
await using var client = clientFactory.CreateFor(asyncApiDocument);
Replace asyncApiDocument with your loaded AsyncAPI document instance.
To publish a message using the AsyncAPI client, construct the operation parameters with your payload and headers, then call PublishAsync:
var parameters = new AsyncApiPublishOperationParameters("Greet")
{
Payload = new
{
Greeting = "Hello, World!"
},
Headers = new
{
SomeHeader = "SomeHeaderValue"
}
};
await using var result = await client.PublishAsync(parameters);
This example publishes a message to the operation named "Greet" with a payload and headers. Adjust the operation name, payload, and headers as needed for your application.
To subscribe and react to messages from an AsyncAPI operation:
await using var result = await client.SubscribeAsync(new AsyncApiSubscribeOperationParameters("Greet"));
result.Messages?.Subscribe(message =>
{
Console.WriteLine(message.Payload?.Greeting);
});
This code subscribes to the "Greet" operation and prints the "Greeting" property of each received message's payload. Modify the operation name and message handling logic based on your specific use case.
A simple ASP.NET 9.0 REST API using a MQTT-powered message bus to send and receive information about environmental lighting conditions for a particular streetlight.
Clone the project in your favorite IDE, launch the app, and navigate to https://localhost:44326/asyncapi/StreetLightsApi/1.0.0. You should see something like the following:
asyncapi: 2.6.0
info:
title: Streetlights API
version: 1.0.0
description: The Smartylighting Streetlights API allows you to remotely manage the city lights.
contact: {}
license:
name: Apache 2.0
url: https://www.apache.org/licenses/LICENSE-2.0
servers:
mosquitto:
url: mqtt://test.mosquitto.org/
protocol: mqtt
channels:
light/measured:
subscribe:
message:
payload:
type: object
properties:
id:
type: integer
lumens:
type: integer
sentAt:
type: string
format: date-time
required:
- Id
- Lumens
- SentAt
name: LightMeasured
title: Light Measured
operationId: lightMeasuredEvent
summary: Inform about environmental lighting conditions for a particular streetlight
publish:
message:
payload:
type: object
properties:
id:
type: integer
lumens:
type: integer
sentAt:
type: string
format: date-time
required:
- Id
- Lumens
- SentAt
name: LightMeasured
title: Light Measured
operationId: onLightMeasured
summary: Inform about environmental lighting conditions for a particular streetlight