lunes, 29 de julio de 2013

Restricción de Usuarios

A petición de un Usuario del Foro de C# les prepare un pequeño ejemplo sobre la restricción de usuarios en un sistema informático, en donde se valida la presencia del Usuario y la concordancia del Password proporcionado con los datos almacenados en la tabla Usuarios.

Para este ejercicio se usa estos datos contenidos en la Bd que esta adjunto al proyecto:
Usuarios

Tabla de configuración del Nivel de Acceso:
NivelAcceso

Teniendo en cuenta la información anterior procedamos a ver el código fuente del ejemplo:
Un de los aspectos mas importantes para poder utilizar el Login esta en el Main de la clase Program que es donde configuraremos que Formulario que deberá de arrancar como inicio, es decir, el formulario que hará la función de Logeo a la Bd.
   1: using System;
   2: using System.Windows.Forms;
   3:  
   4: namespace RestriccionDeUsuariosCSharp
   5: {
   6:     static class Program
   7:     {
   8:         /// <summary>
   9:         /// Punto de entrada principal para la aplicación.
  10:         /// </summary>
  11:         [STAThread]
  12:         static void Main()
  13:         {
  14:             //Application.EnableVisualStyles();
  15:             //Application.SetCompatibleTextRenderingDefault(false);
  16:             //Application.Run(new Form1());
  17:  
  18:             Application.EnableVisualStyles();
  19:             Application.SetCompatibleTextRenderingDefault(false);
  20:  
  21:             Login frm = new Login();
  22:             frm.ShowDialog();
  23:  
  24:             if (frm.DialogResult == DialogResult.OK)
  25:                 Application.Run(new Form1());
  26:         }
  27:     }
  28: }

El Formulario Login será el encargado de realizar la autenticación a la Bd:
Login

Dentro procedemos a crear las Funcion autenticar:

   1: /// <summary>
   2: /// Funcion encargada de validar la existencia del Usuario y Password
   3: /// </summary>
   4: /// <Autor>Jose Luis Garcia Bautista</Autor>
   5: /// <param name="usuario"> Nombre del usuario en la BD</param>
   6: /// <param name="password"> Password perteneciente al Nombre de usuario proporcionado</param>
   7: /// <returns>True si ambos datos conincide con algun registro de la Bd</returns>
   8: public static bool Autenticar(string usuario, string password)
   9: {
  10:     //Utilizamos el Bloque Using...end Using para administrar la conexion
  11:     //este se encarga de cerrar la conexion y destruir todos los objetos encerrados en el
  12:     using (SqlCeConnection cnx = new SqlCeConnection(ConfigurationManager.ConnectionStrings["UserConnectionString"].ToString()))
  13:     {
  14:         //
  15:         //Abrimos la conexion a la Bd
  16:         //
  17:         cnx.Open();
  18:         //
  19:         //Creamos la Consulta SQl utilizando parametros, en donde unicamente recuperaremos el NivelDeAcceso
  20:         //con el que cuenta el Usuario siempre cuando Exista y se hayan proporcionado las credenciales validas
  21:         //
  22:         const string sqlAction = "SELECT NivelAcceso FROM Usuarios WHERE Usuario = @username AND Password = @pass";
  23:         //
  24:         //Creamos el objento Command propio del motor de la Bd que estamos usando
  25:         //
  26:         SqlCeCommand cmd = new SqlCeCommand(sqlAction, cnx);
  27:         //
  28:         //Asignamos valores a la coleccion de parametros
  29:         //
  30:         cmd.Parameters.AddWithValue("@username", usuario);
  31:         cmd.Parameters.AddWithValue("@pass", password);
  32:         //
  33:         //Recuperamos la cantidad de registros coincidentes con el Usuario y Password por logica
  34:         //solo podremos recuperar un solo registro
  35:         //
  36:         int tipoUsuario = Convert.ToInt32(cmd.ExecuteScalar());
  37:         //
  38:         //Si el tipodeusuario es Mayor a cero significa que la Validacion fue cumplida y el usuario
  39:         //tiene un nivel de acceso
  40:         //
  41:         if (tipoUsuario > 0)
  42:         {
  43:             //
  44:             //Enviamos el Nivel de acceso a la propiedad NivelAcceso de la Clase Usuarios
  45:             //
  46:             Usuario.NivelAcceso = tipoUsuario;
  47:             //
  48:             //retornamos True ya que la validacion a sido Exitosa
  49:             //
  50:             return true;
  51:         }
  52:     }
  53:     //
  54:     //Si la validacion no fue cumplica retornamos False para evitar continuar con el proceso
  55:     //
  56:     return false;
  57: }

