Old SDK removed
New dotnet SDK added - Release
diff --git a/sdks/dotnet/basyx-core/BaSyx.Utils/AssemblyHandling/AssemblyUtils.cs b/sdks/dotnet/basyx-core/BaSyx.Utils/AssemblyHandling/AssemblyUtils.cs
new file mode 100644
index 0000000..33f30f2
--- /dev/null
+++ b/sdks/dotnet/basyx-core/BaSyx.Utils/AssemblyHandling/AssemblyUtils.cs
@@ -0,0 +1,67 @@
+/*******************************************************************************
+* Copyright (c) 2020 Robert Bosch GmbH
+* Author: Constantin Ziesche (constantin.ziesche@bosch.com)
+*
+* This program and the accompanying materials are made available under the
+* terms of the Eclipse Public License 2.0 which is available at
+* http://www.eclipse.org/legal/epl-2.0
+*
+* SPDX-License-Identifier: EPL-2.0
+*******************************************************************************/
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using System.Text;
+
+namespace BaSyx.Utils.AssemblyHandling
+{
+ public static class AssemblyUtils
+ {
+ private class AssemblyNameComparer : EqualityComparer<AssemblyName>
+ {
+ public override bool Equals(AssemblyName x, AssemblyName y)
+ {
+ if (x.FullName == y.FullName)
+ return true;
+ else
+ return false;
+ }
+
+ public override int GetHashCode(AssemblyName obj)
+ {
+ unchecked
+ {
+ var result = 0;
+ result = (result * 397) ^ obj.FullName.GetHashCode();
+ return result;
+ }
+ }
+ }
+ /// <summary>
+ /// Returns all loaded or referenced assemblies within the current application domain. Microsoft or system assemblies are excluded.
+ /// </summary>
+ /// <returns></returns>
+ public static List<Assembly> GetLoadedAssemblies()
+ {
+ List<Assembly> assemblies = AppDomain.CurrentDomain.GetAssemblies()
+ .Where(a => !a.FullName.StartsWith("Microsoft") && !a.FullName.StartsWith("System"))
+ .ToList();
+ List<AssemblyName> assemblyNames = new List<AssemblyName>();
+ foreach (var assembly in assemblies)
+ {
+ List<AssemblyName> referencedAssemblyNames = assembly.GetReferencedAssemblies().ToList();
+ assemblyNames.AddRange(referencedAssemblyNames);
+ }
+ assemblyNames = assemblyNames
+ .Distinct(new AssemblyNameComparer())
+ .Where(a => !a.FullName.StartsWith("Microsoft") && !a.FullName.StartsWith("System"))?
+ .ToList();
+
+ List<Assembly> referencedAssemblies = assemblyNames.ConvertAll(c => Assembly.Load(c));
+ assemblies.AddRange(referencedAssemblies);
+
+ return assemblies;
+ }
+ }
+}
diff --git a/sdks/dotnet/basyx-core/BaSyx.Utils/BaSyx.Utils.csproj b/sdks/dotnet/basyx-core/BaSyx.Utils/BaSyx.Utils.csproj
new file mode 100644
index 0000000..dd8b905
--- /dev/null
+++ b/sdks/dotnet/basyx-core/BaSyx.Utils/BaSyx.Utils.csproj
@@ -0,0 +1,45 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+ <PropertyGroup>
+ <TargetFramework>netstandard2.0</TargetFramework>
+ <Configurations>Debug;Release</Configurations>
+ <AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
+ <Authors>Constantin Ziesche</Authors>
+ <Copyright>Copyright 2019 - Robert Bosch GmbH</Copyright>
+ <PackageProjectUrl>https://wiki.eclipse.org/BaSyx</PackageProjectUrl>
+ <RepositoryUrl>https://git.eclipse.org/r/plugins/gitiles/basyx/basyx/+/master/sdks/csnet/</RepositoryUrl>
+ <PackageLicenseUrl></PackageLicenseUrl>
+ <Description>The official BaSyx Collection of Utility Functions for building a BaSys Environment</Description>
+ <Company>Robert Bosch GmbH</Company>
+ <PackageTags>BaSys BaSyx Utils Utilities</PackageTags>
+ <PackageLicenseExpression>EPL-2.0</PackageLicenseExpression>
+ <RunPostBuildEvent>OnOutputUpdated</RunPostBuildEvent>
+ <PackageIcon>basyxlogo.png</PackageIcon>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <None Include="basyxlogo.png" Pack="true" PackagePath="\" />
+ </ItemGroup>
+
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+ <DebugType>full</DebugType>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <PackageReference Include="M2MqttDotnetCore" Version="1.0.8" />
+ <PackageReference Include="Microsoft.AspNetCore.Mvc.Abstractions" Version="2.2.0" />
+ <PackageReference Include="Microsoft.AspNetCore.Mvc.Formatters.Json" Version="2.2.0" />
+ <PackageReference Include="Microsoft.AspNetCore.Rewrite" Version="2.2.0" />
+ <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="3.1.0" />
+ <PackageReference Include="Microsoft.Extensions.Options" Version="3.1.0" />
+ <PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
+ <PackageReference Include="NLog" Version="4.6.8" />
+ <PackageReference Include="NLog.Web.AspNetCore" Version="4.9.0" />
+ <PackageReference Include="XSerializer" Version="0.4.2" />
+ </ItemGroup>
+
+ <Target Name="PostBuild" AfterTargets="PostBuildEvent">
+ <Exec Command="IF EXIST %25BASYX_REPO%25 ( dotnet pack "$(ProjectPath)" --no-build --include-source --include-symbols --output "%25BASYX_REPO%25" ) ELSE ( ECHO BASYX_REPO Environment Variable not found)" />
+ </Target>
+
+</Project>
diff --git a/sdks/dotnet/basyx-core/BaSyx.Utils/Client/Http/LegacyHttpClient.cs b/sdks/dotnet/basyx-core/BaSyx.Utils/Client/Http/LegacyHttpClient.cs
new file mode 100644
index 0000000..f4bfd38
--- /dev/null
+++ b/sdks/dotnet/basyx-core/BaSyx.Utils/Client/Http/LegacyHttpClient.cs
@@ -0,0 +1,69 @@
+/*******************************************************************************
+* Copyright (c) 2020 Robert Bosch GmbH
+* Author: Constantin Ziesche (constantin.ziesche@bosch.com)
+*
+* This program and the accompanying materials are made available under the
+* terms of the Eclipse Public License 2.0 which is available at
+* http://www.eclipse.org/legal/epl-2.0
+*
+* SPDX-License-Identifier: EPL-2.0
+*******************************************************************************/
+using Newtonsoft.Json;
+using System;
+using System.Net;
+using System.Text;
+
+namespace BaSyx.Utils.Client.Http
+{
+ public class LegacyHttpClient
+ {
+ private static readonly JsonSerializerSettings jsonSerializerSettings;
+
+ static LegacyHttpClient()
+ {
+ jsonSerializerSettings = new JsonSerializerSettings() { NullValueHandling = NullValueHandling.Ignore, Formatting = Formatting.Indented };
+ }
+
+ public HttpWebRequest CreateRequest(Uri uri, string method, object content)
+ {
+ var request = WebRequest.CreateHttp(uri);
+ request.Method = method;
+ request.Accept = "application/json";
+
+ if (content != null)
+ {
+ string body = JsonConvert.SerializeObject(content, jsonSerializerSettings);
+ if (!string.IsNullOrEmpty(body))
+ {
+ var payload = Encoding.ASCII.GetBytes(body);
+ request.ContentType = "application/json";
+ request.ContentLength = payload.Length;
+
+ using (var stream = request.GetRequestStream())
+ {
+ stream.Write(payload, 0, payload.Length);
+ }
+ }
+ }
+ return request;
+ }
+
+ private HttpWebResponse SendRequest(HttpWebRequest request)
+ {
+ try
+ {
+ var response = (HttpWebResponse)request.GetResponse();
+ return response;
+ }
+ catch (Exception e)
+ {
+ Console.Out.WriteLine(e.Message);
+ if (e is WebException)
+ return (HttpWebResponse)((WebException)e).Response;
+ else if (e.InnerException != null && e.InnerException is WebException)
+ return (HttpWebResponse)((WebException)e.InnerException).Response;
+ return null;
+ }
+ }
+ }
+}
diff --git a/sdks/dotnet/basyx-core/BaSyx.Utils/Client/Http/SimpleHttpClient.cs b/sdks/dotnet/basyx-core/BaSyx.Utils/Client/Http/SimpleHttpClient.cs
new file mode 100644
index 0000000..d52d1a9
--- /dev/null
+++ b/sdks/dotnet/basyx-core/BaSyx.Utils/Client/Http/SimpleHttpClient.cs
@@ -0,0 +1,193 @@
+/*******************************************************************************
+* Copyright (c) 2020 Robert Bosch GmbH
+* Author: Constantin Ziesche (constantin.ziesche@bosch.com)
+*
+* This program and the accompanying materials are made available under the
+* terms of the Eclipse Public License 2.0 which is available at
+* http://www.eclipse.org/legal/epl-2.0
+*
+* SPDX-License-Identifier: EPL-2.0
+*******************************************************************************/
+using BaSyx.Utils.ResultHandling;
+using BaSyx.Utils.Settings.Sections;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+using System;
+using System.Collections.Generic;
+using System.Net;
+using System.Net.Http;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace BaSyx.Utils.Client.Http
+{
+ public abstract class SimpleHttpClient
+ {
+ public HttpClient HttpClient { get; }
+ public HttpClientHandler HttpClientHandler { get; }
+ public JsonSerializerSettings JsonSerializerSettings { get; protected set; }
+
+ protected SimpleHttpClient()
+ {
+ HttpClientHandler = new HttpClientHandler() { MaxConnectionsPerServer = 100, UseProxy = false };
+ HttpClient = new HttpClient(HttpClientHandler);
+
+ JsonSerializerSettings = new JsonSerializerSettings() { NullValueHandling = NullValueHandling.Ignore, Formatting = Formatting.Indented, DefaultValueHandling = DefaultValueHandling.Include };
+ }
+
+ protected virtual void LoadProxy(ProxyConfiguration proxyConfiguration)
+ {
+ if (proxyConfiguration == null)
+ return;
+
+ if (proxyConfiguration.UseProxy && !string.IsNullOrEmpty(proxyConfiguration.ProxyAddress))
+ {
+ HttpClientHandler.UseProxy = true;
+ if (!string.IsNullOrEmpty(proxyConfiguration.UserName) && !string.IsNullOrEmpty(proxyConfiguration.Password))
+ {
+ NetworkCredential credential;
+ if (!string.IsNullOrEmpty(proxyConfiguration.Domain))
+ credential = new NetworkCredential(proxyConfiguration.UserName, proxyConfiguration.Password, proxyConfiguration.Domain);
+ else
+ credential = new NetworkCredential(proxyConfiguration.UserName, proxyConfiguration.Password);
+
+ HttpClientHandler.Proxy = new WebProxy(proxyConfiguration.ProxyAddress, false, null, credential);
+ }
+ else
+ HttpClientHandler.Proxy = new WebProxy(proxyConfiguration.ProxyAddress);
+ }
+ else
+ HttpClientHandler.UseProxy = false;
+ }
+
+ protected virtual IResult<HttpResponseMessage> SendRequest(HttpRequestMessage message, int timeout)
+ {
+ try
+ {
+ var task = HttpClient.SendAsync(message);
+ if (Task.WhenAny(task, Task.Delay(timeout)).Result == task)
+ {
+ return new Result<HttpResponseMessage>(true, task.Result);
+ }
+ else
+ {
+ return new Result<HttpResponseMessage>(false, new List<IMessage> { new Message(MessageType.Error, "Error while sending the request: timeout") });
+ }
+ }
+ catch (Exception e)
+ {
+ return new Result<HttpResponseMessage>(e);
+ }
+ }
+
+ protected virtual HttpRequestMessage CreateRequest(Uri uri, HttpMethod method)
+ {
+ return new HttpRequestMessage(method, uri);
+ }
+
+ protected virtual HttpRequestMessage CreateRequest(Uri uri, HttpMethod method, HttpContent content)
+ {
+ var message = CreateRequest(uri, method);
+ if (content != null)
+ message.Content = content;
+
+ return message;
+ }
+
+ protected virtual HttpRequestMessage CreateJsonContentRequest(Uri uri, HttpMethod method, object content)
+ {
+ var message = CreateRequest(uri, method, () =>
+ {
+ var serialized = JsonConvert.SerializeObject(content, JsonSerializerSettings);
+ return new StringContent(serialized, Encoding.UTF8, "application/json");
+ });
+ return message;
+ }
+
+ protected virtual HttpRequestMessage CreateRequest(Uri uri, HttpMethod method, Func<HttpContent> content)
+ {
+ var message = CreateRequest(uri, method);
+ if (content != null)
+ message.Content = content.Invoke();
+
+ return message;
+ }
+
+ protected virtual IResult EvaluateResponse(IResult result, HttpResponseMessage response)
+ {
+ var messageList = new List<IMessage>();
+ messageList.AddRange(result.Messages);
+
+ if (response != null)
+ {
+ var responseString = response.Content.ReadAsStringAsync().Result;
+ if (response.IsSuccessStatusCode)
+ {
+ messageList.Add(new Message(MessageType.Information, response.ReasonPhrase, ((int)response.StatusCode).ToString()));
+ return new Result(true, messageList);
+ }
+ else
+ {
+ messageList.Add(new Message(MessageType.Error, response.ReasonPhrase + "| " + responseString, ((int)response.StatusCode).ToString()));
+ return new Result(false, messageList);
+ }
+ }
+ messageList.Add(new Message(MessageType.Error, "Evaluation of response failed - Response from host is null", null));
+ return new Result(false, messageList);
+ }
+
+ protected virtual IResult<T> EvaluateResponse<T>(IResult result, HttpResponseMessage response)
+ {
+ var messageList = new List<IMessage>();
+ messageList.AddRange(result.Messages);
+
+ if (response != null)
+ {
+ var responseString = response.Content.ReadAsStringAsync().Result;
+ if (response.IsSuccessStatusCode)
+ {
+ try
+ {
+ responseString = CheckAndExtractResultContruct(responseString);
+ var requestResult = JsonConvert.DeserializeObject<T>(responseString, JsonSerializerSettings);
+
+ messageList.Add(new Message(MessageType.Information, response.ReasonPhrase, ((int)response.StatusCode).ToString()));
+ return new Result<T>(true, requestResult, messageList);
+ }
+ catch (Exception e)
+ {
+ messageList.Add(new Message(MessageType.Error, e.Message, e.HelpLink));
+ return new Result<T>(false, messageList);
+ }
+ }
+ else
+ {
+ messageList.Add(new Message(MessageType.Error, response.ReasonPhrase + "| " + responseString, ((int)response.StatusCode).ToString()));
+ return new Result<T>(false, messageList);
+ }
+ }
+ messageList.Add(new Message(MessageType.Error, "Evaluation of response failed - Response from host is null", null));
+ return new Result<T>(false, messageList);
+ }
+
+ private string CheckAndExtractResultContruct(string responseString)
+ {
+ if (responseString == null)
+ return null;
+
+ try
+ {
+ JToken jToken = JToken.Parse(responseString);
+ var jEntity = jToken.SelectToken("entity");
+ if (jEntity != null)
+ return jEntity.ToString();
+ else
+ return responseString;
+ }
+ catch
+ {
+ return responseString;
+ }
+ }
+ }
+}
diff --git a/sdks/dotnet/basyx-core/BaSyx.Utils/Client/IMessageClient.cs b/sdks/dotnet/basyx-core/BaSyx.Utils/Client/IMessageClient.cs
new file mode 100644
index 0000000..d3b73da
--- /dev/null
+++ b/sdks/dotnet/basyx-core/BaSyx.Utils/Client/IMessageClient.cs
@@ -0,0 +1,41 @@
+/*******************************************************************************
+* Copyright (c) 2020 Robert Bosch GmbH
+* Author: Constantin Ziesche (constantin.ziesche@bosch.com)
+*
+* This program and the accompanying materials are made available under the
+* terms of the Eclipse Public License 2.0 which is available at
+* http://www.eclipse.org/legal/epl-2.0
+*
+* SPDX-License-Identifier: EPL-2.0
+*******************************************************************************/
+using System;
+using System.Collections.Generic;
+using System.Text;
+using BaSyx.Utils.ResultHandling;
+
+namespace BaSyx.Utils.Client
+{
+ public interface IMessageClient
+ {
+ bool IsConnected { get; }
+
+ IResult Publish(string topic, string message, Action<IMessagePublishedEventArgs> messagePublishedHandler, byte qosLevel, bool retain);
+ IResult Subscribe(string topic, Action<IMessageReceivedEventArgs> messageReceivedHandler, byte qosLevel);
+ IResult Unsubscribe(string topic);
+
+ IResult Start();
+ IResult Stop();
+ }
+ public interface IMessagePublishedEventArgs
+ {
+ bool IsPublished { get; }
+ string MessageId { get; }
+ }
+ public interface IMessageReceivedEventArgs
+ {
+ string Message { get; }
+ string Topic { get; }
+ byte QosLevel { get; }
+ }
+
+}
diff --git a/sdks/dotnet/basyx-core/BaSyx.Utils/Client/Mqtt/MqttConfig.cs b/sdks/dotnet/basyx-core/BaSyx.Utils/Client/Mqtt/MqttConfig.cs
new file mode 100644
index 0000000..756761a
--- /dev/null
+++ b/sdks/dotnet/basyx-core/BaSyx.Utils/Client/Mqtt/MqttConfig.cs
@@ -0,0 +1,104 @@
+/*******************************************************************************
+* Copyright (c) 2020 Robert Bosch GmbH
+* Author: Constantin Ziesche (constantin.ziesche@bosch.com)
+*
+* This program and the accompanying materials are made available under the
+* terms of the Eclipse Public License 2.0 which is available at
+* http://www.eclipse.org/legal/epl-2.0
+*
+* SPDX-License-Identifier: EPL-2.0
+*******************************************************************************/
+using BaSyx.Utils.Config;
+using BaSyx.Utils.Security;
+using System.Security.Cryptography.X509Certificates;
+using System.Xml.Serialization;
+
+namespace BaSyx.Utils.Client.Mqtt
+{
+ public class MqttConfig : IEventHandlerConfig
+ {
+ [XmlElement]
+ public string ClientId { get; set; }
+ [XmlElement]
+ public string BrokerEndpoint { get; set; }
+ [XmlIgnore]
+ public ICredentials Credentials { get; set; }
+ [XmlElement]
+ public bool SecureConnection { get; set; } = false;
+ [XmlElement]
+ public int PublishTimeout { get; set; } = 5000;
+ [XmlElement]
+ public int ReceiveTimeout { get; set; } = 5000;
+ [XmlIgnore]
+ public ISecurity Security { get; set; }
+ [XmlElement]
+ public MqttConnectConfig MqttConnectConfig { get; set; }
+
+ internal MqttConfig() { }
+
+ public MqttConfig(string clientId, string brokerEndpoint)
+ {
+ ClientId = clientId;
+ BrokerEndpoint = brokerEndpoint;
+ MqttConnectConfig = new MqttConnectConfig();
+ }
+ public MqttConfig(string clientId, string brokerEndpoint, MqttCredentials credentials) : this(clientId, brokerEndpoint)
+ {
+ Credentials = credentials;
+ }
+
+ public MqttConfig(string clientId, string brokerEndpoint, MqttCredentials credentials, MqttSecurity security) : this(clientId, brokerEndpoint, credentials)
+ {
+ Security = security;
+ }
+ }
+
+ public class MqttConnectConfig
+ {
+ [XmlElement]
+ public bool WillRetain { get; set; } = false;
+ [XmlElement]
+ public byte WillQosLevel { get; set; } = 0;
+ [XmlElement]
+ public bool WillFlag { get; set; } = false;
+ [XmlElement]
+ public string WillTopic { get; set; } = null;
+ [XmlElement]
+ public string WillMessage { get; set; } = null;
+ [XmlElement]
+ public bool CleanSession { get; set; } = true;
+ [XmlElement]
+ public ushort KeepAlivePeriod { get; set; } = 60;
+ }
+
+ public class MqttCredentials : ICredentials
+ {
+ public string UserName { get; set; }
+ public string Password { get; set; }
+
+ internal MqttCredentials()
+ { }
+
+ public MqttCredentials(string userName, string password)
+ {
+ UserName = userName;
+ Password = password;
+ }
+ }
+
+ public class MqttSecurity : ISecurity
+ {
+ public X509Certificate CaCert { get; }
+ public X509Certificate ClientCert { get; }
+
+ public MqttSecurity(X509Certificate caCert)
+ {
+ CaCert = caCert;
+ }
+ public MqttSecurity(X509Certificate caCert, X509Certificate clientCert)
+ {
+ CaCert = caCert;
+ ClientCert = clientCert;
+ }
+ }
+}
diff --git a/sdks/dotnet/basyx-core/BaSyx.Utils/Client/Mqtt/SimpleMqttClient.cs b/sdks/dotnet/basyx-core/BaSyx.Utils/Client/Mqtt/SimpleMqttClient.cs
new file mode 100644
index 0000000..d7676ff
--- /dev/null
+++ b/sdks/dotnet/basyx-core/BaSyx.Utils/Client/Mqtt/SimpleMqttClient.cs
@@ -0,0 +1,229 @@
+/*******************************************************************************
+* Copyright (c) 2020 Robert Bosch GmbH
+* Author: Constantin Ziesche (constantin.ziesche@bosch.com)
+*
+* This program and the accompanying materials are made available under the
+* terms of the Eclipse Public License 2.0 which is available at
+* http://www.eclipse.org/legal/epl-2.0
+*
+* SPDX-License-Identifier: EPL-2.0
+*******************************************************************************/
+using System;
+using System.Text;
+using System.Threading;
+using NLog;
+using uPLibrary.Networking.M2Mqtt;
+using uPLibrary.Networking.M2Mqtt.Messages;
+using BaSyx.Utils.ResultHandling;
+using System.Security.Cryptography.X509Certificates;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace BaSyx.Utils.Client.Mqtt
+{
+ public class SimpleMqttClient : IMessageClient
+ {
+ private MqttClient mqttClient;
+ public readonly MqttConfig MqttConfig;
+
+ private Dictionary<string, Action<IMessageReceivedEventArgs>> topicMessageReceivedHandler = new Dictionary<string, Action<IMessageReceivedEventArgs>>();
+ private Action<IMessagePublishedEventArgs> msgPublishedMethod = null;
+
+ private ManualResetEvent connectionClosedResetEvent;
+ public EventHandler<EventArgs> ConnectionClosed;
+
+ private static Logger logger = LogManager.GetCurrentClassLogger();
+
+ public SimpleMqttClient(MqttConfig config)
+ {
+ MqttConfig = config;
+ }
+
+ public bool IsConnected
+ {
+ get
+ {
+ if (mqttClient != null)
+ return mqttClient.IsConnected;
+ else
+ return false;
+ }
+ }
+ public IResult Publish(string topic, string message) => Publish(topic, message, null, 2, false);
+
+ public IResult Publish(string topic, string message, Action<IMessagePublishedEventArgs> messagePublishedHandler, byte qosLevel, bool retain = false)
+ {
+ if (messagePublishedHandler != null && this.msgPublishedMethod == null)
+ {
+ msgPublishedMethod = messagePublishedHandler;
+ mqttClient.MqttMsgPublished += MqttClient_MqttMsgPublished;
+ }
+
+ byte[] bMessage = Encoding.UTF8.GetBytes(message);
+
+ ushort messageId = mqttClient.Publish(topic, bMessage, qosLevel, retain);
+ return new Result<ushort>(true, messageId);
+ }
+
+ public IResult Subscribe(string topic, Action<IMessageReceivedEventArgs> messageReceivedHandler, byte qosLevel)
+ {
+ if (string.IsNullOrEmpty(topic))
+ return new Result(new ArgumentNullException("topic", "The topic is null or empty"));
+ if (messageReceivedHandler == null)
+ return new Result(new ArgumentNullException("messageReceivedHandler", "The message received delegate cannot be null since subscribed messages cannot be received"));
+
+ if (!topicMessageReceivedHandler.ContainsKey(topic))
+ topicMessageReceivedHandler.Add(topic, messageReceivedHandler);
+
+ ushort messageId = mqttClient.Subscribe(new string[] { topic }, new byte[] { qosLevel });
+ return new Result<ushort>(true, messageId);
+ }
+
+ public IResult Unsubscribe(string topic)
+ {
+ if (topicMessageReceivedHandler.ContainsKey(topic))
+ topicMessageReceivedHandler.Remove(topic);
+
+ ushort messageId = mqttClient.Unsubscribe(new string[] { topic });
+ return new Result<ushort>(true, messageId);
+ }
+
+ private void MqttClient_MqttMsgPublishReceived(object sender, MqttMsgPublishEventArgs e)
+ {
+ string parentOrSelfTopic = GetParentOrSelfTopic(e.Topic, topicMessageReceivedHandler.Keys);
+ if (topicMessageReceivedHandler.TryGetValue(parentOrSelfTopic, out Action<IMessageReceivedEventArgs> action))
+ action.Invoke(new MqttMsgReceivedEventArgs(e));
+ }
+
+ private string GetParentOrSelfTopic(string topic, Dictionary<string, Action<IMessageReceivedEventArgs>>.KeyCollection keys)
+ {
+ foreach (var key in keys)
+ {
+ if (key == topic)
+ return key;
+ else
+ {
+ string[] splittedKey = key.Split('/');
+ string[] splittedTopic = topic.Split('/');
+ int minLength = Math.Min(splittedKey.Length, splittedTopic.Length);
+ for (int i = 0; i < minLength; i++)
+ {
+ if (splittedKey[i] != splittedTopic[i])
+ {
+ if (splittedKey[i] == "#")
+ return key;
+ }
+ }
+
+ }
+ }
+ return topic;
+ }
+
+ private void MqttClient_MqttMsgSubscribed(object sender, MqttMsgSubscribedEventArgs e)
+ {
+ logger.Debug("Subscribed for id = " + e.MessageId);
+ }
+
+ private void MqttClient_MqttMsgPublished(object sender, uPLibrary.Networking.M2Mqtt.Messages.MqttMsgPublishedEventArgs e)
+ {
+ logger.Debug("Published for id = " + e.MessageId);
+ msgPublishedMethod?.Invoke(new MqttMsgPublishedEventArgs(e));
+ }
+
+ public IResult Start()
+ {
+ Uri endpoint = new Uri(MqttConfig.BrokerEndpoint);
+ MqttSslProtocols protocols = MqttConfig.SecureConnection ? MqttSslProtocols.TLSv1_2 : MqttSslProtocols.None;
+ X509Certificate caCert = null;
+ X509Certificate clientCert = null;
+ if (MqttConfig.Security != null && MqttConfig.Security is MqttSecurity security)
+ {
+ caCert = security.CaCert ?? null;
+ clientCert = security.ClientCert ?? null;
+ }
+ mqttClient = new MqttClient(endpoint.Host, endpoint.Port, MqttConfig.SecureConnection, caCert, clientCert, protocols, null, null);
+ mqttClient.ConnectionClosed += MqttClient_ConnectionClosed;
+
+ try
+ {
+ byte success;
+ MqttConnectConfig config = MqttConfig.MqttConnectConfig;
+ if (MqttConfig.Credentials is MqttCredentials mqttCreds)
+ success = mqttClient.Connect(Guid.NewGuid().ToString(), mqttCreds.UserName, mqttCreds.Password, config.WillRetain, config.WillQosLevel, config.WillFlag, config.WillTopic, config.WillMessage, config.CleanSession, config.KeepAlivePeriod);
+ else
+ success = mqttClient.Connect(Guid.NewGuid().ToString(), null, null, config.WillRetain, config.WillQosLevel, config.WillFlag, config.WillTopic, config.WillMessage, config.CleanSession, config.KeepAlivePeriod);
+
+ if (success != 0)
+ return new Result(false, new Message(MessageType.Error, "Could not connect to MQTT Broker", success.ToString()));
+
+ mqttClient.MqttMsgPublishReceived += MqttClient_MqttMsgPublishReceived;
+ return new Result(true);
+ }
+ catch (Exception e)
+ {
+ logger.Error(e, "Could not connect MQTT-Broker");
+ return new Result(e);
+ }
+ }
+
+ public IResult Stop()
+ {
+ if (mqttClient != null)
+ {
+ if (mqttClient.IsConnected)
+ {
+ connectionClosedResetEvent = new ManualResetEvent(false);
+ //mqttClient.ConnectionClosed += MqttClient_ConnectionClosed;
+ mqttClient.MqttMsgPublishReceived -= MqttClient_MqttMsgPublishReceived;
+
+ mqttClient.Disconnect();
+
+ bool success = connectionClosedResetEvent.WaitOne(5000);
+
+ if (!success)
+ {
+ logger.Error("Could not close MQTT-Client");
+ return new Result(false, new Message(MessageType.Error, "Could not close MQTT-Client"));
+ }
+ }
+ mqttClient = null;
+ }
+ return new Result(true);
+ }
+
+ private void MqttClient_ConnectionClosed(object sender, EventArgs e)
+ {
+ connectionClosedResetEvent?.Set();
+ ConnectionClosed?.Invoke(sender, e);
+ }
+ }
+
+ public class MqttMsgPublishedEventArgs : IMessagePublishedEventArgs
+ {
+ public bool IsPublished { get; }
+ public string MessageId { get; }
+ public MqttMsgPublishedEventArgs(uPLibrary.Networking.M2Mqtt.Messages.MqttMsgPublishedEventArgs e)
+ {
+ IsPublished = e.IsPublished;
+ MessageId = e.MessageId.ToString();
+ }
+ }
+ public class MqttMsgReceivedEventArgs : IMessageReceivedEventArgs
+ {
+ public string Message { get; }
+ public string Topic { get; }
+ public byte QosLevel { get; }
+ public bool Retain { get; }
+ public bool DupFlag { get; }
+ public MqttMsgReceivedEventArgs(MqttMsgPublishEventArgs e)
+ {
+ Message = Encoding.UTF8.GetString(e.Message);
+ Topic = e.Topic;
+ QosLevel = e.QosLevel;
+ Retain = e.Retain;
+ DupFlag = e.DupFlag;
+ }
+ }
+
+}
diff --git a/sdks/dotnet/basyx-core/BaSyx.Utils/Config/IEventHandlerConfig.cs b/sdks/dotnet/basyx-core/BaSyx.Utils/Config/IEventHandlerConfig.cs
new file mode 100644
index 0000000..20a0f15
--- /dev/null
+++ b/sdks/dotnet/basyx-core/BaSyx.Utils/Config/IEventHandlerConfig.cs
@@ -0,0 +1,24 @@
+/*******************************************************************************
+* Copyright (c) 2020 Robert Bosch GmbH
+* Author: Constantin Ziesche (constantin.ziesche@bosch.com)
+*
+* This program and the accompanying materials are made available under the
+* terms of the Eclipse Public License 2.0 which is available at
+* http://www.eclipse.org/legal/epl-2.0
+*
+* SPDX-License-Identifier: EPL-2.0
+*******************************************************************************/
+using BaSyx.Utils.Security;
+
+namespace BaSyx.Utils.Config
+{
+ public interface IEventHandlerConfig
+ {
+ string ClientId { get; }
+ string BrokerEndpoint { get; }
+ int PublishTimeout { get; }
+ int ReceiveTimeout { get; }
+ ICredentials Credentials { get; }
+ ISecurity Security { get; }
+ }
+}
diff --git a/sdks/dotnet/basyx-core/BaSyx.Utils/DIExtensions/DIExtension.cs b/sdks/dotnet/basyx-core/BaSyx.Utils/DIExtensions/DIExtension.cs
new file mode 100644
index 0000000..6024254
--- /dev/null
+++ b/sdks/dotnet/basyx-core/BaSyx.Utils/DIExtensions/DIExtension.cs
@@ -0,0 +1,36 @@
+/*******************************************************************************
+* Copyright (c) 2020 Robert Bosch GmbH
+* Author: Constantin Ziesche (constantin.ziesche@bosch.com)
+*
+* This program and the accompanying materials are made available under the
+* terms of the Eclipse Public License 2.0 which is available at
+* http://www.eclipse.org/legal/epl-2.0
+*
+* SPDX-License-Identifier: EPL-2.0
+*******************************************************************************/
+using Microsoft.Extensions.DependencyInjection;
+using System;
+using System.Collections.Generic;
+
+namespace BaSyx.Utils.DIExtensions
+{
+ public class DIExtension : IDIExtension
+ {
+ IDictionary<Type, Type> typeDictionary = new Dictionary<Type, Type>();
+ public DIExtension(IServiceCollection serviceCollection)
+ {
+ foreach (var service in serviceCollection)
+ {
+ typeDictionary[service.ServiceType] = service.ImplementationType;
+ }
+ }
+ public Type GetRegisteredTypeFor(Type t)
+ {
+ return typeDictionary[t];
+ }
+ public bool IsTypeRegistered(Type t)
+ {
+ return typeDictionary.ContainsKey(t);
+ }
+ }
+}
diff --git a/sdks/dotnet/basyx-core/BaSyx.Utils/DIExtensions/IDIExtension.cs b/sdks/dotnet/basyx-core/BaSyx.Utils/DIExtensions/IDIExtension.cs
new file mode 100644
index 0000000..cddc15a
--- /dev/null
+++ b/sdks/dotnet/basyx-core/BaSyx.Utils/DIExtensions/IDIExtension.cs
@@ -0,0 +1,20 @@
+/*******************************************************************************
+* Copyright (c) 2020 Robert Bosch GmbH
+* Author: Constantin Ziesche (constantin.ziesche@bosch.com)
+*
+* This program and the accompanying materials are made available under the
+* terms of the Eclipse Public License 2.0 which is available at
+* http://www.eclipse.org/legal/epl-2.0
+*
+* SPDX-License-Identifier: EPL-2.0
+*******************************************************************************/
+using System;
+
+namespace BaSyx.Utils.DIExtensions
+{
+ public interface IDIExtension
+ {
+ Type GetRegisteredTypeFor(Type t);
+ bool IsTypeRegistered(Type t);
+ }
+}
diff --git a/sdks/dotnet/basyx-core/BaSyx.Utils/DIExtensions/StandardDIConfiguration.cs b/sdks/dotnet/basyx-core/BaSyx.Utils/DIExtensions/StandardDIConfiguration.cs
new file mode 100644
index 0000000..c12a48e
--- /dev/null
+++ b/sdks/dotnet/basyx-core/BaSyx.Utils/DIExtensions/StandardDIConfiguration.cs
@@ -0,0 +1,32 @@
+/*******************************************************************************
+* Copyright (c) 2020 Robert Bosch GmbH
+* Author: Constantin Ziesche (constantin.ziesche@bosch.com)
+*
+* This program and the accompanying materials are made available under the
+* terms of the Eclipse Public License 2.0 which is available at
+* http://www.eclipse.org/legal/epl-2.0
+*
+* SPDX-License-Identifier: EPL-2.0
+*******************************************************************************/
+using BaSyx.Utils.JsonHandling;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.DependencyInjection.Extensions;
+using Microsoft.Extensions.Options;
+
+namespace BaSyx.Utils.DIExtensions
+{
+ public static class StandardDIConfiguration
+ {
+ public static IServiceCollection ConfigureStandardDI(this IServiceCollection services)
+ {
+ services.TryAddSingleton<IDIExtension>(s =>
+ {
+ return new DIExtension(services);
+ });
+ services.AddTransient<IConfigureOptions<MvcJsonOptions>, JsonOptionsSetup>();
+
+ return services;
+ }
+ }
+}
diff --git a/sdks/dotnet/basyx-core/BaSyx.Utils/JsonHandling/CustomTypeSerializer.cs b/sdks/dotnet/basyx-core/BaSyx.Utils/JsonHandling/CustomTypeSerializer.cs
new file mode 100644
index 0000000..b65fbae
--- /dev/null
+++ b/sdks/dotnet/basyx-core/BaSyx.Utils/JsonHandling/CustomTypeSerializer.cs
@@ -0,0 +1,81 @@
+/*******************************************************************************
+* Copyright (c) 2020 Robert Bosch GmbH
+* Author: Constantin Ziesche (constantin.ziesche@bosch.com)
+*
+* This program and the accompanying materials are made available under the
+* terms of the Eclipse Public License 2.0 which is available at
+* http://www.eclipse.org/legal/epl-2.0
+*
+* SPDX-License-Identifier: EPL-2.0
+*******************************************************************************/
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+using System;
+
+namespace BaSyx.Utils.JsonHandling
+{
+ public class CustomTypeSerializer : JsonConverter
+ {
+ [ThreadStatic]
+ static bool disabled;
+
+ bool Disabled { get { return disabled; } set { disabled = value; } }
+
+ public override bool CanWrite { get { return !Disabled; } }
+
+ public override bool CanConvert(Type objectType)
+ {
+ return true;
+ }
+ public override bool CanRead => false;
+
+ public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
+ {
+ throw new NotImplementedException();
+ }
+
+ public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
+ {
+ JToken t;
+ using (new PushValue<bool>(true, () => Disabled, (canWrite) => Disabled = canWrite))
+ {
+ t = JToken.FromObject(value, serializer);
+ }
+
+ if (t.Type != JTokenType.Object)
+ {
+ t.WriteTo(writer);
+ }
+ else
+ {
+ JObject o = (JObject)t;
+ Type type = value.GetType();
+ JObject typeWrapper = new JObject(new JProperty(type.Namespace + "." + type.Name, o));
+
+ typeWrapper.WriteTo(writer);
+ }
+ }
+ }
+
+ public struct PushValue<T> : IDisposable
+ {
+ Func<T> getValue;
+ Action<T> setValue;
+ T oldValue;
+
+ public PushValue(T value, Func<T> getValue, Action<T> setValue)
+ {
+ if (getValue == null || setValue == null)
+ throw new ArgumentNullException();
+ this.getValue = getValue;
+ this.setValue = setValue;
+ this.oldValue = getValue();
+ setValue(value);
+ }
+
+ public void Dispose()
+ {
+ setValue?.Invoke(oldValue);
+ }
+ }
+}
diff --git a/sdks/dotnet/basyx-core/BaSyx.Utils/JsonHandling/DIContractResolver.cs b/sdks/dotnet/basyx-core/BaSyx.Utils/JsonHandling/DIContractResolver.cs
new file mode 100644
index 0000000..8cd421c
--- /dev/null
+++ b/sdks/dotnet/basyx-core/BaSyx.Utils/JsonHandling/DIContractResolver.cs
@@ -0,0 +1,46 @@
+/*******************************************************************************
+* Copyright (c) 2020 Robert Bosch GmbH
+* Author: Constantin Ziesche (constantin.ziesche@bosch.com)
+*
+* This program and the accompanying materials are made available under the
+* terms of the Eclipse Public License 2.0 which is available at
+* http://www.eclipse.org/legal/epl-2.0
+*
+* SPDX-License-Identifier: EPL-2.0
+*******************************************************************************/
+using BaSyx.Utils.DIExtensions;
+using Newtonsoft.Json.Serialization;
+using System;
+
+namespace BaSyx.Utils.JsonHandling
+{
+ public class DIContractResolver : CamelCasePropertyNamesContractResolver
+ {
+ public IDIExtension DIExtension { get; }
+ public IServiceProvider ServiceProvider { get; }
+ public DIContractResolver(IDIExtension diExtension, IServiceProvider serviceProvider)
+ {
+ DIExtension = diExtension;
+ ServiceProvider = serviceProvider;
+ }
+ protected override JsonObjectContract CreateObjectContract(Type objectType)
+ {
+ if (DIExtension.IsTypeRegistered(objectType))
+ {
+ JsonObjectContract contract = DIResolveContract(objectType);
+ contract.DefaultCreator = () => ServiceProvider.GetService(objectType);
+ return contract;
+ }
+
+ return base.CreateObjectContract(objectType);
+ }
+ private JsonObjectContract DIResolveContract(Type objectType)
+ {
+ var registeredType = DIExtension.GetRegisteredTypeFor(objectType);
+ if (registeredType != null)
+ return base.CreateObjectContract(registeredType);
+ else
+ return CreateObjectContract(objectType);
+ }
+ }
+}
diff --git a/sdks/dotnet/basyx-core/BaSyx.Utils/JsonHandling/JsonOptionsSetup.cs b/sdks/dotnet/basyx-core/BaSyx.Utils/JsonHandling/JsonOptionsSetup.cs
new file mode 100644
index 0000000..6cee2c1
--- /dev/null
+++ b/sdks/dotnet/basyx-core/BaSyx.Utils/JsonHandling/JsonOptionsSetup.cs
@@ -0,0 +1,37 @@
+/*******************************************************************************
+* Copyright (c) 2020 Robert Bosch GmbH
+* Author: Constantin Ziesche (constantin.ziesche@bosch.com)
+*
+* This program and the accompanying materials are made available under the
+* terms of the Eclipse Public License 2.0 which is available at
+* http://www.eclipse.org/legal/epl-2.0
+*
+* SPDX-License-Identifier: EPL-2.0
+*******************************************************************************/
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Options;
+using Microsoft.Extensions.DependencyInjection;
+using System;
+using BaSyx.Utils.DIExtensions;
+
+namespace BaSyx.Utils.JsonHandling
+{
+ public class JsonOptionsSetup : IConfigureOptions<MvcJsonOptions>
+ {
+ IServiceProvider serviceProvider;
+
+ public MvcJsonOptions Options { get; private set; }
+ public JsonOptionsSetup(IServiceProvider serviceProvider)
+ {
+ this.serviceProvider = serviceProvider;
+ }
+ public void Configure(MvcJsonOptions options)
+ {
+ options.SerializerSettings.ContractResolver = new DIContractResolver(serviceProvider.GetService<IDIExtension>(), serviceProvider);
+ options.SerializerSettings.NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore;
+ options.SerializerSettings.Converters.Add(new Newtonsoft.Json.Converters.StringEnumConverter());
+
+ Options = options;
+ }
+ }
+}
diff --git a/sdks/dotnet/basyx-core/BaSyx.Utils/JsonHandling/PrivatePropertyContractResolver.cs b/sdks/dotnet/basyx-core/BaSyx.Utils/JsonHandling/PrivatePropertyContractResolver.cs
new file mode 100644
index 0000000..e6acb36
--- /dev/null
+++ b/sdks/dotnet/basyx-core/BaSyx.Utils/JsonHandling/PrivatePropertyContractResolver.cs
@@ -0,0 +1,37 @@
+/*******************************************************************************
+* Copyright (c) 2020 Robert Bosch GmbH
+* Author: Constantin Ziesche (constantin.ziesche@bosch.com)
+*
+* This program and the accompanying materials are made available under the
+* terms of the Eclipse Public License 2.0 which is available at
+* http://www.eclipse.org/legal/epl-2.0
+*
+* SPDX-License-Identifier: EPL-2.0
+*******************************************************************************/
+using Newtonsoft.Json;
+using Newtonsoft.Json.Serialization;
+using System.Reflection;
+
+namespace BaSyx.Utils.JsonHandling
+{
+ public class PrivatePropertyContractResolver : DefaultContractResolver
+ {
+ protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
+ {
+ var prop = base.CreateProperty(member, memberSerialization);
+
+ if (!prop.Writable)
+ {
+ var property = member as PropertyInfo;
+ if (property != null)
+ {
+ var hasPrivateSetter = property.GetSetMethod(true) != null;
+ prop.Writable = hasPrivateSetter;
+ }
+ }
+
+ return prop;
+ }
+
+ }
+}
diff --git a/sdks/dotnet/basyx-core/BaSyx.Utils/Logging/LoggingExtentions.cs b/sdks/dotnet/basyx-core/BaSyx.Utils/Logging/LoggingExtentions.cs
new file mode 100644
index 0000000..ec5584c
--- /dev/null
+++ b/sdks/dotnet/basyx-core/BaSyx.Utils/Logging/LoggingExtentions.cs
@@ -0,0 +1,66 @@
+/*******************************************************************************
+* Copyright (c) 2020 Robert Bosch GmbH
+* Author: Constantin Ziesche (constantin.ziesche@bosch.com)
+*
+* This program and the accompanying materials are made available under the
+* terms of the Eclipse Public License 2.0 which is available at
+* http://www.eclipse.org/legal/epl-2.0
+*
+* SPDX-License-Identifier: EPL-2.0
+*******************************************************************************/
+using BaSyx.Utils.ResultHandling;
+using NLog;
+using System;
+using System.Net.Http;
+using System.Text;
+
+namespace BaSyx.Utils.Logging
+{
+ public static class LoggingExtentions
+ {
+ public static void LogResult(this IResult result, ILogger logger, LogLevel logLevel, string additionalText = null, Exception exp = null)
+ {
+ StringBuilder logText = new StringBuilder();
+ logText.Append("Success: " + result.Success).Append(" || ");
+
+ if (result.Messages != null)
+ {
+ for (int i = 0; i < result.Messages.Count; i++)
+ {
+ logText.Append("Message[" + i + "] = " + result.Messages[i].Text).Append(" || ");
+ }
+ }
+ if (result.Entity != null && result.Entity is HttpResponseMessage response)
+ {
+ logText.Append("StatusCode: " + ((int)response.StatusCode).ToString()).Append(response.ReasonPhrase).Append(" || ");
+ logText.Append("Body: " + response.Content.ReadAsStringAsync().Result).Append(" || ");
+ }
+ if (!string.IsNullOrEmpty(additionalText))
+ logText.Append("AdditionalText: " + additionalText).Append(" || ");
+
+ string msg = logText.ToString();
+ if (exp != null)
+ logger.Log(logLevel, exp, msg);
+ else
+ logger.Log(logLevel, msg);
+ }
+
+ public static Microsoft.Extensions.Logging.LogLevel GetLogLevel(ILogger logger)
+ {
+ if (logger.IsDebugEnabled)
+ return Microsoft.Extensions.Logging.LogLevel.Debug;
+ else if (logger.IsErrorEnabled)
+ return Microsoft.Extensions.Logging.LogLevel.Error;
+ else if (logger.IsFatalEnabled)
+ return Microsoft.Extensions.Logging.LogLevel.Critical;
+ else if (logger.IsInfoEnabled)
+ return Microsoft.Extensions.Logging.LogLevel.Information;
+ else if (logger.IsTraceEnabled)
+ return Microsoft.Extensions.Logging.LogLevel.Trace;
+ else if (logger.IsWarnEnabled)
+ return Microsoft.Extensions.Logging.LogLevel.Warning;
+ else
+ return Microsoft.Extensions.Logging.LogLevel.None;
+ }
+ }
+}
diff --git a/sdks/dotnet/basyx-core/BaSyx.Utils/ModelHandling/ModelUtils.cs b/sdks/dotnet/basyx-core/BaSyx.Utils/ModelHandling/ModelUtils.cs
new file mode 100644
index 0000000..bab5a82
--- /dev/null
+++ b/sdks/dotnet/basyx-core/BaSyx.Utils/ModelHandling/ModelUtils.cs
@@ -0,0 +1,31 @@
+/*******************************************************************************
+* Copyright (c) 2020 Robert Bosch GmbH
+* Author: Constantin Ziesche (constantin.ziesche@bosch.com)
+*
+* This program and the accompanying materials are made available under the
+* terms of the Eclipse Public License 2.0 which is available at
+* http://www.eclipse.org/legal/epl-2.0
+*
+* SPDX-License-Identifier: EPL-2.0
+*******************************************************************************/
+using System;
+using System.Collections.Generic;
+using System.Reflection;
+using System.Text;
+
+namespace BaSyx.Utils.ModelHandling
+{
+ public class ModelUtils
+ {
+ public static IEnumerable<Type> GetTypesWithAttribute(Assembly assembly, Type attributeType)
+ {
+ foreach (Type type in assembly.GetTypes())
+ {
+ if (type.GetCustomAttributes(attributeType, true).Length > 0)
+ {
+ yield return type;
+ }
+ }
+ }
+ }
+}
diff --git a/sdks/dotnet/basyx-core/BaSyx.Utils/ModelHandling/TreeBuilder.cs b/sdks/dotnet/basyx-core/BaSyx.Utils/ModelHandling/TreeBuilder.cs
new file mode 100644
index 0000000..818b290
--- /dev/null
+++ b/sdks/dotnet/basyx-core/BaSyx.Utils/ModelHandling/TreeBuilder.cs
@@ -0,0 +1,306 @@
+/*******************************************************************************
+* Copyright (c) 2020 Robert Bosch GmbH
+* Author: Constantin Ziesche (constantin.ziesche@bosch.com)
+*
+* This program and the accompanying materials are made available under the
+* terms of the Eclipse Public License 2.0 which is available at
+* http://www.eclipse.org/legal/epl-2.0
+*
+* SPDX-License-Identifier: EPL-2.0
+*******************************************************************************/
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Linq;
+
+namespace BaSyx.Utils.ModelHandling
+{
+ public class ObjectTreeBuilder : TreeBuilder<object>
+ {
+ protected new List<ObjectTreeBuilder> children;
+ public ObjectTreeBuilder(string rootObjectName, object rootObjectValue) : this(rootObjectName, new List<object>() { rootObjectValue })
+ { }
+ public ObjectTreeBuilder(string rootObjectName, List<object> rootObjectValues) : base(rootObjectName, rootObjectValues)
+ {
+ this.children = new List<ObjectTreeBuilder>();
+ }
+
+ public T GetValue<T>()
+ {
+ if (value != null)
+ return (T)value.FirstOrDefault(c => c.GetType().IsSubclassOf(typeof(T)) || c.GetType() == typeof(T));
+ else
+ return default(T);
+ }
+
+ public new ObjectTreeBuilder AddValue(object value)
+ {
+ this.value.Add(value);
+ return this;
+ }
+
+ public ObjectTreeBuilder AddChild(ObjectTreeBuilder child)
+ {
+ child.Parent = this;
+ this.children.Add(child);
+ return this;
+ }
+
+ public new ObjectTreeBuilder AddChild(string name, params object[] value)
+ {
+ return (AddChild(name, value.ToList()));
+ }
+
+ public new ObjectTreeBuilder AddChild(string name, List<object> value)
+ {
+ var node = new ObjectTreeBuilder(name, value) { Parent = this };
+ children.Add(node);
+ return node;
+ }
+
+ public new ObjectTreeBuilder this[int i]
+ {
+ get { return children[i]; }
+ }
+
+ public new ObjectTreeBuilder this[string name]
+ {
+ get { return children.FirstOrDefault(s => s.Name == name); }
+ }
+
+ public new ReadOnlyCollection<ObjectTreeBuilder> Children => children.AsReadOnly();
+
+ public new bool HasChild(string childName)
+ {
+ if (children == null || children.Count == 0)
+ return false;
+ else
+ {
+ var child = children.Find(c => c.Name == childName);
+ if (child == null)
+ return false;
+ else
+ return true;
+ }
+ }
+
+ public new bool HasChildPath(string childPath)
+ {
+ if (string.IsNullOrEmpty(childPath))
+ return false;
+
+ if (children == null || children.Count == 0)
+ return false;
+ else
+ {
+ if (childPath.Contains("/"))
+ {
+ string[] splittedPath = childPath.Split(new char[] { '/' }, StringSplitOptions.RemoveEmptyEntries);
+ if (!HasChild(splittedPath[0]))
+ return false;
+ else
+ {
+ var child = this[splittedPath[0]];
+ return (child.HasChildPath(string.Join("/", splittedPath.Skip(1))));
+ }
+ }
+ else
+ return HasChild(childPath);
+ }
+ }
+
+ public new ObjectTreeBuilder GetChild(string childPath)
+ {
+ if (string.IsNullOrEmpty(childPath))
+ return null;
+
+ if (children == null || children.Count == 0)
+ return null;
+ else
+ {
+ ObjectTreeBuilder superChild = null;
+ if (childPath.Contains("/"))
+ {
+ string[] splittedPath = childPath.Split(new char[] { '/' }, StringSplitOptions.RemoveEmptyEntries);
+ if (!HasChild(splittedPath[0]))
+ return null;
+ else
+ {
+ var child = this[splittedPath[0]];
+ superChild = child.GetChild(string.Join("/", splittedPath.Skip(1)));
+ }
+ }
+ else
+ superChild = this[childPath];
+
+ return superChild;
+ }
+ }
+
+ public new bool HasChildren()
+ {
+ if (children == null)
+ return false;
+ else
+ {
+ if (children.Count == 0)
+ return false;
+ else
+ return true;
+ }
+ }
+
+
+
+ public new void Traverse(Action<List<object>> action)
+ {
+ action(Value);
+ foreach (var child in children)
+ child.Traverse(action);
+ }
+ }
+ public class TreeBuilder<T>
+ {
+ protected List<T> value;
+ protected List<TreeBuilder<T>> children;
+ public TreeBuilder<T> Parent { get; protected set; }
+ public List<T> Value { get { return value; } }
+ public string Name { get; protected set; }
+
+ public TreeBuilder(string name, T data) : this (name, new List<T>() { data })
+ { }
+
+ public TreeBuilder(string name, List<T> data)
+ {
+ this.Name = name;
+ this.value = data;
+ this.children = new List<TreeBuilder<T>>();
+ }
+
+ public ReadOnlyCollection<TreeBuilder<T>> Children
+ {
+ get { return children.AsReadOnly(); }
+ }
+
+ public TreeBuilder<T> AddValue(T value)
+ {
+ this.value.Add(value);
+ return this;
+ }
+
+ public TreeBuilder<T> this[int i]
+ {
+ get { return children[i]; }
+ }
+
+ public TreeBuilder<T> this[string name]
+ {
+ get { return children.FirstOrDefault(s => s.Name == name); }
+ }
+
+ public void Traverse(Action<List<T>> action)
+ {
+ action(Value);
+ foreach (var child in children)
+ child.Traverse(action);
+ }
+
+ #region Child-Operations
+ public TreeBuilder<T> AddChild(TreeBuilder<T> child)
+ {
+ child.Parent = this;
+ this.children.Add(child);
+ return this;
+ }
+
+ public TreeBuilder<T> AddChild(string name, params T[] value)
+ {
+ return (AddChild(name, value.ToList()));
+ }
+
+ public TreeBuilder<T> AddChild(string name, List<T> value)
+ {
+ var node = new TreeBuilder<T>(name, value) { Parent = this };
+ children.Add(node);
+ return node;
+ }
+ public bool HasChildPath(string childPath)
+ {
+ if (string.IsNullOrEmpty(childPath))
+ return false;
+
+ if (children == null || children.Count == 0)
+ return false;
+ else
+ {
+ if (childPath.Contains("/"))
+ {
+ string[] splittedPath = childPath.Split(new char[] { '/' }, StringSplitOptions.RemoveEmptyEntries);
+ if (!HasChild(splittedPath[0]))
+ return false;
+ else
+ {
+ var child = this[splittedPath[0]];
+ return (child.HasChildPath(string.Join("/", splittedPath.Skip(1))));
+ }
+ }
+ else
+ return HasChild(childPath);
+ }
+ }
+ public bool HasChildren()
+ {
+ if (children == null)
+ return false;
+ else
+ {
+ if (children.Count == 0)
+ return false;
+ else
+ return true;
+ }
+ }
+ public bool HasChild(string childName)
+ {
+ if (children == null || children.Count == 0)
+ return false;
+ else
+ {
+ var child = children.Find(c => c.Name == childName);
+ if (child == null)
+ return false;
+ else
+ return true;
+ }
+ }
+
+ public TreeBuilder<T> GetChild(string childPath)
+ {
+ if (string.IsNullOrEmpty(childPath))
+ return null;
+
+ if (children == null || children.Count == 0)
+ return null;
+ else
+ {
+ TreeBuilder<T> superChild = null;
+ if (childPath.Contains("/"))
+ {
+ string[] splittedPath = childPath.Split(new char[] { '/' }, StringSplitOptions.RemoveEmptyEntries);
+ if (!HasChild(splittedPath[0]))
+ return null;
+ else
+ {
+ var child = this[splittedPath[0]];
+ superChild = child.GetChild(string.Join("/", splittedPath.Skip(1)));
+ }
+ }
+ else
+ superChild = this[childPath];
+
+ return superChild;
+ }
+ }
+ #endregion
+ }
+}
diff --git a/sdks/dotnet/basyx-core/BaSyx.Utils/Network/NetworkUtils.cs b/sdks/dotnet/basyx-core/BaSyx.Utils/Network/NetworkUtils.cs
new file mode 100644
index 0000000..e2d6b0f
--- /dev/null
+++ b/sdks/dotnet/basyx-core/BaSyx.Utils/Network/NetworkUtils.cs
@@ -0,0 +1,73 @@
+/*******************************************************************************
+* Copyright (c) 2020 Robert Bosch GmbH
+* Author: Constantin Ziesche (constantin.ziesche@bosch.com)
+*
+* This program and the accompanying materials are made available under the
+* terms of the Eclipse Public License 2.0 which is available at
+* http://www.eclipse.org/legal/epl-2.0
+*
+* SPDX-License-Identifier: EPL-2.0
+*******************************************************************************/
+using System.Collections.Generic;
+using System.Linq;
+using System.Net;
+
+namespace BaSyx.Utils.Network
+{
+ public static class NetworkUtils
+ {
+ /// <summary>
+ /// This method returns the closest source IP address relative to the the target IP address.
+ /// The probality of being able to call the target IP hence increases.
+ /// </summary>
+ /// <param name="target">Target IP address to call</param>
+ /// <param name="sources">Source IP address from where to cal the target</param>
+ /// <returns>The closest source IP address to the the target IP address or the Loopback Address</returns>
+ public static IPAddress GetClosestIPAddress(IPAddress target, List<IPAddress> sources)
+ {
+ Dictionary<int, IPAddress> scoredSourceIPAddresses = new Dictionary<int, IPAddress>();
+ byte[] targetBytes = target.GetAddressBytes();
+ foreach (var source in sources)
+ {
+ byte[] sourceBytes = source.GetAddressBytes();
+ int score = CompareIPByteArray(targetBytes, sourceBytes);
+
+ if(!scoredSourceIPAddresses.ContainsKey(score) && score != 0)
+ scoredSourceIPAddresses.Add(score, source);
+ }
+
+ if(scoredSourceIPAddresses.Count > 0)
+ return scoredSourceIPAddresses[scoredSourceIPAddresses.Keys.Max()];
+
+ return IPAddress.Loopback;
+ }
+
+ private static int CompareIPByteArray(byte[] target, byte[] source)
+ {
+ if (target.Length != source.Length)
+ return 0;
+
+ int score = 0;
+ for (int i = 0; i < source.Length; i++)
+ {
+ if (target[i] == source[i])
+ score++;
+ else
+ return score;
+ }
+ return score;
+ }
+
+ public static List<IPAddress> GetAllNetworkIPAddresses()
+ {
+ IPHostEntry host = Dns.GetHostEntry(Dns.GetHostName());
+ List<IPAddress> ipAddresses = new List<IPAddress>();
+ foreach (var ip in host.AddressList)
+ {
+ if (ip.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork)
+ ipAddresses.Add(ip);
+ }
+ return ipAddresses;
+ }
+ }
+}
diff --git a/sdks/dotnet/basyx-core/BaSyx.Utils/PathHandling/Path.cs b/sdks/dotnet/basyx-core/BaSyx.Utils/PathHandling/Path.cs
new file mode 100644
index 0000000..5f377ab
--- /dev/null
+++ b/sdks/dotnet/basyx-core/BaSyx.Utils/PathHandling/Path.cs
@@ -0,0 +1,43 @@
+/*******************************************************************************
+* Copyright (c) 2020 Robert Bosch GmbH
+* Author: Constantin Ziesche (constantin.ziesche@bosch.com)
+*
+* This program and the accompanying materials are made available under the
+* terms of the Eclipse Public License 2.0 which is available at
+* http://www.eclipse.org/legal/epl-2.0
+*
+* SPDX-License-Identifier: EPL-2.0
+*******************************************************************************/
+using System;
+using System.Linq;
+
+namespace BaSyx.Utils.PathHandling
+{
+ public static class Path
+ {
+ public static Uri Append(this Uri uri, params string[] pathElements)
+ {
+ return new Uri(pathElements.Aggregate(uri.AbsoluteUri, (currentElement, pathElement) => string.Format("{0}/{1}", currentElement.TrimEnd('/'), pathElement.TrimStart('/'))));
+ }
+
+ public static string GetFormattedEndpoint(string endpoint, string aggregateId, string entityId, string separator = "/")
+ {
+ if (endpoint[endpoint.Length - 1] == separator[0])
+ {
+ if (!endpoint.Contains(aggregateId))
+ endpoint += aggregateId + separator + entityId;
+ else
+ endpoint += entityId;
+ }
+ else
+ {
+ if (!endpoint.Contains(aggregateId))
+ endpoint += separator + aggregateId + separator + entityId;
+ else
+ endpoint += separator + entityId;
+ }
+
+ return endpoint;
+ }
+ }
+}
diff --git a/sdks/dotnet/basyx-core/BaSyx.Utils/ResultHandling/FilterExpression.cs b/sdks/dotnet/basyx-core/BaSyx.Utils/ResultHandling/FilterExpression.cs
new file mode 100644
index 0000000..90268c0
--- /dev/null
+++ b/sdks/dotnet/basyx-core/BaSyx.Utils/ResultHandling/FilterExpression.cs
@@ -0,0 +1,116 @@
+/*******************************************************************************
+* Copyright (c) 2020 Robert Bosch GmbH
+* Author: Constantin Ziesche (constantin.ziesche@bosch.com)
+*
+* This program and the accompanying materials are made available under the
+* terms of the Eclipse Public License 2.0 which is available at
+* http://www.eclipse.org/legal/epl-2.0
+*
+* SPDX-License-Identifier: EPL-2.0
+*******************************************************************************/
+using Newtonsoft.Json.Linq;
+using System.Collections.Generic;
+
+namespace BaSyx.Utils.ResultHandling
+{
+ public interface IFilterArgument
+ {
+ FilterArgumentType FilterArgumentType { get; }
+ FilterType FilterType { get; set; }
+ }
+
+ public sealed class KeyValue : IFilterArgument
+ {
+ public FilterType FilterType { get; set; }
+ public string Key { get; set; }
+ public string Value { get; set; }
+ public FilterArgumentType FilterArgumentType => FilterArgumentType.KeyValue;
+ }
+
+ public class FilterExpression : IFilterArgument
+ {
+ public FilterType FilterType { get; set; }
+ public List<IFilterArgument> Arguments { get; set; }
+ public FilterArgumentType FilterArgumentType => FilterArgumentType.FilterExpression;
+
+ public static FilterExpression Parse(JObject query)
+ {
+ if (query != null)
+ {
+ FilterExpression filterExpression = new FilterExpression();
+ filterExpression.FilterType = FilterType.Parse(query.SelectToken("name").Value<string>());
+ var argsArray = (JArray)query.SelectToken("args");
+ if (argsArray.HasValues)
+ {
+ filterExpression.Arguments = new List<IFilterArgument>();
+ FilterExpression subFilter = new FilterExpression();
+ subFilter.Arguments = new List<IFilterArgument>();
+ foreach (var item in argsArray)
+ {
+ subFilter.FilterType = FilterType.Parse(item.SelectToken("name").Value<string>());
+ if (subFilter.FilterType == FilterType.AND || subFilter.FilterType == FilterType.OR)
+ {
+ var argObj = Parse((JObject)item);
+ subFilter.Arguments.Add(argObj);
+ }
+ else
+ {
+ var keyValueArg = new KeyValue();
+ keyValueArg.FilterType = FilterType.Parse(item.SelectToken("name").Value<string>());
+ keyValueArg.Key = item.SelectToken("args[0]").ToString();
+ keyValueArg.Value = item.SelectToken("args[1]").ToString();
+
+ subFilter.Arguments.Add(keyValueArg);
+ }
+ }
+ filterExpression.Arguments.Add(subFilter);
+ }
+ return filterExpression;
+ }
+ return null;
+ }
+ }
+
+
+
+ public enum FilterArgumentType : int
+ {
+ KeyValue = 0,
+ FilterExpression = 1
+ }
+
+ public sealed class FilterType
+ {
+ private readonly string Name;
+ private readonly int Value;
+
+ public static readonly FilterType EQUALS = new FilterType(1, "eq");
+ public static readonly FilterType AND = new FilterType(2, "and");
+ public static readonly FilterType OR = new FilterType(3, "or");
+
+ private FilterType(int value, string name)
+ {
+ Name = name;
+ Value = value;
+ }
+
+ public override string ToString()
+ {
+ return Name;
+ }
+
+ public static FilterType Parse(string s)
+ {
+ switch (s.ToLower())
+ {
+ case "eq": return EQUALS;
+ case "and": return AND;
+ case "or": return OR;
+ default:
+ break;
+ }
+ return null;
+ }
+
+ }
+}
diff --git a/sdks/dotnet/basyx-core/BaSyx.Utils/ResultHandling/IMessage.cs b/sdks/dotnet/basyx-core/BaSyx.Utils/ResultHandling/IMessage.cs
new file mode 100644
index 0000000..5085f7b
--- /dev/null
+++ b/sdks/dotnet/basyx-core/BaSyx.Utils/ResultHandling/IMessage.cs
@@ -0,0 +1,20 @@
+/*******************************************************************************
+* Copyright (c) 2020 Robert Bosch GmbH
+* Author: Constantin Ziesche (constantin.ziesche@bosch.com)
+*
+* This program and the accompanying materials are made available under the
+* terms of the Eclipse Public License 2.0 which is available at
+* http://www.eclipse.org/legal/epl-2.0
+*
+* SPDX-License-Identifier: EPL-2.0
+*******************************************************************************/
+namespace BaSyx.Utils.ResultHandling
+{
+ public interface IMessage
+ {
+ MessageType MessageType { get; set; }
+ string Code { get; set; }
+ string Text { get; set; }
+ string ToString();
+ }
+}
diff --git a/sdks/dotnet/basyx-core/BaSyx.Utils/ResultHandling/IResult.cs b/sdks/dotnet/basyx-core/BaSyx.Utils/ResultHandling/IResult.cs
new file mode 100644
index 0000000..19fc256
--- /dev/null
+++ b/sdks/dotnet/basyx-core/BaSyx.Utils/ResultHandling/IResult.cs
@@ -0,0 +1,40 @@
+/*******************************************************************************
+* Copyright (c) 2020 Robert Bosch GmbH
+* Author: Constantin Ziesche (constantin.ziesche@bosch.com)
+*
+* This program and the accompanying materials are made available under the
+* terms of the Eclipse Public License 2.0 which is available at
+* http://www.eclipse.org/legal/epl-2.0
+*
+* SPDX-License-Identifier: EPL-2.0
+*******************************************************************************/
+using BaSyx.Utils.JsonHandling;
+using Newtonsoft.Json;
+using System;
+using System.Collections.Generic;
+using System.Runtime.Serialization;
+
+namespace BaSyx.Utils.ResultHandling
+{
+ public interface IResult
+ {
+ [DataMember(EmitDefaultValue = false, IsRequired = false)]
+ Type EntityType { get; }
+ [DataMember(EmitDefaultValue = false, IsRequired = false)]
+ object Entity { get; }
+ [DataMember(IsRequired = true)]
+ bool Success { get; }
+ [DataMember(EmitDefaultValue = false, IsRequired = false)]
+ bool? IsException { get; }
+ [DataMember(EmitDefaultValue = false, IsRequired = false)]
+ MessageCollection Messages { get; }
+
+ T GetEntity<T>();
+ }
+
+ public interface IResult<out TEntity> : IResult
+ {
+ [JsonConverter(typeof(CustomTypeSerializer))]
+ new TEntity Entity { get; }
+ }
+}
diff --git a/sdks/dotnet/basyx-core/BaSyx.Utils/ResultHandling/Message.cs b/sdks/dotnet/basyx-core/BaSyx.Utils/ResultHandling/Message.cs
new file mode 100644
index 0000000..83a3221
--- /dev/null
+++ b/sdks/dotnet/basyx-core/BaSyx.Utils/ResultHandling/Message.cs
@@ -0,0 +1,75 @@
+/*******************************************************************************
+* Copyright (c) 2020 Robert Bosch GmbH
+* Author: Constantin Ziesche (constantin.ziesche@bosch.com)
+*
+* This program and the accompanying materials are made available under the
+* terms of the Eclipse Public License 2.0 which is available at
+* http://www.eclipse.org/legal/epl-2.0
+*
+* SPDX-License-Identifier: EPL-2.0
+*******************************************************************************/
+using System.Globalization;
+using System.Net;
+using System.Threading;
+
+namespace BaSyx.Utils.ResultHandling
+{
+ public class Message : IMessage
+ {
+ public MessageType MessageType { get; set; }
+ public string Text { get; set; }
+ public string Code { get; set; }
+
+ public Message(MessageType messageType, string text) : this(messageType, text, null)
+ { }
+ public Message(MessageType messageType, string text, string code)
+ {
+ MessageType = messageType;
+ Text = text;
+ Code = code;
+ }
+
+
+ public override string ToString()
+ {
+ if(!string.IsNullOrEmpty(Code))
+ return string.Format(CultureInfo.CurrentCulture, "{0} | {1} - {2}", MessageType, Code, Text);
+ else
+ return string.Format(CultureInfo.CurrentCulture, "{0} | {1}", MessageType, Text);
+ }
+ }
+
+ public class HttpMessage : Message
+ {
+ public HttpStatusCode HttpStatusCode { get; set; }
+
+ public HttpMessage(MessageType messageType, HttpStatusCode httpStatusCode) : base(messageType, httpStatusCode.ToString(), ((int)httpStatusCode).ToString())
+ {
+ HttpStatusCode = httpStatusCode;
+ }
+ }
+
+ public class NotFoundMessage : Message
+ {
+ public NotFoundMessage() : base(MessageType.Information, "NotFound", "404")
+ { }
+
+ public NotFoundMessage(string what) : base(MessageType.Information, what + " not found", "404")
+ { }
+ }
+
+ public class ConflictMessage : Message
+ {
+ public ConflictMessage() : base(MessageType.Information, "Conflict", "409")
+ { }
+
+ public ConflictMessage(string what) : base(MessageType.Information, what + " already exists", "409")
+ { }
+ }
+
+ public class EmptyMessage : Message
+ {
+ public EmptyMessage() : base(MessageType.Information, "Empty")
+ { }
+ }
+}
diff --git a/sdks/dotnet/basyx-core/BaSyx.Utils/ResultHandling/MessageCollection.cs b/sdks/dotnet/basyx-core/BaSyx.Utils/ResultHandling/MessageCollection.cs
new file mode 100644
index 0000000..bb315cd
--- /dev/null
+++ b/sdks/dotnet/basyx-core/BaSyx.Utils/ResultHandling/MessageCollection.cs
@@ -0,0 +1,29 @@
+/*******************************************************************************
+* Copyright (c) 2020 Robert Bosch GmbH
+* Author: Constantin Ziesche (constantin.ziesche@bosch.com)
+*
+* This program and the accompanying materials are made available under the
+* terms of the Eclipse Public License 2.0 which is available at
+* http://www.eclipse.org/legal/epl-2.0
+*
+* SPDX-License-Identifier: EPL-2.0
+*******************************************************************************/
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace BaSyx.Utils.ResultHandling
+{
+ public class MessageCollection : List<IMessage>
+ {
+ public override string ToString()
+ {
+ string serializedMessageCollection = string.Empty;
+ if (this.Count > 0)
+ foreach (var item in this)
+ serializedMessageCollection += item.ToString() + Environment.NewLine;
+
+ return serializedMessageCollection;
+ }
+ }
+}
diff --git a/sdks/dotnet/basyx-core/BaSyx.Utils/ResultHandling/MessageType.cs b/sdks/dotnet/basyx-core/BaSyx.Utils/ResultHandling/MessageType.cs
new file mode 100644
index 0000000..30c942f
--- /dev/null
+++ b/sdks/dotnet/basyx-core/BaSyx.Utils/ResultHandling/MessageType.cs
@@ -0,0 +1,23 @@
+/*******************************************************************************
+* Copyright (c) 2020 Robert Bosch GmbH
+* Author: Constantin Ziesche (constantin.ziesche@bosch.com)
+*
+* This program and the accompanying materials are made available under the
+* terms of the Eclipse Public License 2.0 which is available at
+* http://www.eclipse.org/legal/epl-2.0
+*
+* SPDX-License-Identifier: EPL-2.0
+*******************************************************************************/
+namespace BaSyx.Utils.ResultHandling
+{
+ public enum MessageType : int
+ {
+ Unspecified = 0,
+ Debug = 1,
+ Information = 2,
+ Warning = 3,
+ Error = 4,
+ Fatal = 5,
+ Exception = 6
+ }
+}
diff --git a/sdks/dotnet/basyx-core/BaSyx.Utils/ResultHandling/Result.cs b/sdks/dotnet/basyx-core/BaSyx.Utils/ResultHandling/Result.cs
new file mode 100644
index 0000000..b777186
--- /dev/null
+++ b/sdks/dotnet/basyx-core/BaSyx.Utils/ResultHandling/Result.cs
@@ -0,0 +1,154 @@
+/*******************************************************************************
+* Copyright (c) 2020 Robert Bosch GmbH
+* Author: Constantin Ziesche (constantin.ziesche@bosch.com)
+*
+* This program and the accompanying materials are made available under the
+* terms of the Eclipse Public License 2.0 which is available at
+* http://www.eclipse.org/legal/epl-2.0
+*
+* SPDX-License-Identifier: EPL-2.0
+*******************************************************************************/
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Runtime.Serialization;
+
+namespace BaSyx.Utils.ResultHandling
+{
+ public class Result : IResult
+ {
+ public bool Success { get; private set; }
+
+ public bool? IsException { get; }
+
+ public object Entity { get; private set; }
+
+ public Type EntityType { get; private set; }
+
+ private MessageCollection messages;
+
+ public MessageCollection Messages
+ {
+ get
+ {
+ if (this.messages == null)
+ this.messages = new MessageCollection();
+ return this.messages;
+ }
+ }
+ public Result(bool success) : this(success, null, null, null)
+ { }
+ public Result(bool success, IMessage message) : this(success, new List<IMessage>() { message })
+ { }
+
+ public Result(bool success, List<IMessage> messages) : this(success, null, null, messages)
+ { }
+
+ public Result(bool success, object entity, Type entityType) : this(success, entity, entityType, null)
+ { }
+
+ public Result(Exception e) :
+ this(false, GetMessageListFromException(e))
+ { }
+
+ public Result(IResult result) : this(result.Success, result.Entity, result.EntityType, result.Messages)
+ { }
+
+ public static List<IMessage> GetMessageListFromException(Exception e)
+ {
+ List<IMessage> messageList = new List<IMessage>();
+
+ if (e.InnerException != null)
+ messageList.AddRange(GetMessageListFromException(e.InnerException));
+
+ messageList.Add(GetMessageFromException(e));
+
+ return messageList;
+ }
+
+ public static IMessage GetMessageFromException(Exception e)
+ {
+ var message = new Message(MessageType.Exception, e.GetType().Name + ":" + e.Message);
+
+ return message;
+ }
+
+ public Result(bool success, object entity, Type entityType, List<IMessage> messages)
+ {
+ Success = success;
+
+ if (messages != null)
+ foreach (Message msg in messages)
+ {
+ if (msg.MessageType == MessageType.Exception)
+ IsException = true;
+
+ Messages.Add(msg);
+ }
+
+ if (entity != null && entityType != null)
+ {
+ Entity = entity;
+ EntityType = entityType;
+ }
+
+ }
+
+ public T GetEntity<T>()
+ {
+ if (Entity != null &&
+ (Entity is T ||
+ Entity.GetType().IsAssignableFrom(typeof(T)) ||
+ Entity.GetType().GetInterfaces().Contains(typeof(T))))
+ return (T)Entity;
+ return default(T);
+ }
+
+ public override string ToString()
+ {
+ string messageTxt = string.Empty;
+ for (int i = 0; i < Messages.Count; i++)
+ messageTxt += Messages[i].ToString() + " || ";
+
+ string entityTxt = string.Empty;
+ if (Entity != null)
+ entityTxt = Entity.ToString();
+
+ var txt = $"Success: {Success}";
+ if (entityTxt != string.Empty)
+ txt += " | Entity: " + entityTxt;
+ if (messageTxt != string.Empty)
+ txt += " | Messages: " + messageTxt;
+ return txt;
+ }
+ }
+
+ public class Result<TEntity> : Result, IResult<TEntity>
+ {
+ [IgnoreDataMember]
+ public new TEntity Entity { get; private set; }
+ public Result(bool success) : this(success, default(TEntity), new List<IMessage>())
+ { }
+ public Result(bool success, TEntity entity) : this(success, entity, new List<IMessage>())
+ { }
+ public Result(bool success, IMessage message) : this(success, default(TEntity), new MessageCollection { message })
+ { }
+ public Result(bool success, List<IMessage> messages) : this(success, default(TEntity), messages)
+ { }
+ public Result(bool success, TEntity entity, IMessage message) : this(success, entity, new MessageCollection { message })
+ { }
+ public Result(Exception e) : base(e)
+ { }
+ public Result(IResult result) : base(result)
+ { }
+ public Result(bool success, TEntity entity, List<IMessage> messages) : base(success, entity, typeof(TEntity), messages)
+ {
+ Entity = entity;
+ }
+
+ public override string ToString()
+ {
+ return base.ToString();
+ }
+ }
+}
diff --git a/sdks/dotnet/basyx-core/BaSyx.Utils/ResultHandling/ResultTypes/OperationResult.cs b/sdks/dotnet/basyx-core/BaSyx.Utils/ResultHandling/ResultTypes/OperationResult.cs
new file mode 100644
index 0000000..b18b24e
--- /dev/null
+++ b/sdks/dotnet/basyx-core/BaSyx.Utils/ResultHandling/ResultTypes/OperationResult.cs
@@ -0,0 +1,28 @@
+/*******************************************************************************
+* Copyright (c) 2020 Robert Bosch GmbH
+* Author: Constantin Ziesche (constantin.ziesche@bosch.com)
+*
+* This program and the accompanying materials are made available under the
+* terms of the Eclipse Public License 2.0 which is available at
+* http://www.eclipse.org/legal/epl-2.0
+*
+* SPDX-License-Identifier: EPL-2.0
+*******************************************************************************/
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace BaSyx.Utils.ResultHandling.ResultTypes
+{
+ public class OperationResult : Result
+ {
+ public OperationResult(bool success) : base(success)
+ { }
+ public OperationResult(Exception e) : base(e)
+ { }
+ public OperationResult(bool success, IMessage message) : base(success, message)
+ { }
+ public OperationResult(bool success, List<IMessage> messages) : base(success, messages)
+ { }
+ }
+}
diff --git a/sdks/dotnet/basyx-core/BaSyx.Utils/ResultHandling/Utils.cs b/sdks/dotnet/basyx-core/BaSyx.Utils/ResultHandling/Utils.cs
new file mode 100644
index 0000000..fc9be51
--- /dev/null
+++ b/sdks/dotnet/basyx-core/BaSyx.Utils/ResultHandling/Utils.cs
@@ -0,0 +1,103 @@
+/*******************************************************************************
+* Copyright (c) 2020 Robert Bosch GmbH
+* Author: Constantin Ziesche (constantin.ziesche@bosch.com)
+*
+* This program and the accompanying materials are made available under the
+* terms of the Eclipse Public License 2.0 which is available at
+* http://www.eclipse.org/legal/epl-2.0
+*
+* SPDX-License-Identifier: EPL-2.0
+*******************************************************************************/
+using Microsoft.AspNetCore.Mvc;
+using System;
+using System.Diagnostics;
+using System.Net;
+using System.Threading;
+
+namespace BaSyx.Utils.ResultHandling
+{
+ public static class Utils
+ {
+ public static bool RetryUntilSuccessOrTimeout(Func<bool> task, TimeSpan timeout, TimeSpan pause)
+ {
+ if (pause.TotalMilliseconds < 0)
+ {
+ throw new ArgumentException("pause must be >= 0 milliseconds");
+ }
+ var stopwatch = Stopwatch.StartNew();
+ do
+ {
+ if (task())
+ return true;
+
+ Thread.Sleep((int)pause.TotalMilliseconds);
+ }
+ while (stopwatch.Elapsed < timeout);
+ return false;
+ }
+
+ public static bool TryParseStatusCode(IResult result, out int iHttpStatusCode)
+ {
+ try
+ {
+ bool success = false;
+ var msgs = result.Messages.FindAll(m => !string.IsNullOrEmpty(m.Code));
+ if (msgs != null && msgs.Count > 0)
+ foreach (var msg in msgs)
+ {
+ success = Enum.TryParse(msg.Code, out HttpStatusCode httpStatusCode);
+ if (success)
+ {
+ iHttpStatusCode = (int)httpStatusCode;
+ return success;
+ }
+ }
+ iHttpStatusCode = (int)HttpStatusCode.BadRequest;
+ return success;
+ }
+ catch
+ {
+ iHttpStatusCode = (int)HttpStatusCode.BadRequest;
+ return false;
+ }
+ }
+
+ public static IActionResult EvaluateResult(IResult result, CrudOperation crud, string route = null)
+ {
+ if (result == null)
+ return new StatusCodeResult(502);
+
+ switch (crud)
+ {
+ case CrudOperation.Create:
+ if (result.Success && result.Entity != null)
+ return new CreatedResult(route, result.Entity);
+ break;
+ case CrudOperation.Retrieve:
+ if (result.Success && result.Entity != null)
+ return new OkObjectResult(result.Entity);
+ else
+ return new NotFoundObjectResult(result);
+ case CrudOperation.Update:
+ if (result.Success)
+ return new OkObjectResult(result.Entity);
+ break;
+ case CrudOperation.Delete:
+ if (result.Success)
+ return new NoContentResult();
+ break;
+ default:
+ return new BadRequestObjectResult(result);
+ }
+ return new BadRequestObjectResult(result);
+ }
+
+ public enum CrudOperation
+ {
+ Create,
+ Retrieve,
+ Update,
+ Delete
+ }
+ }
+}
diff --git a/sdks/dotnet/basyx-core/BaSyx.Utils/Security/ICredentials.cs b/sdks/dotnet/basyx-core/BaSyx.Utils/Security/ICredentials.cs
new file mode 100644
index 0000000..c61e27f
--- /dev/null
+++ b/sdks/dotnet/basyx-core/BaSyx.Utils/Security/ICredentials.cs
@@ -0,0 +1,20 @@
+/*******************************************************************************
+* Copyright (c) 2020 Robert Bosch GmbH
+* Author: Constantin Ziesche (constantin.ziesche@bosch.com)
+*
+* This program and the accompanying materials are made available under the
+* terms of the Eclipse Public License 2.0 which is available at
+* http://www.eclipse.org/legal/epl-2.0
+*
+* SPDX-License-Identifier: EPL-2.0
+*******************************************************************************/
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace BaSyx.Utils.Security
+{
+ public interface ICredentials
+ {
+ }
+}
diff --git a/sdks/dotnet/basyx-core/BaSyx.Utils/Security/ISecurity.cs b/sdks/dotnet/basyx-core/BaSyx.Utils/Security/ISecurity.cs
new file mode 100644
index 0000000..4f33969
--- /dev/null
+++ b/sdks/dotnet/basyx-core/BaSyx.Utils/Security/ISecurity.cs
@@ -0,0 +1,20 @@
+/*******************************************************************************
+* Copyright (c) 2020 Robert Bosch GmbH
+* Author: Constantin Ziesche (constantin.ziesche@bosch.com)
+*
+* This program and the accompanying materials are made available under the
+* terms of the Eclipse Public License 2.0 which is available at
+* http://www.eclipse.org/legal/epl-2.0
+*
+* SPDX-License-Identifier: EPL-2.0
+*******************************************************************************/
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace BaSyx.Utils.Security
+{
+ public interface ISecurity
+ {
+ }
+}
diff --git a/sdks/dotnet/basyx-core/BaSyx.Utils/Server/Http/SimpleLocalHttpServer.cs b/sdks/dotnet/basyx-core/BaSyx.Utils/Server/Http/SimpleLocalHttpServer.cs
new file mode 100644
index 0000000..9f537c5
--- /dev/null
+++ b/sdks/dotnet/basyx-core/BaSyx.Utils/Server/Http/SimpleLocalHttpServer.cs
@@ -0,0 +1,153 @@
+/*******************************************************************************
+* Copyright (c) 2020 Robert Bosch GmbH
+* Author: Constantin Ziesche (constantin.ziesche@bosch.com)
+*
+* This program and the accompanying materials are made available under the
+* terms of the Eclipse Public License 2.0 which is available at
+* http://www.eclipse.org/legal/epl-2.0
+*
+* SPDX-License-Identifier: EPL-2.0
+*******************************************************************************/
+using NLog;
+using System;
+using System.Collections.Generic;
+using System.Collections.Specialized;
+using System.IO;
+using System.Net;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace BaSyx.Utils.Server.Http
+{
+ public class SimpleLocalHttpServer
+ {
+ private HttpListener listener;
+ private string _uriPrefix;
+
+ private CancellationTokenSource cancellationToken;
+ private Action<HttpListenerRequest> messageReception = null;
+ private Action<HttpListenerResponse> messageResponse = null;
+ private Action<HttpListenerContext> messageHandler = null;
+
+ private static Logger logger = LogManager.GetCurrentClassLogger();
+
+ public SimpleLocalHttpServer(string uriPrefix)
+ {
+ if (string.IsNullOrEmpty(uriPrefix))
+ throw new ArgumentNullException(nameof(uriPrefix));
+
+ if (!uriPrefix.EndsWith("/"))
+ _uriPrefix = uriPrefix + "/";
+ else
+ _uriPrefix = uriPrefix;
+ }
+
+
+ public void Start()
+ {
+ listener = new HttpListener();
+ listener.Prefixes.Add(_uriPrefix);
+ cancellationToken = new CancellationTokenSource();
+
+ if (!listener.IsListening)
+ {
+ listener.Start();
+
+ Task.Factory.StartNew(async () =>
+ {
+ while (!cancellationToken.IsCancellationRequested)
+ await Listen(listener);
+ }, cancellationToken.Token, TaskCreationOptions.LongRunning, TaskScheduler.Current);
+
+ logger.Info("Http-Listener started");
+ }
+ }
+
+ public void Start(Action<HttpListenerRequest> receiveMessageMethod)
+ {
+ messageReception = receiveMessageMethod;
+ Start();
+ }
+
+ public void Start(Action<HttpListenerRequest> receiveMessageMethod, Action<HttpListenerResponse> responseMessageMethod)
+ {
+ messageReception = receiveMessageMethod;
+ messageResponse = responseMessageMethod;
+ Start();
+ }
+
+ public void Start(Action<HttpListenerContext> messageHandlerMethod)
+ {
+ messageHandler = messageHandlerMethod;
+ Start();
+ }
+ private async Task Listen(HttpListener listener)
+ {
+ try
+ {
+ HttpListenerContext context = await listener.GetContextAsync();
+ if (messageHandler != null)
+ messageHandler.Invoke(context);
+ else
+ {
+ if (messageReception != null)
+ messageReception.Invoke(context.Request);
+ if (messageResponse != null)
+ messageResponse.Invoke(context.Response);
+ }
+ }
+ catch (Exception e)
+ {
+ logger.Error(e, "Http-Listener Exception: " + e.Message);
+ }
+ }
+
+ public void Stop()
+ {
+ if (listener.IsListening)
+ {
+ cancellationToken.Cancel();
+ listener.Stop();
+ logger.Info("Http-Listener stopped");
+ }
+ }
+
+ /// <summary>
+ /// Formats the entire response, e.g. suitable for a MessageBox
+ /// </summary>
+ /// <param name="request"></param>
+ /// <returns>Beautiful formatted response</returns>
+ public static string GetCompleteResponseAsString(HttpListenerRequest request)
+ {
+ StringBuilder sb = new StringBuilder();
+ sb.AppendLine("URI: " + request.Url.AbsoluteUri);
+ sb.AppendLine("-----------HEADER-----------");
+
+ NameValueCollection headers = request.Headers;
+ for (int i = 0; i < headers.Count; i++)
+ {
+ string key = headers.GetKey(i);
+ string value = headers.Get(i);
+ sb.AppendLine(key + " = " + value);
+ }
+
+ sb.AppendLine("---------HEADER-END--------");
+
+ sb.AppendLine("-----------BODY-----------");
+ string body = new StreamReader(request.InputStream).ReadToEnd();
+ sb.AppendLine(body);
+ sb.AppendLine("---------BODY-END--------");
+
+ return sb.ToString();
+ }
+
+ public static string GetResponseBodyAsString(HttpListenerRequest request)
+ {
+ string responseBody = null;
+ using (var stream = new StreamReader(request.InputStream))
+ responseBody = stream.ReadToEnd();
+ return responseBody;
+ }
+ }
+}
diff --git a/sdks/dotnet/basyx-core/BaSyx.Utils/Settings/DirectoryWatcher.cs b/sdks/dotnet/basyx-core/BaSyx.Utils/Settings/DirectoryWatcher.cs
new file mode 100644
index 0000000..9790ea6
--- /dev/null
+++ b/sdks/dotnet/basyx-core/BaSyx.Utils/Settings/DirectoryWatcher.cs
@@ -0,0 +1,65 @@
+/*******************************************************************************
+* Copyright (c) 2020 Robert Bosch GmbH
+* Author: Constantin Ziesche (constantin.ziesche@bosch.com)
+*
+* This program and the accompanying materials are made available under the
+* terms of the Eclipse Public License 2.0 which is available at
+* http://www.eclipse.org/legal/epl-2.0
+*
+* SPDX-License-Identifier: EPL-2.0
+*******************************************************************************/
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+
+namespace BaSyx.Utils.Settings
+{
+ public class DirectoryWatcher
+ {
+ private readonly FileSystemWatcher fileSystemWatcher;
+ private readonly string directoryPath;
+
+ public delegate void DirectoryChanged(string fullPath);
+ private readonly DirectoryChanged DirectoryChangedHandler;
+
+ public DirectoryWatcher(string pathToDirectory, string filter, bool createIfNotExists, DirectoryChanged directoryChanged)
+ {
+ if (string.IsNullOrEmpty(pathToDirectory))
+ throw new ArgumentNullException("pathToDirectory");
+ else if (!Directory.Exists(pathToDirectory))
+ {
+ if (createIfNotExists)
+ Directory.CreateDirectory(pathToDirectory);
+ else
+ throw new InvalidOperationException(pathToDirectory + "does not exist");
+ }
+ directoryPath = pathToDirectory;
+ DirectoryChangedHandler = directoryChanged;
+
+ fileSystemWatcher = new FileSystemWatcher();
+ fileSystemWatcher.Path = pathToDirectory;
+ fileSystemWatcher.Filter = filter;
+ fileSystemWatcher.NotifyFilter = NotifyFilters.LastWrite;
+
+ fileSystemWatcher.Changed += new FileSystemEventHandler(OnChanged);
+
+ fileSystemWatcher.EnableRaisingEvents = true;
+ }
+
+ private void OnChanged(object sender, FileSystemEventArgs e)
+ {
+ Console.Out.WriteLine("File: " + e.FullPath + " " + e.ChangeType);
+ try
+ {
+ fileSystemWatcher.EnableRaisingEvents = false;
+ DirectoryChangedHandler(e.FullPath);
+ }
+ finally
+ {
+ fileSystemWatcher.EnableRaisingEvents = true;
+ }
+
+ }
+ }
+}
diff --git a/sdks/dotnet/basyx-core/BaSyx.Utils/Settings/FileWatcher.cs b/sdks/dotnet/basyx-core/BaSyx.Utils/Settings/FileWatcher.cs
new file mode 100644
index 0000000..66125a4
--- /dev/null
+++ b/sdks/dotnet/basyx-core/BaSyx.Utils/Settings/FileWatcher.cs
@@ -0,0 +1,51 @@
+/*******************************************************************************
+* Copyright (c) 2020 Robert Bosch GmbH
+* Author: Constantin Ziesche (constantin.ziesche@bosch.com)
+*
+* This program and the accompanying materials are made available under the
+* terms of the Eclipse Public License 2.0 which is available at
+* http://www.eclipse.org/legal/epl-2.0
+*
+* SPDX-License-Identifier: EPL-2.0
+*******************************************************************************/
+using System;
+using System.IO;
+
+namespace BaSyx.Utils.Settings
+{
+ public class FileWatcher
+ {
+ private readonly FileSystemWatcher fileSystemWatcher;
+ private readonly string filePath;
+
+ public delegate void FileChanged(string fullPath);
+ private readonly FileChanged FileChangedHandler;
+
+ public FileWatcher(string pathToFile, FileChanged fileChanged)
+ {
+ if (string.IsNullOrEmpty(pathToFile))
+ throw new ArgumentNullException("pathToFile");
+ else if (!File.Exists(pathToFile))
+ throw new InvalidOperationException(pathToFile + "does not exist");
+
+ filePath = pathToFile;
+ FileChangedHandler = fileChanged;
+
+ fileSystemWatcher = new FileSystemWatcher();
+ fileSystemWatcher.Path = Path.GetDirectoryName(pathToFile);
+ fileSystemWatcher.Filter = Path.GetFileName(pathToFile);
+ fileSystemWatcher.NotifyFilter = NotifyFilters.LastWrite;
+
+ fileSystemWatcher.Changed += new FileSystemEventHandler(OnChanged);
+
+ fileSystemWatcher.EnableRaisingEvents = true;
+ }
+
+ private void OnChanged(object sender, FileSystemEventArgs e)
+ {
+ Console.Out.WriteLine("File: " + e.FullPath + " " + e.ChangeType);
+ if (e.ChangeType == WatcherChangeTypes.Changed && filePath == e.FullPath)
+ FileChangedHandler(e.FullPath);
+ }
+ }
+}
diff --git a/sdks/dotnet/basyx-core/BaSyx.Utils/Settings/ISettings.cs b/sdks/dotnet/basyx-core/BaSyx.Utils/Settings/ISettings.cs
new file mode 100644
index 0000000..dd3aefa
--- /dev/null
+++ b/sdks/dotnet/basyx-core/BaSyx.Utils/Settings/ISettings.cs
@@ -0,0 +1,21 @@
+/*******************************************************************************
+* Copyright (c) 2020 Robert Bosch GmbH
+* Author: Constantin Ziesche (constantin.ziesche@bosch.com)
+*
+* This program and the accompanying materials are made available under the
+* terms of the Eclipse Public License 2.0 which is available at
+* http://www.eclipse.org/legal/epl-2.0
+*
+* SPDX-License-Identifier: EPL-2.0
+*******************************************************************************/
+using System.Collections.Generic;
+
+namespace BaSyx.Utils.Settings
+{
+ public interface ISettings
+ {
+ string Name { get; }
+ string FilePath { get; set; }
+ Dictionary<string, string> Miscellaneous { get; set; }
+ }
+}
diff --git a/sdks/dotnet/basyx-core/BaSyx.Utils/Settings/ResourceChecker.cs b/sdks/dotnet/basyx-core/BaSyx.Utils/Settings/ResourceChecker.cs
new file mode 100644
index 0000000..805ab68
--- /dev/null
+++ b/sdks/dotnet/basyx-core/BaSyx.Utils/Settings/ResourceChecker.cs
@@ -0,0 +1,82 @@
+/*******************************************************************************
+* Copyright (c) 2020 Robert Bosch GmbH
+* Author: Constantin Ziesche (constantin.ziesche@bosch.com)
+*
+* This program and the accompanying materials are made available under the
+* terms of the Eclipse Public License 2.0 which is available at
+* http://www.eclipse.org/legal/epl-2.0
+*
+* SPDX-License-Identifier: EPL-2.0
+*******************************************************************************/
+using NLog;
+using System;
+using System.IO;
+using System.Reflection;
+
+namespace BaSyx.Utils.Settings
+{
+ public static class ResourceChecker
+ {
+ private static Logger logger = LogManager.GetCurrentClassLogger();
+
+ /// <summary>
+ /// Checks whether a resource is available in the assembly
+ /// </summary>
+ /// <param name="resourceName">Name of the resource</param>
+ /// <param name="createFile">If true, writes the resource to a file in the current executing directory</param>
+ /// <returns>
+ /// true = Resource was found
+ /// false = Resource was not found
+ /// </returns>
+ public static bool CheckResourceAvailability(Assembly sourceAssembly, string nameSpace, string resourceName, bool createFile)
+ {
+ if (File.Exists(resourceName) ||
+ File.Exists(Path.Combine(Path.GetDirectoryName(Assembly.GetCallingAssembly().Location), resourceName)) ||
+ File.Exists(Path.Combine(Path.GetDirectoryName(AppDomain.CurrentDomain.BaseDirectory), resourceName)))
+ {
+ return true;
+ }
+ else if (createFile)
+ {
+ if (WriteEmbeddedRessourceToFile(sourceAssembly, nameSpace, resourceName, null))
+ return true;
+ else
+ return false;
+ }
+ else
+ return false;
+ }
+ /// <summary>
+ /// Writes an embedded resource to a file in the executing directory
+ /// </summary>
+ /// <param name="resourceName">Name of the embedded resourcre</param>
+ /// <returns>
+ /// true = Resource was written successfully
+ /// false = Resource was not written successfully
+ /// </returns>
+ public static bool WriteEmbeddedRessourceToFile(Assembly sourceAssembly, string nameSpace, string resourceName, string destinationFilename)
+ {
+ try
+ {
+ Stream configStream = sourceAssembly.GetManifestResourceStream(string.Join(".", nameSpace, resourceName));
+ if (configStream == null)
+ throw new FileNotFoundException("Resource '" + resourceName + "' not found");
+
+ string filePath = string.IsNullOrEmpty(destinationFilename) ? Path.Combine(Path.GetDirectoryName(AppDomain.CurrentDomain.BaseDirectory), resourceName) : destinationFilename;
+
+ using (var fileStream = File.Create(filePath))
+ {
+ configStream.Seek(0, SeekOrigin.Begin);
+ configStream.CopyTo(fileStream);
+ }
+ return (true);
+ }
+ catch (Exception e)
+ {
+ logger.Error(e, "Error creating '" + resourceName + "' from embedded resource. Exception: " + e.ToString());
+ return (false);
+ }
+
+ }
+ }
+}
diff --git a/sdks/dotnet/basyx-core/BaSyx.Utils/Settings/Sections/ClientConfiguration.cs b/sdks/dotnet/basyx-core/BaSyx.Utils/Settings/Sections/ClientConfiguration.cs
new file mode 100644
index 0000000..cdefc0b
--- /dev/null
+++ b/sdks/dotnet/basyx-core/BaSyx.Utils/Settings/Sections/ClientConfiguration.cs
@@ -0,0 +1,25 @@
+/*******************************************************************************
+* Copyright (c) 2020 Robert Bosch GmbH
+* Author: Constantin Ziesche (constantin.ziesche@bosch.com)
+*
+* This program and the accompanying materials are made available under the
+* terms of the Eclipse Public License 2.0 which is available at
+* http://www.eclipse.org/legal/epl-2.0
+*
+* SPDX-License-Identifier: EPL-2.0
+*******************************************************************************/
+using System;
+using System.Xml;
+using System.Xml.Serialization;
+
+namespace BaSyx.Utils.Settings.Sections
+{
+
+ public class ClientConfiguration
+ {
+ [XmlElement]
+ public string ClientId { get; set; }
+ [XmlElement]
+ public string Endpoint { get; set; }
+ }
+}
diff --git a/sdks/dotnet/basyx-core/BaSyx.Utils/Settings/Sections/PathConfiguration.cs b/sdks/dotnet/basyx-core/BaSyx.Utils/Settings/Sections/PathConfiguration.cs
new file mode 100644
index 0000000..a84cb57
--- /dev/null
+++ b/sdks/dotnet/basyx-core/BaSyx.Utils/Settings/Sections/PathConfiguration.cs
@@ -0,0 +1,33 @@
+/*******************************************************************************
+* Copyright (c) 2020 Robert Bosch GmbH
+* Author: Constantin Ziesche (constantin.ziesche@bosch.com)
+*
+* This program and the accompanying materials are made available under the
+* terms of the Eclipse Public License 2.0 which is available at
+* http://www.eclipse.org/legal/epl-2.0
+*
+* SPDX-License-Identifier: EPL-2.0
+*******************************************************************************/
+using System;
+using System.Xml;
+using System.Xml.Serialization;
+
+namespace BaSyx.Utils.Settings.Sections
+{
+
+ public class PathConfiguration
+ {
+ [XmlElement]
+ public string Host { get; set; }
+ [XmlElement]
+ public string BasePath { get; set; }
+ [XmlElement]
+ public string ServicePath { get; set; }
+ [XmlElement]
+ public string AggregatePath { get; set; }
+ [XmlElement]
+ public string EntityPath { get; set; }
+ [XmlElement]
+ public string EntityId { get; set; }
+ }
+}
diff --git a/sdks/dotnet/basyx-core/BaSyx.Utils/Settings/Sections/ProxyConfiguration.cs b/sdks/dotnet/basyx-core/BaSyx.Utils/Settings/Sections/ProxyConfiguration.cs
new file mode 100644
index 0000000..be83617
--- /dev/null
+++ b/sdks/dotnet/basyx-core/BaSyx.Utils/Settings/Sections/ProxyConfiguration.cs
@@ -0,0 +1,31 @@
+/*******************************************************************************
+* Copyright (c) 2020 Robert Bosch GmbH
+* Author: Constantin Ziesche (constantin.ziesche@bosch.com)
+*
+* This program and the accompanying materials are made available under the
+* terms of the Eclipse Public License 2.0 which is available at
+* http://www.eclipse.org/legal/epl-2.0
+*
+* SPDX-License-Identifier: EPL-2.0
+*******************************************************************************/
+using System;
+using System.Xml;
+using System.Xml.Serialization;
+
+namespace BaSyx.Utils.Settings.Sections
+{
+
+ public class ProxyConfiguration
+ {
+ [XmlElement]
+ public bool UseProxy { get; set; }
+ [XmlElement]
+ public string ProxyAddress { get; set; }
+ [XmlElement]
+ public string Domain { get; set; }
+ [XmlElement]
+ public string UserName { get; set; }
+ [XmlElement]
+ public string Password { get; set; }
+ }
+}
diff --git a/sdks/dotnet/basyx-core/BaSyx.Utils/Settings/Sections/ServerConfiguration.cs b/sdks/dotnet/basyx-core/BaSyx.Utils/Settings/Sections/ServerConfiguration.cs
new file mode 100644
index 0000000..9a0b807
--- /dev/null
+++ b/sdks/dotnet/basyx-core/BaSyx.Utils/Settings/Sections/ServerConfiguration.cs
@@ -0,0 +1,49 @@
+/*******************************************************************************
+* Copyright (c) 2020 Robert Bosch GmbH
+* Author: Constantin Ziesche (constantin.ziesche@bosch.com)
+*
+* This program and the accompanying materials are made available under the
+* terms of the Eclipse Public License 2.0 which is available at
+* http://www.eclipse.org/legal/epl-2.0
+*
+* SPDX-License-Identifier: EPL-2.0
+*******************************************************************************/
+using System;
+using System.Collections.Generic;
+using System.Xml;
+using System.Xml.Serialization;
+
+namespace BaSyx.Utils.Settings.Sections
+{
+
+ public class ServerConfiguration
+ {
+ [XmlElement]
+ public string ServerId { get; set; }
+ [XmlElement]
+ public HostingConfiguration Hosting { get; set; }
+ [XmlElement]
+ public string DefaultRoute { get; set; }
+
+ public ServerConfiguration()
+ {
+ Hosting = new HostingConfiguration();
+ }
+ }
+
+
+ public class HostingConfiguration
+ {
+ [XmlElement]
+ public string Environment { get; set; }
+ [XmlArrayItem("Url")]
+ public List<string> Urls { get; set; }
+ [XmlElement]
+ public string ContentPath { get; set; }
+
+ public HostingConfiguration()
+ {
+ Urls = new List<string>();
+ }
+ }
+}
diff --git a/sdks/dotnet/basyx-core/BaSyx.Utils/Settings/ServiceType.cs b/sdks/dotnet/basyx-core/BaSyx.Utils/Settings/ServiceType.cs
new file mode 100644
index 0000000..a967d37
--- /dev/null
+++ b/sdks/dotnet/basyx-core/BaSyx.Utils/Settings/ServiceType.cs
@@ -0,0 +1,20 @@
+/*******************************************************************************
+* Copyright (c) 2020 Robert Bosch GmbH
+* Author: Constantin Ziesche (constantin.ziesche@bosch.com)
+*
+* This program and the accompanying materials are made available under the
+* terms of the Eclipse Public License 2.0 which is available at
+* http://www.eclipse.org/legal/epl-2.0
+*
+* SPDX-License-Identifier: EPL-2.0
+*******************************************************************************/
+namespace BaSyx.Utils.Settings
+{
+ public enum ServiceType
+ {
+ None,
+ Server,
+ Client,
+ Both
+ }
+}
diff --git a/sdks/dotnet/basyx-core/BaSyx.Utils/Settings/Settings.cs b/sdks/dotnet/basyx-core/BaSyx.Utils/Settings/Settings.cs
new file mode 100644
index 0000000..be55b47
--- /dev/null
+++ b/sdks/dotnet/basyx-core/BaSyx.Utils/Settings/Settings.cs
@@ -0,0 +1,218 @@
+/*******************************************************************************
+* Copyright (c) 2020 Robert Bosch GmbH
+* Author: Constantin Ziesche (constantin.ziesche@bosch.com)
+*
+* This program and the accompanying materials are made available under the
+* terms of the Eclipse Public License 2.0 which is available at
+* http://www.eclipse.org/legal/epl-2.0
+*
+* SPDX-License-Identifier: EPL-2.0
+*******************************************************************************/
+using BaSyx.Utils.AssemblyHandling;
+using BaSyx.Utils.Settings.Sections;
+using Newtonsoft.Json;
+using NLog;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Reflection;
+using System.Xml;
+using System.Xml.Linq;
+using System.Xml.Serialization;
+using XSerializer;
+using static BaSyx.Utils.Settings.FileWatcher;
+
+namespace BaSyx.Utils.Settings
+{
+
+ public static class SettingsExtensions
+ {
+ public static T As<T>(this Settings settings) where T : Settings
+ {
+ return settings as T;
+ }
+ }
+
+ public abstract class Settings : ISettings
+ {
+ [XmlIgnore]
+ public string Name => this.GetType().Name;
+ [XmlIgnore]
+ public string FilePath { get; set; }
+ [XmlIgnore]
+ public Dictionary<string, string> Miscellaneous { get; set; }
+
+ [XmlElement]
+ public ServiceType OperationMode { get; set; }
+ [XmlElement(IsNullable = true)]
+ public ServerConfiguration ServerConfig { get; set; } = new ServerConfiguration();
+ [XmlElement(IsNullable = true)]
+ public ClientConfiguration ClientConfig { get; set; } = new ClientConfiguration();
+ [XmlElement(IsNullable = true)]
+ public PathConfiguration PathConfig { get; set; } = new PathConfiguration();
+ [XmlElement(IsNullable = true)]
+ public ProxyConfiguration ProxyConfig { get; set; } = new ProxyConfiguration();
+
+ public static string ExecutingDirectory => AppDomain.CurrentDomain.BaseDirectory;//=> Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
+
+ public const string FileExtension = ".xml";
+ public const string MiscellaneousConfig = "Miscellaneous";
+
+ public static SettingsCollection SettingsCollection { get; }
+
+ private static readonly Logger logger = LogManager.GetCurrentClassLogger();
+ private FileWatcher fileWatcher;
+
+ static Settings()
+ {
+ SettingsCollection = new SettingsCollection();
+ string[] files = Directory.GetFiles(ExecutingDirectory, "*Settings.xml", SearchOption.TopDirectoryOnly);
+ if(files?.Length > 0)
+ {
+ List<Assembly> assemblies = AssemblyUtils.GetLoadedAssemblies();
+
+ for (int i = 0; i < files.Length; i++)
+ {
+ try
+ {
+ XDocument doc = XDocument.Load(files[i]);
+ string rootName = doc.Root.Name.LocalName;
+ Type settingsType = assemblies
+ .SelectMany(a => a.GetTypes())
+ .Where(t => t.Name == rootName)?
+ .FirstOrDefault();
+
+ if (settingsType != null)
+ {
+ Settings setting = LoadSettingsFromFile(files[i], settingsType);
+ if (setting != null)
+ SettingsCollection.Add(setting);
+ }
+ else
+ logger.Info("Cannot load settings of type: " + rootName + " because type is either never used or not referenced");
+ }
+ catch (Exception e)
+ {
+ logger.Warn(e, "Cannot load settings file: " + files[i]);
+ }
+ }
+
+ }
+ }
+
+ protected Settings()
+ {
+ Miscellaneous = new Dictionary<string, string>();
+ }
+
+ public virtual void ConfigureSettingsWatcher(string settingsFilePath, FileChanged settingsFileChangedHandler)
+ {
+ fileWatcher = new FileWatcher(settingsFilePath, settingsFileChangedHandler);
+ }
+
+
+
+ public static Settings LoadSettingsFromFile(string filePath, Type settingsType)
+ {
+ if (string.IsNullOrEmpty(filePath) || !File.Exists(filePath))
+ {
+ logger.Warn("Settings file does not exist: " + filePath);
+ return null;
+ }
+
+ try
+ {
+ Settings settings = null;
+
+ IXSerializer serializer = XSerializer.XmlSerializer.Create(settingsType);
+ string settingsXml = File.ReadAllText(filePath);
+
+ settings = (Settings)serializer.Deserialize(settingsXml);
+
+ if (settings != null)
+ {
+ var miscElement = XElement.Load(filePath).Element(MiscellaneousConfig);
+ if (miscElement != null)
+ settings.Miscellaneous = miscElement.Elements().Where(e => !e.HasElements).ToDictionary(e => e.Name.LocalName, e => e.Value);
+
+ settings.FilePath = filePath;
+
+ if(logger.IsDebugEnabled)
+ logger.Debug("Settings loaded: " + JsonConvert.SerializeObject(settings, Newtonsoft.Json.Formatting.Indented));
+
+ return settings;
+ }
+ logger.Warn("No settings of Type " + settingsType.Name + " loaded: " + filePath);
+ return null;
+
+ }
+ catch (Exception e)
+ {
+ logger.Error(e, "Could not load " + filePath);
+ return null;
+ }
+ }
+
+ public static T LoadSettingsFromFile<T>(string filePath) where T : Settings, new()
+ {
+ return (T)LoadSettingsFromFile(filePath, typeof(T));
+ }
+
+ public static T LoadSettingsByName<T>(string name) where T : Settings
+ {
+ Settings settings = SettingsCollection.Find(s => s.Name == typeof(T).Name);
+ if (settings != null)
+ return (T)settings;
+ return null;
+ }
+
+ public void SaveSettings(string filePath, Type settingsType)
+ {
+ try
+ {
+ IXSerializer serializer = XSerializer.XmlSerializer.Create(settingsType);
+ string settingsXmlContent = serializer.Serialize(this);
+ File.WriteAllText(filePath, settingsXmlContent);
+
+ logger.Info("Settings saved: " + filePath);
+ }
+ catch (Exception e)
+ {
+ logger.Error(e, "Could not serialize to " + filePath);
+ }
+ }
+ }
+
+ public abstract class Settings<T> : Settings where T : Settings, new()
+ {
+ private static Logger logger = LogManager.GetCurrentClassLogger();
+ public static string FileName => typeof(T).Name + FileExtension;
+
+
+ public Settings() : base()
+ { }
+
+ public static T CreateSettings() => new T();
+
+ public void SaveSettings() => SaveSettings(FilePath);
+
+ public void SaveSettings(string filePath) => SaveSettings(filePath, typeof(T));
+
+ public static T LoadSettings()
+ {
+ Settings settings = LoadSettingsByName(typeof(T).Name);
+ if (settings == null)
+ {
+ string settingsFilePath = Path.Combine(ExecutingDirectory, FileName);
+ settings = LoadSettingsFromFile(settingsFilePath);
+ }
+ if(settings != null)
+ return (T)settings;
+ return null;
+ }
+
+ public static T LoadSettingsByName(string name) => LoadSettingsByName<T>(name);
+ public static T LoadSettingsFromFile(string filePath) => LoadSettingsFromFile<T>(filePath);
+ }
+}
diff --git a/sdks/dotnet/basyx-core/BaSyx.Utils/Settings/SettingsCollection.cs b/sdks/dotnet/basyx-core/BaSyx.Utils/Settings/SettingsCollection.cs
new file mode 100644
index 0000000..81114e9
--- /dev/null
+++ b/sdks/dotnet/basyx-core/BaSyx.Utils/Settings/SettingsCollection.cs
@@ -0,0 +1,26 @@
+/*******************************************************************************
+* Copyright (c) 2020 Robert Bosch GmbH
+* Author: Constantin Ziesche (constantin.ziesche@bosch.com)
+*
+* This program and the accompanying materials are made available under the
+* terms of the Eclipse Public License 2.0 which is available at
+* http://www.eclipse.org/legal/epl-2.0
+*
+* SPDX-License-Identifier: EPL-2.0
+*******************************************************************************/
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace BaSyx.Utils.Settings
+{
+ public class SettingsCollection : List<Settings>
+ {
+ public Settings this[string name] => this.Find(e => e.Name == name);
+
+ public T GetSettings<T>(string name) where T : Settings
+ {
+ return (T)this[name];
+ }
+ }
+}
diff --git a/sdks/dotnet/basyx-core/BaSyx.Utils/Settings/Types/DependencySettings.cs b/sdks/dotnet/basyx-core/BaSyx.Utils/Settings/Types/DependencySettings.cs
new file mode 100644
index 0000000..9a04b90
--- /dev/null
+++ b/sdks/dotnet/basyx-core/BaSyx.Utils/Settings/Types/DependencySettings.cs
@@ -0,0 +1,99 @@
+/*******************************************************************************
+* Copyright (c) 2020 Robert Bosch GmbH
+* Author: Constantin Ziesche (constantin.ziesche@bosch.com)
+*
+* This program and the accompanying materials are made available under the
+* terms of the Eclipse Public License 2.0 which is available at
+* http://www.eclipse.org/legal/epl-2.0
+*
+* SPDX-License-Identifier: EPL-2.0
+*******************************************************************************/
+using BaSyx.Utils.AssemblyHandling;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.DependencyInjection.Extensions;
+using NLog;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Reflection;
+using System.Xml.Serialization;
+
+namespace BaSyx.Utils.Settings.Types
+{
+ public class DependencySettings : Settings<DependencySettings>
+ {
+ private static Logger logger = LogManager.GetCurrentClassLogger();
+
+ public DependencyConfiguration DependencyCollection { get; set; } = new DependencyConfiguration();
+
+
+
+ public class DependencyConfiguration
+ {
+ [XmlArrayItem("Dependency")]
+ public List<Dependency> Dependencies { get; set; } = new List<Dependency>();
+ }
+
+ public class Dependency
+ {
+ [XmlElement]
+ public string DllPath { get; set; }
+ [XmlElement]
+ public string InterfaceType { get; set; }
+ [XmlElement]
+ public string ImplementationType { get; set; }
+ [XmlElement]
+ public ServiceLifetime ServiceLifetime { get; set; }
+ }
+
+ public IServiceCollection GetServiceCollection(List<Assembly> externalAssemblies = null)
+ {
+ List<Assembly> assemblies = externalAssemblies ?? AssemblyUtils.GetLoadedAssemblies();
+
+ if (DependencyCollection?.Dependencies?.Count > 0)
+ {
+ ServiceCollection serviceCollection = new ServiceCollection();
+ foreach (var dependency in DependencyCollection.Dependencies)
+ {
+ string dllPath = Path.Combine(Settings.ExecutingDirectory, dependency.DllPath);
+ if(!string.IsNullOrEmpty(dependency.DllPath) && File.Exists(dllPath))
+ {
+ try
+ {
+ Assembly assembly = Assembly.LoadFrom(dllPath);
+ if (!assemblies.Contains(assembly))
+ assemblies.Add(assembly);
+ }
+ catch (Exception e)
+ {
+ logger.Error(e, "Failed to load assembly " + dllPath);
+ }
+
+ }
+
+
+ try
+ {
+ Type interfaceType = assemblies.Find(a => a.GetType(dependency.InterfaceType, false) != null)?.GetType(dependency.InterfaceType);
+ if (interfaceType == null)
+ throw new DllNotFoundException("Dll not found for interfaceType");
+
+ Type implementationType = assemblies.Find(a => a.GetType(dependency.ImplementationType, false) != null)?.GetType(dependency.ImplementationType);
+ if (implementationType == null)
+ throw new DllNotFoundException("Dll not found for implementationType");
+ ServiceDescriptor serviceDescriptor = new ServiceDescriptor(interfaceType, implementationType, dependency.ServiceLifetime);
+ serviceCollection.Add(serviceDescriptor);
+ }
+ catch (Exception e)
+ {
+ logger.Error(e, "Failed to load dependency " + dependency.ImplementationType + " for interface: " + dependency.InterfaceType);
+ continue;
+ }
+ }
+ return serviceCollection;
+ }
+ return null;
+ }
+
+ }
+}
diff --git a/sdks/dotnet/basyx-core/BaSyx.Utils/Settings/Types/ServerSettings.cs b/sdks/dotnet/basyx-core/BaSyx.Utils/Settings/Types/ServerSettings.cs
new file mode 100644
index 0000000..6fad282
--- /dev/null
+++ b/sdks/dotnet/basyx-core/BaSyx.Utils/Settings/Types/ServerSettings.cs
@@ -0,0 +1,29 @@
+/*******************************************************************************
+* Copyright (c) 2020 Robert Bosch GmbH
+* Author: Constantin Ziesche (constantin.ziesche@bosch.com)
+*
+* This program and the accompanying materials are made available under the
+* terms of the Eclipse Public License 2.0 which is available at
+* http://www.eclipse.org/legal/epl-2.0
+*
+* SPDX-License-Identifier: EPL-2.0
+*******************************************************************************/
+using System;
+using System.Xml.Serialization;
+
+namespace BaSyx.Utils.Settings.Types
+{
+ public class ServerSettings : Settings<ServerSettings>
+ {
+ public RegistryConfiguration RegistryConfig { get; set; } = new RegistryConfiguration();
+
+ public class RegistryConfiguration
+ {
+ [XmlElement]
+ public bool Activated { get; set; }
+ [XmlElement]
+ public string RegistryUrl { get; set; }
+ }
+
+ }
+}
diff --git a/sdks/dotnet/basyx-core/BaSyx.Utils/StringOperations/StringOperations.cs b/sdks/dotnet/basyx-core/BaSyx.Utils/StringOperations/StringOperations.cs
new file mode 100644
index 0000000..db243e0
--- /dev/null
+++ b/sdks/dotnet/basyx-core/BaSyx.Utils/StringOperations/StringOperations.cs
@@ -0,0 +1,49 @@
+/*******************************************************************************
+* Copyright (c) 2020 Robert Bosch GmbH
+* Author: Constantin Ziesche (constantin.ziesche@bosch.com)
+*
+* This program and the accompanying materials are made available under the
+* terms of the Eclipse Public License 2.0 which is available at
+* http://www.eclipse.org/legal/epl-2.0
+*
+* SPDX-License-Identifier: EPL-2.0
+*******************************************************************************/
+using System;
+
+namespace BaSyx.Utils.StringOperations
+{
+ public static class StringOperations
+ {
+ public static string GetValueOrStringEmpty<T>(T? nullable) where T : struct
+ {
+ if (nullable != null)
+ {
+ var value = Nullable.GetUnderlyingType(nullable.GetType());
+ if (value != null && value.IsEnum)
+ Enum.GetName(Nullable.GetUnderlyingType(nullable.GetType()), nullable.Value);
+ else
+ return nullable.Value.ToString();
+ }
+ return string.Empty;
+ }
+
+ public static string UppercaseFirst(this string s)
+ {
+ if (string.IsNullOrEmpty(s))
+ {
+ return string.Empty;
+ }
+ return char.ToUpper(s[0]) + s.Substring(1);
+ }
+
+ public static string LowercaseFirst(this string s)
+ {
+ if (string.IsNullOrEmpty(s))
+ {
+ return string.Empty;
+ }
+ return char.ToLower(s[0]) + s.Substring(1);
+ }
+
+ }
+}
diff --git a/sdks/dotnet/basyx-core/BaSyx.Utils/basyxlogo.png b/sdks/dotnet/basyx-core/BaSyx.Utils/basyxlogo.png
new file mode 100644
index 0000000..226a4ff
--- /dev/null
+++ b/sdks/dotnet/basyx-core/BaSyx.Utils/basyxlogo.png
Binary files differ