diff --git a/Build/MQTTnet.nuspec b/Build/MQTTnet.nuspec index c532054..30cb406 100644 --- a/Build/MQTTnet.nuspec +++ b/Build/MQTTnet.nuspec @@ -22,6 +22,9 @@ * [Server] Added interceptor for unsubscriptions. * [MQTTnet.Server] Added interceptor for unsubscriptions. * [MQTTnet.AspNetCore] improved compatibility with AspNetCore 3.1 +* [Core] Added MqttApplicationMessage.GetUserProperty() convenience method (thanks to @PMExtra). +* [Client] Support WithConnectionUri to configure client (thanks to @PMExtra). +* [Server] Removed exceptions when user properties are set with MQTT protocol version 3.1 * [LowLevelMqttClient] Added low level MQTT client in order to provide more flexibility when using the MQTT protocol. This client requires detailed knowledge about the MQTT protocol. Copyright Christian Kratky 2016-2020 diff --git a/Source/MQTTnet/Extensions/MqttClientOptionsBuilderExtension.cs b/Source/MQTTnet/Extensions/MqttClientOptionsBuilderExtension.cs new file mode 100644 index 0000000..fc9da23 --- /dev/null +++ b/Source/MQTTnet/Extensions/MqttClientOptionsBuilderExtension.cs @@ -0,0 +1,48 @@ +using System; +using System.Linq; +using MQTTnet.Client.Options; + +namespace MQTTnet.Extensions +{ + public static class MqttClientOptionsBuilderExtension + { + public static MqttClientOptionsBuilder WithConnectionUri(this MqttClientOptionsBuilder builder, Uri uri) + { + var port = uri.IsDefaultPort ? null : (int?) uri.Port; + switch (uri.Scheme.ToLower()) + { + case "tcp": + case "mqtt": + builder.WithTcpServer(uri.Host, port); + break; + + case "mqtts": + builder.WithTcpServer(uri.Host, port).WithTls(); + break; + + case "ws": + case "wss": + builder.WithWebSocketServer(uri.ToString()); + break; + + default: + throw new ArgumentException("Unexpected scheme in uri."); + } + + if (!string.IsNullOrEmpty(uri.UserInfo)) + { + var userInfo = uri.UserInfo.Split(':'); + var username = userInfo[0]; + var password = userInfo.Length > 1 ? userInfo[1] : ""; + builder.WithCredentials(username, password); + } + + return builder; + } + + public static MqttClientOptionsBuilder WithConnectionUri(this MqttClientOptionsBuilder builder, string uri) + { + return WithConnectionUri(builder, new Uri(uri, UriKind.Absolute)); + } + } +} diff --git a/Source/MQTTnet/Extensions/UserPropertyExtension.cs b/Source/MQTTnet/Extensions/UserPropertyExtension.cs new file mode 100644 index 0000000..5bb28cf --- /dev/null +++ b/Source/MQTTnet/Extensions/UserPropertyExtension.cs @@ -0,0 +1,32 @@ +using System; +using System.ComponentModel; +using System.Linq; + +namespace MQTTnet.Extensions +{ + public static class UserPropertyExtension + { + public static string GetUserProperty(this MqttApplicationMessage message, string propertyName, StringComparison comparisonType = StringComparison.OrdinalIgnoreCase) + { + if (message == null) throw new ArgumentNullException(nameof(message)); + if (propertyName == null) throw new ArgumentNullException(nameof(propertyName)); + + return message.UserProperties?.SingleOrDefault(up => up.Name.Equals(propertyName, comparisonType))?.Value; + } + + public static T GetUserProperty(this MqttApplicationMessage message, string propertyName, StringComparison comparisonType = StringComparison.OrdinalIgnoreCase) + { + var value = GetUserProperty(message, propertyName, comparisonType); + + var typeDescriptor = TypeDescriptor.GetConverter(typeof(T)); + try + { + return (T) typeDescriptor.ConvertFromString(value); + } + catch (Exception ex) + { + throw new InvalidOperationException($"Cannot convert value({value ?? "null"}) of UserProperty({propertyName}) to {typeof(T).FullName}.", ex); + } + } + } +} diff --git a/Source/MQTTnet/Formatter/V3/MqttV310DataConverter.cs b/Source/MQTTnet/Formatter/V3/MqttV310DataConverter.cs index 48d42cd..4bf4944 100644 --- a/Source/MQTTnet/Formatter/V3/MqttV310DataConverter.cs +++ b/Source/MQTTnet/Formatter/V3/MqttV310DataConverter.cs @@ -20,11 +20,6 @@ namespace MQTTnet.Formatter.V3 { if (applicationMessage == null) throw new ArgumentNullException(nameof(applicationMessage)); - if (applicationMessage.UserProperties?.Any() == true) - { - throw new MqttProtocolViolationException("User properties are not supported in MQTT version 3."); - } - return new MqttPublishPacket { Topic = applicationMessage.Topic, @@ -171,11 +166,6 @@ namespace MQTTnet.Formatter.V3 { if (options == null) throw new ArgumentNullException(nameof(options)); - if (options.UserProperties?.Any() == true) - { - throw new MqttProtocolViolationException("User properties are not supported in MQTT version 3."); - } - var subscribePacket = new MqttSubscribePacket(); subscribePacket.TopicFilters.AddRange(options.TopicFilters); @@ -186,11 +176,6 @@ namespace MQTTnet.Formatter.V3 { if (options == null) throw new ArgumentNullException(nameof(options)); - if (options.UserProperties?.Any() == true) - { - throw new MqttProtocolViolationException("User properties are not supported in MQTT version 3."); - } - var unsubscribePacket = new MqttUnsubscribePacket(); unsubscribePacket.TopicFilters.AddRange(options.TopicFilters); diff --git a/Source/MQTTnet/MQTTnet.csproj b/Source/MQTTnet/MQTTnet.csproj index 28e3f3d..07b9161 100644 --- a/Source/MQTTnet/MQTTnet.csproj +++ b/Source/MQTTnet/MQTTnet.csproj @@ -42,6 +42,7 @@ + diff --git a/Tests/MQTTnet.Core.Tests/MqttApplicationMessage_Tests.cs b/Tests/MQTTnet.Core.Tests/MqttApplicationMessage_Tests.cs new file mode 100644 index 0000000..6b791dc --- /dev/null +++ b/Tests/MQTTnet.Core.Tests/MqttApplicationMessage_Tests.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using MQTTnet.Extensions; +using MQTTnet.Packets; + +namespace MQTTnet.Tests +{ + [TestClass] + public class MqttApplicationMessage_Tests + { + [TestMethod] + public void GetUserProperty_Test() + { + var message = new MqttApplicationMessage + { + UserProperties = new List + { + new MqttUserProperty("foo", "bar"), + new MqttUserProperty("value", "1011"), + new MqttUserProperty("CASE", "insensitive") + } + }; + + Assert.AreEqual("bar", message.GetUserProperty("foo")); + Assert.AreEqual(1011, message.GetUserProperty("value")); + Assert.AreEqual("insensitive", message.GetUserProperty("case")); + Assert.AreEqual(null, message.GetUserProperty("nonExists")); + Assert.AreEqual(null, message.GetUserProperty("nonExists")); + Assert.ThrowsException(() => message.GetUserProperty("nonExists")); + } + } +} diff --git a/Tests/MQTTnet.Core.Tests/MqttClientOptionsBuilder_Tests.cs b/Tests/MQTTnet.Core.Tests/MqttClientOptionsBuilder_Tests.cs new file mode 100644 index 0000000..a482081 --- /dev/null +++ b/Tests/MQTTnet.Core.Tests/MqttClientOptionsBuilder_Tests.cs @@ -0,0 +1,22 @@ +using System.Linq; +using System.Text; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using MQTTnet.Client.Options; +using MQTTnet.Extensions; + +namespace MQTTnet.Tests +{ + [TestClass] + public class MqttClientOptionsBuilder_Tests + { + [TestMethod] + public void WithConnectionUri_Credential_Test() + { + var options = new MqttClientOptionsBuilder() + .WithConnectionUri("mqtt://user:password@127.0.0.1") + .Build(); + Assert.AreEqual("user", options.Credentials.Username); + Assert.IsTrue(Encoding.UTF8.GetBytes("password").SequenceEqual(options.Credentials.Password)); + } + } +}