My littlemvc 2008 official

14
myLittleMVC Framework MVC dla ASP.NET Autor: Michał Skowronek

description

Project Introduction: MVC (Model View Controller) framework for ASP.NET 2.0. Contains AJAX module as well. It was created before official Microsoft framework was released so i've learned a lot creating it. (PL Only)

Transcript of My littlemvc 2008 official

Page 1: My littlemvc 2008 official

myLittleMVCFramework MVC dla ASP.NET

Autor: Michał Skowronek

Page 2: My littlemvc 2008 official

1. Cel projektuCelem projektu było stworzenie frameworka, dzięki któremu byłoby możliwe tworzenie aplikacji internetowych

w ASP.NET używając wzorca MVC (Model View Controller). ,”MyLittleMVC”, bo tak nazywa się powstały framework, wspomaga tworzenie, rozwój i testowanie powstającej aplikacji internetowej zgodnie z duchem powyższego wzorca, realizując całą jego funkcjonalność, czyli podział aplikacji na 3 oddzielne jednostki tj.: Model, Widok, Kontroler. Każda z części realizuje swoje zadania w sposób możliwie niezależny od pozostałych.

Model – jest odpowiedzialny za przechowywanie stanu aplikacji, który najczęściej jest ulokowany wewnątrz bazy danych.

Widok – jest odpowiedzialny za wyświetlenie gotowego interfejsu użytkownika, w tym wypadku gotowej strony internetowej. Zazwyczaj widok jest tworzony na podstawie danych pochodzących z modelu.

Kontroler – jest odpowiedzialny za przetwarzanie akcji użytkownika, obsługę modelu i w końcu wybór widoku to wyświetlenia. Jest zatem główną jednostką łączącą pozostałe 2 warstwy.

Główną korzyścią z zastosowania MVC jest wprowadzenie wyraźnej separacji między poszczególnymi warstwami aplikacji (tutaj: model, widok, kontroler), dzięki czemu całość jest łatwiejsza w rozwijaniu oraz testowaniu (przede wszystkim z wykorzystaniem Test Driven Developmnent).

2. Jak działa ASP.NETZanim przedstawię działanie frameworka warto przyjrzeć się jak działa samo ASP.NET.

Rys. 1.1 Page Lifecycle (cykl życia strony aspx)

Kiedy przeglądarka internetowa wysyła do serwera żądanie, serwer (IIS web server) najpierw sprawdza czy żądana strona jest plikiem, który ma być przez niego przetworzonym(domyślnie są to pliki zawierające rozszerzenia: .aspx .asmx .ashx .ascx). Jeśli jest, przekazuje go do ASP.NET runtime (środowiska uruchomieniowego ASP.NET) gdzie jest przetwarzany a wynik tego procesu zwracany do klienta w postaci gotowej strony. Zauważyć można, że największą rolę odgrywa tutaj ASP.NET runtime, którego uproszczony schemat działania przedstawia poniższy rysunek (rys. 1.2.).

Page 3: My littlemvc 2008 official

Rys. 1.2 ASP.NET runtime

Filtr ISAPI (Internet Server Application Programming Interface) w postaci pliku “aspnet_isapi.dll”, który widać na rysunku (rys. 1.2) w skrócie zajmuje się właśnie przechwyceniem żądań plików o konkretnym rozszerzeniu (.aspx i inne). „HttpApplication” natomiast jest to abstrakcyjna klasa reprezentująca aplikacje i ma ona na celu zapewnienie właściwego modelu programowania upraszczając go i upodobniając do modelu aplikacji dla systemu operacyjnego Windows (Cały mechanizm nosi nazwę WebForms i nazwa ta jest oczywistą analogią do WinForms w Windows, a wzorzec który jest domyślnie zaimplementowany w ASP.NET to „Page Controller”). Obiekty klasy HttpApplication są tworzone w miarę potrzeb tak, aby zachować ciągłość przetwarzania żądań http (liczba stworzonych obiektów jest ograniczona do maksymalnej liczby wątków i zależy głównie od sprzętowych możliwości serwera).

