lunes, 25 de noviembre de 2013

Obtener perfiles en un Gridivew

Saber quienes forman parte de nuestra aplicación es sencial, principalmente cuando deseamos hacer una "auditoria" de nuestro sistema. Cuando utilizamos el membership provider de asp.net podemos obtener esos usuarios en una lista para su identificación. Así que, veremos como podemos obtener esos usuarios en un gridivew. Algo tan sencillo como 2 líneas de código. Primeramente creamos una página asp.net y agregamos un gridview,


Ahora lo que a muchos les apasiciona el código, veamos como podemos llenar nuestro gridview. En el vento LOAD de nuestra página agregamos:

gvprofiles.DataSource = ProfileManager.GetAllProfiles(ProfileAuthenticationOption.Authenticated)
gvprofiles.DataBind();

Validar Textbox aceptar solo letras

Últimamente he recibido varias consultas via email sobre como hacer para validar un textbox para aceptar solo letras, así que, aquí les dejo el ejemplo Nuestro primer paso será crear la función JavaScript que hará el trabajo, para ello agregamos el javascript

function lettersOnly(evt) {
evt = (evt) ? evt : event;
var charCode = (evt.charCode) ? evt.charCode : ((evt.keyCode) ? evt.keyCode :
((evt.which) ? evt.which : 0));
if (charCode > 31 && (charCode < 65 || charCode > 90) &&
(charCode < 97 || charCode > 122)) {
alert("Ingrese solo letras.");
return false;
}
return true;
}



Por último iremos al code behind de la página y en el evento Load agregaremos un nuevo evento al textbox para que reconozca el script.


protected void Page_Load(object sender, EventArgs e)
{
txtnumeric.Attributes.Add("onkeypress", "javascript:return lettersOnly(event);");
}


Con esto, tenemos el textbox validado para aceptar solo letras.

Ejemplo del Código para descarga

Validar Textbox para aceptar solo números

Una de las necesidades más habituales en el desarrollo es poder validar los controles Textbox para que solo acepten valore numéricos. En este post eso es lo que haremos, nos apoyaremos en el lenguaje javascript para validar nuestro textbox del lado del cliente. Nuestro primer paso será crear la función JavaScript que hará el trabajo, para ello agregamos las etiquetas de javascript





Posteriormente dentro de esas etiquetas agregaremos el script que hará el trabajo

function ValidNum(e) {
var tecla= document.all ? tecla = e.keyCode : tecla = e.which;
return ((tecla > 47 && tecla < 58) || tecla == 46);
}


Por último iremos al code behind de la página y en el evento Load agregaremos un nuevo evento al textbox para que reconozca el script.

protected void Page_Load(object sender, EventArgs e)
{
TextBox1.Attributes.Add("onkeypress", "javascript:return ValidNum(event);");
}


Con esto, tenemos el textbox validado para aceptar solo números y el punto.

Ejemplo del Código para descarga

Encriptar QueryString en URL

Luego de varios días de trabajo pesado, nuevamente tengo tiempo para publicar en mi blog.

Para este post trataré de dar respuesta a una pregunta que se genera constantemente en los foros de MSDN, como encryptar los valores enviados a través del queryString en una URL.

Primero que nada he de decir que métodos de encriptación hay muchos, desde los personales hasta el MD5 y todos los conocidos. En nuestro ejemplo, trabajaremos con un código de encriptación utilizando el DESCryptoServiceProvider de asp.net más el método Left que se encuentra en la clase que publique en un post anterior

Funciones Con String

Para iniciar, debo comentar que para nuestra clase de encriptación crearemos dos métodos uno para encriptar y otro para desencriptar. Algunos se preguntaran para que el método de desencriptar. Pues sencillo, si estamos enviando valores por medio del QueryString en una URL, es porque esos valores los utilizaremos para realizar algún tipo de operación, entonces, que hacemos con unos valores encriptados? pues complicaríamos el trabajo un poco mas, así que, nuestros objetivos serán:

1. Encriptar los datos

2. Enviarlos encriptados por queryString

3.Recibirlos y desencriptarlos

4. Procesarlos.

Para empezar crearemos una nueva clase llamada Encryption, que contendrá nuestro métodos

Seguidamente, declararemos dos variables de Tipo byte que contendrán los datos que encriptemos y una de tipo string que contendrá los caracteres de encriptación

   
static byte[] key = { };
static byte[] IV = { 0x12, 0x34, 0x56, 0x78, 0x90, 0xab, 0xcd, 0xef };
static string sEncryptionKey = "!#$a54?3"



Ahora, agregamos un nuevo método llamado Decrypt que será el encargado de desencriptar los datos enviados


public string Decrypt(string stringToDecrypt)
{
byte[] inputByteArray = new byte[stringToDecrypt.Length + 1];
try
{
key = System.Text.Encoding.UTF8.GetBytes(LeftRightMid.Left(sEncryptionKey, 8));
DESCryptoServiceProvider des = new DESCryptoServiceProvider();
inputByteArray = Convert.FromBase64String(stringToDecrypt);
MemoryStream ms = new MemoryStream();
CryptoStream cs = new CryptoStream(ms, des.CreateDecryptor(key, IV), CryptoStreamMode.Write);
cs.Write(inputByteArray, 0, inputByteArray.Length);
cs.FlushFinalBlock();
System.Text.Encoding encoding = System.Text.Encoding.UTF8;
return encoding.GetString(ms.ToArray());
}
catch (Exception e)
{
return e.Message;
}
}


Y por ultimo creamos el método mas importante, el de encriptación



public string Encrypt(string stringToEncrypt)
{
try
{
key = System.Text.Encoding.UTF8.GetBytes(LeftRightMid.Left(sEncryptionKey, 8));
DESCryptoServiceProvider des = new DESCryptoServiceProvider();
byte[] inputByteArray = Encoding.UTF8.GetBytes(stringToEncrypt);
MemoryStream ms = new MemoryStream();
CryptoStream cs = new CryptoStream(ms, des.CreateEncryptor(key, IV), CryptoStreamMode.Write);
cs.Write(inputByteArray, 0, inputByteArray.Length);
cs.FlushFinalBlock();
return Convert.ToBase64String(ms.ToArray());
}
catch (Exception e)
{
return e.Message;
}
}


