1. 程式人生 > 實用技巧 >.NET Core 3.x 基於Autofac的AOP快取

.NET Core 3.x 基於Autofac的AOP快取

一、依賴包

二、定義一個簡單的快取介面

    /// <summary>
    /// 簡單的快取介面,只有查詢和新增,以後會進行擴充套件
    /// </summary>
    public interface ICaching
    {
        object Get(string cacheKey);
        void Set(string cacheKey, object cacheValue, TimeSpan absoluteExpirationRelativeToNow);
    }

三、實現快取介面

public class MemoryCaching : ICaching
{
	private IMemoryCache _cache;
	public MemoryCaching(IMemoryCache cache)
	{
		_cache = cache;
	}
	public object Get(string cacheKey)
	{
		return _cache.Get(cacheKey);
	}
	public void Set(string cacheKey, object cacheValue, TimeSpan absoluteExpirationRelativeToNow)
	{
		_cache.Set(cacheKey, cacheValue, absoluteExpirationRelativeToNow);
	}
}

四、定義快取屬性

/// <summary>
/// 這個Attribute就是使用時候的驗證,把它新增到要快取資料的方法中,即可完成快取的操作。
/// 往Server層方法上新增即可使用 [CachingAttribute(AbsoluteExpiration = 10)] //使用快取AOP 快取10分鐘
/// </summary>
[AttributeUsage(AttributeTargets.Method, Inherited = true)]
public class CachingAttribute : Attribute
{
	/// <summary>
	/// 快取絕對過期時間(分鐘)
	/// </summary>
	public int AbsoluteExpiration { get; set; } = 30;

}

五、AOP實現

/// <summary>
/// 面向切面的快取使用
/// </summary>
public class BlogCacheAOP : IInterceptor
{
	//通過注入的方式,把快取操作介面通過建構函式注入
	private readonly ICaching _cache;
	public BlogCacheAOP(ICaching cache)
	{
		_cache = cache;
	}

	//Intercept方法是攔截的關鍵所在,也是IInterceptor介面中的唯一定義
	public void Intercept(IInvocation invocation)
	{
		var method = invocation.MethodInvocationTarget ?? invocation.Method;
		//對當前方法的特性驗證
		var qCachingAttribute = this.GetQCachingAttributeInfo(invocation.MethodInvocationTarget ?? invocation.Method);
		if (qCachingAttribute != null)
		{
			ProceedCaching(invocation, qCachingAttribute);
		}
		else
		{
			invocation.Proceed();//直接執行被攔截方法
		}
	}

	private CachingAttribute GetQCachingAttributeInfo(MethodInfo method)
	{
		return method.GetCustomAttributes(true).FirstOrDefault(x => x.GetType() == typeof(CachingAttribute)) as CachingAttribute;
	}

	private void ProceedCaching(IInvocation invocation, CachingAttribute attribute)
	{
		//獲取自定義快取鍵
		var cacheKey = CustomCacheKey(invocation);
		//根據key獲取相應的快取值
		var cacheValue = _cache.Get(cacheKey);
		if (cacheValue != null)
		{
			//將當前獲取到的快取值,賦值給當前執行方法
			invocation.ReturnValue = cacheValue;
			return;
		}
		//去執行當前的方法
		invocation.Proceed();
		//存入快取
		if (!string.IsNullOrWhiteSpace(cacheKey))
		{
			_cache.Set(cacheKey, invocation.ReturnValue, TimeSpan.FromMinutes(attribute.AbsoluteExpiration));
		}
	}

	/// <summary>
	/// 自定義快取的key
	/// </summary>
	/// <param name="invocation"></param>
	/// <returns></returns>
	protected string CustomCacheKey(IInvocation invocation)
	{
		var typeName = invocation.TargetType.Name;
		var methodName = invocation.Method.Name;
		var methodArguments = invocation.Arguments.Select(GetArgumentValue).Take(3).ToList();//獲取引數列表,最多三個

		string key = $"{typeName}:{methodName}:";
		foreach (var param in methodArguments)
		{
			key = $"{key}{param}:";
		}

		return key.TrimEnd(':');
	}

	/// <summary>
	/// object 轉 string
	/// </summary>
	/// <param name="arg"></param>
	/// <returns></returns>
	protected static string GetArgumentValue(object arg)
	{
		if (arg is DateTime || arg is DateTime?)
			return ((DateTime)arg).ToString("yyyyMMddHHmmss");

		if (arg is string || arg is ValueType || arg is Nullable)
			return arg.ToString();

		if (arg != null)
		{
			if (arg is Expression)
			{
				var obj = arg as Expression;
				var result = Resolve(obj);
				return Common.Helper.MD5Helper.MD5Encrypt16(result);
			}
			else if (arg.GetType().IsClass)
			{
				return Common.Helper.MD5Helper.MD5Encrypt16(Newtonsoft.Json.JsonConvert.SerializeObject(arg));
			}
		}
		return string.Empty;
	}