Następnie żądanie przechodzi przez tak zwany ASP.NET pipeline, który można skrótowo przedstawić jako pewien potok, w którym żądanie jest przetwarzane przez wszystkie istniejące moduły http (Http Modules), które posiadają metody do interakcji z żądaniem. Na końcu trafia natomiast do jednego z uchwytów http (Http Handlers), które zdefiniowane są do przetwarzania odpowiednich żądań zależnie od ustalonych reguł adresu URL i są odpowiedzialne za wygenerowanej właściwej odpowiedzi http (Response) wysyłanej z powrotem do klienta. Należy zwrócić uwagę, że podczas przetwarzania jednego żądania wywoływany jest tylko jeden HttpHandler w przeciwieństwie do HttpModules, których wywoływanych jest więcej. Cały proces ASP.NET pipeline najlepiej ilustruje rysunek (rys. 1.3.).

Page 4: My littlemvc 2008 official

Rys. 1.3. ASP.NET pipeline

3. Jak działa myLittleMVCFramework „myLittleMVC” swe działanie opiera właśnie na wykorzystaniu uchwytu http

(HttpHandler), który w tym wypadku spełnia jedno z zadań głównego kontrolera (front controllera) tj. przechwytuje wszystkie żądania, które są do niego kierowane. Domyślnie są to wszystkie żądania, których url kończy się łańcuchem znaków „default.aspx”. Literał ten jest tak zwanym inicjatorem akcji.

Czym jest zatem akcja? Inaczej niż w standardowym modelu ASP.NET, adres url żądania nie odzwierciedla fizycznie istniejącego na serwerze pliku (w tym wypadku strony aspx), lecz jest raczej poleceniem wykonania danej metody (zwanej tutaj akcją) przez dany kontroler (klasę dziedziczącą po myLittleMVC.Controller). Przykładowo adres „home/index/default.aspx” oznacza wykonanie akcji(metody) „index” przez klasę kontrolera o nazwie „home”. Jak napisano wyżej, default.aspx jest jedynie informacją dla odpowiedniego uchwytu http o przechwyceniu tego właśnie żądania.

Głównym kontrolerem (uchwytem http w tym wypadku) w myLittleMVC jest klasa MainHandler, która jak każdy uchwyt http dziedziczy po System.Web.IHttpHandler. Jego główną metodą jest ProcessRequest, która jest wykonywana przez ASP.NET w celu przetworzenia żądania i to właśnie w tej metodzie myLittleMVC rozpoczyna swoje działanie przekazując żądanie i informacje z nim związane do Routera. Ten z kolei zajmuje się przetworzeniem url i rozbiciem go za pomocą obiektu Action na poszczególne jednostki wywołania czyli:

Nazwę kontrolera, którego metoda akcji ma zostać wykonana,

Nazwę metody akcji do wykonania,

Ewentualne parametry metody akcji.

Tak powstały obiekt klasy Action jest przekazywany do metody InvokeAction klasy ControllerFactory, gdzie zawarte w nim informacje posłużą do próby wykonania akcji. Proces ten wykorzystuje wbudowany w platformę .NET system refleksji, który pozwala na przeglądanie i używanie własnych metadanych. Dalszy proces po pomyślnym wykonaniu akcji danego kontrolera, zależy od samej akcji i zostanie omówiony w następnym rozdziale podczas tworzenia przykładowej aplikacji.

Page 5: My littlemvc 2008 official

4. MyLittleMVC w AkcjiW celu zademonstrowania działania frameworka i opisu dalszego procesu przetwarzania

żądania http przez niego najlepiej będzie posłużyć się przykładem (w katalogu „prg\example” znajduje się projekt utworzony w Visual Web Developer 2008 EE zawierający przykładową aplikację, której część stanowi poniższy przykład).

Typową strukturę katalogów w aplikacji opartej na myLittleMVC przedstawia zrzut ekranu (rys. 4.1).

Rys. 4.1 Struktura folderów aplikacji opartej na myLittleMVC

Folder „App_Code” zawiera kod całej aplikacji podzielony na poszczególne części. W podfolderze „Controller” znajdować będą się klasy kontrolerów, „Helper” służy do przechowywania klas pomocniczych, natomiast „Model” zawierać powinien jak sama nazwa wskazuje klasy warstwy modelu aplikacji. Sama struktura katalogów w folderze „App_Code” ma jedynie charakter umowny i ma na celu zwiększenie przejrzystości aplikacji. Nic nie stoi na przeszkodzie by dodać własne podfoldery w miarę potrzeb, zalecane jest jednak trzymanie się pewnej konwencji znacząco ułatwiającej dalszy rozwój aplikacji.