Utilizamos la función Autenticar:

   1: private void btnaceptar_Click(object sender, EventArgs e)
   2: {
   3:     try
   4:     {
   5:  
   6:         //Mandamos a llamar a la funcion Autenticar mandandole los parametros requeridos
   7:         //Autenticar(ByVal usuario As String, ByVal password As String)
   8:  
   9:         {
  10:             if (Autenticar(txtusuario.Text.Trim(), txtpassword.Text.Trim()))
  11:             {
  12:                 //Si el resultado de la funcion es True entonces enviamos comor resultado un Ok
  13:                 DialogResult = DialogResult.OK;
  14:             }
  15:             else
  16:             {
  17:                 //Si la funcion autenticar envio un False entonces mostramos el mensaje y mandamos un Abort
  18:                 MessageBox.Show("Usuario o Password no validos");
  19:                 DialogResult = DialogResult.Abort;
  20:             }
  21:         }
  22:     }
  23:     catch (Exception ex)
  24:     {
  25:         MessageBox.Show(string.Format("Error : {0}", ex.Message), "Error inesperado", MessageBoxButtons.OK, MessageBoxIcon.Error);
  26:     }
  27: }

Dentro del Form1 procedemos a validar el Nivel de acceso del usuario para habilitar e inhabilitar las opciones del Menú:

   1: using System;
   2: using System.Windows.Forms;
   3:  
   4: namespace RestriccionDeUsuariosCSharp
   5: {
   6:     public partial class Form1 : Form
   7:     {
   8:         public Form1()
   9:         {
  10:             InitializeComponent();
  11:         }
  12:  
  13:         private void Form1_Load(object sender, EventArgs e)
  14:         {
  15:             //Preguntamos por el contenido de la variable publica
  16:             //NivelAcceso contenido en la clase Usuario
  17:  
  18:             {
  19:                 switch (Usuario.NivelAcceso)
  20:                 {
  21:                         //
  22:                         //Opciones validar para un Administrador
  23:                         //
  24:                     case 1:
  25:                         //Configuramos las opciones
  26:                         MenuInvitado.Enabled = false;
  27:                         MenuMiembro.Enabled = false;
  28:                         break;
  29:  
  30:                         //
  31:                         //Opciones validas para un Miembro
  32:                         //
  33:                     case 2:
  34:                         MenuAdmisnitrador.Enabled = false;
  35:                         MenuMiembro.Enabled = false;
  36:                         break;
  37:  
  38:                         //
  39:                         //Opciones validas para un Invitado
  40:                         //
  41:                     case 3:
  42:                         MenuInvitado.Enabled = false;
  43:                         MenuAdmisnitrador.Enabled = false;
  44:                         break;
  45:                 }
  46:             }
  47:         }
  48:     }
  49: }

Si el proceso de Login fue cubierto satisfactoriamente podremos ver el formulario Form1 con las restricciones configuradas:

Menu

El ejemplo proporcionado cuenta con muchas opciones de mejora como.
  • No utilizar una propiedad publica para compartir del Nivel de acceso de Usuario y en cambio usar el Patron Singleton para compartir información en todo el proyecto sin el riesgo de que este sea cambiado por una instancia diferente, es decir, únicamente se instancia una sola vez y nada mas. Para mas información del Patron Singleton: El Patrón Singleton

  • El uso de un Algoritmo de encriptación como Hash para poder encriptar la información de Password dando con esto una mayor seguridad a la aplicación, para mayor informacion: Desmitificando la Encriptación (Parte II)
Esas son las dos principales carencias del Ejercicio, debido a que esta primera entrega únicamente tiene como objetivo orientar sobre el proceso de Login pero, en una entrega posterior se agregaran estas dos funcionalidades.