	private static string Resolve(Expression expression)
	{
		if (expression is LambdaExpression)
		{
			LambdaExpression lambda = expression as LambdaExpression;
			expression = lambda.Body;
			return Resolve(expression);
		}
		if (expression is BinaryExpression)
		{
			BinaryExpression binary = expression as BinaryExpression;
			if (binary.Left is MemberExpression && binary.Right is ConstantExpression)//解析x=>x.Name=="123" x.Age==123這類
				return ResolveFunc(binary.Left, binary.Right, binary.NodeType);
			if (binary.Left is MethodCallExpression && binary.Right is ConstantExpression)//解析x=>x.Name.Contains("xxx")==false這類的
			{
				object value = (binary.Right as ConstantExpression).Value;
				return ResolveLinqToObject(binary.Left, value, binary.NodeType);
			}
			if ((binary.Left is MemberExpression && binary.Right is MemberExpression)
				|| (binary.Left is MemberExpression && binary.Right is UnaryExpression))//解析x=>x.Date==DateTime.Now這種
			{
				LambdaExpression lambda = Expression.Lambda(binary.Right);
				Delegate fn = lambda.Compile();
				ConstantExpression value = Expression.Constant(fn.DynamicInvoke(null), binary.Right.Type);
				return ResolveFunc(binary.Left, value, binary.NodeType);
			}
		}
		if (expression is UnaryExpression)
		{
			UnaryExpression unary = expression as UnaryExpression;
			if (unary.Operand is MethodCallExpression)//解析!x=>x.Name.Contains("xxx")或!array.Contains(x.Name)這類
				return ResolveLinqToObject(unary.Operand, false);
			if (unary.Operand is MemberExpression && unary.NodeType == ExpressionType.Not)//解析x=>!x.isDeletion這樣的 
			{
				ConstantExpression constant = Expression.Constant(false);
				return ResolveFunc(unary.Operand, constant, ExpressionType.Equal);
			}
		}
		if (expression is MemberExpression && expression.NodeType == ExpressionType.MemberAccess)//解析x=>x.isDeletion這樣的 
		{
			MemberExpression member = expression as MemberExpression;
			ConstantExpression constant = Expression.Constant(true);
			return ResolveFunc(member, constant, ExpressionType.Equal);
		}
		if (expression is MethodCallExpression)//x=>x.Name.Contains("xxx")或array.Contains(x.Name)這類
		{
			MethodCallExpression methodcall = expression as MethodCallExpression;
			return ResolveLinqToObject(methodcall, true);
		}
		var body = expression as BinaryExpression;
		//已經修改過程式碼body應該不會是null值了
		if (body == null)
			return string.Empty;
		var Operator = GetOperator(body.NodeType);
		var Left = Resolve(body.Left);
		var Right = Resolve(body.Right);
		string Result = string.Format("({0} {1} {2})", Left, Operator, Right);
		return Result;
	}

	private static string GetOperator(ExpressionType expressiontype)
	{
		switch (expressiontype)
		{
			case ExpressionType.And:
				return "and";
			case ExpressionType.AndAlso:
				return "and";
			case ExpressionType.Or:
				return "or";
			case ExpressionType.OrElse:
				return "or";
			case ExpressionType.Equal:
				return "=";
			case ExpressionType.NotEqual:
				return "<>";
			case ExpressionType.LessThan:
				return "<";
			case ExpressionType.LessThanOrEqual:
				return "<=";
			case ExpressionType.GreaterThan:
				return ">";
			case ExpressionType.GreaterThanOrEqual:
				return ">=";
			default:
				throw new Exception(string.Format("不支援{0}此種運算子查詢!" + expressiontype));
		}
	}

	private static string ResolveFunc(Expression left, Expression right, ExpressionType expressiontype)
	{
		var Name = (left as MemberExpression).Member.Name;
		var Value = (right as ConstantExpression).Value;
		var Operator = GetOperator(expressiontype);
		return Name + Operator + Value ?? "null";
	}