Como vemos, el proceso de encriptación puede ser algo sencillo que realmente puede ayudar mucho a la seguridad de un sitio, especialmente cuando los datos que enviamos son de extremo cuidado.

Como siempre, para terminar el código de ejemplo

miércoles, 30 de octubre de 2013

Colorear celdas y filas en un Gridview

Últimamente he visto muchas consultas sobre como colorear las celdas o filas de un gridview basados en un determinado valor o condición.

Así que el objetivo de este post es mostrar como realizar este procedimiento, pero además añadiremos un elemento mas, que es como mostrar una imagen dependiendo de una condición.
Empezaremos trabajando en como cambiar el color de una fila del Gridview:

Supongamos que tenemos un Gridview que tiene un campo de tipo Literal en un TemplateField que muestra un total o cantidad y queremos con base a ese total colorear la fila completa.

    /**
     * SyntaxHighlighter
     */
    
         
            
            
    
Para lograr que nuestras celdas puedan cambiar de Color, utilizaremos el Evento OnRowDataBound el cual se ejecuta por cada celda del GridView , por tanto, dicho evento debe estar asociado a nuestro Gridview de lo contrario no se ejecutará
    /**
     * SyntaxHighlighter
     */
  
Una vez asociado el evento y creado el ItemTeplate, es hora de programar el evento RowDataBound desde el CodeBehind
    /**
     * SyntaxHighlighter
     */
    protected void GridView1_RowDataBound(object sender, GridViewRowEventArgs e)
    {
       if (e.Row.RowType == DataControlRowType.DataRow)
        {
          Literal tot =(Literal)e.Row.FindControl("ltltotal");
         Double total = Double.Parse(tot.Text);
         if (total == 500)
           {
             e.Row.BackColor = Color.FromName("#c6efce");
             //e.Row.Cells[2].BackColor = Color.FromName("#c6efce");
           }

        if (total >= 600)
           {
              e.Row.BackColor = Color.FromName("#ffeb9c");
           }

        if (total <300 e.row.backcolor="Color.FromName(" ffc7ce="" pre="">

Si examinamos el código anterior, vemos dos cosas importantes:

1. Accedemos al control en el GridView mediante el FindControl

2. Convertir,os ese objeto a double para poder hacer una comparación numérica.

El color a la fila (row) lo asignamos mediante la propiedad BackColor, si observamos con detenimiento, a esa propiedad le estamos asignando la propiedad Color.FromName(“numero de color”).
Por supuesto que esta no es la única forma de asignar el color, también podríamos hacerlo utilizando Color.NombreColor.
Una vez completado esto, nuestro gridview se vería de la siguiente forma
 
 
Ahora, nuestro segundo trabajo será hacer que el gridView también se coloreo pero esta vez no toda la fila sino únicamente las celdas. De igual forma, requerimos el evento RowDataBound, solo que esta vez la forma de asignar el color varia, pues se hace directamente sobre la celda.
    /**
     * SyntaxHighlighter
     */
  protected void GridView2_RowDataBound(object sender, GridViewRowEventArgs e)
   {
     if (e.Row.RowType == DataControlRowType.DataRow)
      {
         Literal tot = (Literal)e.Row.FindControl("ltltotal");
         Double total = Double.Parse(tot.Text);
         if (total == 500)
          {
             e.Row.Cells[3].BackColor = Color.FromName("#c6efce");
          }

        if (total >= 600)
         {
            e.Row.Cells[3].BackColor = Color.FromName("#ffeb9c");        
          }

       if (total < 300)
         {
            e.Row.Cells[3].BackColor = Color.FromName("#ffc7ce");      
         }
       }
   }
Al concluir nuestro GridView se vería así
Para terminar, haremos que nuestro gridview muestre una imagen distinta dependiendo de una condición. Esta vez haremos unos pequeños cambios: 1. Comparamos con base al valor aprobado, es decir un valor True/False para mostrar la imagen 2. Utilizamos la propiedad DataKeyNames del Gridview pues guardar el valor True/False, ya que la columna Aprobado no será mostrada sino únicamente la imagen. 3. Utilizaremos en control Image dentro de un ItemTemplate para mostrar la imagen nueva.
    /**
     * SyntaxHighlighter
     */
    
    /**
     * SyntaxHighlighter
     */
    
         
         
        
    
El evento rowDataBound
    /**
     * SyntaxHighlighter
     */
 protected void GridView3_RowDataBound(object sender, GridViewRowEventArgs e)
  {
    if (e.Row.RowType == DataControlRowType.DataRow)
      {  
         string KeyID = GridView3.DataKeys[e.Row.RowIndex].Value.ToString();
        System.Web.UI.WebControls.Image imagen = (System.Web.UI.WebControls.Image)e.Row.FindControl("imgestado");
        if (KeyID == "True")
          {
             imagen.ImageUrl = "GreenCircle5.jpg";       
          }

        if (KeyID == "False")
         {
            imagen.ImageUrl = "red-24.png";
          }  
       }
   }
En el código anterior lo mas importante, es la forma en que el valor del DataKeysNames es tomado, vemos que lo realizamos mediante el e.Row.RowIndex y lo convertimos a string para luego poder evaluarlo.

Por último les dejo el código de ejemplo
 

JqGrid y Asp.net, Maestro detalle con SubGrid (Parte 2)

Desde que escribí mi primer post sobre como utilizar JqGrid con asp.net, he recibido varias consultas sobre como realizar un maestro detalle utilizando este control de jquery.


Antes de comenzar debo mencionar que para mostrar un maestro detalle con JqGrid tenemos dos opciones:


1. Realizarlo con un subgrid

2. Utilizar dos JqGrid separados


Así que, teniendo esto en cuenta, el objetivo de este post será realizar el maestro detalle utilizando el Subgrid, que a mi parecer es mucho más agradable y por supuesto mas ordenado. Posteriormente en otro post veremos como hacerlo con 2 JqGrid separados.


Para este ejemplo, no voy a profundizar mucho en como esta formado JqGrid ni como llenar los datos, pues eso ya lo vimos en el anterior post y aplica exactamente de la misma forma para el grid padre como para el grid hijo, basicamente sería cambiar el nombre de la tabla y crea un nuevo stored procedure.


En lo que si nos vamos a enfocar es en como realizar la definición del subgrid.


Si recordamos un poco como esta conformado el JqGrid, veremos que al final de la definición del mismo este nos permite configurar las opciones adicionales que tendrá el control, como por ejemplo:

width: "850",
height: "100%


Es precisamente en esta serie de opciones donde tendremos que habilitar nuestro grid para que contenga un subgrid, esto lo hacemos agregando la opción:

subGrid: true

Luego de que hemos habilitado nuestro subgrid debemos crear la función subGridRowExpanded que será la encargada de mostrar nuestro subgrid cuando expandamos el grid padre, es precisamente dentro de esta función donde declararemos nuestro subgrid.

    /**
     * SyntaxHighlighter
     */
subGridRowExpanded: function (subgrid_id, row_id) {
         // we pass two parameters 
         // subgrid_id is a id of the div tag created whitin a table data 
         // the id of this elemenet is a combination of the "sg_" + id of the row 
         // the row_id is the id of the row 
         // If we wan to pass additinal parameters to the url we can use 
         // a method getRowData(row_id) - which returns associative array in type name-value 
         // here we can easy construct the flowing 
         var subgrid_table_id, pager_id;
         subgrid_table_id = subgrid_id + "_t";
         pager_id = "p_" + subgrid_table_id;
       $("#" + subgrid_id).html("
"); jQuery("#" + subgrid_table_id).jqGrid({ //este es mi subgrid datatype: function () { $.ajax( { url: "webservices/LoadChild.asmx/GetChilds", //PageMethod data: "{'pPageSize':'" + $("#" + subgrid_table_id).getGridParam("rowNum") + "','pCurrentPage':'" + $("#" + subgrid_table_id).getGridParam("page") + "','pSortColumn':'" + $("#" + subgrid_table_id).getGridParam("sortname") + "','id':'" + $("#" + subgrid_table_id).getGridParam("ajaxGridOptions") + "','pSortOrder':'" + $("#" + subgrid_table_id).getGridParam("sortorder") + "'}", //PageMethod Parametros de entrada dataType: "json", type: "post", contentType: "application/json; charset=utf-8", complete: function (jsondata, stat) { if (stat == "success") jQuery("#" + subgrid_table_id)[0].addJSONData(JSON.parse(jsondata.responseText).d); else alert(JSON.parse(jsondata.responseText).Message); } }); }, jsonReader: //Set the jsonReader to the JQGridJSonResponse squema to bind the data. { root: "Items", page: "CurrentPage", total: "PageCount", records: "RecordCount", repeatitems: true, cell: "Row", id: "ID" //index of the column with the PK in it }, colModel: [ { name: 'ChildId', index: 'ChildId', width: 40, align: 'left', editable: false, editrules: { edithidden: true }, hidden: true }, { name: 'ChildName', index: 'ChildName', width: 80, align: 'left', editable: true, edittype: 'text' }, { name: 'ChildLastName', index: 'ChildLastName', width: 200, align: 'left', editable: true, edittype: 'text', editoptions: { size: 20, maxlength: 30} }, { name: 'ChildBirthDate', index: 'ChildBirthDate', width: 300, align: 'left', editable: true, edittype: 'select', editoptions: { size: 20, maxlength: 30} }, { name: 'PersonId', index: 'PersonId', width: 200, align: 'left', editable: true, edittype: 'text', editoptions: { size: 20, maxlength: 30 }, formater: 'number'}], pager: pager_id, //Pager. loadtext: 'Cargando datos...', recordtext: "{0} - {1} de {2} elementos", emptyrecords: 'No hay resultados', pgtext: 'Pág: {0} de {1}', //Paging input control text format. rowNum: "10", // PageSize. ajaxGridOptions: row_id, rowList: [10, 20, 30], //Variable PageSize DropDownList. viewrecords: true, //Show the RecordCount in the pager. multiselect: false, sortname: "ChildName", //Default SortColumn sortorder: "asc", //Default SortOrder. width: "800", height: "100%", caption: "Invoice Detail" }); jQuery("#" + subgrid_table_id).jqGrid('navGrid', "#" + pager_id, { edit: false, add: false, del: false }) },
Si vemos el código anterior, la definición del subgrid es exactamente igual a como hemos realizado la definición del grid padre, solo que dentro de una función contenedora. 

 Finalmente, así se vería 

 

Por último el código de ejemplo 



Gracias a los comentarios de muchos, actualizo el post para agregar el Stored Procedure GetChilds, que lo disfruten


 
    /**
     * SyntaxHighlighter
     */
     USE [JQGrid]
 GO
 /****** Object:  StoredProcedure [dbo].[GetChilds]    Script Date: 07/27/2012 17:55:56 ******/
 SET ANSI_NULLS ON
 GO
SET QUOTED_IDENTIFIER ON
 GO
 -- GetPersons 3, 4, 'LastName', 'desc'
 ALTER procedure [dbo].[GetChilds]
 @PageSize int , 
 @CurrentPage int , 
 @SortColumn varchar(20), 
 @SortOrder varchar(4),
 @PersoId int
 as
 declare @RecordCount int
 declare @PageCount int
 declare @PageIndex int
 Select @RecordCount = count(ChildId)
 from Childs
 set @PageCount = Ceiling(cast (@RecordCount as float) / cast (@PageSize as float))
 if (@CurrentPage > @PageCount) set @CurrentPage = @PageCount
 set @PageIndex = @CurrentPage - 1
 Select RecordCount = @RecordCount, PageCount = @PageCount, CurrentPage = @CurrentPage
 declare @Query varchar(300)
 set @Query = 
    'Select ChildId, ChildName, ChildLastName, ChildBirthDate,PersonId, 
     RowNumber = ROW_NUMBER() OVER (ORDER BY ' + @SortColumn + ' ' + @SortOrder + ')
    from Childs' 
 set @Query = 
 'Select ChildId, ChildName, ChildLastName, ChildBirthDate,PersonId
  from (' + @Query + ' )as result 
 where PersonId='+ CAST(@PersoId as varchar(5)) +' and RowNumber BETWEEN ' + cast(@PageSize * @PageIndex + 1 as varchar(10)) + 'AND ' + cast(@PageSize * (@PageIndex + 1) as varchar(10)) 
   Exec (@Query)

martes, 29 de octubre de 2013

Menú Dinámico en MySql

Luego de varios días sin hacer post, hoy nuevamente retomo para mostrar un pequeño ejemplo de un tema que últimamente ha sido muy tocado en los foros de asp.net de MSDN. Cómo hacer un menú dinámico con MySql?

Antes de empezar a mostrar como crear el menú voy a explicar algunos problemas que encontré durante el desarrollo del ejemplo:

1. Para el ejemplo, utilizaremos la versión beta 6.5 del conector de .net para mysql, sin embargo, deben tomar en cuenta que este conector esta en desarrollo y aún carece de muchas funcionalidades que permitan implementar un mejor manejo de un menú dinámico.

2. En MySql lamentablemente no existe el Cache Dependency para poder monitorear los cambios en la base de datos y poder ver las actualizaciones del menú sin problemas. 

Por ello, con esta versión del .Net Connector, cada vez que agreguemos un nuevo ítem de menú a nuestra base de datos, tendremos que detener el servidor de desarrollo de visual studio y volver a iniciar para ver los cambios.

Sin embargo, aunque no sea posible por ahora con el conector monitorear el cambio (se puede hacer a código puro pero no vale la pena pues es mucho), este menú es una buena opción para poder organizar los accesos por roles.

Ahora sí, iniciemos:
Lo primero para nuestro menú, será descargar e instalar la última versión del conector .net para mysql (6.5 beta)


Entrando en el código, lo primero que realizaremos será crear una clase que herede se StaticSiteMapProvider para de esta manera poder contar con todas las opciones y métodos


    /**
     * SyntaxHighlighter
     */
    [MySqlClientPermission(SecurityAction.Demand,Unrestricted = true)]
    public class MySqlSiteMapProvider : StaticSiteMapProvider
    {
    }

Ahora, definimos una serie de variables Privadas (solo para la clase) que nos permitirán mostrar algunos mensajes de error predeterminado y almacenar los valores recuperados del web.config
    /**
     * SyntaxHighlighter
     */
    private const string _errmsg1 = "Missing node ID";
    private const string _errmsg2 = "Duplicate node ID";
    private const string _errmsg3 = "Missing parent ID";
    private const string _errmsg4 = "Invalid parent ID";
    private const string _errmsg5 = "Empty or missing connectionStringName";
    private const string _errmsg6 = "Missing connection string";
    private const string _errmsg7 = "Empty connection string";
    private string _connect;              // Cadena de conexión a  base de datos
    private int _indexId, _indexTitle, _indexUrl, _indexDesc, _indexRoles, _indexParent;
    private Dictionary _nodes = new Dictionary(16);
    private readonly object _lock = new object();
    private SiteMapNode _root;

En las definiciones anteriores, los mas importante sería el Dictionary llamado _nodes, pues será el que almacenara los datos recuperados para el menú. En el siguiente método, haremos un Override del método Initialize original y será en este en donde leamos el web.config y recuperemos todos los datos de la cadena de conexión y los proveedores

    /**
     * SyntaxHighlighter
     */
   public override void Initialize(string name, NameValueCollection config)
     {
        // Verificar que config no es null
         if (config == null)
             throw new ArgumentNullException("config");
         // Asignar al proveedor un nombre por defecto sino posee uno
        if (String.IsNullOrEmpty(name))
           name = "MySqlSiteMapProvider";
        // Add a default "description" attribute to config if the
        // attribute doesn’t exist or is empty
        if (string.IsNullOrEmpty(config["description"]))
         {
            config.Remove("description");
            config.Add("description", "SQL site map provider");
         }
        // Call the base class's Initialize method
        base.Initialize(name, config);
       // Initialize _connect
       string connect = config["connectionStringName"];
       if (String.IsNullOrEmpty(connect))
       throw new ProviderException(_errmsg5);
       config.Remove("connectionStringName");
        if (WebConfigurationManager.ConnectionStrings[connect] == null)
         throw new ProviderException(_errmsg6);
         _connect = WebConfigurationManager.ConnectionStrings[connect].ConnectionString;
         if (String.IsNullOrEmpty(_connect))
             throw new ProviderException(_errmsg7);
         if (config["securityTrimmingEnabled"] != null)
             config.Remove("securityTrimmingEnabled");
         // Throw an exception if unrecognized attributes remain
        if (config.Count > 0)
         {
           string attr = config.GetKey(0);
           if (!String.IsNullOrEmpty(attr))
              throw new ProviderException("Unrecognized attribute: " + attr);
         }
    }

El siguiente método al que vamos a realizar el Override sera el SiteMapNode, este es uno de los mas importantes pues será en el cual nos vamos a conectar a mysql, leer los datos y enviarlos a otro método para armar el menú

    /**
     * SyntaxHighlighter
     */
    public override SiteMapNode BuildSiteMap()
    {
        lock (_lock)
        {
           // Return immediately if this method has been called before
            if (_root != null)
               return _root;
            // Query the database for site map nodes
         MySqlConnection connection = new MySqlConnection(_connect);
          try
           {
             MySqlCommand command = new MySqlCommand("proc_GetSiteMap", connection);
             command.CommandType = CommandType.StoredProcedure;
             connection.Open();
             MySqlDataReader reader = command.ExecuteReader();
             _indexId = reader.GetOrdinal("ID");
             _indexUrl = reader.GetOrdinal("Url");
             _indexTitle = reader.GetOrdinal("Title");
             _indexDesc = reader.GetOrdinal("Description");
             _indexRoles = reader.GetOrdinal("Roles");
            _indexParent = reader.GetOrdinal("Parent");
         if (reader.Read())
             {
                 // Create the root SiteMapNode and add it to the site map
                 _root = CreateSiteMapNodeFromDataReader(reader);
                  AddNode(_root, null);
                  // Build a tree of SiteMapNodes underneath the root node
                  while (reader.Read())
                   {
                      // Create another site map node and add it to the site map
                      SiteMapNode node = CreateSiteMapNodeFromDataReader(reader);
                      AddNode(node, GetParentNodeFromDataReader(reader));
                    }
                }
            }
           finally
            {
               connection.Close();
            }
           // Return the root SiteMapNode
           return _root;
        }
   }

Los siguientes dos métodos no los voy a explicar en detalle, pero serán los encargados de tomar y extraer los roles y datos recuperados de la base de datos con el procedimiento anterior y crear cada SiteNode para el menú, almacenándolo en el Dictionay _nodes

    /**
     * SyntaxHighlighter
     */
    protected override SiteMapNode GetRootNodeCore()
    {
       lock (_lock)
        {
           BuildSiteMap();
           return _root;
        }
    }
    // Helper methods
    private SiteMapNode CreateSiteMapNodeFromDataReader(DbDataReader reader)
    {
       // Make sure the node ID is present
       if (reader.IsDBNull(_indexId))
       throw new ProviderException(_errmsg1);
       // Get the node ID from the DataReader
       int id = reader.GetInt32(_indexId);
       // Make sure the node ID is unique
       if (_nodes.ContainsKey(id))
            throw new ProviderException(_errmsg2);
      // Get title, URL, description, and roles from the DataReader
      string title = reader.IsDBNull(_indexTitle) ? null : reader.GetString(_indexTitle).Trim();
      string url = reader.IsDBNull(_indexUrl) ? null : reader.GetString(_indexUrl).Trim();
      string description = reader.IsDBNull(_indexDesc) ? null : reader.GetString(_indexDesc).Trim();
      string roles = reader.IsDBNull(_indexRoles) ? null : reader.GetString(_indexRoles).Trim();
      // If roles were specified, turn the list into a string array
      string[] rolelist = null;
        if (!String.IsNullOrEmpty(roles))
           rolelist = roles.Split(new char[] { ',', ';' }, 512);
        // Create a SiteMapNode
      SiteMapNode node = new SiteMapNode(this, id.ToString(), url, title, description, rolelist, null, null, null);
      // Record the node in the _nodes dictionary
      _nodes.Add(id, node);
      // Return the node       
      return node;
   }

El último método, se encargara de hacer accesibles o no cada uno de los nodos del menú, eso si, para que funcione la opción SecurityTrimming tiene que ser true en la configuración del SiteMapProvider del web.config

    /**
     * SyntaxHighlighter
     */
    private SiteMapNode GetParentNodeFromDataReader(DbDataReader reader)
    {
       // Make sure the parent ID is present
       if (reader.IsDBNull(_indexParent))
           throw new ProviderException(_errmsg3);

      // Get the parent ID from the DataReader
      int pid = reader.GetInt32(_indexParent);
      // Make sure the parent ID is valid

      if (!_nodes.ContainsKey(pid))
           throw new ProviderException(_errmsg4);

      // Return the parent SiteMapNode
      return _nodes[pid];
    }

Con lo anterior, ya tenemos la clase que se encargará del menu. Ahora veamos como se configura el web.config Entre las etiquetas se debe agregar la configuración del sitemap

    /**
     * SyntaxHighlighter
     */
    
      
       
      
   
Con esto, nuestro web.config ya estará preparado. Por supuesto no olviden cambiar el nombre de la cadena de conexión por la que tienen y el type que será el nombre de la clase Lo ultimo será el html en la pagina

    /**
     * SyntaxHighlighter
     */
    
       
        
          
          
          
          
          
          
         
     
Una vez agregado el html, nuestro menú estará listo. Y como siempre el código de ejemplo

lunes, 28 de octubre de 2013

jqGrid y Asp.net, un primer acercamiento

Luego de varios días de intenso trabajo, hoy he tomado un pequeño espacio de tiempo para dedicar un post al trabajo con datos en forma de tablas en asp.net 

Todos los que hemos trabajado desde hace bastante con asp.net, hemos aprendido a querer al GridView, un excelente control que nos permite mostrar los datos directamente desde una base de datos y trabajar con ellos, llamase trabajar a las operaciones de: editar, buscar, agregar y eliminar. Por supuesto que existen muchas otras opciones que no son el objetivo de este post. A pesar de que e GridView es una buena herramienta para trabajar, tiene un inconveniente, es un control que funciona del lado del servidor, lo cual ocasiona un postback hacia el servidor, afectando muchas veces el rendimiento y obligándonos a controlar los parpadeos de la página con controles adicionales. Así que, para evitarnos todo eso, hoy veremos como trabajar con jqGrid, un control basado en jquery para realizar las operaciones básicas (CRUD). La principal ventaja de trabajar con este control, es que, al ser creado en jquery, es del lado del cliente, lo cual hace mucho mas eficiente y por supuesto, cuenta con opciones adicionales que el gridview no posee. 

Antes de continuar, debo recalcar que existe ya un control basado en jqGrid para asp.net, pero este tiene un costo superior a $300 lo cual para muchos no es accesible, así que tendremos que trabajar con el control opensource y hacer algunas adaptaciones. Para todos aquellos que les gusta ver un ejemplo antes de sumergirse en un proceso de estos, les dejo la pagina de demos Demos jqgrid 

Ahora sí, es momento de empezar con nuestro control.

Descarga del control 
Lo primero que requerimos para iniciar nuestro trabajo es descargar el control de la página oficial, en ella pueden escoger cuales serán las funciones que desean descargar, generalmente recomiendo que las descarguen todas.
Descarga jqgrid 

Preparando el sitio 
Una vez descargado abrimos Visual Studio seleccionamos Archivo>Nuevo>Sitio Web (en blanco sería mejor)

Ahora creamos una nueva carpeta y la llamamos js y ponemos en ella todos los scripts del jqgrid que descargamos anteriormente.

 Nuevamente creamos una nueva carpeta llamada css y de igual forma ponemos hay todos los archivos CSS y las imágenes. Si en la descarga no están incluidos los archivos css, no se preocupen, los encontrarán en el archivo para descargar al final del post.

 Con esto, ya estaríamos listos para iniciar a codificar el control.

Creando el control
Para este primer acercamiento, veremos como implementar la funcionalidades básicas. En otros post, veremos como crear un maestro detalle y como utilizar un SubGrid. Primer paso, agregar las referencias javascript a la página
    /**
     * SyntaxHighlighter
     */
    
    
    
    
    
    
    
    
    
    
    
    
    
Si vemos las referencias, únicamente hemos agregado algunas, que serán las que nos brinden las funcionalidades que requerimos para trabajar con las operaciones básicas. Una vez agregadas, crearemos una nueva función javascript en la cual codificaremos el jqgrid. Si vemos la documentaciónoriginal de jqGrid, veremos que jqgrid utiliza un atributo llamado colModel para crear las columnas del grid y que su definición es manual, es decir, se hace directamente en el control. Algo así: 

    /**
     * SyntaxHighlighter
     */
  colNames:['ID Number','Notes'],
  colModel:[
   {name:'id',index:'id', width:90, sorttype:"int", editable: true},                    
   {name:'note',index:'note', width:200, sortable:false,editable: true,edittype:"textarea", editoptions:
   rows:"2",cols:"10"}}                      
   ],
Esto, a mi parecer es algo no muy agradable, porque simplemente obligaría a tener que tocar código sensible del control cada vez que queramos mostrar información distinta. Así que, me di a la tarea de crear una nueva funcionalidad para el jqGrid que obtiene una serie de elementos desde un webservice vía Json y los mete en un array de jquery, de forma tal que podamos tener las columnas dinámicas. Este método, lo ejecutare antes de empezar la definición del jqGrid, es decir antes de la línea
    /**
     * SyntaxHighlighter
     */
 $("#grid").jqGrid(
Nuestro nuevo método luce así
    /**
     * SyntaxHighlighter
     */
 //aca es modificacion
           $.ajax({
                dataType: "json",
                type: "post",
                url: "JqGridEjemplo.aspx/GetNames",
                data: "{}",
                contentType: "application/json;",
                async: false, //esto es requerido, de otra forma el jqgrid se cargaria antes que el grid
                success: function (data) {
                  var cars = JSON.parse(data.d);
                    $.each(cars, function (index, car) {
                  colMode.push({ name: car.Name, index: car.index, width: car.width, align: 'left', editable: true, editrules: { edithidden: true }, hidden: false });
                     })
               } //or
           }),
El método anterior no tiene mucho especial que explicar: 1. Primero con el type=”post” hacemos referencia a la forma en que obtendremos los datos. 2.Con el data:”{}” le decimos al json que no vamos a enviarle parámetros 3. La url que direcciona al webservice 4. A la variable cars le asignamos el valor obtenido del Json 5. y Por ultimo con un each recorremos cada campo y lo metemos en el arreglo. El código del Jqgrid Al igual que el procedimiento anterior, para el jqgri tenemos que especificar los tipos de datos, la url y en este caso algunos parámetros que luego utilizaremos para traer los datos
    /**
     * SyntaxHighlighter
     */
 $("#grid").jqGrid(
Nuestro nuevo método luce así
    /**
     * SyntaxHighlighter
     */
 $("#grid").jqGrid(
   {
    datatype: function () {
     $.ajax(
         {
          url: "JqGridEjemplo.aspx/GetPersons", //PageMethod
         data: "{'pPageSize':'" + $('#grid').getGridParam("rowNum") +
          "','pCurrentPage':'" + $('#grid').getGridParam("page") +
          "','pSortColumn':'" + $('#grid').getGridParam("sortname") +
          "','pSortOrder':'" + $('#grid').getGridParam("sortorder") + "'}", //PageMethod Parametros de entrada
             dataType: "json",
             type: "post",
             contentType: "application/json; charset=utf-8",
             complete: function (jsondata, stat) {
             if (stat == "success")
               jQuery("#grid")[0].addJSONData(JSON.parse(jsondata.responseText).d);
                   else
               alert(JSON.parse(jsondata.responseText).Message);
                }
              });
            },
         jsonReader: //Set the jsonReader to the JQGridJSonResponse squema to bind the data.
         {
            root: "Items",
            page: "CurrentPage",
            total: "PageCount",
            records: "RecordCount",
            repeatitems: true,
            cell: "Row",
            id: "ID" //index of the column with the PK in it    
        },
Luego, establecemos las columnas utilizando el colmodel y algunos parámetros de configuración de nuestro grid
    /**
     * SyntaxHighlighter
     */
    colModel: colMode,
    pager: "#pager", //Pager.
    loadtext: 'Cargando datos...',
    recordtext: "{0} - {1} de {2} elementos",
    emptyrecords: 'No hay resultados',
    pgtext: 'Pág: {0} de {1}', //Paging input control text format.
    rowNum: "10", // PageSize.
    rowList: [10, 20, 30], //Variable PageSize DropDownList. 
    viewrecords: true, //Show the RecordCount in the pager.
    multiselect: true,
    sortname: "Name", //Default SortColumn
    sortorder: "asc", //Default SortOrder.
    width: "800",
    height: "230",
    caption: "Personas",
    ondblClickRow: function (id) {
      gdCustomers.restoreRow(lastSel);
      gdCustomers.editRow(id, true);
      lastSel = id;
    }
Jqgrid tiene la posibilidad de mostrar en su barra inferior las opciones de insert,delete, add, update y refresh, así que las configuraremos de la siguiente manera
    /**
     * SyntaxHighlighter
     */
    }).navGrid("#pager", { edit: true, add: true, search: true, del: true },
    { url: "jqGridEjemplo.aspx/EditData", closeAfterEdit: true },
    { url: "jqGridEjemplo.aspx/EditData", closeAfterAdd: true },
    { url: "jqGridEjemplo.aspx/DeleteData" });
Acá vemos que al pager, le estamos diciendo que si vamos a permitir todas las opciones y además, para cada una de ellas estamos asignando una dirección en especifico, que de igual forma, apuntan a un procedimiento en un webservice. Estas opciones son completamente configurables y por supuesto que bueno estar deshabilitadas o no presentes por ahora, tenemos que colocarlas manualmente, luego trabajaremos en hacerlo de forma dinámica. Para concluir con la configuración de nuestro jqgrid, necesitamos configurar las opciones de agregar y borrar utilizando el método jquery.extend, de otra forma aunque configuremos el jqgrid estas opciones nos devolverían un error.
    /**
     * SyntaxHighlighter
     */
    jQuery.extend(jQuery.jgrid.edit, {
        ajaxEditOptions: { contentType: "application/json" },
             recreateForm: true,
             serializeEditData: function (postData) {
             return JSON.stringify(postData);
              }
            });

     jQuery.extend(jQuery.jgrid.del, {
           ajaxDelOptions: { contentType: "application/json" },
           serializeDelData: function (postData) {
           return JSON.stringify(postData);
            }
         });
     });
Finalmente, el codigo completo seria
    /**
     * SyntaxHighlighter
     */
    
Ahora que la configuración el grid esta completa, es hora de trabajar el html donde se mostrarán los datos, siendo tan sencillo como agregar una tabla y un div
    /**
     * SyntaxHighlighter
     */
  
Los datos a mostrar, los traeremos de SQL utilizando un procedimiento almacenado que luce asi
    /**
     * SyntaxHighlighter
     */
  USE [JqGrid]
   GO
   /****** Object:  StoredProcedure [dbo].[GetPersons]    Script Date: 05/19/2011 13:29:50 ******/
   SET ANSI_NULLS ON
   GO
   SET QUOTED_IDENTIFIER ON
   GO
  -- GetPersons 3, 4, 'LastName', 'desc'
   ALTER procedure [dbo].[GetPersons]
   @PageSize int , 
   @CurrentPage int , 
   @SortColumn varchar(20), 
   @SortOrder varchar(4)
    as
   declare @RecordCount int
   declare @PageCount int
   declare @PageIndex int
   Select @RecordCount = count(ID)from Person
   set @PageCount = Ceiling(cast (@RecordCount as float) / cast (@PageSize as float))
   if (@CurrentPage > @PageCount) set @CurrentPage = @PageCount
   set @PageIndex = @CurrentPage - 1
   Select RecordCount = @RecordCount, PageCount = @PageCount, CurrentPage = @CurrentPage
   declare @Query varchar(300)
   set @Query = 'Select ID, Name, LastName, BirthDate, Weight, RowNumber = ROW_NUMBER() OVER (ORDER BY ' + @SortColumn + ' ' + @SortOrder + ')from Person' 
  set @Query = 'Select ID, Name, LastName, BirthDate, Weight from (' + @Query + ' )as result 
  where RowNumber BETWEEN ' + cast(@PageSize * @PageIndex + 1 as varchar(10)) + ' 
        AND ' + cast(@PageSize * (@PageIndex + 1) as varchar(10))
 
Exec (@Query)

Si observamos bien el Stored Procedure veremos que recibe algunos parámetros que estamos enviando por Json cuando la url que carga los datos es activada. El primer metodo que utilizaremos para cargar los datos con base en el stored procedure será
    /**
     * SyntaxHighlighter
     */
  
   internal static JQGridJsonResponse GetPersonasJSon(int pPageSize, int pPageNumber, string pSortColumn, string pSortOrder)
   {
       SqlConnection sqlCon = new SqlConnection(ConfigurationManager.ConnectionStrings["DataConnection"].ConnectionString);
       SqlCommand command = new SqlCommand("GetPersons", sqlCon);
       command.CommandType = CommandType.StoredProcedure;
       command.Parameters.Add("PageSize", SqlDbType.Int).Value = pPageSize;
       command.Parameters.Add("CurrentPage", SqlDbType.Int).Value = pPageNumber;
       command.Parameters.Add("SortColumn", SqlDbType.VarChar, 20).Value = pSortColumn;
       command.Parameters.Add("SortOrder", SqlDbType.VarChar, 4).Value = pSortOrder;
       DataSet dataSet = new DataSet();
       SqlDataAdapter dataAdapter = new SqlDataAdapter(command);
       dataAdapter.Fill(dataSet);
       var persons = new List();
       foreach (DataRow row in dataSet.Tables[1].Rows)
        {
            Person person = new Person
            {
               ID = Convert.ToInt32(row["ID"]),
               Name = row["Name"].ToString(),
               LastName = row["LastName"].ToString(),
               BirthDate = Convert.ToDateTime(row["BirthDate"]),
               Weight = Convert.ToSingle(row["Weight"])
            };
            persons.Add(person);
       }
  
return new JQGridJsonResponse(Convert.ToInt32(dataSet.Tables[0].Rows[0]["PageCount"]), Convert.ToInt32(dataSet.Tables[0].Rows[0]["CurrentPage"]),Convert.ToInt32(dataSet.Tables[0].Rows[0]["RecordCount"]), persons);

 }
Sin embargo, este método solo llena los datos, pero aun no los devuelve, eso lo haremos a través de un webservice de la siguiente forma
    /**
     * SyntaxHighlighter
     */
  [WebMethod]
   [ScriptMethod(ResponseFormat = ResponseFormat.Json)]
   public static JQGridJsonResponse GetPersons(int pPageSize, int pCurrentPage, string pSortColumn, string pSortOrder)
   {
      return GetPersonasJSon(pPageSize, pCurrentPage, pSortColumn, pSortOrder);
  }
Es importante que las líneas ResponseFormat=ResponseFormat.Json estén incluidas para que la información que se envié sea codificada de la forma correcta. También debemos notar que ambos metodos hacen referencia a JQGridJsonResponse que es una clase definida para almacenar los datos y llenar cada item
    /**
     * SyntaxHighlighter
     */
   /// 

  /// Respuesta JSON para JQGrid.
 /// 
 public class JQGridJsonResponse
 {

   #region Passive attributes.
  private int _pageCount;
  private int _currentPage;
  private int _recordCount;
  private List _items;
 #endregion
#region Properties
  /// 
  /// Cantidad de páginas del JQGrid.
  /// 
  public int PageCount
  {
    get { return _pageCount; }
   set { _pageCount = value; }
   }

  /// 
 /// Página actual del JQGrid.
   /// 
 public int CurrentPage
   {
     get { return _currentPage; }
    set { _currentPage = value; }
   }

   /// 
   /// Cantidad total de elementos de la lista.
   /// 
   public int RecordCount
   {
     get { return _recordCount; }
     set { _recordCount = value; }
   }

  /// 
  /// Lista de elementos del JQGrid.
  /// 
  public List Items
  {
    get { return _items; }
    set { _items = value; }
  }
  
 #endregion
#region Active attributes

 /// 
 /// Constructor.
 /// 
 /// Lista de elementos a mostrar en el JQGrid
 public JQGridJsonResponse(int pPageCount, int pCurrentPage, int pRecordCount, List pPersons)
  {
     _pageCount = pPageCount;
     _currentPage = pCurrentPage;
    _recordCount = pRecordCount;
   _items = new List();
     foreach (Person person in pPersons)
     _items.Add(new JQGridItem(person.ID, new List  { person.ID.ToString(),person.Name, person.LastName, person.BirthDate.ToShortDateString(), person.Weight.ToString() }));
  }
  
#endregion  
 }

Pero, nuevamente debemos tener cuidado, ya que esta clase llama a otra clase que contiene los atributos del grid llamada JQGridItem
    /**
     * SyntaxHighlighter
     */
   /// 
  /// Item del JQGrid. Elemento de la propiedad Items de la clase JsonJQGridResponse.
 /// 
  public class JQGridItem
   {
    #region Passive attributes
   private long _id;
   private List _row;
   #endregion

 #region Properties
    /// 
   /// RowId de la fila.
   /// 
  public long ID
   {
      get { return _id; }
      set { _id = value; }
    }

  /// 
  /// Fila del JQGrid.
 /// 
 public List Row
   {
      get { return _row; }
     set { _row = value; }
  }
 #endregion

 #region Active Attributes
 /// 
 /// Contructor.
 /// 
  public JQGridItem(long pId, List pRow)
   {
      _id = pId;
      _row = pRow;
    }
#endregion
}

Con esto, nuestro jqGrid sería totalmente funcional para mostrar los datos, paginar y ordenar pero aún no lo es para guardar, editar o borrar. Estos método al igual que los anteriores, serán definidos en un webservice y serán más sencillos de utilizar pues básicamente reciben los datos mediante Json y lo demás se traduce en código que ya conocemos.
    /**
     * SyntaxHighlighter
     */
  
[WebMethod]
 [ScriptMethod(ResponseFormat = ResponseFormat.Json)]
 public static void DeleteData(string id,string oper)
   { 
    if (String.Compare(oper, "del", StringComparison.Ordinal) == 0)
     {
      using (SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["DataConnection"].ConnectionString))
        {
          using (SqlCommand command = new SqlCommand("delete from person where id=@id", conn))
             {
                conn.Open();
                command.Parameters.AddWithValue("@id", id);
                command.ExecuteNonQuery();
               }
          }
       }
     }
    /**
     * SyntaxHighlighter
     */
    [WebMethod]
    [ScriptMethod(ResponseFormat = ResponseFormat.Json)]
    public static void EditData(string BirthDate, string LastName, string Name, string Weight, string id, string oper)
     {
        if (String.Compare(id, "_empty", StringComparison.Ordinal) == 0 ||
           String.Compare(oper, "add", StringComparison.Ordinal) == 0)
         {
      using (SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["DataConnection"].ConnectionString))
            {
               using (SqlCommand command = new SqlCommand("insert into person (name,lastname,birthdate,weight)values(@name,@lname,@date,@peso)", conn))
                 {
                    conn.Open();
                    command.Parameters.AddWithValue("@id", id);
                    command.Parameters.AddWithValue("@name", Name);
                    command.Parameters.AddWithValue("@lname", LastName);
                    command.Parameters.AddWithValue("@date", Convert.ToDateTime(BirthDate));
                    command.Parameters.AddWithValue("@peso", double.Parse(Weight));
                    command.ExecuteNonQuery();
                 }
             }
        }
        else if (String.Compare(oper, "edit", StringComparison.Ordinal) == 0)
        {
          using (SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["DataConnection"].ConnectionString))
          {
          using (SqlCommand command = new SqlCommand("update person set name=@name,lastname=@lname,birthdate=@date,weight=@peso where id=@id", conn))
              {
                 conn.Open();
                 command.Parameters.AddWithValue("@id", id);
                 command.Parameters.AddWithValue("@name", Name);
                 command.Parameters.AddWithValue("@lname", LastName);
                 command.Parameters.AddWithValue("@date", Convert.ToDateTime(BirthDate));
                 command.Parameters.AddWithValue("@peso", double.Parse(Weight));
                 command.ExecuteNonQuery();
                 }
             }
         }     
   }
 



Ejemplo para descargar

Twitter Delicious Facebook Digg Stumbleupon Favorites More

 
Design by Free WordPress Themes | Bloggerized by Lasantha - Premium Blogger Themes | Hosted Desktops