martes, 21 de septiembre de 2010

[WinForms] Inicio de sesión (I PARTE)

Todos los que hemos desarrollados programas de escritorio (C#, VB, FoxPro o demás lenguajes) y para la web (ASP, PHP) sabemos la importancia de controlar los usuarios que tienen acceso a la aplicación y a que opciones del menú estos tienen acceso,.

La forma de iniciar sesión es única para todas la aplicaciones, Usuario y Contraseña de acceso, validar contra la base de datos o conectar con las credenciales del usuario de Windows logeado en el momento, pero para mi esta ultima puede ser peligrosa ya que cualquier usuario podría accesar a la aplicación usando la computadora de otro usuario que se haya descuidado.

Bien, el objetivo de este articulo es ayudar a tomar una decisión sobre como hacer esto cuando estamos desarrollando una aplicación para múltiples usuarios, la técnica que usaremos en nuestro ejemplo será iniciar sesión como usuario local de SQL Server, en este ejemplo utilizares esta base de datos, para que así no nos preocupemos en guardar la contraseña de acceso en una tabla y encryptar está, así también evitamos usar el “sa” de SQL Server para conectarnos, ya que esto nos perjudica, ya que no podemos cambiar la contraseña de este porque si lo hacemos ningún usuario que use nuestra aplicación podrá conectarse y también perdemos la funcionalidad de poder usar original_login() como valor predeterminado en algunos campos de nuestras tablas.

siempre validaremos contra una tabla local en nuestra base de datos para saber si el usuario puede usar nuestra aplicación y saber a que opciones del menú este tiene acceso.

nuestro lenguaje de programación será C# .Net en VS2008 y también lo hare en VB, así que manos al teclado.

Primero lo primero, debemos crear un formulario que permita al usuario ingresar su Usuario y Contraseña de acceso, como también seleccionar el servidor de SQL Server al que se conectaran, este ultimo podría ser tomado de un archivo de configuración el problema puede ser cuando cambiamos el servidor de SQL server tendríamos que ir computadora por computadora cambiando esta dirección en el archivo de configuración, pero bien lo dejo a criterio de cada quien.

así que nuestro formulario debería lucir así:

image

  • TextBox Usuario.
    Name txtUsuario
  • TextBox Contraseña.
    Name txtContraseña
    UseSystemPasswordChar

    True

  • ComboBox Servidor de Conexión.
    Name cboBoxServidorConexion
    Button Aceptar
    Name bAceptar
    Evento Click bAceptar_Click

Además he agregado un label y un ProgressBar (tal como se muestra en la imagen) estos con la intención de buscar los servidores de SQL Server en un hilo y que el usuario vea el progreso de la búsqueda, también lo utilizare al momento de presionar el botón Aceptar, la validación del usuario la hare en un hilo para que el usuario no tenga la sensación de que la aplicación se ha pegado, ya que siempre que hacemos conexión la primera vez suele demorar un tiempo.

ya creado el formulario, veamos el código, pieza por pieza.

1 using System.Data;
2 using System.Data.Sql;
3 using System.Drawing;
4 using System.Linq;
5 using System.Text;
6 using System.Windows.Forms;
7
8 using CP = ConnectionProvider; // Proveedor de Conecciones.
9 using DE = AppEjemplo.Entidades;
10
11 namespace AppEjemplo.Dialogos
12 {
13 public partial class IniciarSession : Form
14 {
15 // Mecanismo para enumera todas las instancias de SQL Server incluidas en la red local.
16 SqlDataSourceEnumerator SqlInstances = SqlDataSourceEnumerator.Instance;
17 }
18 }


Constructor del formulario.



1 public IniciarSession()
2 {
3 InitializeComponent();
4 #region -> Hilo para buscar servidores de SQL Server
5
6 BackgroundWorker buscarServidoresSQL = new BackgroundWorker();
7 buscarServidoresSQL.WorkerReportsProgress = true;
8 buscarServidoresSQL.DoWork += new DoWorkEventHandler(buscarServidoresSQL_DoWork);
9 buscarServidoresSQL.ProgressChanged += new ProgressChangedEventHandler(buscarServidoresSQL_ProgressChanged);
10 buscarServidoresSQL.RunWorkerCompleted += new RunWorkerCompletedEventHandler(buscarServidoresSQL_RunWorkerCompleted);
11 buscarServidoresSQL.RunWorkerAsync();
12
13 #endregion
14 }
15
en el constructor creamos un BackgroundWorker, lo programamos y lo ejecutamos para que inicie a buscar los servidores de SQL Server disponibles en nuestra red.


Hilo para buscar servidores SQL.



1 #region -> Eventos de hilo para recuperar todas las instancias de SQL Server disponibles
2
3 void buscarServidoresSQL_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
4 {
5 // oculta label y barra de progreso.
6 lblMensaje.Visible = false;
7 pbBusqueda.Visible = false;
8 }
9
10 void buscarServidoresSQL_ProgressChanged(object sender, ProgressChangedEventArgs e)
11 {
12 if (e.UserState is bool)
13 {
14 // muestra label y barra de progreso
15 lblMensaje.Visible = true;
16 pbBusqueda.Visible = true;
17 }
18 else if (e.UserState is string)
19 {
20 // agrega servidor SQL server a items del combobox
21 cboBoxServidorConexion.Items.Add((string)e.UserState);
22 }
23 }
24
25 void buscarServidoresSQL_DoWork(object sender, DoWorkEventArgs e)
26 {
27 BackgroundWorker bgw = sender as BackgroundWorker;
28
29 bgw.ReportProgress(0, true); // mostrar label y barra de progreso.
30 DataTable servidores = SqlInstances.GetDataSources(); // obtiene servidores SQL Server disponibles en la red local.
31 foreach(DataRow instance in servidores.Rows)
32 {
33 string serverName = instance.Field<string>("ServerName");
34 string instanceName = instance.Field<string>("InstanceName");
35 // Reporta servidor encontrado para ser agreado a ComboBox
36 bgw.ReportProgress(0, string.Format("{0}\\{1}", serverName, instanceName));
37 }
38 }
39
40 #endregion
41


Botón Aceptar (Evento Click)


1 private void bAceptar_Click(object sender, EventArgs e)
2 {
3 BackgroundWorker bgw = new BackgroundWorker();
4 bgw.WorkerReportsProgress = true;
5 bgw.DoWork += new DoWorkEventHandler(bgw_DoWork);
6 bgw.ProgressChanged += new ProgressChangedEventHandler(bgw_ProgressChanged);
7 bgw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bgw_RunWorkerCompleted);
8 bgw.RunWorkerAsync(new string[] { txtUsuario.Text, txtContraseña.Text, cboBoxServidorConexion.Text });
9 }

