Jason Ulloa
Ideas de Chalu... el blog de .NET

Menú Dinámico en MySql

Thursday, January 5, 2012 6:09 PM

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)

Conector Mysql

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

   1: [MySqlClientPermission(SecurityAction.Demand,Unrestricted = true)]
   2: public class MySqlSiteMapProvider : StaticSiteMapProvider
   3: {
   4:  
   5: }

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

   1: private const string _errmsg1 = "Missing node ID";
   2:    private const string _errmsg2 = "Duplicate node ID";
   3:    private const string _errmsg3 = "Missing parent ID";
   4:    private const string _errmsg4 = "Invalid parent ID";
   5:    private const string _errmsg5 = "Empty or missing connectionStringName";
   6:    private const string _errmsg6 = "Missing connection string";
   7:    private const string _errmsg7 = "Empty connection string";
   8:  
   9:    private string _connect;              // Cadena de conexión a  base de datos
  10:    private int _indexId, _indexTitle, _indexUrl, _indexDesc, _indexRoles, _indexParent;
  11:    private Dictionary<int, SiteMapNode> _nodes = new Dictionary<int, SiteMapNode>(16);
  12:    private readonly object _lock = new object();
  13:    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

   1: public override void Initialize(string name, NameValueCollection config)
   2:     {
   3:         // Verificar que config no es null
   4:         if (config == null)
   5:             throw new ArgumentNullException("config");
   6:  
   7:         // Asignar al proveedor un nombre por defecto sino posee uno
   8:         if (String.IsNullOrEmpty(name))
   9:             name = "MySqlSiteMapProvider";
  10:  
  11:         // Add a default "description" attribute to config if the
  12:         // attribute doesn’t exist or is empty
  13:         if (string.IsNullOrEmpty(config["description"]))
  14:         {
  15:             config.Remove("description");
  16:             config.Add("description", "SQL site map provider");
  17:         }
  18:  
  19:         // Call the base class's Initialize method
  20:         base.Initialize(name, config);
  21:  
  22:         // Initialize _connect
  23:         string connect = config["connectionStringName"];
  24:  
  25:         if (String.IsNullOrEmpty(connect))
  26:             throw new ProviderException(_errmsg5);
  27:  
  28:         config.Remove("connectionStringName");
  29:  
  30:         if (WebConfigurationManager.ConnectionStrings[connect] == null)
  31:             throw new ProviderException(_errmsg6);
  32:  
  33:         _connect = WebConfigurationManager.ConnectionStrings[connect].ConnectionString;
  34:  
  35:         if (String.IsNullOrEmpty(_connect))
  36:             throw new ProviderException(_errmsg7);
  37:  
  38:       
  39:         if (config["securityTrimmingEnabled"] != null)
  40:             config.Remove("securityTrimmingEnabled");
  41:  
  42:         // Throw an exception if unrecognized attributes remain
  43:         if (config.Count > 0)
  44:         {
  45:             string attr = config.GetKey(0);
  46:             if (!String.IsNullOrEmpty(attr))
  47:                 throw new ProviderException("Unrecognized attribute: " + attr);
  48:         }
  49:  
  50:      
  51:     }

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ú

   1: public override SiteMapNode BuildSiteMap()
   2:    {
   3:        lock (_lock)
   4:        {
   5:            // Return immediately if this method has been called before
   6:            if (_root != null)
   7:                return _root;
   8:  
   9:            // Query the database for site map nodes
  10:            MySqlConnection connection = new MySqlConnection(_connect);
  11:  
  12:            try
  13:            {
  14:                MySqlCommand command = new MySqlCommand("proc_GetSiteMap", connection);
  15:                command.CommandType = CommandType.StoredProcedure;
  16:                connection.Open();
  17:  
  18:                MySqlDataReader reader = command.ExecuteReader();
  19:                _indexId = reader.GetOrdinal("ID");
  20:                _indexUrl = reader.GetOrdinal("Url");
  21:                _indexTitle = reader.GetOrdinal("Title");
  22:                _indexDesc = reader.GetOrdinal("Description");
  23:                _indexRoles = reader.GetOrdinal("Roles");
  24:                _indexParent = reader.GetOrdinal("Parent");
  25:  
  26:                if (reader.Read())
  27:                {
  28:                    // Create the root SiteMapNode and add it to the site map
  29:                    _root = CreateSiteMapNodeFromDataReader(reader);
  30:                    AddNode(_root, null);
  31:  
  32:                    // Build a tree of SiteMapNodes underneath the root node
  33:                    while (reader.Read())
  34:                    {
  35:                        // Create another site map node and add it to the site map
  36:                        SiteMapNode node = CreateSiteMapNodeFromDataReader(reader);
  37:                        AddNode(node, GetParentNodeFromDataReader(reader));
  38:                    }
  39:  
  40:                  
  41:                }
  42:            }
  43:            finally
  44:            {
  45:                connection.Close();
  46:            }
  47:  
  48:            // Return the root SiteMapNode
  49:            return _root;
  50:        }
  51:    }

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

   1: protected override SiteMapNode GetRootNodeCore()
   2:    {
   3:        lock (_lock)
   4:        {
   5:            BuildSiteMap();
   6:            return _root;
   7:        }
   8:    }
   9:  
  10:    // Helper methods
  11:    private SiteMapNode CreateSiteMapNodeFromDataReader(DbDataReader reader)
  12:    {
  13:        // Make sure the node ID is present
  14:        if (reader.IsDBNull(_indexId))
  15:            throw new ProviderException(_errmsg1);
  16:  
  17:        // Get the node ID from the DataReader
  18:        int id = reader.GetInt32(_indexId);
  19:  
  20:        // Make sure the node ID is unique
  21:        if (_nodes.ContainsKey(id))
  22:            throw new ProviderException(_errmsg2);
  23:  
  24:        // Get title, URL, description, and roles from the DataReader
  25:        string title = reader.IsDBNull(_indexTitle) ? null : reader.GetString(_indexTitle).Trim();
  26:        string url = reader.IsDBNull(_indexUrl) ? null : reader.GetString(_indexUrl).Trim();
  27:        string description = reader.IsDBNull(_indexDesc) ? null : reader.GetString(_indexDesc).Trim();
  28:        string roles = reader.IsDBNull(_indexRoles) ? null : reader.GetString(_indexRoles).Trim();
  29:  
  30:        // If roles were specified, turn the list into a string array
  31:        string[] rolelist = null;
  32:        if (!String.IsNullOrEmpty(roles))
  33:            rolelist = roles.Split(new char[] { ',', ';' }, 512);
  34:  
  35:  
  36:        // Create a SiteMapNode
  37:        SiteMapNode node = new SiteMapNode(this, id.ToString(), url, title, description, rolelist, null, null, null);
  38:  
  39:        // Record the node in the _nodes dictionary
  40:        _nodes.Add(id, node);
  41:  
  42:        // Return the node       
  43:        return node;
  44:    }

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

   1: private SiteMapNode GetParentNodeFromDataReader(DbDataReader reader)
   2:     {
   3:         // Make sure the parent ID is present
   4:         if (reader.IsDBNull(_indexParent))
   5:             throw new ProviderException(_errmsg3);
   6:  
   7:         // Get the parent ID from the DataReader
   8:         int pid = reader.GetInt32(_indexParent);
   9:  
  10:         // Make sure the parent ID is valid
  11:         if (!_nodes.ContainsKey(pid))
  12:             throw new ProviderException(_errmsg4);
  13:  
  14:         // Return the parent SiteMapNode
  15:         return _nodes[pid];
  16:     }

