Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
cc61d9e
Converted MEDI keyed service tests for Autofac.
tillig Feb 9, 2026
a3ec85d
Update table for Autofac syntax.
tillig Feb 9, 2026
42b272d
Updated tests to use Autofac exception types.
tillig Feb 9, 2026
8142963
Spelling.
tillig Feb 10, 2026
20b455e
Remove disposable from fake service. Not required for these tests.
tillig Feb 10, 2026
c8278ae
IsCollectionServiceType extension to check for collections.
tillig Feb 10, 2026
0a220e3
Remove unused usings.
tillig Feb 10, 2026
c53b231
Remove unused usings.
tillig Feb 10, 2026
f3cb463
Move keyed service tests into features.
tillig Feb 10, 2026
44f34b5
First pass at AnyKey support.
tillig Feb 10, 2026
35bc7ca
Updates for first run at AnyKey.
tillig Feb 10, 2026
a0f638c
Fix comment typos.
tillig Feb 10, 2026
8a66857
Revert to default Autofac functionality for KeyFilter.
tillig Feb 10, 2026
120fc30
Remove test that breaks backward-compatibility.
tillig Feb 10, 2026
fac717a
Remove unused resources.
tillig Feb 10, 2026
03da42d
Note on design in the key filter.
tillig Feb 10, 2026
e511c06
Refactor to use a parameter type instead of wrapper collection.
tillig Feb 10, 2026
5466431
Added service key injection for constructors and properties.
tillig Feb 10, 2026
71879c8
Update tests to ensure open generics work with key injection.
tillig Feb 11, 2026
376fde1
Temporarily turn off NuGetAudit for dev; need to turn it back on later.
tillig Feb 11, 2026
c1953f3
Optimization for service key parameter in keyed service resolution.
tillig Feb 11, 2026
4db776b
Minor perf improvements.
tillig Feb 11, 2026
2232792
v9.1.0 for new AnyKey functionality.
tillig Feb 11, 2026
b970b4e
Revert temporary NuGetAudit disable.
tillig Feb 11, 2026
8ac917c
Design notes.
tillig Feb 12, 2026
f8d4615
Doc updates.
tillig Feb 12, 2026
00446eb
Unit test coverage for new classes.
tillig Feb 12, 2026
8397dfa
Tests.
tillig Feb 12, 2026
e6a3108
Add caching to IsCollectionServiceType.
tillig Feb 12, 2026
34bc69f
Tests.
tillig Feb 12, 2026
0b69349
Tests.
tillig Feb 12, 2026
9e2c5e7
Design docs.
tillig Feb 12, 2026
2df2506
Corrected key parameter usage tests.
tillig Feb 12, 2026
abe2e92
Update pre-commit to latest.
tillig Feb 12, 2026
1161e1f
Fix charset.
tillig Feb 12, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: "cef0300fd0fc4d2a87a85fa2093c6b283ea36f4b" # v5.0.0
rev: "3e8a8703264a2f4a69428a0aa4dcb512790b2c8c" # frozen: v6.0.0
hooks:
- id: check-json
- id: check-yaml
- id: check-merge-conflict
- id: end-of-file-fixer
- id: trailing-whitespace
- repo: https://github.com/igorshubovych/markdownlint-cli
rev: "192ad822316c3a22fb3d3cc8aa6eafa0b8488360" # v0.45.0
rev: "76b3d32d3f4b965e1d6425253c59407420ae2c43" # frozen: v0.47.0
hooks:
- id: markdownlint
args:
- --fix
- repo: https://github.com/tillig/json-sort-cli
rev: "009ab2ab49e1f2fa9d6b9dfc31009ceeca055204" # v3.0.0
rev: "009ab2ab49e1f2fa9d6b9dfc31009ceeca055204" # frozen: v3.0.0
hooks:
- id: json-sort
args:
Expand Down
2 changes: 2 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"finalizers",
"inheritdoc",
"langword",
"medi",
"netcoreapp",
"netstandard",
"notnull",
Expand All @@ -20,6 +21,7 @@
"subclassing",
"typeparam",
"unconfigured",
"unkeyed",
"xunit"
],
"coverage-gutters.coverageBaseDir": "artifacts/logs",
Expand Down
2 changes: 0 additions & 2 deletions bench/Autofac.Benchmarks/RequiredPropertyBenchmark.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
// Copyright (c) Autofac Project. All rights reserved.
// Licensed under the MIT License. See LICENSE in the project root for license information.

using Autofac.Core;

namespace Autofac.Benchmarks;

public class RequiredPropertyBenchmark
Expand Down
2 changes: 1 addition & 1 deletion default.proj
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<Project DefaultTargets="All" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="Current">
<PropertyGroup>
<!-- Increment the overall semantic version here. -->
<Version>9.0.0</Version>
<Version>9.1.0</Version>
<SolutionName>Autofac</SolutionName>
<Configuration Condition="'$(Configuration)'==''">Release</Configuration>
<ArtifactDirectory>$([System.IO.Path]::Combine($(MSBuildProjectDirectory),"artifacts"))</ArtifactDirectory>
Expand Down
5 changes: 5 additions & 0 deletions src/Autofac/Builder/MetadataKeys.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,9 @@ internal static class MetadataKeys
/// Event handler for <see cref="Core.Registration.ComponentRegistryBuilder.RegistrationSourceAdded"/>.
/// </summary>
internal const string RegistrationSourceAddedPropertyKey = "__RegistrationSourceAddedKey";

