【问题标题】:Can I add custom metadata types to razor views?我可以将自定义元数据类型添加到剃刀视图吗?
【发布时间】:2015-12-10 20:20:54
【问题描述】:

目前,我使用继承自 WebViewPage 的抽象类来为 MVC 项目中的 razor 视图提供功能。我们正在使用自定义登录类/解决方案。我的班级是这样的:

public abstract class AuthenticatedViewPageBase : WebViewPage
{
    private Login _user;

    protected override void InitializePage()
    {
        _user = Session["User"] as Login;
    }

    public bool HasPermission(Permissions permission)
    {
        return HasPermission(new List<Permissions> { permission });
    }
    public bool HasPermission(List<Permissions> permissions)
    {
        if (_user == null)
            _user = Session["User"] as Login;

        return _user != null && permissions.Any(thisPerm => _user.Permissions.Any(p => p.PermissionId == (int)thisPerm));
    }

    public bool HasPermission(List<Permissions> permissions, List<PermissionGroups> groups)
    {
        if (_user == null)
            _user = Session["User"] as Login;

        return _user != null &&
            (
                permissions.Any(thisPerm => _user.Permissions.Any(p => p.PermissionId == (int)thisPerm))
                ||
                groups.Any(thisPerm => _user.Permissions.Any(p => p.PermissionGroupId == (int)thisPerm))
            );
    }
}

我的观点是这样的:

@using PublicationSystem.Model.Enums
@model IEnumerable<PublicationSystem.Model.Profile>

@{
    ViewBag.Title = "Index";
    Layout = "~/Views/Shared/_LayoutSmBanner.cshtml";
}

@if (HasPermission(new List<Permissions>
{
    Permissions.userCreate
}))
{
    <p>
        @Html.ActionLink("Create New", "Create")
    </p>
}

这工作正常,但我希望能够清理它。我很想在我的 AuthenticatedViewPageBase 中有一个属性并创建一个增强的 ActionLink 以便我可以这样:

public abstract class AuthenticatedViewPageBase : WebViewPage
{
    //...
    public List<Permissions> ViewPermissions { get; set; }
    //...
}

索引.cshtml:

@using PublicationSystem.Model.Enums
@model IEnumerable<PublicationSystem.Model.Profile>

@{
    ViewBag.Title = "Index";
    Layout = "~/Views/Shared/_LayoutSmBanner.cshtml";
}

<p>
    @Html.SecureActionLink("Create New", "Create") // checks the @permissions metadata
</p>

创建.cshtml:

@model PublicationSystem.Model.Profile
@ViewPermissions new List<Permissions> {Permissions.userCreate} // This would be the custom 
     // property or metadata field I define in the abstract class

@{
    ViewBag.Title = "Create Profile";
    Layout = "~/Views/Shared/_LayoutSmBanner.cshtml";
}

@using (Html.BeginForm()) 
{
    @Html.AntiForgeryToken()
    ...
}

如果我能做到这一点,我的链接只需要询问目标视图需要哪些权限,而不必在每个链接周围包装 if 语句。

我可以向我的抽象 WebViewPage 类添加属性/属性来装饰我的示例中的视图吗?如果有怎么办?

自定义 ActionLink 是否可以“查看”目标 View 的元数据?

编辑:
我可能正在寻找这样的东西:

[Authorize(Permission="userCreate")]
public ActionResult Create()
{
    //...
}

但我希望成为权限级别,而不是角色级别,并且我希望根据具有必要权限的用户启用我的链接隐藏/可见。

