直接引用MrAdvice.dll文件不能实现AOP拦截,教你1分钟解决这个问题

直接引用MrAdvice.dll文件不能实现AOP拦截,教你1分钟解决这个问题。近日工作中,要实现一个功能,那就是业务层方法里面实现自动缓存。编写业务的C#开发人员只关注如何将业务代码编写正确就可以了,而缓存的代码,大多类似,无非就是判断是否有缓存,有就取出返回,没有就调用数据库代码获取数据再缓存起来而已,于是这部分代码通过使用AOP的方式自动接管掉这种重复性代码。

MrAdvice开源项目github地址:https://github.com/ArxOne/MrAdvice

直接引用MrAdvice.dll文件不能实现AOP拦截功能

1月份的时候写过一篇使用AOP组件重构老旧 ado.net 代码,统一管理多表操作的事务的文章,在测试程序中使用的是MrAdvice这个开源组件,对它熟悉,就又使用它了。只不过这次使用有点特殊,以前开发是可以联网的,可以很方便的使用nuget将其安装到本地,而这次是因项目原因内外网隔离,且是断网开发的,就只能在外网写个测试程序,然后将MrAdvice.dll文件复制到内网电脑,内网电脑通过引用dll的方式来使用该组件,结果是不会进入到拦截方法的。

直接引用MrAdvice.dll

通过下图可以看到,成功解决后,可以实现自动缓存了。

实现AOP拦截

下面是全部的演示程序源码。

演示程序解决方案目录一览

该项目是一个控制台项目,解决方案如下图所示:

演示程序解决.

MrAdvice.dll是直接引用的,不是通过nuget安装的,至于这个dll文件的获取,你可以通过nuget获取了找到它即可。

演示程序的源码

控制台入口的代码比较简单,单纯的调用接口。

程序入口代码

class Program
    {
        static void Main(string[] args)
        {
            Console.Title = "jhrs.com AOP演示程序,通过直接引用MrAdvice.dll编写的代码!";
            DateTime dtNow = DateTime.Now;
            IJhrscom api = new Jhrscom();
            var result = api.GetResult("这是a参数", dtNow, 12342);
            Console.WriteLine();
            Console.WriteLine($"第1次调用时返回结果是:"+result.ToJson());
            Console.WriteLine();
            result = api.GetResult("这是a参数", dtNow, 12342);
            Console.WriteLine();
            Console.WriteLine($"第2次调用时返回结果是来自第1次缓存数据,只不过被改了下:" + result.ToJson());
            Console.WriteLine();
            //api.GetPatient(Guid.NewGuid(), result);
        }
    }

  

程序接口代码

程序接口代码主要是模拟业务方法里面的一些类,定义了一个接口,一个实现类,另外实现类上面是标注了一个自动缓存的特性(AutoCache),该特性的实现代码即为下面所述的核心的AOP拦截代码,具体下面会给出的;另外还有一个输出结果(响应消息)的类。整个源码是放到一个文件里面的,如下所示:

public interface IJhrscom
    {
        ResponseResult GetResult(string a, DateTime dateTime, int id);

        ResponseResult GetPatient(Guid id, ResponseResult t);
    }

    public class Jhrscom : IJhrscom
    {
        [AutoCache(10)]
        public ResponseResult GetPatient(Guid id, ResponseResult t)
        {
            string key = GetKey(new object[] { id, t });
            ResponseResult result = new ResponseResult() { Code = 4444, Message = "第2个方法" };
            return result;
        }

        [AutoCache(cacheMinutes: 12, enableSliding: true)]
        public ResponseResult GetResult(string a, DateTime dateTime, int id)
        {
            ResponseResult result = new ResponseResult() { Code = 1122, Message = "缓存测试消息" };
            string key = GetKey(new object[] { a, dateTime, id });
            return result;
        }

        /// <summary>
        /// 缓存key
        /// </summary>
        /// <param name="pars"></param>
        /// <returns></returns>
        private string GetKey(params object[] pars)
        {
            var method = new StackFrame(1).GetMethod();
            var array = method.GetParameters();
            var key = array.Select(x => { return pars[x.Position].ToJson(); }).ToArray();

            var cacheKey = $"{method.DeclaringType.ToString()}|{method.Name.Replace("′", "")}|{string.Join("_", array.Select(x => x.Name))}|{string.Join("_", key)}".GetMd5();
            Console.WriteLine($"【{method.Name.Replace("′", "")}】实现类里面的缓存Key:" + cacheKey);
            return cacheKey;
        }
    }

    /// <summary>
    /// 输出结果
    /// </summary>
    public class ResponseResult
    {
        public int Code { get; set; }
        public string Message { get; set; }

        //.....其它属性
    }

  

