miércoles, 12 de mayo de 2010

[WinForms] ComboBox Add ítems - SelectedValue

 

Introducción


Suele suceder cuando se quiere representar información en ciertos controles como ser combos, o listas, que no se conozca cual es la mejor forma para trabajarlos obteniendo un acceso rápido a sus ítems.

Una claro caso es el uso de combo cuando se necesite agregar ítems de forma manual, y no se dispone de una lista u objetos de datos para bindearlo.

Como proceder en estos casos? bueno este articulo intentara mostrar como trabajar con este control, mostrando como bindearlo y como seleccionar un valor programáticamente.

 

Cargar ítems al ComboBox


El primer paso será la carga de los ítems al combo, en este caso serán valores manuales los que se dispones, estos no proceden de una consulta a una tabla, o de algún servicio para recuperarlos, sino que son ítems que uno quieres desplegar en el control.

La mejor forma para hacerlo es usando una clase, específicamente creada para este caso, podría ser algo como esto:

public class Item
{
    public string Name {get; set;}
    public int Value{get; set;}

    public Item(string name, int value)
    {
        Name = name; 
        Value = value;
    }
    public override string ToString()
    {
        return Name;
    }
}

Es una clase muy simple que solo tiene dos propiedades, las cuales recibirá en el constructor los valores que uno decida cuando cree cada instancia.

La carga de cada ítem será realizada en el Load del formulario:

private void Form1_Load(object sender, EventArgs e)
{
    List<Item> lista = new List<Item>();

    lista.Add(new Item("Blue", 1));
    lista.Add(new Item("Red", 2));
    lista.Add(new Item("Green", 10));
    lista.Add(new Item("Yellow", 5));

    comboBox1.DisplayMember = "Name";
    comboBox1.ValueMember = "Value";
    comboBox1.DataSource=lista;

    //
    // Se asigna el evento para control el cambio de seleccion
    //
    comboBox1.SelectedIndexChanged += new System.EventHandler(this.comboBox1_SelectedIndexChanged);

}

Como se visualiza la carga no respeta un orden especifico, ni sus valores de clave están en orden, esto es porque uno decide como se crea cada valor.

Lo que si es importante es que cada ítem no se agrega directo en el ComboBox, sino que se añade a la lista genérica definida del tipo de la clase creada anteriormente.

Al final se procede a bindear la lista al combo y se define los nombres de las propiedades que representan la visualización y el valor.

 

Asignar la selección de un ítem especifico


Un punto interesante es que será posible asignar directamente un valor al combo de forma programática y este seleccionara el ítem en la lista si es que esta presente.

private void btnSeleccionarValor_Click(object sender, EventArgs e)
{
    int valor = 0;
    if (!int.TryParse(txtValor.Text, out valor))
        return;

    comboBox1.SelectedValue = valor;
}

private void btnSeleccionarTexto_Click(object sender, EventArgs e)
{
    comboBox1.Text = txtTexto.Text;
}

private void comboBox1_SelectedIndexChanged(object sender, EventArgs e)
{
    Item seleccion = comboBox1.SelectedItem as Item;

    if (seleccion == null)
        return;

    MessageBox.Show(string.Format("Se ha seleccionado Color: {0} Valor: {1}", 
                                                seleccion.Name , 
                                                seleccion.Value ));

}

En el código se aprecia dos métodos de selección, ya sea por valor o texto, pero ambos trabajan de forma distintas.

En la selección por valor, si es asignado número que no en encuentra en la lista, el combo pasara a tomar el el SelectedIndex = –1, o sea no tendrá ningún valor activo, el SelectedItem pasara a devolver un null.

En cambio en la por Texto, si el valor no se encuentra en la lista dejara el que esta, no cambiara la selección.

 

