Skip to content

11. Aplikacja [SimuPaie] – wersja 7 – ASP.NET / wiele widoków / wiele stron


Zalecane lektury: numer referencyjny [1], Rozwój WEB z ASP.NET, akapit 1: Przykłady


Obecnie analizujemy wersję funkcjonalnie identyczną z omówioną wcześniej trójwarstwową aplikacją ASP.NET – [pam-v4-3tier-nhibernate-multivues-monopage], jednak modyfikujemy jej architekturę w następujący sposób: podczas gdy w poprzedniej wersji widoki były realizowane przez jedną stronę ASPX, tutaj będą one realizowane przez trzy strony ASPX.

Architektura poprzedniej aplikacji wyglądała następująco:

Mamy tu do czynienia z architekturą MVC (Model – Widok – Kontroler):

  • [Default.aspx.cs] zawiera kod kontrolera. Strona [Default.aspx] jest jedynym punktem kontaktu z klientem. Przetwarza ona wszystkie żądania pochodzące od klienta.
  • [Saisies, Simulation, Simulations, ...] to widoki. Widoki te są tutaj zaimplementowane za pomocą komponentów [View] na stronie [Default.aspx].

Architektura nowej wersji będzie wyglądała następująco:

  • zmienia się jedynie warstwa [web]
  • Widoki (to, co jest wyświetlane użytkownikowi) nie ulegają zmianie.
  • Kod kontrolera, który w poprzedniej wersji znajdował się w całości w pliku [Default.aspx.cs], jest teraz rozdzielony na kilka stron:
    • [MasterPage.master]: strona zawierająca elementy wspólne dla różnych widoków: górny pasek z opcjami menu
    • [Formulaire.aspx]: strona zawierająca formularz symulacji i obsługująca działania wykonywane w tym formularzu
    • [Simulations.aspx]: strona zawierająca listę symulacji i obsługująca działania wykonywane na tej samej stronie
    • [Erreurs.aspx]: strona wyświetlana w przypadku błędu podczas inicjalizacji aplikacji. Na tej stronie nie można wykonać żadnych czynności.

Można uznać, że mamy tu do czynienia z architekturą MVC z wieloma kontrolerami, podczas gdy architektura poprzedniej wersji była architekturą MVC z jednym kontrolerem.

Przetwarzanie żądania klienta przebiega zgodnie z następującymi etapami:

  1. klient wysyła żądanie do aplikacji. Wysyła je do jednej z dwóch stron o nazwie [Formulaire.aspx, Simulations.aspx].
  2. Żądana strona przetwarza to żądanie. W tym celu może potrzebować pomocy warstwy [métier], która z kolei może potrzebować warstwy [dao], jeśli konieczna jest wymiana danych z bazą danych. Aplikacja otrzymuje odpowiedź od warstwy [métier].
  3. Na podstawie tej odpowiedzi wybiera (3) widok (= odpowiedź), który ma zostać wysłany do klienta, dostarczając mu (4) potrzebne informacje (szablon).
  4. Odpowiedź jest wysyłana do klienta (5)

11.1. Widoki aplikacji

Różne widoki prezentowane użytkownikowi to:

  • - widok [VueSaisies], który przedstawia formularz symulacji

Image

  • - widok [VueSimulation] służący do wyświetlania szczegółowych wyników symulacji:

Image

  • - widok [VueSimulations], zawierający listę symulacji przeprowadzonych przez klienta

Image

  • - widok [VueSimulationsVides], który wskazuje, że klient nie ma żadnych symulacji lub nie ma ich już:

Image

  • widok [VueErreurs], który wskazuje na błąd podczas inicjalizacji aplikacji:

Image

11.2. Generowanie widoków w środowisku z wieloma kontrolerami

W poprzedniej wersji wszystkie widoki były generowane na podstawie jednej strony [Default.aspx]. Zawierała ona dwa komponenty [MultiView], a widoki składały się z połączenia jednego lub dwóch komponentów [View] należących do tych dwóch komponentów [MultiView].

Architektura ta, skuteczna przy niewielkiej liczbie widoków, osiąga swoje granice, gdy liczba komponentów tworzących różne widoki staje się znaczna: w rzeczywistości przy każdym żądaniu kierowanym do jedynej strony [Default.aspx] instancjonowane są wszystkie jej komponenty, mimo że tylko niektóre z nich zostaną wykorzystane do wygenerowania odpowiedzi dla użytkownika. W ten sposób przy każdym nowym żądaniu wykonywana jest zbędna praca, która staje się uciążliwa, gdy całkowita liczba komponentów strony jest duża.

Rozwiązaniem jest zatem rozdzielenie widoków na różne strony. Właśnie to tutaj robimy. Przyjrzyjmy się dwóm różnym przypadkom generowania widoków:

  1. zapytanie jest kierowane do strony P1, która generuje odpowiedź
  2. wysyłane jest żądanie do strony P1, a ta z kolei zwraca się do strony P2 o wygenerowanie odpowiedzi

11.2.1. Przypadek 1: strona kontroler / widok

W przypadku 1 powracamy do architektury z jednym kontrolerem z poprzedniej wersji, gdzie strona [Default.aspx] jest stroną P1:

  1. klient wysyła żądanie do strony P1 (1)
  2. strona P1 przetwarza to żądanie. W tym celu może potrzebować pomocy warstwy [métier] (2), która z kolei może potrzebować warstwy [dao], jeśli konieczna jest wymiana danych z bazą danych. Aplikacja otrzymuje odpowiedź od warstwy [métier].
  3. Na podstawie tej odpowiedzi wybiera (3) widok (= odpowiedź), który ma zostać wysłany do klienta, dostarczając mu (4) potrzebne informacje (szablon). Chodzi tu o wybranie na stronie P1 komponentów [Panel] lub [View] do wyświetlenia oraz zainicjowanie zawartych w nich komponentów.
  4. Odpowiedź jest wysyłana do klienta (5)

Oto dwa przykłady zaczerpnięte z analizowanej aplikacji:

[page Formulaire.aspx]

  • w [1]: użytkownik, po wywołaniu strony [Formulaire.aspx], żąda symulacji
  • w przypadku [2]: strona [Formulaire.aspx] przetworzyła to żądanie i sama wygenerowała odpowiedź, wyświetlając komponent [View], który nie był wyświetlany na stronie [1]

[page Simulations.aspx]

  • w [1]: użytkownik, po wywołaniu strony [Simulations.aspx], chce usunąć symulację
  • w [2]: strona [Simulations.aspx] przetworzyła to żądanie i sama wygenerowała odpowiedź, ponownie wyświetlając nową listę symulacji.
2

11.2.2. Przypadek 2: strona 1 – kontroler, strona 2 – kontroler / widok

