重写了Web Api的逻辑,用Emit构造委托加速API处理

This commit is contained in:
fengjiayi
2024-10-10 16:49:37 +08:00
parent d1b9a3f28f
commit 99f82d5772
25 changed files with 792 additions and 628 deletions

View File

@@ -500,7 +500,7 @@ namespace Serein.Library.Api
/// 清理加载的DLL待更改
/// </summary>
void ClearAll();
/// <summary>
/// 开始运行
@@ -549,6 +549,11 @@ namespace Serein.Library.Api
/// <param name="nodeGuid">待移除的节点Guid</param>
void RemoteNode(string nodeGuid);
// 启动触发器
void ActivateFlipflopNode(string nodeGuid);
void TerminateFlipflopNode(string nodeGuid);
/// <summary>
/// 设置节点中断级别
/// </summary>
@@ -612,7 +617,6 @@ namespace Serein.Library.Api
#endregion
#region UI视觉
/// <summary>

View File

@@ -8,6 +8,9 @@ using static Serein.Library.Utils.EmitHelper;
namespace Serein.Library.Entity
{
/// <summary>
/// 委托描述
/// </summary>
public class DelegateDetails
{
public DelegateDetails(EmitMethodType EmitMethodType, Delegate EmitDelegate)

View File

@@ -22,7 +22,7 @@ namespace Serein.Library.Enums
Exit,
/// <summary>
/// 触发器
/// 触发器(建议手动设置返回类型,方便视图直观)
/// </summary>
Flipflop,
/// <summary>

View File

@@ -51,7 +51,7 @@ namespace Serein.Library.Web
[AttributeUsage(AttributeTargets.Method)]
public class WebApiAttribute : Attribute
{
public API Http; // HTTP 请求类型
public ApiType ApiType; // HTTP 请求类型
public string Url; // URL 路径
/// <summary>
/// 方法名称不作为url的部分
@@ -68,15 +68,15 @@ namespace Serein.Library.Web
/// </summary>
/// <param name="http"></param>
/// <param name="url"></param>
public WebApiAttribute(API http = API.POST, bool isUrl = true, string url = "")
public WebApiAttribute(ApiType http = ApiType.POST, bool isUrl = true, string url = "")
{
Http = http;
ApiType = http;
Url = url;
IsUrl = isUrl;
}
}
public enum API
public enum ApiType
{
POST,
GET,

View File

@@ -2,14 +2,18 @@
using Newtonsoft.Json.Linq;
using Serein.Library.Api;
using Serein.Library.Attributes;
using Serein.Library.Entity;
using Serein.Library.Utils;
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.ComponentModel.Design;
using System.IO;
using System.Linq;
using System.Net;
using System.Reflection;
using System.Security.AccessControl;
using System.Text;
using System.Threading.Tasks;
using System.Web;
@@ -20,10 +24,199 @@ namespace Serein.Library.Web
{
public interface IRouter
{
bool RegisterController(Type controllerType);
void AddHandle(Type controllerType);
Task<bool> ProcessingAsync(HttpListenerContext context);
}
public class ApiHandleConfig
{
private readonly Delegate EmitDelegate;
private readonly EmitHelper.EmitMethodType EmitMethodType;
public enum PostArgType
{
None,
IsUrlData,
IsBobyData,
}
public ApiHandleConfig(MethodInfo methodInfo)
{
EmitMethodType = EmitHelper.CreateDynamicMethod(methodInfo, out EmitDelegate);
var parameterInfos = methodInfo.GetParameters();
ParameterType = parameterInfos.Select(t => t.ParameterType).ToArray();
ParameterName = parameterInfos.Select(t => t.Name.ToLower()).ToArray();
PostArgTypes = parameterInfos.Select(p =>
{
bool isUrlData = p.GetCustomAttribute(typeof(UrlAttribute)) != null;
bool isBobyData = p.GetCustomAttribute(typeof(BobyAttribute)) != null;
if (isBobyData)
{
return PostArgType.IsBobyData;
}
else if (isUrlData)
{
return PostArgType.IsUrlData;
}
else
{
return PostArgType.None;
}
}).ToArray();
}
private readonly PostArgType[] PostArgTypes;
private readonly string[] ParameterName;
private readonly Type[] ParameterType;
public async Task<object> HandleGet(object instance, Dictionary<string, string> routeData)
{
object[] args = new object[ParameterType.Length];
for (int i = 0; i < ParameterType.Length; i++)
{
var type = ParameterType[i];
var argName = ParameterName[i];
if (routeData.TryGetValue(argName, out var argValue))
{
if (type == typeof(string))
{
args[i] = argValue;
}
else // if (type.IsValueType)
{
args[i] = JsonConvert.DeserializeObject(argValue, type);
}
}
else
{
args[i] = type.IsValueType ? Activator.CreateInstance(type) : null;
}
}
object result;
try
{
if (EmitMethodType == EmitHelper.EmitMethodType.HasResultTask && EmitDelegate is Func<object, object[], Task<object>> hasResultTask)
{
result = await hasResultTask(instance, args);
}
else if (EmitMethodType == EmitHelper.EmitMethodType.Task && EmitDelegate is Func<object, object[], Task> task)
{
await task.Invoke(instance, args);
result = null;
}
else if (EmitMethodType == EmitHelper.EmitMethodType.Func && EmitDelegate is Func<object, object[], object> func)
{
result = func.Invoke(instance, args);
}
else
{
result = null;
}
}
catch (Exception ex)
{
result = null;
await Console.Out.WriteLineAsync(ex.Message);
}
return result;
}
public async Task<object> HandlePost(object instance, JObject jsonObject, Dictionary<string, string> routeData)
{
object[] args = new object[ParameterType.Length];
for (int i = 0; i < ParameterType.Length; i++)
{
var type = ParameterType[i];
var argName = ParameterName[i];
if (PostArgTypes[i] == PostArgType.IsUrlData)
{
if (routeData.TryGetValue(argName, out var argValue))
{
if (type == typeof(string))
{
args[i] = argValue;
}
else // if (type.IsValueType)
{
args[i] = JsonConvert.DeserializeObject(argValue, type);
}
}
else
{
args[i] = type.IsValueType ? Activator.CreateInstance(type) : null;
}
}
else if (PostArgTypes[i] == PostArgType.IsBobyData)
{
args[i] = jsonObject;
}
else
{
var jsonValue = jsonObject.GetValue(argName);
if (jsonValue is null)
{
// 值类型返回默认值引用类型返回null
args[i] = type.IsValueType ? Activator.CreateInstance(type) : null;
}
else
{
args[i] = jsonValue.ToObject(type);
}
}
}
object result;
try
{
if (EmitMethodType == EmitHelper.EmitMethodType.HasResultTask && EmitDelegate is Func<object, object[], Task<object>> hasResultTask)
{
result = await hasResultTask(instance, args);
}
else if (EmitMethodType == EmitHelper.EmitMethodType.Task && EmitDelegate is Func<object, object[], Task> task)
{
await task.Invoke(instance, args);
result = null;
}
else if (EmitMethodType == EmitHelper.EmitMethodType.Func && EmitDelegate is Func<object, object[], object> func)
{
result = func.Invoke(instance, args);
}
else
{
result = null;
}
}
catch (Exception ex)
{
result = null;
await Console.Out.WriteLineAsync(ex.Message);
}
return result;
}
}
/// <summary>
/// 路由注册与解析
/// </summary>
@@ -35,30 +228,30 @@ namespace Serein.Library.Web
/// <summary>
/// 控制器实例对象的类型,每次调用都会重新实例化,[Url - ControllerType]
/// </summary>
private readonly ConcurrentDictionary<string, Type> _controllerTypes; // 存储控制器类型
private readonly ConcurrentDictionary<string, Type> _controllerTypes = new ConcurrentDictionary<string, Type>();
/// <summary>
/// 用于存储路由信息,[GET|POST - [Url - Method]]
/// </summary>
private readonly ConcurrentDictionary<string, ConcurrentDictionary<string, MethodInfo>> _routes;
//private readonly ConcurrentDictionary<string, ConcurrentDictionary<string, MethodInfo>> _routes = new ConcurrentDictionary<string, ConcurrentDictionary<string, MethodInfo>>();
private readonly ConcurrentDictionary<string, ConcurrentDictionary<string, ApiHandleConfig>> HandleModels = new ConcurrentDictionary<string, ConcurrentDictionary<string, ApiHandleConfig>>();
// private readonly ILoggerService loggerService; // 用于存储路由信息
//private Type PostRequest;
public Router(ISereinIOC SereinIOC)
{
this.SereinIOC = SereinIOC;
_routes = new ConcurrentDictionary<string, ConcurrentDictionary<string, MethodInfo>>(); // 初始化路由字典
_controllerTypes = new ConcurrentDictionary<string, Type>(); // 初始化控制器实例对象字典
foreach (API method in Enum.GetValues(typeof(API))) // 遍历 HTTP 枚举类型的所有值
foreach (ApiType method in Enum.GetValues(typeof(ApiType))) // 遍历 HTTP 枚举类型的所有值
{
_routes.TryAdd(method.ToString(), new ConcurrentDictionary<string, MethodInfo>()); // 初始化每种 HTTP 方法对应的路由字典
HandleModels.TryAdd(method.ToString(), new ConcurrentDictionary<string, ApiHandleConfig>()); // 初始化每种 HTTP 方法对应的路由字典
}
#if false
Type baseAttribute = typeof(AutoHostingAttribute);
Type baseController = typeof(ControllerBase);
// 获取当前程序集
Assembly assembly = Assembly.GetExecutingAssembly();
// 获取包含“Controller”名称的类型
@@ -69,9 +262,44 @@ namespace Serein.Library.Web
foreach (var controllerType in controllerTypes)
{
RegisterController(controllerType);
}
}
#endif
}
public void AddHandle(Type controllerType)
{
if (!controllerType.IsClass || controllerType.IsAbstract) return; // 如果不是类或者是抽象类,则直接返回
var autoHostingAttribute = controllerType.GetCustomAttribute<AutoHostingAttribute>();
var methods = controllerType.GetMethods().Where(m => m.GetCustomAttribute<WebApiAttribute>() != null).ToArray();
foreach (var method in methods) // 遍历控制器类型的所有方法
{
var routeAttribute = method.GetCustomAttribute<WebApiAttribute>(); // 获取方法上的 WebAPIAttribute 自定义属性
if (routeAttribute is null) // 如果存在 WebAPIAttribute 属性
{
continue;
}
var url = AddRoutesUrl(autoHostingAttribute, routeAttribute, controllerType, method);
if (url is null) continue;
Console.WriteLine(url);
var apiType = routeAttribute.ApiType.ToString();
var config = new ApiHandleConfig(method);
if(!HandleModels.TryGetValue(apiType, out var configs))
{
configs = new ConcurrentDictionary<string, ApiHandleConfig>();
HandleModels[apiType] = configs;
}
configs.TryAdd(url, config);
_controllerTypes.TryAdd(url,controllerType);
}
return;
}
/// <summary>
/// 在外部调用API后解析路由调用对应的方法
/// </summary>
@@ -84,14 +312,14 @@ namespace Serein.Library.Web
var url = request.Url; // 获取请求的 URL
var httpMethod = request.HttpMethod; // 获取请求的 HTTP 方法
var template = request.Url.AbsolutePath.ToLower();
if (!_routes[httpMethod].TryGetValue(template, out MethodInfo method))
if (!_controllerTypes.TryGetValue(template, out Type controllerType))
{
return false;
}
var routeValues = GetUrlData(url); // 解析 URL 获取路由参数
ControllerBase controllerInstance = (ControllerBase)SereinIOC.Instantiate(_controllerTypes[template]);// 使用反射创建控制器实例
ControllerBase controllerInstance = (ControllerBase)SereinIOC.Instantiate(controllerType);
if (controllerInstance is null)
{
@@ -99,195 +327,51 @@ namespace Serein.Library.Web
}
controllerInstance.Url = url.AbsolutePath;
if (!HandleModels.TryGetValue(httpMethod, out var modules))
{
return false;
}
if (!modules.TryGetValue(template,out var config))
{
return false;
}
dynamic invokeResult;
switch (httpMethod)
{
case "GET":
invokeResult = config.HandleGet(controllerInstance, routeValues);
break;
case "POST":
var requestBody = await ReadRequestBodyAsync(request); // 读取请求体内容
controllerInstance.BobyData = requestBody;
var requestJObject = JObject.Parse(requestBody);
invokeResult = config.HandlePost(controllerInstance, requestJObject, routeValues);
break;
default:
invokeResult = null;
break;
}
object result;
try
{
object result;
switch (httpMethod) // 根据请求的 HTTP 方法执行不同的操作
{
case "GET": // 如果是 GET 请求传入方法、控制器、url参数
// loggerService.Information(GetLog(template));
result = InvokeControllerMethodWithRouteValues(method, controllerInstance, routeValues);
break;
case "POST": // POST 请求传入方法、控制器、请求体内容url参数
var requestBody = await ReadRequestBodyAsync(request); // 读取请求体内容
controllerInstance.BobyData = requestBody;
var requestJObject = JObject.Parse(requestBody); //requestBody.FromJSON<JObject>();
// loggerService.Information(GetLog(template, requestBody));
result = InvokeControllerMethod(method, controllerInstance, requestJObject, routeValues);
break;
default:
result = null;
break;
}
Return(response, result); // 返回结果
return true;
result = invokeResult.Result;
}
catch (Exception ex)
catch (Exception)
{
response.StatusCode = (int)HttpStatusCode.NotFound; // 返回 404 错误
Return(response, ex.Message); // 返回结果
return true;
}
}
/// <summary>
/// 自动注册并实例化控制器类型
/// </summary>
/// <param name="controllerType"></param>
public bool RegisterController(Type controllerType) // 方法声明,用于注册并实例化控制器类型
{
if (!controllerType.IsClass || controllerType.IsAbstract) return false; // 如果不是类或者是抽象类,则直接返回
var autoHostingAttribute = controllerType.GetCustomAttribute<AutoHostingAttribute>();
var methods = controllerType.GetMethods().Where(m => m.GetCustomAttribute<WebApiAttribute>() != null).ToArray();
foreach (var method in methods) // 遍历控制器类型的所有方法
{
var routeAttribute = method.GetCustomAttribute<WebApiAttribute>(); // 获取方法上的 WebAPIAttribute 自定义属性
if (routeAttribute != null) // 如果存在 WebAPIAttribute 属性
{
var url = AddRoutesUrl(autoHostingAttribute, routeAttribute, controllerType, method);
Console.WriteLine(url);
if (url is null) continue;
_controllerTypes[url] = controllerType;
}
result = invokeResult;
}
Return(response, result); // 返回结果
return true;
}
#region Get Post对应的方法
/// <summary>
/// GET请求的控制器方法
/// </summary>
private object InvokeControllerMethodWithRouteValues(MethodInfo method, object controllerInstance, Dictionary<string, string> routeValues)
{
object[] parameters = GetMethodParameters(method, routeValues);
return InvokeMethod(method, controllerInstance, parameters);
}
/// <summary>
/// GET请求调用控制器方法传入参数
/// </summary>
/// <param name="method">方法</param>
/// <param name="controllerInstance">控制器实例</param>
/// <param name="methodParameters">参数列表</param>
/// <returns></returns>
private object InvokeMethod(MethodInfo method, object controllerInstance, object[] methodParameters)
{
object result = null;
try
{
result = method?.Invoke(controllerInstance, methodParameters);
}
catch (ArgumentException ex)
{
string targetType = ExtractTargetTypeFromExceptionMessage(ex.Message);
// 如果方法调用失败
result = new
{
error = $"函数签名类型[{targetType}]不符合",
};
}
catch (JsonSerializationException ex)
{
// 查找类型信息开始的索引
int startIndex = ex.Message.IndexOf("to type '") + "to type '".Length;
// 查找类型信息结束的索引
int endIndex = ex.Message.IndexOf("'", startIndex);
// 提取类型信息
string typeInfo = ex.Message.Substring(startIndex, endIndex - startIndex);
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
return result; // 调用方法并返回结果
}
private readonly Dictionary<MethodInfo, ParameterInfo[]> methodParameterCache = new Dictionary<MethodInfo, ParameterInfo[]>();
/// <summary>
/// POST请求调用控制器方法传入参数
/// </summary>
public object InvokeControllerMethod(MethodInfo method, object controllerInstance, JObject requestData, Dictionary<string, string> routeValues)
{
if (requestData is null) return null;
ParameterInfo[] parameters;
object[] cachedMethodParameters;
if (!methodParameterCache.TryGetValue(method, out parameters))
{
parameters = method.GetParameters();
}
cachedMethodParameters = new object[parameters.Length];
for (int i = 0; i < parameters.Length; i++)
{
string paramName = parameters[i].Name;
bool isUrlData = parameters[i].GetCustomAttribute(typeof(UrlAttribute)) != null;
bool isBobyData = parameters[i].GetCustomAttribute(typeof(BobyAttribute)) != null;
if (isUrlData)
{
if (routeValues.ContainsKey(paramName))
{
cachedMethodParameters[i] = ConvertValue(routeValues[paramName], parameters[i].ParameterType);
}
else
{
cachedMethodParameters[i] = null;
}
}
else if (isBobyData)
{
cachedMethodParameters[i] = ConvertValue(requestData.ToString(), parameters[i].ParameterType);
}
else
{
if (requestData.ContainsKey(paramName))
{
var rd = requestData[paramName];
if (parameters[i].ParameterType == typeof(string))
{
cachedMethodParameters[i] = rd.ToString();
}
else if (parameters[i].ParameterType == typeof(bool))
{
cachedMethodParameters[i] = rd.ToBool();
}
else if (parameters[i].ParameterType == typeof(int))
{
cachedMethodParameters[i] = rd.ToInt();
}
else if (parameters[i].ParameterType == typeof(double))
{
cachedMethodParameters[i] = rd.ToDouble();
}
else
{
cachedMethodParameters[i] = ConvertValue(rd, parameters[i].ParameterType);
}
}
else
{
cachedMethodParameters[i] = null;
}
}
}
// 缓存方法和参数的映射
//methodParameterCache[method] = cachedMethodParameters;
// 调用方法
return method.Invoke(controllerInstance, cachedMethodParameters);
}
#endregion
#region
/// <summary>
/// 读取Body中的消息
/// </summary>
@@ -304,72 +388,6 @@ namespace Serein.Library.Web
}
}
/// <summary>
/// 检查方法入参参数类型,返回对应的入参数组
/// </summary>
/// <param name="method"></param>
/// <param name="routeValues"></param>
/// <returns></returns>
private object[] GetMethodParameters(MethodInfo method, Dictionary<string, string> routeValues)
{
ParameterInfo[] methodParameters = method.GetParameters();
object[] parameters = new object[methodParameters.Length];
for (int i = 0; i < methodParameters.Length; i++)
{
string paramName = methodParameters[i].Name;
if (routeValues.ContainsKey(paramName))
{
parameters[i] = ConvertValue(routeValues[paramName], methodParameters[i].ParameterType);
}
else
{
parameters[i] = null;
}
}
return parameters;
}
/// <summary>
/// 转为对应的类型
/// </summary>
/// <param name="value"></param>
/// <param name="targetType"></param>
/// <returns></returns>
private object ConvertValue(object value, Type targetType)
{
try
{
if (targetType == typeof(string))
{
return value;
}
else
{
return JsonConvert.DeserializeObject(value.ToString(), targetType);
}
}
catch (JsonReaderException ex)
{
Console.WriteLine(ex);
return value;
}
catch (JsonSerializationException ex)
{
// 如果无法转为对应的JSON对象
int startIndex = ex.Message.IndexOf("to type '") + "to type '".Length; // 查找类型信息开始的索引
int endIndex = ex.Message.IndexOf("'", startIndex); // 查找类型信息结束的索引
var typeInfo = ex.Message.Substring(startIndex, endIndex - startIndex); // 提取出错类型信息,该怎么传出去?
return null;
}
catch // (Exception ex)
{
return value;
}
}
/// <summary>
/// 返回响应消息
/// </summary>
@@ -449,7 +467,7 @@ namespace Serein.Library.Web
controllerName = autoHostingAttribute.Url;
}
var httpMethod = webAttribute.Http; // 获取 HTTP 方法
var httpMethod = webAttribute.ApiType; // 获取 HTTP 方法
var customUrl = webAttribute.Url; // 获取自定义 URL
string url;
@@ -466,7 +484,7 @@ namespace Serein.Library.Web
customUrl = CleanUrl(customUrl);
url = $"/{controllerName}/{method.Name}/{customUrl}".ToLower();// 清理自定义 URL并构建新的 URL
}
_routes[httpMethod.ToString()].TryAdd(url, method); // 将 URL 和方法添加到对应的路由字典中
//HandleModels[httpMethod.ToString()].TryAdd(url ); // 将 URL 和方法添加到对应的路由字典中
}
else
{
@@ -479,7 +497,7 @@ namespace Serein.Library.Web
customUrl = CleanUrl(customUrl);
url = $"/{controllerName}/{customUrl}".ToLower();// 清理自定义 URL并构建新的 URL
}
_routes[httpMethod.ToString()].TryAdd(url, method); // 将 URL 和方法添加到对应的路由字典中
// _routes[httpMethod.ToString()].TryAdd(url, method); // 将 URL 和方法添加到对应的路由字典中
}
return url;
@@ -539,7 +557,7 @@ namespace Serein.Library.Web
foreach (var kvp in parsedQuery)
{
//Console.WriteLine($"{kvp.Key}: {kvp.Value}");
routeValues[kvp.Key] = kvp.Value; // 将键值对添加到路由参数字典中
routeValues[kvp.Key.ToLower()] = kvp.Value; // 将键值对添加到路由参数字典中
}
}
@@ -567,7 +585,6 @@ namespace Serein.Library.Web
return null;
}
#endregion
}

