【问题标题】:Apply an action filter to every controller in only one part of an ASP.NET MVC site?仅在 ASP.NET MVC 站点的一部分中将操作过滤器应用于每个控制器?
【发布时间】:2010-10-28 20:22:45
【问题描述】:

我已经看到了对 similar question 的一个很好的回答,它解释了通过从一个用您自己的 ActionFilter 属性装饰的新基类继承所有控制器,您可以如何将一些逻辑应用于所有请求到您的网站。

我想根据我的用户正在访问的网站区域找到一种方法。

例如,我将有一个带有 View 操作的 Product 控制器,但我希望允许将其用于以下两个 url:

/Product/View/321 - 向“普通”用户显示产品 ID 321 /Admin/Product/View/321 - 使用相同的视图控制器,但为我的管理员用户提供额外的功能。

我可以将“admin”作为名为“user”的参数传递到我的产品控制器上的视图操作中,以向管理员显示额外信息,这样做的方法显示为 here。但是我需要做的是确认我的用户被允许查看该网址。我不想用检查身份验证的 ActionAttribute 来装饰我的 Product 控制器,因为当未经身份验证的用户(和登录的管理员)在 /Product/View/321 上查看它时,我希望他们都看到标准视图。

所以我喜欢做什么,下面用伪代码描述:

当调用格式为“{userlevel}/{controller}/{action}/{id}”的 url 时,我想调用另一个执行身份验证检查的控制器,然后 'chain ' 到原始的 {controller} 并传递 {action}、{id} 和 {userlevel} 属性。

我该怎么做?

