Artykuł

flickr.com/photos/nickwebb/3016498475 flickr.com/photos/nickwebb/3016498475
sie 02 2014
0

Filtry wyjątków w ASP.NET MVC

Pisanie kodu który nie generuje błędów jest trudne, ale mimo wszystko musimy być przygotowani na to, że te błędy się pojawią. Dlatego czasem jeszcze trudniejsze jest zorganizowanie tworzonej aplikacji w taki sposób, by w logiczny sposób obsłużone zostały wszelkie nieoczekiwane wyjątki.

W tym obszarze istnieją różne podejścia - czasem obsługujemy błędy zaraz w miejscu ich wystąpienia, innym razem staramy się przepuścić wyjątek możliwie jak najwyżej w strukturze naszego kodu. Niezależnie od wybranego modelu, warto trzymać się jednej, określonej techniki w całym projekcie.

ASP.NET MVC w pewnym sensie korzysta z tego drugiego wzorca, ponieważ za sprawą filtrów możemy sprowadzić obsługę błędów na całej stronie do jednego miejsca. Cel ten możemy uzyskać rejestrując globalny filtr wyjątków. I to właśnie zagadnienie sprowadza nas do końca tego dość długiego cyklu o filtrach w ASP.NET MVC.

IExceptionFilter

Standardowo by utworzyć nowy filtr, musimy rozszerzyć klasę FilterAttribute oraz zaimplementować odpowiedni interfejs - w tym przypadku IExceptionFilter:

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

W interfejsie tym znajduje się jedna metoda, która wywoływana jest w sytuacji gdy w kodzie aplikacji, bądź też w innych filtrach pojawi się nieobsłużony wyjątek. W praktyce przekłada się to na to, że filtr ten może zostać wywołany w praktycznie dowolnym miejscu całego procesu.

Aby skorzystać z filtru wyjątku, należy zaimplementować metodę OnException, która standardowo przyjmuje pewien kontekst. Oprócz typowych dla tego rodzaju konstrukcji właściwości, w tym przypadku otrzymujemy również dostęp do szczegółów rzuconego wyjątku, co może być przydatne przy rozpatrywaniu określonych rodzajów błędów. Dzięki temu możemy np. zrobić kilka filtrów dla określonych rodzajów wyjątków (każdy z nich zawsze zostanie wywołany, jednak logika wewnątrz filtru może wstrzymać dalsze jego przetwarzanie), bądź też stworzyć jeden super wyjątek, który np. za pomocą konstrukcji switch, będzie zachowywał się inaczej, w zależności od kontekstu.

Filtry wyjątków możemy oczywiście przypisywać do konkretnych kontrolerów, bądź też akcji, ale w większości przypadków zdecydowanie lepiej jest zarejestrować filtr wyjątków jako globalny.

Klasa HandleErrorAttribute

W ASP.NET MVC przygotowano domyślną klasę dla wyjątków, którą bez większych komplikacji można podpiąć do każdej witryny. Aby to zrobić, należy wykonać trzy działania:

  • W pliku Web.Config, w sekcji system.web dodać węzeł customErrors:
    <system.web>
    	<customErrors mode="On" defaultRedirect="/Content/Ex.html"/>
    </system.web>
    Musimy zadbać oto by atrybut mode miał wartość On, a w atrybucie defaultRedirect znalazła się ścieżka do istniejącego w aplikacji pliku, który domyślnie będzie wyświetlany w przypadku błędu (może to być również przekierowanie na określoną akcję)
  • Zarejestrować filtr w pliku Global.asax dodając do treści metody Application_Start poniższą linijkę:
    GlobalFilters.Filters.Add(new HandleErrorAttribute());
    Można również wykorzystać nazwane parametry. Dzięki temu można np. jawnie określić wykorzystywany w wyjątku widok
    GlobalFilters.Filters.Add(new HandleErrorAttribute{ View = "Error"});
    Widok możemy określić po nazwie, bądź też według ścieżki wirtualnej. Jeśli go nie wskażemy, to filtr z automatu będzie szukać widoku o nazwie Error w lokalizacji Views/Shared. Jeśli go nie znajdzie, to wykorzysta domyślny plik z web.configu
  • Dodać widok błędu do projektu - widok musi pasować do przekazanej wyżej nazwy, bądź też powinien nazywać się Error, by system złapał go automatycznie

