Skip to content

Commit cd3ea72

Browse files
committed
support default route template for controller.
1 parent d9446e9 commit cd3ea72

File tree

4 files changed

+130
-65
lines changed

4 files changed

+130
-65
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
using Microsoft.Extensions.Logging;
2+
using MQTTnet;
3+
using MQTTnet.AspNetCore.AttributeRouting;
4+
using System;
5+
using System.Threading.Tasks;
6+
7+
namespace Example.MqttControllers
8+
{
9+
[MqttController]
10+
[MqttRoute("devices/{devname}/[controller]")]
11+
public class ParamMqttWeatherForecastController : MqttBaseController
12+
{
13+
private readonly ILogger<ParamMqttWeatherForecastController> _logger;
14+
15+
// Controllers have full support for dependency injection just like AspNetCore controllers
16+
public ParamMqttWeatherForecastController(ILogger<ParamMqttWeatherForecastController> logger)
17+
{
18+
_logger = logger;
19+
}
20+
21+
22+
public string devname { get; set; }
23+
[MqttRoute("/")]
24+
public Task WeatherReport2()
25+
{
26+
_logger.LogInformation($"WeatherReport2");
27+
return Ok();
28+
}
29+
30+
// Supports template routing with typed constraints
31+
[MqttRoute("{zipCode}/temperature")]
32+
public Task WeatherReport(int zipCode)
33+
{
34+
// We have access to the MqttContext
35+
if (zipCode != 90210) { MqttContext.CloseConnection = true; }
36+
37+
// We have access to the raw message
38+
if (int.TryParse(Message.ConvertPayloadToString(), out int temperature))
39+
{
40+
_logger.LogInformation($"It's {temperature} degrees in Hollywood");
41+
// Example validation
42+
if (temperature <= 0 || temperature >= 130)
43+
{
44+
return BadMessage();
45+
}
46+
else
47+
{
48+
return Ok();
49+
}
50+
}
51+
return BadMessage();
52+
}
53+
}
54+
}

Source/Attributes/MqttRouteAttribute.cs

+8
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,15 @@ public MqttRouteAttribute(string template)
2121
{
2222
Template = template ?? throw new ArgumentNullException(nameof(template));
2323
}
24+
/// <summary>
25+
/// Creates a new <see cref="RouteAttribute"/> default route template for controller.
26+
///
27+
/// </summary>
28+
public MqttRouteAttribute():this("/")
29+
{
2430

31+
}
32+
2533
/// <summary>
2634
/// Gets the route template.
2735
/// </summary>

Source/Routing/MqttRouteTableFactory.cs

+10-2
Original file line numberDiff line numberDiff line change
@@ -79,8 +79,8 @@ internal static MqttRouteTable Create(IEnumerable<MethodInfo> actions)
7979

8080
// If an action starts with a /, we throw away the inherited portion of the path. We don't process ~/
8181
// because it wouldn't make sense in the context of Mqtt routing which has no concept of relative paths.
82-
var templates = controllerTemplates.SelectMany((c) => routeAttributes, (c, a) => a[0] == '/' ? a.Substring(1) : $"{c}{a}").ToArray();
83-
82+
var templates = controllerTemplates.SelectMany((c) => routeAttributes, (c, a) => $"{c}{a}").ToArray();
83+
8484
templatesByHandler.Add(action, templates);
8585
}
8686

@@ -188,6 +188,14 @@ internal static int RouteComparison(MqttRoute x, MqttRoute y)
188188

