Artykuł

freeimages.com freeimages.com
cze 20 2014
0

Wprowadzenie do filtrów w ASP.NET MVC

Gdy pierwszy raz usłyszałem o filtrach w MVC pomyślałem sobie WOW. Ten mechanizm naprawdę zmienia reguły gry. Minęło już trochę czasu od tamtej chwili i wciąż jestem zdania, że jest to jeden z ważniejszych elementów całego frameworka, który świetnie wpisuje się w rozszerzalność tej platformy.

Filtry mają praktycznie same zalety:

  • Unikamy powtórzeń kodu, ponieważ określony fragment kodu znajduje się w jednym miejscu
  • Zapewniamy rozszerzalność kodu - filtry funkcjonują według normalnych zasad programowania zorientowanego obiektowo
  • Postępujemy w myśl zasady Separation of concerns wykluczając konieczną logikę poza akcje kontrolera

Zalet jest oczywiście znacznie więcej, ale wydaje mi się że to na początek wystarczy, by choćby zacząć interesować się tym tematem. A więc pytam Was, czy chcecie zgłębić filtry MVC? Jeśli tak, to zapraszam dalej.

Czym tak naprawdę jest filtr?

Filtr tak naprawdę jest klasą, która musi dziedziczyć po klasie bazowej FilterAttribute (lub jej pochodnych - konkretnych implementacjach określonych typów filtrów). W zależności od rodzaju filtru, zachodzi także konieczność implementacji odpowiedniego interfejsu (oczywiście nie trzeba tego robić gdy rozszerzamy istniejący filtr).

Z pewnością zauważyliście również słówko kluczowe Attribute. Uprzedzając Wasze ewentualne pytania - tak filtry są w świecie MVC nakładane poprzez atrybuty (i nie tylko...), ale o tym więcej napiszę później.

Rodzaje filtrów

ASP.NET MVC obsługuje 5 różnych rodzajów filtrów. Każdy z nich posiada dedykowany interfejs, który programista winien zaimplementować w przypadku chęci stworzenia własnego filtru. Ponadto w przypadku 4 na 5 filtrów, istnieje możliwość rozszerzenia istniejącej klasy takiego atrybutu. Jest to bardzo wygodna opcja np. w przypadku implementacji autoryzacji. Autoryzacja sama w sobie jest bardzo złożonym tematem, więc dziedzicząc z istniejącego atrybutu, zmniejszamy ryzyko popełnienia błędu. Poniżej znajduje się tabela, w której zaprezentowałem podstawowe informacje na temat atrybutów:

Rodzaj filtru Interfejs Domyślna implementacja Opis
Uwierzytelnianie (Authentication) IAuthenticationFilter Brak Uruchamiany przed wszystkimi innymi filtrami. Może być również aktywowany po zakończeniu filtru akcji
Autoryzacja (Authorization) IAuthorizationFilter AuthorizeAttribute Uruchamiany zaraz po pierwszej iteracji filtrów uwierzytelniania oraz przed pozostałymi filtrami oraz samą akcją
Akcja (Action) IActionFilter ActionFilterAttribute Uruchamiany przed (OnActionExecuting) oraz po (OnActionExecuted) konkretnej akcji kontrolera
Rezultat (Result) IResultFilter ActionFilterAttribute Uruchamiany przed zwróceniem (OnResultExecuting) oraz po zwróceniu (OnResultExecuted) rezultatu wybranej akcji kontrolera
Wyjątek (Exception) IExceptionFilter HandleErrorAttribute Wywoływany tylko w sytuacji gdy w innym filtrze, bądź też w samej akcji zostanie rzucony nieobsłużony wyjątek

Jak widać, filtry zostały dosyć dobrze obmyślone i można dla nich znaleźć sporo użytecznych zastosowań. To na co warto zwrócić uwagę w powyższej tabelce to fakt, że dwa rodzaje filtrów posiadają swoją implementację w tej samej klasie. Mowa oczywiście o filtrach akcji i rezultatu.

Implementując określony interfejs nie musicie jednak wypełniać wszystkich jego metod - wypełniacie tylko te, które faktycznie mają coś zrobić. Pozostałe muszą pozostać puste. Tutaj ważna UWAGA. Pod żadnym pozorem do treści takich metod nie wstawiajcie wyjątku NotImplementedException - każda z metod interfejsu jest zawsze wykonywana!

Użycie filtrów w praktyce

Użycie filtrów jest w praktyce bardzo proste. Wystarczy umieścić odpowiedni atrybut przed kontrolerem/akcją. Oczywiście należy pamiętać o pominięciu słówka kluczowego Attribute:

[Custom]
public class TestController : Controller
{
    public ActionResult Index()
    {
        return View();
    }

    [Authorize]
    public string About()
    {
        return "Gratulację. Uzyskałeś dostęp do tajnej metody!";
    }
}

W powyższym przykładzie mamy zastosowane dwa atrybuty. Atrybut Custom (mój wyimaginowany atrybut z klasy CustomAttribute) został przypisany całemu kontrolerowi. Oznacza to że działania w nim zdefiniowane zostaną wykonane przy wywołaniu dowolnej akcji znajdującej się w tym kontrolerze. Dodatkowo metoda About została naznaczona dodatkowym atrybutem i aby z niej skorzystać, użytkownik musi się autoryzować w naszej aplikacji.