	private static string ResolveLinqToObject(Expression expression, object value, ExpressionType? expressiontype = null)
	{
		var MethodCall = expression as MethodCallExpression;
		var MethodName = MethodCall.Method.Name;
		switch (MethodName)
		{
			case "Contains":
				if (MethodCall.Object != null)
					return Like(MethodCall);
				return In(MethodCall, value);
			case "Count":
				return Len(MethodCall, value, expressiontype.Value);
			case "LongCount":
				return Len(MethodCall, value, expressiontype.Value);
			default:
				throw new Exception(string.Format("不支援{0}方法的查詢!", MethodName));
		}
	}

	private static string In(MethodCallExpression expression, object isTrue)
	{
		var Argument1 = (expression.Arguments[0] as MemberExpression).Expression as ConstantExpression;
		var Argument2 = expression.Arguments[1] as MemberExpression;
		var Field_Array = Argument1.Value.GetType().GetFields().First();
		object[] Array = Field_Array.GetValue(Argument1.Value) as object[];
		List<string> SetInPara = new List<string>();
		for (int i = 0; i < Array.Length; i++)
		{
			string Name_para = "InParameter" + i;
			string Value = Array[i].ToString();
			SetInPara.Add(Value);
		}
		string Name = Argument2.Member.Name;
		string Operator = Convert.ToBoolean(isTrue) ? "in" : " not in";
		string CompName = string.Join(",", SetInPara);
		string Result = string.Format("{0} {1} ({2})", Name, Operator, CompName);
		return Result;
	}

	private static string Like(MethodCallExpression expression)
	{

		var Temp = expression.Arguments[0];
		LambdaExpression lambda = Expression.Lambda(Temp);
		Delegate fn = lambda.Compile();
		var tempValue = Expression.Constant(fn.DynamicInvoke(null), Temp.Type);
		string Value = string.Format("%{0}%", tempValue);
		string Name = (expression.Object as MemberExpression).Member.Name;
		string Result = string.Format("{0} like {1}", Name, Value);
		return Result;
	}

	private static string Len(MethodCallExpression expression, object value, ExpressionType expressiontype)
	{
		object Name = (expression.Arguments[0] as MemberExpression).Member.Name;
		string Operator = GetOperator(expressiontype);
		string Result = string.Format("len({0}){1}{2}", Name, Operator, value.ToString());
		return Result;
	}
}

六、注入快取

/// <summary>
/// 快取服務啟動
/// </summary>
public static class MemoryCacheSetup
{
	public static void AddMemoryCacheSetup(this IServiceCollection services)
	{
		if (services == null) throw new ArgumentNullException(nameof(services));

		services.AddScoped<ICaching, MemoryCaching>();
		services.AddSingleton<IMemoryCache>(factory =>
		{
			var cache = new MemoryCache(new MemoryCacheOptions());
			return cache;
		});
	}
}
public class Startup
{        // This method gets called by the runtime. Use this method to add services to the container.
	public void ConfigureServices(IServiceCollection services)
	{//注入快取
		services.AddMemoryCacheSetup();
	}
}

七、註冊AOP

public class Startup
{
	public void ConfigureContainer(ContainerBuilder builder)
	{
		//other ...// AOP 
		var cacheType = new List<Type>();
		
		builder.RegisterType<BlogCacheAOP>();
		cacheType.Add(typeof(BlogCacheAOP));
		// 獲取 Service.dll 程式集服務,並註冊
		var assemblysServices = Assembly.LoadFrom(servicesDllFile);
		builder.RegisterAssemblyTypes(assemblysServices)
					.AsImplementedInterfaces()
					.InstancePerDependency()
					.EnableInterfaceInterceptors()//引用Autofac.Extras.DynamicProxy;
					.InterceptedBy(cacheType.ToArray());//允許將攔截器服務的列表分配給註冊。
		//other ...
	}
}

八、使用

服務層在需要快取的方法上新增屬性,就OK了

public class BlogArticleServices : BaseServices<BlogArticle>, IBlogArticleServices
{
	IBlogArticleRepository _dal;

	public BlogArticleServices(IBlogArticleRepository dal)
	{
		this._dal = dal;
		base.BaseDal = dal;
	}

	/// <summary>
	/// 獲取部落格列表
	/// </summary>
	/// <returns></returns>
	[CachingAttribute(AbsoluteExpiration = 10)] //使用快取AOP 快取10分鐘
	public async Task<List<BlogArticle>> getBlogs()
	{
		var blogList = await _dal.Query(a => a.bID > 0, a => a.bID);
		return blogList;
	}
}

九、執行程式碼

第一次進入沒有快取,走 set 快取10分鐘

第二次進入,有快取得到快取直接返回