Przypadek 2 może obejmować różne architektury. Wybierzemy następującą:

  1. klient wysyła żądanie do strony P1 (1)
  2. strona P1 przetwarza to żądanie. W tym celu może potrzebować pomocy warstwy [métier] (2), która z kolei może potrzebować warstwy [dao], jeśli konieczna jest wymiana danych z bazą danych. Aplikacja otrzymuje odpowiedź od warstwy [métier].
  3. Na podstawie tej odpowiedzi wybiera (3) widok (= odpowiedź), który ma zostać wysłany do klienta, dostarczając mu (4) potrzebne informacje (szablon). Okazuje się, że w tym przypadku widok, który ma zostać wygenerowany, musi zostać wygenerowany przez inną stronę niż P1, a mianowicie stronę P2. Aby wykonać operacje (3) i (4), strona P1 ma dwie możliwości:
    • przekazać wykonanie do strony P2 za pomocą operacji [Server.Transfer(" P2.aspx ")]. W takim przypadku może umieścić szablon przeznaczony dla strony P2 w kontekście żądania [Context.Items[" clé "]=valeur] lub w sesji użytkownika [Session.[" clé "]=valeur]. Strona P2 zostanie wówczas zainicjowana, a podczas przetwarzania jej zdarzenia, na przykład Load, będzie mogła pobrać informacje przekazane przez stronę P1 za pomocą operacji [valeur=(Type)Context.Items[" clé "]] lub [valeur=(Type)Session[" clé "]], w zależności od sytuacji, gdzie Type to typ wartości powiązanej z kluczem. Przesyłanie wartości za pomocą kontekstu Context jest najbardziej odpowiednie, jeśli nie ma potrzeby zachowywania wartości modelu na potrzeby przyszłego żądania klienta.
    • należy poprosić klienta o przekierowanie się na stronę P2 za pomocą operacji [Response.Redirect(" P2.aspx ")]. W tym przypadku strona P1 umieści szablon przeznaczony dla strony P2 w sesji, ponieważ kontekst żądania Context jest usuwany po zakończeniu każdego żądania. W tym przypadku przekierowanie spowoduje zakończenie pierwszego żądania klienta skierowanego do P1 oraz wysłanie drugiego żądania tego samego klienta, tym razem do P2. Mamy do czynienia z dwoma kolejnymi żądaniami. Wiadomo, że sesja jest jednym ze sposobów zachowania „pamięci” między żądaniami. Istnieją jednak inne rozwiązania poza sesją.
  4. Niezależnie od tego, w jaki sposób P2 przejmuje kontrolę, następnie powracamy do przypadku 1: P2 otrzymała żądanie, które przetworzy (5), i sama wygeneruje odpowiedź (6, 7). Można sobie również wyobrazić, że strona P2 po przetworzeniu żądania przekaże kontrolę stronie P3 i tak dalej.

Oto przykład zaczerpnięty z analizowanej aplikacji:

  • do [1]: użytkownik, który zażądał strony [Formulaire.aspx], prosi o wyświetlenie listy symulacji
  • w [2]: strona [Formulaire.aspx] przetwarza to żądanie i przekierowuje klienta na stronę [Simulations.aspx]. To właśnie ta ostatnia strona dostarcza odpowiedź użytkownikowi. Zamiast prosić klienta o przekierowanie, strona [Formulaire.aspx] mogła przekazać żądanie klienta do strony [Simulations.aspx]. W takim przypadku na stronie [2] widoczny byłby ten sam adres URL, co na stronie [1]. Przeglądarka zawsze wyświetla bowiem ostatni żądany adres URL:
    • akcja żądana na stronie [1] jest skierowana do strony [Formulaire.aspx]. Przeglądarka wykonuje żądanie POST do tej strony.
    • Jeśli strona [Formulaire.aspx] przetworzy żądanie, a następnie przekieruje je za pomocą [Server.Transfer(" Simulations.aspx ")] na stronę [Simulations.aspx], pozostajemy w ramach tego samego żądania. Przeglądarka wyświetli wówczas pod adresem [2] stronę URL, do której nastąpiło przekierowanie z [Formulaire.aspx] za pomocą POST.
    • jeśli strona [Formulaire.aspx] przetworzy żądanie, a następnie przekieruje je poprzez [Response.Redirect(" Simulations.aspx ")] na stronę [Simulations.aspx], przeglądarka wysyła wówczas drugie żądanie, GET, skierowane do [Simulations.aspx]. Następnie przeglądarka wyświetli na stronie [2] stronę URL, do której nastąpiło przekierowanie ze strony [Simulations.aspx] za pomocą przekierowania GET. Właśnie to pokazuje powyższy zrzut ekranu o numerze [2].

11.3. Projekt Visual Web Developer warstwy [web]

Projekt Visual Web Developer warstwy [web] wygląda następująco:

  • w [1] znajduje się:
    • plik konfiguracyjny aplikacji [Web.config] – jest identyczny z plikiem aplikacji [pam-v4-3tier-nhibernate-multivues-monopage].
    • strona [Default.aspx] – po prostu przekierowuje klienta na stronę [Formulaire.aspx]
    • strona [Formulaire.aspx], która wyświetla użytkownikowi formularz symulacji i obsługuje działania związane z tym formularzem
    • strona [Simulations.aspx], która wyświetla użytkownikowi listę jego symulacji i obsługuje działania związane z tą stroną
    • stronę [Erreurs.aspx], która wyświetla użytkownikowi stronę informującą o błędzie wystąpionym podczas uruchamiania aplikacji internetowej.
  • Na stronie [2] widoczne są odniesienia do projektu.

Wróćmy do architektury nowego projektu:

W porównaniu z projektem [pam-v4-3tier-nhibernate-multivues-monopage] zmieniają się jedynie widoki. W ten sposób nowy projekt przejmuje niektóre pliki z tego projektu:

  • plik konfiguracyjny [Web.config]
  • pliki DLL odwołujące się do [pam-dao-nhibernate, pam-metier-dao-nhibernate, Spring.Core, NHibernate]
  • globalną klasę aplikacji [Global.asax]
  • foldery [images, ressources, pam]

Aby zachować spójność z projektem w trakcie tworzenia, zadbamy o to, aby przestrzeń nazw widoków i globalnej klasy aplikacji wynosiła [pam-v7]:

  

11.4. Kod odpowiedzialny za wyświetlanie stron

11.4.1. Strona wzorcowa [MasterPage.master]

Widoki aplikacji przedstawione w paragrafie 11.1 mają wspólne elementy, które można wyodrębnić do strony głównej, zwanej w programie Visual Studio „Master Page”. Weźmy na przykład widoki [VueSaisies] i [VueSimulationsVides] poniżej, wygenerowane odpowiednio przez strony [Formulaire.aspx] i [Simulations.aspx]:

Te dwa widoki mają wspólny górny pasek (tytuł i opcje menu). Tak samo jest w przypadku wszystkich widoków, które zostaną wyświetlone użytkownikowi: wszystkie będą miały ten sam górny pasek. Aby różne strony mogły współdzielić ten sam fragment układu, istnieją różne rozwiązania, w tym następujące:

  • umieszczenie tego wspólnego fragmentu w komponencie użytkownika. Była to główna technika stosowana w ASP.NET 1.1
  • umieszczenie tego wspólnego fragmentu na stronie wzorcowej. Technika ta pojawiła się wraz z wersją ASP.NET 2.0. To właśnie tę metodę stosujemy w tym przypadku.

Aby utworzyć stronę wzorcową w aplikacji internetowej, można postępować w następujący sposób:

  • kliknąć prawym przyciskiem myszy na projekt / Dodaj nowy element / Strona wzorcowa:

Dodanie strony wzorcowej powoduje domyślnie dodanie trzech plików do aplikacji internetowej:

  • [MasterPage.master]: kod prezentacji strony głównej
  • [MasterPage.master.cs]: kod sterujący strony głównej
  • [Masterpage.Master.designer.cs]: deklaracja komponentów strony szablonowej

Kod wygenerowany przez Visual Studio w pliku [MasterPage.master] wygląda następująco:


<%@ Master Language="C#" AutoEventWireup="true" CodeBehind="MasterPage.master.cs" Inherits="pam_v7.MasterPage" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title>Untitled Page</title>
    <asp:ContentPlaceHolder id="head" runat="server">
    </asp:ContentPlaceHolder>
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <asp:ContentPlaceHolder id="ContentPlaceHolder1" runat="server">
        
        </asp:ContentPlaceHolder>
    </div>
    </form>
