#c# #asp.net-core #asp.net5
Вопрос:
Я ищу правильный код C# для внедрения этой службы в ASP.NET 5 MVC (ядро) таким образом, чтобы применялись значения по умолчанию для класса.
Если я добавлю службу с областью действия ниже, значения полей экземпляра будут пустыми. Если я это сделаю var a = new HtmlSanitizer();
, поля экземпляра будут заполнены ненулевыми значениями по умолчанию, такими как «длинная строка значений».
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 в этом примере, но я сомневаюсь, что это имеет значение.
Ответ №1:
На самом деле это имеет меньшее отношение к HtmlSanitizer
конкретному и больше связано с тем, как работает внедрение зависимостей конструктора .NET Core.
Согласно документации:
Услуги могут быть решены с помощью:
- IServiceProvider
- Возможности активатора:
- Создает объекты, которые не зарегистрированы в контейнере.
- Используется с некоторыми функциями фреймворка.
Конструкторы могут принимать аргументы, не предоставленные внедрением зависимостей, но аргументам должны быть присвоены значения по умолчанию.
Когда службы разрешаются с помощью IServiceProvider или ActivatorUtilities, для внедрения конструктора требуется общедоступный конструктор.
Когда службы разрешаются с помощью ActivatorUtilities, внедрение конструктора требует, чтобы существовал только один применимый конструктор. Перегрузки конструктора поддерживаются, но может существовать только одна перегрузка, все аргументы которой могут быть выполнены путем внедрения зависимостей.
В этом контексте вы используете IServiceProvider, и платформа может «посещать» аргументы определенного типа IEnumerable<T>
, что требуется HtmlSanitizer
конструктору:
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>
, аргументы обрабатываются специально, и массивы по умолчанию будут созданы для источника:
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 { }
В этом случае фильтры параметров будут равны нулю, и вместо этого при разрешении будут использоваться значения по умолчанию 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 Core DI, поскольку обычно зависимости предназначены для типов, не имеющих числа. Также стоит отметить, что это поведение НЕ относится к параметрам типа IList<T>
или ISet<T>
.