部分视图的安全修整菜单

Security Trimmed Menu as Partial View

我正在 _Layout.cshtml 文件中呈现菜单。只有当用户处于管理员角色时,菜单的一部分才应该呈现。我们在我们的数据库中使用自定义角色。 Admin 字段以单个字符“Y”或“N”形式返回。这最初是一个遗留应用程序,正在迁移到 MVC 5。未使用 LINQ 或 Entity Framework。

我将菜单创建为局部视图,Menu.cshtml:

<ul class="nav" ui-nav>
    ...

    @if ((bool)ViewData["Admin"] == true)  
    {
         <li>
             <a href="#"><span>Users and Roles</span></a>
         </li>
    }
</ul>

我的 _Layout.cshtml 文件:

...
    <aside id="aside" class="ui-aside">
        @Html.Action("Menu")
    </aside>
...

控制器的操作方法是我遇到问题的地方。我的意图是 return 一个布尔值,并根据局部视图中的该值简单地切换菜单受保护部分的呈现。现在它在应该显示菜单的浏览器中呈现 True 或 False,因为该布尔值正在 returned 作为来自 action 方法的 HTML 字符串。

我知道我可以 return 菜单的那部分作为操作方法中的字符串,但我想避免这种情况,因为菜单的其他部分将根据角色值呈现。我不想以大量的局部视图结束。如果可能的话,我也想避免使用标签助手。我只是想根据操作方法中的布尔值切换部分视图中 HTML 菜单部分的呈现。

操作方法:

[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"];
}

如您所知 Html.Action return MvcHtmlString 这就是它显示 TrueFalse

的原因

你喜欢这样的尝试吗?

_Layout.cshtml

 <li>@if(Html.Action("Menu").ToString().ToLower()=="true") { 
    @Html.Partial("_Menu")
}</li>

局部视图

<ul class="nav" ui-nav>
    <li>
        <a href="#"><span>Users and Roles</span></a>
    </li>
</ul>

您的局部视图可以声明一个与之配套的模型。而不是使用 @Html.Action() 到 return 结果作为 HTML 字符串,你可以使用 @Html.RenderAction()

  1. 运行 您获取当前登录用户角色状态的逻辑
  2. 为局部视图构建视图模型
  3. Return 部分视图连同视图模型直接响应

这样,你的如何获取用户角色状态的逻辑就“封装”在了child action方法中,而是否显示或隐藏菜单部分的逻辑则“封装”在了分部视图中.

Menu.cshtml

在您想要呈现仅供管理员使用的部分的菜单中,您使用 @Html.RenderAction()menu 控制器中执行 buildUsersAndRoles 操作(我刚刚编造的):

<ul class="nav" ui-nav>
    ...
    @Html.RenderAction("buildUsersAndRoles", "menu", new { area = "" })
    ...
</ul>

MenuController.cs

基本思想是呈现逻辑以获取所需的必要数据,包括显示用户是否为管理员的标志。基于这些,您可以确定要在视图模型中传回的内容。

我这里的做法是,如果用户不是管理员,则传递 NULL,因为没有可显示的内容,否则传递包含用户和角色信息的视图模型:

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

最后,在您只想在用户具有管理员角色时显示的部分中,您可以使用视图模型:

@model ...UsersAndRolesViewModel

@if (Model != null)
{
    <h5>Users and Roles</h5>

    ...
}

我假设点击菜单的操作 link 将涉及获取控制器或页面,使用 asp.net 授权来控制访问。

所以这是我对 asp.net 核心的看法(它没有回答你的问题,但可能对未来很有趣)。编写一个标签助手来删除用户无权查看的标签。这样您就可以重复使用相同的授权策略名称来控制可见性和权限。

    [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();
        }
    }

用法;

        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>