Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

✨ Feature - MQTTnet.AspNetCore.AttributeRouting Support #101

Open
Rikj000 opened this issue Jul 20, 2021 · 5 comments
Open

✨ Feature - MQTTnet.AspNetCore.AttributeRouting Support #101

Rikj000 opened this issue Jul 20, 2021 · 5 comments
Labels
🔃 integration Features for automatic integration with message brokers & other libraries

Comments

@Rikj000
Copy link

Rikj000 commented Jul 20, 2021

I've been struggling to get Saunter v0.3.1 to work in combination with:

My project has these 2 MQTTnet libraries implemented successfully in ASP.NET 5 now.
I tried to follow Saunters documentation to the letter too. However with MQTTnet there was no need for me to implement MessageBus(Interface)s or Event (model-like) classes.

I can send/receive payloads from the MQTTnet.App client test application, however Saunter AsyncAPI Documentation generation keeps failing, the only thing I see hosted under /asyncapi/ui/index.html is the info defined in new AsyncApiDocument() of Startup.cs.

Which lead me to wondering:

  • Are these packages are actually compatible with each other or not?
  • Are any example projects that combine these packages available somewhere? (Looked but couldn't find any)
  • Does Saunter provide a MQTT Broker? (I assume it doesn't, because I saw the mosquitto references, MQTTnet is my Broker that is embedded in the source of my project in this case, thus I have no need for a stand alone broker like mosquitto)

Any help would be truly welcome!

@m-wild
Copy link
Collaborator

m-wild commented Jul 20, 2021

In theory this is supported.
Currently you'd have a couple of options for generating the AsyncAPI components:

  1. Use the saunter-provided attributes. These should be added to your MQTT controller alongside the exiting MQTT AttributeRouting attributes (a bit redundant 🙁).
  2. Implement an IDocumentFilter to automatically scan the code for the MQTT AttributeRouting attributes.

Option 2 would be ideal as an extension to saunter as an MQTT AttributeRouting support package.

Unfortunately I don't have any experience with MQTTnet or MQTT AttributeRouting, if you could provide a simple example project (or an existing open source project) using these packages, I am happy to spend some time trying to get it working, and prototype a package implementing option 2 above.

I'm not aware of any existing examples using MQTTnet and Saunter.

Saunter doesn't provide any message broker, but has support for documenting them by using bindings.
We currently only have binding implemented for amqp, http, and Kafka, but I am happy to add MQTT.
These have to be added to the document using filters today, autogenerating them from the MQTT AttributeRouting attributes (as above) would be ideal.

@Rikj000
Copy link
Author

Rikj000 commented Jul 20, 2021

Option 2 would be ideal as an extension to saunter as an MQTT AttributeRouting support package.

This also sounds like the cleanest implementation to me!🙂
However I'm unsure if I have enough knowledge of MQTT, AsyncAPI and C# Attributes on my own to make this happen..

Unfortunately I don't have any experience with MQTTnet or MQTT AttributeRouting, if you could provide a simple example project (or an existing open source project) using these packages, I am happy to spend some time trying to get it working, and prototype a package implementing option 2 above.

I just poured my current work into a de-branded example project and invited you as a collaborator,
also added a quick readme page to quickly help find your way through the project:

Saunter-MQTTnet-AspNet5-AttributeRouting-ExampleProject

I'll continue to try and implement this during my working hours for as long as my boss allows me to work on it,
however in my free time I won't have time to work on this, so I hope we can see this through in time! 🚀

@Rikj000 Rikj000 changed the title [Question] MQTTnet Attribute Routing supported? [Feature Request] MQTTnet Attribute Routing Support (+ Example Project) Jul 20, 2021
@Rikj000 Rikj000 changed the title [Feature Request] MQTTnet Attribute Routing Support (+ Example Project) [Documentation] MQTTnet Attribute Routing Support (+ Example Project) Jul 20, 2021
@Rikj000
Copy link
Author

Rikj000 commented Jul 20, 2021

Managed to get Option 1 working! 🎉

However the Channel name string conversion (Different for MQTTnet Attribute Routing & Saunter) is still HardCoded,
I will write up a conversion method and push it to the example project.

Feel free to reference my project in your Documentation or to include the conversion method in Saunter once I finished it up!

@m-wild
Copy link
Collaborator

m-wild commented Jul 20, 2021

Thanks, I will take a look over the next few days.

@m-wild
Copy link
Collaborator

m-wild commented Jul 23, 2021

Ok, there are a couple of issues with Option 2...

  1. Saunter DocumentFilterContext does not expose the ISchemaGenerator -- easy fix from my side.
  2. MQTTnet.AspNetCore.AttributeRouting does not provide any public types/methods for inspecting the routing table.

We can either

  1. Re-implement the assembly scanning behaviour of MqttRouteTableFactory
  2. Use reflection to get into the MqttRouteTable
  3. Raise an issue with MQTTnet.AspNetCore.AttributeRouting to make the route table public.
I have a proof of concept using reflection...

public class MqttNetAspNetCoreAttributeRoutingDocumentFilter : IDocumentFilter
{
    public void Apply(AsyncApiDocument document, DocumentFilterContext context)
    {
        var mqttRouteTableFactory = Type.GetType("MQTTnet.AspNetCore.AttributeRouting.MqttRouteTableFactory, MQTTnet.AspNetCore.AttributeRouting");
        var mqttRouteTable = Type.GetType("MQTTnet.AspNetCore.AttributeRouting.MqttRouteTable, MQTTnet.AspNetCore.AttributeRouting");
        var mqttRoute = Type.GetType("MQTTnet.AspNetCore.AttributeRouting.MqttRoute, MQTTnet.AspNetCore.AttributeRouting");
        var mqttRouteTemplate = Type.GetType("MQTTnet.AspNetCore.AttributeRouting.RouteTemplate, MQTTnet.AspNetCore.AttributeRouting");
        var mqttTemplateSegment = Type.GetType("MQTTnet.AspNetCore.AttributeRouting.TemplateSegment, MQTTnet.AspNetCore.AttributeRouting");

        var create = mqttRouteTableFactory.GetMethod("Create", BindingFlags.Static | BindingFlags.NonPublic, null, CallingConventions.Any,
            new[] {typeof(IEnumerable<Assembly>)}, null);


        var routeTable = create.Invoke(null, new object[] { new [] {Assembly.GetEntryAssembly() } });

        var routes = mqttRouteTable.GetProperty("Routes").GetValue(routeTable);


        foreach (var route in (IEnumerable) routes)
        {
            var template = mqttRoute.GetProperty("Template").GetValue(route);
            var segments = mqttRouteTemplate.GetProperty("Segments").GetValue(template);

            // MQTTnet route templates are not uri safe, which is required by the asyncapi spec.
            var channelItemName = new List<string>();
            foreach (var segment in (IEnumerable)segments)
            {
                var isParameter = (bool) mqttTemplateSegment.GetProperty("IsParameter").GetValue(segment);
                var isCatchAll = (bool) mqttTemplateSegment.GetProperty("IsCatchAll").GetValue(segment);
                var value = (string) mqttTemplateSegment.GetProperty("Value").GetValue(segment);

                channelItemName.Add(isCatchAll ? "#" : isParameter ? "+" : value);
            }


            var handler = (MethodInfo) mqttRoute.GetProperty("Handler").GetValue(route);
            var parameters = handler.GetParameters();

            ISchema payload = null;
            if (parameters != null && parameters.Any())
            {
                payload = context.SchemaGenerator.GenerateSchema(parameters.First().ParameterType, context.SchemaRepository);
            }


            document.Channels.Add(string.Join("/", channelItemName), new ChannelItem
            {
                Publish = new Operation
                {
                    OperationId = handler.Name,
                    Summary = handler.GetXmlDocsSummary(),
                    Message = new Message
                    {
                        Payload = payload,
                    }
                }
            });
        }
    }
}
If we had access to the MQTTnet RouteTable, it would look something like this...

public class MqttNetAspNetCoreAttributeRoutingDocumentFilter : IDocumentFilter
{
    private readonly MqttRouteTable _mqttRouteTable;

    public MqttNetAspNetCoreAttributeRoutingDocumentFilter(MqttRouteTable mqttRouteTable)
    {
        _mqttRouteTable = mqttRouteTable;
    }
    
    public void Apply(AsyncApiDocument document, DocumentFilterContext context)
    {
        foreach (var route in _mqttRouteTable.Routes)
        {
            // MQTTnet route templates are not uri safe, which is required by the asyncapi spec.
            var channelItemName = string.Join("/", route.Template.Segments.Select(s => s.IsCatchAll ? "#" : s.IsParameter ? "+" : s.Value));

            var handler = route.Handler;
            var parameters = handler.GetParameters();

            ISchema payload = null;
            if (parameters != null && parameters.Any())
            {
                payload = context.SchemaGenerator.GenerateSchema(parameters.First().ParameterType, context.SchemaRepository);
            }
            
            document.Channels.Add(channelItemName, new ChannelItem
            {
                Publish = new Operation
                {
                    OperationId = handler.Name,
                    Summary = handler.GetXmlDocsSummary(),
                    Message = new Message
                    {
                        Payload = payload,
                    }
                }
            });
        }
    }
}

I will raise an issue on the MQTTnet.AspNetCore.AttributeRouting project.

@m-wild m-wild changed the title [Documentation] MQTTnet Attribute Routing Support (+ Example Project) MQTTnet.AspNetCore.AttributeRouting Support Jul 28, 2021
@m-wild m-wild added the 🔃 integration Features for automatic integration with message brokers & other libraries label Jul 28, 2021
@Rikj000 Rikj000 changed the title MQTTnet.AspNetCore.AttributeRouting Support ✨ Feature - MQTTnet.AspNetCore.AttributeRouting Support Apr 10, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
🔃 integration Features for automatic integration with message brokers & other libraries
Projects
None yet
Development

No branches or pull requests

2 participants