2
\$\begingroup\$

I am trying to get my head around how MVVM pattern works. Would like to hear some reviews if I am implementing it correctly.

View

<Window x:Class="Login_App.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:Login_App"
        xmlns:vm ="clr-namespace:Login_App.ViewModel"
        mc:Ignorable="d"


    Title="Login" Height="380" Width="650" ResizeMode="NoResize" WindowStartupLocation="CenterScreen">
    <Window.Resources>
        <ResourceDictionary>
            <vm:LoginVM x:Key="vm"/>
        </ResourceDictionary>
    </Window.Resources>
    
    <StackPanel DataContext="{StaticResource vm}">

        <TextBlock Text="Username"/>
        <TextBox Text="{Binding Username, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
        <TextBlock Text="Password"/>
        <TextBox Text="{Binding Password, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
        <Button Content="Login"
                Command="{Binding LoginCommand}"
                CommandParameter="{Binding User}"/>

    </StackPanel>
</Window>

LoginView.xaml.cs

namespace Login_App
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        LoginVM viewModel;
        public MainWindow()
        {
            InitializeComponent();

            viewModel = Resources["vm"] as LoginVM;
            viewModel.Authenticated += ViewModel_Authenticated;
        }

        private void ViewModel_Authenticated(object sender, EventArgs e)
        {
            Close();
        }
    }
}

Model

namespace Login_App.Model
{
    public class UserModel
    {
        public int UserID { get; set; }
        public string Username { get; set; }
        public string Email { get; set; }
        public string Password { get; set; }
        public int DepartmentID { get; set; }
        public int GroupID { get; set; }
        public bool Inactive { get; set; }
    }
}

ViewModel

This is the structure:

enter image description here

LoginVM.cs

namespace Login_App.ViewModel
{
    public class LoginVM : INotifyPropertyChanged
    {
        private UserModel user;
        public UserModel User
        {
            get { return user; }
            set
            {
                user = value;
                OnPropertyChanged("User");
            }
        }

        private string username;
        public string Username
        {
            get { return username; }
            set
            {
                username = value;
                User = new UserModel
                {
                    Username = username,
                    Password = this.Password
                };
                OnPropertyChanged("Username");
            }
        }

        private string password;
        public string Password
        {
            get { return password; }
            set
            {
                password = value;
                User = new UserModel
                {
                    Username = this.Username,
                    Password = password
                };
                OnPropertyChanged("Password");
            }
        }

        public Commands.LoginCommand LoginCommand { get; set; }

        public LoginVM()
        {
            LoginCommand = new Commands.LoginCommand(this);

            user = new UserModel();
        }

        public async void Login()
        {
            bool result = await Helpers.MySQLHelper.Login(User);
            if (result)
            {
                Authenticated?.Invoke(this, new EventArgs());
            }
        }

        public EventHandler Authenticated;

        public event PropertyChangedEventHandler PropertyChanged;
        private void OnPropertyChanged(string propertyName)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

Commands > LoginCommand.cs

namespace Login_App.ViewModel.Commands
{
    public class LoginCommand : ICommand
    {
        public LoginVM ViewModel { get; set; }
        public event EventHandler CanExecuteChanged
        {
            add { CommandManager.RequerySuggested += value; }
            remove { CommandManager.RequerySuggested -= value; }
        }

        public LoginCommand(LoginVM vm)
        {
            ViewModel = vm;
        }

        public bool CanExecute(object parameter)
        {
            UserModel user = parameter as UserModel;

            if (user == null)
                return false;

            if (string.IsNullOrEmpty(user.Username))
                return false;

            if (string.IsNullOrEmpty(user.Password))
                return false;

            return true;

        }

        public void Execute(object parameter)
        {
            ViewModel.Login();
        }
    }
}

Helpers > MySQLHelper.cs

namespace Login_App.ViewModel.Helpers
{
    public class MySQLHelper
    {
        // Connection string
        private static string connectionString = "server=localhost;user id=root;password=password;persistsecurityinfo=True;database=Logindatabase";

