allowing/disallowing asp:menu links based on user roles

The asp:menu control is pretty useful for making  menus and easy 'popout' links, but what if you don't want certain users to be able to navigate to some pages?

If you are using a menu control, it is likely because you want stuff to be easy.  But if you have certain links in there that you do not want a user to be able to click if they are not in certain roles, it is not that obvious what you can do to implement that.  First think you will notice is that MenuItems do not have IDs, so you can't access them in the 'normal' asp.net way.

 

What you have to do is to access them through Menu.FindItem(), and the then you can manipulate them.  But then there is the question, if they do not have IDs, how do I find the item?  The items are looked up by their Text property in a hierarchical manner.  To access things in a hierarchy, you need to first set your Menu's PathSeperator property; set this to a delimiter (I use a comma) that you will use to seperate levels.  Here is a sample menu with this implemented:

<asp:Menu ID="mnu" runat="server" PathSeparator=",">
    <Items>
        <asp:MenuItem Text="home" NavigateUrl="~/Default.aspx"  />
        <asp:MenuItem Text="A" NavigateUrl="#">
            <asp:MenuItem Text="A-1" NavigateUrl="#" />
            <asp:MenuItem Text="A-2" NavigateUrl="#" />
        </asp:MenuItem>
        <asp:MenuItem Text="B" NavigateUrl="#">
            <asp:MenuItem Text="B-1" NavigateUrl="#" />
            <asp:MenuItem Text="B-2" NavigateUrl="#" />
            <asp:MenuItem Text="B-3" NavigateUrl="#" />
        </asp:MenuItem>
    </Items>
</asp:Menu>

Once that is done, you now can simply iterate through the items (that you already put in a list, and disable those you do not want the non-admin user to have access to:

 string[] adminOnlyItems = {"A,A-1", "A,A-2", "B", "B-3"};
if (!User.IsInRole("admins"))
{
    foreach (string s in adminOnlyItems)
    {
        MenuItem mi = mnu.FindItem(s);
        mi.Enabled = false;
    }
}
Notice how the items are written out in a hierarchical string, written like this: <Level 1 Item> <delimeter> <Level 2 Item> <delimeter> <Level 3 Item> and so on with as many or as few levels as you are looking for.  Also notice that "B-3" is unnecessary in the adminOnlyItems array, as disabling "B" disables that whole path of links, anythign under "B" will now be unreachable.  Here is a working example (you must have roles enabled and an "admin" role for this to work):


Moving ASP.NET roles/membership from Test/Local to Production

ASP.NET roles/membership providers are incredibly simple and convenient, but if you move you site straight to a production environment (SQL Server, etc.), it's probably not going to work without a couple changes

Believe it or not, I had never needed to use ASP.NET roles in any large production environment.  But recently at work, that changed.  Everything was working great on my local machine, couldn't be smoother, but then I moved everything to our web server with a SQL 2005 backend... no worky.

 

When you enable roles in ASP.NET within VS/VWD it automatically makes an MDF that resides in your App_Data folder.  And most likely, your machine is not running full-fledged SQL, just Express.  Which is just fine, but when you migrate to the production environment, even if you bring the MDF with you and place it in your App_Data file, your roles will not work.

 

What VS is forgetting to tell you is that your membership provider is using an 'invisible' ConnectionString called 'LocalSqlServer' and that that connectionString uses SQL Express and Windows credentials to access it.  Which is why, in most cases, that that connectionString will not work in your production setting.  What you need to do is two things:

 

  1. Move your MDF to you production SQL Server instance
  2. Make sure your role provider references that instance by changing the connection string

 

This is very easy to do, and here is how you do it:

Move your MDF to your Production SQL

  1. Copy your ASPNETDB.mdf file from your App_Code folder to Program Files\Microsoft SQL Server\MSSQL.1\MSSQL\Data on your SQL machine
  2. Open SQL Server Management Studio and connect to your database
  3. Right click on the 'Databases' folder in the navigation pane, click 'Attach...'
  4. Click the 'Add...' button and navigate to your ASPNETDB.mdf on that machine, click 'OK'
  5. Click 'OK'
  6. You now have it attached and ready to use, I recommend renaming it as the name will be very long (I renamed it simply ASPNETDB.mdf - I'm creative)

Make sure your program references the new database location

  1. Go into your web.config file for your program, locate the connectionStrings section
  2. Add the following 2 lines:
    <remove name="LocalSqlServer"/>
    <add name="LocalSqlServer" connectionString="CONNECTION_STRING_TO_MEMBERSHIP_DB" providerName="System.Data.SqlClient"/>
  3. Upload and you should be all set

The important thing here is that you cleared out the 'invisible' connectionString that .Net uses and replaces it with one that is going to run off of your newly implemented database.

Simplified ASP.Net User Role Management Interface

Messing around with .net roles recently and I came up with a completely portable, simple user management tool

As most of my stuff is, this is not a revolution in coding, but it is useful.  This is a simple page that allows you to add and delete members from whatever roles you have defined for your asp.net project on your live project, no need for the Web Management tool (you still have to manage roles with it though).  The file is completely independant of any other code, so you don't need to do anything to get it to work, just stick in the same directory as your application and use it - customize it if you please.