Parametry atrybutów

Stosując parametry często możemy przekazywać pewne określone wartości sterujące, które w tym przypadku występują jako parametry. Przykładowo, dla atrybutu autoryzacji będą to np. nazwy użytkowników którzy mają dostęp do określonej metody, bądź też nazwy konkretnych ról. Parametry muszą być nazwane i musimy je przekazać w postaci klucz=wartość:

public class TestController : Controller
{
    [Authorize(Roles="admin")]
    public ActionResult Index()
    {
        return View();
    }

    [Authorize(Users="jurek")]
    public string About()
    {
        return "Gratulację. Uzyskałeś dostęp do tajnej metody!";
    }
}

W tym przypadku umożliwiliśmy dostęp do akcji Index wszystkim użytkownikom posiadającym rolę admin. Do podstrony About dostać się może tylko użytkownik jurek.

Kolejność wykonywania filtrów

Kolejność wykonywania filtrów zależy przede wszystkim od ich rodzaju co dość dokładnie opisałem w tabelce we wcześniejszej części tekstu. Dla uproszczenia przedstawię to jeszcze raz w postaci listy numerowanej:

  1. IAuthenticationFilter metoda OnAuthentication
  2. IAuthorizationFilter metoda OnAuthorization
  3. IActionFilter metoda OnActionExecuting
  4. // Akcja
  5. IActionFilter metoda OnActionExecuted
  6. IAuthenticationFilter metoda OnAuthenticationChallenge
  7. IResultFilter metoda OnResultExecuting
  8. // Zwrócenie rezultatu / widoku
  9. IResultFilter metoda OnResultExecuted

Z naturalnych przyczyn pominąłem filtry związane z wyjątkami. Te mogą się pojawić w dowolnym momencie powyższego procesu;-)

Powyższa lista omawia kolejność według rodzaju filtru. Co jednak w sytuacji gdy przy określonej metodzie mamy dwa filtry jednego rodzaju? W takiej sytuacji zadziała przypadek i kolejność będzie stricte losowa. Na szczęście sami możemy określić porządek wykonywania filtrów wykorzystując nazwany parametr Order:

public class TestController : Controller
{
    public ActionResult Index()
    {
        return View();
    }

    [Authorize(Roles="admin",Order=1)]
    [Authorize(Users="jurek",Order = 2)]
    public string About()
    {
        return "Gratulację. Uzyskałeś dostęp do tajnej metody!";
    }
}

Oczywiście ten przykład jest absurdalny, ponieważ w istocie temat dotyczy tego samego parametru i obie wartości możemy połączyć w jednym wywołaniu, ale w tym przypadku chodziło o samą prezentację możliwości sortowania.

Filtry globalne

Istnieją pewne przypadki gdy chcemy by określony filtr działał w każdym miejscu naszej aplikacji. W takiej sytuacji przy ręcznym podpinaniu filtra do każdego kontrolera, łatwo jest o błąd, o przeoczenie. Pewnym lekarstwem na ten problem mogą być filtry globalne, które pozwalają na użycie określonego filtru przy każdej akcji zdefiniowanej w aplikacji. Takie rozwiązanie bardzo często wykorzystywane jest choćby do obsługi błędów.

Rejestracja globalnego filtru przebiega niejako dwuetapowo. Po pierwsze tworzymy klasę FilterConfig w katalogu App_Start:

public class FilterConfig
{
    public static void RegisterGlobalFilters(GlobalFilterCollection filters)
    {
        filters.Add(new HandleErrorAttribute());
    }
}

Wewnątrz tej klasy oraz statycznej metody, dodajemy filtr do kolekcji filtrów globalnych. Warto tutaj zwrócić uwagę, że w tym przypadku należy podać pełną kwalifikowaną nazwę - inaczej program się nie skompiluje. Tworzymy w końcu nowy obiekt istniejącego bytu;-)

Klasa FilterConfig to tylko przykład. W praktyce może ona mieć zupełnie inną nazwę oraz budowę. Przedstawioną powyżej strukturę bardzo często można jednak znaleźć w różnych przykładach oraz projektach.

Klasa FilterConfig sama w sobie nic nam nie daje. Do pełni szczęścia musimy ją wywołać w pliku Global.asax:

public class MvcApplication : System.Web.HttpApplication
{
    protected void Application_Start()
    {
        FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
        // pozostałe konstrukcje
    }
}

Po ponownym uruchomieniu aplikacji, nasz globalny filtr będzie zwarty i gotowy do pracy.

To tyle w pierwszej części mini-cyklu o filtrach. W kolejnych wpisach postaram się powiedzieć trochę więcej o poszczególnych typach filtrów.

Materiały

Cykl

Data ostatniej modyfikacji: 02.08.2014, 13:22.

Podoba Ci się ten wpis? Powiedz o tym innym!

Send to Kindle

Komentarze

blog comments powered by Disqus