Warto dodać kilka słów więcej o samym widoku, ponieważ może on być silnie typowany. Spójrzcie na poniższy kod:

@model HandleErrorInfo

@{
    ViewBag.Title = "Error";
}

<h2>Błąd z widoku</h2>
<p>Wystąpił bardzo poważny błąd: @Model.Exception.Message</p>

Wykorzystując domyślny obiekt klasy HandleErrorInfo, uzyskujemy dostęp m.in. do szczegółów wyjątku. Dzięki temu możemy wyświetlić użytkownikowi konkretny komunikat błędu. Obiekt modelu wykorzystywany w tym widoku, jest domyślnie przesyłany przez nasz filtr. Jak widać, w większości przypadków podstawowy filtr wyjątków spełni swoje zadane. UWAGA - ma on jeszcze kilka innych, fajnych opcji.

Własny filtr wyjątku

Jeśli z jakiś powodów powyższy filtr nie spełnia oczekiwań, to zasadniczo macie dwie opcje:

  • Możecie rozszerzyć klasę HandleErrorAttribute tworząc nowy filtr na jej podstawie
  • Możecie od podstaw stworzyć własny filtr

Poniżej zaprezentuję rozwiązanie wykorzystujące drugą z opcji. Po pierwsze, musimy dodać do projektu nową klasę:

public class ExFilterAttribute : FilterAttribute, IExceptionFilter
{
	public void OnException(ExceptionContext filterContext)
	{
		if (!filterContext.ExceptionHandled)
		{
			filterContext.Result = new RedirectResult("~/Content/Ex.html");
			filterContext.ExceptionHandled = true;
		}
	}
}

W tym przypadku stworzyliśmy prosty filtr, który najpierw sprawdza czy wyjątek został już obsłużony. Jeśli tak się nie stało, to w kolejnym kroku podmienia domyślny rezultat dla aktualnej akcji, wskazanym przez nas statycznym dokumentem HTML. Na koniec oznajmiamy światu, że rzeczony wyjątek został obsłużony.

W kolejnym kroku musimy zarejestrować wyjątek globalnie, bądź też zaaplikować go konkretnej akcji, czy kontrolerowi. Pisałem już o tym wielokrotnie (nawet wyżej w tym tekście), dlatego też pominę ten fragment.

W ostatnim kroku należy umieścić rzeczony wcześniej statyczny HTML w lokalizacji Content/Ex.html. Jego struktura może wyglądać następująco:

<!DOCTYPE html>

<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title></title>
</head>
<body>
    <div>
        <h1>Błąd</h1>
        <p>Wystąpił nieoczekiwany błąd.</p>
    </div>
</body>
</html>

I to zasadniczo wystarczy, by obsłużyć własny filtr wyjątku. W tym przypadku nie musicie dodawać węzła customErrors - aczkolwiek swoją droga jest to bardzo przydatna konstrukcja, która pozwala np. na obsługę strony 404.

Podsumowanie

To by było na tyle w ramach całego cyklu o filtrach. Wydaje mi się, że przekazałem Wam całkiem sporo wiedzy na temat filtrów, które bardzo ułatwiają i uprzyjemniają pracę z ASP.NET MVC. Jeśli przegapiłeś/aś, któryś odcinek z cyklu, poniżej znajdziesz linki do wszystkich pozostałych części.

Materiały

Cykl

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

Send to Kindle

Komentarze

blog comments powered by Disqus