I have this CacheAttribute that accepts Duration Value like such
public class MyTestQuery : IMyTestQuery
{
private readonly ISomeRepository _someRepository;
public TestQuery(ISomeRepository someRepository)
{
_someRepository = someRepository;
}
[Cache(Duration = 10)]
public MyViewModel GetForeignKeysViewModelCache()
{
...code here...
return viewModel;
}
}
The Attribute looks like this
[AttributeUsage(AttributeTargets.Method)]
public class CacheAttribute : Attribute
{
public int Duration { get; set; }
}
When Intercepted using Castle.Proxy.IInterceptor it works but when I perform an Attribute.GetCustomAttribute either by IInvocation.MethodInvocationTarget or IInvocation.Method both returns a null value
Here it is in code
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...
}
}
And this is how I register them
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();
}
}
My Expensive Method Class Ends with Query
Now the question is why invocation.MethodInvocationTarget
and/or invocation.Method
returns null?
What am I doing wrong?
Any other strategies so I can pass a parameter value without creating a Method for each value I can think of?
BTW I am using
UPDATE 1 Here is what it returns when it runs for clarity
Here's what I found.
invocation.Method
returns the method declaration on the interface, in your case IMyTestQuery
.
On the other hand, invocation.MethodInvocationProxy
returns the method that is going to be called when invoking invocation.Proceed()
. This means it can be:
As you can see, MethodInvocationProxy
is less deterministic than Method
, which is why I would recommend you avoid using it, at least for what you're trying to achieve.
When you think about it, an interceptor should not be tied to an implementation as it proxies an interface, so why don't you put the [Cache]
attribute at the interface level?
Using your code, I could successfully retrieve it when put on the interface.
Edit:
OK, I've put together a repository on GitHub that uses the specific versions of the NuGet packages you mentioned and shows how to retrieve an attribute on intercepted methods.
As a reminder, here are the used NuGet packages:
Microsoft.AspNet.Mvc
v5.2.3Autofac
v4.3.0Autofac.Mvc5
4.0.0Autofac.Extras.DynamicProxy
v4.2.1Castle.Core
v4.0.0I created 2 query interfaces, IMyQuery
and IMySecondQuery
. Please note that as mentioned in my original answer, the [Cache]
attributes are placed on the interfaces methods, not on the implementing classes.
public interface IMyQuery
{
[Cache(60000)]
string GetName();
}
public interface IMySecondQuery
{
[Cache(1000)]
string GetSecondName();
}
Then we have 2 very basic implementations of these classes. Not relevant at all, but for the sake of completeness:
public class DefaultMyQuery : IMyQuery
{
public string GetName()
{
return "Raymund";
}
}
public class DefaultMySecondQuery : IMySecondQuery
{
public string GetSecondName()
{
return "Mickaël Derriey";
}
}
And then the interceptor:
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();
}
}
Note that the GetCustomAttribute<T>
method is an extension method over MemberInfo
present in the System.Reflection
namespace.
Let's move on to the registration in the Autofac container. I tried to follow you registration style as much as I could:
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()));
The queries are then used in the 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);
}
}
What I did to test this is just put a breakpoint in the interceptor, F5
the application, open a browser and navigate to both http://localhost:62440/home/myquery
and http://localhost:62440/home/myquery
.
It did hit the interceptor and find the [Cache]
attribute. In the Visual Studio Output window, it did show:
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.
Hopefully that helps you pinpoint what's going on in your project.
I pushed changes to the repository so that the first query calls the second one.
It still works. You should really make an effort and put some code on the question.