        // Login Functionality
        public static async Task<bool> Login(UserModel user)
        {
            try
            {
                using (MySqlConnection conn = new MySqlConnection(connectionString))
                {
                    conn.Open();
                    using (MySqlCommand cmd = new MySqlCommand("SELECT COUNT(*), u.id FROM users u WHERE Username = @Username AND Password = @Password", conn))
                    {
                        cmd.Parameters.AddWithValue("@Username", user.Username);
                        cmd.Parameters.AddWithValue("@Password", user.Password);
                        cmd.ExecuteNonQuery();

                        DataTable dt = new DataTable();
                        MySqlDataAdapter da = new MySqlDataAdapter(cmd);
                        da.Fill(dt);

                        if (dt.Rows[0][0].ToString() == "1")
                        {
                            App.UserId = dt.Rows[0][1].ToString();
                            return true;
                        }
                        else
                        {
                            MessageBox.Show("Incorrect Username or Password", "Login App Error", MessageBoxButton.OK, MessageBoxImage.Warning);
                            return false;
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.ToString(), "Login App Error", MessageBoxButton.OK, MessageBoxImage.Warning);
                return false;
            }

        }
    }
}

Edit 1 - Using dapper

I have been recommended to use Dapper, and I see it is far less coding rather than doing the standard MySQLCommand cmd = new... etc..

So here it is.

I have used message box just to see what value will be returned.

public class MySQLHelper
{
    // Login Functionality
    public static async Task<bool> Login(UserModel user)
    {
        try
        {
            /// Query to get count of users from the parameters provided by the user.
            /// On successful Login, the details from the database,
            /// will be saved in the <App.xaml.cs> file.

            string mysqlUserDetails = "SELECT COUNT(*), u.id AS UserID, Username, Email, DepartmentID, Inactive" +
                " FROM users u WHERE Username = @Username AND Password = MD5(@Password)";

            using(var connection = new MySqlConnection(ConfigurationManager.ConnectionStrings["MySQL"].ConnectionString))
            {
                var userDetails = connection.Query<UserModel>(mysqlUserDetails, new { Username = user.Username, Password = user.Password }).ToList();

                // Determine whether to autherize or not
                if(userDetails.Count == 1)
                {
                    var details = userDetails.First();
                    App.UserId = details.UserID;
                    App.Username = details.Username;
                    App.Email = details.Email;
                    App.DepartmentID = details.DepartmentID;
                    App.Inative = details.Inactive;

                }


                

            }
            return false;
        }
        catch (Exception ex)
        {
            return false;
        }
    }
}
\$\endgroup\$
9
  • \$\begingroup\$ Your code formatting appears to be off in MySQLHelper.cs around the if/else statement. you should double check to make sure you don't have any errors there. it could just be a copy paste error, it happens sometimes. \$\endgroup\$
    – Malachi
    Dec 10 '20 at 15:03
  • 1
    \$\begingroup\$ @Malachi Thank you for that. Have updated the question. \$\endgroup\$ Dec 10 '20 at 15:27
  • \$\begingroup\$ Use github.com/StackExchange/Dapperinstead of ADO.NET. Don't call a class "helper". Don't hardcode a connection string (get it from a config file). Don't mix UI and backend-code (MessageBox.Show in your MySqlHelper class). \$\endgroup\$
    – BCdotWEB
    Dec 10 '20 at 16:16
  • 1
    \$\begingroup\$ Relaying Command Logic and Overriding OnStartup to instantiate ViewModels can be useful here. I'm not sure that ResourceDictionary is the best place to store ViewModel. Learn something about "Composition Root" in MVVM apps. \$\endgroup\$
    – aepot
    Dec 25 '20 at 20:11
  • 1
    \$\begingroup\$ @aepot Spasibo :) \$\endgroup\$ Dec 26 '20 at 9:09

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.