C 開發輔助類庫,和士官長一樣身經百戰且越戰越勇的戰爭機器,能力無人能出其右。 GitHub: "MasterChief" 歡迎Star,歡迎Issues; 項目架構思維導圖: 目錄 ================= "1\. 資料庫訪問" "2\. 日誌" "3\. 緩存" "4\. 配置" " ...
C# 開發輔助類庫,和士官長一樣身經百戰且越戰越勇的戰爭機器,能力無人能出其右。
GitHub:MasterChief 歡迎Star,歡迎Issues;
項目架構思維導圖:
目錄
1. 資料庫訪問
a. 支持Dapper和Entity Framework 兩種ORM框架;
b. 通過IOC可以很少代碼在Dapper和Entity Framework切換;
c. 實現Repository和UnitOfWork;
d. CURD以及事務實現簡單,很大程度關註業務實現即可;
代碼使用說明:
- Create 添加
public bool Create(EFSample samle)
{
using (IDbContext dbcontext = _contextFactory.Create())
{
return dbcontext.Create<EFSample>(samle);
}
}
- Delete 刪除
public bool Delete(EFSample sample)
{
using (IDbContext dbcontext = _contextFactory.Create())
{
return dbcontext.Delete(sample);
}
}
- Update 修改
public bool Update(EFSample sample)
{
using (IDbContext dbcontext = _contextFactory.Create())
{
return dbcontext.Update(sample);
}
}
- GetByKeyID 根據主鍵查詢
public EFSample GetByKeyID(Guid id)
{
using (IDbContext dbcontext = _contextFactory.Create())
{
return dbcontext.GetByKeyID<EFSample>(id);
}
}
- GetList 條件查詢集合
public List<EFSample> GetList(Expression<Func<EFSample, bool>> predicate = null)
{
using (IDbContext dbcontext = _contextFactory.Create())
{
return dbcontext.GetList<EFSample>(predicate);
}
}
- Exist 條件查詢是否存在
public bool Exist(Expression<Func<EFSample, bool>> predicate = null)
{
using (IDbContext dbcontext = _contextFactory.Create())
{
return dbcontext.Exist<EFSample>(predicate);
}
}
- SqlQuery 執行Sql腳本
public List<EFSample> SqlQuery(string sql, DbParameter[] parameter)
{
using (IDbContext dbcontext = _contextFactory.Create())
{
return dbcontext.SqlQuery<EFSample>(sql, parameter)?.ToList();
}
}
- CreateWithTransaction 事務處理
public bool CreateWithTransaction(EFSample sample, EFSample sample2)
{
bool result = true;
using (IDbContext dbcontext = _contextFactory.Create())
{
try
{
dbcontext.BeginTransaction();//開啟事務
dbcontext.Create(sample);
dbcontext.Create(sample2);
dbcontext.Commit();
}
catch (Exception)
{
dbcontext.Rollback();
result = false;
}
}
return result;
}
- GetFirstOrDefault 條件查詢第一項或預設數據
public EFSample GetFirstOrDefault(Expression<Func<EFSample, bool>> predicate = null)
{
using (IDbContext dbcontext = _contextFactory.Create())
{
return dbcontext.GetFirstOrDefault<EFSample>(predicate);
}
}
- 單元測試以及Sql Server腳本
using MasterChief.DotNet.Core.DapperTests;
using MasterChief.DotNet.Core.DapperTests.Model;
using MasterChief.DotNet.Core.DapperTests.Service;
using MasterChief.DotNet4.Utilities.Common;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Ninject;
using System;
using System.Collections.Generic;
using System.Data.Common;
using System.Data.SqlClient;
using System.Threading.Tasks;
namespace MasterChief.DotNet.Core.Dapper.Tests
{
[TestClass()]
public class SampleServiceTests
{
private IKernel _kernel = null;
private ISampleService _sampleService = null;
private readonly Guid _testID = "2F6D3C43-C2C7-4398-AD2B-ED5E82D78888".ToGuidOrDefault(Guid.Empty);
private readonly string _testName = "DapperSample";
[TestInitialize]
public void SetUp()
{
_kernel = new StandardKernel(new ServiceModule());
Assert.IsNotNull(_kernel);
_sampleService = _kernel.Get<ISampleService>();
if (!_sampleService.Exist(ent => ent.ID == _testID))
{
_sampleService.Create(new EFSample() { UserName = _testName, ID = _testID });
}
}
/// <summary>
/// 創建測試
/// </summary>
[TestMethod()]
public void CreateTest()
{
bool actual = _sampleService.Create(new EFSample() { UserName = "Dapper" + DateTime.Now.ToString("MMddHHmmss") });
Assert.IsTrue(actual);
}
[TestMethod()]
public void GetFirstOrDefaultTest()
{
EFSample actual = _sampleService.GetFirstOrDefault(ent => ent.ID == _testID);
Assert.IsNotNull(actual);
}
[TestMethod()]
public void GetByKeyIdTest()
{
EFSample actual = _sampleService.GetByKeyID(_testID);
Assert.IsNotNull(actual);
}
[TestMethod()]
public void DeleteTest()
{
bool actual = _sampleService.Delete(new EFSample() { ID = _testID });
Assert.IsTrue(actual);
}
[TestMethod()]
public void GetListTest()
{
List<EFSample> actual = _sampleService.GetList(ent => ent.Available == true);
Assert.IsNotNull(actual);
CollectionAssert.AllItemsAreNotNull(actual);
}
[TestMethod()]
public void UpdateTest()
{
EFSample sample = new EFSample
{
ID = _testID,
ModifyTime = DateTime.Now,
UserName = "modify"
};
bool actual = _sampleService.Update(sample);
Assert.IsNotNull(actual);
}
[TestMethod()]
public void TransactionSuccessTest()
{
EFSample sample = new EFSample
{
UserName = "TransactionSuccess1"
};
EFSample sample2 = new EFSample
{
UserName = "TransactionSuccess2"
};
bool actual = _sampleService.CreateWithTransaction(sample, sample2);
Assert.IsTrue(actual);
}
[TestMethod()]
public void TransactionFailTest()
{
EFSample sample3 = new EFSample
{
UserName = "TransactionSuccess3"
};
EFSample sample4 = new EFSample
{
UserName = null
};
bool actual = _sampleService.CreateWithTransaction(sample3, sample4);
Assert.IsFalse(actual);
}
[TestMethod()]
public void ExistTest()
{
bool actual = _sampleService.Exist(ent => ent.ID == _testID);
Assert.IsTrue(actual);
actual = _sampleService.Exist(ent => ent.UserName == _testName);
Assert.IsTrue(actual);
actual = _sampleService.Exist(ent => ent.CreateTime >= DateTime.Now.AddDays(-1));
Assert.IsTrue(actual);
actual = _sampleService.Exist(ent => ent.CreateTime <= DateTime.Now);
Assert.IsTrue(actual);
actual = _sampleService.Exist(ent => ent.Available == true);
Assert.IsTrue(actual);
actual = _sampleService.Exist(ent => ent.Available != true);
Assert.IsFalse(actual);
}
[TestMethod()]
public void SqlQueryTest()
{
string sql = @"select * from [dbo].[EFSample]
where CreateTime>=@CreateTime
and Available=@Available
order by CreateTime desc";
DbParameter[] parameter = {
new SqlParameter(){ ParameterName="@CreateTime", Value=DateTime.Now.AddDays(-1) },
new SqlParameter(){ ParameterName="@Available", Value=true }
};
List<EFSample> actual = _sampleService.SqlQuery(sql, parameter);
Assert.IsNotNull(actual);
CollectionAssert.AllItemsAreNotNull(actual);
}
/// <summary>
/// 多線程測試
/// </summary>
[TestMethod()]
public void CreateTestThreadTest()
{
Task[] tasks = {
Task.Factory.StartNew(() => CreateTest()),
Task.Factory.StartNew(() => CreateTest()),
Task.Factory.StartNew(() => CreateTest()),
Task.Factory.StartNew(() => CreateTest()),
Task.Factory.StartNew(() => CreateTest()),
Task.Factory.StartNew(() => CreateTest()),
Task.Factory.StartNew(() => CreateTest()),
Task.Factory.StartNew(() => CreateTest()),
Task.Factory.StartNew(() => CreateTest()),
Task.Factory.StartNew(() => CreateTest()),
};
Task.WaitAll(tasks);
}
}
}
USE [Sample]
GO
/****** Object: Table [dbo].[EFSample] Script Date: 2019/3/9 22:04:45 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[EFSample](
[ID] [uniqueidentifier] NOT NULL,
[CreateTime] [datetime] NOT NULL,
[ModifyTime] [datetime] NOT NULL,
[Available] [bit] NOT NULL,
[UserName] [nvarchar](20) NOT NULL,
CONSTRAINT [EFSamle_PK] PRIMARY KEY CLUSTERED
(
[ID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
EXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=N'' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'EFSample', @level2type=N'COLUMN',@level2name=N'UserName'
GO
2. 日誌
a. 目前實現基於Log4Net的本地文件日誌以及Kafka ELK的日誌;
b. 基於介面ILogService可以很容易擴展其他日誌顯示;
代碼使用說明
- 配置依賴註入,日誌實現方式,這裡採用文件日誌形式
using MasterChief.DotNet.Core.Log;
using Ninject.Modules;
namespace MasterChief.DotNet.Core.LogTests
{
public sealed class LogModule : NinjectModule
{
public override void Load()
{
Bind<ILogService>().To<FileLogService>().InSingletonScope();
}
}
}
- 拷貝日誌config文件到項目內,並設置屬性“始終複製”到輸出目錄,您可以根據項目需求調整config內容
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler" />
</configSections>
<log4net>
<!-- FileLogger -->
<logger name="FATAL_FileLogger">
<level value="ALL" />
<appender-ref ref="FATAL_FileAppender" />
</logger>
<logger name="ERROR_FileLogger">
<level value="ALL" />
<appender-ref ref="ERROR_FileAppender" />
</logger>
<logger name="WARN_FileLogger">
<level value="ALL" />
<appender-ref ref="WARN_FileAppender" />
</logger>
<logger name="INFO_FileLogger">
<level value="ALL" />
<appender-ref ref="INFO_FileAppender" />
</logger>
<logger name="DEBUG_FileLogger">
<level value="ALL" />
<appender-ref ref="DEBUG_FileAppender" />
</logger>
<!-- AdoNetLogger -->
<!--<logger name="AdoNetLogger">
<level value="ALL" />
<appender-ref ref="AdoNetAppender" />
</logger>-->
<!-- ConsoleLogger -->
<logger name="ConsoleLogger">
<level value="ALL" />
<appender-ref ref="ColoredConsoleAppender" />
</logger>
<!--使用Rolling方式記錄日誌按照日來記錄日誌-->
<appender name="FATAL_FileAppender" type="log4net.Appender.RollingFileAppender">
<!--文件名,可以相對路徑,也可以絕對路徑,這裡只給定了文件夾-->
<file value=".\log\\FATAL\\" />
<!--是否增加文件-->
<appendToFile value="true" />
<maxSizeRollBackups value="5" />
<!--日誌追加類型,Date為按日期增加文件,Size為按大小-->
<rollingStyle value="Date" />
<!--最小鎖定模型以允許多個進程可以寫入同一個文件,解決文件獨占問題-->
<lockingModel type="log4net.Appender.FileAppender+MinimalLock" />
<!--最大文件大小-->
<maximumFileSize value="10MB" />
<!--文件命名格式,非日期參數化要進行轉義,如自定義文件尾碼-->
<datePattern value="yyyyMM\\yyyy-MM-dd".log"" />
<!--是否固定文件名-->
<staticLogFileName value="false" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="---------------------------------------------------%newline發生時間:%date %newline事件級別:%-5level %newline事件來源:%logger%newline日誌內容:%message%newline" />
</layout>
</appender>
<appender name="ERROR_FileAppender" type="log4net.Appender.RollingFileAppender">
<!--文件名,可以相對路徑,也可以絕對路徑,這裡只給定了文件夾-->
<file value=".\log\\ERROR\\" />
<!--是否增加文件-->
<appendToFile value="true" />
<maxSizeRollBackups value="5" />
<!--日誌追加類型,Date為按日期增加文件,Size為按大小-->
<rollingStyle value="Date" />
<!--最小鎖定模型以允許多個進程可以寫入同一個文件,解決文件獨占問題-->
<lockingModel type="log4net.Appender.FileAppender+MinimalLock" />
<!--最大文件大小-->
<maximumFileSize value="10MB" />
<!--文件命名格式,非日期參數化要進行轉義,如自定義文件尾碼-->
<datePattern value="yyyyMM\\yyyy-MM-dd".log"" />
<!--是否固定文件名-->
<staticLogFileName value="false" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="---------------------------------------------------%newline發生時間:%date %newline事件級別:%-5level %newline事件來源:%logger%newline日誌內容:%message%newline" />
</layout>
</appender>
<appender name="WARN_FileAppender" type="log4net.Appender.RollingFileAppender">
<!--文件名,可以相對路徑,也可以絕對路徑,這裡只給定了文件夾-->
<file value=".\log\\WARN\\" />
<!--是否增加文件-->
<appendToFile value="true" />
<maxSizeRollBackups value="5" />
<!--日誌追加類型,Date為按日期增加文件,Size為按大小-->
<rollingStyle value="Date" />
<!--最小鎖定模型以允許多個進程可以寫入同一個文件,解決文件獨占問題-->
<lockingModel type="log4net.Appender.FileAppender+MinimalLock" />
<!--最大文件大小-->
<maximumFileSize value="10MB" />
<!--文件命名格式,非日期參數化要進行轉義,如自定義文件尾碼-->
<datePattern value="yyyyMM\\yyyy-MM-dd".log"" />
<!--是否固定文件名-->
<staticLogFileName value="false" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="---------------------------------------------------%newline發生時間:%date %newline事件級別:%-5level %newline事件來源:%logger%newline日誌內容:%message%newline" />
</layout>
</appender>
<appender name="INFO_FileAppender" type="log4net.Appender.RollingFileAppender">
<!--文件名,可以相對路徑,也可以絕對路徑,這裡只給定了文件夾-->
<file value=".\log\\INFO\\" />
<!--是否增加文件-->
<appendToFile value="true" />
<maxSizeRollBackups value="5" />
<!--日誌追加類型,Date為按日期增加文件,Size為按大小-->
<rollingStyle value="Date" />
<!--最小鎖定模型以允許多個進程可以寫入同一個文件,解決文件獨占問題-->
<lockingModel type="log4net.Appender.FileAppender+MinimalLock" />
<!--最大文件大小-->
<maximumFileSize value="10MB" />
<!--文件命名格式,非日期參數化要進行轉義,如自定義文件尾碼-->
<datePattern value="yyyyMM\\yyyy-MM-dd".log"" />
<!--是否固定文件名-->
<staticLogFileName value="false" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="---------------------------------------------------%newline發生時間:%date %newline事件級別:%-5level %newline事件來源:%logger%newline日誌內容:%message%newline" />
</layout>
</appender>
<appender name="DEBUG_FileAppender" type="log4net.Appender.RollingFileAppender">
<!--文件名,可以相對路徑,也可以絕對路徑,這裡只給定了文件夾-->
<file value=".\log\\DEBUG\\" />
<!--是否增加文件-->
<appendToFile value="true" />
<maxSizeRollBackups value="5" />
<!--日誌追加類型,Date為按日期增加文件,Size為按大小-->
<rollingStyle value="Date" />
<!--最小鎖定模型以允許多個進程可以寫入同一個文件,解決文件獨占問題-->
<lockingModel type="log4net.Appender.FileAppender+MinimalLock" />
<!--最大文件大小-->
<maximumFileSize value="10MB" />
<!--文件命名格式,非日期參數化要進行轉義,如自定義文件尾碼-->
<datePattern value="yyyyMM\\yyyy-MM-dd".log"" />
<!--是否固定文件名-->
<staticLogFileName value="false" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="---------------------------------------------------%newline發生時間:%date %newline事件級別:%-5level %newline事件來源:%logger%newline日誌內容:%message%newline" />
</layout>
</appender>
<!--使用AdoNetAppender方式記錄日誌按照日來記錄日誌-->
<!--<appender name="AdoNetAppender" type="log4net.Appender.AdoNetAppender">
<bufferSize value="1" />
<connectionType value="System.Data.SqlClient.SqlConnection, System.Data, Version=1.0.3300.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
<connectionString value="DATABASE=Sample;SERVER=.\SQLEXPRESS;UID=sa;PWD=sasa;Connect Timeout=15;" />
<commandText value="INSERT INTO [Log4Net] ([Date],[Host],[Thread],[Level],[Logger],[Message],[Exception]) VALUES (@log_date, @host, @thread, @log_level, @logger, @message, @exception)" />
<parameter>
<parameterName value="@log_date" />
<dbType value="DateTime" />
<layout type="log4net.Layout.RawTimeStampLayout" />
</parameter>
<parameter>
<parameterName value="@thread" />
<dbType value="String" />
<size value="255" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%thread" />
</layout>
</parameter>
<parameter>
<parameterName value="@host" />
<dbType value="String" />
<size value="50" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%property{log4net:HostName}" />
</layout>
</parameter>
<parameter>
<parameterName value="@log_level" />
<dbType value="String" />
<size value="50" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%level" />
</layout>
</parameter>
<parameter>
<parameterName value="@logger" />
<dbType value="String" />
<size value="255" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%logger" />
</layout>
</parameter>
<parameter>
<parameterName value="@message" />
<dbType value="String" />
<size value="4000" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%message" />
</layout>
</parameter>
<parameter>
<parameterName value="@exception" />
<dbType value="String" />
<size value="4000" />
<layout type="log4net.Layout.ExceptionLayout" />
</parameter>
</appender>-->
<!--使用ConsoleAppender方式記錄日誌按照日來記錄日誌-->
<appender name="ColoredConsoleAppender" type="log4net.Appender.ColoredConsoleAppender">
<mapping>
<level value="INFO" />
<foreColor value="White, HighIntensity" />
<backColor value="Green" />
</mapping>
<mapping>
<level value="DEBUG" />
<foreColor value="White, HighIntensity" />
<backColor value="Blue" />
</mapping>
<mapping>
<level value="WARN" />
<foreColor value="Yellow, HighIntensity" />
<backColor value="Purple" />
</mapping>
<mapping>
<level value="ERROR" />
<foreColor value="Yellow, HighIntensity" />
<backColor value="Red" />
</mapping>
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="---------------------------------------------------%newline發生時間:%date %newline事件級別:%-5level%newline事件來源:%logger%newline事件行號:%line%newline日誌內容:%message%newline" />
</layout>
</appender>
<appender name="UdpAppender" type="log4net.Appender.UdpAppender">
<remoteAddress value="127.0.0.1" />
<remotePort value="7071" />
<layout type="log4net.Layout.XmlLayoutSchemaLog4j" />
</appender>
<root>
<appender-ref ref="UdpAppender" />
</root>
</log4net>
</configuration>
- 單元測試
using MasterChief.DotNet.Core.LogTests;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Ninject;
namespace MasterChief.DotNet.Core.Log.Tests
{
[TestClass()]
public class FileLogServiceTests
{
private IKernel _kernel = null;
private ILogService _logService = null;
[TestInitialize]
public void SetUp()
{
_kernel = new StandardKernel(new LogModule());
Assert.IsNotNull(_kernel);
_logService = _kernel.Get<ILogService>();
}
[TestMethod()]
public void DebugTest()
{
_logService.Debug("DebugTest");
}
[TestMethod()]
public void ErrorTest()
{
_logService.Error("ErrorTest");
}
[TestMethod()]
public void FatalTest()
{
_logService.Fatal("FatalTest");
}
[TestMethod()]
public void InfoTest()
{
_logService.Info("InfoTest");
}
[TestMethod()]
public void WarnTest()
{
_logService.Warn("WarnTest");
}
}
}
3. 緩存
a. 支持本地記憶體緩存,HttpRequest請求緩存,Redis緩存;
b. 基於ICacheProvider介面,可以很容易擴展其他緩存實現;
代碼使用說明:
配置依賴註入,緩存實現方式,這裡採用LocalCacheProvider緩存實現;
using MasterChief.DotNet.Core.Cache; using Ninject.Modules; namespace MasterChief.DotNet.Core.CacheTests { public sealed class CacheModule : NinjectModule { public override void Load() { Bind<ICacheProvider>().To<LocalCacheProvider>().InSingletonScope(); } } }
單元測試
using MasterChief.DotNet.Core.CacheTests; using Microsoft.VisualStudio.TestTools.UnitTesting; using Ninject; namespace MasterChief.DotNet.Core.Cache.Tests { [TestClass()] public class LocalCacheProviderTests { private IKernel _kernel = null; private ICacheProvider _cacheProvider = null; private readonly string _testCacheKey = "sampleKey"; private readonly string _testCache = "sample"; private readonly string _testKeyFormat = "login_{0}"; [TestInitialize] public void SetUp() { _kernel = new StandardKernel(new CacheModule()); Assert.IsNotNull(_kernel); _cacheProvider = _kernel.Get<ICacheProvider>(); _cacheProvider.Set(_testCacheKey, _testCache, 10); } [TestMethod()] public void GetTest() { string actual = _cacheProvider.Get<string>(_testCacheKey); Assert.AreEqual(_testCache, actual); } [TestMethod()] public void IsSetTest() { bool actual = _cacheProvider.IsSet(_testCacheKey); Assert.IsTrue(actual); } [TestMethod()] public void RemoveTest() { _cacheProvider.Remove(_testCacheKey); bool actual = _cacheProvider.IsSet(_testCacheKey); Assert.IsFalse(actual); } [TestMethod()] public void RemoveByPatternTest() { string _loginKey = string.Format(_testKeyFormat, "123"); _cacheProvider.Set(_loginKey, _testCache, 10); bool actual = _cacheProvider.IsSet(_loginKey); Assert.IsTrue(actual); _cacheProvider.RemoveByPattern(_testKeyFormat); actual = _cacheProvider.IsSet(_loginKey); Assert.IsFalse(actual); actual = _cacheProvider.IsSet(_testCacheKey); Assert.IsTrue(actual); } [TestMethod()] public void SetTest() { _cacheProvider.Set("sampleSetKey", "sampleSetCache", 10); bool actual = _cacheProvider.IsSet("sampleSetKey"); Assert.IsTrue(actual); } } }
4. 配置
a. 目前支持配置文件本地持久化,並且支持配置文件緩存依賴減少讀取文件次數;
b. 基於IConfigProvider介面,可以很容易擴展其他配置實現;
代碼使用說明:
配置依賴註入,配置實現方式,這裡採用FileConfigProvider緩存實現;
using MasterChief.DotNet.Core.Config; using Ninject.Modules; namespace MasterChief.DotNet.Core.ConfigTests { public sealed class ConfigModule : NinjectModule { public override void Load() { Bind<IConfigProvider>().To<FileConfigService>().InSingletonScope(); // Bind<ConfigContext>().ToSelf().InSingletonScope(); Bind<ConfigContext>().To<CacheConfigContext>().InSingletonScope(); } } }
擴展配置上下文基於文件依賴
using MasterChief.DotNet.Core.Config; using MasterChief.DotNet4.Utilities.WebForm.Core; using System; using System.Web.Caching; namespace MasterChief.DotNet.Core.ConfigTests { public sealed class CacheConfigContext : ConfigContext { public override T Get<T>(string index = null) { if (!(base.ConfigService is FileConfigService)) { throw new NotSupportedException("CacheConfigContext"); } string filePath = GetClusteredIndex<T>(index); string key = filePath; object cacheContent = CacheManger.Get(key); if (cacheContent != null) { return (T)cacheContent; } T value = base.Get<T>(index); CacheManger.Set(key, value, new CacheDependency(filePath)); return value; } } }
單元測試
using MasterChief.DotNet.Core.ConfigTests; using Microsoft.VisualStudio.TestTools.UnitTesting; using Ninject; using System.Collections.Generic; namespace MasterChief.DotNet.Core.Config.Tests { [TestClass()] public class FileConfigServiceTests { private IKernel _kernel = null; private IConfigProvider _configProvider = null; public ConfigContext _configContext = null; [TestInitialize] public void SetUp() { _kernel = new StandardKernel(new ConfigModule()); Assert.IsNotNull(_kernel); _configProvider = _kernel.Get<IConfigProvider>(); _configContext = _kernel.Get<ConfigContext>(); } [TestMethod()] public void SaveConfigTest() { RedisConfig redisConfig = new RedisConfig { AutoStart = true, LocalCacheTime = 10, MaxReadPoolSize = 1024, MaxWritePoolSize = 1024, ReadServerList = "10", RecordeLog = true, WriteServerList = "10" }; redisConfig.RedisItems = new List<RedisItemConfig> { new RedisItemConfig() { Text = "MasterChief" }, new RedisItemConfig() { Text = "Config." } }; _configContext.Save(redisConfig, "prod"); _configContext.Save(redisConfig, "alpha"); RedisConfig prodRedisConfig = _configContext.Get<RedisConfig>("prod"); Assert.IsNotNull(prodRedisConfig); prodRedisConfig = _configContext.Get<RedisConfig>("prod");//文件緩存測試 Assert.IsNotNull(prodRedisConfig); RedisConfig alphaRedisConfig = _configContext.Get<RedisConfig>("alpha"); Assert.IsNotNull(alphaRedisConfig); DaoConfig daoConfig = new DaoConfig { Log = "server=localhost;database=Sample;uid=sa;pwd=sasa" }; _configContext.Save(daoConfig, "prod"); _configContext.Save(daoConfig, "alpha"); DaoConfig prodDaoConfig = _configContext.Get<DaoConfig>("prod"); Assert.IsNotNull(prodDaoConfig); DaoConfig alphaDaoConfig = _configContext.Get<DaoConfig>("alpha"); Assert.IsNotNull(alphaDaoConfig); } } }
本地配置會在程式根目錄Config下,如圖:
配置文件基於XML持久化存儲,如圖:
5. 驗證碼
a. 派生實現ValidateCodeType抽象類,來自定義驗證碼樣式;
b. 派生實現VerifyCodeHandler抽象類,快速切換需要顯示驗證碼;
代碼使用說明:
Mvc 簡單使用如下:
/// <summary> /// 處理生成Mvc 程式驗證碼 /// </summary> public sealed class MvcVerifyCodeHandler : VerifyCodeHandler { public override void OnValidateCodeCreated(HttpContext context, string validateCode) { context.Session["validateCode"] = validateCode; } public override byte[] CreateValidateCode(string style) { ValidateCodeType createCode; switch (style) { case "type1": createCode = new ValidateCode_Style1(); break; default: createCode = new ValidateCode_Style1(); break; } var buffer = createCode.CreateImage(out var validateCode); OnValidateCodeCreated(HttpContext.Current, validateCode); return buffer; } }
WebForm 簡單使用如下:
/// <summary> /// WebFormVerifyCodeHandler 的摘要說明 /// </summary> public class WebFormVerifyCodeHandler : VerifyCodeHandler, IHttpHandler, IRequiresSessionState { public void ProcessRequest(HttpContext context) { var validateType = context.Request.Params["style"]; var buffer = CreateValidateCode(validateType); context.Response.ClearContent(); context.Response.ContentType = MimeTypes.ImageGif; context.Response.BinaryWrite(buffer); } public bool IsReusable => false; public override void OnValidateCodeCreated(HttpContext context, string validateCode) { context.Session["validateCode"] = validateCode; } public override byte[] CreateValidateCode(string style) { style = style?.Trim(); ValidateCodeType createCode; switch (style) { case "type1": createCode = new ValidateCode_Style1(); break; default: createCode = new ValidateCode_Style1(); break; } var buffer = createCode.CreateImage(out var validateCode); OnValidateCodeCreated(HttpContext.Current, validateCode); return buffer; } }
6. 序列化與反序列化
a. 目前支持Json以及Protobuf兩種方式的序列化與反序列化
b. 可以通過實現介面ISerializer擴展實現其他方式;
代碼使用說明:
private static void Main()
{
SampleSerializer(new JsonSerializer());
Console.WriteLine(Environment.NewLine);
SampleSerializer(new ProtocolBufferSerializer());
Console.ReadLine();
}
private static void SampleSerializer(ISerializer serializer)
{
#region 單個對象序列化與反序列化
var person = new Person();
person.Age = 10;
person.FirstName = "yan";
person.LastName = "zhiwei";
person.Remark = "ISerializer Sample";
var jsonText = serializer.Serialize(person);
Console.WriteLine($"{serializer.GetType().Name}-Serialize" + jsonText);
var getPerson = serializer.Deserialize<Person>(jsonText);
Console.WriteLine($"{serializer.GetType().Name}-Deserialize" + getPerson);
#endregion
#region 集合序列化與反序列化
var persons = new List<Person>();
for (var i = 0; i < 10; i++)
persons.Add(new Person
{
FirstName = "Yan",
Age = 20 + i,
LastName = "Zhiwei",
Remark = DateTime.Now.ToString(CultureInfo.InvariantCulture)
});
jsonText = serializer.Serialize(persons);
Console.WriteLine($"{serializer.GetType().Name}-Serialize" + jsonText);
var getPersons = serializer.Deserialize<List<Person>>(jsonText);
foreach (var item in getPersons)
Console.WriteLine($"{serializer.GetType().Name}-Deserialize" + item);
#endregion
}
7. EXCEL導入導出
a. 基於Npoi實現,可以基於介面IExcelManger擴展實現諸如MyXls等;
b. 目前實現了將Excel導出DataTable和DataTable導出到Excel文件;
c. 後續完善諸如整個Excel文件導入導出等;
代碼使用說明:
將DataTable導出到Excel文件
private void BtnToExcel_Click(object sender, EventArgs e) { var mockTable = BuilderExcelData(); _mockExcelPath = $"D:\\ExcelSample{DateTime.Now.FormatDate(12)}.xls"; _excelManger.ToExcel(mockTable, "員工信息彙總", "員工列表", _mockExcelPath); Process.Start(_mockExcelPath); } private DataTable BuilderExcelData() { var mockTable = new DataTable(); mockTable.Columns.Add(new DataColumn {ColumnName = "序號"}); mockTable.Columns.Add(new DataColumn {ColumnName = "姓名"}); mockTable.Columns.Add(new DataColumn {ColumnName = "工作單位"}); mockTable.Columns.Add(new DataColumn {ColumnName = "性別"}); mockTable.Columns.Add(new DataColumn {ColumnName = "入職時間"}); for (var i = 0; i < 100; i++) mockTable.Rows.Add(i.ToString(), $"張{i}", $"李{i}電腦公司", i % 2 == 0 ? "男" : "女", DateTime.Now.AddDays(i)); return mockTable; }
將Excel文件導出DataTable
private void BtnToDataTable_Click(object sender, EventArgs e) { if (string.IsNullOrEmpty(_mockExcelPath)) { MessageBox.Show("請生成模擬測試EXCEL文件"); return; } var excleTable = _excelManger.ToDataTable(_mockExcelPath, 0, 1, 2); var jsonText = _jsonSerializer.Serialize(excleTable); MessageBox.Show(jsonText); }
8. 文件下載
a.支持下載文件加密;
b.支持下載自定義限速;
c.通過DownloadHandler抽象類實現擴展諸如在Asp.Net Mvc實現;
代碼使用說明:
文件下載配置文件
<?xml version="1.0" encoding="utf-16"?> <DownloadConfig xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" FileNameEncryptorIv="0102030405060708090a0a0c0d010208" FileNameEncryptorKey="DotnetDownloadConfig" LimitDownloadSpeedKb="1024" DownLoadMainDirectory="D:\OneDrive\軟體\工具\"> </DownloadConfig>
在WebForm實現DownloadHandler抽象類,迅速實現文件下載
public class FileDownloadHandler : DownloadHandler, IHttpHandler { public void ProcessRequest(HttpContext context) { var fileName = context.Request["fileName"]; StartDownloading(context, fileName); } public bool IsReusable => false; public override void OnDownloadFailed(HttpContext context, string fileName, string filePath, string ex) { context.Response.Write(ex); } public override void OnDownloadSucceed(HttpContext context, string fileName, string filePath) { var result = $"文件[{fileName}]下載成功,映射路徑:{filePath}"; context.Response.Write(result); } }
修改Web.Config 文件
<system.web> <compilation debug="true" targetFramework="4.5"/> <httpRuntime targetFramework="4.5"/> <httpHandlers> <add verb="*" path="FileDownloadHandler.ashx" type="MasterChief.DotNet.Framework.WbSample.BackHandler.FileDownloadHandler" /> </httpHandlers> </system.web> <system.webServer> <modules runAllManagedModulesForAllRequests="true" /> </system.webServer>