</body>
</html>
  • wiersz 1: tag <%@ Master ... %> służy do zdefiniowania strony jako strony wzorcowej. Kod sterujący strony będzie znajdował się w pliku określonym przez atrybut CodeBehind, a strona odziedziczy klasę zdefiniowaną przez atrybut Inherits.
  • wiersze 12–18: formularz strony głównej
  • wiersze 14–16: pusty kontener, który w naszej aplikacji będzie zawierał jedną ze stron [Formulaire.aspx, Simulations.aspx, Erreurs.aspx]. Klient otrzymuje w odpowiedzi zawsze tę samą stronę, stronę główną, w której kontener [ContentPlaceHolder1] otrzyma strumień HTML dostarczony przez jedną ze stron [Formulaire.aspx, Simulations.aspx, Erreurs.aspx]. Aby zatem zmienić wygląd stron wysyłanych do klientów, wystarczy zmienić wygląd strony głównej.
  • wiersze 8–9: pusty kontener, za pomocą którego strony „potomne” będą mogły dostosować nagłówek <head>...</head>.

Wizualna reprezentacja (zakładka „Design”) tego kodu źródłowego została przedstawiona w punkcie (1) poniżej. Ponadto dzięki komponentowi [ContentPlaceHolder] (2) z paska narzędzi [Standard] można dodać dowolną liczbę kontenerów.

Kod sterujący wygenerowany przez Visual Studio w [MasterPage.master.cs] wygląda następująco:


using System;

public partial class MasterPage : System.Web.UI.MasterPage
{
    protected void Page_Load(object sender, EventArgs e)
    {

    }
}
  • wiersz 3: klasa, do której odwołuje się atrybut [Inherits] w dyrektywie <%@ Master ... %> na stronie [MasterPage.master], wywodzi się z klasy [System.Web.UI.MasterPage]

Powyżej widzimy metodę Page_Load, która obsługuje zdarzenie Load strony głównej. Strona główna będzie zawierała w sobie inną stronę. W jakiej kolejności występują zdarzenia Load obu stron? Jest to ogólna zasada: zdarzenie Load danego komponentu ma miejsce przed zdarzeniem jego kontenera. W tym przypadku zdarzenie Load strony wstawionej do strony głównej nastąpi zatem przed zdarzeniem samej strony głównej.

Aby w programie wygenerować stronę, której stroną szablonową jest poprzednia strona [MasterPage.master], można postępować w następujący sposób:

  • w [1]: kliknij prawym przyciskiem myszy na szablonie strony, a następnie wybierz opcję [Ajouter une page de contenu]
  • w [2]: generowana jest strona domyślna, w tym przypadku [WebForm1.aspx].

Kod prezentacji [WebForm1.aspx] wygląda następująco:


<%@ Page Title="" Language="C#" MasterPageFile="~/MasterPage.Master" AutoEventWireup="true" CodeBehind="WebForm1.aspx.cs" Inherits="pam_v7.WebForm1" %>
<asp:Content ID="Content1" ContentPlaceHolderID="ContentPlaceHolder1" runat="server">
</asp:Content>
  • wiersz 1: dyrektywa Page i jej atrybuty
    • MasterPageFile: wskazuje plik szablonu strony opisanej przez dyrektywę. Znak ~ oznacza folder projektu.
    • pozostałe parametry są typowe dla strony internetowej ASP
  • wiersze 2–3: tagi <asp:Content> są połączone pojedynczo z dyrektywami <asp:ContentPlaceHolder> strony szablonowej za pomocą atrybutu ContentPlaceHolderID. Elementy umieszczone między wierszami 2–3 powyżej zostaną podczas wykonywania umieszczone w kontenerze ID ContentPlaceHolder1 strony szablonowej.

Zmieniając nazwę tak wygenerowanej strony na [WebForm1.aspx], można tworzyć różne strony, których stroną wzorcową jest [MasterPage.master].

W przypadku naszej aplikacji [SimuPaie] wygląd strony wzorcowej będzie następujący:

Nr
Typ
Nazwa
Rola
A
Panel (różowy powyżej)
nagłówek
nagłówek strony
B
Panel (żółty powyżej)
treść
treść strony
1
LinkButton
LinkButtonFaireSimulation
proszę o obliczenie symulacji
2
LinkButton
LinkButtonEffacerSimulation
kasuje formularz wprowadzania danych
3
LinkButton
LinkButtonVoirSimulations
wyświetla listę już przeprowadzonych symulacji
4
LinkButton
LinkButtonFormulaireSimulation
powrót do formularza wprowadzania danych
5
LinkButton
LinkButtonEnregistrerSimulation
zapisuje bieżącą symulację na liście symulacji
6
LinkButton
LinkButtonTerminerSession
kończy bieżącą sesję

Odpowiedni kod źródłowy wygląda następująco:


<%@ Master Language="C#" AutoEventWireup="true" CodeBehind="MasterPage.master.cs" Inherits="pam_v7.MasterPage" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head id="Head1" runat="server">
  <title>Application PAM</title>
</head>
<body background="ressources/standard.jpg">
  <form id="form1" runat="server">
  <asp:ScriptManager ID="ScriptManager1" runat="server" EnablePartialRendering="true" />
  <asp:UpdatePanel runat="server" ID="UpdatePanelPam" UpdateMode="Conditional">
    <ContentTemplate>
      <asp:Panel ID="entete" runat="server" BackColor="#FFE0C0">
        <table>
          <tr>
            <td>
              <h2>
                Simulateur de calcul de paie</h2>
            </td>
            <td>
              <label>
                &nbsp;&nbsp;&nbsp</label>
              <asp:UpdateProgress ID="UpdateProgress1" runat="server">
                <ProgressTemplate>
                  <img alt="" src="images/indicator.gif" />
                  <asp:Label ID="Label5" runat="server" BackColor="#FF8000"
                             EnableViewState="False" Text="Calcul en cours. Patientez ....">
                        </asp:Label>
                </ProgressTemplate>
              </asp:UpdateProgress>
            </td>
            <td>
              <asp:LinkButton ID="LinkButtonFaireSimulation" runat="server"
                         CausesValidation="False">| Faire la simulation<br />
                    </asp:LinkButton>
              <asp:LinkButton ID="LinkButtonEffacerSimulation" runat="server"
                         CausesValidation="False">| Effacer la simulation<br />
                    </asp:LinkButton>
              <asp:LinkButton ID="LinkButtonVoirSimulations" runat="server"
                     CausesValidation="False">| Voir les simulations<br />
                    </asp:LinkButton>
              <asp:LinkButton ID="LinkButtonFormulaireSimulation" runat="server"
                         CausesValidation="False">| Retour au formulaire de simulation<br />
                    </asp:LinkButton>
              <asp:LinkButton ID="LinkButtonEnregistrerSimulation" runat="server"
                         CausesValidation="False">| Enregistrer la simulation<br />
                    </asp:LinkButton>
              <asp:LinkButton ID="LinkButtonTerminerSession" runat="server"
                         CausesValidation="False">| Terminer la session<br />
                    </asp:LinkButton>
            </td>
          </tr>
        </table>
        <hr />
      </asp:Panel>
      <div>
        <asp:Panel ID="contenu" runat="server" BackColor="#FFFFC0">
          <asp:ContentPlaceHolder ID="ContentPlaceHolder1" runat="server">
          </asp:ContentPlaceHolder>
        </asp:Panel>
      </div>
    </ContentTemplate>
  </asp:UpdatePanel>
  </form>
</body>
</html>
  • wiersz 1: należy zwrócić uwagę na nazwę klasy strony szablonowej: MasterPage
  • wiersz 8: definiuje się obraz tła dla strony.
  • wiersze 9–64: formularz
  • wiersz 10: komponent ScriptManager niezbędny do obsługi efektów Ajax
  • wiersze 11–63: kontener AJax
  • wiersze 12–62: treść obsługująca Ajax
  • wiersze 13–55: komponent Panel [entete]
  • wiersze 57–60: komponent Panel [contenu]
  • wiersze 58–59: komponent ID [ContentPlaceHolder1], który będzie zawierał zakapsulowaną stronę [Formulaire.aspx, Simulations.aspx, Erreurs.aspx]

Aby utworzyć tę stronę, można wstawić do panelu [entete] kod ASPX z widoku [VueEntete] strony [Default.aspx] z wersji [pam-v4-3tier-nhibernate-multivues-monopage], opisanej w paragrafie 8.5.2.

