简体   繁体   中英

Asynchronous MVVM for WPF C# using MongoDB

So my situation is this: I want to be able to use MVVM with my WPF application using MongoDB. I am very new to MVVM (I know very little of it), but I've got some experience using .NET and WPF.

I have a namespace for recalling MongoDB collections, with the Model component stored there as a class called "User"

Model (in a separate namespace):

public class User
{
    [BsonElement("_id")]
    public ObjectId Id { get; set; }
    public string name { get; set; }
    // other methods listed here

    public async static Task<List<User>> getUserList()
    {
        // allows me to get a list of users
        var col = MongoDBServer<User>.openMongoDB("Users");
        var filter = Builders<User>.Filter.Exists("name");

        List<User> userList = await col.Find(filter).ToListAsync();

        return userList;
    }
}

I've created a very basic ViewModelBase (abstract ViewModelBase):

public abstract class ViewModelBase : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void OnPropertyChanged(string propertyName)
    {
        PropertyChangedEventHandler handler = this.PropertyChanged;
        if(handler == null)
        {
            var e = new PropertyChangedEventArgs(propertyName);
            handler(this, e);
        }
    }
}

And a derived class for handling the User Lists (ViewModel):

public class UserListViewModel : ViewModelBase
{
    private User _user;
    private ObservableCollection<User> _userList;

    public User user
    {
        get { return _user; }
        set
        {
            _user = value;
            OnPropertyChanged("user");
        }
    }

    public ObservableCollection<User> userList
    {
        get { return _userList; }
        set
        {
            _userList = value;
            OnPropertyChanged("userList");
        }
    }

    public UserListViewModel()
    {
        user = new User();
        this.userList = new ObservableCollection<User>();

        // since MongoDB operations are asyncrhonous, the async method "getUserList()" is used to fill the observable collection
        getUserList().Wait();
    }

    public async Task getUserList()
    {
        var UserListRaw = await User.getUserList();
        this.userList = new ObservableCollection<User>(UserListRaw);
    }
}

The view component is as a simple window with a listbox, as follows (View):

<Window x:Class="UserManagementMVVM.UsersWindow"
    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:UserManagementMVVM"
    mc:Ignorable="d"
    Title="UsersWindow" Height="300" Width="300">
    <Window.Resources>
        <local:UserListViewModel x:Key="ViewModel"/>
        <!-- Receiving error for this XAML block saying "Object reference not set to instance of an object -->
    </Window.Resources>

    <Grid DataContext="{Binding ViewModel}">
        <ListBox Margin="5" ItemsSource="{Binding userList}"/>
    </Grid>
</Window>

The App.Xaml and its codebehind are left untouched, as is the View's codebehind.

When I run the program, nothing shows up (ie: The Window starts, but the ListBox is empty even though there is data). I will soon add some button functionality that will perform atomic operations with MongoDB.

I've been trying for nearly 2 weeks to make my own MVVM program for this, with no success. Any assistance would be greatly appreciated.

You are not putting the getUserList() return value into a variable

I assume you mean to do the following

Task.Run(async ()=>this.userList = await getUserList());

this shall work you should think wether you want to wait for the task to finish or not, and than place a .Wait() after it.

Your other issue might be the way you bind to the ViewModel in the context it should use StaticResource instead of binding

like This:

<Grid DataContext="{StaticResource ViewModel}">
<Window x:Class="UserManagementMVVM.UsersWindow"
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:UserManagementMVVM"
mc:Ignorable="d"
Title="UsersWindow" Height="300" Width="300">
<Window.DataContext>
    <!--You have to set the DataContext -->
    <local:UserListViewModel x:Key="ViewModel"/>
</Window.DataContext>

<Grid>
    <ListBox Margin="5" ItemsSource="{Binding userList}"/>
</Grid>
</Window>

You have to set the DataContext right. i changed your xaml. but i prefer setting the DataContext for the Mainwindow in Codebehind or app.xaml.cs.

eg: app.xaml.cs

 protected override void OnStartup(StartupEventArgs e)
 {
      var data = new MainWindowViewmodel();
      this.MainWindow = new MainWindow(data);
      this.MainWindow.Show();
 }

all other DataContext for my views are done with DataTemplates within the ResourceDictionary

 <DataTemplate DataType="{x:Type local:MyOtherViewmodel}">
    <local::MyOtherViewmodelView />
 </DataTemplate>

I want to give credit to both gilMishal and blindmeis for pointing me in the right direction. Both of your answers have helped. Here is my updated (and functional!) code:

App.xaml.cs has been modified as follows (Credit to blindmeis ):

public partial class App : Application
{
    protected override void OnStartup(StartupEventArgs e)
    {
        base.OnStartup(e);
        UsersWindow window = new UsersWindow();
        var ViewModel = new UserListViewModel();
        window.DataContext = ViewModel;
        window.Show();
    }
}

The ViewModel has been updated:

public class UserListViewModel : ViewModelBase
{
    private User _user;
    private ObservableCollection<string> _userList; // changed from "User" class to string

    public User user
    {
        get { return _user; }
        set
        {
            _user = value;
            OnPropertyChanged("user");
        }
    }

    public ObservableCollection<string> userList
    {
        get { return _userList; }
        set
        {
            _userList = value;
            OnPropertyChanged("userList");
        }
    }

    public UserListViewModel()
    {
        userList = new ObservableCollection<string>();
        Task.Run(async () => this.userList = await getUserList()); // Credit to gilMishal
    }

    public async Task<ObservableCollection<string>> getUserList()
    {
        var UserListRaw = await User.getUserList();
        var userListOC = new ObservableCollection<string>();

        foreach (var doc in UserListRaw) // extracting the "name" property from each "User" object
        {
            userListOC.Add(doc.name);
        }

        return userListOC;
    }
}

And the view:

<Window x:Class="UserManagementMVVM.UsersWindow"
        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:UserManagementMVVM"
        mc:Ignorable="d"
        Title="UsersWindow" Height="300" Width="300">
    <Window.Resources>
        <local:UserListViewModel x:Key="ViewModel"/>
    </Window.Resources>

    <Grid> <!-- data context removed from here, credit blindmeis -->
        <ListBox Margin="5" ItemsSource="{Binding userList}"/>
    </Grid>
</Window>

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM