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