Применить фильтр действий к каждому контроллеру только в одной части сайта ASP.NET MVC? - PullRequest
5 голосов
/ 26 мая 2009

Я видел отличный ответ на подобный вопрос , который объясняет, унаследовав все контроллеры из нового базового класса, украшенного вашим собственным атрибутом ActionFilter, как вы можете применить некоторую логику ко всем запросов на ваш сайт.

Я бы хотел найти способ сделать это, основываясь на области сайта, который посещает мой пользователь.

Например, у меня будет контроллер Product с действием View, но я хочу разрешить его использование для следующих двух URL:

/ Product / View / 321 - отображать идентификатор продукта 321 для «обычных» пользователей / Admin / Product / View / 321 - использовать тот же контроллер View, но выкладывать дополнительные функции для моих администраторов.

Я мог бы передать "admin" в качестве параметра с именем "user" в свое действие просмотра на контроллере продукта, чтобы показать дополнительную информацию для администраторов, метод для этого показан здесь . Но мне нужно было подтвердить, что моему пользователю разрешено просматривать этот URL. Я не хочу украшать свой контроллер Product атрибутом ActionAttribute, который проверяет аутентификацию, потому что, когда неаутентифицированные пользователи (и авторизованные администраторы) просматривают его в / Product / View / 321, я хочу, чтобы все они видели стандартное представление.

То, что я хотел бы сделать , описано ниже в псевдокоде:

Когда вызывается URL в формате "{userlevel} / {controller} / {action} / {id}", я хотел бы вызвать другой контроллер, который выполняет проверку подлинности, а затем 'chain 'в исходный {контроллер} и передайте свойства {action}, {id} и {userlevel}.

Как бы я это сделал?

(Я знаю, что накладные расходы на проверку каждого вызова контроллера, вероятно, минимальны. Я хочу сделать это таким образом, потому что позже мне может понадобиться сделать более дорогие вещи в дополнение к пользователю проверки подлинности, и я бы предпочел запускать этот код только для областей с низким трафиком admin моего сайта. Кажется, нет смысла делать это для каждого публичного пользователя сайта )

Ответы [ 4 ]

4 голосов
/ 02 июня 2009

Сначала я подумал, что это может быть так же просто, как добавить новый маршрут, подобный этому:

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 старого), очевидно, и сообщество SO ...

(очевидно, это не решение, поскольку оно не решает вашу проблему, но я решил поставить его здесь в любом случае, на случай, если вы сможете использовать идеи каким-либо другим способом)


Итак, каково же реальное решение проблемы? Другая идея заключается в использовании 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.

1 голос
/ 01 июня 2009

Вы можете решить эту проблему довольно просто, создав базовый класс контроллера, который проверяет уровень пользователя в OnActionExecuting и, если он авторизован, присваивает свойству Role то же значение и добавляет запись «Role» в ViewData для использования в представлении. Вы можете использовать его в качестве базового класса для всех ваших контроллеров, и все они будут иметь доступ к свойству Role, и для всех ваших представлений будет добавлена ​​запись "Role" в ViewData:

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>
1 голос
/ 26 мая 2009

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.

0 голосов
/ 02 июня 2009

Это ответ «нестандартно»:

Как насчет использования блока внедрения политики в entLib? С этим вы можете создать политику, которая будет запускать «предварительный метод» для вашего действия. Ваш предварительный метод может решить вашу проблему.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...