como podemos ver aqui creamos otro hilo para que al validar el usuario no se congele la interfaz, hacemos uso del Label y ProgressBar para notificar el progreso, pasando como parámetros al hilo el usuario, contraseña e instancia de SQL Server seleccionada.


Hilo para validar usuario



1 void bgw_ProgressChanged(object sender, ProgressChangedEventArgs e)
2 {
3 if (e.UserState is bool)
4 {
5 lblMensaje.Text = (bool)e.UserState ? "Validando Usuario y Contraseña" : lblMensaje.Text;
6 lblMensaje.Visible = (bool)e.UserState;
7 pbBusqueda.Visible = (bool)e.UserState;
8 }
9 }
10
11 void bgw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
12 {
13 if (e.Result is bool)
14 {
15 if ((bool)e.Result)
16 {
17 // Autenticación de usuario exitosa.
18 AppEjemploHelper.SetUserConnected(txtUsuario.Text);
19 DialogResult = DialogResult.OK; // Cierra formulario InicioSession.
20 }
21 else
22 {
23 MessageBox.Show("No se logro establecer conexión\n\nVerifique el usuario y la contraseña de acceso.",
24 "Acceso invalido",
25 MessageBoxButtons.OK,
26 MessageBoxIcon.Error);
27 txtUsuario.Focus();
28 }
29 }
30 }
31
32 void bgw_DoWork(object sender, DoWorkEventArgs e)
33 {
34 #region -> Recuperamos parametros pasados al hilo
35
36 string UID = ((string[])e.Argument)[0];
37 string PWD = ((string[])e.Argument)[1];
38 string DSource = ((string[])e.Argument)[2];
39
40 #endregion
41
42 BackgroundWorker bgw = sender as BackgroundWorker;
43
44 bgw.ReportProgress(0, true); // Mostrar Label y Barra de progreso.
45 // Validar usuario y contraseña.
46 // true = si la autenticación es correcta.
47 // false = en caso contrario
48 e.Result = CP.SQLProvider.ConnectToSqlServer(UID, PWD, DSource, AppEjemploHelper.InitialCatalog);
49 if ((bool)e.Result && UID.ToLower() != "sa")
50 {
51 #region -> verificamos si el usuario esta registrado en nuestr tabla de usuarios.
52
53 DE.Usuarios usuario = DE.UsuariosDA.Usuario(UID);
54 e.Result = (usuario != null);
55 // si el usuario no esta registrado en nuestra tabla de usuarios.
56 // no tendra acceso a la aplicación.
57 if ((bool)e.Result) // El usuario esta registrado.
58 e.Result = usuario.Activo;
59 // si el usuario no esta activo en la tabla de usuario no tendra
60 // acceso a la aplicación.
61
62 #endregion
63 }
64 bgw.ReportProgress(0, false); // ocultar label y barra de progreso.
65 }
66

