Autofac – None of the constructors found can be invoked

The bug

A common pattern used when working with Autofac (or any other IoC containers) is to register multiple implementations of an interface, then resolve them all and call a method on each implementation (e.g.: an IEventHandler interface that you use notify all registered handlers that something happened).

Take the following example:

Trying to resolve all implementors of IHaveDeferredEnumerable via .Resolve<IEnumerable<IHaveDeferredEnumerable>>() will throw

None of the constructors found with ‘Autofac.Core.Activators.Reflection.DefaultConstructorFinder’ on type ‘Autofac.Test.Scenarios.ScannedAssembly.HasDeferredEnumerable+<Get>d__0’ can be invoked with the available services and parameters:
Cannot resolve parameter ‘Int32 <>1__state’ of constructor ‘Void .ctor(Int32)’.

However, trying to resolve a single implementation of the interface via .Resolve<IHaveDeferredEnumerable>() will work as expected. This means that HasDeferredEnumerable can be built independently, however for some reasons it cannot be constructed when trying to get all implementations of IHaveDeferredEnumerable.

The journey

Studying the error message further reveals some strange behavior, Autofac is complaining that the type 'Autofac.Test.Scenarios.ScannedAssembly.HasDeferredEnumerable+<Get>d__0' cannot be resolved because of some 'Int32 <>1__state' parameter required in a 'Void .ctor(Int32)' constructor. Totally weird, as I am indirectly asking for the type 'Autofac.Test.Scenarios.ScannedAssembly.HasDeferredEnumerable' which only has the default parameter-less constructor, weird.

To be studied and understood, a problem must be first isolated, therefore, I stripped all other code doing container work and was only left with IHaveDeferredEnumerableHasDeferredEnumerable, and the container registration cb.RegisterAssemblyTypes(ScenarioAssembly).AsImplementedInterfaces();

To get even better isolation, I changed the container registration from registering all the types found in my test assembly (RegisterAssemblyTypes) with cb.RegisterType<HasDeferredEnumerable>().As<IHaveDeferredEnumerable>(). Now everything worked as expected, I was able to get the single implementation via .Resolve<IHaveDeferredEnumerable>() and all implementations via .Resolve<IEnumerable<IHaveDeferredEnumerable>>(). It was now obvious that the problem occurred sometime during type registration and only when bulk registering types via the RegisterAssemblyTypes method.

Since Autofac is an open source project I checked out the repository, wrote a failing test and step-by-step debugged my way through the type registration process. This is how I found out that a concrete implementation was discovered for IEnumerable<IHaveDeferredEnumerable>, even though there is none in my scanned assembly.

Since this voodoo magic needs to be cleared out, I fired up dotPeek and decompiled my assembly, to find the following:

My HasDeferredEnumerable class has been enriched with a private class, which implements IEnumerable<IHaveDeferredEnumerable> and not surprisingly requires an integer to be constructed. How did that class ended-up in there without me typing it you ask? Well, the .NET compiler does in fact changes your code and generates new types as needed. In this particular case, it was because of the deferred execution in the Get() method – the compiler generates a helper state machine class to handle your deferred execution.

The fix

Fixing the issue was fairly trivial as the helper class is decorated with the [CompilerGenerated] attribute and it was just a matter of filtering out these compiler generated classes when running bulk type discovery. You can find the accepted pull request here and it will probably only end-up in version 4.0.0.

If you cannot wait until version 4.0.0 you can either remove the deferred execution (remove yield return/break) from the classes affected by the bug, or just make sure to manually register them into the container.

How to debug NuGet package scripts

You can step by step debug your Init.ps1, Install.ps1, or Uninstall.ps1 using NuGetDebugTools. It won’t be the best debugging experience you’ll ever have in your life, but it will surely get the job done!

At first you need to open the Package Manager Console in Visual Studio and:

  1. install the NuGetDebugTools package
  2. add the debugger
  3. set breakpoints for the scripts  you want to debug