11.4.2. Strona [Formulaire.aspx]

Aby wygenerować tę stronę, należy postępować zgodnie z metodą opisaną w paragrafie 11.4.1, a wygenerowaną w ten sposób stronę [WebForm1.aspx] należy przemianować na [Formulaire.aspx]. Wygląd strony [Formulaire.aspx] w trakcie tworzenia będzie następujący:

Wygląd strony [Formulaire.aspx] składa się z dwóch elementów:

  • w [1] strona główna wraz z jej kontenerem [ContentPlaceHolder1] (2)
  • w [2] – komponenty umieszczone w kontenerze [ContentPlaceHolder1]. Są one identyczne jak w poprzedniej aplikacji.

Kod źródłowy tej strony wygląda następująco:


<%@ Page Language="C#" MasterPageFile="~/MasterPage.master" AutoEventWireup="true"
  CodeBehind="Formulaire.aspx.cs" Inherits="pam_v7.PageFormulaire" Title="Simulation de calcul de paie : formulaire" %>

<%@ MasterType VirtualPath="~/MasterPage.master" %>
<asp:Content ID="Content1" ContentPlaceHolderID="ContentPlaceHolder1" runat="Server">
  <div>
    <table>
      <tr>
        <td>
          Employé
        </td>
        <td>
          Heures travaillées
        </td>
        <td>
          Jours travaillés
        </td>
        <td>
        </td>
      </tr>
...
</asp:Content>
  • wiersz 1: dyrektywa Page wraz z atrybutem MasterPageFile
  • wiersz 4: klasa kontrolna strony wzorcowej może udostępniać publiczne pola i właściwości. Są one dostępne dla stron osadzonych za pomocą składni Master.[champ] lub Master.[propriété]. Właściwość Master strony określa stronę główną w postaci instancji typu [System.Web.UI.MasterPage]. Dlatego w naszym przykładzie należałoby w rzeczywistości napisać (MasterPage)(Master).[champ] lub (MasterPage)(Master).[propriété]. Można uniknąć tej konwersji typów, wstawiając na stronie dyrektywę MasterType z linii 4. Atrybut VirtualPath tej dyrektywy wskazuje plik strony szablonowej. Kompilator może wówczas rozpoznać publiczne pola, właściwości i metody udostępniane przez klasę strony szablonowej, w tym przypadku typu [MasterPage].
  • wiersze 5–22: treść, która zostanie wstawiona do kontenera [ContentPlaceHolder1] strony szablonowej.

Stronę tę można utworzyć, umieszczając jako zawartość (wiersze 6–21) zawartość widoku [VueSaisies] opisanego w paragrafie 8.5.3 oraz zawartość widoku [VueSimulation] opisanego w paragrafie 8.5.4.

11.4.3. Strona [Simulations.aspx]

Aby wygenerować tę stronę, należy postępować zgodnie z metodą opisaną w paragrafie 11.4.1, a wygenerowaną w ten sposób stronę [WebForm1.aspx] należy przemianować na [Simulations.aspx]. Wygląd strony [Simulations.aspx] w trakcie tworzenia jest następujący:

Wygląd strony [Simulations.aspx] składa się z dwóch elementów:

  • w [1] strona główna wraz z jej kontenerem [ContentPlaceHolder1]
  • w [2] – komponenty umieszczone w kontenerze [ContentPlaceHolder1]. Są one identyczne jak w poprzedniej aplikacji.

Kod źródłowy tej strony wygląda następująco:


<%@ Page Language="C#" MasterPageFile="~/MasterPage.master" AutoEventWireup="true"
  CodeBehind="Simulations.aspx.cs" Inherits="pam_v7.PageSimulations" Title="Pam : liste des simulations" %>

<%@ MasterType VirtualPath="~/MasterPage.master" %>
<asp:Content ID="Content1" ContentPlaceHolderID="ContentPlaceHolder1" runat="Server">
  <asp:MultiView ID="MultiView1" runat="server">
    <asp:View ID="View1" runat="server">
      <h2>
        Liste de vos simulations</h2>
      <p>
        <asp:GridView ID="GridViewSimulations" runat="server" ...>
...
        </asp:GridView>
      </p>
    </asp:View>
    <asp:View ID="View2" runat="server">
      <h2>
        La liste de vos simulations est vide</h2>
    </asp:View>
  </asp:MultiView><br />
</asp:Content>

Stronę tę można utworzyć, umieszczając jako treść (wiersze 5–21) zawartość widoku [VueSimulations] opisanego w paragrafie 8.5.5 oraz zawartość widoku [VueSimulationsVides] opisanego w paragrafie 8.5.6.

11.4.4. Strona [Erreurs.aspx]

Aby wygenerować tę stronę, należy postępować zgodnie z metodą opisaną w paragrafie 11.4.1, a wygenerowaną w ten sposób stronę [WebForm1.aspx] należy przemianować na [Erreurs.aspx]. Wygląd strony [Erreurs.aspx] w trakcie tworzenia jest następujący:

Wygląd strony [Erreurs.aspx] składa się z dwóch elementów:

  • w [1] strona główna wraz z jej kontenerem [ContentPlaceHolder1]
  • w [2] – komponenty umieszczone w kontenerze [ContentPlaceHolder1]. Są one identyczne jak w poprzedniej aplikacji.

Kod źródłowy tej strony wygląda następująco:


<%@ Page Language="C#" MasterPageFile="~/MasterPage.master" AutoEventWireup="true"
  CodeBehind="Erreurs.aspx.cs" Inherits="pam_v7.PageErreurs" Title="Pam : erreurs" %>

<%@ MasterType VirtualPath="~/MasterPage.master" %>
<asp:Content ID="Content1" ContentPlaceHolderID="ContentPlaceHolder1" Runat="Server">
        <h3>Les erreurs suivantes se sont produites au démarrage de l'application</h3>
        <ul>
            <asp:Repeater id="rptErreurs" runat="server">
                <ItemTemplate>
                    <li>
                        <%# Container.DataItem %>
                    </li>
                </ItemTemplate>
            </asp:Repeater>
        </ul>
</asp:Content>

11.5. Kod kontrolny stron

11.5.1. Przegląd

Wróćmy do architektury aplikacji:

  • [Global] to obiekt typu [HttpApplication], który inicjuje (etap 0) aplikację. Klasa ta jest identyczna jak w poprzedniej wersji.
  • Kod kontrolera, który w poprzedniej wersji znajdował się w całości w klasie [Default.aspx.cs], jest teraz rozdzielony na kilka stron:
    • [MasterPage.master]: strona główna dla stron typu [Formulaire.aspx, Simulations.aspx, Erreurs.aspx]. Zawiera menu.
    • [Formulaire.aspx]: strona zawierająca formularz symulacji i zarządzająca działaniami wykonywanymi w tym formularzu
    • [Simulations.aspx]: strona zawierająca listę symulacji i zarządzająca działaniami wykonywanymi na tej stronie
    • [Erreurs.aspx]: strona wyświetlana w przypadku błędu podczas inicjalizacji aplikacji. Na tej stronie nie można wykonać żadnych czynności.

