目前在維護的系統架構主要是 Web Site Project 跟 ASP.NET MVC,近期因 .NET Core 的諸多好處,又新建了 .NET Core 的專案,因此最近都在嘗試將以前的 Code 重寫成也適合 .NET Core 專案使用的寫法。
假如我有一個 File 設定檔,我需要從 File 取得資料並做 Cache,當資料有異動時需清除 Cache 並重新抓資料的話,以前的寫法長這樣
public class BookRepo
{
private string _fileFilePath;
private const string _CACHEKEY = "BookRepo";
private static ObjectCache _memoryCache;
public BookRepo(string filePath)
{
if (_memoryCache == null)
_memoryCache = MemoryCache.Default;
_fileFilePath = filePath;
}
public BookModel GetBookModel()
{
BookModel bookModel = (BookModel)_memoryCache.Get(_CACHEKEY);
if (bookModel == null)
{
if (File.Exists(this._fileFilePath))
{
bookModel = new BookModel(JsonConvert.DeserializeObject<Book>(File.ReadAllText(this._fileFilePath)));
CacheItemPolicy policy = new CacheItemPolicy();
policy.ChangeMonitors.Add(new HostFileChangeMonitor(new List<string> { this._fileFilePath }));
//這邊我cache的是BookModel而不是Book, 因為new BookModel的時候我有將資料再做一些處理,希望cache的是有處理過的物件,
//這樣使用的時候才能直接使用處理過的資料,而不用每次都處理一次資料
_memoryCache.Set(_CACHEKEY, bookModel, policy);
}
else
throw new Exception(
$"File is not exists. Time:{DateTime.Now.ToString()} Source:{this._fileFilePath}");
}
return bookModel;
}
}
這段程式碼在 .NET Core 也是可以運作的,只是發現 .NET Core 在使用 MemoryCache 時是直接用 IMemoryCache;文件的部分也發現是使用 IFileProvider,因此改寫成以下版本
public class BookRepo : IBookRepo
{
private readonly IMemoryCache _memoryCache;
private const string _CACHEKEY = "BookRepo";
private string _filePath;
private IFileProvider _fileProvider;
public BookRepo(string path, IFileProvider fileProvider, IMemoryCache memoryCache)
{
_memoryCache = memoryCache;
_fileProvider = fileProvider;
_filePath = path;
}
public BookModel GetBookModel()
{
if (!_memoryCache.TryGetValue(_CACHEKEY, out BookModel bookModel))
{
bookModel = new BookModel(GetDataFromFile());
SetCache(bookModel);
}
return bookModel;
}
private IEnumerable<Book> GetDataFromFile()
{
IFileInfo fileInfo = _fileProvider.GetFileInfo(_filePath);
FileUtility fileUtility = new FileUtility(fileInfo);
return fileUtility.GetFileContent<IEnumerable<Book>>();
}
private void SetCache(BookModel bookModel)
{
IChangeToken changeToken =_fileProvider.Watch(_filePath);
MemoryCacheEntryOptions cacheOptions = new MemoryCacheEntryOptions().AddExpirationToken(changeToken);
_memoryCache.Set(_CACHEKEY, bookModel, cacheOptions);
}
}
讀取檔案的功能拉出來當通用
public class FileUtility
{
private readonly IFileInfo _contents;
public FileUtility(IFileInfo contents)
{
_contents = contents;
}
public string GetFileContent()
{
int runCount = 1;
while (runCount < 4)
{
try
{
if (_contents.Exists)
{
using (StreamReader reader = new StreamReader(_contents.CreateReadStream()))
{
return reader.ReadToEnd();
}
}
else
{
throw new FileNotFoundException($"File is not exists. Time:{DateTime.Now}", _contents.PhysicalPath);
}
}
catch (IOException ex)
{
if (runCount == 3 || ex.HResult != -2147024864)//0x80070020 - The process cannot access the file because it is being used by another process.
{
throw;
}
else
{
runCount++;
}
}
}
return null;
}
public T GetFileContent<T>()
{
string fileContent = GetFileContent();
return JsonConvert.DeserializeObject<T>(fileContent);
}
}
更精簡一點的寫法,但我自己還不是看很習慣這種寫法,乍看會停格個幾秒思考這是在幹麻…
public class BookRepo : IBookRepo
{
private readonly IMemoryCache _memoryCache;
private const string _CACHEKEY = "BookRepo";
private string _filePath;
private IFileProvider _fileProvider;
public BookRepo(string path, IFileProvider fileProvider, IMemoryCache memoryCache)
{
_memoryCache = memoryCache;
_fileProvider = fileProvider;
_filePath = path;
}
public BookModel GetBookModel()
{
return _memoryCache.GetOrCreate(_CACHEKEY, entry =>
{
entry.AddExpirationToken(_fileProvider.Watch(_filePath));
return new BookModel(new FileUtility(_fileProvider.GetFileInfo(_filePath))
.GetFileContent<IEnumerable<Book>>());
});
}
改完之後在 .NET Core 的 Startup.cs 注入就可以使用了
using Microsoft.Extensions.FileProviders;
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
IFileProvider physicalProvider = new PhysicalFileProvider(AppDomain.CurrentDomain.BaseDirectory);
services.AddSingleton<IFileProvider>(physicalProvider);
services.AddMemoryCache();
}
}
Web Site Project 需要這樣使用
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.FileProviders;
public class BookUtility
{
public static IBookModel GetBookModel()
{
string filePath = Path.Combine("Setting", "Book.json");
PhysicalFileProvider provider = new PhysicalFileProvider(AppDomain.CurrentDomain.BaseDirectory);
//這邊需要傳singleton的memorycache進去,如果傳new MemoryCache()進去等於每次都是新的,就不會有預期的cache效果
return new BookRepo(filePath, provider, MemoryCacheSingleton.Instance).GetBookModel();
}
}
public sealed class MemoryCacheSingleton
{
private static MemoryCache _memoryCache;
private static object _lockObj = new object();
public static MemoryCache Instance
{
get
{
lock (_lockObj)
{
if (_memoryCache == null)
{
_memoryCache = new MemoryCache(new MemoryCacheOptions());
}
return _memoryCache;
}
}
}
}
Web Site Project 安裝這兩個的時候著實驚了我一下
Microsoft.Extensions.Caching.Memory
Microsoft.Extensions.FileProviders
沒想到有如此多相依的 lib…