Folder „Bin” domyślnie zawiera jedynie referencję do biblioteki frameworka. Jego przeznaczenie nie ulega zmianie w stosunku do tego ze standardowego użycia w ASP.NET.

Folder „View” zawiera wszyskie klasy widoków, które dziedziczą po myLittleMVC.BasicView lub myLittleMVC.View<>(klasą bazową dla nich pozostaje dalej klasa System.Web.UI.Page czyli klasa reprezentująca stronę aspx, gdyż właśnie w ten sposób reprezentowane są widoki). Grupy widoków są posegregowane w katalogach ze względu na przynależność do danego kontrolera (np. folder home jak widać na rys. 4.1 zawiera wszystkie widoki przynależące do kontrolera home), jednak jest to również kwestia umowna i chcąc zwiększyć elastyczność aplikacji możliwe jest wybranie przez dany kontroler dowolnego widoku. Jedynym warunkiem jest by klasa widoku znajdowała się w folderze „View”. Na uwagę zasługuje folder „common”, który jest przeznaczony do przechowywania wspólnych widoków oraz szablonów widoków (klas widoków dziedziczących po myLittleMVC.MasterView a „fizycznie” reprezentowanych przez klasę bazową System.Web.UI.MasterPage).

Plik „Web.config” zawiera konfigurację całej aplikacji ASP.NET oraz konfigurację frameworka, której najważniejszą część opisuje listing 4.1.

Istnienie pliku „default.aspx” zapewnia jedynie prawidłowe wykonanie domyślnej akcji.

Page 6: My littlemvc 2008 official

Listing 4.1

<?xml version="1.0"?><configuration> <configSections> <section name="myLittleMVCRouter" type="myLittleMVC.RouterConfig"/> (1) <section name="myLittleMVCMain" type="myLittleMVC.MainConfig"/> (2) </configSections> <myLittleMVCRouter initActionPath="default.aspx" urlSeparator="/" defaultAction="home/index" loginAction="member/login" langPrefix="false"/> <myLittleMVCMain debugFramework="true" projectNamespace=""/> <system.web> <httpHandlers> <add verb="*" path="*default.aspx" type="myLittleMVC.MainHandler"/> </httpHandlers> </system.web></configuration>

Kolejno w sekcji configSections odbywa się rejestracja sekcji konfiguracyjnych myLittleMVC, składających się z głównej konfiguracji(2) oraz konfiguracji routera(1). Główna część konfiguracji obejmują 2 ustawienia:

debugFramework (true|false) ustawiający tryb debug dla myLittleMVC, wartość domyślna: „true”

projectNamespace (np. „Test”) zawierający przestrzeń nazw tworzonego projektu (a dokładnie przestrzeń nazw w której znajdują się kontrolery), wartość domyślna to „”.

Na ustawienia routera składają się:

initActionPath definiuje tak zwany inicjator akcji, czyli literał dołączany na koniec adresu URL zawierającego akcje w formie łańcucha znaków. Element ten musi być zawarty w atrybucie path ustawienia uchwytu myLittleMVC.MainHandler, wartość domyślna: „default.aspx”

urlSeparator to separator akcji url (znak lub grupa znaków oddzielających od siebie poszczególne części akcji url), wartość domyślna: „/”,

defaultAction zawiera domyślną akcję w postaci „kontroler/metoda_akcji”, należy zadbać o odpowiedni separator (w przykładzie powyżej „/”), wartość domyślna: „home/index” (wielkość liter ma znaczenie),

loginAction zawiera akcję wykonywaną w razie wystąpienia wyjątku SecurityException (wyjątek występuje podczas próby dostępu do zasobu lub partii kodu, do którego użytkownik nie ma dostępu), jeżeli element nie zostanie ustawiony zamiast wywołania akcji zostanie wyświetlona odpowiednia strona błędu.

