Saltar al contenido

Inicio de Sesión en WPF, Implementando el Patrón MVVM, C# y SQL Server

Hola, en el tutorial anterior creamos la interfaz de usuario de inicio de sesión en WPF, ahora haremos la autenticación de usuario con C-Sharp, SQL Server e implementaremos el patrón MVVM, como ya mencioné en tutoriales anteriores, WPF se ajusta mejor y aprovecha al máximo  el patrón MVVM. Introduciendo de manera rápida, el patrón MVVM se compone de 3 componentes, el modelo – la vista y el modelo de vista.

Modelo

El modelo representa los objetos de dominio que encapsulan los datos, implementan la lógica empresarial, los servicios y el acceso a datos, al menos hablando en términos generales, ya que estas tareas se dividen en capas, como la capa de dominio, servicios y acceso a datos. Por lo tanto, el modelo solamente representaría las entidades empresariales y pertenece a la capa de dominio, mientras que la vista y el modelo de vista pertenecen a la capa de presentación. Sin embargo, el modo de separación en capas depende mucho de la complejidad de la aplicación.

Modelo de Vista

El modelo de vista proporciona los datos y la funcionalidad para la vista, es decir, expone los datos a la vista y proporciona comandos para controlar la interacción del usuario, por lo que este componente va implementar propiedades, las notificaciones de cambios de propiedad para actualizar el valor de los elementos de la vista, e implementa los comandos para ejecutar tareas cuando un usuario realiza una acción, por ejemplo cuando se hace un clic en un botón. La mayoría de los controles de WPF implementan y admiten la interfaz ICommand, y este invocará el método asociado cuando se produzca la acción predeterminada del control, en este caso, cuando se haga clic en el botón Login, se ejecutará el método de autenticación del usuario.

Vista

Finalmente, la vista son todos los elementos de la interfaz de usuario, como las ventanas, páginas, controles de usuario y los diccionarios de recursos. Los enlaces se declaran en la vista que vinculan las propiedades de la vista a las propiedades del modelo de vista.

Las vistas solamente deben ser responsables de mostrar el contenido del modelo de vista, y deberían ser construidas solamente con XAML, es decir, no debe de haber ningún tipo de código en el código subyacente de la vista, excepto las que son responsabilidad de la vista, por ejemplo, mover y maximizar la ventana, o algún efecto de animación.

Tutorial

Código

Clases principales del Modelo de Vista

Clase ViewModelBase

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

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

Clase ViewModelCommand

    public class ViewModelCommand : ICommand
    {
        //Fields
        private readonly Action<object> _executeAction;
        private readonly Predicate<object> _canExecuteAction;

        //Constructors
        public ViewModelCommand(Action<object> executeAction)
        {
            _executeAction = executeAction;
            _canExecuteAction = null;
        }

        public ViewModelCommand(Action<object> executeAction, Predicate<object> canExecuteAction)
        {
            _executeAction = executeAction;
            _canExecuteAction = canExecuteAction;
        }

        //Events
        public event EventHandler CanExecuteChanged
        {
            add { CommandManager.RequerySuggested += value; }
            remove { CommandManager.RequerySuggested -= value; }
        }

        //Methods
        public bool CanExecute(object parameter)
        {
            return _canExecuteAction == null ? true : _canExecuteAction(parameter);
        }

        public void Execute(object parameter)
        {
            _executeAction(parameter);
        }
    }

Clases del inicio de sesión