(我知道对控制器的每次调用进行检查的开销可能很小。我想这样做是因为我以后可能需要做一些除了用户之外更昂贵的事情身份验证检查,我宁愿只为我网站的低流量 admin 区域运行该代码。似乎没有必要为网站的每个公共用户执行这些操作

【问题讨论】:

    标签: asp.net-mvc


    【解决方案1】:

    起初我认为这可能就像添加这样的新路线一样简单:

    routes.MapRoute(
        "Admin",
        "Admin/{*pathInfo}",
        new { controller="Admin", action="Index", pathInfo="" }
        );
    

    然后有一个类似这样的控制器:

    public class AdminController : Controller
    {
        public ActionResult Index(string pathInfo)
        {
            //Do admin checks, etc here....
            return Redirect("/" + pathInfo);
        }
    }
    

    但是,不幸的是,您可用于执行重定向的所有选项(即 Redirect、RedirectToAction 和 RedirectToRoute)都执行 302 样式重定向。基本上这意味着您的/Admin/Product/Whatever 将执行然后弹回浏览器,告诉它在一个全新的请求中重定向到/Product/Whatever,这意味着您已经丢失了上下文。我不知道保持重定向服务器端的干净方法(即像旧的Server.Transfer),显然是neither does the SO community...

    (显然,这是一个非解决方案,因为它不能解决你的问题,但我想我还是把它放在这里,以防你可以以其他方式使用这些想法)


    那么,这个问题的实际解决方案是什么? 另一个想法是使用 ActionFilter(是的,我知道您说过您不想这样做,但我认为以下内容会起作用你的目的)。像这样添加一条新路线:

    routes.MapRoute(
        "Admin",
        "Admin/{controller}/{action}/{id}",
        new { controller = "Home", action = "Index", id = "", userLevel = "Admin" }
        );
    

    然后像这样添加一个 ActionFilter(您可以通过您提到的基本控制器对象将其应用于所有请求):

    public class ExtendedAdminViewAttribute : ActionFilterAttribute
    {
        public override void OnActionExecuting(ActionExecutingContext filterContext)
        {
            object userLevel = filterContext.RouteData.Values["userLevel"];
            if (userLevel != null && userLevel.ToString() == "Admin")
            {
                //Do your security auth checks to ensure they really are an admin
                //Then do your extra admin logic...
            }
        }
    }
    

    因此,尽管它使用了适用于所有请求的 ActionFilter,但在大多数正常情况下(即对 /Product/Whatever 的请求)所做的唯一额外工作是对那位路由数据(userLevel )。换句话说,您应该真正看到普通用户的性能受到影响,因为如果他们通过/Admin/Product/Whatever 提出请求,您只会进行完整的身份验证检查和额外的管理工作。

    【讨论】:

    • 当之无愧。我知道我的问题相当具体,但我对这个答案很满意,这似乎是一个不错的解决方案。谢谢,Alconja!
    • 干杯,尼尔,我在 Alconja 前一天使用基本控制器并覆盖 OnActionExecuting 以基本相同的方式回答了这个问题,但具有更多细节和大小写不变性等。
    • @Mike - 我猜原因是在我发布我的解决方案时,你没有 MapRoute 位,所以你的解决方案没有多大意义...... & Neil一天后可能看不到您的编辑。此外,正如您现在所拥有的那样,您不能再直接转到 /Product/View/321 (这是问题中所述的要求之一),因为它将解释这个 {role="Product", controller="View" , action="321", id=""} & 失败。
    【解决方案2】:

    1) 你不能只检查视图中的角色吗?

    <% if (HttpContext.Current.User.IsInRole ("Administrator")) { %>
      // insert some admin specific stuff here
      <%= model.ExtraStuff %>
    % } %>
    

    如果您需要设置管理员特定的视图模型属性,您可以在控制器中执行相同的检查。在您的控制器中,您只能在用户已通过身份验证时进行额外处理:

    public ActionResult Details (int productId)
    {
      ProductViewModel model = new ProductViewModel ();
    
      if (User.Identity.IsAuthenticated && User.IsInRole ("Administrator"))
      {
        // do extra admin processing
        model.ExtraStuff = "stuff";
      }
    
      // now fill in the non-admin specific details
      model.ProductName = "gizmo";
    
      return View (model);
    }
    

    这里唯一缺少的是当管理员在未经身份验证的情况下尝试访问视图时重定向到您的登录页面。

    2) 或者,如果您想通过一些额外的位重用默认产品视图,您可以尝试以下操作:

    public class AdminController
    {
      [Authorize(Roles = Roles.Admin)]
      public ActionResult Details(int productId)
      {
        ProductController productController = new ProductController(/*dependencies*/);
    
        ProductViewModel model = new ProductViewModel();
        // set admin specific bits in the model here
        model.ExtraStuff = "stuff";
        model.IsAdmin = true;
    
        return productController.Details(productId, model);
      }
    }
    
    public class ProductController
    {
      public ActionResult Details(int productId, ProductViewModel model)
      {
        if (model == null)
        {
            model = new ProductViewModel();      
        }
    
        // set product bits in the model
    
        return Details(model);
      }
    }
    

    注意:我更喜欢解决方案 1) 而不是 2),因为您需要创建 ProductController 的新实例,并且会带来一系列问题,尤其是在使用 IoC 时。

    【讨论】:

    • 来自问题:“我想这样做,因为除了用户身份验证检查之外,我以后可能还需要做一些更昂贵的事情”-您的基本意思是在每个页面上进行这些昂贵的检查无论如何。
    • 谢谢 jfar :-) 乍一看,我认为这个问题似乎要简单得多,但我希望人们考虑一些细节。
    • 您可以在控制器中执行相同的检查,避免在不是管理员时进行额外处理。只需在视图模型中保留一些字段为空,并且仅在管理员访问视图时使用它们。
    • 再次托德,当我从 /Products/View 和 /Admin/Products/View 访问该控制器时,它会运行该代码。通过后者进行访问必须调用一个控制器(或一些逻辑)来强制验证,然后再将执行传递给 Products。对前一个 url 的调用甚至不能调用这个额外的逻辑。
    • @Neil 我明白你在问什么。我只是提出一个不同的解决方案,我在其他几个应用程序中看到过,它解决了同样的问题。我添加了另一个更接近您想要的解决方案,但它存在一些问题,这就是我建议采用不同方法的原因。
    【解决方案3】:

    您可以通过创建一个基本控制器类来相当容易地解决这个问题,该类检查 OnActionExecuting 中的用户级别,如果获得授权,则将 Role 属性设置为相同的值,并将“Role”条目添加到 ViewData 以在视图中使用。您可以将其用作所有控制器的基类,它们都可以访问 Role 属性,并且您的所有视图都将在 ViewData 中添加一个“Role”条目:

    public abstract class BaseController : Controller
    {
        public string Role { get; protected set; }
    
        protected override void OnActionExecuting( ActionExecutingContext filterContext )
        {
            base.OnActionExecuting( filterContext );
            Role = string.Empty;
            string role = string.Empty;
            object value;
            if ( filterContext.RouteData.Values.TryGetValue( "role", out value ) )
                role = value as string ?? string.Empty;
            if ( filterContext.HttpContext.User.IsInRole( role ) )
                Role = role.ToLowerInvariant();
            ViewData[ "role" ] = Role;
        }
    }
    

    更改 Global.asax.cs 中的默认路由:

    routes.MapRoute(
        "Default",                                              
        "{role}/{controller}/{action}/{id}",                           
        new { role = "", controller = "Home", action = "Index", id = "" }  
    );
    

    现在,在您的控制器操作中,检查 Role 属性,例如“admin”,如果是这样,为管理功能添加任何必要的视图数据。

    使用局部渲染您的管理 UI,并在您的视图中检查角色并调用 RenderPartial:

    <% if ( Equals( ViewData[ "role" ], "admin" ) )
            Html.RenderPartial( "_AdminFunctions" ); %>
    <p>
        This is the standard, non Admin interface...
    </p>
    

    【讨论】:

      【解决方案4】:

      这是一个“开箱即用”的答案:

      如何利用 entLib 中的策略注入块?有了它,您可以创建一个对您的操作运行“预方法”的策略。您的 pre-method 或许可以解决您的问题。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2015-01-02
        • 1970-01-01
        • 2010-11-23
        • 1970-01-01
        • 1970-01-01
        • 2023-03-07
        • 1970-01-01
        • 2010-09-14
        相关资源
        最近更新 更多