LangPrefix (true|false) element ustawia prefix (przedrostek) językowy w akcji znajdującej się w adresie url. Wartość prefixu jest zależna od ustawień regionalnych i automatycznie dostosowywana podczas zmiany języka. Mechanizm prefiksu językowego zapobiega cachowaniu przez przeglądarkę tej samej strony w innych językach jako tego samego dokumentu. Wartość domyślna: „true”.

Page 7: My littlemvc 2008 official

W sekcji „httpHandlers” rejestrowany jest główny uchwyt frameworka (myLittleMVC.MainHandler), który przechwytuje wszystkie żądania (zarówno GET i POST) zakończone inicjatorem akcji.

4.1. Kontroler - Akcja - WidokNie zmieniając pliku konfiguracyjnego stwórzmy pierwszy kontroler jakim będzie klasa

home (pełną definicję klasy home zawiera listing 4.1.1.). Będzie to nasz kontroler domyślny (taki, który zawiera domyślną akcję).

public class home : Controller{ public void index() {}}

Jak widać, wraz z kontrolerem została utworzona publiczna metoda „index()”, która będzie domyślną akcją. Aby jednak tak się stało należy dodać do niej atrybut „ActionMethod”, którym oznaczane będą wszystkie metody akcji (został on wprowadzony w celach bezpieczeństwa by zapobiec nieautoryzowanemu wykonaniu dowolnej metody kontrolera odpowiednio przygotowaną akcją zawartą w adresie url). Prawidłowo więc powyższy przykład powinien wyglądać tak:

public class home : Controller{ [ActionMethod] public void index() {}}

Po uruchomieniu aplikacji w takiej postaci zostanie wykonana, jak zdefiniowano w pliku konfiguracyjnym, domyślna akcja tj. „home/index” i na ekranie ukaże nam się białe tło. Oznacza to, że wszystko przebiegło pomyślnie jednak nie został wyrenderowany żaden widok.

Stwórzmy więc jeden, dodając do katalogu „View\home” stronę „home.aspx” i zmieńmy jej klasę bazową z System.Web.UI.Page na generyczną klasę widoku myLittleMVC.View<> gdzie parametrem będzie typ String. Dzięki temu za pomocą kontrolera możemy wysłać do widoku dane (w tym wypadku będzie to zmienna typu String), które mogą być odczytane z właściwości „DataView” klasy naszego widoku. Typ danych wysyłanych z kontrolera do widoku może być oczywiście dowolny. „MyLittleMVC” posiada również inny mechanizm wysyłania danych do widoku za pomocą tzw. „ActionFields”, który zostanie przedstawiony w dalszej części tekstu. Wracając do naszego przykładu, chcąc wyświetlić przesłane dane posłużymy się kodem (znajdującym się w pliku home.aspx):

<p> Przykładowe dane wysłane z kontrolera przez domyślną akcję za pomocą właściwości DataView: <%=DataView %></p>

Dalej jednak, akcja “index” kontrolera „home” nie posiada kodu odpowiedzialnego za wyświetlenie naszego widoku. Do tego celu służy metoda klasy kontrolera „RenderView” występująca w 3 przeładowanych wersjach (jej uzupełnieniem jest metoda RenderViewMaster). Do naszego celu posłuży wersja przyjmująca 2 argumenty. Pierwszym jest nazwa widoku do wyrenderowania, drugim natomiast obiekt reprezentujący dane wysyłane do tego widoku. Jak łatwo się domyśleć są to właśnie te dane, które będą widoczne w klasie widoku poprzez właściwość „DataView”. Dla przykładu danymi niech będzie data pobrana z serwera (patrz listing 4.1.1).

Page 8: My littlemvc 2008 official

Kolejnym krokiem niech będzie stworzenie szablonu widoku dla naszej aplikacji. Aby to zrobić należy dodać plik .master (w naszym przypadku będzie to domyślny „MasterPage.master”) do katalogu „View\common” i zmienić jego klasę bazową z System.Web.UI.MasterPage na myLittleMVC.MasterView. Na koniec by nasz poprzedni widok korzystał z utworzonego szablonu należy zmodyfikować go w następujący sposób (przy zachowanym domyślnym nazewnictwie kontenerów „head” oraz „ContentPlaceHolder1”):

