.NET Core 默认依赖注入与 Castle DynamicProxy
.NET Core default dependency injection with Castle DynamicProxy
我有许多 AOP 库使用 Castle DynamicProxy 和 Autofac DI 容器来进行日志记录、审计、事务控制等。
我想知道是否有一种方法可以使用默认的 .NET Core DI 容器来声明拦截器。拥有这种灵活性会很好,因为许多 .NET Core 项目不使用 Autofac。
基础 .NET Core 容器没有拦截器等任何额外功能。 .NET Core 中的 DI 容器可以换成 Autofac 之类的东西的全部原因是,一旦超出默认容器,您就可以移动到不同的容器。
是的,您可以使用核心 DI 使用 DynamicProxy。我已经写了一篇博客 post 在 http://codethug.com/2021/03/17/Caching-with-Attributes-in-DotNet-Core5/ 对其进行了解释,但这是它的代码:
创建属性
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
public class CacheAttribute : Attribute
{
public int Seconds { get; set; } = 30;
}
创建拦截器(需要Castle.Core nuget包)
public class CacheInterceptor : IInterceptor
{
private IMemoryCache _memoryCache;
public CacheInterceptor(IMemoryCache memoryCache)
{
_memoryCache = memoryCache;
}
// Create a cache key using the name of the method and the values
// of its arguments so that if the same method is called with the
// same arguments in the future, we can find out if the results
// are cached or not
private static string GenerateCacheKey(string name,
object[] arguments)
{
if (arguments == null || arguments.Length == 0)
return name;
return name + "--" +
string.Join("--", arguments.Select(a =>
a == null ? "**NULL**" : a.ToString()).ToArray());
}
public void Intercept(IInvocation invocation)
{
var cacheAttribute = invocation.MethodInvocationTarget
.GetCustomAttributes(typeof(CacheAttribute), false)
.FirstOrDefault() as CacheAttribute;
// If the cache attribute is added ot this method, we
// need to intercept this call
if (cacheAttribute != null)
{
var cacheKey = GenerateCacheKey(invocation.Method.Name,
invocation.Arguments);
if (_memoryCache.TryGetValue(cacheKey, out object value))
{
// The results were already in the cache so return
// them from the cache instead of calling the
// underlying method
invocation.ReturnValue = value;
}
else
{
// Get the result the hard way by calling
// the underlying method
invocation.Proceed();
// Save the result in the cache
var options = new MemoryCacheEntryOptions
{
AbsoluteExpirationRelativeToNow =
new System.TimeSpan(hours: 0, minutes: 0,
seconds: cacheAttribute.Seconds)
};
_memoryCache.Set(cacheKey, invocation.ReturnValue,
options);
}
}
else
{
// We don't need to cache the results,
// nothing to see here
invocation.Proceed();
}
}
}
添加扩展方法以帮助在 DI 中注册 classes:
public static void AddProxiedScoped<TInterface, TImplementation>
(this IServiceCollection services)
where TInterface : class
where TImplementation : class, TInterface
{
// This registers the underlying class
services.AddScoped<TImplementation>();
services.AddScoped(typeof(TInterface), serviceProvider =>
{
// Get an instance of the Castle Proxy Generator
var proxyGenerator = serviceProvider
.GetRequiredService<ProxyGenerator>();
// Have DI build out an instance of the class that has methods
// you want to cache (this is a normal instance of that class
// without caching added)
var actual = serviceProvider
.GetRequiredService<TImplementation>();
// Find all of the interceptors that have been registered,
// including our caching interceptor. (you might later add a
// logging interceptor, etc.)
var interceptors = serviceProvider
.GetServices<IInterceptor>().ToArray();
// Have Castle Proxy build out a proxy object that implements
// your interface, but adds a caching layer on top of the
// actual implementation of the class. This proxy object is
// what will then get injected into the class that has a
// dependency on TInterface
return proxyGenerator.CreateInterfaceProxyWithTarget(
typeof(TInterface), actual, interceptors);
});
}
将这些行添加到 Startup.cs
中的 ConfigureServices
// Setup Interception
services.AddSingleton(new ProxyGenerator());
services.AddScoped<IInterceptor, CacheInterceptor>(
之后,如果要使用缓存拦截器,需要做两件事:
首先,将属性添加到您的方法中
[Cache(Seconds = 30)]
public async Task<IEnumerable<Person>> GetPeopleByLastName(string lastName)
{
return SomeLongRunningProcess(lastName);
}
其次,使用Proxy/Interception:
在DI中注册class
services.AddProxiedScoped<IPersonRepository, PersonRepository>();
而不是没有Proxy/Interception的正常方式:
services.AddScoped<IPersonRepository, PersonRepository>();
我有许多 AOP 库使用 Castle DynamicProxy 和 Autofac DI 容器来进行日志记录、审计、事务控制等。
我想知道是否有一种方法可以使用默认的 .NET Core DI 容器来声明拦截器。拥有这种灵活性会很好,因为许多 .NET Core 项目不使用 Autofac。
基础 .NET Core 容器没有拦截器等任何额外功能。 .NET Core 中的 DI 容器可以换成 Autofac 之类的东西的全部原因是,一旦超出默认容器,您就可以移动到不同的容器。
是的,您可以使用核心 DI 使用 DynamicProxy。我已经写了一篇博客 post 在 http://codethug.com/2021/03/17/Caching-with-Attributes-in-DotNet-Core5/ 对其进行了解释,但这是它的代码:
创建属性
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
public class CacheAttribute : Attribute
{
public int Seconds { get; set; } = 30;
}
创建拦截器(需要Castle.Core nuget包)
public class CacheInterceptor : IInterceptor
{
private IMemoryCache _memoryCache;
public CacheInterceptor(IMemoryCache memoryCache)
{
_memoryCache = memoryCache;
}
// Create a cache key using the name of the method and the values
// of its arguments so that if the same method is called with the
// same arguments in the future, we can find out if the results
// are cached or not
private static string GenerateCacheKey(string name,
object[] arguments)
{
if (arguments == null || arguments.Length == 0)
return name;
return name + "--" +
string.Join("--", arguments.Select(a =>
a == null ? "**NULL**" : a.ToString()).ToArray());
}
public void Intercept(IInvocation invocation)
{
var cacheAttribute = invocation.MethodInvocationTarget
.GetCustomAttributes(typeof(CacheAttribute), false)
.FirstOrDefault() as CacheAttribute;
// If the cache attribute is added ot this method, we
// need to intercept this call
if (cacheAttribute != null)
{
var cacheKey = GenerateCacheKey(invocation.Method.Name,
invocation.Arguments);
if (_memoryCache.TryGetValue(cacheKey, out object value))
{
// The results were already in the cache so return
// them from the cache instead of calling the
// underlying method
invocation.ReturnValue = value;
}
else
{
// Get the result the hard way by calling
// the underlying method
invocation.Proceed();
// Save the result in the cache
var options = new MemoryCacheEntryOptions
{
AbsoluteExpirationRelativeToNow =
new System.TimeSpan(hours: 0, minutes: 0,
seconds: cacheAttribute.Seconds)
};
_memoryCache.Set(cacheKey, invocation.ReturnValue,
options);
}
}
else
{
// We don't need to cache the results,
// nothing to see here
invocation.Proceed();
}
}
}
添加扩展方法以帮助在 DI 中注册 classes:
public static void AddProxiedScoped<TInterface, TImplementation>
(this IServiceCollection services)
where TInterface : class
where TImplementation : class, TInterface
{
// This registers the underlying class
services.AddScoped<TImplementation>();
services.AddScoped(typeof(TInterface), serviceProvider =>
{
// Get an instance of the Castle Proxy Generator
var proxyGenerator = serviceProvider
.GetRequiredService<ProxyGenerator>();
// Have DI build out an instance of the class that has methods
// you want to cache (this is a normal instance of that class
// without caching added)
var actual = serviceProvider
.GetRequiredService<TImplementation>();
// Find all of the interceptors that have been registered,
// including our caching interceptor. (you might later add a
// logging interceptor, etc.)
var interceptors = serviceProvider
.GetServices<IInterceptor>().ToArray();
// Have Castle Proxy build out a proxy object that implements
// your interface, but adds a caching layer on top of the
// actual implementation of the class. This proxy object is
// what will then get injected into the class that has a
// dependency on TInterface
return proxyGenerator.CreateInterfaceProxyWithTarget(
typeof(TInterface), actual, interceptors);
});
}
将这些行添加到 Startup.cs
中的 ConfigureServices// Setup Interception
services.AddSingleton(new ProxyGenerator());
services.AddScoped<IInterceptor, CacheInterceptor>(
之后,如果要使用缓存拦截器,需要做两件事:
首先,将属性添加到您的方法中
[Cache(Seconds = 30)]
public async Task<IEnumerable<Person>> GetPeopleByLastName(string lastName)
{
return SomeLongRunningProcess(lastName);
}
其次,使用Proxy/Interception:
在DI中注册classservices.AddProxiedScoped<IPersonRepository, PersonRepository>();
而不是没有Proxy/Interception的正常方式:
services.AddScoped<IPersonRepository, PersonRepository>();