核心的AOP拦截代码

该代码是用于实现自动缓存功能,思路就是在调用业务方法前,根据缓存key,缓存key按一定规则生成,保证唯一就可以了,具体源码中有说明,从缓存里面取出数据,如果存在缓存就直接返回给调用者即可,并终止业务方法的执行(体现在不调用context.Proceed()方法上);如果不存在缓存数据或者缓存过期了,则调用业务方法获取数据后并缓存就可以了。

/// <summary>
    /// 用AOP来实现自动缓存
    /// </summary>
    public class AutoCacheAttribute : Attribute, IMethodAdvice
    {
        /// <summary>
        /// 滑动过期
        /// </summary>
        public bool EnableSliding { get; set; }

        /// <summary>
        /// 缓存时间,分钟
        /// </summary>
        public int CacheMinutes { get; set; }

        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="cacheMinutes">缓存时间,分钟,默认5分钟,小于等于0永久缓存</param>
        /// <param name="enableSliding">使用滑动过期缓存控制策略</param>
        public AutoCacheAttribute(int cacheMinutes = 5, bool enableSliding = false)
        {
            EnableSliding = enableSliding;
            CacheMinutes = cacheMinutes;
        }

        /// <summary>
        /// AOP组件拦截方法,用于实现自动缓存,有缓存时直接返回;
        /// 没有缓存时,调用被拦截方法后,有返回值则将数据自动缓存起来
        /// </summary>
        /// <param name="context"></param>
        public void Advise(MethodAdviceContext context)
        {
            var key = GetKey(context);
            if (context.HasReturnValue && key.TryGetCache(out object m))
            {
                var r = m as ResponseResult;
                r.Message = "在拦截方法里面改了缓存里面取出来的数据!";

                context.ReturnValue = r;
                //context.ReturnValue = m;  

                //context.Proceed();  //直接取出缓存返回,不用执行原来取数据方法。
            }
            else
            {
                context.Proceed();//执行被拦截的方法
                if (context.HasReturnValue && context.ReturnValue != null)
                {
                    //被拦截方法有返回值,并且返回值不为null
                    if (EnableSliding && CacheMinutes > 0)
                        context.ReturnValue.SetCache(key, TimeSpan.FromMinutes(CacheMinutes));
                    else if (CacheMinutes > 0)
                        context.ReturnValue.SetCache(key, DateTime.Now.AddMinutes(CacheMinutes));
                    else
                        context.ReturnValue.SetCache(key);
                }
            }
        }

        /// <summary>
        /// 获取缓存key,key的规则为: md5(类全名|方法名|参数列表拆分数组|参数值的json数组),这样可以保证唯一
        /// </summary>
        /// <param name="context"></param>
        /// <returns></returns>
        private string GetKey(MethodAdviceContext context)
        {
            var array = context.TargetMethod.GetParameters();
            var key = array.Select(x => { return context.Arguments[x.Position].ToJson(); }).ToArray();

            var cacheKey = $"{context.Target.ToString()}|{context.TargetName}|{string.Join("_", array.Select(x => x.Name))}|{string.Join("_", key)}".GetMd5();
            return cacheKey;
        }
    }

    /// <summary>
    /// 缓存扩展方法,可使用其它缓存替代
    /// </summary>
    public static class CacheExtensions
    {
        private static MemoryCache cache = new MemoryCache("https://jhrs.com");

        /// <summary>
        /// 设置缓存,一直不过期
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="value"></param>
        /// <param name="key"></param>
        public static void SetCache<T>(this T value, string key)
        {
            if (string.IsNullOrWhiteSpace(key)) throw new ArgumentException($"缓存键参数{nameof(key)}不能为null或空");
            if (value == null) throw new ArgumentException($"缓存值参数{nameof(value)}不能为null");
            CacheItemPolicy policy = new CacheItemPolicy();
            cache.Set(key, value, policy);
        }

        /// <summary>
        /// 设置缓存,固定过期时间
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="value"></param>
        /// <param name="key"></param>
        /// <param name="absoluteExpiration"></param>
        public static void SetCache<T>(this T value, string key, DateTimeOffset? absoluteExpiration)
        {
            if (string.IsNullOrWhiteSpace(key)) throw new ArgumentException($"缓存键参数{nameof(key)}不能为null或空");
            if (value == null) throw new ArgumentException($"缓存值参数{nameof(value)}不能为null");
            CacheItemPolicy policy = new CacheItemPolicy() { AbsoluteExpiration = (DateTimeOffset)absoluteExpiration };
            cache.Set(key, value, policy);
        }

        /// <summary>
        /// 设置缓存,滑动过期
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="value"></param>
        /// <param name="key"></param>
        /// <param name="slidingExpiration"></param>
        public static void SetCache<T>(this T value, string key, TimeSpan? slidingExpiration)
        {
            if (string.IsNullOrWhiteSpace(key)) throw new ArgumentException($"缓存键参数{nameof(key)}不能为null或空");
            if (value == null) throw new ArgumentException($"缓存值参数{nameof(value)}不能为null");
            CacheItemPolicy policy = new CacheItemPolicy() { SlidingExpiration = (TimeSpan)slidingExpiration };
            cache.Set(key, value, policy);
        }

        /// <summary>
        /// 获取缓存数据
        /// </summary>
        /// <typeparam name="T">对象类型</typeparam>
        /// <param name="key"><缓存key/param>
        /// <param name="value">返回的缓存数据对名</param>
        /// <returns></returns>
        public static bool TryGetCache<T>(this string key, out T value)
        {
            value = default(T);
            if (cache.Contains(key))
            {
                value = (T)cache.Get(key);
                return true;
            }
            return false;
        }

        /// <summary>
        /// 获取字符串MD5值
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        public static string GetMd5(this string value)
        {
            byte[] bytes = Encoding.UTF8.GetBytes(value);

            StringBuilder sb = new StringBuilder();
            MD5 hash = new MD5CryptoServiceProvider();
            bytes = hash.ComputeHash(bytes);
            foreach (byte b in bytes)
            {
                sb.AppendFormat("{0:x2}", b);
            }
            return sb.ToString();
        }
    }

  