Now you’re all set and you can begin installing your own package. Depending on which breakpoints you’ve set with the Set-PSBreakpoint command, when a break point gets hit you will be prompted with a dialog. This is where you will be entering your commands debugging commands (e.g. s for step into, v for step over)

 

NuGetDebugTools_cmdprompt

Dialog for entering debugging commands

The basic commands that you can enter are

  • s – StepInto (Step to the next statement into functions, scripts, etc)
  • v – StepOver (Step to the next statement over functions, scripts, etc)
  • o – StepOut (Step out of the current function, script, etc)
  • c – Continue (Continue operation, also on Cancel or empty)
  • q – Quit (Stop operation and exit the debugger)
  • ?, h – Help (Write this help message)
  • k – CallStack (Write call stack, aka Get-PSCallStack)
  • K – CallStackDetailed (Write detailed call stack using Format-List)

And you will be receiving feedback and additional information in the Package Manager Console

NuGetDebugTools_output
You can find an exhaustive list of commands and more detailed information on the NuGetDebugTools github page

Modifying the project during NuGet package installation and removal

By default NuGet does a great job installing and referencing all the assemblies present in the ‘lib‘ directory of the NuGet package. Furthermore, if you have other files which need to be added to the solution, you can simply add them to the ‘content‘ directory of the package. The files added to the ‘content‘ directory will be added to the solution with the following properties:

  • BuildAction – Content
  • CopyToOutputDirectory – Do not copy

This is extremely nice if you have images, unmanaged dlls, or other things that just need to be added to the solution, and not referenced.

However, there are times when the default behavior is not enough, and you need control over how your items get added to the project (e.g. if you need to have CopyToOutputDirectory=CopyIfNewer).

Running scripts when installing the package

You will need to add a Install.ps1 script to your package. You can read more on how to do that here.

Now that you added Install.ps1 to your package, it’s time to change some values here and there. Of course, there are several ways this behavior can be achieved, I will start with how NOT to do it, and continue with how to do it.

How NOT to do it

While trying to figure out myself how to achieve this, I found this answer on stackoverflow.com. Unfortunately, I was hasty enough to take that solution as “good enough”, and didn’t scrolled down a few more answers (spoiler alert: one of them contains the correct way of doing it).

I adjusted the PowerShell code with a more “mean” xquery and got this bad boy

The above script loads the project file from the disk, performs some changes to it, and then flushes it back to the disk. Mission accomplished, however, you are actually changing the project “behind the scenes”, and big surprise, Visual Studio is not entirely happy about it:

Project modified outside Visual Studio

Project modified outside Visual Studio

Q: Should I Save, Discard, Overwrite, Ignore?

A: Can I chose more than one option?

If you saved all your changes before running the package installation, in most of the cases you would be able to click Discard
and get away with it, but not all the times. What’s left, is Save As and later on performing a merge of the two project files (highly uncool). Moreover, I wonder how this will work if there are errors while installing the package and a rollback is needed.

How to do it

The parameters that your Install.ps1 script is receiving are:

You can review the details of each parameter here.

The $project is a reference to the EnvDTE project the package is being installed into. This means that you get complete control over the project file in a standard way.

Here are a few examples on how to use it:

CopyToOutputDirectory

Given the following project structure

RoccatSolutionDLL

Changing the CopyToOutputDirectory property of the talkfx-c.dll can be done with the following construct

Note: You need to work your way down the project structure hierarchy until you reach the item you are after (in this case CroccatTalkWrapper/win32-x86/talkfx-c.dll)

The possible values for CopyToOutputDirectory are

  • 0 = Do not copy
  • 1 = Copy always
  • 2 = Copy if newer

BuildAction

Keeping the same project structure as above

Standard BuildAction values are:

  • 0 = None
  • 1 = Compile
  • 2 = Content
  • 3 = EmbeddedResource

Testing

If you need to test your $project manipulations, you can do it in the Visual Studio Package Manager Console

Now you can test all your manipulations in the console.

Compatibility

Starting with Visual Studio 2017 install.ps1 and uninstall.ps1 are no longer supported (read more).