[英]In C# Autofac, how to resolve class from single interface with multiple concrete classes
[英]Using Autofac to resolve multiple concrete classes of IRepository<T>?
我是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類。 從我在“ 默認注冊 ”的文檔中讀到的內容,它始終是XMLRepository而不是SQLRepository。 然后我嘗試“ 打開通用裝飾器注冊 ”,如:
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文件,我已經在原始問題中給出了代碼。
這是一個很好的文章在這里 autofac文檔。
使用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>();
一種可能的解決方案是使用密鑰而不是名稱來注冊您的存儲庫:
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<>));
其中key是一些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文檔中找到有關IIndex
更多詳細信息。
我不得不承認,當我得到這個答案時,我有點沮喪。 我正在為其他可能遇到同樣問題的人發布這個答案。
由於提供給我的解決方案不能正常工作(直到tdragon更新了他的答案),我去了Googlegroups for Autofac,其他人想出了答案。
然而,我已經把tdragon(感謝老兄!)用於提出IIndex方法,這就是為什么我把他的帖子作為答案,但我從其他來源獲得了更多關於它的反饋,這就是為什么我發布我的帖子回答。
我去了並聯系了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開始,有一個屬性過濾器可以幫助解決這個問題。 我需要做的第一件事是在構建容器時添加“.WithAttributeFiltering()”,如下所示:
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 }
謝謝大家的幫助!
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.