/// <summary>
/// Marks registrations generated by the AnyKey adapter.
/// </summary>
internal const string AnyKeyAdapter = "__AnyKeyAdapter";
}
2 changes: 2 additions & 0 deletions src/Autofac/ContainerBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using Autofac.Features.Collections;
using Autofac.Features.GeneratedFactories;
using Autofac.Features.Indexed;
using Autofac.Features.KeyedServices;
using Autofac.Features.LazyDependencies;
using Autofac.Features.Metadata;
using Autofac.Features.OwnedInstances;
Expand Down Expand Up @@ -233,6 +234,7 @@ private void RegisterDefaultAdapters(IComponentRegistryBuilder componentRegistry
{
this.RegisterGeneric(typeof(KeyedServiceIndex<,>)).As(typeof(IIndex<,>)).InstancePerLifetimeScope();
componentRegistry.AddRegistrationSource(new CollectionRegistrationSource());
componentRegistry.AddRegistrationSource(new AnyKeyRegistrationSource());
componentRegistry.AddRegistrationSource(new OwnedInstanceRegistrationSource());
componentRegistry.AddRegistrationSource(new MetaRegistrationSource());
componentRegistry.AddRegistrationSource(new LazyRegistrationSource());
Expand Down
1 change: 0 additions & 1 deletion src/Autofac/Core/Activators/DefaultPropertySelector.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
// Licensed under the MIT License. See LICENSE in the project root for license information.

using System.Reflection;
using System.Runtime.CompilerServices;

using Autofac.Util;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
// Copyright (c) Autofac Project. All rights reserved.
// Licensed under the MIT License. See LICENSE in the project root for license information.

using System.Collections.Concurrent;
using System.Reflection;
using Autofac.Util;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
// Copyright (c) Autofac Project. All rights reserved.
// Licensed under the MIT License. See LICENSE in the project root for license information.

using System.Collections.Concurrent;
using System.Reflection;

namespace Autofac.Core.Activators.Reflection;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
// Licensed under the MIT License. See LICENSE in the project root for license information.

using System.Reflection;
using System.Runtime.CompilerServices;

using Autofac.Util;

Expand Down
47 changes: 46 additions & 1 deletion src/Autofac/Core/Activators/Reflection/ReflectionActivator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@

using System.Globalization;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text;
using Autofac.Core.Resolving.Pipeline;
using Autofac.Util;
Expand All @@ -18,6 +17,7 @@ public class ReflectionActivator : InstanceActivator, IInstanceActivator
private readonly Type _implementationType;
private readonly Parameter[] _configuredProperties;
private readonly Parameter[] _defaultParameters;
private readonly bool _requiresServiceKeyParameter;

private ConstructorBinder[]? _constructorBinders;
private bool _anyRequiredMembers;
Expand Down Expand Up @@ -50,6 +50,9 @@ public ReflectionActivator(
}

_implementationType = implementationType;
_requiresServiceKeyParameter = ReflectionCacheSet.Shared.Internal.ServiceKeyUsageByType.GetOrAdd(
_implementationType,
static t => UsesServiceKeyAttribute(t));
ConstructorFinder = constructorFinder ?? throw new ArgumentNullException(nameof(constructorFinder));
ConstructorSelector = constructorSelector ?? throw new ArgumentNullException(nameof(constructorSelector));
_configuredProperties = configuredProperties.ToArray();
Expand All @@ -66,6 +69,11 @@ public ReflectionActivator(
/// </summary>
public IConstructorSelector ConstructorSelector { get; }

/// <summary>
/// Gets a value indicating whether the activation pipeline needs a keyed service parameter for this type.
/// </summary>
internal bool RequiresServiceKeyParameter => _requiresServiceKeyParameter;

/// <inheritdoc/>
public void ConfigurePipeline(IComponentRegistryServices componentRegistryServices, IResolvePipelineBuilder pipelineBuilder)
{
Expand Down Expand Up @@ -156,6 +164,43 @@ public void ConfigurePipeline(IComponentRegistryServices componentRegistryServic
});
}

/// <summary>
/// Determines if any constructor parameters or settable properties on the
/// type have a <see cref="ServiceKeyAttribute"/>, which would require
/// special handling in the activation pipeline.
/// </summary>
/// <param name="implementationType">The type to inspect.</param>
/// <returns><see langword="true"/> if the type uses the <see cref="ServiceKeyAttribute"/>; otherwise, <see langword="false"/>.</returns>
private static bool UsesServiceKeyAttribute(Type implementationType)
{
// Intentionally not picky about _which_ constructor or property has the
// attribute. If a different constructor is picked via constructor
// binder/selector, it's fine; we just want to try to shortcut the case
// where we "may or may not need it." If you mark a property with the
// attribute but never inject properties, we'll still provide the
// parameter "just in case" you change your mind at runtime.
foreach (var constructor in implementationType.GetConstructors(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))
{
foreach (var parameter in constructor.GetParameters())
{
if (ServiceKeyAttributeCache.ParameterHasServiceKey(parameter))
{
return true;
}
}
}

foreach (var property in implementationType.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))
{
if (property.CanWrite && ServiceKeyAttributeCache.PropertyHasServiceKey(property))
{
return true;
}
}

return false;
}

private void UseSingleConstructorActivation(IResolvePipelineBuilder pipelineBuilder, ConstructorBinder singleConstructor)
{
if (singleConstructor.ParameterCount == 0)
Expand Down
1 change: 0 additions & 1 deletion src/Autofac/Core/ImplicitRegistrationSource.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
// Copyright (c) Autofac Project. All rights reserved.
// Licensed under the MIT License. See LICENSE in the project root for license information.

using System.Collections.Concurrent;
using System.Reflection;
using Autofac.Builder;
using Autofac.Util;
Expand Down
18 changes: 18 additions & 0 deletions src/Autofac/Core/InternalReflectionCaches.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ public InternalReflectionCaches(ReflectionCacheSet set)
DefaultPublicConstructors = set.GetOrCreateCache<ReflectionCacheDictionary<Type, ConstructorInfo[]>>(nameof(DefaultPublicConstructors));
GenericTypeDefinitionByType = set.GetOrCreateCache<ReflectionCacheDictionary<Type, Type>>(nameof(GenericTypeDefinitionByType));
HasRequiredMemberAttribute = set.GetOrCreateCache<ReflectionCacheDictionary<Type, bool>>(nameof(HasRequiredMemberAttribute));
ServiceKeyParameterAttributes = set.GetOrCreateCache<ReflectionCacheParameterDictionary<bool>>(nameof(ServiceKeyParameterAttributes));
ServiceKeyPropertyAttributes = set.GetOrCreateCache<ReflectionCacheDictionary<PropertyInfo, bool>>(nameof(ServiceKeyPropertyAttributes));
ServiceKeyUsageByType = set.GetOrCreateCache<ReflectionCacheDictionary<Type, bool>>(nameof(ServiceKeyUsageByType));
}

/// <summary>
Expand Down Expand Up @@ -91,4 +94,19 @@ public InternalReflectionCaches(ReflectionCacheSet set)
/// Gets a cache used by <see cref="ReflectionActivator"/>.
/// </summary>
public ReflectionCacheDictionary<Type, bool> HasRequiredMemberAttribute { get; }

/// <summary>
/// Gets a cache used to track <see cref="ServiceKeyAttribute"/> usage on parameters.
/// </summary>
public ReflectionCacheParameterDictionary<bool> ServiceKeyParameterAttributes { get; }

/// <summary>
/// Gets a cache used to track <see cref="ServiceKeyAttribute"/> usage on properties.
/// </summary>
public ReflectionCacheDictionary<PropertyInfo, bool> ServiceKeyPropertyAttributes { get; }

/// <summary>
/// Gets a cache used to determine if a type uses <see cref="ServiceKeyAttribute"/>.
/// </summary>
public ReflectionCacheDictionary<Type, bool> ServiceKeyUsageByType { get; }
}
29 changes: 29 additions & 0 deletions src/Autofac/Core/KeyedService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,18 @@ public KeyedService(object serviceKey, Type serviceType)
ServiceType = serviceType ?? throw new ArgumentNullException(nameof(serviceType));
}

