Thierry
Thierry

Reputation: 6458

Accessing MainViewModel from XAML page when using Autofac and inject service(s)

I'm trying to access my MainPageViewModel directly from my XAML page definition:

<Window x:Class="MyApp.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:MyApp"
        mc:Ignorable="d"
        Title="MyApp" Height="350" Width="650" ShowInTaskbar="True"
        WindowState="Maximized"
        DataContext="{Binding Source={StaticResource Locator}, Path=MainViewModel}">

My Locator is created in my App.XAML:

<Application.Resources>
    <ResourceDictionary>
        <vm:ViewModelLocator x:Key="Locator" />
    </ResourceDictionary>
</Application.Resources>

And my ViewModelLocator is defined as follows:

public class ViewModelLocator
{
    private static readonly IContainer Container;

    static ViewModelLocator()
    {
        // load specific logger - Hardcoded for sample sake
        string loggerAssembly = "Logger.Database.dll";

        // register logger database by type i.e. ILogger
        var builder = new ContainerBuilder();
        Assembly assembly = Assembly.LoadFrom(loggerAssembly);
        builder.RegisterAssemblyTypes(assembly).As<ILogger>();            

        // register MainViewModel and Inject ILogger
        // Is this correct? Is this how you inject a service?
        builder.RegisterType<MainViewModel>().As<ILogger>().InstancePerDependency();

        Container = builder.Build();
    }

    public ILogger Logger => Container.Resolve<ILogger>();

    // property to access MainViewModel in XAML
    public MainViewModel MainViewModel => Container.Resolve<MainViewModel>();
}

But when I run my app, it generates an error from the App.XAML on the locator and it's trying to access the MainViewModel property as defined above. The error I'm getting is:

Exception Message:

None of the constructors found with 
'Autofac.Core.Activators.Reflection.DefaultConstructorFinder' on 
type 'MyCompany.Logger.Database.Logger' can be invoked with the 
available services and parameters: Cannot resolve parameter 
'System.String connectionString' of constructor 'Void .ctor(System.String)'.

Exception StackTrace:

at Autofac.Core.Activators.Reflection.ReflectionActivator.ActivateInstance
(IComponentContext context, IEnumerable`1 parameters)
at Autofac.Core.Resolving.InstanceLookup.Activate(IEnumerable`1 parameters)

And my MainPageViewModel is defined as follows:

Public class MainPageViewModel()
{
    public MainViewModel(ILogger logger)
    {
    ....
    }
}

Any ideas on how I can resolve this?

UPDATE 1:

Note that my original definition:

builder.RegisterAssemblyTypes(assembly).As<ILogger>();

was probably ok as all my "logger" assemblies only implement the ILogger interface but I did originally use AsImplementedInterfaces()

I've now updated my code based on @HenkHolterman suggestions:

builder.RegisterType<MainViewModel>();
builder.RegisterAssemblyTypes(assembly).AsImplementedInterfaces();

But I'm not getting the following exception:

None of the constructors found with 'Autofac.Core.Activators.Reflection.
DefaultConstructorFinder' on type 'MyCompany.Logger.Database.Logger' can be
invoked with the available services and parameters:

Cannot resolve parameter 'System.String connectionString' of constructor 
'Void .ctor(System.String)'.

Now based on the last part of this exception, it seems to be related to the parameter connectionString and this parameter was part of my database logger class (MyCompany.Logger.Database.Logger), so I've temporarily removed it in order to test what @HenkHolterman had suggested and this now works but I still have a problem.

I need to pass a connection string when I'm using the database logger. I need to pass a filename when I'm using the file logger, etc...

so how do I pass a parameter to the logger constructor. I've tried:

    builder.RegisterAssemblyTypes(assembly).AsImplementedInterfaces().
    WithParameter("ConnectionString", connectionString);

but it still calls the parameter less (i.e. Logger) in MyCompany.Logger.Database.Logger.

If I add the connectionString parameter back in to the Logger's constructor, it throws back the exception mentioned above.

I'll continue researching it tomorrow and will update my answer if I find anything but if anyone has any suggestions in the meantime, I'd really appreciate the help.

Update 2

As highlighted by @HenkHolterman, the parameters are case sensitive and must match the convention used in the constructor.

Note that there are 2 ways to pass the parameters with Autofac. At registration time and when resolving. Both work the same way.

So the code above that generated an error when passing the parameters should have been defined as:

builder.RegisterAssemblyTypes(assembly).AsImplementedInterfaces().
WithParameter("connectionString", connectionString);

Upvotes: 0

Views: 354

Answers (1)

Henk Holterman
Henk Holterman

Reputation: 273572

    // register MainViewModel and Inject ILogger
    // Is this correct? Is this how you inject a service?
    builder.RegisterType<MainViewModel>()
           .As<ILogger>()
           .InstancePerDependency();

No, this is not correct. You are registering your MainViewModel as ILogger. A recursive and probably conflicting registration.

You should register the MainVm as itself. I'm not so familiar with AutoFac but I think it simply:

    builder.RegisterType<MainViewModel>();

but iirc there's also a .AsSelf() extension.

But builder.RegisterAssemblyTypes(assembly).As<ILogger>(); also looks wrong. This registers every type in that assembly as an ILogger. You probably want something like

   builder.RegisterAssemblyTypes(assembly)
          .Where(...)                     // optional filter
          .AsImplementedInterfaces();

Upvotes: 1

Related Questions