Nota: El proyecto fue desarrollado en Vs2010 usando una Bd SqlCompact V3.5 y Framework 4.0

martes, 16 de julio de 2013

Select * From Where Between

La selección de datos que están comprendidos en un rango de fechas es un tema muy mencionado en los Foros de Msdn tanto en C# como en Vb.Net.
A si que recordando lo complicado que pudiera ser esto para los que aun se están iniciando en el mundo de la programación, preparé un ejemplo de como utilizar el BETWEEN del Transact-SQL para obtener registros cuyo campo Fecha estén dentro de un rango definido, agregándole como Filtro adicional una tercer condicionante, es decir; filtraremos por fechas y por id.
Esta es la consulta que usaremos en el ejemplo, observen la manera en que se estructura la instrucción sql usando parámetros, recuerden que el uso de parámetros en las consultas Sql son esenciales para prevenir primero errores de sintaxis, segundo para prevenir ataques por inyección de código Sql o Sql Injection.
   1: //

   2: //Para seleccionar registros cuyo campo Fecha esta dentro del rango de Fechas

   3: //utilizamos la clausula Between en una consulta Parametrizada

   4: //

   5: const string sqlAction = @"SELECT Vt.FolioVenta, Vt.NumArticulo, Vt.Precio, Vt.FechaVenta, Vt.NumEmpleado, Em.NomEmpleado 

   6:                            FROM Ventas Vt 

   7:                            INNER JOIN Empleados Em ON Vt.NumEmpleado = Em.NumEmpleado 

   8:                            WHERE FechaVenta 

   9:                            BETWEEN @fechainicial AND @fechafinal"
;
Los prefijos para los parámetros para aquellos que aun no están familiarizados en su uso dependerá del motor de Bd que estemos usando, por ejemplo para este caso que se usa una Bd SQlCe o SqlCompact se usa el prefijo “@” al igual que en SQlServer, pero para motores como Access y MySql se usa “?”.

Descripción del ejemplo:

El ejemplo simula la selección de las ventas realizadas en un rango de fechas agregando como filtro adicional el numero de vendedor.

Imaginemos que necesitamos la ventas generales del 01/01/2013 al 01/07/2013, después queremos saber cuanto vendió el empleado no. 1 en ese rango de fechas, pues bien este ejemplo les servirá para adentrarnos en los filtros condicionales usando el Between de Sql.

SelectBetween

Creamos la función encargada de llenar un DataTable con los datos recuperados por la consulta parametrizada, que recibirá dos parámetros de entrada que son: fechaInicial y fechaFinal.
  1: /// <summary>
  2: /// Devuelve un conjunto de registros existentes dentro de un rango de Fechas
  3: /// </summary>
  4: /// <autor>Jose Luis Garcia Bautista</autor>
  5: /// <param name="fechaInicial">Fecha inicial del rango buscado</param>
  6: /// <param name="fechaFinal">Fecha final del rango buscado</param>
  7: /// <returns>DataTable con los registros encontrados</returns>
  8: /// <remarks></remarks>
  9: private DataTable GetSaleByDate(DateTime fechaInicial, DateTime fechaFinal)
 10: {
 11:     //
 12:     //Defininimos el nombre de nuesta cadena de conexion alojada en el App.config
 13:     //
 14:     using (SqlCeConnection cnx = new SqlCeConnection(ConfigurationManager.ConnectionStrings["ConnectionString"].ToString()))
 15:     {
 16:         //
 17:         //Para seleccionar registros cuyo campo Fecha esta dentro del rango de Fechas
 18:         //utilizamos la clausula Between en una consulta Parametrizada
 19:         //
 20:         const string sqlAction = @"SELECT Vt.FolioVenta, Vt.NumArticulo, Vt.Precio, Vt.FechaVenta,     Vt.NumEmpleado, Em.NomEmpleado 
 21:                                    FROM Ventas Vt 
 22:                                    INNER JOIN Empleados Em ON Vt.NumEmpleado = Em.NumEmpleado 
 23:                                    WHERE FechaVenta 
 24:                                    BETWEEN @fechainicial AND @fechafinal
";
 25:         //
 26:         //Creamos el objeto Command, en este caso uso SQlCecommand porque el ejemplo lleva una Bd SqlCe
 27:         //
 28:         using (SqlCeCommand cmd = new SqlCeCommand(sqlAction, cnx))
 29:         {
 30:             //
 31:             //Creamos los parametros y asignamos valores
 32:             cmd.Parameters.AddWithValue("@fechainicial", fechaInicial);
 33: 
 34:             cmd.Parameters.AddWithValue("@fechafinal", fechaFinal);
 35: 
 36:             //Definimos el datatable que llenaremos con los datos recuperador
 37:             //
 38:             DataTable dt = new DataTable();
 39:             //
 40:             //creamos el objeto DataAdapter, este se encarga de abrir y cerrar la conexion
 41:             //
 42:             SqlCeDataAdapter da = new SqlCeDataAdapter(cmd);
 43:             //
 44:             //llenamos el objeto DataTable
 45:             //
 46:             da.Fill(dt);
 47:             //
 48:             //Devolvemos el datatable con los datos recuperados
 49:             //
 50:             return dt;
 51:             //
 52:             //Se cierra el Using destruyendo los objetos creados dentro 
 53:             //
 54:         }
 55:         //
 56:         //liberamos tambien la conexion a la bd
 57:         //
 58:     }
 59: 
 60: }
 
