As the business gets more complex , Recently, I decided to cache some interfaces that are frequently queried but the data will not change much , This function is generally used AOP You can do it , After looking for a client, there is no ready-made one that can be used directly , A kind of , You can only develop it yourself .
Agent mode and AOP
After understanding the proxy pattern , Yes AOP It's natural to get caught , So let's start with some pre knowledge .
The proxy pattern is an example code that uses one class to control method calls from another class .
The proxy pattern has three roles :
- ISubject Interface , Responsibility is to define behavior .
- ISubject Implementation class of RealSubject, Responsibility is to achieve behavior .
- ISubject Proxy class ProxySubject, Responsibility is to control RealSubject The interview of .
There are three implementations of the proxy pattern :
- General agent .
- Compulsory agency , Coercion means not having direct access to RealSubject Methods , Must be accessed through a proxy class .
- A dynamic proxy , Dynamic means to generate proxy classes through reflection ,AOP It is generally based on dynamic proxy .
AOP There are four key knowledge points :
- The breakthrough point JoinPoint. Namely RealSubject The method of controlled access in .
- notice Advice, Is the method in the proxy class , Can control or enhance RealSubject Methods , With advance notice 、 The rear notice 、 Surround notifications, etc
- Weaving Weave, Is to call the notification and in order RealSubject Method process .
- section Aspect, Multiple entry points will form a facet .
public interface ISubject
{
void DoSomething(string value);
Task DoSomethingAsync(string value);
}
public class RealSubject : ISubject
{
public void DoSomething(string value)
{
Debug.WriteLine(value);
}
public async Task DoSomethingAsync(string value)
{
await Task.Delay(2000);
Debug.WriteLine(value);
}
}
public class Proxy : ISubject
{
private readonly ISubject _realSubject;
public Proxy()
{
_realSubject = new RealSubject();
}
/// <summary>
/// That's the entry point
/// </summary>
/// <param name="value"></param>
public void DoSomething(string value)
{
// This process is weaving in
Before();
_realSubject.DoSomething(value);
After();
}
public Task DoSomethingAsync(string value)
{
throw new NotImplementedException();
}
public void Before()
{
Debug.WriteLine(" General proxy class pre notification ");
}
public void After()
{
Debug.WriteLine(" General proxy class post notification ");
}
}
I'm using Castle.Core This library is used to implement dynamic proxy . But the asynchronous method with return value of this proxy is hard to write , however github There are already many libraries that encapsulate the implementation process , Here I use Castle.Core.AsyncInterceptor To implement an asynchronous method proxy .
public class CastleInterceptor : StandardInterceptor
{
protected override void PostProceed(IInvocation invocation)
{
Debug.WriteLine("Castle Proxy class pre notification ");
}
protected override void PreProceed(IInvocation invocation)
{
Debug.WriteLine("Castle Proxy class post notification ");
}
}
public class AsyncCastleInterceptor : AsyncInterceptorBase
{
protected override async Task InterceptAsync(IInvocation invocation, IInvocationProceedInfo proceedInfo, Func<IInvocation, IInvocationProceedInfo, Task> proceed)
{
Before();
await proceed(invocation, proceedInfo);
After();
}
protected override async Task<TResult> InterceptAsync<TResult>(IInvocation invocation, IInvocationProceedInfo proceedInfo, Func<IInvocation, IInvocationProceedInfo, Task<TResult>> proceed)
{
Before();
var result = await proceed(invocation, proceedInfo);
After();
return result;
}
public void Before()
{
Debug.WriteLine(" asynchronous Castle Proxy class pre notification ");
}
public void After()
{
Debug.WriteLine(" asynchronous Castle Proxy class post notification ");
}
}
Implement facet class and interface caching
Implementation process :
- Definition CacheAttribute Feature to mark the methods that need to be cached .
- Definition CacheInterceptor section , Implement the logic of caching data in memory .
- Using facets , Generate dynamic proxy classes for interfaces , And inject the proxy class into IOC In the container .
- Interface by IOC Get the interface implementation class to access the implementation .
The client uses Prism Of IOC To achieve inversion of control ,Prism Support for multiple IOC, I'm going to use DryIoc, Because the others IOC No more updates .
Client memory cache usage Microsoft.Extensions.Caching.Memory, This is the most commonly used .
- Definition CacheAttribute Feature to mark the methods that need to be cached .
[AttributeUsage(AttributeTargets.Method)]
public class CacheAttribute : Attribute
{
public string? CacheKey { get; }
public long Expiration { get; }
public CacheAttribute(string? cacheKey = null, long expiration = 0)
{
CacheKey = cacheKey;
Expiration = expiration;
}
public override string ToString() => $"{{ CacheKey: {CacheKey ?? "null"}, Expiration: {Expiration} }}";
}
- Definition CacheInterceptor Section class , Implement the logic of caching data in memory
public class CacheInterceptor : AsyncInterceptorBase
{
private readonly IMemoryCache _memoryCache;
public CacheInterceptor(IMemoryCache memoryCache)
{
_memoryCache = memoryCache;
}
...
// Intercept asynchronous methods
protected override async Task<TResult> InterceptAsync<TResult>(IInvocation invocation, IInvocationProceedInfo proceedInfo, Func<IInvocation, IInvocationProceedInfo, Task<TResult>> proceed)
{
var attribute = invocation.Method.GetCustomAttribute<CacheAttribute>();
if (attribute == null)
{
return await proceed(invocation, proceedInfo).ConfigureAwait(false);
}
var cacheKey = attribute.CacheKey ?? GenerateKey(invocation);
if (_memoryCache.TryGetValue(cacheKey, out TResult cacheValue))
{
if (cacheValue is string[] array)
{
Debug.WriteLine($"[Cache] Key: {cacheKey}, Value: {string.Join(',', array)}");
}
return cacheValue;
}
else
{
cacheValue = await proceed(invocation, proceedInfo).ConfigureAwait(false);
_memoryCache.Set(cacheKey, cacheValue);
return cacheValue;
}
}
// Generate cached Key
private string GenerateKey(IInvocation invocation)
{
...
}
// Format it
private string FormatArgumentString(ParameterInfo argument, object value)
{
...
}
}
- Define extension classes to generate facets , And realize chain programming , You can easily add multiple facet classes to an interface .
public static class DryIocInterceptionAsyncExtension
{
private static readonly DefaultProxyBuilder _proxyBuilder = new DefaultProxyBuilder();
// Generate section
public static void Intercept<TService, TInterceptor>(this IRegistrator registrator, object serviceKey = null)
where TInterceptor : class, IInterceptor
{
var serviceType = typeof(TService);
Type proxyType;
if (serviceType.IsInterface())
proxyType = _proxyBuilder.CreateInterfaceProxyTypeWithTargetInterface(
serviceType, ArrayTools.Empty<Type>(), ProxyGenerationOptions.Default);
else if (serviceType.IsClass())
proxyType = _proxyBuilder.CreateClassProxyTypeWithTarget(
serviceType, ArrayTools.Empty<Type>(), ProxyGenerationOptions.Default);
else
throw new ArgumentException(
$"{serviceType} Can't be intercepted , Only interfaces or classes can be intercepted ");
registrator.Register(serviceType, proxyType,
made: Made.Of(pt => pt.PublicConstructors().FindFirst(ctor => ctor.GetParameters().Length != 0),
Parameters.Of.Type<IInterceptor[]>(typeof(TInterceptor[]))),
setup: Setup.DecoratorOf(useDecorateeReuse: true, decorateeServiceKey: serviceKey));
}
// Chain programming , Easy to add multiple slices
public static IContainerRegistry InterceptAsync<TService, TInterceptor>(
this IContainerRegistry containerRegistry, object serviceKey = null)
where TInterceptor : class, IAsyncInterceptor
{
var container = containerRegistry.GetContainer();
container.Intercept<TService, AsyncInterceptor<TInterceptor>>(serviceKey);
return containerRegistry;
}
}
- Define the target interface , And mark the method
public interface ITestService
{
/// <summary>
/// An interface for querying large amounts of data
/// </summary>
/// <returns></returns>
[Cache]
Task<string[]> GetLargeData();
}
public class TestService : ITestService
{
public async Task<string[]> GetLargeData()
{
await Task.Delay(2000);
var result = new[]{" Big "," The amount "," Count "," According to the "};
Debug.WriteLine(" Query data from the interface ");
return result;
}
}
- towards IOC The container injects facet classes and business interfaces .
public partial class App
{
protected override void RegisterTypes(IContainerRegistry containerRegistry)
{
// Inject cache class
containerRegistry.RegisterSingleton<IMemoryCache>(_ => new MemoryCache(new MemoryCacheOptions()));
// Injection section class
containerRegistry.Register<AsyncInterceptor<CacheInterceptor>>();
// Inject interface and application facet classes
containerRegistry.RegisterSingleton<ITestService, TestService>()
.InterceptAsync<ITestService, CacheInterceptor>();
containerRegistry.RegisterSingleton<ITestService2, TestService2>()
.InterceptAsync<ITestService2, CacheInterceptor>();
}
...
}
effect
// AopView.xaml
<Button x:Name="cache" Content="Aop Cache interface data " />
// AopView.xaml.cs
cache.Click += (sender, args) => ContainerLocator.Container.Resolve<ITestService>().GetLargeData();
// Output
// Click Print for the first time
// Query data from the interface
// Then click Print
// [Cache] Key: PrismAop.Service.TestService2.GetLargeData(), Value: Big , The amount , Count , According to the









![[file upload vulnerability 04] server side mime detection and bypass experiment (based on upload-labs-2 shooting range)](/img/b8/521ca4bb8931afab9a3a4d0b015125.jpg)