diff --git a/Pilz.Extensions/Pilz.Extensions.csproj b/Pilz.Extensions/Pilz.Extensions.csproj
new file mode 100644
index 0000000..d713480
--- /dev/null
+++ b/Pilz.Extensions/Pilz.Extensions.csproj
@@ -0,0 +1,13 @@
+
+
+
+ net8.0
+ enable
+ enable
+
+
+
+ 2.0.0
+
+
+
diff --git a/Pilz.Extensions/Reflection/MethodInfoExtensions.cs b/Pilz.Extensions/Reflection/MethodInfoExtensions.cs
new file mode 100644
index 0000000..cb2f9d7
--- /dev/null
+++ b/Pilz.Extensions/Reflection/MethodInfoExtensions.cs
@@ -0,0 +1,24 @@
+using System.Linq.Expressions;
+using System.Reflection;
+
+namespace Pilz.Extensions.Reflection;
+
+public static class MethodInfoExtensions
+{
+ ///
+ /// Source: https://stackoverflow.com/questions/940675/getting-a-delegate-from-methodinfo
+ ///
+ ///
+ ///
+ ///
+ public static Delegate CreateDelegate(this MethodInfo methodInfo, object? target)
+ {
+ var parmTypes = methodInfo.GetParameters().Select(parm => parm.ParameterType);
+ var parmAndReturnTypes = parmTypes.Append(methodInfo.ReturnType).ToArray();
+ var delegateType = Expression.GetDelegateType(parmAndReturnTypes);
+
+ if (methodInfo.IsStatic)
+ return methodInfo.CreateDelegate(delegateType);
+ return methodInfo.CreateDelegate(delegateType, target);
+ }
+}
diff --git a/Pilz.Networking/Api/ApiServer.cs b/Pilz.Networking/Api/ApiServer.cs
index 4b65db1..723c3f7 100644
--- a/Pilz.Networking/Api/ApiServer.cs
+++ b/Pilz.Networking/Api/ApiServer.cs
@@ -1,49 +1,139 @@
-using System.Reflection;
+using Pilz.Extensions.Reflection;
+using System;
+using System.Net;
+using System.Reflection;
+using System.Reflection.PortableExecutable;
namespace Pilz.Networking.Api;
-public class ApiServer : ApiClient, IApiServer
+public class ApiServer(string apiUrl) : ApiClient, IApiServer
{
- protected readonly Dictionary handlers = [];
+ protected readonly Dictionary handlers = [];
protected readonly Dictionary serializers = [];
+ protected readonly HttpListener httpListener = new();
+
+ public string ApiUrl { get; } = apiUrl;
public IMessageSerializer Serializer { get; set; } = new DefaultMessageSerializer();
- public virtual void RegisterHandler(string url, T instance) where T : class
+ public virtual void Start()
{
- var methods = typeof(T).GetMethods(BindingFlags.Instance | BindingFlags.Public);
-
- foreach (var method in methods)
- {
- if (method.GetCustomAttribute() is not MessageHandlerAttribute attribute
- || method.GetParameters().FirstOrDefault() is not ParameterInfo parameterInfo
- || !parameterInfo.ParameterType.IsAssignableTo(typeof(ApiMessage)))
- continue;
-
-
- var serializer = Serializer;
- if (attribute.Serializer is not null)
- {
- if (serializers.TryGetValue(attribute.Serializer, out var s) && s is not null)
- serializer = s;
- else if (Activator.CreateInstance(attribute.Serializer) is IMessageSerializer ss)
- {
- serializer = ss;
- serializers.Add(attribute.Serializer, ss);
- }
- }
-
- // ...
- }
+ httpListener.Start();
+ Listen();
}
- public virtual void RegisterHandler(string url, Delegate handler)
+ public virtual void RegisterHandler(T instance) where T : class
{
- // ...
+ // Get all public instance methods
+ var methods = typeof(T).GetMethods(BindingFlags.Instance | BindingFlags.Public);
+
+ // Register each method
+ foreach (var method in methods)
+ RegisterHandler(method.CreateDelegate(instance));
+ }
+
+ public virtual void RegisterHandler(Delegate handler)
+ {
+ var method = handler.Method;
+
+ // Sanity checks
+ if (method.GetCustomAttribute() is not MessageHandlerAttribute attribute
+ || method.GetParameters().FirstOrDefault() is not ParameterInfo parameter
+ || !parameter.ParameterType.IsAssignableTo(typeof(ApiMessage))
+ || !method.ReturnType.IsAssignableTo(typeof(ApiMessage)))
+ throw new NotSupportedException("The first parameter needs to be of type ApiMessage and must also return an ApiMessage object and the method must have the MessageHAndlerAttribute.");
+
+ // Add handler
+ handlers.Add(ApiUrl + attribute.Url, handler);
+ }
+
+ protected virtual void Listen()
+ {
+ while (httpListener.IsListening)
+ {
+ var context = httpListener.GetContext();
+
+ if (context.Request.HttpMethod != HttpMethod.Post.Method
+ || context.Request.ContentType is not string contentType
+ || contentType.Contains("application/json")
+ || context.Request.AcceptTypes is null
+ || context.Request.AcceptTypes.Contains("application/json"))
+ {
+ close();
+ continue;
+ }
+
+ // Parse url
+ var path = context.Request.Url?.PathAndQuery.Replace(ApiUrl, string.Empty);
+ if (string.IsNullOrWhiteSpace(path) || context.Request.ContentLength64 <= 0)
+ {
+ close();
+ continue;
+ }
+
+ // Read input content
+ using StreamReader input = new(context.Request.InputStream);
+ var contentJson = input.ReadToEnd();
+
+ // Handle message
+ if (HandleMessage(path, contentJson) is not string resultJson)
+ {
+ close();
+ continue;
+ }
+
+ // Set response parameters
+ context.Response.StatusCode = (int)args.ResponseStatusCode;
+ context.Response.StatusDescription = args.ResponseStatusDescription;
+
+ // Write response content
+ context.Response.ContentType = ContentTypes.CONTENT_TYPE_JSON;
+ using StreamWriter output = new(context.Response.OutputStream);
+ output.Write(resultJson);
+
+ close();
+ void close() => context.Response.OutputStream.Close();
+ }
}
protected virtual string? HandleMessage(string url, string json)
{
- // ...
+ // Get handler
+ if (!handlers.TryGetValue(url, out var handler)
+ || handler.Method.GetCustomAttribute() is not MessageHandlerAttribute attribute)
+ return null;
+
+ // Get required infos
+ var targetType = handler.Method.GetParameters().First().ParameterType;
+ var serializer = GetSerializer(attribute.Serializer);
+
+ // Deserialize
+ if (serializer.Deserialize(json, targetType) is not ApiMessage message)
+ return null;
+
+ // Invoke handler
+ if (handler.DynamicInvoke(message) is not ApiMessage result)
+ return null;
+
+ // Serializer
+ if (serializer.Serialize(result) is not string resultStr)
+ return null;
+
+ return resultStr;
+ }
+
+ protected virtual IMessageSerializer GetSerializer(Type? t)
+ {
+ if (t is not null)
+ {
+ if (serializers.TryGetValue(t, out var s) && s is not null)
+ return s;
+ else if (Activator.CreateInstance(t) is IMessageSerializer ss)
+ {
+ serializers.Add(t, ss);
+ return ss;
+ }
+ }
+ return Serializer;
}
}
diff --git a/Pilz.Networking/Api/DefaultMessageSerializer.cs b/Pilz.Networking/Api/DefaultMessageSerializer.cs
index df98251..20ad3be 100644
--- a/Pilz.Networking/Api/DefaultMessageSerializer.cs
+++ b/Pilz.Networking/Api/DefaultMessageSerializer.cs
@@ -1,14 +1,28 @@
-namespace Pilz.Networking.Api;
+using Newtonsoft.Json;
+
+namespace Pilz.Networking.Api;
public class DefaultMessageSerializer : IMessageSerializer
{
- public ApiMessage? Deserialize(string json)
+ private static JsonSerializerSettings? defaultSerializerSettings;
+
+ public JsonSerializerSettings DefaultSerializerSettings => defaultSerializerSettings ??= CreateDefaultSerializerSettings();
+
+ protected virtual JsonSerializerSettings CreateDefaultSerializerSettings()
{
- // ...
+ return new JsonSerializerSettings
+ {
+ TypeNameHandling = TypeNameHandling.Auto,
+ };
}
- public string? Serialize(ApiMessage message)
+ public virtual string? Serialize(ApiMessage message)
{
- // ...
+ return JsonConvert.SerializeObject(message, DefaultSerializerSettings);
+ }
+
+ public virtual ApiMessage? Deserialize(string json, Type target)
+ {
+ return JsonConvert.DeserializeObject(json, target, DefaultSerializerSettings) as ApiMessage;
}
}
diff --git a/Pilz.Networking/Api/IApiServer.cs b/Pilz.Networking/Api/IApiServer.cs
index dfdc4b6..4ebfa4e 100644
--- a/Pilz.Networking/Api/IApiServer.cs
+++ b/Pilz.Networking/Api/IApiServer.cs
@@ -2,6 +2,6 @@
public interface IApiServer : IApiClient
{
- void RegisterHandler(string url, T instance) where T : class;
- void RegisterHandler(string url, Delegate handler);
+ void RegisterHandler(T instance) where T : class;
+ void RegisterHandler(Delegate handler);
}
diff --git a/Pilz.Networking/Api/IMessageSerializer.cs b/Pilz.Networking/Api/IMessageSerializer.cs
index 84597fa..84eda48 100644
--- a/Pilz.Networking/Api/IMessageSerializer.cs
+++ b/Pilz.Networking/Api/IMessageSerializer.cs
@@ -3,5 +3,5 @@
public interface IMessageSerializer
{
string? Serialize(ApiMessage message);
- ApiMessage? Deserialize(string json);
+ ApiMessage? Deserialize(string json, Type target);
}
diff --git a/Pilz.Networking/Pilz.Networking.csproj b/Pilz.Networking/Pilz.Networking.csproj
index cad8bb1..d33d5b4 100644
--- a/Pilz.Networking/Pilz.Networking.csproj
+++ b/Pilz.Networking/Pilz.Networking.csproj
@@ -15,4 +15,8 @@
+
+
+
+
\ No newline at end of file
diff --git a/Pilz.sln b/Pilz.sln
index ebbb460..1846eca 100644
--- a/Pilz.sln
+++ b/Pilz.sln
@@ -43,6 +43,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Pilz.Plugins.Advanced.UI",
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Pilz.Plugins.Advanced.UI.Telerik", "Pilz.Plugins.Advanced.UI.Telerik\Pilz.Plugins.Advanced.UI.Telerik.csproj", "{0A837BD6-A19C-4A05-A57D-CBB0CD64B244}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Pilz.Extensions", "Pilz.Extensions\Pilz.Extensions.csproj", "{63DA7581-F35E-4EDD-BEAE-01E281B0BDC3}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -211,6 +213,14 @@ Global
{0A837BD6-A19C-4A05-A57D-CBB0CD64B244}.Release|Any CPU.Build.0 = Release|Any CPU
{0A837BD6-A19C-4A05-A57D-CBB0CD64B244}.Release|x86.ActiveCfg = Release|Any CPU
{0A837BD6-A19C-4A05-A57D-CBB0CD64B244}.Release|x86.Build.0 = Release|Any CPU
+ {63DA7581-F35E-4EDD-BEAE-01E281B0BDC3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {63DA7581-F35E-4EDD-BEAE-01E281B0BDC3}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {63DA7581-F35E-4EDD-BEAE-01E281B0BDC3}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {63DA7581-F35E-4EDD-BEAE-01E281B0BDC3}.Debug|x86.Build.0 = Debug|Any CPU
+ {63DA7581-F35E-4EDD-BEAE-01E281B0BDC3}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {63DA7581-F35E-4EDD-BEAE-01E281B0BDC3}.Release|Any CPU.Build.0 = Release|Any CPU
+ {63DA7581-F35E-4EDD-BEAE-01E281B0BDC3}.Release|x86.ActiveCfg = Release|Any CPU
+ {63DA7581-F35E-4EDD-BEAE-01E281B0BDC3}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE