使用 Autofac 和注入服务时从 XAML 页面访问 MainViewModel
Accessing MainViewModel from XAML page when using Autofac and inject service(s)
我正在尝试直接从我的 XAML 页面定义访问我的 MainPageViewModel:
<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}">
我的定位器是在我的 App.XAML:
中创建的
<Application.Resources>
<ResourceDictionary>
<vm:ViewModelLocator x:Key="Locator" />
</ResourceDictionary>
</Application.Resources>
而我的ViewModelLocator定义如下:
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>();
}
但是当我 运行 我的应用程序时,它从定位器上的 App.XAML 生成错误,并且它正在尝试访问上面定义的 MainViewModel 属性。我得到的错误是:
异常信息:
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)'.
异常堆栈跟踪:
at Autofac.Core.Activators.Reflection.ReflectionActivator.ActivateInstance
(IComponentContext context, IEnumerable`1 parameters)
at Autofac.Core.Resolving.InstanceLookup.Activate(IEnumerable`1 parameters)
而我的 MainPageViewModel 定义如下:
Public class MainPageViewModel()
{
public MainViewModel(ILogger logger)
{
....
}
}
关于如何解决这个问题的任何想法?
更新 1:
注意我原来的定义:
builder.RegisterAssemblyTypes(assembly).As<ILogger>();
可能没问题,因为我所有的 "logger" 程序集都只实现了 ILogger 接口,但我最初使用的是 AsImplementedInterfaces()
我现在已经根据@HenkHolterman 的建议更新了我的代码:
builder.RegisterType<MainViewModel>();
builder.RegisterAssemblyTypes(assembly).AsImplementedInterfaces();
但我没有收到以下异常:
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)'.
现在根据这个异常的最后一部分,它似乎与参数 connectionString 有关并且这个参数是我的数据库记录器 class (MyCompany.Logger.Database.Logger
) 的一部分,所以我已经暂时删除它以测试@HenkHolterman 的建议,现在可以使用,但我仍然有问题。
我在使用数据库记录器时需要传递一个连接字符串。当我使用文件记录器等时,我需要传递一个文件名...
那么如何将参数传递给记录器构造函数。我试过:
builder.RegisterAssemblyTypes(assembly).AsImplementedInterfaces().
WithParameter("ConnectionString", connectionString);
但它仍然调用MyCompany.Logger.Database.Logger中的参数less(即Logger)。
如果我将 connectionString 参数重新添加到 Logger 的构造函数中,它会抛出上述异常。
明天我会继续研究它,如果发现任何问题,我会更新我的答案,但如果在此期间有人有任何建议,我将非常感谢您的帮助。
更新 2
正如@HenkHolterman 强调的那样,参数区分大小写并且必须与构造函数中使用的约定相匹配。
请注意,有两种方法可以使用 Autofac 传递参数。在注册时和解决时。两者的工作方式相同。
所以上面在传递参数时产生错误的代码应该被定义为:
builder.RegisterAssemblyTypes(assembly).AsImplementedInterfaces().
WithParameter("connectionString", connectionString);
// register MainViewModel and Inject ILogger
// Is this correct? Is this how you inject a service?
builder.RegisterType<MainViewModel>()
.As<ILogger>()
.InstancePerDependency();
不,这是不正确的。您正在将 MainViewModel 注册为 ILogger。递归且可能冲突的注册。
您应该将 MainVm 注册为它自己。我对 AutoFac 不太熟悉,但我认为它很简单:
builder.RegisterType<MainViewModel>();
但是 iirc 还有一个 .AsSelf()
扩展。
但是builder.RegisterAssemblyTypes(assembly).As<ILogger>();
看起来也不对。这会将程序集中的 every 类型注册为 ILogger。你可能想要
builder.RegisterAssemblyTypes(assembly)
.Where(...) // optional filter
.AsImplementedInterfaces();
我正在尝试直接从我的 XAML 页面定义访问我的 MainPageViewModel:
<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}">
我的定位器是在我的 App.XAML:
中创建的<Application.Resources>
<ResourceDictionary>
<vm:ViewModelLocator x:Key="Locator" />
</ResourceDictionary>
</Application.Resources>
而我的ViewModelLocator定义如下:
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>();
}
但是当我 运行 我的应用程序时,它从定位器上的 App.XAML 生成错误,并且它正在尝试访问上面定义的 MainViewModel 属性。我得到的错误是:
异常信息:
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)'.
异常堆栈跟踪:
at Autofac.Core.Activators.Reflection.ReflectionActivator.ActivateInstance
(IComponentContext context, IEnumerable`1 parameters)
at Autofac.Core.Resolving.InstanceLookup.Activate(IEnumerable`1 parameters)
而我的 MainPageViewModel 定义如下:
Public class MainPageViewModel()
{
public MainViewModel(ILogger logger)
{
....
}
}
关于如何解决这个问题的任何想法?
更新 1:
注意我原来的定义:
builder.RegisterAssemblyTypes(assembly).As<ILogger>();
可能没问题,因为我所有的 "logger" 程序集都只实现了 ILogger 接口,但我最初使用的是 AsImplementedInterfaces()
我现在已经根据@HenkHolterman 的建议更新了我的代码:
builder.RegisterType<MainViewModel>();
builder.RegisterAssemblyTypes(assembly).AsImplementedInterfaces();
但我没有收到以下异常:
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)'.
现在根据这个异常的最后一部分,它似乎与参数 connectionString 有关并且这个参数是我的数据库记录器 class (MyCompany.Logger.Database.Logger
) 的一部分,所以我已经暂时删除它以测试@HenkHolterman 的建议,现在可以使用,但我仍然有问题。
我在使用数据库记录器时需要传递一个连接字符串。当我使用文件记录器等时,我需要传递一个文件名...
那么如何将参数传递给记录器构造函数。我试过:
builder.RegisterAssemblyTypes(assembly).AsImplementedInterfaces().
WithParameter("ConnectionString", connectionString);
但它仍然调用MyCompany.Logger.Database.Logger中的参数less(即Logger)。
如果我将 connectionString 参数重新添加到 Logger 的构造函数中,它会抛出上述异常。
明天我会继续研究它,如果发现任何问题,我会更新我的答案,但如果在此期间有人有任何建议,我将非常感谢您的帮助。
更新 2
正如@HenkHolterman 强调的那样,参数区分大小写并且必须与构造函数中使用的约定相匹配。
请注意,有两种方法可以使用 Autofac 传递参数。在注册时和解决时。两者的工作方式相同。
所以上面在传递参数时产生错误的代码应该被定义为:
builder.RegisterAssemblyTypes(assembly).AsImplementedInterfaces().
WithParameter("connectionString", connectionString);
// register MainViewModel and Inject ILogger
// Is this correct? Is this how you inject a service?
builder.RegisterType<MainViewModel>()
.As<ILogger>()
.InstancePerDependency();
不,这是不正确的。您正在将 MainViewModel 注册为 ILogger。递归且可能冲突的注册。
您应该将 MainVm 注册为它自己。我对 AutoFac 不太熟悉,但我认为它很简单:
builder.RegisterType<MainViewModel>();
但是 iirc 还有一个 .AsSelf()
扩展。
但是builder.RegisterAssemblyTypes(assembly).As<ILogger>();
看起来也不对。这会将程序集中的 every 类型注册为 ILogger。你可能想要
builder.RegisterAssemblyTypes(assembly)
.Where(...) // optional filter
.AsImplementedInterfaces();