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));
+ }
+ }
+}