From 726383ef2d6a0005565980097743d12039ab1577 Mon Sep 17 00:00:00 2001
From: hjkl950217 <584880422@qq.com>
Date: Sat, 12 Sep 2020 19:49:41 +0800
Subject: [PATCH 01/15] =?UTF-8?q?=E5=AD=98=E6=A1=A3?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
CkTools/CkTools.sln | 14 ++
.../Extensions/ObjectExtensions.cs | 26 +-
.../BaseType/DictionaryExtensions.cs | 5 -
.../BaseType/ObjectExtensions.cs | 17 +-
.../BaseType/StringExtensions.cs | 104 ++++----
.../Aop/LogicChainStepAttribute.cs | 132 ++++++++++
CkTools/Src/CkTools.Nova/CkTools.Nova.csproj | 19 ++
CkTools/Src/CkTools.Nova/Entity/NullResult.cs | 9 +
.../Entity/StepContext.Static.Funcation.cs | 94 +++++++
.../Src/CkTools.Nova/Entity/StepContext.cs | 16 ++
.../Entity/StepContextExtension.cs | 28 +++
.../CkTools.Nova/Entity/StepContextGeneric.cs | 42 ++++
CkTools/Src/CkTools.Nova/Entity/StepEntity.cs | 32 +++
.../Factory/ILogicalChainFactory.cs | 32 +++
.../Factory/LogicalChainFactory.cs | 183 ++++++++++++++
.../CkTools.Nova/Helper/LogicalChainHelper.cs | 166 +++++++++++++
.../Src/CkTools.Nova/LogicChain/EndStep.cs | 15 ++
.../CkTools.Nova/LogicChain/EndStepGeneric.cs | 16 ++
CkTools/Src/CkTools.Nova/LogicChain/IStep.cs | 31 +++
.../CkTools.Nova/LogicChain/IStepGeneric.cs | 23 ++
.../Src/CkTools.Nova/LogicChain/StepBase.cs | 44 ++++
.../LogicChain/StepBaseGeneric.cs | 44 ++++
.../Middleware/LogicChainExtension.cs | 42 ++++
.../CkTools.Nova/Middleware/TaskMwHelper.cs | 35 +++
CkTools/Src/SDKPulishNuget.targets | 2 +-
CkTools/Test/CKTols.FP.Test/Maybe`1Test.cs | 6 +
.../BaseType/IEnumerableExtensionsTest.cs | 2 +-
.../Extensions/EnumerableExtensions.cs | 81 ++++++
.../Extensions/ObjectExtensionsTest.cs | 139 +++++++++++
.../Aop/LogicChainStepAttributeTest.cs | 85 +++++++
.../CkTools.Nova.Test.csproj | 12 +
.../Test/CkTools.Nova.Test/DI_Test/DI_Test.cs | 105 ++++++++
.../Factory/LogicalChainFactoryTest.cs | 12 +
.../LogicChain/LogicalChainHelperTest.cs | 234 ++++++++++++++++++
.../CkTools.Nova.Test/TestModel/TestConfig.cs | 12 +
.../CkTools.Nova.Test/TestModel/TestResult.cs | 8 +
.../TestModel/TestTaskEnum.cs | 16 ++
.../TestModel/Test_A_Step.cs | 36 +++
.../TestModel/Test_B_Step.cs | 33 +++
.../TestModel/Test_C_Step.cs | 42 ++++
40 files changed, 1913 insertions(+), 81 deletions(-)
create mode 100644 CkTools/Src/CkTools.Nova/Aop/LogicChainStepAttribute.cs
create mode 100644 CkTools/Src/CkTools.Nova/CkTools.Nova.csproj
create mode 100644 CkTools/Src/CkTools.Nova/Entity/NullResult.cs
create mode 100644 CkTools/Src/CkTools.Nova/Entity/StepContext.Static.Funcation.cs
create mode 100644 CkTools/Src/CkTools.Nova/Entity/StepContext.cs
create mode 100644 CkTools/Src/CkTools.Nova/Entity/StepContextExtension.cs
create mode 100644 CkTools/Src/CkTools.Nova/Entity/StepContextGeneric.cs
create mode 100644 CkTools/Src/CkTools.Nova/Entity/StepEntity.cs
create mode 100644 CkTools/Src/CkTools.Nova/Factory/ILogicalChainFactory.cs
create mode 100644 CkTools/Src/CkTools.Nova/Factory/LogicalChainFactory.cs
create mode 100644 CkTools/Src/CkTools.Nova/Helper/LogicalChainHelper.cs
create mode 100644 CkTools/Src/CkTools.Nova/LogicChain/EndStep.cs
create mode 100644 CkTools/Src/CkTools.Nova/LogicChain/EndStepGeneric.cs
create mode 100644 CkTools/Src/CkTools.Nova/LogicChain/IStep.cs
create mode 100644 CkTools/Src/CkTools.Nova/LogicChain/IStepGeneric.cs
create mode 100644 CkTools/Src/CkTools.Nova/LogicChain/StepBase.cs
create mode 100644 CkTools/Src/CkTools.Nova/LogicChain/StepBaseGeneric.cs
create mode 100644 CkTools/Src/CkTools.Nova/Middleware/LogicChainExtension.cs
create mode 100644 CkTools/Src/CkTools.Nova/Middleware/TaskMwHelper.cs
create mode 100644 CkTools/Test/CkTools.Abstraction.Test/Extensions/EnumerableExtensions.cs
create mode 100644 CkTools/Test/CkTools.Abstraction.Test/Extensions/ObjectExtensionsTest.cs
create mode 100644 CkTools/Test/CkTools.Nova.Test/Aop/LogicChainStepAttributeTest.cs
create mode 100644 CkTools/Test/CkTools.Nova.Test/CkTools.Nova.Test.csproj
create mode 100644 CkTools/Test/CkTools.Nova.Test/DI_Test/DI_Test.cs
create mode 100644 CkTools/Test/CkTools.Nova.Test/Factory/LogicalChainFactoryTest.cs
create mode 100644 CkTools/Test/CkTools.Nova.Test/LogicChain/LogicalChainHelperTest.cs
create mode 100644 CkTools/Test/CkTools.Nova.Test/TestModel/TestConfig.cs
create mode 100644 CkTools/Test/CkTools.Nova.Test/TestModel/TestResult.cs
create mode 100644 CkTools/Test/CkTools.Nova.Test/TestModel/TestTaskEnum.cs
create mode 100644 CkTools/Test/CkTools.Nova.Test/TestModel/Test_A_Step.cs
create mode 100644 CkTools/Test/CkTools.Nova.Test/TestModel/Test_B_Step.cs
create mode 100644 CkTools/Test/CkTools.Nova.Test/TestModel/Test_C_Step.cs
diff --git a/CkTools/CkTools.sln b/CkTools/CkTools.sln
index e65c386b..71d254e5 100644
--- a/CkTools/CkTools.sln
+++ b/CkTools/CkTools.sln
@@ -31,6 +31,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CkTools.Abstraction.Test",
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CKTools.BaseExtensions.Test", "Test\CKTools.BaseExtensions.Test\CKTools.BaseExtensions.Test.csproj", "{2C2E3545-4B13-4746-A702-0F999240818F}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CkTools.Nova", "Src\CkTools.Nova\CkTools.Nova.csproj", "{6713F0C2-BB89-463B-AEB6-00BBFEC6DE00}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CkTools.Nova.Test", "Test\CkTools.Nova.Test\CkTools.Nova.Test.csproj", "{5837DBA3-47A0-43BF-9346-F2C8878F6AC4}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -69,6 +73,14 @@ Global
{2C2E3545-4B13-4746-A702-0F999240818F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2C2E3545-4B13-4746-A702-0F999240818F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2C2E3545-4B13-4746-A702-0F999240818F}.Release|Any CPU.Build.0 = Release|Any CPU
+ {6713F0C2-BB89-463B-AEB6-00BBFEC6DE00}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {6713F0C2-BB89-463B-AEB6-00BBFEC6DE00}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {6713F0C2-BB89-463B-AEB6-00BBFEC6DE00}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {6713F0C2-BB89-463B-AEB6-00BBFEC6DE00}.Release|Any CPU.Build.0 = Release|Any CPU
+ {5837DBA3-47A0-43BF-9346-F2C8878F6AC4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {5837DBA3-47A0-43BF-9346-F2C8878F6AC4}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {5837DBA3-47A0-43BF-9346-F2C8878F6AC4}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {5837DBA3-47A0-43BF-9346-F2C8878F6AC4}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -82,6 +94,8 @@ Global
{759F7D6B-B88F-4B90-8DA6-9F6E8DD61DEC} = {D80CD124-05E6-4F2F-ADCD-328ACACA04E5}
{B9045104-1DE3-40C9-9DF2-7E896AEFE751} = {D80CD124-05E6-4F2F-ADCD-328ACACA04E5}
{2C2E3545-4B13-4746-A702-0F999240818F} = {D80CD124-05E6-4F2F-ADCD-328ACACA04E5}
+ {6713F0C2-BB89-463B-AEB6-00BBFEC6DE00} = {D3146C29-9318-45EC-B9BA-1EC7653421FB}
+ {5837DBA3-47A0-43BF-9346-F2C8878F6AC4} = {D80CD124-05E6-4F2F-ADCD-328ACACA04E5}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {AB04A57E-6447-4E76-862D-5D1B1638F2B6}
diff --git a/CkTools/Src/CkTools.Abstraction/Extensions/ObjectExtensions.cs b/CkTools/Src/CkTools.Abstraction/Extensions/ObjectExtensions.cs
index 329f4069..c73b36a9 100644
--- a/CkTools/Src/CkTools.Abstraction/Extensions/ObjectExtensions.cs
+++ b/CkTools/Src/CkTools.Abstraction/Extensions/ObjectExtensions.cs
@@ -1,5 +1,6 @@
using System.Collections;
using System.Collections.Generic;
+using System.Threading.Tasks;
namespace System
{
@@ -14,7 +15,8 @@ public static class ObjectExtensions
/// 要检查的对象
/// 抛出异常时,显示的参数名
/// 为null时抛出
- public static void CheckNullWithException(this T obj, string paramName)
+ public static void CheckNullWithException(this T? obj, string paramName)
+ where T : class
{
if (obj == null) throw new ArgumentNullException(paramName);
}
@@ -27,7 +29,8 @@ public static void CheckNullWithException(this T obj, string paramName)
/// 抛出异常时,显示的参数名
/// 抛出异常时,显示的错误信息
/// 为null时抛出
- public static void CheckNullWithException(this T obj, string paramName, string message)
+ public static void CheckNullWithException(this T? obj, string paramName, string message)
+ where T : class
{
if (obj == null) throw new ArgumentNullException(paramName, message);
}
@@ -38,7 +41,7 @@ public static void CheckNullWithException(this T obj, string paramName, strin
/// 要检查的对象
/// 抛出异常时,显示的参数名
/// 为null或emtpy时抛出
- public static void CheckNullOrEmptyWithException(this IEnumerable obj, string paramName)
+ public static void CheckNullOrEmptyWithException(this IEnumerable? obj, string paramName)
{
if (obj.IsNullOrEmpty()) throw new ArgumentNullException(paramName);
}
@@ -50,7 +53,7 @@ public static void CheckNullOrEmptyWithException(this IEnumerable obj, string pa
/// 抛出异常时,显示的参数名
/// 抛出异常时,显示的错误信息
/// 为null或emtpy时抛出
- public static void CheckNullOrEmptyWithException(this IEnumerable obj, string paramName, string message)
+ public static void CheckNullOrEmptyWithException(this IEnumerable? obj, string paramName, string message)
{
if (obj.IsNullOrEmpty()) throw new ArgumentNullException(paramName, message);
}
@@ -108,7 +111,7 @@ public static bool IsNull(this object value)
/// 对象类型
/// 要判断的数组
/// 判断结果,null或空数组返回true,否则返回false
- public static bool IsNullOrEmpty(this T[] value)
+ public static bool IsNullOrEmpty(this T[]? value)
{
return value == null || value.Length == 0;
}
@@ -140,7 +143,7 @@ public static bool IsNullOrEmpty(this IDictionary value)
///
/// 要判断的字典
/// 判断结果,null或空枚举器返回true,否则返回false
- public static bool IsNullOrEmpty(this IEnumerable value)
+ public static bool IsNullOrEmpty(this IEnumerable? value)
{
return value == null
|| !value.GetEnumerator().MoveNext();
@@ -217,5 +220,16 @@ public static bool IsNotNullOrEmpty(this IEnumerable value)
}
#endregion IsNotNull and IsNotNullOrEmpty
+
+ ///
+ /// 转换为类型
+ ///
+ ///
+ ///
+ ///
+ public static Task ToTask(this TResult obj)
+ {
+ return Task.FromResult(obj);
+ }
}
}
\ No newline at end of file
diff --git a/CkTools/Src/CkTools.BaseExtensions/BaseType/DictionaryExtensions.cs b/CkTools/Src/CkTools.BaseExtensions/BaseType/DictionaryExtensions.cs
index 8d50832b..b92656b7 100644
--- a/CkTools/Src/CkTools.BaseExtensions/BaseType/DictionaryExtensions.cs
+++ b/CkTools/Src/CkTools.BaseExtensions/BaseType/DictionaryExtensions.cs
@@ -15,7 +15,6 @@ public static TValue GetOrDefault(
TKey key,
TValue defaultValue = default)
{
- key.CheckNullWithException(nameof(key));
if (valuePairs.IsNullOrEmpty()) { return defaultValue; }
bool isExist = valuePairs.TryGetValue(key, out TValue value);
@@ -37,8 +36,6 @@ public static bool TryAdd(
TKey key,
TValue value)
{
- key.CheckNullWithException(nameof(key));
- value.CheckNullWithException(nameof(value));
valuePairs.CheckNullWithException(nameof(valuePairs));
bool isExist = valuePairs.ContainsKey(key);
@@ -83,7 +80,6 @@ public static TValue GetOrAdd(
Func factory)
{
- key.CheckNullWithException(nameof(key));
valuePairs.CheckNullWithException(nameof(valuePairs));
factory.CheckNullWithException(nameof(factory));
@@ -168,7 +164,6 @@ public static TValue AddOrUpdate(
TValue value)
{
valuePairs.CheckNullWithException(nameof(valuePairs));
- value.CheckNullWithException(nameof(value));
if (valuePairs.ContainsKey(key))
{
diff --git a/CkTools/Src/CkTools.BaseExtensions/BaseType/ObjectExtensions.cs b/CkTools/Src/CkTools.BaseExtensions/BaseType/ObjectExtensions.cs
index 485f3b26..62471d57 100644
--- a/CkTools/Src/CkTools.BaseExtensions/BaseType/ObjectExtensions.cs
+++ b/CkTools/Src/CkTools.BaseExtensions/BaseType/ObjectExtensions.cs
@@ -65,7 +65,7 @@ public static string ToJsonExt(this T obj, Type type)
#endregion Tojson
- public static T DeepCopy(this T obj)
+ public static T? DeepCopy(this T obj)
where T : class
{
string outPut = obj.ToJsonExt();
@@ -175,21 +175,6 @@ private static IEnumerable> GetAllPropertyValueIter
#region 基础类型与Object之间的转换
- ///
- /// 转换为byte[]
- ///
- ///
- /// 编码格式,默认
- ///
- public static byte[] ToBytes(this T source, Encoding? encoding = null)
- {
- source.CheckNullWithException(nameof(source));
-
- string objStr = JsonConvert.SerializeObject(source, JsonSerializerSettingConst.StorageSetting);
- encoding = encoding ?? Encoding.UTF8;
- return objStr.ToBytes(encoding);
- }
-
public static int ToInt32(this T str)
where T : class
{
diff --git a/CkTools/Src/CkTools.BaseExtensions/BaseType/StringExtensions.cs b/CkTools/Src/CkTools.BaseExtensions/BaseType/StringExtensions.cs
index 8b0600d1..d01d5750 100644
--- a/CkTools/Src/CkTools.BaseExtensions/BaseType/StringExtensions.cs
+++ b/CkTools/Src/CkTools.BaseExtensions/BaseType/StringExtensions.cs
@@ -27,7 +27,7 @@ public static T ToObjectExt(
this string jsonStr,
JsonSerializerSettings? jsonSerializerSettings = null)
{
- jsonSerializerSettings = jsonSerializerSettings ?? JsonSerializerSettingConst.DefaultSetting;
+ jsonSerializerSettings ??= JsonSerializerSettingConst.DefaultSetting;
return JsonConvert.DeserializeObject(jsonStr, jsonSerializerSettings);
}
@@ -77,39 +77,6 @@ public static string ToCountryName(this string countryName)
}
};
- ///
- /// 基础转换,转换失败时会报错
- ///
- ///
- /// 要转换的字符串
- /// 转换的方法
- /// 类型为 的值
- /// The parameter 'str' is invalid、Empty、Null
- private static TValue BaseConvert(
- this string str,
- Func convert)
- {
- Func convertTemp = t => { emptyWithException(t); return convert(t); };
- return str.BaseConvert(convertTemp);
- }
-
- ///
- /// 基础转换,转换失败时会返回默认值
- ///
- ///
- /// 要转换的字符串
- /// 默认值
- /// 转换的方法
- /// 类型为 的值
- private static TValue TryBaseConvert(
- this string str,
- Func convert,
- TValue defaultValue = default)
- {
- Func convertTemp = t => { emptyWithException(t); return convert(t); };
- return str.BaseConvertOrDefalut(defaultValue, convertTemp);
- }
-
public static int ToInt32(this string str)
{
return str.BaseConvert(System.Convert.ToInt32);
@@ -136,8 +103,8 @@ public static decimal ToDecimal(this string str)
}
public static decimal ToDecimalOrDefault(
- this string str,
- decimal defaultValue = 0.00M)
+ this string str,
+ decimal defaultValue = 0.00M)
{
return str.TryBaseConvert(System.Convert.ToDecimal, defaultValue);
}
@@ -148,29 +115,57 @@ public static double ToDouble(this string str)
}
public static double ToDoubleOrDefault(
- this string str,
- double defaultValue = 0.00)
+ this string str,
+ double defaultValue = 0.00)
{
return str.TryBaseConvert(System.Convert.ToDouble, defaultValue);
}
- #region TryToDateTimeOffset
+ public static long ToLong(this string str, long defaultValue = 0L)
+ {
+ return str.TryBaseConvert(TypeConvertDelegate.stringToLong, defaultValue);
+ }
///
- /// 标准时间格式中包含的符号(用于和long区分使用)
+ /// 基础转换,转换失败时会报错
///
- private static string[] timeSysmbols = new string[] { ":", "+", "T", "Z", "-", "/" };
+ ///
+ /// 要转换的字符串
+ /// 转换的方法
+ /// 类型为 的值
+ /// The parameter 'str' is invalid、Empty、Null
+ private static TValue BaseConvert(
+ this string str,
+ Func convert)
+ {
+ Func convertTemp = t => { emptyWithException(t); return convert(t); };
+ return str.BaseConvert(convertTemp);
+ }
- private static DateTimeOffset TryToDateTimeOffsetBase(this string str, Func convert)
+ ///
+ /// 基础转换,转换失败时会返回默认值
+ ///
+ ///
+ /// 要转换的字符串
+ /// 默认值
+ /// 转换的方法
+ /// 类型为 的值
+ private static TValue TryBaseConvert(
+ this string str,
+ Func convert,
+ TValue defaultValue = default)
{
- return str switch
- {
- string a when a.IsNullOrEmpty() => DateTimeOffset.MinValue,
- var a when a.ContainsSymbol(timeSysmbols) => str.TryBaseConvert(TypeConvertDelegate.stringToDateTimeOffset),
- _ => str.TryBaseConvert(convert)//匹配不上则为long
- };
+ Func convertTemp = t => { emptyWithException(t); return convert(t); };
+ return str.BaseConvertOrDefalut(defaultValue, convertTemp);
}
+ #region TryToDateTimeOffset
+
+ ///
+ /// 标准时间格式中包含的符号(用于和long区分使用)
+ ///
+ private static string[] timeSysmbols = new string[] { ":", "+", "T", "Z", "-", "/" };
+
public static DateTimeOffset TryToDateTimeOffset(this string str)
{
return str.TryBaseConvert(TypeConvertDelegate.stringToDateTimeOffset);
@@ -196,13 +191,18 @@ public static DateTimeOffset TryToLocalDateTimeOffsetByMilliseconds(this string
return str.TryToDateTimeOffsetBase(TypeConvertDelegate.longStringToLocalDateTimeOffsetByMilliseconds);
}
- #endregion TryToDateTimeOffset
-
- public static long ToLong(this string str, long defaultValue = 0L)
+ private static DateTimeOffset TryToDateTimeOffsetBase(this string str, Func convert)
{
- return str.TryBaseConvert(TypeConvertDelegate.stringToLong, defaultValue);
+ return str switch
+ {
+ string a when a.IsNullOrEmpty() => DateTimeOffset.MinValue,
+ var a when a.ContainsSymbol(timeSysmbols) => str.TryBaseConvert(TypeConvertDelegate.stringToDateTimeOffset),
+ _ => str.TryBaseConvert(convert)//匹配不上则为long
+ };
}
+ #endregion TryToDateTimeOffset
+
#endregion 基础类型与string之间的转换
///
diff --git a/CkTools/Src/CkTools.Nova/Aop/LogicChainStepAttribute.cs b/CkTools/Src/CkTools.Nova/Aop/LogicChainStepAttribute.cs
new file mode 100644
index 00000000..03146ac5
--- /dev/null
+++ b/CkTools/Src/CkTools.Nova/Aop/LogicChainStepAttribute.cs
@@ -0,0 +1,132 @@
+using CkTools.Nova.Entity;
+using CkTools.Nova.LogicChain;
+using Microsoft.Extensions.DependencyInjection;
+using System;
+using System.Diagnostics.CodeAnalysis;
+
+namespace CkTools.Nova.Aop
+{
+ ///
+ /// 步骤链-自动注册标记
+ ///
+ [AttributeUsage(AttributeTargets.Class | AttributeTargets.Enum, Inherited = false, AllowMultiple = false)]
+ public class StepAttribute : Attribute
+ {
+ #region 构造方法
+
+ ///
+ /// 初始化一个实例
+ ///
+ /// 步骤枚举值
+ public StepAttribute([NotNull] object stepEnumValue)
+ : this(stepEnumValue, ServiceLifetime.Singleton) { }
+
+ ///
+ /// 初始化一个实例
+ ///
+ /// 步骤枚举值
+ ///
+ /// 表示中的类型。
+ ///
+ /// 如果你继承的是,建议指定此属性,提高代码阅读性.
+ ///
+ /// 如果你继承的是,则不用指定此属性,框架会自动赋值.
+ ///
+ public StepAttribute([NotNull] object stepEnumValue, Type contextResultType)
+ : this(stepEnumValue, contextResultType, ServiceLifetime.Singleton) { }
+
+ ///
+ /// 初始化一个实例
+ ///
+ /// 步骤枚举值
+ ///
+ /// 表示在DI中的生命周期配置。默认为
+ ///
+ public StepAttribute([NotNull] object stepEnumValue, ServiceLifetime lifetime)
+ : this(stepEnumValue, null, lifetime) { }
+
+ ///
+ /// 初始化一个实例
+ ///
+ /// 步骤枚举值
+ ///
+ /// 表示中的类型。
+ ///
+ /// 如果你继承的是,建议指定此属性,提高代码阅读性.
+ ///
+ /// 如果你继承的是,则不用指定此属性,框架会自动赋值.
+ ///
+ ///
+ /// 表示在DI中的生命周期配置。默认为
+ ///
+ public StepAttribute(
+ [NotNull] object stepEnumValue,
+ Type? contextResultType,
+ ServiceLifetime lifetime)
+ {
+ #region 检测
+
+ stepEnumValue.CheckNullWithException(nameof(stepEnumValue));
+
+ if (stepEnumValue.GetType().IsEnum == false)
+
+ {
+ throw new TypeAccessException($"{nameof(StepAttribute)} initialization need Enum type.{nameof(stepEnumValue)}'s type is {stepEnumValue.GetType().Name}");
+ }
+
+ #endregion 检测
+
+ this.StepEnumType = stepEnumValue.GetType();
+ this.StepEnumOrder = (int)stepEnumValue;
+
+ this.ContextResultType = contextResultType;
+ this.Lifetime = lifetime;
+ }
+
+ #endregion 构造方法
+
+ ///
+ /// 实现步骤类上面的打的步骤枚举类型
+ ///
+ public Type StepEnumType { get; }
+
+ ///
+ /// 实现步骤类上面的打的步骤枚举值
+ ///
+ public int StepEnumOrder { get; }
+
+ ///
+ /// 表示中的类型
+ ///
+ public Type? ContextResultType { get; set; }
+
+ public Enum StepEnumValue { get; set; }
+
+ ///
+ /// 代表步骤实现的生命周期
+ ///
+ ///
+ /// 用来向IOC注册时指定作用域
+ ///
+ public ServiceLifetime Lifetime { get; }
+
+ /*
+ * todo:特性重构:
+ *
+ * 目标:
+ * 1. 无返回值的只需要打步骤枚举即可
+ * 2. 有返回值的
+ * 可以打在实现类上
+ * 也可以打在返回结果的类型上面
+ *
+ * 如果都有,以返回结果的类型上面为准(但是这一点最后可以询问下群里,那种会比较爽)
+ *
+ *
+ * 3.上面修改完成之后,需要修改UT,还有EndStep<>的获取。
+ *
+ *
+ * 影响: 特性类要改名,2种情况的要分开了(可考虑要不要继承)
+ * 创建的工厂也需要修改,改查找的地方,然后判断用实现类的还是结果类上面的
+ */
+ }
+}
\ No newline at end of file
diff --git a/CkTools/Src/CkTools.Nova/CkTools.Nova.csproj b/CkTools/Src/CkTools.Nova/CkTools.Nova.csproj
new file mode 100644
index 00000000..e6b5065f
--- /dev/null
+++ b/CkTools/Src/CkTools.Nova/CkTools.Nova.csproj
@@ -0,0 +1,19 @@
+
+
+
+
+
+ netstandard2.1
+ $(LibName)的逻辑链,在业务中使用中间件模式,可动态配置执行顺序,随意修改节点逻辑
+ https://github.com/hjkl950217/learningTest
+ https://github.com/hjkl950217/learningTest
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/CkTools/Src/CkTools.Nova/Entity/NullResult.cs b/CkTools/Src/CkTools.Nova/Entity/NullResult.cs
new file mode 100644
index 00000000..7689b3df
--- /dev/null
+++ b/CkTools/Src/CkTools.Nova/Entity/NullResult.cs
@@ -0,0 +1,9 @@
+namespace CkTools.Nova.Entity
+{
+ ///
+ /// 空类,用作框架操作无返回值的情况
+ ///
+ public class NullResult
+ {
+ }
+}
\ No newline at end of file
diff --git a/CkTools/Src/CkTools.Nova/Entity/StepContext.Static.Funcation.cs b/CkTools/Src/CkTools.Nova/Entity/StepContext.Static.Funcation.cs
new file mode 100644
index 00000000..5bfdb976
--- /dev/null
+++ b/CkTools/Src/CkTools.Nova/Entity/StepContext.Static.Funcation.cs
@@ -0,0 +1,94 @@
+namespace CkTools.Nova.Entity
+{
+ //一些静态方法
+ //类似微软一样,Task.XX()
+ public abstract partial class StepContext
+ {
+ ///
+ /// 一个,无于返回值的场景。
+ ///
+ public static readonly NullResult DefualtNullResult = new NullResult();
+
+ #region 创建StepContext
+
+ /////
+ ///// (未启用)创建一个新的.
+ ///// 里面的数据初始化部分不会负责
+ /////
+ /////
+ ///// 此功能需要配合自动注册,暂未开发
+ /////
+ ///// 上下文中返回的类型
+ ///// DI实例
+ /////
+ //public static StepContext CreateContext(IServiceProvider di)
+ // where TResult:class
+ //{
+ // Type strType = typeof(TResult);//获取泛型的结构
+ // Type genericType = typeof(StepContext<>);//获取泛型上下文定义的结构
+ // Type genericType2 = genericType.MakeGenericType(strType);//创建泛型上下文接口
+
+ // //获取实例
+ // StepContext taskContext = di.GetService(genericType2) as StepContext;
+
+ // //初始化结果数据
+ // bool isNullResult = taskContext.Result.GetHashCode() == new object().GetHashCode();
+ // if (isNullResult)
+ // {
+ // taskContext.ResultEntiy = di.GetService();
+ // }
+
+ // return taskContext;
+ //}
+
+ /////
+ ///// 创建一个新的.
+ ///// 里面的数据初始化部分不会负责
+ /////
+ ///// 上下文中返回的类型
+ /////
+ //public static StepContext CreateContext()
+ // where TResult : class
+ //{
+ // StepContext tt = new StepContext
+ // {
+ // ResultEntiy = default
+ // };
+
+ // return tt;
+ //}
+
+ ///
+ /// 创建一个新的.
+ ///
+ /// 上下文中结果的类型
+ /// 要初始化到上下文中的结果
+ ///
+ /// 表示初始化后的上下文是否已经完成任务。用来初始化属性
+ ///
+ /// 初始化后的
+ public static StepContext CreateContext(
+ TResult initObject,
+ bool completed = false)
+ where TResult : class
+ {
+ StepContext context = new StepContext(initObject, completed);
+
+ return context;
+ }
+
+ ///
+ /// 创建一个新的.
+ ///
+ ///
+ /// 表示初始化后的上下文是否已经完成任务。用来初始化属性
+ ///
+ /// 初始化后的
+ public static CkTools.Nova.Entity.StepContext CreateContext(bool completed = false)
+ {
+ return CkTools.Nova.Entity.StepContext.CreateContext(CkTools.Nova.Entity.StepContext.DefualtNullResult, completed);
+ }
+
+ #endregion 创建StepContext
+ }
+}
\ No newline at end of file
diff --git a/CkTools/Src/CkTools.Nova/Entity/StepContext.cs b/CkTools/Src/CkTools.Nova/Entity/StepContext.cs
new file mode 100644
index 00000000..0026e5a1
--- /dev/null
+++ b/CkTools/Src/CkTools.Nova/Entity/StepContext.cs
@@ -0,0 +1,16 @@
+namespace CkTools.Nova.Entity
+{
+ ///
+ /// 执行逻辑链的上下文
+ ///
+ public abstract partial class StepContext
+ {
+ ///
+ /// 是否分析完成
+ ///
+ ///
+ /// 不建议手动设置此属性的值。
+ ///
+ public virtual bool ProcessCompleted { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/CkTools/Src/CkTools.Nova/Entity/StepContextExtension.cs b/CkTools/Src/CkTools.Nova/Entity/StepContextExtension.cs
new file mode 100644
index 00000000..6a91957c
--- /dev/null
+++ b/CkTools/Src/CkTools.Nova/Entity/StepContextExtension.cs
@@ -0,0 +1,28 @@
+namespace CkTools.Nova.Entity
+{
+ ///
+ /// 针对及其泛型的扩展方法
+ ///
+ public static class StepContextExtension
+ {
+ #region 获取TaskContext
+
+ ///
+ /// 获取泛型的上下文
+ ///
+ ///
+ ///
+ ///
+ /// 转换逻辑同As关键字
+ /// 因为使用不太方便,所以提供了此方法,代替了自己转换
+ /// 能转换的前提是已经是类型的上下文了。
+ ///
+ ///
+ public static StepContext As(this CkTools.Nova.Entity.StepContext taskContext)
+ {
+ return taskContext as StepContext;
+ }
+
+ #endregion 获取TaskContext
+ }
+}
\ No newline at end of file
diff --git a/CkTools/Src/CkTools.Nova/Entity/StepContextGeneric.cs b/CkTools/Src/CkTools.Nova/Entity/StepContextGeneric.cs
new file mode 100644
index 00000000..44b3f8c2
--- /dev/null
+++ b/CkTools/Src/CkTools.Nova/Entity/StepContextGeneric.cs
@@ -0,0 +1,42 @@
+using CkTools.Nova.LogicChain;
+
+namespace CkTools.Nova.Entity
+{
+ ///
+ /// 执行逻辑链的泛型上下文
+ ///
+ public class StepContext : StepContext
+ {
+ ///
+ /// 初始化一个实例
+ ///
+ /// 初始化到上下文实例中的结果
+ /// 初始化到上下文中的处理结果
+ public StepContext(TResult result, bool completed)
+ {
+ this.Result = result;
+ base.ProcessCompleted = completed;
+ }
+
+ ///
+ /// 初始化一个实例
+ ///
+ /// 初始化到上下文实例中的结果
+ public StepContext(TResult result) : this(result, false)
+ {
+ }
+
+ ///
+ /// 初始化一个实例
+ ///
+ public StepContext() : this(default, false)
+ {
+ }
+
+ ///
+ /// 当前上下文中的返回数据
+ ///
+ ///
+ public TResult Result { get; internal set; }
+ }
+}
\ No newline at end of file
diff --git a/CkTools/Src/CkTools.Nova/Entity/StepEntity.cs b/CkTools/Src/CkTools.Nova/Entity/StepEntity.cs
new file mode 100644
index 00000000..3987e1a0
--- /dev/null
+++ b/CkTools/Src/CkTools.Nova/Entity/StepEntity.cs
@@ -0,0 +1,32 @@
+using CkTools.Nova.Aop;
+using CkTools.Nova.LogicChain;
+using System;
+
+namespace CkTools.Nova.Entity
+{
+ ///
+ /// 注册、排序过程中使用的最小单位数据。 框架使用
+ ///
+ ///
+ /// 在注册、排序过程中有很多上下文的关系,这里用一个类装起来。
+ /// 每个都是代表单步操作的数据
+ ///
+ public class StepEntity
+ {
+ ///
+ /// 逻辑链注册的特性
+ ///
+ public StepAttribute Attribute { get; set; }
+
+ ///
+ /// 实现类的数据。
+ /// 只会是直接或间接继承接口或接口的类
+ ///
+ public Type StepType { get; set; }
+
+ ///
+ /// 步骤实例对象
+ ///
+ public IStep StepInstanceObject { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/CkTools/Src/CkTools.Nova/Factory/ILogicalChainFactory.cs b/CkTools/Src/CkTools.Nova/Factory/ILogicalChainFactory.cs
new file mode 100644
index 00000000..63a21d06
--- /dev/null
+++ b/CkTools/Src/CkTools.Nova/Factory/ILogicalChainFactory.cs
@@ -0,0 +1,32 @@
+using CkTools.Nova.Entity;
+using CkTools.Nova.LogicChain;
+using System;
+using System.Collections.Generic;
+
+namespace CkTools.Nova.Factory
+{
+ ///
+ /// 逻辑链工厂接口
+ ///
+ public interface INovaFactory
+ {
+ ///
+ /// 按不同的步骤枚举类型,获取第一个可执行的步骤实现
+ ///
+ ///
+ ///
+ IStep GetFirstTask();
+
+ ///
+ /// 获取所有的步骤枚举和对应的实现数据
+ ///
+ /// 帮助整理的集合。默认返回工厂缓存的数据
+ ///
+ /// 此方法提供给调试时使用,开发使用方法即可。
+ /// 如果是启动时调用:会扫描程序集然后注册、添加、排序等等。
+ /// 如果是启动后调用:会返回缓存中的数据。
+ ///
+ ///
+ Dictionary GetNovaList(List? taskList = null);
+ }
+}
\ No newline at end of file
diff --git a/CkTools/Src/CkTools.Nova/Factory/LogicalChainFactory.cs b/CkTools/Src/CkTools.Nova/Factory/LogicalChainFactory.cs
new file mode 100644
index 00000000..d84f42a6
--- /dev/null
+++ b/CkTools/Src/CkTools.Nova/Factory/LogicalChainFactory.cs
@@ -0,0 +1,183 @@
+using CkTools.Nova.Aop;
+using CkTools.Nova.Entity;
+using CkTools.Nova.Helper;
+using CkTools.Nova.LogicChain;
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace CkTools.Nova.Factory
+{
+ ///
+ /// 接口的默认实现
+ ///
+ public class NovaFactory : INovaFactory
+ {
+ ///
+ /// 放存步骤中间件的字典。
+ /// key为步骤枚举的类型
+ /// value为步骤枚举对应的步骤实现,已经排列好调用顺序,第一个既可执行
+ ///
+ private readonly ConcurrentDictionary taskList;
+
+ ///
+ /// DI实例
+ ///
+ private readonly IServiceProvider di;
+
+ ///
+ /// 初始化一个 实例
+ ///
+ /// DI实例
+ ///
+ /// 需要处理的 集合。可调用 获得
+ ///
+ public NovaFactory(IServiceProvider serviceProvider, List taskList)
+ {
+ this.di = serviceProvider;
+
+ var tempTaskList = this.GetNovaList(taskList);
+ this.taskList = new ConcurrentDictionary(tempTaskList);
+ }
+
+ ///
+ /// 按获取第一个可执行的任务实例。
+ ///
+ ///
+ ///
+ public IStep GetFirstTask()
+ {
+ StepEntity[] taskEntity = this.taskList.GetOrAdd(
+ typeof(TEnumType),
+ type => this.GetTaskEntity(type)
+ );
+
+ return taskEntity
+ .FirstOrDefault()
+ ?.StepInstanceObject;
+ }
+
+ #region 获取、排列
+
+ ///
+ /// 获取所有的步骤枚举和对应的实现数据
+ ///
+ /// 帮助整理的集合。默认返回工厂缓存的数据
+ ///
+ /// 此方法提供给调试时使用,开发使用方法即可。
+ /// 如果是启动时调用:会扫描程序集然后注册、添加、排序等等。
+ /// 如果是启动后调用:会返回缓存中的数据。
+ ///
+ ///
+ public Dictionary GetNovaList(List? taskList = null)
+ {
+ if (this.taskList.IsNullOrEmpty())
+ {
+ //1.获取Type数据
+ taskList ??= this.A_FindAllTaskEntity();
+
+ //2.创建或获取接口
+ taskList = this.B_CreateInstance(taskList, this.di);
+
+ //3.排序-排列接口上的Next
+ return this.C_SortAndPermutation(taskList);
+ }
+ else
+ {
+ return this.taskList.ToDictionary(t => t.Key, t => t.Value);
+ }
+ }
+
+ ///
+ /// 整理、获取所有的Task
+ ///
+ ///
+ private StepEntity[] GetTaskEntity(Type enumType)
+ {
+ //1.获取Type数据
+ List tempTaskList = this.A_FindAllTaskEntity();
+
+ //2.创建或获取接口
+ this.B_CreateInstance(tempTaskList, this.di);
+
+ //3.排序-排列接口上的Next
+ //分组-以type为索引
+ var taskGroup = tempTaskList.FindAll(t => t.Attribute.StepEnumType == enumType);
+
+ //排列next属性
+ return LogicalChainHelper.SortList(taskGroup);
+ }
+
+ ///
+ /// 找出所有程序集中所有包含的类数据
+ ///
+ ///
+ private List A_FindAllTaskEntity()
+ {
+ return LogicalChainHelper.FindAllTaskEntity();
+ }
+
+ private List B_CreateInstance(in List taskList, IServiceProvider di)
+ {
+ foreach (var item in taskList)
+ {
+ item.StepInstanceObject = this.CreateInstance(item.StepType, di);
+ }
+ return taskList;
+ }
+
+ ///
+ /// 排序后排列next调用
+ ///
+ ///
+ ///
+ private Dictionary C_SortAndPermutation(in List taskList)
+ {
+ var taskGroup = taskList
+ .ToLookup(t => t.Attribute.StepEnumType);
+
+ Dictionary result = new Dictionary();
+ foreach (var item in taskGroup)
+ {
+ var temp = LogicalChainHelper.SortList(item.ToList());
+
+ result.Add(item.Key, temp);
+ }
+
+ return result;
+ }
+
+ #endregion 获取、排列
+
+ #region 创建实例
+
+ ///
+ ///(适合构造方法无参数) 利用框架自带的激活器创建实例.
+ ///
+ /// 要创建的类型
+ /// DI实例
+ ///
+ private TInstance CreateInstance(Type type, IServiceProvider di)
+ where TInstance : class
+ {
+ return di.GetService(type) as TInstance;
+ }
+
+ ///
+ /// (适合构造方法有参数)利用框架自带的激活器创建实例.
+ ///
+ /// 要创建的类型
+ /// 构造方法中要求的参数,按顺序传递
+ ///
+ private TInstance CreateInstance(Type type, params object[] args)
+ where TInstance : class, new()
+ {
+ TInstance tempInstance = Activator.CreateInstance(type, args) as TInstance;//利用这个自带的激活器创建,也可以修改为DI实例
+
+ return tempInstance;
+ }
+
+ #endregion 创建实例
+ }
+}
\ No newline at end of file
diff --git a/CkTools/Src/CkTools.Nova/Helper/LogicalChainHelper.cs b/CkTools/Src/CkTools.Nova/Helper/LogicalChainHelper.cs
new file mode 100644
index 00000000..709601d9
--- /dev/null
+++ b/CkTools/Src/CkTools.Nova/Helper/LogicalChainHelper.cs
@@ -0,0 +1,166 @@
+using CkTools.Nova.Aop;
+using CkTools.Nova.Entity;
+using CkTools.Nova.LogicChain;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+
+namespace CkTools.Nova.Helper
+{
+ ///
+ /// 逻辑链帮助类
+ ///
+ public static class LogicalChainHelper
+ {
+ #region 筛选
+
+ ///
+ /// 从载入的程序集中查找
+ ///
+ ///
+ /// predicate参数示例:
+ ///
+ /// func = i =>{ return i.IsClass && i.IsPublic ; };]]>
+ ///
+ ///
+ /// 用来过滤type的委托
+ ///
+ public static IList<(TAttribute attribute, Type Type)> GetCustomAttributesByAssemblies(Func predicate)
+ where TAttribute : Attribute
+ {
+ var types = AppDomain.CurrentDomain.GetAssemblies()
+ .SelectMany(i =>
+ {
+ try
+ {
+ return i.GetExportedTypes();
+ }
+ catch
+ {
+ return new Type[0];
+ }
+ })
+ .Where(predicate)
+ .Select(type => (Attribute: type.GetCustomAttribute(false), type))
+ .Where(attr => attr.Attribute != null)
+ .ToList()
+ ;
+
+ //var types = AppDomain.CurrentDomain.GetAssemblies()
+ // .SelectMany(i =>
+ // {
+ // try
+ // {
+ // return i.GetExportedTypes();
+ // }
+ // catch
+ // {
+ // return new Type[0];
+ // }
+ // })
+ // .Where(predicate)
+ // .SelectMany(type => type
+ // .GetCustomAttributes()
+ // .Select(attr => (attr, type)
+ // )
+
+ // );
+
+ return types;
+ }
+
+ ///
+ /// 找出所有程序集中所有包含的类数据
+ ///
+ ///
+ public static List FindAllTaskEntity()
+ {
+ Func func = i =>
+ {
+ return i.IsClass //类
+ && i.IsEnum //枚举
+ && i.IsPublic //公共的
+ //&& i.IsGenericType == false //泛型类型
+ // && i.IsGenericTypeDefinition == false//泛型具体实现
+ // && i.IsAbstract == false //抽象
+
+ ;
+ };
+
+ List taskStructList = LogicalChainHelper
+ .GetCustomAttributesByAssemblies(func)
+ .Select(t => new StepEntity() { StepType = t.Type, Attribute = t.attribute })
+ .ToList();
+ return taskStructList;
+ }
+
+ #endregion 筛选
+
+ #region 排列接口
+
+ ///
+ /// 按输入的排列。返回第一个可执行的
+ /// 适合:已经确定好所有的步骤,仅仅只是排列调用顺序
+ ///
+ /// 需要排序的迭代器
+ ///
+ /// 是否自动添加结尾的中间件,用来标记执行结束.因为排序时有可能是自动排序,有可能是手动排序,所以此方法不能确定最后一个的情况。
+ /// 传递false时会自动附加一个空的以防止空指针异常
+ ///
+ /// 排好顺序后,第一个可执行的
+ public static IStep Sort(
+ IEnumerable taskArray,
+ bool isAutoEnd = true)
+ {
+ return LogicalChainHelper.SortList(taskArray, isAutoEnd)[0].StepInstanceObject;
+ }
+
+ ///
+ /// 按输入的排列。
+ /// 适合:已经确定好所有的步骤,仅仅只是排列调用顺序
+ ///
+ /// 需要排序的迭代器
+ ///
+ /// 是否自动添加结尾的中间件,用来标记执行结束.因为排序时有可能是自动排序,有可能是手动排序,所以此方法不能确定最后一个的情况。
+ /// 传递false时会自动附加一个空的以防止空指针异常
+ ///
+ /// 排列好顺序和next调用的数组
+ public static StepEntity[] SortList(
+ IEnumerable taskArray,
+ bool isAutoEnd = true)
+ {
+ //排序 一组接口内部排序
+ var stepList = taskArray
+ .OrderBy(i => i.Attribute.StepEnumOrder)
+ .ToArray();
+
+ StepEntity tempMw = stepList.First();//获取第一个中间件的引用
+ foreach (var item in stepList)
+ {
+ tempMw.StepInstanceObject.Next = item.StepInstanceObject;
+ tempMw = item;//将指针移动到下一个
+ }
+
+ //处理最后一个中间件的next。 分析完成后tempMw指向最后一个中间件
+ tempMw.StepInstanceObject.Next = isAutoEnd
+ ? LogicalChainHelper.GetEndStep()
+ : null;
+
+ return stepList;
+ }
+
+ #endregion 排列接口
+
+ #region 创建默认的EndStep
+
+ public static readonly IStep DefaultEndStep = new EndStep();
+
+ public static IStep GetEndStep()
+ {
+ return LogicalChainHelper.DefaultEndStep;
+ }
+
+ #endregion 创建默认的EndStep
+ }
+}
\ No newline at end of file
diff --git a/CkTools/Src/CkTools.Nova/LogicChain/EndStep.cs b/CkTools/Src/CkTools.Nova/LogicChain/EndStep.cs
new file mode 100644
index 00000000..47d4f31e
--- /dev/null
+++ b/CkTools/Src/CkTools.Nova/LogicChain/EndStep.cs
@@ -0,0 +1,15 @@
+using StepContext = CkTools.Nova.Entity.StepContext;
+
+namespace CkTools.Nova.LogicChain
+{
+ ///
+ /// 结束调用链,代表最后一步
+ ///
+ public class EndStep : StepBase
+ {
+ protected override void ProcessContext(StepContext context)
+ {
+ base.Complete();
+ }
+ }
+}
\ No newline at end of file
diff --git a/CkTools/Src/CkTools.Nova/LogicChain/EndStepGeneric.cs b/CkTools/Src/CkTools.Nova/LogicChain/EndStepGeneric.cs
new file mode 100644
index 00000000..9efb9598
--- /dev/null
+++ b/CkTools/Src/CkTools.Nova/LogicChain/EndStepGeneric.cs
@@ -0,0 +1,16 @@
+using CkTools.Nova.Entity;
+
+namespace CkTools.Nova.LogicChain
+{
+ ///
+ /// 结束调用链,代表最后一步。
+ ///
+ ///
+ public class EndStep : StepBase
+ {
+ protected override void ProcessContext(StepContext context)
+ {
+ base.Complete();
+ }
+ }
+}
\ No newline at end of file
diff --git a/CkTools/Src/CkTools.Nova/LogicChain/IStep.cs b/CkTools/Src/CkTools.Nova/LogicChain/IStep.cs
new file mode 100644
index 00000000..61106405
--- /dev/null
+++ b/CkTools/Src/CkTools.Nova/LogicChain/IStep.cs
@@ -0,0 +1,31 @@
+using CkTools.Nova.Helper;
+using System.Threading.Tasks;
+using StepContext = CkTools.Nova.Entity.StepContext;
+
+namespace CkTools.Nova.LogicChain
+{
+ ///
+ /// 任务步骤接口,一个实现对应一步逻辑
+ ///
+ public interface IStep
+ {
+ ///
+ /// 下一个中间件
+ ///
+ ///
+ /// 由中排序或初始化时统一指定
+ ///
+ IStep Next { get; set; }
+
+ ///
+ /// 异步执行任务
+ ///
+ /// 要处理的步骤上下文.
+ ///
+ /// 参数初始化:
+ /// 一般来说第一个步骤代码执行时不会判断null,由调用者负责初始化化。如果使用IOC,则不需要关心此参数
+ ///
+ ///
+ Task InvokeAsync(StepContext context);
+ }
+}
\ No newline at end of file
diff --git a/CkTools/Src/CkTools.Nova/LogicChain/IStepGeneric.cs b/CkTools/Src/CkTools.Nova/LogicChain/IStepGeneric.cs
new file mode 100644
index 00000000..4db4956c
--- /dev/null
+++ b/CkTools/Src/CkTools.Nova/LogicChain/IStepGeneric.cs
@@ -0,0 +1,23 @@
+using CkTools.Nova.Entity;
+using System.Threading.Tasks;
+
+namespace CkTools.Nova.LogicChain
+{
+ ///
+ /// 泛型任务步骤接口,一个实现对应一步逻辑
+ ///
+ /// 步骤处理的结果类型
+ public interface IStep : IStep
+ {
+ ///
+ /// 异步执行任务
+ ///
+ /// 要处理的步骤上下文.
+ ///
+ /// 参数初始化:
+ /// 一般来说第一个步骤代码执行时不会判断null,由调用者负责初始化化。如果使用IOC,则不需要关心此参数
+ ///
+ ///
+ Task InvokeAsync(StepContext context);
+ }
+}
\ No newline at end of file
diff --git a/CkTools/Src/CkTools.Nova/LogicChain/StepBase.cs b/CkTools/Src/CkTools.Nova/LogicChain/StepBase.cs
new file mode 100644
index 00000000..ab28c5ee
--- /dev/null
+++ b/CkTools/Src/CkTools.Nova/LogicChain/StepBase.cs
@@ -0,0 +1,44 @@
+using System;
+using System.Threading.Tasks;
+using StepContext = CkTools.Nova.Entity.StepContext;
+
+namespace CkTools.Nova.LogicChain
+{
+ public abstract class StepBase : IStep
+ {
+ public IStep Next { get; set; }
+
+ public StepContext Context { get; set; }
+
+ public Task InvokeAsync(StepContext context)
+ {
+ context.CheckNullWithException(nameof(context));
+ this.Context = context;
+
+ //检查任务是否完成
+ if (this.Context.ProcessCompleted || this.Next == null)
+ {
+ return Task.CompletedTask;
+ }
+
+ //执行任务
+ this.ProcessContext(context);
+
+ //调用下一个任务
+ return this.Next.InvokeAsync(context);
+ }
+
+ ///
+ /// 由子类实现每个步骤的具体逻辑
+ ///
+ ///
+ ///
+ protected abstract void ProcessContext(StepContext context);
+
+ protected virtual StepContext Complete()
+ {
+ this.Context.ProcessCompleted = true;
+ return this.Context;
+ }
+ }
+}
\ No newline at end of file
diff --git a/CkTools/Src/CkTools.Nova/LogicChain/StepBaseGeneric.cs b/CkTools/Src/CkTools.Nova/LogicChain/StepBaseGeneric.cs
new file mode 100644
index 00000000..7d1345b9
--- /dev/null
+++ b/CkTools/Src/CkTools.Nova/LogicChain/StepBaseGeneric.cs
@@ -0,0 +1,44 @@
+using CkTools.Nova.Entity;
+using System.Threading.Tasks;
+using StepContext = CkTools.Nova.Entity.StepContext;
+
+namespace CkTools.Nova.LogicChain
+{
+ public abstract class StepBase : StepBase, IStep
+ {
+ public new IStep Next { get; set; }
+ public new StepContext Context { get; set; }
+
+ public Task InvokeAsync(StepContext context)
+ {
+ base.InvokeAsync(context);
+
+ return Task.FromResult(this.Context.As().Result);
+ }
+
+ ///
+ /// 由子类实现每个步骤的具体逻辑
+ ///
+ ///
+ ///
+ protected abstract void ProcessContext(StepContext context);
+
+ protected override void ProcessContext(StepContext context)
+ {
+ this.ProcessContext(context.As());
+ }
+
+ ///
+ /// 给属性赋值为true,结束当前的步骤链
+ ///
+ ///
+ ///
+ protected virtual StepContext Complete(TResult result)
+ {
+ base.Complete();
+
+ this.Context.Result = result;
+ return this.Context.As();
+ }
+ }
+}
\ No newline at end of file
diff --git a/CkTools/Src/CkTools.Nova/Middleware/LogicChainExtension.cs b/CkTools/Src/CkTools.Nova/Middleware/LogicChainExtension.cs
new file mode 100644
index 00000000..3f84216d
--- /dev/null
+++ b/CkTools/Src/CkTools.Nova/Middleware/LogicChainExtension.cs
@@ -0,0 +1,42 @@
+using CkTools.Nova.Entity;
+using CkTools.Nova.Factory;
+using CkTools.Nova.Helper;
+using System;
+using System.Collections.Generic;
+
+namespace Microsoft.Extensions.DependencyInjection
+{
+ ///
+ /// 逻辑链扩展
+ ///
+ public static class LogicChainExtension
+ {
+ ///
+ /// 注册逻辑链服务
+ ///
+ ///
+ ///
+ public static IServiceCollection AddNova(this IServiceCollection services)
+ {
+ //扫描并注册程序集中所有带标签的实现
+ List taskList = LogicalChainHelper.FindAllTaskEntity();
+ foreach (var item in taskList)
+ {
+ ServiceDescriptor serviceDescriptor = new ServiceDescriptor(
+ item.StepType,
+ item.StepType,
+ item.Attribute.Lifetime);
+
+ services.Add(serviceDescriptor);
+ }
+
+ //注册工厂接口
+ services.AddSingleton(t =>
+ {
+ return new NovaFactory(t.GetService(), taskList);
+ });
+
+ return services;
+ }
+ }
+}
\ No newline at end of file
diff --git a/CkTools/Src/CkTools.Nova/Middleware/TaskMwHelper.cs b/CkTools/Src/CkTools.Nova/Middleware/TaskMwHelper.cs
new file mode 100644
index 00000000..87ed1614
--- /dev/null
+++ b/CkTools/Src/CkTools.Nova/Middleware/TaskMwHelper.cs
@@ -0,0 +1,35 @@
+//using System.Linq;
+
+//namespace Nova.LogicChain
+//{
+// ///
+// /// 还没想好名字的帮助类
+// ///
+// public class TaskHelper
+// {
+// ///
+// /// 按输入顺序排列每个ITaskMw中的下一个中间件,会自动设置执行顺序。
+// ///
+// ///
+// ///
+// /// 排好顺序后,第一个执行的中间件
+// ///
+// ///
+// /// 关于最后一个中间件:
+// /// 这种方式
+// ///
+// /// 但是因执行限制,请自行控制最后一个中间件的Next属性,避免运行时造成空指针异常
+// ///
+// public ITask SortByUse(params ITask[] taskList)
+// {
+// ITask tempMw = taskList.FirstOrDefault();
+// foreach (var item in taskList)
+// {
+// tempMw.Next = item;
+// tempMw = item;//将指针移动到下一个
+// }
+
+// return taskList[0];
+// }
+// }
+//}
\ No newline at end of file
diff --git a/CkTools/Src/SDKPulishNuget.targets b/CkTools/Src/SDKPulishNuget.targets
index b572687c..643379fe 100644
--- a/CkTools/Src/SDKPulishNuget.targets
+++ b/CkTools/Src/SDKPulishNuget.targets
@@ -15,7 +15,7 @@
$(VersionSuffix)
- 3.1.0.7
+ 3.1.0.8
$(Version)$(VersionSuffix)
$(Version)
$(Version)
diff --git a/CkTools/Test/CKTols.FP.Test/Maybe`1Test.cs b/CkTools/Test/CKTols.FP.Test/Maybe`1Test.cs
index d601f474..b5fb9b0b 100644
--- a/CkTools/Test/CKTols.FP.Test/Maybe`1Test.cs
+++ b/CkTools/Test/CKTols.FP.Test/Maybe`1Test.cs
@@ -5,6 +5,9 @@ namespace CKTols.FP.Test
{
public class Maybe_1Test
{
+#pragma warning disable CS8600 // 将 null 文本或可能的 null 值转换为不可为 null 类型。
+#pragma warning disable CS8625 // 无法将 null 文本转换为不可为 null 的引用类型。
+
public class ConstructorTest
{
[Fact]
@@ -68,6 +71,9 @@ public void MaybeParameterIsHasValueIsFalse_WithHasValueIsFalse()
}
}
+#pragma warning restore CS8625 // 无法将 null 文本转换为不可为 null 的引用类型。
+#pragma warning restore CS8600 // 将 null 文本或可能的 null 值转换为不可为 null 类型。
+
public class ImplicitOperatorTest
{
[Fact]
diff --git a/CkTools/Test/CKTools.BaseExtensions.Test/Extensions/BaseType/IEnumerableExtensionsTest.cs b/CkTools/Test/CKTools.BaseExtensions.Test/Extensions/BaseType/IEnumerableExtensionsTest.cs
index 3d292af0..410dc481 100644
--- a/CkTools/Test/CKTools.BaseExtensions.Test/Extensions/BaseType/IEnumerableExtensionsTest.cs
+++ b/CkTools/Test/CKTools.BaseExtensions.Test/Extensions/BaseType/IEnumerableExtensionsTest.cs
@@ -197,7 +197,7 @@ public class IntersectTest
[Fact]
public void Source_Null()
{
- List testData = null;
+ List? testData = null;
Func func = (a, b) => a.UserName == b.UserName;
var ex = Assert.Throws(() =>
diff --git a/CkTools/Test/CkTools.Abstraction.Test/Extensions/EnumerableExtensions.cs b/CkTools/Test/CkTools.Abstraction.Test/Extensions/EnumerableExtensions.cs
new file mode 100644
index 00000000..087f0795
--- /dev/null
+++ b/CkTools/Test/CkTools.Abstraction.Test/Extensions/EnumerableExtensions.cs
@@ -0,0 +1,81 @@
+using System;
+using System.Collections;
+using Xunit;
+
+namespace Nova.LogicChain.Test.Extensions
+{
+ public class EnumerableExtensions
+ {
+ #region IsNullOrEmpty
+
+ [Fact]
+ public void IsNullOrEmpty_Null_True()
+ {
+ IEnumerable testObject = null;
+ bool result = testObject.IsNullOrEmpty();
+ Assert.True(result);
+ }
+
+ [Fact]
+ public void IsNullOrEmpty_Empty_True()
+ {
+ IEnumerable testObject = new int[0];
+ bool result = testObject.IsNullOrEmpty();
+ Assert.True(result);
+ }
+
+ [Fact]
+ public void IsNullOrEmpty_ZeroIsNull_Fasle()
+ {
+ IEnumerable testObject = new string[2] { null, null };
+ bool result = testObject.IsNullOrEmpty();
+ Assert.False(result);
+ }
+
+ [Fact]
+ public void IsNullOrEmpty_TwoElement_Fasle()
+ {
+ IEnumerable testObject = new string[2] { "1", "1" };
+ bool result = testObject.IsNullOrEmpty();
+ Assert.False(result);
+ }
+
+ #endregion IsNullOrEmpty
+
+ #region IsNotNullOrEmpty
+
+ [Fact]
+ public void IsNotNullOrEmpty_Null_Fasle()
+ {
+ IEnumerable testObject = null;
+ bool result = testObject.IsNotNullOrEmpty();
+ Assert.False(result);
+ }
+
+ [Fact]
+ public void IsNotNullOrEmpty_Empty_Fasle()
+ {
+ IEnumerable testObject = new int[0];
+ bool result = testObject.IsNotNullOrEmpty();
+ Assert.False(result);
+ }
+
+ [Fact]
+ public void IsNotNullOrEmpty_ZeroIsNull_True()
+ {
+ IEnumerable testObject = new string[2] { null, null };
+ bool result = testObject.IsNotNullOrEmpty();
+ Assert.True(result);
+ }
+
+ [Fact]
+ public void IsNotNullOrEmpty_TwoElement_True()
+ {
+ IEnumerable testObject = new string[2] { "1", "1" };
+ bool result = testObject.IsNotNullOrEmpty();
+ Assert.True(result);
+ }
+
+ #endregion IsNotNullOrEmpty
+ }
+}
\ No newline at end of file
diff --git a/CkTools/Test/CkTools.Abstraction.Test/Extensions/ObjectExtensionsTest.cs b/CkTools/Test/CkTools.Abstraction.Test/Extensions/ObjectExtensionsTest.cs
new file mode 100644
index 00000000..c2d4cec8
--- /dev/null
+++ b/CkTools/Test/CkTools.Abstraction.Test/Extensions/ObjectExtensionsTest.cs
@@ -0,0 +1,139 @@
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using Xunit;
+
+namespace Nova.LogicChain.Test.Extensions
+{
+ public class ObjectExtensionsTest
+ {
+ #region CheckNull
+
+ [Fact]
+ public void CheckNull_True()
+ {
+ object obj = null;
+ var ex = Assert.Throws(() => obj.CheckNullWithException("test"));
+
+ Assert.NotNull(ex);
+ }
+
+ [Fact]
+ public void CheckNull_False()
+ {
+ object obj = new object();
+ var ex = Record.Exception(() => obj.CheckNullWithException("test"));
+
+ Assert.Null(ex);
+ }
+
+ [Fact]
+ public void CheckNull2_True()
+ {
+ object obj = null;
+ var ex = Assert.Throws(() => obj.CheckNullWithException("test", "testMsg"));
+
+ Assert.NotNull(ex);
+ Assert.Contains("testMsg", ex.Message);
+ }
+
+ [Fact]
+ public void CheckNull2_False()
+ {
+ object obj = new object();
+ var ex = Record.Exception(() => obj.CheckNullWithException("test", "testMsg"));
+
+ Assert.Null(ex);
+ }
+
+ #endregion CheckNull
+
+ #region CheckNullOrEmpty
+
+ [Fact]
+ public void CheckNullOrEmpty_Null_True()
+ {
+ List obj = null;
+ var ex = Assert.Throws(() => obj.CheckNullOrEmptyWithException("test"));
+
+ Assert.NotNull(ex);
+ }
+
+ [Fact]
+ public void CheckNullOrEmpty_Empty_True()
+ {
+ List obj = new List();
+ var ex = Assert.Throws(() => obj.CheckNullOrEmptyWithException("test"));
+
+ Assert.NotNull(ex);
+ }
+
+ [Fact]
+ public void CheckNullOrEmpty_False()
+ {
+ List obj = new List() { 1 };
+ var ex = Record.Exception(() => obj.CheckNullOrEmptyWithException("test"));
+
+ Assert.Null(ex);
+ }
+
+ [Fact]
+ public void CheckNullOrEmpty2_Null_True()
+ {
+ List obj = null;
+ var ex = Assert.Throws(() => obj.CheckNullOrEmptyWithException("test", "testMsg"));
+
+ Assert.NotNull(ex);
+ Assert.Contains("testMsg", ex.Message);
+ }
+
+ [Fact]
+ public void CheckNullOrEmpty2_Empty_True()
+ {
+ List obj = new List();
+ var ex = Assert.Throws(() => obj.CheckNullOrEmptyWithException("test", "testMsg"));
+
+ Assert.NotNull(ex);
+ Assert.Contains("testMsg", ex.Message);
+ }
+
+ [Fact]
+ public void CheckNullOrEmpty2_False()
+ {
+ List obj = new List() { 1 };
+ var ex = Record.Exception(() => obj.CheckNullOrEmptyWithException("test", "testMsg"));
+
+ Assert.Null(ex);
+ }
+
+ #endregion CheckNullOrEmpty
+
+ #region ToTask
+
+ [Fact]
+ public void ToTask_NullObject_NotException()
+ {
+ object obj = null;
+ Task