1 Core.NopException
using System.Runtime.Serialization;
namespace Core
{
/// <summary>
/// 【自定义异常--类】
/// <remarks>
/// 摘要:
/// 通过参数实例该类的构造方法调用其基类(“System.Exception”)构造方法,从而实现当前类的自定义异常功能。
/// 通过对“System.Exception”类的继承,从而
/// Serializable:
/// 要使一个类可序列化,最简单的方法是使用Serializable属性对它进行标记。
/// 序列化就是是将对象转换为容易传输的格式的过程,一般情况下转化数据流文件,放入内存或者IO文件中。例如,可以序列化一个对象,
/// 然后使用HTTP通过Internet在客户端和服务器之间传输该对象,或者和其它应用程序共享使用。反之,反序列化根据流重新构造对象。
/// </remarks>
/// </summary>
[Serializable]
public class NopException : Exception
{
#region 构造方法
/// <summary>
/// 【默认构造方法】
/// <remarks>
/// 摘要:
/// 直接通过其基类(“System.Exception”)构造方法,获取相应的异常信息实例。
/// </remarks>
/// </summary>
public NopException()
{
}
/// <param name="message">1个用于描述错误或异常的信息字符串。</param>
/// <summary>
/// 【拷贝构造方法】
/// <remarks>
/// 摘要:
/// 该拷贝构造方法,把参数实例传递到其基类(“System.Exception”)构造方法后,通过其基类构造方法,获取相应的异常信息实例。
/// </remarks>
/// </summary>
public NopException(string message) : base(message)
{
}
/// <param name="messageFormat">1个用于描述错误或异常的信息字符串。</param>
/// <param name="args">对象数组实例,该实例中存储着1/n个异常信息相关的实例。</param>
/// <summary>
/// 【拷贝构造方法】
/// <remarks>
/// 摘要:
/// 该拷贝构造方法,把参数实例传递到其基类(“System.Exception”)构造方法后,通过其基类构造方法,获取相应的异常信息实例。
/// </remarks>
/// </summary>
public NopException(string messageFormat, params object[] args) : base(string.Format(messageFormat, args))
{
}
/// <param name="info">序列化信息类的1个指定实例。</param>
/// <param name="context">流上下文类的1个指定实例。</param>
/// <summary>
/// 【拷贝构造方法】
/// <remarks>
/// 摘要:
/// 该拷贝构造方法,把参数实例传递到其基类(“System.Exception”)构造方法后,通过其基类构造方法,获取相应的异常信息实例。
/// </remarks>
/// </summary>
protected NopException(SerializationInfo info, StreamingContext context) : base(info, context)
{
}
/// <param name="message">1个用于描述错误或异常的信息字符串。</param>
/// <param name="innerException">导致当前异常实例的异常实例,或与当前异常实例相关的异常实例(也叫内联/级联异常)。</param>
/// <summary>
/// 【拷贝构造方法】
/// <remarks>
/// 摘要:
/// 该拷贝构造方法,把参数实例传递到其基类(“System.Exception”)构造方法后,通过其基类构造方法,获取相应的异常信息实例。
/// </remarks>
/// </summary>
public NopException(string message, Exception innerException) : base(message, innerException)
{
}
#endregion
}
}
2 Core.SecureRandomNumberGenerator
using System.Security.Cryptography;
namespace Core
{
/// <summary>
/// 【安全随机数生成器--类】
/// <remarks>
/// 摘要:
/// 通过该类中的方法成员生成1个高质量、高安全性的(整型或双精度的)随机数。
/// </remarks>
/// </summary>
public class SecureRandomNumberGenerator : RandomNumberGenerator
{
#region 变量--私有/保护
/// <summary>
/// 【已经销毁?】
/// <remarks>
/// 摘要:
/// 设置1个值false(默认值:未销毁)/true(已经销毁),该值指示当前类的实例(非托管资源)是否已经被操作系统标记为:“已经销毁”状态;或已经被操作系统所销毁。
/// 说明:
/// 在当前类的定义中特指:随机数生成器(RandomNumberGenerator--RNG)类的1个指定实例(非托管资源)是否已经被操作系统标记为:“已经销毁”状态;或已经被操作系统所销毁。
/// </remarks>
/// </summary>
private bool _disposed = false;
/// <summary>
/// 【随机数发生器】
/// <remarks>
/// 摘要:
/// 设置随机数生成器(RandomNumberGenerator--RNG)类的1个指定实例,通过Random方法所生成的随机数的安全性并不高;
/// 而RandomNumberGenerator是一种密码强度的随机数生成器,所以通过RandomNumberGenerator实例会生成1个高质量、高安全性的随机数。
/// </remarks>
/// </summary>
private readonly RandomNumberGenerator _rng;
#endregion
#region 拷贝构造方法
/// <summary>
/// 【拷贝构造方法】
/// <remarks>
/// 摘要:
/// 该拷贝构造方法,调用其基类中的成员方法获取随机数生成器(RandomNumberGenerator--RNG)类的1个指定实例。
/// </remarks>
/// </summary>
public SecureRandomNumberGenerator()
{
_rng = Create();
}
#endregion
#region 方法--销毁
/// <summary>
/// 【销毁】
/// <remarks>
/// 摘要:
/// 通过显式调用当前方法把当前类的实例被操作系统标(非托管资源)标记为:“已经销毁”状态;或已经被操作系统所销毁。
/// </remarks>
/// </summary>
public new void Dispose()
{
Dispose(true);
// SuppressFinalize:当开发者已经显式调用当前方法(Dispose或者Close),通过操作系统已经把当前类的实例(非托管资源)标记为:“已经销毁”状态时,
// 如果开发者重复通过显式调用当前方法来销毁当前类的实例(非托管资源)时,由于当前类的实例(非托管资源)已经处于“已经销毁”状态,或已经被销毁,
// 因而需要“ SuppressFinalize”强制通过终止执行当前类的析构方法来避免当前类的实例(非托管资源)再1次的销毁操作,从而避免未知异常的产生。
GC.SuppressFinalize(this);
}
/// <param name="disposing">指示当前类的实例(非托管资源)是否需要执行销毁操作,默认值:true,即执行销毁操作。</param>
/// <summary>
/// 【销毁】
/// <remarks>
/// 摘要:
/// 为当前类的实例(非托管资源)被操作系统标(非托管资源)标记为:“已经销毁”状态;或已经被操作系统所销毁,提供方法支撑。
/// </remarks>
/// </summary>
protected override void Dispose(bool disposing)
{
if (_disposed)
return;
if (disposing)
{
//显式的把随机数生成器(RandomNumberGenerator--RNG)类的1个指定实例(非托管资源)标记为:“已经销毁”状态。
_rng?.Dispose();
}
_disposed = true;
}
#endregion
#region 方法--覆写
/// <summary>
/// 【获取字节】
/// <param name="data">字节数组的1个指定实例。</param>
/// <remarks>
/// 摘要:
/// 通过随机数生成器(RandomNumberGenerator--RNG)实例,把字节数组的1个指定实例中的16进制数据(这些16进制数据中可以包含0值),按照从后-->前的索引顺序把这些数据进行排列。
/// </remarks>
/// </summary>
public override void GetBytes(byte[] data)
{
_rng.GetBytes(data);
}
/// <summary>
/// 【获取非零字节】
/// <param name="data">字节数组的1个指定实例。</param>
/// <remarks>
/// 摘要:
/// 通过随机数生成器(RandomNumberGenerator--RNG)实例,把字节数组的1个指定实例中的16进制数据(这些16进制数据中不能包含0值),按照从后-->前的索引顺序把这些数据进行排列。
/// </remarks>
/// </summary>
public override void GetNonZeroBytes(byte[] data)
{
_rng.GetNonZeroBytes(data);
}
#endregion
#region 方法
/// <summary>
/// 【下一个】
/// <remarks>
/// 摘要:
/// 通过该方法获取1个整型的随机数。
/// </remarks>
/// <returns>
/// 返回:
/// 1个整型的随机数。
/// </returns>
/// </summary>
public int Next()
{
var data = new byte[sizeof(int)];
//对于字节数组的1个指定实例,按照从后-->前的索引顺序把这些数据进行排列。
_rng.GetBytes(data);
//把经过通过排序后的字节数组的1个指定实例进行按位于操作,获取1个整型的随机数。
return BitConverter.ToInt32(data, 0) & (int.MaxValue - 1);
}
/// <param name="maxValue">1个最大的整型的数据值。</param>
/// <summary>
/// 【下一个】
/// <remarks>
/// 摘要:
/// 通过该方法获取1个整型的随机数。
/// </remarks>
/// <returns>
/// 返回:
/// 1个整型的随机数。
/// </returns>
/// </summary>
public int Next(int maxValue)
{
//最小整型的数据值被设定为:0。
return Next(0, maxValue);
}
/// <param name="minValue">1个最小的整型的数据值。</param>
/// <param name="maxValue">1个最大的整型的数据值。</param>
/// <summary>
/// 【下一个】
/// <remarks>
/// 摘要:
/// 通过该方法获取1个整型的随机数。
/// </remarks>
/// <returns>
/// 返回:
/// 1个整型的随机数。
/// </returns>
/// </summary>
public int Next(int minValue, int maxValue)
{
//如果参数的最小值大于最大则值抛出异常。
if (minValue > maxValue)
{
throw new ArgumentOutOfRangeException();
}
//通过下行操作,获取小于或等于下行操作中的最大整数值。
return (int)Math.Floor(minValue + ((double)maxValue - minValue) * NextDouble());
}
/// <summary>
/// 【下一个双精度】
/// <remarks>
/// 摘要:
/// 通过该方法获取1个双精度浮点随机数。
/// </remarks>
/// <returns>
/// 返回:
/// 1个双精度浮点随机数。
/// </returns>
/// </summary>
public double NextDouble()
{
var data = new byte[sizeof(uint)];
//对于字节数组的1个指定实例,按照从后-->前的索引顺序把这些数据进行排列。
_rng.GetBytes(data);
//把经过通过排序后的字节数组的1个指定实例转换为无符号整型数。
var randUint = BitConverter.ToUInt32(data, 0);
//对无符号整型数进行除法操作,获取1个双精度浮点随机数。
return randUint / (uint.MaxValue + 1.0);
}
#endregion
}
}
3 Core.Infrastructure.BaseSingleton
namespace Core.Infrastructure
{
/// <summary>
/// 【基于单例--类】
/// <remarks>
/// 摘要:
/// 该类通过该静态拷贝构造方法,实例化该类中的字典属性成员的实例(即为该字典属性成员实例分配内存空间),该字典实例以键/值对的形式存储1个类的类型实例及其(单例的)对象实例。
/// </remarks>
/// </summary>
public class BaseSingleton
{
#region 静态拷贝构造方法
/// <summary>
/// 【静态拷贝构造方法】
/// <remarks>
/// 摘要:
/// 通过该静态拷贝构造方法,实例化该类中的字典属性成员的实例(即为该字典属性成员实例分配内存空间)。
/// 静态构造方法:
/// 1、静态构造方法要求没有访问修饰符,也没有参数,
/// 在C#中如果一个类的成员没有定义访问修饰符,则该成员访问权限被隐式(默认)的定义为:private
/// 2、private权限的构造方法和单例模式要求类型的实例化不能通过new关键字来获取,
/// 该类通过该类公有方法或者属性来定义该类的实例(即该类实例操作只能在该类中定义,其它类调用该类的实例只能从该类的公有方法或者属性获取)。
/// 静态构造函数具有以下属性:
/// 1、静态构造函数不使用访问修饰符或不具有参数。
/// 2、在创建第一个实例或引用任何静态成员之前,将自动调用静态构造函数以初始化类。
/// 3、不能直接调用静态构造函数。
/// 4、用户无法控制在程序中执行静态构造函数的时间。
/// 5、静态构造函数的一种典型用法是在类使用日志文件且将构造函数用于将条目写入到此文件中时使用。
/// 6、静态构造函数对于创建非托管代码的包装类也非常有用,这种情况下构造函数可调用 LoadLibrary 方法。
/// 7、如果静态构造函数引发异常,运行时将不会再次调用该函数,并且类型在程序运行所在的应用程序域的生存期内将保持未初始化。
/// 8、线程安全:关于线程安全需要特别说明一下,由于程序可能在多线程环境下运行,也就是可能出现同时多个线程预处理执行静态构造函数的情况。CLR确保这个过程是安全的,
/// 实际上调用静态构造函数的线程需要先获得一个互斥线程同步锁,如果有多个线程试图执行类型的静态构造函数,只有一个线程能获得该锁;获得锁的线程完成初始类型初始化操作,
/// 其它线程只能等待;当初始化完成,等待的线程被唤醒,然后发现静态构造函数已经被执行过,就不会再执行。
/// </remarks>
/// </summary>
static BaseSingleton()
{
AllSingletons = new Dictionary<Type, object>();
}
#endregion
#region 属性
/// <summary>
/// 【所有单例集】
/// <remarks>
/// 摘要:
/// 1个字典实例,该实例以键/值对的形式存储1个指定类的类型实例及其(单例的)对象实例。
/// 说明:
/// 1个指定类,在整个程序中只能有1个与实例化相关的定义,即存储在该字典中的1个指定类的实例,在整个程序中能且只能被实例化1次。
/// </remarks>
/// </summary>
public static IDictionary<Type, object> AllSingletons { get; }
#endregion
}
}
4 Core.Infrastructure.Singleton
namespace Core.Infrastructure
{
/// <typeparam name = "T"> 泛型类型实例(1个指定类的类型实例)。</typeparam>
/// <summary>
/// 【单例--类】
/// <remarks>
/// 摘要:
/// 以泛型形式,通过该类中的属性成员,把1个类的类型实例及其(单例的)对象实例以键/值对的形式存储到基类的字典属性成员实例中。
/// 单例模式介绍:
/// 意图:
/// 保证一个类仅有一个实例,并提供一个访问它的全局访问点。
/// 主要解决:
/// 一个全局使用的类频繁地创建与销毁。
/// 何时使用:
/// 当您想控制实例数目,节省系统资源的时候。
/// 如何解决:
/// 判断系统是否已经有这个单例,如果有则返回,如果没有则创建。
/// 关键代码:
/// 构造函数是私有的。
/// 应用实例:
/// 1、一个班级只有一个班主任。
/// 2、Windows 是多进程多线程的,在操作一个文件的时候,就不可避免地出现多个进程或线程同时操作一个文件的现象,所以所有文件的处理必须通过唯一的实例来进行。
/// 3、一些设备管理器常常设计为单例模式,比如一个电脑有两台打印机,在输出的时候就要处理不能两台打印机打印同一个文件。
/// 优点:
/// 1、在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例(比如管理学院首页页面缓存)。
/// 2、避免对资源的多重占用(比如写文件操作)。
/// 缺点:
/// 没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。
/// 使用场景:
/// 1、要求生产唯一序列号。
/// 2、WEB 中的计数器,不用每次刷新都在数据库里加一次,用单例先缓存起来。
/// 3、创建的一个对象需要消耗的资源过多,比如 I/O 与数据库的连接等。
/// </remarks>
/// </summary>
public class Singleton<T> : BaseSingleton
{
#region 变量--私有/保护
/// <summary>
/// 【实例】
/// <remarks>
/// 摘要:
/// 以泛型形式,设置1个指定类的1个指定的(单例的)对象实例。
/// </remarks>
/// </summary>
private static T instance;
#endregion
#region 属性
/// <summary>
/// 【实例】
/// <remarks>
/// 摘要:
/// 以泛型形式,获取/设置1个指定类的1个指定的(单例的)对象实例,并把1个类的类型实例及其(单例的)对象实例以键/值对的形式存储到基类的字典属性成员实例中。
/// </remarks>
/// </summary>
public static T Instance
{
get => instance;
set
{
instance = value;
AllSingletons[typeof(T)] = value;
}
}
#endregion
}
}
5 Core.Infrastructure.INopFileProvider
using System.Text;
using System.Security.AccessControl;
using Microsoft.Extensions.FileProviders;
namespace Core.Infrastructure
{
/// <summary>
/// 【自定义文件提供程序--接口】
/// <remarks>
/// 摘要:
/// 继承于该接口的具体实现类中的方法成员通过对其基类成员方法的扩展,使该类中的方法成员拥有了对服务器启动项中的目录/文件进行读、写、删除和移动等操作功能。
/// </remarks>
/// </summary>
public interface INopFileProvider : IFileProvider
{
#region 属性
/// <summary>
/// 【Web根路径】
/// <remarks>
/// 摘要:
/// 获取程序启动项中“wwwroot”文件夹,在服务器端磁盘中位置绝对路径字符串(例如:...\WebApi\wwwroot)。
/// 说明:
/// 如果启动项是API模式,启动项默认是不包含“wwwroot”文件夹的,所以该属性成员实例在API模式中的默认值为:null;
/// 如果启动项是API模式必须先在启动项中新建“wwwroot”文件夹,才能保证该属性成员实例的默认值不为:null。
/// </remarks>
/// </summary>
string WebRootPath { get; }
#endregion
#region 方法
/// <param name="paths">数组实例,该实例中存储着用于拼接操作的n个路径字符串。</param>
/// <summary>
/// 【拼接】
/// <remarks>
/// 摘要:
/// 拼接出本地路径形式的1个路径字符串(例如:C:\Program Files (x86)\Microsoft Visual Studio\2019)。
/// </remarks>
/// <returns>
/// 返回:
/// 本地路径形式的1个路径字符串(例如:C:\Program Files (x86)\Microsoft Visual Studio\2019)。
/// </returns>
/// </summary>
string Combine(params string[] paths);
/// <param name="path">1个指定的路径字符串,该字符串包含将要被新建目录及其子目录。</param>
/// <summary>
/// 【新建目录】
/// <remarks>
/// 摘要:
/// 在服务器物理磁盘的指定位置上新建1个指定的目录及其子目录(文件夹及其子文件夹),除非它们已经存在于服务器物理磁盘的指定位置上。
/// </remarks>
/// </summary>
void CreateDirectory(string path);
/// <summary>
/// <summary>
/// 【新建文件】
/// <param name="path">1个指定的路径字符串(带有文件的扩展名),该字符串包含将要被新建的文件。</param>
/// <remarks>
/// 摘要:
/// 在服务器物理磁盘的指定位置上新建1个指定的文件(带有扩展名的文件),除非该文件已经存在于服务器物理磁盘的指定位置上。
/// </remarks>
/// </summary>
void CreateFile(string path);
/// <param name="path">1个指定的路径字符串,该字符串包含将要被删除的目录及其子目录。</param>
/// <summary>
/// 【删除目录】
/// <remarks>
/// 摘要:
/// 在服务器物理磁盘的指定位置上,通过递归操作删除1个指定的目录及其子目录(文件夹及其子文件夹,还有这些文件夹中的所有文件)。
/// </remarks>
/// </summary>
void DeleteDirectory(string path);
/// <param name="path">1个指定的路径字符串(带有文件的扩展名),该字符串包含将要被删除的文件。</param>
/// <summary>
/// 【删除文件】
/// <remarks>
/// 摘要:
/// 在服务器物理磁盘的指定位置上删除1个指定的文件(带有扩展名的文件),除非该文件已经删除或不存在。
/// </remarks>
/// </summary>
void DeleteFile(string filePath);
/// <param name="path">1个指定目录的路径字符串</param>
/// <summary>
/// 【目录存在?】
/// <remarks>
/// 摘要:
/// 获取1个值false1个值false(不存在1个指定的目录(文件夹))/true(存在1个指定的目录(文件夹)),该值指示在服务器物理磁盘的指定位置上是否存在1个指定的目录(文件夹)。
/// <returns>
/// 返回:
/// 1个值false(不存在1个指定的目录(文件夹))/true(存在1个指定的目录(文件夹))。
/// </returns>
/// </summary>
bool DirectoryExists(string path);
/// <param name="sourceDirName">1个指定的路径字符串,该字符串包含1个将要被移动的目录(文件夹)。</param>
/// <param name="destDirName">1个指定的路径字符串,该字符串包含1个目标目录(移动到的目录及其子目录)。</param>
/// <summary>
/// 【目录移动】
/// <remarks>
/// 摘要:
/// 在服务器物理磁盘中,把1个源目录(被移动的目录及其子目录)移动到指定的目标目录中。
/// </remarks>
/// </summary>
void DirectoryMove(string sourceDirName, string destDirName);
/// <param name="directoryPath">1个指定的路径字符串,该字符串包含一个指定目录(文件夹)。</param>
/// <param name="searchPattern">
/// 1个指定的搜索模式字符串,该字符串参数实例中,可以包含有效的文字路径和通配符(*和?)等字符的组合,但不支持正则表达式,
/// 通过该搜索模式字符串,可以从服务器物理磁盘的相应位置指定目录中,依次的获取符合该搜索模式字符串规则的所有文件的路径字符串。
/// </param>
/// <param name="topDirectoryOnly">
/// 指示只搜索服务器中物理磁盘的相应位置的当前目录,还是搜索当前目录及其所有子目录,默认值为:true,即搜索当前目录及其所有子目录。
/// </param>
/// <summary>
/// 【获取文件集】
/// <remarks>
/// 摘要:
/// 把指定目录(文件夹)及其子目录中,与指定搜索模式字符串相匹配的所有文件的路径字符串存储到一个枚举数实例中。
/// </remarks>
/// <returns>
/// 返回:
/// 1个枚举数实例,该实例存储着与指定搜索模式字符串相匹配的所有文件的路径字符串。
/// </returns>
/// </summary>
IEnumerable<string> EnumerateFiles(string directoryPath, string searchPattern, bool topDirectoryOnly = true);
/// <param name="sourceFileName">1个指定的路径字符串,该字符串包含1个将要被复制的文件。</param>
/// <param name="destFileName">1个指定的路径字符串,该字符串包含1个将要被复制到的文件(被复制的文件作为其子文件)。</param>
/// <param name="overwrite">指示在文件复制过程中是否可以覆盖同名的目标文件,默认值为:false,即不可以覆盖同名的目标文件。</param>
/// <summary>
/// 【文件复制】
/// <remarks>
/// 摘要:
/// 把将要被复制的文件复制到服务器中物理磁盘的相应位置上;如果指定位置上已经有同名文件,在默认状态下不能对同名文件执行覆盖操作。
/// </remarks>
/// </summary>
void FileCopy(string sourceFileName, string destFileName, bool overwrite = false);
/// <param name="path">1个指定文件(包括扩展名)的路径字符串</param>
/// <summary>
/// 【文件存在?】
/// <remarks>
/// 摘要:
/// 获取1个值false1个值false(不存在1个指定的文件(包括扩展名))/true(存在1个指定的文件(包括扩展名)),该值指示在服务器物理磁盘的指定位置上是否存在1个指定的文件(包括扩展名)。
/// <returns>
/// 返回:
/// 1个值false1个值false(不存在1个指定的文件(包括扩展名))/true(存在1个指定的文件(包括扩展名))。
/// </returns>
/// </summary>
bool FileExists(string filePath);
/// <param name="path">1个指定文件(包括扩展名)的路径字符串</param>
/// <summary>
/// 【文件大小】
/// <remarks>
/// 摘要:
/// 获取1个指定文件(包括扩展名)大小的数据值(以字节为单位),如果文件不存在则直接返回数据值:-1。
/// </remarks>
/// <returns>
/// 返回:
/// 1个指定文件(包括扩展名)大小的数据值(以字节为单位),如果文件不存在则直接返回数据值:-1。
/// </returns>
/// </summary>
long FileLength(string path);
/// <param name="sourceFileName">1个指定的路径字符串,该字符串包含1个将要被移动的文件(包括扩展名)。</param>
/// <param name="destDirName">1个指定的路径字符串,该字符串包含目标文件(包括扩展名,移动到的文件)。</param>
/// <summary>
/// 【文件移动】
/// <remarks>
/// 摘要:
/// 在服务器物理磁盘中,把1个源文件(被移动的文件)移动到指定的目标文件中。
/// </remarks>
/// </summary>
void FileMove(string sourceFileName, string destFileName);
/// <param name="paths">数组实例,该实例中存储着用于拼接操作的n个路径字符串。</param>
/// <summary>
/// 【获得绝对路径】
/// <remarks>
/// 摘要:
/// 拼接出1个以“wwwroot”开头的路径字符串。
/// </remarks>
/// <returns>
/// 返回:
/// 1个以“wwwroot”开头的路径字符串。
/// </returns>
/// </summary>
string GetAbsolutePath(params string[] paths);
/// <param name="path">1个指定的路径字符串,该字符串包含1个指定的目录/文件。</param>
/// <summary>
/// 【获取访问控制】
/// <remarks>
/// 摘要:
/// 通过服务器的操作系统,可以远程的获取物理磁盘相应位置上,
/// 指定目录/文件的访问控制和安全审核权限(他们由访问控制列表(ACL)项控件,
/// 以便于对服务器中的指定目录/文件进行读、写、删和移动等后续操作。
/// [SupportedOSPlatform("windows")]:--最近定义于nopCommerce_4.40
/// 该特性标记用限定平台或操作系统支持 API。如果指定了一个版本,则无法从较早的版本调用该API方法。
/// 可以应用多个特性标记来表示对多个操作系统的支持。在这里该特性标记用来限定,
/// 该方法只能用于获取"windows"操作系统中指定目录访问控制和安全审核权限的相关数据信息(即启动项被部署的操作系统/平台必须是"windows"操作系统)。
/// </remarks>
/// <returns>
/// 返回:
/// 1个指定目录/文件的访问控制和安全审核权限实例。
/// </returns>
/// </summary>
DirectorySecurity GetAccessControl(string path);
/// <param name="path">1个指定的路径字符串,该字符串包含1个指定的目录/文件。</param>
/// <summary>
/// 【获取新建时间】
/// <remarks>
/// 摘要:
/// 获取1指定目录/文件被新建时的日期和时间。
/// </remarks>
/// <returns>
/// 返回:
/// 1指定目录/文件被新建时的日期和时间。
/// </returns>
/// </summary>
DateTime GetCreationTime(string path);
/// <param name="path">1个指定的路径字符串,该字符串包含1个指定目录(文件夹)。</param>
/// <param name="searchPattern">
/// 1个指定的搜索模式字符串,该字符串参数实例中,可以包含有效的文字路径和通配符(*和?)等字符的组合,但不支持正则表达式,
/// 通过该搜索模式字符串,可以从服务器物理磁盘的相应位置指定目录中,依次的获取符合该搜索模式字符串规则的所有文件的路径字符串。
/// </param>
/// <param name="topDirectoryOnly">
/// 指示只搜索服务器中物理磁盘的相应位置的当前目录,还是搜索当前目录及其所有子目录,默认值为:true,即搜索当前目录及其所有子目录。
/// </param>
/// <summary>
/// 【获取目录集】
/// <remarks>
/// 摘要:
/// 把1个指定目录(文件夹)及其子目录中,与指定搜索模式字符串相匹配的所有目录(文件夹)的路径字符串存储到一个数组实例中。
/// </remarks>
/// <returns>
/// 返回:
/// 1个数组实例,该实例存储着1个指定目录(文件夹)中与指定搜索模式字符串相匹配的所有目录(文件夹)的路径字符串。
/// </returns>
/// </summary>
string[] GetDirectories(string path, string searchPattern = "", bool topDirectoryOnly = true);
/// <param name="paths">1个指定的路径字符串,该字符串包含1个指定的目录(文件夹)。</param>
/// <summary>
/// 【获取目录路径】
/// <remarks>
/// 摘要:
/// 通过截取操作,获取1个指定的目录(文件夹)的绝对路径字符串(该路径字符串最后不包含“\”号)
/// </remarks>
/// <returns>
/// 返回:
/// 1个指定的目录(文件夹)的绝对路径字符串(该路径字符串最后不包含“\”号)。
/// </returns>
/// </summary>
string GetDirectoryName(string path);
/// <param name="paths">1个指定的路径字符串,该字符串包含1个指定的目录(文件夹)。</param>
/// <summary>
/// 【获取目录名称】
/// <remarks>
/// 摘要:
/// 获取1个指定的目录(文件夹)的名称。
/// </remarks>
/// <returns>
/// 返回:
/// 1个指定的目录(文件夹)的名称。
/// </returns>
/// </summary>
string GetDirectoryNameOnly(string path);
/// <param name="path">1个指定文件(包括扩展名)的路径字符串。</param>
/// <summary>
/// 【获取文件扩展名】
/// <remarks>
/// 摘要:
/// 获取1个指定文件的扩展名(包括句号“.”)。
/// </remarks>
/// <returns>
/// 返回:
/// 1个指定文件的扩展名(包括句号“.”)。
/// </returns>
/// </summary>
string GetFileExtension(string filePath);
/// <param name="path">1个指定文件(包括扩展名)的路径字符串。</param>
/// <summary>
/// 【获取文件名】
/// <remarks>
/// 摘要:
/// 获取1个指定文件的名称(包括其扩展名)。
/// </remarks>
/// <returns>
/// 返回:
/// 1个指定文件的名称(包括其扩展名)。
/// </returns>
/// </summary>
string GetFileName(string path);
/// <param name="path">1个指定文件(包括扩展名)的路径字符串。</param>
/// <summary>
/// 【获取文件名不包括扩展名】
/// <remarks>
/// 摘要:
/// 获取1个指定文件的名称(不包括其扩展名,即不包括“.”及其后面的所有字符)。
/// <returns>
/// 返回:
/// 1个指定文件的名称(不包括其扩展名,即不包括“.”及其后面的所有字符)。
/// </returns>
/// </summary>
string GetFileNameWithoutExtension(string filePath);
/// <param name="path">1个指定的路径字符串,该字符串包含1个指定目录(文件夹)。</param>
/// <param name="searchPattern">
/// 1个指定的搜索模式字符串,该字符串参数实例中,可以包含有效的文字路径和通配符(*和?)等字符的组合,但不支持正则表达式,
/// 通过该搜索模式字符串,可以从服务器物理磁盘的相应位置指定目录中,依次的获取符合该搜索模式字符串规则的所有文件的路径字符串。
/// </param>
/// <param name="topDirectoryOnly">
/// 指示只搜索服务器中物理磁盘的相应位置的当前目录,还是搜索当前目录及其所有子目录,默认值为:true,即搜索当前目录及其所有子目录。
/// </param>
/// <summary>
/// 【获取文件集】
/// <remarks>
/// 摘要:
/// 把1个指定目录(文件夹)及其子目录中,与指定搜索模式字符串相匹配的所有文件的路径字符串存储到一个数组实例中。
/// </remarks>
/// <returns>
/// 返回:
/// 1个数组实例,该实例存储着1个指定目录(文件夹)中与指定搜索模式字符串相匹配的所有文件的路径字符串。
/// </returns>
/// </summary>
string[] GetFiles(string directoryPath, string searchPattern = "", bool topDirectoryOnly = true);
/// <param name="path">1个指定的路径字符串,该字符串包含1个指定的文件。</param>
/// <summary>
/// 【获取最后访问时间】
/// <remarks>
/// 摘要:
/// 获取1指定文件的内容最后1次被打开后的日期和时间。
/// </remarks>
/// <returns>
/// 返回:
/// 1指定文件的内容最后1次被打开后的日期和时间。
/// </returns>
/// </summary>
DateTime GetLastAccessTime(string path);
/// <param name="path">1个指定的路径字符串,该字符串包含1个指定的文件。</param>
/// <summary>
/// 【获取最后写入时间】
/// <remarks>
/// 摘要:
/// 获取1指定文件的内容最后1次被修改后的日期和时间。
/// </remarks>
/// <returns>
/// 返回:
/// 1指定文件的内容最后1次被修改后的日期和时间。
/// </returns>
/// </summary>
DateTime GetLastWriteTime(string path);
/// <param name="path">1个指定的路径字符串,该字符串包含1个指定的文件。</param>
/// <summary>
/// 【获取最后写入UTC时间】
/// <remarks>
/// 摘要:
/// 获取1指定文件的内容最后1次被修改后的国际标准时间值(UTC),即该时间值+8小时,即为本地时间。
/// </remarks>
/// <returns>
/// 返回:
/// 1指定文件的内容最后1次被修改后的国际标准时间值(UTC)。
/// </returns>
/// </summary>
DateTime GetLastWriteTimeUtc(string path);
/// <param name="path">1个指定的路径字符串,该字符串包含1个指定的文件。</param>
/// <summary>
/// 【获取或新建文件】
/// <remarks>
/// 摘要:
/// 获取1个指定文件的内容信息(如果该没有该文件,则把新建该件及其把内容信息持久化到该文件中),并把该内容信息以文件流的形式进行存储(为以后的网络传输作好预处理工作)。
/// </remarks>
/// <returns>
/// 返回:
/// 文件流实例(为以后的网络传输作好预处理工作),该实例存储着1个指定文件的内容信息。
/// </returns>
/// </summary>
FileStream GetOrCreateFile(string path);
/// <param name="paths">1个指定的路径字符串,该字符串包含1个指定的目录(文件夹)。</param>
/// <summary>
/// 【获取父目录路径】
/// <remarks>
/// 摘要:
/// 获取1个指定目录的上1级目录的路径字符串(字符串最后不包含“\”号)。
/// </remarks>
/// <returns>
/// 返回:
/// 上1级目录的路径字符串(该字符串最后不包含“\”号)。
/// </returns>
/// </summary>
string GetParentDirectory(string directoryPath);
/// <param name="path">1个指定的路径字符串,该字符串包含1个指定目录/文件。</param>
/// <summary>
/// 【获取虚拟(相对)路径】
/// <remarks>
/// 摘要:
/// 把1个本地格式的路径字符串转换为与之相对应的网络格式的路径字符串(例如: @"C:\inetpub\wwwroot\bin"--->“~/bin”)。
/// 例如:
/// </remarks>
/// <returns>
/// 返回:
/// 1个网络格式的路径字符串或空字符串,
/// </returns>
/// </summary>
string GetVirtualPath(string path);
/// <param name="paths">1个指定的路径字符串,该字符串包含1个指定的目录(文件夹)。</param>
/// <summary>
/// 【目录存在?】
/// <remarks>
/// 摘要:
/// 获取1个值false1个值false(不存在1个指定的目录(文件夹))/true(存在1个指定的目录(文件夹))),该值指示在服务器物理磁盘的指定位置上是否存在1个指定的目录(文件夹)。
/// <returns>
/// 返回:
/// 1个值false1个值false(不存在1个指定的目录(文件夹))/true(存在1个指定的目录(文件夹))。
/// </returns>
/// </summary>
bool IsDirectory(string path);
/// <param name="path">1个指定的路径字符串,该字符串包含1个指定目录/文件。</param>
/// <summary>
/// 【映射路径】
/// <remarks>
/// 摘要:
///
/// 把1个网络格式的路径字符串转换为与之相对应的本地格式的路径字符串(例如:“~/bin”---> "C:\inetpub\wwwroot\bin")。
/// 例如:
/// </remarks>
/// <returns>
/// 返回:
/// 1个网络格式的路径字符串或空字符串,
/// </returns>
/// </summary>
string MapPath(string path);
/// <param name="path">1个指定的路径字符串,该字符串包含1个指定的文件(包括扩展名)。</param>
/// <summary>
/// 【异步读所有字节】
/// <remarks>
/// 摘要:
/// 获取1指定文件中的所有内容数据,以字节的编码格式存储到数组实例中。
/// </remarks>
/// <returns>
/// 返回:
/// 字节数组实例,该实例以字节的编码格式存储着1个指定文件中的所有内容数据。
/// </returns>
/// </summary>
Task<byte[]> ReadAllBytesAsync(string filePath);
/// <param name="path">1个指定的路径字符串,该字符串包含1个指定的文件(包括扩展名)。</param>
/// <param name="encoding">1个编码格式实例(常见的编号格式有:简体中文码:GB2312、繁体中文码:BIG5、西欧字符:UTF-8等),按照该参数实例对文件中的所有内容进行读取操作。</param>
/// <summary>
/// 【异步读所有文本】
/// <remarks>
/// 摘要:
/// 获取1指定文件中的所有内容数据,以指定的编码格式存储到到字符串实例中。
/// </remarks>
/// <returns>
/// 返回:
/// 字符串实例,该字符串实例以指定编码格式存储着1指定文件中的所有内容数据。
/// </returns>
/// </summary>
Task<string> ReadAllTextAsync(string path, Encoding encoding);
/// <param name="path">1个指定的路径字符串,该字符串包含1个指定的文件(包括扩展名)。</param>
/// <param name="encoding">1个编码格式实例(常见的编号格式有:简体中文码:GB2312、繁体中文码:BIG5、西欧字符:UTF-8等),按照该参数实例对文件中的所有内容进行读取操作。</param>
/// <summary>
/// 【读所有文本】
/// <remarks>
/// 摘要:
/// 获取1指定文件中的所有内容数据,以指定的编码格式存储到到字符串实例中。
/// </remarks>
/// <returns>
/// 返回:
/// 字符串实例,该字符串实例以指定编码格式存储着1指定文件中的所有内容数据。
/// </returns>
/// </summary>
string ReadAllText(string path, Encoding encoding);
/// <param name="path">1个指定的路径字符串,该字符串包含1个指定的文件(包括扩展名)。</param>
/// <param name="bytes">1个指定的字节数组实例,该实例以字节的编码格式存储着的一些内容数据。</param>
/// <summary>
/// 【异步写入所有字节】
/// <remarks>
/// 摘要:
/// 把 1个指定的字节数组实例,持久化保存到1个指定的文件(包括扩展名)中,
/// 如果文件中已经有其它的内容存在,则使用新的内容对把该文件中旧的内容进行覆盖,最后关闭该文件的写入操作。
/// 注意:
/// 如果指定持久化写入的文件不存在,该方法会先在服务器端相应磁盘位置上自动新建该文件,然后再把内容持久化写入到该文件中。
/// </remarks>
/// </summary>
Task WriteAllBytesAsync(string filePath, byte[] bytes);
/// <param name="path">1个指定的路径字符串,该字符串包含1个指定的文件(包括扩展名)。</param>
/// <param name="contents">将要被写入的内容字符串。</param>
/// <param name="encoding">1个编码格式实例(常见的编号格式有:简体中文码:GB2312、繁体中文码:BIG5、西欧字符:UTF-8等),按照该参数实例对文件中的所有内容进行读取操作。</param>
/// <summary>
/// 【异步写入所有文本】
/// <remarks>
/// 摘要:
/// 把1字符串实例,持久化保存到1个指定的文件(包括扩展名)中,
/// 如果文件中已经有其它的内容存在,则使用新的内容对把该文件中旧的内容进行覆盖,最后关闭该文件的写入操作。
/// 注意:
/// 如果指定持久化写入的文件不存在,该方法会先在服务器端相应磁盘位置上自动新建该文件,然后再把内容持久化写入到该文件中。
/// </remarks>
/// </summary>
Task WriteAllTextAsync(string path, string contents, Encoding encoding);
/// <param name="path">1个指定的路径字符串,该字符串包含1个指定的文件(包括扩展名)。</param>
/// <param name="contents">将要被写入的内容字符串。</param>
/// <param name="encoding">1个编码格式实例(常见的编号格式有:简体中文码:GB2312、繁体中文码:BIG5、西欧字符:UTF-8等),按照该参数实例对文件中的所有内容进行读取操作。</param>
/// <summary>
/// 【写入所有文本】
/// <remarks>
/// 摘要:
/// 把1字符串实例,持久化保存到1个指定的文件(包括扩展名)中,
/// 如果文件中已经有其它的内容存在,则使用新的内容对把该文件中旧的内容进行覆盖,最后关闭该文件的写入操作。
/// 注意:
/// 如果指定持久化写入的文件不存在,该方法会先在服务器端相应磁盘位置上自动新建该文件,然后再把内容持久化写入到该文件中。
/// </remarks>
/// </summary>
void WriteAllText(string path, string contents, Encoding encoding);
#endregion
}
}
6 Core.Infrastructure.NopFileProvider
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.FileProviders;
using System.Runtime.Versioning;
using System.Security.AccessControl;
using System.Text;
namespace Core.Infrastructure
{
/// <summary>
/// 【自定义文件提供程序--类】
/// <remarks>
/// 摘要:
/// 该类方法成员通过对其基类成员方法的扩展,使该类中的方法成员拥有了对服务器启动项中的目录/文件进行读、写、删除和移动等操作功能。
/// </remarks>
/// </summary>
public class NopFileProvider : PhysicalFileProvider, INopFileProvider
{
#region 拷贝构造方法
///<param name="webHostEnvironment">.Net(Core)框架内置主机环境接口实例。</param>
/// <summary>
/// 【拷贝构造方法】
/// <remarks>
/// 摘要:
/// 通过该拷贝构造方法获取程序启动项中“wwwroot”文件夹,在服务器端磁盘中位置绝对路径字符串(例如:...\WebApi\wwwroot)。
/// 说明:
/// 1、如果启动项是API模式,启动项默认是不包含“wwwroot”文件夹的,所以该属性成员实例在API模式中的默认值为:null;
/// 如果启动项是API模式必须先在启动项中新建“wwwroot”文件夹,才能保证该属性成员实例的默认值不为:null。
/// 2、webHostEnvironment.ContentRootPath:获取程序启动项根目录,在服务器端磁盘中位置绝对路径字符串(例如:...\WebApi)。
/// </remarks>
/// </summary>
public NopFileProvider(IWebHostEnvironment webHostEnvironment)
: base(File.Exists(webHostEnvironment.ContentRootPath) ? Path.GetDirectoryName(webHostEnvironment.ContentRootPath)! : webHostEnvironment.ContentRootPath)
{
WebRootPath = File.Exists(webHostEnvironment.WebRootPath)
? Path.GetDirectoryName(webHostEnvironment.WebRootPath)
: webHostEnvironment.WebRootPath;
}
#endregion
#region 属性
/// <summary>
/// 【Web根路径】
/// <remarks>
/// 摘要:
/// 获取程序启动项中“wwwroot”文件夹,在服务器端磁盘中位置绝对路径字符串(例如:...\WebApi\wwwroot)。
/// 说明:
/// 如果启动项是API模式,启动项默认是不包含“wwwroot”文件夹的,所以该属性成员实例在API模式中的默认值为:null;
/// 如果启动项是API模式必须先在启动项中新建“wwwroot”文件夹,才能保证该属性成员实例的默认值不为:null。
/// </remarks>
/// </summary>
public string WebRootPath { get; }
#endregion
#region 方法--私有/保护
/// <param name="path">1个指定的绝对路径字符串,该字符串包含1个指定的将要被删除目录的绝对路径字。</param>
/// <summary>
/// 【递归删除目录】
/// <remarks>
/// 摘要:
/// 服务器物理磁盘中1个目录进行物理删除操作。
/// </remarks>
/// </summary>
private static void DeleteDirectoryRecursive(string path)
{
//对服务器物理磁盘中1个指目录进行物理删除操作,注意:该操作只是把该目录标记为“deleted(删除)”状态,存在还未被物理删除的可能性。
Directory.Delete(path, true);
const int maxIterationToWait = 10;
var curIteration = 0;
// 使用System.IO.Directory.Delete方法,对服务器物理磁盘中1个指定目录进行物理删除操作,
//会先调用托主机系统中的RemoveDirectory方法会先将该 目录标记为“deleted(删除)”状态后,
//可能需要等待服务器系统有空闲时间,来物理删除在服务器物理磁盘中的该目录,这种情况下删除操作的执行与目录确认被删除可能不同步,
//如果出现该现象,只需要我们等待一段时间,可能就会物理删除在服务器物理磁盘中的该目录。
// 如果出现上述现象,当前方法通过等待1秒(10*100毫秒)操作,以保证该目录的物理删除已经执行完成,
//面的操作就是防止上面所描述现象的产生(见:https://msdn.microsoft.com/ru-ru/library/windows/desktop/aa365488.aspx和https://stackoverflow.com/a/4245121)。
while (Directory.Exists(path))
{
curIteration += 1;
if (curIteration > maxIterationToWait)
return;
Thread.Sleep(100);
}
}
/// <param name="path">1个指定的路径字符串。</param>
/// <summary>
/// 【UNC路径?】
/// <remarks>
/// 摘要:
/// 获取 1个值false(不是1个网络资源的绝对路径,或该路径字符串不符合(UNC)命名约定规则)/true(是1个网络资源的绝对路径,且该路径字符串符合(UNC)命名约定规则),
/// 该值指示1指定的路径字符串,是否是1个网络资源的绝对路径,且该路径字符串符合(UNC)命名约定规则。
/// 本地路径命名约定规则:
/// local host形如:C:\Program Files (x86)\Microsoft Visual Studio\2019。
/// UNC:
/// 1、UNC(Universal Naming Convention)通用命名规则,也称通用命名规范、通用命名约定。
/// 2、UNC为网络(主要指局域网)。
/// 3、UNC路径就是形如:\\softer这样以网络路径格式进行命名的路径字符串。
/// 4、格式:\\servername\sharename,其中servername是服务器名。sharename是共享资源的名称,或"file://server/filename.ext"。
/// </remarks>
/// <returns>
/// 返回:
/// 1个值false(不是1个网络资源的绝对路径,或该路径字符串不符合(UNC)命名约定规则)/true(是1个网络资源的绝对路径,且该路径字符串符合(UNC)命名约定规则)。
/// </returns>
/// </summary>
protected static bool IsUncPath(string path)
{
return Uri.TryCreate(path, UriKind.Absolute, out var uri) && uri.IsUnc;
}
#endregion
#region 方法--接口实现
/// <param name="paths">数组实例,该实例中存储着用于拼接操作的n个路径字符串。</param>
/// <summary>
/// 【拼接】
/// <remarks>
/// 摘要:
/// 拼接出本地路径形式的1个路径字符串(例如:C:\Program Files (x86)\Microsoft Visual Studio\2019)。
/// </remarks>
/// <returns>
/// 返回:
/// 本地路径形式的1个路径字符串(例如:C:\Program Files (x86)\Microsoft Visual Studio\2019)。
/// </returns>
/// </summary>
public virtual string Combine(params string[] paths)
{
//如果数组中的1个指定路径字符串符合(UNC)命名约定规则,则直接对该字符串进行拼接;如果数组中的1个指定路径字符串不符合(UNC)命名约定规则,则对该字符串按照分隔规则('\\', '/')分隔后进行拼接。
//该操作主要是指定的资源的网络形式的路径字符串,在拼接操作中转换为本地路径形式的路径字符串。
var path = Path.Combine(paths.SelectMany(p => IsUncPath(p) ? new[] { p } : p.Split('\\', '/')).ToArray());
if (Environment.OSVersion.Platform == PlatformID.Unix && !IsUncPath(path))
//在Unix系统中添加前导斜杠以形成正确格式或规范的路径字符串。
path = "/" + path;
return path;
}
/// <param name="path">1个指定的路径字符串,该字符串包含将要被新建目录及其子目录。</param>
/// <summary>
/// 【新建目录】
/// <remarks>
/// 摘要:
/// 在服务器物理磁盘的指定位置上新建1个指定的目录及其子目录(文件夹及其子文件夹),除非它们已经存在于服务器物理磁盘的指定位置上。
/// </remarks>
/// </summary>
public virtual void CreateDirectory(string path)
{
if (!DirectoryExists(path))
Directory.CreateDirectory(path);
}
/// <summary>
/// 【新建文件】
/// <param name="path">1个指定的路径字符串(带有文件的扩展名),该字符串包含将要被新建的文件。</param>
/// <remarks>
/// 摘要:
/// 在服务器物理磁盘的指定位置上新建1个指定的文件(带有扩展名的文件),除非该文件已经存在于服务器物理磁盘的指定位置上。
/// </remarks>
/// </summary>
public virtual void CreateFile(string path)
{
if (FileExists(path))
return;
var fileInfo = new FileInfo(path);
CreateDirectory(fileInfo.DirectoryName);
// 文件在服务器物理磁盘的指定位置上新建后,使用“using”关键字来关闭该文件的创建操作,并释放和销毁当前方法在文件新建过程中所产生的内存空间。
// 如果不使用“using”关键字,也可能会在服务器物理磁盘的指定位置上有该新建文件,但也可能会没有该新建文件,更不会有该新建文件的内存需要被释放,所以就必然会产生异常,
//所以必须使用“using”关键字来操作来保证服务器物理磁盘的指定位置上有该新建文件,从而避免产生异常。
using (File.Create(path))
{
}
}
/// <param name="path">1个指定的路径字符串,该字符串包含将要被删除的目录及其子目录。</param>
/// <summary>
/// 【删除目录】
/// <remarks>
/// 摘要:
/// 在服务器物理磁盘的指定位置上,通过递归操作删除1个指定的目录及其子目录(文件夹及其子文件夹,还有这些文件夹中的所有文件)。
/// </remarks>
/// </summary>
public virtual void DeleteDirectory(string path)
{
if (string.IsNullOrEmpty(path))
throw new ArgumentNullException(path);
//如果1个指定被删除目录中有子目录,则通过递归操作获取该指定被删除目录中的最后一个子目录的路径字符串。
//为什么使用深度优先递归删除删除目录见:/https://stackoverflow.com/questions/329355/cannot-delete-directory-with-directory-deletepath-true
foreach (var directory in Directory.GetDirectories(path))
{
DeleteDirectory(directory);
}
try
{
//根据递归操作获取的路径字符串,从最后一个子目录依次向父目录执行删除操作。
DeleteDirectoryRecursive(path);
}
catch (IOException)
{
DeleteDirectoryRecursive(path);
}
catch (UnauthorizedAccessException)
{
DeleteDirectoryRecursive(path);
}
}
/// <param name="path">1个指定的路径字符串(带有文件的扩展名),该字符串包含将要被删除的文件。</param>
/// <summary>
/// 【删除文件】
/// <remarks>
/// 摘要:
/// 在服务器物理磁盘的指定位置上删除1个指定的文件(带有扩展名的文件),除非该文件已经删除或不存在。
/// </remarks>
/// </summary>
public virtual void DeleteFile(string filePath)
{
if (!FileExists(filePath))
return;
File.Delete(filePath);
}
/// <param name="path">1个指定目录的路径字符串</param>
/// <summary>
/// 【目录存在?】
/// <remarks>
/// 摘要:
/// 获取1个值false1个值false(不存在1个指定的目录(文件夹))/true(存在1个指定的目录(文件夹)),该值指示在服务器物理磁盘的指定位置上是否存在1个指定的目录(文件夹)。
/// <returns>
/// 返回:
/// 1个值false(不存在1个指定的目录(文件夹))/true(存在1个指定的目录(文件夹))。
/// </returns>
/// </summary>
public virtual bool DirectoryExists(string path)
{
return Directory.Exists(path);
}
/// <param name="sourceDirName">1个指定的路径字符串,该字符串包含1个将要被移动的目录(文件夹)。</param>
/// <param name="destDirName">1个指定的路径字符串,该字符串包含1个目标目录(移动到的目录及其子目录)。</param>
/// <summary>
/// 【目录移动】
/// <remarks>
/// 摘要:
/// 在服务器物理磁盘中,把1个源目录(被移动的目录及其子目录)移动到指定的目标目录中。
/// </remarks>
/// </summary>
public virtual void DirectoryMove(string sourceDirName, string destDirName)
{
Directory.Move(sourceDirName, destDirName);
}
/// <param name="directoryPath">1个指定的路径字符串,该字符串包含一个指定目录(文件夹)。</param>
/// <param name="searchPattern">
/// 1个指定的搜索模式字符串,该字符串参数实例中,可以包含有效的文字路径和通配符(*和?)等字符的组合,但不支持正则表达式,
/// 通过该搜索模式字符串,可以从服务器物理磁盘的相应位置指定目录中,依次的获取符合该搜索模式字符串规则的所有文件的路径字符串。
/// </param>
/// <param name="topDirectoryOnly">
/// 指示只搜索服务器中物理磁盘的相应位置的当前目录,还是搜索当前目录及其所有子目录,默认值为:true,即搜索当前目录及其所有子目录。
/// </param>
/// <summary>
/// 【获取文件集】
/// <remarks>
/// 摘要:
/// 把指定目录(文件夹)及其子目录中,与指定搜索模式字符串相匹配的所有文件的路径字符串存储到一个枚举数实例中。
/// </remarks>
/// <returns>
/// 返回:
/// 1个枚举数实例,该实例存储着与指定搜索模式字符串相匹配的所有文件的路径字符串。
/// </returns>
/// </summary>
public virtual IEnumerable<string> EnumerateFiles(string directoryPath, string searchPattern, bool topDirectoryOnly = true)
{
return Directory.EnumerateFiles(directoryPath, searchPattern,
topDirectoryOnly ? SearchOption.TopDirectoryOnly : SearchOption.AllDirectories);
}
/// <param name="sourceFileName">1个指定的路径字符串,该字符串包含1个将要被复制的文件。</param>
/// <param name="destFileName">1个指定的路径字符串,该字符串包含1个将要被复制到的文件(被复制的文件作为其子文件)。</param>
/// <param name="overwrite">指示在文件复制过程中是否可以覆盖同名的目标文件,默认值为:false,即不可以覆盖同名的目标文件。</param>
/// <summary>
/// 【文件复制】
/// <remarks>
/// 摘要:
/// 把将要被复制的文件复制到服务器中物理磁盘的相应位置上;如果指定位置上已经有同名文件,在默认状态下不能对同名文件执行覆盖操作。
/// </remarks>
/// </summary>
public virtual void FileCopy(string sourceFileName, string destFileName, bool overwrite = false)
{
File.Copy(sourceFileName, destFileName, overwrite);
}
/// <param name="path">1个指定文件(包括扩展名)的路径字符串</param>
/// <summary>
/// 【文件存在?】
/// <remarks>
/// 摘要:
/// 获取1个值false1个值false(不存在1个指定的文件(包括扩展名))/true(存在1个指定的文件(包括扩展名)),该值指示在服务器物理磁盘的指定位置上是否存在1个指定的文件(包括扩展名)。
/// <returns>
/// 返回:
/// 1个值false1个值false(不存在1个指定的文件(包括扩展名))/true(存在1个指定的文件(包括扩展名))。
/// </returns>
/// </summary>
public virtual bool FileExists(string filePath)
{
return File.Exists(filePath);
}
/// <param name="path">1个指定文件(包括扩展名)的路径字符串</param>
/// <summary>
/// 【文件大小】
/// <remarks>
/// 摘要:
/// 获取1个指定文件(包括扩展名)大小的数据值(以字节为单位),如果文件不存在则直接返回数据值:-1。
/// </remarks>
/// <returns>
/// 返回:
/// 1个指定文件(包括扩展名)大小的数据值(以字节为单位),如果文件不存在则直接返回数据值:-1。
/// </returns>
/// </summary>
public virtual long FileLength(string path)
{
if (!FileExists(path))
return -1;
return new FileInfo(path).Length;
}
/// <param name="sourceFileName">1个指定的路径字符串,该字符串包含1个将要被移动的文件(包括扩展名)。</param>
/// <param name="destDirName">1个指定的路径字符串,该字符串包含目标文件(包括扩展名,移动到的文件)。</param>
/// <summary>
/// 【文件移动】
/// <remarks>
/// 摘要:
/// 在服务器物理磁盘中,把1个源文件(被移动的文件)移动到指定的目标文件中。
/// </remarks>
/// </summary>
public virtual void FileMove(string sourceFileName, string destFileName)
{
File.Move(sourceFileName, destFileName);
}
/// <param name="paths">数组实例,该实例中存储着用于拼接操作的n个路径字符串。</param>
/// <summary>
/// 【获得绝对路径】
/// <remarks>
/// 摘要:
/// 拼接出1个以“wwwroot”开头的路径字符串。
/// </remarks>
/// <returns>
/// 返回:
/// 1个以“wwwroot”开头的路径字符串。
/// </returns>
/// </summary>
public virtual string GetAbsolutePath(params string[] paths)
{
var allPaths = new List<string>();
//如果参数数组实例不为空,且数数组实例的第1个路径字符串中不包含有“wwwroot”,则把“wwwroot”字符串存储到列表实例中。
if (paths.Any() && !paths[0].Contains(WebRootPath, StringComparison.InvariantCulture))
allPaths.Add(WebRootPath);
//把参数数组实例中的所有实例按照前后顺序,依次存储到列表实例中。
allPaths.AddRange(paths);
//拼接出1个以“wwwroot”开头的路径字符串。
return Combine(allPaths.ToArray());
}
/// <param name="path">1个指定的路径字符串,该字符串包含1个指定的目录/文件。</param>
/// <summary>
/// 【获取访问控制】
/// <remarks>
/// 摘要:
/// 通过服务器的操作系统,可以远程的获取物理磁盘相应位置上,
/// 指定目录/文件的访问控制和安全审核权限(他们由访问控制列表(ACL)项控件,
/// 以便于对服务器中的指定目录/文件进行读、写、删和移动等后续操作。
/// [SupportedOSPlatform("windows")]:--最近定义于nopCommerce_4.40
/// 该特性标记用限定平台或操作系统支持 API。如果指定了一个版本,则无法从较早的版本调用该API方法。
/// 可以应用多个特性标记来表示对多个操作系统的支持。在这里该特性标记用来限定,
/// 该方法只能用于获取"windows"操作系统中指定目录访问控制和安全审核权限的相关数据信息(即启动项被部署的操作系统/平台必须是"windows"操作系统)。
/// </remarks>
/// <returns>
/// 返回:
/// 1个指定目录/文件的访问控制和安全审核权限实例。
/// </returns>
/// </summary>
[SupportedOSPlatform("windows")]
public virtual DirectorySecurity GetAccessControl(string path)
{
return new DirectoryInfo(path).GetAccessControl();
}
/// <param name="path">1个指定的路径字符串,该字符串包含1个指定的目录/文件。</param>
/// <summary>
/// 【获取新建时间】
/// <remarks>
/// 摘要:
/// 获取1指定目录/文件被新建时的日期和时间。
/// </remarks>
/// <returns>
/// 返回:
/// 1指定目录/文件被新建时的日期和时间。
/// </returns>
/// </summary>
public virtual DateTime GetCreationTime(string path)
{
return File.GetCreationTime(path);
}
/// <param name="path">1个指定的路径字符串,该字符串包含1个指定目录(文件夹)。</param>
/// <param name="searchPattern">
/// 1个指定的搜索模式字符串,该字符串参数实例中,可以包含有效的文字路径和通配符(*和?)等字符的组合,但不支持正则表达式,
/// 通过该搜索模式字符串,可以从服务器物理磁盘的相应位置指定目录中,依次的获取符合该搜索模式字符串规则的所有文件的路径字符串。
/// </param>
/// <param name="topDirectoryOnly">
/// 指示只搜索服务器中物理磁盘的相应位置的当前目录,还是搜索当前目录及其所有子目录,默认值为:true,即搜索当前目录及其所有子目录。
/// </param>
/// <summary>
/// 【获取目录集】
/// <remarks>
/// 摘要:
/// 把1个指定目录(文件夹)及其子目录中,与指定搜索模式字符串相匹配的所有目录(文件夹)的路径字符串存储到一个数组实例中。
/// </remarks>
/// <returns>
/// 返回:
/// 1个数组实例,该实例存储着1个指定目录(文件夹)中与指定搜索模式字符串相匹配的所有目录(文件夹)的路径字符串。
/// </returns>
/// </summary>
public virtual string[] GetDirectories(string path, string searchPattern = "", bool topDirectoryOnly = true)
{
if (string.IsNullOrEmpty(searchPattern))
searchPattern = "*";
return Directory.GetDirectories(path, searchPattern,
topDirectoryOnly ? SearchOption.TopDirectoryOnly : SearchOption.AllDirectories);
}
/// <param name="paths">1个指定的路径字符串,该字符串包含1个指定的目录(文件夹)。</param>
/// <summary>
/// 【获取目录路径】
/// <remarks>
/// 摘要:
/// 通过截取操作,获取1个指定的目录(文件夹)的绝对路径字符串(该路径字符串最后不包含“\”号)
/// </remarks>
/// <returns>
/// 返回:
/// 1个指定的目录(文件夹)的绝对路径字符串(该路径字符串最后不包含“\”号)。
/// </returns>
/// </summary>
public virtual string GetDirectoryName(string path)
{
return Path.GetDirectoryName(path);
}
/// <param name="paths">1个指定的路径字符串,该字符串包含1个指定的目录(文件夹)。</param>
/// <summary>
/// 【获取目录名称】
/// <remarks>
/// 摘要:
/// 获取1个指定的目录(文件夹)的名称。
/// </remarks>
/// <returns>
/// 返回:
/// 1个指定的目录(文件夹)的名称。
/// </returns>
/// </summary>
public virtual string GetDirectoryNameOnly(string path)
{
return new DirectoryInfo(path).Name;
}
/// <param name="path">1个指定文件(包括扩展名)的路径字符串。</param>
/// <summary>
/// 【获取文件扩展名】
/// <remarks>
/// 摘要:
/// 获取1个指定文件的扩展名(包括句号“.”)。
/// </remarks>
/// <returns>
/// 返回:
/// 1个指定文件的扩展名(包括句号“.”)。
/// </returns>
/// </summary>
public virtual string GetFileExtension(string filePath)
{
return Path.GetExtension(filePath);
}
/// <param name="path">1个指定文件(包括扩展名)的路径字符串。</param>
/// <summary>
/// 【获取文件名】
/// <remarks>
/// 摘要:
/// 获取1个指定文件的名称(包括其扩展名)。
/// </remarks>
/// <returns>
/// 返回:
/// 1个指定文件的名称(包括其扩展名)。
/// </returns>
/// </summary>
public virtual string GetFileName(string path)
{
return Path.GetFileName(path);
}
/// <param name="path">1个指定文件(包括扩展名)的路径字符串。</param>
/// <summary>
/// 【获取文件名不包括扩展名】
/// <remarks>
/// 摘要:
/// 获取1个指定文件的名称(不包括其扩展名,即不包括“.”及其后面的所有字符)。
/// <returns>
/// 返回:
/// 1个指定文件的名称(不包括其扩展名,即不包括“.”及其后面的所有字符)。
/// </returns>
/// </summary>
public virtual string GetFileNameWithoutExtension(string filePath)
{
return Path.GetFileNameWithoutExtension(filePath);
}
/// <param name="path">1个指定的路径字符串,该字符串包含1个指定目录(文件夹)。</param>
/// <param name="searchPattern">
/// 1个指定的搜索模式字符串,该字符串参数实例中,可以包含有效的文字路径和通配符(*和?)等字符的组合,但不支持正则表达式,
/// 通过该搜索模式字符串,可以从服务器物理磁盘的相应位置指定目录中,依次的获取符合该搜索模式字符串规则的所有文件的路径字符串。
/// </param>
/// <param name="topDirectoryOnly">
/// 指示只搜索服务器中物理磁盘的相应位置的当前目录,还是搜索当前目录及其所有子目录,默认值为:true,即搜索当前目录及其所有子目录。
/// </param>
/// <summary>
/// 【获取文件集】
/// <remarks>
/// 摘要:
/// 把1个指定目录(文件夹)及其子目录中,与指定搜索模式字符串相匹配的所有文件的路径字符串存储到一个数组实例中。
/// </remarks>
/// <returns>
/// 返回:
/// 1个数组实例,该实例存储着1个指定目录(文件夹)中与指定搜索模式字符串相匹配的所有文件的路径字符串。
/// </returns>
/// </summary>
public virtual string[] GetFiles(string directoryPath, string searchPattern = "", bool topDirectoryOnly = true)
{
if (string.IsNullOrEmpty(searchPattern))
searchPattern = "*.*";
return Directory.GetFileSystemEntries(directoryPath, searchPattern,
new EnumerationOptions
{
IgnoreInaccessible = true,
MatchCasing = MatchCasing.CaseInsensitive,
RecurseSubdirectories = !topDirectoryOnly,
});
}
/// <param name="path">1个指定的路径字符串,该字符串包含1个指定的文件。</param>
/// <summary>
/// 【获取最后访问时间】
/// <remarks>
/// 摘要:
/// 获取1指定文件的内容最后1次被打开后的日期和时间。
/// </remarks>
/// <returns>
/// 返回:
/// 1指定文件的内容最后1次被打开后的日期和时间。
/// </returns>
/// </summary>
public virtual DateTime GetLastAccessTime(string path)
{
return File.GetLastAccessTime(path);
}
/// <param name="path">1个指定的路径字符串,该字符串包含1个指定的文件。</param>
/// <summary>
/// 【获取最后写入时间】
/// <remarks>
/// 摘要:
/// 获取1指定文件的内容最后1次被修改后的日期和时间。
/// </remarks>
/// <returns>
/// 返回:
/// 1指定文件的内容最后1次被修改后的日期和时间。
/// </returns>
/// </summary>
public virtual DateTime GetLastWriteTime(string path)
{
return File.GetLastWriteTime(path);
}
/// <param name="path">1个指定的路径字符串,该字符串包含1个指定的文件。</param>
/// <summary>
/// 【获取最后写入UTC时间】
/// <remarks>
/// 摘要:
/// 获取1指定文件的内容最后1次被修改后的国际标准时间值(UTC),即该时间值+8小时,即为本地时间。
/// </remarks>
/// <returns>
/// 返回:
/// 1指定文件的内容最后1次被修改后的国际标准时间值(UTC)。
/// </returns>
/// </summary>
public virtual DateTime GetLastWriteTimeUtc(string path)
{
return File.GetLastWriteTimeUtc(path);
}
/// <param name="path">1个指定的路径字符串,该字符串包含1个指定的文件。</param>
/// <summary>
/// 【获取或新建文件】
/// <remarks>
/// 摘要:
/// 获取1个指定文件的内容信息(如果该没有该文件,则把新建该件及其把内容信息持久化到该文件中),并把该内容信息以文件流的形式进行存储(为以后的网络传输作好预处理工作)。
/// </remarks>
/// <returns>
/// 返回:
/// 文件流实例(为以后的网络传输作好预处理工作),该实例存储着1个指定文件的内容信息。
/// </returns>
/// </summary>
public FileStream GetOrCreateFile(string path)
{
if (FileExists(path))
return File.Open(path, FileMode.Open, FileAccess.ReadWrite);
var fileInfo = new FileInfo(path);
CreateDirectory(fileInfo.DirectoryName);
return File.Create(path);
}
/// <param name="paths">1个指定的路径字符串,该字符串包含1个指定的目录(文件夹)。</param>
/// <summary>
/// 【获取父目录路径】
/// <remarks>
/// 摘要:
/// 获取1个指定目录的上1级目录的路径字符串(字符串最后不包含“\”号)。
/// </remarks>
/// <returns>
/// 返回:
/// 上1级目录的路径字符串(该字符串最后不包含“\”号)。
/// </returns>
/// </summary>
public virtual string GetParentDirectory(string directoryPath)
{
return Directory.GetParent(directoryPath)!.FullName;
}
/// <param name="path">1个指定的路径字符串,该字符串包含1个指定目录/文件。</param>
/// <summary>
/// 【获取虚拟(相对)路径】
/// <remarks>
/// 摘要:
/// 把1个本地格式的路径字符串转换为与之相对应的网络格式的路径字符串(例如: @"C:\inetpub\wwwroot\bin"--->“~/bin”)。
/// 例如:
/// </remarks>
/// <returns>
/// 返回:
/// 1个网络格式的路径字符串或空字符串,
/// </returns>
/// </summary>
public virtual string GetVirtualPath(string path)
{
if (string.IsNullOrEmpty(path))
return path;
// 如果服务器物理磁盘相应位置上不存在指定目录/文件,则在服务器物理磁盘相应位置上新建该指定目录 / 文件。
if (!IsDirectory(path) && FileExists(path))
path = new FileInfo(path).DirectoryName;
//把1个本地格式的路径字符串转换为与之相对应的网络格式的路径字符串(例如: @"C:\inetpub\wwwroot\bin"--->“~/ bin”)。
path = path?.Replace(WebRootPath, string.Empty).Replace('\\', '/').Trim('/').TrimStart('~', '/');
//获取1个网络格式的路径字符串或空字符串,
return $"~/{path ?? string.Empty}";
}
/// <param name="paths">1个指定的路径字符串,该字符串包含1个指定的目录(文件夹)。</param>
/// <summary>
/// 【目录存在?】
/// <remarks>
/// 摘要:
/// 获取1个值false1个值false(不存在1个指定的目录(文件夹))/true(存在1个指定的目录(文件夹))),该值指示在服务器物理磁盘的指定位置上是否存在1个指定的目录(文件夹)。
/// <returns>
/// 返回:
/// 1个值false1个值false(不存在1个指定的目录(文件夹))/true(存在1个指定的目录(文件夹))。
/// </returns>
/// </summary>
public virtual bool IsDirectory(string path)
{
return DirectoryExists(path);
}
/// <param name="path">1个指定的路径字符串,该字符串包含1个指定目录/文件。</param>
/// <summary>
/// 【映射路径】
/// <remarks>
/// 摘要:
///
/// 把1个网络格式的路径字符串转换为与之相对应的本地格式的路径字符串(例如:“~/bin”---> "C:\inetpub\wwwroot\bin")。
/// 例如:
/// </remarks>
/// <returns>
/// 返回:
/// 1个网络格式的路径字符串或空字符串,
/// </returns>
/// </summary>
public virtual string MapPath(string path)
{
//去除网络格式路径字符中的第一个字符“~/”。
path = path.Replace("~/", string.Empty).TrimStart('/');
//去除网络格式路径字符中的最后一个字符“/”。
var pathEnd = path.EndsWith('/') ? Path.DirectorySeparatorChar.ToString() : string.Empty;
//通过拼接操作,拼接出与之相对应的1个本地格式的路径字符串。
return Combine(Root ?? string.Empty, path) + pathEnd;
}
/// <param name="path">1个指定的路径字符串,该字符串包含1个指定的文件(包括扩展名)。</param>
/// <summary>
/// 【异步读所有字节】
/// <remarks>
/// 摘要:
/// 获取1指定文件中的所有内容数据,以字节的编码格式存储到数组实例中。
/// </remarks>
/// <returns>
/// 返回:
/// 字节数组实例,该实例以字节的编码格式存储着1个指定文件中的所有内容数据。
/// </returns>
/// </summary>
public virtual async Task<byte[]> ReadAllBytesAsync(string filePath)
{
//如果存在指定文件,把该文件中的所有内容数据以字节的编码格式存储到数组实例中,并返回该数组实例;
//如果不存在指定文件,返回一个空实例数组。
return File.Exists(filePath) ? await File.ReadAllBytesAsync(filePath) : Array.Empty<byte>();
}
/// <param name="path">1个指定的路径字符串,该字符串包含1个指定的文件(包括扩展名)。</param>
/// <param name="encoding">1个编码格式实例(常见的编号格式有:简体中文码:GB2312、繁体中文码:BIG5、西欧字符:UTF-8等),按照该参数实例对文件中的所有内容进行读取操作。</param>
/// <summary>
/// 【异步读所有文本】
/// <remarks>
/// 摘要:
/// 获取1指定文件中的所有内容数据,以指定的编码格式存储到到字符串实例中。
/// </remarks>
/// <returns>
/// 返回:
/// 字符串实例,该字符串实例以指定编码格式存储着1指定文件中的所有内容数据。
/// </returns>
/// </summary>
public virtual async Task<string> ReadAllTextAsync(string path, Encoding encoding)
{
//以流文件实例获取指定文件中的所有内容数据。
await using var fileStream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
//以指定的编码格式,对流文件实例进行解析,并把解析结果存储到流读取实例中。
using var streamReader = new StreamReader(fileStream, encoding);
//把流读取实例转换为字符串实例。
return await streamReader.ReadToEndAsync();
}
/// <param name="path">1个指定的路径字符串,该字符串包含1个指定的文件(包括扩展名)。</param>
/// <param name="encoding">1个编码格式实例(常见的编号格式有:简体中文码:GB2312、繁体中文码:BIG5、西欧字符:UTF-8等),按照该参数实例对文件中的所有内容进行读取操作。</param>
/// <summary>
/// 【读所有文本】
/// <remarks>
/// 摘要:
/// 获取1指定文件中的所有内容数据,以指定的编码格式存储到到字符串实例中。
/// </remarks>
/// <returns>
/// 返回:
/// 字符串实例,该字符串实例以指定编码格式存储着1指定文件中的所有内容数据。
/// </returns>
/// </summary>
public virtual string ReadAllText(string path, Encoding encoding)
{
//以流文件实例获取指定文件中的所有内容数据。
using var fileStream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
// 以指定的编码格式,对流文件实例进行解析,并把解析结果存储到流读取实例中。
using var streamReader = new StreamReader(fileStream, encoding);
//把流读取实例转换为字符串实例。
return streamReader.ReadToEnd();
}
/// <param name="path">1个指定的路径字符串,该字符串包含1个指定的文件(包括扩展名)。</param>
/// <param name="bytes">1个指定的字节数组实例,该实例以字节的编码格式存储着的一些内容数据。</param>
/// <summary>
/// 【异步写入所有字节】
/// <remarks>
/// 摘要:
/// 把 1个指定的字节数组实例,持久化保存到1个指定的文件(包括扩展名)中,
/// 如果文件中已经有其它的内容存在,则使用新的内容对把该文件中旧的内容进行覆盖,最后关闭该文件的写入操作。
/// 注意:
/// 如果指定持久化写入的文件不存在,该方法会先在服务器端相应磁盘位置上自动新建该文件,然后再把内容持久化写入到该文件中。
/// </remarks>
/// </summary>
public virtual async Task WriteAllBytesAsync(string filePath, byte[] bytes)
{
await File.WriteAllBytesAsync(filePath, bytes);
}
/// <param name="path">1个指定的路径字符串,该字符串包含1个指定的文件(包括扩展名)。</param>
/// <param name="contents">将要被写入的内容字符串。</param>
/// <param name="encoding">1个编码格式实例(常见的编号格式有:简体中文码:GB2312、繁体中文码:BIG5、西欧字符:UTF-8等),按照该参数实例对文件中的所有内容进行读取操作。</param>
/// <summary>
/// 【异步写入所有文本】
/// <remarks>
/// 摘要:
/// 把1字符串实例,持久化保存到1个指定的文件(包括扩展名)中,
/// 如果文件中已经有其它的内容存在,则使用新的内容对把该文件中旧的内容进行覆盖,最后关闭该文件的写入操作。
/// 注意:
/// 如果指定持久化写入的文件不存在,该方法会先在服务器端相应磁盘位置上自动新建该文件,然后再把内容持久化写入到该文件中。
/// </remarks>
/// </summary>
public virtual async Task WriteAllTextAsync(string path, string contents, Encoding encoding)
{
await File.WriteAllTextAsync(path, contents, encoding);
}
/// <param name="path">1个指定的路径字符串,该字符串包含1个指定的文件(包括扩展名)。</param>
/// <param name="contents">将要被写入的内容字符串。</param>
/// <param name="encoding">1个编码格式实例(常见的编号格式有:简体中文码:GB2312、繁体中文码:BIG5、西欧字符:UTF-8等),按照该参数实例对文件中的所有内容进行读取操作。</param>
/// <summary>
/// 【写入所有文本】
/// <remarks>
/// 摘要:
/// 把1字符串实例,持久化保存到1个指定的文件(包括扩展名)中,
/// 如果文件中已经有其它的内容存在,则使用新的内容对把该文件中旧的内容进行覆盖,最后关闭该文件的写入操作。
/// 注意:
/// 如果指定持久化写入的文件不存在,该方法会先在服务器端相应磁盘位置上自动新建该文件,然后再把内容持久化写入到该文件中。
/// </remarks>
/// </summary>
public virtual void WriteAllText(string path, string contents, Encoding encoding)
{
File.WriteAllText(path, contents, encoding);
}
#endregion
#region 方法
/// <param name="subpath">1个指定的路径字符串,该字符串包含1个指定的文件(包括扩展名)。</param>
/// <summary>
/// 【获取文件信息】
/// <remarks>
/// 摘要:
/// 获取1个指定的文件(包括扩展名)的 相关信息(例如:名称、大小、新建日期等)。
/// </remarks>
/// <returns>
/// 返回:
/// 1个指定的文件(包括扩展名)的 相关信息(例如:名称、大小、新建日期等)。
/// </returns>
/// </summary>
public virtual new IFileInfo GetFileInfo(string subpath)
{
subpath = subpath.Replace(Root, string.Empty);
return base.GetFileInfo(subpath);
}
#endregion
}
}
7 Core.CommonHelper
using Core.Infrastructure;
using System.ComponentModel;
using System.Globalization;
using System.Net;
using System.Text.RegularExpressions;
namespace Core
{
/// <summary>
/// 【通用助手--类】
/// <remarks>
/// 摘要:
/// 该类中的方法成员现了程序中一些能够被经常被用到的通用操作,这些操作包括:电子邮箱输入验证、随机数生成、不同类型间的数据转换、以泛型形式对指定类中属性成员时行赋值,以泛型形式实现两个数组的比较和年差计算等。
/// </remarks>
/// </summary>
public class CommonHelper
{
#region 变量--私有/保护
/// <summary>
/// 【电子邮箱表达式】
/// <remarks>
/// 摘要:
/// 一个用于验证电子邮箱的以正则表达式进行定义的验证规则字符串常量。
/// 当前EmailValidator来自FluentValidation。所以让它们保持同步请访问:https://github.com/JeremySkinner/FluentValidation/blob/master/src/FluentValidation/Validators/EmailValidator.cs
/// </remarks>
/// </summary>
private const string EMAIL_EXPRESSION = @"^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-||_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+([a-z]+|\d|-|\.{0,1}|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])?([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))$";
/// <summary>
/// 【电子邮箱正则表达式】
/// <remarks>
/// 摘要:
/// 电子邮箱正则表达式实例,通过该实例可以用于验证用户输入电子邮箱字符串是否与指定的正则表达式字符串所规定规则相符合。
/// </summary>
private static readonly Regex _emailRegex;
#endregion
#region 拷贝构造方法
/// <summary>
/// 【静态构造方法】
/// <remarks>
/// 摘要:
/// 通过该静态构造方法,对当前类中的电子邮箱正则表达式变量成员进行实例化。
/// </remarks>
/// </summary>
static CommonHelper()
{
_emailRegex = new Regex(EMAIL_EXPRESSION, RegexOptions.IgnoreCase);
}
#endregion
#region 属性--静态
/// <summary>
/// 【自定义文件提供程序】
/// <remarks>
/// 摘要:
/// 获取/设置自定义文件提供程序接口的1个指定实例。
/// </remarks>
/// </summary>
public static INopFileProvider DefaultFileProvider { get; set; }
#endregion
#region 方法
/// <param name="email">1个指定的电子邮箱字符串。</param>
/// <summary>
/// 【有效电子邮箱?】
/// <remarks>
/// 摘要:
/// 获取1个值false(无效)/true(有效),该值指示用户输入电子邮箱字符串是否与指定的正则表达式字符串所规定规则相符合,如果符合则有效,反之则无效。
/// <returns>
/// 返回:
/// 1个值false(无效)/true(有效)。
/// </returns>
/// </summary>
public static bool IsValidEmail(string email)
{
if (string.IsNullOrEmpty(email))
return false;
email = email.Trim();
return _emailRegex.IsMatch(email);
}
/// <param name="email">1个指定的电子邮箱字符串。</param>
/// <summary>
/// 【确保电子邮箱或异常抛出】
/// <remarks>
/// 摘要:
/// 获取1个指定的电子邮箱字符串,且该电子邮箱字符串必须与正则表达式字符串所规定规则相符合,如果不符合则,获取异常信息。
/// </remarks>
/// <returns>
/// 返回:
/// 1个指定的电子邮箱字符串或异常信息。
/// </returns>
/// </summary>
public static string EnsureSubscriberEmailOrThrow(string email)
{
//如果字符串为:null,则设置其为空字符串。
var output = EnsureNotNull(email);
//去除字符串前后的空格。
output = output.Trim();
//如果字符串的长度大于255,则执行截取和拼接操作。
output = EnsureMaximumLength(output, 255);
if (!IsValidEmail(output))
{
throw new NopException("所输入的不是电子邮箱。");
}
return output;
}
/// <param name="ipAddress">1个指定的IP地址字符串。</param>
/// <summary>
/// 【有效IP地址?】
/// <remarks>
/// 摘要:
/// 获取1个值false(无效)/true(有效),该值指示1个指定的字符串是否与IP地址格式相符合,如果符合则有效,反之则无效。
/// <returns>
/// 返回:
/// 1个值false(无效)/true(有效)
/// </returns>
/// </summary>
public static bool IsValidIpAddress(string ipAddress)
{
return IPAddress.TryParse(ipAddress, out var _);
}
/// <param name="length">将要被生成的随机数字符串的长度。</param>
/// <summary>
/// 【字符串随机数生成器】
/// <remarks>
/// 摘要:
/// 获取1个指定长度的随机数字符串(主要为用户登录页面中验证码提供数据支撑)。
/// </remarks>
/// <returns>
/// 返回:
/// 1个指定长度的随机数字符串(主要为用户登录页面中验证码提供数据支撑)。
/// </returns>
/// </summary>
public static string GenerateRandomDigitCode(int length)
{
using var random = new SecureRandomNumberGenerator();
var str = string.Empty;
for (var i = 0; i < length; i++)
str = string.Concat(str, random.Next(10).ToString());
return str;
}
/// <param name="minValue">1个最小的整型的数据值。</param>
/// <param name="maxValue">1个最大的整型的数据值。</param>
/// <summary>
/// 【整型随机数生成器】
/// <remarks>
/// 摘要:
/// 获取1个整型的随机数(主要为用户登录页面中验证码提供数据支撑)。
/// </remarks>
/// <returns>
/// 返回:
/// 1个整型的随机数(主要为用户登录页面中验证码提供数据支撑)。
/// </returns>
/// </summary>
public static int GenerateRandomInteger(int min = 0, int max = int.MaxValue)
{
using var random = new SecureRandomNumberGenerator();
return random.Next(min, max);
}
/// <param name="str">1个指定的字符串。</param>
/// <param name="maxLength">1个指定的字符串的最大长度值。</param>
/// <param name="postfix">1个指定的后缀字符串,该后缀字符串的默认值为:null,即不存在后缀字符串。</param>
/// <summary>
/// 【确保最大长度】
/// <remarks>
/// 摘要:
/// 获取1个字符串,该字符串可能是:空字符串/字符串本身(小于等于最大长度值)/截取字符串(大于最大长度值且不存在后缀字符串)/拼接字符串(大于最大长度值且存在后缀字符串)。
/// </remarks>
/// <returns>
/// 返回:
/// 1个字符串,该字符串可能是:空字符串/字符串本身(小于等于最大长度值)/截取字符串(大于最大长度值且不存在后缀字符串)/拼接字符串(大于最大长度值且存在后缀字符串)。
/// </returns>
/// </summary>
public static string EnsureMaximumLength(string str, int maxLength, string postfix = null)
{
//如果字符串为空字符串,则直接返回空字符串。
if (string.IsNullOrEmpty(str))
return str;
//如果字符串的长度小于等于最大长度值,则直接返回该字符串。
if (str.Length <= maxLength)
return str;
var pLen = postfix?.Length ?? 0;
//如果字符串的长度大于最大长度值,则把字符串截取;如果后缀字符串为空字符串,则直接获取截取字符串;如果后缀字符串不为空字符串,与后缀字符串进行拼接。
var result = str[0..(maxLength - pLen)];
if (!string.IsNullOrEmpty(postfix))
{
result += postfix;
}
return result;
}
/// <param name="str">1个指定的字符串。</param>
/// <summary>
/// 【确保仅有数字】
/// <remarks>
/// 摘要:
/// 找出1个指定的字符串中的所有数字,并所这些数据字按照顺序拼接成1个只包含数字的字符串;或空字符串。
/// </remarks>
/// <returns>
/// 返回:
/// 1个只包含数字的字符串;或空字符串。
/// </returns>
/// </summary>
public static string EnsureNumericOnly(string str)
{
//把指定字串中的所有数字依次拼接成1个只包含数字的字符串,并返回该只包含数字的字符串;如果指定字串为空或不包含有一个数字,则返回空字符串。
return string.IsNullOrEmpty(str) ? string.Empty : new string(str.Where(char.IsDigit).ToArray());
}
/// <param name="str">1个指定的字符串。</param>
/// <summary>
/// 【确保不为null】
/// <remarks>
/// 摘要:
/// 如果1个字符串实例值为:null,则把其实例值转换空字符串;如果不为:null,则直接返回符串本身。
/// </remarks>
/// <returns>
/// 返回:
/// 字符串本身或空字符串。
/// </returns>
/// </summary>
public static string EnsureNotNull(string str)
{
return str ?? string.Empty;
}
/// <param name="stringsToValidate">1个字符串数组实例,该实例中存储着n个字符串。</param>
/// <summary>
/// 【null或Empty?】
/// <remarks>
/// 摘要:
/// 获取1个值false(不包含)/true(包含),该值指示1个字符串数组实例中的所有字符串中是否至少包含有1个空字符串或null值。
/// <returns>
/// 返回:
/// 1个值false(不包含)/true(包含)。
/// </returns>
/// </summary>
public static bool AreNullOrEmpty(params string[] stringsToValidate)
{
return stringsToValidate.Any(string.IsNullOrEmpty);
}
/// <typeparam name = "T"> 泛型类型实例(1个指定类的类型实例)。</typeparam>
/// <param name="a1">1个指定类型的数组实例</param>
/// <param name="a2">另1个指定类型的数组实例</param>
/// <summary>
/// 【数组相等?】
/// <remarks>
/// 摘要:
/// 获取1个值false(不相等)/true(相等),该值指示2个数组实例是否相等(当前方法以泛型类方式实现2个数组实例的比较操作)。
/// <returns>
/// 返回:
/// 1个值false(不相等)/true(相等)。
/// </returns>
/// </summary>
public static bool ArraysEqual<T>(T[] a1, T[] a2)
{
//如果两个数组实例来同1个引用,则直接返回true。
if (ReferenceEquals(a1, a2))
return true;
//只要两个数组实例中有1个数组实例值为:null,则直接返回false。
if (a1 == null || a2 == null)
return false;
//如果两个数组实例的长度值不相等,则直接返回false。
if (a1.Length != a2.Length)
return false;
//把第1个数组实例中的每个元素按照顺序与第2个数组实例中相应元素进行比较,两者之间只要有任意1个元素不相等则返回false,如果所有的元素都相等则返回true。
var comparer = EqualityComparer<T>.Default;
return !a1.Where((t, i) => !comparer.Equals(t, a2[i])).Any();
}
/// <param name="instance">1个指定的泛型实例。</param>
/// <param name="propertyName">1个指定类中属性成员的名称。</param>
/// <param name="value">1个指定类中属性成员的实例值。</param>
/// <summary>
/// 【设置属性】
/// <remarks>
/// 摘要:
/// 为1个指定类中的1个指定的属性成员进行赋值。
/// </remarks>
/// </summary>
public static void SetProperty(object instance, string propertyName, object value)
{
if (instance == null)
throw new ArgumentNullException(nameof(instance));
if (propertyName == null)
throw new ArgumentNullException(nameof(propertyName));
var instanceType = instance.GetType();
var pi = instanceType.GetProperty(propertyName);
if (pi == null)
throw new NopException("类'{1}'中不存在属性成员'{0}'的定义。", propertyName, instanceType);
if (!pi.CanWrite)
throw new NopException("类'{1}'中的属性成员'{0}'不能被赋值。因为该属性成员是只读的。", propertyName, instanceType);
if (value != null && !value.GetType().IsAssignableFrom(pi.PropertyType))
value = To(value, pi.PropertyType);
pi.SetValue(instance, value, Array.Empty<object>());
}
/// <param name="value">1个指定的泛型实例。</param>
/// <param name="destinationType">1个指定的目标类型实例。</param>
/// <summary>
/// 【实例转换】
/// <remarks>
/// 摘要:
/// 以泛型形式,把1个指定类型的实例转换为另1上指定类型的实例。
/// </remarks>
/// <returns>
/// 返回:
/// 转换类型后的实例值。
/// </returns>
/// </summary>
public static object To(object value, Type destinationType)
{
return To(value, destinationType, CultureInfo.InvariantCulture);
}
/// <param name="value">1个指定的泛型实例。</param>
/// <param name="destinationType">1个指定的目标类型实例。</param>
/// <param name="culture">1个指定的区域性实例。</param>
/// <summary>
/// 【实例转换】
/// <remarks>
/// 摘要:
/// 以泛型形式,把1个指定类型的实例转换为另1上指定类型的实例。
/// CultureInfo 类:
/// 提供有关特定区域性的信息(称为区域设置对于非托管的代码开发),这些信息包括区域性的名称、书写系统、使用的日历、字符串的排序顺序以及对日期和数字的格式化设置。
/// </remarks>
/// <returns>
/// 返回:
/// 转换类型后的实例值。
/// </returns>
/// </summary>
public static object To(object value, Type destinationType, CultureInfo culture)
{
if (value == null)
return null;
//获取泛型实例的类型实例。
var sourceType = value.GetType();
//根据指定的目标类型实例,获取类型转换器实例。
var destinationConverter = TypeDescriptor.GetConverter(destinationType);
//如果目标类型支持对泛型实例的类型进行转换,则直接从泛型实例转换为目标类型实例。
if (destinationConverter.CanConvertFrom(value.GetType()))
return destinationConverter.ConvertFrom(null, culture, value);
//如果泛型实例的类型支持转换到目标类型,则直接把泛型实例转换为目标类型实例。
var sourceConverter = TypeDescriptor.GetConverter(sourceType);
if (sourceConverter.CanConvertTo(destinationType))
return sourceConverter.ConvertTo(null, culture, value, destinationType);
//如果目标类型的是1个枚举,并且泛型实例是整型的数据值,则把整型的数据值转换为相应的枚举实例。
if (destinationType.IsEnum && value is int)
return Enum.ToObject(destinationType, (int)value);
//如果目标类型不支持对泛型实例的类型进行转换;则需要使用强制转换把泛型实例转换为目标类型的实例(例如:Double d = -2.345; int i = (int)Convert.ChangeType(d, typeof(int));,i=-2)。
if (!destinationType.IsInstanceOfType(value))
return Convert.ChangeType(value, destinationType, culture);
//转换类型后的实例值。
return value;
}
/// <typeparam name = "T">泛型类型实例(1个指定类的类型实例)。</typeparam>
/// <param name="value">1个指定的泛型实例。</param>
/// <summary>
/// 【实例转换】
/// <remarks>
/// 摘要:
/// 以泛型形式,把1个指定类型的实例转换为另1上指定类型的实例。
/// </remarks>
/// <returns>
/// 返回:
/// 转换类型后的实例值。
/// </returns>
/// </summary>
public static T To<T>(object value)
{
//return (T)Convert.ChangeType(value, typeof(T), CultureInfo.InvariantCulture);
return (T)To(value, typeof(T));
}
/// <param name="str">1个指定的字符串。</param>
/// <summary>
/// 【字符串分割】
/// <remarks>
/// 摘要:
/// 根据大写字母,把1个指定字符串,分割为1行行的语句。
/// </remarks>
/// <returns>
/// 返回:
/// 分割后的字符串。
/// </returns>
/// </summary>
public static string ConvertEnum(string str)
{
if (string.IsNullOrEmpty(str))
return string.Empty;
var result = string.Empty;
//根据大写字母,把1个指定字符串,分割为1行行的语句。
foreach (var c in str)
if (c.ToString() != c.ToString().ToLowerInvariant()) //如果该字母是大写字母,则在该大写大写字母前加一个空格。
result += " " + c.ToString();
else
result += c.ToString();
//如果字符串中最开始有空格,则去除该字符串中的最开始空格。
result = result.TrimStart();
return result;
}
/// <param name="startDate">1个指定的开始日期。</param>
/// <param name="endDate">1个指定的结束日期。</param>
/// <summary>
/// 【年差】
/// <remarks>
/// 摘要:
/// 获取两个时间之间的年差整型数据值,年差整型数据值可以为负值(即结束日期小于开始日期)。
/// </remarks>
/// <returns>
/// 返回:
/// 年差整型数据值,年差整型数据值可以为负值(即结束日期小于开始日期)。
/// </returns>
/// </summary>
public static int GetDifferenceInYears(DateTime startDate, DateTime endDate)
{
//资源: http://stackoverflow.com/questions/9/how-do-i-calculate-someones-age-in-c,
// 该资源假设你是按照西方的纪年方式进行年差的行计算,而不是按照东亚(中国)的纪年方式进行年差的行计算。
var age = endDate.Year - startDate.Year;
if (startDate > endDate.AddYears(-age))
age--;
return age;
}
/// <param name="year">1个指定的(年格式的)整型值。</param>
/// <param name="month">1个指定的(月格式的)整型值。</param>
/// <param name="day">1个指定的(日格式的)整型值。</param>
/// <summary>
/// 【日期解析】
/// <remarks>
/// 摘要:
/// 把整型值拼接为1个指定的日期,如果3个整型值有任意1个为:null,则日期实例的值也为:null。
/// </remarks>
/// <returns>
/// 返回:
/// 1个指定的日期或null。
/// </returns>
/// </summary>
public static DateTime? ParseDate(int? year, int? month, int? day)
{
if (!year.HasValue || !month.HasValue || !day.HasValue)
return null;
DateTime? date = null;
try
{
date = new DateTime(year.Value, month.Value, day.Value, CultureInfo.CurrentCulture.Calendar);
}
catch { }
return date;
}
#endregion
}
}
8 Core.Infrastructure.ITypeFinder
using System.Reflection;
namespace Core.Infrastructure
{
/// <summary>
/// 【类型查找器--接口】
/// <remarks>
/// 摘要:
/// 继承于该接口的具体实现类根据当前程序启动项中的程序集(*.dll)文件,获取程序中所有的具体实现类的类型实例,为以反射方式对这些具体实现类进行实例化操作提供数据和方法支撑。
/// </remarks>
/// </summary>
public interface ITypeFinder
{
#region 方法
/// <typeparam name = "T"> 泛型类型实例(1个指定类的类型实例,这里特指:1个被继承的指定类/接口的类型实例(该类或接口可以是以泛型形式进行定义的))。</typeparam>
/// <param name="onlyConcreteClasses">指示1个指定的类型实例,是否为具体实现类的类型实例(不是接口/抽象类的类型实例),默认值为:true,即是具体实现类的类型实例(不是接口/抽象类的类型实例)。 </param>
/// <summary>
/// 【查找类的类型】
/// <remarks>
/// 摘要:
/// 用于获取继承于1指定类/接口(该类或接口可以是以泛型形式进行定义的)所有具体实现类的类型实例,并把这些实例存储到可枚举数实例中。
/// </remarks>
/// <returns>
/// 返回:
/// 1个可枚举数实例,该实例存储着继承于1指定类/接口(该类或接口可以是以泛型形式进行定义的)所有具体实现类的类型实例。
/// </returns>
/// </summary>
IEnumerable<Type> FindClassesOfType<T>(bool onlyConcreteClasses = true);
/// <param name="assignTypeFrom">1个被继承的指定类/接口的类型实例(该类或接口可以是以泛型形式进行定义的)。</param>
/// <param name="onlyConcreteClasses">指示1个指定的类型实例,是否为具体实现类的类型实例(不是接口/抽象类的类型实例),默认值为:true,即是具体实现类的类型实例(不是接口/抽象类的类型实例)。 </param>
/// <summary>
/// 【查找类的类型】
/// <remarks>
/// 摘要:
/// 用于获取继承于1指定类/接口(该类或接口可以是以泛型形式进行定义的)所有具体实现类的类型实例,并把这些实例存储到可枚举数实例中。
/// </remarks>
/// <returns>
/// 返回:
/// 1个可枚举数实例,该实例存储着继承于1指定类/接口(该类或接口可以是以泛型形式进行定义的)所有具体实现类的类型实例。
/// </returns>
/// </summary>
IEnumerable<Type> FindClassesOfType(Type assignTypeFrom, bool onlyConcreteClasses = true);
///<summary>
/// 【获取程序集】
/// <remarks>
/// 摘要:
/// 获取1个程序集列表实例,该实例中存储着当前程序启动项中所有符合条件的程序集实例(这里特指“..\WebApi\bin\Debug\net7.0”文件夹中的所有符合条件的*.dll文件,即由开发者自定的具体实现类所在的程序集)。
/// </remarks>
/// <returns>
/// 返回:
/// 1个程序集列表实例,该实例中存储着当前程序启动项中所有符合条件的程序集实例(这里特指“..\WebApi\bin\Debug\net7.0”文件夹中的所有符合条件的*.dll文件,即由开发者自定的具体实现类所在的程序集)。
/// </returns>
/// </summary>
IList<Assembly> GetAssemblies();
#endregion
}
}
9 Core.Infrastructure.AppDomainTypeFinder
using System.Diagnostics;
using System.Reflection;
using System.Text.RegularExpressions;
namespace Core.Infrastructure
{
/// <summary>
/// 【应用程序域类型查找器--类】
/// <remarks>
/// 摘要:
/// 该类根据当前程序启动项中的程序集(*.dll)文件,获取程序中所有的具体实现类的类型实例,为以反射方式对这些具体实现类进行实例化操作提供数据和方法支撑。
/// </remarks>
/// </summary>
public class AppDomainTypeFinder : ITypeFinder
{
#region 变量--私有/保护
/// <summary>
/// 【忽略引用错误?】
/// <remarks>
/// 摘要:
/// 设置1个值false(不执行)/true(默认值:执行),该值指示在异常/错误产生时是否执行异常的抛出操作。
/// 说明:
/// 1、该变量的主要作用是对“Entity Framework 6”的兼容,由于当前程序是基于“EntityFrameworkCore”的,所以当前程序中使用的是该变量的非操作,即不执行异常的抛出操作。
/// 2、实际上该变量是对以前版本的兼容,基本上可以被删除,并对其调用进行重构。
/// </remarks>
/// </summary>
private bool _ignoreReflectionErrors = true;
/// <summary>
/// 【自定义文件提供程序】
/// <remarks>
/// 摘要:
/// 获取/设置自定义文件提供程序接口的1个指定实例。
/// </remarks>
/// </summary>
protected INopFileProvider _fileProvider;
#endregion
#region 拷贝构造方法
/// <summary>
/// 【拷贝构造方法】
/// <param name="fileProvider">自定义文件提供程序接口的1个指定实例,默认值为:null,即该参数实例还没有执行实例化操作。</param>
/// <remarks>
/// 摘要:
/// 通过该拷贝构造方法,对当前类中的自定义文件提供程序接口变量成员进行实例化。
/// </remarks>
/// </summary>
public AppDomainTypeFinder(INopFileProvider fileProvider = null)
{
_fileProvider = fileProvider ?? CommonHelper.DefaultFileProvider;
}
#endregion
#region 属性
/// <summary>
/// 【应用程序域】
/// <remarks>
/// 摘要:
/// 应用程序域实例,该实例用于存储当前程序启动项的相关数据信息(该实例中包含:AppContext.BaseDirectory),为获取当前程序启动项中的程序集(*.dll)文件提供数据支撑。
/// AppDomain:
/// 1、在 .NET中应用程序域AppDomain是CLR的运行单元,它可以加载应用程序集(Assembly)、创建对象以及执行程序。
/// 2、在 CLR 里、AppDomain就是用来实现代码隔离的,每一个AppDomain可以单独创建、运行、卸载。
/// </remarks>
/// </summary>
public virtual AppDomain App => AppDomain.CurrentDomain;
/// <summary>
/// 【必须加载应用程序域程序集?】
/// <remarks>
/// 摘要:
/// 获取/设置1个值false(不必须加载)/true(默认值:必须加载),该值指示是必须对当前程序启动项程序集(*.dll)文件(这里特指“..\WebApi\bin\Debug\net7.0”)进行加载。
/// </remarks>
/// </summary>
public bool LoadAppDomainAssemblies { get; set; } = true;
/// <summary>
/// 【程序集的名称集】
/// <remarks>
/// 摘要:
/// 获取/设置1个字符串列表实例,该实例用存储开发者设定的程序集的全名字符串。
/// 说明:
/// 在当前程序中该实例只为了增加程序的可扩展性,实例中没有任何的成员
/// </remarks>
/// </summary>
public IList<string> AssemblyNames { get; set; } = new List<string>();
/// <summary>
/// 【程序集加载跳过模式】
/// <remarks>
/// 摘要:
/// 由于通过反射进行实例化操作只针对于开发者自定的具体实现类,所以由.Net框架或第3方中间件所提供的程序集的全名字符串就不需要储到列表实例中,
/// 那么在程序集执行加载操作时,就需要通过把程序集的全名字符串与该模式字符串进行配置,如果不配置就把该程序集的全名字符串储到列表实例中。
/// 注意:
/// 我认为该属性成员被限定为静态的const成员更为合适。
/// </remarks>
/// </summary>
public string AssemblySkipLoadingPattern { get; set; } = "^System|^mscorlib|^Microsoft|^AjaxControlToolkit|^Antlr3|^Autofac|^AutoMapper|^Castle|^ComponentArt|^CppCodeProvider|^DotNetOpenAuth|^EntityFramework|^EPPlus|^FluentValidation|^ImageResizer|^itextsharp|^log4net|^MaxMind|^MbUnit|^MiniProfiler|^Mono.Math|^MvcContrib|^Newtonsoft|^NHibernate|^nunit|^Org.Mentalis|^PerlRegex|^QuickGraph|^Recaptcha|^Remotion|^RestSharp|^Rhino|^Telerik|^Iesi|^TestDriven|^TestFu|^UserAgentStringLibrary|^VJSharpCodeProvider|^WebActivator|^WebDev|^WebGrease";
/// <summary>
/// 【程序集加载限制模式】
/// <remarks>
/// 摘要:
/// 该属性成员被实例化为“.*”,即不对程序集的加载进行限制,即所有程序集的全名字符串都符合该模式所规则的条件。
/// 1、同上我认为该属性成员被限定为静态的const成员更为合适。
/// </remarks>
public string AssemblyRestrictToLoadingPattern { get; set; } = ".*";
#endregion
#region 方法--私有/保护
/// <param name="addedAssemblyNames">1个字符串列表实例,该实例用存储当前程序中所有符合条件的程序集的全名字符串。</param>
/// <param name="assemblies">1个字符串列表实例,该实例用存储当前程序中所有符合条件的程序集的全名字符串。</param>
/// <summary>
/// 【加应用程序域程序集】
/// <remarks>
/// 摘要:
/// 把当前程序启动项中所有符合条件的程序集(*.dll)文件(这里特指“..\WebApi\bin\Debug\net7.0”)加载到字符串列表实例和程序集列表实例中。
/// </remarks>
/// </summary>
private void AddAssembliesInAppDomain(List<string> addedAssemblyNames, List<Assembly> assemblies)
{
foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
{
//如果一个程序集实例的全名字符串,没有通过匹配操作,则对下一个程序集实例进行加载操作。
if (!Matches(assembly.FullName))
continue;
//如果字符串列表实例中已经包含有一个程序集实例的全名字符串,则对下一个程序集实例进行加载操作。
if (addedAssemblyNames.Contains(assembly.FullName))
continue;
//把1个指定的程序集存储到字符串列表实例和程序集列表实例中。
assemblies.Add(assembly);
addedAssemblyNames.Add(assembly.FullName);
}
}
/// <param name="addedAssemblyNames">1个字符串列表实例,该实例用存储当前程序中所有符合条件的程序集的全名字符串。</param>
/// <param name="assemblies">1个字符串列表实例,该实例用存储当前程序中所有符合条件的程序集的全名字符串。</param>
/// <summary>
/// 【加应用程序域程序集】
/// <remarks>
/// 摘要:
/// 把开发者设定的程序集加载到字符串列表实例和程序集列表实例中。
/// 注意:
/// 由于开发者设定的程序集中不包含任何成员,所以当前方法具有功能扩展作用,而不具有实现作用。
/// </remarks>
/// </summary>
protected virtual void AddConfiguredAssemblies(List<string> addedAssemblyNames, List<Assembly> assemblies)
{
foreach (var assemblyName in AssemblyNames)
{
var assembly = Assembly.Load(assemblyName);
if (addedAssemblyNames.Contains(assembly.FullName))
continue;
assemblies.Add(assembly);
addedAssemblyNames.Add(assembly.FullName);
}
}
/// <param name="assemblies">1个指定程序集的全名字符串。</param>
/// <summary>
/// 【匹配?】
/// <remarks>
/// 摘要:
/// 获取1个值false1个值false(不需要存储)/true(需要存储),该值指示1个指定程序集是否需要存储到字符串列表实例中(为反射方式实例提供数据支撑)。
/// <returns>
/// 返回:
/// 1个值false1个值false(不需要存储)/true(需要存储)
/// </returns>
/// </summary>
protected virtual bool Matches(string assemblyFullName)
{
return !Matches(assemblyFullName, AssemblySkipLoadingPattern)
&& Matches(assemblyFullName, AssemblyRestrictToLoadingPattern);
}
/// <summary>
/// 【匹配?】
/// <remarks>
/// 摘要:
/// 获取1个值false1个值false(不需要存储)/true(需要存储),该值指示1个指定程序集全名字符串是否符合1个由开发者定义的正则表达式进行定义的验证规则字符串。
/// <returns>
/// 返回:
/// 1个值false1个值false(不符合)/true(符合)
/// </returns>
/// </summary>
protected virtual bool Matches(string assemblyFullName, string pattern)
{
return Regex.IsMatch(assemblyFullName, pattern, RegexOptions.IgnoreCase | RegexOptions.Compiled);
}
/// <param name="directoryPath">当前程序启动项中程序集(*.dll)文件所在的文件夹(这里特指“..\WebApi\bin\Debug\net7.0”)。</param>
/// <summary>
/// 【匹配加载程序集】
/// <remarks>
/// 摘要:
/// 把当前程序启动项中所有符合条件的程序集实例(这里特指由开发者自定的具体实现类所在的程序集)存储到【应用程序域】实例中。
/// </summary>
protected virtual void LoadMatchingAssemblies(string directoryPath)
{
var loadedAssemblyNames = new List<string>();
foreach (var a in GetAssemblies())
{
loadedAssemblyNames.Add(a.FullName);
}
if (!_fileProvider.DirectoryExists(directoryPath))
{
return;
}
foreach (var dllPath in _fileProvider.GetFiles(directoryPath, "*.dll"))
{
try
{
var an = AssemblyName.GetAssemblyName(dllPath);
if (Matches(an.FullName) && !loadedAssemblyNames.Contains(an.FullName))
{
App.Load(an);
}
}
catch (BadImageFormatException ex)
{
Trace.TraceError(ex.ToString());
}
}
}
///<param name="openGeneric">1个被继承的指定泛型类/接口的类型实例(该类或接口必须是以泛型形式进行定义的)。</param>
///<param name="type">1个指定继承目标类/接口的类型实例。</param>
/// <summary>
/// 【继承于泛型?】
/// <remarks>
/// 摘要:
/// 获取1个值false(不继承于泛型)/true(继承于泛型),该值指示1个指定类/接口是否继承于1个指定的泛型类/接口。
/// 说明:
/// 1、由于“IsAssignableFrom”只能用于验证1个指定类/接口是否继承于1个指定的非泛型的类/接口,如果用于验证泛型的类/接口,则会一直返回:false。
/// 2、如果1个具体实现类继承了泛型类/接口,那么该具体实现类以反射方式实例化就需要调用该方法进行支撑。
/// </remarks>
/// <returns>
/// 返回:
/// 1个值false(不继承于泛型)/true(继承于泛型)。
/// </returns>
/// </summary>
protected virtual bool DoesTypeImplementOpenGeneric(Type type, Type openGeneric)
{
try
{
//获取1个被继承的指定泛型类/接口的类型实例。
var genericTypeDefinition = openGeneric.GetGenericTypeDefinition();
//依次对继承目标类/接口,中的所有被继承的类/接口的类型实例进行验证,只要所继承的目标类/接口中有1个是泛型的就返回:true。
foreach (var implementedInterface in type.FindInterfaces((objType, objCriteria) => true, null))
{
//如果被继承的类/接口的类型实例,不是泛型的,则继续验证继承目标类/接口中其它的被继承的类/接口的类型实例。
if (!implementedInterface.IsGenericType)
continue;
//如果被继承的类/接口的类型实例是泛型的,就立即返回:true,因为已经可以确认目标类/接口继承了指定泛型类/接口。
if (genericTypeDefinition.IsAssignableFrom(implementedInterface.GetGenericTypeDefinition()))
return true;
}
return false;
}
catch
{
return false;
}
}
/// <param name="assignTypeFrom">1个被继承的指定类/接口的类型实例(该类或接口可以是以泛型形式进行定义的)。</param>
/// <param name="assemblies">1个可枚举数实例,该实例存储着当前程序中所有的符合条件的程序集实例。</param>
/// <param name="onlyConcreteClasses">指示1个指定的类型实例,是否为具体实现类的类型实例(不是接口/抽象类的类型实例),默认值为:true,即是具体实现类的类型实例(不是接口/抽象类的类型实例)。 </param>
/// <summary>
/// 【查找类的类型】
/// <remarks>
/// 摘要:
/// 用于获取继承于1指定类/接口(该类或接口可以是以泛型形式进行定义的)所有具体实现类的类型实例,并把这些实例存储到可枚举数实例中。
/// </remarks>
/// <returns>
/// 返回:
/// 1个可枚举数实例,该实例存储着继承于1指定类/接口(该类或接口可以是以泛型形式进行定义的)所有具体实现类的类型实例。
/// </returns>
/// </summary>
protected virtual IEnumerable<Type> FindClassesOfType(Type assignTypeFrom, IEnumerable<Assembly> assemblies, bool onlyConcreteClasses = true)
{
var result = new List<Type>();
try
{
foreach (var a in assemblies)
{
Type[] types = null;
try
{
//把1个指定程序集中的所有类型实例存储到数组实例中。
types = a.GetTypes();
}
catch
{
//如果当前程序基于Entity Framework6框架实现则执行异常抛出操作,当前是非操作即不执行异常抛出操作。
if (!_ignoreReflectionErrors)
{
throw;
}
}
if (types == null)
continue;
//依次对1个指定程序集中的所有类型实例进行验证,如果类型实例具体实现类的类型实例(不是接口/抽象类的类型实例),则把该实例存储到列表实例中。
foreach (var t in types)
{
//如果1个指定类型实例不继承于指定的类或接口,且该类型实例不是以泛型形式进行定义的,或该类型实例不继承于以泛型形式进行定义的的类/接口,则对数组中的下一个1个指定类型实例进行验证。
if (!assignTypeFrom.IsAssignableFrom(t) && (!assignTypeFrom.IsGenericTypeDefinition || !DoesTypeImplementOpenGeneric(t, assignTypeFrom)))
continue;
//如果1个指定类型实例,是以接口形式进行定义的,则对数组中的下一个1个指定类型实例进行验证。
if (t.IsInterface)
continue;
//如果1个指定类型实例,是以类形式进行定义的。
if (onlyConcreteClasses)
{
//如果1个指定类型实例,是以类形式进行定义的而不以抽象类形式进行定义,则把该类型实例存储到可枚举数实例中。
if (t.IsClass && !t.IsAbstract)
{
result.Add(t);
}
}
else
{
result.Add(t);
}
}
}
}
catch (ReflectionTypeLoadException ex)
{
var msg = string.Empty;
foreach (var e in ex.LoaderExceptions)
msg += e!.Message + Environment.NewLine;
var fail = new Exception(msg, ex);
Debug.WriteLine(fail.Message, fail);
throw fail;
}
return result;
}
#endregion
#region 方法--接口实现
/// <typeparam name = "T"> 泛型类型实例(1个指定类的类型实例,这里特指:1个被继承的指定类/接口的类型实例(该类或接口可以是以泛型形式进行定义的))。</typeparam>
/// <param name="onlyConcreteClasses">指示1个指定的类型实例,是否为具体实现类的类型实例(不是接口/抽象类的类型实例),默认值为:true,即是具体实现类的类型实例(不是接口/抽象类的类型实例)。 </param>
/// <summary>
/// 【查找类的类型】
/// <remarks>
/// 摘要:
/// 用于获取继承于1指定类/接口(该类或接口可以是以泛型形式进行定义的)所有具体实现类的类型实例,并把这些实例存储到可枚举数实例中。
/// </remarks>
/// <returns>
/// 返回:
/// 1个可枚举数实例,该实例存储着继承于1指定类/接口(该类或接口可以是以泛型形式进行定义的)所有具体实现类的类型实例。
/// </returns>
/// </summary>
public IEnumerable<Type> FindClassesOfType<T>(bool onlyConcreteClasses = true)
{
return FindClassesOfType(typeof(T), onlyConcreteClasses);
}
/// <param name="assignTypeFrom">1个被继承的指定类/接口的类型实例(该类或接口可以是以泛型形式进行定义的)。</param>
/// <param name="onlyConcreteClasses">指示1个指定的类型实例,是否为具体实现类的类型实例(不是接口/抽象类的类型实例),默认值为:true,即是具体实现类的类型实例(不是接口/抽象类的类型实例)。 </param>
/// <summary>
/// 【查找类的类型】
/// <remarks>
/// 摘要:
/// 用于获取继承于1指定类/接口(该类或接口可以是以泛型形式进行定义的)所有具体实现类的类型实例,并把这些实例存储到可枚举数实例中。
/// </remarks>
/// <returns>
/// 返回:
/// 1个可枚举数实例,该实例存储着继承于1指定类/接口(该类或接口可以是以泛型形式进行定义的)所有具体实现类的类型实例。
/// </returns>
/// </summary>
public IEnumerable<Type> FindClassesOfType(Type assignTypeFrom, bool onlyConcreteClasses = true)
{
return FindClassesOfType(assignTypeFrom, GetAssemblies(), onlyConcreteClasses);
}
///<summary>
/// 【获取程序集】
/// <remarks>
/// 摘要:
/// 获取1个程序集列表实例,该实例中存储着当前程序启动项中所有符合条件的程序集实例(这里特指“..\WebApi\bin\Debug\net7.0”文件夹中的所有符合条件的*.dll文件,即由开发者自定的具体实现类所在的程序集)。
/// </remarks>
/// <returns>
/// 返回:
/// 1个程序集列表实例,该实例中存储着当前程序启动项中所有符合条件的程序集实例(这里特指“..\WebApi\bin\Debug\net7.0”文件夹中的所有符合条件的*.dll文件,即由开发者自定的具体实现类所在的程序集)。
/// </returns>
/// </summary>
public virtual IList<Assembly> GetAssemblies()
{
var addedAssemblyNames = new List<string>();
var assemblies = new List<Assembly>();
//必须获取当前程序启动项中所有符合条件的程序集。
if (LoadAppDomainAssemblies)
AddAssembliesInAppDomain(addedAssemblyNames, assemblies);
//该调用基本无用。
AddConfiguredAssemblies(addedAssemblyNames, assemblies);
return assemblies;
}
#endregion
}
}
10 Core.Infrastructure.WebAppTypeFinder
using System.Reflection;
namespace Core.Infrastructure
{
/// <summary>
/// 【Web应用程序域类型查找器--类】
/// <remarks>
/// 摘要:
/// 通过该类中的方法成员与变量成员来共同限定实现在程序运行期间是否需要重新把程序集存储到【应用程序域】实例中。
/// </remarks>
/// </summary>
public class WebAppTypeFinder : AppDomainTypeFinder
{
#region 变量--私有/保护
/// <summary>
/// 【已经加载bin文件夹中程序集?】
/// <remarks>
/// 摘要:
/// 设置1个值false(隐式默认值:需要)/true(不需要),该值指示是否需要重新把程序集存储到【应用程序域】实例中。
/// 说明:
/// 由于该变量成员被隐式默认实例化为:false,所在程序第1次启动运行时必须需要把程序集存储到【应用程序域】实例中,其它的时候则不必。
/// </remarks>
/// </summary>
private bool _binFolderAssembliesLoaded;
#endregion
#region 拷贝构造方法
/// <summary>
/// 【拷贝构造方法】
/// <param name="fileProvider">自定义文件提供程序接口的1个指定实例,默认值为:null,即该参数实例还没有执行实例化操作。</param>
/// <remarks>
/// 摘要:
/// 通过该拷贝构造方法,对当前类中的自定义文件提供程序接口变量成员进行实例化。
/// </remarks>
/// </summary>
public WebAppTypeFinder(INopFileProvider fileProvider = null) : base(fileProvider)
{
}
#endregion
#region 属性
/// <summary>
/// 【确认已经加载bin文件夹中程序集?】
/// <remarks>
/// 摘要:
/// 获取/设置1个值false(不需要)/true(默认值:需要),该值指示是否需要重新把程序集存储到【应用程序域】实例中。
/// 说明:
/// 1、由于该属性成员被默认实例化为:true,在整个程序中无覆盖性赋值操作,且只有1次的非操作调用,所以是不需要重新把程序集存储到【应用程序域】实例中。
/// 2、该属性成员基本无用,本从认为可以被删除,并重构其只有1次的非操作调用。
/// </remarks>
/// </summary>
public bool EnsureBinFolderAssembliesLoaded { get; set; } = true;
#endregion
#region 方法
/// <summary>
/// 【获取Bin目录】
/// <remarks>
/// 摘要:
/// 获取当前程序启动项程序集(*.dll)文件所在目录(文件夹)的绝对路径字符串(这里特指“..\WebApi\bin\Debug\net7.0”)。
/// <returns>
/// 返回:
/// 当前程序启动项程序集(*.dll)文件所在目录(文件夹)的绝对路径字符串(这里特指“..\WebApi\bin\Debug\net7.0”)。
/// </returns>
/// </remarks>
public virtual string GetBinDirectory()
{
return AppContext.BaseDirectory;
}
///<summary>
/// 【获取程序集】
/// <remarks>
/// 摘要:
/// 获取1个程序集列表实例,该实例中存储着当前程序启动项中所有符合条件的程序集实例(这里特指“..\WebApi\bin\Debug\net7.0”文件夹中的所有符合条件的*.dll文件,即由开发者自定的具体实现类所在的程序集)。
/// </remarks>
/// <returns>
/// 返回:
/// 1个程序集列表实例,该实例中存储着当前程序启动项中所有符合条件的程序集实例(这里特指“..\WebApi\bin\Debug\net7.0”文件夹中的所有符合条件的*.dll文件,即由开发者自定的具体实现类所在的程序集)。
/// </returns>
/// </summary>
public override IList<Assembly> GetAssemblies()
{
//由于“EnsureBinFolderAssembliesLoaded”属性成员的实例值会值总是为:true;所以是否执行下行的语句只取决于“_binFolderAssembliesLoaded”变量成员的实例值。
//如果“_binFolderAssembliesLoaded”变量成员的实例值为:true,则直接执行“AppDomainTypeFinder.GetAssemblies()”方法。
if (!EnsureBinFolderAssembliesLoaded || _binFolderAssembliesLoaded)
return base.GetAssemblies();
//如果“_binFolderAssembliesLoaded”变量成员的实例值为:false,则设定“_binFolderAssembliesLoaded”变量成员的实例值为:true。
_binFolderAssembliesLoaded = true;
//如果“_binFolderAssembliesLoaded”变量成员的实例值为:false,则必须把前程序启动项中所有符合条件的程序集实例(这里特指由开发者自定的具体实现类所在的程序集)存储到【应用程序域】实例中;
//并执行“AppDomainTypeFinder.GetAssemblies()”方法。
var binPath = GetBinDirectory();
LoadMatchingAssemblies(binPath);
return base.GetAssemblies();
}
#endregion
}
}
对以上功能更为具体实现和注释见:230115_010shopDemo(为程序集反射方式实例化支撑之ITypeFinder)。