Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

InvalidCastException at kernel.Get<type>

Tags:

c#

wpf

ninject

I have a WPF app which may or may not be started with command-line args. I used to have my composition root in the OnStartup(StartupEventArgs e) method in the App.xaml code-behind, but this was causing app shutdown issues so I turned App.xaml into a "Page" (rather than "App Definition") and wrote my own Program class containing my own app entry point, which would become my new composition root location.

Since that change, I've been unable to get the app to start, Ninject can't seem to resolve the app's primary object (or could it be one of its dependencies?).

This exception is making me waste lots of time, and the stack trace is all Ninject-internal, I don't know what to fix in my code, the way I'm binding the type that's now causing this exception has not changed lately:

at DynamicInjector54d92ac63a2e47fda5ffbcc19b9942a9(Object[] )
at Ninject.Activation.Providers.StandardProvider.Create(IContext context)
at Ninject.Activation.Context.Resolve()
at Ninject.KernelBase.<>c__DisplayClass10.<Resolve>b__c(IBinding binding)
at System.Linq.Enumerable.WhereSelectEnumerableIterator`2.MoveNext()
at System.Linq.Enumerable.SingleOrDefault[TSource](IEnumerable`1 source)
at Ninject.Planning.Targets.Target`1.GetValue(Type service, IContext parent)
at Ninject.Planning.Targets.Target`1.ResolveWithin(IContext parent)
at Ninject.Activation.Providers.StandardProvider.GetValue(IContext context, ITarget target)
at Ninject.Activation.Providers.StandardProvider.<>c__DisplayClass4.<Create>b__2(ITarget target)
at System.Linq.Enumerable.WhereSelectArrayIterator`2.MoveNext()
at System.Linq.Buffer`1..ctor(IEnumerable`1 source)
at System.Linq.Enumerable.ToArray[TSource](IEnumerable`1 source)
at Ninject.Activation.Providers.StandardProvider.Create(IContext context)
at Ninject.Activation.Context.Resolve()
at Ninject.KernelBase.<>c__DisplayClass10.<Resolve>b__c(IBinding binding)
at System.Linq.Enumerable.WhereSelectEnumerableIterator`2.MoveNext()
at System.Linq.Enumerable.SingleOrDefault[TSource](IEnumerable`1 source)
at Ninject.Planning.Targets.Target`1.GetValue(Type service, IContext parent)
at Ninject.Planning.Targets.Target`1.ResolveWithin(IContext parent)
at Ninject.Activation.Providers.StandardProvider.GetValue(IContext context, ITarget target)
at Ninject.Activation.Providers.StandardProvider.<>c__DisplayClass4.<Create>b__2(ITarget target)
at System.Linq.Enumerable.WhereSelectArrayIterator`2.MoveNext()
at System.Linq.Buffer`1..ctor(IEnumerable`1 source)
at System.Linq.Enumerable.ToArray[TSource](IEnumerable`1 source)
at Ninject.Activation.Providers.StandardProvider.Create(IContext context)
at Ninject.Activation.Context.Resolve()
at Ninject.KernelBase.<>c__DisplayClass10.<Resolve>b__c(IBinding binding)
at System.Linq.Enumerable.WhereSelectEnumerableIterator`2.MoveNext()
at System.Linq.Enumerable.SingleOrDefault[TSource](IEnumerable`1 source)
at Ninject.Planning.Targets.Target`1.GetValue(Type service, IContext parent)
at Ninject.Planning.Targets.Target`1.ResolveWithin(IContext parent)
at Ninject.Activation.Providers.StandardProvider.GetValue(IContext context, ITarget target)
at Ninject.Activation.Providers.StandardProvider.<>c__DisplayClass4.<Create>b__2(ITarget target)
at System.Linq.Enumerable.WhereSelectArrayIterator`2.MoveNext()
at System.Linq.Buffer`1..ctor(IEnumerable`1 source)
at System.Linq.Enumerable.ToArray[TSource](IEnumerable`1 source)
at Ninject.Activation.Providers.StandardProvider.Create(IContext context)
at Ninject.Activation.Context.Resolve()
at Ninject.KernelBase.<>c__DisplayClass10.<Resolve>b__c(IBinding binding)
at System.Linq.Enumerable.WhereSelectEnumerableIterator`2.MoveNext()
at System.Linq.Enumerable.<CastIterator>d__b1`1.MoveNext()
at System.Linq.Enumerable.Single[TSource](IEnumerable`1 source)
at Ninject.ResolutionExtensions.Get[T](IResolutionRoot root, IParameter[] parameters)
at MyProgram.Program.Main(String[] args) in C:\Dev\MyProject\MyProject.WinPresentation\Program.cs:ligne 40
at System.AppDomain._nExecuteAssembly(RuntimeAssembly assembly, String[] args)
at System.AppDomain.ExecuteAssembly(String assemblyFile, Evidence assemblySecurity, String[] args)
at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
at System.Threading.ThreadHelper.ThreadStart_Context(Object state)
at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
at System.Threading.ThreadHelper.ThreadStart()

Here's the Main method / app entry point:

[STAThread]
public static void Main(string[] args)
{
    var module = new MyAppNinjectModule(args);
    var kernel = new StandardKernel(module);

    var argsHelper = module.CommandLineArgs;

    var logProvider = kernel.Get<ILogProvider>();
    var logger = logProvider.GetLogger(typeof(Program).Name);

    if (argsHelper.LoggingDisabledArgument.IsSpecified()) logProvider.DisableLogging();
    logger.Info(log.LogAppStart);

    var installer = kernel.Get<IInstaller>(); // >>> InvalidCastException here

    if (argsHelper.QuietInterfaceArgument.IsSpecified())
    {
        // running with -quiet command-line switch: just execute and exit.
        installer.Execute();
    }
    else
    {
        // instantiate a new App object (WPF), and run it.
        // installer.Execute() may or may not be executed, depending on user actions.
        var app = new App(installer);
        app.Run();
    }
}

The NinjectModule binds IInstaller to such or such implementation, depending on supplied command-line arguments (e.g. SilentInstaller when QuietInterfaceArgument is specified, ManualInstaller when it's not, etc.).

Usually Ninject gives very useful and detailed exception messages - when it's an ActivationException life is good. But this InvalidCastException leaves me clueless, I'm not the one performing the invalid cast, and I don't even know what types are being involved. I just know that I might have written some code that Ninject doesn't like, and that maybe it has something to do with the way I'm binding IInstaller to its implementations, but if I comment-out the "branching" part of the NinjectModule to force-bind the specific implementation (ManualInstaller) that I'm after, it still fails with this InvalidCastException.

The implementation's constructor:

public ManualInstaller(IView<MainWindowViewModel> view, 
                       IProcessHelper processHelper,
                       ISettingsHelper settingsHelper,
                       ILogProvider logProvider,
                       ISetupBootstrapper installer,
                       bool notifySuccess)
    : base(notifySuccess, processHelper, settingsHelper, logProvider, installer)

The corresponding binding code (rest of dependencies are already bound and there's no ActivationException so not sure how relevant this is to my issue):

var msg = string.Empty;
if (CommandLineArgs.CompletionMessageArgument.IsSpecified()) 
    msg = CommandLineArgs.CompletionMessageArgument.ParameterValue();

Bind<MainWindowViewModel>().ToSelf().WithConstructorArgument("completionMessage", msg);
Bind<IView<MainWindowViewModel>>().To<MainWindow>();

Bind<IInstaller>().To<ManualInstaller>()
                  .WithConstructorArgument("notifySuccess", notifySuccess);

Don't hesitate to let me know if anything else is needed to find out what's going on...

EDIT

Resolving the installer's dependencies individually yields more information:

// resolve installer dependencies:
var view = kernel.Get<IView<MainWindowViewModel>>(); // >>> InvalidCastException here
var processHelper = kernel.Get<IProcessHelper>();
var settingsHelper = kernel.Get<ISettingsHelper>();
var bootstrapper = kernel.Get<ISetupBootstrapper>();
var installer = new ManualInstaller(view, processHelper, settingsHelper, logProvider, bootstrapper, true);

So I can at least narrow it down to one specific dependency of the type I'm trying to resolve: the problem is either with the View, or with its sole dependency, the ViewModel. So I did this:

// resolve ViewModel dependencies:
var processHelper = kernel.Get<IProcessHelper>(); // >>> InvalidCastException here
var settingsHelper = kernel.Get<ISettingsHelper>();
var messenger = kernel.Get<INetworkMessenger>();
var factory = kernel.Get<IBuildServerFactory>();
var dialogs = kernel.Get<ICommonDialogs>();

So apparently it's the IProcessHelper implementation that's problematic - again:

// resolve ProcessHelper dependencies:
var processWrapper = kernel.Get<IProcessWrapper>();
var wmiWrapper = kernel.Get<IWindowsManagementInstrumentationWrapper>();
var helper = new ProcessHelper(processWrapper, wmiWrapper, logProvider, 300);

And now I no longer get the InvalidCastException.

Here's the problematic class' constructor and fields:

private readonly ILogProvider _logProvider;
private readonly IProcessWrapper _process;
private readonly IWindowsManagementInstrumentationWrapper _wmi;
public int TimeoutSeconds { get; private set; }

public ProcessHelper(IProcessWrapper process, 
                     IWindowsManagementInstrumentationWrapper wmiWrapper, 
                     ILogProvider logProvider, 
                     int timeout)
{
    _logProvider = logProvider;
    _process = process;
    _wmi = wmiWrapper;
    TimeoutSeconds = timeout;
}

And how the NinjectModule binds it:

Bind<IProcessHelper>().To<ProcessHelper>()
                      .WithConstructorArgument("timeout", Properties.Settings.Default.ProcessTimeoutSeconds);

** RE-EDIT **

With the help of @jure's comment, I've found that the type of ProcessTimeoutSeconds in Properties.Settings.Default is actually set to string - when obviously it wants an int. Bang on!

like image 484
Mathieu Guindon Avatar asked Jun 27 '13 14:06

Mathieu Guindon


1 Answers

As you are passing constant values to the WithConstructorArguments setup, you need to be sure that the type of the object is valid for implementation class constructor argument.

As you later discovered, you were passing string object as the constructor argument in the setup but the constructor expected an int. And that gave you InvalidCastException.

As a side note, it would be nice if the Ninject gave you some nicer exception, this is indeed hard to debug.

like image 140
Jurica Smircic Avatar answered Oct 19 '22 08:10

Jurica Smircic