附加的JSON扩展类

该扩展类只是方便将对象转为JSON而已,代码不复如,如下所示:

 public static class JsonExtensions
    {
        /// <summary>
        /// 将对象转换为JSON字符串
        /// </summary>
        /// <param name="obj">要转换的对象</param>
        /// <param name="camelCase">是否小写名称</param>
        /// <param name="indented"></param>
        /// <returns></returns>
        public static string ToJson(this object obj, bool camelCase = false, bool indented = false)
        {
            JsonSerializerSettings settings = new JsonSerializerSettings();
            if (camelCase)
            {
                settings.ContractResolver = new CamelCasePropertyNamesContractResolver();
            }
            if (indented)
            {
                settings.Formatting = Formatting.Indented;
            }
            return JsonConvert.SerializeObject(obj, settings);
        }

        /// <summary>
        /// 把Json字符串转换为强类型对象
        /// </summary>
        public static T FromJson<T>(string json)
        {
            if (string.IsNullOrWhiteSpace(json)) return default(T);
            json = JsonDateTimeFormat(json);
            return JsonConvert.DeserializeObject<T>(json);
        }

        /// <summary>
        /// 处理Json的时间格式为正常格式
        /// </summary>
        private static string JsonDateTimeFormat(string json)
        {
            json = Regex.Replace(json,
                @"\\/Date\((\d+)\)\\/",
                match =>
                {
                    DateTime dt = new DateTime(1970, 1, 1);
                    dt = dt.AddMilliseconds(long.Parse(match.Groups[1].Value));
                    dt = dt.ToLocalTime();
                    return dt.ToString("yyyy-MM-dd HH:mm:ss.fff");
                });
            return json;
        }
    }

  

