diff --git a/bench/Autofac.BenchmarkProfiling/Autofac.BenchmarkProfiling.csproj b/bench/Autofac.BenchmarkProfiling/Autofac.BenchmarkProfiling.csproj
index 248ba87d6..165a5eac8 100644
--- a/bench/Autofac.BenchmarkProfiling/Autofac.BenchmarkProfiling.csproj
+++ b/bench/Autofac.BenchmarkProfiling/Autofac.BenchmarkProfiling.csproj
@@ -2,7 +2,7 @@
Exe
- net6.0
+ net7.0
x64
enable
diff --git a/bench/Autofac.Benchmarks/Autofac.Benchmarks.csproj b/bench/Autofac.Benchmarks/Autofac.Benchmarks.csproj
index eb53fe099..a3de60a98 100644
--- a/bench/Autofac.Benchmarks/Autofac.Benchmarks.csproj
+++ b/bench/Autofac.Benchmarks/Autofac.Benchmarks.csproj
@@ -1,7 +1,7 @@
- net6.0
+ net7.0
$(NoWarn);CS1591
Exe
false
diff --git a/bench/Autofac.Benchmarks/Benchmarks.cs b/bench/Autofac.Benchmarks/Benchmarks.cs
index c4bd6f04c..93f20013a 100644
--- a/bench/Autofac.Benchmarks/Benchmarks.cs
+++ b/bench/Autofac.Benchmarks/Benchmarks.cs
@@ -6,27 +6,28 @@ public static class Benchmarks
{
public static readonly Type[] All =
{
- typeof(ChildScopeResolveBenchmark),
- typeof(ConcurrencyBenchmark),
- typeof(ConcurrencyNestedScopeBenchmark),
- typeof(KeyedGenericBenchmark),
- typeof(KeyedNestedBenchmark),
- typeof(KeyedSimpleBenchmark),
- typeof(KeylessGenericBenchmark),
- typeof(KeylessNestedBenchmark),
- typeof(KeylessNestedSharedInstanceBenchmark),
- typeof(KeylessNestedLambdaBenchmark),
- typeof(KeylessNestedSharedInstanceLambdaBenchmark),
- typeof(KeylessSimpleBenchmark),
- typeof(KeylessSimpleSharedInstanceBenchmark),
- typeof(KeylessSimpleLambdaBenchmark),
- typeof(KeylessSimpleSharedInstanceLambdaBenchmark),
- typeof(DeepGraphResolveBenchmark),
- typeof(EnumerableResolveBenchmark),
- typeof(PropertyInjectionBenchmark),
- typeof(RootContainerResolveBenchmark),
- typeof(OpenGenericBenchmark),
- typeof(MultiConstructorBenchmark),
- typeof(LambdaResolveBenchmark),
- };
+ typeof(ChildScopeResolveBenchmark),
+ typeof(ConcurrencyBenchmark),
+ typeof(ConcurrencyNestedScopeBenchmark),
+ typeof(KeyedGenericBenchmark),
+ typeof(KeyedNestedBenchmark),
+ typeof(KeyedSimpleBenchmark),
+ typeof(KeylessGenericBenchmark),
+ typeof(KeylessNestedBenchmark),
+ typeof(KeylessNestedSharedInstanceBenchmark),
+ typeof(KeylessNestedLambdaBenchmark),
+ typeof(KeylessNestedSharedInstanceLambdaBenchmark),
+ typeof(KeylessSimpleBenchmark),
+ typeof(KeylessSimpleSharedInstanceBenchmark),
+ typeof(KeylessSimpleLambdaBenchmark),
+ typeof(KeylessSimpleSharedInstanceLambdaBenchmark),
+ typeof(DeepGraphResolveBenchmark),
+ typeof(EnumerableResolveBenchmark),
+ typeof(PropertyInjectionBenchmark),
+ typeof(RootContainerResolveBenchmark),
+ typeof(OpenGenericBenchmark),
+ typeof(MultiConstructorBenchmark),
+ typeof(LambdaResolveBenchmark),
+ typeof(RequiredPropertyBenchmark),
+ };
}
diff --git a/bench/Autofac.Benchmarks/ConcurrencyBenchmak.cs b/bench/Autofac.Benchmarks/ConcurrencyBenchmark.cs
similarity index 100%
rename from bench/Autofac.Benchmarks/ConcurrencyBenchmak.cs
rename to bench/Autofac.Benchmarks/ConcurrencyBenchmark.cs
diff --git a/bench/Autofac.Benchmarks/RequiredPropertyBenchmark.cs b/bench/Autofac.Benchmarks/RequiredPropertyBenchmark.cs
new file mode 100644
index 000000000..7b343afd2
--- /dev/null
+++ b/bench/Autofac.Benchmarks/RequiredPropertyBenchmark.cs
@@ -0,0 +1,61 @@
+using Autofac.Core;
+
+namespace Autofac.Benchmarks;
+
+public class RequiredPropertyBenchmark
+{
+ private IContainer _container;
+
+ [GlobalSetup]
+ public void Setup()
+ {
+ var builder = new ContainerBuilder();
+
+ builder.RegisterType();
+ builder.RegisterType();
+ builder.RegisterType();
+ builder.RegisterType();
+
+ _container = builder.Build();
+ }
+
+ [Benchmark(Baseline = true)]
+ public void NormalConstructor()
+ {
+ _container.Resolve();
+ }
+
+ [Benchmark]
+ public void RequiredProperties()
+ {
+ _container.Resolve();
+ }
+
+ public class ServiceA
+ {
+ }
+
+ public class ServiceB
+ {
+ }
+
+ public class ConstructorComponent
+ {
+ public ConstructorComponent(ServiceA serviceA, ServiceB serviceB)
+ {
+ ServiceA = serviceA;
+ ServiceB = serviceB;
+ }
+
+ public ServiceA ServiceA { get; }
+
+ public ServiceB ServiceB { get; }
+ }
+
+ public class RequiredPropertyComponent
+ {
+ required public ServiceA ServiceA { get; set; }
+
+ required public ServiceA ServiceB { get; set; }
+ }
+}
diff --git a/global.json b/global.json
index 1982217ab..ed6506965 100644
--- a/global.json
+++ b/global.json
@@ -1,6 +1,6 @@
{
"sdk": {
- "version": "7.0.100",
+ "version": "7.0.101",
"rollForward": "latestFeature"
},
diff --git a/src/Autofac/Core/Activators/Reflection/BoundConstructor.cs b/src/Autofac/Core/Activators/Reflection/BoundConstructor.cs
index 3488b2e12..ad323b592 100644
--- a/src/Autofac/Core/Activators/Reflection/BoundConstructor.cs
+++ b/src/Autofac/Core/Activators/Reflection/BoundConstructor.cs
@@ -71,6 +71,12 @@ internal BoundConstructor(ConstructorBinder binder, ParameterInfo firstNonBindab
///
public ConstructorBinder Binder { get; }
+ ///
+ /// Gets a value indicating whether the constructor has the SetsRequiredMembers attribute,
+ /// indicating we can skip population of required properties.
+ ///
+ public bool SetsRequiredMembers => Binder.SetsRequiredMembers;
+
///
/// Gets the constructor on the target type. The actual constructor used
/// might differ, e.g. if using a dynamic proxy.
diff --git a/src/Autofac/Core/Activators/Reflection/ConstructorBinder.cs b/src/Autofac/Core/Activators/Reflection/ConstructorBinder.cs
index 08d07c4d9..bb70bb5b3 100644
--- a/src/Autofac/Core/Activators/Reflection/ConstructorBinder.cs
+++ b/src/Autofac/Core/Activators/Reflection/ConstructorBinder.cs
@@ -26,6 +26,10 @@ public ConstructorBinder(ConstructorInfo constructorInfo)
Constructor = constructorInfo ?? throw new ArgumentNullException(nameof(constructorInfo));
_constructorArgs = constructorInfo.GetParameters();
+#if NET7_0_OR_GREATER
+ SetsRequiredMembers = constructorInfo.GetCustomAttribute() is not null;
+#endif
+
// If any of the parameters are unsafe, do not create an invoker, and store the parameter
// that broke the rule.
_illegalParameter = DetectIllegalParameter(_constructorArgs);
@@ -44,6 +48,12 @@ public ConstructorBinder(ConstructorInfo constructorInfo)
///
public ConstructorInfo Constructor { get; }
+ ///
+ /// Gets a value indicating whether the constructor has the SetsRequiredMembers attribute,
+ /// indicating we can skip population of required properties.
+ ///
+ public bool SetsRequiredMembers { get; }
+
///
/// Gets the set of parameters to bind against.
///
diff --git a/src/Autofac/Core/Activators/Reflection/InjectableProperty.cs b/src/Autofac/Core/Activators/Reflection/InjectableProperty.cs
new file mode 100644
index 000000000..f439f4cd5
--- /dev/null
+++ b/src/Autofac/Core/Activators/Reflection/InjectableProperty.cs
@@ -0,0 +1,69 @@
+// Copyright (c) Autofac Project. All rights reserved.
+// Licensed under the MIT License. See LICENSE in the project root for license information.
+
+using System.Globalization;
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.Serialization;
+using System.Text;
+using Autofac.Core.Resolving;
+using Autofac.Core.Resolving.Pipeline;
+using Autofac.Util;
+using Autofac.Util.Cache;
+
+namespace Autofac.Core.Activators.Reflection;
+
+///
+/// Holds an instance of a known property on a type instantiated by the .
+///
+internal class InjectableProperty
+{
+ private readonly MethodInfo _setter;
+ private readonly ParameterInfo _setterParameter;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The property info.
+ public InjectableProperty(PropertyInfo prop)
+ {
+ Property = prop;
+
+ _setter = prop.SetMethod!;
+
+ _setterParameter = _setter.GetParameters()[0];
+
+#if NET7_0_OR_GREATER
+ IsRequired = prop.GetCustomAttribute() is not null;
+#endif
+ }
+
+ ///
+ /// Gets the underlying property.
+ ///
+ public PropertyInfo Property { get; }
+
+ ///
+ /// Gets a value indicating whether this field is marked as required.
+ ///
+ public bool IsRequired { get; }
+
+ ///
+ /// Try and supply a value for this property using the given parameter.
+ ///
+ /// The object instance.
+ /// The parameter that may provide the value.
+ /// The component context.
+ /// True if the parameter could provide a value, and the property was set. False otherwise.
+ public bool TrySupplyValue(object instance, Parameter p, IComponentContext ctxt)
+ {
+ if (p.CanSupplyValue(_setterParameter, ctxt, out var vp))
+ {
+ Property.SetValue(instance, vp(), null);
+
+ return true;
+ }
+
+ return false;
+ }
+}
diff --git a/src/Autofac/Core/Activators/Reflection/InjectablePropertyState.cs b/src/Autofac/Core/Activators/Reflection/InjectablePropertyState.cs
new file mode 100644
index 000000000..d5d55b860
--- /dev/null
+++ b/src/Autofac/Core/Activators/Reflection/InjectablePropertyState.cs
@@ -0,0 +1,30 @@
+// Copyright (c) Autofac Project. All rights reserved.
+// Licensed under the MIT License. See LICENSE in the project root for license information.
+
+namespace Autofac.Core.Activators.Reflection;
+
+///
+/// Structure used to track whether or not we have set a property during activation.
+///
+internal struct InjectablePropertyState
+{
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The property.
+ public InjectablePropertyState(InjectableProperty property)
+ {
+ Property = property;
+ Set = false;
+ }
+
+ ///
+ /// Gets the property.
+ ///
+ public InjectableProperty Property { get; }
+
+ ///
+ /// Gets or sets a value indicating whether this property has already been set.
+ ///
+ public bool Set { get; set; }
+}
diff --git a/src/Autofac/Core/Activators/Reflection/ReflectionActivator.cs b/src/Autofac/Core/Activators/Reflection/ReflectionActivator.cs
index 0561d060b..1c0ad351c 100644
--- a/src/Autofac/Core/Activators/Reflection/ReflectionActivator.cs
+++ b/src/Autofac/Core/Activators/Reflection/ReflectionActivator.cs
@@ -4,9 +4,12 @@
using System.Globalization;
using System.Reflection;
using System.Runtime.CompilerServices;
+using System.Runtime.Serialization;
using System.Text;
using Autofac.Core.Resolving;
using Autofac.Core.Resolving.Pipeline;
+using Autofac.Util;
+using Autofac.Util.Cache;
namespace Autofac.Core.Activators.Reflection;
@@ -21,6 +24,8 @@ public class ReflectionActivator : InstanceActivator, IInstanceActivator
private readonly Parameter[] _defaultParameters;
private ConstructorBinder[]? _constructorBinders;
+ private bool _anyRequiredMembers;
+ private InjectablePropertyState[]? _defaultFoundPropertySet;
///
/// Initializes a new instance of the class.
@@ -78,6 +83,40 @@ public void ConfigurePipeline(IComponentRegistryServices componentRegistryServic
throw new ArgumentNullException(nameof(pipelineBuilder));
}
+#if NET7_0_OR_GREATER
+ // The RequiredMemberAttribute has Inherit = false on its AttributeUsage options,
+ // so we can't use the expected GetCustomAttribute(inherit: true) option, and must walk the tree.
+ var currentType = _implementationType;
+ while (currentType is not null && !currentType.Equals(typeof(object)))
+ {
+ if (currentType.GetCustomAttribute() is not null)
+ {
+ _anyRequiredMembers = true;
+ break;
+ }
+
+ currentType = currentType.BaseType;
+ }
+#else
+ _anyRequiredMembers = false;
+#endif
+
+ if (_anyRequiredMembers || _configuredProperties.Length > 0)
+ {
+ // Get the full set of properties.
+ var actualProperties = _implementationType
+ .GetRuntimeProperties()
+ .Where(pi => pi.CanWrite)
+ .ToList();
+
+ _defaultFoundPropertySet = new InjectablePropertyState[actualProperties.Count];
+
+ for (int idx = 0; idx < actualProperties.Count; idx++)
+ {
+ _defaultFoundPropertySet[idx] = new InjectablePropertyState(new InjectableProperty(actualProperties[idx]));
+ }
+ }
+
// Locate the possible constructors at container build time.
var availableConstructors = ConstructorFinder.FindConstructors(_implementationType);
@@ -96,6 +135,7 @@ public void ConfigurePipeline(IComponentRegistryServices componentRegistryServic
if (binders.Length == 1)
{
UseSingleConstructorActivation(pipelineBuilder, binders[0]);
+
return;
}
else if (ConstructorSelector is IConstructorSelectorWithEarlyBinding earlyBindingSelector)
@@ -147,7 +187,7 @@ private void UseSingleConstructorActivation(IResolvePipelineBuilder pipelineBuil
var instance = boundConstructor.Instantiate();
- InjectProperties(instance, ctxt);
+ InjectProperties(instance, ctxt, boundConstructor, GetAllParameters(ctxt.Parameters));
ctxt.Instance = instance;
@@ -171,7 +211,7 @@ private void UseSingleConstructorActivation(IResolvePipelineBuilder pipelineBuil
var instance = bound.Instantiate();
- InjectProperties(instance, ctxt);
+ InjectProperties(instance, ctxt, bound, prioritisedParameters);
ctxt.Instance = instance;
@@ -204,7 +244,9 @@ private object ActivateInstance(IComponentContext context, IEnumerable parameters)
+ private BoundConstructor[] GetAllBindings(ConstructorBinder[] availableConstructors, IComponentContext context, IEnumerable allParameters)
{
- var prioritisedParameters = GetAllParameters(parameters);
-
var boundConstructors = new BoundConstructor[availableConstructors.Length];
var validBindings = availableConstructors.Length;
for (var idx = 0; idx < availableConstructors.Length; idx++)
{
- var bound = availableConstructors[idx].Bind(prioritisedParameters, context);
+ var bound = availableConstructors[idx].Bind(allParameters, context);
boundConstructors[idx] = bound;
@@ -310,33 +350,112 @@ private string GetBindingFailureMessage(BoundConstructor[] constructorBindings)
}
}
- private void InjectProperties(object instance, IComponentContext context)
+ private void InjectProperties(object instance, IComponentContext context, BoundConstructor constructor, IEnumerable allParameters)
{
- if (_configuredProperties.Length == 0)
+ // We only need to do any injection if we have a set of property information.
+ if (_defaultFoundPropertySet is null)
{
return;
}
- var actualProperties = instance
- .GetType()
- .GetRuntimeProperties()
- .Where(pi => pi.CanWrite)
- .ToList();
+ // If this constructor sets all required members,
+ // and we have no configured properties, we can just jump out.
+ if (_configuredProperties.Length == 0 && constructor.SetsRequiredMembers)
+ {
+ return;
+ }
+
+ var workingSetOfProperties = (InjectablePropertyState[])_defaultFoundPropertySet.Clone();
foreach (var configuredProperty in _configuredProperties)
{
- foreach (var actualProperty in actualProperties)
+ for (var propIdx = 0; propIdx < workingSetOfProperties.Length; propIdx++)
{
- var setter = actualProperty.SetMethod;
+ ref var prop = ref workingSetOfProperties[propIdx];
+
+ if (prop.Set)
+ {
+ // Skip, already seen.
+ continue;
+ }
- if (setter != null &&
- configuredProperty.CanSupplyValue(setter.GetParameters()[0], context, out var vp))
+ if (prop.Property.TrySupplyValue(instance, configuredProperty, context))
{
- actualProperties.Remove(actualProperty);
- actualProperty.SetValue(instance, vp(), null);
+ prop.Set = true;
break;
}
}
}
+
+ if (_anyRequiredMembers && !constructor.SetsRequiredMembers)
+ {
+ List? failingRequiredProperties = null;
+
+ for (var propIdx = 0; propIdx < workingSetOfProperties.Length; propIdx++)
+ {
+ ref var prop = ref workingSetOfProperties[propIdx];
+
+ if (!prop.Property.IsRequired)
+ {
+ // Only auto-populate required properties.
+ continue;
+ }
+
+ if (prop.Set)
+ {
+ // Only auto-populate things not already populated by a specific property
+ // being set.
+ continue;
+ }
+
+ foreach (var parameter in allParameters)
+ {
+ if (parameter is NamedParameter || parameter is PositionalParameter)
+ {
+ // Skip Named and Positional parameters, because if someone uses 'value' as a
+ // constructor parameter name, it would also match the property, and cause confusion.
+ continue;
+ }
+
+ if (prop.Property.TrySupplyValue(instance, parameter, context))
+ {
+ prop.Set = true;
+
+ break;
+ }
+ }
+
+ if (!prop.Set)
+ {
+ failingRequiredProperties ??= new();
+ failingRequiredProperties.Add(prop.Property);
+ }
+ }
+
+ if (failingRequiredProperties is not null)
+ {
+ throw new DependencyResolutionException(BuildRequiredPropertyResolutionMessage(failingRequiredProperties));
+ }
+ }
+ }
+
+ private string BuildRequiredPropertyResolutionMessage(IReadOnlyList failingRequiredProperties)
+ {
+ var propertyDescriptions = new StringBuilder();
+
+ foreach (var failed in failingRequiredProperties)
+ {
+ propertyDescriptions.AppendLine();
+ propertyDescriptions.Append(failed.Property.Name);
+ propertyDescriptions.Append(" (");
+ propertyDescriptions.Append(failed.Property.PropertyType.FullName);
+ propertyDescriptions.Append(')');
+ }
+
+ return string.Format(
+ CultureInfo.CurrentCulture,
+ ReflectionActivatorResources.RequiredPropertiesCouldNotBeBound,
+ _implementationType,
+ propertyDescriptions);
}
}
diff --git a/src/Autofac/Core/Activators/Reflection/ReflectionActivatorResources.resx b/src/Autofac/Core/Activators/Reflection/ReflectionActivatorResources.resx
index 947acb4d6..81ffa790c 100644
--- a/src/Autofac/Core/Activators/Reflection/ReflectionActivatorResources.resx
+++ b/src/Autofac/Core/Activators/Reflection/ReflectionActivatorResources.resx
@@ -1,17 +1,17 @@
-
@@ -128,4 +128,7 @@
See https://autofac.rtfd.io/help/no-constructors-bindable for more info.
-
+
+ One or more of the required properties on type '{0}' could not be resolved:{1}
+
+
\ No newline at end of file
diff --git a/test/Autofac.Specification.Test/Features/RequiredPropertyTests.cs b/test/Autofac.Specification.Test/Features/RequiredPropertyTests.cs
new file mode 100644
index 000000000..00dfe07b9
--- /dev/null
+++ b/test/Autofac.Specification.Test/Features/RequiredPropertyTests.cs
@@ -0,0 +1,355 @@
+// Copyright (c) Autofac Project. All rights reserved.
+// Licensed under the MIT License. See LICENSE in the project root for license information.
+
+using System.Diagnostics.CodeAnalysis;
+using Autofac.Core;
+
+namespace Autofac.Specification.Test.Features;
+
+#if NET7_0_OR_GREATER
+
+public class RequiredPropertyTests
+{
+ [Fact]
+ public void ResolveRequiredProperties()
+ {
+ var builder = new ContainerBuilder();
+ builder.RegisterType();
+ builder.RegisterType();
+ builder.RegisterType();
+
+ var container = builder.Build();
+
+ var component = container.Resolve();
+
+ Assert.NotNull(component.ServiceA);
+ Assert.NotNull(component.ServiceB);
+ }
+
+ [Fact]
+ public void MissingRequiredPropertyServiceThrowsException()
+ {
+ var builder = new ContainerBuilder();
+ builder.RegisterType();
+ builder.RegisterType();
+
+ var container = builder.Build();
+
+ var exception = Assert.Throws(() => container.Resolve());
+
+ Assert.Contains(nameof(Component.ServiceB), exception.InnerException.Message);
+ }
+
+ [Fact]
+ public void CanMixConstructorsAndProperties()
+ {
+ var builder = new ContainerBuilder();
+ builder.RegisterType();
+ builder.RegisterType();
+ builder.RegisterType();
+
+ var container = builder.Build();
+
+ var component = container.Resolve();
+
+ Assert.NotNull(component.ServiceA);
+ Assert.NotNull(component.ServiceB);
+ }
+
+ [Fact]
+ public void PropertiesPopulatedOnAllSubsequentResolves()
+ {
+ var builder = new ContainerBuilder();
+ builder.RegisterType();
+ builder.RegisterType();
+ builder.RegisterType();
+
+ var container = builder.Build();
+
+ var component = container.Resolve();
+
+ Assert.NotNull(component.ServiceA);
+ Assert.NotNull(component.ServiceB);
+
+ var component2 = container.Resolve();
+
+ Assert.NotNull(component2.ServiceA);
+ Assert.NotNull(component2.ServiceB);
+
+ var component3 = container.Resolve();
+
+ Assert.NotNull(component3.ServiceA);
+ Assert.NotNull(component3.ServiceB);
+ }
+
+ [Fact]
+ public void ExplicitParameterOverridesRequiredAutowiring()
+ {
+ var builder = new ContainerBuilder();
+ builder.RegisterType();
+ builder.RegisterType();
+ builder.RegisterType().WithProperty(nameof(Component.ServiceB), new ServiceB { Tag = "custom" });
+
+ var container = builder.Build();
+
+ var component = container.Resolve();
+
+ Assert.Equal("custom", component.ServiceB.Tag);
+ }
+
+ [Fact]
+ public void ExplicitParameterCanTakePlaceOfRegistration()
+ {
+ var builder = new ContainerBuilder();
+ builder.RegisterType();
+ builder.RegisterType().WithProperty(nameof(Component.ServiceB), new ServiceB { Tag = "custom" });
+
+ var container = builder.Build();
+
+ var component = container.Resolve();
+
+ Assert.Equal("custom", component.ServiceB.Tag);
+ }
+
+ [Fact]
+ public void GeneralTypeParameterCanTakePlaceOfRegistration()
+ {
+ var builder = new ContainerBuilder();
+ builder.RegisterType();
+ builder.RegisterType().WithParameter(new TypedParameter(typeof(ServiceB), new ServiceB()));
+
+ var container = builder.Build();
+
+ var component = container.Resolve();
+
+ Assert.NotNull(component.ServiceB);
+ }
+
+ [Fact]
+ public void ParameterPassedAtResolveUsedForRequiredProperty()
+ {
+ var builder = new ContainerBuilder();
+ builder.RegisterType();
+ builder.RegisterType();
+
+ var container = builder.Build();
+
+ var component = container.Resolve(new TypedParameter(typeof(ServiceB), new ServiceB()));
+
+ Assert.NotNull(component.ServiceB);
+ }
+
+ [Fact]
+ public void NamedParametersIgnoredForRequiredProperties()
+ {
+ var builder = new ContainerBuilder();
+ builder.RegisterType();
+ builder.RegisterType().WithParameter("value", new ServiceB());
+
+ var container = builder.Build();
+
+ Assert.Throws(() => container.Resolve());
+ }
+
+ [Fact]
+ public void PositionalParametersIgnoredForRequiredProperties()
+ {
+ var builder = new ContainerBuilder();
+ builder.RegisterType();
+ builder.RegisterType().WithParameter(new PositionalParameter(0, new ServiceB()));
+
+ var container = builder.Build();
+
+ Assert.Throws(() => container.Resolve());
+ }
+
+ [Fact]
+ public void SetsRequiredMembersConstructorSkipsRequiredProperties()
+ {
+ var builder = new ContainerBuilder();
+ builder.RegisterType();
+
+ var container = builder.Build();
+
+ var component = container.Resolve();
+
+ Assert.Null(component.ServiceA);
+ Assert.Null(component.ServiceB);
+ }
+
+ [Fact]
+ public void SetsRequiredMembersConstructorSkipsRequiredPropertiesEvenWhenRegistered()
+ {
+ var builder = new ContainerBuilder();
+ builder.RegisterType();
+ builder.RegisterType();
+ builder.RegisterType();
+
+ var container = builder.Build();
+
+ var component = container.Resolve();
+
+ Assert.Null(component.ServiceA);
+ Assert.Null(component.ServiceB);
+ }
+
+ [Fact]
+ public void OnlySelectedConstructorConsideredForSetsRequiredProperties()
+ {
+ var builder = new ContainerBuilder();
+ builder.RegisterType();
+
+ var container = builder.Build();
+
+ var component = container.Resolve();
+
+ // Allowed to not be set, because empty constructor was selected.
+ Assert.Null(component.ServiceA);
+
+ using var scope = container.BeginLifetimeScope(b => b.RegisterType());
+
+ // Fails, because constructor with SetsRequiredProperties attribute is selected.
+ Assert.Throws(() => scope.Resolve());
+ }
+
+ [Fact]
+ public void DerivedClassResolvesPropertiesInParent()
+ {
+ var builder = new ContainerBuilder();
+ builder.RegisterType();
+ builder.RegisterType();
+ builder.RegisterType();
+
+ var container = builder.Build();
+
+ var component = container.Resolve();
+
+ Assert.NotNull(component.ServiceA);
+ Assert.NotNull(component.ServiceB);
+ }
+
+ [Fact]
+ public void DerivedClassResolvesPropertiesInParentAndSelf()
+ {
+ var builder = new ContainerBuilder();
+ builder.RegisterType();
+ builder.RegisterType();
+ builder.RegisterType();
+ builder.RegisterType();
+
+ var container = builder.Build();
+
+ var component = container.Resolve();
+
+ Assert.NotNull(component.ServiceA);
+ Assert.NotNull(component.ServiceB);
+ Assert.NotNull(component.ServiceC);
+ }
+
+ [Fact]
+ public void CanResolveOpenGenericComponentRequiredProperties()
+ {
+ var builder = new ContainerBuilder();
+ builder.RegisterGeneric(typeof(OpenGenericService<>));
+ builder.RegisterGeneric(typeof(OpenGenericComponent<>));
+
+ var container = builder.Build();
+
+ var component = container.Resolve>();
+
+ Assert.NotNull(component.Service);
+ }
+
+ private class OpenGenericComponent
+ {
+ required public OpenGenericService Service { get; set; }
+ }
+
+ private class OpenGenericService
+ {
+ }
+
+ private class ConstructorComponent
+ {
+ [SetsRequiredMembers]
+ public ConstructorComponent()
+ {
+ }
+
+ required public ServiceA ServiceA { get; set; }
+
+ required public ServiceB ServiceB { get; set; }
+ }
+
+ private class MixedConstructorAndPropertyComponent
+ {
+ public MixedConstructorAndPropertyComponent(ServiceA serviceA)
+ {
+ ServiceA = serviceA;
+ }
+
+ public ServiceA ServiceA { get; set; }
+
+ required public ServiceB ServiceB { get; set; }
+ }
+
+ private class MultiConstructorComponent
+ {
+ [SetsRequiredMembers]
+ public MultiConstructorComponent()
+ {
+ }
+
+ public MultiConstructorComponent(ServiceC serviceC)
+ {
+ }
+
+ required public ServiceA ServiceA { get; set; }
+ }
+
+ private class Component
+ {
+ required public ServiceA ServiceA { get; set; }
+
+ required public ServiceB ServiceB { get; set; }
+ }
+
+ private class DerivedComponentWithProp : Component
+ {
+ public DerivedComponentWithProp()
+ {
+ }
+
+ required public ServiceC ServiceC { get; set; }
+ }
+
+ private class DerivedComponent : Component
+ {
+ }
+
+ private class ServiceA
+ {
+ public ServiceA()
+ {
+ Tag = "Default";
+ }
+
+ public string Tag { get; set; }
+ }
+
+ private class ServiceB
+ {
+ public ServiceB()
+ {
+ Tag = "Default";
+ }
+
+ public string Tag { get; set; }
+ }
+
+ private class ServiceC
+ {
+ }
+}
+
+#endif