将动作过滤器应用于ASP.NET MVC站点的一个部分中的每个控制器?

时间:2009-05-25 23:57:42

标签: asp.net-mvc

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

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

例如,我将有一个带有View操作的Product控制器,但我想允许它用于以下两个URL:

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

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

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

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

我该怎么做?

(我知道对每次调用控制器进行检查的头脑可能是最小的。我想这样做,因为我可能以后需要做一些更昂贵的东西除了用户身份验证检查,我更愿意只为我网站的低流量 admin 区域运行该代码。对于网站的每个公共用户来说似乎都没有意义

4 个答案:

答案 0 :(得分:4)

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

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的请求)执行的唯一额外工作是对该位路由数据的单一检查({ {1}})。换句话说,您应该真正看到普通用户的性能损失,因为您只是通过userLevel进行完整的身份验证和额外的管理工作。

答案 1 :(得分:1)

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时。

答案 2 :(得分:1)

您可以通过创建一个基本控制器类来轻松解决这个问题,该类在OnActionExecuting中检查用户级别,如果获得授权,则将Role属性设置为相同的值,并向ViewData添加“Role”条目以供在视图中使用。您可以将其用作所有控制器的基类,并且它们都可以访问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”,如果是,则为管理功能添加任何必要的视图数据。

使用部分渲染管理界面,并在视图中检查角色并调用RenderPartial:

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

答案 3 :(得分:0)

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

如何利用entLib中的策略注入块?有了它,您可以创建一个策略,在您的操作上运行“预先方法”。您的预处理方法也许可以解决您的问题。