189189
if (xTemplate.Segments.Count != y.Template.Segments.Count)
190190
{
191+
if (xTemplate.Segments.Count ==0 || y.Template.Segments.Count==0)
192+
{
193+
return -1;
194+
}
195+
if (xTemplate.Segments.Count == 0 && y.Template.Segments.Count == 0)
196+
{
197+
return 1;
198+
}
191199
if (!xTemplate.Segments[xTemplate.Segments.Count - 1].IsCatchAll && yTemplate.Segments[yTemplate.Segments.Count - 1].IsCatchAll)
192200
{
193201
return -1;

Source/Templates/TemplateParser.cs

+58-63
Original file line numberDiff line numberDiff line change
@@ -18,93 +18,88 @@ internal static RouteTemplate ParseTemplate(string template)
1818
var originalTemplate = template;
1919

2020
template = template.Trim('/');
21-
22-
if (template == string.Empty)
23-
{
24-
throw new InvalidOperationException(
25-
$"Empty routes are not allowed. Use a catchall route instead.");
26-
}
27-
28-
var segments = template.Split('/');
29-
var templateSegments = new List<TemplateSegment>(segments.Length);
30-
31-
for (int i = 0; i < segments.Length; i++)
21+
var templateSegments = new List<TemplateSegment>();
22+
if (template != string.Empty)
3223
{
33-
var segment = segments[i];
34-
35-
if (string.IsNullOrEmpty(segment))
24+
var segments = template.Split('/');
25+
templateSegments = new List<TemplateSegment>(segments.Length);
26+
for (int i = 0; i < segments.Length; i++)
3627
{
37-
throw new InvalidOperationException(
38-
$"Invalid template '{template}'. Empty segments are not allowed.");
39-
}
28+
var segment = segments[i];
4029

41-
if (segment[0] != '{')
42-
{
43-
if (segment[segment.Length - 1] == '}')
30+
if (string.IsNullOrEmpty(segment))
4431
{
4532
throw new InvalidOperationException(
46-
$"Invalid template '{template}'. Missing '{{' in parameter segment '{segment}'.");
33+
$"Invalid template '{template}'. Empty segments are not allowed.");
4734
}
4835

49-
templateSegments.Add(new TemplateSegment(originalTemplate, segment, isParameter: false));
50-
}
51-
else
52-
{
53-
if (segment[segment.Length - 1] != '}')
36+
if (segment[0] != '{')
5437
{
55-
throw new InvalidOperationException(
56-
$"Invalid template '{template}'. Missing '}}' in parameter segment '{segment}'.");
57-
}
38+
if (segment[segment.Length - 1] == '}')
39+
{
40+
throw new InvalidOperationException(
41+
$"Invalid template '{template}'. Missing '{{' in parameter segment '{segment}'.");
42+
}
5843

59-
if (segment.Length < 3)
60-
{
61-
throw new InvalidOperationException(
62-
$"Invalid template '{template}'. Empty parameter name in segment '{segment}' is not allowed.");
44+
templateSegments.Add(new TemplateSegment(originalTemplate, segment, isParameter: false));
6345
}
64-
65-
var invalidCharacter = segment.IndexOfAny(InvalidParameterNameCharacters, 1, segment.Length - 2);
66-
67-
if (invalidCharacter != -1)
46+
else
6847
{
69-
throw new InvalidOperationException(
70-
$"Invalid template '{template}'. The character '{segment[invalidCharacter]}' in parameter segment '{segment}' is not allowed.");
48+
if (segment[segment.Length - 1] != '}')
49+
{
50+
throw new InvalidOperationException(
51+
$"Invalid template '{template}'. Missing '}}' in parameter segment '{segment}'.");
52+
}
53+
54+
if (segment.Length < 3)
55+
{
56+
throw new InvalidOperationException(
57+
$"Invalid template '{template}'. Empty parameter name in segment '{segment}' is not allowed.");
58+
}
59+
60+
var invalidCharacter = segment.IndexOfAny(InvalidParameterNameCharacters, 1, segment.Length - 2);
61+
62+
if (invalidCharacter != -1)
63+
{
64+
throw new InvalidOperationException(
65+
$"Invalid template '{template}'. The character '{segment[invalidCharacter]}' in parameter segment '{segment}' is not allowed.");
66+
}
67+
68+
templateSegments.Add(new TemplateSegment(originalTemplate, segment.Substring(1, segment.Length - 2), isParameter: true));
7169
}
72-
73-
templateSegments.Add(new TemplateSegment(originalTemplate, segment.Substring(1, segment.Length - 2), isParameter: true));
7470
}
75-
}
7671

77-
for (int i = 0; i < templateSegments.Count; i++)
78-
{
79-
var currentSegment = templateSegments[i];
80-
81-
if (currentSegment.IsCatchAll && i != templateSegments.Count - 1)
72+
for (int i = 0; i < templateSegments.Count; i++)
8273
{
83-
throw new InvalidOperationException($"Invalid template '{template}'. A catch-all parameter can only appear as the last segment of the route template.");
84-
}
74+
var currentSegment = templateSegments[i];
8575

86-
if (!currentSegment.IsParameter)
87-
{
88-
continue;
89-
}
90-
91-
for (int j = i + 1; j < templateSegments.Count; j++)
92-
{
93-
var nextSegment = templateSegments[j];
76+
if (currentSegment.IsCatchAll && i != templateSegments.Count - 1)
77+
{
78+
throw new InvalidOperationException($"Invalid template '{template}'. A catch-all parameter can only appear as the last segment of the route template.");
79+
}
9480

95-
if (currentSegment.IsOptional && !nextSegment.IsOptional)
81+
if (!currentSegment.IsParameter)
9682
{
97-
throw new InvalidOperationException($"Invalid template '{template}'. Non-optional parameters or literal routes cannot appear after optional parameters.");
83+
continue;
9884
}
9985

100-
if (string.Equals(currentSegment.Value, nextSegment.Value, StringComparison.OrdinalIgnoreCase))
86+
for (int j = i + 1; j < templateSegments.Count; j++)
10187
{
102-
throw new InvalidOperationException(
103-
$"Invalid template '{template}'. The parameter '{currentSegment}' appears multiple times.");
88+
var nextSegment = templateSegments[j];
89+
90+
if (currentSegment.IsOptional && !nextSegment.IsOptional)
91+
{
92+
throw new InvalidOperationException($"Invalid template '{template}'. Non-optional parameters or literal routes cannot appear after optional parameters.");
93+
}
94+
95+
if (string.Equals(currentSegment.Value, nextSegment.Value, StringComparison.OrdinalIgnoreCase))
96+
{
97+
throw new InvalidOperationException(
98+
$"Invalid template '{template}'. The parameter '{currentSegment}' appears multiple times.");
99+
}
104100
}
105101
}
106102
}
107-
108103
return new RouteTemplate(template, templateSegments);
109104
}
110105
}

0 commit comments

Comments
 (0)