Clase LoginViewModel

    public class LoginViewModel : ViewModelBase
    {
        //Fields
        private string _username;
        private SecureString _password;
        private string _errorMessage;
        private bool _isViewVisible = true;

        private IUserRepository userRepository;

        //Properties
        public string Username
        {
            get
            {
                return _username;
            }

            set
            {
                _username = value;
                OnPropertyChanged(nameof(Username));
            }
        }

        public SecureString Password
        {
            get
            {
                return _password;
            }

            set
            {
                _password = value;
                OnPropertyChanged(nameof(Password));
            }
        }

        public string ErrorMessage
        {
            get
            {
                return _errorMessage;
            }

            set
            {
                _errorMessage = value;
                OnPropertyChanged(nameof(ErrorMessage));
            }
        }

        public bool IsViewVisible
        {
            get
            {
                return _isViewVisible;
            }

            set
            {
                _isViewVisible = value;
                OnPropertyChanged(nameof(IsViewVisible));
            }
        }

        //-> Commands
        public ICommand LoginCommand { get; }
        public ICommand RecoverPasswordCommand { get; }
        public ICommand ShowPasswordCommand { get; }
        public ICommand RememberPasswordCommand { get; }

        //Constructor
        public LoginViewModel()
        {
            userRepository = new UserRepository();
            LoginCommand = new ViewModelCommand(ExecuteLoginCommand, CanExecuteLoginCommand);
            RecoverPasswordCommand = new ViewModelCommand(p => ExecuteRecoverPassCommand("", ""));
        }

        private bool CanExecuteLoginCommand(object obj)
        {
            bool validData;
            if (string.IsNullOrWhiteSpace(Username) || Username.Length < 3 ||
                Password == null || Password.Length < 3)
                validData = false;
            else
                validData = true;
            return validData;
        }

        private void ExecuteLoginCommand(object obj)
        {
            var isValidUser = userRepository.AuthenticateUser(new NetworkCredential(Username, Password));
            if (isValidUser)
            {
                Thread.CurrentPrincipal = new GenericPrincipal(
                    new GenericIdentity(Username), null);
                IsViewVisible = false;
            }
            else
            {
                ErrorMessage = "* Invalid username or password";
            }
        }

        private void ExecuteRecoverPassCommand(string username, string email)
        {
            throw new NotImplementedException();
        }
    }

Interfaz de usuario LoginView (XAML – Bindings)

<Window.DataContext>
    <viewModel:LoginViewModel/>
</Window.DataContext>

<Window.Resources>
    <BooleanToVisibilityConverter x:Key="BooleanToVisibility"/>
</Window.Resources>

<Window.Visibility>
    <Binding Path="IsViewVisible" Mode="TwoWay" Converter="{StaticResource BooleanToVisibility}"/>
</Window.Visibility>

                <TextBlock Text="Username"
                           Foreground="DarkGray"
                           FontSize="12"
                           FontWeight="Medium"
                           FontFamily="Montserrat"                             
                           Margin="0,35,0,0"/>

                <TextBox x:Name="txtUser"
                         Text="{Binding Username, UpdateSourceTrigger=PropertyChanged}"
                         FontSize="13"
                         FontWeight="Medium"
                         FontFamily="Montserrat"                            
                         Foreground="White"
                         CaretBrush="LightGray"
                         BorderBrush="DarkGray"
                         BorderThickness="0,0,0,2"
                         Height="28"
                         VerticalContentAlignment="Center"
                         Margin="0,5,0,0"
                         Padding="20,0,0,0">

                    <TextBox.Background>
                        <ImageBrush ImageSource="/Images/user-icon.png"
                                    Stretch="None"
                                    AlignmentX="Left"/>
                    </TextBox.Background>
                </TextBox>

                <TextBlock Text="Password"
                           Foreground="DarkGray"
                           FontSize="12"
                           FontWeight="Medium"
                           FontFamily="Montserrat"                             
                           Margin="0,15,0,0"/>

                <customcontrols:BindablePasswordBox Password="{Binding Password, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
                                                    Height="28"                            
                                                    Margin="0,5,0,0">
                </customcontrols:BindablePasswordBox>

                <TextBlock Text="{Binding ErrorMessage}"
                           Foreground="#D7596D"
                           FontSize="12"
                           FontWeight="Medium"
                           FontFamily="Montserrat"                             
                           Margin="0,10,0,0"
                           TextWrapping="Wrap"/>

                <Button x:Name="btnLogin" 
                        Command="{Binding LoginCommand}"
                        BorderThickness="0"
                        Content="LOG IN"
                        Foreground="White"
                        FontSize="12"
                        FontFamily="Montserrat"
                        Cursor="Hand"                           
                        Margin="0,30,0,0">
                </Button>

Clase UserModel

public class UserModel
{
    public string Id { get; set; }
    public string Username { get; set; }
    public string Password { get; set; }
    public string Name { get; set; }
    public string LastName { get; set; }
    public string Email { get; set; }
}

Clase UserRepository (Data Access)

public class UserRepository : RepositoryBase, IUserRepository
{ 
    public bool AuthenticateUser(NetworkCredential credential)
    {
        bool validUser;
        using (var connection = GetConnection())
        using (var command = new SqlCommand())
        {
            connection.Open();
            command.Connection = connection;
            command.CommandText = "select *from [User] where username=@username and [password]=@password";
            command.Parameters.Add("@username", SqlDbType.NVarChar).Value = credential.UserName;
            command.Parameters.Add("@password", SqlDbType.NVarChar).Value = credential.Password;
            validUser = command.ExecuteScalar() == null ? false : true;
        }
        return validUser;
    }

Descargar Proyecto completo + Base de datos