I’m rendering a menu in my _Layout.cshtml file. One section of the menu should only render if the user is in the admin role. We are using custom roles in our DB. The Admin field comes back as a single character, “Y” or “N”. This was originally a legacy application and it’s being migrated to MVC 5. LINQ or Entity Framework is not being used.
I’m creating the menu as a partial view, Menu.cshtml:
<ul class="nav" ui-nav>
...
@if ((bool)ViewData["Admin"] == true)
{
<li>
<a href="#"><span>Users and Roles</span></a>
</li>
}
</ul>
My _Layout.cshtml file:
...
<aside id="aside" class="ui-aside">
@Html.Action("Menu")
</aside>
...
The controller’s action method is where I’m having the problem. My intention was to return a boolean value and simply toggle the rendering of the protected part of the menu based on that value in the partial view. Right now it’s rendering True or False in the browser where the menu should display, because that boolean value is being returned as a HTML string from the action method.
I know I could return that part of the menu as a string from the action method but I want to avoid this because there will be other sections of the menu rendered depending on role values. I don’t want to end up with a slew of partial views. If possible I also want to avoid tag helpers. I simply want to toggle the rendering of the HTML menu section in the partial view based on the boolean value in the action method.
Action method:
[ChildActionOnly]
public bool Menu()
{
using (connection...)
{
...
object adminObject = command.ExecuteScalar();
if (adminObject != null)
{
string admin = adminObject.ToString();
if (admin == "Y")
ViewData["Admin"] = true;
else
ViewData["Admin"] = false;
}
}
return (bool)ViewData["Admin"];
}
Answers:
Thank you for visiting the Q&A section on Magenaut. Please note that all the answers may not help you solve the issue immediately. So please treat them as advisements. If you found the post helpful (or not), leave a comment & I’ll get back to you as soon as possible.
Method 1
Your partial view can declare a model that goes with it. Instead of using @Html.Action() to return the result as HTML string, you can use @Html.RenderAction() to
- Run your logic to get the role status for the current logged-in user
- Build the view model for the partial view
- Return the partial view along with the view model directly to the response
In this way, your logic of how to get user role status is “encapsulated” in the child action method, and the logic of whether to show or hide the portion of the menu is “encapsulated” in the partial view.
Menu.cshtml
In your menu where you want to render the portion only available for admins, you use @Html.RenderAction() to execute buildUsersAndRoles action in menu controller (I just made them up):
<ul class="nav" ui-nav>
...
@Html.RenderAction("buildUsersAndRoles", "menu", new { area = "" })
...
</ul>
MenuController.cs
The basic idea is to render your logic to fetch necessary data you need, including the flag to show whether the user is admin or not. And based on those, you can determine what you want to pass back inside the view model.
My approach here is to pass NULL if the user is not admin since there is nothing to display, and pass the view model that contains users and roles information otherwise:
public class MenuController : Controller
{
...
[ChildActionOnly]
public ActionResult BuildUsersAndRoles()
{
using (connection...)
{
...
object adminObject = command.ExecuteScalar();
if (adminObject != null)
{
string admin = adminObject.ToString();
if (admin == "Y")
{
var vm = new UsersAndRolesViewModel
{
Users = new List<...>(),
Roles = new List<...>(),
...
};
return PartialView("_UsersAndRolesPartial", vm);
}
}
return PartialView("_UsersAndRolesPartial");
}
}
...
}
_UsersAndRolesPartial.cshtml
Finally, in the portion you only want to display when the user has admin role, you have a view model to work with:
@model ...UsersAndRolesViewModel
@if (Model != null)
{
<h5>Users and Roles</h5>
...
}
Method 2
As you know Html.Action return MvcHtmlString that is why its displays True or False
Are you happy to try something like this?
_Layout.cshtml
<li>@if(Html.Action("Menu").ToString().ToLower()=="true") {
@Html.Partial("_Menu")
}</li>
Partial View
<ul class="nav" ui-nav>
<li>
<a href="#"><span>Users and Roles</span></a>
</li>
</ul>
Method 3
I assume that the action of clicking your menu link will involve fetching a controller or page, using asp.net authorisation to control access.
So here’s my take for asp.net core (which doesn’t answer your question, but might be interesting for the future). Write a tag helper that deletes tags that the user is not authorised to see. That way you can re-use the same authorisation policy names to control visibility and permissions.
[HtmlTargetElement(Attributes = "policy")]
public class PolicyTagHelper : TagHelper
{
private readonly IAuthorizationService authService;
private readonly IHttpContextAccessor httpContextAccessor;
public PolicyTagHelper(IAuthorizationService authService, IHttpContextAccessor httpContextAccessor)
{
this.authService = authService;
this.httpContextAccessor = httpContextAccessor;
}
public string Policy { get; set; }
public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
{
if (!(await authService.AuthorizeAsync(httpContextAccessor.HttpContext.User, Policy)).Succeeded)
output.SuppressOutput();
}
}
Usage;
public void ConfigureServices(IServiceCollection services)
{
services.AddRazorPages(options => {
options.Conventions
.AuthorizePage("/UserRole", "Admin");
}
services.AddAuthorization(options =>
{
options.AddPolicy("Admin", policy => ...);
}
}
<ul class="nav" ui-nav>
<li policy="Admin">
<a href="#"><span>Users and Roles</span></a>
</li>
</ul>
All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0