【问题标题】:Security Trimmed Menu as Partial View安全修剪菜单作为局部视图
【发布时间】:2021-02-22 20:57:55
【问题描述】:

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

我将菜单创建为部分视图,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>
...

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

我知道我可以将菜单的那部分作为字符串从操作方法返回,但我想避免这种情况,因为菜单的其他部分会根据角色值呈现。我不想以一连串的片面观点告终。如果可能的话,我还想避免使用标签助手。我只是想根据 action 方法中的布尔值来切换部分视图中 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"];
}

【问题讨论】:

    标签: c# asp.net asp.net-mvc model-view-controller asp.net-mvc-5


    【解决方案1】:

    您的局部视图可以声明一个与之配套的模型。您可以使用@Html.RenderAction() 来代替使用@Html.Action() 将结果作为HTML 字符串返回

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

    这样,你如何获取用户角色状态的逻辑就被“封装”在了子action方法中,菜单部分是显示还是隐藏的逻辑被“封装”在了局部视图中。

    菜单.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>
    
        ...
    }
    

    【讨论】:

    • 感谢大卫,这很有效。我对布局文件中的 Html.RenderAction 调用的语法有一些问题。这最终奏效了:@{Html.RenderAction("CheckUserAdmin"); }...“CheckUserAdmin”是您称为“BuildUsersAndRoles”的操作的名称。
    • @IrishChieftain:对不起,这些都是我亲手写的。我想我会使用@Html.Action() 代替,因为我不喜欢将{;} 放在剃须刀代码的中间。我可能应该改为调用BuildUsersAndRolesMenuSection 操作。我不喜欢CheckUserAdmin 的名字。我不喜欢将其视为检查用户是否具有管理员角色的逻辑。构建一些局部视图确实是逻辑。
    • 好电话,我重命名了 ;-)
    【解决方案2】:

    如您所知 Html.Action 返回 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>
    

    【讨论】:

    • 感谢 Raju,将整个菜单作为 HTML 字符串发回是我的 B 计划。但我需要一些可以更好地扩展的东西。
    【解决方案3】:

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

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

        [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>
    

    【讨论】:

    • 我认为 OP 在 ASP.NET MVC 5 中还不是 ASP.NET Core MVC,但我不得不承认这确实是一种有趣的方法,我没有想到 :)
    • 是的,我发现标签丢失了。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-07-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多