diff --git a/Directory.Build.props b/Directory.Build.props new file mode 100644 index 0000000..45b1b57 --- /dev/null +++ b/Directory.Build.props @@ -0,0 +1,24 @@ + + + + + net8.0 + $(DefaultTargetFramework)-windows + 12.0 + true + false + true + annotations + CS1591 + enable + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + diff --git a/Source/Core/MAX.Core/Autofac/AutofaceMiddleware.cs b/Source/Core/MAX.Core/Autofac/AutofaceMiddleware.cs new file mode 100644 index 0000000..3dc6506 --- /dev/null +++ b/Source/Core/MAX.Core/Autofac/AutofaceMiddleware.cs @@ -0,0 +1,85 @@ +using Autofac; +using MAX.Core.DependencyInjection; +using MAX.Core.Helpers; +using MAX.Core.Infrastructure; +using MAX.Core.Reflections; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace MAX.Core.Autofac; + +public static class AutofaceMiddlewareExtensions +{ + public static Bootstrap UseAutofac(this Bootstrap engine) + { + engine.UseMiddleware(); + return engine; + } +} + +internal class AutofaceMiddleware : IBootstrap +{ + private ContainerBuilder _builder; + public void Build() + { + IocManager.Container = _builder.Build(); + } + + public void Use() + { + _builder = new ContainerBuilder(); + IocManager.ContainerBuilder = _builder; + RegisterDependencies(_builder); + } + + + /// + /// 注册IOC + /// + /// + /// + private void RegisterDependencies(ContainerBuilder builder) + { + #region 注册所有IOC接口 + + IEnumerable ListDependency = ReflectionHelper.GetInstancesByInterface(AppTypeFinder.Assemblies.ToArray()); + foreach (IDependencyRegistrar item in ListDependency) + { + item.Register(builder); + } + + + //ISingletonDependency + Type singtype = typeof(ISingletonDependency); + builder.RegisterAssemblyTypes(AppTypeFinder.Assemblies.ToArray()) + .Where(a => + singtype.IsAssignableFrom(a) + && a.IsClass + && !a.IsAbstract + && !typeof(INoDependency).IsAssignableFrom(a) + ) + .AsImplementedInterfaces() + .AsSelf() + .PropertiesAutowired(new CustomPropertySelector()) + .SingleInstance(); + + + //ITransientDependency + Type transienttype = typeof(ITransientDependency); + builder.RegisterAssemblyTypes(AppTypeFinder.Assemblies.ToArray()) + .Where(a => + transienttype.IsAssignableFrom(a) + && a.IsClass + && !a.IsAbstract + && !typeof(INoDependency).IsAssignableFrom(a) + ) + .AsImplementedInterfaces() + .AsSelf() + .PropertiesAutowired(new CustomPropertySelector()) + .InstancePerLifetimeScope(); + + + #endregion + } +} diff --git a/Source/Core/MAX.Core/Autofac/AutowiredAttribute.cs b/Source/Core/MAX.Core/Autofac/AutowiredAttribute.cs new file mode 100644 index 0000000..e6c0853 --- /dev/null +++ b/Source/Core/MAX.Core/Autofac/AutowiredAttribute.cs @@ -0,0 +1,11 @@ +using System; + +namespace MAX.Core.Autofac; + +/// +/// 自动填充属性特性 +/// +[AttributeUsage(AttributeTargets.Property)] +public class AutowiredAttribute : Attribute +{ +} diff --git a/Source/Core/MAX.Core/Autofac/CustomPropertySelector.cs b/Source/Core/MAX.Core/Autofac/CustomPropertySelector.cs new file mode 100644 index 0000000..28938cb --- /dev/null +++ b/Source/Core/MAX.Core/Autofac/CustomPropertySelector.cs @@ -0,0 +1,22 @@ +using Autofac.Core; +using System.Linq; +using System.Reflection; + +namespace MAX.Core.Autofac; + +/// +/// autofac 找出所有属性上是否摸个特性 +/// +public class CustomPropertySelector : IPropertySelector +{ + public bool InjectProperty(PropertyInfo propertyInfo, object instance) + { + if (propertyInfo.CustomAttributes.Any(iterator => iterator.AttributeType == typeof(AutowiredAttribute))) + { + + } + //需要一个判断 + //;如果标记的有CustomPropertyAttribute特性返回True:返回true;就构造实例 + return propertyInfo.CustomAttributes.Any(iterator => iterator.AttributeType == typeof(AutowiredAttribute)); + } +} diff --git a/Source/Core/MAX.Core/Autofac/IDependencyRegistrar.cs b/Source/Core/MAX.Core/Autofac/IDependencyRegistrar.cs new file mode 100644 index 0000000..ca54fc9 --- /dev/null +++ b/Source/Core/MAX.Core/Autofac/IDependencyRegistrar.cs @@ -0,0 +1,8 @@ +using Autofac; + +namespace MAX.Core.Autofac; + +public interface IDependencyRegistrar +{ + void Register(ContainerBuilder builder); +} diff --git a/Source/Core/MAX.Core/Autofac/IocManager.cs b/Source/Core/MAX.Core/Autofac/IocManager.cs new file mode 100644 index 0000000..22c50c4 --- /dev/null +++ b/Source/Core/MAX.Core/Autofac/IocManager.cs @@ -0,0 +1,96 @@ +using Autofac; +using Autofac.Core; +using System; +using System.Collections.Generic; + +namespace MAX.Core.Autofac; + +/// +/// 容器 +/// +public static partial class IocManager +{ + /// + /// 默认容器 + /// + public static IContainer Container { get; internal set; } + public static void SetContainer(IContainer container) + { + Container = container; + } + + /// + /// 注册容器 + /// + public static ContainerBuilder ContainerBuilder { get; internal set; } + public static void SetContainerBuilder(ContainerBuilder containerBuilder) + { + ContainerBuilder = containerBuilder; + } + public static void RegisterType() + { + ContainerBuilder.RegisterType().As().InstancePerLifetimeScope(); + } + /// + /// 确定指定的服务在上下文中是否可用。 + /// + /// + /// + public static bool IsRegistered() + { + return Container.IsRegistered(); + } + /// + /// 确定指定的服务在上下文中是否可用。 + /// + /// + /// + public static bool IsRegistered(Type serverType) + { + return Container.IsRegistered(serverType); + } + + public static TService Resolve() + { + if (Container.IsRegistered()) + { + return Container.Resolve(); + } + return default; + } + public static object Resolve(Type serviceType) + { + if (Container.IsRegistered(serviceType)) + { + return Container.Resolve(serviceType); + } + return default; + } + public static TService Resolve(params Parameter[] parameters) + { + if (Container.IsRegistered()) + { + return Container.Resolve(parameters); + } + return default; + } + /// + /// 获取所有注册 + /// + /// + /// + public static TService[] ResolveAll() + { + return ResolveAll(typeof(TService)) as TService[]; + } + /// + /// 获取所有注册 + /// + /// + /// + public static object[] ResolveAll(Type type) + { + Type enumerableOfType = typeof(IEnumerable<>).MakeGenericType(type); + return (object[])Container.ResolveService(new TypedService(enumerableOfType)); + } +} diff --git a/Source/Core/MAX.Core/Caches/ICache.cs b/Source/Core/MAX.Core/Caches/ICache.cs new file mode 100644 index 0000000..9407565 --- /dev/null +++ b/Source/Core/MAX.Core/Caches/ICache.cs @@ -0,0 +1,99 @@ +using System; + +namespace MAX.Core.Caches; + +/// +/// 缓存 +/// +public interface ICache +{ + /// + /// 是否存在指定键的缓存 + /// + /// 缓存键 + bool Exists(string key); + /// + /// 从缓存中获取数据 + /// + /// + /// + T Get(string key); + /// + /// 从缓存中获取数据,如果不存在,则执行获取数据操作并添加到缓存中 + /// + /// 缓存数据类型 + /// 缓存键 + /// 获取数据操作 + T Get(string key, Func func); + /// + /// 当缓存数据不存在则添加,已存在不会添加,添加成功返回true + /// + /// 缓存数据类型 + /// 缓存键 + /// 值 + bool TryAdd(string key, T value); + /// + /// 覆盖添加 + /// + /// + /// + /// + /// + bool OverlayAdd(string key, T value); + /// + /// 移除缓存 + /// + /// 缓存键 + void Remove(string key); + /// + /// 清空缓存 + /// + void Clear(); +} + +public interface ICache +{ + /// + /// 是否存在指定键的缓存 + /// + /// 缓存键 + bool Exists(string key); + + /// + /// 从缓存中获取数据,如果不存在,则执行获取数据操作并添加到缓存中 + /// + /// 缓存数据类型 + /// 缓存键 + T Get(string key); + /// + /// 从缓存中获取数据,如果不存在,则执行获取数据操作并添加到缓存中 + /// + /// 缓存数据类型 + /// 缓存键 + /// 获取数据操作 + T Get(string key, Func func); + /// + /// 当缓存数据不存在则添加,已存在不会添加,添加成功返回true + /// + /// 缓存数据类型 + /// 缓存键 + /// 值 + bool TryAdd(string key, T value); + /// + /// 覆盖添加 + /// + /// + /// + /// + /// + bool OverlayAdd(string key, T value); + /// + /// 移除缓存 + /// + /// 缓存键 + void Remove(string key); + /// + /// 清空缓存 + /// + void Clear(); +} diff --git a/Source/Core/MAX.Core/Caches/MemoryCache.cs b/Source/Core/MAX.Core/Caches/MemoryCache.cs new file mode 100644 index 0000000..b2bc36a --- /dev/null +++ b/Source/Core/MAX.Core/Caches/MemoryCache.cs @@ -0,0 +1,168 @@ + +using MAX.Core.DependencyInjection; +using MAX.Core.Infrastructure; +using System; +using System.Collections.Concurrent; +using System.Linq; + +namespace MAX.Core.Caches; + +/// +/// 内存缓存 +/// +public class MemoryCache : ICache, ISingletonDependency +{ + //private static readonly Object _locker = new object(); + private readonly ConcurrentDictionary _cache = new(); + + public virtual void Clear() + { + _cache.Clear(); + } + + public virtual bool Exists(string key) + { + return _cache.ContainsKey(key); + } + public virtual T Get(string key) + { + if (Exists(key)) + { + return (T)_cache[key]; + } + return default; + } + public virtual TResult Get(string key, Func func) + { + if (Exists(key)) + { + return (TResult)_cache[key]; + } + else + { + if (func != null) + { + TResult t = func(); + TryAdd(key, t); + return t; + } + } + return default; + } + + public virtual bool OverlayAdd(string key, T value) + { + if (Exists(key)) + { + _cache[key] = value; + } + else + { + _cache.TryAdd(key, value); + } + return true; + } + + public virtual void Remove(string key) + { + _cache.TryRemove(key, out _); + } + + public virtual bool TryAdd(string key, T value) + { + _cache.AddOrUpdate(key, value, (oldkey, oldvalue) => value); + return true; + } + /// + /// 获取所有Key + /// + /// + public string[] AllKeys() + { + return _cache.Keys.ToArray(); + } +} +/// +/// 单例缓存抽象类 +/// +/// 单例对象 +/// 缓存类型 +public abstract class SingleInstanceLittleCache : SingleInstance, ICache where TModel : class +{ + private readonly ConcurrentDictionary _cache = new(); + /// + /// 自定义前缀 + /// + //public abstract string KeyPrefix { get; } + //private string FormatKey(string key) + //{ + // return KeyPrefix + key; + //} + + public virtual void Clear() + { + _cache.Clear(); + } + + public virtual bool Exists(string key) + { + return _cache.ContainsKey(key); + } + public virtual TValue Get(string key) + { + if (Exists(key)) + { + return _cache[key]; + } + return default; + } + public virtual TValue Get(string key, Func func) + { + if (Exists(key)) + { + return _cache[key]; + } + else + { + if (func != null) + { + TValue t = func(); + TryAdd(key, t); + return t; + } + } + return default; + } + + public virtual bool OverlayAdd(string key, TValue value) + { + if (Exists(key)) + { + _cache[key] = value; + } + else + { + _cache.TryAdd(key, value); + } + return true; + } + + public virtual void Remove(string key) + { + _cache.TryRemove(key, out _); + } + + public virtual bool TryAdd(string key, TValue value) + { + _cache.AddOrUpdate(key, value, (oldkey, oldvalue) => value); + return true; + } + /// + /// 获取所有Key + /// + /// + public string[] AllKeys() + { + return _cache.Keys.ToArray(); + } +} diff --git a/Source/Core/MAX.Core/Commons/CommonValue.cs b/Source/Core/MAX.Core/Commons/CommonValue.cs new file mode 100644 index 0000000..ddcd731 --- /dev/null +++ b/Source/Core/MAX.Core/Commons/CommonValue.cs @@ -0,0 +1,37 @@ +namespace MAX.Core.Commons; + +/// +/// 公共Value +/// +/// +public class CommonValue +{ + public CommonValue() { } + public CommonValue(TValue value) + { + Value = value; + } + public TValue Value { get; set; } +} +/// +/// 公共TextValue +/// +/// +public class CommonTextValue : CommonValue +{ + public string Text { get; set; } +} +/// +/// 公共TextValue +/// +public class CommonTextValue : CommonTextValue +{ + +} +/// +/// 默认公共键值对 Key Value +/// +public class CommonKeyValue : CommonValue +{ + public string Key { get; set; } +} diff --git a/Source/Core/MAX.Core/Commons/Result.cs b/Source/Core/MAX.Core/Commons/Result.cs new file mode 100644 index 0000000..5d71941 --- /dev/null +++ b/Source/Core/MAX.Core/Commons/Result.cs @@ -0,0 +1,55 @@ +using System; + +namespace MAX.Core.Commons; + +/// +/// 通用返回 +/// +public class Result +{ + /// + /// 返回结果是否成功 + /// + public bool Success { get; set; } = false; + /// + /// 返回消息 + /// + public string Msg { get; set; } = ""; + /// + /// 返回内容 + /// + public object Data { get; set; } = null; + + /// + /// 返回状态码 + /// + public int Code { get; set; } + +} +/// +/// 通用返回 指定Data类型 +/// +/// 数据类型 +public class Result : Result +{ + /// + /// 返回内容 + /// + public new TData Data { get; set; } +} +/// +/// 通用返回 +/// +/// 状态类型枚举 +/// 数据类型 +public class Result : Result where TStatus : Enum +{ + /// + /// 状态 + /// + public TStatus Status { get; set; } + /// + /// 返回内容 + /// + public new TData Data { get; set; } +} diff --git a/Source/Core/MAX.Core/Cryptography/Base64Helper.cs b/Source/Core/MAX.Core/Cryptography/Base64Helper.cs new file mode 100644 index 0000000..768c732 --- /dev/null +++ b/Source/Core/MAX.Core/Cryptography/Base64Helper.cs @@ -0,0 +1,69 @@ +using System; +using System.Text; + +namespace MAX.Core.Cryptography; + +public class Base64Helper +{ + /// + /// Base64加密 + /// + /// 加密采用的编码方式 + /// 待加密的明文 + /// + public static string Encode(string source, Encoding encode) + { + string encodeValue = ""; + byte[] bytes = encode.GetBytes(source); + try + { + encodeValue = Convert.ToBase64String(bytes); + } + catch + { + encodeValue = source; + } + return encodeValue; + } + + /// + /// Base64加密,采用utf8编码方式加密 + /// + /// 待加密的明文 + /// 加密后的字符串 + public static string Encode(string source) + { + return Encode(source, Encoding.UTF8); + } + + /// + /// Base64解密 + /// + /// 解密采用的编码方式,注意和加密时采用的方式一致 + /// 待解密的密文 + /// 解密后的字符串 + public static string Decode(string result, Encoding encode) + { + string decode = ""; + byte[] bytes = Convert.FromBase64String(result); + try + { + decode = encode.GetString(bytes); + } + catch + { + decode = result; + } + return decode; + } + + /// + /// Base64解密,采用utf8编码方式解密 + /// + /// 待解密的密文 + /// 解密后的字符串 + public static string Decode(string result) + { + return Decode(result, Encoding.UTF8); + } +} diff --git a/Source/Core/MAX.Core/Cryptography/DESHelper.cs b/Source/Core/MAX.Core/Cryptography/DESHelper.cs new file mode 100644 index 0000000..67e37f0 --- /dev/null +++ b/Source/Core/MAX.Core/Cryptography/DESHelper.cs @@ -0,0 +1,215 @@ +using System; +using System.IO; +using System.Security.Cryptography; +using System.Text; + +namespace MAX.Core.Cryptography; + +public sealed class DESHelper +{ + + //默认密钥向量 + private static readonly byte[] IVKey = { 36, 109, 97, 120, 111, 110, 120, 36 }; + /// + /// DES加密字符串 + /// + /// 待加密的字符串 + /// 加密密钥,要求为8位 + /// 加密成功返回加密后的字符串,失败返回源串 + public static string Encrypt(string encryptString, string encryptKey) + { + try + { + encryptKey = GetSubString(encryptKey, 8, ""); + encryptKey = encryptKey.PadRight(8, ' '); + byte[] rgbKey = Encoding.UTF8.GetBytes(encryptKey.Substring(0, 8)); + byte[] rgbIV = IVKey; + byte[] inputByteArray = Encoding.UTF8.GetBytes(encryptString); + DES dCSP = DES.Create(); + MemoryStream mStream = new(); + CryptoStream cStream = new(mStream, dCSP.CreateEncryptor(rgbKey, rgbIV), CryptoStreamMode.Write); + cStream.Write(inputByteArray, 0, inputByteArray.Length); + cStream.FlushFinalBlock(); + return Convert.ToBase64String(mStream.ToArray()); + } + catch + { + return encryptString; + } + } + + /// + /// DES解密字符串 + /// + /// 待解密的字符串 + /// 解密密钥,要求为8位,和加密密钥相同 + /// 解密成功返回解密后的字符串,失败返源串 + public static string Decrypt(string decryptString, string decryptKey) + { + try + { + decryptKey = GetSubString(decryptKey, 0, 8, ""); + decryptKey = decryptKey.PadRight(8, ' '); + byte[] rgbKey = Encoding.UTF8.GetBytes(decryptKey); + byte[] rgbIV = IVKey; + byte[] inputByteArray = Convert.FromBase64String(decryptString); + DES DCSP = DES.Create(); + + MemoryStream mStream = new(); + CryptoStream cStream = new(mStream, DCSP.CreateDecryptor(rgbKey, rgbIV), CryptoStreamMode.Write); + cStream.Write(inputByteArray, 0, inputByteArray.Length); + cStream.FlushFinalBlock(); + return Encoding.UTF8.GetString(mStream.ToArray()); + } + catch + { + return string.Empty; + } + + } + + #region 截取字符串函数 + + /// + /// 截取字符串函数 + /// + /// 所要截取的字符串 + /// 截取字符串的长度 + /// + public static string GetSubString(string Str, int Num) + { + if (Str is null or "") + { + return ""; + } + string outstr = string.Empty; + int n = 0; + foreach (char ch in Str) + { + n += Encoding.Default.GetByteCount(ch.ToString()); + + if (n > Num) + { + break; + } + else + { + outstr += ch; + } + } + + return outstr; + } + /// + /// 截取字符串函数,如果超过指定长度则将超出的部分用指定字符串代替 + /// + /// 所要截取的字符串 + /// 截取字符串的长度 + /// 截取字符串后省略部分的字符串 + /// + public static string GetSubString(string Str, int Num, string LastStr) + { + return GetSubString(Str, 0, Num, LastStr); + } + + #endregion + + #region 截取字符串,超过部分用指定字符串替换 + + /* 宗波于 2010-6-12 日添加, 来源于DiscuzNt */ + /// + /// 取指定长度的字符串 + /// + /// 要检查的字符串 + /// 起始位置 + /// 指定长度 + /// 用于替换的字符串 + /// 截取后的字符串 + public static string GetSubString(string p_SrcString, int p_StartIndex, int p_Length, string p_TailString) + { + string myResult = p_SrcString; + + byte[] bComments = Encoding.UTF8.GetBytes(p_SrcString); + foreach (char c in Encoding.UTF8.GetChars(bComments)) + { //当是日文或韩文时(注:中文的范围:\u4e00 - \u9fa5, 日文在\u0800 - \u4e00, 韩文为\xAC00-\xD7A3) + if (c is (> '\u0800' and < '\u4e00') or (> '\xAC00' and < '\xD7A3')) + { + //if (System.Text.RegularExpressions.Regex.IsMatch(p_SrcString, "[\u0800-\u4e00]+") || System.Text.RegularExpressions.Regex.IsMatch(p_SrcString, "[\xAC00-\xD7A3]+")) + //当截取的起始位置超出字段串长度时 + if (p_StartIndex >= p_SrcString.Length) + { + return ""; + } + else + { + return p_SrcString.Substring(p_StartIndex, + p_Length + p_StartIndex > p_SrcString.Length ? p_SrcString.Length - p_StartIndex : p_Length); + } + } + } + + if (p_Length >= 0) + { + byte[] bsSrcString = Encoding.Default.GetBytes(p_SrcString); + + //当字符串长度大于起始位置 + if (bsSrcString.Length > p_StartIndex) + { + int p_EndIndex = bsSrcString.Length; + + //当要截取的长度在字符串的有效长度范围内 + if (bsSrcString.Length > p_StartIndex + p_Length) + { + p_EndIndex = p_Length + p_StartIndex; + } + else + { //当不在有效范围内时,只取到字符串的结尾 + + p_Length = bsSrcString.Length - p_StartIndex; + p_TailString = ""; + } + + int nRealLength = p_Length; + int[] anResultFlag = new int[p_Length]; + byte[] bsResult = null; + + int nFlag = 0; + for (int i = p_StartIndex; i < p_EndIndex; i++) + { + if (bsSrcString[i] > 127) + { + nFlag++; + if (nFlag == 3) + { + nFlag = 1; + } + } + else + { + nFlag = 0; + } + + anResultFlag[i] = nFlag; + } + + if (bsSrcString[p_EndIndex - 1] > 127 && anResultFlag[p_Length - 1] == 1) + { + nRealLength = p_Length + 1; + } + + bsResult = new byte[nRealLength]; + + Array.Copy(bsSrcString, p_StartIndex, bsResult, 0, nRealLength); + + myResult = Encoding.Default.GetString(bsResult); + myResult += p_TailString; + } + } + + return myResult; + } + + + #endregion + +} diff --git a/Source/Core/MAX.Core/Cryptography/MD5Helper.cs b/Source/Core/MAX.Core/Cryptography/MD5Helper.cs new file mode 100644 index 0000000..8397396 --- /dev/null +++ b/Source/Core/MAX.Core/Cryptography/MD5Helper.cs @@ -0,0 +1,62 @@ +using System; +using System.Security.Cryptography; +using System.Text; + +namespace MAX.Core.Cryptography; + +public class MD5Helper +{ + /// + /// MD5加密 + /// + /// + /// + public static string Md5_32(string instring) + { + return _Md532(instring); + } + public static string Md5_Lower32(string instring) + { + return _Md532(instring, true); + } + public static string _Md532(string instring, bool IsLower = false) + { + byte[] result = Encoding.UTF8.GetBytes(string.IsNullOrEmpty(instring) ? "" : instring.Trim()); + MD5 md5 = MD5.Create(); + byte[] output = md5.ComputeHash(result); + string value = BitConverter.ToString(output).Replace("-", ""); + if (IsLower) + { + return value.ToLower(); + } + return value; + } + + + /// + /// MD5加密 + /// + /// + public static long GetLong(byte[] result) + { + return + Convert.ToInt64( + BitConverter.ToString(MD5.Create().ComputeHash(result), 4, + 8).Replace("-", ""), 16); + } + + /// + /// MD5 16位加密转long + /// + /// + /// + public static long GetLong(string url) + { + if (string.IsNullOrEmpty(url)) + { + return 0; + } + + return Convert.ToInt64(BitConverter.ToString(MD5.Create().ComputeHash(Encoding.Default.GetBytes(url)), 4, 8).Replace("-", ""), 16); + } +} diff --git a/Source/Core/MAX.Core/Cryptography/UrlHelper.cs b/Source/Core/MAX.Core/Cryptography/UrlHelper.cs new file mode 100644 index 0000000..35f2521 --- /dev/null +++ b/Source/Core/MAX.Core/Cryptography/UrlHelper.cs @@ -0,0 +1,51 @@ +using System.Text; + +namespace MAX.Core.Cryptography; + +/// +/// Url 加密 解密 +/// +public class UrlHelper +{ + /// + /// Url加密 + /// + /// 加密采用的编码方式 + /// 待加密的明文 + /// + public static string Encode(string source, Encoding encode) + { + return System.Web.HttpUtility.UrlEncode(source, encode); + } + + /// + /// Url加密,采用utf8编码方式加密 + /// + /// 待加密的明文 + /// 加密后的字符串 + public static string Encode(string source) + { + return Encode(source, Encoding.UTF8); + } + + /// + /// Url解密 + /// + /// 解密采用的编码方式,注意和加密时采用的方式一致 + /// 待解密的密文 + /// 解密后的字符串 + public static string Decode(string result, Encoding encode) + { + return System.Web.HttpUtility.UrlDecode(result, Encoding.UTF8); + } + + /// + /// Url解密,采用utf8编码方式解密 + /// + /// 待解密的密文 + /// 解密后的字符串 + public static string Decode(string result) + { + return Decode(result, Encoding.UTF8); + } +} diff --git a/Source/Core/MAX.Core/DependencyInjection/INoDependency.cs b/Source/Core/MAX.Core/DependencyInjection/INoDependency.cs new file mode 100644 index 0000000..e30b6a7 --- /dev/null +++ b/Source/Core/MAX.Core/DependencyInjection/INoDependency.cs @@ -0,0 +1,5 @@ +namespace MAX.Core.DependencyInjection; + +public interface INoDependency +{ +} diff --git a/Source/Core/MAX.Core/DependencyInjection/ISingletonDependency.cs b/Source/Core/MAX.Core/DependencyInjection/ISingletonDependency.cs new file mode 100644 index 0000000..bcacfcb --- /dev/null +++ b/Source/Core/MAX.Core/DependencyInjection/ISingletonDependency.cs @@ -0,0 +1,8 @@ +namespace MAX.Core.DependencyInjection; + +/// +/// 单例 +/// +public interface ISingletonDependency +{ +} diff --git a/Source/Core/MAX.Core/DependencyInjection/ITransientDependency.cs b/Source/Core/MAX.Core/DependencyInjection/ITransientDependency.cs new file mode 100644 index 0000000..3da15a0 --- /dev/null +++ b/Source/Core/MAX.Core/DependencyInjection/ITransientDependency.cs @@ -0,0 +1,8 @@ +namespace MAX.Core.DependencyInjection; + +/// +/// 每次实例化 +/// +public interface ITransientDependency +{ +} diff --git a/Source/Core/MAX.Core/DependencyInjection/LazyServiceProvider.cs b/Source/Core/MAX.Core/DependencyInjection/LazyServiceProvider.cs new file mode 100644 index 0000000..b7e9a59 --- /dev/null +++ b/Source/Core/MAX.Core/DependencyInjection/LazyServiceProvider.cs @@ -0,0 +1,37 @@ +using MAX.Core.Autofac; +using MAX.Core.Infrastructure; +using System; +using System.Collections.Concurrent; + +namespace MAX.Core.DependencyInjection; + +public interface ILazyServiceProvider +{ + object LazyGetService(Type serviceType); + T LazyGetService(); +} + +public class LazyServiceProvider : SingleInstance, ILazyServiceProvider +{ + protected ConcurrentDictionary> CachedServices { get; } + private LazyServiceProvider() + { + CachedServices = new ConcurrentDictionary>(); + } + public virtual T LazyGetService() + { + return (T)LazyGetService(typeof(T)); + } + + public virtual object LazyGetService(Type serviceType) + { + return GetService(serviceType); + } + public virtual object GetService(Type serviceType) + { + return CachedServices.GetOrAdd( + serviceType, + _ => new Lazy(() => IocManager.Resolve(serviceType)) + ).Value; + } +} diff --git a/Source/Core/MAX.Core/Extensions/Convert.cs b/Source/Core/MAX.Core/Extensions/Convert.cs new file mode 100644 index 0000000..382d8b2 --- /dev/null +++ b/Source/Core/MAX.Core/Extensions/Convert.cs @@ -0,0 +1,218 @@ +using MAX.Core.Helpers; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace MAX.Core.Extensions; + +/// +/// 系统扩展 - 类型转换 +/// +public static partial class Extensions +{ + /// + /// IsNullOrEmpty + /// + /// + /// + public static bool IsNullOrEmpty(this string input) + { + return string.IsNullOrEmpty(input); + } + /// + /// 安全转换为字符串,去除两端空格,当值为null时返回"" + /// + /// 输入值 + public static string SafeString(this object input) + { + return input?.ToString().Trim() ?? string.Empty; + } + + /// + /// 转换为bool + /// + /// 数据 + public static bool ToBool(this string obj, bool defvalue = false) + { + return Helpers.Convert.ToBool(obj, defvalue); + } + + /// + /// 转换为可空bool + /// + /// 数据 + public static bool? ToBoolOrNull(this string obj) + { + return Helpers.Convert.ToBoolOrNull(obj); + } + + /// + /// 转换为int + /// + /// 数据 + public static int ToInt(this string obj, int defvalue = 0) + { + return Helpers.Convert.ToInt(obj, defvalue); + } + + /// + /// 转换为可空int + /// + /// 数据 + public static int? ToIntOrNull(this string obj) + { + return Helpers.Convert.ToIntOrNull(obj); + } + + /// + /// 转换为long + /// + /// 数据 + public static long ToLong(this string obj, long defvalue = 0) + { + return Helpers.Convert.ToLong(obj, defvalue); + } + + /// + /// 转换为可空long + /// + /// 数据 + public static long? ToLongOrNull(this string obj) + { + return Helpers.Convert.ToLongOrNull(obj); + } + + /// + /// 转换为double + /// + /// 数据 + public static double ToDouble(this string obj, double defvalue = 0) + { + return Helpers.Convert.ToDouble(obj, defvalue: defvalue); + } + + /// + /// 转换为可空double + /// + /// 数据 + public static double? ToDoubleOrNull(this string obj) + { + return Helpers.Convert.ToDoubleOrNull(obj); + } + + /// + /// 转换为decimal + /// + /// 数据 + public static decimal ToDecimal(this string obj, decimal defvalue = 0) + { + return Helpers.Convert.ToDecimal(obj, defvalue: defvalue); + } + + /// + /// 转换为可空decimal + /// + /// 数据 + public static decimal? ToDecimalOrNull(this string obj) + { + return Helpers.Convert.ToDecimalOrNull(obj); + } + + /// + /// 转换为日期 + /// + /// 数据 + public static DateTime ToDate(this string obj) + { + return Helpers.Convert.ToDate(obj); + } + + /// + /// 转换为可空日期 + /// + /// 数据 + public static DateTime? ToDateOrNull(this string obj) + { + return Helpers.Convert.ToDateOrNull(obj); + } + + /// + /// 转换为Guid + /// + /// 数据 + public static Guid ToGuid(this string obj) + { + return Helpers.Convert.ToGuid(obj); + } + + /// + /// 转换为可空Guid + /// + /// 数据 + public static Guid? ToGuidOrNull(this string obj) + { + return Helpers.Convert.ToGuidOrNull(obj); + } + + /// + /// 转换为Guid集合 + /// + /// 数据,范例: "83B0233C-A24F-49FD-8083-1337209EBC9A,EAB523C6-2FE7-47BE-89D5-C6D440C3033A" + public static List ToGuidList(this string obj) + { + return Helpers.Convert.ToGuidList(obj); + } + + /// + /// 转换为Guid集合 + /// + /// 字符串集合 + public static List ToGuidList(this IList obj) + { + if (obj == null) + { + return []; + } + + return obj.Select(t => t.ToGuid()).ToList(); + } + /// + /// 转换枚举描述 + /// + /// + /// + public static string ToDescription(this Enum obj) + { + return EnumHelper.GetDescription(obj.GetType(), obj); + } + + /// + /// 枚举值 → 单个 EnumItem + /// + /// + /// + /// + public static EnumItem ToItem(this TEnum value) where TEnum : struct, Enum + { + return new EnumItem(value); + } + /// + /// 是否单一标记 + /// + /// + /// + public static bool IsSingleFlag(Enum obj) + { + return EnumHelper.IsSingleFlag(obj); + } + /// + /// 获取第一个标记 + /// + /// + /// + /// + public static Enum GetFirstFlag(this Enum obj) + { + return EnumHelper.GetFirstFlag(obj); + } +} diff --git a/Source/Core/MAX.Core/Extensions/DictionaryExtension.cs b/Source/Core/MAX.Core/Extensions/DictionaryExtension.cs new file mode 100644 index 0000000..d71917e --- /dev/null +++ b/Source/Core/MAX.Core/Extensions/DictionaryExtension.cs @@ -0,0 +1,33 @@ +using MAX.Core.Cryptography; +using System.Collections.Generic; +using System.Text; + +namespace MAX.Core.Extensions; + +public static class DictionaryExtension +{ + /// + /// 字典转url Get 参数 + /// + /// + /// + public static string ToUrlParams(this IDictionary parameters, bool IsUrlEncode = true) + { + IDictionary sortedParams = new SortedDictionary(parameters); + IEnumerator> dem = sortedParams.GetEnumerator(); + StringBuilder sb = new(""); + while (dem.MoveNext()) + { + string key = dem.Current.Key; + string value = dem.Current.Value; + if (!string.IsNullOrEmpty(key)) + { + sb.Append(key).Append("=").Append(IsUrlEncode ? UrlHelper.Encode(value) : value).Append("&"); + + } + } + string content = sb.ToString().Substring(0, sb.Length - 1); + + return content; + } +} diff --git a/Source/Core/MAX.Core/Helpers/Common.cs b/Source/Core/MAX.Core/Helpers/Common.cs new file mode 100644 index 0000000..172d10d --- /dev/null +++ b/Source/Core/MAX.Core/Helpers/Common.cs @@ -0,0 +1,55 @@ +using System; + +namespace MAX.Core.Helpers; + +/// +/// 常用公共操作 +/// +public static partial class Common +{ + /// + /// 获取类型 + /// + /// 类型 + public static Type GetType() + { + return GetType(typeof(T)); + } + + /// + /// 获取类型 + /// + /// 类型 + public static Type GetType(Type type) + { + return Nullable.GetUnderlyingType(type) ?? type; + } + + /// + /// 换行符 + /// + public static string Line => Environment.NewLine; + + +#if NET5_0 + /// + /// 是否Linux操作系统 + /// + public static bool IsLinux => RuntimeInformation.IsOSPlatform(OSPlatform.Linux); + + /// + /// 是否Windows操作系统 + /// + public static bool IsWindows => RuntimeInformation.IsOSPlatform(OSPlatform.Windows); + + /// + /// 是否苹果操作系统 + /// + public static bool IsOsx => RuntimeInformation.IsOSPlatform(OSPlatform.OSX); + + /// + /// 当前操作系统 + /// + public static string System => IsWindows ? "Windows" : IsLinux ? "Linux" : IsOsx ? "OSX" : string.Empty; +#endif +} diff --git a/Source/Core/MAX.Core/Helpers/CommonItem.cs b/Source/Core/MAX.Core/Helpers/CommonItem.cs new file mode 100644 index 0000000..7d83ee4 --- /dev/null +++ b/Source/Core/MAX.Core/Helpers/CommonItem.cs @@ -0,0 +1,78 @@ +using System; + +namespace MAX.Core.Helpers; + +public class CommonItem : IComparable +{ + /// + /// 初始化 + /// + /// 文本 + /// 值 + /// 排序号 + /// 组 + /// 禁用 + public CommonItem(string text, object value, string des = "", int? sortId = null, string group = null, bool? disabled = null) + { + Text = text; + Value = value; + Description = des; + SortId = sortId; + Group = group; + Disabled = disabled; + } + + /// + /// 文本 + /// + public string Text { get; } + + /// + /// 值 + /// + public object Value { get; protected set; } + /// + /// 描述 + /// + public string Description { get; set; } + /// + /// 排序号 + /// + public int? SortId { get; set; } + + /// + /// 组 + /// + public string Group { get; } + /// + /// 是否选中 + /// + public bool IsSelected { get; set; } + /// + /// 禁用 + /// + public bool? Disabled { get; } + + /// + /// 比较 + /// + /// 其它列表项 + public int CompareTo(CommonItem other) + { + return string.Compare(Text, other.Text, StringComparison.CurrentCulture); + } +} + +public class CommonItem : CommonItem +{ + public new T Value + { + get { return (T)base.Value; } + set { base.Value = value; } + } + public CommonItem(string text, T value, string des = "", int? sortId = null, string group = null, bool? disabled = null) : base(text, value, des, sortId, group, disabled) + { + } + + +} diff --git a/Source/Core/MAX.Core/Helpers/Convert.cs b/Source/Core/MAX.Core/Helpers/Convert.cs new file mode 100644 index 0000000..03dc271 --- /dev/null +++ b/Source/Core/MAX.Core/Helpers/Convert.cs @@ -0,0 +1,385 @@ +using MAX.Core.Extensions; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace MAX.Core.Helpers; + +[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field)] +public class ConvertAttribute : Attribute +{ + public Type ConverterType { get; set; } + public ConvertAttribute(Type type) + { + ConverterType = type; + } +} +public interface IConverter +{ + object Converter(object input); +} +/// +/// 类型转换 +/// +public static partial class Convert +{ + /// + /// 转换为32位整型 + /// + /// 输入值 + public static int ToInt(object input, int defvalue = 0) + { + return ToIntOrNull(input) ?? defvalue; + } + + /// + /// 转换为32位可空整型 + /// + /// 输入值 + public static int? ToIntOrNull(object input) + { + bool success = int.TryParse(input.SafeString(), out int result); + if (success) + { + return result; + } + + try + { + double? temp = ToDoubleOrNull(input, 0); + if (temp == null) + { + return null; + } + + return System.Convert.ToInt32(temp); + } + catch + { + return null; + } + } + + /// + /// 转换为64位整型 + /// + /// 输入值 + public static long ToLong(object input, long defvalue = 0) + { + return ToLongOrNull(input) ?? defvalue; + } + + /// + /// 转换为64位可空整型 + /// + /// 输入值 + public static long? ToLongOrNull(object input) + { + bool success = long.TryParse(input.SafeString(), out long result); + if (success) + { + return result; + } + + try + { + decimal? temp = ToDecimalOrNull(input, 0); + if (temp == null) + { + return null; + } + + return System.Convert.ToInt64(temp); + } + catch + { + return null; + } + } + + /// + /// 转换为32位浮点型,并按指定小数位舍入 + /// + /// 输入值 + /// 小数位数 + public static float ToFloat(object input, int? digits = null) + { + return ToFloatOrNull(input, digits) ?? 0; + } + + /// + /// 转换为32位可空浮点型,并按指定小数位舍入 + /// + /// 输入值 + /// 小数位数 + public static float? ToFloatOrNull(object input, int? digits = null) + { + bool success = float.TryParse(input.SafeString(), out float result); + if (!success) + { + return null; + } + + if (digits == null) + { + return result; + } + + return (float)Math.Round(result, digits.Value); + } + + /// + /// 转换为64位浮点型,并按指定小数位舍入 + /// + /// 输入值 + /// 小数位数 + public static double ToDouble(object input, int? digits = null, double defvalue = 0) + { + return ToDoubleOrNull(input, digits) ?? defvalue; + } + + /// + /// 转换为64位可空浮点型,并按指定小数位舍入 + /// + /// 输入值 + /// 小数位数 + public static double? ToDoubleOrNull(object input, int? digits = null) + { + if (input is bool b) + { + return b ? 1 : 0; + } + bool success = double.TryParse(input.SafeString(), out double result); + if (!success) + { + return null; + } + + if (digits == null) + { + return result; + } + + return Math.Round(result, digits.Value); + } + + /// + /// 转换为128位浮点型,并按指定小数位舍入 + /// + /// 输入值 + /// 小数位数 + public static decimal ToDecimal(object input, int? digits = null, decimal defvalue = 0) + { + return ToDecimalOrNull(input, digits) ?? defvalue; + } + + /// + /// 转换为128位可空浮点型,并按指定小数位舍入 + /// + /// 输入值 + /// 小数位数 + public static decimal? ToDecimalOrNull(object input, int? digits = null) + { + bool success = decimal.TryParse(input.SafeString(), out decimal result); + if (!success) + { + return null; + } + + if (digits == null) + { + return result; + } + + return Math.Round(result, digits.Value); + } + + /// + /// 转换为布尔值 + /// + /// 输入值 + public static bool ToBool(object input, bool defvalue = false) + { + return ToBoolOrNull(input) ?? defvalue; + } + + /// + /// 转换为可空布尔值 + /// + /// 输入值 + public static bool? ToBoolOrNull(object input) + { + bool? value = GetBool(input); + if (value != null) + { + return value.Value; + } + + return bool.TryParse(input.SafeString(), out bool result) ? result : null; + } + + /// + /// 获取布尔值 + /// + private static bool? GetBool(object input) + { + return input.SafeString().ToLower() switch + { + "0" => false, + "否" => false, + "不" => false, + "no" => false, + "fail" => false, + "1" => true, + "是" => true, + "ok" => true, + "yes" => true, + _ => null, + }; + } + + /// + /// 转换为日期 + /// + /// 输入值 + public static DateTime ToDate(object input) + { + return ToDateOrNull(input) ?? DateTime.MinValue; + } + + /// + /// 转换为可空日期 + /// + /// 输入值 + public static DateTime? ToDateOrNull(object input) + { + return DateTime.TryParse(input.SafeString(), out DateTime result) ? result : null; + } + + /// + /// 转换为Guid + /// + /// 输入值 + public static Guid ToGuid(object input) + { + return ToGuidOrNull(input) ?? Guid.Empty; + } + + /// + /// 转换为可空Guid + /// + /// 输入值 + public static Guid? ToGuidOrNull(object input) + { + return Guid.TryParse(input.SafeString(), out Guid result) ? result : null; + } + + /// + /// 转换为Guid集合 + /// + /// 以逗号分隔的Guid集合字符串,范例:83B0233C-A24F-49FD-8083-1337209EBC9A,EAB523C6-2FE7-47BE-89D5-C6D440C3033A + public static List ToGuidList(string input) + { + return ToList(input); + } + + /// + /// 泛型集合转换 + /// + /// 目标元素类型 + /// 以逗号分隔的元素集合字符串,范例:83B0233C-A24F-49FD-8083-1337209EBC9A,EAB523C6-2FE7-47BE-89D5-C6D440C3033A + public static List ToList(string input) + { + List result = []; + if (string.IsNullOrWhiteSpace(input)) + { + return result; + } + + string[] array = input.Split(','); + result.AddRange(from each in array where !string.IsNullOrWhiteSpace(each) select To(each)); + return result; + } + + /// + /// 通用泛型转换 + /// + /// 目标类型 + /// 输入值 + public static T To(object input) + { + Type type = Common.GetType(); + return (T)To(type, input); + } + public static object To(Type type, object input) + { + object defValue = null; + if (type != null) + { + defValue = type.IsValueType ? Activator.CreateInstance(type) : null; + } + if (input == null) + { + return defValue; + } + + if (input is string && string.IsNullOrWhiteSpace(input.ToString())) + { + return defValue; + } + + string typeName = type.Name.ToLower(); + try + { + if (typeName == "string") + { + return input.ToString(); + } + + if (typeName == "guid") + { + return new Guid(input.ToString()); + } + + if (type.IsEnum) + { + return EnumHelper.Parse(type, input); + } + + if (typeof(IConverter).IsAssignableFrom(type)) + { + object instance = Activator.CreateInstance(type); + return ((IConverter)instance).Converter(input); + } + if (input is IConvertible) + { + return System.Convert.ChangeType(input, type); + } + + return input; + } + catch + { + return defValue; + } + } + /// + /// 转换为字节数组 + /// + /// 输入值 + public static byte[] ToBytes(string input) + { + return ToBytes(input, Encoding.UTF8); + } + + /// + /// 转换为字节数组 + /// + /// 输入值 + /// 字符编码 + public static byte[] ToBytes(string input, Encoding encoding) + { + return string.IsNullOrWhiteSpace(input) ? new byte[] { } : encoding.GetBytes(input); + } +} diff --git a/Source/Core/MAX.Core/Helpers/CopyHelper.cs b/Source/Core/MAX.Core/Helpers/CopyHelper.cs new file mode 100644 index 0000000..dfb4e5f --- /dev/null +++ b/Source/Core/MAX.Core/Helpers/CopyHelper.cs @@ -0,0 +1,1151 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; + +namespace MAX.Core.Helpers; + +public class CopyIgnoreAttribute : Attribute { } + +/// +/// 拷贝帮助 +/// +public class CopyHelper +{ + /// + /// 反射深拷贝 + /// + /// + /// + /// + public static T DeepCopy(T obj) + { + return (T)DeepCopy((Object)obj, null); + } + + public static T DeepCopy(T obj, Func filter = null) + { + return (T)DeepCopy((Object)obj, filter); + } + + public static Object DeepCopy(Object originalObject, Func filter = null) + { + return InternalCopy( + originalObject, + new Dictionary(new ReferenceEqualityComparer()), + filter + ); + } + + private static readonly MethodInfo CloneMethod = typeof(Object).GetMethod( + "MemberwiseClone", + BindingFlags.NonPublic | BindingFlags.Instance + ); + + public static bool IsPrimitive(Type type) + { + if (type == typeof(String)) + { + return true; + } + + return type.IsValueType & type.IsPrimitive; + } + + private static Object InternalCopy( + Object originalObject, + IDictionary visited, + Func filter = null + ) + { + if (originalObject == null) + { + return null; + } + + Type typeToReflect = originalObject.GetType(); + if (IsPrimitive(typeToReflect)) + { + return originalObject; + } + + if (visited.ContainsKey(originalObject)) + { + return visited[originalObject]; + } + + if (typeof(Delegate).IsAssignableFrom(typeToReflect)) + { + return null; + } + + object cloneObject = CloneMethod.Invoke(originalObject, null); + if (typeToReflect.IsArray) + { + Type arrayType = typeToReflect.GetElementType(); + if (IsPrimitive(arrayType) == false) + { + Array clonedArray = (Array)cloneObject; + clonedArray.ForEach( + (array, indices) => + array.SetValue( + InternalCopy(clonedArray.GetValue(indices), visited, filter), + indices + ) + ); + } + } + visited.Add(originalObject, cloneObject); + CopyFields(originalObject, visited, cloneObject, typeToReflect, filter: filter); + RecursiveCopyBaseTypePrivateFields( + originalObject, + visited, + cloneObject, + typeToReflect, + filter + ); + return cloneObject; + } + + private static void RecursiveCopyBaseTypePrivateFields( + object originalObject, + IDictionary visited, + object cloneObject, + Type typeToReflect, + Func filter = null + ) + { + if (typeToReflect.BaseType != null) + { + RecursiveCopyBaseTypePrivateFields( + originalObject, + visited, + cloneObject, + typeToReflect.BaseType + ); + CopyFields( + originalObject, + visited, + cloneObject, + typeToReflect.BaseType, + BindingFlags.Instance | BindingFlags.NonPublic, + info => info.IsPrivate + ); + } + } + + private static void CopyFields( + object originalObject, + IDictionary visited, + object cloneObject, + Type typeToReflect, + BindingFlags bindingFlags = + BindingFlags.Instance + | BindingFlags.NonPublic + | BindingFlags.Public + | BindingFlags.FlattenHierarchy, + Func filter = null + ) + { + foreach (FieldInfo fieldInfo in typeToReflect.GetFields(bindingFlags)) + { + if (filter != null && filter(fieldInfo) == false) + { + continue; + } + + if (IsPrimitive(fieldInfo.FieldType)) + { + continue; + } + + object originalFieldValue = fieldInfo.GetValue(originalObject); + object clonedFieldValue = InternalCopy(originalFieldValue, visited, filter: filter); + fieldInfo.SetValue(cloneObject, clonedFieldValue); + } + } +} + +/// +/// Superfast deep copier class, which uses Expression trees. +/// +public static class DeepCopyByExpressionTrees +{ + private static readonly object IsStructTypeToDeepCopyDictionaryLocker = new(); + private static Dictionary IsStructTypeToDeepCopyDictionary = + []; + + private static readonly object CompiledCopyFunctionsDictionaryLocker = new(); + private static Dictionary< + Type, + Func, object> + > CompiledCopyFunctionsDictionary = + []; + + private static readonly Type ObjectType = typeof(Object); + private static readonly Type ObjectDictionaryType = typeof(Dictionary); + + /// + /// Creates a deep copy of an object. + /// + /// Object type. + /// Object to copy. + /// Dictionary of already copied objects (Keys: original objects, Values: their copies). + /// + public static T DeepCopyByExpressionTree( + this T original, + Dictionary copiedReferencesDict = null + ) + { + return (T)DeepCopyByExpressionTreeObj( + original, + false, + copiedReferencesDict + ?? new Dictionary(new ReferenceEqualityComparer()) + ); + } + + private static object DeepCopyByExpressionTreeObj( + object original, + bool forceDeepCopy, + Dictionary copiedReferencesDict + ) + { + if (original == null) + { + return null; + } + + Type type = original.GetType(); + + if (IsDelegate(type)) + { + return null; + } + + if (!forceDeepCopy && !IsTypeToDeepCopy(type)) + { + return original; + } + + + if (copiedReferencesDict.TryGetValue(original, out object alreadyCopiedObject)) + { + return alreadyCopiedObject; + } + + if (type == ObjectType) + { + return new object(); + } + + Func, object> compiledCopyFunction = GetOrCreateCompiledLambdaCopyFunction(type); + + object copy = compiledCopyFunction(original, copiedReferencesDict); + + return copy; + } + + private static Func< + object, + Dictionary, + object + > GetOrCreateCompiledLambdaCopyFunction(Type type) + { + // The following structure ensures that multiple threads can use the dictionary + // even while dictionary is locked and being updated by other thread. + // That is why we do not modify the old dictionary instance but + // we replace it with a new instance everytime. + + + if (!CompiledCopyFunctionsDictionary.TryGetValue(type, out Func, object> compiledCopyFunction)) + { + lock (CompiledCopyFunctionsDictionaryLocker) + { + if ( + !CompiledCopyFunctionsDictionary.TryGetValue(type, out compiledCopyFunction) + ) + { + Expression, object>> uncompiledCopyFunction = CreateCompiledLambdaCopyFunctionForType(type); + + compiledCopyFunction = uncompiledCopyFunction.Compile(); + + Dictionary, object>> dictionaryCopy = CompiledCopyFunctionsDictionary.ToDictionary( + pair => pair.Key, + pair => pair.Value + ); + + dictionaryCopy.Add(type, compiledCopyFunction); + + CompiledCopyFunctionsDictionary = dictionaryCopy; + } + } + } + + return compiledCopyFunction; + } + + private static Expression< + Func, object> + > CreateCompiledLambdaCopyFunctionForType(Type type) + { + + ///// INITIALIZATION OF EXPRESSIONS AND VARIABLES + + InitializeExpressions( + type, + out ParameterExpression inputParameter, + out ParameterExpression inputDictionary, + out ParameterExpression outputVariable, + out ParameterExpression boxingVariable, + out LabelTarget endLabel, + out List variables, + out List expressions + ); + + ///// RETURN NULL IF ORIGINAL IS NULL + + IfNullThenReturnNullExpression(inputParameter, endLabel, expressions); + + ///// MEMBERWISE CLONE ORIGINAL OBJECT + + MemberwiseCloneInputToOutputExpression( + type, + inputParameter, + outputVariable, + expressions + ); + + ///// STORE COPIED OBJECT TO REFERENCES DICTIONARY + + if (IsClassOtherThanString(type)) + { + StoreReferencesIntoDictionaryExpression( + inputParameter, + inputDictionary, + outputVariable, + expressions + ); + } + + ///// COPY ALL NONVALUE OR NONPRIMITIVE FIELDS + + FieldsCopyExpressions( + type, + inputParameter, + inputDictionary, + outputVariable, + boxingVariable, + expressions + ); + + ///// COPY ELEMENTS OF ARRAY + + if (IsArray(type) && IsTypeToDeepCopy(type.GetElementType())) + { + CreateArrayCopyLoopExpression( + type, + inputParameter, + inputDictionary, + outputVariable, + variables, + expressions + ); + } + + ///// COMBINE ALL EXPRESSIONS INTO LAMBDA FUNCTION + + Expression, object>> lambda = CombineAllIntoLambdaFunctionExpression( + inputParameter, + inputDictionary, + outputVariable, + endLabel, + variables, + expressions + ); + + return lambda; + } + + private static void InitializeExpressions( + Type type, + out ParameterExpression inputParameter, + out ParameterExpression inputDictionary, + out ParameterExpression outputVariable, + out ParameterExpression boxingVariable, + out LabelTarget endLabel, + out List variables, + out List expressions + ) + { + inputParameter = Expression.Parameter(ObjectType); + + inputDictionary = Expression.Parameter(ObjectDictionaryType); + + outputVariable = Expression.Variable(type); + + boxingVariable = Expression.Variable(ObjectType); + + endLabel = Expression.Label(); + + variables = []; + + expressions = []; + + variables.Add(outputVariable); + variables.Add(boxingVariable); + } + + private static void IfNullThenReturnNullExpression( + ParameterExpression inputParameter, + LabelTarget endLabel, + List expressions + ) + { + ///// Intended code: + ///// + ///// if (input == null) + ///// { + ///// return null; + ///// } + + ConditionalExpression ifNullThenReturnNullExpression = Expression.IfThen( + Expression.Equal(inputParameter, Expression.Constant(null, ObjectType)), + Expression.Return(endLabel) + ); + + expressions.Add(ifNullThenReturnNullExpression); + } + + private static void MemberwiseCloneInputToOutputExpression( + Type type, + ParameterExpression inputParameter, + ParameterExpression outputVariable, + List expressions + ) + { + ///// Intended code: + ///// + ///// var output = ()input.MemberwiseClone(); + + MethodInfo memberwiseCloneMethod = ObjectType.GetMethod( + "MemberwiseClone", + BindingFlags.NonPublic | BindingFlags.Instance + ); + + BinaryExpression memberwiseCloneInputExpression = Expression.Assign( + outputVariable, + Expression.Convert(Expression.Call(inputParameter, memberwiseCloneMethod), type) + ); + + expressions.Add(memberwiseCloneInputExpression); + } + + private static void StoreReferencesIntoDictionaryExpression( + ParameterExpression inputParameter, + ParameterExpression inputDictionary, + ParameterExpression outputVariable, + List expressions + ) + { + ///// Intended code: + ///// + ///// inputDictionary[(Object)input] = (Object)output; + + BinaryExpression storeReferencesExpression = Expression.Assign( + Expression.Property( + inputDictionary, + ObjectDictionaryType.GetProperty("Item"), + inputParameter + ), + Expression.Convert(outputVariable, ObjectType) + ); + + expressions.Add(storeReferencesExpression); + } + + private static Expression< + Func, object> + > CombineAllIntoLambdaFunctionExpression( + ParameterExpression inputParameter, + ParameterExpression inputDictionary, + ParameterExpression outputVariable, + LabelTarget endLabel, + List variables, + List expressions + ) + { + expressions.Add(Expression.Label(endLabel)); + + expressions.Add(Expression.Convert(outputVariable, ObjectType)); + + BlockExpression finalBody = Expression.Block(variables, expressions); + + Expression, object>> lambda = Expression.Lambda, object>>( + finalBody, + inputParameter, + inputDictionary + ); + + return lambda; + } + + private static void CreateArrayCopyLoopExpression( + Type type, + ParameterExpression inputParameter, + ParameterExpression inputDictionary, + ParameterExpression outputVariable, + List variables, + List expressions + ) + { + ///// Intended code: + ///// + ///// int i1, i2, ..., in; + ///// + ///// int length1 = inputarray.GetLength(0); + ///// i1 = 0; + ///// while (true) + ///// { + ///// if (i1 >= length1) + ///// { + ///// goto ENDLABELFORLOOP1; + ///// } + ///// int length2 = inputarray.GetLength(1); + ///// i2 = 0; + ///// while (true) + ///// { + ///// if (i2 >= length2) + ///// { + ///// goto ENDLABELFORLOOP2; + ///// } + ///// ... + ///// ... + ///// ... + ///// int lengthn = inputarray.GetLength(n); + ///// in = 0; + ///// while (true) + ///// { + ///// if (in >= lengthn) + ///// { + ///// goto ENDLABELFORLOOPn; + ///// } + ///// outputarray[i1, i2, ..., in] + ///// = ()DeepCopyByExpressionTreeObj( + ///// (Object)inputarray[i1, i2, ..., in]) + ///// in++; + ///// } + ///// ENDLABELFORLOOPn: + ///// ... + ///// ... + ///// ... + ///// i2++; + ///// } + ///// ENDLABELFORLOOP2: + ///// i1++; + ///// } + ///// ENDLABELFORLOOP1: + + int rank = type.GetArrayRank(); + + List indices = GenerateIndices(rank); + + variables.AddRange(indices); + + Type elementType = type.GetElementType(); + + BinaryExpression assignExpression = ArrayFieldToArrayFieldAssignExpression( + inputParameter, + inputDictionary, + outputVariable, + elementType, + type, + indices + ); + + Expression forExpression = assignExpression; + + for (int dimension = 0; dimension < rank; dimension++) + { + ParameterExpression indexVariable = indices[dimension]; + + forExpression = LoopIntoLoopExpression( + inputParameter, + indexVariable, + forExpression, + dimension + ); + } + + expressions.Add(forExpression); + } + + private static List GenerateIndices(int arrayRank) + { + ///// Intended code: + ///// + ///// int i1, i2, ..., in; + + List indices = []; + + for (int i = 0; i < arrayRank; i++) + { + ParameterExpression indexVariable = Expression.Variable(typeof(Int32)); + + indices.Add(indexVariable); + } + + return indices; + } + + private static BinaryExpression ArrayFieldToArrayFieldAssignExpression( + ParameterExpression inputParameter, + ParameterExpression inputDictionary, + ParameterExpression outputVariable, + Type elementType, + Type arrayType, + List indices + ) + { + ///// Intended code: + ///// + ///// outputarray[i1, i2, ..., in] + ///// = ()DeepCopyByExpressionTreeObj( + ///// (Object)inputarray[i1, i2, ..., in]); + + IndexExpression indexTo = Expression.ArrayAccess(outputVariable, indices); + + MethodCallExpression indexFrom = Expression.ArrayIndex( + Expression.Convert(inputParameter, arrayType), + indices + ); + + bool forceDeepCopy = elementType != ObjectType; + + UnaryExpression rightSide = Expression.Convert( + Expression.Call( + DeepCopyByExpressionTreeObjMethod, + Expression.Convert(indexFrom, ObjectType), + Expression.Constant(forceDeepCopy, typeof(Boolean)), + inputDictionary + ), + elementType + ); + + BinaryExpression assignExpression = Expression.Assign(indexTo, rightSide); + + return assignExpression; + } + + private static BlockExpression LoopIntoLoopExpression( + ParameterExpression inputParameter, + ParameterExpression indexVariable, + Expression loopToEncapsulate, + int dimension + ) + { + ///// Intended code: + ///// + ///// int length = inputarray.GetLength(dimension); + ///// i = 0; + ///// while (true) + ///// { + ///// if (i >= length) + ///// { + ///// goto ENDLABELFORLOOP; + ///// } + ///// loopToEncapsulate; + ///// i++; + ///// } + ///// ENDLABELFORLOOP: + + ParameterExpression lengthVariable = Expression.Variable(typeof(Int32)); + + LabelTarget endLabelForThisLoop = Expression.Label(); + + LoopExpression newLoop = Expression.Loop( + Expression.Block( + new ParameterExpression[0], + Expression.IfThen( + Expression.GreaterThanOrEqual(indexVariable, lengthVariable), + Expression.Break(endLabelForThisLoop) + ), + loopToEncapsulate, + Expression.PostIncrementAssign(indexVariable) + ), + endLabelForThisLoop + ); + + BinaryExpression lengthAssignment = GetLengthForDimensionExpression( + lengthVariable, + inputParameter, + dimension + ); + + BinaryExpression indexAssignment = Expression.Assign(indexVariable, Expression.Constant(0)); + + return Expression.Block( + new[] { lengthVariable }, + lengthAssignment, + indexAssignment, + newLoop + ); + } + + private static BinaryExpression GetLengthForDimensionExpression( + ParameterExpression lengthVariable, + ParameterExpression inputParameter, + int i + ) + { + ///// Intended code: + ///// + ///// length = ((Array)input).GetLength(i); + + MethodInfo getLengthMethod = typeof(Array).GetMethod( + "GetLength", + BindingFlags.Public | BindingFlags.Instance + ); + + ConstantExpression dimensionConstant = Expression.Constant(i); + + return Expression.Assign( + lengthVariable, + Expression.Call( + Expression.Convert(inputParameter, typeof(Array)), + getLengthMethod, + new[] { dimensionConstant } + ) + ); + } + + private static void FieldsCopyExpressions( + Type type, + ParameterExpression inputParameter, + ParameterExpression inputDictionary, + ParameterExpression outputVariable, + ParameterExpression boxingVariable, + List expressions + ) + { + FieldInfo[] fields = GetAllRelevantFields(type); + + List readonlyFields = fields.Where(f => f.IsInitOnly).ToList(); + List writableFields = fields.Where(f => !f.IsInitOnly).ToList(); + + ///// READONLY FIELDS COPY (with boxing) + + bool shouldUseBoxing = readonlyFields.Any(); + + if (shouldUseBoxing) + { + BinaryExpression boxingExpression = Expression.Assign( + boxingVariable, + Expression.Convert(outputVariable, ObjectType) + ); + + expressions.Add(boxingExpression); + } + + foreach (FieldInfo field in readonlyFields) + { + if (IsDelegate(field.FieldType)) + { + ReadonlyFieldToNullExpression(field, boxingVariable, expressions); + } + else + { + ReadonlyFieldCopyExpression( + type, + field, + inputParameter, + inputDictionary, + boxingVariable, + expressions + ); + } + } + + if (shouldUseBoxing) + { + BinaryExpression unboxingExpression = Expression.Assign( + outputVariable, + Expression.Convert(boxingVariable, type) + ); + + expressions.Add(unboxingExpression); + } + + ///// NOT-READONLY FIELDS COPY + + foreach (FieldInfo field in writableFields) + { + if (IsDelegate(field.FieldType)) + { + WritableFieldToNullExpression(field, outputVariable, expressions); + } + else + { + WritableFieldCopyExpression( + type, + field, + inputParameter, + inputDictionary, + outputVariable, + expressions + ); + } + } + } + + private static FieldInfo[] GetAllRelevantFields(Type type, bool forceAllFields = false) + { + List fieldsList = []; + + Type typeCache = type; + + while (typeCache != null) + { + fieldsList.AddRange( + typeCache + .GetFields( + BindingFlags.Instance + | BindingFlags.Public + | BindingFlags.NonPublic + | BindingFlags.FlattenHierarchy + ) + .Where(field => forceAllFields || IsTypeToDeepCopy(field.FieldType)) + ); + + typeCache = typeCache.BaseType; + } + + return fieldsList.ToArray(); + } + + private static FieldInfo[] GetAllFields(Type type) + { + return GetAllRelevantFields(type, forceAllFields: true); + } + + private static readonly Type FieldInfoType = typeof(FieldInfo); + private static readonly MethodInfo SetValueMethod = FieldInfoType.GetMethod( + "SetValue", + new[] { ObjectType, ObjectType } + ); + + private static void ReadonlyFieldToNullExpression( + FieldInfo field, + ParameterExpression boxingVariable, + List expressions + ) + { + // This option must be implemented by Reflection because of the following: + // https://visualstudio.uservoice.com/forums/121579-visual-studio-2015/suggestions/2727812-allow-expression-assign-to-set-readonly-struct-f + + ///// Intended code: + ///// + ///// fieldInfo.SetValue(boxing, null); + + MethodCallExpression fieldToNullExpression = Expression.Call( + Expression.Constant(field), + SetValueMethod, + boxingVariable, + Expression.Constant(null, field.FieldType) + ); + + expressions.Add(fieldToNullExpression); + } + + private static readonly Type ThisType = typeof(DeepCopyByExpressionTrees); + private static readonly MethodInfo DeepCopyByExpressionTreeObjMethod = ThisType.GetMethod( + "DeepCopyByExpressionTreeObj", + BindingFlags.NonPublic | BindingFlags.Static + ); + + private static void ReadonlyFieldCopyExpression( + Type type, + FieldInfo field, + ParameterExpression inputParameter, + ParameterExpression inputDictionary, + ParameterExpression boxingVariable, + List expressions + ) + { + // This option must be implemented by Reflection (SetValueMethod) because of the following: + // https://visualstudio.uservoice.com/forums/121579-visual-studio-2015/suggestions/2727812-allow-expression-assign-to-set-readonly-struct-f + + ///// Intended code: + ///// + ///// fieldInfo.SetValue(boxing, DeepCopyByExpressionTreeObj((Object)(()input).)) + + MemberExpression fieldFrom = Expression.Field(Expression.Convert(inputParameter, type), field); + + bool forceDeepCopy = field.FieldType != ObjectType; + + MethodCallExpression fieldDeepCopyExpression = Expression.Call( + Expression.Constant(field, FieldInfoType), + SetValueMethod, + boxingVariable, + Expression.Call( + DeepCopyByExpressionTreeObjMethod, + Expression.Convert(fieldFrom, ObjectType), + Expression.Constant(forceDeepCopy, typeof(Boolean)), + inputDictionary + ) + ); + + expressions.Add(fieldDeepCopyExpression); + } + + private static void WritableFieldToNullExpression( + FieldInfo field, + ParameterExpression outputVariable, + List expressions + ) + { + ///// Intended code: + ///// + ///// output. = ()null; + + MemberExpression fieldTo = Expression.Field(outputVariable, field); + + BinaryExpression fieldToNullExpression = Expression.Assign( + fieldTo, + Expression.Constant(null, field.FieldType) + ); + + expressions.Add(fieldToNullExpression); + } + + private static void WritableFieldCopyExpression( + Type type, + FieldInfo field, + ParameterExpression inputParameter, + ParameterExpression inputDictionary, + ParameterExpression outputVariable, + List expressions + ) + { + ///// Intended code: + ///// + ///// output. = ()DeepCopyByExpressionTreeObj((Object)(()input).); + + MemberExpression fieldFrom = Expression.Field(Expression.Convert(inputParameter, type), field); + + Type fieldType = field.FieldType; + + MemberExpression fieldTo = Expression.Field(outputVariable, field); + + bool forceDeepCopy = field.FieldType != ObjectType; + + BinaryExpression fieldDeepCopyExpression = Expression.Assign( + fieldTo, + Expression.Convert( + Expression.Call( + DeepCopyByExpressionTreeObjMethod, + Expression.Convert(fieldFrom, ObjectType), + Expression.Constant(forceDeepCopy, typeof(Boolean)), + inputDictionary + ), + fieldType + ) + ); + + expressions.Add(fieldDeepCopyExpression); + } + + private static bool IsArray(Type type) + { + return type.IsArray; + } + + private static bool IsDelegate(Type type) + { + return typeof(Delegate).IsAssignableFrom(type); + } + + private static bool IsTypeToDeepCopy(Type type) + { + return IsClassOtherThanString(type) || IsStructWhichNeedsDeepCopy(type); + } + + private static bool IsClassOtherThanString(Type type) + { + return !type.IsValueType && type != typeof(String); + } + + private static bool IsStructWhichNeedsDeepCopy(Type type) + { + // The following structure ensures that multiple threads can use the dictionary + // even while dictionary is locked and being updated by other thread. + // That is why we do not modify the old dictionary instance but + // we replace it with a new instance everytime. + + + if (!IsStructTypeToDeepCopyDictionary.TryGetValue(type, out bool isStructTypeToDeepCopy)) + { + lock (IsStructTypeToDeepCopyDictionaryLocker) + { + if ( + !IsStructTypeToDeepCopyDictionary.TryGetValue( + type, + out isStructTypeToDeepCopy + ) + ) + { + isStructTypeToDeepCopy = IsStructWhichNeedsDeepCopy_NoDictionaryUsed(type); + + Dictionary newDictionary = IsStructTypeToDeepCopyDictionary.ToDictionary( + pair => pair.Key, + pair => pair.Value + ); + + newDictionary[type] = isStructTypeToDeepCopy; + + IsStructTypeToDeepCopyDictionary = newDictionary; + } + } + } + + return isStructTypeToDeepCopy; + } + + private static bool IsStructWhichNeedsDeepCopy_NoDictionaryUsed(Type type) + { + return IsStructOtherThanBasicValueTypes(type) + && HasInItsHierarchyFieldsWithClasses(type); + } + + private static bool IsStructOtherThanBasicValueTypes(Type type) + { + return type.IsValueType && !type.IsPrimitive && !type.IsEnum && type != typeof(Decimal); + } + + private static bool HasInItsHierarchyFieldsWithClasses( + Type type, + HashSet alreadyCheckedTypes = null + ) + { + alreadyCheckedTypes ??= []; + + alreadyCheckedTypes.Add(type); + + FieldInfo[] allFields = GetAllFields(type); + + List allFieldTypes = allFields.Select(f => f.FieldType).Distinct().ToList(); + + bool hasFieldsWithClasses = allFieldTypes.Any(IsClassOtherThanString); + + if (hasFieldsWithClasses) + { + return true; + } + + List notBasicStructsTypes = allFieldTypes + .Where(IsStructOtherThanBasicValueTypes) + .ToList(); + + List typesToCheck = notBasicStructsTypes + .Where(t => !alreadyCheckedTypes.Contains(t)) + .ToList(); + + foreach (Type typeToCheck in typesToCheck) + { + if (HasInItsHierarchyFieldsWithClasses(typeToCheck, alreadyCheckedTypes)) + { + return true; + } + } + + return false; + } + + public class ReferenceEqualityComparer : EqualityComparer + { + public override bool Equals(object x, object y) + { + return ReferenceEquals(x, y); + } + + public override int GetHashCode(object obj) + { + if (obj == null) + { + return 0; + } + + return obj.GetHashCode(); + } + } +} + +public class ReferenceEqualityComparer : EqualityComparer +{ + public override bool Equals(object x, object y) + { + return ReferenceEquals(x, y); + } + + public override int GetHashCode(object obj) + { + if (obj == null) + { + return 0; + } + + return obj.GetHashCode(); + } +} + +public static class ArrayExtensions +{ + public static void ForEach(this Array array, Action action) + { + if (array.LongLength == 0) + { + return; + } + + ArrayTraverse walker = new(array); + do + { + action(array, walker.Position); + } + while (walker.Step()); + } +} + +internal class ArrayTraverse +{ + public int[] Position; + private readonly int[] maxLengths; + + public ArrayTraverse(Array array) + { + maxLengths = new int[array.Rank]; + for (int i = 0; i < array.Rank; ++i) + { + maxLengths[i] = array.GetLength(i) - 1; + } + Position = new int[array.Rank]; + } + + public bool Step() + { + for (int i = 0; i < Position.Length; ++i) + { + if (Position[i] < maxLengths[i]) + { + Position[i]++; + for (int j = 0; j < i; j++) + { + Position[j] = 0; + } + return true; + } + } + return false; + } +} diff --git a/Source/Core/MAX.Core/Helpers/DateHelper.cs b/Source/Core/MAX.Core/Helpers/DateHelper.cs new file mode 100644 index 0000000..d9eb110 --- /dev/null +++ b/Source/Core/MAX.Core/Helpers/DateHelper.cs @@ -0,0 +1,110 @@ +using System; + +namespace MAX.Core.Helpers; + +public class DateHelper +{ + /// + /// Unix纪元时间 + /// + private static readonly DateTime UnixEpoch = new(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); + + /// + /// 将时间转换为秒级时间戳 + /// + /// + /// + public static long GetUnixTimestampInSeconds(DateTime dateTime) + { + TimeSpan timeSpan = dateTime.ToUniversalTime() - UnixEpoch; + return (long)timeSpan.TotalSeconds; + } + + /// + /// 将时间转换为毫秒级时间戳 + /// + /// + /// + public static long GetUnixTimestampInMilliseconds(DateTime dateTime) + { + TimeSpan timeSpan = dateTime.ToUniversalTime() - UnixEpoch; + return (long)timeSpan.TotalMilliseconds; + } + + /// + /// 将时间转换为微秒时间戳 + /// + /// + /// + public static long GetUnixTimestampInMicroseconds(DateTime dateTime) + { + long us = dateTime.ToUniversalTime().Ticks - UnixEpoch.Ticks; + return us; + } + /// + /// 将时间Ticks转换为毫微秒时间戳 + /// + /// + /// + public static long GetUnixTimestampInMicroseconds(long ticks) + { + long us = ticks - UnixEpoch.Ticks; + return us; + } + + /// + /// 将秒级时间戳转换为时间 + /// + /// + /// + public static DateTime ConvertFromUnixTimestampInSeconds(long timestamp) + { + return UnixEpoch.AddSeconds(timestamp).ToLocalTime(); + } + + /// + /// 将毫秒级时间戳转换为时间 + /// + /// + /// + public static DateTime ConvertFromUnixTimestampInMilliseconds(long timestamp) + { + return UnixEpoch.AddMilliseconds(timestamp).ToLocalTime(); + } + /// + /// 将微秒级时间戳转换为时间 + /// + /// + /// + public static DateTime ConvertFromUnixTimestampInMicroseconds(long timestamp) + { + long us = timestamp + UnixEpoch.Ticks; + return new DateTime(us); + } + /// + /// 获取当前时间的秒级时间戳 + /// + /// + public static long GetCurrentUnixTimestampInSeconds() + { + return GetUnixTimestampInSeconds(DateTime.UtcNow); + } + + /// + /// 获取当前时间的毫秒级时间戳 + /// + /// + public static long GetCurrentUnixTimestampInMilliseconds() + { + return GetUnixTimestampInMilliseconds(DateTime.UtcNow); + } + + /// + /// 获取当前时间的微秒级时间戳 + /// + /// + public static long GetCurrentUnixTimestampInMicroseconds() + { + return GetUnixTimestampInMicroseconds(DateTime.UtcNow); + } +} diff --git a/Source/Core/MAX.Core/Helpers/EnumHelper.cs b/Source/Core/MAX.Core/Helpers/EnumHelper.cs new file mode 100644 index 0000000..439042d --- /dev/null +++ b/Source/Core/MAX.Core/Helpers/EnumHelper.cs @@ -0,0 +1,341 @@ +using MAX.Core.Extensions; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Reflection; + +namespace MAX.Core.Helpers; + +/// +/// 枚举操作 +/// +public static partial class EnumHelper +{ + /// + /// 获取实例 + /// + /// 枚举类型 + /// 成员名或值,范例:Enum1枚举有成员A=0,则传入"A"或"0"获取 Enum1.A + public static TEnum Parse(object member) + { + string value = member.SafeString(); + if (string.IsNullOrWhiteSpace(value)) + { + if (typeof(TEnum).IsGenericType) + { + return default; + } + + throw new ArgumentNullException(nameof(member)); + } + return (TEnum)Enum.Parse(Common.GetType(), value, true); + } + public static object Parse(Type enumObj, object member) + { + string value = member.SafeString(); + if (string.IsNullOrWhiteSpace(value)) + { + if (enumObj.IsGenericType) + { + return null; + } + + throw new ArgumentNullException(nameof(member)); + } + return Enum.Parse(enumObj, value, true); + } + public static TEnum ParseByDescription(string description) + { + return (TEnum)ParseByDescription(Common.GetType(), description); + } + public static object ParseByDescription(Type enumObj, string description) + { + List listitem = GetItems(enumObj); + foreach (EnumItem item in listitem) + { + if (item.Description == description) + { + return Enum.Parse(enumObj, item.Numeric.ToString(), true); + } + } + return null; + } + /// + /// 获取成员名 + /// + /// 枚举类型 + /// 成员名、值、实例均可,范例:Enum1枚举有成员A=0,则传入Enum1.A或0,获取成员名"A" + public static string GetName(object member) + { + return GetName(Common.GetType(), member); + } + + /// + /// 获取成员名 + /// + /// 枚举类型 + /// 成员名、值、实例均可 + public static string GetName(Type type, object member) + { + if (type == null) + { + return string.Empty; + } + + if (member == null) + { + return string.Empty; + } + + if (member is string) + { + return member.ToString(); + } + + if (type.GetTypeInfo().IsEnum == false) + { + return string.Empty; + } + + return Enum.GetName(type, member); + } + + /// + /// 获取成员值 + /// + /// 枚举类型 + /// 成员名、值、实例均可,范例:Enum1枚举有成员A=0,可传入"A"、0、Enum1.A,获取值0 + public static int GetValue(object member) + { + return GetValue(Common.GetType(), member); + } + + /// + /// 获取成员值 + /// + /// 枚举类型 + /// 成员名、值、实例均可 + public static int GetValue(Type type, object member) + { + string value = member.SafeString(); + if (string.IsNullOrWhiteSpace(value)) + { + throw new ArgumentNullException(nameof(member)); + } + + return (int)Enum.Parse(type, member.ToString(), true); + } + /// + /// 获取描述,使用System.ComponentModel.Description特性设置描述 + /// + /// 枚举类型 + /// 成员名、值、实例均可 + public static string GetDescription(object member) + { + return ReflectionHelper.GetDescription(GetName(member)); + } + + /// + /// 获取描述,使用System.ComponentModel.Description特性设置描述 + /// + /// 枚举类型 + /// 成员名、值、实例均可 + public static string GetDescription(Type type, object member) + { + return ReflectionHelper.GetDescription(type, GetName(type, member)); + } + + + /// + /// 获取项集合,文本设置为Description,值为Value + /// + /// 枚举类型 + public static List GetItems() + { + return GetItems(typeof(TEnum)); + } + + /// + /// 获取项集合,文本设置为Description,值为Value + /// + /// 枚举类型 + public static List GetItems(Type type) + { + type = Common.GetType(type); + if (type.IsEnum == false) + { + throw new InvalidOperationException($"类型 {type} 不是枚举"); + } + + List result = []; + foreach (FieldInfo field in type.GetFields()) + { + AddItem(type, result, field); + } + + return result.OrderBy(t => t.Numeric).ToList(); + } + + public static CommonItem GetItem(object obj) + { + CommonItem item = null; + if (obj is Enum em) + { + //item.Text = em.get + string text = em.ToString(); + int value = GetValue(obj.GetType(), obj); + string description = em.ToDescription(); + item = new CommonItem(text, value, des: description); + } + return item; + } + + /// + /// 添加描述项 + /// + private static void AddItem(Type type, ICollection result, FieldInfo field) + { + if (!field.FieldType.IsEnum) + { + return; + } + + string text = field.Name; + int value = GetValue(type, field.Name); + string description = ReflectionHelper.GetDescription(field); + result.Add(new EnumItem(text, value, description)); + } + + /// + /// 获取名称集合 + /// + /// 枚举类型 + public static List GetNames() + { + return GetNames(typeof(TEnum)); + } + + /// + /// 获取名称集合 + /// + /// 枚举类型 + public static List GetNames(Type type) + { + type = Common.GetType(type); + if (type.IsEnum == false) + { + throw new InvalidOperationException($"类型 {type} 不是枚举"); + } + + List result = []; + foreach (FieldInfo field in type.GetFields()) + { + if (!field.FieldType.IsEnum) + { + continue; + } + + result.Add(field.Name); + } + return result; + } + + + /// + /// 获取名称集合 + /// + /// 枚举类型 + public static List GetDescriptions() + { + return GetDescriptions(typeof(TEnum)); + } + + /// + /// 获取名称集合 + /// + /// 枚举类型 + public static List GetDescriptions(Type type) + { + type = Common.GetType(type); + if (type.IsEnum == false) + { + throw new InvalidOperationException($"类型 {type} 不是枚举"); + } + + List result = []; + foreach (FieldInfo field in type.GetFields()) + { + if (!field.FieldType.IsEnum) + { + continue; + } + + string description = ReflectionHelper.GetDescription(field); + result.Add(description); + } + return result; + } + /// + /// 是否单一标记 + /// + /// + /// + public static bool IsSingleFlag(Enum obj) + { + int intValue = GetValue(obj.GetType(), obj); + return (intValue > 0) && ((intValue & (intValue - 1)) == 0); + } + /// + /// 获取第一个标记 + /// + /// + /// + /// + public static T GetFirstFlag(T value) where T : Enum + { + long v = System.Convert.ToInt64(value); + long first = v & -v; + return Enum.GetValues(typeof(T)).Cast().First(flag => System.Convert.ToInt64(flag) == first); + } +} +/// +/// Enum展示类型 +/// +public enum EnumDisplayType +{ + Text, + Value, + Description, + CommonItem +} +public class EnumItem +{ + public string Name { get; init; } + public int Numeric { get; init; } + public string Description { get; init; } + + public EnumItem(string name, int numeric, string description) + { + Name = name; + Numeric = numeric; + Description = description; + } +} +public class EnumItem : EnumItem where TEnum : struct, Enum +{ + public TEnum Value { get; } + + public EnumItem(TEnum value) + : base(value.ToString(), System.Convert.ToInt32(value), GetDescription(value)) + { + Value = value; + } + private static string GetDescription(TEnum value) + { + var field = typeof(TEnum).GetField(value.ToString())!; + var attr = field.GetCustomAttribute(); + return attr?.Description ?? string.Empty; + } + +} \ No newline at end of file diff --git a/Source/Core/MAX.Core/Helpers/FileHelper.cs b/Source/Core/MAX.Core/Helpers/FileHelper.cs new file mode 100644 index 0000000..e03a4d6 --- /dev/null +++ b/Source/Core/MAX.Core/Helpers/FileHelper.cs @@ -0,0 +1,188 @@ +using System.IO; +using System.Text; +using System.Threading.Tasks; + +namespace MAX.Core.Helpers; + +/// +/// 文件和流操作 +/// +public static partial class FileHelper +{ + /// + /// 流转换为字节流 + /// + /// 流 + public static async Task ToBytesAsync(Stream stream) + { + stream.Seek(0, SeekOrigin.Begin); + byte[] buffer = new byte[stream.Length]; + await stream.ReadAsync(buffer, 0, buffer.Length); + return buffer; + } + + /// + /// 流转换为字节流 + /// + /// 流 + public static byte[] ToBytes(Stream stream) + { + stream.Seek(0, SeekOrigin.Begin); + byte[] buffer = new byte[stream.Length]; + stream.Read(buffer, 0, buffer.Length); + return buffer; + } + + /// + /// 字符串转换成字节数组 + /// + /// 数据,默认字符编码utf-8 + public static byte[] ToBytes(string data) + { + return ToBytes(data, Encoding.UTF8); + } + + /// + /// 字符串转换成字节数组 + /// + /// 数据 + /// 字符编码 + public static byte[] ToBytes(string data, Encoding encoding) + { + if (string.IsNullOrWhiteSpace(data)) + { + return new byte[] { }; + } + + return encoding.GetBytes(data); + } + + /// + /// 将文件读取到字节流中 + /// + /// 文件的绝对路径 + public static byte[] Read(string filePath) + { + if (!File.Exists(filePath)) + { + return null; + } + + FileInfo fileInfo = new(filePath); + using BinaryReader reader = new(fileInfo.Open(FileMode.Open)); + return reader.ReadBytes((int)fileInfo.Length); + } + + /// + /// 流转换成字符串 + /// + /// 流 + /// 字符编码 + /// 缓冲区大小 + /// 读取完成是否释放流,默认为true + public static string ToString(Stream stream, Encoding encoding = null, int bufferSize = 1024 * 2, bool isCloseStream = true) + { + if (stream == null) + { + return string.Empty; + } + + encoding ??= Encoding.UTF8; + + if (stream.CanRead == false) + { + return string.Empty; + } + + using StreamReader reader = new(stream, encoding, true, bufferSize, !isCloseStream); + if (stream.CanSeek) + { + stream.Seek(0, SeekOrigin.Begin); + } + + string result = reader.ReadToEnd(); + if (stream.CanSeek) + { + stream.Seek(0, SeekOrigin.Begin); + } + + return result; + } + + /// + /// 流转换成字符串 + /// + /// 流 + /// 字符编码 + /// 缓冲区大小 + /// 读取完成是否释放流,默认为true + public static async Task ToStringAsync(Stream stream, Encoding encoding = null, int bufferSize = 1024 * 2, bool isCloseStream = true) + { + if (stream == null) + { + return string.Empty; + } + + encoding ??= Encoding.UTF8; + + if (stream.CanRead == false) + { + return string.Empty; + } + + using StreamReader reader = new(stream, encoding, true, bufferSize, !isCloseStream); + if (stream.CanSeek) + { + stream.Seek(0, SeekOrigin.Begin); + } + + string result = await reader.ReadToEndAsync(); + if (stream.CanSeek) + { + stream.Seek(0, SeekOrigin.Begin); + } + + return result; + } + + /// + /// 复制流并转换成字符串 + /// + /// 流 + /// 字符编码 + public static async Task CopyToStringAsync(Stream stream, Encoding encoding = null) + { + if (stream == null) + { + return string.Empty; + } + + encoding ??= Encoding.UTF8; + + if (stream.CanRead == false) + { + return string.Empty; + } + + using MemoryStream memoryStream = new(); + using StreamReader reader = new(memoryStream, encoding); + if (stream.CanSeek) + { + stream.Seek(0, SeekOrigin.Begin); + } + + stream.CopyTo(memoryStream); + if (memoryStream.CanSeek) + { + memoryStream.Seek(0, SeekOrigin.Begin); + } + + string result = await reader.ReadToEndAsync(); + if (stream.CanSeek) + { + stream.Seek(0, SeekOrigin.Begin); + } + + return result; + } +} diff --git a/Source/Core/MAX.Core/Helpers/HttpHelper.cs b/Source/Core/MAX.Core/Helpers/HttpHelper.cs new file mode 100644 index 0000000..f85098f --- /dev/null +++ b/Source/Core/MAX.Core/Helpers/HttpHelper.cs @@ -0,0 +1,1047 @@ +using System; +using System.IO; +using System.IO.Compression; +using System.Net; +using System.Net.Security; +using System.Security.Cryptography.X509Certificates; +using System.Text; +using System.Text.RegularExpressions; + +namespace MAX.Core.Helpers; + + +/// +/// Http连接操作帮助类 +/// +public class HttpHelper +{ + public static HttpResult Get(string Url) + { + return new HttpHelper().GetHtml(new HttpItem + { + URL = Url, + Method = MethodType.Get, + }); + } + + public static HttpResult Post(string Url, string PostData) + { + return new HttpHelper().GetHtml(new HttpItem + { + URL = Url, + Method = MethodType.Get, + Postdata = PostData, + PostDataType = PostDataType.String + }); + } + public static HttpResult Request(HttpItem item) + { + return new HttpHelper().GetHtml(item); + } + /// + /// 下载文件 + /// + /// + /// + public static byte[] DownloadFile(string address) + { + HttpItem item = new() + { + URL = address, + Timeout = 3000, + ResultType = ResultType.Byte + }; + HttpHelper http = new(); + HttpResult result = http.GetHtml(item); + if (result.StatusCode == HttpStatusCode.OK) + { + return result.ResultByte; + } + else + { + throw new HttpListenerException((int)result.StatusCode, result.StatusDescription); + } + } + + /// + /// 下载文件 + /// + /// + /// + public static bool DownloadFile(string address, string fileName) + { + HttpItem item = new() + { + URL = address, + Timeout = 3000, + ResultType = ResultType.Byte + }; + HttpHelper http = new(); + HttpResult result = http.GetHtml(item); + if (result.StatusCode == HttpStatusCode.OK) + { + FileStream fs = new(fileName, FileMode.CreateNew); + BinaryWriter bw = new(fs); + bw.Write(result.ResultByte); + fs.Close(); + bw.Close(); + } + else + { + throw new HttpListenerException((int)result.StatusCode, result.StatusDescription); + } + + return true; + } + + #region 预定义方法或者变更 + //默认的编码 + private Encoding encoding = Encoding.Default; + //Post数据编码 + private Encoding postencoding = Encoding.Default; + //HttpWebRequest对象用来发起请求 + private HttpWebRequest request = null; + //获取影响流的数据对象 + private HttpWebResponse response = null; + /// + /// 根据相传入的数据,得到相应页面数据 + /// + /// 参数类对象 + /// 返回HttpResult类型 + public HttpResult GetHtml(HttpItem item) + { + //返回参数 + HttpResult result = new() + { + item = item + }; + try + { + //准备参数 + SetRequest(item); + } + catch (Exception ex) + { + result = new HttpResult + { + Cookie = string.Empty, + Header = null, + Html = ex.Message, + StatusDescription = "配置参数时出错:" + ex.Message + }; + return result; + } + try + { + //请求数据 + using (response = (HttpWebResponse)request.GetResponse()) + { + GetData(item, result); + } + //#region 得到请求的response + //using (response = (HttpWebResponse)request.GetResponse()) + //{ + // result.StatusCode = response.StatusCode; + // result.StatusDescription = response.StatusDescription; + // result.Header = response.Headers; + // if (response.Cookies != null) result.CookieCollection = response.Cookies; + // if (response.Headers["set-cookie"] != null) result.Cookie = response.Headers["set-cookie"]; + // MemoryStream _stream = new MemoryStream(); + // //GZIIP处理 + // if (response.ContentEncoding != null && response.ContentEncoding.Equals("gzip", StringComparison.InvariantCultureIgnoreCase)) + // { + // //开始读取流并设置编码方式 + // //new GZipStream(response.GetResponseStream(), CompressionMode.Decompress).CopyTo(_stream, 10240); + // //.net4.0以下写法 + // _stream = GetMemoryStream(new GZipStream(response.GetResponseStream(), CompressionMode.Decompress)); + + // } + // else + // { + // //开始读取流并设置编码方式 + // //response.GetResponseStream().CopyTo(_stream, 10240); + // //.net4.0以下写法 + // _stream = GetMemoryStream(response.GetResponseStream()); + // } + // //获取Byte + // byte[] ResponseByte = _stream.ToArray(); + // _stream.Close(); + // if (ResponseByte != null & ResponseByte.Length > 0) + // { + // //是否返回Byte类型数据 + // if (item.ResultType == ResultType.Byte) result.ResultByte = ResponseByte; + // //从这里开始我们要无视编码了 + // if (encoding == null) + // { + // Match meta = Regex.Match(Encoding.Default.GetString(ResponseByte), " 1) ? meta.Groups[2].Value.ToLower().Trim() : string.Empty; + // if (c.Length > 2) + // { + // try + // { + // if (c.IndexOf(" ") > 0) c = c.Substring(0, c.IndexOf(" ")); + // encoding = Encoding.GetEncoding(c.Replace("\"", "").Replace("'", "").Replace(";", "").Replace("iso-8859-1", "gbk").Trim()); + // } + // catch + // { + // if (string.IsNullOrEmpty(response.CharacterSet)) encoding = Encoding.UTF8; + // else encoding = Encoding.GetEncoding(response.CharacterSet); + // } + // } + // else + // { + // if (string.IsNullOrEmpty(response.CharacterSet)) encoding = Encoding.UTF8; + // else encoding = Encoding.GetEncoding(response.CharacterSet); + // } + // } + // //得到返回的HTML + // result.Html = encoding.GetString(ResponseByte); + // } + // else + // { + // //得到返回的HTML + // result.Html = "本次请求并未返回任何数据"; + // } + //} + //#endregion + } + catch (WebException ex) + { + if (ex.Response != null) + { + using (response = (HttpWebResponse)ex.Response) + { + GetData(item, result); + } + } + else + { + result.Html = ex.Message; + } + + } + catch (Exception ex) + { + result.Html = ex.Message; + } + if (item.IsToLower) + { + result.Html = result.Html.ToLower(); + } + + return result; + } + + #region GetData + /// + /// 获取数据的并解析的方法 + /// + /// + /// + private void GetData(HttpItem item, HttpResult result) + { + if (response == null) + { + return; + } + #region base + //获取StatusCode + result.StatusCode = response.StatusCode; + //获取最后访问的URl + //获取StatusDescription + result.StatusDescription = response.StatusDescription; + //获取Headers + result.Header = response.Headers; + //获取CookieCollection + if (response.Cookies != null) + { + result.CookieCollection = response.Cookies; + } + //获取set-cookie + if (response.Headers["set-cookie"] != null) + { + result.Cookie = response.Headers["set-cookie"]; + } + + #endregion + #region 用户设置用编码 + //处理网页Byte + byte[] ResponseByte = GetByte(item); + + if (ResponseByte != null && ResponseByte.Length > 0) + { + //设置编码 + SetEncoding(item, result, ResponseByte); + + //设置返回的Byte + SetResultByte(item, result, ResponseByte); + } + else + { + //没有返回任何Html代码 + result.Html = string.Empty; + } + + #endregion + } + /// + /// 设置返回的Byte + /// + /// HttpItem + /// result + /// byte + private void SetResultByte(HttpItem item, HttpResult result, byte[] enByte) + { + //是否返回Byte类型数据 + if (item.ResultType == ResultType.Byte) + { + //Byte数据 + result.ResultByte = enByte; + } + else if (item.ResultType == ResultType.String) + { + //得到返回的HTML + result.Html = encoding.GetString(enByte); + } + + } + /// + /// 设置编码 + /// + /// HttpItem + /// HttpResult + /// byte[] + private void SetEncoding(HttpItem item, HttpResult result, byte[] ResponseByte) + { + //从这里开始我们要无视编码了 + if (encoding == null) + { + Match meta = Regex.Match(Encoding.Default.GetString(ResponseByte), " 0) + { + c = meta.Groups[1].Value.ToLower().Trim(); + } + string cs = string.Empty; + if (!string.IsNullOrWhiteSpace(response.CharacterSet)) + { + cs = response.CharacterSet.Trim().Replace("\"", "").Replace("\'", ""); + } + + if (c.Length > 2) + { + try + { + encoding = Encoding.GetEncoding(c.Replace("\"", string.Empty).Replace("'", "").Replace(";", "").Replace("iso-8859-1", "gbk").Trim()); + } + catch + { + if (string.IsNullOrEmpty(cs)) + { + encoding = Encoding.UTF8; + } + else + { + encoding = Encoding.GetEncoding(cs); + } + } + } + else + { + if (string.IsNullOrEmpty(cs)) + { + encoding = Encoding.UTF8; + } + else + { + encoding = Encoding.GetEncoding(cs); + } + } + } + } + /// + /// 提取网页Byte + /// + /// + private byte[] GetByte(HttpItem item) + { + byte[] ResponseByte = null; + using (MemoryStream _stream = new()) + { + + //GZIIP处理 + if (response.ContentEncoding != null && response.ContentEncoding.Equals("gzip", StringComparison.InvariantCultureIgnoreCase)) + { + //开始读取流并设置编码方式 + new GZipStream(response.GetResponseStream(), CompressionMode.Decompress).CopyTo(_stream, 10240); + } + else + { + //开始读取流并设置编码方式 + response.GetResponseStream().CopyTo(_stream, 10240); + } + + //获取Byte + ResponseByte = _stream.ToArray(); + } + return ResponseByte; + } + #endregion + + + /// + /// 4.0以下.net版本取数据使用 + /// + /// 流 + private MemoryStream GetMemoryStream(Stream streamResponse) + { + MemoryStream _stream = new(); + int Length = 256; + Byte[] buffer = new Byte[Length]; + int bytesRead = streamResponse.Read(buffer, 0, Length); + while (bytesRead > 0) + { + _stream.Write(buffer, 0, bytesRead); + bytesRead = streamResponse.Read(buffer, 0, Length); + } + return _stream; + } + /// + /// 为请求准备参数 + /// + ///参数列表 + private void SetRequest(HttpItem item) + + { + // 验证证书 + SetCer(item); + //设置Header参数 + if (item.Header != null && item.Header.Count > 0) + { + foreach (string key in item.Header.AllKeys) + { + request.Headers.Add(key, item.Header[key]); + } + } + // 设置代理 + SetProxy(item); + if (item.ProtocolVersion != null) + { + request.ProtocolVersion = item.ProtocolVersion; + } + + request.ServicePoint.Expect100Continue = item.Expect100Continue; + //请求方式Get或者Post + request.Method = item.Method.ToString(); + request.Timeout = item.Timeout; + request.KeepAlive = item.KeepAlive; + request.ReadWriteTimeout = item.ReadWriteTimeout; + //Accept + request.Accept = item.Accept; + //ContentType返回类型 + request.ContentType = item.ContentType; + //UserAgent客户端的访问类型,包括浏览器版本和操作系统信息 + request.UserAgent = item.UserAgent; + // 编码 + encoding = item.Encoding; + //设置安全凭证 + request.Credentials = item.ICredentials; + //设置Cookie + SetCookie(item); + //来源地址 + request.Referer = item.Referer; + //是否执行跳转功能 + request.AllowAutoRedirect = item.Allowautoredirect; + //设置Post数据 + SetPostData(item); + //设置最大连接 + if (item.Connectionlimit > 0) + { + request.ServicePoint.ConnectionLimit = item.Connectionlimit; + } + } + /// + /// 设置证书 + /// + /// +#pragma warning disable SYSLIB0014 // Type or member is obsolete + private void SetCer(HttpItem item) + { + if (!string.IsNullOrEmpty(item.CerPath)) + { + //这一句一定要写在创建连接的前面。使用回调的方法进行证书验证。 + ServicePointManager.ServerCertificateValidationCallback = new RemoteCertificateValidationCallback(CheckValidationResult); + //初始化对像,并设置请求的URL地址 + request = (HttpWebRequest)WebRequest.Create(item.URL); + SetCerList(item); + //将证书添加到请求里 + request.ClientCertificates.Add(new X509Certificate(item.CerPath)); + } + else + { + //初始化对像,并设置请求的URL地址 + request = (HttpWebRequest)WebRequest.Create(item.URL); + SetCerList(item); + } + } +#pragma warning restore SYSLIB0014 // Type or member is obsolete + /// + /// 设置多个证书 + /// + /// + private void SetCerList(HttpItem item) + { + if (item.ClentCertificates != null && item.ClentCertificates.Count > 0) + { + foreach (X509Certificate c in item.ClentCertificates) + { + request.ClientCertificates.Add(c); + } + } + } + /// + /// 设置Cookie + /// + /// Http参数 + private void SetCookie(HttpItem item) + { + if (!string.IsNullOrEmpty(item.Cookie)) + { + request.Headers[HttpRequestHeader.Cookie] = item.Cookie; + } + //设置CookieCollection + if (item.ResultCookieType == ResultCookieType.CookieCollection) + { + request.CookieContainer = new CookieContainer(); + if (item.CookieCollection != null && item.CookieCollection.Count > 0) + { + request.CookieContainer.Add(item.CookieCollection); + } + } + } + /// + /// 设置Post数据 + /// + /// Http参数 + private void SetPostData(HttpItem item) + { + //验证在得到结果时是否有传入数据 + if (request.Method.Trim().ToLower().Contains("post")) + { + if (item.PostEncoding != null) + { + postencoding = item.PostEncoding; + } + byte[] buffer = null; + //写入Byte类型 + if (item.PostDataType == PostDataType.Byte && item.PostdataByte != null && item.PostdataByte.Length > 0) + { + //验证在得到结果时是否有传入数据 + buffer = item.PostdataByte; + }//写入文件 + else if (item.PostDataType == PostDataType.FilePath && !string.IsNullOrEmpty(item.Postdata)) + { + StreamReader r = new(item.Postdata, postencoding); + buffer = postencoding.GetBytes(r.ReadToEnd()); + r.Close(); + } //写入字符串 + else if (!string.IsNullOrEmpty(item.Postdata)) + { + buffer = postencoding.GetBytes(item.Postdata); + } + if (buffer != null) + { + request.ContentLength = buffer.Length; + request.GetRequestStream().Write(buffer, 0, buffer.Length); + } + } + } + /// + /// 设置代理 + /// + /// 参数对象 + private void SetProxy(HttpItem item) + { + bool isIeProxy = item.ProxyIp.ToLower().Contains("ieproxy"); + if (!string.IsNullOrEmpty(item.ProxyIp) && !isIeProxy) + { + //设置代理服务器 + if (item.ProxyIp.Contains(":")) + { + string[] plist = item.ProxyIp.Split(':'); + WebProxy myProxy = new(plist[0].Trim(), Convert.ToInt(plist[1].Trim())) + { + //建议连接 + Credentials = new NetworkCredential(item.ProxyUserName, item.ProxyPwd) + }; + //给当前请求对象 + request.Proxy = myProxy; + } + else + { + WebProxy myProxy = new(item.ProxyIp, false) + { + //建议连接 + Credentials = new NetworkCredential(item.ProxyUserName, item.ProxyPwd) + }; + //给当前请求对象 + request.Proxy = myProxy; + } + } + else if (isIeProxy) + { + //设置为IE代理 + } + else + { + request.Proxy = item.WebProxy; + } + } + /// + /// 回调验证证书问题 + /// + /// 流对象 + /// 证书 + /// X509Chain + /// SslPolicyErrors + /// bool + public bool CheckValidationResult(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors errors) { return true; } + #endregion +} +/// +/// Http请求参考类 +/// +public class HttpItem +{ + private string _URL = string.Empty; + /// + /// 请求URL必须填写 + /// + public string URL + { + get { return _URL; } + set { _URL = value; } + } + + private MethodType _Method = MethodType.Get; + /// + /// 请求方式默认为GET方式,当为POST方式时必须设置Postdata的值 + /// + public MethodType Method + { + get { return _Method; } + set + { + _Method = value; + if (_Method == MethodType.Post) + { + _ContentType = "application/x-www-form-urlencoded"; + } + } + } + + private int _Timeout = 100000; + /// + /// 默认请求超时时间 + /// + public int Timeout + { + get { return _Timeout; } + set { _Timeout = value; } + } + + private int _ReadWriteTimeout = 30000; + /// + /// 默认写入Post数据超时间 + /// + public int ReadWriteTimeout + { + get { return _ReadWriteTimeout; } + set { _ReadWriteTimeout = value; } + } + + private Boolean _KeepAlive = true; + /// + /// 获取或设置一个值,该值指示是否与 Internet 资源建立持久性连接默认为true。 + /// + public Boolean KeepAlive + { + get { return _KeepAlive; } + set { _KeepAlive = value; } + } + + private string _Accept = "text/html, application/xhtml+xml, */*"; + /// + /// 请求标头值 默认为text/html, application/xhtml+xml, */* + /// + public string Accept + { + get { return _Accept; } + set { _Accept = value; } + } + + private string _ContentType = "text/html"; + /// + /// 请求返回类型默认 text/html + /// + public string ContentType + { + get { return _ContentType; } + set { _ContentType = value; } + } + + private string _UserAgent = "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0)"; + /// + /// 客户端访问信息默认Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0) + /// + public string UserAgent + { + get { return _UserAgent; } + set { _UserAgent = value; } + } + + private Encoding _Encoding = null; + /// + /// 返回数据编码默认为NUll,可以自动识别,一般为utf-8,gbk,gb2312 + /// + public Encoding Encoding + { + get { return _Encoding; } + set { _Encoding = value; } + } + private PostDataType _PostDataType = PostDataType.String; + /// + /// Post的数据类型 + /// + public PostDataType PostDataType + { + get { return _PostDataType; } + set { _PostDataType = value; } + } + + private string _Postdata = string.Empty; + /// + /// Post请求时要发送的字符串Post数据 + /// + public string Postdata + { + get { return _Postdata; } + set { _Postdata = value; } + } + private byte[] _PostdataByte = null; + /// + /// Post请求时要发送的Byte类型的Post数据 + /// + public byte[] PostdataByte + { + get { return _PostdataByte; } + set { _PostdataByte = value; } + } + private WebProxy _WebProxy; + /// + /// 设置代理对象,不想使用IE默认配置就设置为Null,而且不要设置ProxyIp + /// + public WebProxy WebProxy + { + get { return _WebProxy; } + set { _WebProxy = value; } + } + + private CookieCollection cookiecollection = null; + /// + /// Cookie对象集合 + /// + public CookieCollection CookieCollection + { + get { return cookiecollection; } + set { cookiecollection = value; } + } + + private string _Cookie = string.Empty; + /// + /// 请求时的Cookie + /// + public string Cookie + { + get { return _Cookie; } + set { _Cookie = value; } + } + + private string _Referer = string.Empty; + /// + /// 来源地址,上次访问地址 + /// + public string Referer + { + get { return _Referer; } + set { _Referer = value; } + } + + private string _CerPath = string.Empty; + /// + /// 证书绝对路径 + /// + public string CerPath + { + get { return _CerPath; } + set { _CerPath = value; } + } + private Boolean isToLower = false; + /// + /// 是否设置为全文小写,默认为不转化 + /// + public Boolean IsToLower + { + get { return isToLower; } + set { isToLower = value; } + } + private Boolean allowautoredirect = true; + /// + /// 支持跳转页面,查询结果将是跳转后的页面 + /// + public Boolean Allowautoredirect + { + get { return allowautoredirect; } + set { allowautoredirect = value; } + } + private int connectionlimit = 1024; + /// + /// 最大连接数 + /// + public int Connectionlimit + { + get { return connectionlimit; } + set { connectionlimit = value; } + } + private string proxyusername = string.Empty; + /// + /// 代理Proxy 服务器用户名 + /// + public string ProxyUserName + { + get { return proxyusername; } + set { proxyusername = value; } + } + private string proxypwd = string.Empty; + /// + /// 代理 服务器密码 + /// + public string ProxyPwd + { + get { return proxypwd; } + set { proxypwd = value; } + } + private string proxyip = string.Empty; + /// + /// 代理 服务IP ,如果要使用IE代理就设置为ieproxy + /// + public string ProxyIp + { + get { return proxyip; } + set { proxyip = value; } + } + private ResultType resulttype = ResultType.String; + /// + /// 设置返回类型String和Byte + /// + public ResultType ResultType + { + get { return resulttype; } + set { resulttype = value; } + } + private WebHeaderCollection header = []; + /// + /// header对象 + /// + public WebHeaderCollection Header + { + get { return header; } + set { header = value; } + } + + private Version _ProtocolVersion; + + /// + /// 获取或设置用于请求的 HTTP 版本。返回结果:用于请求的 HTTP 版本。默认为 System.Net.HttpVersion.Version11。 + /// + public Version ProtocolVersion + { + get { return _ProtocolVersion; } + set { _ProtocolVersion = value; } + } + private Boolean _expect100continue = true; + /// + /// 获取或设置一个 System.Boolean 值,该值确定是否使用 100-Continue 行为。如果 POST 请求需要 100-Continue 响应,则为 true;否则为 false。默认值为 true。 + /// + public Boolean Expect100Continue + { + get { return _expect100continue; } + set { _expect100continue = value; } + } + private X509CertificateCollection _ClentCertificates; + /// + /// 设置509证书集合 + /// + public X509CertificateCollection ClentCertificates + { + get { return _ClentCertificates; } + set { _ClentCertificates = value; } + } + private Encoding _PostEncoding; + /// + /// 设置或获取Post参数编码,默认的为Default编码 + /// + public Encoding PostEncoding + { + get { return _PostEncoding; } + set { _PostEncoding = value; } + } + private ResultCookieType _ResultCookieType = ResultCookieType.String; + /// + /// Cookie返回类型,默认的是只返回字符串类型 + /// + public ResultCookieType ResultCookieType + { + get { return _ResultCookieType; } + set { _ResultCookieType = value; } + } + + private ICredentials _ICredentials = CredentialCache.DefaultCredentials; + /// + /// 获取或设置请求的身份验证信息。 + /// + public ICredentials ICredentials + { + get { return _ICredentials; } + set { _ICredentials = value; } + } + +} +/// +/// Http返回参数类 +/// +public class HttpResult +{ + private string _Cookie; + /// + /// Http请求返回的Cookie + /// + public string Cookie + { + get { return _Cookie; } + set { _Cookie = value; } + } + + private CookieCollection _CookieCollection; + /// + /// Cookie对象集合 + /// + public CookieCollection CookieCollection + { + get { return _CookieCollection; } + set { _CookieCollection = value; } + } + private string _Html; + /// + /// 返回的String类型数据 只有ResultType.String时才返回数据,其它情况为空 + /// + public string Html + { + get { return _Html; } + set { _Html = value; } + } + private byte[] _ResultByte; + /// + /// 返回的Byte数组 只有ResultType.Byte时才返回数据,其它情况为空 + /// + public byte[] ResultByte + { + get { return _ResultByte; } + set { _ResultByte = value; } + } + + private WebHeaderCollection _Header; + /// + /// header对象 + /// + public WebHeaderCollection Header + { + get { return _Header; } + set { _Header = value; } + } + + private string _StatusDescription; + /// + /// 返回状态说明 + /// + public string StatusDescription + { + get { return _StatusDescription; } + set { _StatusDescription = value; } + } + private HttpStatusCode _StatusCode; + /// + /// 返回状态码,默认为OK + /// + public HttpStatusCode StatusCode + { + get { return _StatusCode; } + set { _StatusCode = value; } + } + /// + /// HttpItem类,用于存储实际执行所用到的HttpItem对象 + /// + public HttpItem item { get; set; } +} +/// +/// 返回类型 +/// +public enum ResultType +{ + /// + /// 表示只返回字符串 只有Html有数据 + /// + String, + /// + /// 表示返回字符串和字节流 ResultByte和Html都有数据返回 + /// + Byte +} +/// +/// Post的数据格式默认为string +/// +public enum PostDataType +{ + /// + /// 字符串类型,这时编码Encoding可不设置 + /// + String, + /// + /// Byte类型,需要设置PostdataByte参数的值编码Encoding可设置为空 + /// + Byte, + /// + /// 传文件,Postdata必须设置为文件的绝对路径,必须设置Encoding的值 + /// + FilePath +} +/// +/// Cookie返回类型 +/// +public enum ResultCookieType +{ + /// + /// 只返回字符串类型的Cookie + /// + String, + /// + /// CookieCollection格式的Cookie集合同时也返回String类型的cookie + /// + CookieCollection +} +public enum MethodType +{ + Get, + Post +} + diff --git a/Source/Core/MAX.Core/Helpers/IdHelper.cs b/Source/Core/MAX.Core/Helpers/IdHelper.cs new file mode 100644 index 0000000..224a70c --- /dev/null +++ b/Source/Core/MAX.Core/Helpers/IdHelper.cs @@ -0,0 +1,45 @@ +namespace MAX.Core.Helpers; + +/// +/// 标识生成器 +/// +public static partial class IdHelper +{ + /// + /// 标识 + /// + private static string _id; + + /// + /// 设置标识 + /// + /// Id + public static void SetId(string id) + { + _id = id; + } + + /// + /// 重置标识 + /// + public static void Reset() + { + _id = null; + } + + /// + /// 用Guid创建标识,去掉分隔符 + /// + public static string Guid() + { + return string.IsNullOrWhiteSpace(_id) ? System.Guid.NewGuid().ToString("N") : _id; + } + + ///// + ///// 获取Guid + ///// + //public static Guid GetGuid() + //{ + // return string.IsNullOrWhiteSpace(_id) ? System.Guid.NewGuid() : _id.ToGuid(); + //} +} diff --git a/Source/Core/MAX.Core/Helpers/ParametersBase.cs b/Source/Core/MAX.Core/Helpers/ParametersBase.cs new file mode 100644 index 0000000..9cf8d97 --- /dev/null +++ b/Source/Core/MAX.Core/Helpers/ParametersBase.cs @@ -0,0 +1,369 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Text; + + +namespace MAX.Core.Helpers; + +/// +/// This is a generic parameters base class used for Dialog Parameters and Navigation Parameters. +/// +public abstract class ParametersBase : IEnumerable> +{ + private readonly List> _entries = []; + + /// + /// Default constructor. + /// + protected ParametersBase() + { + } + + /// + /// Constructs a list of parameters. + /// + /// Query string to be parsed. + protected ParametersBase(string query) + { + if (!string.IsNullOrWhiteSpace(query)) + { + int num = query.Length; + for (int i = ((query.Length > 0) && (query[0] == '?')) ? 1 : 0; i < num; i++) + { + int startIndex = i; + int num4 = -1; + while (i < num) + { + char ch = query[i]; + if (ch == '=') + { + if (num4 < 0) + { + num4 = i; + } + } + else if (ch == '&') + { + break; + } + i++; + } + string key = null; + string value; + if (num4 >= 0) + { + key = query.Substring(startIndex, num4 - startIndex); + value = query.Substring(num4 + 1, i - num4 - 1); + } + else + { + value = query.Substring(startIndex, i - startIndex); + } + + if (key != null) + { + Add(Uri.UnescapeDataString(key), Uri.UnescapeDataString(value)); + } + } + } + } + + /// + /// Searches Parameter collection and returns value if Collection contains key. + /// Otherwise returns null. + /// + /// The key for the value to be returned. + /// The value of the parameter referenced by the key; otherwise null. + public object this[string key] + { + get + { + foreach (KeyValuePair entry in _entries) + { + if (string.Compare(entry.Key, key, StringComparison.Ordinal) == 0) + { + return entry.Value; + } + } + + return null; + } + } + + /// + /// The count, or number, of parameters in collection. + /// + public int Count => _entries.Count; + + /// + /// Returns an IEnumerable of the Keys in the collection. + /// + public IEnumerable Keys => + _entries.Select(x => x.Key); + + /// + /// Adds the key and value to the parameters collection. + /// + /// The key to reference this value in the parameters collection. + /// The value of the parameter to store. + public void Add(string key, object value) => + _entries.Add(new KeyValuePair(key, value)); + + + public void Remove(string key) + { + _entries.RemoveAll(x => x.Key == key); + } + + public void Update(string key, object value) + { + _entries.RemoveAll(x => x.Key == key); + Add(key, value); + } + + /// + /// Checks collection for presence of key. + /// + /// The key to check in the collection. + /// true if key exists; else returns false. + public bool ContainsKey(string key) + { + return _entries.Any(x => string.Compare(x.Key, key, StringComparison.Ordinal) == 0); + } + + + /// + /// Gets an enumerator for the KeyValuePairs in parameter collection. + /// + /// Enumerator. + public IEnumerator> GetEnumerator() => + _entries.GetEnumerator(); + + /// + /// Returns the value of the member referenced by key. + /// + /// The type of object to be returned. + /// The key for the value to be returned. + /// Returns a matching parameter of if one exists in the Collection. + public T GetValue(string key) => + _entries.GetValue(key); + + /// + /// Returns an IEnumerable of all parameters. + /// + /// The type for the values to be returned. + /// The key for the values to be returned. + ///Returns a IEnumerable of all the instances of type . + public IEnumerable GetValues(string key) => + _entries.GetValues(key); + + /// + /// Checks to see if the parameter collection contains the value. + /// + /// The type for the values to be returned. + /// The key for the value to be returned. + /// Value of the returned parameter if it exists. + public bool TryGetValue(string key, out T value) => + _entries.TryGetValue(key, out value); + + IEnumerator IEnumerable.GetEnumerator() => + GetEnumerator(); + + /// + /// Converts parameter collection to a parameter string. + /// + /// A string representation of the parameters. + public override string ToString() + { + StringBuilder queryBuilder = new(); + + if (_entries.Count > 0) + { + queryBuilder.Append('?'); + bool first = true; + + foreach (KeyValuePair kvp in _entries) + { + if (!first) + { + queryBuilder.Append('&'); + } + else + { + first = false; + } + + queryBuilder.Append(Uri.EscapeDataString(kvp.Key)); + queryBuilder.Append('='); + queryBuilder.Append(Uri.EscapeDataString(kvp.Value != null ? kvp.Value.ToString() : "")); + } + } + + return queryBuilder.ToString(); + } + + /// + /// Adds a collection of parameters to the local parameter list. + /// + /// An IEnumerable of KeyValuePairs to add to the current parameter list. + public void FromParameters(IEnumerable> parameters) => + _entries.AddRange(parameters); +} + + +public static class ParametersExtensions +{ + /// + /// Searches for + /// + /// The type of the parameter to return + /// A collection of parameters to search + /// The key of the parameter to find + /// A matching value of if it exists + [EditorBrowsable(EditorBrowsableState.Never)] + public static T GetValue(this IEnumerable> parameters, string key) => + (T)GetValue(parameters, key, typeof(T)); + + /// + /// Searches for value referenced by + /// + /// A collection of parameters to search + /// The key of the parameter to find + /// The type of the parameter to return + /// A matching value of if it exists + /// Unable to convert the value of Type + [EditorBrowsable(EditorBrowsableState.Never)] + public static object GetValue(this IEnumerable> parameters, string key, Type type) + { + foreach (KeyValuePair kvp in parameters) + { + if (string.Compare(kvp.Key, key, StringComparison.Ordinal) == 0) + { + if (TryGetValueInternal(kvp, type, out object value)) + { + return value; + } + + throw new InvalidCastException($"Unable to convert the value of Type '{kvp.Value.GetType().FullName}' to '{type.FullName}' for the key '{key}' "); + } + } + + return GetDefault(type); + } + + /// + /// Searches for value referenced by + /// + /// The type of the parameter to return + /// A collection of parameters to search + /// The key of the parameter to find + /// The value of parameter to return + /// Success if value is found; otherwise returns false + [EditorBrowsable(EditorBrowsableState.Never)] + public static bool TryGetValue(this IEnumerable> parameters, string key, out T value) + { + Type type = typeof(T); + + foreach (KeyValuePair kvp in parameters) + { + if (string.Compare(kvp.Key, key, StringComparison.Ordinal) == 0) + { + bool success = TryGetValueInternal(kvp, typeof(T), out object valueAsObject); + value = (T)valueAsObject; + return success; + } + } + + value = default; + return false; + } + + /// + /// Searches for value referenced by + /// + /// The type of the parameter to return + /// A collection of parameters to search + /// The key of the parameter to find + /// An IEnumerable{T} of all the values referenced by key + [EditorBrowsable(EditorBrowsableState.Never)] + public static IEnumerable GetValues(this IEnumerable> parameters, string key) + { + List values = []; + Type type = typeof(T); + + foreach (KeyValuePair kvp in parameters) + { + if (string.Compare(kvp.Key, key, StringComparison.Ordinal) == 0) + { + TryGetValueInternal(kvp, type, out object value); + values.Add((T)value); + } + } + + return values.AsEnumerable(); + } + + private static bool TryGetValueInternal(KeyValuePair kvp, Type type, out object value) + { + value = GetDefault(type); + bool success = false; + if (kvp.Value == null) + { + success = true; + } + else if (kvp.Value.GetType() == type) + { + success = true; + value = kvp.Value; + } + else if (type.IsAssignableFrom(kvp.Value.GetType())) + { + success = true; + value = kvp.Value; + } + else if (type.IsEnum) + { + string valueAsString = kvp.Value.ToString(); + if (Enum.IsDefined(type, valueAsString)) + { + success = true; + value = Enum.Parse(type, valueAsString); + } + else if (int.TryParse(valueAsString, out int numericValue)) + { + success = true; + value = Enum.ToObject(type, numericValue); + } + } + + if (!success && type.GetInterface("System.IConvertible") != null) + { + success = true; + value = System.Convert.ChangeType(kvp.Value, type); + } + + return success; + } + + /// + /// Checks to see if key exists in parameter collection + /// + /// IEnumerable to search + /// The key to search the for existence + /// true if key exists; false otherwise + [EditorBrowsable(EditorBrowsableState.Never)] + public static bool ContainsKey(this IEnumerable> parameters, string key) => + parameters.Any(x => string.Compare(x.Key, key, StringComparison.Ordinal) == 0); + + private static object GetDefault(Type type) + { + if (type.IsValueType) + { + return Activator.CreateInstance(type); + } + return null; + } +} diff --git a/Source/Core/MAX.Core/Helpers/ReflectionHelper.cs b/Source/Core/MAX.Core/Helpers/ReflectionHelper.cs new file mode 100644 index 0000000..bbb0048 --- /dev/null +++ b/Source/Core/MAX.Core/Helpers/ReflectionHelper.cs @@ -0,0 +1,618 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Reflection; + +namespace MAX.Core.Helpers; + +/// +/// 反射操作 +/// +public static class ReflectionHelper +{ + /// + /// 获取类型描述,使用DescriptionAttribute设置描述 + /// + /// 类型 + public static string GetDescription() + { + return GetDescription(Common.GetType()); + } + + /// + /// 获取类型成员描述,使用DescriptionAttribute设置描述 + /// + /// 类型 + /// 成员名称 + public static string GetDescription(string memberName) + { + return GetDescription(Common.GetType(), memberName); + } + + /// + /// 获取类型成员描述,使用DescriptionAttribute设置描述 + /// + /// 类型 + /// 成员名称 + public static string GetDescription(Type type, string memberName) + { + if (type == null) + { + return string.Empty; + } + + if (string.IsNullOrWhiteSpace(memberName)) + { + return string.Empty; + } + + return GetDescription(type.GetTypeInfo().GetMember(memberName).FirstOrDefault()); + } + + /// + /// 获取类型成员描述,使用DescriptionAttribute设置描述 + /// + /// 成员 + public static string GetDescription(MemberInfo member) + { + if (member == null) + { + return string.Empty; + } + + return member.GetCustomAttribute() is DescriptionAttribute attribute ? attribute.Description : member.Name; + } + + /// + /// 获取显示名称,使用DisplayNameAttribute设置显示名称 + /// + /// 类型 + public static string GetDisplayName() + { + return GetDisplayName(Common.GetType()); + } + + /// + /// 获取显示名称,使用DisplayAttribute或DisplayNameAttribute设置显示名称 + /// + public static string GetDisplayName(MemberInfo member) + { + if (member == null) + { + return string.Empty; + } + + if (member.GetCustomAttribute() is DisplayAttribute displayAttribute) + { + return displayAttribute.Name; + } + + if (member.GetCustomAttribute() is DisplayNameAttribute displayNameAttribute) + { + return displayNameAttribute.DisplayName; + } + + return string.Empty; + } + + /// + /// 获取显示名称或描述,使用DisplayNameAttribute设置显示名称,使用DescriptionAttribute设置描述 + /// + /// 类型 + public static string GetDisplayNameOrDescription() + { + return GetDisplayNameOrDescription(Common.GetType()); + } + + /// + /// 获取属性显示名称或描述,使用DisplayAttribute或DisplayNameAttribute设置显示名称,使用DescriptionAttribute设置描述 + /// + public static string GetDisplayNameOrDescription(MemberInfo member) + { + string result = GetDisplayName(member); + return string.IsNullOrWhiteSpace(result) ? GetDescription(member) : result; + } + + /// + /// 查找类型列表 + /// + /// 查找类型 + /// 待查找的程序集列表 + public static List FindTypes(params Assembly[] assemblies) + { + Type findType = typeof(TFind); + return FindTypes(findType, assemblies); + } + + /// + /// 查找类型列表 + /// + /// 查找类型 + /// 待查找的程序集列表 + public static List FindTypes(Type findType, params Assembly[] assemblies) + { + List result = []; + foreach (Assembly assembly in assemblies) + { + result.AddRange(GetTypes(findType, assembly)); + } + + return result.Distinct().ToList(); + } + + /// + /// 获取类型列表 + /// + private static List GetTypes(Type findType, Assembly assembly) + { + List result = []; + if (assembly == null) + { + return result; + } + + Type[] types; + try + { + types = assembly.GetTypes(); + } + catch (ReflectionTypeLoadException ex) + { + Debug.WriteLine(ex); + return result; + } + foreach (Type type in types) + { + AddType(result, findType, type); + } + + return result; + } + + /// + /// 添加类型 + /// + private static void AddType(List result, Type findType, Type type) + { + if (type.IsInterface || type.IsAbstract) + { + return; + } + + if (findType.IsAssignableFrom(type) == false && MatchGeneric(findType, type) == false) + { + return; + } + + result.Add(type); + } + + /// + /// 泛型匹配 + /// + private static bool MatchGeneric(Type findType, Type type) + { + if (findType.IsGenericTypeDefinition == false) + { + return false; + } + + Type definition = findType.GetGenericTypeDefinition(); + foreach (Type implementedInterface in type.FindInterfaces((filter, criteria) => true, null)) + { + if (implementedInterface.IsGenericType == false) + { + continue; + } + + return definition.IsAssignableFrom(implementedInterface.GetGenericTypeDefinition()); + } + return false; + } + + /// + /// 获取实现了接口的所有实例 + /// + /// 接口类型 + /// 待查找的程序集列表 + public static List GetInstancesByInterface(params Assembly[] assemblies) + { + return FindTypes(assemblies) + .Select(t => CreateInstance(t)).ToList(); + } + + /// + /// 动态创建实例 + /// + /// 目标类型 + /// 类型 + /// 传递给构造函数的参数 + public static T CreateInstance(Type type, params object[] parameters) + { + return Convert.To(Activator.CreateInstance(type, parameters)); + } + + public static Type GetType(string typeName) + { + + Type type = null; + + Assembly[] assemblyArray = AppDomain.CurrentDomain.GetAssemblies(); + + int assemblyArrayLength = assemblyArray.Length; + + for (int i = 0; i < assemblyArrayLength; ++i) + { + + type = assemblyArray[i].GetType(typeName); + + if (type != null) + { + return type; + } + } + + for (int i = 0; i < assemblyArrayLength; ++i) + { + Type[] typeArray = assemblyArray[i].GetTypes(); + int typeArrayLength = typeArray.Length; + for (int j = 0; j < typeArrayLength; ++j) + { + if (typeArray[j].Name.Equals(typeName)) + { + return typeArray[j]; + } + } + } + + return type; + + } + + /// + /// 获取程序集 + /// + /// 程序集名称 + public static Assembly GetAssembly(string assemblyName) + { + return Assembly.Load(new AssemblyName(assemblyName)); + } + + /// + /// 是否布尔类型 + /// + /// 成员 + public static bool IsBool(MemberInfo member) + { + if (member == null) + { + return false; + } + + return member.MemberType switch + { + MemberTypes.TypeInfo => member.ToString() == "System.Boolean", + MemberTypes.Property => IsBool((PropertyInfo)member), + _ => false, + }; + } + + /// + /// 是否布尔类型 + /// + private static bool IsBool(PropertyInfo property) + { + return property.PropertyType == typeof(bool) || property.PropertyType == typeof(bool?); + } + + /// + /// 是否枚举类型 + /// + /// 成员 + public static bool IsEnum(MemberInfo member) + { + if (member == null) + { + return false; + } + + return member.MemberType switch + { + MemberTypes.TypeInfo => ((TypeInfo)member).IsEnum, + MemberTypes.Property => IsEnum((PropertyInfo)member), + _ => false, + }; + } + + /// + /// 是否枚举类型 + /// + private static bool IsEnum(PropertyInfo property) + { + if (property.PropertyType.GetTypeInfo().IsEnum) + { + return true; + } + + Type value = Nullable.GetUnderlyingType(property.PropertyType); + if (value == null) + { + return false; + } + + return value.GetTypeInfo().IsEnum; + } + + /// + /// 是否日期类型 + /// + /// 成员 + public static bool IsDate(MemberInfo member) + { + if (member == null) + { + return false; + } + + return member.MemberType switch + { + MemberTypes.TypeInfo => member.ToString() == "System.DateTime", + MemberTypes.Property => IsDate((PropertyInfo)member), + _ => false, + }; + } + + /// + /// 是否日期类型 + /// + private static bool IsDate(PropertyInfo property) + { + if (property.PropertyType == typeof(DateTime)) + { + return true; + } + + if (property.PropertyType == typeof(DateTime?)) + { + return true; + } + + return false; + } + + /// + /// 是否整型 + /// + /// 成员 + public static bool IsInt(MemberInfo member) + { + if (member == null) + { + return false; + } + + return member.MemberType switch + { + MemberTypes.TypeInfo => member.ToString() is "System.Int32" or "System.Int16" or "System.Int64", + MemberTypes.Property => IsInt((PropertyInfo)member), + _ => false, + }; + } + + /// + /// 是否整型 + /// + private static bool IsInt(PropertyInfo property) + { + if (property.PropertyType == typeof(int)) + { + return true; + } + + if (property.PropertyType == typeof(int?)) + { + return true; + } + + if (property.PropertyType == typeof(short)) + { + return true; + } + + if (property.PropertyType == typeof(short?)) + { + return true; + } + + if (property.PropertyType == typeof(long)) + { + return true; + } + + if (property.PropertyType == typeof(long?)) + { + return true; + } + + return false; + } + + /// + /// 是否数值类型 + /// + /// 成员 + public static bool IsNumber(MemberInfo member) + { + if (member == null) + { + return false; + } + + if (IsInt(member)) + { + return true; + } + + return member.MemberType switch + { + MemberTypes.TypeInfo => member.ToString() is "System.Double" or "System.Decimal" or "System.Single", + MemberTypes.Property => IsNumber((PropertyInfo)member), + _ => false, + }; + } + + /// + /// 是否数值类型 + /// + private static bool IsNumber(PropertyInfo property) + { + if (property.PropertyType == typeof(double)) + { + return true; + } + + if (property.PropertyType == typeof(double?)) + { + return true; + } + + if (property.PropertyType == typeof(decimal)) + { + return true; + } + + if (property.PropertyType == typeof(decimal?)) + { + return true; + } + + if (property.PropertyType == typeof(float)) + { + return true; + } + + if (property.PropertyType == typeof(float?)) + { + return true; + } + + return false; + } + + /// + /// 是否集合 + /// + /// 类型 + public static bool IsCollection(Type type) + { + if (type.IsArray) + { + return true; + } + + return IsGenericCollection(type); + } + + /// + /// 是否泛型集合 + /// + /// 类型 + public static bool IsGenericCollection(Type type) + { + if (!type.IsGenericType) + { + return false; + } + + Type typeDefinition = type.GetGenericTypeDefinition(); + return typeDefinition == typeof(IEnumerable<>) + || typeDefinition == typeof(IReadOnlyCollection<>) + || typeDefinition == typeof(IReadOnlyList<>) + || typeDefinition == typeof(ICollection<>) + || typeDefinition == typeof(IList<>) + || typeDefinition == typeof(List<>); + } + + /// + /// 从目录中获取所有程序集 + /// + /// 目录绝对路径 + public static List GetAssemblies(string directoryPath) + { + return Directory.GetFiles(directoryPath, "*.*", SearchOption.AllDirectories).ToList() + .Where(t => t.EndsWith(".exe") || t.EndsWith(".dll")) + .Select(path => Assembly.Load(new AssemblyName(path))).ToList(); + } + + /// + /// 获取公共属性列表 + /// + /// 实例 + public static List GetPublicProperties(object instance) + { + PropertyInfo[] properties = instance.GetType().GetProperties(); + return properties.ToList().Select(t => new CommonItem(t.Name, t.GetValue(instance))).ToList(); + } + + /// + /// 获取顶级基类 + /// + /// 类型 + public static Type GetTopBaseType() + { + return GetTopBaseType(typeof(T)); + } + + /// + /// 获取顶级基类 + /// + /// 类型 + public static Type GetTopBaseType(Type type) + { + if (type == null) + { + return null; + } + + if (type.IsInterface) + { + return type; + } + + if (type.BaseType == typeof(object)) + { + return type; + } + + return GetTopBaseType(type.BaseType); + } + + /// + /// 获取元素类型,如果是集合,返回集合的元素类型 + /// + /// 类型 + public static Type GetElementType(Type type) + { + if (IsCollection(type) == false) + { + return type; + } + + if (type.IsArray) + { + return type.GetElementType(); + } + + Type[] genericArgumentsTypes = type.GetTypeInfo().GetGenericArguments(); + if (genericArgumentsTypes == null || genericArgumentsTypes.Length == 0) + { + throw new ArgumentException("泛型类型参数不能为空"); + } + + return genericArgumentsTypes[0]; + } +} diff --git a/Source/Core/MAX.Core/Helpers/UriHelper.cs b/Source/Core/MAX.Core/Helpers/UriHelper.cs new file mode 100644 index 0000000..bfa9f33 --- /dev/null +++ b/Source/Core/MAX.Core/Helpers/UriHelper.cs @@ -0,0 +1,149 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace MAX.Core.Helpers; + +public class UriHelper +{ + #region 对外公开方法 + /// + /// 带有前缀的域名 + /// + /// + /// + public static string GetSchemeDomain(string url) + { + Uri uri = new(url); + return $"{uri.Scheme}://{uri.Authority}"; + } + /// + /// 获取域名 + /// + /// + /// + public static string GetDomain(string url) + { + Uri uri = new(url); + return uri.Host; + } + /// + /// 获取 URL + /// + /// URL 地址 + /// + public static string GetUrl(string url) + { + Uri uri = new(url); + return uri.AbsolutePath; + } + /// + /// URL 解码 + /// + /// URL 地址 + /// + public static string UrlDecode(string url) + { + return System.Web.HttpUtility.UrlDecode(url); + } + /// + /// URL 解码 + /// + /// URL 地址 + /// 编码格式 + /// + public static string UrlDecode(string url, Encoding encoding) + { + return System.Web.HttpUtility.UrlDecode(url, encoding); + } + /// + /// URL 编码 + /// + /// URL 地址 + /// + public static string UrlEncode(string url) + { + return System.Web.HttpUtility.UrlEncode(url); + } + /// + /// URL 编码 + /// + /// URL 地址 + /// 编码格式 + /// + public static string UrlEncode(string url, Encoding encoding) + { + return System.Web.HttpUtility.UrlEncode(url, encoding); + } + /// + /// 获取参数列表 + /// + /// Url 地址 + /// + public static List GetQueryList(string url) + { + return GetQueryList(url, Encoding.UTF8); + } + /// + /// 获取参数列表 + /// + /// Url 地址 + /// 编码格式 + /// + public static List GetQueryList(string url, Encoding encoding) + { + Uri uri = new(url); + string query = uri.PathAndQuery; + if (query.IndexOf(".") > 0) + { + query = query.Substring(0, query.IndexOf(".")); + } + + List dataList = []; + string[] queryDataList = query.Split(new string[] { "/" }, StringSplitOptions.RemoveEmptyEntries); + if (queryDataList != null && queryDataList.Length > 0) + { + foreach (string queryData in queryDataList) + { + dataList.Add(System.Web.HttpUtility.UrlDecode(queryData, encoding)); + } + } + return dataList; + } + /// + /// 获取参数字典 + /// + /// Url 地址 + /// + public static Dictionary GetQueryDict(string url) + { + return GetQueryDict(url, Encoding.UTF8); + } + /// + /// 获取参数字典 + /// + /// Url 地址 + /// 编码格式 + /// + public static Dictionary GetQueryDict(string url, Encoding encoding) + { + Uri uri = new(url); + string query = uri.Query.TrimStart(new char[] { '?' }); + + Dictionary dataDict = []; + string[] queryDataList = query.Split(new string[] { "&" }, StringSplitOptions.RemoveEmptyEntries); + if (queryDataList != null && queryDataList.Length > 0) + { + foreach (string queryData in queryDataList) + { + string[] keyValue = queryData.Split(new string[] { "=" }, StringSplitOptions.RemoveEmptyEntries); + if (!dataDict.ContainsKey(keyValue[0])) + { + dataDict.Add(keyValue[0].ToLower(), System.Web.HttpUtility.UrlDecode(keyValue[1], encoding)); + } + } + } + return dataDict; + } + #endregion +} diff --git a/Source/Core/MAX.Core/Infrastructure/Bootstrap.cs b/Source/Core/MAX.Core/Infrastructure/Bootstrap.cs new file mode 100644 index 0000000..9d401a6 --- /dev/null +++ b/Source/Core/MAX.Core/Infrastructure/Bootstrap.cs @@ -0,0 +1,32 @@ +using System.Collections.Generic; + +namespace MAX.Core.Infrastructure; + +public class Bootstrap//Bootstrap +{ + public Bootstrap() + { + + } + + private readonly List _listUse = []; + public static Bootstrap Create() + { + return new Bootstrap(); + } + + + public void UseMiddleware() where T : IBootstrap, new() + { + T t = new(); + t.Use(); + _listUse.Add(t); + } + public void Builder() + { + foreach (IBootstrap item in _listUse) + { + item.Build(); + } + } +} diff --git a/Source/Core/MAX.Core/Infrastructure/IBootstrap.cs b/Source/Core/MAX.Core/Infrastructure/IBootstrap.cs new file mode 100644 index 0000000..ae02eb8 --- /dev/null +++ b/Source/Core/MAX.Core/Infrastructure/IBootstrap.cs @@ -0,0 +1,7 @@ +namespace MAX.Core.Infrastructure; + +public interface IBootstrap +{ + void Use(); + void Build(); +} \ No newline at end of file diff --git a/Source/Core/MAX.Core/Infrastructure/IStartup.cs b/Source/Core/MAX.Core/Infrastructure/IStartup.cs new file mode 100644 index 0000000..a0c0d28 --- /dev/null +++ b/Source/Core/MAX.Core/Infrastructure/IStartup.cs @@ -0,0 +1,15 @@ +using MAX.Core.Autofac; + +namespace MAX.Core; + +public interface IStartup : IDependencyRegistrar +{ + /// + /// 优先级(优先级越高越先执行) + /// + int Priority { get; } + /// + /// 初始化 + /// + void Initialize(); +} diff --git a/Source/Core/MAX.Core/Infrastructure/MediatorManager.cs b/Source/Core/MAX.Core/Infrastructure/MediatorManager.cs new file mode 100644 index 0000000..e173fd6 --- /dev/null +++ b/Source/Core/MAX.Core/Infrastructure/MediatorManager.cs @@ -0,0 +1,103 @@ +using MAX.Core.Autofac; +using MediatR; +using Microsoft.Win32; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace MAX.Core.Infrastructure; +public class MediatorManager : Mediator +{ + private readonly DynamicNotificationHandlerRegistry _registry; + + public static MediatorManager Instance + { + get + { + return IocManager.Resolve(); + } + } + + public MediatorManager(ServiceFactory serviceFactory) : base(serviceFactory) + { + _registry = new DynamicNotificationHandlerRegistry(); + } + protected override async Task PublishCore(IEnumerable> allHandlers, INotification notification, CancellationToken cancellationToken) + { + await base.PublishCore(allHandlers, notification, cancellationToken); + + + await _registry.Publish(notification, cancellationToken); + } + + public void RegisterNotification(INotificationHandler handler) where TNotification : INotification + { + _registry.Register(handler); + } +} + +class DynamicNotificationHandlerRegistry +{ + public DynamicNotificationHandlerRegistry() + { + } + // Key: notification type, Value: 已注册的 handler 调用委托列表 + private readonly ConcurrentDictionary>> + _handlers = new(); + + /// 插件加载时调用,注册 Handler 实例 + public void Register(INotificationHandler handler) + where TNotification : INotification + { + var list = _handlers.GetOrAdd( + typeof(TNotification), + _ => new List>()); + + lock (list) + { + list.Add((n, ct) => handler.Handle((TNotification)n, ct)); + } + } + + /// 插件卸载时调用(可选) + public void Unregister(Assembly pluginAssembly) + { + // 移除该程序集注册的所有 handler(需配合工厂模式追踪) + // 简单实现:重建列表时过滤掉目标程序集的类型 + foreach (var key in _handlers.Keys) + { + if (_handlers.TryGetValue(key, out var list)) + { + lock (list) + { + list.RemoveAll(f => f.Method.DeclaringType?.Assembly == pluginAssembly); + } + } + } + } + + /// Publish 时由 PluginAwareMediator 调用 + public async Task Publish(INotification notification, CancellationToken cancellationToken) + { + var notificationType = notification.GetType(); + if (!_handlers.TryGetValue(notificationType, out var handlers)) + return; + + List> snapshot; + lock (handlers) + { + snapshot = handlers.ToList(); // 快照,避免遍历时被修改 + } + + foreach (var handler in snapshot) + { + await handler(notification, cancellationToken); + } + } +} \ No newline at end of file diff --git a/Source/Core/MAX.Core/Infrastructure/SingleInstance.cs b/Source/Core/MAX.Core/Infrastructure/SingleInstance.cs new file mode 100644 index 0000000..b6eb50a --- /dev/null +++ b/Source/Core/MAX.Core/Infrastructure/SingleInstance.cs @@ -0,0 +1,42 @@ +using System; +using System.Reflection; + +namespace MAX.Core.Infrastructure; + +/// +/// 单例,需要在当前类中定义 +/// +/// +public class SingleInstance where T : class +{ + private static readonly object lockObj = new(); + private static T mySelf;//= default(T); + public static T Instance + { + get + { + lock (lockObj) + { + mySelf ??= InstanceCreator.CreateInstance(); + } + + return mySelf; + } + } +} + +public static class InstanceCreator +{ + public static T CreateInstance() + { + Type type = typeof(T); + try + { + return (T)type.Assembly.CreateInstance(type.FullName, true, BindingFlags.NonPublic | BindingFlags.Instance, null, null, null, null); + } + catch (MissingMethodException ex) + { + throw new Exception($"{ex.Message}\n(单例模式下,构造函数必须为 private)"); + } + } +} diff --git a/Source/Core/MAX.Core/Logger/ConsoleLogger.cs b/Source/Core/MAX.Core/Logger/ConsoleLogger.cs new file mode 100644 index 0000000..2c330dd --- /dev/null +++ b/Source/Core/MAX.Core/Logger/ConsoleLogger.cs @@ -0,0 +1,124 @@ +using System; +using System.Diagnostics; +using System.Globalization; +using System.IO; + +namespace MAX.Core.Logger; + +/// +/// Implementation of that logs into a . +/// +public class ConsoleLogger : ILoggerFacade +{ + private readonly bool _debug; + private readonly bool _console; + + public bool IsDebugEnabled => true; + + /// + /// Initializes a new instance of that writes to + /// the console output. + /// + public ConsoleLogger(bool debug = true, bool console = true) + { + _debug = debug; + _console = console; + } + + + + + + + ///// + ///// Disposes the associated . + ///// + ///// When , disposes the associated . + //protected virtual void Dispose(bool disposing) + //{ + // if (disposing) + // { + // writer?.Dispose(); + // } + //} + + /// + ///Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + /// Calls . + ///2 + public void Dispose() + { + GC.SuppressFinalize(this); + } + + public void Debug(Exception ex, string message) + { + Log(message, LogLevel.Debug); + } + + public void Debug(string message) + { + Log(message, LogLevel.Debug); + } + + public void Info(Exception ex, string message) + { + Log(message, LogLevel.Info); + } + + public void Info(string message) + { + Log(message, LogLevel.Info); + } + + public void Warn(Exception ex, string message) + { + Log(message, LogLevel.Warn); + } + + public void Warn(string message) + { + Log(message, LogLevel.Warn); + } + + public void Error(Exception ex, string message) + { + Log(message, LogLevel.Error); + } + + public void Error(string message) + { + Log(message, LogLevel.Error); + } + + public void Fatal(Exception ex, string message) + { + Log(message, LogLevel.Fatal); + } + + public void Fatal(string message) + { + Log(message, LogLevel.Fatal); + } + + public void Error(Exception ex) + { + Log(ex.Message, LogLevel.Error); + } + + public void Log(string message, LogLevel level) + { + string messageToLog = string.Format(CultureInfo.InvariantCulture, "{1}: {2}. Timestamp:{0:u}.", DateTime.Now, + level.ToString().ToUpper(CultureInfo.InvariantCulture), message); + //Trace.WriteLine(messageToLog); + if (_debug) + { + System.Diagnostics.Debug.WriteLine(messageToLog); + } + if (_console) + { + Console.WriteLine(messageToLog); + } + } +} \ No newline at end of file diff --git a/Source/Core/MAX.Core/Logger/ICustomLogger.cs b/Source/Core/MAX.Core/Logger/ICustomLogger.cs new file mode 100644 index 0000000..456a96b --- /dev/null +++ b/Source/Core/MAX.Core/Logger/ICustomLogger.cs @@ -0,0 +1,12 @@ +using MAX.Core.Infrastructure; + +namespace MAX.Core.Logger; + +/// 自定义日志记录器接口 +public interface ICustomLogger +{ + /// 记录日志信息 + /// 日志分组 + /// 日志内容 + void Log(string group, string message); +} diff --git a/Source/Core/MAX.Core/Logger/ILoggerFacade.cs b/Source/Core/MAX.Core/Logger/ILoggerFacade.cs new file mode 100644 index 0000000..02bd991 --- /dev/null +++ b/Source/Core/MAX.Core/Logger/ILoggerFacade.cs @@ -0,0 +1,68 @@ +using System; + +namespace MAX.Core.Logger; + +/// +/// 日志操作 +/// +public interface ILoggerFacade +{ + /// + /// 调试级别是否启用 + /// + bool IsDebugEnabled { get; } + + /// + /// 调试 + /// + void Debug(Exception ex, string message); + /// + /// 调试 + /// + /// 日志消息 + void Debug(string message); + /// + /// 信息 + /// + void Info(Exception ex, string message); + /// + /// 信息 + /// + /// 日志消息 + void Info(string message); + /// + /// 警告 + /// + void Warn(Exception ex, string message); + /// + /// 警告 + /// + /// 日志消息 + void Warn(string message); + /// + /// 错误 + /// + void Error(Exception ex, string message); + /// + /// 错误 + /// + /// 日志消息 + void Error(string message); + /// + /// 致命错误 + /// + void Fatal(Exception ex, string message); + /// + /// 致命错误 + /// + /// 日志消息 + void Fatal(string message); + void Error(Exception ex); + + /// + /// 写入日志 + /// + /// + /// + void Log(string message, LogLevel level); +} diff --git a/Source/Core/MAX.Core/Logger/LogLevel.cs b/Source/Core/MAX.Core/Logger/LogLevel.cs new file mode 100644 index 0000000..941b991 --- /dev/null +++ b/Source/Core/MAX.Core/Logger/LogLevel.cs @@ -0,0 +1,15 @@ +namespace MAX.Core.Logger; + +/// +/// ÈÕ־µȼ¶ +/// +public enum LogLevel +{ + Trace = 0, + Debug = 1, + Info = 2, + Warn = 3, + Error = 4, + Fatal = 5, + None = 6 +} \ No newline at end of file diff --git a/Source/Core/MAX.Core/MAX.Core.csproj b/Source/Core/MAX.Core/MAX.Core.csproj new file mode 100644 index 0000000..66f7466 --- /dev/null +++ b/Source/Core/MAX.Core/MAX.Core.csproj @@ -0,0 +1,14 @@ + + + + $(DefaultTargetFramework) + NU1608;CS1574;CS1573;CS1572;CS1571;CS1711;CS1570;CS1591 + True + + + + + + + + diff --git a/Source/Core/MAX.Core/Reflections/AppTypeFinder.cs b/Source/Core/MAX.Core/Reflections/AppTypeFinder.cs new file mode 100644 index 0000000..0fd8be2 --- /dev/null +++ b/Source/Core/MAX.Core/Reflections/AppTypeFinder.cs @@ -0,0 +1,197 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text.RegularExpressions; + +namespace MAX.Core.Reflections; + +public partial class AppTypeFinder : Finder +{ + public static AppTypeFinder Instance { get; } = new AppTypeFinder(); + + /// + /// 所有程序集 + /// + public static List Assemblies => Instance.GetAssemblies(); + + public static Exception LoadDLL(string dllPath, out Assembly assembly) + { + return Instance.LoadAssemblyToAppDomain(dllPath, out assembly); + } + + /// + /// 程序集是否匹配 + /// + protected override bool Match(string assemblyName) + { + if (base.Match(assemblyName)) + { + if (AvailablePrefix().Exists(assemblyName.StartsWith)) + { + return true; + } + return Regex.IsMatch(assemblyName, "^(MAX\\.)", RegexOptions.IgnoreCase | RegexOptions.Compiled); + } + return false; + } + + + protected virtual List AvailablePrefix() + { + return ["Nexus."]; + } + + public static IEnumerable GetAllTypes() + { + List types = []; + foreach (Assembly item in Assemblies) + { + try + { + types.AddRange(item.GetTypes()); + } + catch + { + + } + } + return types; + } + + /// + /// 查找当前程序集中所有继承或实现了指定类型的非抽象类。 + /// + /// + /// 要匹配的目标类型,可以是: + /// + /// 普通接口类型(例如 IMyService) + /// 开放泛型接口类型(例如 IRepository<>) + /// 普通基类类型(例如 BaseHandler) + /// 开放泛型基类类型(例如 BaseHandler<>) + /// + /// + /// + /// 所有满足条件的具体类(非抽象类)的 数组。 + /// + /// + /// 当 为 null 时抛出。 + /// + /// + /// + /// 此方法会遍历 GetAllTypes() 返回的所有类型,并筛选出: + /// + /// 实现了指定接口的类(包括开放泛型接口的封闭实现) + /// 继承了指定基类的类(包括开放泛型基类的封闭实现) + /// + /// + /// 示例: + /// + /// // 查找所有实现了 IEnumDisplayHandler<T> 的类 + /// var handlers = FindAllDerivedTypes(typeof(IEnumDisplayHandler<>)); + /// + /// // 查找所有实现了 IMyService 的类 + /// var services = FindAllDerivedTypes(typeof(IMyService)); + /// + /// // 查找所有继承 BaseHandler 的类 + /// var subclasses = FindAllDerivedTypes(typeof(BaseHandler)); + /// + /// // 查找所有继承开放泛型基类 BaseHandler<T> 的类 + /// var genericSubclasses = FindAllDerivedTypes(typeof(BaseHandler<>)); + /// + /// + public static Type[] FindAllDerivedTypes(Type targetType) + { + if (targetType == null) + { + throw new ArgumentNullException(nameof(targetType)); + } + + return GetAllTypes().Where(t => + { + try + { + if (!t.IsClass || t.IsAbstract) + { + return false; + } + + // 开放泛型接口 + if (targetType.IsInterface && targetType.IsGenericTypeDefinition) + { + return t.GetInterfaces().Any(i => + i.IsGenericType && i.GetGenericTypeDefinition() == targetType); + } + + // 普通接口 + if (targetType.IsInterface) + { + return targetType.IsAssignableFrom(t); + } + + // 开放泛型基类 + if (targetType.IsGenericTypeDefinition) + { + return IsSubclassOfRawGeneric(t, targetType); + } + + // 普通基类 + return targetType.IsAssignableFrom(t) && t != targetType; + } + catch + { + return false; + } + }) + .ToArray(); + } + + /// + /// 检查指定类型是否继承自某个开放泛型基类。 + /// + /// 要检查的类型。 + /// 开放泛型基类类型,例如 BaseHandler<>。 + /// + /// 如果 的继承链中存在指定的开放泛型基类,则返回 true;否则返回 false。 + /// + /// + /// 此方法会沿着 的继承链向上遍历, + /// 并在每一级检查是否为指定的开放泛型基类定义。 + /// + private static bool IsSubclassOfRawGeneric(Type type, Type generic) + { + while (type != null && type != typeof(object)) + { + Type cur = type.IsGenericType ? type.GetGenericTypeDefinition() : type; + if (generic == cur) + { + return true; + } + + type = type.BaseType; + } + return false; + } + + /// + /// 泛型包装方法,查找所有继承或实现了指定类型 的非抽象类。 + /// + /// + /// 目标类型,可以是接口、开放泛型接口、基类或开放泛型基类。 + /// + /// + /// 所有满足条件的具体类(非抽象类)的 数组。 + /// + /// + /// 调用示例: + /// + /// var handlers = FindAllDerivedTypes<IEnumDisplayHandler<>>(); + /// var services = FindAllDerivedTypes<IMyService>(); + /// var subclasses = FindAllDerivedTypes<BaseHandler>(); + /// var genericSubclasses = FindAllDerivedTypes<BaseHandler<>>(); + /// + /// + public static Type[] FindAllDerivedTypes() => FindAllDerivedTypes(typeof(T)); + + +} diff --git a/Source/Core/MAX.Core/Reflections/Finder.cs b/Source/Core/MAX.Core/Reflections/Finder.cs new file mode 100644 index 0000000..a941c95 --- /dev/null +++ b/Source/Core/MAX.Core/Reflections/Finder.cs @@ -0,0 +1,129 @@ +using MAX.Core.Helpers; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Text.RegularExpressions; + +namespace MAX.Core.Reflections; + +public class Finder +{ + /// + /// 跳过的程序集 + /// + private const string SkipAssemblies = "^System|^Microsoft|^mscorlib|^netstandard|^Autofac|^AutoMapper|^EntityFramework|^Newtonsoft|^Castle|^NLog|^Pomelo|^AspectCore|^Xunit|^Nito|^Npgsql|^Exceptionless|^MySqlConnector|^Anonymously|^libuv|^api-ms|^clrcompression|^clretwrc|^clrjit|^coreclr|^dbgshim|^e_sqlite3|^hostfxr|^hostpolicy|^MessagePack|^mscordaccore|^mscordbi|^mscorrc|^sni|^sos|^SOS\\.NETCore|^sos_amd64|^SQLitePCLRaw|^StackExchange|^Swashbuckle|^WindowsBase|^ucrtbase|^DotNetCore\\.CAP|^MongoDB|^Confluent\\.Kafka|^librdkafka|^EasyCaching|^RabbitMQ|^Consul|^Dapper|^EnyimMemcachedCore|^Pipelines|^DnsClient|^IdentityModel|^zlib|^Runtime\\.|^Google|^Grpc|^Internal\\.|^Azure|^DevExpress\\.|^FreeSql\\.|^PresentationFramework|^PresentationCore|^Prism|^DirectWriteForwarder|^MAX\\.Common|^ReflectionHelperTypesPublicWrappers|^UIAutomation|^WindowsFormsIntegration"; + + /// + /// 获取程序集列表 + /// + public virtual List GetAssemblies() + { + LoadAssemblies(AppDomain.CurrentDomain.BaseDirectory); + return GetAssembliesFromCurrentAppDomain(); + } + + /// + /// 加载程序集到当前应用程序域 + /// + /// 目录绝对路径 + protected void LoadAssemblies(string path) + { + foreach (string file in Directory.GetFiles(path, "*.dll")) + { + if (Match(Path.GetFileName(file)) == false) + { + continue; + } + + LoadAssemblyToAppDomain(file, out _); + } + } + + /// + /// 程序集是否匹配 + /// + protected virtual bool Match(string assemblyName) + { + return Regex.IsMatch(assemblyName, SkipAssemblies, RegexOptions.IgnoreCase | RegexOptions.Compiled) == false; + } + + /// + /// 将程序集添加当前应用程序域 + /// + protected Exception LoadAssemblyToAppDomain(string file, out Assembly assembly) + { + try + { + //Debug.WriteLine($"Load Assembly : {Path.GetFileName(file)}"); + AssemblyName assemblyName = AssemblyName.GetAssemblyName(file); + assembly = AppDomain.CurrentDomain.Load(assemblyName); + return null; + } + catch (FileNotFoundException) + { + // 如果找不到文件,回退到 LoadFrom 方式 + try + { + assembly = Assembly.LoadFrom(file); + return null; + } + catch (Exception ex) + { + assembly = null; + return ex; + } + } + catch (Exception ex) + { + assembly = null; + return ex; + } + } + + /// + /// 从当前应用程序域获取程序集列表 + /// + private List GetAssembliesFromCurrentAppDomain() + { + List result = []; + foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies()) + { + if (Match(assembly)) + { + result.Add(assembly); + } + } + return result.Distinct().ToList(); + } + + /// + /// 程序集是否匹配 + /// + private bool Match(Assembly assembly) + { + return !Regex.IsMatch(assembly.FullName, SkipAssemblies, RegexOptions.IgnoreCase | RegexOptions.Compiled); + } + + /// + /// 查找类型列表 + /// + /// 查找类型 + /// 在指定的程序集列表中查找 + public List Find(List assemblies = null) + { + return Find(typeof(T), assemblies); + } + + /// + /// 查找类型列表 + /// + /// 查找类型 + /// 在指定的程序集列表中查找 + public List Find(Type findType, List assemblies = null) + { + assemblies ??= GetAssemblies(); + return ReflectionHelper.FindTypes(findType, assemblies.ToArray()); + } +} diff --git a/Source/Core/MAX.Core/Startup.cs b/Source/Core/MAX.Core/Startup.cs new file mode 100644 index 0000000..c466aec --- /dev/null +++ b/Source/Core/MAX.Core/Startup.cs @@ -0,0 +1,78 @@ +/*** + * _ooOoo_ + * o8888888o + * 88" . "88 + * (| -_- |) + * O\ = /O + * ____/`---'\____ + * . ' \\| |// `. + * / \\||| : |||// \ + * / _||||| -:- |||||- \ + * | | \\\ - /// | | + * | \_| ''\---/'' | | + * \ .-\__ `-` ___/-. / + * ___`. .' /--.--\ `. . __ + * ."" '< `.___\_<|>_/___.' >'"". + * | | : `- \`.;`\ _ /`;.`/ - ` : | | + * \ \ `-. \_ __\ /__ _/ .-` / / + * ======`-.____`-.___\_____/___.-`____.-'====== + * `=---=' + * + * ............................................. + * 佛祖保佑 永无BUG + * 快加工资 不改需求 + */ + + +using Autofac; +using MAX.Core.DependencyInjection; +using MAX.Core.Infrastructure; +using MAX.Core.Reflections; +using MediatR; +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Threading; +using System.Threading.Tasks; + +namespace MAX.Core; + +public class Startup : IStartup +{ + public int Priority => int.MaxValue; + + public void Initialize() + { + + } + + public void Register(ContainerBuilder builder) + { + builder.RegisterInstance(LazyServiceProvider.Instance).As().AsSelf().SingleInstance(); + + #region 注册MediatR + Assembly[] assemblies = AppTypeFinder.Assemblies.ToArray(); + + builder.RegisterType().As().AsSelf().SingleInstance(); + + builder.RegisterAssemblyTypes(assemblies) + .AsClosedTypesOf(typeof(INotificationHandler<>)) + .AsImplementedInterfaces() + .AsSelf() + .InstancePerLifetimeScope(); + + builder.RegisterAssemblyTypes(assemblies) + .AsClosedTypesOf(typeof(IRequestHandler<>)) + .AsImplementedInterfaces() + .AsSelf() + .InstancePerLifetimeScope(); + + builder.Register(ctx => + { + var c = ctx.Resolve(); + return t => c.Resolve(t); + }).SingleInstance(); + + #endregion + } +} \ No newline at end of file diff --git a/Source/Core/MAX.Framework/Commons/KeyValue.cs b/Source/Core/MAX.Framework/Commons/KeyValue.cs new file mode 100644 index 0000000..9a90116 --- /dev/null +++ b/Source/Core/MAX.Framework/Commons/KeyValue.cs @@ -0,0 +1,34 @@ +using System.Text; + +namespace MAX.Framework.Commons; + +public struct KeyValue +{ + public KeyValue(string key, string value) + { + Key = key; + Value = value; + } + + public string Key { get; set; } + public string Value { get; set; } + + public override string ToString() + { + StringBuilder stringBuilder = new(); + stringBuilder.Append('['); + if (Key != null) + { + stringBuilder.Append(Key.ToString()); + } + + stringBuilder.Append(", "); + if (Value != null) + { + stringBuilder.Append(Value.ToString()); + } + + stringBuilder.Append(']'); + return stringBuilder.ToString(); + } +} diff --git a/Source/Core/MAX.Framework/Controls/Attach/DialogWindowProperty.cs b/Source/Core/MAX.Framework/Controls/Attach/DialogWindowProperty.cs new file mode 100644 index 0000000..de2308b --- /dev/null +++ b/Source/Core/MAX.Framework/Controls/Attach/DialogWindowProperty.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; + +namespace MAX.Framework.Controls.Attach; + +/// +/// 弹出窗口属性值 +/// +public class DialogWindowProperty +{ + public static readonly DependencyProperty WindowStartupLocationProperty = DependencyProperty.RegisterAttached( + "WindowStartupLocation", typeof(WindowStartupLocation), typeof(DialogWindowProperty), new PropertyMetadata(WindowStartupLocation.CenterScreen)); + public static void SetWindowStartupLocation(DependencyObject element, WindowStartupLocation value) + => element.SetValue(WindowStartupLocationProperty, value); + public static WindowStartupLocation GetWindowStartupLocation(DependencyObject element) + => (WindowStartupLocation)element.GetValue(WindowStartupLocationProperty); + + + public static readonly DependencyProperty ResizeModeProperty = DependencyProperty.RegisterAttached( + "ResizeMode", typeof(ResizeMode), typeof(DialogWindowProperty), new PropertyMetadata(ResizeMode.CanResize)); + public static void SetResizeMode(DependencyObject element, ResizeMode value) + => element.SetValue(ResizeModeProperty, value); + public static ResizeMode GetResizeMode(DependencyObject element) + => (ResizeMode)element.GetValue(ResizeModeProperty); + + + + public static readonly DependencyProperty WidthProperty = DependencyProperty.RegisterAttached( + "Width", typeof(double), typeof(DialogWindowProperty), new PropertyMetadata(double.NaN)); + public static void SetWidth(DependencyObject element, double value) + => element.SetValue(WidthProperty, value); + public static double GetWidth(DependencyObject element) + => (double)element.GetValue(WidthProperty); + + + + public static readonly DependencyProperty HeightProperty = DependencyProperty.RegisterAttached( + "Height", typeof(double), typeof(DialogWindowProperty), new PropertyMetadata(double.NaN)); + public static void SetHeight(DependencyObject element, double value) + => element.SetValue(HeightProperty, value); + public static double GetHeight(DependencyObject element) + => (double)element.GetValue(HeightProperty); +} diff --git a/Source/Core/MAX.Framework/Controls/ConfirmDialog.cs b/Source/Core/MAX.Framework/Controls/ConfirmDialog.cs new file mode 100644 index 0000000..610d819 --- /dev/null +++ b/Source/Core/MAX.Framework/Controls/ConfirmDialog.cs @@ -0,0 +1,290 @@ +using MAX.Core.Commons; +using Prism.Commands; +using Prism.Mvvm; +using Prism.Services.Dialogs; +using System; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Input; +using System.Windows.Media; + +namespace MAX.Framework.Controls; + +public static class IDialogServiceExtension +{ + public static void ShowDialog(this IDialogService dialogService, string name, TParameter parameter, Action callback) where TParameter : class + { + dialogService.ShowDialog(name, new DialogParameters + { + {"parameter", parameter}, + }, callback); + } + public static void Show(this IDialogService dialogService, string name, TParameter parameter, Action callback) where TParameter : class + { + dialogService.Show(name, new DialogParameters + { + {"parameter", parameter}, + }, callback); + } +} + + +/// +/// 确定取消对话框 ViewModel +/// +public class ConfirmDialogViewModel : BindableBase, IDialogAware +{ + public ConfirmDialogViewModel() + { + + } + + private string title = ""; + private bool _enableConfirm = true; + private bool _ShowConfirm = true; + private bool _ShowCancel = true; + private bool _ShowErrorMsg = true; + private string _ErrorMsg = ""; + private string confirmText = "确定"; + private string cancelText = "取消"; + private string backText = "返回"; + private bool allowEscape = true; + private bool allowEnter = true; + private bool isLoading = false; + + public string Title + { + get => title; + protected set => SetProperty(ref title, value); + } + + public bool AllowEscape + { + get => allowEscape; + set => SetProperty(ref allowEscape, value); + } + + public bool AllowEnter + { + get => allowEnter; + set => SetProperty(ref allowEnter, value); + } + + public bool IsLoading + { + get => isLoading; + set => SetProperty(ref isLoading, value, () => + { + AllowEscape = !value; + AllowEnter = !value; + EnableConfirm = !value; + }); + } + // + // + // + private SolidColorBrush _Underpaint = new SolidColorBrush(Color.FromRgb(238, 238, 238)); + /// + /// 背景底色 + /// + public SolidColorBrush Underpaint + { + get => _Underpaint; + set => SetProperty(ref _Underpaint, value); + } + + public event Action RequestClose; + protected void OnRequestClose(IDialogResult result) + { + RequestClose?.Invoke(result); + } + + public virtual bool CanCloseDialog() + { + return true; + } + + public virtual void OnDialogClosed() + { + + } + + public virtual void OnDialogOpened(IDialogParameters parameters) + { + + } + + public bool EnableConfirm + { + get => _enableConfirm; + set => SetProperty(ref _enableConfirm, value); + } + + public bool ShowConfirm + { + get => _ShowConfirm; + set => SetProperty(ref _ShowConfirm, value); + } + public bool ShowCancel + { + get => _ShowCancel; + set => SetProperty(ref _ShowCancel, value); + } + public bool ShowErrorMsg + { + get => _ShowErrorMsg; + set => SetProperty(ref _ShowErrorMsg, value); + } + + public string ConfirmText + { + get => confirmText; + set => SetProperty(ref confirmText, value); + } + public string CancelText + { + get => cancelText; + set => SetProperty(ref cancelText, value); + } + public string BackText + { + get => backText; + set => SetProperty(ref backText, value); + } + + /// + /// 错误提示 + /// + public string ErrorMsg + { + get => _ErrorMsg; + set => SetProperty(ref _ErrorMsg, value); + } + + public virtual ICommand ConfirmCommand => new DelegateCommand(_Confirm); + + protected virtual void _Confirm() + { + DialogResult ret = Confirm(); + if (ret != null) + { + RequestClose?.Invoke(ret); + } + } + + protected virtual DialogResult Confirm() + { + return new DialogResult(ButtonResult.OK); + } + + public virtual ICommand CancelCommand => new DelegateCommand(_Cancel); + + protected virtual void _Cancel() + { + DialogResult ret = Cancel(); + if (ret != null) + { + RequestClose?.Invoke(ret); + } + else + { + RequestClose?.Invoke(new DialogResult(ButtonResult.Cancel)); + } + } + protected virtual DialogResult Cancel() + { + return new DialogResult(ButtonResult.Cancel); + } + + public virtual void Loaded(object content) + { + var bb = Application.Current.TryFindResource("BackgroundBrush") as SolidColorBrush; + if (bb != null) + { + Underpaint = bb; + } + + + } + + public class AsyncConfirmDialogViewModel : ConfirmDialogViewModel + { + public override ICommand ConfirmCommand => new AsyncDelegateCommand(_Confirm); + + protected new async Task _Confirm() + { + DialogResult ret = await Confirm(); + if (ret != null) + { + OnRequestClose(ret); + } + } + + protected new virtual async Task Confirm() + { + return await Task.FromResult(new DialogResult(ButtonResult.OK)); + } + } + + public interface IDialogClassParameters + { + + } +} +/// 确定取消对话框 +/// +/// 参数Model +public class ConfirmDialogViewModel : ConfirmDialogViewModel where TParameter : class +{ + protected TParameter Args { get; set; } + public override void OnDialogOpened(IDialogParameters parameters) + { + base.OnDialogOpened(parameters); + if (parameters.TryGetValue("parameter", out TParameter parameter)) + { + Args = parameter; + } + } +} + +public abstract class ConfirmDialogViewModel : ConfirmDialogViewModel where TParameter : class where TResult : DialogResult/*, new()*/ +{ + protected TParameter Args { get; set; } + public override void OnDialogOpened(IDialogParameters parameters) + { + base.OnDialogOpened(parameters); + if (parameters.TryGetValue("parameter", out TParameter parameter)) + { + Args = parameter; + } + } + protected override void _Confirm() + { + TResult ret = Confirm(); + if (ret != null) + { + base.OnRequestClose(ret); + } + } + protected new abstract TResult Confirm(); + + protected override void _Cancel() + { + TResult ret = Cancel(); + if (ret != null) + { + base.OnRequestClose(ret); + } + else + { + base.OnRequestClose(new DialogResult(ButtonResult.Cancel)); + } + } + protected new abstract TResult Cancel(); +} + + +//public abstract class ConfirmDialogParameterViewModel : ConfirmDialogViewModel +//{ +// public abstract class Parameter; +//} \ No newline at end of file diff --git a/Source/Core/MAX.Framework/Extensions/StyleExtensions.cs b/Source/Core/MAX.Framework/Extensions/StyleExtensions.cs new file mode 100644 index 0000000..208c9d2 --- /dev/null +++ b/Source/Core/MAX.Framework/Extensions/StyleExtensions.cs @@ -0,0 +1,93 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; + +namespace MAX.Framework.Extensions; + +public static class StyleExtensions +{ + /// + /// 合并指定的 。 + /// + /// + /// + public static void Merge(this Style style1, Style style2) + { + if (style1 == null) + { + throw new ArgumentNullException("style1"); + } + if (style2 == null) + { + throw new ArgumentNullException("style2"); + } + + if (style1.TargetType.IsAssignableFrom(style2.TargetType)) + { + style1.TargetType = style2.TargetType; + } + + if (style2.BasedOn != null) + { + style1.Merge(style2.BasedOn); + } + + foreach (SetterBase currentSetter in style2.Setters) + { + style1.Setters.Add(currentSetter); + } + + + foreach (TriggerBase currentTrigger in style2.Triggers) + { + style1.Triggers.Add(currentTrigger); + } + + // This code is only needed when using DynamicResources. + foreach (object key in style2.Resources.Keys) + { + style1.Resources[key] = style2.Resources[key]; + } + } + + /// + /// 集合是否有值。 + /// + /// + /// + /// + public static bool HasValue(this IEnumerable source) + { + return source != null && source.Any(); + } + + public static ResourceDictionary FindResourceDictionary(this Style style, object resourceKey) + { + if (style.Resources != null && style.Resources.Contains(resourceKey)) + { + return style.Resources; + } + if (style.BasedOn != null) + { + return style.BasedOn.FindResourceDictionary(resourceKey); + } + return null; + } + + public static void Merge(this ResourceDictionary resourceDictionary, ResourceDictionary mergee) + { + if (resourceDictionary != null && mergee != null && mergee.Count > 0) + { + foreach (var key in mergee.Keys) + { + if (!resourceDictionary.Contains(key)) + { + resourceDictionary.Add(key, mergee[key]); + } + } + } + } +} diff --git a/Source/Core/MAX.Framework/Helpers/ObjectHelper.cs b/Source/Core/MAX.Framework/Helpers/ObjectHelper.cs new file mode 100644 index 0000000..1caa2de --- /dev/null +++ b/Source/Core/MAX.Framework/Helpers/ObjectHelper.cs @@ -0,0 +1,76 @@ +using System; +using System.Collections.Concurrent; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; + +namespace MAX.Framework.Helpers; + +public static class ObjectHelper +{ + private static readonly ConcurrentDictionary CachedObjectProperties = + new(); + + public static void TrySetProperty( + TObject obj, + Expression> propertySelector, + Func valueFactory, + params Type[] ignoreAttributeTypes + ) + { + TrySetProperty(obj, propertySelector, x => valueFactory(), ignoreAttributeTypes); + } + + public static void TrySetProperty( + TObject obj, + Expression> propertySelector, + Func valueFactory, + params Type[] ignoreAttributeTypes + ) + { + string cacheKey = + $"{obj?.GetType().FullName}-" + + $"{propertySelector}-" + + $"{(ignoreAttributeTypes != null ? "-" + string.Join("-", ignoreAttributeTypes.Select(x => x.FullName)) : "")}"; + + PropertyInfo property = CachedObjectProperties.GetOrAdd( + cacheKey, + (name) => + { + if (propertySelector.Body.NodeType != ExpressionType.MemberAccess) + { + return null; + } + + MemberExpression memberExpression = (MemberExpression)propertySelector.Body; + + PropertyInfo propertyInfo = obj?.GetType() + .GetProperties() + .FirstOrDefault( + x => + x.Name == memberExpression.Member.Name + && x.GetSetMethod(true) != null + ); + + if (propertyInfo == null) + { + return null; + } + + if ( + ignoreAttributeTypes != null + && ignoreAttributeTypes.Any( + ignoreAttribute => propertyInfo.IsDefined(ignoreAttribute, true) + ) + ) + { + return null; + } + + return propertyInfo; + } + ); + + property?.SetValue(obj, valueFactory(obj)); + } +} diff --git a/Source/Core/MAX.Framework/Helpers/SimpleSchedulerHelper.cs b/Source/Core/MAX.Framework/Helpers/SimpleSchedulerHelper.cs new file mode 100644 index 0000000..7a8fff8 --- /dev/null +++ b/Source/Core/MAX.Framework/Helpers/SimpleSchedulerHelper.cs @@ -0,0 +1,45 @@ +using System.Threading; +using System.Threading.Tasks; +using Timer = System.Threading.Timer; + +namespace MAX.Framework.Helpers; + +/// +/// 定义一个简单的调度器接口 +/// +public interface IJob +{ + Task Execute(); +} + +/// +/// 简单调度器帮助类,用于实现简单的调度功能。 +/// +public class SimpleSchedulerHelper +{ + private readonly Timer _timer; + + public SimpleSchedulerHelper() + { + _timer = new Timer(DoTime, null, Timeout.Infinite, Timeout.Infinite); + } + private void DoTime(object obj) + { + + } + /// + /// 启动调度器 + /// + public void Start() + { + _timer.Change(10, 1000); + } + + /// + /// 停止调度器 + /// + public void Stop() + { + _timer.Change(Timeout.Infinite, Timeout.Infinite); + } +} diff --git a/Source/Core/MAX.Framework/Helpers/WinCmdHelper.cs b/Source/Core/MAX.Framework/Helpers/WinCmdHelper.cs new file mode 100644 index 0000000..2d31c9b --- /dev/null +++ b/Source/Core/MAX.Framework/Helpers/WinCmdHelper.cs @@ -0,0 +1,156 @@ +using MAX.Core.Commons; +using System; +using System.Diagnostics; +using System.Text; + +namespace MAX.Framework.Helpers; + +public class WinCmdHelper +{ + #region 执行Dos命令 + + /// + /// 执行Dos命令 + /// + /// Dos命令及参数 + /// 是否显示cmd窗口 + /// 执行完毕后是否关闭cmd进程 + /// 成功返回true,失败返回false + public static Result ExcuteDosCommand(string cmd, string workdir, bool isShowCmdWindow = false, bool isWaitForExit = false, bool isCloseCmdProcess = true) + { + Result result = new(); + try + { + StringBuilder errString = new(); + StringBuilder outString = new(); + Process p = new(); + p.StartInfo.FileName = "cmd"; + p.StartInfo.WorkingDirectory = workdir; + p.StartInfo.Arguments = "/c " + cmd; + p.StartInfo.StandardOutputEncoding = Encoding.UTF8; + p.StartInfo.StandardErrorEncoding = Encoding.UTF8; + p.StartInfo.UseShellExecute = false; + p.StartInfo.RedirectStandardOutput = true; + p.StartInfo.RedirectStandardError = true; + p.StartInfo.CreateNoWindow = !isShowCmdWindow; + p.OutputDataReceived += new DataReceivedEventHandler(delegate (object sender, DataReceivedEventArgs e) + { + if (!string.IsNullOrEmpty(e.Data)) + { + outString.AppendLine(e.Data); + Debug.WriteLine(e.Data); + } + }); + p.ErrorDataReceived += new DataReceivedEventHandler(delegate (object sender, DataReceivedEventArgs e) + { + if (!string.IsNullOrEmpty(e.Data)) + { + errString.AppendLine(e.Data); + } + }); + p.Start(); + p.BeginOutputReadLine(); + p.BeginErrorReadLine(); + if (isWaitForExit) + { + p.WaitForExit(); + } + + if (isCloseCmdProcess) + { + p.Close(); + } + if (errString.Length <= 0) + { + result.Success = true; + result.Data = outString.ToString(); + } + Console.WriteLine(string.Format("成功执行Dos命令[{0}]!", cmd)); + return result; + } + catch (Exception ex) + { + Console.WriteLine("执行命令失败,请检查输入的命令是否正确:" + ex.Message, ex); + result.Msg = ex.Message; + } + + return result; + } + + private static void P_ErrorDataReceived(object sender, DataReceivedEventArgs e) + { + throw new NotImplementedException(); + } + + #endregion + + #region 判断指定的进程是否在运行中 + + /// + /// 判断指定的进程是否在运行中 + /// + /// 要判断的进程名称,不包括扩展名exe + /// 进程文件的完整路径 + /// 存在返回true,否则返回false + public static bool CheckProcessExists(string processName, string processFileName) + { + Process[] processes = Process.GetProcessesByName(processName); + foreach (Process p in processes) + { + if (!string.IsNullOrEmpty(processFileName)) + { + if (processFileName == p?.MainModule?.FileName) + { + return true; + } + } + else + { + return true; + } + } + return false; + } + + #endregion + + #region 结束指定的windows进程 + + /// + /// 结束指定的windows进程,如果进程存在 + /// + /// 进程名称,不包含扩展名 + /// 进程文件完整路径,如果为空则删除所有进程名为processName参数值的进程 + public static bool KillProcessExists(string processName, string processFileName) + { + try + { + Process[] processes = Process.GetProcessesByName(processName); + foreach (Process p in processes) + { + if (!string.IsNullOrEmpty(processFileName)) + { + if (processFileName == p.MainModule.FileName) + { + p.Kill(); + p.Close(); + } + } + else + { + p.Kill(); + p.Close(); + } + } + Console.WriteLine(string.Format("成功结束[{0}]进程!", processes)); + return true; + } + catch (Exception ex) + { + Console.WriteLine("结束指定的Widnows进程异常:" + ex.Message, ex); + return false; + } + } + + #endregion +} diff --git a/Source/Core/MAX.Framework/Interfaces/IAppClosedHandler.cs b/Source/Core/MAX.Framework/Interfaces/IAppClosedHandler.cs new file mode 100644 index 0000000..7780080 --- /dev/null +++ b/Source/Core/MAX.Framework/Interfaces/IAppClosedHandler.cs @@ -0,0 +1,19 @@ +using System; + +namespace MAX.Framework.Interfaces; + +public interface IAppClosedHandler +{ + /// 优先级(优先级越高越先执行) + int Priority { get; } + + /// 描述信息 + string Description { get; } + + /// 应用关闭时执行的方法 + /// 参数 + /// + void AppClosed(AppCloseParameter parameter); +} + +public record class AppCloseParameter(bool IsAppInitialized); \ No newline at end of file diff --git a/Source/Core/MAX.Framework/Interfaces/IAppConfig.cs b/Source/Core/MAX.Framework/Interfaces/IAppConfig.cs new file mode 100644 index 0000000..c0adaa0 --- /dev/null +++ b/Source/Core/MAX.Framework/Interfaces/IAppConfig.cs @@ -0,0 +1,8 @@ +using MAX.Core.DependencyInjection; + +namespace MAX.Framework.Interfaces; + +public interface IAppConfig : ISingletonDependency +{ + T GetValue(string key, T defaultValue = default); +} diff --git a/Source/Core/MAX.Framework/Interfaces/IAppInitialize.cs b/Source/Core/MAX.Framework/Interfaces/IAppInitialize.cs new file mode 100644 index 0000000..f97fffd --- /dev/null +++ b/Source/Core/MAX.Framework/Interfaces/IAppInitialize.cs @@ -0,0 +1,41 @@ +using MAX.Core.DependencyInjection; +using System.Threading.Tasks; + +namespace MAX.Framework.Interfaces; + +/// +/// 初始化接口 +/// +public interface IAppInitialize : ISingletonDependency, IPriority +{ + /// + /// 描述 + /// + string Description { get; } + + /// + /// App 初始化数据 + /// + void AppInitialize(IStatusReport statusReport); +} + +/// +/// 初始化接口(异步) +/// +public interface IAppInitializeAsync : ISingletonDependency, IPriority +{ + /// + /// 描述 + /// + string Description { get; } + + /// + /// App 初始化数据 + /// + Task AppInitializeAsync(IStatusReport statusReport); +} + +public interface IStatusReport +{ + void SetStatus(string status); +} \ No newline at end of file diff --git a/Source/Core/MAX.Framework/Interfaces/IPriority.cs b/Source/Core/MAX.Framework/Interfaces/IPriority.cs new file mode 100644 index 0000000..bba7af2 --- /dev/null +++ b/Source/Core/MAX.Framework/Interfaces/IPriority.cs @@ -0,0 +1,9 @@ +namespace MAX.Framework.Interfaces; + +public interface IPriority +{ + /// + /// 优先级(优先级越高越先执行) + /// + int Priority { get; } +} diff --git a/Source/Core/MAX.Framework/Logger/NLogger.cs b/Source/Core/MAX.Framework/Logger/NLogger.cs new file mode 100644 index 0000000..578d8bc --- /dev/null +++ b/Source/Core/MAX.Framework/Logger/NLogger.cs @@ -0,0 +1,146 @@ +using MAX.Core.Infrastructure; +using MAX.Core.Logger; +using NLog; +using System; +using LLogLevel = MAX.Core.Logger.LogLevel; +using NLogLevel = NLog.LogLevel; + +namespace MAX.Framework.Logger; + +public class NLogger : SingleInstance, ILoggerFacade +{ + private readonly ILogger _log; + + private NLogger() + { + //Target target = new FileTarget("log1") + //{ + // FileName = $"{FolderConfig.LoggingFolder}/{DateTime.Now:yyyy-MM-dd}.log", + //}; + //LoggingConfiguration config = new(); + //config.AddRule(NLogLevel.Debug, NLogLevel.Fatal, target); + + //LogManager.Configuration = config; + _log = LogManager.GetLogger("GlobalLogger"); + } + + public bool IsDebugEnabled { get; private set; } = true; + + public void Debug(Exception ex, string message) + { + Execute(NLogLevel.Debug, ex, message); + } + + public void Debug(string message) + { + Execute(NLogLevel.Debug, message); + } + public void Error(Exception ex) + { + Execute(NLogLevel.Error, ex); + } + public void Error(Exception ex, string message) + { + Execute(NLogLevel.Error, ex, message); + } + + public void Error(string message) + { + Execute(NLogLevel.Error, message); + } + + public void Fatal(Exception ex, string message) + { + Execute(NLogLevel.Fatal, ex, message); + } + + public void Fatal(string message) + { + Execute(NLogLevel.Fatal, message); + } + + public void Info(Exception ex, string message) + { + Execute(NLogLevel.Info, ex, message); + } + + public void Info(string message) + { + Execute(NLogLevel.Info, message); + } + + public void Warn(Exception ex, string message) + { + Execute(NLogLevel.Warn, ex, message); + } + + public void Warn(string message) + { + Execute(NLogLevel.Warn, message); + } + public void Log(string message, LLogLevel level) + { + NLogLevel lv = NLogLevel.Info; + switch (level) + { + case LLogLevel.Trace: + lv = NLogLevel.Trace; + break; + case LLogLevel.Debug: + lv = NLogLevel.Debug; + break; + case LLogLevel.Info: + lv = NLogLevel.Info; + break; + case LLogLevel.Warn: + lv = NLogLevel.Warn; + break; + case LLogLevel.Error: + lv = NLogLevel.Error; + break; + case LLogLevel.Fatal: + lv = NLogLevel.Fatal; + break; + case LLogLevel.None: + lv = NLogLevel.Off; + break; + } + Execute(lv, message); + } + /// + /// 是否启用 + /// + private bool Enabled(NLogLevel level) + { + if (level > NLogLevel.Debug) + { + return true; + } + + return IsDebugEnabled; + } + + /// + /// 执行 + /// + private void Execute(NLogLevel level, Exception ex, string message = "") + { + if (Enabled(level)) + { + _log.Log(level, ex, message, null); + } + } + + + /// + /// 执行 + /// + private void Execute(NLogLevel level, string message) + { + if (Enabled(level)) + { + _log.Log(level, message); + } + } + +} diff --git a/Source/Core/MAX.Framework/MAX.Framework.csproj b/Source/Core/MAX.Framework/MAX.Framework.csproj new file mode 100644 index 0000000..df16ed7 --- /dev/null +++ b/Source/Core/MAX.Framework/MAX.Framework.csproj @@ -0,0 +1,19 @@ + + + + true + NU1608;CS0168;CS1591;CS1574;CS1573;CS1711 + True + + + + + + + + + + + + + diff --git a/Source/Core/MAX.Framework/PrismExtension/Commands/AsyncDelegateCommand.cs b/Source/Core/MAX.Framework/PrismExtension/Commands/AsyncDelegateCommand.cs new file mode 100644 index 0000000..8a7a4e9 --- /dev/null +++ b/Source/Core/MAX.Framework/PrismExtension/Commands/AsyncDelegateCommand.cs @@ -0,0 +1,243 @@ +using System.Linq.Expressions; +using System; +using System.Threading; +using System.Threading.Tasks; +using System.Windows.Input; +using Prism.Properties; + + +namespace Prism.Commands +{ + + /// + /// Provides an implementation of the + /// + public class AsyncDelegateCommand : DelegateCommandBase, IAsyncCommand + { + private bool _enableParallelExecution = false; + private bool _isExecuting = false; + private readonly Func _executeMethod; + private Func _canExecuteMethod; + private Func _getCancellationToken = () => CancellationToken.None; + + /// + /// Creates a new instance of with the to invoke on execution. + /// + /// The to invoke when is called. + public AsyncDelegateCommand(Func executeMethod) +#if NET6_0_OR_GREATER + : this (c => executeMethod().WaitAsync(c), () => true) +#else + : this(c => executeMethod(), () => true) +#endif + { + + } + + /// + /// Creates a new instance of with the to invoke on execution. + /// + /// The to invoke when is called. + public AsyncDelegateCommand(Func executeMethod) + : this(executeMethod, () => true) + { + + } + + /// + /// Creates a new instance of with the to invoke on execution + /// and a to query for determining if the command can execute. + /// + /// The to invoke when is called. + /// The delegate to invoke when is called + public AsyncDelegateCommand(Func executeMethod, Func canExecuteMethod) +#if NET6_0_OR_GREATER + : this(c => executeMethod().WaitAsync(c), canExecuteMethod) +#else + : this(c => executeMethod(), canExecuteMethod) +#endif + { + } + + /// + /// Creates a new instance of with the to invoke on execution + /// and a to query for determining if the command can execute. + /// + /// The to invoke when is called. + /// The delegate to invoke when is called + public AsyncDelegateCommand(Func executeMethod, Func canExecuteMethod) + : base() + { + if (executeMethod == null || canExecuteMethod == null) + throw new ArgumentNullException(nameof(executeMethod), "Neither the executeMethod nor the canExecuteMethod delegates can be null"); + + _executeMethod = executeMethod; + _canExecuteMethod = canExecuteMethod; + } + + /// + /// Gets the current state of the AsyncDelegateCommand + /// + public bool IsExecuting + { + get => _isExecuting; + private set + { + _isExecuting = value; + OnCanExecuteChanged(); + } + } + + /// + /// Executes the command. + /// + public async Task Execute(CancellationToken? cancellationToken = null) + { + var token = cancellationToken ?? _getCancellationToken(); + try + { + if (!_enableParallelExecution && IsExecuting) + return; + + IsExecuting = true; + await _executeMethod(token) + .ConfigureAwait(false); + } + catch + { + throw; + + } + finally + { + IsExecuting = false; + } + } + + /// + /// Determines if the command can be executed. + /// + /// Returns if the command can execute,otherwise returns . + public bool CanExecute() + { + try + { + if (!_enableParallelExecution && IsExecuting) + return false; + + return _canExecuteMethod?.Invoke() ?? true; + } + catch (Exception ex) + { + return false; + } + } + + /// + /// Handle the internal invocation of + /// + /// Command Parameter + protected override async void Execute(object parameter) + { + // We don't want to wrap this in a try/catch because we already handle + // or mean to rethrow the exception in the call with the CancellationToken. + var cancellationToken = _getCancellationToken(); + await Execute(cancellationToken) + .ConfigureAwait(false); + } + + /// + /// Handle the internal invocation of + /// + /// + /// if the Command Can Execute, otherwise + protected override bool CanExecute(object parameter) + { + return CanExecute(); + } + + /// + /// Enables Parallel Execution of Async Tasks + /// + /// The current instance of . + public AsyncDelegateCommand EnableParallelExecution() + { + _enableParallelExecution = true; + return this; + } + + /// + /// Sets the based on the specified timeout. + /// + /// A specified timeout. + /// The current instance of . + public AsyncDelegateCommand CancelAfter(TimeSpan timeout) => + CancellationTokenSourceFactory(() => new CancellationTokenSource(timeout).Token); + + /// + /// Provides a delegate callback to provide a default CancellationToken when the Command is invoked. + /// + /// The default Factory. + /// The current instance of . + public AsyncDelegateCommand CancellationTokenSourceFactory(Func factory) + { + _getCancellationToken = factory; + return this; + } + + /// + /// Observes a property that implements INotifyPropertyChanged, and automatically calls DelegateCommandBase.RaiseCanExecuteChanged on property changed notifications. + /// + /// The object type containing the property specified in the expression. + /// The property expression. Example: ObservesProperty(() => PropertyName). + /// The current instance of . + public AsyncDelegateCommand ObservesProperty(Expression> propertyExpression) + { + ObservesPropertyInternal(propertyExpression); + return this; + } + + /// + /// Observes a property that is used to determine if this command can execute, and if it implements INotifyPropertyChanged it will automatically call DelegateCommandBase.RaiseCanExecuteChanged on property changed notifications. + /// + /// The property expression. Example: ObservesCanExecute(() => PropertyName). + /// The current instance of . + public AsyncDelegateCommand ObservesCanExecute(Expression> canExecuteExpression) + { + _canExecuteMethod = canExecuteExpression.Compile(); + ObservesPropertyInternal(canExecuteExpression); + return this; + } + + /// + /// Provides the ability to connect a delegate to catch exceptions encountered by CanExecute or the Execute methods of the DelegateCommand + /// + /// TThe callback when a specific exception is encountered + /// The current instance of . + public AsyncDelegateCommand Catch(Action @catch) + where TException : Exception + { + return this; + } + + /// + /// Provides the ability to connect a delegate to catch exceptions encountered by CanExecute or the Execute methods of the DelegateCommand + /// + /// The generic / default callback when an exception is encountered + /// The current instance of . + public AsyncDelegateCommand Catch(Action @catch) + { + return this; + } + + Task IAsyncCommand.ExecuteAsync(object parameter) + { + return Execute(_getCancellationToken()); + } + + Task IAsyncCommand.ExecuteAsync(object parameter, CancellationToken cancellationToken) + { + return Execute(cancellationToken); + } + } +} \ No newline at end of file diff --git a/Source/Core/MAX.Framework/PrismExtension/Commands/AsyncDelegateCommand{T}.cs b/Source/Core/MAX.Framework/PrismExtension/Commands/AsyncDelegateCommand{T}.cs new file mode 100644 index 0000000..55d6aec --- /dev/null +++ b/Source/Core/MAX.Framework/PrismExtension/Commands/AsyncDelegateCommand{T}.cs @@ -0,0 +1,284 @@ +using System.Linq.Expressions; +using System; +using System.Threading; +using System.Threading.Tasks; +using System.Windows.Input; +using System.Reflection.Emit; +using System.Resources; + +namespace Prism.Commands +{ + /// + /// Provides an implementation of the with a generic parameter type. + /// + /// + public class AsyncDelegateCommand : DelegateCommandBase, IAsyncCommand + { + private bool _enableParallelExecution = false; + private bool _isExecuting = false; + private readonly Func _executeMethod; + private Func _canExecuteMethod; + private Func _getCancellationToken = () => CancellationToken.None; + + /// + /// Creates a new instance of with the to invoke on execution. + /// + /// The to invoke when is called. + public AsyncDelegateCommand(Func executeMethod) +#if NET6_0_OR_GREATER + : this((p,t) => executeMethod(p).WaitAsync(t), _ => true) +#else + : this((p, t) => executeMethod(p), _ => true) +#endif + { + + } + + /// + /// Creates a new instance of with the to invoke on execution. + /// + /// The to invoke when is called. + public AsyncDelegateCommand(Func executeMethod) + : this(executeMethod, _ => true) + { + + } + + /// + /// Creates a new instance of with the to invoke on execution + /// and a to query for determining if the command can execute. + /// + /// The to invoke when is called. + /// The delegate to invoke when is called + public AsyncDelegateCommand(Func executeMethod, Func canExecuteMethod) +#if NET6_0_OR_GREATER + : this((p, c) => executeMethod(p).WaitAsync(c), canExecuteMethod) +#else + : this((p, c) => executeMethod(p), canExecuteMethod) +#endif + { + + } + + /// + /// Creates a new instance of with the to invoke on execution + /// and a to query for determining if the command can execute. + /// + /// The to invoke when is called. + /// The delegate to invoke when is called + public AsyncDelegateCommand(Func executeMethod, Func canExecuteMethod) + : base() + { + if (executeMethod == null || canExecuteMethod == null) + throw new ArgumentNullException(nameof(executeMethod), "Neither the executeMethod nor the canExecuteMethod delegates can be null"); + + + _executeMethod = executeMethod; + _canExecuteMethod = canExecuteMethod; + } + + /// + /// Gets the current state of the AsyncDelegateCommand + /// + public bool IsExecuting + { + get => _isExecuting; + private set + { + _isExecuting = value; + OnCanExecuteChanged(); + } + } + + /// + /// Executes the command. + /// + public async Task Execute(T parameter, CancellationToken? cancellationToken = null) + { + var token = cancellationToken ?? _getCancellationToken(); + + try + { + if (!_enableParallelExecution && IsExecuting) + return; + + IsExecuting = true; + await _executeMethod(parameter, token) + .ConfigureAwait(false); + } + catch (Exception ex) + { + throw; + } + finally + { + IsExecuting = false; + } + } + + /// + /// Determines if the command can be executed. + /// + /// Returns if the command can execute,otherwise returns . + public bool CanExecute(T parameter) + { + try + { + if (!_enableParallelExecution && IsExecuting) + return false; + + return _canExecuteMethod?.Invoke(parameter) ?? true; + } + catch (Exception ex) + { + + throw; + + return false; + } + } + + /// + /// Handle the internal invocation of + /// + /// Command Parameter + protected override async void Execute(object parameter) + { + var cancellationToken = _getCancellationToken(); + T parameterAsT; + try + { + parameterAsT = (T)parameter; + } + catch (Exception ex) + { + throw; + } + + // If we had an exception casting the parameter to T , + // we would have already returned. We want to surface any + // exceptions thrown by the Execute method. + await Execute(parameterAsT, cancellationToken) + .ConfigureAwait(false); + } + + /// + /// Handle the internal invocation of + /// + /// + /// if the Command Can Execute, otherwise + protected override bool CanExecute(object parameter) + { + try + { + return CanExecute((T)parameter); + } + catch (Exception ex) + { + throw; + } + } + + /// + /// Enables Parallel Execution of Async Tasks + /// + /// The current instance of . + public AsyncDelegateCommand EnableParallelExecution() + { + _enableParallelExecution = true; + return this; + } + + /// + /// Sets the based on the specified timeout. + /// + /// A specified timeout. + /// The current instance of . + public AsyncDelegateCommand CancelAfter(TimeSpan timeout) => + CancellationTokenSourceFactory(() => new CancellationTokenSource(timeout).Token); + + /// + /// Provides a delegate callback to provide a default CancellationToken when the Command is invoked. + /// + /// The default Factory. + /// The current instance of . + public AsyncDelegateCommand CancellationTokenSourceFactory(Func factory) + { + _getCancellationToken = factory; + return this; + } + + /// + /// Observes a property that implements INotifyPropertyChanged, and automatically calls DelegateCommandBase.RaiseCanExecuteChanged on property changed notifications. + /// + /// The type of the return value of the method that this delegate encapsulates + /// The property expression. Example: ObservesProperty(() => PropertyName). + /// The current instance of . + public AsyncDelegateCommand ObservesProperty(Expression> propertyExpression) + { + ObservesPropertyInternal(propertyExpression); + return this; + } + + /// + /// Observes a property that is used to determine if this command can execute, and if it implements INotifyPropertyChanged it will automatically call DelegateCommandBase.RaiseCanExecuteChanged on property changed notifications. + /// + /// The property expression. Example: ObservesCanExecute(() => PropertyName). + /// The current instance of . + public AsyncDelegateCommand ObservesCanExecute(Expression> canExecuteExpression) + { + Expression> expression = Expression.Lambda>(canExecuteExpression.Body, Expression.Parameter(typeof(T), "o")); + _canExecuteMethod = expression.Compile(); + ObservesPropertyInternal(canExecuteExpression); + return this; + } + + /// + /// Provides the ability to connect a delegate to catch exceptions encountered by CanExecute or the Execute methods of the DelegateCommand + /// + /// TThe callback when a specific exception is encountered + /// The current instance of . + public AsyncDelegateCommand Catch(Action @catch) + where TException : Exception + { + return this; + } + + /// + /// Provides the ability to connect a delegate to catch exceptions encountered by CanExecute or the Execute methods of the DelegateCommand + /// + /// The generic / default callback when an exception is encountered + /// The current instance of . + public AsyncDelegateCommand Catch(Action @catch) + { + return this; + } + + async Task IAsyncCommand.ExecuteAsync(object parameter) + { + try + { + // If T is not nullable this may throw an exception + await Execute((T)parameter, _getCancellationToken()); + } + catch (Exception ex) + { + throw; + + } + } + + async Task IAsyncCommand.ExecuteAsync(object parameter, CancellationToken cancellationToken) + { + try + { + // If T is not nullable this may throw an exception + await Execute((T)parameter, cancellationToken); + } + catch (Exception ex) + { + throw; + } + } + } +} \ No newline at end of file diff --git a/Source/Core/MAX.Framework/PrismExtension/Commands/IAsyncCommand.cs b/Source/Core/MAX.Framework/PrismExtension/Commands/IAsyncCommand.cs new file mode 100644 index 0000000..72a86cd --- /dev/null +++ b/Source/Core/MAX.Framework/PrismExtension/Commands/IAsyncCommand.cs @@ -0,0 +1,28 @@ +using System.Threading; +using System.Threading.Tasks; +using System.Windows.Input; + +namespace Prism.Commands +{ + + /// + /// Provides an abstraction layer for custom controls which want to make use of Async Commands + /// + public interface IAsyncCommand : ICommand + { + /// + /// Executes the Command with a specified parameter and the Default . + /// + /// The Command Parameter + /// An Asynchronous Task + Task ExecuteAsync(object parameter); + + /// + /// Executes the Command with a specified parameter and using a + /// + /// The Command Parameter + /// The . + /// An Asynchronous Task + Task ExecuteAsync(object parameter, CancellationToken cancellationToken); + } +} \ No newline at end of file diff --git a/Source/Core/MAX.Framework/PrismExtension/Prism.Autofac/AutofacContainerExtension.cs b/Source/Core/MAX.Framework/PrismExtension/Prism.Autofac/AutofacContainerExtension.cs new file mode 100644 index 0000000..cfa2cac --- /dev/null +++ b/Source/Core/MAX.Framework/PrismExtension/Prism.Autofac/AutofacContainerExtension.cs @@ -0,0 +1,323 @@ +using Autofac; +using Autofac.Core; +using Autofac.Features.ResolveAnything; +using MAX.Core; +using MAX.Core.Autofac; +using MAX.Core.Helpers; +using MAX.Core.Reflections; +using MAX.Framework.Prism.Autofac; +using Prism.Ioc; +using Prism.Ioc.Internals; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Reflection; + +namespace Prism.Autofac; + +public class AutofacContainerExtension : IContainerExtension, IContainerInfo +{ + private AutofacScopedProvider _currentScope; + public readonly ContainerBuilder Builder; + + public IContainer Instance { get; private set; } + + public IScopedProvider CurrentScope => _currentScope; + public AutofacContainerExtension() + { + Builder = new ContainerBuilder(); + IocManager.SetContainerBuilder(Builder); + string currentContainer = "CurrentContainer"; + Builder.RegisterInstance(this).Named(currentContainer, typeof(AutofacContainerExtension)); + Builder.RegisterInstance(this).As(); + Builder.RegisterInstance(this).As(); + //设置过滤AOP + //IAspectConfiguration configuration = new AspectConfiguration(); + //foreach (var item in AppTypeFinder.Assemblies) + //{ + // string _namespace = item.FullName; + // if (!Regex.IsMatch(_namespace, "^MAX", RegexOptions.IgnoreCase | RegexOptions.Compiled)) + // { + // configuration.NonAspectPredicates.AddNamespace(_namespace); + // } + //} + //Builder.RegisterDynamicProxy(configuration); + } + public AutofacContainerExtension(ContainerBuilder builder) + { + Builder = builder; + IocManager.SetContainerBuilder(Builder); + string currentContainer = "CurrentContainer"; + //Builder.RegisterDynamicProxy(); + Builder.RegisterInstance(this).Named(currentContainer, typeof(AutofacContainerExtension)); + Builder.RegisterInstance(this).As(); + Builder.RegisterInstance(this).As(); + } + public IScopedProvider CreateScope() + { + return CreateScopeInternal(); + } + protected IScopedProvider CreateScopeInternal() + { + _currentScope = new AutofacScopedProvider(Instance); + return _currentScope; + } + public void FinalizeExtension() + { + if (Instance != null) + { + return; + } + + Assembly[] assemblies = AppTypeFinder.Assemblies.ToArray(); + + // Make sure any not specifically registered concrete type can resolve. + IEnumerable startupList = ReflectionHelper.GetInstancesByInterface(assemblies); + int index = 1; + foreach (IStartup item in startupList.OrderByDescending(a => a.Priority)) + { + Debug.WriteLine($"Startup - {index++} : {item.GetType()}"); + item.Initialize(); + } + + // Make sure any not specifically registered concrete type can resolve. + IEnumerable ListDependency = ReflectionHelper.GetInstancesByInterface(assemblies); + foreach (IDependencyRegistrar item in ListDependency) + { + item.Register(Builder); + } + + IEnumerable PreRegisterTypes = ReflectionHelper.GetInstancesByInterface(assemblies); + foreach (IPreRegisterTypes item in PreRegisterTypes) + { + item.RegisterTypes(this); + } + + Builder.RegisterSource(new AnyConcreteTypeNotAlreadyRegisteredSource()); + Instance = Builder.Build(); + IocManager.SetContainer(Instance); + + } + + public bool IsRegistered(Type type) + { + return Instance.IsRegistered(type); + } + + public bool IsRegistered(Type type, string name) + { + return Instance.IsRegisteredWithName(name, type); + } + + public IContainerRegistry Register(Type from, Type to) + { + Builder.RegisterType(to).As(from); + return this; + } + + public IContainerRegistry Register(Type from, Type to, string name) + { + Builder.RegisterType(to).Named(name, from); + return this; + } + + + + public IContainerRegistry RegisterInstance(Type type, object instance) + { + Builder.RegisterInstance(instance).As(type); + return this; + } + + public IContainerRegistry RegisterInstance(Type type, object instance, string name) + { + Builder.RegisterInstance(instance).Named(name, type); + return this; + } + + public IContainerRegistry RegisterSingleton(Type from, Type to) + { + Builder.RegisterType(to).As(from).SingleInstance(); + return this; + } + + public IContainerRegistry RegisterSingleton(Type from, Type to, string name) + { + Builder.RegisterType(to).Named(name, from).SingleInstance(); + return this; + } + + public IContainerRegistry RegisterSingleton(Type type, Func factoryMethod) + { + throw new NotImplementedException(); + } + + public IContainerRegistry RegisterSingleton(Type type, Func factoryMethod) + { + throw new NotImplementedException(); + } + + public object Resolve(Type type) + { + return Instance.Resolve(type); + } + + public object Resolve(Type type, params (Type Type, object Instance)[] parameters) + { + List listps = []; + foreach ((Type Type, object Instance) item in parameters) + { + TypedParameter ps = new(item.Type, item.Instance); + listps.Add(ps); + } + return Instance.Resolve(type, listps); + } + + public object Resolve(Type type, string name) + { + return Instance.ResolveNamed(name, type); + } + + public object Resolve(Type type, string name, params (Type Type, object Instance)[] parameters) + { + List listps = []; + foreach ((Type Type, object Instance) item in parameters) + { + TypedParameter ps = new(item.Type, item.Instance); + listps.Add(ps); + } + return Instance.ResolveNamed(name, type, listps); + } + + public IContainerRegistry Register(Type type, Func factoryMethod) + { + throw new NotImplementedException(); + } + + public IContainerRegistry Register(Type type, Func factoryMethod) + { + throw new NotImplementedException(); + } + public IContainerRegistry RegisterMany(Type type, params Type[] serviceTypes) + { + throw new NotImplementedException(); + } + + public IContainerRegistry RegisterManySingleton(Type type, params Type[] serviceTypes) + { + throw new NotImplementedException(); + } + + public IContainerRegistry RegisterScoped(Type from, Type to) + { + throw new NotImplementedException(); + } + + public IContainerRegistry RegisterScoped(Type type, Func factoryMethod) + { + throw new NotImplementedException(); + } + + public IContainerRegistry RegisterScoped(Type type, Func factoryMethod) + { + throw new NotImplementedException(); + } + + public Type GetRegistrationType(string key) + { + Type retType = null; + //First try friendly name registration. If not found, try type registration + foreach (IComponentRegistration registration in Instance.ComponentRegistry.Registrations) + { + //System.Diagnostics.Debug.WriteLine(registration.ToString()); + foreach (Service service in registration.Services) + { + //System.Diagnostics.Debug.WriteLine(service.ToString()); + if (service is KeyedService ks) + { + if (key.Equals(ks.ServiceKey.ToString(), StringComparison.Ordinal)) + { + if (Instance.ComponentRegistry.TryGetServiceRegistration(service, out ServiceRegistration serviceRegistration)) + { + retType = serviceRegistration.Registration.Target.Activator.LimitType; + } + } + } + } + } + + //Instance.TryResolveService + //retType = Instance.ResolveNamed(key).GetType(); + if (retType == null) + { + retType = Instance.ResolveNamed(key).GetType(); + if (retType == null) + { + retType = typeof(System.Object); + } + } + return retType; + } + + public Type GetRegistrationType(Type serviceType) + { + throw new NotImplementedException(); + } + + private class AutofacScopedProvider : IScopedProvider + { + private readonly IContainer _container; + + public bool IsAttached { get; set; } + + public IScopedProvider CurrentScope => this; + + public IScopedProvider CreateScope() + { + return this; + } + public AutofacScopedProvider(IContainer container) + { + _container = container; + } + + public void Dispose() + { + _container.Dispose(); + } + + public object Resolve(Type type) + { + return _container.Resolve(type); + } + + public object Resolve(Type type, params (Type Type, object Instance)[] parameters) + { + List listps = []; + foreach ((Type Type, object Instance) item in parameters) + { + TypedParameter ps = new(item.Type, item.Instance); + listps.Add(ps); + } + return _container.Resolve(type, listps); + } + + public object Resolve(Type type, string name) + { + return _container.ResolveNamed(name, type); + } + + public object Resolve(Type type, string name, params (Type Type, object Instance)[] parameters) + { + List list = []; + foreach ((Type Type, object Instance) item in parameters) + { + TypedParameter ps = new(item.Type, item.Instance); + list.Add(ps); + } + return _container.ResolveNamed(name, type, list); + } + + } +} \ No newline at end of file diff --git a/Source/Core/MAX.Framework/PrismExtension/Prism.Autofac/IAutofacContainerExtension.cs b/Source/Core/MAX.Framework/PrismExtension/Prism.Autofac/IAutofacContainerExtension.cs new file mode 100644 index 0000000..d819123 --- /dev/null +++ b/Source/Core/MAX.Framework/PrismExtension/Prism.Autofac/IAutofacContainerExtension.cs @@ -0,0 +1,15 @@ +using Autofac; +using Prism.Ioc; + +namespace Prism.Autofac; + +/// +/// Provides customization extensions for Autofac Applications +/// +public interface IAutofacContainerExtension : IContainerExtension +{ + /// + /// Gets the used to register the services for the application + /// + ContainerBuilder Builder { get; } +} diff --git a/Source/Core/MAX.Framework/PrismExtension/Prism.Autofac/IPreRegisterTypes.cs b/Source/Core/MAX.Framework/PrismExtension/Prism.Autofac/IPreRegisterTypes.cs new file mode 100644 index 0000000..4d8ac39 --- /dev/null +++ b/Source/Core/MAX.Framework/PrismExtension/Prism.Autofac/IPreRegisterTypes.cs @@ -0,0 +1,8 @@ +using Prism.Ioc; + +namespace MAX.Framework.Prism.Autofac; + +public interface IPreRegisterTypes +{ + void RegisterTypes(IContainerRegistry containerRegistry); +} diff --git a/Source/Core/MAX.Framework/PrismExtension/Prism.Autofac/PrismApplication.cs b/Source/Core/MAX.Framework/PrismExtension/Prism.Autofac/PrismApplication.cs new file mode 100644 index 0000000..2882366 --- /dev/null +++ b/Source/Core/MAX.Framework/PrismExtension/Prism.Autofac/PrismApplication.cs @@ -0,0 +1,32 @@ +using Prism.Ioc; +using System; +using AutofacCore = Autofac.Core; + +namespace Prism.Autofac; + +/// +/// Base application class that uses as it's container. +/// +public abstract class PrismApplication : PrismApplicationBase +{ + /// + /// Create a new used by Prism. + /// + /// A new . + protected override IContainerExtension CreateContainerExtension() + { + return new AutofacContainerExtension(); + } + + /// + /// Registers the s of the Exceptions that are not considered + /// root exceptions by the . + /// + protected override void RegisterFrameworkExceptionTypes() + { + ExceptionExtensions.RegisterFrameworkExceptionType(typeof(AutofacCore.DependencyResolutionException)); + ExceptionExtensions.RegisterFrameworkExceptionType(typeof(AutofacCore.Registration.ComponentNotRegisteredException)); + + } + +} diff --git a/Source/Core/MAX.Framework/PrismExtension/Prism.Autofac/PrismBootstrapper.cs b/Source/Core/MAX.Framework/PrismExtension/Prism.Autofac/PrismBootstrapper.cs new file mode 100644 index 0000000..2fc43d7 --- /dev/null +++ b/Source/Core/MAX.Framework/PrismExtension/Prism.Autofac/PrismBootstrapper.cs @@ -0,0 +1,30 @@ +using Prism.Ioc; +using System; +using AutofacCore = Autofac.Core; + +namespace Prism.Autofac; + +/// +/// Base bootstrapper class that uses as it's container. +/// +public abstract class PrismBootstrapper : PrismBootstrapperBase +{ + /// + /// Create a new used by Prism. + /// + /// A new . + protected override IContainerExtension CreateContainerExtension() + { + return new AutofacContainerExtension(); + } + + /// + /// Registers the s of the Exceptions that are not considered + /// root exceptions by the . + /// + protected override void RegisterFrameworkExceptionTypes() + { + ExceptionExtensions.RegisterFrameworkExceptionType(typeof(AutofacCore.DependencyResolutionException)); + ExceptionExtensions.RegisterFrameworkExceptionType(typeof(AutofacCore.Registration.ComponentNotRegisteredException)); + } +} diff --git a/Source/Core/MAX.Framework/PrismExtension/Prism.Autofac/PrismIocExtensions.cs b/Source/Core/MAX.Framework/PrismExtension/Prism.Autofac/PrismIocExtensions.cs new file mode 100644 index 0000000..ff33af7 --- /dev/null +++ b/Source/Core/MAX.Framework/PrismExtension/Prism.Autofac/PrismIocExtensions.cs @@ -0,0 +1,39 @@ +using Autofac; +using Prism.Ioc; + +namespace Prism.Autofac; + +/// +/// Extensions help get the underlying +/// +public static class PrismIocExtensions +{ + /// + /// Gets the from the + /// + /// The current + /// The underlying + public static IContainer GetContainer(this IContainerProvider containerProvider) + { + return ((IContainerExtension)containerProvider).Instance; + } + + /// + /// Gets the from the + /// + /// The current + /// The underlying + public static IContainer GetContainer(this IContainerRegistry containerRegistry) + { + return ((IContainerExtension)containerRegistry).Instance; + } + /// + /// Gets the used to register services. + /// + /// + /// The current + public static ContainerBuilder GetBuilder(this IContainerRegistry registry) + { + return ((IAutofacContainerExtension)registry).Builder; + } +} diff --git a/Source/Core/MAX.Framework/Startup.cs b/Source/Core/MAX.Framework/Startup.cs new file mode 100644 index 0000000..1a624e3 --- /dev/null +++ b/Source/Core/MAX.Framework/Startup.cs @@ -0,0 +1,64 @@ +using Autofac; +using MAX.Core; +using MAX.Core.Autofac; +using MAX.Core.DependencyInjection; +using MAX.Core.Logger; +using MAX.Core.Reflections; +using MAX.Framework.Logger; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Reflection; + +namespace MAX.Framework; + +public class Startup : IStartup +{ + public int Priority => int.MaxValue - 1; + + public void Initialize() + { + + } + + public void Register(ContainerBuilder builder) + { + builder.RegisterInstance(NLogger.Instance).As().SingleInstance(); + + #region 注册所有IOC接口 + + Assembly[] assemblies = AppTypeFinder.Assemblies.ToArray(); + + Type typeSingleton = typeof(ISingletonDependency); + builder.RegisterAssemblyTypes(assemblies) + .Where(a => + typeSingleton.IsAssignableFrom(a) + && a.IsClass + && !a.IsAbstract + && !typeof(INoDependency).IsAssignableFrom(a) + ) + .AsImplementedInterfaces() + .AsSelf() + .PropertiesAutowired(new CustomPropertySelector()) + .SingleInstance(); + + + Type typeTransient = typeof(ITransientDependency); + builder.RegisterAssemblyTypes(assemblies) + .Where(a => + typeTransient.IsAssignableFrom(a) + && a.IsClass + && !a.IsAbstract + && !typeof(INoDependency).IsAssignableFrom(a) + ) + .AsImplementedInterfaces() + .AsSelf() + .PropertiesAutowired(new CustomPropertySelector()) + .InstancePerDependency(); + + + #endregion + + } +} diff --git a/Source/Store.Management.Framework/AssemblyInfo.cs b/Source/Store.Management.Framework/AssemblyInfo.cs new file mode 100644 index 0000000..638d92b --- /dev/null +++ b/Source/Store.Management.Framework/AssemblyInfo.cs @@ -0,0 +1,8 @@ +using System.Windows.Markup; + +[assembly: XmlnsDefinition("Store.Management.Framework", "Store.Management.Framework")] +[assembly: XmlnsDefinition("Store.Management.Framework", "Store.Management.Framework.Extensions")] +[assembly: XmlnsDefinition("Store.Management.Framework", "Store.Management.Framework.Controls")] +[assembly: XmlnsDefinition("Store.Management.Framework", "Store.Management.Framework.Controls.Attach")] + +[assembly: XmlnsPrefix("Store.Management.Framework", "mf")] \ No newline at end of file diff --git a/Source/Store.Management.Framework/Controls/Attach/DataGridHelper.cs b/Source/Store.Management.Framework/Controls/Attach/DataGridHelper.cs new file mode 100644 index 0000000..acb21af --- /dev/null +++ b/Source/Store.Management.Framework/Controls/Attach/DataGridHelper.cs @@ -0,0 +1,58 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; + +namespace Store.Management.Framework.Controls.Attach; + +public class DataGridHelper +{ + /// + /// + /// + /// + /// + public static string GetNoDataTips(DependencyObject obj) + { + return (string)obj.GetValue(NoDataTipsProperty); + } + + /// + /// + /// + /// + /// + public static void SetNoDataTips(DependencyObject obj, string value) + { + obj.SetValue(NoDataTipsProperty, value); + } + + public static readonly DependencyProperty NoDataTipsProperty = + DependencyProperty.RegisterAttached("NoDataTips", typeof(string), typeof(DataGridHelper),new PropertyMetadata("无数据")); + + /// + /// + /// + /// + /// + public static bool GetIsActive(DependencyObject obj) + { + return (bool)obj.GetValue(IsActiveProperty); + } + + /// + /// + /// + /// + /// + public static void SetIsActive(DependencyObject obj, bool value) + { + obj.SetValue(IsActiveProperty, value); + } + + public static readonly DependencyProperty IsActiveProperty = + DependencyProperty.RegisterAttached("IsActive", typeof(bool), typeof(DataGridHelper)); + +} diff --git a/Source/Store.Management.Framework/Controls/DialogWindow.xaml b/Source/Store.Management.Framework/Controls/DialogWindow.xaml new file mode 100644 index 0000000..cd9e2cd --- /dev/null +++ b/Source/Store.Management.Framework/Controls/DialogWindow.xaml @@ -0,0 +1,12 @@ + + + + + diff --git a/Source/Store.Management.Framework/Controls/DialogWindow.xaml.cs b/Source/Store.Management.Framework/Controls/DialogWindow.xaml.cs new file mode 100644 index 0000000..5975094 --- /dev/null +++ b/Source/Store.Management.Framework/Controls/DialogWindow.xaml.cs @@ -0,0 +1,137 @@ + +using MAX.Framework.Controls; +using MAX.Framework.Controls.Attach; +using Prism.Services.Dialogs; +using Store.Management.Framework.Controls; +using Store.Management.Framework.Controls.Attach; +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.Shapes; +using MAX.Framework.Extensions; + +namespace Store.Management.Framework.Controls; + +/// +/// DialogWindow.xaml 的交互逻辑 +/// +public partial class DialogWindow : WindowBase, IDialogWindow +{ + private readonly Style dialogWindowStyle; + + /// + /// The of the dialog. + /// + public IDialogResult Result { get; set; } + private bool isConstructorSuccessful = false; + public DialogWindow() + { + InitializeComponent(); + Owner = Application.Current.MainWindow; + isConstructorSuccessful = true; + dialogWindowStyle = (Style)TryFindResource("DialogWindowStyle"); + + { + RoutedCommand escapeCommand = new RoutedCommand(); + CommandBindings.Add(new CommandBinding(escapeCommand, new ExecutedRoutedEventHandler((s, e) => + { + if (DataContext is ConfirmDialogViewModel vm && vm.AllowEscape) + { + vm.CancelCommand?.Execute(null); + } + }))); + InputBindings.Add(new InputBinding(escapeCommand, new KeyGesture(Key.Escape))); + } + + { + RoutedCommand enterCommand = new RoutedCommand(); + CommandBindings.Add(new CommandBinding(enterCommand, new ExecutedRoutedEventHandler((s, e) => + { + if (DataContext is ConfirmDialogViewModel vm && vm.AllowEnter) + { + vm.ConfirmCommand?.Execute(null); + } + }))); + InputBindings.Add(new InputBinding(enterCommand, new KeyGesture(Key.Enter))); + } + + Loaded += DialogWindow_Loaded; + } + + private void DialogWindow_Loaded(object sender, RoutedEventArgs e) + { + Loaded -= DialogWindow_Loaded; + if (DataContext is ConfirmDialogViewModel viewModel) + { + viewModel.Loaded(Content); + } + } + + protected override void OnContentChanged(object oldContent, object newContent) + { + this.Style = dialogWindowStyle; + if (newContent is UserControl control) + { + + Setter widthSetter = this.Style.Setters.OfType().FirstOrDefault(a => a.Property == WidthProperty); + if (widthSetter == null) + { + if (double.IsNaN(control.Width)) + { + this.Width = DialogWindowProperty.GetWidth(control); + } + else + { + this.Width = control.Width; + control.Width = double.NaN; + } + } + + + Setter heightSetter = this.Style.Setters.OfType().FirstOrDefault(a => a.Property == HeightProperty); + if (heightSetter == null) + { + if (double.IsNaN(control.Height)) + { + this.Height = DialogWindowProperty.GetHeight(control); + } + else + { + this.Height = control.Height; + control.Height = double.NaN; + } + } + + + ResizeMode resizeExt = DialogWindowProperty.GetResizeMode(control); + if (resizeExt != ResizeMode.CanResize) + { + this.ResizeMode = resizeExt; + } + + } + base.OnContentChanged(oldContent, newContent); + } + + protected override void OnStyleChanged(Style oldStyle, Style newStyle) + { + if (newStyle.TargetType == typeof(Window)) + { + if (isConstructorSuccessful) + { + dialogWindowStyle.Merge(newStyle); + } + return; + } + base.OnStyleChanged(oldStyle, newStyle); + } +} diff --git a/Source/Store.Management.Framework/Controls/UserControlBase.cs b/Source/Store.Management.Framework/Controls/UserControlBase.cs new file mode 100644 index 0000000..f7b4ef8 --- /dev/null +++ b/Source/Store.Management.Framework/Controls/UserControlBase.cs @@ -0,0 +1,17 @@ +using Prism.Mvvm; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Controls; + +namespace Store.Management.Framework.Controls; + +public class UserControlBase: UserControl +{ + public UserControlBase() + { + ViewModelLocator.SetAutoWireViewModel(this, true); + } +} diff --git a/Source/Store.Management.Framework/Controls/WindowBase.cs b/Source/Store.Management.Framework/Controls/WindowBase.cs new file mode 100644 index 0000000..b0fa165 --- /dev/null +++ b/Source/Store.Management.Framework/Controls/WindowBase.cs @@ -0,0 +1,22 @@ +using Prism.Mvvm; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Media; + +namespace Store.Management.Framework.Controls; + +public class WindowBase : HandyControl.Controls.Window +{ + public WindowBase() + { + NonClientAreaBackground = FindResource("PrimaryBrush") as Brush; + NonClientAreaForeground = FindResource("TextIconBrush") as Brush; + CloseButtonForeground = FindResource("TextIconBrush") as Brush; + OtherButtonForeground = FindResource("TextIconBrush") as Brush; + BorderThickness = new Thickness(0); + } +} diff --git a/Source/Store.Management.Framework/Extensions/DISource.cs b/Source/Store.Management.Framework/Extensions/DISource.cs new file mode 100644 index 0000000..8875b71 --- /dev/null +++ b/Source/Store.Management.Framework/Extensions/DISource.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Markup; + +namespace Store.Management.Framework.Extensions; + +public class DISource : MarkupExtension +{ + internal static Func Resolver { get; set; } + + public Type Type { get; set; } + public string Name { get; set; } + + public override object ProvideValue(IServiceProvider serviceProvider) + { + return Resolver?.Invoke(Type, Name); + } +} \ No newline at end of file diff --git a/Source/Store.Management.Framework/FrameworkApplication.cs b/Source/Store.Management.Framework/FrameworkApplication.cs new file mode 100644 index 0000000..61e89ba --- /dev/null +++ b/Source/Store.Management.Framework/FrameworkApplication.cs @@ -0,0 +1,51 @@ +using Prism.Ioc; +using Prism.Mvvm; +using Prism.Services.Dialogs; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using Store.Management.Framework.Extensions; +using Prism.Autofac; + +namespace Store.Management.Framework; + +public abstract class FrameworkApplication : PrismApplication +{ + + protected FrameworkApplication() + { + + } + + + protected override void OnStartup(StartupEventArgs e) + { + DISource.Resolver = Resolve; + base.OnStartup(e); + } + + protected override void OnInitialized() + { + (Container as IContainerExtension).RegisterDialog(); + base.OnInitialized(); + } + + + private object Resolve(Type type, string name) + { + if (type == null || Container is null) + { + return null; + } + + if (name != null) + { + return Container.Resolve(type, name); + } + + return Container.Resolve(type); + } +} diff --git a/Source/Store.Management.Framework/Store.Management.Framework.csproj b/Source/Store.Management.Framework/Store.Management.Framework.csproj new file mode 100644 index 0000000..21cc889 --- /dev/null +++ b/Source/Store.Management.Framework/Store.Management.Framework.csproj @@ -0,0 +1,20 @@ + + + + net10.0-windows + enable + true + enable + + + + + + + + + + + + + diff --git a/Source/Store.Management/App.xaml b/Source/Store.Management/App.xaml new file mode 100644 index 0000000..a0edb54 --- /dev/null +++ b/Source/Store.Management/App.xaml @@ -0,0 +1,9 @@ + + + diff --git a/Source/Store.Management/App.xaml.cs b/Source/Store.Management/App.xaml.cs new file mode 100644 index 0000000..b3f8bba --- /dev/null +++ b/Source/Store.Management/App.xaml.cs @@ -0,0 +1,29 @@ +using Prism.Ioc; +using Store.Management.Framework; +using System.Configuration; +using System.Data; +using System.Windows; + +namespace Store.Management +{ + /// + /// Interaction logic for App.xaml + /// + public partial class App : FrameworkApplication + { + protected override void OnStartup(StartupEventArgs e) + { + base.OnStartup(e); + } + protected override Window CreateShell() + { + throw new NotImplementedException(); + } + + protected override void RegisterTypes(IContainerRegistry containerRegistry) + { + + } + } + +} diff --git a/Source/Store.Management/AssemblyInfo.cs b/Source/Store.Management/AssemblyInfo.cs new file mode 100644 index 0000000..d9a1bde --- /dev/null +++ b/Source/Store.Management/AssemblyInfo.cs @@ -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) +)] diff --git a/Source/Store.Management/MainWindow.xaml b/Source/Store.Management/MainWindow.xaml new file mode 100644 index 0000000..b76693d --- /dev/null +++ b/Source/Store.Management/MainWindow.xaml @@ -0,0 +1,12 @@ + + + + + diff --git a/Source/Store.Management/MainWindow.xaml.cs b/Source/Store.Management/MainWindow.xaml.cs new file mode 100644 index 0000000..99902b3 --- /dev/null +++ b/Source/Store.Management/MainWindow.xaml.cs @@ -0,0 +1,24 @@ +using System.Text; +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 Store.Management +{ + /// + /// Interaction logic for MainWindow.xaml + /// + public partial class MainWindow : Window + { + public MainWindow() + { + InitializeComponent(); + } + } +} \ No newline at end of file diff --git a/Source/Store.Management/Store.Management.csproj b/Source/Store.Management/Store.Management.csproj new file mode 100644 index 0000000..ba6cccc --- /dev/null +++ b/Source/Store.Management/Store.Management.csproj @@ -0,0 +1,15 @@ + + + + WinExe + net10.0-windows + enable + enable + true + + + + + + + diff --git a/Store.Management.slnx b/Store.Management.slnx new file mode 100644 index 0000000..234bf1b --- /dev/null +++ b/Store.Management.slnx @@ -0,0 +1,11 @@ + + + + + + + + + + +