Skip to content

4. Aplikacja [SimuPaie] – wersja 1 – ASP.NET

4.1. Introduction

Chcemy stworzyć aplikację o nazwie .NET, która umożliwi użytkownikowi przeprowadzanie symulacji obliczeń wynagrodzeń opiekunek dziecięcych ze stowarzyszenia „Maison de la petite enfance” w danej gminie.

formularz ASP.NET służący do obliczania wynagrodzenia będzie wyglądał następująco:

Image

Aplikacja ASP.NET będzie miała następującą architekturę:

  • Podczas pierwszego żądania skierowanego do aplikacji tworzona jest instancja obiektu typu [Global], wywodzącego się z typu [System.Web.HttpApplication]. To właśnie on wykorzysta plik konfiguracyjny [web.config] aplikacji internetowej i zapisał w pamięci podręcznej niektóre dane z bazy danych (operacja 0 powyżej).
  • Podczas pierwszego żądania (operacja 1) skierowanego do strony [Default.aspx], która jest jedyną stroną aplikacji, przetwarzane jest zdarzenie [Load]. W ramach tego zdarzenia realizowane jest wypełnienie listy rozwijanej pracowników. Dane niezbędne do wypełnienia listy rozwijanej są pobierane z obiektu [Global], który je zapisał w pamięci podręcznej. Jest to operacja 2 powyżej. Operacja 4 wysyła tak zainicjowaną stronę do użytkownika.
  • Podczas żądania obliczenia wynagrodzenia (operacja 1) skierowanego do strony [Default.aspx] ponownie przetwarzane jest zdarzenie [Load]. Nie podejmuje się żadnych działań, ponieważ jest to żądanie typu POST, a moduł obsługi [Pam_Load] został zaprogramowany tak, aby w tym przypadku nie podejmować żadnych działań (wykorzystanie wartości logicznej IsPostBack). Po przetworzeniu zdarzenia [Load] następnym przetwarzanym zdarzeniem jest zdarzenie [Click] dotyczące przycisku [Salaire]. To zdarzenie wymaga danych, które nie zostały zapisane w pamięci podręcznej w zdarzeniu [Global]. Dlatego też obsługa zdarzenia pobiera je z bazy danych. Jest to operacja nr 3 opisana powyżej. Następnie przeprowadzane jest obliczenie wynagrodzenia, a operacja nr 4 wysyła wyniki do użytkownika.

4.2. Projekt Visual Web Developer 2008

  • w pliku [1] tworzy się nowy projekt
  • w [2], typu [Web / Application Web ASP.NET]
  • w [3] nadajemy nazwę projektowi
  • w [4] podajemy jego nazwę, a w [5] lokalizację. Dla projektu zostanie utworzony folder [c:\temp\pam-aspnet\pam-v1-adonet].
  • w [5] projekt Visual Web Developer
  • w [6] – właściwości projektu (kliknij prawym przyciskiem myszy na projekcie / Właściwości / Aplikacja).
  • na [7], nazwa zestawu, który zostanie wygenerowany podczas kompilacji projektu
  • w [8], domyślna przestrzeń nazw, którą chcemy zastosować dla klas projektu. Klasa [_Default] zdefiniowana w plikach [Default.aspx.cs] i [Default.aspx.designer.cs] została utworzona w przestrzeni nazw [pam_v1_adonet], wywodzącej się od nazwy projektu. Przestrzeń nazw tę można zmienić bezpośrednio w kodzie tych dwóch plików:

[Default.aspx.cs]


using System;
....