y modificamos el archivo Program.cs para que tras que inicie la aplicación nos muestre el formulario para iniciar sesión.


1 using System;
2 using System.Collections.Generic;
3 using System.Linq;
4 using System.Windows.Forms;
5
6 namespace AppEjemplo
7 {
8 static class Program
9 {
10 /// <summary>
11 /// The main entry point for the application.
12 /// </summary>
13 [STAThread]
14 static void Main()
15 {
16 Application.EnableVisualStyles();
17 Application.SetCompatibleTextRenderingDefault(false);
18
19 Dialogos.IniciarSession loginIn = new Dialogos.IniciarSession();
20 if (loginIn.ShowDialog() == DialogResult.OK)
21 {
22 Application.Run(new Principal());
23 }
24 else
25 {
26 Application.Exit();
27 }
28 }
29 }
30 }
31

Para VBasic .Net en VS2008 se a usado ApplicationEvents.vb


[VB]


1 Namespace My
2
3 ' The following events are available for MyApplication:
4 '
5 ' Startup: Raised when the application starts, before the startup form is created.
6 ' Shutdown: Raised after all application forms are closed. This event is not raised if the application terminates abnormally.
7 ' UnhandledException: Raised if the application encounters an unhandled exception.
8 ' StartupNextInstance: Raised when launching a single-instance application and the application is already active.
9 ' NetworkAvailabilityChanged: Raised when the network connection is connected or disconnected.
10
11 Partial Friend Class MyApplication
12 Private Sub MyApplication_Startup(ByVal sender As Object, _
13 ByVal e As Microsoft.VisualBasic.ApplicationServices.StartupEventArgs) Handles Me.Startup
14 Dim loginIn As New IniciarSession()
15 If loginIn.ShowDialog() <> DialogResult.OK Then
16 e.Cancel = True
17 End If
18 End Sub
19 End Class
20
21 End Namespace
22


Obteniendo el siguiente resultado como se muestra en la imagen a continuación.


image



image


 


Bien, si les ha parecido bueno este articulo, no dejen de leer la segunda parte en la cual explicare como activar y desactivar las opciones del Menú a las cuales tiene acceso el usuario.


