MiddelwareCzęść NR.2 Najpierw zrozummy czym jest Middelware zanim przejdziemy dalej. W poprzednich wersjach ASP.NET zarządzania przepływami żądań HTTP nie było takie oczywiste i proste. Przykładowo w ASP.NET Web Form, które nie jest już wspierane przez Microsoft mieliśmy system zdarzeniowy, w którym łatwo można było się pogubić.

W ASP.NET Core proces przetworzenia żądania HTTP jest znacznie prostszy w obsłudze, jak się zaraz przekonasz.

Przykładowo wysyłamy żądanie strony poems poprzez adres URL. Gdy wpisujemy do przeglądarki adres strony,  wtedy najczęściej wysyłamy zapytanie HTTP typu GET. Jeżeli wypełniamy formularz będzie to żądanie typu POST.

Na razie nie ma to większego znaczenia. Co się dzieje z tym zapytaniem?

ASP.NET Core : Middelware

Zapytanie idzie do serwera oczywiście, ale jakieś oprogramowanie musi odpowiedzieć na to żądanie. W ASP.NET Core komponenty, które obsłużą moje żądanie HTTP nazywają się Middelware.

Każdy element Middleware jest obiektem i każda część Middleware służy jakiemuś jednemu celowi. Ostatecznie potrzebujemy więc wiele kawałków Middleware aby przetworzyć żądanie HTTP.

Wysłaliśmy więc żądanie HTTP typu POST i po stronie serwera moje żądanie napotkało na pierwszy kompoment Middelware.

Jest to kompoment logujący, który przykładowo zapisze gdzieś do pliku, że taka operacja została wykonana.

ASP.NET Core : Middelware 2

Ten Logger widzi wszystko na temat nadchodzącego żądania. Ścieżkę URL, parametry, nagłówki, a nawet ciasteczka i tokeny dostępu. Logger może zapisać te wszystkie informacje. Może te informacje  nawet zmienić, jeśli by chciał. Może nawet odrzucić całe żądanie, jeśli byłby tak zaprogramowany. Gdyby tak się stało cały proces przetworzenia żądania HTTP zostałby przerwany.

Istnieje jednak szansa, że Logger po prostu zapisze informacje i prześle żądanie dalej do kolejnego kawałka Middelware.

Komponenty Middelware tworzą więc coś na wzór różnych sal i pokojów w myjni samochodowej. To co jest jednak obsługiwanie to nie samochód a żądanie HTTP.

Powiedzmy, że następny komponent Middelware to Authorizer. Ten komponent może szukać specyficznych informacji, jak ciasteczka czy tokeny autoryzacyjne w nagłówkach HTTP. Jeżeli ten komponent znajdzie to, czego szuka oznacza to, że obecnie użytkownik jest zalogowany i może przejść dalej ponieważ ma pozwolenie by obejrzeć tę stronę.

ASP.NET Core : Middelware 3

Jeśli tak nie będzie, to być może ten komponent odpowie kodem błędu HTTP 401. Być może ten komponent w takim wypadku przekieruje użytkownika na inną stronę np. na stronę logowania.

Załóżmy, że jesteśmy zalogowani więc przechodzimy dalej. Proces przetworzenia żądania HTTP napotyka na kolejny kawałek kodu Middleware.

ASP.NET Core : Middelware 4

W tym przykładzie następnym kawałkiem Middleware jest obiekt “Router”. Router jest to klasa ze środowiska ASP.NET MVC. Kod Routera przenalizuje adres URL i określi jaki widok i jaki kontroler ma obsłużyć, aby wyświetlić stronę dla użytkownika.

Jest to oczywiście bardziej złożone. W kontrolerze, który jest klasą jest wywołana określona metoda. A ta metoda może zwrócić stronę HTML, informacje w formacie JSON lub formacie XML.

Router zawsze szuka sposobu na odpowiedź. Jeśli tak się nie stanie to Router może zwrócić błąd HTTP 404, który określa, że dane źródło nie zostało znalezione.

ASP.NET Core : Middelware 5

Załóżmy jednak, że nasz Router znalazł odpowiedni kontroler i widok, i wyświetlił nam stronę HTML. Sukces jest deklarowany kodem HTTP o numerze 200.

ASP.NET Core : Middelware 5

Teraz proces przetwarzania żądania będzie się cofać.

ASP.NET Core : Middelware 6

Obiekt w C# reprezentujący stronę HTML zostanie przekazany do komponentu Router, a potem do komponentu Authorizer.

Na końcu obiekt ten wyląduje do komponentu logującego, gdzie mogę zapisać swój sukces przetworzonego żądania. Co więcej mogę nawet zapisać czas przetworzenia tego żądania.

Ostatecznie strona HTML zostaje zwrócona do serwera, potem strona przepływa przez Internet i trafia do przeglądarki użytkownika.

Oto esencja tego, czym jest Middelware i wiesz mi jest to łatwiej wyjaśnić niż cykl zdarzeniowy ASP.NET Web Forms.

Wracając do naszego projektu ustawimy zaraz serię komponentów Middelware,  aby nasza aplikacja zaczęła się zachowywać poprawnie/ 

