mirror of
https://gitee.com/langsisi_admin/serein-flow
synced 2026-03-03 00:00:49 +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