Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
553 changes: 553 additions & 0 deletions .editorconfig

Large diffs are not rendered by default.

11 changes: 8 additions & 3 deletions Directory.Build.targets
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,16 @@
<PackageReference Update="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Update="CommandLineParser" Version="2.9.1" />

<FrameworkReference Update="Microsoft.AspNetCore.App" Version="$(dotnetVersion)"/>
<PackageReference Update="MQTTnet" Version="4.3.1.873"/>
<PackageReference Update="MQTTnet.Extensions.ManagedClient" Version="4.3.1.873"/>

<FrameworkReference Update="Microsoft.AspNetCore.App" Version="$(dotnetVersion)"/>
<PackageReference Update="Microsoft.Extensions.Logging" Version="$(dotnetVersion)" />
<PackageReference Update="Microsoft.Extensions.Configuration.Json" Version="$(dotnetVersion)" />
<PackageReference Update="Microsoft.Extensions.Configuration.FileExtensions" Version="$(dotnetVersion)" />
<PackageReference Update="Microsoft.Extensions.Hosting" Version="$(dotnetVersion)" />
<PackageReference Update="Microsoft.Extensions.Configuration.Binder" Version="$(dotnetVersion)" />
<PackageReference Update="Microsoft.Extensions.Hosting.Abstractions" Version="$(dotnetVersion)"/>
<PackageReference Update="Microsoft.Extensions.Hosting" Version="$(dotnetVersion)" />
<PackageReference Update="Microsoft.Data.Sqlite" Version="$(dotnetVersion)" />

<PackageReference Update="Microsoft.EntityFrameworkCore" Version="$(efCoreVersion)" />
Expand All @@ -52,4 +57,4 @@

</ItemGroup>

