c# - MVVM 中的后台加载和构造函数注入(inject)

标签 c# wpf asynchronous mvvm dependency-injection

我有一个关于如何以及在何处使用 WPF .NET 4.0 中的 ViewModel 加载大量数据的问题(所以没有 async/await :/)。

这是我的 View 模型:

public class PersonsViewModel : ViewModelBase
{
    private readonly IRepository<Person> _personRepository;

    private IEnumerable<Person> _persons;
    public IEnumerable<Person> Persons
    {
        get { return _persons; }
        private set { _persons = value; OnPropertyChanged("Persons"); }
    }

    public PersonsViewModel(IRepository<Person> personRepository)
    {
        if (personRepository == null)
            throw new ArgumentNullException("personRepository");

        _personRepository = personRepository;
    }
}

此 ViewModel 在窗口中使用,我需要在窗口打开时加载所有人员。我想到了很多解决方案,但我无法确定哪个是最好的(或者也许有更好的方法来做到这一点)。我有两个禁忌: - 所有数据都必须在另一个线程中加载,因为加载可能需要几秒钟(数据库中的大量数据)而且我不想卡住 UI。 - ViewModel 必须是可测试的。

--=[第一种方案:延迟加载]=--

public class PersonsViewModel : ViewModelBase
{
    private IEnumerable<Person> _persons;
    public IEnumerable<Person> Persons
    {
        get
        {
            if (_persons == null)
                _persons = _personRepository.GetAll();
            return _persons;
        }
    }
}

我不喜欢这个解决方案,因为数据是在主线程中加载的。

--=[第二种方案:加载事件]=--

public class PersonsViewModel : ViewModelBase
{
    // ...

    private Boolean _isDataLoaded;
    public Boolean IsDataLoaded
    {
        get { return _isDataLoaded; }
        private set { _isDataLoaded = value; OnPropertyChanged("IsDataLoaded"); }
    }

    public void LoadDataAsync()
    {
        if(this.IsDataLoaded)
            return;

        var bwLoadData = new BackgroundWorker();
        bwLoadData.DoWork +=
            (sender, e) => e.Result = _personRepository.GetAll();
        bwLoadData.RunWorkerCompleted +=
            (sender, e) => 
            {
                this.Persons = (IEnumerable<Person>)e.Result;
                this.IsDataLoaded = true;
            };
        bwLoadData.RunWorkerAsync();
    }
}

public class PersonWindow : Window
{
    private readonly PersonsViewModel _personsViewModel;

    public PersonWindow(IRepository<Person> personRepository)
    {
        _personsViewModel = new PersonsViewModel(personRepository);

        this.Loaded += PersonWindow_Loaded;
    }

    private void PersonWindow_Loaded(Object sender, RoutedEventArgs e)
    {
        this.Loaded -= PersonWindow_Loaded;

        _personsViewModel.LoadDataAsync();
    }
}

我不太喜欢这个解决方案,因为它强制 ViewModel 的用户调用 LoadDataAsync 方法。

--=[方案三:在ViewModel构造函数中加载数据]=--

public class PersonsViewModel : ViewModelBase
{
    // ...

    public PersonsViewModel(IRepository<Person> personRepository)
    {
        if (personRepository == null)
            throw new ArgumentNullException("personRepository");

        _personRepository = personRepository;

        this.LoadDataAsync();
    }   

    private Boolean _isDataLoaded;
    public Boolean IsDataLoaded
    {
        get { return _isDataLoaded; }
        private set { _isDataLoaded = value; OnPropertyChanged("IsDataLoaded"); }
    }

    public void LoadDataAsync()
    {
        if(this.IsDataLoaded)
            return;

        var bwLoadData = new BackgroundWorker();
        bwLoadData.DoWork +=
            (sender, e) => e.Result = _personRepository.GetAll();
        bwLoadData.RunWorkerCompleted +=
            (sender, e) => 
            {
                this.Persons = (IEnumerable<Person>)e.Result;
                this.IsDataLoaded = true;
            };
        bwLoadData.RunWorkerAsync();
    }
}

在这个解决方案中,ViewModel 的用户不需要调用额外的方法来加载数据,但它违反了单一责任原则,正如 Mark Seeman 在他的“依赖注入(inject)”一书中所说:“让构造函数不受任何逻辑。SRP 意味着成员应该只做一件事,现在我们使用构造函数来注入(inject) DEPENDENCIES,我们应该更愿意让它摆脱其他问题。”

有什么想法可以以适当的方式解决这个问题吗?

最佳答案

如果不知道如何将 ViewModel 与 View 联系起来,就很难给出准确的答案。

一种做法是拥有一个“导航感知”ViewModel(一个实现特定接口(interface)的 ViewModel,如 INavigationAware 并让您的导航服务在实例化 ViewModel/View 并将它们绑定(bind)在一起时调用此方法. 这就是 Prism 的 FrameNavigationService 的工作方式。

public interface INavigationAware
{
    Task NavigatedTo(object param);
    Task NavigatedFrom(...)
}

public class PersonWindow : ViewModelBase, INavigationAware 
{

}

并在 NavigatedTo 中实现您的初始化代码,如果 ViewModel 实现了 INavigationAware,它将被导航服务调用。

Prism for Windows 应用商店应用引用:

关于c# - MVVM 中的后台加载和构造函数注入(inject),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/28457037/

相关文章:

c# - 如何在 C# 中将一些类的列表格式化为漂亮的 ASCII 表?

c# - 如何在 serilog 的输出 .net core 中添加 'request body'?

c# - 将具体类型的泛型集合转换为基类型的集合

c# - 在 Windows 中覆盖的透明窗口应用程序

c# - 无法通过 Entity Framework 从 WPF 数据网格更新数据库

c# - Visual Studio 2013,调试中未显示更改

c# - 如何以编程方式设置附加属性?

javascript - 如何在 Node.js 函数中应用 Promise

javascript - go 函数内的await js 异步函数(promise)

ios - 我应该把这段应该在后台完成的代码放在哪里?