代码之家  ›  专栏  ›  技术社区  ›  Zachary Scott

AddScoped:如何调用正确的构造函数?

  •  0
  • Zachary Scott  · 技术社区  · 4 年前

    我正在寻找正确的C#代码来在ASP中注入此服务。NET 5 MVC(核心),以应用类的默认值。

    如果我在下面添加作用域服务,则实例字段值为 空的 。如果我这样做了 var a = new HtmlSanitizer(); ,实例字段填充有非null默认值,如“值的长字符串”。

    services.AddScoped<IHtmlSanitizer, HtmlSanitizer>();
    

    如果我如下重写注入,实例字段为 密集的 .此代码是否符合上述预期效果?当然,为什么产生的对象不同?

    services.AddScoped<IHtmlSanitizer, HtmlSanitizer>(
        _ => { return new HtmlSanitizer(); }
        // Why the difference?
        // Is this how one passes constant parameter values?
    );
    

    我用过 HtmlSanitizer 和Asp。Net 5,但我怀疑它是否重要。

    0 回复  |  直到 4 年前
        1
  •  3
  •   David L    4 年前

    这实际上与 HtmlSanitizer 具体来说,更多的是与如何。NET核心构造函数依赖项注入有效。

    根据 documentation :

    服务可以通过以下方式解决:

    • IServiceProvider
    • ActivatorUtilities:
      • 创建未在容器中注册的对象。
      • 与一些框架功能一起使用。

    构造函数可以接受依赖项未提供的参数 注入,但参数必须分配默认值。

    当服务由IServiceProvider或ActivatorUtilities解析时, 构造函数注入需要一个公共构造函数。

    当ActivatorUtilities解析服务时,构造函数 注入只需要存在一个适用的构造函数。 支持构造函数重载,但只能存在一个重载 其参数都可以通过依赖注入来实现。

    在这种情况下,您使用的是IServiceProvider,框架可以“访问”特定类型的参数 IEnumerable<T> ,这是所需的 HtmlSanitizer constructor :

    public HtmlSanitizer(IEnumerable<string>? allowedTags = null, IEnumerable<string>? allowedSchemes = null,
        IEnumerable<string>? allowedAttributes = null, IEnumerable<string>? uriAttributes = null, IEnumerable<string>? allowedCssProperties = null)
    {
        AllowedTags = new HashSet<string>(allowedTags ?? DefaultAllowedTags, StringComparer.OrdinalIgnoreCase);
        AllowedSchemes = new HashSet<string>(allowedSchemes ?? DefaultAllowedSchemes, StringComparer.OrdinalIgnoreCase);
        AllowedAttributes = new HashSet<string>(allowedAttributes ?? DefaultAllowedAttributes, StringComparer.OrdinalIgnoreCase);
        UriAttributes = new HashSet<string>(uriAttributes ?? DefaultUriAttributes, StringComparer.OrdinalIgnoreCase);
        AllowedCssProperties = new HashSet<string>(allowedCssProperties ?? DefaultAllowedCssProperties, StringComparer.OrdinalIgnoreCase);
        AllowedAtRules = new HashSet<CssRuleType>(DefaultAllowedAtRules);
        AllowedClasses = new HashSet<string>(DefaultAllowedClasses, StringComparer.OrdinalIgnoreCase);
    }
    

    当服务解析程序看到带有参数的构造函数时,它将尝试访问每个参数。假使 IEnumerable<T> ,参数是专门处理的,默认数组将根据 source :

    protected override object VisitIEnumerable(IEnumerableCallSite enumerableCallSite, RuntimeResolverContext context)
    {
        var array = Array.CreateInstance(
            enumerableCallSite.ItemType,
            enumerableCallSite.ServiceCallSites.Length);
    
        for (int index = 0; index < enumerableCallSite.ServiceCallSites.Length; index++)
        {
            object value = VisitCallSite(enumerableCallSite.ServiceCallSites[index], context);
            array.SetValue(value, index);
        }
        return array;
    }
    

    您可以用一个非常简单的测试工具来证明这一点:

    public class Test : ITest
    {
        private ISet<string> _defaults = new HashSet<string> { "one", "two", "three" };
        private ISet<string> _filters;
    
        public Test(List<string> filters = null)
        {
            _filters = new HashSet<string>(filters.ToHashSet() ?? _defaults);
        }
    }
    
    public interface ITest { }
    

    在这种情况下,参数过滤器将为null,并且在解析时将使用默认值 provider.GetService(typeof(ITest)); 。但是,如果我需要 IEnumerable 相反

    public class Test : ITest
    {
        private ISet<string> _defaults = new HashSet<string> { "one", "two", "three" };
        private ISet<string> _filters;
    
        public Test(IEnumerable<string> filters = null)
        {
            _filters = new HashSet<string>(filters.ToHashSet() ?? _defaults);
        }
    }
    
    public interface ITest { }
    

    您会发现传递了一个默认数组,导致不使用默认的筛选器。

    通过使用返回的工厂实例化 new HtmlSanitizer() ,您可以绕过此实现行为,并为每个参数传递null,从而允许使用默认值。

    这是一个非常令人惊讶的行为,我找不到任何将其描述为预期行为的文档。我相信这可能只是一个疏忽。NET核心DI团队,因为通常依赖项是不可IEnumerable类型。同样值得注意的是,此行为不适用于的参数 IList<T> ISet<T> 类型