.Net Core WebAPI 利用 IActionFilter 實作請求快取
本文使用Redis快取方式
- 1 新建類
首先新建一個快取類CustomActionCacheAttribute繼承Attribute,因為需要給方法做標記,再參考并實作IActionFilter介面
public class CustomActionCacheAttribute: Attribute, IActionFilter
{
// 標記的方法執行前執行
public void OnActionExecuting(ActionExecutingContext context)
{
}
// 標記的方法執行完的回呼
public void OnActionExecuted(ActionExecutedContext context)
{
}
}
- 2 注入redis快取
在 .Net core 中,提供了一個操作所有快取物件的介面IDistributedCache,需要參考
using Microsoft.Extensions.Caching.Distributed,然后在類中宣告變數,
private IDistributedCache _cache;
因為該Filter標注在方法上,運行時決議,所以無法通過注入的方式來注入該物件
所以需要使用擴展來獲取IDistributedCache 物件
- 3 擴展獲取DI注入物件
新建靜態類CustomDIContainer
代碼如下:
/// <summary>
/// 自定義依賴獲取容器
/// </summary>
public static class CustomDIContainer
{
private static IHttpContextAccessor _httpContextAccessor;
/// <summary>
/// 配置全域HttpContext
/// </summary>
/// <param name="app"></param>
/// <returns></returns>
public static IApplicationBuilder UseCustomHttpContext(this IApplicationBuilder app)
{
_httpContextAccessor = app.ApplicationServices.GetRequiredService<IHttpContextAccessor>();
return app;
}
/// <summary>
/// 當前請求Http背景關系
/// </summary>
public static HttpContext Current => _httpContextAccessor.HttpContext;
/// <summary>
/// 獲取DI注入的組件
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public static T GetSerivce<T>() => (T)Current?.RequestServices.GetService(typeof(T));
}
使用到了IHttpContextAccessor,所以需要在Startup的ConfigureServices方法中進行http背景關系初始化注入.
public void ConfigureServices(IServiceCollection services)
{
......
// 注入NewtonsoftJson組件
services.AddControllers().AddNewtonsoftJson();
// 注入快取組件
services.AddDistributedRedisCache(r => r.Configuration = Configuration["Redis:ConnectionString"]);
// 注入請求背景關系
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
......
}
- 4 快取擴展
在快取中,不單單僅是快取一個方法,應該做到方法的拓展快取,既根據不同的傳參進行方法回傳值的快取,所以需要對方法的引數進行序列化,
方法如下:
/// <summary>
/// 序列化請求引數
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
public string GetParams(HttpContext context)
{
try
{
NameValueCollection form = HttpUtility.ParseQueryString(context.Request.QueryString.ToString());
HttpRequest request = context.Request;
string data = string.Empty;
switch (request.Method)
{
case "POST":
request.Body.Seek(0, SeekOrigin.Begin);
using (var reader = new StreamReader(request.Body, Encoding.UTF8))
{
data = reader.ReadToEndAsync().Result;
data = data.StringReplaceEmpty("{", "}", "\"", "\'").Replace(":", "=").Replace(",", "&");
}
break;
case "GET":
//第一步:取出所有get引數
IDictionary<string, string> parameters = new Dictionary<string, string>();
for (int f = 0; f < form.Count; f++)
{
string key = form.Keys[f];
parameters.Add(key, form[key]);
}
// 第二步:把字典按Key的字母順序排序
IDictionary<string, string> sortedParams = new SortedDictionary<string, string>(parameters);
IEnumerator<KeyValuePair<string, string>> dem = sortedParams.GetEnumerator();
// 第三步:把所有引數名和引數值串在一起
StringBuilder query = new StringBuilder();
while (dem.MoveNext())
{
string key = dem.Current.Key;
if (!string.IsNullOrEmpty(key))
query.Append(key).Append("=").Append(dem.Current.Value).Append("&");
}
data = query.ToString().TrimEnd('&');
break;
default:
data = string.Empty;
break;
}
return data;
}
catch
{
return string.Empty;
}
}
并在方法內部宣告存盤請求引數及方法的字串
private string _urlParams;
- 5 拓展快取過期時間
每個方法回傳結果都會根據不同的更新頻率設定不同的快取時間,宣告屬性可供頭部標注,
/// <summary>
/// 快取過期時間[分鐘] 默認快取時間30分鐘
/// </summary>
public int ValidTimeMinutes { get; set; } = 30;
- 6 完整代碼
public class CustomActionCacheAttribute : Attribute, IActionFilter
{
/// <summary>
/// 快取過期時間[分鐘] 默認快取時間30分鐘
/// </summary>
public int ValidTimeMinutes { get; set; } = 30;
private string _urlParams;
private IDistributedCache _cache;
public void OnActionExecuting(ActionExecutingContext context)
{
// 根據上方拓展的DI容器獲取 IDistributedCache 物件
this._cache = CustomDIContainer.GetSerivce<IDistributedCache>();
// 獲取呼叫引數拼裝快取的key
_urlParams = $"{context.HttpContext.Request.Path}?{GetParams(context.HttpContext)}";
// 讀取快取
byte[] cacheValue = _cache.Get(_urlParams);
// 判斷是否存在快取
if (cacheValue == null || cacheValue.Length == 0) return;
// 重新序列為結果
// Deserialize 是自定義的byte[]轉指定型別的拓展方法
IActionResult result = cacheValue.Deserialize<string>().Json2Object<ObjectResult>();
// 如果序列化成功,指定方法的 context.Result 將自動回傳結果,不再執行方法體
if (result != null)
context.Result = result;
}
public void OnActionExecuted(ActionExecutedContext context)
{
var result = context.Result.ConvertJson();
var options = new DistributedCacheEntryOptions().SetSlidingExpiration(TimeSpan.FromMinutes(ValidTimeMinutes));
// Serialize 是自定義將String字串轉換為byte[]陣列的方法
this._cache.SetAsync(_urlParams, result.Serialize(), options);
}
/// <summary>
/// 序列化請求引數
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
public string GetParams(HttpContext context)
{
try
{
NameValueCollection form = HttpUtility.ParseQueryString(context.Request.QueryString.ToString());
HttpRequest request = context.Request;
string data = string.Empty;
switch (request.Method)
{
case "POST":
request.Body.Seek(0, SeekOrigin.Begin);
using (var reader = new StreamReader(request.Body, Encoding.UTF8))
{
data = reader.ReadToEndAsync().Result;
data = data.StringReplaceEmpty("{", "}", "\"", "\'").Replace(":", "=").Replace(",", "&");
}
break;
case "GET":
//第一步:取出所有get引數
IDictionary<string, string> parameters = new Dictionary<string, string>();
for (int f = 0; f < form.Count; f++)
{
string key = form.Keys[f];
parameters.Add(key, form[key]);
}
// 第二步:把字典按Key的字母順序排序
IDictionary<string, string> sortedParams = new SortedDictionary<string, string>(parameters);
IEnumerator<KeyValuePair<string, string>> dem = sortedParams.GetEnumerator();
// 第三步:把所有引數名和引數值串在一起
StringBuilder query = new StringBuilder();
while (dem.MoveNext())
{
string key = dem.Current.Key;
if (!string.IsNullOrEmpty(key))
query.Append(key).Append("=").Append(dem.Current.Value).Append("&");
}
data = query.ToString().TrimEnd('&');
break;
default:
data = string.Empty;
break;
}
return data;
}
catch
{
return string.Empty;
}
}
}
- 7 最后注意
在.Net Core 3需要配置讀取body,在代碼中方法執行前和方法執行后中多次使用了HttpContext的Body引數,所以需要在Startup中Configure方法添加可多次讀取,
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
......
app.Use(next => context =>
{
context.Request.EnableBuffering();
return next(context);
});
......
}
- 8 使用
/// <summary>
/// 獲取系統的組織架構
/// </summary>
/// <returns></returns>
[HttpGet("get_organization")]
[CustomActionCache(ValidTimeMinutes = 60 * 24)]
public async Task<ExecuteResult<string>> GetOrganizationAsync()
{
// 具體的業務邏輯
......
}
如果文中有錯誤之處,歡迎各位看官指正,謝謝,如需轉載,請指明原出處,
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/287136.html
標籤:其他