y aqui les dejo el código completo del formulario para inicio de sesión, en C# y VB, y al final estará disponible el proyecto para que pueda ser descargado en el cual se incluirá el fuente de “ConnectionProvider” este ultimo proyecto funciona a puro Reflection para crear las sentencias Select, Insert, Update y Delete según la entidad.


[C#] – Código completo.



1 using System;
2 using System.Collections.Generic;
3 using System.ComponentModel;
4 using System.Data;
5 using System.Data.Sql;
6 using System.Drawing;
7 using System.Linq;
8 using System.Text;
9 using System.Windows.Forms;
10
11 using CP = ConnectionProvider;
12 using DE = AppEjemplo.Entidades;
13
14 namespace AppEjemplo.Dialogos
15 {
16 public partial class IniciarSession : Form
17 {
18 // Mecanismo para enumera todas las instancias de SQL Server incluidas en la red local.
19 SqlDataSourceEnumerator SqlInstances = SqlDataSourceEnumerator.Instance;
20
21 public IniciarSession()
22 {
23 InitializeComponent();
24 #region -> Hilo para buscar servidores de SQL Server
25
26 BackgroundWorker buscarServidoresSQL = new BackgroundWorker();
27 buscarServidoresSQL.WorkerReportsProgress = true;
28 buscarServidoresSQL.DoWork += new DoWorkEventHandler(buscarServidoresSQL_DoWork);
29 buscarServidoresSQL.ProgressChanged += new ProgressChangedEventHandler(buscarServidoresSQL_ProgressChanged);
30 buscarServidoresSQL.RunWorkerCompleted += new RunWorkerCompletedEventHandler(buscarServidoresSQL_RunWorkerCompleted);
31 buscarServidoresSQL.RunWorkerAsync();
32
33 #endregion
34 }
35
36 #region -> Eventos de hilo para recuperar todas las instancias de SQL Server disponibles
37
38 void buscarServidoresSQL_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
39 {
40 lblMensaje.Visible = false;
41 pbBusqueda.Visible = false;
42 }
43
44 void buscarServidoresSQL_ProgressChanged(object sender, ProgressChangedEventArgs e)
45 {
46 if (e.UserState is bool)
47 {
48 lblMensaje.Visible = true;
49 pbBusqueda.Visible = true;
50 }
51 else if (e.UserState is string)
52 {
53 cboBoxServidorConexion.Items.Add((string)e.UserState);
54 }
55 }
56
57 void buscarServidoresSQL_DoWork(object sender, DoWorkEventArgs e)
58 {
59 BackgroundWorker bgw = sender as BackgroundWorker;
60 bgw.ReportProgress(0, true);
61 DataTable servidores = SqlInstances.GetDataSources();
62 foreach(DataRow instance in servidores.Rows)
63 {
64 string serverName = instance.Field<string>("ServerName");
65 string instanceName = instance.Field<string>("InstanceName");
66 bgw.ReportProgress(0, string.Format("{0}\\{1}", serverName, instanceName));
67 }
68 }
69
70 #endregion
71
72 private void bAceptar_Click(object sender, EventArgs e)
73 {
74 BackgroundWorker bgw = new BackgroundWorker();
75 bgw.WorkerReportsProgress = true;
76 bgw.DoWork += new DoWorkEventHandler(bgw_DoWork);
77 bgw.ProgressChanged += new ProgressChangedEventHandler(bgw_ProgressChanged);
78 bgw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bgw_RunWorkerCompleted);
79 bgw.RunWorkerAsync(new string[] { txtUsuario.Text, txtContraseña.Text, cboBoxServidorConexion.Text });
80 }
81
82 #region -> Eventos de hilo para validar usuario y contraseña de acceso a SQL Server
83
84 void bgw_ProgressChanged(object sender, ProgressChangedEventArgs e)
85 {
86 if (e.UserState is bool)
87 {
88 lblMensaje.Text = (bool)e.UserState ? "Validando Usuario y Contraseña" : lblMensaje.Text;
89 lblMensaje.Visible = (bool)e.UserState;
90 pbBusqueda.Visible = (bool)e.UserState;
91 }
92 }
93
94 void bgw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
95 {
96 if (e.Result is bool)
97 {
98 if ((bool)e.Result)
99 {
100 AppEjemploHelper.SetUserConnected(txtUsuario.Text);
101 DialogResult = DialogResult.OK;
102 }
103 else
104 {
105 MessageBox.Show("No se logro establecer conexión\n\nVerifique el usuario y la contraseña de acceso.",
106 "Acceso invalido",
107 MessageBoxButtons.OK,
108 MessageBoxIcon.Error);
109 txtUsuario.Focus();
110 }
111 }
112 }
113
114 void bgw_DoWork(object sender, DoWorkEventArgs e)
115 {
116 #region -> Recuperamos parametros pasados al hilo
117
118 string UID = ((string[])e.Argument)[0];
119 string PWD = ((string[])e.Argument)[1];
120 string DSource = ((string[])e.Argument)[2];
121
122 #endregion
123
124 BackgroundWorker bgw = sender as BackgroundWorker;
125 bgw.ReportProgress(0, true);
126 e.Result = CP.SQLProvider.ConnectToSqlServer(UID, PWD, DSource, AppEjemploHelper.InitialCatalog);
127 if ((bool)e.Result && UID.ToLower() != "sa")
128 {
129 #region -> verificamos si el usuario esta registrado en nuestr tabla de usuarios.
130
131 DE.Usuarios usuario = DE.UsuariosDA.Usuario(UID);
132 e.Result = (usuario != null); // si el usuario no esta registrado en nuestra tabla de usuarios.
133 // no tendra acceso a la aplicación.
134 if ((bool)e.Result)
135 e.Result = usuario.Activo; // si el usuario no esta activo en la tabla de usuario no tendra
136 // acceso a la aplicación.
137
138 #endregion
139 }
140 bgw.ReportProgress(0, false);
141 }
142
143 #endregion
144 }
145 }
146