解决直接引用MrAdvice.dll不能拦截的问题

出现这个问题的根源是,MrAdvice这个组件是在编译时会给你的项目源码编织一些AOP拦截代码,熟悉PostSharp的应该对此了解,这也是在MrAdvice项目地址的issues处得到解答,地址是:https://github.com/ArxOne/MrAdvice/issues/140

所以我们需要在项目文件csproj里面添加一些配置,并且把MrAdvice的目录复制到断网开发项目的packages目录。通过完成这两个步骤就可以解决了。

You’ve missed the point: Mr Advice is a post-build weaver, which changes the assembly at build-time after the csc compiler has generated it. To achieve this, is inserts a task in the csproj. So if you want to do the same manually, you need to also add the build task in your csproj. If you have a VS2017 solution with a project working, you’ll only need to copy the lines that were added to the csproj into your own project.

解决步骤

  • 联网新建一个项目,通过nuget安装MrAdvice,然后在解决方案的packages目录里面将nuget下载的MrAdvice目录包,复制到你断网环境的解决方案的packages目录,如下图所示:

MrAdvice 目录

  • 修改项目文件,即修改csproj文件,csproj文件可以使用记事本或者其它软件打开,增加以下节点,如下图所示:

csproj文件

配置节点为如下:

<Import Project="..\packages\MrAdvice.2.8.8\build\MrAdvice.targets" Condition="Exists(‘..\packages\MrAdvice.2.8.8\build\MrAdvice.targets‘)" />
  <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
    <PropertyGroup>
      <ErrorText>这台计算机上缺少此项目引用的 NuGet 程序包。使用“NuGet 程序包还原”可下载这些程序包。有关更多信息,请参见 http://go.microsoft.com/fwlink/?LinkID=322105。缺少的文件是 {0}。</ErrorText>
    </PropertyGroup>
    <Error Condition="!Exists(‘..\packages\MrAdvice.2.8.8\build\MrAdvice.targets‘)" Text="$([System.String]::Format(‘$(ErrorText)‘, ‘..\packages\MrAdvice.2.8.8\build\MrAdvice.targets‘))" />
  </Target>

  

好了,通过以上步骤就可以在断网环境里面愉快的使用MrAdvice这个AOP拦截组件来省点体力劳动了。

源码可以在首发地址下载,本文首发于:

https://jhrs.com/2019/33367.html

原文地址:https://www.cnblogs.com/jessory/p/11888630.html

时间: 2024-11-05 20:44:21

直接引用MrAdvice.dll文件不能实现AOP拦截,教你1分钟解决这个问题的相关文章

VS2010 项目引用了DLL文件,也写了Using,但是编译时提示:未能找到类型或命名空间名称 &lt;转&gt;

昨天写了一个很小的winform程序,其中引用了自己写的两个dll文件. 本来认为轻松搞定,结果一编译居然提示:未能找到类型或命名空间名称..... 于是删掉两个dll重新引用,再编译结果依旧!很是郁闷. 后来经过调查,终于发现了解决方法: 在项目上点右键-->属性-->应用程序-->目标框架-->修改为.NET Framework 4. 而我原来的设置是.NET Framework 4 Client Profile.问题就出在这里. 以下是MSDN给出的提示: If you ar

引用动态链接库Dll文件 引用失败 未能添加对HD.dll的引用。请确保此文件可访问并且是一个有效的程序集或COM组件