Middleware obsługuje błędy, statyczne pliki oraz środowisko MVC, które ostatecznie pozwoli nam stworzyć tę stronę internetową, którą piszemy.

IApplicationBuilder

A gdzie w naszej aplikacji są te komponenty Middelware. Komponenty Middelware deklaruje się w metodzie Configure wewnątrz klasy Startup.cs.

Obecnie korzystamy z dwóch komponentów Middelware.

app.UseIIsPlatformHandler

Middelware IISPlatformHandler pozwala mi na korzystanie z autentykacji Windows, które zostanie obsłużone głównie przez serwer Microsoftu IIS. Mimo iż nie planuję skorzystać z tego tokena uwierzytelniającego, to ta metoda tutaj pozostaje. 

Middelware Run obsłużona przez kod na razie wyświetla prosty napis. Middelware Run jest rzadko używany. Wynika to z tego, że po nim żaden kolejny Middelware nie zostanie obsłużony. 

Wewnątrz metody Run  mamy wgląd na nasze zapytania wewnątrz parametru context.

Request object

Wewnątrz parametru context możemy też uzyskać dostęp do odpowiedzi naszego zapytania.

Result object

Jak jednak mówiłem ten Middelware jest rzadko używany. W końcu obsługa żądania jak i jego rezultatu leżą po stronie naszego kodu. Zdecydowanie nie zaleca się obsługiwać całego serwisu strony www w tej metodzie.

Przepływ żądań HTTP w naszej aplikacji wygląda tak.

Przepływ HTTp

Na razie w naszej aplikacji nic się nie dzieje i będziemy musieli dodać dodatkowe komponenty Middelware.

Pisząc kod w metodzie Configure możesz zwrócić uwagę, że obecnie nie mamy żadnych innych komponentów. Brakuje nam bibliotek. Gdzie je można zdobyć.

App use middleware

Oczywiście wszystko możemy dodać używając menadżera paczek NuGet.

Manger Nuget

Znajdź paczkę Microsoft.ASPNET.Diagnostics i ją zainstaluj.

nuget installing package 2

Przejdź przez irytujące okna…i

nuget installing package

Zobacz całą nową listę dodatkowych komponentów Middelware.

use muddelware

Na początek skorzystaj z komponentu Middelware “WelcomePage”.

public void Configure(IApplicationBuilder app, IMessages messages)
{
    app.UseIISPlatformHandler();

    app.UseWelcomePage();

    app.Run(async (context) =>
    {
        await context.Response.WriteAsync(messages.Hello());
    });
}

Tak by zobaczyć, że ścieżka URL ma wpływ na żądania możesz dodać konkretny adres URL, który zostanie obsłużony przez ten komponent.

ASP.NET welcome Page  1

Ja wpisałem stronę “/IIS”. Jak widać wpisując taki adres URL otrzymuję standardową powitalną stronę ASP.NET. Jeśli wpiszesz inny adres, tym razem komponent RUN się uruchomi i wyświetli napis “Hello”.

ASP.NET welcome Page

Zwróć uwagę na to, że w pliku project.json został dodany nowy wpis przy polu “dependencies”. W końcu dodaliśmy nową paczkę.

project.json

Obsługa wyjątków

Obsługa wyjątków na razie nie istnieje. Dlaczego? Bo do tego też jest komponent Middelware, z którego jeszcze nie korzystamy.

public void Configure(IApplicationBuilder app, IMessages messages)
{
    app.UseIISPlatformHandler();

    app.UseWelcomePage("/IIS");

    app.Run(async (context) =>
    {
        throw new Exception("H0LY MAKARNIONY");
        await context.Response.WriteAsync(messages.Hello());
    });
}

W komponencie Run wyrzucam teraz brutalnie wyjątek “H0LY MAKARNIONY”. Teraz każde żądanie HTTP powinno się kończyć wyjątkiem. W starym ASP.NET, może pamiętasz legendarny żółty ekran śmierci, pojawiał się on gdy coś w aplikacji poszło nie tak.

Gdy odświeżysz stronę zobaczysz, że wyświetla ci się dosłownie NIC. Tylko patrząc w konsoli przeglądarki np. Chrome wciskając F12 możesz zobaczyć, że serwer zwróci kod błędu HTTP 500.

Chrome console 500 error

Błąd wyświetlił się w konsoli nawet dwa razy ponieważ zapytanie o favicona strony też zwróciło ten kod błędu.

Jak więc dodać obsługę wyjątku tak, aby ją wyświetlić na stronie? Trzeba skorzystać z kolejnego kompomentu Middelware. Komponentu “DeveloperExceptionPage”.

public void Configure(IApplicationBuilder app, IMessages messages)
{
    app.UseIISPlatformHandler();

    app.UseDeveloperExceptionPage();
    app.UseWelcomePage("/IIS");
   
    app.Run(async (context) =>
    {
        throw new Exception("H0LY MAKARNIONY");
        await context.Response.WriteAsync(messages.Hello());
    });
}

Ten komponent w trakcie przepływu żądania i jego zwrotu będzie sprawdzał czy przypadkiem nie wystąpił wyjątek. Jeśli tak będzie, ten wyjątek wyświetli mi się na stronie w bardzo informacyjny sposób.