Przetwarzanie żądania klienta przebiega zgodnie z następującymi etapami:

  1. klient wysyła żądanie do aplikacji. Zazwyczaj robi to na jednej z dwóch stron [Formulaire.aspx, Simulations.aspx], ale nic nie stoi na przeszkodzie, aby zwrócił się do strony [Erreurs.aspx]. Należy uwzględnić ten przypadek.
  2. Żądana strona przetwarza to żądanie (etap 1). W tym celu może potrzebować pomocy warstwy [métier] (etap 2), która z kolei może potrzebować warstwy [dao], jeśli konieczna jest wymiana danych z bazą danych. Aplikacja otrzymuje odpowiedź od warstwy [métier].
  3. Na podstawie tej odpowiedzi aplikacja wybiera (krok 3) widok (= odpowiedź), który ma zostać wysłany do klienta, i dostarcza mu (krok 4) potrzebne informacje (szablon). Omówiliśmy trzy możliwości wygenerowania tej odpowiedzi:
    • żądana strona (D) jest jednocześnie stroną (R) wysłaną w odpowiedzi. Utworzenie szablonu odpowiedzi (R) polega wówczas na nadaniu niektórym elementom strony (D) wartości, jakie powinny mieć w odpowiedzi.
    • żądana strona (D) nie jest stroną (R) wysyłaną w odpowiedzi. Strona (D) może wówczas:
      • przekazać tok wykonania do strony (R) za pomocą instrukcji Server.Transfer(" R "). Szablon może wówczas zostać umieszczony w kontekście za pomocą instrukcji Context.Items("klucz")=wartość lub, rzadziej, w sesji za pomocą instrukcji Session.Items("klucz")=wartość
      • przekierować klienta na stronę (R) za pomocą instrukcji Response.redirect(" R "). Szablon można wówczas umieścić w sesji, ale nie w kontekście.
  4. odpowiedź jest wysyłana do klienta (krok 5)

Każda ze stron [MasterPage.master, Formulaire.aspx, Simulations.aspx, Erreurs.aspx] będzie reagować na jedno lub więcej z poniższych zdarzeń:

  • Init: pierwsze zdarzenie w cyklu życia strony
  • Load: występuje podczas ładowania strony
  • Click: kliknięcie jednego z linków w menu strony głównej

Przetwarzamy strony kolejno, zaczynając od strony głównej.

11.5.2. Kod sterujący strony [MasterPage.master]

11.5.2.1. Szkielet klasy

Kod sterujący strony głównej ma następujący szkielet:


using System.Web.UI.WebControls;

namespace pam_v7
{
  public partial class MasterPage : System.Web.UI.MasterPage
  {

    // menu 
    public LinkButton OptionFaireSimulation
    {
      get { return LinkButtonFaireSimulation; }
    }
...

    // ustawiamy menu 
    public void SetMenu(bool boolFaireSimulation, bool boolEnregistrerSimulation, bool boolEffacerSimulation, bool boolFormulaireSimulation, bool boolVoirSimulations, bool boolTerminerSession)
    {
....
    }

    // zarządzanie opcją [Terminer la session] 
    protected void LinkButtonTerminerSession_Click(object sender, System.EventArgs e)
    {
....
    }

    // inicjowanie strony głównej 
    protected void Page_Init(object sender, System.EventArgs e)
    {
....
      }
    }
  }
}
  • wiersz 5: klasa nosi nazwę [MasterPage] i wywodzi się z klasy systemowej [System.Web.UI.MasterPage].
  • wiersze 9–14: 6 opcji menu jest przedstawionych jako właściwości publiczne klasy
  • wiersze 16–19: metoda publiczna SetMenu umożliwi stronom [Formulaire.aspx, Simulations.aspx, Erreurs.aspx] ustawienie menu strony głównej
  • wiersze 22–25: procedura obsługująca kliknięcie linku [LinkButtonTerminerSession]
  • wiersze 28–31: procedura obsługi zdarzenia Init na stronie głównej

11.5.2.2. Właściwości publiczne klasy


using System.Web.UI.WebControls;

namespace pam_v7
{
  public partial class MasterPage : System.Web.UI.MasterPage
  {

    // menu 
    public LinkButton OptionFaireSimulation
    {
      get { return LinkButtonFaireSimulation; }
    }

    public LinkButton OptionEffacerSimulation
    {
      get { return LinkButtonEffacerSimulation; }
    }

    public LinkButton OptionEnregistrerSimulation
    {
      get { return LinkButtonEnregistrerSimulation; }
    }

    public LinkButton OptionVoirSimulations
    {
      get { return LinkButtonVoirSimulations; }
    }

    public LinkButton OptionTerminerSession
    {
      get { return LinkButtonTerminerSession; }
    }

    public LinkButton OptionFormulaireSimulation
    {
      get { return LinkButtonFormulaireSimulation; }
    }

...
  }
}

Aby zrozumieć ten kod, należy przypomnieć sobie elementy składające się na stronę główną:

Nr
Typ
Nazwa
Rola
A
Panel (różowy powyżej)
nagłówek
nagłówek strony
B
Panel (żółty powyżej)
treść
treść strony
1
LinkButton
LinkButtonFaireSimulation
proszę o obliczenie symulacji
2
LinkButton
LinkButtonEffacerSimulation
kasuje formularz wprowadzania danych
3
LinkButton
LinkButtonVoirSimulations
wyświetla listę już przeprowadzonych symulacji
4
LinkButton
LinkButtonFormulaireSimulation
powrót do formularza wprowadzania danych
5
LinkButton
LinkButtonEnregistrerSimulation
zapisuje bieżącą symulację na liście symulacji
6
LinkButton
LinkButtonTerminerSession
zamknie bieżącą sesję

Komponenty od 1 do 6 nie są dostępne poza stroną, na której się znajdują. Właściwości w wierszach od 9 do 37 mają na celu udostępnienie ich klasom zewnętrznym, w tym przypadku klasom z innych stron aplikacji.

11.5.2.3. Metoda SetMenu

Publiczna metoda SetMenu pozwala stronom [Formulaire.aspx, Simulations.aspx, Erreurs.aspx] ustalić menu strony głównej. Jej kod jest prosty:


        // ustawianie menu 
        public void SetMenu(bool boolFaireSimulation, bool boolEnregistrerSimulation, bool boolEffacerSimulation, bool boolFormulaireSimulation, bool boolVoirSimulations, bool boolTerminerSession)
        {
            // ustawiamy opcje menu 
            LinkButtonFaireSimulation.Visible = boolFaireSimulation;
            LinkButtonEnregistrerSimulation.Visible = boolEnregistrerSimulation;
            LinkButtonEffacerSimulation.Visible = boolEffacerSimulation;
            LinkButtonVoirSimulations.Visible = boolVoirSimulations;
            LinkButtonFormulaireSimulation.Visible = boolFormulaireSimulation;
            LinkButtonTerminerSession.Visible = boolTerminerSession;
}

11.5.2.4. Obsługa zdarzeń strony głównej

Strona główna będzie obsługiwać dwa zdarzenia:

  • zdarzenie Init, które jest pierwszym zdarzeniem w cyklu życia strony
  • zdarzenie Click związane z linkiem [LinkButtonTerminerSession]

Strona główna zawiera pięć innych linków: [LinkButtonFaireSimulation, LinkButtonEnregistrerSimulation, LinkButtonEffacerSimulation, LinkButtonVoirSimulations, LinkButtonFormulaireSimulation]. Jako przykład przyjrzyjmy się, co należy zrobić po kliknięciu linku [LinkButtonFaireSimulation]:

  1. sprawdzić dane wprowadzone (godziny, dni) na stronie [Formulaire.aspx]
  2. wykonanie obliczeń wynagrodzenia
  3. wyświetlić wyniki na stronie [Formulaire.aspx]

Czynności 1 i 3 wymagają dostępu do komponentów strony [Formulaire.aspx]. Tak jednak nie jest. Strona nadrzędna nie ma bowiem żadnej wiedzy na temat komponentów stron, które mogą zostać wstawione do jej kontenera [ContentPlaceHolder1]. W naszym przykładzie to strona [Formulaire.aspx] powinna obsłużyć kliknięcie linku [LinkButtonFaireSimulation], ponieważ to właśnie ona jest wyświetlana w momencie wystąpienia tego zdarzenia. W jaki sposób może zostać o nim powiadomiona?

  • Ponieważ link [LinkButtonFaireSimulation] nie jest częścią strony [Formulaire.aspx], nie można zapisać w [Formulaire.aspx] standardowej procedury:

    private void LinkButtonFaireSimulation_Click(object sender, System.EventArgs e)
    {
...
}

