GIT练习

This commit is contained in:
fengjiayi
2024-08-05 10:11:58 +08:00
parent 1b7d61c390
commit 75333e621f
84 changed files with 11677 additions and 0 deletions

63
.gitattributes vendored Normal file
View 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
View 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
View 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;
}
}
}

View 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;
}
}
}
}

View 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);
}
}

View 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;
}
}
}
}

View 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
{
}
}

View 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
{
}
}

View 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");
}

View 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;
}
}
}

View 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();
}
}
}

View 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);
// }*/
//}
}
}

View 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;
}
}
}

View 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
{
}
}

View 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]);
}
}*/

View 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);
// }
//}
}
}

View 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;
//}
}
}

View 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;
}
}
}
}

View 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);
// }
// }
// }
//}
}
}

View 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;
}
}
}

View 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.")
};
}
}

View 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;
}
}
}

View 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);
}
}
}
}

View 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
}

View 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
}
}

View 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();
}
}
}
}

View 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);
}*/
}

View 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
View 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
View 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
View 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
}
}

View 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
View 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;
}
}
}

View 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
View 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
View 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 ApiDb
// 初始化完成,已注入依赖项,可以开始逻辑上的操作
/*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
View File

@@ -0,0 +1,3 @@
一款基于WPFDotnet 8的流程可视化编辑器需二次开发
B站个人空间https://space.bilibili.com/33526379
第一次用git不太懂

37
SereinFlow.sln Normal file
View 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
View 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
View 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
View 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
View 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>

View 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
View 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

File diff suppressed because it is too large Load Diff

270
WorkBench/Node/NodeBase.cs Normal file
View 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;
}
}
*/

View 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>

View 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();
}
}
}

View 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>

View 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);
}
}
}
}
}

View 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>

View 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();
}
}
}

View 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>

View 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);
}
}
}
*/
}
}

View 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>

View 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);
}
}
}
}
}

View 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>

View 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();
}
}
}

View 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>

View 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");
//}
}
}

View 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));
}
}
}

View 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;
//}
}
}
}

View 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";
}
}
}

View 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;
}
}
}

View 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;
}
}
}

View 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();
}
}
}

View 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>

View 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; }
}
}

View 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>

View 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();
}
}
}

View 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>

View 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();
}
}
}

View 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;
// }
//}
}

View 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>

View 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();
}
}
}

View 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>

View 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);
}
}
}
}

View 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;
}
}

View 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>

View 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].
}
}
}

View 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>

View 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>

View 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);
}
}
}
}

View 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();
}
}
}