mirror of
https://gitee.com/langsisi_admin/serein-flow
synced 2026-03-02 15:50:47 +08:00
GIT练习
This commit is contained in:
63
.gitattributes
vendored
Normal file
63
.gitattributes
vendored
Normal file
@@ -0,0 +1,63 @@
|
||||
###############################################################################
|
||||
# Set default behavior to automatically normalize line endings.
|
||||
###############################################################################
|
||||
* text=auto
|
||||
|
||||
###############################################################################
|
||||
# Set default behavior for command prompt diff.
|
||||
#
|
||||
# This is need for earlier builds of msysgit that does not have it on by
|
||||
# default for csharp files.
|
||||
# Note: This is only used by command line
|
||||
###############################################################################
|
||||
#*.cs diff=csharp
|
||||
|
||||
###############################################################################
|
||||
# Set the merge driver for project and solution files
|
||||
#
|
||||
# Merging from the command prompt will add diff markers to the files if there
|
||||
# are conflicts (Merging from VS is not affected by the settings below, in VS
|
||||
# the diff markers are never inserted). Diff markers may cause the following
|
||||
# file extensions to fail to load in VS. An alternative would be to treat
|
||||
# these files as binary and thus will always conflict and require user
|
||||
# intervention with every merge. To do so, just uncomment the entries below
|
||||
###############################################################################
|
||||
#*.sln merge=binary
|
||||
#*.csproj merge=binary
|
||||
#*.vbproj merge=binary
|
||||
#*.vcxproj merge=binary
|
||||
#*.vcproj merge=binary
|
||||
#*.dbproj merge=binary
|
||||
#*.fsproj merge=binary
|
||||
#*.lsproj merge=binary
|
||||
#*.wixproj merge=binary
|
||||
#*.modelproj merge=binary
|
||||
#*.sqlproj merge=binary
|
||||
#*.wwaproj merge=binary
|
||||
|
||||
###############################################################################
|
||||
# behavior for image files
|
||||
#
|
||||
# image files are treated as binary by default.
|
||||
###############################################################################
|
||||
#*.jpg binary
|
||||
#*.png binary
|
||||
#*.gif binary
|
||||
|
||||
###############################################################################
|
||||
# diff behavior for common document formats
|
||||
#
|
||||
# Convert binary document formats to text before diffing them. This feature
|
||||
# is only available from the command line. Turn it on by uncommenting the
|
||||
# entries below.
|
||||
###############################################################################
|
||||
#*.doc diff=astextplain
|
||||
#*.DOC diff=astextplain
|
||||
#*.docx diff=astextplain
|
||||
#*.DOCX diff=astextplain
|
||||
#*.dot diff=astextplain
|
||||
#*.DOT diff=astextplain
|
||||
#*.pdf diff=astextplain
|
||||
#*.PDF diff=astextplain
|
||||
#*.rtf diff=astextplain
|
||||
#*.RTF diff=astextplain
|
||||
21
LICENSE
Normal file
21
LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2024 fengjiayi
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
576
Library/DbSql/DBSync.cs
Normal file
576
Library/DbSql/DBSync.cs
Normal file
@@ -0,0 +1,576 @@
|
||||
using SqlSugar;
|
||||
using System.ComponentModel;
|
||||
using System.Net.Sockets;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Serein.DbSql
|
||||
{
|
||||
public enum DBSyncStart
|
||||
{
|
||||
/// <summary>
|
||||
/// 无需同步
|
||||
/// </summary>
|
||||
[Description("无需同步")]
|
||||
NotNeed,
|
||||
/// <summary>
|
||||
/// 同步成功
|
||||
/// </summary>
|
||||
[Description("同步成功")]
|
||||
SyncSuccess,
|
||||
/// <summary>
|
||||
/// 同步失败
|
||||
/// </summary>
|
||||
[Description("同步失败")]
|
||||
SyncFailure,
|
||||
/// <summary>
|
||||
/// 连接异常
|
||||
/// </summary>
|
||||
[Description("配置/连接异常")]
|
||||
NetworkError,
|
||||
/// <summary>
|
||||
/// 没有同步事件
|
||||
/// </summary>
|
||||
[Description("没有同步事件,请使用 DBSync.SetSyncDataEvent() 方法设置同步事件")]
|
||||
NoEvent,
|
||||
}
|
||||
public enum DBSyncExType
|
||||
{
|
||||
[Description("连接异常")]
|
||||
ConnectError,
|
||||
[Description("读写异常")]
|
||||
CrudError,
|
||||
[Description("同步异常")]
|
||||
SyncError,
|
||||
}
|
||||
|
||||
public class DBSyncConfig
|
||||
{
|
||||
public DBSyncConfig(ConnectionConfig primaryDBConfig,
|
||||
ConnectionConfig secondaryDBConfig)
|
||||
{
|
||||
PrimaryDBConfig = primaryDBConfig;
|
||||
SecondaryDBConfig = secondaryDBConfig;
|
||||
}
|
||||
/// <summary>
|
||||
/// 主数据库IP
|
||||
/// </summary>
|
||||
//private string Host { get; }
|
||||
/// <summary>
|
||||
/// 主数据库端口
|
||||
/// </summary>
|
||||
//private int Port { get; }
|
||||
/// <summary>
|
||||
/// 主数据库配置
|
||||
/// </summary>
|
||||
private ConnectionConfig PrimaryDBConfig { get; }
|
||||
/// <summary>
|
||||
/// 从数据库配置
|
||||
/// </summary>
|
||||
private ConnectionConfig SecondaryDBConfig { get; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"[主数据库配置]{PrimaryDBConfig.ConnectionString}" + Environment.NewLine +
|
||||
$"[从数据库配置]{SecondaryDBConfig.ConnectionString}" + Environment.NewLine;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查网络状态
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public bool GetNetworkState()
|
||||
{
|
||||
var isOpen = DBSync.IsPortOpen(); // 数据库基类获取网络状态
|
||||
if (!isOpen)
|
||||
{
|
||||
DBSync.SetIsNeedSyncData(true); // 远程数据库查询失败,尝试本地数据库
|
||||
}
|
||||
return isOpen;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 返回从数据库
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public SqlSugarClient GetSecondaryDB()
|
||||
{
|
||||
DBSync.SyncEvent.Wait();
|
||||
return new SqlSugarClient(SecondaryDBConfig);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 返回主数据库
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="DBSyncException"></exception>
|
||||
public SqlSugarClient GetPrimaryDB()
|
||||
{
|
||||
try
|
||||
{
|
||||
// 等待同步事件
|
||||
DBSync.SyncEvent.Wait();
|
||||
// 检查主数据库连接状态
|
||||
if (!DBSync.IsPortOpen()) // 返回主数据库检测网络状态
|
||||
{
|
||||
// Console.WriteLine($"主数据库无法连接,IP:{IP},端口:{Port}");
|
||||
DBSync.SetIsNeedSyncData(true); // 网络不可达
|
||||
return null;
|
||||
}
|
||||
|
||||
// 检查是否需要同步数据
|
||||
/*if (DBSync.GetIsNeedSyncData())
|
||||
{
|
||||
var syncState = DBSync.StartSyncDataBase();
|
||||
if (syncState != DBSyncStart.SyncSuccess && syncState != DBSyncStart.NotNeed)
|
||||
{
|
||||
// Console.WriteLine($"获取读写客户端前,尝试同步时发生异常:{DBSync.GetDescription(syncState)}");
|
||||
return null;
|
||||
}
|
||||
}*/
|
||||
|
||||
// 返回主数据库客户端
|
||||
return new SqlSugarClient(PrimaryDBConfig);
|
||||
}
|
||||
catch // (Exception ex)
|
||||
{
|
||||
// Console.WriteLine($"发生异常:{ex.Message}");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 数据库同步异常
|
||||
/// </summary>
|
||||
public class DBSyncException : Exception
|
||||
{
|
||||
public DBSyncExType ExceptionType { get; private set; }
|
||||
|
||||
public DBSyncException(DBSyncExType exceptionType)
|
||||
{
|
||||
ExceptionType = exceptionType;
|
||||
}
|
||||
|
||||
public DBSyncException(DBSyncExType exceptionType, string message) : base(message)
|
||||
{
|
||||
ExceptionType = exceptionType;
|
||||
}
|
||||
|
||||
public DBSyncException(DBSyncExType exceptionType, string message, Exception innerException) : base(message, innerException)
|
||||
{
|
||||
ExceptionType = exceptionType;
|
||||
}
|
||||
public override string ToString()
|
||||
{
|
||||
return $"异常: {ExceptionType}: {GetDescription(ExceptionType)}. Message: {Message}";
|
||||
}
|
||||
public static string GetDescription(DBSyncExType value)
|
||||
{
|
||||
FieldInfo field = value.GetType().GetField(value.ToString());
|
||||
DescriptionAttribute attribute = (DescriptionAttribute)field.GetCustomAttribute(typeof(DescriptionAttribute));
|
||||
return attribute == null ? value.ToString() : attribute.Description;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 远程、本地数据库同步
|
||||
/// </summary>
|
||||
public static class DBSync
|
||||
{
|
||||
/// <summary>
|
||||
/// 主数据库配置
|
||||
/// </summary>
|
||||
private static ConnectionConfig PrimaryConfig { get; set; }
|
||||
/// <summary>
|
||||
/// 从数据库配置
|
||||
/// </summary>
|
||||
private static ConnectionConfig SecondaryConfig { get; set; }
|
||||
/// <summary>
|
||||
/// 主数据库IP
|
||||
/// </summary>
|
||||
private static string Host { get; set; }
|
||||
/// <summary>
|
||||
/// 主数据库端口
|
||||
/// </summary>
|
||||
private static int Port { get; set; }
|
||||
/// <summary>
|
||||
/// 同步数据事件(远程数据库,本地数据库,是否执行成功)
|
||||
/// </summary>
|
||||
private static Func<SqlSugarClient, SqlSugarClient, bool> SyncDataEvent { get; set; }
|
||||
private static Action<bool> StateChangeEvent { get; set; }
|
||||
/// <summary>
|
||||
/// 数据库设置锁
|
||||
/// </summary>
|
||||
//private static object DBSetLock { get; set; } = new object();
|
||||
/// <summary>
|
||||
/// 是否需要同步数据
|
||||
/// </summary>
|
||||
private static bool IsNeedSyncData { get; set; } = false;
|
||||
/// <summary>
|
||||
/// 等待次数(执行了多少次操作后才尝试进行同步,设置为0容易影响性能)
|
||||
/// </summary>
|
||||
private static int WaitCount { get; set; } = 10;
|
||||
|
||||
/// <summary>
|
||||
/// 客户端获取计数
|
||||
/// </summary>
|
||||
private static int CrudDBGetCount { get; set; } = 0;
|
||||
/// <summary>
|
||||
/// 同步端获取计数
|
||||
/// </summary>
|
||||
private static int SyncDBGetCount { get; set; } = 0;
|
||||
|
||||
|
||||
//public static ManualResetEventSlim SyncEvent { get; } = new ManualResetEventSlim(true); // 同步事件
|
||||
/// <summary>
|
||||
/// 远程本地同步阻塞事件
|
||||
/// </summary>
|
||||
public static FifoManualResetEvent SyncEvent { get; } = new FifoManualResetEvent(true);
|
||||
/// <summary>
|
||||
/// 数据同步锁
|
||||
/// </summary>
|
||||
private static object SyncLock { get; } = new object();
|
||||
/// <summary>
|
||||
/// 是否需要同步数据读写锁
|
||||
/// </summary>
|
||||
private static readonly ReaderWriterLockSlim NeedSyncStateLock = new ReaderWriterLockSlim();
|
||||
|
||||
/// <summary>
|
||||
/// 是否断开过,true=断开过,false=没有断开过
|
||||
/// 设置为 false 时自动检测网络情况,只有在网络正常的情况下才能成功设置为 true
|
||||
/// </summary>
|
||||
/// <param name="value"></param>
|
||||
public static void SetIsNeedSyncData(bool value)
|
||||
{
|
||||
if (value == IsNeedSyncData)
|
||||
{
|
||||
return;
|
||||
}
|
||||
//Console.WriteLine("变更数据库");
|
||||
// 写入锁
|
||||
NeedSyncStateLock.EnterWriteLock();
|
||||
try
|
||||
{
|
||||
if (value)
|
||||
{
|
||||
IsNeedSyncData = true;
|
||||
return;
|
||||
}
|
||||
IsNeedSyncData = !IsPortOpen(); // 变更 是否同步 属性时获取网络状态
|
||||
}
|
||||
finally
|
||||
{
|
||||
NeedSyncStateLock.ExitWriteLock();
|
||||
StateChangeEvent?.Invoke(IsNeedSyncData);
|
||||
}
|
||||
}
|
||||
public static bool GetIsNeedSyncData()
|
||||
{
|
||||
// 读取锁
|
||||
NeedSyncStateLock.EnterReadLock();
|
||||
try
|
||||
{
|
||||
return IsNeedSyncData; //是否需要同步数据
|
||||
}
|
||||
finally
|
||||
{
|
||||
NeedSyncStateLock.ExitReadLock();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 配置主数据库
|
||||
/// </summary>
|
||||
public static void PrimaryConnect(DbType dbType, string host, int port, string dbName, string user, string password)
|
||||
{
|
||||
Host = host;
|
||||
Port = port;
|
||||
PrimaryConfig = GetConnectionConfig(dbType, host, port.ToString(), dbName, user, password);
|
||||
|
||||
/*SyncEvent.Wait();
|
||||
|
||||
if (true || IsPortOpen(host, port))
|
||||
{
|
||||
// 目标端口打通时才会更改数据库配置
|
||||
lock (DBSetLock)
|
||||
{
|
||||
Host = host;
|
||||
Port = port;
|
||||
PrimaryConfig = GetConnectionConfig(dbType, host, port.ToString(), dbName, user, password);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new DBSyncException(DBSyncExType.ConnectError, $"主数据库配置失败,无法连接,目标配置:IP:{host},端口:{port},目标库名:{dbName},账户:{user}");
|
||||
}*/
|
||||
}
|
||||
/// <summary>
|
||||
/// 配置从数据库
|
||||
/// </summary>
|
||||
public static void SecondaryConnect(DbType dbType, string host, int port, string dbName, string user, string password)
|
||||
{
|
||||
SecondaryConfig = GetConnectionConfig(dbType, host, port.ToString(), dbName, user, password);
|
||||
|
||||
/*if (IsPortOpen(host, port))
|
||||
{
|
||||
lock (DBSetLock)
|
||||
{
|
||||
SecondaryConfig = GetConnectionConfig(dbType, host, port.ToString(), dbName, user, password);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new DBSyncException(DBSyncExType.ConnectError, $"从数据库配置失败,无法连接,目标配置:{host},端口:{port},目标库名:{dbName},账户:{user}");
|
||||
}*/
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 尝试执行一次数据同步
|
||||
/// </summary>
|
||||
public static bool SyncData()
|
||||
{
|
||||
SetIsNeedSyncData(true);
|
||||
var state = StartSyncDataBase(true); // 手动同步
|
||||
return state == DBSyncStart.SyncSuccess || state == DBSyncStart.NotNeed;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 设置同步事件与等待次数。
|
||||
/// </summary>
|
||||
/// <param name="syncDataEvent">同步事件(需要手动同步数据)</param>
|
||||
/// <param name="waitCount">等待次数(执行了多少次操作后才尝试进行同步,设置为0容易影响性能)</param>
|
||||
public static void SetSyncEvent(Func<SqlSugarClient, SqlSugarClient, bool> syncDataEvent, int waitCount = 0)
|
||||
{
|
||||
SyncDataEvent = syncDataEvent;
|
||||
WaitCount = waitCount;
|
||||
}
|
||||
/// <summary>
|
||||
/// 设置状态变化事件
|
||||
/// </summary>
|
||||
/// <param name="stateChangeEvent"></param>
|
||||
/// <param name="isAtOnce"></param>
|
||||
public static void SetStateChangeEvent(Action<bool> stateChangeEvent)
|
||||
{
|
||||
StateChangeEvent = stateChangeEvent;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取数据库配置(不推荐使用在除了Repository的地方外部调用)
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static DBSyncConfig GetSyncSqlConfig()
|
||||
{
|
||||
/*SyncEvent.Wait();
|
||||
*/
|
||||
|
||||
if (GetIsNeedSyncData())
|
||||
{
|
||||
_ = Task.Run(() => StartSyncDataBase()); // new了一个RepositoryBase时尝试同步数据
|
||||
}
|
||||
|
||||
lock (SyncLock)
|
||||
{
|
||||
CrudDBGetCount++;
|
||||
//Console.WriteLine($"获取客户端:{CrudDBGetCount}");
|
||||
return new DBSyncConfig(PrimaryConfig, SecondaryConfig);
|
||||
}
|
||||
}
|
||||
|
||||
public static void ReSetCrudDb()
|
||||
{
|
||||
CrudDBGetCount--;
|
||||
Task.Run(() => StartSyncDataBase()); // 释放数据库连接时尝试同步数据
|
||||
|
||||
/*if (GetIsNeedSyncData())
|
||||
{
|
||||
|
||||
}*/
|
||||
// Console.WriteLine($"释放客户端:{CrudDBGetCount}");
|
||||
}
|
||||
|
||||
public static DBSyncStart StartSyncDataBase(bool isAtOnce = false)
|
||||
{
|
||||
/*if (!isAtOnce && WaitCount > 0)
|
||||
{
|
||||
WaitCount--;
|
||||
return DBSyncStart.NotNeed;
|
||||
}*/
|
||||
|
||||
SyncEvent.Reset(); // 锁定线程,保证只有一个线程进入该方法
|
||||
|
||||
if (!GetIsNeedSyncData())
|
||||
{
|
||||
SyncEvent.Set();
|
||||
return DBSyncStart.NotNeed;
|
||||
}
|
||||
|
||||
if (!IsPortOpen()) // 同步时获取网络状态
|
||||
{
|
||||
SetIsNeedSyncData(true);
|
||||
SyncEvent.Set();
|
||||
return DBSyncStart.NetworkError;
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (SyncDataEvent == null)
|
||||
{
|
||||
SyncEvent.Set();
|
||||
return DBSyncStart.NoEvent;
|
||||
}
|
||||
|
||||
|
||||
lock (SyncLock) // 同步锁,避免其它符合进入条件的线程执行多次同步
|
||||
{
|
||||
if (!GetIsNeedSyncData())
|
||||
{
|
||||
SyncEvent.Set();
|
||||
return DBSyncStart.NotNeed;
|
||||
}
|
||||
Console.WriteLine("网络检测OK,准备同步数据");
|
||||
try
|
||||
{
|
||||
bool isSuccess = SyncDataEvent.Invoke(new SqlSugarClient(PrimaryConfig), new SqlSugarClient(SecondaryConfig));
|
||||
SetIsNeedSyncData(!isSuccess);
|
||||
|
||||
if (isSuccess)
|
||||
{
|
||||
return DBSyncStart.SyncSuccess;
|
||||
}
|
||||
else
|
||||
{
|
||||
return DBSyncStart.SyncFailure;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// 记录异常日志
|
||||
Console.WriteLine($"同步数据时发生异常: {ex.Message}");
|
||||
return DBSyncStart.SyncFailure;
|
||||
}
|
||||
finally
|
||||
{
|
||||
SyncEvent.Set(); // 释放同步事件,以防止其他线程一直被阻塞
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
public static string GetDescription(DBSyncStart value)
|
||||
{
|
||||
FieldInfo field = value.GetType().GetField(value.ToString());
|
||||
DescriptionAttribute attribute = (DescriptionAttribute)field.GetCustomAttribute(typeof(DescriptionAttribute));
|
||||
return attribute == null ? value.ToString() : attribute.Description;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检测目标地址是否打通
|
||||
/// </summary>
|
||||
/// <param name="ip">ip地址</param>
|
||||
/// <param name="port">端口号</param>
|
||||
/// <param name="timeout">超时时间</param>
|
||||
/// <returns></returns>
|
||||
public static bool IsPortOpen(string ip, int port, int timeout = 300)
|
||||
{
|
||||
using (var client = new TcpClient())
|
||||
{
|
||||
var result = client.ConnectAsync(ip, port);
|
||||
try
|
||||
{
|
||||
var open = result.Wait(timeout);
|
||||
return open;
|
||||
}
|
||||
catch (SocketException)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// 检测目标地址是否打通:主数据库IP和端口是否打通(true通,false断)
|
||||
/// </summary>
|
||||
/// <param name="timeout">超时时间</param>
|
||||
/// <returns></returns>
|
||||
public static bool IsPortOpen(int timeout = 300)
|
||||
{
|
||||
string ip = Host;
|
||||
int port = Port;
|
||||
using (var client = new TcpClient())
|
||||
{
|
||||
bool isOpen = true;
|
||||
try
|
||||
{
|
||||
var result = client.ConnectAsync(ip, port);
|
||||
isOpen = result.Wait(timeout);
|
||||
if (!isOpen)
|
||||
{
|
||||
//Console.WriteLine($"连接超时{ip},{port}");
|
||||
}
|
||||
return isOpen;
|
||||
}
|
||||
catch
|
||||
{
|
||||
isOpen = false;
|
||||
return isOpen;
|
||||
}
|
||||
finally
|
||||
{
|
||||
//Console.WriteLine("网络检测:" + isOpen);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 返回数据库连接串
|
||||
/// </summary>
|
||||
/// <param name="dbType">数据库类型</param>
|
||||
/// <param name="host">服务器IP地址</param>
|
||||
/// <param name="dbName">数据库名</param>
|
||||
/// <param name="name">登录账户</param>
|
||||
/// <param name="password">登录密码</param>
|
||||
private static ConnectionConfig GetConnectionConfig(DbType dbType, string host, string port, string dbName, string name, string password)
|
||||
{
|
||||
ConnectionConfig config;
|
||||
string ConnectionString;
|
||||
switch (dbType)
|
||||
{
|
||||
case DbType.MySql:
|
||||
ConnectionString = $"Server={host};DataBase={dbName};Port={port};UserId={name};Password={password};Persist Security Info=True;Allow Zero Datetime=True;Character Set=utf8;";
|
||||
config = new ConnectionConfig()
|
||||
{
|
||||
ConnectionString = ConnectionString,//连接符字串
|
||||
DbType = DbType.MySql,
|
||||
IsAutoCloseConnection = true,
|
||||
InitKeyType = InitKeyType.Attribute //从实体特性中读取主键自增列信息
|
||||
};
|
||||
|
||||
break;
|
||||
case DbType.SqlServer:
|
||||
ConnectionString = $"Server={host},{port};DataBase={dbName};uid={name};pwd={password}";
|
||||
config = new ConnectionConfig()
|
||||
{
|
||||
ConnectionString = ConnectionString,//连接符字串
|
||||
DbType = DbType.SqlServer,
|
||||
IsAutoCloseConnection = true,
|
||||
InitKeyType = InitKeyType.Attribute //从实体特性中读取主键自增列信息
|
||||
};
|
||||
break;
|
||||
default:
|
||||
config = null;
|
||||
break;
|
||||
}
|
||||
return config;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
71
Library/DbSql/FifoManualResetEvent.cs
Normal file
71
Library/DbSql/FifoManualResetEvent.cs
Normal file
@@ -0,0 +1,71 @@
|
||||
namespace Serein.DbSql
|
||||
{
|
||||
/// <summary>
|
||||
/// 线程阻塞
|
||||
/// </summary>
|
||||
public class FifoManualResetEvent
|
||||
{
|
||||
private readonly object lockObj = new object();
|
||||
/// <summary>
|
||||
/// 让线程按进入时间顺序调用
|
||||
/// </summary>
|
||||
private readonly Queue<Thread> waitQueue = new Queue<Thread>();
|
||||
private bool isSet;
|
||||
|
||||
public bool IsSet { get => isSet; set => isSet = value; }
|
||||
|
||||
public FifoManualResetEvent(bool initialState = false)
|
||||
{
|
||||
IsSet = initialState;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 等待解锁
|
||||
/// </summary>
|
||||
public void Wait()
|
||||
{
|
||||
lock (lockObj)
|
||||
{
|
||||
if (IsSet)
|
||||
{
|
||||
// 获取到了发送的信号,线程开始重新执行
|
||||
return;
|
||||
}
|
||||
|
||||
var currentThread = Thread.CurrentThread;
|
||||
waitQueue.Enqueue(currentThread);
|
||||
|
||||
while (!IsSet || waitQueue.Peek() != currentThread)
|
||||
{
|
||||
Monitor.Wait(lockObj);
|
||||
}
|
||||
|
||||
waitQueue.Dequeue();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 发送信号
|
||||
/// </summary>
|
||||
public void Set()
|
||||
{
|
||||
lock (lockObj)
|
||||
{
|
||||
IsSet = true;
|
||||
Monitor.PulseAll(lockObj);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 锁定当前线程
|
||||
/// </summary>
|
||||
public void Reset()
|
||||
{
|
||||
lock (lockObj)
|
||||
{
|
||||
IsSet = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
19
Library/DbSql/IRepositoryBase.cs
Normal file
19
Library/DbSql/IRepositoryBase.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
using System.Linq.Expressions;
|
||||
|
||||
namespace Serein.DbSql
|
||||
{
|
||||
public interface IRepositoryBase<TEntity> where TEntity : class, new()
|
||||
{
|
||||
TEntity GetModelByID(dynamic ID);
|
||||
|
||||
int Add(TEntity Model);
|
||||
|
||||
int Update(TEntity Model);
|
||||
|
||||
bool DeleteByID(dynamic ID);
|
||||
|
||||
bool Delete(Expression<Func<TEntity, bool>> where);
|
||||
|
||||
int UpdateColumns(TEntity model, Expression<Func<TEntity, object>> expression);
|
||||
}
|
||||
}
|
||||
855
Library/DbSql/RepositoryBase.cs
Normal file
855
Library/DbSql/RepositoryBase.cs
Normal file
@@ -0,0 +1,855 @@
|
||||
|
||||
using Serein.DbSql;
|
||||
using Newtonsoft.Json.Converters;
|
||||
using Newtonsoft.Json;
|
||||
using SqlSugar;
|
||||
using System.Data;
|
||||
using System.Linq.Expressions;
|
||||
using Serein.Tool;
|
||||
|
||||
namespace Serein.Helper
|
||||
{
|
||||
|
||||
// public class RepositoryBase<TEntity> : DataBase, IRepositoryBase<TEntity> where TEntity : class, new()
|
||||
public class RepositoryBase<TEntity> : IRepositoryBase<TEntity> where TEntity : class, new()
|
||||
{
|
||||
public bool isHaveErr;
|
||||
|
||||
public string ErrMsg = "";
|
||||
|
||||
public string filterName = "SubSystemName";
|
||||
~RepositoryBase()
|
||||
{
|
||||
DBSync.ReSetCrudDb();
|
||||
}
|
||||
public RepositoryBase()
|
||||
{
|
||||
}
|
||||
/// <summary>
|
||||
/// 是否优先使用本地数据库
|
||||
/// </summary>
|
||||
public bool IsUseLoaclDB = false;
|
||||
|
||||
|
||||
#region 数据库操作 泛型抽象方法
|
||||
|
||||
#region 优先查询 主数据库
|
||||
|
||||
/// <summary>
|
||||
/// 无状态数据操作(查询)泛型抽象方法
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="func"></param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="DBSyncException"></exception>
|
||||
public virtual T SyncExecuteRead<T>(Func<SqlSugarClient, T> func)
|
||||
{
|
||||
var syncSqlConfig = DBSync.GetSyncSqlConfig(); // 基类获取数据库配置
|
||||
if (IsUseLoaclDB)
|
||||
{
|
||||
var secondaryDB = syncSqlConfig.GetSecondaryDB();
|
||||
return func.Invoke(secondaryDB); // 尝试查询本地数据库
|
||||
}
|
||||
|
||||
|
||||
if (syncSqlConfig.GetNetworkState()) // 网络检测
|
||||
{
|
||||
try
|
||||
{
|
||||
var primaryDB = syncSqlConfig.GetPrimaryDB();
|
||||
if (primaryDB != null)
|
||||
{
|
||||
return func.Invoke(primaryDB); // 尝试查询本地数据库
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine("远程数据库不可用");
|
||||
}
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
DBSync.SetIsNeedSyncData(true); // 网络不可达
|
||||
Console.WriteLine(ex.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var secondaryDB = syncSqlConfig.GetSecondaryDB();
|
||||
return func.Invoke(secondaryDB); // 尝试查询本地数据库
|
||||
}
|
||||
catch
|
||||
{
|
||||
throw new DBSyncException(DBSyncExType.CrudError, $"主从数据库不可用。\r\n {syncSqlConfig.ToString()} ");
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// 无状态数据操作(查询)泛型抽象方法
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="func"></param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="DBSyncException"></exception>
|
||||
public virtual T SyncExecuteRead<T>(Func<SimpleClient<TEntity>, T> func)
|
||||
{
|
||||
var syncSqlConfig = DBSync.GetSyncSqlConfig(); // 基类获取数据库配置
|
||||
|
||||
if (IsUseLoaclDB)
|
||||
{
|
||||
var secondaryDB = syncSqlConfig.GetSecondaryDB().GetSimpleClient<TEntity>();
|
||||
return func.Invoke(secondaryDB); // 尝试查询本地数据库
|
||||
}
|
||||
|
||||
if (syncSqlConfig.GetNetworkState()) // 网络检测
|
||||
{
|
||||
try
|
||||
{
|
||||
var primaryDB = syncSqlConfig.GetPrimaryDB()?.GetSimpleClient<TEntity>();
|
||||
if (primaryDB != null)
|
||||
{
|
||||
return func.Invoke(primaryDB); // 尝试查询远程数据库
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine("远程数据库不可用");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
DBSync.SetIsNeedSyncData(true); // 网络不可达
|
||||
Console.WriteLine(ex.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var secondaryDB = syncSqlConfig.GetSecondaryDB().GetSimpleClient<TEntity>();
|
||||
return func.Invoke(secondaryDB); // 尝试查询本地数据库
|
||||
}
|
||||
catch
|
||||
{
|
||||
throw new DBSyncException(DBSyncExType.CrudError, $"主从数据库不可用。\r\n {syncSqlConfig.ToString()} ");
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region 优先查询 从数据库 (已注释)
|
||||
/* /// <summary>
|
||||
/// 无状态数据操作(查询)泛型抽象方法
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="func"></param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="DBSyncException"></exception>
|
||||
public virtual T ExecuteSyncOperation<T>(Func<SqlSugarClient, T> func)
|
||||
{
|
||||
DBSync.SyncEvent.Wait();
|
||||
var secondaryDB = SyncSqlConfig.GetSecondaryDB();
|
||||
|
||||
try
|
||||
{
|
||||
return func.Invoke(secondaryDB); // 优先尝试查询本地数据库
|
||||
}
|
||||
catch
|
||||
{
|
||||
try
|
||||
{
|
||||
var primaryDB = SyncSqlConfig.GetPrimaryDB();
|
||||
if (primaryDB != null)
|
||||
{
|
||||
if (SyncSqlConfig.GetNetworkState()) // 网络检测
|
||||
{
|
||||
DBSync.SyncEvent.Wait();
|
||||
return func.Invoke(primaryDB); // 尝试查询远程数据库
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new DBSyncException(DBSyncExType.CrudError, "网络不可达,无法查询远程数据库。");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new DBSyncException(DBSyncExType.CrudError, "远程数据库不可用。");
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
throw new DBSyncException(DBSyncExType.CrudError, $"远程数据库查询失败。\r\n {SyncSqlConfig.ToString()} ");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 无状态数据操作(查询)泛型抽象方法
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="func"></param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="DBSyncException"></exception>
|
||||
public virtual T ExecuteSyncOperation<T>(Func<SimpleClient<TEntity>, T> func)
|
||||
{
|
||||
DBSync.SyncEvent.Wait();
|
||||
|
||||
var secondaryDB = SyncSqlConfig.GetSecondaryDB().GetSimpleClient<TEntity>();
|
||||
|
||||
try
|
||||
{
|
||||
return func.Invoke(secondaryDB); // 优先尝试查询本地数据库
|
||||
}
|
||||
catch
|
||||
{
|
||||
// 本地数据库查询失败,尝试远程数据库
|
||||
try
|
||||
{
|
||||
var primaryDB = SyncSqlConfig.GetPrimaryDB()?.GetSimpleClient<TEntity>();
|
||||
if (primaryDB != null)
|
||||
{
|
||||
if (SyncSqlConfig.GetNetworkState()) // 网络检测
|
||||
{
|
||||
DBSync.SyncEvent.Wait();
|
||||
return func.Invoke(primaryDB); // 尝试查询远程数据库
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new DBSyncException(DBSyncExType.CrudError, "网络不可达,无法查询远程数据库。");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new DBSyncException(DBSyncExType.CrudError, "远程数据库不可用。");
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
throw new DBSyncException(DBSyncExType.CrudError, $"远程数据库查询失败。\r\n {SyncSqlConfig.ToString()} ");
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
#endregion
|
||||
|
||||
#region 增加、更新、删除 操作泛型方法
|
||||
/// <summary>
|
||||
/// 有状态数据操作(更新、增加、删除)泛型抽象方法,优先操作本地数据库,操作远程数据库失败时调用DBSync.SetIsNeedSyncData(true);
|
||||
/// </summary>
|
||||
/// <typeparam name="TResult"></typeparam>
|
||||
/// <param name="primaryFunc"></param>
|
||||
/// <param name="secondaryFunc"></param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="DBSyncException"></exception>
|
||||
public virtual T SyncExecuteCUD<T>(Func<SqlSugarClient, T> func)
|
||||
{
|
||||
var syncSqlConfig = DBSync.GetSyncSqlConfig(); // 基类获取数据库配置
|
||||
var secondaryDB = syncSqlConfig.GetSecondaryDB();
|
||||
try
|
||||
{
|
||||
var secondaryResult = func.Invoke(secondaryDB); // 本地数据库操作
|
||||
if (IsUseLoaclDB)
|
||||
{
|
||||
return secondaryResult;
|
||||
}
|
||||
if (syncSqlConfig.GetNetworkState()) // 网络检测
|
||||
{
|
||||
var primaryDB = syncSqlConfig.GetPrimaryDB();
|
||||
if(primaryDB != null)
|
||||
{
|
||||
var primaryResult = func.Invoke(primaryDB); // 远程数据库操作
|
||||
return primaryResult;
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine("远程数据库不可用");
|
||||
}
|
||||
}
|
||||
return secondaryResult;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine("主从数据库不可用:" + ex.ToString());
|
||||
DBSync.SetIsNeedSyncData(true);
|
||||
throw new DBSyncException(DBSyncExType.CrudError, $"主从数据库不可用。\r\n {syncSqlConfig.ToString()} ");
|
||||
}
|
||||
}
|
||||
|
||||
public virtual T SyncExecuteCUD<T>(Func<SimpleClient<TEntity>, T> func)
|
||||
{
|
||||
var syncSqlConfig = DBSync.GetSyncSqlConfig(); // 基类获取数据库配置
|
||||
var secondaryDB = syncSqlConfig.GetSecondaryDB().GetSimpleClient<TEntity>();
|
||||
|
||||
try
|
||||
{
|
||||
var secondaryResult = func.Invoke(secondaryDB); // 本地数据库操作
|
||||
if (IsUseLoaclDB)
|
||||
{
|
||||
return secondaryResult;
|
||||
}
|
||||
if (syncSqlConfig.GetNetworkState()) // 网络检测
|
||||
{
|
||||
var primaryDB = syncSqlConfig.GetPrimaryDB().GetSimpleClient<TEntity>();
|
||||
if(primaryDB != null)
|
||||
{
|
||||
var primaryResult = func.Invoke(primaryDB); // 远程数据库操作
|
||||
return primaryResult;
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine("远程数据库不可用");
|
||||
}
|
||||
}
|
||||
return secondaryResult;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine("主从数据库不可用:" + ex.ToString());
|
||||
DBSync.SetIsNeedSyncData(true);
|
||||
throw new DBSyncException(DBSyncExType.CrudError, $"主从数据库不可用。\r\n {syncSqlConfig.ToString()} ");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
public TEntity SyncRead(Func<SqlSugarClient, TEntity> func)
|
||||
{
|
||||
return SyncExecuteRead(func);
|
||||
}
|
||||
|
||||
public bool SyncRead(Func<SqlSugarClient, bool> func)
|
||||
{
|
||||
return SyncExecuteRead(func);
|
||||
}
|
||||
|
||||
public List<T> SyncRead<T>(Func<SqlSugarClient, List<T>> func)
|
||||
{
|
||||
return SyncExecuteRead(func);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 查询返回实体
|
||||
/// </summary>
|
||||
public TEntity SyncRead(Func<SimpleClient<TEntity>, TEntity> func)
|
||||
{
|
||||
return SyncExecuteRead(func);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查询返回实体列表
|
||||
/// </summary>
|
||||
public List<T> SyncRead<T>(Func<SimpleClient<TEntity>, List<T>> func)
|
||||
{
|
||||
return SyncExecuteRead(func);
|
||||
}
|
||||
|
||||
public TEntity SyncCUD(Func<SqlSugarClient, TEntity> func)
|
||||
{
|
||||
return SyncExecuteCUD(func);
|
||||
}
|
||||
|
||||
public int SyncCUD(Func<SqlSugarClient, int> func)
|
||||
{
|
||||
return SyncExecuteCUD(func);
|
||||
}
|
||||
|
||||
public bool SyncCUD(Func<SqlSugarClient, bool> func)
|
||||
{
|
||||
return SyncExecuteCUD(func);
|
||||
}
|
||||
|
||||
public TEntity SyncSimpleCUD(Func<SimpleClient<TEntity>, TEntity> func)
|
||||
{
|
||||
|
||||
return SyncExecuteCUD(func);
|
||||
}
|
||||
|
||||
public int SyncSimpleCUD(Func<SimpleClient<TEntity>, int> func)
|
||||
{
|
||||
return SyncExecuteCUD(func);
|
||||
}
|
||||
|
||||
public bool SyncSimpleCUD(Func<SimpleClient<TEntity>, bool> func)
|
||||
{
|
||||
return SyncExecuteCUD(func);
|
||||
}
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
|
||||
public virtual TEntity GetModelByID(dynamic ID)
|
||||
{
|
||||
return SyncRead(db => db.GetById(ID));
|
||||
}
|
||||
|
||||
public virtual TEntity GetModel(Expression<Func<TEntity, bool>> where)
|
||||
{
|
||||
try
|
||||
{
|
||||
return SyncRead(db => db.Queryable<TEntity>().Where(where).First()); //db.GetSingle(where));
|
||||
// GetSingle结果不能大于1
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
isHaveErr = true;
|
||||
ErrMsg = ex.Message;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public virtual int Add(TEntity model)
|
||||
{
|
||||
try
|
||||
{
|
||||
return SyncCUD(db => db.Insertable(model).ExecuteCommand());
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
isHaveErr = true;
|
||||
ErrMsg = ex.Message;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public virtual int AddAndReturnIndex(TEntity model)
|
||||
{
|
||||
try
|
||||
{
|
||||
return SyncCUD(db => db.Insertable(model).ExecuteReturnIdentity());
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
isHaveErr = true;
|
||||
ErrMsg = ex.Message;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public virtual bool Exist(Expression<Func<TEntity, bool>> where)
|
||||
{
|
||||
try
|
||||
{
|
||||
return SyncRead(db => db.Queryable<TEntity>().Where(where).Take(1).Any());
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
isHaveErr = true;
|
||||
ErrMsg = ex.Message;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public int AddOrUpdate(TEntity model, string keyValue)
|
||||
{
|
||||
if (keyValue == "")
|
||||
{
|
||||
try
|
||||
{
|
||||
return SyncCUD(db => db.Insertable(model).ExecuteCommand());
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
isHaveErr = true;
|
||||
ErrMsg = ex.Message;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return SyncCUD(db => db.Updateable(model).ExecuteCommand());
|
||||
}
|
||||
|
||||
|
||||
public virtual int Update(TEntity model)
|
||||
{
|
||||
return SyncCUD(db => db.Updateable(model).ExecuteCommand());
|
||||
}
|
||||
|
||||
|
||||
public virtual int UpdateColumns(TEntity model, Expression<Func<TEntity, object>> expression)
|
||||
{
|
||||
//DatabaseSync.StartcaControls();
|
||||
try
|
||||
{
|
||||
return SyncCUD(db => db.Updateable(model).UpdateColumns(expression).ExecuteCommand());
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
isHaveErr = true;
|
||||
ErrMsg = ex.Message;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public virtual bool DeleteByID(dynamic ID)
|
||||
{
|
||||
|
||||
//SyncCUD(db => db.Updateable<TEntity>().RemoveDataCache().ExecuteCommand());
|
||||
return SyncSimpleCUD(db => (bool)db.DeleteById(ID));
|
||||
}
|
||||
|
||||
|
||||
public virtual bool Delete(Expression<Func<TEntity, bool>> where)
|
||||
{
|
||||
return SyncSimpleCUD(db => db.Delete(where));
|
||||
}
|
||||
|
||||
|
||||
public virtual string GetPageList(Pagination pagination, Expression<Func<TEntity, bool>> where = null)
|
||||
{
|
||||
//DatabaseSync.StartcaControls();
|
||||
return new
|
||||
{
|
||||
rows = GetList(pagination, where),
|
||||
total = pagination.total,
|
||||
page = pagination.page,
|
||||
records = pagination.records
|
||||
}.ToJson();
|
||||
}
|
||||
|
||||
|
||||
public virtual TEntity GetSingle(Expression<Func<TEntity, bool>> expression)
|
||||
{
|
||||
//DatabaseSync.StartcaControls();
|
||||
return SyncRead(db => db.Queryable<TEntity>().Filter(filterName, isDisabledGobalFilter: true).Single(expression));
|
||||
}
|
||||
|
||||
|
||||
public virtual List<TEntity> GetTop(int Top, Expression<Func<TEntity, object>> expression, OrderByType _OrderByType = OrderByType.Asc, Expression<Func<TEntity, bool>> where = null, string selstr = "*")
|
||||
{
|
||||
return SyncRead(db => db.Queryable<TEntity>().Select(selstr).WhereIF(where != null, where)
|
||||
.Take(Top)
|
||||
.OrderBy(expression, _OrderByType)
|
||||
.Filter(filterName, isDisabledGobalFilter: true)
|
||||
.ToList());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 排序表达式所用的键,排序方式,搜索条件
|
||||
/// </summary>
|
||||
/// <param name="OrderExpression"></param>
|
||||
/// <param name="_OrderByType"></param>
|
||||
/// <param name="where"></param>
|
||||
/// <returns></returns>
|
||||
public virtual TEntity GetFirst(Expression<Func<TEntity, object>> OrderExpression, OrderByType _OrderByType = OrderByType.Asc, Expression<Func<TEntity, bool>> where = null)
|
||||
{
|
||||
return SyncRead(db => db.Queryable<TEntity>().Filter(filterName, isDisabledGobalFilter: true).WhereIF(where != null, where)
|
||||
.OrderBy(OrderExpression, _OrderByType)
|
||||
.First());
|
||||
}
|
||||
|
||||
public virtual List<TEntity> GetList(Pagination pagination, Expression<Func<TEntity, bool>> where = null)
|
||||
{
|
||||
int totalNumber = 0;
|
||||
List<TEntity> result = SyncRead(db => db.Queryable<TEntity>().WhereIF(where != null, where).OrderBy(pagination.sidx + " " + pagination.sord)
|
||||
.Filter(filterName, isDisabledGobalFilter: true)
|
||||
.ToPageList(pagination.page, pagination.rows, ref totalNumber));
|
||||
pagination.records = totalNumber;
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
public virtual List<TEntity> GetList(Expression<Func<TEntity, bool>> where = null)
|
||||
{
|
||||
return SyncRead(db => db.Queryable<TEntity>().WhereIF(where != null, where).Filter(filterName, isDisabledGobalFilter: true)
|
||||
.ToList());
|
||||
}
|
||||
|
||||
public virtual List<TEntity> GetList()
|
||||
{
|
||||
return SyncRead(db => db.Queryable<TEntity>().ToList());
|
||||
}
|
||||
|
||||
|
||||
public virtual DataTable GetDataTable(Expression<Func<TEntity, bool>> where = null, Pagination pagination = null)
|
||||
{
|
||||
if (pagination != null)
|
||||
{
|
||||
return DataHelper.ListToDataTable(GetList(pagination, where));
|
||||
}
|
||||
return DataHelper.ListToDataTable(GetList(where));
|
||||
}
|
||||
|
||||
public virtual void UseFilter(SqlFilterItem item)
|
||||
{
|
||||
SyncCUD(db =>
|
||||
{
|
||||
db.QueryFilter.Remove(item.FilterName);
|
||||
db.QueryFilter.Add(item);
|
||||
return 0;
|
||||
});
|
||||
filterName = item.FilterName;
|
||||
}
|
||||
|
||||
|
||||
public virtual void ClearFilter()
|
||||
{
|
||||
SyncCUD(db =>
|
||||
{
|
||||
db.QueryFilter.Clear();
|
||||
return 0;
|
||||
});
|
||||
|
||||
filterName = null;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/* public void ReSetConnStr(string constr, SqlSugar.DbType _dbtype)
|
||||
{
|
||||
db = DBHelper.CreateDB(constr, _dbtype);
|
||||
Sclient = db.GetSimpleClient<TEntity>();
|
||||
}
|
||||
|
||||
|
||||
public virtual TEntity GetModelByID(dynamic ID)
|
||||
{
|
||||
return Sclient.GetById(ID);
|
||||
}
|
||||
|
||||
|
||||
public virtual TEntity GetModel(Expression<Func<TEntity, bool>> where)
|
||||
{
|
||||
try
|
||||
{
|
||||
return Sclient.GetSingle(where);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
isHaveErr = true;
|
||||
ErrMsg = ex.Message;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public virtual int Add(TEntity model)
|
||||
{
|
||||
try
|
||||
{
|
||||
return db.Insertable(model).ExecuteCommand();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
isHaveErr = true;
|
||||
ErrMsg = ex.Message;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public virtual int AddAndReturnIndex(TEntity model)
|
||||
{
|
||||
try
|
||||
{
|
||||
return db.Insertable(model).ExecuteReturnIdentity();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
isHaveErr = true;
|
||||
ErrMsg = ex.Message;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public virtual bool Exist(Expression<Func<TEntity, bool>> where)
|
||||
{
|
||||
return db.Queryable<TEntity>().Where(where).Take(1).Any();
|
||||
}
|
||||
|
||||
|
||||
public int AddOrUpdate(TEntity model, string Keyvale)
|
||||
{
|
||||
if (Keyvale == "")
|
||||
{
|
||||
return db.Insertable(model).ExecuteCommand();
|
||||
}
|
||||
return db.Updateable(model).ExecuteCommand();
|
||||
}
|
||||
|
||||
|
||||
public virtual int Update(TEntity model)
|
||||
{
|
||||
return db.Updateable(model).ExecuteCommand();
|
||||
}
|
||||
|
||||
|
||||
public virtual int UpdateColumns(TEntity model, Expression<Func<TEntity, object>> expression)
|
||||
{
|
||||
return db.Updateable(model).UpdateColumns(expression).ExecuteCommand();
|
||||
}
|
||||
|
||||
|
||||
public virtual bool DeleteByID(dynamic ID)
|
||||
{
|
||||
db.Updateable<TEntity>().RemoveDataCache().ExecuteCommand();
|
||||
return Sclient.DeleteById(ID);
|
||||
}
|
||||
|
||||
|
||||
public virtual bool Delete(Expression<Func<TEntity, bool>> where)
|
||||
{
|
||||
return Sclient.Delete(where);
|
||||
}
|
||||
|
||||
|
||||
public virtual string GetPageList(Pagination pagination, Expression<Func<TEntity, bool>> where = null)
|
||||
{
|
||||
return new
|
||||
{
|
||||
rows = GetList(pagination, where),
|
||||
total = pagination.total,
|
||||
page = pagination.page,
|
||||
records = pagination.records
|
||||
}.ToJson();
|
||||
}
|
||||
|
||||
|
||||
public virtual TEntity GetSingle(Expression<Func<TEntity, bool>> expression)
|
||||
{
|
||||
return db.Queryable<TEntity>().Filter(filterName, isDisabledGobalFilter: true).Single(expression);
|
||||
}
|
||||
|
||||
|
||||
public virtual List<TEntity> GetTop(int Top, Expression<Func<TEntity, object>> expression, OrderByType _OrderByType = OrderByType.Asc, Expression<Func<TEntity, bool>> where = null, string selstr = "*")
|
||||
{
|
||||
return db.Queryable<TEntity>().Select(selstr).WhereIF(where != null, where)
|
||||
.Take(Top)
|
||||
.OrderBy(expression, _OrderByType)
|
||||
.Filter(filterName, isDisabledGobalFilter: true)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
|
||||
public virtual TEntity GetFirst(Expression<Func<TEntity, object>> OrderExpression, OrderByType _OrderByType = OrderByType.Asc, Expression<Func<TEntity, bool>> where = null)
|
||||
{
|
||||
return db.Queryable<TEntity>().Filter(filterName, isDisabledGobalFilter: true).WhereIF(where != null, where)
|
||||
.OrderBy(OrderExpression, _OrderByType)
|
||||
.First();
|
||||
}
|
||||
|
||||
public virtual List<TEntity> GetList(Pagination pagination, Expression<Func<TEntity, bool>> where = null)
|
||||
{
|
||||
int totalNumber = 0;
|
||||
List<TEntity> result = db.Queryable<TEntity>().WhereIF(where != null, where).OrderBy(pagination.sidx + " " + pagination.sord)
|
||||
.Filter(filterName, isDisabledGobalFilter: true)
|
||||
.ToPageList(pagination.page, pagination.rows, ref totalNumber);
|
||||
pagination.records = totalNumber;
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
public virtual List<TEntity> GetList(Expression<Func<TEntity, bool>> where = null)
|
||||
{
|
||||
return db.Queryable<TEntity>().WhereIF(where != null, where).Filter(filterName, isDisabledGobalFilter: true)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
public virtual List<TEntity> GetList()
|
||||
{
|
||||
return db.Queryable<TEntity>().ToList();
|
||||
}
|
||||
|
||||
|
||||
public virtual DataTable GetDataTable(Expression<Func<TEntity, bool>> where = null, Pagination pagination = null)
|
||||
{
|
||||
if (pagination != null)
|
||||
{
|
||||
return DataHelper.ListToDataTable(GetList(pagination, where));
|
||||
}
|
||||
return DataHelper.ListToDataTable(GetList(where));
|
||||
}
|
||||
|
||||
public virtual void UseFilter(SqlFilterItem item)
|
||||
{
|
||||
db.QueryFilter.Remove(item.FilterName);
|
||||
db.QueryFilter.Add(item);
|
||||
filterName = item.FilterName;
|
||||
}
|
||||
|
||||
|
||||
public virtual void ClearFilter()
|
||||
{
|
||||
db.QueryFilter.Clear();
|
||||
filterName = null;
|
||||
}
|
||||
|
||||
public void BeginTran()
|
||||
{
|
||||
db.Ado.BeginTran();
|
||||
}
|
||||
|
||||
public void CommitTran()
|
||||
{
|
||||
db.Ado.CommitTran();
|
||||
}
|
||||
|
||||
public void RollbackTran()
|
||||
{
|
||||
db.Ado.RollbackTran();
|
||||
}*/
|
||||
}
|
||||
public class Pagination
|
||||
{
|
||||
/// <summary>
|
||||
/// 每页行数
|
||||
/// </summary>
|
||||
public int rows { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 当前页
|
||||
/// </summary>
|
||||
public int page { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// /排序列
|
||||
/// </summary>
|
||||
public string sidx { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 排序类型
|
||||
/// </summary>
|
||||
public string sord { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 总记录数
|
||||
/// </summary>
|
||||
public int records { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 总页数
|
||||
/// </summary>
|
||||
public int total
|
||||
{
|
||||
get
|
||||
{
|
||||
if (records > 0)
|
||||
{
|
||||
if (records % rows != 0)
|
||||
{
|
||||
return records / rows + 1;
|
||||
}
|
||||
|
||||
return records / rows;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
13
Library/DynamicFlow/Api.cs
Normal file
13
Library/DynamicFlow/Api.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Serein.DynamicFlow
|
||||
{
|
||||
public interface IDynamicFlowNode
|
||||
{
|
||||
}
|
||||
|
||||
}
|
||||
73
Library/DynamicFlow/Attribute.cs
Normal file
73
Library/DynamicFlow/Attribute.cs
Normal file
@@ -0,0 +1,73 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Serein.DynamicFlow
|
||||
{
|
||||
|
||||
public enum DynamicNodeType
|
||||
{
|
||||
/// <summary>
|
||||
/// 初始化
|
||||
/// </summary>
|
||||
Init,
|
||||
/// <summary>
|
||||
/// 开始载入
|
||||
/// </summary>
|
||||
Loading,
|
||||
/// <summary>
|
||||
/// 结束
|
||||
/// </summary>
|
||||
Exit,
|
||||
|
||||
/// <summary>
|
||||
/// 触发器
|
||||
/// </summary>
|
||||
Flipflop,
|
||||
/// <summary>
|
||||
/// 条件节点
|
||||
/// </summary>
|
||||
Condition,
|
||||
/// <summary>
|
||||
/// 动作节点
|
||||
/// </summary>
|
||||
Action,
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 用来判断一个类是否需要注册并构建实例(单例模式场景使用)
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class)]
|
||||
public class DynamicFlowAttribute(bool scan = true) : Attribute
|
||||
{
|
||||
public bool Scan { get; set; } = scan;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 标记一个方法是什么类型,加载dll后用来拖拽到画布中
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
|
||||
public class MethodDetailAttribute(DynamicNodeType methodDynamicType,
|
||||
string methodTips = "",
|
||||
bool scan = true,
|
||||
string lockName = "") : Attribute
|
||||
{
|
||||
public bool Scan { get; set; } = scan;
|
||||
public string MethodTips { get; } = methodTips;
|
||||
public DynamicNodeType MethodDynamicType { get; } = methodDynamicType;
|
||||
public string LockName { get; } = lockName;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 是否为显式参数
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Parameter)]
|
||||
public class ExplicitAttribute : Attribute // where TEnum : Enum
|
||||
{
|
||||
}
|
||||
|
||||
}
|
||||
165
Library/DynamicFlow/DynamicContext.cs
Normal file
165
Library/DynamicFlow/DynamicContext.cs
Normal file
@@ -0,0 +1,165 @@
|
||||
using DynamicDemo.Node;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using static System.Collections.Specialized.BitVector32;
|
||||
using static System.Runtime.InteropServices.JavaScript.JSType;
|
||||
|
||||
namespace Serein.DynamicFlow
|
||||
{
|
||||
|
||||
public enum FfState
|
||||
{
|
||||
Succeed,
|
||||
Cancel,
|
||||
}
|
||||
/// <summary>
|
||||
/// 触发器上下文
|
||||
/// </summary>
|
||||
public class FlipflopContext
|
||||
{
|
||||
public FfState State { get; set; }
|
||||
public object? Data { get; set; }
|
||||
/*public FlipflopContext()
|
||||
{
|
||||
State = FfState.Cancel;
|
||||
}*/
|
||||
public FlipflopContext(FfState ffState,object? data = null)
|
||||
{
|
||||
State = ffState;
|
||||
Data = data;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 动态流程上下文
|
||||
/// </summary>
|
||||
public class DynamicContext(IServiceContainer serviceContainer)
|
||||
{
|
||||
|
||||
private readonly string contextGuid = "";//System.Guid.NewGuid().ToString();
|
||||
|
||||
public IServiceContainer ServiceContainer { get; } = serviceContainer;
|
||||
private List<Type> InitServices { get; set; } = [];
|
||||
|
||||
// private ConcurrentDictionary<string, object?> ContextData { get; set; } = [];
|
||||
|
||||
//public void SetFlowData(object data)
|
||||
//{
|
||||
// var threadId = Thread.CurrentThread.ManagedThreadId.ToString();
|
||||
// var name = $"{threadId}.{contextGuid}FlowData";
|
||||
// SetData(name,data);
|
||||
//}
|
||||
//public object GetFlowData(bool IsRetain = false)
|
||||
//{
|
||||
// var threadId = Thread.CurrentThread.ManagedThreadId.ToString();
|
||||
// var name = $"{threadId}.{contextGuid}FlowData";
|
||||
// if (IsRetain)
|
||||
// {
|
||||
// return GetData(name);
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// return GetAndRemoteData(name);
|
||||
|
||||
// }
|
||||
//}
|
||||
|
||||
|
||||
public void InitService<T>()
|
||||
{
|
||||
InitService(typeof(T));
|
||||
}
|
||||
public void InitService(Type type)
|
||||
{
|
||||
if (!InitServices.Contains(type))
|
||||
{
|
||||
InitServices.Add(type);
|
||||
}
|
||||
else
|
||||
{
|
||||
//throw new Exception("初始化时试图添加已存在的类型:"+type.Name);
|
||||
Console.WriteLine("初始化时试图添加已存在的类型:" + type.Name);
|
||||
}
|
||||
}
|
||||
public void Biuld()
|
||||
{
|
||||
foreach (var item in InitServices)
|
||||
{
|
||||
ServiceContainer.Register(item);
|
||||
}
|
||||
ServiceContainer.Build();
|
||||
}
|
||||
|
||||
//public object? RemoveData(string key)
|
||||
//{
|
||||
// if (ContextData.Remove(key, out var data))
|
||||
// {
|
||||
// return data;
|
||||
// }
|
||||
// return null;
|
||||
//}
|
||||
|
||||
//public void SetData<T>(string key, T value)
|
||||
//{
|
||||
// ContextData[key] = value;
|
||||
//}
|
||||
|
||||
//public T? GetData<T>(string key)
|
||||
//{
|
||||
// if (ContextData.TryGetValue(key, out object? value))
|
||||
// {
|
||||
// if(value == null)
|
||||
// {
|
||||
// return default;
|
||||
// }
|
||||
// if (value.GetType() == typeof(T))
|
||||
// {
|
||||
// return (T)value;
|
||||
// }
|
||||
|
||||
// }
|
||||
// return default;
|
||||
//}
|
||||
|
||||
//public object? GetData(string key)
|
||||
//{
|
||||
// if (ContextData.TryGetValue(key, out object? value))
|
||||
// {
|
||||
// return value;
|
||||
// }
|
||||
// return null;
|
||||
//}
|
||||
|
||||
|
||||
//public ConcurrentDictionary<string,Task> FlipFlopTasks { get; set; } = [];
|
||||
|
||||
public NodeRunTcs NodeRunCts { get; set; }
|
||||
public Task CreateTimingTask(Action action, int time = 100, int count = -1)
|
||||
{
|
||||
NodeRunCts ??= ServiceContainer.Get<NodeRunTcs>();
|
||||
return Task.Factory.StartNew(async () =>
|
||||
{
|
||||
for(int i = 0; i < count; i++)
|
||||
{
|
||||
NodeRunCts.Token.ThrowIfCancellationRequested();
|
||||
await time;
|
||||
action.Invoke();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public static class MyExtensions
|
||||
{
|
||||
public static TaskAwaiter GetAwaiter(this int i) => Task.Delay(i).GetAwaiter();
|
||||
}
|
||||
|
||||
|
||||
// if (time <= 0) throw new ArgumentException("时间不能≤0");
|
||||
}
|
||||
218
Library/DynamicFlow/MethodDetails.cs
Normal file
218
Library/DynamicFlow/MethodDetails.cs
Normal file
@@ -0,0 +1,218 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using static Dm.net.buffer.ByteArrayBuffer;
|
||||
|
||||
namespace Serein.DynamicFlow
|
||||
{
|
||||
/// <summary>
|
||||
/// 显式参数
|
||||
/// </summary>
|
||||
public class ExplicitData
|
||||
{
|
||||
/// <summary>
|
||||
/// 索引
|
||||
/// </summary>
|
||||
public int Index { get; set; }
|
||||
/// <summary>
|
||||
/// 是否为显式参数
|
||||
/// </summary>
|
||||
public bool IsExplicitData { get; set; }
|
||||
/// <summary>
|
||||
/// 显式类型
|
||||
/// </summary>
|
||||
public Type? ExplicitType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 显示类型编号>
|
||||
/// </summary>
|
||||
public string ExplicitTypeName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 方法需要的类型
|
||||
/// </summary>
|
||||
public Type DataType { get; set; }
|
||||
/// <summary>
|
||||
/// 方法入参参数名称
|
||||
/// </summary>
|
||||
public string ParameterName { get; set; }
|
||||
/// <summary>
|
||||
/// 入参值
|
||||
/// </summary>
|
||||
public string DataValue { get; set; }
|
||||
|
||||
public string[] Items { get; set; }
|
||||
|
||||
|
||||
|
||||
public ExplicitData Clone() => new()
|
||||
{
|
||||
Index = Index,
|
||||
IsExplicitData = IsExplicitData,
|
||||
ExplicitType = ExplicitType,
|
||||
DataType = DataType,
|
||||
ParameterName = ParameterName,
|
||||
ExplicitTypeName = ExplicitTypeName,
|
||||
DataValue = string.IsNullOrEmpty(DataValue) ? string.Empty : DataValue,
|
||||
Items = [.. Items],
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
||||
public class MethodDetails
|
||||
{
|
||||
public MethodDetails CpoyNew()
|
||||
{
|
||||
return new MethodDetails
|
||||
{
|
||||
ActingInstance = ActingInstance,
|
||||
ActingInstanceType = ActingInstanceType,
|
||||
MethodDelegate = MethodDelegate,
|
||||
MethodDynamicType = MethodDynamicType,
|
||||
MethodGuid = Guid.NewGuid().ToString(),
|
||||
MethodTips = MethodTips + " Cpoy",
|
||||
//ParameterTypes = ParameterTypes,
|
||||
ReturnType = ReturnType,
|
||||
MethodName = MethodName,
|
||||
MethodLockName = MethodLockName,
|
||||
//ExplicitDataValues = ExplicitDataValues.Select(it => "").ToArray(),
|
||||
ExplicitDatas = ExplicitDatas.Select(it => it.Clone()).ToArray(),
|
||||
//IsExplicits = IsExplicits,
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 作用实例
|
||||
/// </summary>
|
||||
public Type ActingInstanceType { get; set; }
|
||||
/// <summary>
|
||||
/// 作用实例
|
||||
/// </summary>
|
||||
public object ActingInstance { get; set; }
|
||||
/// <summary>
|
||||
/// 方法GUID
|
||||
/// </summary>
|
||||
public string MethodGuid { get; set; }
|
||||
/// <summary>
|
||||
/// 方法名称
|
||||
/// </summary>
|
||||
public string MethodName { get; set; }
|
||||
/// <summary>
|
||||
/// 方法委托
|
||||
/// </summary>
|
||||
public Delegate MethodDelegate { get; set; }
|
||||
/// <summary>
|
||||
/// 节点类型
|
||||
/// </summary>
|
||||
public DynamicNodeType MethodDynamicType { get; set; }
|
||||
/// <summary>
|
||||
/// 锁名称
|
||||
/// </summary>
|
||||
public string MethodLockName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 方法说明
|
||||
/// </summary>
|
||||
public string MethodTips { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 参数内容
|
||||
/// </summary>
|
||||
public ExplicitData[] ExplicitDatas { get; set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 出参类型
|
||||
/// </summary>
|
||||
public Type ReturnType { get; set; }
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
public bool IsCanConnect(Type returnType)
|
||||
{
|
||||
if (ExplicitDatas.Length == 0)
|
||||
{
|
||||
// 目标不需要传参,可以舍弃结果?
|
||||
return true;
|
||||
}
|
||||
var types = ExplicitDatas.Select(it => it.DataType).ToArray();
|
||||
// 检查返回类型是否是元组类型
|
||||
if (returnType.IsGenericType && IsValueTuple(returnType))
|
||||
{
|
||||
|
||||
return CompareGenericArguments(returnType, types);
|
||||
}
|
||||
else
|
||||
{
|
||||
int index = 0;
|
||||
if (types[index] == typeof(DynamicContext))
|
||||
{
|
||||
index++;
|
||||
if (types.Length == 1)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// 被连接节点检查自己需要的参数类型,与发起连接的节点比较返回值类型
|
||||
if (returnType == types[index])
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查元组类型
|
||||
/// </summary>
|
||||
/// <param name="type"></param>
|
||||
/// <returns></returns>
|
||||
private bool IsValueTuple(Type type)
|
||||
{
|
||||
if (!type.IsGenericType) return false;
|
||||
|
||||
var genericTypeDef = type.GetGenericTypeDefinition();
|
||||
return genericTypeDef == typeof(ValueTuple<>) ||
|
||||
genericTypeDef == typeof(ValueTuple<,>) ||
|
||||
genericTypeDef == typeof(ValueTuple<,,>) ||
|
||||
genericTypeDef == typeof(ValueTuple<,,,>) ||
|
||||
genericTypeDef == typeof(ValueTuple<,,,,>) ||
|
||||
genericTypeDef == typeof(ValueTuple<,,,,,>) ||
|
||||
genericTypeDef == typeof(ValueTuple<,,,,,,>) ||
|
||||
genericTypeDef == typeof(ValueTuple<,,,,,,,>);
|
||||
}
|
||||
|
||||
private bool CompareGenericArguments(Type returnType, Type[] parameterTypes)
|
||||
{
|
||||
var genericArguments = returnType.GetGenericArguments();
|
||||
var length = parameterTypes.Length;
|
||||
|
||||
for (int i = 0; i < genericArguments.Length; i++)
|
||||
{
|
||||
if (i >= length) return false;
|
||||
|
||||
if (IsValueTuple(genericArguments[i]))
|
||||
{
|
||||
// 如果当前参数也是 ValueTuple,递归检查嵌套的泛型参数
|
||||
if (!CompareGenericArguments(genericArguments[i], parameterTypes.Skip(i).ToArray()))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (genericArguments[i] != parameterTypes[i])
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
158
Library/DynamicFlow/NodeFlowStarter.cs
Normal file
158
Library/DynamicFlow/NodeFlowStarter.cs
Normal file
@@ -0,0 +1,158 @@
|
||||
using Serein;
|
||||
using Serein.DynamicFlow;
|
||||
using Serein.DynamicFlow.NodeModel;
|
||||
using Serein.DynamicFlow.Tool;
|
||||
using Serein.Web;
|
||||
using SqlSugar;
|
||||
using System.Collections.Concurrent;
|
||||
|
||||
namespace DynamicDemo.Node
|
||||
{
|
||||
|
||||
public class NodeRunTcs: CancellationTokenSource
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public class NodeFlowStarter(IServiceContainer serviceContainer,List<MethodDetails> methodDetails)
|
||||
{
|
||||
private readonly IServiceContainer ServiceContainer = serviceContainer;
|
||||
private readonly List<MethodDetails> methodDetails = methodDetails;
|
||||
private Action ExitAction = null;
|
||||
private DynamicContext context = null;
|
||||
|
||||
public NodeRunTcs MainCts;
|
||||
|
||||
/// <summary>
|
||||
/// 运行测试
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
public async Task RunAsync(List<NodeBase> nodes)
|
||||
{
|
||||
var startNode = nodes.FirstOrDefault(p => p.IsStart);
|
||||
if (startNode == null) { return; }
|
||||
context = new(ServiceContainer);
|
||||
|
||||
MainCts = ServiceContainer.CreateServiceInstance<NodeRunTcs>();
|
||||
|
||||
var initMethods = methodDetails.Where(it => it.MethodDynamicType == DynamicNodeType.Init).ToList();
|
||||
var loadingMethods = methodDetails.Where(it => it.MethodDynamicType == DynamicNodeType.Loading).ToList();
|
||||
var exitMethods = methodDetails.Where(it => it.MethodDynamicType == DynamicNodeType.Exit).ToList();
|
||||
ExitAction = () =>
|
||||
{
|
||||
ServiceContainer.Run<WebServer>((web) =>
|
||||
{
|
||||
web?.Stop();
|
||||
});
|
||||
foreach (MethodDetails? md in exitMethods)
|
||||
{
|
||||
object?[]? args = [context];
|
||||
object?[]? data = [md.ActingInstance, args];
|
||||
md.MethodDelegate.DynamicInvoke(data);
|
||||
}
|
||||
if(context != null && context.NodeRunCts != null && !context.NodeRunCts.IsCancellationRequested)
|
||||
{
|
||||
context.NodeRunCts.Cancel();
|
||||
}
|
||||
if (MainCts!=null && !MainCts.IsCancellationRequested) MainCts.Cancel();
|
||||
ServiceContainer.Reset();
|
||||
};
|
||||
|
||||
|
||||
foreach (var md in initMethods) // 初始化 - 调用方法
|
||||
{
|
||||
//md.ActingInstance = context.ServiceContainer.Get(md.ActingInstanceType);
|
||||
object?[]? args = [context];
|
||||
object?[]? data = [md.ActingInstance, args];
|
||||
md.MethodDelegate.DynamicInvoke(data);
|
||||
}
|
||||
context.Biuld();
|
||||
|
||||
foreach (var md in loadingMethods) // 加载
|
||||
{
|
||||
//md.ActingInstance = context.ServiceContainer.Get(md.ActingInstanceType);
|
||||
object?[]? args = [context];
|
||||
object?[]? data = [md.ActingInstance, args];
|
||||
md.MethodDelegate.DynamicInvoke(data);
|
||||
}
|
||||
|
||||
var flipflopNodes = nodes.Where(it => it.MethodDetails?.MethodDynamicType == DynamicNodeType.Flipflop
|
||||
&& it.PreviousNodes.Count == 0
|
||||
&& it.IsStart != true).ToArray();
|
||||
|
||||
var singleFlipflopNodes = flipflopNodes.Select(it => (SingleFlipflopNode)it).ToArray();
|
||||
|
||||
// 使用 TaskCompletionSource 创建未启动的任务
|
||||
var tasks = singleFlipflopNodes.Select(async node =>
|
||||
{
|
||||
await FlipflopExecute(node);
|
||||
}).ToArray();
|
||||
|
||||
|
||||
|
||||
try
|
||||
{
|
||||
await Task.WhenAll([startNode.ExecuteStack(context),.. tasks]);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await Console.Out.WriteLineAsync(ex.ToString());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private async Task FlipflopExecute(SingleFlipflopNode singleFlipFlopNode)
|
||||
{
|
||||
DynamicContext context = new DynamicContext(ServiceContainer);
|
||||
MethodDetails md = singleFlipFlopNode.MethodDetails;
|
||||
|
||||
try
|
||||
{
|
||||
if (!DelegateCache.GlobalDicDelegates.TryGetValue(md.MethodName, out Delegate del))
|
||||
{
|
||||
return;
|
||||
}
|
||||
var func = md.ExplicitDatas.Length == 0 ? ((Func<object, object, Task<FlipflopContext>>)del) : ((Func<object, object[], Task<FlipflopContext>>)del);
|
||||
|
||||
while (!MainCts.IsCancellationRequested) // 循环中直到栈为空才会退出
|
||||
{
|
||||
object?[]? parameters = singleFlipFlopNode.GetParameters(context, md);
|
||||
// 调用委托并获取结果
|
||||
FlipflopContext flipflopContext = await func.Invoke(md.ActingInstance, parameters);
|
||||
|
||||
if (flipflopContext == null)
|
||||
{
|
||||
break;
|
||||
}
|
||||
else if (flipflopContext.State == FfState.Cancel)
|
||||
{
|
||||
break;
|
||||
}
|
||||
else if (flipflopContext.State == FfState.Succeed)
|
||||
{
|
||||
singleFlipFlopNode.FlowState = true;
|
||||
singleFlipFlopNode.FlowData = flipflopContext.Data;
|
||||
var tasks = singleFlipFlopNode.TrueBranch.Select(nextNode =>
|
||||
{
|
||||
var context = new DynamicContext(ServiceContainer);
|
||||
nextNode.PreviousNode = singleFlipFlopNode;
|
||||
return nextNode.ExecuteStack(context);
|
||||
}).ToArray();
|
||||
Task.WaitAll(tasks);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await Console.Out.WriteLineAsync(ex.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void Exit()
|
||||
{
|
||||
ExitAction?.Invoke();
|
||||
}
|
||||
}
|
||||
}
|
||||
54
Library/DynamicFlow/NodeModel/CompositeActionNode.cs
Normal file
54
Library/DynamicFlow/NodeModel/CompositeActionNode.cs
Normal file
@@ -0,0 +1,54 @@
|
||||
using Serein.DynamicFlow;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Serein.DynamicFlow.NodeModel
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// 组合动作节点(用于动作区域)
|
||||
/// </summary>
|
||||
public class CompositeActionNode : NodeBase
|
||||
{
|
||||
public List<SingleActionNode> ActionNodes;
|
||||
/// <summary>
|
||||
/// 组合动作节点(用于动作区域)
|
||||
/// </summary>
|
||||
public CompositeActionNode(List<SingleActionNode> actionNodes)
|
||||
{
|
||||
ActionNodes = actionNodes;
|
||||
}
|
||||
public void AddNode(SingleActionNode node)
|
||||
{
|
||||
ActionNodes.Add(node);
|
||||
MethodDetails ??= node.MethodDetails;
|
||||
}
|
||||
|
||||
//public override void Execute(DynamicContext context)
|
||||
//{
|
||||
// //Dictionary<int,object> dict = new Dictionary<int,object>();
|
||||
// for (int i = 0; i < ActionNodes.Count; i++)
|
||||
// {
|
||||
// SingleActionNode? action = ActionNodes[i];
|
||||
// try
|
||||
// {
|
||||
// action.Execute(context);
|
||||
// }
|
||||
// catch (Exception ex)
|
||||
// {
|
||||
// Debug.Write(ex.Message);
|
||||
// return;
|
||||
// }
|
||||
// }
|
||||
|
||||
// CurrentState = true;
|
||||
// return;
|
||||
|
||||
|
||||
// /*foreach (var nextNode in TrueBranchNextNodes)
|
||||
// {
|
||||
// nextNode.ExecuteStack(context);
|
||||
// }*/
|
||||
//}
|
||||
}
|
||||
|
||||
}
|
||||
69
Library/DynamicFlow/NodeModel/CompositeConditionNode.cs
Normal file
69
Library/DynamicFlow/NodeModel/CompositeConditionNode.cs
Normal file
@@ -0,0 +1,69 @@
|
||||
using Serein.DynamicFlow.Tool;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Serein.DynamicFlow.NodeModel
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// 组合条件节点(用于条件区域)
|
||||
/// </summary>
|
||||
public class CompositeConditionNode : NodeBase
|
||||
{
|
||||
public List<SingleConditionNode> ConditionNodes { get; } =[];
|
||||
|
||||
|
||||
public void AddNode(SingleConditionNode node)
|
||||
{
|
||||
ConditionNodes.Add(node);
|
||||
MethodDetails ??= node.MethodDetails;
|
||||
}
|
||||
|
||||
public override object? Execute(DynamicContext context)
|
||||
{
|
||||
// bool allTrue = ConditionNodes.All(condition => Judge(context,condition.MethodDetails));
|
||||
// bool IsAllTrue = true; // 初始化为 true
|
||||
FlowState = true;
|
||||
foreach (SingleConditionNode? node in ConditionNodes)
|
||||
{
|
||||
if (!Judge(context, node))
|
||||
{
|
||||
FlowState = false;
|
||||
break;// 一旦发现条件为假,立即退出循环
|
||||
}
|
||||
}
|
||||
|
||||
return PreviousNode?.FlowData;
|
||||
//if (IsAllTrue)
|
||||
//{
|
||||
// foreach (var nextNode in TrueBranchNextNodes)
|
||||
// {
|
||||
// nextNode.ExecuteStack(context);
|
||||
// }
|
||||
//}
|
||||
//else
|
||||
//{
|
||||
// foreach (var nextNode in FalseBranchNextNodes)
|
||||
// {
|
||||
// nextNode.ExecuteStack(context);
|
||||
// }
|
||||
//}
|
||||
}
|
||||
private bool Judge(DynamicContext context, SingleConditionNode node)
|
||||
{
|
||||
try
|
||||
{
|
||||
node.Execute(context);
|
||||
return node.FlowState;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.Write(ex.Message);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
12
Library/DynamicFlow/NodeModel/CompositeLoopNode.cs
Normal file
12
Library/DynamicFlow/NodeModel/CompositeLoopNode.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Serein.DynamicFlow.NodeModel
|
||||
{
|
||||
public class CompositeLoopNode : NodeBase
|
||||
{
|
||||
}
|
||||
}
|
||||
449
Library/DynamicFlow/NodeModel/NodeBase.cs
Normal file
449
Library/DynamicFlow/NodeModel/NodeBase.cs
Normal file
@@ -0,0 +1,449 @@
|
||||
using Serein.DynamicFlow;
|
||||
using Serein.DynamicFlow.Tool;
|
||||
using Newtonsoft.Json;
|
||||
using SqlSugar;
|
||||
|
||||
namespace Serein.DynamicFlow.NodeModel
|
||||
{
|
||||
|
||||
public enum ConnectionType
|
||||
{
|
||||
IsTrue,
|
||||
IsFalse,
|
||||
IsEx,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 节点基类(数据):条件控件,动作控件,条件区域,动作区域
|
||||
/// </summary>
|
||||
public abstract class NodeBase : IDynamicFlowNode
|
||||
{
|
||||
public MethodDetails MethodDetails { get; set; }
|
||||
public string Guid { get; set; }
|
||||
public string DisplayName { get; set; }
|
||||
public bool IsStart { get; set; }
|
||||
public string DelegateName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 运行时的上一节点
|
||||
/// </summary>
|
||||
public NodeBase? PreviousNode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 上一节点集合
|
||||
/// </summary>
|
||||
public List<NodeBase> PreviousNodes { get; set; } = [];
|
||||
/// <summary>
|
||||
/// 下一节点集合(真分支)
|
||||
/// </summary>
|
||||
public List<NodeBase> TrueBranch { get; set; } = [];
|
||||
/// <summary>
|
||||
/// 下一节点集合(假分支)
|
||||
/// </summary>
|
||||
public List<NodeBase> FalseBranch { get; set; } = [];
|
||||
/// <summary>
|
||||
/// 异常分支
|
||||
/// </summary>
|
||||
public List<NodeBase> ExBranch { get; set; } = [];
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 当前状态(进入真分支还是假分支,异常分支在异常中确定)
|
||||
/// </summary>
|
||||
public bool FlowState { get; set; } = true;
|
||||
//public ConnectionType NextType { get; set; } = ConnectionType.IsTrue;
|
||||
/// <summary>
|
||||
/// 当前传递数据
|
||||
/// </summary>
|
||||
public object? FlowData { get; set; } = null;
|
||||
|
||||
|
||||
// 正常流程节点调用
|
||||
public virtual object? Execute(DynamicContext context)
|
||||
{
|
||||
MethodDetails md = MethodDetails;
|
||||
object? result = null;
|
||||
if (DelegateCache.GlobalDicDelegates.TryGetValue(md.MethodName, out Delegate del))
|
||||
{
|
||||
if (md.ExplicitDatas.Length == 0)
|
||||
{
|
||||
if (md.ReturnType == typeof(void))
|
||||
{
|
||||
((Action<object>)del).Invoke(md.ActingInstance);
|
||||
}
|
||||
else
|
||||
{
|
||||
result = ((Func<object, object>)del).Invoke(md.ActingInstance);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
object?[]? parameters = GetParameters(context, MethodDetails);
|
||||
if (md.ReturnType == typeof(void))
|
||||
{
|
||||
((Action<object, object[]>)del).Invoke(md.ActingInstance, parameters);
|
||||
}
|
||||
else
|
||||
{
|
||||
result = ((Func<object, object[], object>)del).Invoke(md.ActingInstance, parameters);
|
||||
}
|
||||
}
|
||||
// context.SetFlowData(result);
|
||||
// CurrentData = result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// 触发器调用
|
||||
public virtual async Task<object?> ExecuteAsync(DynamicContext context)
|
||||
{
|
||||
MethodDetails md = MethodDetails;
|
||||
object? result = null;
|
||||
if (DelegateCache.GlobalDicDelegates.TryGetValue(md.MethodName, out Delegate del))
|
||||
{
|
||||
if (md.ExplicitDatas.Length == 0)
|
||||
{
|
||||
// 调用委托并获取结果
|
||||
FlipflopContext flipflopContext = await ((Func<object, Task<FlipflopContext>>)del).Invoke(MethodDetails.ActingInstance);
|
||||
|
||||
if (flipflopContext != null)
|
||||
{
|
||||
if (flipflopContext.State == FfState.Cancel)
|
||||
{
|
||||
throw new Exception("this async task is cancel.");
|
||||
}
|
||||
else
|
||||
{
|
||||
if (flipflopContext.State == FfState.Succeed)
|
||||
{
|
||||
FlowState = true;
|
||||
result = flipflopContext.Data;
|
||||
}
|
||||
else
|
||||
{
|
||||
FlowState = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
object?[]? parameters = GetParameters(context, MethodDetails);
|
||||
// 调用委托并获取结果
|
||||
FlipflopContext flipflopContext = await ((Func<object, object[], Task<FlipflopContext>>)del).Invoke(MethodDetails.ActingInstance, parameters);
|
||||
|
||||
if (flipflopContext != null)
|
||||
{
|
||||
if (flipflopContext.State == FfState.Cancel)
|
||||
{
|
||||
throw new Exception("取消此异步");
|
||||
}
|
||||
else
|
||||
{
|
||||
FlowState = flipflopContext.State == FfState.Succeed;
|
||||
result = flipflopContext.Data;
|
||||
}
|
||||
}
|
||||
}
|
||||
// context.SetFlowData(result);
|
||||
// CurrentData = result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public async Task ExecuteStack(DynamicContext context)
|
||||
{
|
||||
var cts = context.ServiceContainer.Get<CancellationTokenSource>();
|
||||
|
||||
Stack<NodeBase> stack =[];
|
||||
stack.Push(this);
|
||||
|
||||
while (stack.Count > 0 && !cts.IsCancellationRequested) // 循环中直到栈为空才会退出循环
|
||||
{
|
||||
|
||||
// 从栈中弹出一个节点作为当前节点进行处理
|
||||
var currentNode = stack.Pop();
|
||||
|
||||
//currentNode.MethodDetails.ActingInstance ??= context.ServiceContainer.Get(
|
||||
// currentNode.MethodDetails.ActingInstanceType
|
||||
// );
|
||||
|
||||
if (currentNode.MethodDetails != null)
|
||||
{
|
||||
currentNode.MethodDetails.ActingInstance ??= context.ServiceContainer.Get(MethodDetails.ActingInstanceType);
|
||||
}
|
||||
|
||||
if (currentNode.MethodDetails != null && currentNode.MethodDetails.MethodDynamicType == DynamicNodeType.Flipflop)
|
||||
{
|
||||
currentNode.FlowData = await currentNode.ExecuteAsync(context);
|
||||
}
|
||||
else
|
||||
{
|
||||
currentNode.FlowData = currentNode.Execute(context);
|
||||
}
|
||||
|
||||
|
||||
var nextNodes = currentNode.FlowState ? currentNode.TrueBranch
|
||||
: currentNode.FalseBranch;
|
||||
|
||||
// 将下一个节点集合中的所有节点逆序推入栈中
|
||||
for (int i = nextNodes.Count - 1; i >= 0; i--)
|
||||
{
|
||||
nextNodes[i].PreviousNode = currentNode;
|
||||
stack.Push(nextNodes[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public object[]? GetParameters(DynamicContext context, MethodDetails md)
|
||||
{
|
||||
// 用正确的大小初始化参数数组
|
||||
var types = md.ExplicitDatas.Select(it => it.DataType).ToArray();
|
||||
if (types.Length == 0)
|
||||
{
|
||||
return [md.ActingInstance];
|
||||
}
|
||||
|
||||
object[]? parameters = new object[types.Length];
|
||||
|
||||
for (int i = 0; i < types.Length; i++)
|
||||
{
|
||||
|
||||
var mdEd = md.ExplicitDatas[i];
|
||||
Type type = mdEd.DataType;
|
||||
if (type == typeof(DynamicContext))
|
||||
{
|
||||
parameters[i] = context;
|
||||
}
|
||||
else if (type == typeof(MethodDetails))
|
||||
{
|
||||
parameters[i] = md;
|
||||
}
|
||||
else if (type == typeof(NodeBase))
|
||||
{
|
||||
parameters[i] = this;
|
||||
}
|
||||
else if (mdEd.IsExplicitData) // 显式参数
|
||||
{
|
||||
if (mdEd.DataType.IsEnum)
|
||||
{
|
||||
var enumValue = Enum.Parse(mdEd.DataType, mdEd.DataValue);
|
||||
parameters[i] = enumValue;
|
||||
}
|
||||
else if (mdEd.ExplicitType == typeof(string))
|
||||
{
|
||||
parameters[i] = mdEd.DataValue;
|
||||
}
|
||||
else if (mdEd.ExplicitType == typeof(bool))
|
||||
{
|
||||
parameters[i] = bool.Parse(mdEd.DataValue);
|
||||
}
|
||||
else if (mdEd.ExplicitType == typeof(int))
|
||||
{
|
||||
parameters[i] = int.Parse(mdEd.DataValue);
|
||||
}
|
||||
else if (mdEd.ExplicitType == typeof(double))
|
||||
{
|
||||
parameters[i] = double.Parse(mdEd.DataValue);
|
||||
}
|
||||
else
|
||||
{
|
||||
parameters[i] = ConvertValue(mdEd.DataValue, mdEd.ExplicitType);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
//var tmpParameter = context.GetFlowData()?.ToString();
|
||||
var tmpParameter = PreviousNode?.FlowData?.ToString();
|
||||
if (mdEd.DataType.IsEnum)
|
||||
{
|
||||
var enumValue = Enum.Parse(mdEd.DataType, tmpParameter);
|
||||
parameters[i] = enumValue;
|
||||
}
|
||||
else if (mdEd.DataType == typeof(string))
|
||||
{
|
||||
parameters[i] = tmpParameter;
|
||||
}
|
||||
else if (mdEd.DataType == typeof(bool))
|
||||
{
|
||||
parameters[i] = bool.Parse(tmpParameter);
|
||||
}
|
||||
else if (mdEd.DataType == typeof(int))
|
||||
{
|
||||
parameters[i] = int.Parse(tmpParameter);
|
||||
}
|
||||
else if (mdEd.DataType == typeof(double))
|
||||
{
|
||||
parameters[i] = double.Parse(tmpParameter);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (tmpParameter != null && mdEd.DataType!= null)
|
||||
{
|
||||
parameters[i] = ConvertValue(tmpParameter, mdEd.DataType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
return parameters;
|
||||
}
|
||||
|
||||
|
||||
private dynamic? ConvertValue(string value, Type targetType)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!string.IsNullOrEmpty(value))
|
||||
{
|
||||
return JsonConvert.DeserializeObject(value, targetType);
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
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('\''); // 查找类型信息结束的索引
|
||||
var typeInfo = ex.Message[startIndex..endIndex]; // 提取出错类型信息,该怎么传出去?
|
||||
Console.WriteLine("无法转为对应的JSON对象:"+typeInfo);
|
||||
return null;
|
||||
}
|
||||
catch // (Exception ex)
|
||||
{
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
#region 完整的ExecuteAsync调用方法(不要删除)
|
||||
//public virtual async Task<object?> ExecuteAsync(DynamicContext context)
|
||||
//{
|
||||
// MethodDetails md = MethodDetails;
|
||||
// object? result = null;
|
||||
// if (DelegateCache.GlobalDicDelegates.TryGetValue(md.MethodName, out Delegate del))
|
||||
// {
|
||||
// if (md.ExplicitDatas.Length == 0)
|
||||
// {
|
||||
// if (md.ReturnType == typeof(void))
|
||||
// {
|
||||
// ((Action<object>)del).Invoke(md.ActingInstance);
|
||||
// }
|
||||
// else if (md.ReturnType == typeof(Task<FlipflopContext>))
|
||||
// {
|
||||
// // 调用委托并获取结果
|
||||
// FlipflopContext flipflopContext = await ((Func<object, Task<FlipflopContext>>)del).Invoke(MethodDetails.ActingInstance);
|
||||
|
||||
// if (flipflopContext != null)
|
||||
// {
|
||||
// if (flipflopContext.State == FfState.Cancel)
|
||||
// {
|
||||
// throw new Exception("this async task is cancel.");
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// if (flipflopContext.State == FfState.Succeed)
|
||||
// {
|
||||
// CurrentState = true;
|
||||
// result = flipflopContext.Data;
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// CurrentState = false;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// result = ((Func<object, object>)del).Invoke(md.ActingInstance);
|
||||
// }
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// object?[]? parameters = GetParameters(context, MethodDetails);
|
||||
// if (md.ReturnType == typeof(void))
|
||||
// {
|
||||
// ((Action<object, object[]>)del).Invoke(md.ActingInstance, parameters);
|
||||
// }
|
||||
// else if (md.ReturnType == typeof(Task<FlipflopContext>))
|
||||
// {
|
||||
// // 调用委托并获取结果
|
||||
// FlipflopContext flipflopContext = await ((Func<object, object[], Task<FlipflopContext>>)del).Invoke(MethodDetails.ActingInstance, parameters);
|
||||
|
||||
// if (flipflopContext != null)
|
||||
// {
|
||||
// if (flipflopContext.State == FfState.Cancel)
|
||||
// {
|
||||
// throw new Exception("取消此异步");
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// CurrentState = flipflopContext.State == FfState.Succeed;
|
||||
// result = flipflopContext.Data;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// result = ((Func<object, object[], object>)del).Invoke(md.ActingInstance, parameters);
|
||||
// }
|
||||
// }
|
||||
// context.SetFlowData(result);
|
||||
// }
|
||||
// return result;
|
||||
//}
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
/* while (stack.Count > 0) // 循环中直到栈为空才会退出
|
||||
{
|
||||
// 从栈中弹出一个节点作为当前节点进行处理
|
||||
var currentNode = stack.Pop();
|
||||
|
||||
if(currentNode is CompositeActionNode || currentNode is CompositeConditionNode)
|
||||
{
|
||||
currentNode.currentState = true;
|
||||
}
|
||||
else if (currentNode is CompositeConditionNode)
|
||||
{
|
||||
|
||||
}
|
||||
currentNode.Execute(context);
|
||||
// 根据当前节点的执行结果选择下一节点集合
|
||||
// 如果 currentState 为真,选择 TrueBranchNextNodes;否则选择 FalseBranchNextNodes
|
||||
var nextNodes = currentNode.currentState ? currentNode.TrueBranchNextNodes
|
||||
: currentNode.FalseBranchNextNodes;
|
||||
|
||||
// 将下一个节点集合中的所有节点逆序推入栈中
|
||||
for (int i = nextNodes.Count - 1; i >= 0; i--)
|
||||
{
|
||||
stack.Push(nextNodes[i]);
|
||||
}
|
||||
|
||||
}*/
|
||||
71
Library/DynamicFlow/NodeModel/SingleActionNode.cs
Normal file
71
Library/DynamicFlow/NodeModel/SingleActionNode.cs
Normal file
@@ -0,0 +1,71 @@
|
||||
using Serein.DynamicFlow.Tool;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Serein.DynamicFlow.NodeModel
|
||||
{
|
||||
/// <summary>
|
||||
/// 单动作节点(用于动作控件)
|
||||
/// </summary>
|
||||
public class SingleActionNode : NodeBase
|
||||
{
|
||||
//public override void Execute(DynamicContext context)
|
||||
//{
|
||||
// try
|
||||
// {
|
||||
// Execute(context, base.MethodDetails);
|
||||
// CurrentState = true;
|
||||
// }
|
||||
// catch (Exception ex)
|
||||
// {
|
||||
// Debug.Write(ex.Message);
|
||||
// CurrentState = false;
|
||||
// }
|
||||
//}
|
||||
|
||||
//public void Execute(DynamicContext context, MethodDetails md)
|
||||
//{
|
||||
// if (DelegateCache.GlobalDicDelegates.TryGetValue(md.MethodName, out Delegate del))
|
||||
// {
|
||||
|
||||
// object? result = null;
|
||||
|
||||
// if (md.ExplicitDatas.Length == 0)
|
||||
// {
|
||||
// if (md.ReturnType == typeof(void))
|
||||
// {
|
||||
// ((Action<object>)del).Invoke(md.ActingInstance);
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// result = ((Func<object, object>)del).Invoke(md.ActingInstance);
|
||||
// }
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// object?[]? parameters = GetParameters(context, MethodDetails);
|
||||
// if (md.ReturnType == typeof(void))
|
||||
// {
|
||||
// ((Action<object, object[]>)del).Invoke(md.ActingInstance, parameters);
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// result = ((Func<object, object[], object>)del).Invoke(md.ActingInstance, parameters);
|
||||
// }
|
||||
// }
|
||||
|
||||
// // 根据 ExplicitDatas.Length 判断委托类型
|
||||
// //var action = (Action<object, object[]>)del;
|
||||
|
||||
// // 调用委托并获取结果
|
||||
// // action.Invoke(MethodDetails.ActingInstance, parameters);
|
||||
|
||||
// //parameters = [md.ActingInstance, "", 123, ""];
|
||||
|
||||
// context.SetFlowData(result);
|
||||
// }
|
||||
//}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
72
Library/DynamicFlow/NodeModel/SingleConditionNode.cs
Normal file
72
Library/DynamicFlow/NodeModel/SingleConditionNode.cs
Normal file
@@ -0,0 +1,72 @@
|
||||
using Serein.DynamicFlow.SerinExpression;
|
||||
using Serein.DynamicFlow.Tool;
|
||||
using System.Diagnostics;
|
||||
using System.Linq.Expressions;
|
||||
|
||||
namespace Serein.DynamicFlow.NodeModel
|
||||
{
|
||||
/// <summary>
|
||||
/// 条件节点(用于条件控件)
|
||||
/// </summary>
|
||||
public class SingleConditionNode : NodeBase
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// 是否为自定义参数
|
||||
/// </summary>
|
||||
public bool IsCustomData { get; set; }
|
||||
/// <summary>
|
||||
/// 自定义参数值
|
||||
/// </summary>
|
||||
public object? CustomData { get; set; }
|
||||
/// <summary>
|
||||
/// 条件表达式
|
||||
/// </summary>
|
||||
public string Expression { get; set; }
|
||||
|
||||
public override object? Execute(DynamicContext context)
|
||||
{
|
||||
// 接收上一节点参数or自定义参数内容
|
||||
object? result;
|
||||
if (IsCustomData)
|
||||
{
|
||||
result = CustomData;
|
||||
}
|
||||
else
|
||||
{
|
||||
result = PreviousNode?.FlowData;
|
||||
}
|
||||
FlowState = SerinConditionParser.To(result, Expression);
|
||||
Console.WriteLine($"{result} {Expression} -> " + FlowState);
|
||||
return result;
|
||||
}
|
||||
|
||||
//public override void Execute(DynamicContext context)
|
||||
//{
|
||||
// CurrentState = Judge(context, base.MethodDetails);
|
||||
//}
|
||||
|
||||
//private bool Judge(DynamicContext context, MethodDetails md)
|
||||
//{
|
||||
// try
|
||||
// {
|
||||
// if (DelegateCache.GlobalDicDelegates.TryGetValue(md.MethodName, out Delegate del))
|
||||
// {
|
||||
// object[] parameters = GetParameters(context, md);
|
||||
// var temp = del.DynamicInvoke(parameters);
|
||||
// //context.GetData(GetDyPreviousKey());
|
||||
// return (bool)temp;
|
||||
// }
|
||||
// }
|
||||
// catch (Exception ex)
|
||||
// {
|
||||
// Debug.Write(ex.Message);
|
||||
// }
|
||||
// return false;
|
||||
//}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
44
Library/DynamicFlow/NodeModel/SingleExpOpNode.cs
Normal file
44
Library/DynamicFlow/NodeModel/SingleExpOpNode.cs
Normal file
@@ -0,0 +1,44 @@
|
||||
using Serein.DynamicFlow.SerinExpression;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Serein.DynamicFlow.NodeModel
|
||||
{
|
||||
/// <summary>
|
||||
/// Expression Operation - 表达式操作
|
||||
/// </summary>
|
||||
public class SingleExpOpNode : NodeBase
|
||||
{
|
||||
public string Expression { get; set; }
|
||||
|
||||
|
||||
public override object? Execute(DynamicContext context)
|
||||
{
|
||||
//if (PreviousNode != null && PreviousNode.FlowData == null)
|
||||
//{
|
||||
// // 存在
|
||||
// throw new InvalidOperationException("previous node data is null.");
|
||||
//}
|
||||
//else
|
||||
//{
|
||||
|
||||
//}
|
||||
var data = PreviousNode?.FlowData;
|
||||
var newData = SerinExpressionEvaluator.Evaluate(Expression, data, out bool isChange);
|
||||
FlowState = true;
|
||||
Console.WriteLine(newData);
|
||||
if (isChange)
|
||||
{
|
||||
return newData;
|
||||
}
|
||||
else
|
||||
{
|
||||
return PreviousNode?.FlowData;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
40
Library/DynamicFlow/NodeModel/SingleFlipflopNode.cs
Normal file
40
Library/DynamicFlow/NodeModel/SingleFlipflopNode.cs
Normal file
@@ -0,0 +1,40 @@
|
||||
using Serein.DynamicFlow.Tool;
|
||||
|
||||
namespace Serein.DynamicFlow.NodeModel
|
||||
{
|
||||
|
||||
public class SingleFlipflopNode : NodeBase
|
||||
{
|
||||
//public override void Execute(DynamicContext context)
|
||||
//{
|
||||
// throw new NotImplementedException("无法以非await/async的形式调用触发器");
|
||||
//}
|
||||
|
||||
//public virtual async Task ExecuteAsync(DynamicContext context, Action NextTask = null)
|
||||
//{
|
||||
// if (DelegateCache.GlobalDicDelegates.TryGetValue(MethodDetails.MethodName, out Delegate? del))
|
||||
// {
|
||||
// object?[]? parameters = GetParameters(context, MethodDetails);
|
||||
|
||||
// // 根据 ExplicitDatas.Length 判断委托类型
|
||||
// var func = (Func<object, object[], Task<FlipflopContext>>)del;
|
||||
|
||||
// // 调用委托并获取结果
|
||||
// FlipflopContext flipflopContext = await func.Invoke(MethodDetails.ActingInstance, parameters);
|
||||
|
||||
// if (flipflopContext != null)
|
||||
// {
|
||||
// if (flipflopContext.State == FfState.Cancel)
|
||||
// {
|
||||
// throw new Exception("取消此异步");
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// CurrentState = flipflopContext.State == FfState.Succeed;
|
||||
// context.SetFlowData(flipflopContext.Data);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
}
|
||||
}
|
||||
320
Library/DynamicFlow/SerinExpression/ConditionResolver.cs
Normal file
320
Library/DynamicFlow/SerinExpression/ConditionResolver.cs
Normal file
@@ -0,0 +1,320 @@
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Serein.DynamicFlow.SerinExpression
|
||||
{
|
||||
|
||||
public abstract class ConditionResolver
|
||||
{
|
||||
public abstract bool Evaluate(object obj);
|
||||
}
|
||||
|
||||
public class PassConditionResolver : ConditionResolver
|
||||
{
|
||||
public Operator Op { get; set; }
|
||||
public override bool Evaluate(object obj)
|
||||
{
|
||||
return Op switch
|
||||
{
|
||||
Operator.Pass => true,
|
||||
Operator.NotPass => false,
|
||||
_ => throw new NotSupportedException("不支持的条件类型")
|
||||
};
|
||||
}
|
||||
|
||||
public enum Operator
|
||||
{
|
||||
Pass,
|
||||
NotPass,
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public class ValueTypeConditionResolver<T> : ConditionResolver where T : struct, IComparable<T>
|
||||
{
|
||||
public enum Operator
|
||||
{
|
||||
/// <summary>
|
||||
/// 不进行任何操作
|
||||
/// </summary>
|
||||
Node,
|
||||
/// <summary>
|
||||
/// 大于
|
||||
/// </summary>
|
||||
GreaterThan,
|
||||
/// <summary>
|
||||
/// 小于
|
||||
/// </summary>
|
||||
LessThan,
|
||||
/// <summary>
|
||||
/// 等于
|
||||
/// </summary>
|
||||
Equal,
|
||||
/// <summary>
|
||||
/// 大于或等于
|
||||
/// </summary>
|
||||
GreaterThanOrEqual,
|
||||
/// <summary>
|
||||
/// 小于或等于
|
||||
/// </summary>
|
||||
LessThanOrEqual,
|
||||
/// <summary>
|
||||
/// 在两者之间
|
||||
/// </summary>
|
||||
InRange,
|
||||
/// <summary>
|
||||
/// 不在两者之间
|
||||
/// </summary>
|
||||
OutOfRange
|
||||
}
|
||||
|
||||
public Operator Op { get; set; }
|
||||
public T Value { get; set; }
|
||||
public T RangeStart { get; set; }
|
||||
public T RangeEnd { get; set; }
|
||||
public string ArithmeticExpression { get; set; }
|
||||
|
||||
public override bool Evaluate(object obj)
|
||||
{
|
||||
if (obj is T typedObj)
|
||||
{
|
||||
double numericValue = Convert.ToDouble(typedObj);
|
||||
if (!string.IsNullOrEmpty(ArithmeticExpression))
|
||||
{
|
||||
numericValue = SerinArithmeticExpressionEvaluator.Evaluate(ArithmeticExpression, numericValue);
|
||||
}
|
||||
|
||||
T evaluatedValue = (T)Convert.ChangeType(numericValue, typeof(T));
|
||||
|
||||
return Op switch
|
||||
{
|
||||
Operator.GreaterThan => evaluatedValue.CompareTo(Value) > 0,
|
||||
Operator.LessThan => evaluatedValue.CompareTo(Value) < 0,
|
||||
Operator.Equal => evaluatedValue.CompareTo(Value) == 0,
|
||||
Operator.GreaterThanOrEqual => evaluatedValue.CompareTo(Value) >= 0,
|
||||
Operator.LessThanOrEqual => evaluatedValue.CompareTo(Value) <= 0,
|
||||
Operator.InRange => evaluatedValue.CompareTo(RangeStart) >= 0 && evaluatedValue.CompareTo(RangeEnd) <= 0,
|
||||
Operator.OutOfRange => evaluatedValue.CompareTo(RangeStart) < 0 || evaluatedValue.CompareTo(RangeEnd) > 0,
|
||||
_ => throw new NotSupportedException("不支持的条件类型")
|
||||
};
|
||||
/* switch (Op)
|
||||
{
|
||||
case Operator.GreaterThan:
|
||||
return evaluatedValue.CompareTo(Value) > 0;
|
||||
case Operator.LessThan:
|
||||
return evaluatedValue.CompareTo(Value) < 0;
|
||||
case Operator.Equal:
|
||||
return evaluatedValue.CompareTo(Value) == 0;
|
||||
case Operator.GreaterThanOrEqual:
|
||||
return evaluatedValue.CompareTo(Value) >= 0;
|
||||
case Operator.LessThanOrEqual:
|
||||
return evaluatedValue.CompareTo(Value) <= 0;
|
||||
case Operator.InRange:
|
||||
return evaluatedValue.CompareTo(RangeStart) >= 0 && evaluatedValue.CompareTo(RangeEnd) <= 0;
|
||||
case Operator.OutOfRange:
|
||||
return evaluatedValue.CompareTo(RangeStart) < 0 || evaluatedValue.CompareTo(RangeEnd) > 0;
|
||||
}*/
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public class BoolConditionResolver : ConditionResolver
|
||||
{
|
||||
public enum Operator
|
||||
{
|
||||
/// <summary>
|
||||
/// 是
|
||||
/// </summary>
|
||||
Is
|
||||
}
|
||||
|
||||
public Operator Op { get; set; }
|
||||
public bool Value { get; set; }
|
||||
|
||||
public override bool Evaluate(object obj)
|
||||
{
|
||||
|
||||
if (obj is bool boolObj)
|
||||
{
|
||||
return boolObj == Value;
|
||||
/*switch (Op)
|
||||
{
|
||||
case Operator.Is:
|
||||
return boolObj == Value;
|
||||
}*/
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public class StringConditionResolver : ConditionResolver
|
||||
{
|
||||
public enum Operator
|
||||
{
|
||||
/// <summary>
|
||||
/// 出现过
|
||||
/// </summary>
|
||||
Contains,
|
||||
/// <summary>
|
||||
/// 没有出现过
|
||||
/// </summary>
|
||||
DoesNotContain,
|
||||
/// <summary>
|
||||
/// 相等
|
||||
/// </summary>
|
||||
Equal,
|
||||
/// <summary>
|
||||
/// 不相等
|
||||
/// </summary>
|
||||
NotEqual,
|
||||
/// <summary>
|
||||
/// 起始字符串等于
|
||||
/// </summary>
|
||||
StartsWith,
|
||||
/// <summary>
|
||||
/// 结束字符串等于
|
||||
/// </summary>
|
||||
EndsWith
|
||||
}
|
||||
|
||||
public Operator Op { get; set; }
|
||||
public string Value { get; set; }
|
||||
|
||||
public override bool Evaluate(object obj)
|
||||
{
|
||||
if (obj is string strObj)
|
||||
{
|
||||
return Op switch
|
||||
{
|
||||
Operator.Contains => strObj.Contains(Value),
|
||||
Operator.DoesNotContain => !strObj.Contains(Value),
|
||||
Operator.Equal => strObj == Value,
|
||||
Operator.NotEqual => strObj != Value,
|
||||
Operator.StartsWith => strObj.StartsWith(Value),
|
||||
Operator.EndsWith => strObj.EndsWith(Value),
|
||||
_ => throw new NotSupportedException("不支持的条件类型"),
|
||||
};
|
||||
|
||||
/* switch (Op)
|
||||
{
|
||||
case Operator.Contains:
|
||||
return strObj.Contains(Value);
|
||||
case Operator.DoesNotContain:
|
||||
return !strObj.Contains(Value);
|
||||
case Operator.Equal:
|
||||
return strObj == Value;
|
||||
case Operator.NotEqual:
|
||||
return strObj != Value;
|
||||
case Operator.StartsWith:
|
||||
return strObj.StartsWith(Value);
|
||||
case Operator.EndsWith:
|
||||
return strObj.EndsWith(Value);
|
||||
}*/
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
public class MemberConditionResolver<T> : ConditionResolver where T : struct, IComparable<T>
|
||||
{
|
||||
//public string MemberPath { get; set; }
|
||||
public ValueTypeConditionResolver<T>.Operator Op { get; set; }
|
||||
public object? TargetObj { get; set; }
|
||||
public T Value { get; set; }
|
||||
public string ArithmeticExpression { get; set; }
|
||||
public override bool Evaluate(object? obj)
|
||||
{
|
||||
//object? memberValue = GetMemberValue(obj, MemberPath);
|
||||
if (TargetObj is T typedObj)
|
||||
{
|
||||
return new ValueTypeConditionResolver<T>
|
||||
{
|
||||
Op = Op,
|
||||
Value = Value,
|
||||
ArithmeticExpression = ArithmeticExpression,
|
||||
}.Evaluate(typedObj);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
//private object? GetMemberValue(object? obj, string memberPath)
|
||||
//{
|
||||
// string[] members = memberPath[1..].Split('.');
|
||||
// foreach (var member in members)
|
||||
// {
|
||||
// if (obj == null) return null;
|
||||
// Type type = obj.GetType();
|
||||
// PropertyInfo? propertyInfo = type.GetProperty(member);
|
||||
// FieldInfo? fieldInfo = type.GetField(member);
|
||||
// if (propertyInfo != null)
|
||||
// obj = propertyInfo.GetValue(obj);
|
||||
// else if (fieldInfo != null)
|
||||
// obj = fieldInfo.GetValue(obj);
|
||||
// else
|
||||
// throw new ArgumentException($"Member {member} not found in type {type.FullName}");
|
||||
// }
|
||||
// return obj;
|
||||
//}
|
||||
}
|
||||
|
||||
public class MemberStringConditionResolver : ConditionResolver
|
||||
{
|
||||
public string MemberPath { get; set; }
|
||||
public StringConditionResolver.Operator Op { get; set; }
|
||||
public string Value { get; set; }
|
||||
|
||||
public override bool Evaluate(object obj)
|
||||
{
|
||||
object memberValue = GetMemberValue(obj, MemberPath);
|
||||
if (memberValue is string strObj)
|
||||
{
|
||||
return new StringConditionResolver
|
||||
{
|
||||
Op = Op,
|
||||
Value = Value
|
||||
}.Evaluate(strObj);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private object GetMemberValue(object? obj, string memberPath)
|
||||
{
|
||||
string[] members = memberPath[1..].Split('.');
|
||||
foreach (var member in members)
|
||||
{
|
||||
if (obj == null) return null;
|
||||
Type type = obj.GetType();
|
||||
PropertyInfo? propertyInfo = type.GetProperty(member);
|
||||
FieldInfo? fieldInfo = type.GetField(member);
|
||||
if (propertyInfo != null)
|
||||
obj = propertyInfo.GetValue(obj);
|
||||
else if (fieldInfo != null)
|
||||
obj = fieldInfo.GetValue(obj);
|
||||
else
|
||||
throw new ArgumentException($"Member {member} not found in type {type.FullName}");
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
private static string GetArithmeticExpression(string part)
|
||||
{
|
||||
int startIndex = part.IndexOf('[');
|
||||
int endIndex = part.IndexOf(']');
|
||||
if (startIndex >= 0 && endIndex > startIndex)
|
||||
{
|
||||
return part.Substring(startIndex + 1, endIndex - startIndex - 1);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
318
Library/DynamicFlow/SerinExpression/SerinConditionParser.cs
Normal file
318
Library/DynamicFlow/SerinExpression/SerinConditionParser.cs
Normal file
@@ -0,0 +1,318 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Serein.DynamicFlow.SerinExpression;
|
||||
|
||||
public class SerinConditionParser
|
||||
{
|
||||
public static bool To<T>(T data, string expression)
|
||||
{
|
||||
try
|
||||
{
|
||||
return ConditionParse(data, expression).Evaluate(data);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine(ex);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public static ConditionResolver ConditionParse(object data, string expression)
|
||||
{
|
||||
if (expression.StartsWith('.') /*&& expression.Contains('<') && expression.Contains('>')*/)
|
||||
{
|
||||
return ParseObjectExpression(data, expression);
|
||||
}
|
||||
else
|
||||
{
|
||||
return ParseSimpleExpression(data, expression);
|
||||
}
|
||||
|
||||
bool ContainsArithmeticOperators(string expression)
|
||||
{
|
||||
return expression.Contains('+') || expression.Contains('-') || expression.Contains('*') || expression.Contains('/');
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetArithmeticExpression(string part)
|
||||
{
|
||||
int startIndex = part.IndexOf('[');
|
||||
int endIndex = part.IndexOf(']');
|
||||
if (startIndex >= 0 && endIndex > startIndex)
|
||||
{
|
||||
return part.Substring(startIndex + 1, endIndex - startIndex - 1);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
private static object? GetMemberValue(object? obj, string memberPath)
|
||||
{
|
||||
string[] members = memberPath[1..].Split('.');
|
||||
foreach (var member in members)
|
||||
{
|
||||
if (obj == null) return null;
|
||||
Type type = obj.GetType();
|
||||
PropertyInfo? propertyInfo = type.GetProperty(member);
|
||||
FieldInfo? fieldInfo = type.GetField(member);
|
||||
if (propertyInfo != null)
|
||||
obj = propertyInfo.GetValue(obj);
|
||||
else if (fieldInfo != null)
|
||||
obj = fieldInfo.GetValue(obj);
|
||||
else
|
||||
throw new ArgumentException($"Member {member} not found in type {type.FullName}");
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
private static ConditionResolver ParseObjectExpression(object data, string expression)
|
||||
{
|
||||
var parts = expression.Split(' ');
|
||||
string operatorStr = parts[0];
|
||||
string valueStr = string.Join(' ', parts, 1, parts.Length - 1);
|
||||
|
||||
int typeStartIndex = expression.IndexOf('<');
|
||||
int typeEndIndex = expression.IndexOf('>');
|
||||
|
||||
string memberPath;
|
||||
Type type;
|
||||
object? targetObj;
|
||||
if ((typeStartIndex + typeStartIndex) == -2)
|
||||
{
|
||||
memberPath = operatorStr;
|
||||
targetObj = GetMemberValue(data, operatorStr);
|
||||
type = targetObj.GetType();
|
||||
operatorStr = parts[1].ToLower();
|
||||
valueStr = string.Join(' ', parts.Skip(2));
|
||||
}
|
||||
else
|
||||
{
|
||||
if (typeStartIndex >= typeEndIndex)
|
||||
{
|
||||
throw new ArgumentException("无效的表达式格式");
|
||||
}
|
||||
memberPath = expression.Substring(0, typeStartIndex).Trim();
|
||||
string typeStr = expression.Substring(typeStartIndex + 1, typeEndIndex - typeStartIndex - 1).Trim().ToLower();
|
||||
parts = expression.Substring(typeEndIndex + 1).Trim().Split(' ');
|
||||
if (parts.Length == 3)
|
||||
{
|
||||
operatorStr = parts[1].ToLower();
|
||||
valueStr = string.Join(' ', parts.Skip(2));
|
||||
}
|
||||
else
|
||||
{
|
||||
operatorStr = parts[0].ToLower();
|
||||
valueStr = string.Join(' ', parts.Skip(1));
|
||||
}
|
||||
targetObj = GetMemberValue(data, memberPath);
|
||||
Type tempType = typeStr switch
|
||||
{
|
||||
"int" => typeof(int),
|
||||
"double" => typeof(double),
|
||||
"bool" => typeof(bool),
|
||||
"string" => typeof(string),
|
||||
};
|
||||
type = (tempType ?? Type.GetType(typeStr)) ?? throw new ArgumentException("对象表达式无效的类型声明");
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (type == typeof(int))
|
||||
{
|
||||
int value = int.Parse(valueStr, CultureInfo.InvariantCulture);
|
||||
return new MemberConditionResolver<int>
|
||||
{
|
||||
TargetObj = targetObj,
|
||||
//MemberPath = memberPath,
|
||||
Op = ParseValueTypeOperator<int>(operatorStr),
|
||||
Value = value,
|
||||
ArithmeticExpression = GetArithmeticExpression(parts[0])
|
||||
};
|
||||
}
|
||||
else if (type == typeof(double))
|
||||
{
|
||||
double value = double.Parse(valueStr, CultureInfo.InvariantCulture);
|
||||
return new MemberConditionResolver<double>
|
||||
{
|
||||
//MemberPath = memberPath,
|
||||
TargetObj = targetObj,
|
||||
Op = ParseValueTypeOperator<double>(operatorStr),
|
||||
Value = value,
|
||||
ArithmeticExpression = GetArithmeticExpression(parts[0])
|
||||
};
|
||||
|
||||
}
|
||||
else if (type == typeof(bool))
|
||||
{
|
||||
return new MemberConditionResolver<bool>
|
||||
{
|
||||
//MemberPath = memberPath,
|
||||
TargetObj = targetObj,
|
||||
Op = (ValueTypeConditionResolver<bool>.Operator)ParseBoolOperator(operatorStr)
|
||||
};
|
||||
}
|
||||
else if (type == typeof(string))
|
||||
{
|
||||
return new MemberStringConditionResolver
|
||||
{
|
||||
MemberPath = memberPath,
|
||||
Op = ParseStringOperator(operatorStr),
|
||||
Value = valueStr
|
||||
};
|
||||
}
|
||||
|
||||
throw new NotSupportedException($"Type {type} is not supported.");
|
||||
}
|
||||
|
||||
private static ConditionResolver ParseSimpleExpression(object data, string expression)
|
||||
{
|
||||
if ("pass".Equals(expression.ToLower()))
|
||||
{
|
||||
return new PassConditionResolver
|
||||
{
|
||||
Op = PassConditionResolver.Operator.Pass,
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
if ("not pass".Equals(expression.ToLower()))
|
||||
{
|
||||
return new PassConditionResolver
|
||||
{
|
||||
Op = PassConditionResolver.Operator.NotPass,
|
||||
};
|
||||
}
|
||||
if ("!pass".Equals(expression.ToLower()))
|
||||
{
|
||||
return new PassConditionResolver
|
||||
{
|
||||
Op = PassConditionResolver.Operator.NotPass,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var parts = expression.Split(' ');
|
||||
|
||||
if (parts.Length < 2)
|
||||
throw new ArgumentException("无效的表达式格式。");
|
||||
|
||||
//string typeStr = parts[0];
|
||||
string operatorStr = parts[0];
|
||||
string valueStr = string.Join(' ', parts, 1, parts.Length - 1);
|
||||
|
||||
Type type = data.GetType();//Type.GetType(typeStr);
|
||||
if (type == typeof(int))
|
||||
{
|
||||
var op = ParseValueTypeOperator<int>(operatorStr);
|
||||
if (op == ValueTypeConditionResolver<int>.Operator.InRange || op == ValueTypeConditionResolver<int>.Operator.OutOfRange)
|
||||
{
|
||||
var temp = valueStr.Split('-');
|
||||
if (temp.Length < 2)
|
||||
throw new ArgumentException($"范围无效:{valueStr}。");
|
||||
int rangeStart = int.Parse(temp[0], CultureInfo.InvariantCulture);
|
||||
int rangeEnd = int.Parse(temp[1], CultureInfo.InvariantCulture);
|
||||
return new ValueTypeConditionResolver<int>
|
||||
{
|
||||
Op = op,
|
||||
RangeStart = rangeStart,
|
||||
RangeEnd = rangeEnd,
|
||||
ArithmeticExpression = GetArithmeticExpression(parts[0]),
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
int value = int.Parse(valueStr, CultureInfo.InvariantCulture);
|
||||
return new ValueTypeConditionResolver<int>
|
||||
{
|
||||
Op = op,
|
||||
Value = value,
|
||||
ArithmeticExpression = GetArithmeticExpression(parts[0])
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
else if (type == typeof(double))
|
||||
{
|
||||
double value = double.Parse(valueStr, CultureInfo.InvariantCulture);
|
||||
return new ValueTypeConditionResolver<double>
|
||||
{
|
||||
Op = ParseValueTypeOperator<double>(operatorStr),
|
||||
Value = value,
|
||||
ArithmeticExpression = GetArithmeticExpression(parts[0])
|
||||
};
|
||||
}
|
||||
else if (type == typeof(bool))
|
||||
{
|
||||
bool value = bool.Parse(valueStr);
|
||||
return new BoolConditionResolver
|
||||
{
|
||||
Op = ParseBoolOperator(operatorStr),
|
||||
Value = value,
|
||||
};
|
||||
}
|
||||
else if (type == typeof(string))
|
||||
{
|
||||
return new StringConditionResolver
|
||||
{
|
||||
Op = ParseStringOperator(operatorStr),
|
||||
Value = valueStr
|
||||
};
|
||||
}
|
||||
|
||||
throw new NotSupportedException($"Type {type} is not supported.");
|
||||
}
|
||||
|
||||
|
||||
private static ValueTypeConditionResolver<T>.Operator ParseValueTypeOperator<T>(string operatorStr) where T : struct, IComparable<T>
|
||||
{
|
||||
return operatorStr switch
|
||||
{
|
||||
">" => ValueTypeConditionResolver<T>.Operator.GreaterThan,
|
||||
"<" => ValueTypeConditionResolver<T>.Operator.LessThan,
|
||||
"==" => ValueTypeConditionResolver<T>.Operator.Equal,
|
||||
">=" => ValueTypeConditionResolver<T>.Operator.GreaterThanOrEqual,
|
||||
"≥" => ValueTypeConditionResolver<T>.Operator.GreaterThanOrEqual,
|
||||
"<=" => ValueTypeConditionResolver<T>.Operator.LessThanOrEqual,
|
||||
"≤" => ValueTypeConditionResolver<T>.Operator.LessThanOrEqual,
|
||||
"equals" => ValueTypeConditionResolver<T>.Operator.Equal,
|
||||
"in" => ValueTypeConditionResolver<T>.Operator.InRange,
|
||||
"!in" => ValueTypeConditionResolver<T>.Operator.OutOfRange,
|
||||
_ => throw new ArgumentException($"Invalid operator {operatorStr} for value type.")
|
||||
};
|
||||
}
|
||||
|
||||
private static BoolConditionResolver.Operator ParseBoolOperator(string operatorStr)
|
||||
{
|
||||
return operatorStr switch
|
||||
{
|
||||
"is" => BoolConditionResolver.Operator.Is,
|
||||
"==" => BoolConditionResolver.Operator.Is,
|
||||
"equals" => BoolConditionResolver.Operator.Is,
|
||||
//"isFalse" => BoolConditionNode.Operator.IsFalse,
|
||||
_ => throw new ArgumentException($"Invalid operator {operatorStr} for bool type.")
|
||||
};
|
||||
}
|
||||
|
||||
private static StringConditionResolver.Operator ParseStringOperator(string operatorStr)
|
||||
{
|
||||
return operatorStr switch
|
||||
{
|
||||
"c" => StringConditionResolver.Operator.Contains,
|
||||
"nc" => StringConditionResolver.Operator.DoesNotContain,
|
||||
"sw" => StringConditionResolver.Operator.StartsWith,
|
||||
"ew" => StringConditionResolver.Operator.EndsWith,
|
||||
|
||||
"contains" => StringConditionResolver.Operator.Contains,
|
||||
"doesNotContain" => StringConditionResolver.Operator.DoesNotContain,
|
||||
"equals" => StringConditionResolver.Operator.Equal,
|
||||
"==" => StringConditionResolver.Operator.Equal,
|
||||
"notEquals" => StringConditionResolver.Operator.NotEqual,
|
||||
"!=" => StringConditionResolver.Operator.NotEqual,
|
||||
"startsWith" => StringConditionResolver.Operator.StartsWith,
|
||||
"endsWith" => StringConditionResolver.Operator.EndsWith,
|
||||
_ => throw new ArgumentException($"Invalid operator {operatorStr} for string type.")
|
||||
};
|
||||
}
|
||||
}
|
||||
196
Library/DynamicFlow/SerinExpression/SerinExpressionEvaluator.cs
Normal file
196
Library/DynamicFlow/SerinExpression/SerinExpressionEvaluator.cs
Normal file
@@ -0,0 +1,196 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using static Serein.DynamicFlow.NodeModel.SingleExpOpNode;
|
||||
using static System.Runtime.InteropServices.JavaScript.JSType;
|
||||
|
||||
namespace Serein.DynamicFlow.SerinExpression
|
||||
{
|
||||
public class SerinArithmeticExpressionEvaluator
|
||||
{
|
||||
private static readonly DataTable table = new DataTable();
|
||||
|
||||
public static double Evaluate(string expression, double inputValue)
|
||||
{
|
||||
// 替换占位符@为输入值
|
||||
expression = expression.Replace("@", inputValue.ToString());
|
||||
try
|
||||
{
|
||||
// 使用 DataTable.Compute 方法计算表达式
|
||||
var result = table.Compute(expression, string.Empty);
|
||||
return Convert.ToDouble(result);
|
||||
}
|
||||
catch
|
||||
{
|
||||
throw new ArgumentException("Invalid arithmetic expression.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class SerinExpressionEvaluator
|
||||
{
|
||||
public static object Evaluate(string expression, object targetObJ,out bool IsChange)
|
||||
{
|
||||
var parts = expression.Split([' '], 2);
|
||||
if (parts.Length != 2)
|
||||
{
|
||||
throw new ArgumentException("Invalid expression format.");
|
||||
}
|
||||
|
||||
var operation = parts[0].ToLower();
|
||||
var operand = parts[1][0] == '.' ? parts[1][1..]: parts[1];
|
||||
|
||||
var result = operation switch
|
||||
{
|
||||
"@num" => ComputedNumber(targetObJ, operand),
|
||||
"@call" => InvokeMethod(targetObJ, operand),
|
||||
"@get" => GetMember(targetObJ, operand),
|
||||
"@set" => SetMember(targetObJ, operand),
|
||||
_ => throw new NotSupportedException($"Operation {operation} is not supported.")
|
||||
};
|
||||
|
||||
IsChange = operation switch
|
||||
{
|
||||
"@num" => true,
|
||||
"@call" => true,
|
||||
"@get" => true,
|
||||
"@set" => false,
|
||||
_ => throw new NotSupportedException($"Operation {operation} is not supported.")
|
||||
};
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
private static readonly char[] separator = ['(', ')'];
|
||||
private static readonly char[] separatorArray = [','];
|
||||
|
||||
private static object InvokeMethod(object target, string methodCall)
|
||||
{
|
||||
var methodParts = methodCall.Split(separator, StringSplitOptions.RemoveEmptyEntries);
|
||||
if (methodParts.Length != 2)
|
||||
{
|
||||
throw new ArgumentException("Invalid method call format.");
|
||||
}
|
||||
|
||||
var methodName = methodParts[0];
|
||||
var parameterList = methodParts[1];
|
||||
var parameters = parameterList.Split(separatorArray, StringSplitOptions.RemoveEmptyEntries)
|
||||
.Select(p => p.Trim())
|
||||
.ToArray();
|
||||
|
||||
var method = target.GetType().GetMethod(methodName);
|
||||
if (method == null)
|
||||
{
|
||||
throw new ArgumentException($"Method {methodName} not found on target.");
|
||||
}
|
||||
|
||||
var parameterValues = method.GetParameters()
|
||||
.Select((p, index) => Convert.ChangeType(parameters[index], p.ParameterType))
|
||||
.ToArray();
|
||||
|
||||
return method.Invoke(target, parameterValues);
|
||||
}
|
||||
|
||||
private static object GetMember(object target, string memberPath)
|
||||
{
|
||||
var members = memberPath.Split('.');
|
||||
foreach (var member in members)
|
||||
{
|
||||
if (target == null) return null;
|
||||
|
||||
var property = target.GetType().GetProperty(member);
|
||||
if (property != null)
|
||||
{
|
||||
target = property.GetValue(target);
|
||||
}
|
||||
else
|
||||
{
|
||||
var field = target.GetType().GetField(member);
|
||||
if (field != null)
|
||||
{
|
||||
target = field.GetValue(target);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentException($"Member {member} not found on target.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return target;
|
||||
}
|
||||
|
||||
private static object SetMember(object target, string assignment)
|
||||
{
|
||||
var parts = assignment.Split(new[] { '=' }, 2);
|
||||
if (parts.Length != 2)
|
||||
{
|
||||
throw new ArgumentException("Invalid assignment format.");
|
||||
}
|
||||
|
||||
var memberPath = parts[0].Trim();
|
||||
var value = parts[1].Trim();
|
||||
|
||||
var members = memberPath.Split('.');
|
||||
for (int i = 0; i < members.Length - 1; i++)
|
||||
{
|
||||
var member = members[i];
|
||||
var property = target.GetType().GetProperty(member);
|
||||
if (property != null)
|
||||
{
|
||||
target = property.GetValue(target);
|
||||
}
|
||||
else
|
||||
{
|
||||
var field = target.GetType().GetField(member);
|
||||
if (field != null)
|
||||
{
|
||||
target = field.GetValue(target);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentException($"Member {member} not found on target.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var lastMember = members.Last();
|
||||
var lastProperty = target.GetType().GetProperty(lastMember);
|
||||
if (lastProperty != null)
|
||||
{
|
||||
var convertedValue = Convert.ChangeType(value, lastProperty.PropertyType);
|
||||
lastProperty.SetValue(target, convertedValue);
|
||||
}
|
||||
else
|
||||
{
|
||||
var lastField = target.GetType().GetField(lastMember);
|
||||
if (lastField != null)
|
||||
{
|
||||
var convertedValue = Convert.ChangeType(value, lastField.FieldType);
|
||||
lastField.SetValue(target, convertedValue);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentException($"Member {lastMember} not found on target.");
|
||||
}
|
||||
}
|
||||
|
||||
return target;
|
||||
}
|
||||
|
||||
private static double ComputedNumber(object value,string expression)
|
||||
{
|
||||
double numericValue = Convert.ToDouble(value);
|
||||
if (!string.IsNullOrEmpty(expression))
|
||||
{
|
||||
numericValue = SerinArithmeticExpressionEvaluator.Evaluate(expression, numericValue);
|
||||
}
|
||||
|
||||
return numericValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
186
Library/DynamicFlow/Tool/DelegateGenerator.cs
Normal file
186
Library/DynamicFlow/Tool/DelegateGenerator.cs
Normal file
@@ -0,0 +1,186 @@
|
||||
using Serein;
|
||||
using Serein.DynamicFlow;
|
||||
using Serein.DynamicFlow.NodeModel;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Diagnostics;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Serein.DynamicFlow.Tool;
|
||||
|
||||
|
||||
public static class DelegateCache
|
||||
{
|
||||
/// <summary>
|
||||
/// 委托缓存全局字典
|
||||
/// </summary>
|
||||
public static ConcurrentDictionary<string, Delegate> GlobalDicDelegates { get; } = new ConcurrentDictionary<string, Delegate>();
|
||||
}
|
||||
|
||||
public static class DelegateGenerator
|
||||
{
|
||||
// 缓存的实例对象(键:类型名称)
|
||||
public static ConcurrentDictionary<string, object> DynamicInstanceToType { get; } = new ConcurrentDictionary<string, object>();
|
||||
// 缓存的实例对象 (键:生成的方法名称)
|
||||
// public static ConcurrentDictionary<string, object> DynamicInstance { get; } = new ConcurrentDictionary<string, object>();
|
||||
|
||||
/// <summary>
|
||||
/// 生成方法信息
|
||||
/// </summary>
|
||||
/// <param name="serviceContainer"></param>
|
||||
/// <param name="type"></param>
|
||||
/// <returns></returns>
|
||||
public static ConcurrentDictionary<string, MethodDetails> GenerateMethodDetails(IServiceContainer serviceContainer, Type type)
|
||||
{
|
||||
var methodDetailsDictionary = new ConcurrentDictionary<string, MethodDetails>();
|
||||
var assemblyName = type.Assembly.GetName().Name;
|
||||
var methods = GetMethodsToProcess(type);
|
||||
|
||||
foreach (var method in methods)
|
||||
{
|
||||
var methodDetails = CreateMethodDetails(serviceContainer, type, method, assemblyName);
|
||||
methodDetailsDictionary.TryAdd(methodDetails.MethodName, methodDetails);
|
||||
}
|
||||
|
||||
return methodDetailsDictionary;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取处理方法
|
||||
/// </summary>
|
||||
private static IEnumerable<MethodInfo> GetMethodsToProcess(Type type)
|
||||
{
|
||||
return type.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
|
||||
.Where(m => m.GetCustomAttribute<MethodDetailAttribute>()?.Scan == true);
|
||||
}
|
||||
/// <summary>
|
||||
/// 创建方法信息
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private static MethodDetails CreateMethodDetails(IServiceContainer serviceContainer, Type type, MethodInfo method, string assemblyName)
|
||||
{
|
||||
var methodName = method.Name;
|
||||
var attribute = method.GetCustomAttribute<MethodDetailAttribute>();
|
||||
|
||||
var explicitDataOfParameters = GetExplicitDataOfParameters(method.GetParameters());
|
||||
// 生成委托
|
||||
var methodDelegate = GenerateMethodDelegate(type, // 方法所在的对象类型
|
||||
method, // 方法信息
|
||||
method.GetParameters(),// 方法参数
|
||||
method.ReturnType);// 返回值
|
||||
|
||||
|
||||
var dllTypeName = $"{assemblyName}.{type.Name}";
|
||||
serviceContainer.Register(type);
|
||||
object instance = serviceContainer.GetOrCreateServiceInstance(type);
|
||||
var dllTypeMethodName = $"{assemblyName}.{type.Name}.{method.Name}";
|
||||
|
||||
|
||||
return new MethodDetails
|
||||
{
|
||||
ActingInstanceType = type,
|
||||
ActingInstance = instance,
|
||||
MethodName = dllTypeMethodName,
|
||||
MethodDelegate = methodDelegate,
|
||||
MethodDynamicType = attribute.MethodDynamicType,
|
||||
MethodLockName = attribute.LockName,
|
||||
MethodTips = attribute.MethodTips,
|
||||
ExplicitDatas = explicitDataOfParameters,
|
||||
ReturnType = method.ReturnType,
|
||||
};
|
||||
}
|
||||
|
||||
private static ExplicitData[] GetExplicitDataOfParameters(ParameterInfo[] parameters)
|
||||
{
|
||||
|
||||
return parameters.Select((it, index) =>
|
||||
{
|
||||
//Console.WriteLine($"{it.Name}-{it.HasDefaultValue}-{it.DefaultValue}");
|
||||
string explicitTypeName = GetExplicitTypeName(it.ParameterType);
|
||||
var items = GetExplicitItems(it.ParameterType, explicitTypeName);
|
||||
if ("Bool".Equals(explicitTypeName)) explicitTypeName = "Select"; // 布尔值 转为 可选类型
|
||||
return new ExplicitData
|
||||
{
|
||||
IsExplicitData = it.GetCustomAttribute(typeof(ExplicitAttribute)) is ExplicitAttribute,
|
||||
Index = index,
|
||||
ExplicitType = it.ParameterType,
|
||||
ExplicitTypeName = explicitTypeName,
|
||||
DataType = it.ParameterType,
|
||||
ParameterName = it.Name,
|
||||
DataValue = it.HasDefaultValue ? it.DefaultValue.ToString() : "",
|
||||
Items = items.ToArray(),
|
||||
};
|
||||
}).ToArray();
|
||||
}
|
||||
|
||||
private static string GetExplicitTypeName(Type type)
|
||||
{
|
||||
return type switch
|
||||
{
|
||||
Type t when t.IsEnum => "Select",
|
||||
Type t when t == typeof(bool) => "Bool",
|
||||
Type t when t == typeof(string) => "Value",
|
||||
Type t when t == typeof(int) => "Value",
|
||||
Type t when t == typeof(double) => "Value",
|
||||
_ => "Value"
|
||||
};
|
||||
}
|
||||
|
||||
private static IEnumerable<string> GetExplicitItems(Type type, string explicitTypeName)
|
||||
{
|
||||
return explicitTypeName switch
|
||||
{
|
||||
"Select" => Enum.GetNames(type),
|
||||
"Bool" => ["True", "False"],
|
||||
_ => []
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
private static Delegate GenerateMethodDelegate(Type type, MethodInfo methodInfo, ParameterInfo[] parameters, Type returnType)
|
||||
{
|
||||
var parameterTypes = parameters.Select(p => p.ParameterType).ToArray();
|
||||
var parameterCount = parameters.Length;
|
||||
|
||||
if (returnType == typeof(void))
|
||||
{
|
||||
if (parameterCount == 0)
|
||||
{
|
||||
// 无返回值,无参数
|
||||
return ExpressionHelper.MethodCaller(type, methodInfo);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 无返回值,有参数
|
||||
return ExpressionHelper.MethodCaller(type, methodInfo, parameterTypes);
|
||||
}
|
||||
}
|
||||
else if (returnType == typeof(Task<FlipflopContext>)) // 触发器
|
||||
{
|
||||
if (parameterCount == 0)
|
||||
{
|
||||
// 有返回值,无参数
|
||||
return ExpressionHelper.MethodCallerAsync(type, methodInfo);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 有返回值,有参数
|
||||
return ExpressionHelper.MethodCallerAsync(type, methodInfo, parameterTypes);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (parameterCount == 0)
|
||||
{
|
||||
// 有返回值,无参数
|
||||
return ExpressionHelper.MethodCallerHaveResult(type, methodInfo);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 有返回值,有参数
|
||||
return ExpressionHelper.MethodCallerHaveResult(type, methodInfo, parameterTypes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
203
Library/DynamicFlow/Tool/DynamicTool.cs
Normal file
203
Library/DynamicFlow/Tool/DynamicTool.cs
Normal file
@@ -0,0 +1,203 @@
|
||||
using Serein;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing.Printing;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Serein.DynamicFlow.Tool
|
||||
{
|
||||
|
||||
#region 锁、tsk工具 (已注释)
|
||||
/*public class LockManager
|
||||
{
|
||||
private readonly ConcurrentDictionary<string, LockQueue> _locks = new ConcurrentDictionary<string, LockQueue>();
|
||||
|
||||
public void CreateLock(string name)
|
||||
{
|
||||
_locks.TryAdd(name, new LockQueue());
|
||||
}
|
||||
|
||||
public async Task AcquireLockAsync(string name, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (!_locks.ContainsKey(name))
|
||||
{
|
||||
throw new ArgumentException($"Lock with name '{name}' does not exist.");
|
||||
}
|
||||
|
||||
var lockQueue = _locks[name];
|
||||
var tcs = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
|
||||
lock (lockQueue.Queue)
|
||||
{
|
||||
lockQueue.Queue.Enqueue(tcs);
|
||||
if (lockQueue.Queue.Count == 1)
|
||||
{
|
||||
tcs.SetResult(true);
|
||||
}
|
||||
}
|
||||
|
||||
await tcs.Task.ConfigureAwait(false);
|
||||
|
||||
// 处理取消操作
|
||||
if (cancellationToken.CanBeCanceled)
|
||||
{
|
||||
cancellationToken.Register(() =>
|
||||
{
|
||||
lock (lockQueue.Queue)
|
||||
{
|
||||
if (lockQueue.Queue.Contains(tcs))
|
||||
{
|
||||
tcs.TrySetCanceled();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public void ReleaseLock(string name)
|
||||
{
|
||||
if (!_locks.ContainsKey(name))
|
||||
{
|
||||
throw new ArgumentException($"Lock with name '{name}' does not exist.");
|
||||
}
|
||||
|
||||
var lockQueue = _locks[name];
|
||||
|
||||
lock (lockQueue.Queue)
|
||||
{
|
||||
if (lockQueue.Queue.Count > 0)
|
||||
{
|
||||
lockQueue.Queue.Dequeue();
|
||||
|
||||
if (lockQueue.Queue.Count > 0)
|
||||
{
|
||||
var next = lockQueue.Queue.Peek();
|
||||
next.SetResult(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class LockQueue
|
||||
{
|
||||
public Queue<TaskCompletionSource<bool>> Queue { get; } = new Queue<TaskCompletionSource<bool>>();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public interface ITaskResult
|
||||
{
|
||||
object Result { get; }
|
||||
}
|
||||
|
||||
public class TaskResult<T> : ITaskResult
|
||||
{
|
||||
public TaskResult(T result)
|
||||
{
|
||||
Result = result;
|
||||
}
|
||||
|
||||
public T Result { get; }
|
||||
|
||||
object ITaskResult.Result => Result;
|
||||
}
|
||||
|
||||
public class DynamicTasks
|
||||
{
|
||||
private static readonly ConcurrentDictionary<string, Task<ITaskResult>> TaskGuidPairs = new();
|
||||
public static Task<ITaskResult> GetTask(string Guid)
|
||||
{
|
||||
TaskGuidPairs.TryGetValue(Guid, out Task<ITaskResult> task);
|
||||
return task;
|
||||
}
|
||||
|
||||
public static bool AddTask<T>(string Guid, T result)
|
||||
{
|
||||
var task = Task.FromResult<ITaskResult>(new TaskResult<T>(result));
|
||||
|
||||
return TaskGuidPairs.TryAdd(Guid, task);
|
||||
}
|
||||
}
|
||||
public class TaskNodeManager
|
||||
{
|
||||
private readonly ConcurrentDictionary<string, TaskQueue> _taskQueues = new ConcurrentDictionary<string, TaskQueue>();
|
||||
|
||||
public void CreateTaskNode(string name)
|
||||
{
|
||||
_taskQueues.TryAdd(name, new TaskQueue());
|
||||
}
|
||||
|
||||
public async Task WaitForTaskNodeAsync(string name, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (!_taskQueues.ContainsKey(name))
|
||||
{
|
||||
throw new ArgumentException($"Task node with name '{name}' does not exist.");
|
||||
}
|
||||
|
||||
var taskQueue = _taskQueues[name];
|
||||
var tcs = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
|
||||
lock (taskQueue.Queue)
|
||||
{
|
||||
taskQueue.Queue.Enqueue(tcs);
|
||||
if (taskQueue.Queue.Count == 1)
|
||||
{
|
||||
tcs.SetResult(true);
|
||||
}
|
||||
}
|
||||
|
||||
await tcs.Task.ConfigureAwait(false);
|
||||
|
||||
// 处理取消操作
|
||||
if (cancellationToken.CanBeCanceled)
|
||||
{
|
||||
cancellationToken.Register(() =>
|
||||
{
|
||||
lock (taskQueue.Queue)
|
||||
{
|
||||
if (taskQueue.Queue.Contains(tcs))
|
||||
{
|
||||
tcs.TrySetCanceled();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public void CompleteTaskNode(string name)
|
||||
{
|
||||
if (!_taskQueues.ContainsKey(name))
|
||||
{
|
||||
throw new ArgumentException($"Task node with name '{name}' does not exist.");
|
||||
}
|
||||
|
||||
var taskQueue = _taskQueues[name];
|
||||
|
||||
lock (taskQueue.Queue)
|
||||
{
|
||||
if (taskQueue.Queue.Count > 0)
|
||||
{
|
||||
taskQueue.Queue.Dequeue();
|
||||
|
||||
if (taskQueue.Queue.Count > 0)
|
||||
{
|
||||
var next = taskQueue.Queue.Peek();
|
||||
next.SetResult(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class TaskQueue
|
||||
{
|
||||
public Queue<TaskCompletionSource<bool>> Queue { get; } = new Queue<TaskCompletionSource<bool>>();
|
||||
}
|
||||
}*/
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
}
|
||||
740
Library/DynamicFlow/Tool/ExpressionHelper.cs
Normal file
740
Library/DynamicFlow/Tool/ExpressionHelper.cs
Normal file
@@ -0,0 +1,740 @@
|
||||
using System.Collections.Concurrent;
|
||||
using System.Linq.Expressions;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Serein.DynamicFlow.Tool
|
||||
{
|
||||
/// <summary>
|
||||
/// 对于实例创建的表达式树反射
|
||||
/// </summary>
|
||||
public static class ExpressionHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// 缓存表达式树反射方法
|
||||
/// </summary>
|
||||
private static ConcurrentDictionary<string, Delegate> Cache { get; } = new ConcurrentDictionary<string, Delegate>();
|
||||
|
||||
public static List<string> GetCacheKey()
|
||||
{
|
||||
return [.. Cache.Keys];
|
||||
}
|
||||
|
||||
#region 基于类型的表达式反射构建委托
|
||||
|
||||
#region 属性、字段的委托创建(表达式反射)
|
||||
|
||||
/// <summary>
|
||||
/// 动态获取属性值
|
||||
/// </summary>
|
||||
public static Delegate PropertyGetter(Type type, string propertyName)
|
||||
{
|
||||
string cacheKey = $"{type.FullName}.{propertyName}.Getter";
|
||||
return Cache.GetOrAdd(cacheKey, _ => CreateGetterDelegate(type, propertyName));
|
||||
}
|
||||
/// <summary>
|
||||
/// 动态获取属性值
|
||||
/// </summary>
|
||||
private static Delegate CreateGetterDelegate(Type type, string propertyName)
|
||||
{
|
||||
var parameter = Expression.Parameter(typeof(object), "instance");
|
||||
var property = Expression.Property(Expression.Convert(parameter, type), propertyName);
|
||||
var lambda = Expression.Lambda(Expression.Convert(property, typeof(object)), parameter);
|
||||
return lambda.Compile();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 动态设置属性值
|
||||
/// </summary>
|
||||
public static Delegate PropertySetter(Type type, string propertyName)
|
||||
{
|
||||
string cacheKey = $"{type.FullName}.{propertyName}.Setter";
|
||||
return Cache.GetOrAdd(cacheKey, _ => CreateSetterDelegate(type, propertyName));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 动态设置属性值
|
||||
/// </summary>
|
||||
private static Delegate CreateSetterDelegate(Type type, string propertyName)
|
||||
{
|
||||
var parameter = Expression.Parameter(typeof(object), "instance");
|
||||
var value = Expression.Parameter(typeof(object), "value");
|
||||
var property = Expression.Property(Expression.Convert(parameter, type), propertyName);
|
||||
var assign = Expression.Assign(property, Expression.Convert(value, property.Type));
|
||||
var lambda = Expression.Lambda(assign, parameter, value);
|
||||
return lambda.Compile();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 动态获取字段值
|
||||
/// </summary>
|
||||
public static Delegate FieldGetter(Type type, string fieldName)
|
||||
{
|
||||
string cacheKey = $"{type.FullName}.{fieldName}.FieldGetter";
|
||||
return Cache.GetOrAdd(cacheKey, _ => CreateFieldGetterDelegate(type, fieldName));
|
||||
}
|
||||
/// <summary>
|
||||
/// 动态获取字段值
|
||||
/// </summary>
|
||||
private static Delegate CreateFieldGetterDelegate(Type type, string fieldName)
|
||||
{
|
||||
var parameter = Expression.Parameter(typeof(object), "instance");
|
||||
var field = Expression.Field(Expression.Convert(parameter, type), fieldName);
|
||||
var lambda = Expression.Lambda(Expression.Convert(field, typeof(object)), parameter);
|
||||
return lambda.Compile();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 动态设置字段值
|
||||
/// </summary>
|
||||
public static Delegate FieldSetter(Type type, string fieldName)
|
||||
{
|
||||
string cacheKey = $"{type.FullName}.{fieldName}.FieldSetter";
|
||||
return Cache.GetOrAdd(cacheKey, _ => CreateFieldSetterDelegate(type, fieldName));
|
||||
}
|
||||
/// <summary>
|
||||
/// 动态设置字段值
|
||||
/// </summary>
|
||||
private static Delegate CreateFieldSetterDelegate(Type type, string fieldName)
|
||||
{
|
||||
var parameter = Expression.Parameter(typeof(object), "instance");
|
||||
var value = Expression.Parameter(typeof(object), "value");
|
||||
var field = Expression.Field(Expression.Convert(parameter, type), fieldName);
|
||||
var assign = Expression.Assign(field, Expression.Convert(value, field.Type));
|
||||
var lambda = Expression.Lambda(assign, parameter, value);
|
||||
return lambda.Compile();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 表达式树构建无参数,无返回值方法
|
||||
/// </summary>
|
||||
public static Delegate MethodCaller(Type type, MethodInfo methodInfo)
|
||||
{
|
||||
string cacheKey = $"{type.FullName}.{methodInfo.Name}.MethodCaller";
|
||||
return Cache.GetOrAdd(cacheKey, _ => CreateMethodCallerDelegate(type, methodInfo));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 表达式树构建无参数,无返回值方法
|
||||
/// </summary>
|
||||
private static Delegate CreateMethodCallerDelegate(Type type, MethodInfo methodInfo)
|
||||
{
|
||||
var parameter = Expression.Parameter(typeof(object), "instance");
|
||||
var methodCall = Expression.Call(Expression.Convert(parameter, type), methodInfo);
|
||||
var lambda = Expression.Lambda(methodCall, parameter);
|
||||
// Action<object>
|
||||
return lambda.Compile();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 表达式树构建无参数,有返回值方法
|
||||
/// </summary>
|
||||
public static Delegate MethodCallerHaveResult(Type type, MethodInfo methodInfo)
|
||||
{
|
||||
string cacheKey = $"{type.FullName}.{methodInfo.Name}.MethodCallerHaveResult";
|
||||
return Cache.GetOrAdd(cacheKey, _ => CreateMethodCallerDelegateHaveResult(type, methodInfo));
|
||||
}
|
||||
/// <summary>
|
||||
/// 表达式树构建无参数,有返回值方法
|
||||
/// </summary>
|
||||
private static Delegate CreateMethodCallerDelegateHaveResult(Type type, MethodInfo methodInfo)
|
||||
{
|
||||
var parameter = Expression.Parameter(typeof(object), "instance");
|
||||
var methodCall = Expression.Call(Expression.Convert(parameter, type), methodInfo);
|
||||
var lambda = Expression.Lambda(Expression.Convert(methodCall, typeof(object)), parameter);
|
||||
// Func<object, object>
|
||||
return lambda.Compile();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 表达式树构建多个参数,无返回值的方法
|
||||
/// </summary>
|
||||
public static Delegate MethodCaller(Type type, MethodInfo methodInfo, params Type[] parameterTypes)
|
||||
{
|
||||
string cacheKey = $"{type.FullName}.{methodInfo.Name}.MethodCaller";
|
||||
return Cache.GetOrAdd(cacheKey, _ => CreateMethodCallerDelegate(type, methodInfo, parameterTypes));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 表达式树构建多个参数,无返回值的方法
|
||||
/// </summary>
|
||||
private static Delegate CreateMethodCallerDelegate(Type type, MethodInfo methodInfo, Type[] parameterTypes)
|
||||
{
|
||||
/* var parameter = Expression.Parameter(typeof(object), "instance");
|
||||
|
||||
var arguments = parameterTypes.Select((t, i) => Expression.Parameter(typeof(object), $"arg{i}")).ToArray();
|
||||
|
||||
var convertedArguments = arguments.Select((arg, i) => Expression.Convert(arg, parameterTypes[i])).ToArray();
|
||||
var methodCall = Expression.Call(Expression.Convert(parameter, type),
|
||||
methodInfo,
|
||||
convertedArguments);
|
||||
var lambda = Expression.Lambda(methodCall, new[] { parameter }.Concat(arguments));
|
||||
var tmpAction = lambda.Compile();
|
||||
|
||||
// Action<object, object[]>
|
||||
return lambda.Compile();*/
|
||||
|
||||
var instanceParam = Expression.Parameter(typeof(object), "instance");
|
||||
var argsParam = Expression.Parameter(typeof(object[]), "args");
|
||||
|
||||
// 创建参数表达式
|
||||
var convertedArgs = parameterTypes.Select((paramType, index) =>
|
||||
Expression.Convert(Expression.ArrayIndex(argsParam, Expression.Constant(index)), paramType)
|
||||
).ToArray();
|
||||
|
||||
|
||||
// 创建方法调用表达式
|
||||
var methodCall = Expression.Call(
|
||||
Expression.Convert(instanceParam, type),
|
||||
methodInfo,
|
||||
(Expression[])convertedArgs
|
||||
);
|
||||
|
||||
// 创建 lambda 表达式
|
||||
var lambda = Expression.Lambda(
|
||||
methodCall,
|
||||
instanceParam,
|
||||
argsParam
|
||||
);
|
||||
|
||||
// Func<object, object[], object>
|
||||
return lambda.Compile();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 表达式树构建多个参数,有返回值的方法
|
||||
/// </summary>
|
||||
public static Delegate MethodCallerHaveResult(Type type, MethodInfo methodInfo, Type[] parameterTypes)
|
||||
{
|
||||
string cacheKey = $"{type.FullName}.{methodInfo.Name}.MethodCallerHaveResult";
|
||||
return Cache.GetOrAdd(cacheKey, _ => CreateMethodCallerDelegateHaveResult(type, methodInfo, parameterTypes));
|
||||
}
|
||||
/// <summary>
|
||||
/// 表达式树构建多个参数,有返回值的方法
|
||||
/// </summary>
|
||||
private static Delegate CreateMethodCallerDelegateHaveResult(Type type, MethodInfo methodInfo, Type[] parameterTypes)
|
||||
{
|
||||
/*var instanceParam = Expression.Parameter(typeof(object), "instance");
|
||||
var argsParam = Expression.Parameter(typeof(object[]), "args");
|
||||
|
||||
// 创建参数表达式
|
||||
var convertedArgs = parameterTypes.Select((paramType, index) =>
|
||||
Expression.Convert(Expression.ArrayIndex(argsParam, Expression.Constant(index)), paramType)
|
||||
).ToArray();
|
||||
|
||||
|
||||
// 创建方法调用表达式
|
||||
var methodCall = Expression.Call(
|
||||
Expression.Convert(instanceParam, type),
|
||||
methodInfo,
|
||||
convertedArgs
|
||||
);
|
||||
|
||||
// 创建 lambda 表达式
|
||||
var lambda = Expression.Lambda(
|
||||
Expression.Convert(methodCall, typeof(object)),
|
||||
instanceParam,
|
||||
argsParam
|
||||
);
|
||||
|
||||
// Func<object, object[], object>
|
||||
return lambda.Compile();*/
|
||||
|
||||
var instanceParam = Expression.Parameter(typeof(object), "instance");
|
||||
var argsParam = Expression.Parameter(typeof(object[]), "args");
|
||||
|
||||
// 创建参数表达式
|
||||
var convertedArgs = parameterTypes.Select((paramType, index) =>
|
||||
Expression.Convert(Expression.ArrayIndex(argsParam, Expression.Constant(index)), paramType)
|
||||
).ToArray();
|
||||
|
||||
|
||||
// 创建方法调用表达式
|
||||
var methodCall = Expression.Call(
|
||||
Expression.Convert(instanceParam, type),
|
||||
methodInfo,
|
||||
convertedArgs
|
||||
);
|
||||
|
||||
// 创建 lambda 表达式
|
||||
var lambda = Expression.Lambda<Func<object, object[], object>>(
|
||||
Expression.Convert(methodCall, typeof(object)),
|
||||
instanceParam,
|
||||
argsParam
|
||||
);
|
||||
//var resule = task.DynamicInvoke((object)[Activator.CreateInstance(type), [new DynamicContext(null)]]);
|
||||
return lambda.Compile();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 表达式树构建无参数,有返回值(Task<object>)的方法(触发器)
|
||||
/// </summary>
|
||||
public static Delegate MethodCallerAsync(Type type, MethodInfo methodInfo)
|
||||
{
|
||||
string cacheKey = $"{type.FullName}.{methodInfo.Name}.MethodCallerAsync";
|
||||
return Cache.GetOrAdd(cacheKey, _ => CreateMethodCallerDelegateAsync(type, methodInfo));
|
||||
}
|
||||
/// <summary>
|
||||
/// 表达式树构建无参数,有返回值(Task<object>)的方法(触发器)
|
||||
/// </summary>
|
||||
private static Delegate CreateMethodCallerDelegateAsync(Type type, MethodInfo methodInfo)
|
||||
{
|
||||
var parameter = Expression.Parameter(typeof(object), "instance");
|
||||
var methodCall = Expression.Call(Expression.Convert(parameter, type), methodInfo);
|
||||
var lambda = Expression.Lambda<Func<object, Task<object>>>(
|
||||
Expression.Convert(methodCall, typeof(Task<object>)), parameter);
|
||||
// Func<object, Task<object>>
|
||||
return lambda.Compile();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 表达式树构建多个参数,有返回值(Task-object)的方法(触发器)
|
||||
/// </summary>
|
||||
public static Delegate MethodCallerAsync(Type type, MethodInfo method, params Type[] parameterTypes)
|
||||
{
|
||||
|
||||
string cacheKey = $"{type.FullName}.{method.Name}.MethodCallerAsync";
|
||||
return Cache.GetOrAdd(cacheKey, _ => CreateMethodCallerDelegateAsync(type, method, parameterTypes));
|
||||
}
|
||||
/// <summary>
|
||||
/// 表达式树构建多个参数,有返回值(Task<object>)的方法(触发器)
|
||||
/// </summary>
|
||||
private static Delegate CreateMethodCallerDelegateAsync(Type type, MethodInfo methodInfo, Type[] parameterTypes)
|
||||
{
|
||||
var instanceParam = Expression.Parameter(typeof(object), "instance");
|
||||
var argsParam = Expression.Parameter(typeof(object[]), "args");
|
||||
|
||||
// 创建参数表达式
|
||||
var convertedArgs = parameterTypes.Select((paramType, index) =>
|
||||
Expression.Convert(Expression.ArrayIndex(argsParam, Expression.Constant(index)),paramType)
|
||||
).ToArray();
|
||||
|
||||
|
||||
// 创建方法调用表达式
|
||||
var methodCall = Expression.Call(
|
||||
Expression.Convert(instanceParam, type),
|
||||
methodInfo,
|
||||
(Expression[])convertedArgs
|
||||
);
|
||||
|
||||
// 创建 lambda 表达式
|
||||
var lambda = Expression.Lambda<Func<object, object[], Task<FlipflopContext>>>(
|
||||
Expression.Convert(methodCall, typeof(Task<FlipflopContext>)),
|
||||
instanceParam,
|
||||
argsParam
|
||||
);
|
||||
//var resule = task.DynamicInvoke((object)[Activator.CreateInstance(type), [new DynamicContext(null)]]);
|
||||
return lambda.Compile();
|
||||
}
|
||||
|
||||
|
||||
|
||||
#region 单参数
|
||||
|
||||
/// <summary>
|
||||
/// 表达式树构建单参数,无返回值的方法
|
||||
/// </summary>
|
||||
public static Delegate MethodCaller(Type type, string methodName, Type parameterType)
|
||||
{
|
||||
string cacheKey = $"{type.FullName}.{methodName}.MethodCallerWithParam";
|
||||
return Cache.GetOrAdd(cacheKey, _ => CreateMethodCallerDelegate(type, methodName, parameterType));
|
||||
}
|
||||
/// <summary>
|
||||
/// 表达式树构建单参数,无返回值的方法
|
||||
/// </summary>
|
||||
private static Delegate CreateMethodCallerDelegate(Type type, string methodName, Type parameterType)
|
||||
{
|
||||
var parameter = Expression.Parameter(typeof(object), "instance");
|
||||
var argument = Expression.Parameter(typeof(object), "argument");
|
||||
var methodCall = Expression.Call(Expression.Convert(parameter, type),
|
||||
type.GetMethod(methodName, [parameterType])!,
|
||||
Expression.Convert(argument, parameterType));
|
||||
var lambda = Expression.Lambda(methodCall, parameter, argument);
|
||||
return lambda.Compile();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 表达式树构建单参数,有返回值的方法
|
||||
/// </summary>
|
||||
public static Delegate MethodCallerWithResult(Type type, string methodName, Type parameterType, Type returnType)
|
||||
{
|
||||
string cacheKey = $"{type.FullName}.{methodName}.MethodCallerWithResult";
|
||||
return Cache.GetOrAdd(cacheKey, _ => CreateMethodCallerDelegateWithResult(type, methodName, parameterType, returnType));
|
||||
}
|
||||
/// <summary>
|
||||
/// 表达式树构建单参数,有返回值的方法
|
||||
/// </summary>
|
||||
private static Delegate CreateMethodCallerDelegateWithResult(Type type, string methodName, Type parameterType, Type returnType)
|
||||
{
|
||||
var parameter = Expression.Parameter(typeof(object), "instance");
|
||||
var argument = Expression.Parameter(typeof(object), "argument");
|
||||
var methodCall = Expression.Call(Expression.Convert(parameter, type),
|
||||
type.GetMethod(methodName, [parameterType])!,
|
||||
Expression.Convert(argument, parameterType));
|
||||
var lambda = Expression.Lambda(Expression.Convert(methodCall, typeof(object)), parameter, argument);
|
||||
|
||||
|
||||
return lambda.Compile();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region 泛型表达式反射构建方法(已注释)
|
||||
/*
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 动态获取属性值
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <typeparam name="TProperty"></typeparam>
|
||||
/// <param name="propertyName"></param>
|
||||
/// <returns></returns>
|
||||
public static Func<T, TProperty> PropertyGetter<T, TProperty>(string propertyName)
|
||||
{
|
||||
string cacheKey = $"{typeof(T).FullName}.{propertyName}.Getter";
|
||||
return (Func<T, TProperty>)Cache.GetOrAdd(cacheKey, _ => CreateGetterDelegate<T, TProperty>(propertyName));
|
||||
}
|
||||
|
||||
private static Func<T, TProperty> CreateGetterDelegate<T, TProperty>(string propertyName)
|
||||
{
|
||||
var parameter = Expression.Parameter(typeof(T), "instance");
|
||||
var property = Expression.Property(parameter, propertyName);
|
||||
var lambda = Expression.Lambda<Func<T, TProperty>>(property, parameter);
|
||||
return lambda.Compile();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 动态设置属性值
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <typeparam name="TProperty"></typeparam>
|
||||
/// <param name="propertyName"></param>
|
||||
/// <returns></returns>
|
||||
public static Action<T, TProperty> PropertySetter<T, TProperty>(string propertyName)
|
||||
{
|
||||
string cacheKey = $"{typeof(T).FullName}.{propertyName}.Setter";
|
||||
return (Action<T, TProperty>)Cache.GetOrAdd(cacheKey, _ => CreateSetterDelegate<T, TProperty>(propertyName));
|
||||
}
|
||||
|
||||
private static Action<T, TProperty> CreateSetterDelegate<T, TProperty>(string propertyName)
|
||||
{
|
||||
var parameter = Expression.Parameter(typeof(T), "instance");
|
||||
var value = Expression.Parameter(typeof(TProperty), "value");
|
||||
var property = Expression.Property(parameter, propertyName);
|
||||
var assign = Expression.Assign(property, value);
|
||||
var lambda = Expression.Lambda<Action<T, TProperty>>(assign, parameter, value);
|
||||
return lambda.Compile();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 动态获取字段值
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <typeparam name="TField"></typeparam>
|
||||
/// <param name="fieldName"></param>
|
||||
/// <returns></returns>
|
||||
public static Func<T, TField> FieldGetter<T, TField>(string fieldName)
|
||||
{
|
||||
string cacheKey = $"{typeof(T).FullName}.{fieldName}.FieldGetter";
|
||||
return (Func<T, TField>)Cache.GetOrAdd(cacheKey, _ => CreateFieldGetterDelegate<T, TField>(fieldName));
|
||||
}
|
||||
|
||||
private static Func<T, TField> CreateFieldGetterDelegate<T, TField>(string fieldName)
|
||||
{
|
||||
var parameter = Expression.Parameter(typeof(T), "instance");
|
||||
var field = Expression.Field(parameter, fieldName);
|
||||
var lambda = Expression.Lambda<Func<T, TField>>(field, parameter);
|
||||
return lambda.Compile();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 动态设置字段值
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <typeparam name="TField"></typeparam>
|
||||
/// <param name="fieldName"></param>
|
||||
/// <returns></returns>
|
||||
public static Action<T, TField> FieldSetter<T, TField>(string fieldName)
|
||||
{
|
||||
string cacheKey = $"{typeof(T).FullName}.{fieldName}.FieldSetter";
|
||||
return (Action<T, TField>)Cache.GetOrAdd(cacheKey, _ => CreateFieldSetterDelegate<T, TField>(fieldName));
|
||||
}
|
||||
|
||||
private static Action<T, TField> CreateFieldSetterDelegate<T, TField>(string fieldName)
|
||||
{
|
||||
var parameter = Expression.Parameter(typeof(T), "instance");
|
||||
var value = Expression.Parameter(typeof(TField), "value");
|
||||
var field = Expression.Field(parameter, fieldName);
|
||||
var assign = Expression.Assign(field, value);
|
||||
var lambda = Expression.Lambda<Action<T, TField>>(assign, parameter, value);
|
||||
return lambda.Compile();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 动态调用无参数方法
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="methodName"></param>
|
||||
/// <returns></returns>
|
||||
public static Action<T> MethodCaller<T>(string methodName)
|
||||
{
|
||||
string cacheKey = $"{typeof(T).FullName}.{methodName}.MethodCaller";
|
||||
return (Action<T>)Cache.GetOrAdd(cacheKey, _ => CreateMethodCallerDelegate<T>(methodName));
|
||||
}
|
||||
|
||||
private static Action<T> CreateMethodCallerDelegate<T>(string methodName)
|
||||
{
|
||||
var parameter = Expression.Parameter(typeof(T), "instance");
|
||||
var methodCall = Expression.Call(parameter, typeof(T).GetMethod(methodName));
|
||||
var lambda = Expression.Lambda<Action<T>>(methodCall, parameter);
|
||||
return lambda.Compile();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 动态调用无参有返回值方法
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <typeparam name="TResult"></typeparam>
|
||||
/// <param name="methodName"></param>
|
||||
/// <returns></returns>
|
||||
public static Func<T, TResult> MethodCallerHaveResul<T, TResult>(string methodName)
|
||||
{
|
||||
string cacheKey = $"{typeof(T).FullName}.{methodName}.MethodCaller";
|
||||
return (Func<T, TResult>)Cache.GetOrAdd(cacheKey, _ => CreateMethodCallerDelegateHaveResult<T, TResult>(methodName));
|
||||
}
|
||||
|
||||
private static Func<T, TResult> CreateMethodCallerDelegateHaveResult<T, TResult>(string methodName)
|
||||
{
|
||||
var parameter = Expression.Parameter(typeof(T), "instance");
|
||||
var methodCall = Expression.Call(parameter, typeof(T).GetMethod(methodName));
|
||||
var lambda = Expression.Lambda<Func<T, TResult>>(methodCall, parameter);
|
||||
return lambda.Compile();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 动态调用单参数无返回值的方法
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <typeparam name="TParam"></typeparam>
|
||||
/// <param name="methodName"></param>
|
||||
/// <returns></returns>
|
||||
public static Action<T, TParam> MethodCaller<T, TParam>(string methodName)
|
||||
{
|
||||
string cacheKey = $"{typeof(T).FullName}.{methodName}.MethodCallerWithParam";
|
||||
return (Action<T, TParam>)Cache.GetOrAdd(cacheKey, _ => CreateMethodCallerDelegate<T, TParam>(methodName));
|
||||
}
|
||||
|
||||
private static Action<T, TParam> CreateMethodCallerDelegate<T, TParam>(string methodName)
|
||||
{
|
||||
var parameter = Expression.Parameter(typeof(T), "instance");
|
||||
var argument = Expression.Parameter(typeof(TParam), "argument");
|
||||
var methodCall = Expression.Call(parameter, typeof(T).GetMethod(methodName), argument);
|
||||
var lambda = Expression.Lambda<Action<T, TParam>>(methodCall, parameter, argument);
|
||||
return lambda.Compile();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 动态调用单参数有返回值的方法
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <typeparam name="TParam"></typeparam>
|
||||
/// <typeparam name="TResult"></typeparam>
|
||||
/// <param name="methodName"></param>
|
||||
/// <returns></returns>
|
||||
public static Func<T, TParam, TResult> MethodCallerWithResult<T, TParam, TResult>(string methodName)
|
||||
{
|
||||
string cacheKey = $"{typeof(T).FullName}.{methodName}.MethodCallerWithResult";
|
||||
return (Func<T, TParam, TResult>)Cache.GetOrAdd(cacheKey, _ => CreateMethodCallerDelegate<T, TParam, TResult>(methodName));
|
||||
}
|
||||
|
||||
private static Func<T, TParam, TResult> CreateMethodCallerDelegate<T, TParam, TResult>(string methodName)
|
||||
{
|
||||
var parameter = Expression.Parameter(typeof(T), "instance");
|
||||
var argument = Expression.Parameter(typeof(TParam), "argument");
|
||||
var methodCall = Expression.Call(parameter, typeof(T).GetMethod(methodName), argument);
|
||||
var lambda = Expression.Lambda<Func<T, TParam, TResult>>(methodCall, parameter, argument);
|
||||
return lambda.Compile();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 动态调用多参无返回值的方法
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="methodName"></param>
|
||||
/// <param name="parameterTypes"></param>
|
||||
/// <returns></returns>
|
||||
public static Action<T, object[]> MethodCaller<T>(string methodName, params Type[] parameterTypes)
|
||||
{
|
||||
string cacheKey = $"{typeof(T).FullName}.{methodName}.MethodCaller";
|
||||
return (Action<T, object[]>)Cache.GetOrAdd(cacheKey, _ => CreateMethodCallerDelegate<T>(methodName, parameterTypes));
|
||||
}
|
||||
|
||||
private static Action<T, object[]> CreateMethodCallerDelegate<T>(string methodName, Type[] parameterTypes)
|
||||
{
|
||||
var parameter = Expression.Parameter(typeof(T), "instance");
|
||||
var arguments = parameterTypes.Select((type, index) =>
|
||||
Expression.Parameter(typeof(object), $"arg{index}")
|
||||
).ToList();
|
||||
|
||||
var convertedArguments = arguments.Select((arg, index) =>
|
||||
Expression.Convert(arg, parameterTypes[index])
|
||||
).ToList();
|
||||
|
||||
var methodInfo = typeof(T).GetMethod(methodName, parameterTypes);
|
||||
|
||||
if (methodInfo == null)
|
||||
{
|
||||
throw new ArgumentException($"Method '{methodName}' not found in type '{typeof(T).FullName}' with given parameter types.");
|
||||
}
|
||||
|
||||
var methodCall = Expression.Call(parameter, methodInfo, convertedArguments);
|
||||
var lambda = Expression.Lambda<Action<T, object[]>>(methodCall, new[] { parameter }.Concat(arguments));
|
||||
return lambda.Compile();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 动态调用多参有返回值的方法
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <typeparam name="TResult"></typeparam>
|
||||
/// <param name="methodName"></param>
|
||||
/// <param name="parameterTypes"></param>
|
||||
/// <returns></returns>
|
||||
public static Func<T, object[], TResult> MethodCallerHaveResult<T, TResult>(string methodName, Type[] parameterTypes)
|
||||
{
|
||||
string cacheKey = $"{typeof(T).FullName}.{methodName}.MethodCallerHaveResult";
|
||||
return (Func<T, object[], TResult>)Cache.GetOrAdd(cacheKey, _ => CreateMethodCallerDelegate<T, TResult>(methodName, parameterTypes));
|
||||
}
|
||||
|
||||
private static Func<T, object[], TResult> CreateMethodCallerDelegate<T, TResult>(string methodName, Type[] parameterTypes)
|
||||
{
|
||||
var instanceParam = Expression.Parameter(typeof(T), "instance");
|
||||
var argsParam = Expression.Parameter(typeof(object[]), "args");
|
||||
|
||||
var convertedArgs = new Expression[parameterTypes.Length];
|
||||
for (int i = 0; i < parameterTypes.Length; i++)
|
||||
{
|
||||
var index = Expression.Constant(i);
|
||||
var argType = parameterTypes[i];
|
||||
var arrayIndex = Expression.ArrayIndex(argsParam, index);
|
||||
var convertedArg = Expression.Convert(arrayIndex, argType);
|
||||
convertedArgs[i] = convertedArg;
|
||||
}
|
||||
|
||||
var methodInfo = typeof(T).GetMethod(methodName, parameterTypes);
|
||||
|
||||
if (methodInfo == null)
|
||||
{
|
||||
throw new ArgumentException($"Method '{methodName}' not found in type '{typeof(T).FullName}' with given parameter types.");
|
||||
}
|
||||
|
||||
var methodCall = Expression.Call(instanceParam, methodInfo, convertedArgs);
|
||||
var lambda = Expression.Lambda<Func<T, object[], TResult>>(methodCall, instanceParam, argsParam);
|
||||
return lambda.Compile();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
*/
|
||||
|
||||
#endregion
|
||||
#region 暂时不删(已注释)
|
||||
/* /// <summary>
|
||||
/// 表达式树构建多个参数,有返回值的方法
|
||||
/// </summary>
|
||||
public static Delegate MethodCallerHaveResult(Type type, string methodName, Type[] parameterTypes)
|
||||
{
|
||||
string cacheKey = $"{type.FullName}.{methodName}.MethodCallerHaveResult";
|
||||
return Cache.GetOrAdd(cacheKey, _ => CreateMethodCallerDelegateHaveResult(type, methodName, parameterTypes));
|
||||
}
|
||||
|
||||
private static Delegate CreateMethodCallerDelegateHaveResult(Type type, string methodName, Type[] parameterTypes)
|
||||
{
|
||||
var instanceParam = Expression.Parameter(typeof(object), "instance");
|
||||
var argsParam = Expression.Parameter(typeof(object[]), "args");
|
||||
var convertedArgs = parameterTypes.Select((paramType, index) =>
|
||||
Expression.Convert(Expression.ArrayIndex(argsParam, Expression.Constant(index)), paramType)
|
||||
).ToArray();
|
||||
var methodCall = Expression.Call(Expression.Convert(instanceParam, type), type.GetMethod(methodName, parameterTypes), convertedArgs);
|
||||
var lambda = Expression.Lambda(Expression.Convert(methodCall, typeof(object)), instanceParam, argsParam);
|
||||
return lambda.Compile();
|
||||
}*/
|
||||
|
||||
|
||||
|
||||
/*/// <summary>
|
||||
/// 表达式反射 构建 无返回值、无参数 的委托
|
||||
/// </summary>
|
||||
/// <param name="type"></param>
|
||||
/// <param name="methodName"></param>
|
||||
/// <param name="parameterTypes"></param>
|
||||
/// <returns></returns>
|
||||
public static Delegate MethodCaller(Type type, string methodName, Type[] parameterTypes)
|
||||
{
|
||||
string cacheKey = $"{type.FullName}.{methodName}.{string.Join(",", parameterTypes.Select(t => t.FullName))}.MethodCaller";
|
||||
return Cache.GetOrAdd(cacheKey, _ => CreateMethodCallerDelegate(type, methodName, parameterTypes));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 表达式反射 构建 无返回值、无参数 的委托
|
||||
/// </summary>
|
||||
/// <param name="type"></param>
|
||||
/// <param name="methodName"></param>
|
||||
/// <param name="parameterTypes"></param>
|
||||
/// <returns></returns>
|
||||
private static Delegate CreateMethodCallerDelegate(Type type, string methodName, Type[] parameterTypes)
|
||||
{
|
||||
var parameter = Expression.Parameter(typeof(object), "instance");
|
||||
var arguments = parameterTypes.Select((paramType, index) => Expression.Parameter(paramType, $"param{index}")).ToArray();
|
||||
var methodCall = Expression.Call(Expression.Convert(parameter, type), type.GetMethod(methodName, parameterTypes), arguments);
|
||||
|
||||
var delegateType = Expression.GetActionType(new[] { typeof(object) }.Concat(parameterTypes).ToArray());
|
||||
var lambda = Expression.Lambda(delegateType, methodCall, new[] { parameter }.Concat(arguments).ToArray());
|
||||
return lambda.Compile();
|
||||
}
|
||||
*/
|
||||
/*public static Delegate MethodCallerHaveResult(Type type, string methodName, Type returnType, Type[] parameterTypes)
|
||||
{
|
||||
string cacheKey = $"{type.FullName}.{methodName}.{string.Join(",", parameterTypes.Select(t => t.FullName))}.MethodCallerHaveResult";
|
||||
return Cache.GetOrAdd(cacheKey, _ => CreateMethodCallerDelegateHaveResult(type, methodName, returnType, parameterTypes));
|
||||
}
|
||||
|
||||
private static Delegate CreateMethodCallerDelegateHaveResult(Type type, string methodName, Type returnType, Type[] parameterTypes)
|
||||
{
|
||||
var parameter = Expression.Parameter(typeof(object), "instance");
|
||||
var arguments = parameterTypes.Select((paramType, index) => Expression.Parameter(paramType, $"param{index}")).ToArray();
|
||||
var methodCall = Expression.Call(Expression.Convert(parameter, type), type.GetMethod(methodName, parameterTypes), arguments);
|
||||
|
||||
var delegateType = Expression.GetFuncType(new[] { typeof(object) }.Concat(parameterTypes).Concat(new[] { typeof(object) }).ToArray());
|
||||
var lambda = Expression.Lambda(delegateType, Expression.Convert(methodCall, typeof(object)), new[] { parameter }.Concat(arguments).ToArray());
|
||||
return lambda.Compile();
|
||||
}
|
||||
|
||||
*/
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
94
Library/DynamicFlow/Tool/TcsSignal.cs
Normal file
94
Library/DynamicFlow/Tool/TcsSignal.cs
Normal file
@@ -0,0 +1,94 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Serein.DynamicFlow.Tool
|
||||
{
|
||||
public class TcsSignalException : Exception
|
||||
{
|
||||
public FfState FfState { get; set; }
|
||||
public TcsSignalException(string? message) : base(message)
|
||||
{
|
||||
FfState = FfState.Cancel;
|
||||
}
|
||||
}
|
||||
|
||||
public class TcsSignal<TSignal> where TSignal : struct, Enum
|
||||
{
|
||||
|
||||
public ConcurrentDictionary<TSignal, Stack<TaskCompletionSource<object>>> TcsEvent { get; } = new();
|
||||
|
||||
// public object tcsObj = new object();
|
||||
|
||||
public bool TriggerSignal<T>(TSignal signal, T state)
|
||||
{
|
||||
if (TcsEvent.TryRemove(signal, out var waitTcss))
|
||||
{
|
||||
while (waitTcss.Count > 0)
|
||||
{
|
||||
waitTcss.Pop().SetResult(state);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
lock (TcsEvent)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public TaskCompletionSource<object> CreateTcs(TSignal signal)
|
||||
{
|
||||
|
||||
var tcs = new TaskCompletionSource<object>();
|
||||
TcsEvent.GetOrAdd(signal, _ => new Stack<TaskCompletionSource<object>>()).Push(tcs);
|
||||
return tcs;
|
||||
lock (TcsEvent)
|
||||
{
|
||||
/*if(TcsEvent.TryRemove(signal, out var tcss))
|
||||
{
|
||||
//tcs.TrySetException(new TcsSignalException("试图获取已存在的任务"));
|
||||
throw new TcsSignalException("试图获取已存在的任务");
|
||||
}*/
|
||||
|
||||
|
||||
/*TcsEvent.TryAdd(signal, tcs);
|
||||
return tcs;*/
|
||||
}
|
||||
}
|
||||
//public TaskCompletionSource<object> GetOrCreateTcs(TSignal signal)
|
||||
//{
|
||||
// lock (tcsObj)
|
||||
// {
|
||||
// var tcs = TcsEvent.GetOrAdd(signal, _ => new TaskCompletionSource<object>());
|
||||
// if (tcs.Task.IsCompleted)
|
||||
// {
|
||||
// TcsEvent.TryRemove(signal, out _);
|
||||
// tcs = new TaskCompletionSource<object>();
|
||||
// TcsEvent[signal] = tcs;
|
||||
// }
|
||||
// return tcs;
|
||||
// }
|
||||
//}
|
||||
|
||||
public void CancelTask()
|
||||
{
|
||||
lock(TcsEvent)
|
||||
{
|
||||
|
||||
foreach (var tcss in TcsEvent.Values)
|
||||
{
|
||||
while (tcss.Count > 0)
|
||||
{
|
||||
tcss.Pop().SetException(new TcsSignalException("Task Cancel"));
|
||||
}
|
||||
}
|
||||
TcsEvent.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
43
Library/DynamicFlow/Tool/TypeDefinition.cs
Normal file
43
Library/DynamicFlow/Tool/TypeDefinition.cs
Normal file
@@ -0,0 +1,43 @@
|
||||
using Serein.DynamicFlow;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Reflection.Metadata.Ecma335;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Serein.DynamicFlow.Tool
|
||||
{
|
||||
|
||||
|
||||
/* /// <summary>
|
||||
/// 标记一个方法是什么类型,加载dll后用来拖拽到画布中
|
||||
/// </summary>
|
||||
[AttributeUsage( AttributeTargets.Parameter)]
|
||||
public class ObjDetailAttribute : Attribute
|
||||
{
|
||||
public bool Scan { get; set; }
|
||||
public object @object { get; }
|
||||
public DynamicNodeType MethodDynamicType { get; }
|
||||
|
||||
public ObjDetailAttribute(DynamicNodeType methodDynamicType, object tmpObject = null, bool scan = true)
|
||||
{
|
||||
@object = tmpObject;
|
||||
MethodDynamicType = methodDynamicType;
|
||||
Scan = scan;
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
|
||||
/* /// <summary>
|
||||
/// 状态接口
|
||||
/// </summary>
|
||||
public interface IState: IDynamic
|
||||
{
|
||||
/// <summary>
|
||||
/// 返回状态
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
string GetState(DynamicContext context);
|
||||
}*/
|
||||
}
|
||||
15
Library/Serein.Library.csproj
Normal file
15
Library/Serein.Library.csproj
Normal file
@@ -0,0 +1,15 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0-windows7.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<BaseOutputPath>D:\Project\C#\DynamicControl\SereinFlow\.Output</BaseOutputPath>
|
||||
<OutputType>Library</OutputType>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="SqlSugarCore" Version="5.1.4.166" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
376
Library/ServiceContainer.cs
Normal file
376
Library/ServiceContainer.cs
Normal file
@@ -0,0 +1,376 @@
|
||||
using Serein.Web;
|
||||
using Microsoft.Win32;
|
||||
using NetTaste;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Serein
|
||||
{
|
||||
|
||||
|
||||
public interface IServiceContainer
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取或创建类型的实例(不注入依赖项)
|
||||
/// </summary>
|
||||
object GetOrCreateServiceInstance(Type serviceType, params object[] parameters);
|
||||
T CreateServiceInstance<T>(params object[] parameters);
|
||||
IServiceContainer Reset(); // 清空
|
||||
IServiceContainer Register(Type type, params object[] parameters);
|
||||
IServiceContainer Register<T>(params object[] parameters);
|
||||
IServiceContainer Register<TService, TImplementation>(params object[] parameters) where TImplementation : TService;
|
||||
T Get<T>();
|
||||
object Get(Type type);
|
||||
|
||||
/// <summary>
|
||||
/// 创建目标类型的对象, 并注入依赖项
|
||||
/// </summary>
|
||||
object? Instantiate(Type type, params object[] parameters);
|
||||
IServiceContainer Build();
|
||||
IServiceContainer Run<T>(Action<T> action);
|
||||
IServiceContainer Run<T1, T2>(Action<T1, T2> action);
|
||||
IServiceContainer Run<T1, T2, T3>(Action<T1, T2, T3> action);
|
||||
IServiceContainer Run<T1, T2, T3, T4>(Action<T1, T2, T3, T4> action);
|
||||
IServiceContainer Run<T1, T2, T3, T4, T5>(Action<T1, T2, T3, T4, T5> action);
|
||||
IServiceContainer Run<T1, T2, T3, T4, T5, T6>(Action<T1, T2, T3, T4, T5, T6> action);
|
||||
IServiceContainer Run<T1, T2, T3, T4, T5, T6, T7>(Action<T1, T2, T3, T4, T5, T6, T7> action);
|
||||
IServiceContainer Run<T1, T2, T3, T4, T5, T6, T7, T8>(Action<T1, T2, T3, T4, T5, T6, T7, T8> action);
|
||||
}
|
||||
|
||||
public class ServiceContainer : IServiceContainer
|
||||
{
|
||||
|
||||
private readonly ConcurrentDictionary<string, object> _dependencies;
|
||||
private readonly ConcurrentDictionary<string, Type> _typeMappings;
|
||||
private readonly List<Type> _waitingForInstantiation;
|
||||
|
||||
public ServiceContainer()
|
||||
{
|
||||
_dependencies = new ConcurrentDictionary<string, object>
|
||||
{
|
||||
[typeof(IServiceContainer).FullName] = this
|
||||
};
|
||||
_typeMappings = new ConcurrentDictionary<string, Type>();
|
||||
_waitingForInstantiation = [];
|
||||
}
|
||||
public object GetOrCreateServiceInstance(Type type, params object[] parameters)
|
||||
{
|
||||
Register(type);
|
||||
object instance;
|
||||
if (_dependencies.ContainsKey(type.FullName))
|
||||
{
|
||||
instance = _dependencies[type.FullName];
|
||||
}
|
||||
else
|
||||
{
|
||||
instance = Activator.CreateInstance(type);
|
||||
_dependencies[type.FullName] = instance;
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
public T CreateServiceInstance<T>(params object[] parameters)
|
||||
{
|
||||
return (T)GetOrCreateServiceInstance(typeof(T), parameters);
|
||||
}
|
||||
|
||||
public IServiceContainer Reset()
|
||||
{
|
||||
foreach(var instancei in _dependencies.Values)
|
||||
{
|
||||
if (typeof(IDisposable).IsAssignableFrom(instancei.GetType()) && instancei is IDisposable disposable)
|
||||
{
|
||||
disposable.Dispose();
|
||||
}
|
||||
}
|
||||
_dependencies.Clear();
|
||||
_waitingForInstantiation.Clear();
|
||||
//_typeMappings.Clear();
|
||||
return this;
|
||||
}
|
||||
|
||||
public IServiceContainer Register(Type type, params object[] parameters)
|
||||
{
|
||||
if (!_typeMappings.ContainsKey(type.FullName))
|
||||
{
|
||||
_typeMappings[type.FullName] = type;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
public IServiceContainer Register<T>(params object[] parameters)
|
||||
{
|
||||
Register(typeof(T), parameters);
|
||||
return this;
|
||||
}
|
||||
|
||||
public IServiceContainer Register<TService, TImplementation>(params object[] parameters)
|
||||
where TImplementation : TService
|
||||
{
|
||||
_typeMappings[typeof(TService).FullName!] = typeof(TImplementation);
|
||||
return this;
|
||||
}
|
||||
|
||||
public object Get(Type type)
|
||||
{
|
||||
if (!_dependencies.TryGetValue(type.FullName, out object value))
|
||||
{
|
||||
Register(type);
|
||||
value = Instantiate(type);
|
||||
InjectDependencies(type);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
|
||||
public T Get<T>()
|
||||
{
|
||||
if(!_dependencies.TryGetValue(typeof(T).FullName, out object value))
|
||||
{
|
||||
Register<T>();
|
||||
value = Instantiate(typeof(T));
|
||||
}
|
||||
return (T)value;
|
||||
//throw new InvalidOperationException("目标类型未创建实例");
|
||||
}
|
||||
public IServiceContainer Build()
|
||||
{
|
||||
foreach (var type in _typeMappings.Values)
|
||||
{
|
||||
if(!_dependencies.ContainsKey(type.FullName))
|
||||
{
|
||||
_dependencies[type.FullName] = Activator.CreateInstance(type);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var instance in _dependencies.Values)
|
||||
{
|
||||
|
||||
InjectDependencies(instance); // 替换占位符
|
||||
}
|
||||
|
||||
//var instance = Instantiate(item.Value);
|
||||
|
||||
TryInstantiateWaitingDependencies();
|
||||
return this;
|
||||
}
|
||||
|
||||
public object? Instantiate(Type controllerType, params object[] parameters)
|
||||
{
|
||||
var instance = Activator.CreateInstance(controllerType, parameters);
|
||||
if(instance != null)
|
||||
{
|
||||
InjectDependencies(instance);
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
private void InjectDependencies(object instance)
|
||||
{
|
||||
var properties = instance.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public)
|
||||
.Where(p => p.CanWrite && p.GetCustomAttribute<AutoInjectionAttribute>() != null);
|
||||
|
||||
foreach (var property in properties)
|
||||
{
|
||||
var propertyType = property.PropertyType;
|
||||
if (_dependencies.TryGetValue(propertyType.FullName, out var dependencyInstance))
|
||||
{
|
||||
property.SetValue(instance, dependencyInstance);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void TryInstantiateWaitingDependencies()
|
||||
{
|
||||
foreach (var waitingType in _waitingForInstantiation.ToList())
|
||||
{
|
||||
if (_typeMappings.TryGetValue(waitingType.FullName!, out var implementationType))
|
||||
{
|
||||
var instance = Instantiate(implementationType);
|
||||
if (instance != null)
|
||||
{
|
||||
_dependencies[waitingType.FullName] = instance;
|
||||
_waitingForInstantiation.Remove(waitingType);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#region run()
|
||||
public IServiceContainer Run<T>(Action<T> action)
|
||||
{
|
||||
var service = Get<T>();
|
||||
if (service != null)
|
||||
{
|
||||
action(service);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public IServiceContainer Run<T1, T2>(Action<T1, T2> action)
|
||||
{
|
||||
var service1 = Get<T1>();
|
||||
var service2 = Get<T2>();
|
||||
|
||||
action(service1, service2);
|
||||
return this;
|
||||
}
|
||||
|
||||
public IServiceContainer Run<T1, T2, T3>(Action<T1, T2, T3> action)
|
||||
{
|
||||
var service1 = Get<T1>();
|
||||
var service2 = Get<T2>();
|
||||
var service3 = Get<T3>();
|
||||
action(service1, service2, service3);
|
||||
return this;
|
||||
}
|
||||
|
||||
public IServiceContainer Run<T1, T2, T3, T4>(Action<T1, T2, T3, T4> action)
|
||||
{
|
||||
var service1 = Get<T1>();
|
||||
var service2 = Get<T2>();
|
||||
var service3 = Get<T3>();
|
||||
var service4 = Get<T4>();
|
||||
action(service1, service2, service3, service4);
|
||||
return this;
|
||||
}
|
||||
|
||||
public IServiceContainer Run<T1, T2, T3, T4, T5>(Action<T1, T2, T3, T4, T5> action)
|
||||
{
|
||||
var service1 = Get<T1>();
|
||||
var service2 = Get<T2>();
|
||||
var service3 = Get<T3>();
|
||||
var service4 = Get<T4>();
|
||||
var service5 = Get<T5>();
|
||||
action(service1, service2, service3, service4, service5);
|
||||
return this;
|
||||
}
|
||||
|
||||
public IServiceContainer Run<T1, T2, T3, T4, T5, T6>(Action<T1, T2, T3, T4, T5, T6> action)
|
||||
{
|
||||
var service1 = Get<T1>();
|
||||
var service2 = Get<T2>();
|
||||
var service3 = Get<T3>();
|
||||
var service4 = Get<T4>();
|
||||
var service5 = Get<T5>();
|
||||
var service6 = Get<T6>();
|
||||
action(service1, service2, service3, service4, service5, service6);
|
||||
return this;
|
||||
}
|
||||
|
||||
public IServiceContainer Run<T1, T2, T3, T4, T5, T6, T7>(Action<T1, T2, T3, T4, T5, T6, T7> action)
|
||||
{
|
||||
var service1 = Get<T1>();
|
||||
var service2 = Get<T2>();
|
||||
var service3 = Get<T3>();
|
||||
var service4 = Get<T4>();
|
||||
var service5 = Get<T5>();
|
||||
var service6 = Get<T6>();
|
||||
var service7 = Get<T7>();
|
||||
action(service1, service2, service3, service4, service5, service6, service7);
|
||||
return this;
|
||||
}
|
||||
|
||||
public IServiceContainer Run<T1, T2, T3, T4, T5, T6, T7, T8>(Action<T1, T2, T3, T4, T5, T6, T7, T8> action)
|
||||
{
|
||||
var service1 = Get<T1>();
|
||||
var service2 = Get<T2>();
|
||||
var service3 = Get<T3>();
|
||||
var service4 = Get<T4>();
|
||||
var service5 = Get<T5>();
|
||||
var service6 = Get<T6>();
|
||||
var service7 = Get<T7>();
|
||||
var service8 = Get<T8>();
|
||||
action(service1, service2, service3, service4, service5, service6, service7, service8);
|
||||
return this;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
|
||||
/* public interface IServiceContainer
|
||||
{
|
||||
ServiceContainer Register<T>(params object[] parameters);
|
||||
ServiceContainer Register<TService, TImplementation>(params object[] parameters) where TImplementation : TService;
|
||||
TService Resolve<TService>();
|
||||
void Get<T>(Action<T> action);
|
||||
object Instantiate(Type type, params object[] parameters);
|
||||
|
||||
}
|
||||
public class ServiceContainer : IServiceContainer
|
||||
{
|
||||
private readonly Dictionary<Type, object> _dependencies;
|
||||
public ServiceContainer()
|
||||
{
|
||||
_dependencies = new Dictionary<Type, object>
|
||||
{
|
||||
[typeof(IServiceContainer)] = this
|
||||
};
|
||||
}
|
||||
|
||||
public void Get<T>(Action<T> action)
|
||||
{
|
||||
var service = Resolve<T>();
|
||||
action(service);
|
||||
}
|
||||
public ServiceContainer Register<T>(params object[] parameters)
|
||||
{
|
||||
var instance = Instantiate(typeof(T), parameters);
|
||||
_dependencies[typeof(T)] = instance;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ServiceContainer Register<TService, TImplementation>(params object[] parameters)
|
||||
where TImplementation : TService
|
||||
{
|
||||
|
||||
_dependencies[typeof(TService)] = Instantiate(typeof(TImplementation), parameters);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
public TService Resolve<TService>()
|
||||
{
|
||||
return (TService)_dependencies[typeof(TService)];
|
||||
}
|
||||
|
||||
public object Instantiate(Type controllerType, params object[] parameters)
|
||||
{
|
||||
var constructors = controllerType.GetConstructors(); // 获取控制器的所有构造函数
|
||||
|
||||
// 查找具有最多参数的构造函数
|
||||
var constructor = constructors.OrderByDescending(c => c.GetParameters().Length).FirstOrDefault();
|
||||
|
||||
if (constructor != null)
|
||||
{
|
||||
if (parameters.Length > 0)
|
||||
{
|
||||
return Activator.CreateInstance(controllerType, parameters);
|
||||
}
|
||||
else {
|
||||
var tmpParameters = constructor.GetParameters();
|
||||
var dependencyInstances = new List<object>();
|
||||
|
||||
foreach (var parameter in tmpParameters)
|
||||
{
|
||||
var parameterType = parameter.ParameterType;
|
||||
_dependencies.TryGetValue(parameterType, out var dependencyInstance);
|
||||
dependencyInstances.Add(dependencyInstance);
|
||||
if (dependencyInstance == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
// 用解析的依赖项实例化目标类型
|
||||
return Activator.CreateInstance(controllerType, dependencyInstances.ToArray());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return Activator.CreateInstance(controllerType);
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
||||
|
||||
|
||||
}
|
||||
169
Library/Tool/DataHelper.cs
Normal file
169
Library/Tool/DataHelper.cs
Normal file
@@ -0,0 +1,169 @@
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Converters;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Serein.Tool
|
||||
{
|
||||
public static class DataHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// 把Object转换为Json字符串
|
||||
/// </summary>
|
||||
/// <param name="obj"></param>
|
||||
/// <returns></returns>
|
||||
public static string ToJson(this object obj)
|
||||
{
|
||||
IsoDateTimeConverter val = new IsoDateTimeConverter();
|
||||
val.DateTimeFormat = "yyyy-MM-dd HH:mm:ss";
|
||||
IsoDateTimeConverter val2 = val;
|
||||
return JsonConvert.SerializeObject(obj, (JsonConverter[])(object)new JsonConverter[1] { (JsonConverter)val2 });
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 把Json文本转为实体
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
public static T FromJSON<T>(this string input)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (typeof(T).IsAssignableFrom(typeof(T)))
|
||||
{
|
||||
|
||||
}
|
||||
return JsonConvert.DeserializeObject<T>(input);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine(ex.Message);
|
||||
// return default(T);
|
||||
return default;
|
||||
}
|
||||
}
|
||||
|
||||
public static List<T> IListToList<T>(IList list)
|
||||
{
|
||||
T[] array = new T[list.Count];
|
||||
list.CopyTo(array, 0);
|
||||
return new List<T>(array);
|
||||
}
|
||||
|
||||
public static DataTable GetNewDataTable(DataTable dt, string condition)
|
||||
{
|
||||
if (!IsExistRows(dt))
|
||||
{
|
||||
if (condition.Trim() == "")
|
||||
{
|
||||
return dt;
|
||||
}
|
||||
|
||||
DataTable dataTable = new DataTable();
|
||||
dataTable = dt.Clone();
|
||||
DataRow[] array = dt.Select(condition);
|
||||
for (int i = 0; i < array.Length; i++)
|
||||
{
|
||||
dataTable.ImportRow(array[i]);
|
||||
}
|
||||
|
||||
return dataTable;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static bool IsExistRows(DataTable dt)
|
||||
{
|
||||
if (dt != null && dt.Rows.Count > 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static Hashtable DataTableToHashtable(DataTable dt)
|
||||
{
|
||||
Hashtable hashtable = new Hashtable();
|
||||
foreach (DataRow row in dt.Rows)
|
||||
{
|
||||
for (int i = 0; i < dt.Columns.Count; i++)
|
||||
{
|
||||
string columnName = dt.Columns[i].ColumnName;
|
||||
hashtable[columnName] = row[columnName];
|
||||
}
|
||||
}
|
||||
|
||||
return hashtable;
|
||||
}
|
||||
|
||||
public static DataTable ListToDataTable<T>(List<T> entitys)
|
||||
{
|
||||
if (entitys == null || entitys.Count < 1)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
Type type = entitys[0].GetType();
|
||||
PropertyInfo[] properties = type.GetProperties();
|
||||
DataTable dataTable = new DataTable();
|
||||
for (int i = 0; i < properties.Length; i++)
|
||||
{
|
||||
dataTable.Columns.Add(properties[i].Name);
|
||||
}
|
||||
|
||||
foreach (T entity in entitys)
|
||||
{
|
||||
object obj = entity;
|
||||
if (obj.GetType() != type)
|
||||
{
|
||||
throw new Exception("要转换的集合元素类型不一致");
|
||||
}
|
||||
|
||||
object[] array = new object[properties.Length];
|
||||
for (int j = 0; j < properties.Length; j++)
|
||||
{
|
||||
array[j] = properties[j].GetValue(obj, null);
|
||||
}
|
||||
|
||||
dataTable.Rows.Add(array);
|
||||
}
|
||||
|
||||
return dataTable;
|
||||
}
|
||||
|
||||
public static string DataTableToXML(DataTable dt)
|
||||
{
|
||||
if (dt != null && dt.Rows.Count > 0)
|
||||
{
|
||||
StringWriter stringWriter = new StringWriter();
|
||||
dt.WriteXml((TextWriter)stringWriter);
|
||||
return stringWriter.ToString();
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
public static string DataSetToXML(DataSet ds)
|
||||
{
|
||||
if (ds != null)
|
||||
{
|
||||
StringWriter stringWriter = new StringWriter();
|
||||
ds.WriteXml((TextWriter)stringWriter);
|
||||
return stringWriter.ToString();
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
107
Library/Web/Attribute.cs
Normal file
107
Library/Web/Attribute.cs
Normal file
@@ -0,0 +1,107 @@
|
||||
namespace Serein.Web
|
||||
{
|
||||
/// <summary>
|
||||
/// 表示参数为url中的数据(Get请求中不需要显式标注)
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Parameter)]
|
||||
public sealed class IsUrlDataAttribute : Attribute
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 表示入参参数为整个boby的数据
|
||||
/// <para>
|
||||
/// 例如:User类型含有int id、string name字段</para>
|
||||
/// <para>
|
||||
/// ① Add(User user)</para>
|
||||
/// <para>请求需要传入的json为
|
||||
/// {"user":{
|
||||
/// "id":2,
|
||||
/// "name":"李志忠"}}</para>
|
||||
/// <para>
|
||||
/// ② Add([Boby]User user)</para>
|
||||
/// <para>请求需要传入的json为
|
||||
/// {"id":2,"name":"李志忠"}</para>
|
||||
///
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Parameter)]
|
||||
public sealed class IsBobyDataAttribute : Attribute
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 表示该控制器会被自动注册(与程序集同一命名空间,暂时不支持运行时自动加载DLL,需要手动注册)
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class)]
|
||||
public sealed class AutoHostingAttribute(string url = "") : Attribute
|
||||
{
|
||||
public string Url { get; } = url;
|
||||
}
|
||||
/// <summary>
|
||||
/// 表示该属性为自动注入依赖项
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Property)]
|
||||
public sealed class AutoInjectionAttribute : Attribute
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 方法的接口类型与附加URL
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 假设UserController.Add()的WebAPI特性中
|
||||
/// http是HTTP.POST
|
||||
/// url被显示标明“temp”
|
||||
/// 那么请求的接口是POST,URL是
|
||||
/// [http://localhost:8080]/user/add/temp
|
||||
/// </remarks>
|
||||
/// <param name="http"></param>
|
||||
/// <param name="url"></param>
|
||||
[AttributeUsage(AttributeTargets.Method)]
|
||||
public sealed class WebApiAttribute() : Attribute
|
||||
{
|
||||
public API Type ;
|
||||
public string Url ;
|
||||
/// <summary>
|
||||
/// 方法名称不作为url的部分
|
||||
/// </summary>
|
||||
public bool IsUrl;
|
||||
}
|
||||
[AttributeUsage(AttributeTargets.Method)]
|
||||
public sealed class ApiPostAttribute() : Attribute
|
||||
{
|
||||
public string Url;
|
||||
/// <summary>
|
||||
/// 方法名称不作为url的部分
|
||||
/// </summary>
|
||||
public bool IsUrl = true;
|
||||
}
|
||||
[AttributeUsage(AttributeTargets.Method)]
|
||||
public sealed class ApiGetAttribute() : Attribute
|
||||
{
|
||||
public string Url;
|
||||
/// <summary>
|
||||
/// 方法名称不作为url的部分
|
||||
/// </summary>
|
||||
public bool IsUrl = true;
|
||||
}
|
||||
/*public sealed class WebApiAttribute(API http, bool isUrl = true, string url = "") : Attribute
|
||||
{
|
||||
public API Http { get; } = http;
|
||||
public string Url { get; } = url;
|
||||
/// <summary>
|
||||
/// 方法名称不作为url的部分
|
||||
/// </summary>
|
||||
public bool IsUrl { get; } = isUrl;
|
||||
}*/
|
||||
public enum API
|
||||
{
|
||||
POST,
|
||||
GET,
|
||||
//PUT,
|
||||
//DELETE
|
||||
}
|
||||
}
|
||||
15
Library/Web/ControllerBase.cs
Normal file
15
Library/Web/ControllerBase.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
namespace Serein.Web
|
||||
{
|
||||
public class ControllerBase
|
||||
{
|
||||
public string Url { get; set; }
|
||||
public string BobyData { get; set; }
|
||||
|
||||
public string GetLog(Exception ex)
|
||||
{
|
||||
return "Url : " + Url + Environment.NewLine +
|
||||
"Ex : " + ex.Message + Environment.NewLine +
|
||||
"Data : " + BobyData + Environment.NewLine;
|
||||
}
|
||||
}
|
||||
}
|
||||
692
Library/Web/Router.cs
Normal file
692
Library/Web/Router.cs
Normal file
@@ -0,0 +1,692 @@
|
||||
using Serein.Tool;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using System.Collections;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Net;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Web;
|
||||
using Enum = System.Enum;
|
||||
using Type = System.Type;
|
||||
|
||||
namespace Serein.Web
|
||||
{
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 路由注册与解析
|
||||
/// </summary>
|
||||
public class Router
|
||||
{
|
||||
|
||||
private readonly ConcurrentDictionary<string, bool> _controllerAutoHosting; // 存储是否实例化
|
||||
private readonly ConcurrentDictionary<string, Type> _controllerTypes; // 存储控制器类型
|
||||
private readonly ConcurrentDictionary<string, object> _controllerInstances; // 存储控制器实例对象
|
||||
private readonly ConcurrentDictionary<string, ConcurrentDictionary<string, MethodInfo>> _routes; // 用于存储路由信息
|
||||
|
||||
private readonly IServiceContainer serviceRegistry; // 用于存储路由信息
|
||||
|
||||
//private Type PostRequest;
|
||||
|
||||
public Router(IServiceContainer serviceRegistry) // 构造函数,初始化 Router 类的新实例
|
||||
{
|
||||
this.serviceRegistry = serviceRegistry;
|
||||
|
||||
_routes = new ConcurrentDictionary<string, ConcurrentDictionary<string, MethodInfo>>(); // 初始化路由字典
|
||||
|
||||
_controllerAutoHosting = new ConcurrentDictionary<string, bool>(); // 初始化控制器实例对象字典
|
||||
_controllerTypes = new ConcurrentDictionary<string, Type>(); // 初始化控制器实例对象字典
|
||||
_controllerInstances = new ConcurrentDictionary<string, object>(); // 初始化控制器实例对象字典
|
||||
|
||||
foreach (API method in Enum.GetValues(typeof(API))) // 遍历 HTTP 枚举类型的所有值
|
||||
{
|
||||
_routes.TryAdd(method.ToString(), new ConcurrentDictionary<string, MethodInfo>()); // 初始化每种 HTTP 方法对应的路由字典
|
||||
}
|
||||
|
||||
// 获取当前程序集
|
||||
Assembly assembly = Assembly.GetExecutingAssembly();
|
||||
|
||||
// 获取包含“Controller”名称的类型
|
||||
var controllerTypes = assembly.GetTypes()
|
||||
.Where(t => t.Name.Contains("Controller"));
|
||||
|
||||
Type baseAttribute = typeof(AutoHostingAttribute);
|
||||
Type baseController = typeof(ControllerBase);
|
||||
foreach (var controllerType in controllerTypes)
|
||||
{
|
||||
if (controllerType.IsSubclassOf(baseController) && controllerType.IsDefined(baseAttribute))
|
||||
{
|
||||
|
||||
// 如果属于控制器,并标记了AutoHosting特性,进行自动注册
|
||||
AutoRegisterAutoController(controllerType);
|
||||
}
|
||||
else
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 自动注册 自动实例化控制器 类型
|
||||
/// </summary>
|
||||
/// <param name="controllerType"></param>
|
||||
public void AutoRegisterAutoController(Type controllerType) // 方法声明,用于注册并实例化控制器类型
|
||||
{
|
||||
if (!controllerType.IsClass || controllerType.IsAbstract) return; // 如果不是类或者是抽象类,则直接返回
|
||||
|
||||
var autoHostingAttribute = controllerType.GetCustomAttribute<AutoHostingAttribute>();
|
||||
if (autoHostingAttribute != null) {
|
||||
foreach (var method in controllerType.GetMethods()) // 遍历控制器类型的所有方法
|
||||
{
|
||||
var apiGetAttribute = method.GetCustomAttribute<ApiGetAttribute>();
|
||||
var apiPostAttribute = method.GetCustomAttribute<ApiPostAttribute>();
|
||||
if( apiGetAttribute == null && apiPostAttribute == null )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
WebApiAttribute webApiAttribute = new WebApiAttribute()
|
||||
{
|
||||
Type = apiGetAttribute != null ? API.GET : API.POST,
|
||||
Url = apiGetAttribute != null ? apiGetAttribute.Url : apiPostAttribute.Url,
|
||||
IsUrl = apiGetAttribute != null ? apiGetAttribute.IsUrl : apiPostAttribute.IsUrl,
|
||||
};
|
||||
|
||||
if (apiPostAttribute != null) // 如果存在 WebAPIAttribute 属性
|
||||
{
|
||||
var url = AddRoutesUrl(autoHostingAttribute,
|
||||
webApiAttribute,
|
||||
controllerType, method);
|
||||
Console.WriteLine(url);
|
||||
if (url == null) continue;
|
||||
_controllerAutoHosting[url] = true;
|
||||
_controllerTypes[url] = controllerType;
|
||||
_controllerInstances[url] = null;
|
||||
}
|
||||
|
||||
|
||||
/* var routeAttribute = method.GetCustomAttribute<WebApiAttribute>(); // 获取方法上的 WebAPIAttribute 自定义属性
|
||||
if (routeAttribute != null) // 如果存在 WebAPIAttribute 属性
|
||||
{
|
||||
var url = AddRoutesUrl(autoHostingAttribute, routeAttribute, controllerType, method);
|
||||
Console.WriteLine(url);
|
||||
if (url == null) continue;
|
||||
_controllerAutoHosting[url] = true;
|
||||
_controllerTypes[url] = controllerType;
|
||||
_controllerInstances[url] = null;
|
||||
}*/
|
||||
}
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// 手动注册 自动实例化控制器实例
|
||||
/// </summary>
|
||||
public void RegisterAutoController<T>() // 方法声明,用于动态注册路由
|
||||
{
|
||||
Type controllerType = typeof(T); // 获取控制器实例的类型
|
||||
foreach (var method in controllerType.GetMethods()) // 遍历控制器类型的所有方法
|
||||
{
|
||||
var apiGetAttribute = method.GetCustomAttribute<ApiGetAttribute>();
|
||||
var apiPostAttribute = method.GetCustomAttribute<ApiPostAttribute>();
|
||||
if (apiGetAttribute == null && apiPostAttribute == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
WebApiAttribute webApiAttribute = new WebApiAttribute()
|
||||
{
|
||||
Type = apiGetAttribute != null ? API.GET : API.POST,
|
||||
Url = apiGetAttribute != null ? apiGetAttribute.Url : apiPostAttribute.Url,
|
||||
IsUrl = apiGetAttribute != null ? apiGetAttribute.IsUrl : apiPostAttribute.IsUrl,
|
||||
};
|
||||
var url = AddRoutesUrl(null, webApiAttribute, controllerType, method);
|
||||
if (url == null) continue;
|
||||
_controllerAutoHosting[url] = true;
|
||||
_controllerTypes[url] = controllerType;
|
||||
_controllerInstances[url] = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 手动注册 实例持久控制器实例
|
||||
/// </summary>
|
||||
/// <param name="controllerInstance"></param>
|
||||
public void RegisterController<T>(T controllerInstance) // 方法声明,用于动态注册路由
|
||||
{
|
||||
if(controllerInstance == null) return;
|
||||
Type controllerType = controllerInstance.GetType(); // 获取控制器实例的类型
|
||||
foreach (var method in controllerType.GetMethods()) // 遍历控制器类型的所有方法
|
||||
{
|
||||
var apiGetAttribute = method.GetCustomAttribute<ApiGetAttribute>();
|
||||
var apiPostAttribute = method.GetCustomAttribute<ApiPostAttribute>();
|
||||
if (apiGetAttribute == null && apiPostAttribute == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
WebApiAttribute webApiAttribute = new WebApiAttribute()
|
||||
{
|
||||
Type = apiGetAttribute != null ? API.GET : API.POST,
|
||||
Url = apiGetAttribute != null ? apiGetAttribute.Url : apiPostAttribute.Url,
|
||||
IsUrl = apiGetAttribute != null ? apiGetAttribute.IsUrl : apiPostAttribute.IsUrl,
|
||||
};
|
||||
var url = AddRoutesUrl(null, webApiAttribute, controllerType, method);
|
||||
if (url == null) continue;
|
||||
_controllerInstances[url] = controllerInstance;
|
||||
_controllerAutoHosting[url] = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从方法中收集路由信息
|
||||
/// </summary>
|
||||
/// <param name="controllerType"></param>
|
||||
public string AddRoutesUrl(AutoHostingAttribute autoHostingAttribute, WebApiAttribute webAttribute, Type controllerType, MethodInfo method)
|
||||
{
|
||||
string controllerName;
|
||||
if (autoHostingAttribute == null || string.IsNullOrWhiteSpace(autoHostingAttribute.Url))
|
||||
{
|
||||
controllerName = controllerType.Name.Replace("Controller", "").ToLower(); // 获取控制器名称并转换为小写
|
||||
}
|
||||
else
|
||||
{
|
||||
controllerName = autoHostingAttribute.Url;
|
||||
}
|
||||
|
||||
var httpMethod = webAttribute.Type; // 获取 HTTP 方法
|
||||
var customUrl = webAttribute.Url; // 获取自定义 URL
|
||||
|
||||
string url;
|
||||
|
||||
if (webAttribute.IsUrl)
|
||||
{
|
||||
|
||||
if (string.IsNullOrEmpty(customUrl)) // 如果自定义 URL 为空
|
||||
{
|
||||
url = $"/{controllerName}/{method.Name}".ToLower(); // 构建默认 URL
|
||||
}
|
||||
else
|
||||
{
|
||||
customUrl = CleanUrl(customUrl);
|
||||
url = $"/{controllerName}/{method.Name}/{customUrl}".ToLower();// 清理自定义 URL,并构建新的 URL
|
||||
}
|
||||
_routes[httpMethod.ToString()].TryAdd(url, method); // 将 URL 和方法添加到对应的路由字典中
|
||||
}
|
||||
else
|
||||
{
|
||||
if (string.IsNullOrEmpty(customUrl)) // 如果自定义 URL 为空
|
||||
{
|
||||
url = $"/{controllerName}".ToLower(); // 构建默认 URL
|
||||
}
|
||||
else
|
||||
{
|
||||
customUrl = CleanUrl(customUrl);
|
||||
url = $"/{controllerName}/{customUrl}".ToLower();// 清理自定义 URL,并构建新的 URL
|
||||
}
|
||||
_routes[httpMethod.ToString()].TryAdd(url, method); // 将 URL 和方法添加到对应的路由字典中
|
||||
}
|
||||
|
||||
return url;
|
||||
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 收集路由信息
|
||||
/// </summary>
|
||||
/// <param name="controllerType"></param>
|
||||
public void CollectRoutes(Type controllerType)
|
||||
{
|
||||
string controllerName = controllerType.Name.Replace("Controller", "").ToLower(); // 获取控制器名称并转换为小写
|
||||
foreach (var method in controllerType.GetMethods()) // 遍历控制器类型的所有方法
|
||||
{
|
||||
var routeAttribute = method.GetCustomAttribute<WebApiAttribute>(); // 获取方法上的 WebAPIAttribute 自定义属性
|
||||
if (routeAttribute != null) // 如果存在 WebAPIAttribute 属性
|
||||
{
|
||||
var customUrl = routeAttribute.Url; // 获取自定义 URL
|
||||
string url;
|
||||
if (string.IsNullOrEmpty(customUrl)) // 如果自定义 URL 为空
|
||||
{
|
||||
url = $"/api/{controllerName}/{method.Name}".ToLower(); // 构建默认 URL
|
||||
}
|
||||
else
|
||||
{
|
||||
customUrl = CleanUrl(customUrl);
|
||||
url = $"/api/{controllerName}/{method.Name}/{customUrl}".ToLower();// 清理自定义 URL,并构建新的 URL
|
||||
}
|
||||
var httpMethod = routeAttribute.Type; // 获取 HTTP 方法
|
||||
_routes[httpMethod.ToString()].TryAdd(url, method); // 将 URL 和方法添加到对应的路由字典中
|
||||
}
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// 解析路由,调用对应的方法
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<bool> RouteAsync(HttpListenerContext context)
|
||||
{
|
||||
var request = context.Request; // 获取请求对象
|
||||
var response = context.Response; // 获取响应对象
|
||||
var url = request.Url; // 获取请求的 URL
|
||||
var httpMethod = request.HttpMethod; // 获取请求的 HTTP 方法
|
||||
var template = request.Url.AbsolutePath.ToLower();
|
||||
if (!_routes[httpMethod].TryGetValue(template, out MethodInfo method))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var routeValues = GetUrlData(url); // 解析 URL 获取路由参数
|
||||
ControllerBase controllerInstance;
|
||||
if (!_controllerAutoHosting[template])
|
||||
{
|
||||
controllerInstance = (ControllerBase)_controllerInstances[template];
|
||||
}
|
||||
else
|
||||
{
|
||||
controllerInstance = (ControllerBase)serviceRegistry.Instantiate(_controllerTypes[template]);// 使用反射创建控制器实例
|
||||
|
||||
}
|
||||
|
||||
if (controllerInstance == null)
|
||||
{
|
||||
return false; // 未找到控制器实例
|
||||
}
|
||||
|
||||
controllerInstance.Url = url.AbsolutePath;
|
||||
object result;
|
||||
switch (httpMethod) // 根据请求的 HTTP 方法执行不同的操作
|
||||
{
|
||||
case "GET": // 如果是 GET 请求,传入方法、控制器、url参数
|
||||
result = InvokeControllerMethodWithRouteValues(method, controllerInstance, routeValues);
|
||||
break;
|
||||
case "POST": // POST 请求传入方法、控制器、请求体内容,url参数
|
||||
var requestBody = await ReadRequestBodyAsync(request); // 读取请求体内容
|
||||
controllerInstance.BobyData = requestBody;
|
||||
var requestJObject = requestBody.FromJSON<object>();
|
||||
|
||||
result = InvokeControllerMethod(method, controllerInstance, requestJObject, routeValues);
|
||||
break;
|
||||
default:
|
||||
result = null;
|
||||
break;
|
||||
}
|
||||
Return(response, result); // 返回结果
|
||||
return true;
|
||||
}
|
||||
|
||||
public static string GetLog(string Url, string BobyData = "")
|
||||
{
|
||||
return Environment.NewLine +
|
||||
"Url : " + Url + Environment.NewLine +
|
||||
"Data : " + BobyData + Environment.NewLine;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// GET请求的控制器方法
|
||||
/// </summary>
|
||||
private object InvokeControllerMethodWithRouteValues(MethodInfo method, object controllerInstance, Dictionary<string, string> routeValues)
|
||||
{
|
||||
object[] parameters = GetMethodParameters(method, routeValues);
|
||||
return InvokeMethod(method, controllerInstance, parameters);
|
||||
}
|
||||
|
||||
private static readonly Dictionary<MethodInfo, ParameterInfo[]> methodParameterCache = [];
|
||||
/// <summary>
|
||||
/// POST请求的调用控制器方法
|
||||
/// </summary>
|
||||
public object InvokeControllerMethod(MethodInfo method, object controllerInstance, dynamic requestData, Dictionary<string, string> routeValues)
|
||||
{
|
||||
object?[]? cachedMethodParameters;
|
||||
if (!methodParameterCache.TryGetValue(method, out ParameterInfo[] 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(IsUrlDataAttribute)) != null;
|
||||
bool isBobyData = parameters[i].GetCustomAttribute(typeof(IsBobyDataAttribute)) != null;
|
||||
|
||||
if (isUrlData)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(paramName) && routeValues.TryGetValue(paramName, out string? value))
|
||||
{
|
||||
cachedMethodParameters[i] = ConvertValue(value, parameters[i].ParameterType);
|
||||
}
|
||||
else
|
||||
{
|
||||
cachedMethodParameters[i] = null;
|
||||
}
|
||||
}
|
||||
else if (isBobyData)
|
||||
{
|
||||
cachedMethodParameters[i] = ConvertValue(requestData.ToString(), parameters[i].ParameterType);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (requestData.ContainsKey(paramName))
|
||||
{
|
||||
if (parameters[i].ParameterType == typeof(string))
|
||||
{
|
||||
cachedMethodParameters[i] = requestData[paramName].ToString();
|
||||
}
|
||||
else if (parameters[i].ParameterType == typeof(bool))
|
||||
{
|
||||
cachedMethodParameters[i] = requestData[paramName?.ToLower()].ToBool();
|
||||
}
|
||||
else if (parameters[i].ParameterType == typeof(int))
|
||||
{
|
||||
cachedMethodParameters[i] = requestData[paramName].ToInt();
|
||||
}
|
||||
else if (parameters[i].ParameterType == typeof(double))
|
||||
{
|
||||
cachedMethodParameters[i] = requestData[paramName].ToDouble();
|
||||
}
|
||||
else
|
||||
{
|
||||
cachedMethodParameters[i] = ConvertValue(requestData[paramName], parameters[i].ParameterType);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
cachedMethodParameters[i] = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 缓存方法和参数的映射
|
||||
//methodParameterCache[method] = cachedMethodParameters;
|
||||
|
||||
|
||||
// 调用方法
|
||||
return method.Invoke(controllerInstance, cachedMethodParameters);
|
||||
}
|
||||
|
||||
|
||||
/// <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.TryGetValue(paramName, out string? value))
|
||||
{
|
||||
parameters[i] = ConvertValue(value, 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
|
||||
{
|
||||
return JsonConvert.DeserializeObject(value.ToString(), targetType);
|
||||
}
|
||||
catch (JsonReaderException 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>
|
||||
/// <param name="value"></param>
|
||||
/// <param name="targetType"></param>
|
||||
/// <returns></returns>
|
||||
private object ConvertValue(string value, Type targetType)
|
||||
{
|
||||
if(targetType == typeof(string))
|
||||
{
|
||||
return value;
|
||||
}
|
||||
try
|
||||
{
|
||||
return JsonConvert.DeserializeObject(value.ToString(), targetType);
|
||||
}
|
||||
catch (JsonReaderException ex)
|
||||
{
|
||||
return value;
|
||||
}
|
||||
catch (JsonSerializationException ex)
|
||||
{
|
||||
// 如果无法转为对应的JSON对象
|
||||
int startIndex = ex.Message.IndexOf("to type '") + "to type '".Length; // 查找类型信息开始的索引
|
||||
int endIndex = ex.Message.IndexOf('\''); // 查找类型信息结束的索引
|
||||
var typeInfo = ex.Message[startIndex..endIndex]; // 提取出错类型信息,该怎么传出去?
|
||||
return null;
|
||||
}
|
||||
catch // (Exception ex)
|
||||
{
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 调用控制器方法传入参数
|
||||
/// </summary>
|
||||
/// <param name="method">方法</param>
|
||||
/// <param name="controllerInstance">控制器实例</param>
|
||||
/// <param name="methodParameters">参数列表</param>
|
||||
/// <returns></returns>
|
||||
private static 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('\'');
|
||||
// 提取类型信息
|
||||
string typeInfo = ex.Message[startIndex..endIndex];
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine(ex.ToString());
|
||||
}
|
||||
return result; // 调用方法并返回结果
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 方法声明,用于解析 URL 获取路由参数
|
||||
/// </summary>
|
||||
/// <param name="uri"></param>
|
||||
/// <returns></returns>
|
||||
private static Dictionary<string, string> GetUrlData(Uri uri)
|
||||
{
|
||||
Dictionary<string, string> routeValues = [];
|
||||
|
||||
var pathParts = uri.ToString().Split('?'); // 拆分 URL,获取路径部分
|
||||
|
||||
if (pathParts.Length > 1) // 如果包含查询字符串
|
||||
{
|
||||
var queryParams = HttpUtility.ParseQueryString(pathParts[1]); // 解析查询字符串
|
||||
|
||||
foreach (string key in queryParams) // 遍历查询字符串的键值对
|
||||
{
|
||||
if (key == null) continue;
|
||||
routeValues[key] = queryParams[key]; // 将键值对添加到路由参数字典中
|
||||
}
|
||||
}
|
||||
|
||||
return routeValues; // 返回路由参数字典
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 读取Body中的消息
|
||||
/// </summary>
|
||||
/// <param name="request"></param>
|
||||
/// <returns></returns>
|
||||
private static async Task<string> ReadRequestBodyAsync(HttpListenerRequest request)
|
||||
{
|
||||
using Stream stream = request.InputStream;
|
||||
using StreamReader reader = new(stream, Encoding.UTF8);
|
||||
return await reader.ReadToEndAsync();
|
||||
}
|
||||
/// <summary>
|
||||
/// 返回响应消息
|
||||
/// </summary>
|
||||
/// <param name="response"></param>
|
||||
/// <param name="msg"></param>
|
||||
private static void Return(HttpListenerResponse response, dynamic msg)
|
||||
{
|
||||
string resultData;
|
||||
if (response != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (msg is IEnumerable && msg is not string)
|
||||
{
|
||||
// If msg is a collection (e.g., array or list), serialize it as JArray
|
||||
resultData = JArray.FromObject(msg).ToString();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Otherwise, serialize it as JObject
|
||||
resultData = JObject.FromObject(msg).ToString();
|
||||
}
|
||||
byte[] buffer = Encoding.UTF8.GetBytes(resultData);
|
||||
response.ContentLength64 = buffer.Length;
|
||||
response.OutputStream.Write(buffer, 0, buffer.Length);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// If serialization fails, use the original message's string representation
|
||||
resultData = msg.ToString();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 解析JSON
|
||||
/// </summary>
|
||||
/// <param name="requestBody"></param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="Exception"></exception>
|
||||
private static dynamic ParseJson(string requestBody)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(requestBody))
|
||||
{
|
||||
throw new Exception("Invalid JSON format");
|
||||
}
|
||||
return JObject.Parse(requestBody);
|
||||
}
|
||||
catch
|
||||
{
|
||||
throw new Exception("Invalid JSON format");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 修正方法特性中的URL格式
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <returns></returns>
|
||||
private static string CleanUrl(string url)
|
||||
{
|
||||
|
||||
while (url.Length > 0 && url[0] == '/') // 去除开头的斜杠
|
||||
{
|
||||
url = url[1..];
|
||||
}
|
||||
|
||||
while (url.Length > 0 && url[^1] == '/') // 去除末尾的斜杠
|
||||
{
|
||||
url = url[..^1];
|
||||
}
|
||||
|
||||
for (int i = 0; i < url.Length - 1; i++) // 去除连续的斜杠
|
||||
{
|
||||
if (url[i] == '/' && url[i + 1] == '/')
|
||||
{
|
||||
url = url.Remove(i, 1);
|
||||
i--;
|
||||
}
|
||||
}
|
||||
|
||||
return url; // 返回清理后的 URL
|
||||
}
|
||||
/// <summary>
|
||||
/// 从控制器调用方法的异常中获取出出错类型的信息
|
||||
/// </summary>
|
||||
/// <param name="errorMessage"></param>
|
||||
/// <returns></returns>
|
||||
public static string ExtractTargetTypeFromExceptionMessage(string errorMessage)
|
||||
{
|
||||
string targetText = "为类型“";
|
||||
int startIndex = errorMessage.IndexOf(targetText);
|
||||
if (startIndex != -1)
|
||||
{
|
||||
startIndex += targetText.Length;
|
||||
int endIndex = errorMessage.IndexOf('\'');
|
||||
if (endIndex != -1)
|
||||
{
|
||||
return errorMessage[startIndex..endIndex];
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
184
Library/Web/WebAPIAttribute.cs
Normal file
184
Library/Web/WebAPIAttribute.cs
Normal file
@@ -0,0 +1,184 @@
|
||||
using System.Collections.Concurrent;
|
||||
using System.Net;
|
||||
using System.Security.AccessControl;
|
||||
|
||||
namespace Serein.Web
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// HTTP接口监听类
|
||||
/// </summary>
|
||||
public class WebServer
|
||||
{
|
||||
private readonly HttpListener listener; // HTTP 监听器
|
||||
private Router router; // 路由器
|
||||
private readonly RequestLimiter requestLimiter; //接口防刷
|
||||
|
||||
|
||||
public WebServer()
|
||||
{
|
||||
listener = new HttpListener();
|
||||
|
||||
requestLimiter = new RequestLimiter(5, 8);
|
||||
|
||||
}
|
||||
|
||||
// 启动服务器
|
||||
public WebServer Start(string prefixe, IServiceContainer serviceContainer)
|
||||
{
|
||||
try
|
||||
{
|
||||
router = new Router(serviceContainer);
|
||||
if (listener.IsListening)
|
||||
{
|
||||
return this;
|
||||
}
|
||||
|
||||
if (!prefixe.Substring(prefixe.Length - 1, 1).Equals(@"/"))
|
||||
{
|
||||
prefixe += @"/";
|
||||
}
|
||||
|
||||
|
||||
listener.Prefixes.Add(prefixe); // 添加监听前缀
|
||||
listener.Start(); // 开始监听
|
||||
|
||||
Console.WriteLine($"开始监听:{prefixe}");
|
||||
Task.Run(async () =>
|
||||
{
|
||||
while (listener.IsListening)
|
||||
{
|
||||
var context = await listener.GetContextAsync(); // 获取请求上下文
|
||||
_ = Task.Run(() => ProcessRequestAsync(context)); // 处理请求)
|
||||
}
|
||||
});
|
||||
return this;
|
||||
}
|
||||
catch (HttpListenerException ex) when (ex.ErrorCode == 183)
|
||||
{
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 处理请求
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <returns></returns>
|
||||
private async Task ProcessRequestAsync(HttpListenerContext context)
|
||||
{
|
||||
// 添加CORS头部
|
||||
context.Response.Headers.Add("Access-Control-Allow-Origin", "*");
|
||||
context.Response.Headers.Add("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
|
||||
context.Response.Headers.Add("Access-Control-Allow-Headers", "Content-Type");
|
||||
|
||||
// 处理OPTIONS预检请求
|
||||
if (context.Request.HttpMethod == "OPTIONS")
|
||||
{
|
||||
context.Response.StatusCode = (int)HttpStatusCode.OK;
|
||||
context.Response.Close();
|
||||
return;
|
||||
}
|
||||
|
||||
var isPass = await router.RouteAsync(context); // 路由解析
|
||||
if (isPass)
|
||||
{
|
||||
context.Response.StatusCode = (int)HttpStatusCode.OK;
|
||||
context.Response.Close(); // 关闭响应
|
||||
}
|
||||
else
|
||||
{
|
||||
context.Response.StatusCode = (int)HttpStatusCode.NotFound;
|
||||
context.Response.Close(); // 关闭响应
|
||||
}
|
||||
|
||||
//var isPass = requestLimiter.AllowRequest(context.Request);
|
||||
//if (isPass)
|
||||
//{
|
||||
// // 如果路由没有匹配,返回 404
|
||||
// router.RouteAsync(context); // 路由解析
|
||||
//}
|
||||
//else
|
||||
//{
|
||||
// context.Response.StatusCode = (int)HttpStatusCode.NotFound; // 返回 404 错误
|
||||
// context.Response.Close(); // 关闭响应
|
||||
//}
|
||||
|
||||
// var request = context.Request;
|
||||
// 获取远程终结点信息
|
||||
//var remoteEndPoint = context.Request.RemoteEndPoint;
|
||||
//// 获取用户的IP地址和端口
|
||||
//IPAddress ipAddress = remoteEndPoint.Address;
|
||||
//int port = remoteEndPoint.Port;
|
||||
//Console.WriteLine("外部连接:" + ipAddress.ToString() + ":" + port);
|
||||
}
|
||||
|
||||
// 停止服务器
|
||||
public void Stop()
|
||||
{
|
||||
if (listener.IsListening)
|
||||
{
|
||||
listener?.Stop(); // 停止监听
|
||||
listener?.Close(); // 关闭监听器
|
||||
}
|
||||
}
|
||||
|
||||
public void RegisterAutoController<T>()
|
||||
{
|
||||
//var instance = Activator.CreateInstance(typeof(T));
|
||||
router.RegisterAutoController<T>();
|
||||
}
|
||||
|
||||
/*public void RegisterRoute<T>(T controllerInstance)
|
||||
{
|
||||
router.RegisterRoute(controllerInstance);
|
||||
}*/
|
||||
}
|
||||
/// <summary>
|
||||
/// 判断访问接口的频次是否正常
|
||||
/// </summary>
|
||||
public class RequestLimiter(int seconds, int maxRequests)
|
||||
{
|
||||
private readonly ConcurrentDictionary<string, Queue<DateTime>> requestHistory = new ();
|
||||
private readonly TimeSpan interval = TimeSpan.FromSeconds(seconds);
|
||||
private readonly int maxRequests = maxRequests;
|
||||
|
||||
/// <summary>
|
||||
/// 判断访问接口的频次是否正常
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public bool AllowRequest(HttpListenerRequest request)
|
||||
{
|
||||
var clientIp = request.RemoteEndPoint.Address.ToString();
|
||||
var clientPort = request.RemoteEndPoint.Port;
|
||||
var clientKey = clientIp + ":" + clientPort;
|
||||
|
||||
var now = DateTime.Now;
|
||||
|
||||
// 尝试从字典中获取请求队列,不存在则创建新的队列
|
||||
var requests = requestHistory.GetOrAdd(clientKey, new Queue<DateTime>());
|
||||
|
||||
lock (requests)
|
||||
{
|
||||
// 移除超出时间间隔的请求记录
|
||||
while (requests.Count > 0 && now - requests.Peek() > interval)
|
||||
{
|
||||
requests.Dequeue();
|
||||
}
|
||||
|
||||
// 如果请求数超过限制,拒绝请求
|
||||
if (requests.Count >= maxRequests)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// 添加当前请求时间,并允许请求
|
||||
requests.Enqueue(now);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
22
MyDll/MyDll.csproj
Normal file
22
MyDll/MyDll.csproj
Normal file
@@ -0,0 +1,22 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0-windows7.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<BaseOutputPath>D:\Project\C#\DynamicControl\SereinFlow\.Output</BaseOutputPath>
|
||||
<OutputType>Library</OutputType>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Remove="bin\**" />
|
||||
<EmbeddedResource Remove="bin\**" />
|
||||
<None Remove="bin\**" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Library\Serein.Library.csproj" />
|
||||
<ProjectReference Include="..\WorkBench\Serein.WorkBench.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
247
MyDll/SampleCondition.cs
Normal file
247
MyDll/SampleCondition.cs
Normal file
@@ -0,0 +1,247 @@
|
||||
using Serein.DynamicFlow;
|
||||
using Serein.DynamicFlow.NodeModel;
|
||||
using Serein.DynamicFlow.Tool;
|
||||
using Serein.Web;
|
||||
using DynamicDemo.Node;
|
||||
using SqlSugar;
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using static MyDll.PlcDevice;
|
||||
namespace MyDll
|
||||
{
|
||||
# region Web Api 层
|
||||
public class ApiController: ControllerBase
|
||||
{
|
||||
[AutoInjection]
|
||||
public required PlcDevice PLCDevice { get; set; }
|
||||
|
||||
// example => http://127.0.0.1:8089/api/trigger?type=超宽光电信号&value=网络触发
|
||||
[ApiPost]
|
||||
public dynamic Trigger([IsUrlData] string type, [IsUrlData]string value)
|
||||
{
|
||||
if (Enum.TryParse(type, out SignalType result) && Enum.IsDefined(typeof(SignalType), result))
|
||||
{
|
||||
PLCDevice.TriggerSignal(result, value);// 通过 Web Api 模拟外部输入信号
|
||||
return new {state = "succeed" };
|
||||
}
|
||||
return new { state = "fail" };
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region 设备层
|
||||
|
||||
public class PlcDevice : TcsSignal<SignalType>
|
||||
{
|
||||
public int Count;
|
||||
public enum SignalType
|
||||
{
|
||||
光电1,
|
||||
光电2,
|
||||
光电3,
|
||||
光电4
|
||||
}
|
||||
|
||||
public void InitDevice(string ip,int port, string tips)
|
||||
{
|
||||
Write($"模拟设备初始化 :{Environment.NewLine}" +
|
||||
$" ip :{ip}{Environment.NewLine}" +
|
||||
$"port:{port}{Environment.NewLine}" +
|
||||
$"tips:{tips}{Environment.NewLine}");
|
||||
}
|
||||
|
||||
public void Write<T>(T value)
|
||||
{
|
||||
Console.WriteLine($"{value}");
|
||||
}
|
||||
public void Read<T>()
|
||||
{
|
||||
Console.WriteLine($"读取数据:... ");
|
||||
}
|
||||
public void Disconnect()
|
||||
{
|
||||
Console.WriteLine($"断开连接...");
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 逻辑控制层
|
||||
[DynamicFlow]
|
||||
public class LogicControl
|
||||
{
|
||||
[AutoInjection]
|
||||
public required PlcDevice MyPlc { get; set; }
|
||||
|
||||
|
||||
#region 初始化、初始化完成以及退出的事件
|
||||
[MethodDetail(DynamicNodeType.Init)]
|
||||
public void Init(DynamicContext context)
|
||||
{
|
||||
context.InitService<PlcDevice>();
|
||||
}
|
||||
|
||||
[MethodDetail(DynamicNodeType.Loading)]
|
||||
public void Loading(DynamicContext context)
|
||||
{
|
||||
#region 初始化Web Api、Db
|
||||
|
||||
// 初始化完成,已注入依赖项,可以开始逻辑上的操作
|
||||
/*context.ServiceContainer.Run<WebServer>((web) =>
|
||||
{
|
||||
// 启动 Web (先启动,再注册控制器)
|
||||
web.Start("http://*:8089/", context.ServiceContainer);
|
||||
web.RegisterAutoController<ApiController>();
|
||||
});*/
|
||||
|
||||
/*dynamicContext.ServiceContainer.Run<AppConfig>((config) =>
|
||||
{
|
||||
// 配置数据库连接
|
||||
var host = config.Get<string>["127.0.0.1"];
|
||||
var port = config.Get<string>[3306];
|
||||
var dbName = config.Get<string>["system"];
|
||||
var account = config.Get<int>["sa"];
|
||||
var password = config.Get<string>["123456"];
|
||||
DBSync.SecondaryConnect(SqlSugar.DbType.MySql, host, port, dbName, account, password);
|
||||
});*/
|
||||
#endregion
|
||||
|
||||
#region 模拟信号触发
|
||||
//var MainCts = context.ServiceContainer.CreateServiceInstance<NodeRunTcs>();
|
||||
//async Task action(string signalTypeName)
|
||||
//{
|
||||
// Random random = new();
|
||||
// Enum.TryParse(signalTypeName, out SignalType triggerType);
|
||||
// while (MainCts != null && !MainCts.IsCancellationRequested)
|
||||
// {
|
||||
// int waitSec = 2000;
|
||||
// await Task.Delay(waitSec);
|
||||
// MyPlc.TriggerSignal(triggerType, MyPlc.Count);
|
||||
// }
|
||||
//}
|
||||
//var tasks = typeof(SignalType).GetFields().Select(it => action(it.Name)).ToArray();
|
||||
//Task.WhenAll(tasks);
|
||||
#endregion
|
||||
|
||||
Console.WriteLine("初始化完成");
|
||||
}
|
||||
|
||||
[MethodDetail(DynamicNodeType.Exit)]
|
||||
public void Exit(DynamicContext context)
|
||||
{
|
||||
MyPlc.Disconnect();
|
||||
MyPlc.CancelTask();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 触发器
|
||||
|
||||
[MethodDetail(DynamicNodeType.Flipflop, "等待信号触发")]
|
||||
public async Task<FlipflopContext> WaitTask([Explicit] SignalType triggerType = SignalType.光电1)
|
||||
{
|
||||
/*if (!Enum.TryParse(triggerValue, out SignalType triggerType) && Enum.IsDefined(typeof(SignalType), triggerType))
|
||||
{
|
||||
return new FlipflopContext();
|
||||
}*/
|
||||
|
||||
try
|
||||
{
|
||||
//Console.WriteLine($"{Environment.NewLine}订阅信号 - {triggerValue}");
|
||||
|
||||
var tcs = MyPlc.CreateTcs(triggerType);
|
||||
var result = await tcs.Task;
|
||||
//Interlocked.Increment(ref MyPlc.Count); // 原子自增
|
||||
//Console.WriteLine($"信号触发[{triggerType}] : {MyPlc.Count}{Environment.NewLine} thread :{Thread.CurrentThread.ManagedThreadId}{Environment.NewLine}");
|
||||
return new FlipflopContext(FfState.Succeed, MyPlc.Count);
|
||||
}
|
||||
catch (TcsSignalException)
|
||||
{
|
||||
// await Console.Out.WriteLineAsync($"取消等待信号[{triggerType}]");
|
||||
return new FlipflopContext(FfState.Cancel);
|
||||
}
|
||||
}
|
||||
[MethodDetail(DynamicNodeType.Flipflop, "等待信号触发")]
|
||||
public async Task<FlipflopContext> WaitTask2([Explicit] string triggerValue = nameof(SignalType.光电1))
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!Enum.TryParse(triggerValue, out SignalType triggerType) && Enum.IsDefined(typeof(SignalType), triggerType))
|
||||
{
|
||||
throw new TcsSignalException("parameter[triggerValue] is not a value in an enumeration");
|
||||
}
|
||||
|
||||
var tcs = MyPlc.CreateTcs(triggerType);
|
||||
var result = await tcs.Task;
|
||||
Interlocked.Increment(ref MyPlc.Count); // 原子自增
|
||||
Console.WriteLine($"信号触发[{triggerType}] : {MyPlc.Count}");
|
||||
return new FlipflopContext(FfState.Succeed, MyPlc.Count);
|
||||
}
|
||||
catch(TcsSignalException ex)
|
||||
{
|
||||
// await Console.Out.WriteLineAsync($"取消等待信号[{triggerValue}]");
|
||||
return new FlipflopContext(ex.FfState);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 动作
|
||||
|
||||
[MethodDetail(DynamicNodeType.Action, "初始化")]
|
||||
public PlcDevice PlcInit([Explicit] string ip = "192.168.1.1",
|
||||
[Explicit] int port = 6688,
|
||||
[Explicit] string tips = "测试")
|
||||
{
|
||||
MyPlc.InitDevice(ip, port, tips);
|
||||
return MyPlc;
|
||||
}
|
||||
[MethodDetail(DynamicNodeType.Action, "自增")]
|
||||
public PlcDevice 自增([Explicit] int number = 1)
|
||||
{
|
||||
MyPlc.Count += number;
|
||||
return MyPlc;
|
||||
}
|
||||
|
||||
|
||||
[MethodDetail(DynamicNodeType.Action, "模拟循环触发")]
|
||||
public void 模拟循环触发(DynamicContext context,
|
||||
[Explicit] int time = 20,
|
||||
[Explicit] int count = 5,
|
||||
[Explicit] SignalType signal = SignalType.光电1)
|
||||
{
|
||||
Action action = () =>
|
||||
{
|
||||
MyPlc.TriggerSignal(signal, count);
|
||||
};
|
||||
_ = context.CreateTimingTask(action, time, count);
|
||||
}
|
||||
[MethodDetail(DynamicNodeType.Action, "重置计数")]
|
||||
public void 重置计数()
|
||||
{
|
||||
MyPlc.Count = 0;
|
||||
}
|
||||
[MethodDetail(DynamicNodeType.Action, "触发光电")]
|
||||
public void 光电1信号触发(int data)
|
||||
{
|
||||
MyPlc.Write($"信号源[光电1] - 模拟写入 : {data}{Environment.NewLine}");
|
||||
}
|
||||
|
||||
[MethodDetail(DynamicNodeType.Action, "触发光电")]
|
||||
public void 光电2信号触发(int data)
|
||||
{
|
||||
MyPlc.Write($"信号源[光电2] - 模拟写入 : {data}{Environment.NewLine}");
|
||||
}
|
||||
|
||||
[MethodDetail(DynamicNodeType.Action, "触发光电")]
|
||||
public void 光电3信号触发(int data)
|
||||
{
|
||||
MyPlc.Write($"信号源[光电3] - 模拟写入 : {data}{Environment.NewLine}");
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
#endregion
|
||||
|
||||
}
|
||||
3
README.md
Normal file
3
README.md
Normal file
@@ -0,0 +1,3 @@
|
||||
一款基于WPF(Dotnet 8)的流程可视化编辑器(需二次开发)
|
||||
B站个人空间:https://space.bilibili.com/33526379
|
||||
第一次用git,不太懂
|
||||
37
SereinFlow.sln
Normal file
37
SereinFlow.sln
Normal file
@@ -0,0 +1,37 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.9.34728.123
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MyDll", "MyDll\MyDll.csproj", "{69190D46-0B07-47CD-BC67-D542BA7F6C91}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Serein.WorkBench", "WorkBench\Serein.WorkBench.csproj", "{EC933A9F-DAD3-4D26-BF27-DA9DE5263BCD}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Serein.Library", "Library\Serein.Library.csproj", "{4A7D23E7-B05C-4B6D-A8B9-1A488DC356FD}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{69190D46-0B07-47CD-BC67-D542BA7F6C91}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{69190D46-0B07-47CD-BC67-D542BA7F6C91}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{69190D46-0B07-47CD-BC67-D542BA7F6C91}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{69190D46-0B07-47CD-BC67-D542BA7F6C91}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{EC933A9F-DAD3-4D26-BF27-DA9DE5263BCD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{EC933A9F-DAD3-4D26-BF27-DA9DE5263BCD}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{EC933A9F-DAD3-4D26-BF27-DA9DE5263BCD}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{EC933A9F-DAD3-4D26-BF27-DA9DE5263BCD}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{4A7D23E7-B05C-4B6D-A8B9-1A488DC356FD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{4A7D23E7-B05C-4B6D-A8B9-1A488DC356FD}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{4A7D23E7-B05C-4B6D-A8B9-1A488DC356FD}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{4A7D23E7-B05C-4B6D-A8B9-1A488DC356FD}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {39DB5BD0-BC17-48C3-A307-BFF1A7C7ECEB}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
16
WorkBench/App.xaml
Normal file
16
WorkBench/App.xaml
Normal file
@@ -0,0 +1,16 @@
|
||||
<Application x:Class="Serein.WorkBench.App"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="clr-namespace:Serein.WorkBench"
|
||||
StartupUri="MainWindow.xaml"
|
||||
Startup="Application_Startup">
|
||||
<Application.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<!--<ResourceDictionary Source="/Themes/ExplicitDataControl.xaml" />-->
|
||||
<ResourceDictionary Source="/Themes/MethodDetailsControl.xaml" />
|
||||
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
</Application.Resources>
|
||||
</Application>
|
||||
170
WorkBench/App.xaml.cs
Normal file
170
WorkBench/App.xaml.cs
Normal file
@@ -0,0 +1,170 @@
|
||||
using Serein;
|
||||
using Serein.DynamicFlow.SerinExpression;
|
||||
using Serein.WorkBench.Themes;
|
||||
using Newtonsoft.Json;
|
||||
using SqlSugar;
|
||||
using SqlSugar.Extensions;
|
||||
using System.Diagnostics;
|
||||
using System.Linq.Expressions;
|
||||
using System.Windows;
|
||||
|
||||
namespace Serein.WorkBench
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for App.xaml
|
||||
/// </summary>
|
||||
public partial class App : Application
|
||||
{
|
||||
public class TestObject
|
||||
{
|
||||
public NestedObject Data { get; set; }
|
||||
public class NestedObject
|
||||
{
|
||||
public int Code { get; set; }
|
||||
public int Code2 { get; set; }
|
||||
public string Tips { get; set; }
|
||||
}
|
||||
public string ToUpper(string input)
|
||||
{
|
||||
return input.ToUpper();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
public App()
|
||||
{
|
||||
|
||||
#if false //测试 操作表达式,条件表达式
|
||||
|
||||
#region 测试数据
|
||||
string expression = "";
|
||||
|
||||
var testObj = new TestObject
|
||||
{
|
||||
Data = new TestObject.NestedObject
|
||||
{
|
||||
Code = 15,
|
||||
Code2 = 20,
|
||||
Tips = "测试数据"
|
||||
}
|
||||
};
|
||||
|
||||
#endregion
|
||||
#region 对象操作表达式
|
||||
// 获取对象成员
|
||||
var result = SerinExpressionEvaluator.Evaluate("get .Data.Code", testObj);
|
||||
Debug.WriteLine(result); // 15
|
||||
|
||||
// 设置对象成员
|
||||
SerinExpressionEvaluator.Evaluate("set .Data.Code = 20", testObj);
|
||||
Debug.WriteLine(testObj.Data.Code); // 20
|
||||
|
||||
SerinExpressionEvaluator.Evaluate("set .Data.Tips = 123", testObj);
|
||||
// 调用对象方法
|
||||
result = SerinExpressionEvaluator.Evaluate($"invoke .ToUpper({SerinExpressionEvaluator.Evaluate("get .Data.Tips", testObj)})", testObj);
|
||||
Debug.WriteLine(result); // HELLO
|
||||
|
||||
expression = "@number (@+1)/100";
|
||||
result = SerinExpressionEvaluator.Evaluate(expression, 2);
|
||||
Debug.WriteLine($"{expression} -> {result}"); // HELLO
|
||||
#endregion
|
||||
#region 条件表达式
|
||||
|
||||
expression = ".Data.Code == 15";
|
||||
var pass = SerinConditionParser.To(testObj, expression);
|
||||
Debug.WriteLine($"{expression} -> " + pass);
|
||||
|
||||
expression = ".Data.Code<int>[@*2] == 31";
|
||||
//expression = ".Data.Tips<string> contains 数据";
|
||||
pass = SerinConditionParser.To(testObj, expression);
|
||||
Debug.WriteLine($"{expression} -> " + pass);
|
||||
|
||||
expression = ".Data.Code<int> < 20";
|
||||
pass = SerinConditionParser.To(testObj, expression);
|
||||
Debug.WriteLine($"{expression} -> " + pass);
|
||||
|
||||
|
||||
|
||||
int i = 43;
|
||||
|
||||
expression = "in 11-22";
|
||||
pass = SerinConditionParser.To(i, expression);
|
||||
Debug.WriteLine($"{i} {expression} -> " + pass);
|
||||
|
||||
expression = "== 43";
|
||||
pass = SerinConditionParser.To(i, expression);
|
||||
Debug.WriteLine($"{i} {expression} -> " + pass);
|
||||
|
||||
string str = "MY NAME IS COOOOL";
|
||||
expression = "c NAME";
|
||||
pass = SerinConditionParser.To(str, expression);
|
||||
Debug.WriteLine($"{str} {expression} -> " + pass);
|
||||
|
||||
#endregion
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
protected override void OnExit(ExitEventArgs e)
|
||||
{
|
||||
base.OnExit/**/(e);
|
||||
|
||||
// 强制关闭所有窗口
|
||||
foreach (Window window in Windows)
|
||||
{
|
||||
window.Close();
|
||||
}
|
||||
}
|
||||
|
||||
public static SereinOutputFileData? FData;
|
||||
public static string FileDataPath = "";
|
||||
private void Application_Startup(object sender, StartupEventArgs e)
|
||||
{
|
||||
|
||||
// 示例:写入调试信息
|
||||
Debug.WriteLine("应用程序启动");
|
||||
|
||||
// 检查是否传入了参数
|
||||
if (e.Args.Length == 1)
|
||||
{
|
||||
// 获取文件路径
|
||||
string filePath = e.Args[0];
|
||||
// 检查文件是否存在
|
||||
if (!System.IO.File.Exists(filePath))
|
||||
{
|
||||
MessageBox.Show($"文件未找到:{filePath}");
|
||||
Shutdown(); // 关闭应用程序
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// 读取文件内容
|
||||
string content = System.IO.File.ReadAllText(filePath); // 读取整个文件内容
|
||||
FData = JsonConvert.DeserializeObject<SereinOutputFileData>(content);
|
||||
FileDataPath = System.IO.Path.GetDirectoryName(filePath) ?? "";
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
MessageBox.Show($"读取文件时发生错误:{ex.Message}");
|
||||
Shutdown(); // 关闭应用程序
|
||||
}
|
||||
}
|
||||
else if(1 == 11)
|
||||
{
|
||||
string filePath =
|
||||
@"D:\Project\C#\DynamicControl\DynamicControl\DynamicDemo\bin\Debug\net8.0-windows7.0\project 233.dnf";
|
||||
//@"D:\Project\C#\DynamicControl\DynamicControl\DynamicDemo\bin\Debug\net8.0-windows7.0\demo project\node project.dnf";
|
||||
string content = System.IO.File.ReadAllText(filePath); // 读取整个文件内容
|
||||
FData = JsonConvert.DeserializeObject<SereinOutputFileData>(content);
|
||||
App.FileDataPath = System.IO.Path.GetDirectoryName(filePath);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
10
WorkBench/AssemblyInfo.cs
Normal file
10
WorkBench/AssemblyInfo.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
using System.Windows;
|
||||
|
||||
[assembly: ThemeInfo(
|
||||
ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
|
||||
//(used if a resource is not found in the page,
|
||||
// or application resource dictionaries)
|
||||
ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
|
||||
//(used if a resource is not found in the page,
|
||||
// app, or any theme specific resource dictionaries)
|
||||
)]
|
||||
22
WorkBench/LogWindow.xaml
Normal file
22
WorkBench/LogWindow.xaml
Normal file
@@ -0,0 +1,22 @@
|
||||
<Window x:Class="Serein.WorkBench.LogWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:local="clr-namespace:Serein.WorkBench"
|
||||
mc:Ignorable="d"
|
||||
Title="LogWindow" Height="600" Width="400"
|
||||
Closing="Window_Closing">
|
||||
<Grid>
|
||||
<TextBox x:Name="LogTextBox"
|
||||
VerticalScrollBarVisibility="Auto"
|
||||
HorizontalScrollBarVisibility="Auto"
|
||||
IsReadOnly="True"
|
||||
TextWrapping="Wrap"/>
|
||||
<Grid.ContextMenu>
|
||||
<ContextMenu>
|
||||
<MenuItem Header="Clear Log" Click="ClearLog_Click"/>
|
||||
</ContextMenu>
|
||||
</Grid.ContextMenu>
|
||||
</Grid>
|
||||
</Window>
|
||||
33
WorkBench/LogWindow.xaml.cs
Normal file
33
WorkBench/LogWindow.xaml.cs
Normal file
@@ -0,0 +1,33 @@
|
||||
using System.Windows;
|
||||
|
||||
namespace Serein.WorkBench
|
||||
{
|
||||
/// <summary>
|
||||
/// DebugWindow.xaml 的交互逻辑
|
||||
/// </summary>
|
||||
public partial class LogWindow : Window
|
||||
{
|
||||
public LogWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
public void AppendText(string text)
|
||||
{
|
||||
Dispatcher.BeginInvoke(() =>
|
||||
{
|
||||
LogTextBox.AppendText(text);
|
||||
LogTextBox.ScrollToEnd();
|
||||
});
|
||||
}
|
||||
private void ClearLog_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
LogTextBox.Clear();
|
||||
}
|
||||
|
||||
private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
|
||||
{
|
||||
e.Cancel = true; // 取消关闭操作
|
||||
this.Hide(); // 隐藏窗体而不是关闭
|
||||
}
|
||||
}
|
||||
}
|
||||
92
WorkBench/MainWindow.xaml
Normal file
92
WorkBench/MainWindow.xaml
Normal file
@@ -0,0 +1,92 @@
|
||||
<Window x:Class="Serein.WorkBench.MainWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="clr-namespace:Serein.WorkBench"
|
||||
xmlns:custom="clr-namespace:Serein.WorkBench.Node.View"
|
||||
Title="Dynamic Node Flow" Height="700" Width="1200"
|
||||
AllowDrop="True" Drop="Window_Drop" DragOver="Window_DragOver"
|
||||
Loaded="Window_Loaded"
|
||||
Closing="Window_Closing">
|
||||
|
||||
<Window.InputBindings>
|
||||
<KeyBinding Key="Escape" Command="{Binding CancelConnectionCommand}"/>
|
||||
</Window.InputBindings>
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="1*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="3*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<DockPanel Grid.Column="0" Background="#F5F5F5">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="40"></RowDefinition>
|
||||
<RowDefinition Height="Auto"></RowDefinition>
|
||||
<RowDefinition Height="2*"></RowDefinition>
|
||||
<RowDefinition Height="*"></RowDefinition>
|
||||
</Grid.RowDefinitions>
|
||||
<!--<Button Grid.Row="0" Content="Button 1"></Button>
|
||||
<Button Grid.Row="1" Content="Button 2"></Button>
|
||||
<Button Grid.Row="2" Content="Button 3"></Button>
|
||||
<Button Grid.Row="3" Content="Button 4"></Button>-->
|
||||
<!--DockPanel.Dock="Top"-->
|
||||
|
||||
<Grid Margin="2,2,2,5">
|
||||
<Button Grid.Row="0" Content="保存项目" Click="ButtonSaveFile_Click" HorizontalAlignment="Left" Margin="5,5,5,5"/>
|
||||
<Button Grid.Row="0" Content="卸载清空" Click="UnloadAllButton_Click" HorizontalAlignment="Right" Margin="5,5,5,5"/>
|
||||
</Grid>
|
||||
|
||||
<ScrollViewer Grid.Row="1" HorizontalScrollBarVisibility="Auto">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<!--<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>-->
|
||||
|
||||
<custom:ExpOpNodeControl x:Name="ExpOpNodeControl" Margin="10" AllowDrop="True" Drop="ConditionRegionControl_Drop" PreviewMouseMove="BaseNodeControl_PreviewMouseMove"/>
|
||||
<custom:ConditionNodeControl x:Name="ConditionNodeControl" Margin="10" AllowDrop="True" Drop="ConditionNodeControl_Drop" PreviewMouseMove="BaseNodeControl_PreviewMouseMove"/>
|
||||
<custom:ConditionRegionControl x:Name="ConditionRegionControl" Margin="10" AllowDrop="True" Drop="ConditionRegionControl_Drop" PreviewMouseMove="RegionControl_PreviewMouseMove"/>
|
||||
<!--<custom:ActionRegionControl x:Name="ActionRegionControl" Grid.Column="1" Margin="10" AllowDrop="True" Drop="ActionRegionControl_Drop" PreviewMouseMove="RegionControl_PreviewMouseMove"/>-->
|
||||
<!--<TextBlock Text="触发器" Grid.Column="2"/>-->
|
||||
<!--<custom:StateRegionControl x:Name="StateRegionControl" Grid.Column="2" Margin="10" AllowDrop="True" Drop="StateRegionControl_Drop" PreviewMouseMove="RegionControl_PreviewMouseMove"/>-->
|
||||
<!--</Grid>-->
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
<ScrollViewer Grid.Row="2" VerticalScrollBarVisibility="Auto">
|
||||
<StackPanel x:Name="DllStackPanel" Margin="5"/>
|
||||
</ScrollViewer>
|
||||
</Grid>
|
||||
</DockPanel>
|
||||
|
||||
|
||||
<GridSplitter Grid.Column="1" Width="5" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" ResizeBehavior="PreviousAndNext" Background="Gray"/>
|
||||
|
||||
<Grid Grid.Column="2" >
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="60"></RowDefinition>
|
||||
<RowDefinition Height="*"></RowDefinition>
|
||||
</Grid.RowDefinitions>
|
||||
<StackPanel Grid.Row="0" Background="#F5F5F5" Orientation="Horizontal" >
|
||||
<Button x:Name="ButtonDebugRun" Content="运行" Width="100" Margin="10" Click="ButtonDebugRun_Click"></Button>
|
||||
<Button x:Name="ButtonDebugFlipflopNode" Content="停止" Width="100" Margin="10" Click="ButtonDebugFlipflopNode_Click"></Button>
|
||||
</StackPanel>
|
||||
|
||||
<ScrollViewer Grid.Row="1"
|
||||
x:Name="FlowChartScrollViewer"
|
||||
HorizontalScrollBarVisibility="Auto"
|
||||
VerticalScrollBarVisibility="Auto"
|
||||
SizeChanged="FlowChartScrollViewer_SizeChanged">
|
||||
<Canvas x:Name="FlowChartCanvas"
|
||||
Background="#F2EEE8"
|
||||
Width="1000" Height="1000"
|
||||
AllowDrop="True"
|
||||
MouseLeftButtonDown="FlowChartCanvas_MouseLeftButtonDown"
|
||||
Drop="FlowChartCanvas_Drop"
|
||||
DragOver="FlowChartCanvas_DragOver"/>
|
||||
</ScrollViewer>
|
||||
</Grid>
|
||||
|
||||
</Grid>
|
||||
</Window>
|
||||
2031
WorkBench/MainWindow.xaml.cs
Normal file
2031
WorkBench/MainWindow.xaml.cs
Normal file
File diff suppressed because it is too large
Load Diff
270
WorkBench/Node/NodeBase.cs
Normal file
270
WorkBench/Node/NodeBase.cs
Normal file
@@ -0,0 +1,270 @@
|
||||
namespace DySerin;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/*public class ConditionNode : NodeBase, ICondition
|
||||
{
|
||||
private Func<bool> ConditionFunc { get; set; }
|
||||
|
||||
public ConditionNode(Func<bool> conditionFunc)
|
||||
{
|
||||
ConditionFunc = conditionFunc;
|
||||
}
|
||||
|
||||
public override void Execute()
|
||||
{
|
||||
if (ConditionFunc())
|
||||
{
|
||||
EnterTrueBranch();
|
||||
}
|
||||
else
|
||||
{
|
||||
EnterFalseBranch();
|
||||
}
|
||||
}
|
||||
|
||||
// 实现条件接口的方法
|
||||
public bool Evaluate(DynamicContext context)
|
||||
{
|
||||
Context = context; // 更新节点的上下文
|
||||
return ConditionFunc();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
public class ActionNode : NodeBase, IAction
|
||||
{
|
||||
private Action ActionMethod { get; set; }
|
||||
|
||||
public ActionNode(Action actionMethod)
|
||||
{
|
||||
ActionMethod = actionMethod;
|
||||
}
|
||||
|
||||
public override void Execute()
|
||||
{
|
||||
try
|
||||
{
|
||||
ActionMethod();
|
||||
EnterTrueBranch(); // 动作成功,进入真分支
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// 可添加异常处理逻辑
|
||||
Return(); // 动作失败,终止节点
|
||||
}
|
||||
}
|
||||
|
||||
// 实现动作接口的方法
|
||||
public void Execute(DynamicContext context)
|
||||
{
|
||||
Context = context; // 更新节点的上下文
|
||||
Execute();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
*/
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
/// <summary>
|
||||
/// 根节点
|
||||
/// </summary>
|
||||
public abstract class NodeControl : UserControl
|
||||
{
|
||||
public string Id { get; set; }
|
||||
public string Name { get; set; }
|
||||
|
||||
public abstract void Execute(NodeContext context);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 条件节点
|
||||
/// </summary>
|
||||
public class ConditionNodeControl : NodeControl
|
||||
{
|
||||
public Func<NodeContext, bool> Condition { get; set; }
|
||||
public NodeControl TrueNode { get; set; }
|
||||
public NodeControl FalseNode { get; set; }
|
||||
|
||||
public ConditionNodeControl()
|
||||
{
|
||||
this.Content = new TextBlock { Text = "条件节点" };
|
||||
this.Background = Brushes.LightBlue;
|
||||
}
|
||||
|
||||
public override void Execute(NodeContext context)
|
||||
{
|
||||
if (Condition(context))
|
||||
{
|
||||
TrueNode?.Execute(context);
|
||||
}
|
||||
else
|
||||
{
|
||||
FalseNode?.Execute(context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 动作节点
|
||||
/// </summary>
|
||||
public class ActionNodeControl : NodeControl
|
||||
{
|
||||
public Action<NodeContext> Action { get; set; }
|
||||
|
||||
public ActionNodeControl()
|
||||
{
|
||||
this.Content = new TextBlock { Text = "动作节点" };
|
||||
this.Background = Brushes.LightGreen;
|
||||
}
|
||||
|
||||
public override void Execute(NodeContext context)
|
||||
{
|
||||
Action?.Invoke(context);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 状态节点
|
||||
/// </summary>
|
||||
public class StateNodeControl : NodeControl
|
||||
{
|
||||
public Func<NodeContext, object> StateFunction { get; set; }
|
||||
|
||||
public StateNodeControl()
|
||||
{
|
||||
this.Content = new TextBlock { Text = "状态节点" };
|
||||
this.Background = Brushes.LightYellow;
|
||||
}
|
||||
|
||||
public override void Execute(NodeContext context)
|
||||
{
|
||||
var result = StateFunction(context);
|
||||
context.Set("StateResult", result);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 节点上下文
|
||||
/// </summary>
|
||||
public class NodeContext
|
||||
{
|
||||
private readonly Dictionary<string, object> _data = new Dictionary<string, object>();
|
||||
|
||||
public void Set<T>(string key, T value)
|
||||
{
|
||||
_data[key] = value;
|
||||
}
|
||||
|
||||
public T Get<T>(string key)
|
||||
{
|
||||
return _data.ContainsKey(key) ? (T)_data[key] : default;
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
/*
|
||||
public class Context
|
||||
{
|
||||
public Dictionary<string, object> Data { get; set; } = new Dictionary<string, object>();
|
||||
public void Set<T>(string key, T value)
|
||||
{
|
||||
Data[key] = value;
|
||||
}
|
||||
|
||||
public T Get<T>(string key)
|
||||
{
|
||||
return Data.ContainsKey(key) ? (T)Data[key] : default;
|
||||
}
|
||||
}
|
||||
|
||||
public interface INode
|
||||
{
|
||||
Context Enter(Context context);
|
||||
Context Exit(Context context);
|
||||
}
|
||||
|
||||
|
||||
|
||||
public partial class ConditionNode : INode
|
||||
{
|
||||
public Func<Context, bool> Condition { get; set; }
|
||||
public INode TrueBranch { get; set; }
|
||||
public INode FalseBranch { get; set; }
|
||||
|
||||
public Context Enter(Context context)
|
||||
{
|
||||
if (Condition(context))
|
||||
{
|
||||
return TrueBranch?.Enter(context) ?? context;
|
||||
}
|
||||
else
|
||||
{
|
||||
return FalseBranch?.Enter(context) ?? context;
|
||||
}
|
||||
}
|
||||
|
||||
public Context Exit(Context context)
|
||||
{
|
||||
return context;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
public partial class ActionNode : INode
|
||||
{
|
||||
public Action<Context> Action { get; set; }
|
||||
public bool IsTask { get; set; }
|
||||
|
||||
public Context Enter(Context context)
|
||||
{
|
||||
if (IsTask)
|
||||
{
|
||||
Task.Run(() => Action(context));
|
||||
}
|
||||
else
|
||||
{
|
||||
Action(context);
|
||||
}
|
||||
return context;
|
||||
}
|
||||
|
||||
public Context Exit(Context context)
|
||||
{
|
||||
return context;
|
||||
}
|
||||
}
|
||||
|
||||
public partial class StateNode : INode
|
||||
{
|
||||
public Context Enter(Context context)
|
||||
{
|
||||
return context;
|
||||
}
|
||||
|
||||
public Context Exit(Context context)
|
||||
{
|
||||
return context;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
*/
|
||||
41
WorkBench/Node/View/ActionNodeControl.xaml
Normal file
41
WorkBench/Node/View/ActionNodeControl.xaml
Normal file
@@ -0,0 +1,41 @@
|
||||
<local:NodeControlBase x:Class="Serein.WorkBench.Node.View.ActionNodeControl"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="clr-namespace:Serein.WorkBench.Node.View"
|
||||
xmlns:vm="clr-namespace:Serein.WorkBench.Node.ViewModel"
|
||||
xmlns:themes="clr-namespace:Serein.WorkBench.Themes"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="450" d:DesignWidth="800"
|
||||
>
|
||||
<Grid>
|
||||
<Grid.ToolTip>
|
||||
<ToolTip Background="LightYellow" Foreground="Black" Content="{Binding MethodDetails.MethodTips, UpdateSourceTrigger=PropertyChanged}" />
|
||||
</Grid.ToolTip>
|
||||
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Border Grid.Row="0" Background="#FFCFDF" BorderBrush="#FFCFDF" BorderThickness="1">
|
||||
<TextBlock Text="{Binding MethodDetails.MethodName, UpdateSourceTrigger=PropertyChanged}" HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
<themes:MethodDetailsControl Grid.Row="1" MethodDetails="{Binding MethodDetails}" />
|
||||
<Grid Grid.Row="2" >
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="50"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Border Grid.Column="0" Background="#EAFFD0" BorderBrush="#EAFFD0" BorderThickness="1">
|
||||
<TextBlock Text="result" HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
<Border Grid.Column="1" Background="#EAFFD0" BorderBrush="#EAFFD0" BorderThickness="1">
|
||||
<TextBlock Text="{Binding MethodDetails.ReturnType}" HorizontalAlignment="Left" VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
</Grid>
|
||||
|
||||
|
||||
</Grid>
|
||||
</local:NodeControlBase>
|
||||
22
WorkBench/Node/View/ActionNodeControl.xaml.cs
Normal file
22
WorkBench/Node/View/ActionNodeControl.xaml.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using Serein.DynamicFlow.NodeModel;
|
||||
using Serein.WorkBench.Node.ViewModel;
|
||||
|
||||
namespace Serein.WorkBench.Node.View
|
||||
{
|
||||
/// <summary>
|
||||
/// ActionNode.xaml 的交互逻辑
|
||||
/// </summary>
|
||||
public partial class ActionNodeControl : NodeControlBase
|
||||
{
|
||||
private readonly ActionNodeControlViewModel actionNodeControlViewModel;
|
||||
public ActionNodeControl(SingleActionNode node) : base(node)
|
||||
{
|
||||
Node = node;
|
||||
actionNodeControlViewModel = new ActionNodeControlViewModel(node);
|
||||
DataContext = actionNodeControlViewModel;
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
30
WorkBench/Node/View/ActionRegionControl.xaml
Normal file
30
WorkBench/Node/View/ActionRegionControl.xaml
Normal file
@@ -0,0 +1,30 @@
|
||||
<local:NodeControlBase x:Class="Serein.WorkBench.Node.View.ActionRegionControl"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="clr-namespace:Serein.WorkBench.Node.View"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="450" d:DesignWidth="800">
|
||||
<Grid>
|
||||
<Border BorderBrush="Black" BorderThickness="1" Padding="10">
|
||||
<StackPanel>
|
||||
<Grid Margin="2,2,2,5">
|
||||
<TextBlock Text="动作区域" FontWeight="Bold" HorizontalAlignment="Left" FontSize="14" Margin="0,1,0,0"/>
|
||||
<Button Content="编辑" FontWeight="Bold" HorizontalAlignment="Right"/>
|
||||
</Grid>
|
||||
<ListBox x:Name="ActionsListBox" AllowDrop="True" Drop="ActionsListBox_Drop" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<Thumb x:Name="ResizeTop" Cursor="SizeNS" Height="4" HorizontalAlignment="Stretch" VerticalAlignment="Top"
|
||||
DragDelta="ResizeTop_DragDelta"/>
|
||||
<Thumb x:Name="ResizeBottom" Cursor="SizeNS" Height="4" HorizontalAlignment="Stretch" VerticalAlignment="Bottom"
|
||||
DragDelta="ResizeBottom_DragDelta"/>
|
||||
<Thumb x:Name="ResizeLeft" Cursor="SizeWE" Width="4" HorizontalAlignment="Left" VerticalAlignment="Stretch"
|
||||
DragDelta="ResizeLeft_DragDelta"/>
|
||||
<Thumb x:Name="ResizeRight" Cursor="SizeWE" Width="4" HorizontalAlignment="Right" VerticalAlignment="Stretch"
|
||||
DragDelta="ResizeRight_DragDelta"/>
|
||||
</Grid>
|
||||
|
||||
</local:NodeControlBase>
|
||||
147
WorkBench/Node/View/ActionRegionControl.xaml.cs
Normal file
147
WorkBench/Node/View/ActionRegionControl.xaml.cs
Normal file
@@ -0,0 +1,147 @@
|
||||
using Serein.DynamicFlow.NodeModel;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Controls.Primitives;
|
||||
using System.Windows.Input;
|
||||
using static Serein.WorkBench.MainWindow;
|
||||
|
||||
namespace Serein.WorkBench.Node.View
|
||||
{
|
||||
/// <summary>
|
||||
/// ActionRegion.xaml 的交互逻辑
|
||||
/// </summary>
|
||||
public partial class ActionRegionControl : NodeControlBase
|
||||
{
|
||||
private Point _dragStartPoint;
|
||||
|
||||
private new readonly CompositeActionNode Node;
|
||||
public ActionRegionControl() : base()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
public ActionRegionControl(CompositeActionNode node) : base(node)
|
||||
{
|
||||
InitializeComponent();
|
||||
Node = node;
|
||||
base.Name = "动作组合节点";
|
||||
}
|
||||
|
||||
#region 动态调整区域大小
|
||||
private void ResizeTop_DragDelta(object sender, DragDeltaEventArgs e)
|
||||
{
|
||||
double oldHeight = Height;
|
||||
double newHeight = Math.Max(MinHeight, oldHeight - e.VerticalChange);
|
||||
Height = newHeight;
|
||||
Canvas.SetTop(this, Canvas.GetTop(this) + (oldHeight - newHeight));
|
||||
}
|
||||
|
||||
private void ResizeBottom_DragDelta(object sender, DragDeltaEventArgs e)
|
||||
{
|
||||
double newHeight = Math.Max(MinHeight, Height + e.VerticalChange);
|
||||
Height = newHeight;
|
||||
}
|
||||
|
||||
private void ResizeLeft_DragDelta(object sender, DragDeltaEventArgs e)
|
||||
{
|
||||
double oldWidth = Width;
|
||||
double newWidth = Math.Max(MinWidth, oldWidth - e.HorizontalChange);
|
||||
Width = newWidth;
|
||||
Canvas.SetLeft(this, Canvas.GetLeft(this) + (oldWidth - newWidth));
|
||||
}
|
||||
|
||||
private void ResizeRight_DragDelta(object sender, DragDeltaEventArgs e)
|
||||
{
|
||||
double newWidth = Math.Max(MinWidth, Width + e.HorizontalChange);
|
||||
Width = newWidth;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
public void AddAction(NodeControlBase node, bool isTask = false)
|
||||
{
|
||||
/*TextBlock actionText = new TextBlock
|
||||
{
|
||||
Text = node.MethodDetails.MethodName + (isTask ? " (Task)" : ""),
|
||||
Margin = new Thickness(10, 2, 0, 0),
|
||||
Tag = node.MethodDetails,
|
||||
};*/
|
||||
Node?.AddNode((SingleActionNode)node.Node);
|
||||
ActionsListBox.Items.Add(node);
|
||||
}
|
||||
|
||||
/* public async Task ExecuteActions(DynamicContext context)
|
||||
{
|
||||
foreach (TextBlock item in ActionsListBox.Items)
|
||||
{
|
||||
dynamic tag = item.Tag;
|
||||
IAction action = tag.Action;
|
||||
bool isTask = tag.IsTask;
|
||||
|
||||
if (isTask)
|
||||
{
|
||||
await Task.Run(() => action.Execute(Node.MethodDetails, context));
|
||||
}
|
||||
else
|
||||
{
|
||||
action.Execute(Node.MethodDetails, context);
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
||||
|
||||
|
||||
private void ActionsListBox_Drop(object sender, DragEventArgs e)
|
||||
{
|
||||
/*if (e.Data.GetDataPresent("Type"))
|
||||
{
|
||||
Type droppedType = e.Data.GetData("Type") as Type;
|
||||
|
||||
if (droppedType != null && typeof(ICondition).IsAssignableFrom(droppedType) && droppedType.IsClass)
|
||||
{
|
||||
// 创建一个新的 TextBlock 并设置其属性
|
||||
TextBlock conditionText = new TextBlock
|
||||
{
|
||||
Text = droppedType.Name,
|
||||
Margin = new Thickness(10, 2, 0, 0),
|
||||
Tag = droppedType
|
||||
};
|
||||
|
||||
// 为 TextBlock 添加鼠标左键按下事件处理程序
|
||||
// conditionText.MouseLeftButtonDown += TypeText_MouseLeftButtonDown;
|
||||
// 为 TextBlock 添加鼠标移动事件处理程序
|
||||
// conditionText.MouseMove += TypeText_MouseMove;
|
||||
|
||||
// 将 TextBlock 添加到 ActionsListBox 中
|
||||
ActionsListBox.Items.Add(conditionText);
|
||||
}
|
||||
}*/
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
// 用于拖动的鼠标事件处理程序
|
||||
private void TypeText_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
_dragStartPoint = e.GetPosition(null);
|
||||
}
|
||||
|
||||
private void TypeText_MouseMove(object sender, MouseEventArgs e)
|
||||
{
|
||||
Point mousePos = e.GetPosition(null);
|
||||
Vector diff = _dragStartPoint - mousePos;
|
||||
|
||||
if (e.LeftButton == MouseButtonState.Pressed &&
|
||||
(Math.Abs(diff.X) > SystemParameters.MinimumHorizontalDragDistance ||
|
||||
Math.Abs(diff.Y) > SystemParameters.MinimumVerticalDragDistance))
|
||||
{
|
||||
if (sender is TextBlock typeText)
|
||||
{
|
||||
var dragData = new DataObject(MouseNodeType.RegionType, typeText.Tag);
|
||||
DragDrop.DoDragDrop(typeText, dragData, DragDropEffects.Move);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
75
WorkBench/Node/View/ConditionNodeControl.xaml
Normal file
75
WorkBench/Node/View/ConditionNodeControl.xaml
Normal file
@@ -0,0 +1,75 @@
|
||||
<local:NodeControlBase x:Class="Serein.WorkBench.Node.View.ConditionNodeControl"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="clr-namespace:Serein.WorkBench.Node.View"
|
||||
xmlns:vm="clr-namespace:Serein.WorkBench.Node.ViewModel"
|
||||
xmlns:themes="clr-namespace:Serein.WorkBench.Themes"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="450" d:DesignWidth="800">
|
||||
|
||||
<!--d:DataContext="{d:DesignInstance Type=vm:ConditionNodeControlViewModel}"-->
|
||||
<UserControl.Resources>
|
||||
<!--<vm:TypeToStringConverter x:Key="TypeToStringConverter"/>-->
|
||||
<BooleanToVisibilityConverter x:Key="BoolToVis" />
|
||||
</UserControl.Resources>
|
||||
|
||||
|
||||
<Grid>
|
||||
<Grid.ToolTip>
|
||||
<ToolTip Background="LightYellow" Foreground="Black" Content="{Binding MethodDetails.MethodTips, UpdateSourceTrigger=PropertyChanged}" />
|
||||
</Grid.ToolTip>
|
||||
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Border Grid.Row="0" Background="#A8D8EA" BorderBrush="#A8D8EA" BorderThickness="1" HorizontalAlignment="Stretch">
|
||||
<TextBlock Text="条件节点" HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
<Grid Grid.Row="1" Background="#F1FFDF" HorizontalAlignment="Stretch">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="20"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<CheckBox Grid.Column="0" IsChecked="{Binding IsCustomData, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/> <!--Converter={StaticResource BoolToVis}-->
|
||||
<TextBox Grid.Column="1" MinWidth="50" Text="{Binding CustomData, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
|
||||
HorizontalAlignment="Stretch" VerticalAlignment="Center">
|
||||
<TextBox.Style>
|
||||
<Style TargetType="TextBox">
|
||||
<Setter Property="Visibility" Value="Collapsed" />
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding IsCustomData}" Value="True">
|
||||
<Setter Property="Visibility" Value="Visible" />
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</TextBox.Style>
|
||||
</TextBox>
|
||||
|
||||
<TextBlock Grid.Column="1" MinWidth="50" Text="上一节点数据" HorizontalAlignment="Stretch" VerticalAlignment="Center">
|
||||
<TextBlock.Style>
|
||||
<Style TargetType="TextBlock">
|
||||
<Setter Property="Visibility" Value="Collapsed" />
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding IsCustomData}" Value="False">
|
||||
<Setter Property="Visibility" Value="Visible" />
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</TextBlock.Style>
|
||||
</TextBlock>
|
||||
</Grid>
|
||||
<TextBox Grid.Row="2" Background="#f1F66F" MinWidth="100" Text="{Binding Expression, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
|
||||
HorizontalAlignment="Stretch" VerticalAlignment="Center"/>
|
||||
|
||||
<!--<themes:MethodDetailsControl Grid.Row="1" MethodDetails="{Binding MethodDetails}" />
|
||||
<Border Grid.Row="2" Background="#EAFFD0" BorderBrush="#EAFFD0" BorderThickness="1">
|
||||
<TextBlock Text="{Binding MethodDetails.MethodTips, Converter={StaticResource TypeToStringConverter}, StringFormat=return:{0}, UpdateSourceTrigger=PropertyChanged}"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"/>
|
||||
</Border>-->
|
||||
</Grid>
|
||||
</local:NodeControlBase>
|
||||
37
WorkBench/Node/View/ConditionNodeControl.xaml.cs
Normal file
37
WorkBench/Node/View/ConditionNodeControl.xaml.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
using Serein.DynamicFlow;
|
||||
using Serein.DynamicFlow.NodeModel;
|
||||
using Serein.WorkBench.Node.ViewModel;
|
||||
using static Serein.WorkBench.MainWindow;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using System.Windows;
|
||||
using static Dm.net.buffer.ByteArrayBuffer;
|
||||
|
||||
namespace Serein.WorkBench.Node.View
|
||||
{
|
||||
/// <summary>
|
||||
/// ConditionNode.xaml 的交互逻辑
|
||||
/// </summary>
|
||||
public partial class ConditionNodeControl : NodeControlBase
|
||||
{
|
||||
private readonly ConditionNodeControlViewModel viewModel;
|
||||
|
||||
public ConditionNodeControl() : base()
|
||||
{
|
||||
|
||||
viewModel = new (new ());
|
||||
DataContext = viewModel;
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
public ConditionNodeControl(SingleConditionNode node) : base(node)
|
||||
{
|
||||
Node = node;
|
||||
viewModel = new ConditionNodeControlViewModel(node);
|
||||
DataContext = viewModel;
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
34
WorkBench/Node/View/ConditionRegionControl.xaml
Normal file
34
WorkBench/Node/View/ConditionRegionControl.xaml
Normal file
@@ -0,0 +1,34 @@
|
||||
<local:NodeControlBase x:Class="Serein.WorkBench.Node.View.ConditionRegionControl"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="clr-namespace:Serein.WorkBench.Node.View"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="450" d:DesignWidth="800">
|
||||
<Grid>
|
||||
|
||||
<Border BorderBrush="Black" BorderThickness="1" Padding="10">
|
||||
<StackPanel>
|
||||
<DockPanel Margin="2,2,2,5">
|
||||
<!--<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="40"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>-->
|
||||
<TextBlock Text="条件区域" FontWeight="Bold" HorizontalAlignment="Left" FontSize="14" Margin="0,1,0,0"/>
|
||||
<Button Content="编辑" FontWeight="Bold" HorizontalAlignment="Right"/>
|
||||
</DockPanel>
|
||||
<ListBox x:Name="ConditionsListBox" AllowDrop="True" Drop="ConditionsListBox_Drop"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<Thumb x:Name="ResizeTop" Cursor="SizeNS" Height="4" HorizontalAlignment="Stretch" VerticalAlignment="Top"
|
||||
DragDelta="ResizeTop_DragDelta"/>
|
||||
<Thumb x:Name="ResizeBottom" Cursor="SizeNS" Height="4" HorizontalAlignment="Stretch" VerticalAlignment="Bottom"
|
||||
DragDelta="ResizeBottom_DragDelta"/>
|
||||
<Thumb x:Name="ResizeLeft" Cursor="SizeWE" Width="4" HorizontalAlignment="Left" VerticalAlignment="Stretch"
|
||||
DragDelta="ResizeLeft_DragDelta"/>
|
||||
<Thumb x:Name="ResizeRight" Cursor="SizeWE" Width="4" HorizontalAlignment="Right" VerticalAlignment="Stretch"
|
||||
DragDelta="ResizeRight_DragDelta"/>
|
||||
</Grid>
|
||||
</local:NodeControlBase>
|
||||
125
WorkBench/Node/View/ConditionRegionControl.xaml.cs
Normal file
125
WorkBench/Node/View/ConditionRegionControl.xaml.cs
Normal file
@@ -0,0 +1,125 @@
|
||||
using Serein.DynamicFlow;
|
||||
using Serein.DynamicFlow.NodeModel;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Controls.Primitives;
|
||||
using System.Windows.Input;
|
||||
using static Serein.WorkBench.MainWindow;
|
||||
|
||||
namespace Serein.WorkBench.Node.View
|
||||
{
|
||||
/// <summary>
|
||||
/// ConditionRegion.xaml 的交互逻辑
|
||||
/// </summary>
|
||||
public partial class ConditionRegionControl : NodeControlBase
|
||||
{
|
||||
private Point _dragStartPoint;
|
||||
public ConditionRegionControl() : base()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
public ConditionRegionControl(CompositeConditionNode node) : base(node)
|
||||
{
|
||||
Node = node;
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
#region 动态调整区域大小
|
||||
private void ResizeTop_DragDelta(object sender, DragDeltaEventArgs e)
|
||||
{
|
||||
double oldHeight = Height;
|
||||
double newHeight = Math.Max(MinHeight, oldHeight - e.VerticalChange);
|
||||
Height = newHeight;
|
||||
Canvas.SetTop(this, Canvas.GetTop(this) + (oldHeight - newHeight));
|
||||
}
|
||||
|
||||
private void ResizeBottom_DragDelta(object sender, DragDeltaEventArgs e)
|
||||
{
|
||||
double newHeight = Math.Max(MinHeight, Height + e.VerticalChange);
|
||||
Height = newHeight;
|
||||
}
|
||||
|
||||
private void ResizeLeft_DragDelta(object sender, DragDeltaEventArgs e)
|
||||
{
|
||||
double oldWidth = Width;
|
||||
double newWidth = Math.Max(MinWidth, oldWidth - e.HorizontalChange);
|
||||
Width = newWidth;
|
||||
Canvas.SetLeft(this, Canvas.GetLeft(this) + (oldWidth - newWidth));
|
||||
}
|
||||
|
||||
private void ResizeRight_DragDelta(object sender, DragDeltaEventArgs e)
|
||||
{
|
||||
double newWidth = Math.Max(MinWidth, Width + e.HorizontalChange);
|
||||
Width = newWidth;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 添加条件控件
|
||||
/// </summary>
|
||||
/// <param name="condition"></param>
|
||||
public void AddCondition(NodeControlBase node)
|
||||
{
|
||||
((CompositeConditionNode)Node).AddNode((SingleConditionNode)node.Node);
|
||||
|
||||
this.Width += node.Width;
|
||||
this.Height += node.Height;
|
||||
ConditionsListBox.Items.Add(node);
|
||||
}
|
||||
|
||||
|
||||
|
||||
private void ConditionsListBox_Drop(object sender, DragEventArgs e)
|
||||
{
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
// Mouse event handlers for dragging
|
||||
private void TypeText_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
_dragStartPoint = e.GetPosition(null);
|
||||
}
|
||||
|
||||
private void TypeText_MouseMove(object sender, MouseEventArgs e)
|
||||
{
|
||||
Point mousePos = e.GetPosition(null);
|
||||
Vector diff = _dragStartPoint - mousePos;
|
||||
|
||||
if (e.LeftButton == MouseButtonState.Pressed &&
|
||||
(Math.Abs(diff.X) > SystemParameters.MinimumHorizontalDragDistance ||
|
||||
Math.Abs(diff.Y) > SystemParameters.MinimumVerticalDragDistance))
|
||||
{
|
||||
if (sender is TextBlock typeText)
|
||||
{
|
||||
var dragData = new DataObject(MouseNodeType.RegionType, typeText.Tag);
|
||||
DragDrop.DoDragDrop(typeText, dragData, DragDropEffects.Move);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*private void TypeText_MouseMove(object sender, MouseEventArgs e)
|
||||
{
|
||||
Point mousePos = e.GetPosition(null);
|
||||
Vector diff = _dragStartPoint - mousePos;
|
||||
|
||||
if (e.LeftButton == MouseButtonState.Pressed &&
|
||||
(Math.Abs(diff.X) > SystemParameters.MinimumHorizontalDragDistance ||
|
||||
Math.Abs(diff.Y) > SystemParameters.MinimumVerticalDragDistance))
|
||||
{
|
||||
TextBlock typeText = sender as TextBlock;
|
||||
if (typeText != null)
|
||||
{
|
||||
DataObject dragData = new DataObject("Type", typeText.Tag);
|
||||
DragDrop.DoDragDrop(typeText, dragData, DragDropEffects.Move);
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
}
|
||||
}
|
||||
41
WorkBench/Node/View/DllControlControl.xaml
Normal file
41
WorkBench/Node/View/DllControlControl.xaml
Normal file
@@ -0,0 +1,41 @@
|
||||
<UserControl x:Class="Serein.WorkBench.Node.View.DllControl"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="clr-namespace:Serein.WorkBench.Node.View"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="450" d:DesignWidth="800"
|
||||
>
|
||||
<DockPanel>
|
||||
<StackPanel DockPanel.Dock="Top" >
|
||||
<TextBlock Text="{Binding Path=Header, RelativeSource={RelativeSource AncestorType=UserControl}}"
|
||||
FontWeight="Bold" FontSize="14" Margin="5" Background="#dbe2ef"/>
|
||||
</StackPanel>
|
||||
<DockPanel>
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<!--<ColumnDefinition Width="*" />-->
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<GroupBox Grid.Row="0" Header="条件" Margin="5">
|
||||
<ListBox x:Name="ConditionsListBox" Background="#A8D8EA"/>
|
||||
</GroupBox>
|
||||
<GroupBox Grid.Row="1" Header="动作" Margin="5">
|
||||
<ListBox x:Name="ActionsListBox" Background="#FFCFDF"/>
|
||||
</GroupBox>
|
||||
<GroupBox Grid.Row="2" Header="触发器" Margin="5">
|
||||
<ListBox x:Name="FlipflopsListBox" Background="#FFFFD2"/>
|
||||
</GroupBox>
|
||||
</Grid>
|
||||
|
||||
|
||||
</DockPanel>
|
||||
</DockPanel>
|
||||
</UserControl>
|
||||
146
WorkBench/Node/View/DllControlControl.xaml.cs
Normal file
146
WorkBench/Node/View/DllControlControl.xaml.cs
Normal file
@@ -0,0 +1,146 @@
|
||||
using Serein.DynamicFlow;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using static Serein.WorkBench.MainWindow;
|
||||
|
||||
namespace Serein.WorkBench.Node.View
|
||||
{
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// UserControl1.xaml 的交互逻辑
|
||||
/// </summary>
|
||||
public partial class DllControl : UserControl
|
||||
{
|
||||
|
||||
public DllControl()
|
||||
{
|
||||
Header = "DLL文件"; // 设置初始值
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Header 依赖属性,用于绑定标题
|
||||
/// </summary>
|
||||
public string Header
|
||||
{
|
||||
get { return (string)GetValue(HeaderProperty); }
|
||||
set { SetValue(HeaderProperty, value); }
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty HeaderProperty =
|
||||
DependencyProperty.Register("Header", typeof(string), typeof(DllControl), new PropertyMetadata(string.Empty));
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 向条件面板添加类型的文本块
|
||||
/// </summary>
|
||||
/// <param name="type">要添加的类型</param>
|
||||
public void AddCondition(MethodDetails md)
|
||||
{
|
||||
AddTypeToListBox(md, ConditionsListBox);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 向动作面板添加类型的文本块
|
||||
/// </summary>
|
||||
/// <param name="type">要添加的类型</param>
|
||||
public void AddAction(MethodDetails md)
|
||||
{
|
||||
AddTypeToListBox(md, ActionsListBox);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 向状态面板添加类型的文本块
|
||||
/// </summary>
|
||||
/// <param name="type">要添加的类型</param>
|
||||
public void AddFlipflop(MethodDetails md)
|
||||
{
|
||||
AddTypeToListBox(md, FlipflopsListBox);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 向指定面板添加类型的文本块
|
||||
/// </summary>
|
||||
/// <param name="type">要添加的类型</param>
|
||||
/// <param name="panel">要添加到的面板</param>
|
||||
private void AddTypeToListBox(MethodDetails md, ListBox listBox)
|
||||
{
|
||||
// 创建一个新的 TextBlock 并设置其属性
|
||||
TextBlock typeText = new TextBlock
|
||||
{
|
||||
Text = $"{md.MethodName}",
|
||||
Margin = new Thickness(10, 2, 0, 0),
|
||||
Tag = md
|
||||
};
|
||||
// 为 TextBlock 添加鼠标左键按下事件处理程序
|
||||
typeText.MouseLeftButtonDown += TypeText_MouseLeftButtonDown;
|
||||
// 为 TextBlock 添加鼠标移动事件处理程序
|
||||
typeText.MouseMove += TypeText_MouseMove;
|
||||
// 将 TextBlock 添加到指定的面板
|
||||
listBox.Items.Add(typeText);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 存储拖拽开始时的鼠标位置
|
||||
/// </summary>
|
||||
private Point _dragStartPoint;
|
||||
|
||||
/// <summary>
|
||||
/// 处理 TextBlock 的鼠标左键按下事件
|
||||
/// </summary>
|
||||
/// <param name="sender">事件源</param>
|
||||
/// <param name="e">事件参数</param>
|
||||
private void TypeText_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
// 记录鼠标按下时的位置
|
||||
_dragStartPoint = e.GetPosition(null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理 TextBlock 的鼠标移动事件
|
||||
/// </summary>
|
||||
/// <param name="sender">事件源</param>
|
||||
/// <param name="e">事件参数</param>
|
||||
private void TypeText_MouseMove(object sender, MouseEventArgs e)
|
||||
{
|
||||
// 获取当前鼠标位置
|
||||
Point mousePos = e.GetPosition(null);
|
||||
// 计算鼠标移动的距离
|
||||
Vector diff = _dragStartPoint - mousePos;
|
||||
|
||||
// 判断是否符合拖拽的最小距离要求
|
||||
if (e.LeftButton == MouseButtonState.Pressed &&
|
||||
(Math.Abs(diff.X) > SystemParameters.MinimumHorizontalDragDistance ||
|
||||
Math.Abs(diff.Y) > SystemParameters.MinimumVerticalDragDistance))
|
||||
{
|
||||
// 获取触发事件的 TextBlock
|
||||
TextBlock typeText = sender as TextBlock;
|
||||
if (typeText != null)
|
||||
{
|
||||
// 创建一个 DataObject 用于拖拽操作,并设置拖拽效果
|
||||
DataObject dragData = new DataObject(MouseNodeType.DllNodeType, typeText.Tag);
|
||||
DragDrop.DoDragDrop(typeText, dragData, DragDropEffects.Move);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
20
WorkBench/Node/View/ExpOpNodeControl.xaml
Normal file
20
WorkBench/Node/View/ExpOpNodeControl.xaml
Normal file
@@ -0,0 +1,20 @@
|
||||
<local:NodeControlBase x:Class="Serein.WorkBench.Node.View.ExpOpNodeControl"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="clr-namespace:Serein.WorkBench.Node.View"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="450" d:DesignWidth="800">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
<!--<TextBlock Grid.Row="0" Text=""></TextBlock>-->
|
||||
|
||||
<StackPanel Grid.Row="0" Orientation="Vertical" Background="LightSteelBlue">
|
||||
<TextBlock Grid.Row="2" Text="表达式"></TextBlock>
|
||||
<TextBox Text="{Binding Expression, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" HorizontalAlignment="Stretch"></TextBox>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</local:NodeControlBase>
|
||||
43
WorkBench/Node/View/ExpOpNodeControl.xaml.cs
Normal file
43
WorkBench/Node/View/ExpOpNodeControl.xaml.cs
Normal file
@@ -0,0 +1,43 @@
|
||||
using Serein.DynamicFlow.NodeModel;
|
||||
using Serein.WorkBench.Node.ViewModel;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Windows.Navigation;
|
||||
using System.Windows.Shapes;
|
||||
|
||||
namespace Serein.WorkBench.Node.View
|
||||
{
|
||||
/// <summary>
|
||||
/// ExprOpNodeControl.xaml 的交互逻辑
|
||||
/// </summary>
|
||||
public partial class ExpOpNodeControl : NodeControlBase
|
||||
{
|
||||
private readonly ExpOpNodeViewModel viewModel;
|
||||
|
||||
|
||||
public ExpOpNodeControl()
|
||||
{
|
||||
viewModel = new (new());
|
||||
DataContext = viewModel;
|
||||
InitializeComponent();
|
||||
}
|
||||
public ExpOpNodeControl(SingleExpOpNode node):base(node)
|
||||
{
|
||||
Node = node;
|
||||
viewModel = new(node);
|
||||
DataContext = viewModel;
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
34
WorkBench/Node/View/FlipflopNodeControl.xaml
Normal file
34
WorkBench/Node/View/FlipflopNodeControl.xaml
Normal file
@@ -0,0 +1,34 @@
|
||||
<local:NodeControlBase x:Class="Serein.WorkBench.Node.View.FlipflopNodeControl"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="clr-namespace:Serein.WorkBench.Node.View"
|
||||
xmlns:vm="clr-namespace:Serein.WorkBench.Node.ViewModel"
|
||||
xmlns:themes="clr-namespace:Serein.WorkBench.Themes"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="450" d:DesignWidth="1400">
|
||||
|
||||
<UserControl.Resources>
|
||||
<vm:TypeToStringConverter x:Key="TypeToStringConverter"/>
|
||||
<!--<themes:ConditionControl x:Key="ConditionControl"/>-->
|
||||
</UserControl.Resources>
|
||||
|
||||
<Grid>
|
||||
<Grid.ToolTip>
|
||||
<ToolTip Background="LightYellow" Foreground="Black" Content="{Binding MethodDetails.MethodTips, UpdateSourceTrigger=PropertyChanged}" />
|
||||
</Grid.ToolTip>
|
||||
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Border Grid.Row="0" Background="#FFFFD2" BorderBrush="Black" BorderThickness="1">
|
||||
<TextBlock Text="{Binding MethodDetails.MethodName, UpdateSourceTrigger=PropertyChanged}" HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
<!--<themes:ExplicitDataControl Grid.Row="1" ExplicitDatas="{Binding ExplicitDatas}" />-->
|
||||
<themes:MethodDetailsControl Grid.Row="1" MethodDetails="{Binding MethodDetails}" />
|
||||
<!--<themes:ConditionControl Grid.Row="2" ></themes:ConditionControl>-->
|
||||
</Grid>
|
||||
</local:NodeControlBase>
|
||||
41
WorkBench/Node/View/FlipflopNodeControl.xaml.cs
Normal file
41
WorkBench/Node/View/FlipflopNodeControl.xaml.cs
Normal file
@@ -0,0 +1,41 @@
|
||||
using Serein.DynamicFlow;
|
||||
using Serein.DynamicFlow.NodeModel;
|
||||
using Serein.WorkBench.Node.ViewModel;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace Serein.WorkBench.Node.View
|
||||
{
|
||||
/// <summary>
|
||||
/// StateNode.xaml 的交互逻辑
|
||||
/// </summary>
|
||||
public partial class FlipflopNodeControl : NodeControlBase
|
||||
{
|
||||
private readonly FlipflopNodeControlViewModel viewModel;
|
||||
|
||||
public FlipflopNodeControl(SingleFlipflopNode node) : base(node)
|
||||
{
|
||||
Node = node;
|
||||
viewModel = new FlipflopNodeControlViewModel(node);
|
||||
DataContext = viewModel;
|
||||
InitializeComponent();
|
||||
|
||||
}
|
||||
|
||||
//private void ComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||
//{
|
||||
// var comboBox = sender as ComboBox;
|
||||
// if (comboBox == null)
|
||||
// {
|
||||
// return;
|
||||
// }
|
||||
// var selectedExplicitData = comboBox.DataContext as ExplicitData;
|
||||
// if (selectedExplicitData == null)
|
||||
// {
|
||||
// return;
|
||||
// }
|
||||
|
||||
// Console.WriteLine (selectedExplicitData.DataValue, "Selected Value");
|
||||
//}
|
||||
}
|
||||
}
|
||||
77
WorkBench/Node/View/NodeControlBase.cs
Normal file
77
WorkBench/Node/View/NodeControlBase.cs
Normal file
@@ -0,0 +1,77 @@
|
||||
using Serein.DynamicFlow;
|
||||
using Serein.DynamicFlow.NodeModel;
|
||||
using Serein.WorkBench.Themes;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
using System.ComponentModel;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace Serein.WorkBench.Node.View
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// 节点控件基类(控件)
|
||||
/// </summary>
|
||||
public abstract class NodeControlBase : UserControl, IDynamicFlowNode
|
||||
{
|
||||
public NodeBase Node { get; set; }
|
||||
protected NodeControlBase()
|
||||
{
|
||||
this.Background = Brushes.Transparent;
|
||||
}
|
||||
protected NodeControlBase(NodeBase node)
|
||||
{
|
||||
Node = node;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
public abstract class NodeControlViewModel : INotifyPropertyChanged
|
||||
{
|
||||
|
||||
public MethodDetails methodDetails;
|
||||
|
||||
public MethodDetails MethodDetails
|
||||
{
|
||||
get => methodDetails;
|
||||
set
|
||||
{
|
||||
methodDetails = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
|
||||
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
|
||||
{
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
public class FLowNodeObObservableCollection<T> : ObservableCollection<T>
|
||||
{
|
||||
|
||||
public void AddRange(IEnumerable<T> items)
|
||||
{
|
||||
foreach (var item in items)
|
||||
{
|
||||
this.Items.Add(item);
|
||||
}
|
||||
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
33
WorkBench/Node/ViewModel/ActionNodeControlViewModel.cs
Normal file
33
WorkBench/Node/ViewModel/ActionNodeControlViewModel.cs
Normal file
@@ -0,0 +1,33 @@
|
||||
using Serein.DynamicFlow;
|
||||
using Serein.DynamicFlow.NodeModel;
|
||||
using Serein.WorkBench.Node.View;
|
||||
using System.ComponentModel;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Serein.WorkBench.Node.ViewModel
|
||||
{
|
||||
public class ActionNodeControlViewModel : NodeControlViewModel
|
||||
{
|
||||
private readonly SingleActionNode node;
|
||||
|
||||
public ActionNodeControlViewModel(SingleActionNode node)
|
||||
{
|
||||
this.node = node;
|
||||
|
||||
|
||||
MethodDetails = node.MethodDetails;
|
||||
//if (node.MethodDetails.ExplicitDatas.Length == 0)
|
||||
//{
|
||||
// // 没有显式项
|
||||
// IsExistExplicitData = false;
|
||||
// ExplicitDatas = [];
|
||||
//}
|
||||
//else
|
||||
//{
|
||||
// explicitDatas = node.MethodDetails.ExplicitDatas;
|
||||
// //ExplicitDatas = node.MethodDetails.ExplicitDatas;
|
||||
// IsExistExplicitData = true;
|
||||
//}
|
||||
}
|
||||
}
|
||||
}
|
||||
49
WorkBench/Node/ViewModel/ConditionNodeControlViewModel.cs
Normal file
49
WorkBench/Node/ViewModel/ConditionNodeControlViewModel.cs
Normal file
@@ -0,0 +1,49 @@
|
||||
using Serein.DynamicFlow;
|
||||
using Serein.DynamicFlow.NodeModel;
|
||||
using Serein.WorkBench.Node.View;
|
||||
using System.ComponentModel;
|
||||
using System.Runtime.CompilerServices;
|
||||
using static Dm.net.buffer.ByteArrayBuffer;
|
||||
|
||||
namespace Serein.WorkBench.Node.ViewModel
|
||||
{
|
||||
public class ConditionNodeControlViewModel : NodeControlViewModel
|
||||
{
|
||||
private readonly SingleConditionNode singleConditionNode;
|
||||
|
||||
/// <summary>
|
||||
/// 是否为自定义参数
|
||||
/// </summary>
|
||||
public bool IsCustomData
|
||||
{
|
||||
get => singleConditionNode.IsCustomData;
|
||||
set { singleConditionNode.IsCustomData= value; OnPropertyChanged(); }
|
||||
}
|
||||
/// <summary>
|
||||
/// 自定义参数值
|
||||
/// </summary>
|
||||
public object? CustomData
|
||||
{
|
||||
get => singleConditionNode.CustomData;
|
||||
set { singleConditionNode.CustomData = value ; OnPropertyChanged(); }
|
||||
}
|
||||
/// <summary>
|
||||
/// 表达式
|
||||
/// </summary>
|
||||
public string Expression
|
||||
{
|
||||
get => singleConditionNode.Expression;
|
||||
set { singleConditionNode.Expression = value; OnPropertyChanged(); }
|
||||
}
|
||||
|
||||
public ConditionNodeControlViewModel(SingleConditionNode node)
|
||||
{
|
||||
this.singleConditionNode = node;
|
||||
MethodDetails = node.MethodDetails;
|
||||
IsCustomData = false;
|
||||
CustomData = "";
|
||||
Expression = "PASS";
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
31
WorkBench/Node/ViewModel/ExpOpNodeViewModel.cs
Normal file
31
WorkBench/Node/ViewModel/ExpOpNodeViewModel.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
using Serein.DynamicFlow.NodeModel;
|
||||
using Serein.WorkBench.Node.View;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Serein.WorkBench.Node.ViewModel
|
||||
{
|
||||
public class ExpOpNodeViewModel: NodeControlViewModel
|
||||
{
|
||||
public readonly SingleExpOpNode node;
|
||||
|
||||
public string Expression
|
||||
{
|
||||
get => node.Expression;
|
||||
set
|
||||
{
|
||||
node.Expression = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public ExpOpNodeViewModel(SingleExpOpNode node)
|
||||
{
|
||||
this.node = node;
|
||||
}
|
||||
}
|
||||
}
|
||||
15
WorkBench/Node/ViewModel/FlipflopNodeControlViewModel.cs
Normal file
15
WorkBench/Node/ViewModel/FlipflopNodeControlViewModel.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using Serein.DynamicFlow.NodeModel;
|
||||
using Serein.WorkBench.Node.View;
|
||||
|
||||
namespace Serein.WorkBench.Node.ViewModel
|
||||
{
|
||||
public class FlipflopNodeControlViewModel : NodeControlViewModel
|
||||
{
|
||||
private readonly SingleFlipflopNode node;
|
||||
public FlipflopNodeControlViewModel(SingleFlipflopNode node)
|
||||
{
|
||||
this.node = node;
|
||||
MethodDetails = node.MethodDetails;
|
||||
}
|
||||
}
|
||||
}
|
||||
27
WorkBench/Node/ViewModel/TypeToStringConverter.cs
Normal file
27
WorkBench/Node/ViewModel/TypeToStringConverter.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Data;
|
||||
|
||||
namespace Serein.WorkBench.Node.ViewModel
|
||||
{
|
||||
public class TypeToStringConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
if (value is Type type)
|
||||
{
|
||||
return type.ToString();
|
||||
}
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
52
WorkBench/Serein.WorkBench.csproj
Normal file
52
WorkBench/Serein.WorkBench.csproj
Normal file
@@ -0,0 +1,52 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>net8.0-windows7.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<UseWPF>True</UseWPF>
|
||||
<BaseOutputPath>D:\Project\C#\DynamicControl\SereinFlow\.Output</BaseOutputPath>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Remove="Node\NodeModel\**" />
|
||||
<Compile Remove="Themes\Condition\**" />
|
||||
<EmbeddedResource Remove="Node\NodeModel\**" />
|
||||
<EmbeddedResource Remove="Themes\Condition\**" />
|
||||
<None Remove="Node\NodeModel\**" />
|
||||
<None Remove="Themes\Condition\**" />
|
||||
<Page Remove="Node\NodeModel\**" />
|
||||
<Page Remove="Themes\Condition\**" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Remove="Node\FlipflopRegionControl.xaml.cs" />
|
||||
<Compile Remove="Node\NodeBase.cs" />
|
||||
<Compile Remove="Themes\ConditionControl.xaml.cs" />
|
||||
<Compile Remove="Themes\ConditionControlModel.cs" />
|
||||
<Compile Remove="Themes\ExplicitDataControl.xaml.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Page Remove="Node\FlipflopRegionControl.xaml" />
|
||||
<Page Remove="Themes\ConditionControl.xaml" />
|
||||
<Page Remove="Themes\ExplicitDataControl.xaml" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<!--<ProjectReference Include="..\DynamicControl\DynamicControl.csproj" />-->
|
||||
<ProjectReference Include="..\Library\Serein.Library.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Update="Themes\MethodDetailsControl.xaml.cs">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
133
WorkBench/SereinOutputFileData.cs
Normal file
133
WorkBench/SereinOutputFileData.cs
Normal file
@@ -0,0 +1,133 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Serein.WorkBench
|
||||
{
|
||||
|
||||
public class SereinOutputFileData
|
||||
{
|
||||
/// <summary>
|
||||
/// 基础
|
||||
/// </summary>
|
||||
public Basic basic { get; set; }
|
||||
/// <summary>
|
||||
/// 依赖的DLL
|
||||
/// </summary>
|
||||
public Library[] library { get; set; }
|
||||
/// <summary>
|
||||
/// 起始节点GUID
|
||||
/// </summary>
|
||||
public string startNode { get; set; }
|
||||
/// <summary>
|
||||
/// 节点信息集合
|
||||
/// </summary>
|
||||
public NodeInfo[] nodes { get; set; }
|
||||
/// <summary>
|
||||
/// 区域集合
|
||||
/// </summary>
|
||||
public Region[] regions { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 基础
|
||||
/// </summary>
|
||||
public class Basic
|
||||
{
|
||||
/// <summary>
|
||||
/// 画布
|
||||
/// </summary>
|
||||
public FlowCanvas canvas { get; set; }
|
||||
/// <summary>
|
||||
/// 版本
|
||||
/// </summary>
|
||||
public string versions { get; set; }
|
||||
}
|
||||
/// <summary>
|
||||
/// 画布
|
||||
/// </summary>
|
||||
public class FlowCanvas
|
||||
{
|
||||
/// <summary>
|
||||
/// 宽度
|
||||
/// </summary>
|
||||
public float width { get; set; }
|
||||
/// <summary>
|
||||
/// 高度
|
||||
/// </summary>
|
||||
public float lenght { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// DLL
|
||||
/// </summary>
|
||||
public class Library
|
||||
{
|
||||
/// <summary>
|
||||
/// DLL名称
|
||||
/// </summary>
|
||||
public string name { get; set; }
|
||||
/// <summary>
|
||||
/// 路径
|
||||
/// </summary>
|
||||
public string path { get; set; }
|
||||
/// <summary>
|
||||
/// 提示
|
||||
/// </summary>
|
||||
public string tips { get; set; }
|
||||
}
|
||||
/// <summary>
|
||||
/// 节点
|
||||
/// </summary>
|
||||
public class NodeInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// GUID
|
||||
/// </summary>
|
||||
public string guid { get; set; }
|
||||
/// <summary>
|
||||
/// 名称
|
||||
/// </summary>
|
||||
public string name { get; set; }
|
||||
/// <summary>
|
||||
/// 显示标签
|
||||
/// </summary>
|
||||
public string label { get; set; }
|
||||
/// <summary>
|
||||
/// 类型
|
||||
/// </summary>
|
||||
public string type { get; set; }
|
||||
/// <summary>
|
||||
/// 于画布中的位置
|
||||
/// </summary>
|
||||
public Position position { get; set; }
|
||||
/// <summary>
|
||||
/// 真分支节点GUID
|
||||
/// </summary>
|
||||
public string[] trueNodes { get; set; }
|
||||
/// <summary>
|
||||
/// 假分支节点
|
||||
/// </summary>
|
||||
public string[] falseNodes { get; set; }
|
||||
}
|
||||
/// <summary>
|
||||
/// 节点于画布中的位置
|
||||
/// </summary>
|
||||
public class Position
|
||||
{
|
||||
public float x { get; set; }
|
||||
public float y { get; set; }
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 区域
|
||||
/// </summary>
|
||||
public class Region
|
||||
{
|
||||
public string guid { get; set; }
|
||||
public NodeInfo[] childNodes { get; set; }
|
||||
}
|
||||
}
|
||||
16
WorkBench/Themes/Condition/BoolConditionControl.xaml
Normal file
16
WorkBench/Themes/Condition/BoolConditionControl.xaml
Normal file
@@ -0,0 +1,16 @@
|
||||
<UserControl x:Class="DynamicDemo.Themes.Condition.BoolConditionControl"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="clr-namespace:DynamicDemo.Themes.Condition"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="450" d:DesignWidth="800">
|
||||
<Grid>
|
||||
<ComboBox x:Name="ConditionComboBox"
|
||||
SelectedValue="{Binding Condition, Mode=TwoWay}">
|
||||
<ComboBoxItem Content="Is True" Tag="IsTrue" />
|
||||
<ComboBoxItem Content="Is False" Tag="IsFalse" />
|
||||
</ComboBox>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
28
WorkBench/Themes/Condition/BoolConditionControl.xaml.cs
Normal file
28
WorkBench/Themes/Condition/BoolConditionControl.xaml.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Windows.Navigation;
|
||||
using System.Windows.Shapes;
|
||||
|
||||
namespace DynamicDemo.Themes.Condition
|
||||
{
|
||||
/// <summary>
|
||||
/// BoolConditionControl.xaml 的交互逻辑
|
||||
/// </summary>
|
||||
public partial class BoolConditionControl : UserControl
|
||||
{
|
||||
public BoolConditionControl()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
||||
21
WorkBench/Themes/Condition/IntConditionControl.xaml
Normal file
21
WorkBench/Themes/Condition/IntConditionControl.xaml
Normal file
@@ -0,0 +1,21 @@
|
||||
<UserControl x:Class="DynamicDemo.Themes.Condition.IntConditionControl"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="clr-namespace:DynamicDemo.Themes.Condition"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="450" d:DesignWidth="800">
|
||||
<Grid>
|
||||
<ComboBox x:Name="ConditionComboBox"
|
||||
SelectedValue="{Binding Condition, Mode=TwoWay}">
|
||||
<ComboBoxItem Content="Greater Than" Tag="GreaterThan" />
|
||||
<ComboBoxItem Content="Less Than" Tag="LessThan" />
|
||||
<ComboBoxItem Content="Equal To" Tag="EqualTo" />
|
||||
<ComboBoxItem Content="Between" Tag="Between" />
|
||||
<ComboBoxItem Content="Not Between" Tag="NotBetween" />
|
||||
<ComboBoxItem Content="Not In Range" Tag="NotInRange" />
|
||||
</ComboBox>
|
||||
<TextBox x:Name="ValueTextBox" Text="{Binding Value, Mode=TwoWay}" />
|
||||
</Grid>
|
||||
</UserControl>
|
||||
28
WorkBench/Themes/Condition/IntConditionControl.xaml.cs
Normal file
28
WorkBench/Themes/Condition/IntConditionControl.xaml.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Windows.Navigation;
|
||||
using System.Windows.Shapes;
|
||||
|
||||
namespace DynamicDemo.Themes.Condition
|
||||
{
|
||||
/// <summary>
|
||||
/// IntConditionControl.xaml 的交互逻辑
|
||||
/// </summary>
|
||||
public partial class IntConditionControl : UserControl
|
||||
{
|
||||
public IntConditionControl()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
||||
88
WorkBench/Themes/Condition/Model.cs
Normal file
88
WorkBench/Themes/Condition/Model.cs
Normal file
@@ -0,0 +1,88 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace DynamicDemo.Themes.Condition
|
||||
{
|
||||
//public class IntConditionNode : ConditionNode
|
||||
//{
|
||||
// public int Value { get; set; }
|
||||
// public int MinValue { get; set; }
|
||||
// public int MaxValue { get; set; }
|
||||
// public List<int> ExcludeValues { get; set; }
|
||||
|
||||
// public override bool Evaluate(object value)
|
||||
// {
|
||||
// if (value is int intValue)
|
||||
// {
|
||||
// switch (Condition)
|
||||
// {
|
||||
// case ConditionType.GreaterThan:
|
||||
// return intValue > Value;
|
||||
// case ConditionType.LessThan:
|
||||
// return intValue < Value;
|
||||
// case ConditionType.EqualTo:
|
||||
// return intValue == Value;
|
||||
// case ConditionType.Between:
|
||||
// return intValue >= MinValue && intValue <= MaxValue;
|
||||
// case ConditionType.NotBetween:
|
||||
// return intValue < MinValue || intValue > MaxValue;
|
||||
// case ConditionType.NotInRange:
|
||||
// return !ExcludeValues.Contains(intValue);
|
||||
// default:
|
||||
// return false;
|
||||
// }
|
||||
// }
|
||||
// return false;
|
||||
// }
|
||||
//}
|
||||
|
||||
//public class BoolConditionNode : ConditionNode
|
||||
//{
|
||||
// public override bool Evaluate(object value)
|
||||
// {
|
||||
// if (value is bool boolValue)
|
||||
// {
|
||||
// switch (Condition)
|
||||
// {
|
||||
// case ConditionType.IsTrue:
|
||||
// return boolValue;
|
||||
// case ConditionType.IsFalse:
|
||||
// return !boolValue;
|
||||
// default:
|
||||
// return false;
|
||||
// }
|
||||
// }
|
||||
// return false;
|
||||
// }
|
||||
//}
|
||||
|
||||
|
||||
//public class StringConditionNode : ConditionNode
|
||||
//{
|
||||
// public string Substring { get; set; }
|
||||
|
||||
// public override bool Evaluate(object value)
|
||||
// {
|
||||
// if (value is string stringValue)
|
||||
// {
|
||||
// switch (Condition)
|
||||
// {
|
||||
// case ConditionType.Contains:
|
||||
// return stringValue.Contains(Substring);
|
||||
// case ConditionType.DoesNotContain:
|
||||
// return !stringValue.Contains(Substring);
|
||||
// case ConditionType.IsNotEmpty:
|
||||
// return !string.IsNullOrEmpty(stringValue);
|
||||
// default:
|
||||
// return false;
|
||||
// }
|
||||
// }
|
||||
// return false;
|
||||
// }
|
||||
//}
|
||||
|
||||
|
||||
}
|
||||
18
WorkBench/Themes/Condition/StringConditionControl.xaml
Normal file
18
WorkBench/Themes/Condition/StringConditionControl.xaml
Normal file
@@ -0,0 +1,18 @@
|
||||
<UserControl x:Class="DynamicDemo.Themes.Condition.StringConditionControl"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="clr-namespace:DynamicDemo.Themes.Condition"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="450" d:DesignWidth="800">
|
||||
<Grid>
|
||||
<ComboBox x:Name="ConditionComboBox"
|
||||
SelectedValue="{Binding Condition, Mode=TwoWay}">
|
||||
<ComboBoxItem Content="Contains" Tag="Contains" />
|
||||
<ComboBoxItem Content="Does Not Contain" Tag="DoesNotContain" />
|
||||
<ComboBoxItem Content="Is Not Empty" Tag="IsNotEmpty" />
|
||||
</ComboBox>
|
||||
<TextBox x:Name="SubstringTextBox" Text="{Binding Substring, Mode=TwoWay}" />
|
||||
</Grid>
|
||||
</UserControl>
|
||||
28
WorkBench/Themes/Condition/StringConditionControl.xaml.cs
Normal file
28
WorkBench/Themes/Condition/StringConditionControl.xaml.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Windows.Navigation;
|
||||
using System.Windows.Shapes;
|
||||
|
||||
namespace DynamicDemo.Themes.Condition
|
||||
{
|
||||
/// <summary>
|
||||
/// StringConditionControl.xaml 的交互逻辑
|
||||
/// </summary>
|
||||
public partial class StringConditionControl : UserControl
|
||||
{
|
||||
public StringConditionControl()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
||||
35
WorkBench/Themes/ConditionControl.xaml
Normal file
35
WorkBench/Themes/ConditionControl.xaml
Normal file
@@ -0,0 +1,35 @@
|
||||
<UserControl x:Class="DynamicDemo.Themes.ConditionControl"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="clr-namespace:DynamicDemo.Themes"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="450" d:DesignWidth="800">
|
||||
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
<!--<ComboBox x:Name="ConditionTypeComboBox" SelectionChanged="ConditionTypeComboBox_SelectionChanged">
|
||||
<ComboBoxItem Content="GreaterThan" Tag="{x:Static local:ConditionType.GreaterThan}"/>
|
||||
<ComboBoxItem Content="LessThan" Tag="{x:Static local:ConditionType.LessThan}"/>
|
||||
<ComboBoxItem Content="EqualTo" Tag="{x:Static local:ConditionType.EqualTo}"/>
|
||||
<ComboBoxItem Content="InRange" Tag="{x:Static local:ConditionType.InRange}"/>
|
||||
<ComboBoxItem Content="NotInRange" Tag="{x:Static local:ConditionType.NotInRange}"/>
|
||||
<ComboBoxItem Content="NotInSpecificRange" Tag="{x:Static local:ConditionType.NotInSpecificRange}"/>
|
||||
<ComboBoxItem Content="IsTrue" Tag="{x:Static local:ConditionType.IsTrue}"/>
|
||||
<ComboBoxItem Content="IsFalse" Tag="{x:Static local:ConditionType.IsFalse}"/>
|
||||
<ComboBoxItem Content="Contains" Tag="{x:Static local:ConditionType.Contains}"/>
|
||||
<ComboBoxItem Content="DoesNotContain" Tag="{x:Static local:ConditionType.DoesNotContain}"/>
|
||||
<ComboBoxItem Content="IsNotEmpty" Tag="{x:Static local:ConditionType.IsNotEmpty}"/>
|
||||
</ComboBox>
|
||||
<TextBox x:Name="ValueTextBox" Visibility="Collapsed"/>
|
||||
<TextBox x:Name="Value2TextBox" Visibility="Collapsed"/>-->
|
||||
|
||||
<StackPanel Grid.Row="0" x:Name="ConditionsPanel" Orientation="Vertical" Height="400"/>
|
||||
<Button Grid.Row="1" Content="Add Condition" Click="OnAddConditionClicked" />
|
||||
<!-- 其他控件 -->
|
||||
</Grid>
|
||||
</UserControl>
|
||||
85
WorkBench/Themes/ConditionControl.xaml.cs
Normal file
85
WorkBench/Themes/ConditionControl.xaml.cs
Normal file
@@ -0,0 +1,85 @@
|
||||
using DynamicDemo.Themes.Condition;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace DynamicDemo.Themes
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// ConditionControl.xaml 的交互逻辑
|
||||
/// </summary>
|
||||
public partial class ConditionControl : UserControl
|
||||
{
|
||||
public ConditionControl()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
//private void ConditionTypeComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||
//{
|
||||
// var selectedType = (ConditionType)((ComboBoxItem)ConditionTypeComboBox.SelectedItem).Tag;
|
||||
// UpdateInputVisibility(selectedType);
|
||||
//}
|
||||
|
||||
//private void UpdateInputVisibility(ConditionType type)
|
||||
//{
|
||||
// ValueTextBox.Visibility = Visibility.Collapsed;
|
||||
// Value2TextBox.Visibility = Visibility.Collapsed;
|
||||
|
||||
// switch (type)
|
||||
// {
|
||||
// case ConditionType.GreaterThan:
|
||||
// case ConditionType.LessThan:
|
||||
// case ConditionType.EqualTo:
|
||||
// case ConditionType.Contains:
|
||||
// case ConditionType.DoesNotContain:
|
||||
// ValueTextBox.Visibility = Visibility.Visible;
|
||||
// break;
|
||||
// case ConditionType.InRange:
|
||||
// case ConditionType.NotInRange:
|
||||
// ValueTextBox.Visibility = Visibility.Visible;
|
||||
// Value2TextBox.Visibility = Visibility.Visible;
|
||||
// break;
|
||||
// case ConditionType.IsTrue:
|
||||
// case ConditionType.IsFalse:
|
||||
// case ConditionType.IsNotEmpty:
|
||||
// // No additional input needed
|
||||
// break;
|
||||
// case ConditionType.NotInSpecificRange:
|
||||
// // Handle specific range input, possibly with a different control
|
||||
// break;
|
||||
// }
|
||||
//}
|
||||
|
||||
private void OnAddConditionClicked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
// 示例:添加一个IntConditionNode
|
||||
var intConditionNode = new IntConditionNode { Condition = ConditionType.GreaterThan, Value = 10 };
|
||||
AddConditionNode(intConditionNode);
|
||||
}
|
||||
|
||||
public void AddConditionNode(ConditionNode node)
|
||||
{
|
||||
UserControl control = null;
|
||||
|
||||
if (node is IntConditionNode)
|
||||
{
|
||||
control = new IntConditionControl { DataContext = node };
|
||||
}
|
||||
else if (node is BoolConditionNode)
|
||||
{
|
||||
control = new BoolConditionControl { DataContext = node };
|
||||
}
|
||||
else if (node is StringConditionNode)
|
||||
{
|
||||
control = new StringConditionControl { DataContext = node };
|
||||
}
|
||||
|
||||
if (control != null)
|
||||
{
|
||||
ConditionsPanel.Children.Add(control);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
99
WorkBench/Themes/ConditionControlModel.cs
Normal file
99
WorkBench/Themes/ConditionControlModel.cs
Normal file
@@ -0,0 +1,99 @@
|
||||
namespace DynamicDemo.Themes;
|
||||
|
||||
public enum ConditionType
|
||||
{
|
||||
GreaterThan,
|
||||
LessThan,
|
||||
EqualTo,
|
||||
Between,
|
||||
NotBetween,
|
||||
NotInRange,
|
||||
IsTrue,
|
||||
IsFalse,
|
||||
Contains,
|
||||
DoesNotContain,
|
||||
IsNotEmpty
|
||||
}
|
||||
|
||||
public abstract class ConditionNode
|
||||
{
|
||||
public ConditionType Condition { get; set; }
|
||||
public abstract bool Evaluate(object value);
|
||||
}
|
||||
|
||||
public class IntConditionNode : ConditionNode
|
||||
{
|
||||
public int Value { get; set; }
|
||||
public int MinValue { get; set; }
|
||||
public int MaxValue { get; set; }
|
||||
public List<int> ExcludeValues { get; set; }
|
||||
|
||||
public override bool Evaluate(object value)
|
||||
{
|
||||
if (value is int intValue)
|
||||
{
|
||||
switch (Condition)
|
||||
{
|
||||
case ConditionType.GreaterThan:
|
||||
return intValue > Value;
|
||||
case ConditionType.LessThan:
|
||||
return intValue < Value;
|
||||
case ConditionType.EqualTo:
|
||||
return intValue == Value;
|
||||
case ConditionType.Between:
|
||||
return intValue >= MinValue && intValue <= MaxValue;
|
||||
case ConditionType.NotBetween:
|
||||
return intValue < MinValue || intValue > MaxValue;
|
||||
case ConditionType.NotInRange:
|
||||
return !ExcludeValues.Contains(intValue);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public class BoolConditionNode : ConditionNode
|
||||
{
|
||||
public override bool Evaluate(object value)
|
||||
{
|
||||
if (value is bool boolValue)
|
||||
{
|
||||
switch (Condition)
|
||||
{
|
||||
case ConditionType.IsTrue:
|
||||
return boolValue;
|
||||
case ConditionType.IsFalse:
|
||||
return !boolValue;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public class StringConditionNode : ConditionNode
|
||||
{
|
||||
public string Substring { get; set; }
|
||||
|
||||
public override bool Evaluate(object value)
|
||||
{
|
||||
if (value is string stringValue)
|
||||
{
|
||||
switch (Condition)
|
||||
{
|
||||
case ConditionType.Contains:
|
||||
return stringValue.Contains(Substring);
|
||||
case ConditionType.DoesNotContain:
|
||||
return !stringValue.Contains(Substring);
|
||||
case ConditionType.IsNotEmpty:
|
||||
return !string.IsNullOrEmpty(stringValue);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
115
WorkBench/Themes/MethodDetailsControl.xaml
Normal file
115
WorkBench/Themes/MethodDetailsControl.xaml
Normal file
@@ -0,0 +1,115 @@
|
||||
<ResourceDictionary
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="clr-namespace:Serein.WorkBench.Themes"
|
||||
xmlns:sys="clr-namespace:System;assembly=mscorlib">
|
||||
|
||||
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
|
||||
<Style TargetType="{x:Type local:MethodDetailsControl}">
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="{x:Type local:MethodDetailsControl}">
|
||||
|
||||
<ItemsControl ItemsSource="{Binding MethodDetails.ExplicitDatas, RelativeSource={RelativeSource TemplatedParent}}">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<ContentControl Content="{Binding}">
|
||||
<ContentControl.Style>
|
||||
<Style TargetType="ContentControl">
|
||||
<Style.Triggers>
|
||||
<MultiDataTrigger>
|
||||
<MultiDataTrigger.Conditions>
|
||||
<Condition Binding="{Binding IsExplicitData}" Value="false" />
|
||||
</MultiDataTrigger.Conditions>
|
||||
<Setter Property="ContentTemplate">
|
||||
<Setter.Value>
|
||||
<DataTemplate>
|
||||
<Grid Background="#E3FDFD">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="50"/>
|
||||
<ColumnDefinition Width="30"/>
|
||||
<ColumnDefinition Width="50"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Grid.Column="0" Text="{Binding Index,StringFormat=agr{0}}" HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||
<CheckBox Grid.Column="1" IsChecked="{Binding IsExplicitData, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"></CheckBox>
|
||||
<TextBlock Grid.Column="2" MinWidth="50" Text="{Binding ParameterName}" HorizontalAlignment="Left" VerticalAlignment="Center"/>
|
||||
<TextBlock Grid.Column="3" MinWidth="50" Text="无须指定参数"/>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</MultiDataTrigger>
|
||||
|
||||
<MultiDataTrigger>
|
||||
<MultiDataTrigger.Conditions>
|
||||
<Condition Binding="{Binding IsExplicitData}" Value="true" />
|
||||
<Condition Binding="{Binding ExplicitTypeName}" Value="Select" />
|
||||
</MultiDataTrigger.Conditions>
|
||||
<Setter Property="ContentTemplate">
|
||||
<Setter.Value>
|
||||
<DataTemplate>
|
||||
<Grid Background="#E3FDFD">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="50"/>
|
||||
<ColumnDefinition Width="30"/>
|
||||
<ColumnDefinition Width="50"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Grid.Column="0" Text="{Binding Index,StringFormat=agr{0}}" HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||
<CheckBox Grid.Column="1" IsChecked="{Binding IsExplicitData, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"></CheckBox>
|
||||
<TextBlock Grid.Column="2" MinWidth="50" Text="{Binding ParameterName}" HorizontalAlignment="Left" VerticalAlignment="Center"/>
|
||||
<ComboBox Grid.Column="3"
|
||||
MinWidth="50"
|
||||
ItemsSource="{Binding Items}"
|
||||
SelectedItem="{Binding DataValue, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</MultiDataTrigger>
|
||||
|
||||
<MultiDataTrigger>
|
||||
<MultiDataTrigger.Conditions>
|
||||
<Condition Binding="{Binding IsExplicitData}" Value="true" />
|
||||
<Condition Binding="{Binding ExplicitTypeName}" Value="Value" />
|
||||
<!--<Condition Binding="{Binding ExplicitTypeName}" Value="{x:Type sys:String}" />
|
||||
<Condition Binding="{Binding ExplicitTypeName}" Value="{x:Type sys:Double}" />-->
|
||||
</MultiDataTrigger.Conditions>
|
||||
<Setter Property="ContentTemplate">
|
||||
<Setter.Value>
|
||||
<DataTemplate>
|
||||
<Grid Background="#E3FDFD">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="50"/>
|
||||
<ColumnDefinition Width="30"/>
|
||||
<ColumnDefinition Width="50"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Grid.Column="0" Text="{Binding Index,StringFormat=agr{0}}" HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||
<CheckBox Grid.Column="1" IsChecked="{Binding IsExplicitData, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"></CheckBox>
|
||||
<TextBlock Grid.Column="2" MinWidth="50" Text="{Binding ParameterName}" HorizontalAlignment="Left" VerticalAlignment="Center"/>
|
||||
<TextBox Grid.Column="3" MinWidth="50" Text="{Binding DataValue, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</MultiDataTrigger>
|
||||
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</ContentControl.Style>
|
||||
</ContentControl>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
</ResourceDictionary>
|
||||
74
WorkBench/Themes/MethodDetailsControl.xaml.cs
Normal file
74
WorkBench/Themes/MethodDetailsControl.xaml.cs
Normal file
@@ -0,0 +1,74 @@
|
||||
using Serein.DynamicFlow;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Windows.Navigation;
|
||||
using System.Windows.Shapes;
|
||||
|
||||
namespace Serein.WorkBench.Themes
|
||||
{
|
||||
public class MultiConditionConverter : IMultiValueConverter
|
||||
{
|
||||
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
if (values.Length == 2 && values[0] is Type valueType && values[1] is bool isEnabled)
|
||||
{
|
||||
if (isEnabled)
|
||||
{
|
||||
if (valueType == typeof(string) || valueType == typeof(int) || valueType == typeof(double))
|
||||
{
|
||||
return "TextBoxTemplate";
|
||||
}
|
||||
else if (typeof(IEnumerable).IsAssignableFrom(valueType))
|
||||
{
|
||||
return "ComboBoxTemplate";
|
||||
}
|
||||
}
|
||||
}
|
||||
return DependencyProperty.UnsetValue;
|
||||
}
|
||||
|
||||
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
public partial class MethodDetailsControl : UserControl//,ItemsControl
|
||||
{
|
||||
static MethodDetailsControl()
|
||||
{
|
||||
DefaultStyleKeyProperty.OverrideMetadata(typeof(MethodDetailsControl), new FrameworkPropertyMetadata(typeof(MethodDetailsControl)));
|
||||
}
|
||||
|
||||
|
||||
public MethodDetails MethodDetails
|
||||
{
|
||||
get { return (MethodDetails)GetValue(MethodDetailsProperty); }
|
||||
set { SetValue(MethodDetailsProperty, value); }
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty MethodDetailsProperty = DependencyProperty.Register("MethodDetails", typeof(MethodDetails),
|
||||
typeof(MethodDetailsControl), new PropertyMetadata(null, new PropertyChangedCallback(OnPropertyChange)));
|
||||
|
||||
static void OnPropertyChange(DependencyObject sender, DependencyPropertyChangedEventArgs args)
|
||||
{
|
||||
|
||||
var MethodDetails = (MethodDetails)args.NewValue;
|
||||
//MethodDetails.ExplicitDatas[0].
|
||||
}
|
||||
}
|
||||
}
|
||||
4
WorkBench/Themes/MultiConditionConverter.xaml
Normal file
4
WorkBench/Themes/MultiConditionConverter.xaml
Normal file
@@ -0,0 +1,4 @@
|
||||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
|
||||
</ResourceDictionary>
|
||||
15
WorkBench/Themes/TypeViewerWindow.xaml
Normal file
15
WorkBench/Themes/TypeViewerWindow.xaml
Normal file
@@ -0,0 +1,15 @@
|
||||
<Window x:Class="Serein.WorkBench.Themes.TypeViewerWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:local="clr-namespace:Serein.WorkBench.Themes"
|
||||
mc:Ignorable="d"
|
||||
Topmost="True"
|
||||
Title="TypeViewerWindow" Height="300" Width="300">
|
||||
<Grid>
|
||||
<Grid>
|
||||
<TreeView x:Name="TypeTreeView"/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Window>
|
||||
71
WorkBench/Themes/TypeViewerWindow.xaml.cs
Normal file
71
WorkBench/Themes/TypeViewerWindow.xaml.cs
Normal file
@@ -0,0 +1,71 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Windows.Shapes;
|
||||
|
||||
namespace Serein.WorkBench.Themes
|
||||
{
|
||||
/// <summary>
|
||||
/// TypeViewerWindow.xaml 的交互逻辑
|
||||
/// </summary>
|
||||
public partial class TypeViewerWindow : Window
|
||||
{
|
||||
public TypeViewerWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
public Type Type { get; set; }
|
||||
|
||||
public void LoadTypeInformation()
|
||||
{
|
||||
if (Type == null)
|
||||
return;
|
||||
|
||||
var rootNode = new TreeViewItem { Header = Type.Name };
|
||||
AddMembersToTreeNode(rootNode, Type);
|
||||
TypeTreeView.Items.Clear();
|
||||
TypeTreeView.Items.Add(rootNode);
|
||||
}
|
||||
|
||||
private void AddMembersToTreeNode(TreeViewItem node, Type type)
|
||||
{
|
||||
var members = type.GetMembers(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly);
|
||||
foreach (var member in members)
|
||||
{
|
||||
var memberNode = new TreeViewItem { Header = member.Name };
|
||||
if (member is PropertyInfo property)
|
||||
{
|
||||
var propertyType = property.PropertyType;
|
||||
memberNode.Header = $"{member.Name} : {propertyType.Name}";
|
||||
if (!propertyType.IsPrimitive && propertyType != typeof(string))
|
||||
{
|
||||
AddMembersToTreeNode(memberNode, propertyType);
|
||||
}
|
||||
}
|
||||
else if (member is MethodInfo method)
|
||||
{
|
||||
var parameters = method.GetParameters();
|
||||
var paramStr = string.Join(", ", parameters.Select(p => $"{p.ParameterType.Name} {p.Name}"));
|
||||
memberNode.Header = $"{member.Name}({paramStr})";
|
||||
}
|
||||
else if (member is FieldInfo field)
|
||||
{
|
||||
memberNode.Header = $"{member.Name} : {field.FieldType.Name}";
|
||||
}
|
||||
|
||||
node.Items.Add(memberNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
45
WorkBench/tool/LogTextWriter.cs
Normal file
45
WorkBench/tool/LogTextWriter.cs
Normal file
@@ -0,0 +1,45 @@
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
namespace Serein.WorkBench.tool
|
||||
{
|
||||
/// <summary>
|
||||
/// 可以捕获类库输出的打印输出
|
||||
/// </summary>
|
||||
public class LogTextWriter(Action<string> logAction) : TextWriter
|
||||
{
|
||||
private readonly Action<string> logAction = logAction;
|
||||
private readonly StringWriter stringWriter = new();
|
||||
|
||||
public override Encoding Encoding => Encoding.UTF8;
|
||||
|
||||
public override void Write(char value)
|
||||
{
|
||||
stringWriter.Write(value);
|
||||
if (value == '\n')
|
||||
{
|
||||
logAction(stringWriter.ToString());
|
||||
stringWriter.GetStringBuilder().Clear();
|
||||
}
|
||||
}
|
||||
|
||||
public override void Write(string? value)
|
||||
{
|
||||
if(string.IsNullOrWhiteSpace(value)) { return; }
|
||||
stringWriter.Write(value);
|
||||
if (value.Contains('\n'))
|
||||
{
|
||||
logAction(stringWriter.ToString());
|
||||
stringWriter.GetStringBuilder().Clear();
|
||||
}
|
||||
}
|
||||
|
||||
public override void WriteLine(string? value)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(value)) { return; }
|
||||
stringWriter.WriteLine(value);
|
||||
logAction(stringWriter.ToString());
|
||||
stringWriter.GetStringBuilder().Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user