Skip to content

Commit

Permalink
Fix #1030 - Allow ParameterFilterAttribute to check if parameter can …
Browse files Browse the repository at this point in the history
…be resolved
  • Loading branch information
tillig authored Nov 26, 2019
2 parents 13ea57d + cb79617 commit 53a10c1
Show file tree
Hide file tree
Showing 5 changed files with 114 additions and 1 deletion.
14 changes: 14 additions & 0 deletions src/Autofac/Features/AttributeFilters/KeyFilterAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using Autofac.Core;

namespace Autofac.Features.AttributeFilters
{
Expand Down Expand Up @@ -136,5 +137,18 @@ public override object ResolveParameter(ParameterInfo parameter, IComponentConte
context.TryResolveKeyed(Key, parameter.ParameterType, out value);
return value;
}

/// <summary>
/// Checks a constructor parameter can be resolved based on keyed service requirements.
/// </summary>
/// <param name="parameter">The specific parameter being resolved that is marked with this attribute.</param>
/// <param name="context">The component context under which the parameter is being resolved.</param>
/// <returns>true if parameter can be resolved; otherwise, false.</returns>
public override bool CanResolveParameter(ParameterInfo parameter, IComponentContext context)
{
if (parameter == null) throw new ArgumentNullException(nameof(parameter));
if (context == null) throw new ArgumentNullException(nameof(context));
return context.ComponentRegistry.IsRegistered(new KeyedService(Key, parameter.ParameterType));
}
}
}
29 changes: 29 additions & 0 deletions src/Autofac/Features/AttributeFilters/MetadataFilterAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,8 @@ public sealed class MetadataFilterAttribute : ParameterFilterAttribute

private static readonly MethodInfo FilterAllMethod = typeof(MetadataFilterAttribute).GetTypeInfo().GetDeclaredMethod(nameof(FilterAll));

private static readonly MethodInfo CanResolveMethod = typeof(MetadataFilterAttribute).GetTypeInfo().GetDeclaredMethod(nameof(CanResolve));

/// <summary>
/// Initializes a new instance of the <see cref="MetadataFilterAttribute"/> class.
/// </summary>
Expand Down Expand Up @@ -165,6 +167,26 @@ public override object ResolveParameter(ParameterInfo parameter, IComponentConte
: FilterOneMethod.MakeGenericMethod(elementType).Invoke(null, new[] { context, Key, Value });
}

/// <summary>
/// Checks a constructor parameter can be resolved based on metadata requirements.
/// </summary>
/// <param name="parameter">The specific parameter being resolved that is marked with this attribute.</param>
/// <param name="context">The component context under which the parameter is being resolved.</param>
/// <returns>true if parameter can be resolved; otherwise, false.</returns>
public override bool CanResolveParameter(ParameterInfo parameter, IComponentContext context)
{
if (parameter == null) throw new ArgumentNullException(nameof(parameter));
if (context == null) throw new ArgumentNullException(nameof(context));

// GetElementType currently is the effective equivalent of "Determine if the type
// is in IEnumerable and if it is, get the type being enumerated." This doesn't support
// the other relationship types like Lazy<T>, Func<T>, etc. If we need to add that,
// this is the place to do it.
var elementType = GetElementType(parameter.ParameterType);

return (bool)CanResolveMethod.MakeGenericMethod(elementType).Invoke(null, new[] { context, Key, Value });
}

