2009-05-25 11 views
5

Widziałem wspaniałą odpowiedź na similar question, która wyjaśnia, dziedzicząc wszystkie kontrolery z nowej klasy bazowej ozdobionej własnym atrybutem ActionFilter, w jaki sposób można zastosować pewną logikę do żądań wszystkich do swojej witryny.Zastosuj filtr działania do każdego kontrolera tylko w jednej części witryny ASP.NET MVC?

Chciałbym znaleźć sposób na zrobienie tego w oparciu o obszar strony, którą odwiedza mój użytkownik.

Na przykład, Mam kontroler produkt pod kątem działania, ale chcę pozwolić, aby być wykorzystane do dwóch następujących adresów URL:

/produktu/Widok/321 - wyświetlacz ID produktu 321 do „normalnego 'users /Admin/Product/View/321 - używaj tego samego kontrolera View, ale wypluj dodatkowe funkcje dla moich administratorów.

Mogę przekazać "admin" jako parametr o nazwie "użytkownik" do mojej akcji widoku na moim kontrolerze produktu, aby wyświetlić dodatkowe informacje dla administratorów, metoda ta jest pokazana here. Ale wtedy musiałbym potwierdzić, że mój użytkownik mógł zobaczyć ten adres URL. Nie chcę ozdobić kontrolera produktu akcją ActionAttribute, która sprawdza uwierzytelnianie, ponieważ gdy nieuwierzytelnieni użytkownicy (i zalogowani administratorzy) przeglądają go w produkcie/Product/View/321, chcę, aby wszyscy zobaczyli widok standardowy.

Więc co ja jak do zrobienia, jest opisany poniżej w pseudo-kod:

Kiedy url w formacie „{userlevel}/{kontroler}/{działania}/{id} "Nazywa się, chciałbym zadzwonić do innego kontrolera, który wykonuje sprawdzanie autentyczności, a następnie" łańcuch "do pierwotnego {kontrolera} i przejść przez właściwości {akcja}, {id} i {userlevel}.

Jak to zrobić?

(Wiem, że przelot w celu sprawdzenia każdego połączenia z kontrolerem jest prawdopodobnie minimalny Chcę to zrobić w ten sposób, ponieważ może potrzebować później pewnych droższych rzeczy oprócz uwierzytelnienia użytkownika kontrole i wolałbym tylko kiedykolwiek uruchomić ten kod na niskim natężeniu ruchu administratora obszary mojej stronie. wydaje ma sensu robić te dla każdego użytkownika publicznym miejscu)

Odpowiedz

4

Na początku myślałem, że to może być tak proste, jak dodanie nowej trasy tak:

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

i wtedy kontroler coś takiego:

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

jednak niestety wszystko opcje dostępne w celu wykonania przekierowania (np. Redirect, RedirectToAction & RedirectToRoute) wszystkie wykonują przekierowania stylu 302. Zasadniczo oznacza to, że twój /Admin/Product/Whatever wykona &, a następnie powróci do przeglądarki, nakazując przekierowanie do /Product/Whatever w całkowicie nowym żądaniu, co oznacza, że ​​zgubiłeś swój kontekst. Nie wiem o czystym sposobie utrzymywania strony serwera przekierowania (np. O starym, Server.Transfer), najwyraźniej neither does the SO community ...

(oczywiście, to nie jest rozwiązanie, ponieważ nie rozwiązuje problemu problem, ale pomyślałem, że go tu umieścić tak, w przypadku można użyć idee w jakiś inny sposób)


Więc, co jest rzeczywiste rozwiązanie problemu wtedy? Innym pomysłem jest użycie ActionFiltera (tak, wiem, że powiedziałeś, że nie chcesz tego robić, ale myślę, że poniższe będą służyć twoim celom). Dodaj nową trasę tak:

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

a następnie dodać ActionFilter tak (które można zastosować do wszystkich żądań za pomocą obiektu sterownika bazowego jak wspomniano):

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

Więc chociaż jest za pomocą ActionFiltera, który będzie stosowany do wszystkich żądań, jedyną dodatkową pracą wykonaną w większości normalnych przypadków (tj. żądanie dla /Product/Whatever), jest pojedyncze sprawdzenie danych tego kawałka trasy (userLevel). Innymi słowy, powinieneś zobaczyć wydajność dla zwykłych użytkowników, ponieważ wykonujesz tylko pełne sprawdzenie autentyczności i dodatkową pracę administratora, jeśli poprosili o to przez /Admin/Product/Whatever.

