quinta-feira, 29 de julho de 2010

Menu dinâmico buscando itens da base de dados (ASP.Net)

Fala pessoaL!
Nossa, faz muito tempo que não posto nada e para falar a verdade, faz tempo que eu nem acesso meu blog. Estou sem tempo e com muitas preocupações. Viajarei para a Australia no fim desse ano e isso esta me deixando ansioso e muito nervoso. Correria total!!
Bom, como o blog não é sobre mim mas sim sobre análise e desenvolvimento de sistemas, vamos ao que interessa.

Devido a um novo projeto da empresa, comecei a programar em asp.net C# há poucos dias. Precisei fazer um controle de login dos usuários e claro, um menu contendo todos os menus que o usuário tem acesso.
Só que esses menus estão cadastrados na base de dados. E agora?
Não achei nada na internet, nenhum blog de pessoas que já fizeram isso em asp.net e disponibilizaram.

TECNOLOGIAS E FERRAMENTAS UTILIZADAS

Estou utilizando o modelo entidade relacional Entity Framework da Microsoft, acessando uma base de dados MySQL (the best database ever! ;) )

Não irei tratar aqui sobre como fazer os relacionamentos das tabelas ou criação e associação entre as classes do nosso modelo objeto relacional, mas sim como criar o menu para o site e seus respectivos filhos.

TABELAS USADAS NO EXEMPLO


Eu utilizei uma tabela com relacionamento Self One to Many. Para quem não conhece esse tipo de relacionamento, vale a pena dar uma olhada nesse outro post em meu blog:

http://devutils.blogspot.com/2008/12/sql-relacionamentos-self-to-self.html

CREATE DATABASE `exemplomenu`

USE `exemplomenu`

DROP TABLE IF EXISTS `menus_web`;

CREATE TABLE `menus_web` (
`idmenus` int(7) unsigned NOT NULL AUTO_INCREMENT,
`nome` varchar(45) NOT NULL COMMENT 'nome do item de menu',
`ordem` int(7) unsigned DEFAULT NULL COMMENT 'para ordenação dos itens de menu',
`pai` int(7) unsigned DEFAULT NULL,
`hint` varchar(255) DEFAULT NULL,
`bitmap` int(7) DEFAULT NULL COMMENT 'icone do item de menu',
`url` varchar(50) DEFAULT NULL,
PRIMARY KEY (`idmenus`),
KEY `fk_menus_menus1` (`pai`)
) ENGINE=InnoDB AUTO_INCREMENT=12 DEFAULT CHARSET=latin1;

/*Data for the table `menus_web` */

insert into `menus_web`(`idmenus`,`nome`,`ordem`,`pai`,`hint`,`bitmap`,`url`) values (1,'Veículos',50,NULL,NULL,NULL,''),(2,'Relatórios',NULL,1,NULL,NULL,''),(3,'Por responsabilidade',NULL,2,NULL,NULL,'/veiculos/relatorios/responsabilidade.aspx'),(4,'Avarias por montadora',NULL,7,NULL,NULL,''),(5,'Avarias por modelo',NULL,7,NULL,NULL,''),(6,'Avarias por peça',NULL,7,NULL,NULL,''),(7,'Gráficos',NULL,1,NULL,NULL,''),(8,'Por responsabilidade e período',NULL,2,NULL,NULL,''),(9,'Algodão',NULL,NULL,NULL,NULL,''),(10,'Relatórios',NULL,9,NULL,NULL,''),(11,'Sair',NULL,NULL,NULL,NULL,'sair.aspx');


CRIAÇÃO DO MENU

Utilizarei o componente Menu, que se encontra na aba Navigation da ToolBox. Vamos deixar o ID dele como Menu1.

Repare que o menu possui uma propriedade chamada MaximumDynamicDisplayLevels. Essa propriedade diz quantos nodes filhos o menu poderá ter.

//Objeto do tipo MenuItem (node de um menu)
MenuItem vMenu;
//Objeto array, do tipo MenuItem
MenuItem[] vMenu2 = new MenuItem[5];
int vCont = 0;

Primeriamente vamos gerar um procedimento que nos traz todos os menus pais, ou seja, que contenha o valor pai igual a vazio.

private void GerarMenu()
{
using (kerpModel.kerpModelEntities dm = new kerpModel.kerpModelEntities())
{
//SELECIONA OS DADOS DO MENU POR MEIO DO LINQ
var consMenu = (from m in dm.menus_web
where m.pai == null
select m);


//CORRE OS REGISTROS DO MENU
foreach(kerpModel.menus_web row in consMenu)
{
//Cria um novo node para o menu
vMenu = new MenuItem(row.nome.ToString());

vMenu.NavigateUrl = row.url.ToString();
Menu1.Items.Add(vMenu);
//Chama o procedimento que verifica se o menu possui filhos, passando o id do menu como parametro
this.GerarMenuFilho(int.Parse(row.idmenus.ToString()));
}
}
}


//Função que adiciona os filhos do item de menu
private void GerarMenuFilho(int idpai)
{
using (kerpModel.kerpModelEntities dm = new kerpModel.kerpModelEntities())
{
//SELECIONA OS DADOS DO MENU, QUE TEVE O ID PASSADO POR PARAMETRO
var consMenu = (from m in dm.menus_web
where m.pai == idpai
select m);

//CORRE OS REGISTROS DO MENU
foreach (kerpModel.menus_web row in consMenu)
{
//Chama a função que verifica se o menu atual também possui filhos
if (totalFilhos(int.Parse(row.idmenus.ToString())) > 0)
{
//Adiciona o menu ao array de menus, para que ele possa ser recuperado posteriormente
vMenu2[vCont] = vMenu;
//Variavel que conta quantos nodes principais o menu possui
vCont++;
//Adiciona o filho atual ao menu e ao mesmo tempo diz que agora o novo menu é ele mesmo
vMenu.ChildItems.Add(vMenu = new MenuItem(row.nome.ToString(), null, null, row.url.ToString(), null));
//Chama novamente a função, e adiciona seus filhos
this.GerarMenuFilho(int.Parse(row.idmenus.ToString()));
}
else
{
//Caso o item não possua filos, apenas o adiciona como um novo node
vMenu.ChildItems.Add(new MenuItem(row.nome.ToString(), null, null, "~"+row.url.ToString(), null));
}
}

//Aqui esta o segredinho. Foi para isso que eu criei o contador. Cada vez que o node e seus filhos sao criados, ainda é necessário criar os outros nodes faltantes, mas esses nodes precisam ser inseridos em seus pais. Cada vez que o cursor passa por aqui, dizemos que o menu atual é o menu pai anterior. Dificil de entender, né? para entender, debuge a aplicação.
vCont--;
if(vCont >= 0)
vMenu = vMenu2[vCont];

}
}

//Função que verifica se o menu possui filhos
private int totalFilhos(int idmenu)
{
int count = 0;
using (kerpModel.kerpModelEntities dm = new kerpModel.kerpModelEntities())
{
count = (from m in dm.menus_web
where m.pai == idmenu
select m).Count();
}
return count;

}


//Para finalizar, no nosso Page_Load nós chamamos o procedimento
protected void Page_Load(object sender, EventArgs e)
{
if (!Page.IsPostBack)
{
this.GerarMenu();
}
}

Pessoal, peço desculpas por estar ausente do blog. Esta muito dificil de acessá-lo. Mas assim que for possível, responderei a seus comentários. Obrigado.