<%@ Page Language="C#" MasterPageFile="~/View/common/MasterPage.master" (1) AutoEventWireup="true" CodeFile="home.aspx.cs" Inherits="View_home_home" %>

<asp:Content ID="Content1" ContentPlaceHolderID="head" runat="Server"></asp:Content>

<asp:Content ID="Content2" ContentPlaceHolderID="”ContentPlaceHolder1" runat="Server"><p> Przykładowe dane wysłane z kontrolera przez domyślną akcję za pomocą właściwości DataView: <%=DataView %></p></asp:Content>

Kod odpowiedzialny za wykorzystanie szablonu widoku w naszym widoku znajduje się w miejscu oznaczonym punktem (1). Sam sposób tworzenia szablonów jak widać nie odbiega od tego jaki się stosuje w „czystym” ASP.NET. Na większą uwagę zasługuje klasa myLittleMVC.MasterView, po której dziedziczy nasz szablon. Korzystając z niej uzyskujemy bowiem możliwość wysyłania danych z kontrolera do naszego szablonu widoku. Sposób w jaki uzyskamy tę funkcjonalność musi być oczywiście inny, niż przedstawiony wyżej bazujący na generycznej klasie View<>. Użyjemy wspomnianych wcześniej pól „ActionFields”.

Listing 4.1.1.

using System;using myLittleMVC;

/// <summary>/// Domyslny kontroler/// </summary>public class home : Controller{ /// <summary> /// tytul strony wsylany do widoku szablonu (masterpage) /// </summary> [ActionField] public string title; (1)