namespace pam_v1
{
  public partial class _Default : System.Web.UI.Page
  {

[Default.aspx.designer.cs]


//------------------------------------------------------------------------------
// <wygenerowane automatycznie>
//      Ten kod został wygenerowany przez narzędzie.
//      Wersja środowiska uruchomieniowego: 2.0.50727.3603
//
//      Zmiany wprowadzone w tym pliku mogą spowodować nieprawidłowe działanie i zostaną utracone, jeśli
//      kod zostanie wygenerowany ponownie.
// </auto-generated>
//------------------------------------------------------------------------------

namespace pam_v1 {
    
    
    public partial class _Default {
        
.........

Należy również zmodyfikować znaczniki w pliku [Default.aspx]:

  

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

Powyższy atrybut Inherits odnosi się do klasy zdefiniowanej w plikach [Default.aspx.cs] i [Default.aspx.designer.cs]. Wykorzystuje się w nich przestrzeń nazw pam_v1.

4.2.1. Formularz [Default.aspx]

Wygląd formularza [Default.aspx] jest następujący:

Składniki są następujące:

Nr
Typ
Nazwa
Rola
1
DropDownList
ComboBoxEmployes
Zawiera listę nazwisk pracowników
2
TextBox
TextBoxHeures
Liczba przepracowanych godzin – rzeczywista
3
TextBox
TextBoxJours
Liczba przepracowanych dni – liczba całkowita
4
Przycisk
ButtonSalaire
Żądanie obliczenia wynagrodzenia
5
TextBox
TextBoxErreur
Komunikat informacyjny dla użytkownika
ReadOnly=true, TextMode=MultiLine
6
Etykieta
LabelNom
Nazwisko wybranego pracownika w (1)
7
Etykieta
LabelPrénom
Imię wybranego pracownika (1)
8
Etykieta
LabelAdresse
Adres
9
Etykieta
LabelVille
Miasto
10
Etykieta
LabelCP
Kod pocztowy
11
Etykieta
LabelIndice
Indeks
12
Etykieta
LabelCSGRDS
Stawka składki CSGRDS
13
Etykieta
LabelCSGD
Stawka składki CSGD
14
Etykieta
LabelRetraite
Stawka składki emerytalnej
15
Etykieta
LabelSS
Stawka składki na ubezpieczenie społeczne
16
Etykieta
LabelSH
Podstawowa stawka godzinowa dla wskaźnika wskazanego w polu (11)
17
Etykieta
LabelEJ
Dzienna dieta na utrzymanie dla wskaźnika wskazanego w polu (11)
18
Etykieta
LabelRJ
Dzienna dieta na posiłki dla wskaźnika wskazanego w polu (11)
19
Etykieta
LabelCongés
Stawka dodatku za płatny urlop, którą należy zastosować do wynagrodzenia podstawowego
20
Etykieta
LabelSB
Kwota wynagrodzenia podstawowego
21
Etykieta
LabelCS
Kwota należnych składek na ubezpieczenie społeczne
22
Etykieta
LabelIE
Kwota zasiłków na utrzymanie dziecka pozostającego pod opieką
23
Etykieta
LabelIR
Kwota dodatku na posiłki dla dziecka przebywającego w placówce opiekuńczej
24
Etykieta
LabelSN
Wynagrodzenie netto należne pracownikowi

Formularz zawiera ponadto dwa pola typu [Panel]:

PanelErreurs
zawiera komponent (5) TextBoxErreur
PanelSalaire
zawiera komponenty od (6) do (24)

Komponent typu [Panel] można programowo ustawić jako widoczny lub niewidoczny za pomocą jego właściwości boolowskiej [Panel].Visible.

4.2.2. Weryfikacja danych wprowadzonych

Aby obliczyć wynagrodzenie, użytkownik:

  • wybiera pracownika w polu [1]
  • wprowadza liczbę przepracowanych godzin w polu [2]. Liczba ta może być ułamkowa, np. 2,5 dla 2 godz. 30 min.
  • wprowadza liczbę przepracowanych dni w polu [3]. Liczba ta jest liczbą całkowitą.
  • żądaj wypłaty, klikając przycisk [4]

Gdy użytkownik wprowadzi błędne dane w polach [2] i [3], są one sprawdzane natychmiast po zmianie pola wprowadzania danych przez użytkownika. W związku z tym poniższy zrzut ekranu został wykonany jeszcze przed kliknięciem przez użytkownika przycisku [Salaire]:

Aby uzyskać powyższe zachowanie, muszą być spełnione dwa warunki:

  • komponenty walidacyjne muszą mieć właściwość EnableClientScript ustawioną na wartość „prawda”:
  
