2

I'm trying to find a way in Autofac to support specific properties injection instead of overall properties injection. Authought Autofac currently provides PropertiesAutowired to support an overall properties injection, but in some cases I need only specific properties injection (like [Inject] attribute used in NInject).

Here I found a snippet (WithPropertiesAutowired, provided by Alexandr Nikitin, thanks!) in this post that the codes can support specific properties injections:

public static class PropertiesAutowiredExtensions
{
    // Extension that registers only needed properties
    // Filters by property name for simplicity
    public static IRegistrationBuilder<TLimit, TReflectionActivatorData, TRegistrationStyle>
        WithPropertiesAutowiredExcept<TLimit, TReflectionActivatorData, TRegistrationStyle>(
        this IRegistrationBuilder<TLimit, TReflectionActivatorData, TRegistrationStyle> registrationBuilder,
        params string[] propertiesNames)
        where TReflectionActivatorData : ReflectionActivatorData
    {
        var type = ((IServiceWithType)registrationBuilder.RegistrationData.Services.Single()).ServiceType;

        foreach (var property in type
            .GetProperties(BindingFlags.Public | BindingFlags.Instance)
            .Where(pi => pi.CanWrite && !propertiesNames.Contains(pi.Name)))
        {
            // There's no additional checks like in PropertiesAutowired for simplicity
            // You can add them from Autofac.Core.Activators.Reflection.AutowiringPropertyInjector.InjectProperties

            var localProperty = property;
            registrationBuilder.WithProperty(
                new ResolvedParameter(
                    (pi, c) =>
                        {
                            PropertyInfo prop;
                            return pi.TryGetDeclaringProperty(out prop) &&
                                   prop.Name == localProperty.Name;
                        },
                    (pi, c) => c.Resolve(localProperty.PropertyType)));
        }

        return registrationBuilder;
    }

    // From Autofac.Util.ReflectionExtensions
    public static bool TryGetDeclaringProperty(this ParameterInfo pi, out PropertyInfo prop)
    {
        var mi = pi.Member as MethodInfo;
        if (mi != null && mi.IsSpecialName && mi.Name.StartsWith("set_", StringComparison.Ordinal)
            && mi.DeclaringType != null)
        {
            prop = mi.DeclaringType.GetProperty(mi.Name.Substring(4));
            return true;
        }

        prop = null;
        return false;
    }
}

it works fine in general cases. But when I use the codes in a LifeTime scope, it fails:

using (var scope = container.BeginLifetimeScope(
           builder => {
              builder.RegisterInstance(SomeInstance).As<ISomeInstance>()
             .WithPropertiesAutowired("Property1", "Property2"); }))
     {
     ...
     }

Here is the error message:

The type 'Autofac.Builder.SimpleActivatorData' cannot be used as type parameter 'TReflectionActivatorData' in the generic type or method
 '....AutofacHelpers.WithPropertiesAutowired<TLimit,TReflectionActivatorData,TRegistrationStyle>(
    Autofac.Builder.IRegistrationBuilder<TLimit,TReflectionActivatorData,TRegistrationStyle>, params string[])'.

There is no implicit reference conversion from 'Autofac.Builder.SimpleActivatorData' to 'Autofac.Builder.ReflectionActivatorData'.

It seems WithPropertiesAutowired can only be used in a Container, but not for LitfeTime scope. How can I resolve this issue?

Community
  • 1
  • 1
jones
  • 317
  • 3
  • 13
  • I just found that it doesn't work is not because of the Lifetime scope, it's because of using RegisterInstance instead of using RegisterType. However I still don't know how to resolve this problem. Hope someone can help. – jones Jul 16 '15 at 06:17

1 Answers1

2

Out of the box, Autofac doesn't allow you to register an instance and inject properties on that instance (as you discovered).

From a logical perspective, if you have an instance of an object, an IoC framework should be able to assume it's fully constructed and that it's effectively a singleton - it is, after all, a very specific instance you're registering. By trying to tie an instance to property injection, you may be introducing all sorts of issues, like...

  • Lifetime scope mismatch - if you're injecting per-instance or per-lifetime-scope property values into the instance, it means the property values may not be intended to live as long as the instance/singleton.
  • Threading/race conditions - if two things in different threads resolve the instance and you inject properties on each resolve of the instance, the property values will change on the singleton and cause inconsistent behavior.

If you still want to inject properties, out of the box you can call lifetimeScope.InjectProperties(yourObjectInstance) to inject all of the properties; or lifetimeScope.InjectUnsetProperties(yourObjectInstance) to inject only the properties that are null/unset.

If you need to narrow it down further, you'll need to create your own injection method. You can look at the Autofac source to see how it works and adapt accordingly.

Travis Illig
  • 23,195
  • 2
  • 62
  • 85
  • Yes, I found some sorts of issues as trying to inject specific properties of an instance. I have made a few changes of my design so that I don't need specific properties injection any more this moment. Also thanks for your explanation about why Autofac doesn't support a more well properties injection as the [document](http://docs.autofac.org/en/latest/register/registration.html#property-injection) described. Maybe these explanation should also be put into the document so that developers can realize why this is not recommended. – jones Jul 17 '15 at 22:07
  • That "incorrect code" site appears to be stealing SO content: http://stackoverflow.com/q/21606629/8116 – Travis Illig Dec 06 '15 at 02:42