.NET Core 默认依赖注入与 Castle DynamicProxy

Posted

技术标签:

【中文标题】.NET Core 默认依赖注入与 Castle DynamicProxy【英文标题】:.NET Core default dependency injection with Castle DynamicProxy 【发布时间】:2020-06-21 22:26:06 【问题描述】:

我有许多 AOP 库,它们使用 Castle DynamicProxy 和 Autofac DI 容器来进行日志记录、审计、事务控制等。

我想知道是否有一种方法可以使用默认的 .NET Core DI 容器来声明拦截器。拥有这种灵活性会很好,因为许多 .NET Core 项目不使用 Autofac。

【问题讨论】:

【参考方案1】:

基本 .NET Core 容器没有任何额外的功能,例如拦截器。 .NET Core 中的 DI 容器可以换成 Autofac 之类的东西的全部原因是,一旦超出默认容器,您就可以移动到不同的容器。

【讨论】:

【参考方案2】:

是的,您可以通过 Core DI 使用 DynamicProxy。我在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 中注册类:

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);

其次,使用代理/拦截在DI中注册类:

services.AddProxiedScoped<IPersonRepository, PersonRepository>();

而不是没有代理/拦截的正常方式:

services.AddScoped<IPersonRepository, PersonRepository>();

【讨论】:

以上是关于.NET Core 默认依赖注入与 Castle DynamicProxy的主要内容,如果未能解决你的问题,请参考以下文章

csharp 将Castle Windsor依赖项注入ASP.NET Web API过滤器C#

如何在 .NET Core 中使用默认依赖注入从父级创建子范围?

依赖注入 Castle.Windsor高级应用

使用默认 ASP.NET Core DI 容器在 Service Fabric 上设置依赖注入

ASP.NET Core 依赖注入(DI)

将依赖注入与 .NET Core 类库 (.NET Standard) 结合使用