2

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");
}
Ruby
  • 949
  • 7
  • 31
  • 68

2 Answers2

3

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.

Sergey Litvinov
  • 7,408
  • 5
  • 46
  • 67
  • Thank you. But the profiles will be generated at the client. There could be many and each with different roles. So hardcoding is not an option. – Ruby Dec 19 '13 at 09:38
  • It shouldn't be hardcoded. I've added samples how to use DB, data from current page in that custom sitemap to my answer – Sergey Litvinov Dec 19 '13 at 09:55
  • I see .."Administration", "Admin"..I am sorry, but i dont get it. A client will develop his own profiles/roles for his company. a page can be viewed by many profiles not just admin. – Ruby Dec 19 '13 at 09:58
  • All these profiles\roles will be saved in the DB, and current user will have access only to these pages that his profile\role has access. Am I right? In that case you can just move your logic that you used for removing denied pages from page to that custom SiteMapProvider. Then it will be in one place, and Asp.Net will use it to verify access to each node. – Sergey Litvinov Dec 19 '13 at 10:02
  • You are right. Its just that I am unable to comprehend your code. Novice here. Could you plz just have a look at mine...updated. It works good. Its just that I want to do that operation only once for a session life. – Ruby Dec 19 '13 at 10:22
  • Thank you very much @sergey. I am failing at providing the type. No matter what, it says could not load type 'WebApp.MySiteMapProvider' – Ruby Dec 19 '13 at 12:19
  • 1
    WebApp.MySiteMapProvider should be FullNamespaces.TypeName. If type is located not in the web application, but in additional Class Library, then it should include assembly name like this: type="SomeNameSpace.MySiteMapProvider, SomeClassLibraryName" – Sergey Litvinov Dec 19 '13 at 12:21
  • I figured it out. It is working. But just a lil issue. I have 2 main menus - Home, People. Here people is not in DB, but its list of items are. So, this code is hiding all the items under people as desired, but the 'people' main menu is still visible. Any simple trick for that. The code i wrote, also deletes any empty parent menus..... – Ruby Dec 19 '13 at 12:26
  • 1
    I've updated sample :) this is just suggestion, but you can add isParent="true" node as in sample to root nodes, and then if all sub nodes are denied to access, then this root node won't be displayed – Sergey Litvinov Dec 19 '13 at 13:26
  • is it possible using several ***sitemap files*** _web.sitemap_ ? Load any *web.sitemap* in **execution time** – Kiquenet Jan 03 '17 at 15:40
  • @Kiquenet that's a bit offtopic and can be asked as new question, but yeah, you can have root `web.sitemap` that points to any number of child sitemaps but in static way. If you want to get them in `execution time`, then you need to create own one `SiteMapProvider` that inherits from `XmlSiteMapProvider` and has custom logic – Sergey Litvinov Jan 03 '17 at 19:20
1

after successful login you can rebind the menu

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

if(mainMenu!=null)

{ 
   mainMenu.DataBind();
}
Damith
  • 62,401
  • 13
  • 102
  • 153