  • przeglądarka wyświetlająca stronę musi obsługiwać kod JavaScript osadzony na stronie HTML.

Jeśli przeglądarka klienta nie sprawdza samodzielnie poprawności danych, zostaną one zweryfikowane dopiero w momencie, gdy przeglądarka prześle dane z formularza na serwer. Wówczas to kod znajdujący się na serwerze, który przetwarza żądanie przeglądarki, sprawdzi poprawność danych. Należy pamiętać, że weryfikacja ta musi być zawsze przeprowadzana, nawet jeśli strona wyświetlana w przeglądarce klienta zawiera kod JavaScript wykonujący tę samą weryfikację. Serwer nie ma bowiem pewności, że przesłane do niego żądanie POST rzeczywiście pochodzi z tej strony, a zatem że weryfikacja danych została przeprowadzona.

Lista walidatorów jest następująca:

Nr
Typ
Nazwa
Rola
25
RequiredFieldValidator
RequiredFieldValidatorHeures
sprawdza, czy pole [2] [TextBoxHeures] nie jest puste
26
RangeValidator
RangeValidatorHeures
sprawdza, czy pole [2] [TextBoxHeures] jest liczbą rzeczywistą z przedziału [0, 200]
27
RequiredFieldValidator
RequiredFieldValidatorJours
sprawdza, czy pole [3] [TextBoxJours] nie jest puste
28
RangeValidator
RangeValidatorJours
sprawdza, czy pole [3] [TextBoxJours] jest liczbą całkowitą z przedziału [0,31]

Zadanie: należy utworzyć stronę [Default.aspx]. Najpierw należy umieścić dwa kontenery [PanelErreurs] i [PanelSalaire], aby następnie móc umieścić w nich komponenty, które mają zawierać.


4.2.3. Elementy aplikacji

Po odczytaniu wiersze z tabel [cotisations], [employes], [indemnites] zostaną umieszczone w obiektach typu [Cotisations], [Employe] oraz [Indemnites], zdefiniowanych w następujący sposób:


namespace Pam.Entites
{
  public class Cotisations
  {
    // właściwości automatyczne
    public double CsgRds { get; set; }
    public double Csgd { get; set; }
    public double Secu { get; set; }
    public double Retraite { get; set; }

    // konstruktory
    public Cotisations()
    {
    }

    public Cotisations(double csgRds, double csgd, double secu, double retraite)
    {
      CsgRds = csgRds;
      Csgd = csgd;
      Secu = secu;
      Retraite = retraite;
    }

    // ToString
    public override string ToString()
    {
      return string.Format("[{0},{1},{2},{3}]", CsgRds, Csgd, Secu, Retraite);
    }
  }
}

namespace Pam.Entites
{
  public class Employe
  {
    // właściwości automatyczne
    public string SS { get; set; }
    public string Nom { get; set; }
    public string Prenom { get; set; }
    public string Adresse { get; set; }
    private string Ville { get; set; }
    private string CodePostal { get; set; }
    private int Indice { get; set; }

    // producenci
    public Employe()
    {

    }

    public Employe(string ss, string nom, string prenom, string adresse, string codePostal, string ville, int indice)
    {
      SS = ss;
      Nom = nom;
      Prenom = prenom;
      Adresse = adresse;
      CodePostal = codePostal;
      Ville = ville;
      Indice = indice;
    }

    // ToString
    public override string ToString()
    {
      return string.Format("[{0},{1},{2},{3},{4},{5},{6}]", SS, Nom, Prenom, Adresse, Ville, CodePostal, Indice);
    }
  }
}

namespace Pam.Entites
{
  public class Indemnites
  {

    // właściwości automatyczne
    public int Indice { get; set; }
    public double BaseHeure { get; set; }
    public double EntretienJour { get; set; }
    public double RepasJour { get; set; }
    public double IndemnitesCP { get; set; }

    // konstruktorzy
    public Indemnites()
    {

    }

    public Indemnites(int indice, double baseHeure, double entretienJour, double repasJour, double indemnitesCP)
    {
      Indice = indice;
      BaseHeure = baseHeure;
      EntretienJour = entretienJour;
      RepasJour = repasJour;
      IndemnitesCP = indemnitesCP;
    }

    // tożsamość
    public override string ToString()
    {
      return string.Format("[{0}, {1}, {2}, {3}, {4}]", Indice, BaseHeure, EntretienJour, RepasJour, IndemnitesCP);
    }

  }
}

4.2.4. Konfiguracja aplikacji

Plik [Web.config], który konfiguruje aplikację, będzie wyglądał następująco:


<?xml version="1.0" encoding="utf-8"?>

<configuration>
    <configSections>
...
    </configSections>