[C#]
[VB.NET]

miércoles, 5 de mayo de 2010

Crystal Reports – Parameters – Como asignarlos desde código .net

 

Introducción


Un problema frecuente cuando se usa Crystal Reports es el pasaje de información por medio de parámetros al reporte.

El uso de parámetros es un aspecto importante para la creación de reportes, y suele suceder que no es tan intuitiva, ni directa la forma en como se debe programar esta funcionalidad.

 

Definición del parámetro en Crystal


En este ejemplo se usara el parámetro como filtro de los datos del reporte, por lo tanto se creara en el mismo momento en que se define la información.

Para esto se usara la opción “DataBase Expert”:

En el cuadro de dialogo que se despliega, se deber crear una nueva conexión a la base de datos Access usada en el reporte:

Haciendo uso en este cuadro, no directamente la tabla mostrada, sino la opción “Add Command”, la cual permitirá definir una consulta personalizada.

Es por eso que desplegara el cuadro:

 

Y se definirá un parámetro mediante la opción de la derecha.

Solo queda definir la consulta, haciendo uso del parámetro:

Seguramente cuando se acepte el dialogo, mostrara el mensaje pidiendo el ingreso de un valor al parámetro creado, se puede ingresar un valor cualquiera, o simplemente aceptar.

En cualquier momento la consulta escrita podrá ser modificada.

Luego de arrastrar los campos al diseñador del Crystal, podría visualizarse algo como lo siguiente:

En el “Field Explorer” se puede inspeccionar tanto los campos de la consulta, como los parámetros definidos para el reporte.

 

Pasaje de parámetros desde código


Al momento de ejecutar la aplicación será necesario pasarle al reporte desde el código el filtro que se ha definido, sino se hace seguramente el propio Crystal desplegara un cuadro pidiendo el ingreso de un valor.

En el siguiente código se muestra como llevar a cabo esta operación:

 

private void Form1_Load(object sender, EventArgs e)
{
          
		  //
          // Creo el parametro y asigno el nombre
          //
          ParameterField param = new ParameterField();
          param.ParameterFieldName = "CargoParam";

          //
          // creo el valor que se asignara al parametro
          //
          ParameterDiscreteValue discreteValue = new ParameterDiscreteValue();
          discreteValue.Value = "Developer";
          param.CurrentValues.Add(discreteValue);

          //
          // Asigno el paramametro a la coleccion
          //
          ParameterFields paramFiels = new ParameterFields();
          paramFiels.Add(param);

          //
          // Asigno la coleccion de parametros al Crystal Viewer
          //
          crystalReportViewer1.ParameterFieldInfo = paramFiels;

          //
          // Creo la instancia del reporte
          //
		  crListado report = new crListado();

          //
          // Cambio el path de la base de datos
          //
		  string rutadb = Path.Combine(Application.StartupPath, "TestDb.mdb");
		  report.DataSourceConnections[0].SetConnection("", rutadb, false);

          //
          // Asigno el reporte a visor
          //
		  crystalReportViewer1.ReportSource = report;

}

 

[C#]
[VB.NET]

martes, 4 de mayo de 2010

[Linq] DataSet – Agrupar y totalizar

 

Introducción


En este articulo analizaremos como trabajar con los datos procedentes de un datatable, realizando operaciones por medio de la ayuda de linq, y luego confeccionado un datatable distinto, cuya estructura de campos difiere de la original.

Para esta operación se contara con un datatable con documento de clientes, y un campo total que informa cuantos de estos se tienen registrados.

La idea es obtener en otro datatable la cantidad total agrupando todos los clientes.

 

Agrupar con Linq


Para esta operación se realizaría una agrupación mediante la funcionalidad que provee linq.

private void button1_Click(object sender, EventArgs e)
{
    IEnumerable<IGrouping<string, DataRow>> query = from item in Datos().AsEnumerable()
                                                    group item by item["Documento"].ToString() into g
                                                    select g;


    DataTable resultado = Transformar(query);

    dataGridView1.AutoGenerateColumns = true;
    dataGridView1.DataSource = resultado;

}

Como se observa en la query de linq se agrupa por medio del campo “Documento”, y el resultado de esta operación se alojo en “g”, valor que es devuelto en la consulta.

Si bien podría haberse igualado toda la consulta a una variable definida como “var”, esto hubiera imposibilitado el pasaje de la información a un método de transformación, para la creación del nuevo datatable, es por eso que se crea el IEnumerable<>, que contiene el IGrouping<> también genérico.

 

Creación del nuevo DataTable


En esta operación en una primera implementación es algo manual, ya que toma el resultado de la consulta linq e itera para cargar el nuevo datatable, y en su camino realiza las operaciones de calculo.

private DataTable Transformar(IEnumerable<IGrouping<string, DataRow>> datos)
{
    //
    // Se define la estructura del DataTable
    //
    DataTable dt = new DataTable();
    dt.Columns.Add("Documento");
    dt.Columns.Add("CantRegistros");
    dt.Columns.Add("Total");

    //
    // Se recorre cada valor agruparo por linq y se vuelca el resultado 
    // en un nuevo registro del datatable
    //
    foreach (IGrouping<string, DataRow> item in datos)
    {
        DataRow row2 = dt.NewRow();
        row2["Documento"] = item.Key;
        row2["CantRegistros"] = item.Count();
        row2["Total"] = item.Sum<DataRow>(x => Convert.ToInt32(x["Total"]));

        dt.Rows.Add(row2);
    }

    return dt;
}

Debe observarse como se trabaja con los métodos extendidos, y en ellos se aplica la funcionalidad Lambda, puntualmente al indica que campo debe sumarse.

 

[C#]
 

 

Creación del DataTable con CopyToDataTable()


Una alternativa algo mejor a la transformación de datos podrías ser por medio del uso del método de extensión CopyToDataTable()

DataTableExtensions.CopyToDataTable<(Of <(T>)>)

Hay que remarcar un punto con este método, ya que de forma estándar solo permite convertir a DataTable aquellas consulta linq que devuelvan como resultado una DataRow.

Pero en nuestro ejemplo planteado esto es diferente ya que el datatable a devolver requiere de una transformación de los registro con respecto a los datos originales.

Pero esta situación esta contemplada, ya que es posible implementar un método CopyToDataTable<> genérico, en donde a base de un tipo de dato se arme un datatable.

Para esta implementación, se hará uso de la explicación proporcionada por siguiente articulo:

Cómo implementar CopyToDataTable<T> donde el tipo genérico T no es un objeto DataRow

El código del link de msdn, se ha volcado al archivo de nombre “CopytoDataTable.cs”, se podrá observar en el código de ejemplo adjunto en el articulo.

La implementación de la funcionalidad quedo reducida al siguiente código:

private void button1_Click(object sender, EventArgs e)
{

    var query = from item in Datos().AsEnumerable()
                group item by item["Documento"].ToString() into g
                select new DocumentoResult
                {
                    Documento = g.Key,
                    CantRegistros = g.Count(),
                    Total = g.Sum(x => Convert.ToInt32(x["Total"]))
                };


    DataTable resultado = query.CopyToDataTable<DocumentoResult>();

    dataGridView1.AutoGenerateColumns = true;
    dataGridView1.DataSource = resultado;

}

Esta nueva alternativa ya no necesita iterar por cada registro que dio como resultado la consulta linq, es mas se puede hacer uso del “var”, puesto que no es necesario pasar por parámetro la consulta para trabajarla.

Pero si se necesito de un pequeño cambio, en donde se ha definido una clase, de nombre “DocumentoResult”, la cual define los campos del nuevo DataTable.

public class DocumentoResult
{
    public string Documento { get; set; }
    public int CantRegistros { get; set; }
    public int Total { get; set; }

}

Siendo esta la clase usada en la definición del método genérico CopyToDataTable<>, cuando se usa en la línea:

DataTable resultado = query.CopyToDataTable<DocumentoResult>();

 

[C#]
 

domingo, 2 de mayo de 2010

C# - [Winforms] UserControl - Focus()

 

Introducción

Suele suceder que la implementación de cierta funcionalidad cuando se desarrollan componentes no sea tan obvia como uno se imagina.

Este es el caso de la implementación del método Focus() en un User Control.

User Control - Método Focus

La clase base de la cual hereda el componente es UserControl, y este método en particular no esta marcado para poder realizar un override, es por eso que hay que redefinirlo mediante el uso del modificador new

public new bool Focus()
{
    //
    // Es necesario activar el control para que tome el foco
    //
    this.Select();
    return textBox1.Focus();
}

Un detalle a remarcar en el código es el uso del método Selected(), el cual activa el User Control y permite así que un control contenido en este tome el foco.

 

[C#]
 

C# - [DataGridView] DataGridViewComboBoxColumn – Variar contenido del combobox respecto a la fila

 

Introducción

El objetivo del articulo es demostrar algunos problemas que pueden surgir cuando se necesita que el ComboBox ubicado en las celdas del DataGridView, varíe su contenido de acuerdo al registro que se visualiza.

Para demostrar el problema, se ha definido un dominio del ejemplo intentara cargar una grilla de sucursales, de los cuales mostraran el listado de los teléfonos disponibles.

Por supuesto para cada sucursal los teléfonos disponibles varían, por lo tanto definir el contenido del combo como una única lista no es posible.

Carga del contenido variable en ComboBox

En el evento Load del formulario se procede a la carga de las sucursales, e inmediatamente después de esta operación se invoca a un método creado con el fin de la carga de los combos de cada registro de a grilla.

Este método recorre en un ciclo cada registro, toma el id de la sucursal que se encuentra en la celda del registro actual, y también toma la celda que representa el combo.

Con las anteriores dos selecciones procede a realizar una consulta a los datos y cargar en el combo los teléfonos de esa sucursal.

 

private void frmSucursales_Load(object sender, EventArgs e)
{
    SucursalesEntities sucursalesContext = new SucursalesEntities();

    dgvScursales.AutoGenerateColumns=false;
    dgvScursales.DataSource = sucursalesContext.Sucursales;

    //
    // Luego de bindear la grilla, cargo el contenido de cada fila
    // en el combo de telefonos para cada sucursal
    //
    LoadGrillaComboBoxTelefonos();

}

private void LoadGrillaComboBoxTelefonos()
{
    foreach (DataGridViewRow row in dgvScursales.Rows)
    {
        int idSucursal = Convert.ToInt32(row.Cells["IdSucursal"].Value);

        DataGridViewComboBoxCell comboboxCell = row.Cells["Telefonos"] as DataGridViewComboBoxCell;
        
        SucursalesEntities sucursalesContext = new SucursalesEntities();

        comboboxCell.DataSource = sucursalesContext.Telefonos.Where(x => x.IdSucursal == idSucursal);
        comboboxCell.ValueMember = "Id";
        comboboxCell.DisplayMember = "Numero";

    }
}

Algunos Comentarios

Durante la realización del ejemplo se realizaron varias pruebas, con algunos eventos que se adaptaran a la situación y evitaran tener que recorrer en un “foreach” cada registro del DataGridView.

Después de investigar un poco los únicos dos eventos que se adaptarían a este caso serian el RowsAdded y el CellFormatting, pero ambos trajeron problemas, lo que impidió su uso.

El CellFormatting, tiene como problema que se ejecuta contantemente, ya que es lanzado al redibujar la celda, y como esta operación requiere la consulta a los datos, generaba un efecto de bloqueo en la pantalla, el cual no es deseado.

El RowsAdded, si bien se ejecuta por cada fila agregada en la operación de bindeo de los datos, no había forma interna en el evento de detectar que fila se estaba agregando, ya que el argumento del evento e.RowIndex o e.RowCount, no devuelven un valor correcto.

Como conclusión se dejo de lado los eventos y se hizo uso del “foreach”, implementado esto en un método, cuya utilización se realiza luego que se bindearon los datos en el control.

 

[C#]
[VB.NET]

[Crystal Reports] Exportar DataGridView a Crystal

 

Introducción


Comúnmente cuando uno confecciona un reporte la información que se usara proviene de una tabla o consulta a una base de datos, pero suele suceder que a veces se necesite información de orígenes distintos, como ser el caso de controles del propio formulario, donde el usuario cargado información.

Este el caso planteado en el articulo, como generar un reporte en Crystal Reprots, pero utilizando información que no se encuentra en una base de datos, sino que esta aun contenida en los controles del formulario, no hay una tabla a la cual se puede vincular el reporte.

Para resolver este problema se hará uso de DataSet Tipados, los cuales no solo brindaran el esquema necesario para definir la estructura de datos que usara el reporte, sino que además será contenedor de los datos que luego se asignaran como origen de datos.

 

Información Adicional


Como referencia adicional podría mencionar dos excelente artículos que han sido de mucha ayuda, los cuales detallan muy bien como hacer uso de los reportes vinculados a DataSet Tipados, pero en este caso si toman la información mediante una consulta a la db.

Informes Crystal Reports

Creación de Reportes con Crystal Reports en Visual Studio 2005/2008

 

Creación del reporte


Uno de los primeros paso consiste en la creación del reporte, para el mismo es necesario definir los datos o mejor dicho el esquema de los mismos, tarea que será responsabilidad del DataSet Tipado.

Es por ello que en el ejemplo podrán ver un archivo de nombre dtCompra.xsd, este es precisamente el DataSet que se usara en el reporte y definirá la estructura de datos del mismo.

crystal4

En este se han creado manualmente dos DataTable que representan los datos del cliente y los ítems que ha seleccionado en la compra.

Ambas tablas se han creado de forma manual, arrastrando los datatable de la toolbox de la izquierda, y luego agregando sus columnas. Al no existir base de dato alguna, es necesario crear estas de forma manual, no se tiene ayuda del “Solution Explorer” para arrastrar tablas y crearlas dinámicamente, justamente porque no existen.

Una vez creado el esquema de los datos se procede con el reporte, para ello se agrega uno nuevo en el proyecto, en este caso con el nombre de Factura.rpt, y se configuran los datos mediante la opciones del “Field Explorer”, agregando el origen de datos, como muestra la imagen:

crystal1

Del cuadro de dialogo siguiente se selecciona el DataSet creado en el paso previo:

crystal2

Por ultimo, el asistente de Crystal permitirá redefinir como se vinculan las tablas, en este caso por representar información a distinto nivel no existe una relación, pero si se desarrollara algún reporte del estilo maestro-detalles, podría existir un vinculo entre las tablas.

crystal3

Una vez concluidos los pasos para configurar el esquema de datos del reporte, arrastrando los campos al reporte, podría armar un reporte similar a este:

crystal5

 

Carga de los datos en el DataSet Tipado


El siguiente paso consiste en volcar la información ingresada por el usuario en los controles al dataset tipado, creando en dtComrpas.xsd

Para esto se codifico un método responsable de esta tarea:

private dtCompra GenerarFactura()
{
    dtCompra facturacion = new dtCompra();

    //
    // Agrego el registro con la info del cliente
    //
    dtCompra.DatosClienteRow rowDatosCliente = facturacion.DatosCliente.NewDatosClienteRow();
    rowDatosCliente.Nombre = txtNombre.Text;
    rowDatosCliente.Direccion = txtDireccion.Text;
    rowDatosCliente.Telefono = txtTelefono.Text;
    rowDatosCliente.DNI = txtDni.Text;

    facturacion.DatosCliente.AddDatosClienteRow(rowDatosCliente);

    //
    // Itero por cada fila del DataGridView creando el registro 
    // en el DataTabla 
    //
    foreach (DataGridViewRow row in dgvCompras.Rows)
    {
        dtCompra.ComprasRow rowCompra = facturacion.Compras.NewComprasRow();  
        rowCompra.Descripcion = Convert.ToString(row.Cells["Descripcion"].Value);
        rowCompra.PrecioUnitario = Convert.ToInt32(row.Cells["PrecioUnitario"].Value);
        rowCompra.Cantidad = Convert.ToInt32(row.Cells["Cantidad"].Value);

        facturacion.Compras.AddComprasRow(rowCompra);
    }


    return facturacion;
}

Este método es fundamental ya que será el encargado de cargar los datos en una instancia del dataset tipado, para ello tomara el contenido de los controles y los agregara como registros en el DataTable que corresponda.

Es de apreciar como opera con el DataGridView, recorriendo cada fila del mismo, tomando los valores de sus celdas y creando nuevos registros en el datatable que representa las Compras.

 

Visualización del Reporte


Una vez que tenemos los datos cargados en una instancia del DataSet, solo queda asignarlos como origen de datos del reporte.

Para esta operación el reporte será mostrador en un formulario especialmente creado para tal fin.

Pero hay un detalle, los datos han sido creado en un formulario distinto, es por eso usa la técnica por medio del constructor para pasar la información de un formulario a otro. Esto podrá visualizarse en el formulario frmVisorFactura.

public partial class frmVisorFactura : Form
{
    dtCompra _datosreporte;

    private frmVisorFactura()
    {
        InitializeComponent();
    }

    public frmVisorFactura(dtCompra datos): this()
    {
        _datosreporte = datos;
    }

    private void frmVisorFactura_Load(object sender, EventArgs e)
    {
        Factura _factura = new Factura();
        _factura.SetDataSource(_datosreporte);

        crwFactura.ReportSource = _factura;
    }
}

Allí se ha agregado un constructor que acepta como parámetro el dataset que requiere el reporte para visualizarse.

También puede verse como en el evento Load se hace uso de los datos pasados al formulario y como son asignados al DataSource de la instancia del reporte.

Desde el frmCompra (donde el Usuario de la aplicación ingresa los datos), solo basta con invocar al formulario de la siguiente manera:

private void btnReporte_Click(object sender, EventArgs e)
{
    dtCompra datos = GenerarFactura();

    frmVisorFactura frm = new frmVisorFactura(datos);
    frm.Show();
}

En al primer línea se invoca al método que carga la estructura de datos, y en las siguiente se crea la instancia del formulario que contienen el control Crystal Report Viewer, al cual se le pasa en el constructor la información a visualizar.

Hay que remarcar en este punto el porque de todo esta lógica en al comunicación de los formularios. Resulta que uno de ellos es el que contiene la información y el otro es el que permite la visualización, la idea es mantener bien clara la separación de responsabilidades de cada uno, y no tener que acceder desde un formulario a los controles del otro, eso no es correcto, pero si se usa un medio que permita la comunicación, como ser en este caso el dataset tipado ,se puede trabajar en cada formulario de forma transparente y prolija.

 

[C#]
[VB.NET]

sábado, 1 de mayo de 2010

Login – Usando Password con Hash

 


Introducción


Este artículo intentara plantear la forma en que se puede crear un login que pueda ser usado tanto para un entorno Web, como para uno WinForms.

Se vera como mediante la definición de funcionalidad en una capa de Servicio el código de autenticación puede ser reutilizado, en realidad este nombre que le he dado a la capa podría llevar cualquier otro nombre, podrías ser directo la capa de Datos, o un Repository, la idea es mantener la técnica que permita reutilizar la funcionalidad.

Algo particular que encontraran en el ejemplo esta referido al password no se guarda de forma plana en la db, sino que por motivos de seguridad se aplica una función de hash.

La misma tiene una particularidad interesante, ya que el algoritmo solo se puede aplicar en un solo sentido, siempre obteniendo el hash de un texto o mensaje, pero nunca de un valor hash obtener el mensaje original.

Al aplicar el algoritmo hash a un mensaje, el resultado será siempre el mismo, por lo tanto esta técnica utilizada en el login permite que el password este siempre seguro. En el ejemplo verán como en el momento de login se aplica el algoritmo al la entrada del usuario y este valor se busca como password en la tabla, al asegurarse que un mismo texto produce el mismo resultado de hash, si se ingreso correctamente usuario y password este debe coincidí con el presente en la base de datos.


Definición de la capa de Servicio



El primer paso que explicaremos en el articulo es el código común que será reutilizado tanto por la capa web como winform, se trata de la denominada capa de servicio.

La misma básicamente se compone de una fachada estática, con métodos para autenticar o insertar un usuario, por supuesto la funcionalidad adicional para actualizar no se incluyo en el ejemplo, pero podrías agregarse si fuera necesario.

public static bool Autenticar(string usuario, string password)
{
    string sql = @"SELECT COUNT(*)
                      FROM Usuarios
                      WHERE NombreLogin = @nombre AND Password = @password";


    using (SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["default"].ToString()))
    {
        conn.Open();

        SqlCommand command = new SqlCommand(sql, conn);
        command.Parameters.AddWithValue("@nombre", usuario);

        string hash = Helper.EncodePassword(string.Concat(usuario, password));
        command.Parameters.AddWithValue("@password", hash);

        int count = Convert.ToInt32(command.ExecuteScalar());

        if (count == 0)
            return false;
        else
            return true;

    }
}
public static UsuarioEntity Insert(string nombre, string apellido, string nombreLogin, string password)
{
    UsuarioEntity usuario = new UsuarioEntity();

    usuario.Nombre = nombre;
    usuario.Apellido = apellido;
    usuario.NombreLogin = nombreLogin;
    usuario.Password = password;

    return Insert(usuario);
}

public static UsuarioEntity Insert(UsuarioEntity usuario)
{

    string sql = @"INSERT INTO Usuarios (
                           Nombre
                          ,Apellido
                          ,NombreLogin
                          ,Password)
                      VALUES (
                            @Nombre, 
                            @Apellido, 
                            @NombreLogin,
                            @Password)
                    SELECT SCOPE_IDENTITY()";


    using (SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["default"].ToString()))
    {

        SqlCommand command = new SqlCommand(sql, conn);
        command.Parameters.AddWithValue("Nombre", usuario.Nombre);
        command.Parameters.AddWithValue("Apellido", usuario.Apellido);
        command.Parameters.AddWithValue("NombreLogin", usuario.NombreLogin);

        string password = Helper.EncodePassword(string.Concat(usuario.NombreLogin, usuario.Password));
        command.Parameters.AddWithValue("Password", password);

        conn.Open();

        usuario.Id = Convert.ToInt32(command.ExecuteScalar());

        return usuario;
    }
}

El método importante en estos dos métodos es el siguiente:

string password = Helper.EncodePassword(string.Concat(usuario.NombreLogin, usuario.Password));

Esta línea es la que arma el password real que se salvara en el campo de la tabla, como se observa para una seguridad aun mayor se une el nombre del usuario y el password, pasándolos luego por la función que aplica el hash.

internal class Helper
{
    public static string EncodePassword(string originalPassword)
    {
        SHA1 sha1 = new SHA1CryptoServiceProvider();

        byte[] inputBytes = (new UnicodeEncoding()).GetBytes(originalPassword);
        byte[] hash = sha1.ComputeHash(inputBytes);

        return Convert.ToBase64String(hash);
    }
}

En el método Autenticar() como se había comentado no se obtiene el password que originalmente el usuario tienen, sino que se aplica el hash sobre los valores ingresados al momento de realizar el login, ejecutando el query con esta información como filtro.

Si el usuario y password ingresados en la autenticación son los correctos al aplicar el hash retornara la misma cadena que se tiene en la tabla, por lo tanto al ejecutar el query debería devolver el registro del usuario.


Autenticación desde una aplicación Winform



En el método Main() del archivo Program.cs se encontrara la llamada a el formulario que será responsable de al autenticación.

static class Program
{
    /// <summary>
    /// The main entry point for the application.
    /// </summary>
    [STAThread]
    static void Main()
    {
        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);
        
        frmLogin frm = new frmLogin();
        frm.ShowDialog();

        if(frm.DialogResult == DialogResult.OK)
            Application.Run(new frmPrincipal());

    }
}

El formulario de login (frmLogin.cs), tendrá un código muy simple, solo tomara el usuario y password invocara al servicio y según el resultado retornara la respuesta al método Main() para que se abra o no el formulario principal.

private void btnAceptar_Click(object sender, EventArgs e)
{
    string nombre = txtNombre.Text;
    string password = txtPassword.Text;

    if (LoginService.Autenticar(nombre, password))
        this.DialogResult = DialogResult.OK;
    else
        this.DialogResult = DialogResult.Abort;

}

private void btnCancelar_Click(object sender, EventArgs e)
{
    this.DialogResult = DialogResult.Cancel;
}

Adicionalmente se agrego un formulario para la creación de nuevos usuarios, si bien en este no se ha programado la edición completa del usuario, o sea toda la funcionalidad de ABM, el Insert permitirá analizar como se registra un nuevo usuario y se guarda su password.

private void btnAceptar_Click(object sender, EventArgs e)
{
    UsuarioEntity usuario = new UsuarioEntity();

    usuario.Nombre = txtUsuario.Text;
    usuario.Apellido = txtApellido.Text;
    usuario.NombreLogin= txtNombreLogin.Text;
    usuario.Password = txtPassword.Text;

    usuario = LoginService.Insert(usuario);

    MessageBox.Show(string.Format("Se ha creado el usuario, ID: {0}", usuario.Id));
}

Nuevamente gracias a la funcionalidad encapsulada del Servicio, el código del formulario es muy simple.

 


Autenticación desde una aplicación Web



La autenticación en un entorno web es algo distinta a una aplicación Windows, en este caso se ha configurado en el web.config, las líneas necesarias para que todo el sitio este bajo una autenticación por medio de “Forms”

<authentication mode="Forms">
	<forms name="appNameAuth" path="/" loginUrl="frmLogin.aspx" defaultUrl="Default.aspx" protection="All" />
</authentication>
  
  <authorization>
    <deny users="?" />
  </authorization>

En el archivo de configuración del sitio se especifica cual será la pagina por defecto y cual la de login, de esta forma si no hay usuario autenticado, solo el sitio redirección a estas url.
El formulario web frmLogin.aspx tiene un código muy simple:

protected void ProcessLogin(object sender, EventArgs e)
{
    if (LoginService.Autenticar(txtUser.Text, txtPassword.Text))
    {
        FormsAuthentication.RedirectFromLoginPage(txtUser.Text, chkPersistLogin.Checked);
    }
    else
        ErrorMessage.InnerHtml = "<b>Usuario o contraseña incorrectos...</b> por favor re-ingrese las credenciales...";
}

Como se observa luego de la autenticación se hace uso de los métodos provistos por .net en el namespace System.Web.Security, para trabajar con al seguridad.
Aquí se esta indicando el nombre del usuario que paso la validación, y esto hace que solo el sitio se redireccione a la pagina marcada por defecto en el web.config.

En la pagina principal del sitio se ha agregado algo de código para mostrar el nombre del usuario autenticado, y dar la posibilidad de un logout.

protected void Page_Load(object sender, EventArgs e)
{
    Label1.Text = string.Format("Bienvenido al Sistema: {0}", Thread.CurrentPrincipal.Identity.Name); 
}

protected void Menu1_MenuItemClick(object sender, MenuEventArgs e)
{
    if (Menu1.SelectedValue == "Salir")
    {
        FormsAuthentication.SignOut();
        FormsAuthentication.RedirectToLoginPage();
    }
}

También se ha creado una pagina para crear nuevos usuarios (Usuarios.aspx)

protected void btnAceptar_Click(object sender, EventArgs e)
{
    UsuarioEntity usuario = new UsuarioEntity();

    usuario.Nombre = txtNombre.Text;
    usuario.Apellido = txtApellido.Text;
    usuario.NombreLogin = txtLogin.Text;
    usuario.Password = txtPassword.Text;

    usuario = LoginService.Insert(usuario);

    ClearControls();
    lblMessage.InnerHtml = string.Format("Se ha creado el usuario, ID: {0}", usuario.Id);

}

protected void btnCancelar_Click(object sender, EventArgs e)
{
    Response.Redirect("~/Default.aspx");
}

private void ClearControls()
{
    txtNombre.Text="";
    txtApellido.Text="";
    txtLogin.Text="";
    txtPassword.Text="";
}

Código de ejemplo


Como requerimiento será necesario contar con al menos Sql Server Express 2008, para que pueda ejecutarse la aplicación.

La tabla de usuario ya posee un registro para ingresar la primera vez, la información es la siguiente:
Usuario: admin
Password: pass123

Para no tener dos base de datos iguales, se agrego un Build Event en el proyecto web para que copie la db desde el proyecto de Class Library a la carpeta App_Data del sitio Web.

Puede que a veces si se compila repetida veces la compilación lance un error al intentar copiar la db a la carpeta App_Data, cuya operación no pueda hacerlo porque el servicio de sql server la tenga loqueada, en ese caso simplemente ejecuten a pesar de este mensaje de error, ya que funcionara sin problemas.

[C#] 
[VB.NET]