我是AutoFac的新手,我遇到了两个需要在使用MVVM的WPF项目中实现的问题。我正在使用接口来实现存储库,但我将为SQL,XML和CSV实现多个存储库。所以我的界面有这个:
public interface IRepository<T> : IReadOnlyRepository<T>, IWriteOnlyRepository<T>
{
}
// covariance interface
public interface IReadOnlyRepository<out T> : IDisposable
{
T FindById(int id);
IEnumerable<T> GetAllRecords();
}
// contravariance interface
public interface IWriteOnlyRepository<in T> : IDisposable
{
void Add(T item);
void Delete(T item);
int Save();
}
public class SQLRepository<T> : IRepository<T>
{
// implements the interface using Entity Framework
}
public class XMLRepository<T> : IRepository<T>
{
// implements the interface using XML Serializer/Deserializer
}
public class CSVRepository<T> : IRepository<T>
{
// Implements the interface for TextReader/TextWriter for CSV Files (Excel)
}
所以这就是问题:老板告诉我,客户需要在运行程序的同时更改存储库。所以我需要在运行时动态更改存储库。默认值为SQL Server,但客户端可能希望更改为XML ...而不会丢失存储库中已有的数据。其背后的原因是,如果他们从SQL加载配置但他们想将其保存到XML文件并将其发送到客户端,他们可以这样做
- 或 -
他们从其中一个客户端获取XML文件,他们希望将配置保存到SQL,他们可以这样做而不必担心重新输入数据。
我通过使用Generics解决了一个问题,因为我将使用相同的POCO数据模型类,因此它保留了数据,但随后:
我考虑使用“命名服务”来区分具体的存储库类和模型基类。然后我会使用一个bootstrapper看起来像这样:
public class BootStrapper
{
public IContainer BootStrap()
{
var builder = new ContainerBuilder();
builder.RegisterType<MainWindow>.AsSelf();
builder.RegisterType<MainViewModel>.As<IMainViewModel>();
//?? How do I resolve T of IRepository<T>?
builder.RegisterType<SQLRepository>.Named<IRepository>("SQL")
builder.RegisterType<XMLRepository>.Named<IRepository>("XML")
builder.RegisterType<CSVRepository>.Named<IRepository>("CSV")
return builder.Build();
}
}
public partial class App : Application
{
protected override void OnStartUp(StartUpEventArgs e)
{
base.OnStartUp(e);
var bootsrapper = new BootStrapper();
var container = bootstrapper.BootStrap();
// ?? How do I set the SQLRepository as default?
var mainWindow = container.Resolve<MainWindow>();
mainWindow.Show();
}
}
有什么建议吗?
编辑:我忘记在那里添加我在ViewModel上使用依赖注入,因此,在我的MainViewModel中: public class MainViewModel
{
private IRepository<Model> _repository;
public MainViewModel(IRepository<Model> repo)
{
_repository = _repo;
}
}
现在我按照建议尝试将代码更改为:
builder.RegisterGeneric(typeof(SQLRepository<>).As(typeof(IRepository<>));
builder.RegisterGeneric(typeof(XMLRepository<>).As(typeof(IRepository<>));
然后我通过插入它调试代码,当我点击MainViewModel构造函数时,它给了我XMLRepository类。从我在“default registrations”的文档中读到的内容,它始终是XMLRepository,而不是SQLRepository。然后我尝试“open generic decorator registration”喜欢:
builder.RegisterGeneric(typeof(SQLRepository<>).Named("SQL", typeof(IRepository<>));
builder.RegisterGeneric(typeof(XMLRepository<>).Named("XML", typeof(IRepository<>));
builder.RegisterGenericDecorator(typeof(SQLRepository<>), typeof(IRepository<>), fromKey: "SQL");
builder.RegisterGenericDecorator(typeof(XMLRepository<>), typeof(IRepository<>), fromKey: "XML");
但是当我尝试使用MainWindow时如何解决呢?
更新编辑#2
好的,所以我被tdragon的一个合理问题问到我是如何解决的。 MainWindow.xaml.cs文件如下所示:
public partial class MainWindow : Window
{
private MainViewModel _viewModel;
public MainWindow(MainViewModel viewModel)
{
InitializeComponent();
_viewModel = viewModel;
DataContext = _viewModel;
}
}
但真正的问题在于App.xaml.cs文件,我已经在原始问题中给出了代码。
答案 0 :(得分:1)
autofac文档中有一篇好文章here。
使用 RegisterGeneric()构建器方法注册通用组件,如下所示。
var builder = new ContainerBuilder();
builder.RegisterGeneric(typeof(SQLRepository<>));
builder.RegisterGeneric(typeof(XMLRepository<>));
builder.RegisterGeneric(typeof(CSVRepository<>));
builder.RegisterGeneric(typeof(SQLRepository<>))
.As(typeof(IRepository<>))
.InstancePerLifetimeScope();
builder.RegisterGeneric(typeof(XMLRepository<>))
.As(typeof(IRepository<>))
.InstancePerLifetimeScope();
builder.RegisterGeneric(typeof(CSVRepository<>))
.As(typeof(IRepository<>))
.InstancePerLifetimeScope();
builder.Register(c => new Myclass()).OnActivating(
e =>
{
e.Instance.SqlTaskRepo = e.Context.Resolve<SQLRepository<Task>>();
}
);
<强>已更新强>
您可以通过扫描程序集解决T而不是更好的解决方法,请看下面的代码,希望它能帮助您
builder.RegisterGeneric(typeof(SQLRepository<>));
builder.RegisterGeneric(typeof(XMLRepository<>));
builder.RegisterGeneric(typeof(CSVRepository<>));
var dataAccess = Assembly.GetExecutingAssembly();
builder.RegisterAssemblyTypes(dataAccess)
.Where(t => typeof(SQLRepository<>).IsAssignableFrom(t));
builder.RegisterAssemblyTypes(dataAccess)
.Where(t => typeof(XMLRepository<>).IsAssignableFrom(t));
builder.RegisterAssemblyTypes(dataAccess)
.Where(t => typeof(CSVRepository<>).IsAssignableFrom(t));
builder.RegisterType<MainViewModel>();
答案 1 :(得分:0)
可能的解决方案之一是使用密钥而不是名称来注册您的存储库:
var builder = new ContainerBuilder();
builder.RegisterGeneric(typeof(SqlRepository<>)).Keyed(RepositoryType.Sql, typeof(IRepository<>));
builder.RegisterGeneric(typeof(XmlRepository<>)).Keyed(RepositoryType.Xml, typeof(IRepository<>));
builder.RegisterGeneric(typeof(CsvRepository<>)).Keyed(RepositoryType.Csv, typeof(IRepository<>));
其中键值为enum
个值(string
也可以使用,但imho enum
更清晰,更不容易出错),例如
enum RepositoryType { Sql, Xml, Csv }
然后,您可以注入IRepository<Model>
,而不是注入始终为您提供最新注册依赖项的IIndex<RepositoryType, IRepository<Model>>
。使用索引运算符可以获得正确的存储库类型。此外,您可以实现某种ConfigurationProvider
,您可以在其中存储当前选定的存储库类型,例如:
public interface IConfigurationProvider
{
RepositoryType SelectedRepositoryType { get; set; }
}
public class ConfigurationProvider : IConfigurationProvider
{
public RepositoryType SelectedRepositoryType
{
get { /* read the value from some configuration file */ }
set { /* store the new value */ }
}
}
当然,它也应该在容器中注册。您可以将此值存储在任何位置(app.config,任何其他自定义文件)。
然后,MainViewModel
的构造函数看起来像这样:
public MainViewModel(
IIndex<RepositoryType, IRepository<Model>> repoIndex,
IConfigurationProvider configurationProvider)
{
var repository = repoIndex[configurationProvider.SelectedRepositoryType]; // would return the repository of currently selected type
}
您可以在Autofac documentation中找到有关IIndex
的更多详细信息。
答案 2 :(得分:0)
我不得不承认,当我得到这个答案时,我有点沮丧。我正在为其他可能遇到同样问题的人发布此答案。
由于提供给我的解决方案没有正常工作(直到tdragon更新了他的答案),我去了Googlegroups for Autofac,其他人想出了答案。
然而,我已经将tdragon(感谢老兄!)用于提出IIndex方法,这就是为什么我把他的帖子作为答案,但我从其他来源获得了更多关于它的反馈,这就是为什么我&# 39;我发布了我的答案。
我去了并联系了Thomas Claudius Huber,他是WPF和MVVM两个伟大的Pluralsight课程的作者。一个在做ModelWrappers,另一个在用ViewModels进行单元测试。我强烈建议那些尝试改进WPF和MVVM技能的新手课程。正是他的课程让我开启了Autofac,它帮了大忙。 Thomas和tdragon使用IIndexing的解决方案确实有助于解决问题。
但Alex Meyer-Gleaves在Autofac Googlegroup上有一个有趣的选择。他的第一个选择是使用Lambda表达式:
builder.Register(c => new MainViewModel(c.ResolveNamed<IRepository<Stock>>("XMLrepository"), c.ResolveNamed<IRepository<Vendor>>("SQLRepository"))).AsSelf();
但他也提到从Autofac 4.3.0开始,有一个属性过滤器可以帮助解决这个问题。我需要做的第一件事是添加&#34; .WithAttributeFiltering()&#34;当像这样构建容器时:
public IContainer BootStrap()
{
builder.RegisterType<MainViewModel>().AsSelf().WithAttributeFiltering();
builder.RegisterType<MainView>().AsSelf();
builder.RegisterGeneric(typeof(XMLRepository<>)).Keyed("XMLRepository", typeof(IRepository<>));
builder.RegisterGeneric(typeof(SQLRepository<>)).Keyed("SQLRepository", typeof(IRepository<>));
return builder.Build();
}
然后在构造函数中,您可以这样做:
public MainViewModel([KeyFilter("XMLRepository")]IRepository<Stock> stockRepo,
[KeyFilter("XMLRepository")]IRepository<Vendor> vendorRepo)
{ ... // code here }
谢谢大家的帮助!