获取成员拦截的属性值

Getting Attribute Value on Member Interception

我有这个 CacheAttribute 接受 Duration 这样的值

public class MyTestQuery : IMyTestQuery
{
    private readonly ISomeRepository _someRepository;

    public TestQuery(ISomeRepository someRepository)
    {
        _someRepository = someRepository;
    }

    [Cache(Duration = 10)]
    public MyViewModel GetForeignKeysViewModelCache()
    {
        ...code here...
        return viewModel;
    }
}

属性看起来像这样

[AttributeUsage(AttributeTargets.Method)]
public class CacheAttribute : Attribute
{
    public int Duration { get; set; }
}

当使用 Castle.Proxy.IInterceptor 拦截时它有效但是当我通过 [=38 执行 Attribute.GetCustomAttribute =]IInvocation.MethodInvocationTarget or IInvocation.Method both returns a null value

这是代码

public class CacheResultInterceptor : IInterceptor
{
    public CacheAttribute GetCacheResultAttribute(IInvocation invocation)
    {
        var methodInfo = invocation.MethodInvocationTarget;
        if (methodInfo == null)
        {
            methodInfo = invocation.Method;
        }

        return Attribute.GetCustomAttribute(
            methodInfo,
            typeof(CacheAttribute),
            true
        )
        as CacheAttribute;
    }
    public void Intercept(IInvocation invocation)
    {
        var cacheAttribute = GetCacheResultAttribute(invocation);
        //cacheAttribute is null always

        ...more code here...
    }
}

这就是我注册它们的方式

public class Bootstrapper
{
    public static ContainerBuilder Builder;

    public static void Initialise()
    {
        Builder = new ContainerBuilder();

        ...other codes in here...
        CacheInstaller.Install();

        var container = Builder.Build();
        DependencyResolver.SetResolver(new AutofacDependencyResolver(container));
    }

}

public class CacheInstaller 
{
    public static void Install()
    {

        Bootstrapper.Builder.RegisterType<CacheResultInterceptor>()
            .SingleInstance();

        Bootstrapper.Builder.RegisterAssemblyTypes(Assembly.Load("MyApplication.Web"))
            .Where(t => t.Name.EndsWith("Query"))
            .AsImplementedInterfaces()
            .EnableInterfaceInterceptors()
            .InterceptedBy(typeof(CacheResultInterceptor))
            .SingleInstance();

    }
}

我的昂贵方法Class以查询结束

现在的问题是为什么invocation.MethodInvocationTargetand/orinvocation.Methodreturns为null? 我究竟做错了什么? 任何其他策略,以便我可以传递参数值而无需为我能想到的每个值创建方法?

顺便说一句,我正在使用

更新 1 为清楚起见,下面是它 returns 运行时的内容

这是我的发现。

invocation.Method returns 接口上的方法声明,在你的例子中 IMyTestQuery.

另一方面,invocation.MethodInvocationProxy returns 将在调用 invocation.Proceed() 时调用的方法。这意味着它可以是:

  • 下一个拦截器如果你有几个
  • 一个装饰器,如果你的界面上有装饰器
  • 你的接口的最终实现

如您所见,MethodInvocationProxy 的确定性不如 Method,这就是为什么我建议您避免使用它,至少对于您要实现的目标而言。

当你想到它时,拦截器不应该绑定到实现,因为它代理了一个接口,那么你为什么不把 [Cache] 属性放在接口级别?

使用你的代码,我在界面上可以成功检索它。


编辑:

好的,我整理了一个 repository on GitHub,它使用您提到的特定版本的 NuGet 包,并展示了如何检索拦截方法的属性。

提醒一下,这里是使用过的 NuGet 包:

  • Microsoft.AspNet.Mvc v5.2.3
  • Autofac v4.3.0
  • Autofac.Mvc5 4.0.0
  • Autofac.Extras.DynamicProxy v4.2.1
  • Castle.Core v4.0.0

我创建了 2 个 query 接口,IMyQueryIMySecondQuery。请注意,正如我最初的回答中提到的,[Cache] 属性位于接口方法上,而不是实现 类.

public interface IMyQuery
{
    [Cache(60000)]
    string GetName();
}

public interface IMySecondQuery
{
    [Cache(1000)]
    string GetSecondName();
}

然后我们有 2 个非常基本的实现 类。完全不相关,但为了完整起见:

public class DefaultMyQuery : IMyQuery
{
    public string GetName()
    {
        return "Raymund";
    }
}

public class DefaultMySecondQuery : IMySecondQuery
{
    public string GetSecondName()
    {
        return "Mickaël Derriey";
    }
}

然后是拦截器:

public class CacheResultInterceptor : IInterceptor
{
    public void Intercept(IInvocation invocation)
    {
        var cacheAttribute = invocation.Method.GetCustomAttribute<CacheAttribute>();
        if (cacheAttribute != null)
        {
            Trace.WriteLine($"Found a [Cache] attribute on the {invocation.Method.Name} method with a duration of {cacheAttribute.Duration}.");
        }

        invocation.Proceed();
    }
}

请注意,GetCustomAttribute<T> 方法是 System.Reflection 命名空间中 MemberInfo 的扩展方法。

让我们继续在 Autofac 容器中注册。我尽量按照你的注册方式:

var builder = new ContainerBuilder();

builder.RegisterControllers(typeof(MvcApplication).Assembly);

builder
    .RegisterType<CacheResultInterceptor>()
    .SingleInstance();

builder
    .RegisterAssemblyTypes(typeof(MvcApplication).Assembly)
    .Where(x => x.Name.EndsWith("Query"))
    .AsImplementedInterfaces()
    .EnableInterfaceInterceptors()
    .InterceptedBy(typeof(CacheResultInterceptor));

DependencyResolver.SetResolver(new AutofacDependencyResolver(builder.Build()));

然后在 HomeController:

中使用查询
public class HomeController : Controller
{
    private readonly IMyQuery _myQuery;
    private readonly IMySecondQuery _mySecondQuery;

    public HomeController(IMyQuery myQuery, IMySecondQuery mySecondQuery)
    {
        _myQuery = myQuery;
        _mySecondQuery = mySecondQuery;
    }

    public ActionResult MyQuery()
    {
        return Json(_myQuery.GetName(), JsonRequestBehavior.AllowGet);
    }

    public ActionResult MySecondQuery()
    {
        return Json(_mySecondQuery.GetSecondName(), JsonRequestBehavior.AllowGet);
    }
}

我所做的测试只是在拦截器中放置一个断点,F5 应用程序,打开浏览器并导航到 http://localhost:62440/home/myqueryhttp://localhost:62440/home/myquery

确实命中了拦截器,找到了[Cache]属性。在 Visual Studio 输出 window 中,确实显示:

Found a [Cache] attribute on the GetName method with a duration of 60000.
Found a [Cache] attribute on the GetSecondName method with a duration of 1000.

希望这能帮助您查明项目中发生的事情。


pushed changes to the repository 以便第一个查询调用第二个。

它仍然有效。你真的应该努力把一些代码放在这个问题上。