+0

Bardzo dobrze zasłużony. Wiem, że byłem raczej konkretny w moim pytaniu, ale jestem zadowolony z tej odpowiedzi, wydaje się to być dobrym rozwiązaniem. Dzięki, Alconja! –

+0

Pozdrowienia, Neil, Odpowiadałem na to w zasadzie w ten sam sposób, używając kontrolera bazowego i nadpisując OnActionExecuting, ale z większą ilością szczegółów i niezmiennością przypadku itp. Dzień przed Alconją. –

+0

@Mike - Zgaduję, że rozumowanie było takie, że w momencie opublikowania mojego rozwiązania nie miałeś trochę kodu MapRoute, więc twoje rozwiązanie nie miało większego sensu ... i Neil prawdopodobnie nie widział twojej dzień później. Ponadto, jak już masz, nie możesz już przejść bezpośrednio do/Product/View/321 (który był jednym z wymagań podanych w pytaniu), ponieważ zinterpretuje to {rola = "Produkt", kontroler = "Widok" , action = "321", id = ""} & fail. – Alconja

1

1) nie może po prostu sprawdzasz rolę w widoku?

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

Można wykonać tę samą kontrolę w kontrolerze, jeśli konieczne jest ustawienie właściwości modelu widoku specyficznego dla administratora. W kontrolerze można wykonać dodatkową obróbkę tylko wtedy, gdy użytkownik jest już uwierzytelniony:

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); 
} 

Brakuje tylko o to przekierowanie do strony logowania, gdy administrator próbuje uzyskać dostęp do widoku bez uwierzytelniania.

2) Alternatywnie, jeśli chcesz ponownie użyć domyślnego widoku produktów z niektórych dodatkowych bitów można spróbować wykonać następujące czynności:

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); 
    } 
} 

UWAGA: Wolałbym rozwiązanie 1) w ciągu 2) ze względu na fakt, że trzeba utworzyć nową instancję ProductController i która wywołuje własny zestaw problemów, szczególnie podczas korzystania z IoC.

+0

Od pytania: "Chcę to zrobić w ten sposób, ponieważ mógłbym później potrzebować dodatkowych kosztownych elementów oprócz sprawdzania autentyczności użytkownika" - Zasadniczo powiem, że w każdym przypadku są to drogie kontrole na każdej stronie. – jfar

+0

Dzięki jfar :-) Myślę, że pytanie wydaje się o wiele prostsze na pierwszy rzut oka, ale są pewne szczegóły, które chcę aby ludzie wzięli pod uwagę. –

+0

Można wykonać tę samą kontrolę w kontrolerze i uniknąć dodatkowego przetwarzania, gdy nie jest to administrator. Po prostu zostaw kilka pól w modelu widoku null i używaj ich tylko wtedy, gdy administrator uzyskuje dostęp do widoku. –

1

Można rozwiązać to dość łatwo, tworząc klasę sterownika bazowego, który sprawdza poziom obsługi w OnActionExecuting i autoryzowany, ustawia właściwość rolę do tej samej wartości i dodaje „rolę” wpis do ViewData do użytku w widoku . Można to wykorzystać jako klasa bazowa dla wszystkich kontrolerów i wszyscy oni będą mieć dostęp do właściwości rola i wszystkie widoki będą mieć „rolę” wpis dodany do 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; 
    } 
} 

zmienić trasę domyślną w Global.asax.cs:

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

Teraz, w swoich działaniach kontrolera, sprawdź właściwość Rola dla np. "admin", a jeśli tak, dodaj wszelkie niezbędne dane widoku dla funkcji administracyjnych.

Render administrator UI przy użyciu partials i Państwa zdaniem, należy sprawdzić rolę i nazywają renderPartial:

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

To jest „poza pole” odpowiedź:

Co o wykorzystaniu bloku wtrysku polityki w entLib? Dzięki temu możesz stworzyć politykę, która uruchomi "wstępną metodę" w twoim działaniu. Twoja metoda wstępna mogłaby rozwiązać twój problem.

Powiązane problemy