[ActionMethod] public void index() { title = "home";

string czas = DateTime.Now.ToString();

// Wyswietlenie widoku i wysłanie do niego zmiennej czas RenderView("home/home", czas); }}

Page 9: My littlemvc 2008 official

Polami „ActionFields” są wszystkie publiczne pola klasy kontrolera posiadające atrybut „ActionField” (1). Kiedy w metodzie akcji zostanie przypisana do takiego pola wartość, zostanie ono wysłane do widoku podczas wykonania metody wyswietlającej widok („RenderView” lub „RenderViewMaster”). Aby w widoku (tutaj w szablonie widoku) odebrać wysłane pole „ActionField” wystarczy zdefiniować wewnątrz klasy widoku publiczne pole tego samego typu i posiadające taką samą nazwę oraz opatrzyć je atrybutem „ViewField”. W naszym przykładzie plik codebehind (plik zawierający kod klasy strony aspx a w tym wypadku widoku, zdefiniowanej jako klasa częsciowa (ang. partial class)) prezentować się będzie następująco:

Listing 4.1.2.

using myLittleMVC;

public partial class View_common_MasterPage : MasterView{ [ViewField] public string title;}

Po tym prostym zabiegu, możemy korzystać ze zmiennej „title” w szablonie widoku. W przykładzie pole to będzie wykorzystywane do wyświetenia tytułu strony, bez potrzeby umieszczania go w każdej klasie widoku. Jak widać transfer danych z kontrolera do konretnego widoku jest bardzo prosty i intuicyjny, a dane są ściśle typowane co znacząco ułatwia i przyspiesza pracę oraz pozwala uniknąć wielu błędów. Wybór sposobu jakim dane zostaną wysłane zależy głownie od użytkownika frameworka i jego osobistych preferencji (warto zaznaczyć iż 1 sposób wykorzystujący generyczną klasę myLittleMVC.View<> został wprowadzony w celu zachowania zgodności z oficjalnym frameworkiem MVC Microsoftu i mimo iż posiada on kilka niedogodności w porównaniu z polami „ActionField” może być efektywnie stosowany w celu przesłania do widoku danych jednego typu).

4.2. Parametry akcjiWiadomo już jak wywoływana jest akcja, w jaki sposób odbywa się wyświetlenie widoku

oraz przekazanie do niego danych. Jak jednak przesłać do metody akcji parametry? Nic prostrzego. Jeśli parametry akcji pochodzą z adresu url wystarczy zdefiniować klasyczne parametry metody będącej akcją w liczbie i kolejności jaka występuje w adresie url. Czasem istnieje potrzeba zastąpienia parametru pochodzącego z url wartością domyślną. Język C# z uzasadnionych przyczyn nie zapewnia wbudowanego mechanizmu parametrów domyślnych. Z pomocą przychodzi jeden z atrybutów z grupy „ActionMethodParams”:

[DefParam(object defValue)], defValue jest wartością domyślną przypisywaną do parametru w przypadku nie znalezienia wartości w adresie url akcji. Jego stosowanie do parametrów akcji jest zgodne z przyjętą konwencją stosowania parametrów domyślnych stosowanych w innych językach (m.in. C/C++) co oznacza ze atrybutem tym mogą być jedynie oznaczane ostatnie parametry metody akcji (nie biorąc pod uwagę parametrów oznaczonych innym atrybutem grupy „ActionMethodParam”).

Kolejnymi atrybutami stosowanymi do parametrów metody akcji są:

[FormParam], Atrybut parametru akcji przypisujący do parametru wartość pochodzącą z formularza. Atrybut usuwa działanie innych atrybutów parametrów akcji z grupy ActionMethodParams, wiec powinien być stosowany samodzielnie,

[SessionParam], Atrybut parametru akcji przypisujący do parametru wartość pochodzącą z obiektu sesji,

Page 10: My littlemvc 2008 official

[QueryParam], Atrybut parametru akcji przypisujący do parametru wartość pochodzącą z query stringa,

[CookieParam], Atrybut parametru akcji przypisujący do parametru wartość pochodzącą z pamięci cache,

[AppParam], Atrybut parametru akcji przypisujący do parametru wartość pochodzącą z obiektu Application.

Sam proces mapowania parametrów odbywa się w klasie myLittleMVC.ControllerFactory tuż przed wykonaniem akcji. Sposób wykorzystania parametrów akcji (ActinonMethodParams) jest zawarty w dołączonym przykładzie.

4.3. ActionObjectsOstatnim ważnym mechanizmem jaki wykorzystuje „myLittleMVC” jest grupa atrybutów

„ActionObjects”. Dotyczą one pól klasy kontrolera i dostarczają funkcjonalności mapowania ich na poszczególne obiekty należące do aplikacji (Cookies, Session, Application).

Grupa „ActionObjects” zawiera:

[SessionField], Atrybut dodawany do pól mapowanych na obiekt sesji (Session),

[AppField], Atrybut dodawany do pól mapowanych na obiekt aplikacji (Application),

[CookieField(int days[optional])], Atrybut dodawany do pól mapowanych na obiekt ciastka aktualnej odpowiedzi, konstruktor atrybutu zawiera opcjonalny parametr „days” ustawiający liczbę dni istnienia ciastka na komputerze klienta aplikacji. Domyślnie wynosi ona 1 dzień.

Proces mapowania pół zawierających atrybuty z grupy „ActionObjects” odbywa się w metodach kończących akcję (metodach renderujących, przekierowujących akcję, wysyłających plik oraz gdy żadna z powyższych metod nie zostanie użyta w metodzie Flush() kontrolera).

Framework zapewnia również usuwanie wartości z obiektów zmapowanych polami „ActionObjects”. Służy do tego pomocnicza metoda kontrolera „RemoveField(string fieldName)”, gdzie field name to nazwa pola w postaci łańcucha znaków.

5. Na zakończenieMam nadzieję, że w tym dość powierzchownym opisie frameworka „myLittleMVC” udało

mi się choć trochę przybliżyć sposób jego działania oraz użycia go do budowy aplikacji. Sam framework oferuje znacznie więcej niż tu opisano, jednak z zamierzenia miał to być jedynie szkic zachęcający do dalszego poznawania go. Więcej przykładów użycia w konkretnych sytuacjach znajduje się w przykładowej aplikacji. Informacje na temat dalszego rozwoju frameworka oraz jego kolejnych wersji będą sukcesywnie dodawane na stronie http://www.skowronkow.pl. Wersja o której mowa w tekście objęta jest licencją GNU GPL.

Literatura[1] Stephen C. Perry, C# i .NET, Core 2006

[2] Microsoft, msdn, http://www.msdn.com

[3] Microsoft, ASP.NET community, http://www.asp.net

[4] Scott Guthrie, Microsoft, ScottGu's Blog, http://weblogs.asp.net/scottgu/