  <connectionStrings>
    <add name="dbpamSqlServer2005" connectionString="Data Source=.\SQLEXPRESS;AttachDbFilename=C:\data\...\dbpam.mdf;User Id=sa;Password=msde;Connect Timeout=30;" providerName="System.Data.SqlClient"/>
  </connectionStrings>

  <appSettings>
    <add key="selectEmploye" value="select NOM,PRENOM,ADRESSE,VILLE,CODEPOSTAL,INDICE from EMPLOYES where SS=@SS"/>
    <add key="selectEmployes" value="select PRENOM, NOM, SS from EMPLOYES"/>
    <add key="selectCotisations" value="select CSGRDS,CSGD,SECU,RETRAITE from COTISATIONS"/>
    <add key="SelectIndemnites" value="select INDICE,BASEHEURE,ENTRETIENJOUR,REPASJOUR,INDEMNITESCP from INDEMNITES"/>
  </appSettings>

  <system.web>
        <!-- 
            Définissez compilation debug="true" pour insérer des symboles 
            de débogage dans la page compilée. Comme ceci 
            affecte les performances, définissez cette valeur à true uniquement 
            lors du développement.
        -->
        <compilation debug="false">
...
</configuration>
  • wiersz 9: definiuje ciąg połączenia z poprzednią bazą danych serwera SQL
  • wiersze 13–16: definiują zapytania SQL wykorzystywane przez aplikację, aby uniknąć ich stałego zapisywania w kodzie.
  • wiersz 13: zapytanie SQL jest skonfigurowane. Notacja parametrów jest specyficzna dla serwera SQL.

Zadanie do wykonania: wprowadzić te parametry do pliku [Web.config]. Wiersz 9 należy dostosować do rzeczywistej ścieżki do bazy danych [dbpam.mdf].


4.2.5. Inicjalizacja aplikacji

Inicjalizacja aplikacji ASP.NET odbywa się za pomocą pliku [Global.asax.cs]. Plik ten ma następującą strukturę:

  • w pliku [1] dodaje się nowy element do projektu
  • w pliku [2] dodaje się globalną klasę aplikacji, która domyślnie nosi nazwę [Global.asax] [3]
  • w [4], plik [Global.asax] i powiązana klasa [Global.asax.cs]
  • w [5] wyświetla się znaczniki pliku [Global.asax]

<%@ Application Codebehind="Global.asax.cs" Inherits="pam_v1.Global" Language="C#" %>

Klasa [pam_v1.Global] jest zdefiniowana w pliku [Global.asax.cs]. W naszym przypadku zostanie ona zdefiniowana w następujący sposób:


using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data.SqlClient;
using Pam.Entites;

namespace pam_v1
{
  public class Global : System.Web.HttpApplication
  {
    // --- statyczne dane aplikacji ---
    public static Employe[] Employes;
    public static Cotisations Cotisations;
    public static Dictionary<int, Indemnites> Indemnites = new Dictionary<int, Indemnites>();
    public static string Msg = string.Empty;
    public static bool Erreur = false;
    public static string ConnectionString = null;

    // uruchomienie aplikacji
    public void Application_Start(object sender, EventArgs e)
    {
...
      try
      {
        // logowanie
        ConnectionString = ...
        using (SqlConnection connexion = new SqlConnection(ConnectionString))
        {
          connexion.Open();
          // pobieramy listę pracowników i umieszczamy ją w tablicy statycznej [Employes]
...
          // pobieranie stawek składek do zmiennej statycznej [Cotisations]
...
          // pobieramy dodatki do statycznego słownika [Indemnites]
...
          // operacja zakończyła się powodzeniem
          Msg = "Base chargée...";
        }
      }
      catch (Exception ex)
      {
        // odnotowujemy błąd
        Msg = string.Format("L'erreur suivante s'est produite lors de l'accès à la base de données : {0}", ex);
        Erreur = true;
      }
    }
  }
}
  • wiersze 20: metoda [Application_Start] jest wykonywana przy uruchomieniu aplikacji internetowej. Jest wykonywana tylko raz.
  • wiersze 12–17: pola publiczne i statyczne klasy. Pole statyczne jest wspólne dla wszystkich instancji klasy. Zatem jeśli utworzonych zostanie kilka instancji klasy [Global], wszystkie one będą współdzielić to samo pole statyczne [Employes], dostępne poprzez odwołania [Global.Employes] oraz c.a.d. [NomDeClasse].ChampStatique. [Global] to nazwa klasy. Jest to zatem typ danych. Nazwa ta jest dowolna. Klasa zawsze wywodzi się z klasy [System.Web.HttpApplication].

Wróćmy do naszej klasy [Global]. Można się zastanawiać, czy konieczne jest zadeklarowanie jej pól jako statycznych. W rzeczywistości wydaje się, że w niektórych przypadkach może istnieć wiele instancji klasy [Global], co uzasadnia nadanie charakteru statycznego polom, które mają być współdzielone przez wszystkie te instancje.

Istnieje inny sposób udostępniania danych o zasięgu „aplikacji” między różnymi stronami aplikacji internetowej. W ten sposób tablica pracowników Employes mogłaby zostać zapisana w procedurze Application_Start w następujący sposób:


