Skip to content

Commit

Permalink
Merge pull request #40 from neuroglia-io/fix-documentation
Browse files Browse the repository at this point in the history
Update the README.md file to document AsyncAPI v3 usage
  • Loading branch information
cdavernas authored Jan 3, 2025
2 parents 79c2c7b + 23ff09f commit ab116ed
Show file tree
Hide file tree
Showing 13 changed files with 243 additions and 81 deletions.
9 changes: 9 additions & 0 deletions Neuroglia.AsyncApi.sln
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,14 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "docs", "docs", "{90558701-3
README.md = README.md
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "assets", "assets", "{C50B254B-1890-463B-B889-C3986AF64804}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "img", "img", "{C5D2623D-841D-48E9-935C-EF4EF985C292}"
ProjectSection(SolutionItems) = preProject
assets\img\logo_white_on_blue_256.png = assets\img\logo_white_on_blue_256.png
assets\img\ui.png = assets\img\ui.png
EndProjectSection
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -116,6 +124,7 @@ Global
{3A707FE1-4AA3-4232-9E7D-88376580EAD5} = {D5DC0A71-F39C-4AA1-A284-66E622868D47}
{9FF8A714-BFBF-4A09-87E4-57CEF088BFA7} = {4B933DF9-CD24-44B1-AF64-0D5E75B9AB45}
{1FB272DF-4FE6-4417-B927-98DD5A821EC0} = {4A936028-09D1-4ACD-8383-5B496DAB2E16}
{C5D2623D-841D-48E9-935C-EF4EF985C292} = {C50B254B-1890-463B-B889-C3986AF64804}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {DC433DEB-01E5-4328-B0BB-6FFFE8C7363F}
Expand Down
228 changes: 158 additions & 70 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,17 @@
- [AsyncAPI document serving library](#asyncapi-document-serving-library)
- [AsyncAPI UI](#asyncapi-ui)
+ [Usage](#usage)
- [Building an AsyncAPI Document](#building-an-asyncapi-document)
+ [AsyncAPI v2](#asyncapi-v2)
+ [AsyncAPI v3](#asyncapi-v3)
- [Writing an AsyncAPI Document](#writing-an-asyncapi-document)
- [Reading an AsyncAPI document](#reading-an-asyncapi-document)
- [Generating code-first AsyncAPI documents](#generating-code-first-asyncapi-documents)
+ [AsyncAPI v2](#asyncapi-v2-1)
+ [AsyncAPI v3](#asyncapi-v3-1)
- [Generating documents manually](#21-generating-documents-manually)
- [Generating documents automatically and serve them using ASP](#22-generating-documents-automatically-and-serve-them-using-asp)
- [Using the AsyncAPI UI](#using-the-asyncapi-ui)
- [Build AsyncAPI documents](#build-asyncapi-documents)
+ [Using AsyncAPI v2](#using-asyncapi-v2)
+ [Using AsyncAPI v3](#using-asyncapi-v3)
- [Write AsyncAPI documents](#write-asyncapi-documents)
- [Read AsyncAPI documents](#read-asyncapi-documents)
- [Generate code-first AsyncAPI documents](#generate-code-first-asyncapi-documents)
+ [Using AsyncAPI v2](#using-asyncapi-v2-1)
+ [Using AsyncAPI v3](#using-asyncapi-v3-1)
- [Generate documents explicitly](#generate-documents-explicitly)
- [Generate documents implicitly](#generate-documents-implicitly)
- [Use the AsyncAPI UI](#use-the-asyncapi-ui)
+ [Samples](#samples)
- [Streetlights API - Server](#streetlights-api---server)

Expand Down Expand Up @@ -108,6 +108,7 @@ 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
Expand Down Expand Up @@ -160,52 +161,111 @@ var document = builder
#### AsyncAPI V3

```csharp

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")))));
```

### Writing an AsyncAPI document
### Write AsyncAPI documents

```csharp
var writer = serviceProvider.GetRequiredService<IAsyncApiDocumentWriter>();
using MemoryStream stream = new();
await writer.WriteAsync(document, stream, AsyncApiDocumentFormat.Yaml, cancellationToken);
```

### Reading an AsyncAPI document
### Read AsyncAPI documents

```csharp
var reader = serviceProvider.GetRequiredService<IAsyncApiDocumentReader>();
var asyncApi = await reader.ReadAsync(stream, cancellationToken);
```

### Generating code-first AsyncAPI documents
### Generate code-first AsyncAPI documents

#### AsyncAPI V2
#### Using AsyncAPI V2

```csharp
[AsyncApiV2("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")]
[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
: BackgroundService
{

... //Omitted for brevity
[ChannelV2("light/measured"), PublishOperation(OperationId = "onLightMeasured", Summary = "Inform about environmental lighting conditions for a particular streetlight")]
[Channel("light/measured"), PublishOperation(OperationId = "onLightMeasured", Summary = "Inform about environmental lighting conditions for a particular streetlight")]
public async Task PublishLightMeasured(LightMeasuredEvent e)
{
MqttApplicationMessage message = new()
{
Topic = "onLightMeasured",
ContentType = "application/json",
Payload = Encoding.UTF8.GetBytes(await this.Serializer.SerializeAsync(e))
};
await this.MqttClient.PublishAsync(message);
...
}

[ChannelV2("light/measured"), SubscribeOperation(OperationId = "lightMeasuredEvent", Summary = "Inform about environmental lighting conditions for a particular streetlight")]
[Channel("light/measured"), SubscribeOperation(OperationId = "lightMeasuredEvent", Summary = "Inform about environmental lighting conditions for a particular streetlight")]
protected async Task OnLightMeasured(LightMeasuredEvent e)
{
this.Logger.LogInformation($"Event received:{Environment.NewLine}{await this.Serializer.SerializeAsync(e)}");
...
}

...
Expand All @@ -219,13 +279,36 @@ Note the usage of the following attributes:
- `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 V3
#### Using AsyncAPI V3

```csharp
[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)
{
...
}

}
```

#### 2.1. Generating documents manually
#### Generate documents explicitly

```csharp
var generator = serviceProvider.GetRequiredService<IAsyncApiDocumentGenerator>();
Expand All @@ -243,62 +326,67 @@ var options = new AsyncApiDocumentGenerationOptions()
IEnumerable<AsyncApiDocument> documents = generator.GenerateAsync(typeof(StreetLightsService), options);
```

#### 2.2. Generating documents automatically and serve them using ASP
#### Generate documents implicitly

Go to your ASP project's `Startup.cs` file and add the following lines:
```csharp
//Startup.cs
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
}));
```

public class Startup
{
### Using the AsyncAPI UI

...
#### 1. Configure services

public void ConfigureServices(IServiceCollection services)
{
...
//Registers and configures the AsyncAPI code-first generation
services.AddAsyncApiGeneration(builder =>
builder.WithMarkupType<StreetLightsService>()
.UseDefaultV2Configuration(asyncApi =>
{
asyncApi
.UseServer("mosquitto", server => server
.WithUrl(new Uri("mqtt://test.mosquitto.org"))
.WithProtocol(AsyncApiProtocols.Mqtt));
}));
...
}

public void Configure(IApplicationBuilder app)
{
...
//Adds the middleware used to serve AsyncAPI documents
app..MapAsyncApiDocuments();
...
}
```csharp
...
builder.Services.AddAsyncApiUI();
...
```

}
#### 2. Map documents

```csharp
...
var app = builder.Build();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.MapAsyncApiDocuments();
app.MapRazorPages();
...
```

### Using the AsyncAPI UI

Go to your ASP project's `Startup.cs` file and add the following line to your `ConfigureServices` method:
**Note**: Since Razor Pages are used to render the UI, make sure to configure its services and map the pages:

```csharp
services.AddAsyncApiUI();
...
builder.Services.AddRazorPages();
...
var app = builder.Build();
...
app.MapRazorPages();
...
```

**Note**: Since RazorPages are used, make sure you add it to the service collection: `services.AddRazorPages();` and use the middleware to serve the pages: `app.MapRazorPages();`.
You will also need to register an `IJsonSchemaResolver` and a `HttpClient`:
You will also need to register an `IJsonSchemaResolver` and an `HttpClient`:
```csharp
services.AddSingleton<IJsonSchemaResolver, JsonSchemaResolver>();
services.AddHttpClient();
services.AddSingleton<IJsonSchemaResolver, JsonSchemaResolver>();
services.AddHttpClient();
```
For reference take a look at the [sample](#streetlights-api---server)
*For reference, please refer to the [sample](#streetlights-api---server).*

#### 3. Enjoy!

Launch your ASP project, then navigate to `http://localhost:44236/asyncapi`. You should see something like this:
Launch your application, then navigate to `/asyncapi`. You should see something like this:

![AsyncAPI UI - Screenshot](/assets/img/ui.png)

Expand Down
Binary file modified assets/img/ui.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit ab116ed

Please sign in to comment.