出现这个问题,是由于使用了非.NET 的动态链接库,需要注册 方法如下: 1.在搜索程序和文件中使用 regsvr32 "D:\Projects\8.01.01.03-重庆大足\lib\ValidateServerRelease32.dll" 命令就可以了 引用动态链接库Dll文件 引用失败 未能添加对HD.dll的引用.请确保此文件可访问并且是一个有效的程序集或COM组件

C# 动态修改dll的签名 以及修改引用该dll文件的签名

原文:C# 动态修改dll的签名 以及修改引用该dll文件的签名 在读取RedisSessionStateProvider配置 提到用mono ceil 来修改程序集以及它的签名,里面GetPublicKey 和GetPubliKeyToken 方法里面那个字符串的获取 以及后来的签名 我们都应该 用code来实现,还有应用该dll文件的签名也一同需要修改. 所以我这里实现了一个简单的helper方法 如下: namespace ConsoleSession { using Mono.Cecil

转载:C# 将引用的DLL文件放到指定的目录下

当软件引用的DLL比较多的时候,全部的DLL都放在exe同目录下,显得比较乱,如果能把dll放到响应的文件夹下面,就方便很多 下面是解决该问题的一种方法: 右键点击项目:属性->设置,项目会生成一个app.config文件,在<configuration>节点后面添加下面的节点,重新生成一下即可,指定的目录即为生成exe所在路径的次级dll文件目录 <?xml version="1.0" encoding="utf-8" ?> <

Vs 引用第三方DLL文件 版本不一致问题 (npoi与memcached中的ICSharpCode.SharpZipLib版本冲突的解决方案)

最近在 做 MailChimp 与网站功能 集成时,发现 MailChimp 2API 中的 MailChimp.dll  中的依赖项 SerivceStack.Text.dll (版本为3.9.71.0)与我们WebSite 中的 ServiceStack.Text.dll (版本为4.0.0.0)的版本相冲突,当我们 写了  MailChimpManager mc = new MailChimpManager(ApiKey); 时 提示 无法加载 SerivceStack.Text.dll

VS 项目(c#)引用了 DLL文件,也写了Using,但是编译时提示:未能找到类型或命名空间名称

1. 在项目上点右键-->属性-->应用程序-->目标框架-->修改为.NET Framework 4. 而我原来的设置是.NET Framework 4 Client Profile.问题就出在这里. 以下是MSDN给出的提示: If you are targeting the .NET Framework 4 Client Profile, you cannot reference an assembly that is not in the .NET Framework 4

ocx文件转换成C#程序引用的DLL

将ocx文件转换成C#程序引用的DLL文件的办法 将ocx文件转换成C#程序引用的DLL文件的办法,需要的朋友可以参考一下 1.打开VS2008或VS2010命令提示符(此例用VS2008) 将ocx文件拷贝到d:\Program Files\Microsoft Visual Studio 9.0\VC下 2.注册ActiveX控件 regsvr32 IB_USBKEY.ocx 3.编译OCX文件 aximp IB_USBKEY.ocx 生成两个dll文件,项目中引用Ax前缀的文件 4.将生成的

C#生成DLL文件

使用csc命令将.cs文件编译成.dll的过程 很多时候,我们需要将.cs文件单独编译成.dll文件, 操作如下: 打开命令窗口->输入cmd到控制台->cd C:\WINDOWS\Microsoft.NET\Framework\v1.1.4322 转到vs.net安装的该目录下->执行csc命令csc /target:library File.cs->在该目录下产生一个对应名字的.dll文件(前提:把.cs文件放到C:\WINDOWS\Microsoft.NET\Framewor

如何写类库方法、属性等的注释,才能在其他地方调用dll文件时,在代码里出现智能提示?

我的本意是想整理下以往写过的代码库,给自己的代码增加复用性.一段时间后,可能自己对写过的代码是什么含义会忘掉,或者别人看自己的代码, 增加可懂性的考虑,决定要添加注释.(好像语句不通:)可是发现,在其他位置引用dll文件,并不能获得之前添加的注释. 解决方法,只需一步:在项目下,把生成里的xml文档文件勾选.简单实用技能get到手!