View File

@@ -10,9 +10,18 @@ namespace Serein.Library.Network.WebSocketCommunication
[AttributeUsage(AttributeTargets.Method)]
public sealed class AutoSocketHandleAttribute : Attribute
{
public string ThemeValue;
public string ThemeValue = string.Empty;
public bool IsReturnValue = true;
//public Type DataType;
}
public class SocketHandleModel
{
public string ThemeValue { get; set; } = string.Empty;
public bool IsReturnValue { get; set; } = true;
}
[AttributeUsage(AttributeTargets.Class)]
public sealed class AutoSocketModuleAttribute : Attribute
{

View File

@@ -20,17 +20,24 @@ namespace Serein.Library.Network.WebSocketCommunication.Handle
private readonly Delegate EmitDelegate;
private readonly EmitHelper.EmitMethodType EmitMethodType;
public MyHandleConfig(ISocketControlBase instance, MethodInfo methodInfo)
private Action<Exception, Action<object>> OnExceptionTracking;
public MyHandleConfig(SocketHandleModel model,ISocketControlBase instance, MethodInfo methodInfo, Action<Exception, Action<object>> onExceptionTracking)
{
EmitMethodType = EmitHelper.CreateDynamicMethod(methodInfo,out EmitDelegate);
this.Model = model;
Instance = instance;
var parameterInfos = methodInfo.GetParameters();
ParameterType = parameterInfos.Select(t => t.ParameterType).ToArray();
ParameterName = parameterInfos.Select(t => t.Name).ToArray();
this.HandleGuid = instance.HandleGuid;
this.OnExceptionTracking = onExceptionTracking;
}
public ISocketControlBase Instance { get; private set; }
private SocketHandleModel Model;
private ISocketControlBase Instance;
public Guid HandleGuid { get; }
private string[] ParameterName;
private Type[] ParameterType;
@@ -92,30 +99,44 @@ namespace Serein.Library.Network.WebSocketCommunication.Handle
}
Stopwatch sw = new Stopwatch();
sw.Start();
//Stopwatch sw = new Stopwatch();
//sw.Start();
object result;
if (EmitMethodType == EmitHelper.EmitMethodType.HasResultTask && EmitDelegate is Func<object, object[], Task<object>> hasResultTask)
try
{
result = await hasResultTask(Instance, args);
if (EmitMethodType == EmitHelper.EmitMethodType.HasResultTask && EmitDelegate is Func<object, object[], Task<object>> hasResultTask)
{
result = await hasResultTask(Instance, args);
}
else if (EmitMethodType == EmitHelper.EmitMethodType.Task && EmitDelegate is Func<object, object[], Task> task)
{
await task.Invoke(Instance, args);
result = null;
}
else if (EmitMethodType == EmitHelper.EmitMethodType.Func && EmitDelegate is Func<object, object[], object> func)
{
result = func.Invoke(Instance, args);
}
else
{
result = null;
}
}
else if (EmitMethodType == EmitHelper.EmitMethodType.Task && EmitDelegate is Func<object, object[], Task> task)
catch (Exception ex)
{
await task.Invoke(Instance, args);
result = null;
}
else if (EmitMethodType == EmitHelper.EmitMethodType.Func && EmitDelegate is Func<object, object[], object> func)
{
result = func.Invoke(Instance, args);
}
else
{
throw new NotImplementedException("构造委托无法正确调用");
}
sw.Stop();
Console.WriteLine($"Emit Invoke{sw.ElapsedTicks * 1000000F / Stopwatch.Frequency:n3}μs");
await Console.Out.WriteLineAsync(ex.Message);
this.OnExceptionTracking.Invoke(ex, (async data =>
{
if(result != null && result.GetType().IsClass)
var jsonText = JsonConvert.SerializeObject(data);
await RecoverAsync.Invoke(jsonText);
}));
}
//sw.Stop();
//Console.WriteLine($"Emit Invoke{sw.ElapsedTicks * 1000000F / Stopwatch.Frequency:n3}μs");
if(Model.IsReturnValue && result != null && result.GetType().IsClass)
{
var reusltJsonText = JsonConvert.SerializeObject(result);
_ = RecoverAsync.Invoke($"{reusltJsonText}");

View File

@@ -18,25 +18,30 @@ namespace Serein.Library.Network.WebSocketCommunication.Handle
public string ThemeJsonKey { get; }
public string DataJsonKey { get; }
public ConcurrentDictionary<string, MyHandleConfig> MyHandleConfigs = new ConcurrentDictionary<string, MyHandleConfig>();
public void AddHandleConfigs(string themeValue, ISocketControlBase instance, MethodInfo methodInfo)
public void AddHandleConfigs(SocketHandleModel model, ISocketControlBase instance, MethodInfo methodInfo
, Action<Exception, Action<object>> onExceptionTracking)
{
if (!MyHandleConfigs.ContainsKey(themeValue))
if (!MyHandleConfigs.ContainsKey(model.ThemeValue))
{
var myHandleConfig = new MyHandleConfig(instance, methodInfo);
MyHandleConfigs[themeValue] = myHandleConfig;
var myHandleConfig = new MyHandleConfig(model,instance, methodInfo, onExceptionTracking);
MyHandleConfigs[model.ThemeValue] = myHandleConfig;
}
}
public void ResetConfig(ISocketControlBase socketControlBase)
public bool ResetConfig(ISocketControlBase socketControlBase)
{
foreach (var kv in MyHandleConfigs.ToArray())
{
var config = kv.Value;
if (config.Instance.HandleGuid.Equals(socketControlBase.HandleGuid))
if (config.HandleGuid.Equals(socketControlBase.HandleGuid))
{
MyHandleConfigs.TryRemove(kv.Key, out _);
}
}
return MyHandleConfigs.Count == 0;
}
public void ResetConfig()

View File

@@ -28,7 +28,11 @@ namespace Serein.Library.Network.WebSocketCommunication.Handle
public ConcurrentDictionary<(string, string), MyHandleModule> MyHandleModuleDict
= new ConcurrentDictionary<(string, string), MyHandleModule>();
private Action<Exception, Action<object>> _onExceptionTracking;
/// <summary>
/// 异常跟踪
/// </summary>
public event Action<Exception, Action<object>> OnExceptionTracking;
private MyHandleModule AddMyHandleModule(string themeKeyName, string dataKeyName)
{
@@ -52,13 +56,14 @@ namespace Serein.Library.Network.WebSocketCommunication.Handle
var themeKeyName = moduleAttribute.JsonThemeField;
var dataKeyName = moduleAttribute.JsonDataField;
var key = (themeKeyName, dataKeyName);
if (MyHandleModuleDict.TryRemove(key, out var myHandleModules))
if (MyHandleModuleDict.TryGetValue(key, out var myHandleModules))
{
myHandleModules.ResetConfig(socketControlBase);
var isRemote = myHandleModules.ResetConfig(socketControlBase);
if (isRemote) MyHandleModuleDict.TryGetValue(key, out _);
}
}
public void AddModule(ISocketControlBase socketControlBase)
public void AddModule(ISocketControlBase socketControlBase, Action<Exception, Action<object>> onExceptionTracking)
{
var type = socketControlBase.GetType();
var moduleAttribute = type.GetCustomAttribute<AutoSocketModuleAttribute>();
@@ -67,10 +72,9 @@ namespace Serein.Library.Network.WebSocketCommunication.Handle
return;
}
// 添加处理模块
var themeKey = moduleAttribute.JsonThemeField;
var dataKey = moduleAttribute.JsonDataField;
var handlemodule = AddMyHandleModule(themeKey, dataKey);
var methods = type.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
.Select(method =>
@@ -78,23 +82,35 @@ namespace Serein.Library.Network.WebSocketCommunication.Handle
var methodsAttribute = method.GetCustomAttribute<AutoSocketHandleAttribute>();
if (methodsAttribute is null)
{
return (string.Empty, null);
return (null, null);
}
else
{
if (string.IsNullOrEmpty(methodsAttribute.ThemeValue))
{
methodsAttribute.ThemeValue = method.Name;
}
var model = new SocketHandleModel
{
IsReturnValue = methodsAttribute.IsReturnValue,
ThemeValue = methodsAttribute.ThemeValue,
};
var value = methodsAttribute.ThemeValue;
return (value, method);
return (model, method);
}
})
.Where(x => !string.IsNullOrEmpty(x.value)).ToList();
.Where(x => !(x.model is null)).ToList();
if (methods.Count == 0)
{
return;
}
foreach ((var value, var method) in methods)
Console.WriteLine($"add websocket handle model :");
Console.WriteLine($"theme key, data key : {themeKey}, {dataKey}");
foreach ((var model, var method) in methods)
{
handlemodule.AddHandleConfigs(value, socketControlBase, method);
Console.WriteLine($"theme value : {model.ThemeValue}");
handlemodule.AddHandleConfigs(model, socketControlBase, method, onExceptionTracking);
}
}
@@ -106,7 +122,9 @@ namespace Serein.Library.Network.WebSocketCommunication.Handle
{
foreach (var module in MyHandleModuleDict.Values)
{
module.HandleSocketMsg(RecoverAsync, json);
}
});