Sobre cargamos la función anterior con un parámetro adicional que corresponderá al Numero de Empleado, cambiando con esta sobrecarga de dos parámetros a tres, entonces nuestra función GetSaleByDate queda de la siguiente manera, ¡ojo! es una sobrecarga no una modificación de nuestra primera función, una sobre carga en palabras menos técnicas es una función o método con el mismo nombre pero diferente firma;
  1:         /// <summary>
  2:         /// Devuelve un conjunto de registros existentes dentro de un rango de Fechas pertenecientes al Empleado solicitado
  3:         /// </summary>
  4:         /// <autor>Jose Luis Garcia Bautista</autor>
  5:         /// <param name="fechaInicial">Fecha inicial del rango buscado</param>
  6:         /// <param name="fechaFinal">Fecha final del rango buscado</param>
  7:         /// <param name="numEmpleado">Numero del empleado consultado</param>
  8:         /// <returns>DataTable con los registros encontrados</returns>
  9:         /// <remarks></remarks>
 10:         private DataTable GetSaleByDate(DateTime fechaInicial, DateTime fechaFinal, int numEmpleado)
 11:         {
 12:             //
 13:             //Defininimos el nombre de nuesta cadena de conexion alojada en el App.config
 14:             //
 15:             using (SqlCeConnection cnx = new SqlCeConnection(ConfigurationManager.ConnectionStrings["ConnectionString"].ToString()))
 16:             {
 17:                 //
 18:                 //Para seleccionar registros cuyo campo Fecha esta dentro del rango de Fechas
 19:                 //utilizamos la clausula Between en una consulta Parametrizada mas el Id del Empleado consultado
 20:                 //
 21:                 const string sqlAction = "SELECT Vt.FolioVenta, Vt.NumArticulo, Vt.Precio, Vt.FechaVenta, Vt.NumEmpleado, Em.NomEmpleado FROM Ventas Vt " +
 22:                                           "INNER JOIN Empleados Em ON Vt.NumEmpleado = Em.NumEmpleado " +
 23:                                           "WHERE FechaVenta BETWEEN @fechainicial AND @fechafinal " +
 24:                                           "AND Vt.NumEmpleado = @numempleado";
 25:                 //
 26:                 //Creamos el objeto Command, en este caso uso SQlCecommand porque el ejemplo lleva una Bd SqlCe
 27:                 //
 28:                 using (SqlCeCommand cmd = new SqlCeCommand(sqlAction, cnx))
 29:                 {
 30:                     //
 31:                     //Creamos los parametros y asignamos valores
 32:                     cmd.Parameters.AddWithValue("@fechainicial", fechaInicial);
 33:                     cmd.Parameters.AddWithValue("@fechafinal", fechaFinal);
 34:                     cmd.Parameters.AddWithValue("@numempleado", numEmpleado);
 35:                     //Definimos el datatable que llenaremos con los datos recuperador
 36:                     //
 37:                     DataTable dt = new DataTable();
 38:                     //
 39:                     //creamos el objeto DataAdapter, este se encarga de abrir y cerrar la conexion
 40:                     //
 41:                     SqlCeDataAdapter da = new SqlCeDataAdapter(cmd);
 42:                     //
 43:                     //llenamos el objeto DataTable
 44:                     //
 45:                     da.Fill(dt);
 46:                     //
 47:                     //Devolvemos el datatable con los datos recuperados
 48:                     //
 49:                     return dt;
 50:                 }
 51:                 //
 52:                 //liberamos tambien la conexion a la bd
 53:                 //
 54:             }
 55: 
 56:         }