</Project>
</Project>
1 change: 0 additions & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
Expand Down
22 changes: 20 additions & 2 deletions MORYX-Framework.sln
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Moryx.TestTools.NUnit", "sr
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Moryx.TestTools.IntegrationTest", "src\Moryx.TestTools.IntegrationTest\Moryx.TestTools.IntegrationTest.csproj", "{C949164C-0345-4893-9E4C-A79BC1F93F85}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Moryx.Communication.Mqtt", "src\Moryx.Communication.Mqtt\Moryx.Communication.Mqtt.csproj", "{1ACB424B-5C5C-766E-9547-17B72A3EF8E5}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Moryx.Communication.Mqtt.Services", "src\Moryx.Communication.Mqtt.Services\Moryx.Communication.Mqtt.Services.csproj", "{6FD8D118-B72A-1362-F0AE-A8F9A09F7515}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Moryx.Communication.Mqtt.Endpoints", "src\Moryx.Communication.Mqtt.Endpoints\Moryx.Communication.Mqtt.Endpoints.csproj", "{479C11E4-67E1-4739-A47B-1ED1AA5F59AA}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -309,6 +315,18 @@ Global
{C949164C-0345-4893-9E4C-A79BC1F93F85}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C949164C-0345-4893-9E4C-A79BC1F93F85}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C949164C-0345-4893-9E4C-A79BC1F93F85}.Release|Any CPU.Build.0 = Release|Any CPU
{1ACB424B-5C5C-766E-9547-17B72A3EF8E5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1ACB424B-5C5C-766E-9547-17B72A3EF8E5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1ACB424B-5C5C-766E-9547-17B72A3EF8E5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1ACB424B-5C5C-766E-9547-17B72A3EF8E5}.Release|Any CPU.Build.0 = Release|Any CPU
{6FD8D118-B72A-1362-F0AE-A8F9A09F7515}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6FD8D118-B72A-1362-F0AE-A8F9A09F7515}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6FD8D118-B72A-1362-F0AE-A8F9A09F7515}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6FD8D118-B72A-1362-F0AE-A8F9A09F7515}.Release|Any CPU.Build.0 = Release|Any CPU
{479C11E4-67E1-4739-A47B-1ED1AA5F59AA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{479C11E4-67E1-4739-A47B-1ED1AA5F59AA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{479C11E4-67E1-4739-A47B-1ED1AA5F59AA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{479C11E4-67E1-4739-A47B-1ED1AA5F59AA}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -357,8 +375,8 @@ Global
{C949164C-0345-4893-9E4C-A79BC1F93F85} = {953AAE25-26C8-4A28-AB08-61BAFE41B22F}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {36EFC961-F4E7-49DC-A36A-99594FFB8243}
RESX_TaskErrorCategory = Message
RESX_ShowErrorsInErrorList = True
RESX_TaskErrorCategory = Message
SolutionGuid = {36EFC961-F4E7-49DC-A36A-99594FFB8243}
EndGlobalSection
EndGlobal
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
8.5.3
8.6.0
265 changes: 265 additions & 0 deletions docs/articles/Mqtt/ManagedMqtt.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,265 @@
# The goal:

The goal is to allow any MORYX developer to create their own Mqtt To MORYX facade, that way you can use Mqtt to communicate to with any MORYX facade ex: `ProductManagement`,`ResourceManagement` etc.

To achieve that, we took inspiration from 3 repositories :

1. [Hosted Mqtt Server](https://github.com/dotnet/MQTTnet/wiki/Server)
2. [How to use Mqtt as a service in asp.net controller](https://github.com/rafiulgits/mqtt-client-dotnet-core)
3. [Mqtt AttributeRouting](https://www.nuget.org/packages/MQTTnet.AspNetCore.AttributeRouting)

_Checking those repositories will give you an idea of what the `ManagedMqtt` is trying to achieve._

# APIs and classes

#### IManagedMqttClient :
A managed `IMqttClient` from the [MQTTnet](https://github.com/dotnet/MQTTnet) extensions package.
#### IMqttService :
Preferred base class for all services based on Mqtt .
#### IMqttEndpoint :
Mqtt Endpoint interface. Every endpoint will implement this interface.

# Usage:

To use the `IManagedMqttClient` you need to configure it in the `Program.cs` like so:

```cs
builder.Services.AddMqttClient();
```
Optionally you can also configure the Mqtt client connection details like so :
```cs
builder.Services.AddMqttClient(options =>
{
options.Connection = new MqttConnectionConfig
{
Port = 1883,
Id = "EndpointDemo1",
Host = "localhost",
QoS = MQTTnet.Protocol.MqttQualityOfServiceLevel.ExactlyOnce,
RootTopic = "mqtt/moryx"
};
// optional: add your custom converter/serializer
options.JsonSerializerOptions.Converters.Add(new MyConverter())
})
```

You can also configure the Mqtt client using a configuration file just like any `MORYX` module.
```cs
builder.Services.AddMqttClient("Moryx.Mqtt.Config"); // specify your config file name without extension
```
Comment on lines +46 to +49
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why must the filename be defined? The config manager normally does that automatically by the typename. Which class is used for the serialization?


Add your configuration file `Moryx.Mqtt.Config.json`, inside the default MORYX Config folder. The content is as follow:

``` json
{
"Id": "my-id",
"Host": "localhost",
"Port": 1883,
"Username": null,
"Password": null,
"Tls": false,
"QoS": 1, // check the MqttQualityOfServiceLevel enum for more info
"ReconnectDelayMs": 30000,
"ReconnectWithClientSession": true,
"RootTopic": "" // optional
}
```

## Serialization
The serialization of payload is done via `MqttMessageSerialization` helper class, serialization and deserialization methods requires `JsonSerializerOptions` that is configured during setup of the Mqtt client.

- JsonSerializationOptions

```cs
// Program.cs
builder.Services.AddMqttClient(builder: (provider, options) =>
{
options.JsonSerializerOptions.Converters.Add(new MyCustomConverter()) // here you add your custom converter
options.JsonSerializerOptions.ReferenceHandler = ReferenceHandler.IgnoreCycles; // change how references are handled
//etc..
})
//.. other code
```

if you want to implement your custom service based on the `IManagedMqttClient` the recommended way is as follow :
- In your `program.cs` add the following:

```cs
builder.Services.AddMqttClient(); // this adds the IManagedMqttClient to the service collection
```
- Create a new C# class and inherit from `IMqttService`. `Note: Dependency injection is supported `. Now your service has access to the `IManagedMqttClient`,
Example:

```cs
public class BlablaService : IMqttService
{
private IMyDependency _dependency;
private IManagedMqttClient _client;
public Blabla(IMyDependency dependency, IManagedMqttClient client)
{
_dependency = dependency;
_client = client;
}

// </inherited>
public override Task StartAsync(CancellationToken cancellationToken)
{
// your logic here
// _client.OnConnectedAsync += ....
// _client.OnMessageReceived += ...
return Task.CompletedTask;
}

// </inherited>
public override Task StopAsync(CancellationToken cancellationToken)
{
//your logic here
return Task.CompletedTask;
}
}
```

To add your new service to the service collection do the following:
```cs
builder.Services.AddMqttClient();
.AddMqttService<BlablaService>(); // you custom service
```

#### Note : the connection to the broker (Connect, re-connect on connection lost etc...) is automatically handled by the `IManagedMqttClient`.

# Endpoints

If you need something with topic template and constraint support, you can use the `IMqttEndpoint`. Here is an example:
- In your `program.cs` add the following:

```cs
builder.Services.AddMqttClient() // must be present
.AddMqttEndpoints(); // <-- this add supports for the endpoints
```
- Create a new C# class that implements the `IMqttEndpoint`:
```cs
public class Blabla : IMqttEndpoint
{
private IMyDependency _dependency;
public Blabla(IMyDependency dependency)
{
_dependency = dependency;
}

public void Map(IMqttRouteBuilder route)
{
//topic template is supported here
route.MapGet("my-topic/{id:int}/method/{methodName}", context =>
{
var messageBuilder = new MqttApplicationMessageBuilder();
messageBuilder.WithTopic("another-topic");
messageBuilder.WithPayload(MqttMessageSerialization.GetJsonPayload("Hello World!!!!"));
return messageBuilder.Build();
});
}
}
```

There is a minimal version of the Mqtt endpoint, that you can use directly in the `program.cs`:

```cs
builder.Services.AddMqttClient()
.AddMqttEndpoints(route =>
{
route.MapGet("my-topic/{id:int}/method/{methodName}", context =>
{
var messageBuilder = new MqttApplicationMessageBuilder();
messageBuilder.WithTopic("another-topic");
messageBuilder.WithPayload(MqttMessageSerialization.GetJsonPayload("Hello World!!!!"));
return messageBuilder.Build();
});
// optional : this behave like an eventStream
route.MapPost(stream =>
{
var messageBuilder = new MqttApplicationMessageBuilder();
messageBuilder.WithTopic("another-topic");
var timer = new Timer(async arg =>
{
var messageBuilder = new MqttApplicationMessageBuilder();
messageBuilder.WithPayload(MqttMessageSerialization.GetJsonPayload("Hello World!!!!"));
var message = messageBuilder.Build();
await stream.WriteAsync(message);
}, null, 0, 10 * 1000);
});
});
```
This can be useful for quick prototyping and testing.

### Available features in endpoints
Endpoints topics support constraint just like in ASP.net controller route.
#### Route constraint

The available constraints are:

- {name: int}
- {name: int?}
- {name: string}
- {name: string?}
- {name: decimal}
- {name: decimal?}
- {name: bool}
- {name: bool?}
- {name: datetime}
- {name: datetime?}
- {name: double}
- {name: double?}
- {name: float}
- {name: float?}
- {name: guid}
- {name: guid?}
- {name: long}
- {name: long?}
- {name?}
- {name/*}

#### Message Body and Parameters inside a Topic

When the endpoint receives a message from the broker, it might come with a Body/payload and a parameter inside the topic.
To access the payload inside your endpoint you can use one of the following methods:
```cs
FromBody<T>(string propertyName)
```
```cs
FromBody(string propertyName, Type type)
```
```cs
RequestBody<T>()
```
```cs
RequestBodyObject()
```

Given the following message body in Json:
```json
{
"Name": "Bloomberg",
"Temperature":"-2 degrees Celsius"
}
```

The following examples shows how to use the Message body methods to extract the relevant data:
```cs
...
route.MapGet("/my-topic/{id:int}", ctx => {
string name = ctx.FromBody<string>("Name"); // "Bloomberg"
City city = RequestBody<City>(); // "{Name, Temperature}"
});
...
```

The same thing is possible for the topic parameters. Example:
```cs
...
route.MapGet("/my-topic/{id:int}", ctx => {
int id = ctx.FromParameterValues<int>("id"); // this return 2 for '/my-topic/2'
});
...
```



Loading
Loading