View File

@@ -14,15 +14,8 @@ using System.Threading.Tasks;
namespace Serein.Library.Network.WebSocketCommunication
{
[AutoRegister]
public class WebSocketServer
{
public WebSocketServer()
{
}
public SocketMsgHandleHelper MsgHandleHelper { get; } = new SocketMsgHandleHelper();
HttpListener listener;

View File

@@ -56,7 +56,7 @@ namespace Serein.Library.Attributes
/// <summary>
/// 建议触发器手动设置返回类型
/// 生成的节点类型
/// </summary>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class NodeActionAttribute : Attribute

View File

@@ -8,7 +8,6 @@ using Serein.Library.Attributes;
using Serein.Library.Enums;
using Serein.Library.Ex;
using Serein.Library.Framework.NodeFlow;
using Serein.Library.Network.WebSocketCommunication;
using Serein.Library.NodeFlow.Tool;
using Serein.Library.Web;
using System;
@@ -16,64 +15,33 @@ using System.Threading.Tasks;
namespace Net462DllTest.LogicControl
{
[AutoRegister]
[DynamicFlow("[SiemensPlc]")]
public class PlcLogicControl
[DynamicFlow("[SiemensPlc]")]
public class PlcLogicControl
{
public Guid HandleGuid { get; } = new Guid();
private readonly SiemensPlcDevice MyPlc;
private readonly PlcVarModelDataProxy plcVarModelDataProxy;
public PlcLogicControl(SiemensPlcDevice MyPlc,
PlcVarModelDataProxy plcVarModelDataProxy)
PlcVarModelDataProxy plcVarModelDataProxy)
{
this.MyPlc = MyPlc;
this.plcVarModelDataProxy = plcVarModelDataProxy;
}
#region 退
[NodeAction(NodeType.Init)]
public void Init(IDynamicContext context)
{
context.Env.IOC.Register<WebSocketServer>();
context.Env.IOC.Register<WebSocketClient>();
context.Env.IOC.Register<IRouter, Router>();
context.Env.IOC.Register<WebApiServer>();
}
#region
[NodeAction(NodeType.Loading)] // Loading 初始化完成已注入依赖项,可以开始逻辑上的操作
public void Loading(IDynamicContext context)
{
// 注册控制器
context.Env.IOC.Run<IRouter, WebApiServer>((router, apiServer) => {
router.RegisterController(typeof(FlowController));
apiServer.Start("http://*:8089/"); // 开启 Web Api 服务
});
context.Env.IOC.Run<WebSocketServer>(async (socketServer) => {
// socketServer.RegisterModuleInstance(userService);
await socketServer.StartAsync("http://localhost:5005/"); // 开启 Web Socket 监听
});
context.Env.IOC.Run<WebSocketClient>(async client => {
await client.ConnectAsync("ws://localhost:5005/"); // 连接到服务器
});
}
[NodeAction(NodeType.Exit)] // 流程结束时自动执行
public void Exit(IDynamicContext context)
{
context.Env.IOC.Run<WebApiServer>((apiServer) =>
{
apiServer?.Stop(); // 关闭 Web 服务
});
context.Env.IOC.Run<WebSocketServer>((socketServer) =>
{
socketServer?.Stop(); // 关闭 Web 服务
});
MyPlc.Close();
MyPlc.CancelAllTasks();
}
@@ -120,13 +88,14 @@ namespace Net462DllTest.LogicControl
}
[NodeAction(NodeType.Action, "PLC初始化")]
public SiemensPlcDevice PlcInit(SiemensVersion version = SiemensVersion.None,
string ip = "192.168.10.100",
int port = 102)
{
//MyPlc.Model.Set(PlcVarName.DoorVar,1);
//MyPlc.Model.Value.SpaceNum = 1;
MyPlc.Model.Set(PlcVarName.DoorVar,(Int16)1);
MyPlc.Model.Get(PlcVarName.DoorVar);
if (MyPlc.Client is null)
{
try
@@ -155,6 +124,7 @@ namespace Net462DllTest.LogicControl
return MyPlc;
}
[NodeAction(NodeType.Action, "PLC获取变量")]
public object ReadVar(PlcVarName varName)
{
@@ -163,6 +133,7 @@ namespace Net462DllTest.LogicControl
return result;
}
[NodeAction(NodeType.Action, "PLC写入变量")]
public SiemensPlcDevice WriteVar(object value, PlcVarName varName)
{
@@ -171,10 +142,13 @@ namespace Net462DllTest.LogicControl
}
[NodeAction(NodeType.Action, "批量读取")]
public void BatchReadVar()
public PlcVarModelDataProxy BatchReadVar()
{
MyPlc.BatchRefresh();
return plcVarModelDataProxy;
}
[NodeAction(NodeType.Action, "开启定时刷新")]
public void OpenTimedRefresh()
{

View File

@@ -35,7 +35,7 @@ namespace Net462DllTest.LogicControl
#region
[NodeAction(NodeType.Flipflop, "等待视图命令", ReturnType = typeof(int))]
public async Task<IFlipflopContext> WaitTask(CommandSignal command = CommandSignal.Command_1)
public async Task<IFlipflopContext> WaitTask(CommandSignal command)
{
try
{

View File

@@ -92,7 +92,7 @@
<DependentUpon>TestFormView.cs</DependentUpon>
</Compile>
<Compile Include="Web\FlowController.cs" />
<Compile Include="Web\UserService.cs" />
<Compile Include="Web\PlcSocketService.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\DynamicControl\SereinFlow\Library.Framework\Serein.Library.Framework.csproj">

View File

@@ -17,6 +17,7 @@ using System.Collections.Concurrent;
using System.Threading.Tasks;
using static System.Windows.Forms.VisualStyles.VisualStyleElement.TrackBar;
using System.Linq;
using Serein.Library.Network.WebSocketCommunication;
namespace Net462DllTest.Trigger
{

View File

@@ -11,11 +11,13 @@ namespace Net462DllTest.Utils
{
public interface IGSModel<TKey, TModel>
{
TModel Value { get; set; }
//TModel Value { get; set; }
void Set(TKey tEnum, object value);
object Get(TKey tEnum);
}
/// <summary>
/// 通过 Emit 创建 set/get 委托
/// </summary>
@@ -23,7 +25,8 @@ namespace Net462DllTest.Utils
where TKey : struct, Enum
where TModel : class
{
public TModel Value { get; set; }
private TModel Value;
public GSModel(TModel Model)
{
this.Value = Model;
@@ -32,55 +35,6 @@ namespace Net462DllTest.Utils
private readonly Dictionary<TKey, Action<TModel, object>> _setterCache = new Dictionary<TKey, Action<TModel, object>>();
private readonly Dictionary<TKey, Func<TModel, object>> _getterCache = new Dictionary<TKey, Func<TModel, object>>();
// 动态创建调用Setter方法
private Action<TModel, object> CreateSetter(PropertyInfo property)
{
var method = new DynamicMethod("Set" + property.Name, null, new[] { typeof(TModel), typeof(object) }, true);
var il = method.GetILGenerator();
il.Emit(OpCodes.Ldarg_0); // 加载实例PlcVarValue
il.Emit(OpCodes.Ldarg_1); // 加载值object
if (property.PropertyType.IsValueType)
{
il.Emit(OpCodes.Unbox_Any, property.PropertyType); // 解箱并转换为值类型
}
else
{
il.Emit(OpCodes.Castclass, property.PropertyType); // 引用类型转换
}
il.Emit(OpCodes.Callvirt, property.GetSetMethod()); // 调用属性的Setter方法
il.Emit(OpCodes.Ret); // 返回
return (Action<TModel, object>)method.CreateDelegate(typeof(Action<TModel, object>));
}
/// <summary>
/// 动态创建调用Getter方法
/// </summary>
/// <param name="property"></param>
/// <returns></returns>
private Func<TModel, object> CreateGetter(PropertyInfo property)
{
var method = new DynamicMethod("Get" + property.Name, typeof(object), new[] { typeof(TModel) }, true);
var il = method.GetILGenerator();
il.Emit(OpCodes.Ldarg_0); // 加载实例PlcVarValue
il.Emit(OpCodes.Callvirt, property.GetGetMethod()); // 调用属性的Getter方法
if (property.PropertyType.IsValueType)
{
il.Emit(OpCodes.Box, property.PropertyType); // 值类型需要装箱
}
il.Emit(OpCodes.Ret); // 返回
return (Func<TModel, object>)method.CreateDelegate(typeof(Func<TModel, object>));
}
public void Set(TKey tEnum, object value)
{
if (!_setterCache.TryGetValue(tEnum, out var setter))
@@ -89,7 +43,6 @@ namespace Net462DllTest.Utils
if (property == null)
{
_setterCache[tEnum] = (s, o) => throw new ArgumentException($"没有对应的Model属性{{{tEnum}");
//throw new ArgumentException($"Property not found for {plcVarEnum}");
}
else
{
@@ -141,5 +94,67 @@ namespace Net462DllTest.Utils
}
return null;
}
// 动态创建调用Setter方法
private Action<TModel, object> CreateSetter(PropertyInfo property)
{
var method = new DynamicMethod("Set" + property.Name, null, new[] { typeof(TModel), typeof(object) }, true);
var il = method.GetILGenerator();
il.Emit(OpCodes.Ldarg_0); // 加载实例PlcVarValue
il.Emit(OpCodes.Ldarg_1); // 加载值object
if (property.PropertyType.IsValueType)
{
il.Emit(OpCodes.Unbox_Any, property.PropertyType); // 解箱并转换为值类型
}
else
{
il.Emit(OpCodes.Castclass, property.PropertyType); // 引用类型转换
}
il.Emit(OpCodes.Callvirt, property.GetSetMethod()); // 调用属性的Setter方法
il.Emit(OpCodes.Ret); // 返回
return (Action<TModel, object>)method.CreateDelegate(typeof(Action<TModel, object>));
}
/// <summary>
/// 动态创建调用Getter方法
/// </summary>
/// <param name="property"></param>
/// <returns></returns>
private Func<TModel, object> CreateGetter(PropertyInfo property)
{
var method = new DynamicMethod("Get" + property.Name, typeof(object), new[] { typeof(TModel) }, true);
var il = method.GetILGenerator();
il.Emit(OpCodes.Ldarg_0); // 加载实例PlcVarValue
il.Emit(OpCodes.Callvirt, property.GetGetMethod()); // 调用属性的Getter方法
if (property.PropertyType.IsValueType)
{
il.Emit(OpCodes.Box, property.PropertyType); // 值类型需要装箱
}
il.Emit(OpCodes.Ret); // 返回
return (Func<TModel, object>)method.CreateDelegate(typeof(Func<TModel, object>));
}
}
}

View File

@@ -49,7 +49,7 @@ namespace Net462DllTest
private void FromWorkBenchView_FormClosing(object sender, FormClosingEventArgs e)
{
ViewModel.CommandCloseForm.Execute();
ViewModel.CommandCloseForm?.Execute();
}
private void button2_Click(object sender, EventArgs e)

View File

@@ -30,10 +30,8 @@ namespace Net462DllTest.ViewModel
}
}
[AutoSocketModule(JsonThemeField = "theme", JsonDataField = "data")]
public class FromWorkBenchViewModel : INotifyPropertyChanged, ISocketControlBase
public class FromWorkBenchViewModel : INotifyPropertyChanged
{
public Guid HandleGuid { get; } = new Guid();
private readonly SiemensPlcDevice Device;
private readonly ViewManagement viewManagement;
@@ -51,81 +49,6 @@ namespace Net462DllTest.ViewModel
this.plcVarModelDataProxy = plcVarModelDataProxy;
InitCommand(); // 初始化指令
webSocketServer.MsgHandleHelper.AddModule(this); // 用于关闭窗体时移除监听
CommandCloseForm = new RelayCommand((p) =>
{
webSocketServer.MsgHandleHelper.RemoteModule(this); // 用于关闭窗体时移除监听
});
//Console.WriteLine($"Emit {sw.ElapsedTicks * 1000000F / Stopwatch.Frequency:n3}μs");
//var msgHandl = new WsMsgHandl<LibSpace>()
//{
// DataJsonKey = "data", // 数据键
// ThemeJsonKey = "theme", // 主题键
//};
//msgHandl.AddHandle<LibSpace>(nameof(GetSpace), GetSpace); // theme:AddUser
//msgHandl.AddHandle<LibSpace>(nameof(UpData), UpData); // theme:DeleteUser
//webSocketServer.AddModule(msgHandl);
//CommandCloseForm = new RelayCommand((p) =>
//{
// webSocketServer.RemoteModule(msgHandl); // 用于关闭窗体时移除监听
//});
}
Action<string> Recover;
[AutoSocketHandle(ThemeValue = "SavaRecover")]
public async Task SavaRecover(int waitTime , Action<string> Recover)
{
if(waitTime <=0 || waitTime>= 10000)
{
return;
}
await Task.Delay(waitTime);
Recover("haha~" );
this.Recover = Recover;
return;
}
[AutoSocketHandle(ThemeValue = "Invoke")]
public void Invoke(string a)
{
if (Recover is null)
return;
Recover("haha~"+a);
}
[AutoSocketHandle(ThemeValue = "PlcModel")]
public async Task<PlcVarModelDataProxy> GetModelData(Action<object> Recover)
{
Recover("等待5秒");
await Task.Delay(5000);
//Recover.Invoke(plcVarModelDataProxy);
return plcVarModelDataProxy;
}
[AutoSocketHandle(ThemeValue = "Up")]
public void UpData(LibSpace libSpace, Action<dynamic> Recover)
{
Recover("收到");
return;
}
[AutoSocketHandle(ThemeValue = "Bind")]
public LibSpace SpaceBind(string spaceNum, string plateNumber, Action<string> Recover)
{
return new LibSpace
{
PlateNumber = plateNumber,
SpaceNum = spaceNum,
};
}

View File

@@ -36,7 +36,7 @@ namespace Net462DllTest.Web
* "value":0,
* }
*/
[WebApi(API.POST)]
[WebApi(ApiType.POST)]
public dynamic PlcOp([Url] string var, int value)
{
if (EnumHelper.TryConvertEnum<PlcVarName>(var,out var signal))
@@ -61,7 +61,7 @@ namespace Net462DllTest.Web
* "value":0,
* }
*/
[WebApi(API.POST)]
[WebApi(ApiType.POST)]
public dynamic Trigger([Url] string command, int value)
{
if (EnumHelper.TryConvertEnum<CommandSignal>(command, out var signal))

View File

@@ -0,0 +1,170 @@
using IoTClient.Common.Enums;
using Net462DllTest.Enums;
using Net462DllTest.Model;
using Net462DllTest.Trigger;
using Serein.Library.Api;
using Serein.Library.Attributes;
using Serein.Library.Enums;
using Serein.Library.Ex;
using Serein.Library.Framework.NodeFlow;
using Serein.Library.Network.WebSocketCommunication;
using Serein.Library.NodeFlow.Tool;
using Serein.Library.Web;
using System;
using System.Threading.Tasks;
namespace Net462DllTest.Web
{
[DynamicFlow("[PlcSocketService]")]
[AutoRegister]
[AutoSocketModule(JsonThemeField = "theme", JsonDataField = "data")]
public class PlcSocketService : ISocketControlBase
{
public Guid HandleGuid { get; } = new Guid();
private readonly SiemensPlcDevice MyPlc;
private readonly PlcVarModelDataProxy plcVarModelDataProxy;
public PlcSocketService(SiemensPlcDevice MyPlc,
PlcVarModelDataProxy plcVarModelDataProxy)
{
this.MyPlc = MyPlc;
this.plcVarModelDataProxy = plcVarModelDataProxy;
}
#region 退
[NodeAction(NodeType.Init)]
public void Init(IDynamicContext context)
{
context.Env.IOC.Register<WebSocketServer>();
context.Env.IOC.Register<WebSocketClient>();
context.Env.IOC.Register<IRouter, Router>();
context.Env.IOC.Register<WebApiServer>();
}
[NodeAction(NodeType.Loading)] // Loading 初始化完成已注入依赖项,可以开始逻辑上的操作
public void Loading(IDynamicContext context)
{
// 注册控制器
context.Env.IOC.Run<IRouter, WebApiServer>((router, apiServer) => {
router.AddHandle(typeof(FlowController));
apiServer.Start("http://*:8089/"); // 开启 Web Api 服务
});
context.Env.IOC.Run<WebSocketServer>(async (socketServer) => {
socketServer.MsgHandleHelper.AddModule(this, (ex, recover) =>
{
recover(new
{
ex = ex.Message,
storehouseInfo = ex.StackTrace
});
});
await socketServer.StartAsync("http://localhost:5005/"); // 开启 Web Socket 监听
});
context.Env.IOC.Run<WebSocketClient>(async client => {
await client.ConnectAsync("ws://localhost:5005/"); // 连接到服务器
});
}
[NodeAction(NodeType.Exit)] // 流程结束时自动执行
public void Exit(IDynamicContext context)
{
context.Env.IOC.Run<WebApiServer>((apiServer) =>
{
apiServer?.Stop(); // 关闭 Web 服务
});
context.Env.IOC.Run<WebSocketServer>((socketServer) =>
{
socketServer?.Stop(); // 关闭 Web 服务
});
MyPlc.Close();
MyPlc.CancelAllTasks();
}
#endregion
[NodeAction(NodeType.Action, "等待")]
public async Task Delay(int ms = 5000)
{
await Console.Out.WriteLineAsync("开始等待");
await Task.Delay(ms);
await Console.Out.WriteLineAsync("不再等待");
}
[AutoSocketHandle(IsReturnValue = false)]
public SiemensPlcDevice PlcInit(SiemensVersion version = SiemensVersion.None,
string ip = "192.168.10.100",
int port = 102)
{
MyPlc.Model.Set(PlcVarName.DoorVar, (Int16)1);
MyPlc.Model.Get(PlcVarName.DoorVar);
if (MyPlc.Client is null)
{
try
{
MyPlc.Init(version, ip, port);
Console.WriteLine($"西门子PLC初始化成功[{version},{ip}:{port}]");
}
catch (Exception ex)
{
Console.WriteLine($"西门子PLC[{version},{ip}:{port}]初始化异常:{ex.Message}");
}
}
else
{
Console.WriteLine($"西门子PLC已经初始化[{version},{ip}:{port}]");
}
return MyPlc;
}
[AutoSocketHandle(IsReturnValue = false)]
public SiemensPlcDevice SetState(PlcState state = PlcState.PowerOff)
{
var oldState = MyPlc.State;
MyPlc.State = state;
Console.WriteLine($"PLC状态从[{oldState}]转为[{state}]");
return MyPlc;
}
[AutoSocketHandle]
public object ReadVar(PlcVarName varName)
{
var result = MyPlc.Read(varName);
Console.WriteLine($"获取变量成功:({varName})\t result = {result}");
return result;
}
[AutoSocketHandle(IsReturnValue = false)]
public SiemensPlcDevice WriteVar(object value, PlcVarName varName)
{
MyPlc.Write(varName, value); // 新数据
return MyPlc;
}
public PlcVarModelDataProxy BatchReadVar()
{
MyPlc.BatchRefresh();
return plcVarModelDataProxy;
}
public void OpenTimedRefresh()
{
Task.Run(async () => await MyPlc.OpenTimedRefreshAsync());
}
public void CloseTimedRefresh()
{
MyPlc.CloseTimedRefresh();
}
}
}

View File

@@ -1,15 +0,0 @@
using Net462DllTest.ViewModel;
using Serein.Library.Attributes;
using Serein.Library.Network.WebSocketCommunication;
using Serein.Library.Web;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Net462DllTest.Web
{
}

View File

@@ -395,37 +395,66 @@ namespace Serein.NodeFlow.Base
try
{
string? valueStr = inputParameter?.ToString();
parameters[i] = ed.DataType switch
if (ed.DataType.IsValueType)
{
Type t when t == typeof(IDynamicContext) => context, // 上下文
Type t when t.IsEnum => Enum.Parse(ed.DataType, ed.DataValue),// 需要枚举
Type t when t == typeof(string) => inputParameter?.ToString(),
Type t when t == typeof(char) && !string.IsNullOrEmpty(valueStr) => char.Parse(valueStr),
Type t when t == typeof(bool) && !string.IsNullOrEmpty(valueStr) => inputParameter is not null && bool.Parse(valueStr),
Type t when t == typeof(float) && !string.IsNullOrEmpty(valueStr) => float.Parse(valueStr),
Type t when t == typeof(decimal) && !string.IsNullOrEmpty(valueStr) => decimal.Parse(valueStr),
Type t when t == typeof(double) && !string.IsNullOrEmpty(valueStr) => double.Parse(valueStr),
Type t when t == typeof(sbyte) && !string.IsNullOrEmpty(valueStr) => sbyte.Parse(valueStr),
Type t when t == typeof(byte) && !string.IsNullOrEmpty(valueStr) => byte.Parse(valueStr),
Type t when t == typeof(short) && !string.IsNullOrEmpty(valueStr) => short.Parse(valueStr),
Type t when t == typeof(ushort) && !string.IsNullOrEmpty(valueStr) => ushort.Parse(valueStr),
Type t when t == typeof(int) && !string.IsNullOrEmpty(valueStr) => int.Parse(valueStr),
Type t when t == typeof(uint) && !string.IsNullOrEmpty(valueStr) => uint.Parse(valueStr),
Type t when t == typeof(long) && !string.IsNullOrEmpty(valueStr) => long.Parse(valueStr),
Type t when t == typeof(ulong) && !string.IsNullOrEmpty(valueStr) => ulong.Parse(valueStr),
Type t when t == typeof(nint) && !string.IsNullOrEmpty(valueStr) => nint.Parse(valueStr),
Type t when t == typeof(nuint) && !string.IsNullOrEmpty(valueStr) => nuint.Parse(valueStr),
//Type t when t == typeof(DateTime) => string.IsNullOrEmpty(valueStr) ? 0 : DateTime.Parse(valueStr),
if (inputParameter is null)
{
parameters[i] = Activator.CreateInstance(ed.DataType);
}
else
{
string? valueStr = inputParameter?.ToString();
if (string.IsNullOrEmpty(valueStr))
{
parameters[i] = Activator.CreateInstance(ed.DataType);
}
else
{
parameters[i] = ed.DataType switch
{
Type t when t.IsEnum => Enum.Parse(ed.DataType, ed.DataValue),// 需要枚举
Type t when t == typeof(char) => char.Parse(valueStr),
Type t when t == typeof(bool) => bool.Parse(valueStr),
Type t when t == typeof(float) => float.Parse(valueStr),
Type t when t == typeof(decimal) => decimal.Parse(valueStr),
Type t when t == typeof(double) => double.Parse(valueStr),
Type t when t == typeof(sbyte) => sbyte.Parse(valueStr),
Type t when t == typeof(byte) => byte.Parse(valueStr),
Type t when t == typeof(short) => short.Parse(valueStr),
Type t when t == typeof(ushort) => ushort.Parse(valueStr),
Type t when t == typeof(int) => int.Parse(valueStr),
Type t when t == typeof(uint) => uint.Parse(valueStr),
Type t when t == typeof(long) => long.Parse(valueStr),
Type t when t == typeof(ulong) => ulong.Parse(valueStr),
Type t when t == typeof(nint) => nint.Parse(valueStr),
Type t when t == typeof(nuint) => nuint.Parse(valueStr),
_ => throw new Exception($"调用节点对应方法[{nodeModel.MethodDetails.MethodName}]时,遇到了未在预期内的值类型入参:{ed.DataType.FullName}"),
// Type t when Nullable.GetUnderlyingType(t) != null => inputParameter is null ? null : Convert.ChangeType(inputParameter, Nullable.GetUnderlyingType(t)),
};
}
}
}
else
{
parameters[i] = ed.DataType switch
{
Type t when t == typeof(IDynamicContext) => context, // 上下文
Type t when t == typeof(string) => inputParameter?.ToString(),
Type t when t == typeof(MethodDetails) => md, // 节点方法描述
Type t when t == typeof(NodeModelBase) => nodeModel, // 节点实体类
//Type t when t == typeof(DateTime) => string.IsNullOrEmpty(valueStr) ? 0 : DateTime.Parse(valueStr),
Type t when t.IsArray => (inputParameter as Array)?.Cast<object>().ToList(),
Type t when t.IsGenericType && t.GetGenericTypeDefinition() == typeof(List<>) => inputParameter,
_ => inputParameter,
// Type t when Nullable.GetUnderlyingType(t) != null => inputParameter is null ? null : Convert.ChangeType(inputParameter, Nullable.GetUnderlyingType(t)),
};
Type t when t == typeof(MethodDetails) => md, // 节点方法描述
Type t when t == typeof(NodeModelBase) => nodeModel, // 节点实体类
Type t when t.IsArray => (inputParameter as Array)?.Cast<object>().ToList(),
Type t when t.IsGenericType && t.GetGenericTypeDefinition() == typeof(List<>) => inputParameter,
_ => inputParameter,
// Type t when Nullable.GetUnderlyingType(t) != null => inputParameter is null ? null : Convert.ChangeType(inputParameter, Nullable.GetUnderlyingType(t)),
};
}
}

View File

@@ -526,7 +526,7 @@ namespace Serein.NodeFlow
/// <summary>
/// 运行时创建节点
/// 流程正在运行时创建节点
/// </summary>
/// <param name="nodeControlType"></param>
/// <param name="position"></param>
@@ -536,12 +536,12 @@ namespace Serein.NodeFlow
var nodeModel = CreateNode(nodeControlType, methodDetails);
TryAddNode(nodeModel);
if (flowStarter?.FlowState != RunState.Completion
&& nodeControlType == NodeControlType.Flipflop
&& nodeModel is SingleFlipflopNode flipflopNode)
{
_ = flowStarter?.RunGlobalFlipflopAsync(this, flipflopNode); // 当前添加节点属于触发器,且当前正在运行,则加载到运行环境中
}
//if (flowStarter?.FlowState != RunState.Completion
// && nodeControlType == NodeControlType.Flipflop
// && nodeModel is SingleFlipflopNode flipflopNode)
//{
// _ = flowStarter?.RunGlobalFlipflopAsync(this, flipflopNode); // 当前添加节点属于触发器,且当前正在运行,则加载到运行环境中
//}
// 通知UI更改
OnNodeCreate?.Invoke(new NodeCreateEventArgs(nodeModel, position));
@@ -642,33 +642,7 @@ namespace Serein.NodeFlow
}
/// <summary>
/// 移除连接关系
/// </summary>
/// <param name="fromNodeGuid">起始节点Model</param>
/// <param name="toNodeGuid">目标节点Model</param>
/// <param name="connectionType">连接关系</param>
/// <exception cref="NotImplementedException"></exception>
private void RemoteConnect(NodeModelBase fromNode, NodeModelBase toNode, ConnectionType connectionType)
{
fromNode.SuccessorNodes[connectionType].Remove(toNode);
toNode.PreviousNodes[connectionType].Remove(fromNode);
if (toNode is SingleFlipflopNode flipflopNode) // 子节点为触发器
{
if (flowStarter?.FlowState != RunState.Completion
&& flipflopNode.NotExitPreviousNode()) // 正在运行,且该触发器没有上游节点
{
flowStarter?.RunGlobalFlipflopAsync(this, flipflopNode); // 被父节点移除连接关系的子节点若为触发器,且无上级节点,则当前流程正在运行,则加载到运行环境中
}
}
// 通知UI
OnNodeConnectChange?.Invoke(new NodeConnectChangeEventArgs(fromNode.Guid,
toNode.Guid,
connectionType,
NodeConnectChangeEventArgs.ConnectChangeType.Remote));
}
/// <summary>
/// 获取方法描述
@@ -709,6 +683,8 @@ namespace Serein.NodeFlow
}
}
/// <summary>
/// 设置起点控件
/// </summary>
@@ -781,55 +757,6 @@ namespace Serein.NodeFlow
return true;
}
}
//public bool AddInterruptExpression(string nodeGuid, string expression)
//{
// var nodeModel = GuidToModel(nodeGuid);
// if (nodeModel is null) return false;
// if (string.IsNullOrEmpty(expression))
// {
// nodeModel.DebugSetting.InterruptExpressions.Clear(); // 传入空表达式时清空
// return true;
// }
// if (nodeModel.DebugSetting.InterruptExpressions.Contains(expression))
// {
// Console.WriteLine("表达式已存在");
// return false;
// }
// else
// {
// nodeModel.DebugSetting.InterruptExpressions.Clear();// 暂时删除等UI做好了
// nodeModel.DebugSetting.InterruptExpressions.Add(expression);
// return true;
// }
//}
/// <summary>
/// 监视节点的数据(暂时注释)
/// </summary>
/// <param name="nodeGuid">需要监视的节点Guid</param>
//public void SetMonitorObjState(string nodeGuid, bool isMonitor)
//{
// var nodeModel = GuidToModel(nodeGuid);
// if (nodeModel is null) return;
// nodeModel.DebugSetting.IsMonitorFlowData = isMonitor;
// if (isMonitor)
// {
// var obj = nodeModel.GetFlowData();
// if(obj is not null)
// {
// FlowDataNotification(nodeGuid, obj);
// }
// }
// else
// {
// // 不再监视的节点清空表达式
// nodeModel.DebugSetting.InterruptExpressions.Clear();
// }
//}
/// <summary>
/// 要监视的对象,以及与其关联的表达式
@@ -894,6 +821,36 @@ namespace Serein.NodeFlow
OnInterruptTrigger?.Invoke(new InterruptTriggerEventArgs(nodeGuid, expression, type));
}
/// <summary>
/// 激活全局触发器
/// </summary>
/// <param name="nodeGuid"></param>
public void ActivateFlipflopNode(string nodeGuid)
{
var nodeModel = GuidToModel(nodeGuid);
if (nodeModel is null) return;
if (flowStarter is not null && nodeModel is SingleFlipflopNode flipflopNode) // 子节点为触发器
{
if (flowStarter.FlowState != RunState.Completion
&& flipflopNode.NotExitPreviousNode()) // 正在运行,且该触发器没有上游节点
{
_ = flowStarter.RunGlobalFlipflopAsync(this, flipflopNode);// 被父节点移除连接关系的子节点若为触发器,且无上级节点,则当前流程正在运行,则加载到运行环境中
}
}
} /// <summary>
/// 关闭全局触发器
/// </summary>
/// <param name="nodeGuid"></param>
public void TerminateFlipflopNode(string nodeGuid)
{
var nodeModel = GuidToModel(nodeGuid);
if (nodeModel is null) return;
if (flowStarter is not null && nodeModel is SingleFlipflopNode flipflopNode) // 子节点为触发器
{
flowStarter.TerminateGlobalFlipflopRuning(flipflopNode);
}
}
public Task<CancelType> GetOrCreateGlobalInterruptAsync()
{
@@ -902,7 +859,6 @@ namespace Serein.NodeFlow
}
/// <summary>
/// Guid 转 NodeModel
/// </summary>
@@ -955,7 +911,24 @@ namespace Serein.NodeFlow
}
}
/// <summary>
/// 移除连接关系
/// </summary>
/// <param name="fromNodeGuid">起始节点Model</param>
/// <param name="toNodeGuid">目标节点Model</param>
/// <param name="connectionType">连接关系</param>
/// <exception cref="NotImplementedException"></exception>
private void RemoteConnect(NodeModelBase fromNode, NodeModelBase toNode, ConnectionType connectionType)
{
fromNode.SuccessorNodes[connectionType].Remove(toNode);
toNode.PreviousNodes[connectionType].Remove(fromNode);
// 通知UI
OnNodeConnectChange?.Invoke(new NodeConnectChangeEventArgs(fromNode.Guid,
toNode.Guid,
connectionType,
NodeConnectChangeEventArgs.ConnectChangeType.Remote));
}
private (NodeLibrary?, Dictionary<RegisterSequence, List<Type>>, List<MethodDetails>) LoadAssembly(string dllPath)
{

View File

@@ -304,7 +304,7 @@ namespace Serein.NodeFlow
// 使用 TaskCompletionSource 创建未启动的触发器任务
var tasks = flipflopNodes.Select(async node =>
{
await RunGlobalFlipflopAsync(env,node);
await RunGlobalFlipflopAsync(env,node); // 启动流程时启动全局触发器
}).ToArray();
_ = Task.WhenAll(tasks);
}
@@ -362,7 +362,7 @@ namespace Serein.NodeFlow
}
/// <summary>
/// 结所有全局触发器
/// 结所有全局触发器
/// </summary>
private void TerminateAllGlobalFlipflop()
{

View File

@@ -882,16 +882,40 @@ namespace Serein.WorkBench
{
var contextMenu = new ContextMenu();
var nodeGuid = nodeControl.ViewModel?.Node?.Guid;
#region
if(nodeControl.ViewModel?.Node.ControlType == NodeControlType.Flipflop)
{
contextMenu.Items.Add(CreateMenuItem("启动触发器", (s, e) =>
{
if (s is MenuItem menuItem)
{
if (menuItem.Header.ToString() == "启动触发器")
{
FlowEnvironment.ActivateFlipflopNode(nodeGuid);
if (nodeControl.ViewModel.Node?.MethodDetails?.ReturnType is Type returnType && returnType != typeof(void))
menuItem.Header = "终结触发器";
}
else
{
FlowEnvironment.TerminateFlipflopNode(nodeGuid);
menuItem.Header = "启动触发器";
}
}
}));
}
#endregion
if (nodeControl?.ViewModel?.Node?.MethodDetails?.ReturnType is Type returnType && returnType != typeof(void))
{
contextMenu.Items.Add(CreateMenuItem("查看返回类型", (s, e) =>
{
DisplayReturnTypeTreeViewer(returnType);
}));
}
var nodeGuid = nodeControl.ViewModel?.Node?.Guid;
#region -
@@ -926,7 +950,7 @@ namespace Serein.WorkBench
contextMenu.Items.Add(CreateMenuItem("添加 上游分支", (s, e) => StartConnection(nodeControl, ConnectionType.Upstream)));
#region -
var AvoidMenu = new MenuItem();