Como en el ejemplo necesitamos tener la lista de todos los Empleados cargados en un control ComboBox, creamos una función que retornara un DataTable con todos los empleados existentes en la Bd misma que se usara con DataSource del ComboBox:
1: /// <summary>
  2:         /// Funcion que retorna un objeto DataTable con el contenido de la tabla Empleados
  3:         /// </summary>
  4:         /// <autor>Jose Luis Garcia Bautista</autor>
  5:         /// <returns>DataTable con los registros encontrados</returns>
  6:         /// <remarks></remarks>
  7:         private DataTable GetAllEmployee()
  8:         {
  9: 
 10:             using (SqlCeConnection cnx = new SqlCeConnection(ConfigurationManager.ConnectionStrings["ConnectionString"].ToString()))
 11:             {
 12:                 const string sqlAction = "SELECT * FROM Empleados";
 13: 
 14:                 using (SqlCeCommand cmd = new SqlCeCommand(sqlAction, cnx))
 15:                 {
 16: 
 17:                     DataTable dt = new DataTable();
 18: 
 19:                     SqlCeDataAdapter da = new SqlCeDataAdapter(cmd);
 20:                     da.Fill(dt);
 21: 
 22:                     return dt;
 23:                 }
 24:             }
 25: 
 26:         }

Para cargar el ComboBox con la lista de Empleados, usamos el evento Load del Form, observen como se establecen los valores a mostrar y el valor miembro de este a las propiedades DisplayMember y ValueMember respectivamente.
  1:         private void Form1_Load(object sender, EventArgs e)
  2:         {
  3:             try
  4:             {
  5:                 //
  6:                 //Llenamos el combobox con la lista de los empleados, para eso usamos la funcion GetAllEmployee
  7:                 //
  8:                 ComboBox1.DataSource = GetAllEmployee();
  9:                 //
 10:                 //Indicamos que valor es el que se va a mostrar al usuario
 11:                 ComboBox1.DisplayMember = "NomEmpleado";
 12:                 //
 13:                 //Indicamos el valor que esta ligado al valor desplegado
 14:                 ComboBox1.ValueMember = "NumEmpleado";
 15:             }
 16:             catch (Exception ex)
 17:             {
 18:                 MessageBox.Show(ex.Message);
 19:             }
 20:         }
Nuestro formulario debe de cargarse con los datos de los Empleados.

LoadComboBox

Ahora usemos la función GetSaleByDate() ello en el Evento Click de un botón mandamos a llamar a la función enviando los parámetros fechaInicial y fechaFinal los valores parar estos parámetros los tomaremos de los controles DateTimePicker:

fechaInicial = DateTimePicker1
fechaFinal = DateTimePicker2

