diff --git a/ln.http.api.demo/ln.http.api.demo.csproj b/ln.http.api.demo/ln.http.api.demo.csproj
index 12e29b0..f32b3c3 100644
--- a/ln.http.api.demo/ln.http.api.demo.csproj
+++ b/ln.http.api.demo/ln.http.api.demo.csproj
@@ -3,7 +3,7 @@
-
+
diff --git a/ln.http.api/HttpResponseExtensions.cs b/ln.http.api/HttpResponseExtensions.cs
index 9c4a853..ad3e833 100644
--- a/ln.http.api/HttpResponseExtensions.cs
+++ b/ln.http.api/HttpResponseExtensions.cs
@@ -8,7 +8,7 @@ namespace ln.http.api
public static HttpResponse Content(this HttpResponse response, JSONValue json)
{
- response.SetHeader("Content-type", "application/json");
+ response.ContentType("application/json");
response.ContentWriter.Write(json.ToString());
response.ContentWriter.Flush();
return response;
diff --git a/ln.http.api/JSONEventWebSocketResponse.cs b/ln.http.api/JSONEventWebSocketResponse.cs
new file mode 100644
index 0000000..a414a18
--- /dev/null
+++ b/ln.http.api/JSONEventWebSocketResponse.cs
@@ -0,0 +1,235 @@
+using System;
+using System.Collections.Generic;
+using System.Reflection;
+using System.Threading;
+using ln.http.websocket;
+using ln.json;
+using ln.json.mapping;
+using ln.logging;
+using ln.type;
+
+namespace ln.http.api
+{
+ public class JSONEventWebSocketResponse : JSONWebSocketResponse, IDisposable
+ {
+ static List activeWebSockets = new List();
+ public static void SendUpdates()
+ {
+ if (Monitor.TryEnter(activeWebSockets, 100))
+ {
+ try
+ {
+ foreach (JSONEventWebSocketResponse websocket in activeWebSockets)
+ websocket.SendUpdate();
+ } catch (Exception e)
+ {
+ Logging.Log(e);
+ } finally
+ {
+ Monitor.Exit(activeWebSockets);
+ }
+ } else {
+ Logging.Log(LogLevel.WARNING, "JSONEventWebSocketResponse: SendUpdates(): failed to enter critical section");
+ }
+ }
+
+ public string DefaultTargetName { get; set; }
+
+ Dictionary publishedTargets = new Dictionary();
+
+
+ public JSONEventWebSocketResponse()
+ {
+ OnWebSocketReceivedJSON += JSONReceived;
+ OnWebSocketStateChanged += (WebSocketResponse websocket, WebSocketState newState) => {
+ switch (newState)
+ {
+ case WebSocketState.CLOSED:
+ lock (activeWebSockets)
+ activeWebSockets.Remove(this);
+ break;
+ case WebSocketState.OPEN:
+ lock (activeWebSockets)
+ activeWebSockets.Add(this);
+ break;
+ }
+ };
+ }
+
+ public JSONEventWebSocketResponse(string defaultTargetName)
+ :this()
+ {
+ DefaultTargetName = defaultTargetName;
+ }
+
+ public JSONEventWebSocketResponse(object defaultInstance)
+ :this()
+ {
+ AddTarget(null, defaultInstance, defaultInstance.GetType().GetCustomAttribute()?.PublishPublicMembers ?? false, out Target target);
+ DefaultTargetName = target.Name;
+ }
+ public JSONEventWebSocketResponse(object defaultInstance, bool publishPublicMembers)
+ :this()
+ {
+ AddTarget(null, defaultInstance, publishPublicMembers, out Target target);
+ DefaultTargetName = target.Name;
+ }
+
+ public void AddTarget(object instance) => AddTarget(null, instance, instance.GetType().GetCustomAttribute()?.PublishPublicMembers ?? false);
+ public void AddTarget(object instance, bool publishPublicMembers) => AddTarget(null, instance, publishPublicMembers);
+ public void AddTarget(string targetName, object instance, bool publishPublicMembers) => AddTarget(targetName, instance, publishPublicMembers, out Target target);
+ public void AddTarget(string targetName, object instance, bool publishPublicMembers, out Target target)
+ {
+ target = new Target(targetName, instance, publishPublicMembers);
+ publishedTargets.Add(target.Name, target);
+ }
+
+
+ void JSONReceived(JSONWebSocketResponse sender, JSONValue json)
+ {
+ if (json is JSONObject jsonMessage)
+ {
+ string eventName = jsonMessage["event"].ToNative().ToString();
+ string targetName = jsonMessage["target"].ToNative().ToString();
+ JSONValue jsonValue = jsonMessage["value"];
+
+ Target target = publishedTargets[targetName ?? DefaultTargetName];
+
+ switch (eventName)
+ {
+ case "action":
+ target.Call(jsonValue);
+ break;
+ case "set":
+ target.SetProperty(jsonValue);
+ break;
+ }
+
+ }
+ }
+
+ public virtual void SendUpdate()
+ {
+ JSONObject jsonUpdateValue = new JSONObject();
+ JSONObject jsonUpdateMessage = new JSONObject()
+ .Add("event", "update")
+ .Add("value", jsonUpdateValue);
+
+ foreach (Target target in publishedTargets.Values)
+ jsonUpdateValue.Add(target.Name, target.CreateUpdateValue());
+
+ Send(jsonUpdateMessage);
+ }
+
+ public void Dispose()
+ {
+ lock (activeWebSockets)
+ activeWebSockets.Remove(this);
+ }
+
+ public class Target
+ {
+ public string Name { get; }
+ public object Instance { get; }
+
+ Dictionary publishedMethods = new Dictionary();
+ Dictionary publishedProperties = new Dictionary();
+ Dictionary publishedFields = new Dictionary();
+
+
+ public Target(object instance, bool publishPublicMembers) :this(instance.GetType().Name, instance, publishPublicMembers) { }
+ public Target(object instance) :this(instance.GetType().Name, instance, false) { }
+ public Target(string targetName, object instance) : this(targetName, instance, false) { }
+ public Target(string targetName, object instance, bool publishPublicMembers)
+ {
+ Instance = instance;
+ Name = targetName ?? Instance.GetType().Name;
+
+ Initialize(publishPublicMembers);
+ }
+
+
+ void Initialize(bool publishPublicMembers)
+ {
+ foreach (MethodInfo methodInfo in Instance.GetType().GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy))
+ {
+ ESMethodAttribute methodAttribute = methodInfo.GetCustomAttribute();
+ if ((methodAttribute != null) || (publishPublicMembers && methodInfo.IsPublic))
+ publishedMethods.Add(methodAttribute?.Name ?? methodInfo.Name, methodInfo);
+ }
+
+ foreach (FieldInfo fieldInfo in Instance.GetType().GetFields(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy))
+ {
+ ESPropertyAttribute propertyAttribute = fieldInfo.GetCustomAttribute();
+ if ((propertyAttribute != null) || (publishPublicMembers && fieldInfo.IsPublic))
+ publishedFields.Add(propertyAttribute?.Name ?? fieldInfo.Name, fieldInfo);
+ }
+
+ foreach (PropertyInfo propertyInfo in Instance.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy))
+ {
+ ESPropertyAttribute propertyAttribute = propertyInfo.GetCustomAttribute();
+ if ((propertyAttribute != null) || (publishPublicMembers && propertyInfo.GetMethod.IsPublic))
+ publishedProperties.Add(propertyAttribute?.Name ?? propertyInfo.Name, propertyInfo);
+ }
+ }
+
+ public JSONValue CreateUpdateValue()
+ {
+ JSONObject jsonUpdate = new JSONObject();
+
+ foreach (PropertyInfo propertyInfo in publishedProperties.Values)
+ if (propertyInfo.CanRead)
+ jsonUpdate.Add(propertyInfo.Name, JSONMapper.DefaultMapper.ToJson(propertyInfo.GetValue(Instance)));
+
+ foreach (FieldInfo fieldInfo in publishedFields.Values)
+ jsonUpdate.Add(fieldInfo.Name, JSONMapper.DefaultMapper.ToJson(fieldInfo.GetValue(Instance)));
+
+ return jsonUpdate;
+ }
+
+ public void Call(JSONValue callValue)
+ {
+ string methodName;
+ MethodInfo methodInfo;
+ object[] arguments;
+
+ if (callValue is JSONString jsonMethodName)
+ {
+ methodName = jsonMethodName.Value;
+ methodInfo = publishedMethods[methodName];
+ arguments = new object[0];
+ }
+ else if (callValue is JSONObject callObject)
+ {
+ methodName = callObject["method"].ToNative().ToString();
+ methodInfo = publishedMethods[methodName];
+ if (!JSONMapper.DefaultMapper.MapMethodParameters(methodInfo, callObject["arguments"], out arguments))
+ throw new ArgumentException(nameof(callValue));
+ }
+ else
+ throw new ArgumentOutOfRangeException(nameof(callValue));
+
+ methodInfo.Invoke(Instance, arguments);
+ }
+
+ public void Call(string methodName, object[] arguments)
+ {
+ MethodInfo methodInfo = publishedMethods[methodName];
+ methodInfo.Invoke(Instance, arguments);
+ }
+
+ public void SetProperty(JSONValue jsonValue)
+ {
+ if (jsonValue is JSONObject setObject)
+ {
+ string propertyName = setObject["name"].ToNative().ToString();
+ if (publishedProperties.TryGetValue(propertyName, out PropertyInfo propertyInfo) && propertyInfo.CanWrite)
+ propertyInfo.SetValue(Instance, Cast.To(setObject["value"].ToNative(), propertyInfo.PropertyType));
+ else if (publishedFields.TryGetValue(propertyName, out FieldInfo fieldInfo) && !fieldInfo.IsInitOnly)
+ fieldInfo.SetValue(Instance, Cast.To(setObject["value"].ToNative(), fieldInfo.FieldType));
+ }
+ }
+
+ }
+ }
+}
\ No newline at end of file
diff --git a/ln.http.api/JSONWebSocketResponse.cs b/ln.http.api/JSONWebSocketResponse.cs
new file mode 100644
index 0000000..26c0cbf
--- /dev/null
+++ b/ln.http.api/JSONWebSocketResponse.cs
@@ -0,0 +1,39 @@
+using System;
+using ln.http.websocket;
+using ln.json;
+using ln.logging;
+
+namespace ln.http.api
+{
+
+ public delegate void WebSocketReceivedJSON(JSONWebSocketResponse sender, JSONValue json);
+
+ public class JSONWebSocketResponse : WebSocketResponse
+ {
+ public event WebSocketReceivedJSON OnWebSocketReceivedJSON;
+
+ public JSONWebSocketResponse(){ }
+
+ public override void Received(string textMessage)
+ {
+ base.Received(textMessage);
+ try
+ {
+ JSONValue json = JSONParser.Parse(textMessage);
+ try {
+ OnWebSocketReceivedJSON?.Invoke(this, json);
+ } catch (Exception ex)
+ {
+ Logging.Log(ex);
+ }
+
+ } catch (Exception e)
+ {
+ Logging.Log(LogLevel.ERROR, "JSONWebSocketResponse: received unparsable json message: {0}", textMessage);
+ Close();
+ }
+ }
+
+ public void Send(JSONValue json) => Send(json.ToString());
+ }
+}
\ No newline at end of file
diff --git a/ln.http.api/WebApiController.cs b/ln.http.api/WebApiController.cs
index eab368b..4c8a83e 100644
--- a/ln.http.api/WebApiController.cs
+++ b/ln.http.api/WebApiController.cs
@@ -51,12 +51,14 @@ namespace ln.http.api
return httpResponse;
if (result is JSONValue jsonResult)
- return defaultResponseFactory().Content(jsonResult);
+ return defaultResponseFactory()
+ .ContentType("application/json")
+ .Content(jsonResult);
if (JSONMapper.DefaultMapper.Serialize(result, out JSONValue json))
return defaultResponseFactory()
.ContentType("application/json")
- .Content(result.ToString())
+ .Content(json.ToString())
;
return HttpResponse.InternalServerError().Content("Method result could not be serialized");
diff --git a/ln.http.api/attributes/ESMethodAttribute.cs b/ln.http.api/attributes/ESMethodAttribute.cs
new file mode 100644
index 0000000..7f51a19
--- /dev/null
+++ b/ln.http.api/attributes/ESMethodAttribute.cs
@@ -0,0 +1,10 @@
+
+using System;
+
+namespace ln.http.api
+{
+ public class ESMethodAttribute : Attribute
+ {
+ public string Name { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/ln.http.api/attributes/ESPropertyAttribute.cs b/ln.http.api/attributes/ESPropertyAttribute.cs
new file mode 100644
index 0000000..9ef1c88
--- /dev/null
+++ b/ln.http.api/attributes/ESPropertyAttribute.cs
@@ -0,0 +1,11 @@
+
+using System;
+
+namespace ln.http.api
+{
+ public class ESPropertyAttribute : Attribute
+ {
+ public string Name { get; set; }
+ public bool ReadOnly { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/ln.http.api/attributes/ESTargetAttribute.cs b/ln.http.api/attributes/ESTargetAttribute.cs
new file mode 100644
index 0000000..e99e2b8
--- /dev/null
+++ b/ln.http.api/attributes/ESTargetAttribute.cs
@@ -0,0 +1,12 @@
+
+
+using System;
+using System.Reflection;
+
+namespace ln.http.api
+{
+ public class ESTargetAttribute : Attribute
+ {
+ public bool PublishPublicMembers { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/ln.http.api/ln.http.api.csproj b/ln.http.api/ln.http.api.csproj
index 19db803..072346f 100644
--- a/ln.http.api/ln.http.api.csproj
+++ b/ln.http.api/ln.http.api.csproj
@@ -4,7 +4,7 @@
netcoreapp3.1
true
- 0.0.4
+ 0.0.6
Harald Wolff-Thobaben
l--n.de
Framework to create REST like APIs
@@ -13,8 +13,8 @@
-
-
+
+