AOP v ASP.NET MVC – filtry

Pravidelnému čtenáři mého blogu (takže nikomu ;-)) je jistě nadpis tohoto článku jasný, ale pro ostatní dám hinty. AOP znamená aspektově orientované programování a blogoval jsem o něm zde. ASP.NET MVC je framework pro tvorbu webových stránek v prostředí ASP.NET a blogoval jsem o něm zde.
V tomto článku se podíváme na to, jak využít principy AOP v ASP.NET MVC, na což je ASP.NET MVC velmi dobře připraveno díky tzv. filtrům.

Když jsem začal psát jednu jednoduchou aplikaci v ASP.NET MVC, zjistil jsem, že ve většině metodách potřebuji dělat tytéž nebo velmi podobné akce a proto jsem se rozhodl použít AOP a to ve formě PostSharpu. Každá metoda mi např. začínala tímto kódem, který zajistil, že se mi zobrazil nastavený title stránky a nadpis v master page:

this.ViewData["Title"] = "Nadpis stránky"; 

Proto jsem si v PostSharpu napsal takovýto pěkný aspekt, který se o vše postaral a metodu pak stačilo odekorovat tímto atributem:

[Serializable]
public class PageTitleAttribute : PostSharp.Laos.OnMethodBoundaryAspect
{
    public PageTitleAttribute(string name)
    {
        this.Name = name;
    }

    public string Name
    {
        get;
        protected set;
    }

    public override void OnEntry(PostSharp.Laos.MethodExecutionEventArgs eventArgs)
    {
        var controller = eventArgs.Instance as System.Web.Mvc.Controller;
        if (controller != null)
        {
            controller.ViewData["Title"] = this.Name;
        }
    }

} 

Když jsem se ale hrabal ve zdrojácích ASP.NET MVC, narazil jsem na zajímavý mechanismus filtrů, který de facto vnáší sám o sobě do ASP.NET MVC aspektově orientované programování. Jak to tedy funguje?

Filtry

Před vykonáním akce jsou vykonány autorizační filtry (reprezentované rozhraním IAuthorizationFilter), hned potom akční filtry (IActionFilter), v případě výjimky jsou vykonány filtry IExceptionFilter a úplně nakonec přijde řada na IResultFilter. A to je přesně to, co se potřebujeme – mít možnost kdykoliv vstoupit do zpracování požadavku. Protože je ASP.NET MVC takto pěkně připravené, nemusíme používat PostSharp, protože ve většině případů vystačíme s těmito standardními vstupními body.
Deklarace jednotlivých rozhraní vypadá takto:

public interface IAuthorizationFilter
{
    void OnAuthorization(AuthorizationContext filterContext);
}

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

public interface IExceptionFilter
{
    void OnException(ExceptionContext filterContext);
}

public interface IResultFilter
{
    void OnResultExecuting(ResultExecutingContext filterContext);
    void OnResultExecuted(ResultExecutedContext filterContext);
} 

Pokud chceme vytvořit nějaký vlastní filtr, musíme ho zdědit z třídy System.Web.Mvc.FilterAttribute a dále třída musí implementovat požadované rozhraní – podle toho, kdy chceme, aby byl aspekt spouštěn (nic nám tak nebrání udělat filtr, který bude obsluhovat všechny čtyři aspekty).
Třída System.Web.Mvc.FilterAttribute obsahuje pouze položku Order, která určuje prioritu jednotlivých filtrů.

V ASP.NET MVC máme některé základní implementace těchto rozhraní.

Standardní implementace filtrů

AuthorizeAttribute je určen pro přímé použití a umožňuje specifikovat, že danou akci může provádět jen autorizovaný uživatel a případně specifikovat konkrétního uživatele nebo roli, která má mít k dané akci přístup. Celé je to založeno na standardní ASP.NET autorizaci.

ActionFilterAttribute je určen pro další rozšíření a pouze implementuje rozhraní IActionFilter a IResultFilter virtuálními metodami s prázdnými těly.

HandleErrorAttribute implementuje filtr pro zachytávání výjimek. Lze specifikovat, že se má odchytávat pouze určitý typ výjimek, a nastavit stránku pro zobrazení výjimky – defaultně je to view s názvem Error, který je automaticky vytvořen při založení nového projektu.

ContentTypeAttribute pouze nastaví content-type na zadanou hodnotu.

OutputCacheAttribute slouží k tomu, o čem vypovídá jeho název 😉 Lze nastavit maximální dobu cachování a podle čeho se má cachovat (např. podle parametrů).

Velkým usnadněním pak je to, že třída Controller implementuje všechna filtrační rozhraní (prázdnými virtuálními metodami) a ač není odvozena od třídy System.Web.Mvc.FilterAttribute, je s ní počítáno. Filtry definované v kontroleru pak mají nejvyšší prioritu (jsou spouštěny jako první).
Pokud tedy máme nějaký filtr, který bychom chtěli aplikovat ve všech akcích kontroleru, nemusíme vytvářet speciální filtr a tím odekorovat tuto třídu, ale můžeme namísto toho filtrační kód napsat přímo do těla třídy do overridnuté příslušné metody. Pokud chceme nějaký filtr použít dokonce ve všech kontrolerech v projektu, není nic snadnějšího než si vytvořit vlastní bázový kontroler, v něm filtry definovat a všechny kontrolery v projektu podědit od toho bázového.

Selekce akcí

Kromě výše popsaného filtrování lze ještě specifikovat, zda je povolena odekorovaná akce v daném kontextu. K tomu slouží abstraktní třída ActionMethodSelectorAttribute, která má metodu IsValidForRequest, která vrací bool, zda lze danou metodu použít v daném kontextu jako akci. Od této abstraktní třídy jsou standardně odvozené dva filtry: NonActionAttribute, který vždy vrací false a tudíž slouží k označení metod kontroleru, které nejsou akcemi. Druhým filtrem je AcceptVerbsAttribute, který specifikuje, pro jaký typ požadavku lze danou akci použít (POST nebo GET).

Shrnutí

ASP.NET MVC poskytuje dostatečné množství vstupních bodů, pomocí kterých můžeme deklarativně (pomocí atributů) zasahovat do zpracování požadavku. Pokud nevyžadujeme nějaké hodně speciální chování, vystačíme s těmito prostředky a nemusíme si tak do projektu (nebo přinejmenším prezentační vrstvy) zatahovat závislost na PostSharpu.

A jak jsem přepsal PageTitle atribut ze začátku článku s využitím filtrů? Kód je téměř stejný jako v případě PostSharpovského atributu, ale zbavil jsem se závislosti na PostSharpu:

public class PageTitleAttribute : System.Web.Mvc.ActionFilterAttribute
{
    public PageTitleAttribute(string name)
    {
        this.Name = name;
    }

    public string Name
    {
        get;
        protected set;
    }

    public override void OnActionExecuting(System.Web.Mvc.ActionExecutingContext filterContext)
    {
        if (filterContext != null && filterContext.Controller != null && filterContext.Controller.ViewData != null)
        {
            filterContext.Controller.ViewData["Title"] = this.Name;
        }
    }
} 

Zanechat odpověď

Vyplňte detaily níže nebo klikněte na ikonu pro přihlášení:

Logo WordPress.com

Komentujete pomocí vašeho WordPress.com účtu. Odhlásit /  Změnit )

Facebook photo

Komentujete pomocí vašeho Facebook účtu. Odhlásit /  Změnit )

Připojování k %s

Tento web používá Akismet na redukci spamu. Zjistěte více o tom, jak jsou data z komentářů zpracovávána.