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

0 comentarios:

Publicar un comentario

Twitter Delicious Facebook Digg Stumbleupon Favorites More

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