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
/**
* 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

11:57
Jaullo
0 comentarios:
Publicar un comentario