Question

The asp menu control is in the master page. Its datasource is a web.sitemap file. This file has all the items/Pages declared as nodes, initially. I have written this code to remove the items from the menu based on the user permissions, after user logs in.

protected void MyMenu_MenuItemDataBound(object sender, MenuEventArgs e) 
{
  if(Session["MenuLoaded"]==null)
  {
    SiteMapNode node = (SiteMapNode)e.Item.DataItem;
    bool deleteItem = true;
    if(lstRoles.Count==0)
       lstRoles = (List<tblDetail>)Session["sRoles"];
    if(!string.IsNullOrEmpty(node.Description))
     {
       foreach(var item in lstRoles)
        {
          if(Convert.ToInt32(node.Description)==item.FormId)
           {
             deleteItem = false;
             break;
           }
        }
        if(deleteItem)
         {
           if(e.Item.Parent !=null)
            {
              MenuItem mItem = e.Item.Parent;
              mItem.ChildItems.Remove(e.Item);
              if(mItem.ChildItems.Count==0)
                {
                  if(mItem.Parent !=null)
                   {
                     MenuItem Item = mItem.Parent;
                     Item.ChildItems.Remove(mItem);
                   }
                  else
                   {
                     Menu menu = (Menu)sender;
                     menu.Items.Remove(mItem);
                   }
                }
              else
                {
                  Menu menu = (Menu)sender;
                  menu.Items.Remove(e.Item);
                }
            }
         }
     }

  }
}

protected void MyMenu_DataBound(object sender, EventArgs e)
{
  Session["MenuLoaded"]=true;
}

The reason for the session variable is - The menuitemdatabound fires for every refresh/page request clicks, and I wanted the menu to be loaded only once for a user session.

PROBLEM:

The 'Remove Item' code works fine. When the user logs in, the menu items do not show as desired. But when he clicks on an existing item to move to another page, All the menus appear again in the menubar.

Why is this happening. Should I allow the menuitemdatabound event everytime a page is refreshed or new url requested. Thats not right.is it?. But any other way around? or i could just remove the session condition.

using C#

TRIED THIS:

page_load()
{
  if(Session["sMenuLoaded"]==null)
    lstRoles = (List<tblRoles>)Session["sMenuLoaded"];
  else
    {
      Menu mainMenu = (Menu)Session["sMenuLoaded"];
      mainMenu.DataBind();
    }

}

mymenu_menuitemdatabound()
{
  //  remains the same as above
}

mymenu_databound()
{
  Session["sMenuLoaded"] = (Menu)Page.Master.FindControl("menuBar");
}
Was it helpful?

Solution

If you are using xml sitemap, there is another way to make it. Asp.Net has mechanism to override site map logic. There is base SiteMapProvider, and default implementation XmlSiteMapProvider. Base SiteMapProvider has IsAccessibleToUser. You can create own sitemap provider like this:

public class MySiteMapProvider : XmlSiteMapProvider 
{
    public override bool IsAccessibleToUser(HttpContext context, SiteMapNode node)
    {
        var lstRoles = (List<tblDetail>)context.Session["sRoles"];

        // when we are accessing ChildNodes, it will execute the same IsAccessibleToUser method for each of this sub nodes
        var childs = node.ChildNodes;

        var isParentNode = node["isParent"] == "true";
        if (childs.Count == 0 && isParentNode)
        {
            // it means that this is node is parent node, and all it sub nodes are not accessible, so we just return false to remove it
            return false;
        }

        if (string.IsNullOrWhiteSpace(node.Description))
            return true;

        var formId = Convert.ToInt32(node.Description);
        foreach (var item in lstRoles)
        {
            if (item.FormID == formId)
                return true;
        }

        return false;
    }
}

Then you can specify it in web.config:

  <siteMap defaultProvider="myProvider">
      <providers>
          <add name="myProvider" securityTrimmingEnabled="true"  
          type="WebApplication5.MySiteMapProvider"  siteMapFile="web.sitemap" />
      </providers>
  </siteMap>

And then when Asp.Net will render menu based on sitemap, it will call IsAccessibleToUser each time for each user to verify that it has access to it. And if you return false in that method, then this node and it's children will be hidden.

UPDATE1

I've updated code to reflect original idea of binding. For testing I used such web.sitemap

<siteMap xmlns="http://schemas.microsoft.com/AspNet/SiteMap-File-1.0" >
<siteMapNode url="" title="Root">
    <siteMapNode url="Default.aspx" title="1"  description="1">
        <siteMapNode url="SubDefault.aspx" title="2"  description="11" />
        <siteMapNode url="SubDefault1.aspx" title="3"  description="12" />
    </siteMapNode>
    <siteMapNode url="Default2.aspx" title="2_1" isParent="true">
        <siteMapNode url="Default3.aspx" title="21" description="2"/>
    </siteMapNode>
</siteMapNode>

Here is my test roles:

Session["sRoles"] = new List<tblDetail>()
                    {
                        new tblDetail() { FormID = 12 }, 
                        new tblDetail() { FormID = 1 }
                    };

Also securityTrimmingEnabled="true" was added to web.config in sample above. Now when it checks for each node. If node hasn't access based on tblDetail from session, then IsAccessibleNode returns false, and this node and all subnodes are hidden on UI. It will execute this method for each node. In my test case only Default.aspx and SubDefault1.aspx nodes were displayed, as I specified only two FormIDs 1, 11. So MySiteMapProvider displayed only them.

UPDATE2 Added aspx UI that i used

<asp:Menu ID="Menu1" runat="server" DataSourceID="SiteMapDataSource1"></asp:Menu>
<asp:SiteMapDataSource ID="SiteMapDataSource1" Runat="Server" 
     StartFromCurrentNode="False" ShowStartingNode="True" />

UPDATE 3

I've added new attribute to web.sitemap - isParent. You can specify it in nodes that should be hidden, if all childs are not accessible. And also I've updated code for provider to use this isParent node.

OTHER TIPS

after successful login you can rebind the menu

Menu mainMenu=(Menu)Page.Master.FindControl("MyMenu");

if(mainMenu!=null)

{ 
   mainMenu.DataBind();
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top