0

I'm an old ASP.NET Web Forms developer that is really rusty and trying to give MVC, Razor and Bootstrap a go.

In my _Layout.cshtml file I have a Hello World menu. I would like to enable/disable menu items based upon the security of the login user.

Since the menu is being created with server side logic and I am getting determining whether a menu item is available based on info from the Active Directory and that is also on the server, I am thinking that I could have a server side common routine enable or disable menu items based on membership or lack of membership to a global group.

Taking the default auto generated MVC _Layout.cshtml file and simply adding a dropdown "Tools" item, how can I pro grammatically disable a menu as described above? I'm thinking that I'd like to programmatically add a "Disabled" class and set the href property to # to make it unclickable if the user was not allowed to access that function.

 <!DOCTYPE html>
    <html>
    <head>
        <meta charset="utf-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>@ViewBag.Title - My ASP.NET Application</title>
        @Styles.Render("~/Content/css")
        @Scripts.Render("~/bundles/modernizr")
    </head>
    <body>
        <div class="navbar navbar-inverse navbar-fixed-top">
            <div class="container">
                <div class="navbar-header">
                    <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
                        <span class="icon-bar"></span>
                        <span class="icon-bar"></span>
                        <span class="icon-bar"></span>
                    </button>
                    @Html.ActionLink("Application name", "Index", "Home", new { area = "" }, new { @class = "navbar-brand" })
                </div>
                <div class="navbar-collapse collapse">
                    <ul class="nav navbar-nav">
                        <li>@Html.ActionLink("Home", "Index", "Home")</li>
                        <li>@Html.ActionLink("About", "About", "Home")</li>
                        <li>@Html.ActionLink("Contact", "Contact", "Home")</li>
                        <li class="dropdown">
                            <a href="#" class="dropdown-toggle" data-toggle="dropdown">Tools<span class="caret"></span></a>
                            <ul class="dropdown-menu" role="menu">
                                <li class="disabled">
                                    <a href="#">Show me the Contact Page</a>
                                </li>
                                <li role="separator" class="divider"></li>
                                <li>
                                    <a href="@Url.Action("About", "Home")">Show me the About Page!</a>
                                </li>
                            </ul>    
                        </li>
                    </ul>                                                          
                    <p class="nav navbar-text navbar-right">Hello, @User.Identity.Name!</p>
                </div>
            </div>
        </div>
        <div class="container body-content">
            @RenderBody()
            <hr />
            <footer>
                <p>&copy; @DateTime.Now.Year - My ASP.NET Application</p>
            </footer>
        </div>

        @Scripts.Render("~/bundles/jquery")
        @Scripts.Render("~/bundles/bootstrap")
        @RenderSection("scripts", required: false)
    </body>
    </html>

Sorry for being a noob

1 Answer 1

1

First I would start by putting your nav in it's own partial view since it will probably have logic in it that just apply's to the nav so we can keep our _Layout clean and less confusing.

_Layout

...
<div class="navbar-collapse collapse">
    @Html.Partial("_MainNav")
</div>
...

In that partial you can use Razor to check if the user is in a role or not and build your nav that way:

_MainNav

<ul class="nav navbar-nav">
    <li>@Html.ActionLink("Home", "Index", "Home")</li>
    <li>@Html.ActionLink("About", "About", "Home")</li>
    <li>@Html.ActionLink("Contact", "Contact", "Home")</li>
    <li class="dropdown">
        <a href="#" class="dropdown-toggle" data-toggle="dropdown">Tools<span class="caret"></span></a>
        <ul class="dropdown-menu" role="menu">
            @if (User.IsInRole("..."))
            {
                <li>
                    <a href="@Url.Action("Index", "Admin")">Admin Page</a>
                </li>
            }
            else
            {
                <li>
                    <a href="#" class="disabled">Admin Page</a>
                </li>
            }
        </ul>
    </li>
</ul> 

You don't really need that else condition but you said you want to add a disabled class and set the href to #.

Another approach would to make a controller action that would build your menu dynamically and return it as a HTML string to your view. i.e. @Html.Action("Home", "BuildNav") in your _Layout nav area.

A third way might be to create your own HtmlHelper to create action links based on your custom role access logic. It might look something like this:

_Layout

...
<li>@Html.RestrictedActionLink("Admin", "Index", "Admin")</li>
...

HtmlHelper

public partial class HtmlHelpers {
    public static MvcHtmlString RestrictedActionLink(this HtmlHelper htmlHelper, string linkText, string actionName,
            string controllerName, RoleAccessLevels accessLevel, object routeValues = null, object htmlAttributes = null) {

            if (//check is user has access) 
               return MvcHtmlString.Create(string.Empty);

            var routeValuesDict = new RouteValueDictionary(routeValues);
            var customAttributes = HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes);

            var currentAction = htmlHelper.ViewContext.RouteData.GetRequiredString("action");
            var currentController = htmlHelper.ViewContext.RouteData.GetRequiredString("controller");

            if (actionName == currentAction && controllerName == currentController) {
                if (!customAttributes.ContainsKey("class"))
                    customAttributes.Add("class", "active");
                else 
                    customAttributes["class"] = string.Format("{0} active", customAttributes["class"]);
            }

            return MvcHtmlString.Create(string.Format("<li>{0}</li>", htmlHelper.ActionLink(linkText, actionName, controllerName, routeValuesDict, customAttributes).ToHtmlString()));
        }
}
Sign up to request clarification or add additional context in comments.

3 Comments

Thanks for your reply. My knee jerk reaction is that the if-else approach is tedious and quickly becomes unmanagable if you want to dynamically control multiple attributes dynamically because the number of permutations will grow quickly. If a hard code menu already exists, I should be able to just tap into it an tweak attributes as needed without having to rewrite it as a dynamic string.
Another approach which I have used is to make you own HtmlHelper called like RestrictedActionLink and put your access logic in that helper. In the view it would look like <li>@Html.RestrictedActionLink("Admin", "Index", "Admin")</li>. I can update my answer with an example.
It looks like I have to conditionally add a class="disabled" attribute to the enclosing <li> tag which surrounds the ActionLink, not the link itself, which suggests to me that am not enhancing the ActionLink helper but something new that includes a enclosing parent list item tag. Not sure how to keep it clean. I'd also like to avoid concatenating strings when building the list item tag with an conditional disabled attribute. is there a way to create an HTML object with a specified tag that has an attributes collection?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.