Artykuł

freeimages.com freeimages.com
lip 05 2014
0

Filtry akcji w ASP.NET MVC

W końcu przyszła pora na czwarty wpis poświęcony filtrom. W poprzednich trzech odcinkach zrobiłem teoretyczne wprowadzenie, a także opowiedziałem Wam o filtrach uwierzytelniania i autoryzacji. Dziś przyszła pora na filtry akcji, które pozornie nie mają żadnych interesujących zastosowań, ale na szczęście pozory często mylą. Do czego możemy zatem je wykorzystać? Do wielu różnych rzeczy, ale bardzo często są wykorzystywane do optymalizacji oraz do zwracania odpowiednich widoków w zależności od rodzaju żądania.

IActionFilter

Stworzyć własny filtr akcji możemy zasadniczo na dwa sposoby:

  • Poprzez rozszerzenie istniejącej klasy ActionFilterAttribute
  • Poprzez rozszerzenie klasy FilterAttribute oraz implementację interfejsu IActionFilter

W większości przypadków to właśnie to drugie rozwiązanie jest lepszym wyborem, dlatego też jego będę się trzymał w dzisiejszym wpisie. W tym przypadku nie ma bowiem mowy o żadnej skomplikowanej logice, która kryła by się za tym filtrem, tak jak miało to miejsce np. w filtrach autoryzacji.

Wróćmy zatem do interfejsu IActionFilter wykorzystywanego do tworzenia filtrów akcji. Zasadniczo zawiera on dwie metody, ale jak z pewnością pamiętacie z wcześniejszych wpisów, w przypadku filtrów wypełniacie treścią tylko te metodę(y), która Was naprawdę interesuje (pamiętajcie o nierzucaniu wyjątkiem NotImplementedException - nawet jeśli nie wypełnicie określonej metody, to i tak zostanie ona wywołana w ramach flow).

public interface IActionFilter {
    void OnActionExecuting(ActionExecutingContext filterContext);
    void OnActionExecuted(ActionExecutedContext filterContext);
}

Standardowo obie metody przyjmują pewien określony kontekst. Nie będę się w tym tekście rozwodził na temat możliwości oferowanych przez oba obiekty, zamiast tego odeślę Was wprost do dokumentacji tutaj i tutaj.

Pokrótce warto również nadmienić kiedy wywoływane są obie metody. Pierwsza z nich wykonywana jest przed realizacją samej akcji, druga tuż po zakończeniu tej samej akcji (o tym jak wygląda cały proces uwzględniający również inne filtry przeczytacie tutaj). Warto również nadmienić, że przy okazji jednej akcji, filtr akcji występuje w jednej instancji. Innymi słowy możemy przenosić informacje pomiędzy pierwszą, a drugą metodą wykorzystując pola klasy. Dzięki temu łatwo jest np. policzyć czas wykonywania całej akcji;-)

Przykładowy filtr

Na potrzeby tekstu stworzyłem przykładowy filtr, który wylicza czas przetwarzania akcji i dopisuje go do rezultatu akcji (tak naprawdę to wynik działania tego filtru pojawia się na samej górze strony). Rezultat działania tego filtru widoczny jest tylko dla lokalnych żądań (z tej samej maszyny), ponieważ te informacje są zbędne przeciętnemu użytkownikowi witryny. Spójrzcie na poniższy kod*:

public class ProfileFilterAttribute : FilterAttribute, IActionFilter
{
    private Stopwatch _timer = null;

    public void OnActionExecuting(ActionExecutingContext filterContext)
    {
        if (!filterContext.HttpContext.Request.IsLocal)
        {
            return;
        }
        _timer = new Stopwatch();
        _timer.Start();
    }

    public void OnActionExecuted(ActionExecutedContext filterContext)
    {
        if (!filterContext.HttpContext.Request.IsLocal ||
            (_timer == null))
        {
            return;
        }
        _timer.Stop();
        filterContext.HttpContext.Response.Write(
            string.Format("<p>Czas wykonania akcji to: {0:F6} sekundy.</p>",
            _timer.Elapsed.TotalSeconds)
        );
    }
}

W tym przykładzie wypełniłem treścią obie metody i dodałem pole klasy _timer. W obu przypadkach sprawdzam najpierw, czy żądanie jest lokalne. Jeśli nie, to przerywam proces, w przeciwnym przypadku mogę kontynuować. Dla bezpieczeństwa w drugiej metodzie występuje również dodatkowe sprawdzenie, czy obiekt klasy Stopwatch nie jest przypadkiem nullem.

Kodu obsługującego Stopwatch raczej nie muszę Wam objaśniać, dlatego też bardziej skupię się na wydrukowaniu wyniku. Dokonujemy tego dobierając się do obiektu Response w drugiej z metod. Ponieważ w praktyce jest ona parsowana jeszcze przed rezultatem, to cały ten string pojawi się na samej górze strony.

Tak stworzony filtr musimy przypisać jeszcze do metody kontrolera:

public class HomeController : Controller
{
    [ProfileFilter]
    public string ActionTest()
    {
        Thread.Sleep(500);
        return "Testowa akcja";
    }
}

Rozwiązanie działa, lecz nie jest ono do końca ładne, ponieważ wynikowy tekst wypluwany jest na samej górze strony. Na szczęście dostępny obiekt kontekstu niesie odrobinę więcej możliwości i daje dostęp np. do ViewBaga. Wystarczy zatem zastąpić konstrukcję Response.Write czymś takim:

public void OnActionExecuted(ActionExecutedContext filterContext)
{
    if (!filterContext.HttpContext.Request.IsLocal ||
        (_timer == null))
    {
        return;
    }
    _timer.Stop();
    filterContext.Controller.ViewBag.Time = string.Format(
        "Czas wykonania akcji to: {0:F6} sekundy.",
        _timer.Elapsed.TotalSeconds);
}

W wyniku tej zmiany konieczne staje się zastosowanie widoku, który użyje zapisanej przez nas wartości. W związku z tym dodajemy nową akcję kontrolera:

[ProfileFilter]
public ActionResult ActionTest2()
{
    ViewBag.Time = 1;
    Thread.Sleep(500);
    return View();
}

Oraz widok, gdzie w dowolnym jego miejscu wstawiamy konstrukcję:

@ViewBag.Time

* Przykład na bazie książki Adama Freemana - patrz materiały niżej

Materiały

Cykl

Data ostatniej modyfikacji: 02.08.2014, 13:23.

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

Send to Kindle

Komentarze

blog comments powered by Disqus