[VB] - Código completo


1 Imports System.Data.Sql
2 Imports System.ComponentModel
3
4 Imports CP = ConnectionProvider
5 Imports DE = AppEjemploVB.Entidades
6
7 Public Class IniciarSession
8
9 'Mecanismo para enumera todas las instancias de SQL Server incluidas en la red local.
10 Dim SqlInstances As SqlDataSourceEnumerator = SqlDataSourceEnumerator.Instance
11
12 Sub New()
13
14 ' This call is required by the Windows Form Designer.
15 InitializeComponent()
16
17 ' Add any initialization after the InitializeComponent() call.
18 Dim buscarServidoresSQL As New BackgroundWorker()
19 buscarServidoresSQL.WorkerReportsProgress = True
20 AddHandler buscarServidoresSQL.DoWork, AddressOf buscarServidoresSQL_DoWork
21 AddHandler buscarServidoresSQL.ProgressChanged, AddressOf buscarServidoresSQL_ProgressChanged
22 AddHandler buscarServidoresSQL.RunWorkerCompleted, AddressOf buscarServidoresSQL_RunWorkerCompleted
23 buscarServidoresSQL.RunWorkerAsync()
24 End Sub
25
26 #Region "-> Eventos de hilo para recuperar todas las instancias de SQL Server disponibles"
27
28 Private Sub buscarServidoresSQL_RunWorkerCompleted(ByVal sender As Object, ByVal e As RunWorkerCompletedEventArgs)
29 lblMensaje.Visible = False
30 pbBusqueda.Visible = False
31 End Sub
32
33 Private Sub buscarServidoresSQL_ProgressChanged(ByVal sender As Object, ByVal e As ProgressChangedEventArgs)
34 If (TypeOf e.UserState Is Boolean) Then
35 lblMensaje.Visible = True
36 pbBusqueda.Visible = True
37 ElseIf (TypeOf e.UserState Is String) Then
38 cboBoxServidorConexion.Items.Add(CType(e.UserState, String))
39 End If
40 End Sub
41
42 Private Sub buscarServidoresSQL_DoWork(ByVal sender As Object, ByVal e As DoWorkEventArgs)
43 Dim bgw As BackgroundWorker = CType(sender, BackgroundWorker)
44 bgw.ReportProgress(0, True)
45 Dim servidore As DataTable = SqlInstances.GetDataSources()
46 For Each instance As DataRow In servidore.Rows
47 Dim serverName As String = instance.Field(Of String)("ServerName")
48 Dim instanceName As String = instance.Field(Of String)("InstanceName")
49 bgw.ReportProgress(0, String.Format("{0}\{1}", serverName, instanceName))
50 Next
51 End Sub
52
53 #End Region
54
55 Private Sub bAceptar_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles bAceptar.Click
56 Dim bgw As New BackgroundWorker()
57 bgw.WorkerReportsProgress = True
58 AddHandler bgw.ProgressChanged, AddressOf bgw_ProgressChanged
59 AddHandler bgw.RunWorkerCompleted, AddressOf bgw_RunWorkerCompleted
60 AddHandler bgw.DoWork, AddressOf bgw_DoWork
61 bgw.RunWorkerAsync(New String() {txtUsuario.Text, txtContraseña.Text, cboBoxServidorConexion.Text})
62 End Sub
63
64 #Region "-> Eventos de hilo para validar usuario y contraseña de acceso a SQL Server"
65
66 Private Sub bgw_ProgressChanged(ByVal sender As Object, ByVal e As ProgressChangedEventArgs)
67 If TypeOf e.UserState Is Boolean Then
68 lblMensaje.Text = IIf(CType(e.UserState, Boolean), "Validando Usuario y Contraseña", "")
69 lblMensaje.Visible = CType(e.UserState, Boolean)
70 pbBusqueda.Visible = CType(e.UserState, Boolean)
71 End If
72 End Sub
73
74 Private Sub bgw_RunWorkerCompleted(ByVal sender As Object, ByVal e As RunWorkerCompletedEventArgs)
75 If TypeOf e.Result Is Boolean Then
76 If CType(e.Result, Boolean) Then
77 AppEjemploHelper.SetUserConnected(txtUsuario.Text)
78 DialogResult = Windows.Forms.DialogResult.OK
79 Else
80 MessageBox.Show("No se logro establecer conexión" & vbCrLf & vbCrLf & "Verifique el usuario y la contraseña de acceso.", _
81 "Acceso invalido", _
82 MessageBoxButtons.OK, _
83 MessageBoxIcon.Error)
84 txtUsuario.Focus()
85 End If
86 End If
87 End Sub
88
89 Private Sub bgw_DoWork(ByVal sender As Object, ByVal e As DoWorkEventArgs)
90 Dim UID As String = CType(e.Argument, String())(0)
91 Dim PWD As String = CType(e.Argument, String())(1)
92 Dim DSource As String = CType(e.Argument, String())(2)
93
94 Dim bgw As BackgroundWorker = CType(sender, BackgroundWorker)
95 bgw.ReportProgress(0, True)
96
97 e.Result = CP.SQLProvider.ConnectToSqlServer(UID, PWD, DSource, AppEjemploHelper.InitialCatalog)
98 If CType(e.Result, Boolean) And UID.ToLower() <> "sa" Then
99
100 Dim usuario As DE.Usuarios = DE.UsuariosDA.Usuarios(UID)
101 e.Result = usuario IsNot Nothing
102 If CType(e.Result, Boolean) Then
103 e.Result = usuario.Activo
104 End If
105
106 End If
107
108 bgw.ReportProgress(0, False)
109 End Sub
110
111 #End Region
112
113 End Class