【问题讨论】:

    标签: razor asp.net-mvc-5


    【解决方案1】:

    您可以使用自定义 HTML 助手轻松完成此操作。这是一个接受角色参数的示例SecureLink。如果用户已登录并具有指定的任何角色,则链接将可见,否则将不可见。

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Security.Principal;
    using System.Web.Mvc;
    using System.Web.Routing;
    
    public static class HtmlHelperExtensions
    {
        public static MvcHtmlString SecureLink(this HtmlHelper htmlHelper, string linkText, 
            string roles, string actionName)
        {
            return htmlHelper.SecureLink(linkText, roles, actionName, null, 
                new RouteValueDictionary(), new RouteValueDictionary());
        }
    
        public static MvcHtmlString SecureLink(this HtmlHelper htmlHelper, string linkText, 
            string roles, string actionName, object routeValues)
        {
            return htmlHelper.SecureLink(linkText, roles, actionName, null, 
                new RouteValueDictionary(routeValues), new RouteValueDictionary());
        }
    
        public static MvcHtmlString SecureLink(this HtmlHelper htmlHelper, string linkText, 
            string roles, string actionName, string controllerName)
        {
            return htmlHelper.SecureLink(linkText, roles, actionName, controllerName, 
                new RouteValueDictionary(), new RouteValueDictionary());
        }
    
        public static MvcHtmlString SecureLink(this HtmlHelper htmlHelper, string linkText, 
            string roles, string actionName, RouteValueDictionary routeValues)
        {
            return htmlHelper.SecureLink(linkText, roles, actionName, null, 
                routeValues, new RouteValueDictionary());
        }
    
        public static MvcHtmlString SecureLink(this HtmlHelper htmlHelper, string linkText, 
            string roles, string actionName, object routeValues, object htmlAttributes)
        {
            return htmlHelper.SecureLink(linkText, roles, actionName, null, 
                new RouteValueDictionary(routeValues), 
                HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes));
        }
    
        public static MvcHtmlString SecureLink(this HtmlHelper htmlHelper, string linkText, 
            string roles, string actionName, RouteValueDictionary routeValues, 
            IDictionary<string, object> htmlAttributes)
        {
            return htmlHelper.SecureLink(linkText, roles, actionName, null, 
                routeValues, htmlAttributes);
        }
    
        public static MvcHtmlString SecureLink(this HtmlHelper htmlHelper, string linkText, 
            string roles, string actionName, string controllerName, 
            object routeValues, object htmlAttributes)
        {
            return htmlHelper.SecureLink(linkText, roles, actionName, controllerName, 
                new RouteValueDictionary(routeValues), 
                HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes));
        }
    
        public static MvcHtmlString SecureLink(this HtmlHelper htmlHelper, string linkText, 
            string roles, string actionName, string controllerName, 
            RouteValueDictionary routeValues, IDictionary<string, object> htmlAttributes)
        {
            if (string.IsNullOrEmpty(linkText))
            {
                throw new ArgumentNullException("linkText");
            }
            if (!htmlHelper.IsInAnyRole(roles))
            {
                return MvcHtmlString.Create("");
            }
            return MvcHtmlString.Create(HtmlHelper.GenerateLink(
                htmlHelper.ViewContext.RequestContext, htmlHelper.RouteCollection, linkText, 
                null, actionName, controllerName, routeValues, htmlAttributes));
        }
    
        public static MvcHtmlString SecureLink(this HtmlHelper htmlHelper, string linkText, 
            string roles, string actionName, string controllerName, string protocol, 
            string hostName, string fragment, object routeValues, object htmlAttributes)
        {
            return htmlHelper.SecureLink(linkText, roles, actionName, controllerName, 
                protocol, hostName, fragment, new RouteValueDictionary(routeValues), 
                HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes));
        }
    
        public static MvcHtmlString SecureLink(this HtmlHelper htmlHelper, string linkText, 
            string roles, string actionName, string controllerName, string protocol, 
            string hostName, string fragment, RouteValueDictionary routeValues, 
            IDictionary<string, object> htmlAttributes)
        {
            if (string.IsNullOrEmpty(linkText))
            {
                throw new ArgumentNullException("linkText");
            }
            if (!htmlHelper.IsInAnyRole(roles))
            {
                return MvcHtmlString.Create("");
            }
            return MvcHtmlString.Create(HtmlHelper.GenerateLink(
                htmlHelper.ViewContext.RequestContext, htmlHelper.RouteCollection, linkText, 
                null, actionName, controllerName, protocol, hostName, fragment, routeValues, 
                htmlAttributes));
        }
    
        private static bool IsInAnyRole(this HtmlHelper htmlHelper, string roles)
        {
            var user = htmlHelper.ViewContext.HttpContext.User;
            if (user == null || user.Identity == null || !user.Identity.IsAuthenticated)
            {
                return false;
            }
            if (string.IsNullOrEmpty(roles))
            {
                return true;
            }
            return user.IsInAnyRole(SplitString(roles));
        }
    
        private static bool IsInAnyRole(this IPrincipal user, IEnumerable<string> roles)
        {
            foreach (var role in roles)
            {
                if (user.IsInRole(role))
                {
                    return true;
                }
            }
            return false;
        }
    
        private static string[] SplitString(string original)
        {
            if (String.IsNullOrEmpty(original))
            {
                return new string[0];
            }
    
            var split = from piece in original.Split(',')
                        let trimmed = piece.Trim()
                        where !String.IsNullOrEmpty(trimmed)
                        select trimmed;
            return split.ToArray();
        }
    }
    

    用法

    @Html.SecureLink("Logged In Users Will See This", "", "About", "Home")
    @Html.SecureLink("Admins and Managers Will See This", "Admin,Manager", "About", "Home")
    @Html.ActionLink("Everyone Will See This", "About", "Home")
    

    另一个选项是使用MvcSiteMapProvider's security trimming feature,它将自动使用AuthorizeAttribute(或其任何子类)来控制导航链接的可见性。

    完整披露:我是MvcSiteMapProvider 项目的主要贡献者。

    但是无论您如何完成工作,您都应该使用 MVC 的内置 IPrincipalIIdendity 接口和 AuthorizeAttribute,而不是重新发明轮子。您可以轻松扩展 AuthorizeAttribute 以使用任何权限方案。

    将这种额外的混乱添加到视图基类不是一个好方法。

    【讨论】:

    • 很好,但我仍然需要在链接中列出权限/角色。我希望链接查看视图,并让视图定义它自己的权限。
    • 授权不是视图的责任。这是authorzation filters 的责任(AuthorizeAttribute 就是其中之一)。隐藏链接的问题在于您实际上并没有为操作方法(公共端点)提供任何安全性。一次写入的方法是使用授权过滤器定义您的权限,然后让自定义 HTML 帮助程序使用该授权过滤器来确定链接是否应该可见。
    • 这就是MvcSiteMapProviderAuthorizeAttributeAclModule采取的方法。
    猜你喜欢
    • 1970-01-01
    • 2013-09-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-09-12
    • 2020-11-17
    相关资源
    最近更新 更多