/// <summary>
/// Gets a sentinel key representing a wildcard keyed registration.
/// </summary>
/// <value>
/// A singleton object that can be used as a key to match any keyed registration.
/// </value>
/// <remarks>
/// Registering a service with this key will allow it to be resolved by any
/// keyed service request for the same service type.
/// </remarks>
public static object AnyKey { get; } = new object();

/// <summary>
/// Gets the key of the service.
/// </summary>
Expand All @@ -37,6 +49,23 @@ public KeyedService(object serviceKey, Type serviceType)
/// <value>The description.</value>
public override string Description => ServiceKey + " (" + ServiceType.FullName + ")";

/// <summary>
/// Determines whether the provided key is the <see cref="AnyKey"/> sentinel.
/// </summary>
/// <param name="serviceKey">The key to test.</param>
/// <returns>
/// <see langword="true"/> when the key is <see cref="AnyKey"/>; otherwise, <see langword="false"/>.
/// </returns>
public static bool IsAnyKey(object serviceKey)
{
if (serviceKey == null)
{
throw new ArgumentNullException(nameof(serviceKey));
}

return ReferenceEquals(serviceKey, AnyKey);
}

/// <summary>
/// Indicates whether the current object is equal to another object of the same type.
/// </summary>
Expand Down
Loading