            Application.Add("employes",Employes);

[Application] jest domyślnie w każdej aplikacji ASP.NET odwołaniem do instancji klasy zdefiniowanej przez [Global.asax.cs]. Application jest kontenerem, który może przechowywać obiekty dowolnego typu. Tablice Employes można następnie pobrać w kodzie dowolnej strony aplikacji internetowej w następujący sposób:


            Employe[] employes=Application.Get["employes"] as Employe[];

Ponieważ kontener Application przechowuje obiekty dowolnego typu, otrzymujemy typ Object, który następnie trzeba przekonwertować. Metoda ta jest nadal możliwa do zastosowania, jednak udostępnianie danych za pośrednictwem statycznych pól typowanych obiektu [Global] pozwala uniknąć rzutowania typów i umożliwia kompilatorowi przeprowadzanie weryfikacji typów, co stanowi pomoc dla programisty. W tym przypadku zastosujemy właśnie tę metodę.

Dane udostępniane wszystkim użytkownikom są następujące:

  • wiersz 12: tablica obiektów typu [Employe], która będzie przechowywać uproszczoną listę (SS, NOM, PRENOM) wszystkich pracowników
  • wiersz 13: obiekt typu [Cotisations], w którym będą przechowywane stawki składek
  • wiersz 14: słownik, w którym zapisane zostaną dodatki związane z różnymi wskaźnikami pracowników. Będzie on indeksowany według wskaźnika pracownika, a jego wartości będą miały typ [Indemnites]
  • wiersz 15: komunikat wskazujący, jak zakończyła się inicjalizacja (pomyślnie lub z błędem)
  • wiersz 16: wartość logiczna wskazująca, czy inicjalizacja zakończyła się błędem, czy nie.
  • wiersz 17: ciąg znaków służący do połączenia z bazą danych.

Zadanie: uzupełnij kod klasy [Application_Start].


4.3. Zdarzenia formularza [Default.aspx]

4.3.1. Procedura [Page_Load]

Gdy formularz [Default.aspx] zostanie załadowany, wyszukuje on w tablicy [Global.Employes] (wiersz 12) nazwiska pracowników, aby umieścić je w liście rozwijanej [1]:

  • lista rozwijana [1] została wypełniona
  • TextBox [5] wskazuje, że baza danych została poprawnie odczytana

Jeśli podczas uruchamiania aplikacji wystąpiły błędy inicjalizacji, wskazują na to kody TextBox i [5]:

Image


Pytanie: Należy opisać procedurę [Page_Load] ze strony internetowej [Default.aspx], która po uruchomieniu aplikacji zapewnia powyższe działanie.


4.3.2. Obliczanie wynagrodzenia

Kliknięcie przycisku [4] powoduje uruchomienie menedżera:


    protected void ButtonSalaire_Click(object sender, System.EventArgs e)

Moduł ten rozpoczyna od sprawdzenia poprawności danych wprowadzonych w [2] i [3]. Jeśli którekolwiek z nich jest nieprawidłowe, błąd jest sygnalizowany tak, jak pokazano powyżej. Po zweryfikowaniu i uznaniu za prawidłowe danych wprowadzonych w [2] i [3] aplikacja musi wyświetlić dodatkowe dane dotyczące użytkownika wybranego w [1], a także jego wynagrodzenie (patrz zrzut ekranu w paragrafie 4.1).


Zadanie: napisać kod procedury [ButtonSalaire_Click].