Mogę zobaczyć cały stos wywołań kodu w C#, zapytanie URL, ciasteczka czy nagłówki HTTP.

New ASP.NET error page

Kolejny plus dla ASP.NET Core za to, że strona błędu jest bardziej czytelna niż żółty ekran śmierci. Dla przypomnienia wyglądał on tak.

Old ASP.NET error page

W zależności od środowiska

Nie chcemy aby strona z informacją o wyjątku była dostępna dla wszystkich. Taka strona może tworzyć poważny problem bezpieczeństwa ponieważ w sumie każdy w stosie może podejrzeć jak mój kod w C# wygląda.

Na szczęście w prosty sposób możemy uwarunkować to zachowanie w zależności od środowiska, w którym jest uruchomiona aplikacja.

Do metody Configure dodaj kolejny parametr typu IHostingEnviroment. Zostanie on obsłużony przez mechanizm wstrzykiwania zależności, który omówiliśmy w poprzednim wpisie.

IHostingEnviroment

Mając ten parametry określający nasze środowisko mogę sprawić przy pomocy prostej instrukcji warunkowej, że dane komponenty Middleware będą uruchamiane tylko, gdy pracuję w środowisku deweloperskim.

public void Configure(IApplicationBuilder app,
    IHostingEnvironment enviroment,
    IMessages messages)
{
    app.UseIISPlatformHandler();

    if (enviroment.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();

        app.UseRuntimeInfoPage("/info");
        app.UseWelcomePage("/IIS");
    }
   
    app.Run(async (context) =>
    {
        await context.Response.WriteAsync(messages.Hello());
    });
}

Tak więc wyjątki, strony powitalne i strony techniczne nie wyświetlą się już na serwerze produkcyjnym. Pytanie jednak brzmi skąd moja aplikacja wie, że jestem obecnie w serwerze deweloperskim.

Otóż we właściwościach projektu w zakładce debug jest zmienna środowiskowa projektu, która o tym decyduje. Wystarczy ją zmienić w trakcie kompilacji i mamy kod działający w innym środowisku.

Hosting.Enviroment

Jest to wielki krok do przodu. W starym ASP.NET konfiguracja strony błędów jest w plikach web.config jak i w ustawieniach serwera IIS jak i w kodzie w C#. Przez ten chaos miejsc czasem nie wiadomo dlaczego nasze ustawienia w jednym miejscu nie działają w ogóle. Nawet na tym blogu, mimo iż go napisałem nie wiem do końca dlaczego dla błędów serwerowych wyświetla się żółty ekran śmierci, zamiast mojej dedykowanej strony dla błędów o kodzie 500.

W ASP.NET Core obsługa środowiska jest w kodzie, więc wszystko jest proste, jasne i logiczne. Bez czytania artykułów w Google jak mam to ustawić w pliku konfiguracyjnym XML web.config.

Obsługa plików

Aby zakończyć tę część kursu pozostało już tylko obsłużyć prosty plik HTML.

Add new Item

Jak pamiętasz z poprzedniej części kursu pliki HTLM, JavaScript, CSS i obrazki powinny się znajdować wewnątrz folderu wwwroot.

Dlatego w tym folderze dodaj plik statycznej strony HTML.

HTML Page

Ja stronę nazwałem index i dodałem do niej małą zawartość tak, abym wiedział, że na pewno wywołałem ją żądaniem HTTP.

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title></title>
</head>
<body>
    Hello this is a static file
</body>
</html>

Aby wywołać stronę w bardzo szybki sposób wystarczy z menu wybrać opcję “View in Browser”.

View in Browser

Pod adresem URL index.html nie wyświetli ci się jednak ta strona. Wyświetli ci się wyjątek, jeśli nie usnułeś jeszcze wyrzucenia błędu z kodu tak, jak ja.

Mi się wyświetla wynik komponentu Run, który wyświetla napis “Cez Hello”.

index

Dlaczego statyczny plik nie jest obsłużony? Proste brakuje nam kolejnego Middelware. Znajdź w menadżerze NuGet paczkę “Microsoft.ASPNET.StaticFiles”.

Nuget

Dodaj ten komponent w metodzie Configure.

UseStaticFiles

Moja metoda Configure wygląda teraz tak.

public void Configure(IApplicationBuilder app,
    IHostingEnvironment enviroment,
    IMessages messages)
{
    app.UseIISPlatformHandler();

    app.UseStaticFiles();

    if (enviroment.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();

        app.UseRuntimeInfoPage("/info");
        app.UseWelcomePage("/IIS");
    }
   
    app.Run(async (context) =>
    {
        await context.Response.WriteAsync(messages.Hello());
    });
}

Wywołując adres URL index.html teraz mogę zobaczyć swoją stronę HTML.

Static file

Wpisując jakikolwiek inny adres oprócz “/IIS” i “/Info” wyświetla mi napis “Cez Hello”.

To wszystko, co musisz wiedzieć na temat Middelware. W następnym wpisie przejdziemy do świata MVC, ASP.NET MVC.