Problem ten można obejść, wprowadzając następujący kod w pliku [Formulaire.aspx]:


using System.Collections.Generic;
...

namespace pam_v7
{
    public partial class Formulaire : System.Web.UI.Page
    {
// ładowanie strony 
    protected void Page_Load(object sender, System.EventArgs e)
    {
        // menedżer zdarzeń
        Master.OptionFaireSimulation.Click += OptFaireSimulation_Click;
        Master.OptionEffacerSimulation.Click += OptEffacerSimulation_Click;
        Master.OptionVoirSimulations.Click += OptVoirSimulations_Click;
        Master.OptionEnregistrerSimulation.Click += OptEnregistrerSimulation_Click;
...
    }

    // obliczanie wynagrodzeń 
    private void OptFaireSimulation_Click(object sender, System.EventArgs e)
    {
....
    }

    // usunięcie symulacji 
    private void OptEffacerSimulation_Click(object sender, System.EventArgs e)
    {
...
    }

    protected void OptVoirSimulations_Click(object sender, System.EventArgs e)
    {
...
    }

    protected void OptEnregistrerSimulation_Click(object sender, System.EventArgs e)
    {
...
    }
 }
}
  • wiersze 12–15: gdy występuje zdarzenie Load na stronie [Formulaire.aspx], klasa [MasterPage] ze strony głównej została zainicjowana. Jej właściwości publiczne Optionxx są dostępne i mają typ LinkButton – jest to komponent obsługujący zdarzenie Click. Do tych zdarzeń Click przypisujemy następujące metody:
    • OptFaireSimulation_Click dla zdarzenia Click na linku LinkButtonFaireSimulation
    • OptEffacerSimulation_Click dla zdarzenia Click na łączu LinkButtonEffacerSimulation
    • OptVoirSimulations_Click dla wydarzenia Click pod linkiem LinkButtonVoirSimulations
    • OptEnregistrerSimulation_Click dla zdarzenia Click pod linkiem LinkButtonEnregistrerSimulation

Zarządzanie wydarzeniami Click w sześciu linkach menu zostanie rozdzielone w następujący sposób:

  • strona [Formulaire.aspx] będzie obsługiwać linki [LinkButtonFaireSimulation, LinkButtonEnregistrerSimulation, LinkButtonEffacerSimulation, LinkButtonVoirSimulations]
  • strona [Simulations.aspx] będzie obsługiwać link [LinkButtonFormulaireSimulation]
  • strona główna [MasterPage.master] będzie obsługiwać link [LinkButtonTerminerSession]. W przypadku tego zdarzenia nie musi ona bowiem znać strony, którą obejmuje.

11.5.2.5. Zdarzenie Init strony nadrzędnej

Trzy strony [Formulaire.aspx, Simulations.aspx, Erreurs.aspx] w aplikacji mają stronę główną [MasterPage.master]. Nazwijmy stronę główną M, a stronę zawartą E. Gdy strona E jest żądana przez klienta, następują kolejno następujące zdarzenia:

  • E.Init
  • M.Init
  • E.Load
  • M.Load
  • ...

Wykorzystamy zdarzenie Init ze strony M do wykonania kodu, który warto uruchomić jak najwcześniej, niezależnie od strony docelowej E. Aby zapoznać się z tym kodem, przyjrzyjmy się ponownie ogólnemu schematowi aplikacji:

Powyżej [Global] jest obiektem typu [HttpApplication], który inicjuje aplikację. Ta klasa jest taka sama jak w wersji [pam-v4-3tier-nhibernate-multivues-monopage]:


using System;
...

namespace pam_v7
{
  public class Global : System.Web.HttpApplication
  {
    // --- statyczne dane aplikacji ---
    public static Employe[] Employes;
    public static IPamMetier PamMetier = null;
    public static string Msg;
    public static bool Erreur = false;

    // uruchomienie aplikacji
    public void Application_Start(object sender, EventArgs e)
    {
...
    }

    public void Session_Start(object sender, EventArgs e)
    {
...
    }
  }
}

Jeśli klasa [Global] nie zdoła poprawnie zainicjować aplikacji, ustawia dwie publiczne zmienne statyczne:

  • zmienna logiczna „Błąd” w linii 12 zostaje ustawiona na vrai
  • zmienna „Msg” z linii 11 zawiera komunikat z szczegółami na temat napotkanego błędu

Gdy użytkownik wywołuje jedną ze stron [Formulaire.aspx, Simulations.aspx], a aplikacja nie została poprawnie zainicjowana, żądanie to musi zostać przekierowane na stronę [Erreurs.aspx], która wyświetli komunikat o błędzie klasy [Global]. Sytuację tę można obsłużyć na różne sposoby:

  • przeprowadzić test błędu inicjalizacji w menedżerze zdarzeń Init lub Load każdej ze stron [Formulaire.aspx, Simulations.aspx]
  • przeprowadzić test błędów inicjalizacji w menedżerze zdarzeń Init lub Load na stronie głównej obu tych stron. Zaletą tej metody jest umieszczenie testu błędów inicjalizacji w jednym miejscu.

Decydujemy się na przeprowadzenie testu błędów inicjalizacji w menedżerze zdarzeń Init strony głównej:


        protected void Page_Init(object sender, System.EventArgs e)
        {
            // menedżer zdarzeń 
            LinkButtonTerminerSession.Click += LinkButtonTerminerSession_Click;
            // czy wystąpiły błędy inicjalizacji? 
            if (Global.Erreur)
            {
                // czy zakapsulowana strona to strona błędów? 
                bool isPageErreurs =...;
                // jeśli wyświetlana jest strona błędów, pozostawiamy ją bez zmian, w przeciwnym razie przekierowujemy klienta na stronę błędów 
                if (!isPageErreurs)
                    Response.Redirect("Erreurs.aspx");
                return;
            }
}

Powyższy kod zostanie wykonany, gdy tylko zostanie wywołana jedna ze stron o numerze [Formulaire.aspx, Simulations.aspx, Erreurs.aspx]. W przypadku, gdy żądana jest strona [Formulaire.aspx, Simulations.aspx], wystarczy (w linii 12) przekierować klienta na stronę [Erreurs.aspx], która wyświetli komunikat o błędzie klasy [Global]. W przypadku, gdy żądana strona to [Erreurs.aspx], przekierowanie to nie powinno mieć miejsca: należy pozwolić na wyświetlenie strony [Erreurs.aspx]. Musimy zatem ustalić w metodzie [Page_Init] strony głównej, jaką stronę ta strona zawiera.

Wróćmy do drzewa komponentów strony głównej:


...
<body background="ressources/standard.jpg">
    <form id="form1" runat="server">
        <asp:Panel ID="entete" runat="server" BackColor="#FFE0C0" Width="1239px" >
...
        </asp:Panel>
        <div>
            <asp:Panel ID="contenu" runat="server" BackColor="#FFFFC0">
                <asp:ContentPlaceHolder ID="ContentPlaceHolder1" runat="server">
                </asp:ContentPlaceHolder>
            </asp:Panel>
        </div>
    </form>
</body>
</html>
  • wiersze 1–13: kontener o identyfikatorze „form1”
  • wiersze 4–6: kontener o identyfikatorze „entete”, zawarty w kontenerze o identyfikatorze „form1”
  • wiersze 8–11: kontener o identyfikatorze „contenu”, zawarty w kontenerze o identyfikatorze „form1”
  • wiersze 9–10: kontener o identyfikatorze „ContentPlaceHolder1”, zawarty w kontenerze o identyfikatorze „contenu”

Strona E, zawarta w stronie głównej M, znajduje się w kontenerze o identyfikatorze „ContentPlaceHolder1”. Aby odwołać się do komponentu o identyfikatorze C na tej stronie E, należy napisać:


this.FindControl("form1").FindControl("contenu").FindControl("ContentPlaceHolder1").FindControl("C");

Drzewo komponentów strony [Erreurs.aspx] wygląda następująco:


<%@ Page Language="C#" MasterPageFile="~/MasterPage.master" AutoEventWireup="true"
  CodeBehind="Erreurs.aspx.cs" Inherits="pam_v7.PageErreurs" Title="Pam : erreurs" %>

<%@ MasterType VirtualPath="~/MasterPage.master" %>
<asp:Content ID="Content1" ContentPlaceHolderID="ContentPlaceHolder1" Runat="Server">
        <h3>Les erreurs suivantes se sont produites au démarrage de l'application</h3>
        <ul>
            <asp:Repeater id="rptErreurs" runat="server">
                <ItemTemplate>
                    <li>
                        <%# Container.DataItem %>
                    </li>
                </ItemTemplate>
            </asp:Repeater>
        </ul>
</asp:Content>

Gdy strona [Erreurs.aspx] zostanie połączona ze stroną szablonową M, treść powyższego tagu <asp:Content> (wiersze 5–16) zostaje włączona do tagu <asp:ContentPlaceHolder> o identyfikatorze „ContentPlaceholder1” na stronie M, a drzewo komponentów tej strony przyjmuje wówczas następujący wygląd:

...
<body background="ressources/standard.jpg">
    <form id="form1" runat="server">
        <asp:panel ID="entete" runat="server" BackColor="#FFE0C0" Width="1239px" >
...
        </asp:Panel>
        <div>
            <asp:Panel ID="contenu" runat="server" BackColor="#FFFFC0">
                <asp:ContentPlaceHolder ID="ContentPlaceHolder1" runat="server">
              <h3>Les erreurs suivantes se sont produites au démarrage de l'application</h3>
              <ul>
                  <asp:Repeater id="rptErreurs" runat="server">
                      <ItemTemplate>
                          <li>
                              <%# Container.DataItem %>
                          </li>
                      </ItemTemplate>
                  </asp:Repeater>
              </ul>
                </asp:ContentPlaceHolder>
            </asp:Panel>
        </div>
    </form>
</body>
</html>
  • wiersz 12: komponent [rptErreurs] może służyć do sprawdzenia, czy strona główna M zawiera stronę [Erreurs.aspx]. Komponent ten występuje bowiem wyłącznie na tej stronie.

Te wyjaśnienia wystarczą do zrozumienia kodu procedury [Page_Init] na stronie głównej:


protected void Page_Init(object sender, System.EventArgs e)
        {
            // menedżer zdarzeń 
            LinkButtonTerminerSession.Click += LinkButtonTerminerSession_Click;
            // błędy inicjalizacji? 
            if (Global.Erreur)
            {
                // czy osadzona strona to strona błędów? 
                bool isPageErreurs = this.FindControl("form1").FindControl("contenu").FindControl("ContentPlaceHolder1").FindControl("rptErreurs") != null;
                // jeśli wyświetla się strona błędów, pozostawiamy ją bez zmian, w przeciwnym razie przekierowujemy klienta na stronę błędów 
                if (!isPageErreurs)
                    Response.Redirect("Erreurs.aspx");
                return;
            }
        }
  • wiersz 4: przypisuje się procedurę obsługi zdarzenia do zdarzenia Click dla linku LinkButtonTerminerSession. Procedura ta znajduje się w klasie MasterPage.
  • wiersz 6: sprawdzamy, czy klasa [Global] ustawiła swoją wartość logiczną Erreur
  • wiersz 9: jeśli tak, to zmienna logiczna IsPageErreurs wskazuje, czy strona zawarta w stronie głównej to strona [Erreurs.aspx]
  • wiersz 12: jeśli strona zawarta w stronie głównej nie jest stroną [Erreurs.aspx], wówczas klient jest przekierowywany na tę stronę, w przeciwnym razie nie podejmuje się żadnych działań.

11.5.2.6. Zdarzenie kliknięcia linku [LinkButtonTerminerSession]

Gdy użytkownik kliknie link [Terminer la session] w widoku (1) powyżej, należy wyczyścić zawartość sesji i wyświetlić pusty formularz (2).

Kod obsługi tego zdarzenia mógłby wyglądać następująco:


        protected void LinkButtonTerminerSession_Click(object sender, System.EventArgs e)
        {
            // sesja zostaje przerwana 
            Session.Abandon();
            // wyświetla się widok [formulaire] 
            Response.Redirect("Formulaire.aspx");
}
  • wiersz 4: bieżąca sesja zostaje zakończona
  • wiersz 6: klient jest przekierowywany na stronę [Formulaire.aspx]

Widać, że kod ten nie angażuje żadnego z komponentów stron [Formulaire.aspx, Simulations.aspx, Erreurs.aspx]. Zdarzenie może zatem być obsługiwane przez samą stronę główną.

11.5.3. Kod sterujący strony [Erreurs.aspx]

Kod sterujący strony [Erreurs.aspx] mógłby wyglądać następująco:


using System.Collections.Generic;

namespace pam_v7
{
  public partial class Erreurs : System.Web.UI.Page
  {
    protected void Page_Load(object sender, System.EventArgs e)
    {
      // błędy inicjalizacji? 
      if (Global.Erreur)
      {
        // przygotowuje się szablon strony [erreurs] 
        List<string> erreursInitialisation = new List<string>();
        erreursInitialisation.Add(Global.Msg);
        // powiązanie listy błędów z odpowiednim komponentem 
        rptErreurs.DataSource = erreursInitialisation;
        rptErreurs.DataBind();
      }
      // ustawianie menu 
      Master.SetMenu(false, false, false, false, false, false);
    }
  }
}

Przypomnijmy, że jedyną rolą strony [Erreurs.aspx] jest wyświetlenie błędu inicjalizacji aplikacji, gdy ten wystąpi:

  • wiersz 10: sprawdzane jest, czy inicjalizacja zakończyła się błędem
  • wiersze 13–14: jeśli tak, komunikat o błędzie (Global.Msg) jest umieszczany na liście [ErreursInitialisation]
  • wiersze 16–17: komponent [rptErreurs] otrzymuje polecenie wyświetlenia tej listy
  • wiersz 20: w każdym przypadku (niezależnie od tego, czy wystąpił błąd, czy nie) opcje menu strony głównej nie są wyświetlane, dzięki czemu użytkownik nie może zainicjować żadnej nowej czynności z tej strony.

Co się stanie, jeśli użytkownik bezpośrednio wywoła stronę [Erreurs.aspx] (czego nie powinien robić podczas normalnego korzystania z aplikacji)? Analizując kod stron [MasterPage.master.cs] i [Erreurs.aspx.cs], można zauważyć, że:

  • jeśli wystąpił błąd inicjalizacji, zostanie on wyświetlony
  • jeśli nie wystąpił błąd inicjalizacji, użytkownik otrzymuje stronę zawierającą wyłącznie nagłówek [MasterPage.master] bez wyświetlanych opcji menu.

11.5.4. Kod kontrolny strony [Formulaire.aspx]

11.5.4.1. Szkielet klasy

Szkielet kodu sterującego strony [Formulaire.aspx] mógłby wyglądać następująco:


using Pam.Metier.Entites;
...

partial class PageFormulaire : System.Web.UI.Page
{

    // ładowanie strony 
    protected void Page_Load(object sender, System.EventArgs e)
    {
        // menedżer zdarzeń
        Master.OptionFaireSimulation.Click += OptFaireSimulation_Click;
        Master.OptionEffacerSimulation.Click += OptEffacerSimulation_Click;
        Master.OptionVoirSimulations.Click += OptVoirSimulations_Click;
        Master.OptionEnregistrerSimulation.Click += OptEnregistrerSimulation_Click;
....
    }

