从EFCore上下文的使用到深入剖析DI的生命周期最后实现自动属性

2020-01-18 19:39:09王冬梅

这2个有什么区别呢?分别查看各自的方法摘要可以看到,通过GetService获取一个没有注册的服务时会返回null,而GetRequiredService会抛出一个InvalidOperationException,仅此而已。


// 返回结果:
    //   A service object of type T or null if there is no such service.
    public static T GetService<T>(this IServiceProvider provider);

    // 返回结果:
    //   A service object of type T.
    //
    // 异常:
    //  T:System.InvalidOperationException:
    //   There is no service of type T.
    public static T GetRequiredService<T>(this IServiceProvider provider);

终极大招

到现在为止,尽管找到了一种看起来合理的方案,但还是不够优雅,使用过其他第三方DI框架的朋友应该知道,属性注入的快感无可比拟。那原生DI有没有实现这个功能呢,我满心欢喜上G站搜Issue,看到这样一个回复(https://github.com/aspnet/Extensions/issues/2406):

官方明确表示没有开发属性注入的计划,没办法,只能靠自己了。

我的思路大概是:创建一个自定义标签(Attribute),用来给需要注入的属性打标签,然后写一个服务激活类,用来解析给定实例需要注入的属性并赋值,在某个类型被创建实例的时候也就是构造函数中调用这个激活方法实现属性注入。这里有个核心点要注意的是,从DI容器获取实例的时候一定要保证是和当前请求是同一个Scope,也就是说,必须要从当前的HttpContext中拿到这个IServiceProvider。

先创建一个自定义标签:


  [AttributeUsage(AttributeTargets.Property)]
  public class AutowiredAttribute : Attribute
  {

  }

解析属性的方法:


public void PropertyActivate(object service, IServiceProvider provider)
    {
      var serviceType = service.GetType();
      var properties = serviceType.GetProperties().AsEnumerable().Where(x => x.Name.StartsWith("_"));
      foreach (PropertyInfo property in properties)
      {
        var autowiredAttr = property.GetCustomAttribute<AutowiredAttribute>();
        if (autowiredAttr != null)
        {
          //从DI容器获取实例
          var innerService = provider.GetService(property.PropertyType);
          if (innerService != null)
          {
            //递归解决服务嵌套问题
            PropertyActivate(innerService, provider);
            //属性赋值
            property.SetValue(service, innerService);
          }
        }
      }
    }

然后在控制器中激活属性:


[Autowired]
    public IAccountService _accountService { get; set; }

    public LoginController(IHttpContextAccessor httpContextAccessor)
    {
      var pro = new AutowiredServiceProvider();
      pro.PropertyActivate(this, httpContextAccessor.HttpContext.RequestServices);
    }