Con lo anterior, ya tenemos la clase que se encargará del menu. Ahora veamos como se configura el web.config

Entre las etiquetas <System.Web></System.Web> se debe agregar la configuración del sitemap

   1: <siteMap defaultProvider="AspNetSqlSiteMapProvider" enabled="true">
   2:      <providers>
   3:        <add name="AspNetSqlSiteMapProvider" type="MySqlSiteMapProvider" securityTrimmingEnabled="true" connectionStringName="ApplicationServices"/>
   4:      </providers>
   5:    </siteMap>

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

   1: <asp:SiteMapDataSource ID="SiteMapDataSource2" runat="server" SiteMapProvider="AspNetSqlSiteMapProvider"  />
   2:                    <asp:Menu ID="Menu2" runat="server" DataSourceID="SiteMapDataSource2" Orientation="Horizontal"
   3:                        DynamicHorizontalOffset="1" Font-Names="Verdana" Font-Size="0.8em" ForeColor="#990000"
   4:                        Height="10px" StaticSubMenuIndent="0px" StaticDisplayLevels="2">
   5:                        <StaticMenuStyle CssClass="toolbar" />
   6:                        <StaticSelectedStyle BackColor="#FFCC66" />
   7:                        <StaticMenuItemStyle CssClass="toolbar" HorizontalPadding="0px" VerticalPadding="1px" />
   8:                        <DynamicHoverStyle CssClass="toolbar" BackColor="#990000" ForeColor="White" />
   9:                        <DynamicMenuStyle CssClass="toolbar" BackColor="#FFFBD6" />
  10:                        <DynamicSelectedStyle BackColor="#FFCC66" />
  11:                        <DynamicMenuItemStyle HorizontalPadding="0px" VerticalPadding="1px" />
  12:                        <StaticHoverStyle BackColor="#990000" ForeColor="White" />
  13:                    </asp:Menu>

 

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




Feedback

# re: Menú Dinámico en MySql

Great information and details coding makes it the most inform-able information.Great Article. Overall we can get the latest code with better outcome as well as detail leaning.

Android app development London
1/6/2012 3:22 AM | Android app development London

# re: Menú Dinámico en MySql

gracias por tu ayuda de menu dinamico pero como puedo correr si no tengo la structura de la base de datos del menu...

gracias 4/19/2012 3:38 PM | jose florer

# re: Menú Dinámico en MySql

Tienes la BD para provar tu ejemplo? 1/18/2013 9:20 PM | Diego

Post a comment