    // obliczanie wynagrodzeń 
    private void OptFaireSimulation_Click(object sender, System.EventArgs e)
    {
....
    }

    // usunięcie symulacji 
    private void OptEffacerSimulation_Click(object sender, System.EventArgs e)
    {
...
    }

    protected void OptVoirSimulations_Click(object sender, System.EventArgs e)
    {
....
    }

    protected void OptEnregistrerSimulation_Click(object sender, System.EventArgs e)
    {
...
    }

}

Kod sterujący strony [Formulaire.aspx] obsługuje pięć zdarzeń:

  1. zdarzenie Load na stronie
  2. zdarzenie Click dotyczące linku [LinkButtonFaireSimulation] na stronie głównej
  3. zdarzenie Click na linku [LinkButtonEffacerSimulation] na stronie głównej
  4. zdarzenie Click w linku [LinkButtonEnregistrerSimulation] na stronie głównej
  5. zdarzenie Click w odnośniku [LinkButtonVoirSimulations] na stronie głównej

11.5.4.2. Zdarzenie załadowania strony

Szkielet procedury obsługi zdarzenia Load na stronie mógłby wyglądać następująco:


    protected void Page_Load(object sender, System.EventArgs e)
    {
        // menedżer zdarzeń
        Master.OptionFaireSimulation.Click += OptFaireSimulation_Click;
        Master.OptionEffacerSimulation.Click += OptEffacerSimulation_Click;
        Master.OptionVoirSimulations.Click += OptVoirSimulations_Click;
        Master.OptionEnregistrerSimulation.Click += OptEnregistrerSimulation_Click;
        // wyświetlanie widoku [saisies] 
        ...
        // pozycjonowanie menu strony głównej 
        ...
        // przetwarzanie zapytania GET 
        if (!IsPostBack)
        {
            // ładowanie nazwisk pracowników do listy rozwijanej 
...
            // inicjowanie widoku [saisies] z danymi zapamiętanymi w sesji, jeśli takie istnieją 
....
        }
}

Przykładem wyjaśniającym komentarz w wierszu 17 może być następujący:

  • w [1] żąda się wyświetlenia listy symulacji. Wprowadzono dane w [A, B, C].
  • W [2] wyświetla się lista
  • w [3] żąda się powrotu do formularza
  • w [4] formularz jest taki, jak go zostawiliśmy. Ponieważ miały miejsce dwa żądania, (1,2) i (3,4), oznacza to, że:
    • podczas przejścia z [1] do [2] dane wprowadzone w [1] zostały zapisane
    • podczas przejścia z [3] do [4] zostały one przywrócone. Przywrócenie to realizuje procedura [Page_Load] z [Formulaire.aspx].

Zadanie: uzupełnij procedurę Page_Load, korzystając z komentarzy i kodu wersji [pam-v4-3tier-nhibernate-multivues-monopage]


11.5.4.3. Obsługa zdarzeń kliknięć na linkach w menu

Szkielet procedur obsługi zdarzeń Click dotyczących linków na stronie głównej wygląda następująco:


// obliczanie wynagrodzenia 
    private void OptFaireSimulation_Click(object sender, System.EventArgs e)
    {
        // efekt Ajax
        Thread.Sleep(3000);
        // czy strona jest poprawna? 
        Page.Validate();
        if (!Page.IsValid)
        {
            // wyświetlenie widoku [saisie] 
...
        }
        // strona jest poprawna – pobieramy wprowadzone dane 
...
        // obliczamy wynagrodzenie pracownika 
        FeuilleSalaire feuillesalaire;
        try
        {
            feuillesalaire = ...;
        }
        catch (PamException ex)
        {
            // wystąpił błąd 
...
            return;
        }
        // zapisujemy wynik w sesji 
        Session["simulation"] = ...;
        // dane wprowadzane są zapisywane w sesji 
...
        // wyświetlanie 
...
        // wyświetlanie widoków 
...
        // wyświetlanie menu MasterPage 
...
    }

    // usunięcie symulacji 
    private void OptEffacerSimulation_Click(object sender, System.EventArgs e)
    {
        // wyświetlanie panelu [saisie] 
...
        // wybór pierwszego pracownika 
...
    }

    protected void OptVoirSimulations_Click(object sender, System.EventArgs e)
    {
        // dane są zapisywane w sesji 
...
        // wyświetlanie widoku [simulations] 
        Response.Redirect("simulations.aspx");
    }

    protected void OptEnregistrerSimulation_Click(object sender, System.EventArgs e)
    {
        // zapisywanie bieżącej symulacji w sesji użytkownika 
...
        // wyświetla się widok [simulations] 
        Response.Redirect("simulations.aspx");
}

Zadanie: uzupełnij kod powyższych procedur, korzystając z komentarzy i kodu z wersji [pam-v4-3tier-nhibernate-multivues-monopage]


11.5.5. Kod kontrolny strony [Simulations.aspx]

Szkielet kodu sterującego strony [Simulations.aspx] mógłby wyglądać następująco:


using System.Collections.Generic;
using Pam.Web;
using System.Web.UI.WebControls;

partial class PageSimulations : System.Web.UI.Page
{

    // symulacje 
    private List<Simulation> simulations;

    // ładowanie strony
    protected void Page_Load(object sender, System.EventArgs e)
    {
        // menedżer zdarzeń 
        Master.OptionFormulaireSimulation.Click += OptFormulaireSimulation_Click;
        GridViewSimulations.RowDeleting += GridViewSimulations_RowDeleting;
        // pobieranie symulacji z sesji
        simulations = ...;
        // czy są jakieś symulacje? 
        if (simulations.Count != 0)
        {
            // pierwszy widoczny widok 
            ...
            // wypełnianie widoku siatki 
            ...
        }
        else
        {
            // drugi widok 
            ...
        }
        // ustawiamy menu 
        ...
    }

    protected void GridViewSimulations_RowDeleting(object sender, System.Web.UI.WebControls.GridViewDeleteEventArgs e)
    {
        // pobieranie symulacji z sesji
        List<Simulation> simulations = ...;
        // usuwanie wskazanej symulacji (e.RowIndex oznacza numer usuniętego wiersza w widoku siatki)
        ..
        // czy pozostały jeszcze symulacje? 
        if (simulations.Count != 0)
        {
            // wypełnia się widok siatki 
            ...
        }
        else
        {
            // widok [SimulationsVides] 
            ...
        }
    }

    protected void OptFormulaireSimulation_Click(object sender, System.EventArgs e)
    {
        // wyświetlany jest widok [formulaire] 
        Response.Redirect("formulaire.aspx");
    }

}

Zadanie: uzupełnij kod procedur powyżej, korzystając z komentarzy i kodu z wersji [pam-v4-3tier-nhibernate-multivues-monopage]


11.5.6. Kod kontrolny strony [Default.aspx]

W aplikacji można przewidzieć stronę [Default.aspx], aby umożliwić użytkownikowi wywołanie URL z aplikacji bez określania konkretnej strony, jak poniżej:

W odpowiedzi na żądanie [1] otrzymano stronę [Formulaire.aspx] (2). Wiadomo, że żądanie (1) jest domyślnie obsługiwane przez stronę [Default.aspx] aplikacji. Aby uzyskać stronę (2), wystarczy, aby strona [Default.aspx] przekierowała klienta na stronę [Formulaire.aspx]. Można to osiągnąć za pomocą następującego kodu:


partial class _Default : System.Web.UI.Page
{

    protected void Page_Init(object sender, System.EventArgs e)
    {
        // przekierowuje do formularza wprowadzania danych 
        Response.Redirect("Formulaire.aspx");
    }
}

Strona prezentacyjna [Default.aspx] zawiera jedynie dyrektywę, która łączy ją ze stroną [Default.aspx.cs]:


<%@ Page Language="C#" AutoEventWireup="true"
  CodeBehind="Default.aspx.cs" Inherits="pam_v7._Default" Title="Untitled Page" %>