Recordemos que tenemos esta función sobrecargada y solo estaremos usando una sobrecarga (la que acepta dos parámetros de entrada), también observen como nosotros no necesitamos hacer nada mas que enviar los parámetros que tenemos y automáticamente en tiempo de ejecución se resuelve que Función se ejecutara, es pocas palabras, no tenemos que preocuparnos por indicarle al CLR que función ejecutara ya que este lo resuelve en relación a la cantidad de parámetros enviados.
  1:         private void Button1_Click(object sender, EventArgs e)
  2:         {
  3:             try
  4:             {
  5:                 //El evento Click del boton unicamente cargara las ventas realizadas en el rango de fechas seleccionado, sin filtrar por empleado
  6:                 //
  7:                 //Par esto usamos al funcion sobrecargada GetSaleByDate
  8:                 //
  9:                 //Observa como unicamente se envia el valor de la fecha sin la hora usando la Propiedad Date del control DateTimePicker
 10:                 DataGridView1.DataSource = GetSaleByDate(DateTimePicker1.Value.Date, DateTimePicker2.Value.Date);
 11:                 //
 12:                 //Atrapamos cualquier Excepcion que pudiera con el uso de Try...Catch...End Try
 13:                 //
 14:             }
 15:             catch (Exception ex)
 16:             {
 17:                 //
 18:                 //Mostramos el mensaje de error controlado por el bloque Catch
 19:                 MessageBox.Show(ex.Message);
 20:             }
 21:         }

1

Nota: En esta imagen se muestra el nombre del vendedor LIN porque es el primero que se carga al ComboBox pero aun no se utiliza para aplicar el filtro

Ahora para seleccionar solo las ventas de un vendedor en ese mismo rango de fechas configuramos el Evento SelectionChangeCommitted del control ComboBox para lanzar la búsqueda por Rango de Fechas y Numero de empleado solo cuando se cambie el valor seleccionado en el control Combobox; recordemos que el SelectionChangeCommitted únicamente se activa cuando el usuario cambia de item manualmente, este evento no se activa por código como así lo hace el SelectedIndexChanged para mas información:

SelectionChangeCommitted
SelectedIndexChanged

Entonces usamos el SelectionChangeCommitted para llamar a la función sobrecargada GetSaleByDate() enviándole tres parámetros, fechaInicial, fechaFinal y numEmpleado


  1:         private void ComboBox1_SelectionChangeCommitted(object sender, EventArgs e)
  2:         {
  3:             try
  4:             {
  5:                 DataGridView1.DataSource = GetSaleByDate(DateTimePicker1.Value.Date, DateTimePicker2.Value.Date, Convert.ToInt32(ComboBox1.SelectedValue));
  6: 
  7:             }
  8:             catch (Exception ex)
  9:             {
 10:                 MessageBox.Show(ex.Message);
 11:             }
 12:         }

2

Obsérvese que tanto en el Evento Click del control Button1 y el SeleccionChangeCommmited del control ComboBox1, únicamente enviamos el valor de la Fecha de los controles DTP, en consultas condicionales en donde únicamente se requiere de un filtro por fecha (“SELECT * FROM Tabla WHERE CampoFecha = @fecha”) este punto es muy importante y les funcionara solo si no tienen el valor de la hora junto con la fecha en su campo de la Bd por que…

No es lo mismo esto 2013-07-07 00:00:00.000 que esto 2013-07-07 18:30:25.156

En el primer caso (que es el caso de nuestro ejemplo) con solo tomar el Date del DateTimePicker bastara:

   1: GetSaleByDate(DateTimePicker1.Value.Date, DateTimePicker2.Value.Date);

pero no será tan sencillo para el segundo caso ya que para que se encuentren resultados se deberá de enviar el valor a buscar tal cual como esta en la Bd, es decir; Fecha, horas, minutos, segundos, milisegundos; como ya se habrán imaginado seleccionar un tiempo tan preciso desde un control manualmente es casi imposible, entonces para solucionar este problema tenemos dos opciones.

1) Convertir el valor de FechaHora a un formato de Fecha Corta  en la consulta sql
o

2) Enviar el valor de Fecha hora, minutos, segundos desde la aplicación tal cual como lo tienen almacenados en su campo.

Como recomendación si no necesitan el valor de la hora en el campo no lo envíen!, esto es muy sencillo ya que únicamente deberán de tomar el valor de la fecha del control DTP usando el Date como ya se mostro arriba DateTimePicker1.Value.Date.

Si tienen alguna duda tienen la sección de comentarios para hacérmela llegar.

Saludos desde Monterrey, Nuevo León México!


Aquí les dejo el proyecto de ejemplo desarrollado en C# y la versión en Vb.Net.

Ejemplo en C# :
Ejemplo en Vb.Net

Nota: El proyecto fue desarrollado en Vs2010 usando una Bd SqlCompact V3.5 y Framework 4.0