private static Type GetElementType(Type type)
{
return type.IsGenericEnumerableInterfaceType() ? type.GetTypeInfo().GenericTypeArguments[0] : type;
Expand All @@ -187,5 +209,12 @@ private static IEnumerable<T> FilterAll<T>(IComponentContext context, string met
.Select(m => m.Value.Value)
.ToArray();
}

private static bool CanResolve<T>(IComponentContext context, string metadataKey, object metadataValue)
{
// Using Lazy<T> to ensure components that aren't actually used won't get activated.
return context.Resolve<IEnumerable<Meta<Lazy<T>>>>()
.Any(m => m.Metadata.ContainsKey(metadataKey) && metadataValue.Equals(m.Metadata[metadataKey]));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -63,5 +63,13 @@ public abstract class ParameterFilterAttribute : Attribute
/// <param name="context">The component context under which the parameter is being resolved.</param>
/// <returns>The instance of the object that should be used for the parameter value.</returns>
public abstract object ResolveParameter(ParameterInfo parameter, IComponentContext context);

/// <summary>
/// Implemented in derived classes to check a specific parameter can be resolved.
/// </summary>
/// <param name="parameter">The specific parameter being resolved that is marked with this attribute.</param>
/// <param name="context">The component context under which the parameter is being resolved.</param>
/// <returns>true if parameter can be resolved; otherwise, false.</returns>
public abstract bool CanResolveParameter(ParameterInfo parameter, IComponentContext context);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,11 @@ public static IRegistrationBuilder<TLimit, TReflectionActivatorData, TRegistrati
if (builder == null) throw new ArgumentNullException(nameof(builder));

return builder.WithParameter(
(p, c) => p.GetCustomAttributes<ParameterFilterAttribute>(true).Any(),
(p, c) =>
{
var filter = p.GetCustomAttributes<ParameterFilterAttribute>(true).FirstOrDefault();
return filter != null && filter.CanResolveParameter(p, c);
},
(p, c) =>
{
var filter = p.GetCustomAttributes<ParameterFilterAttribute>(true).First();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Autofac.Core;
using Autofac.Features.AttributeFilters;
using Autofac.Features.Metadata;
using Autofac.Features.OwnedInstances;
Expand Down Expand Up @@ -80,6 +81,9 @@ public void KeyFilterIsAppliedOnConstructorDependencyMultiple()
builder.RegisterType<ToolWindowAdapter>()
.Keyed<IAdapter>("Other");

builder.RegisterType<ConsoleLogger>()
.Keyed<ILogger>("Solution");

builder.RegisterType<SolutionExplorerKeyed>()
.WithAttributeFiltering();

Expand Down Expand Up @@ -266,6 +270,10 @@ public void MetadataFilterIsAppliedOnConstructorDependencyMultiple()
.WithMetadata<AdapterMetadata>(m => m.For(am => am.Target, "Other"))
.As<IAdapter>();

builder.RegisterType<ConsoleLogger>()
.WithMetadata("LoggerName", "Solution")
.As<ILogger>();

builder.RegisterType<SolutionExplorerMetadata>().WithAttributeFiltering();

var container = builder.Build();
Expand Down Expand Up @@ -299,6 +307,10 @@ public void ComponentsThatAreNotUsedDoNotGetActivated()
.As<IAdapter>()
.OnActivating(h => adapterActivationCount++);

builder.RegisterType<ConsoleLogger>()
.WithMetadata("LoggerName", "Solution")
.As<ILogger>();

builder.RegisterType<SolutionExplorerMetadata>()
.WithAttributeFiltering();

Expand All @@ -309,6 +321,32 @@ public void ComponentsThatAreNotUsedDoNotGetActivated()
Assert.Equal(2, adapterActivationCount);
}

[Fact]
public void RequiredParameterWithKeyFilterCanNotBeResolvedWhenNotRegister()
{
var builder = new ContainerBuilder();

builder.RegisterType<RequiredParameterWithKeyFilter>()
.WithAttributeFiltering()
.As<RequiredParameterWithKeyFilter>();

Assert.Throws<DependencyResolutionException>(() => builder.Build().Resolve<RequiredParameterWithKeyFilter>());
}

[Fact]
public void OptionalParameterWithKeyFilterCanBeResolvedBySpecifiedDefaultValue()
{
var builder = new ContainerBuilder();

builder.RegisterType<OptionalParameterWithKeyFilter>()
.WithAttributeFiltering()
.As<OptionalParameterWithKeyFilter>();

var instance = builder.Build().Resolve<OptionalParameterWithKeyFilter>();

Assert.Equal(15, instance.Parameter);
}

public interface ILogger
{
}
Expand Down Expand Up @@ -454,5 +492,25 @@ public class AdapterMetadata
public class EmptyMetadata
{
}

public class RequiredParameterWithKeyFilter
{
public int Parameter { get; set; }

public RequiredParameterWithKeyFilter([KeyFilter(0)] int parameter)
{
Parameter = parameter;
}
}

public class OptionalParameterWithKeyFilter
{
public int Parameter { get; set; }

public OptionalParameterWithKeyFilter([KeyFilter(0)] int parameter = 15)
{
Parameter = parameter;
}
}
}
}

0 comments on commit 53a10c1

Please sign in to comment.