Zadania (21-30)
Zadanie 21 - pewniak
Section titled “Zadanie 21 - pewniak”Rola operatorów is oraz as w języku C#
Operator is sprawdza, czy obiekt jest danego typu (lub typu pochodnego), i zwraca wartość logiczną true/false.
Operator as próbuje rzutować obiekt na wskazany typ referencyjny lub typ dopuszczający null. Jeśli rzutowanie się nie powiedzie, zwraca null zamiast rzucać wyjątek (w przeciwieństwie do zwykłego rzutowania w nawiasach, które zgłasza InvalidCastException). Dlatego as jest bezpieczniejszy, gdy nie mamy pewności co do typu.
// object przechowa cokolwiek; tu trzyma napis, ale nie wiemy tego z typuobject o = "tekst";
// is sprawdza typ i zwraca bool; tu od razu z przypisaniem do zmiennej sif (o is string s){ Console.WriteLine(s.Length);}
// as próbuje rzutować; przy niepowodzeniu zwraca null (bez wyjątku)string t = o as string;if (t != null){ Console.WriteLine(t);}Zwykłe rzutowanie (Typ)o przy złym typie rzuca wyjątek, a as zwraca null. Dla typów wartościowych as wymaga wersji nullowalnej (int?).
// zwykłe rzutowanie - przy złym typie InvalidCastExceptionstring a = (string)o;
// as z typem wartościowym wymaga int? (inaczej błąd kompilacji)object obj = 5;int? n = obj as int?;Operator is można łączyć z dopasowaniem wzorca (pattern matching), co pozwala w jednym warunku sprawdzić typ, wartość lub zakres:
// wzorzec relacyjny i logiczny: sprawdzenie zakresuint age = 25;bool isAdult = age is >= 18 and < 65;Skrót do zapamiętania: is sprawdza typ i zwraca bool (z możliwym dopasowaniem wzorca). as próbuje rzutować i przy niepowodzeniu zwraca null zamiast wyjątku.
- is: test typu -> bool; od C# 7 z pattern matching (
o is string s). - as: bezpieczne rzutowanie typów referencyjnych/nullable; niepowodzenie -> null.
- Zwykłe rzutowanie
(Typ)oprzy błędzie rzuca InvalidCastException. - as nie działa na zwykłych typach wartościowych (trzeba int?, double? itd.).
- as używamy, gdy nie mamy pewności co do typu i chcemy dalej użyć wartości.
- Wzorce z is: type pattern,
is null/is not null, wzorce relacyjne/logiczne (and,or,not). - is + wzorzec często zastępuje as + sprawdzenie nulla.
Zadanie 22
Section titled “Zadanie 22”Konstrukcja match (wzorce aktywne) i funkcje rekurencyjne w F#
Section titled “Konstrukcja match (wzorce aktywne) i funkcje rekurencyjne w F#”Konstrukcja match … with w F# to rozbudowane dopasowanie wzorców, działające jak wielokrotny switch, ale potrafiące dodatkowo rozbijać strukturę danych i wiązać jej części ze zmiennymi. Każdy przypadek to wzorzec poprzedzony kreską pionową, a _ oznacza dowolną wartość.
Wzorce aktywne (active patterns) pozwalają definiować własne kategorie dopasowania w nawiasach z kreskami pionowymi, np. podział liczb na parzyste i nieparzyste. Funkcje rekurencyjne deklaruje się słowem let rec i często łączy z match, gdzie jeden przypadek jest warunkiem zakończenia (bazowym), a drugi zawiera wywołanie rekurencyjne.
// funkcja rekurencyjna - oznaczona slowem reclet rec factorial n = // dopasowanie wzorca do wartosci n match n with // przypadek bazowy konczacy rekurencje | 0 -> 1 // przypadek ogolny z wywolaniem rekurencyjnym | _ -> n * factorial (n - 1)
// aktywny wzorzec dzielacy liczby na Even i Oddlet (|Even|Odd|) n = if n % 2 = 0 then Even else Odd
// uzycie aktywnego wzorca w matchlet describe n = match n with | Even -> "parzysta" | Odd -> "nieparzysta"Skrót do zapamiętania: match…with to dopasowanie wzorców (switch z rozbijaniem struktury). Wzorce aktywne (|A|B|) to własne kategorie dopasowania. Funkcje rekurencyjne to let rec, zwykle z przypadkiem bazowym w match.
- match…with: dopasowanie wzorca,
|na przypadek,_= dowolny. - Potrafi rozbijać strukturę i wiązać części ze zmiennymi.
- Wzorce aktywne: własne wzorce definiowane jako
(|A|B|). - Rekurencja:
let rec, przypadek bazowy + wywołanie rekurencyjne.
Zadanie 23
Section titled “Zadanie 23”Program w F# do obchodzenia drzewa binarnego (post-order oraz dwa pozostałe porządki)
Section titled “Program w F# do obchodzenia drzewa binarnego (post-order oraz dwa pozostałe porządki)”Drzewo binarne reprezentujemy jako typ unii (discriminated union), czyli typ, który ma kilka możliwych wariantów. Tutaj węzeł drzewa to albo Leaf (liść pusty, koniec gałęzi), albo Node przechowujący wartość oraz dwa poddrzewa (lewe i prawe), które też są drzewami. Taka definicja jest rekurencyjna: drzewo składa się z mniejszych drzew, aż dojdziemy do Leaf.
Obchodzenie (traversal) to odwiedzanie wszystkich węzłów w ustalonej kolejności i zbieranie ich wartości do listy. Realizujemy je funkcjami rekurencyjnymi z dopasowaniem match: jeden przypadek to warunek bazowy (Leaf zwraca pustą listę i kończy rekurencję), drugi rozbija Node na wartość v oraz poddrzewa l i r, dla których funkcja woła samą siebie. Trzy porządki różnią się wyłącznie kolejnością sklejania trzech kawałków: lewego poddrzewa, prawego poddrzewa i wartości korzenia. Operator @ łączy listy.
- pre-order (korzeń, lewe, prawe) - korzeń przed poddrzewami.
- in-order (lewe, korzeń, prawe) - korzeń między poddrzewami; dla drzewa BST daje wartości po kolei (rosnąco).
- post-order (lewe, prawe, korzeń) - korzeń po poddrzewach.
// definicja drzewa binarnego: lisc (pusty) lub wezel z wartoscia i dwoma poddrzewamitype Tree = | Leaf | Node of int * Tree * Tree
// post-order: lewe, prawe, korzenlet rec postOrder tree = match tree with // pusty lisc daje pusta liste | Leaf -> [] // najpierw lewe poddrzewo, potem prawe, na koncu wartosc wezla | Node(v, l, r) -> postOrder l @ postOrder r @ [v]
// in-order: lewe, korzen, prawelet rec inOrder tree = match tree with | Leaf -> [] // lewe poddrzewo, wartosc, prawe poddrzewo | Node(v, l, r) -> inOrder l @ [v] @ inOrder r
// pre-order: korzen, lewe, prawelet rec preOrder tree = match tree with | Leaf -> [] // najpierw wartosc, potem lewe i prawe poddrzewo | Node(v, l, r) -> [v] @ preOrder l @ preOrder r
// przykladowe drzewolet tree = Node(1, Node(2, Leaf, Leaf), Node(3, Leaf, Leaf))
// wypisanie wynikow obchodzeniaprintfn "post-order: %A" (postOrder tree)printfn "in-order: %A" (inOrder tree)printfn "pre-order: %A" (preOrder tree)Skrót do zapamiętania: Drzewo to typ unii (Leaf | Node of wartosc, lewe, prawe). Trzy rekurencyjne funkcje różnią się tylko kolejnością: post = L,P,korzeń; in = L,korzeń,P; pre = korzeń,L,P. Listy łączy @.
- Typ:
Leaf | Node of int * Tree * Tree. - post-order:
postOrder l @ postOrder r @ [v]. - in-order:
inOrder l @ [v] @ inOrder r. - pre-order:
[v] @ preOrder l @ preOrder r. - Wszystkie rekurencyjne (let rec) z dopasowaniem match.
Zadanie 24
Section titled “Zadanie 24”Przeciążanie operatorów w F#
Section titled “Przeciążanie operatorów w F#”W F# operatory można przeciążać, definiując je jako statyczne składowe typu (klasy lub rekordu) o nazwie operatora w nawiasach, np. (+). Działa to analogicznie do C#: gdy użyjemy operatora na obiektach danego typu, wywoła się nasza definicja. Można też definiować własne operatory infiksowe na poziomie modułu.
// typ Vector z przecionym operatorem +type Vector(x: int, y: int) = // wlasnosci tylko do odczytu member this.X = x member this.Y = y // przeciazenie operatora + jako statyczna skladowa typu static member (+) (a: Vector, b: Vector) = // tworzymy nowy wektor jako sume skladowych Vector(a.X + b.X, a.Y + b.Y)
// uzycie operatora na obiektach Vectorlet v = Vector(1, 2) + Vector(3, 4)Skrót do zapamiętania: W F# operator przeciąża się jako statyczną składową typu o nazwie operatora w nawiasach, np. static member (+) (a, b) = .... Można też tworzyć własne operatory infiksowe.
- Składnia:
static member (+) (a, b) = ...wewnątrz typu. - Działa po użyciu operatora na obiektach tego typu.
- Można definiować własne operatory infiksowe (na poziomie modułu).
- Idea jak w C#, inna składnia.
Zadanie 25
Section titled “Zadanie 25”Modele programowania ADO.NET - dwa modele dostępu do baz danych
Section titled “Modele programowania ADO.NET - dwa modele dostępu do baz danych”ADO.NET udostępnia dwa podstawowe modele dostępu do danych: połączeniowy i bezpołączeniowy. Różnią się one tym, czy połączenie z bazą jest utrzymywane przez cały czas pracy na danych.
Model połączeniowy opiera się na obiekcie DataReader. Połączenie z bazą jest otwarte przez cały czas odczytu, a dane czyta się strumieniowo, tylko do przodu i tylko do odczytu. Jest szybki i oszczędny pamięciowo, ale blokuje połączenie i nie pozwala swobodnie nawigować po danych.
Model bezpołączeniowy opiera się na obiektach DataSet i DataAdapter. Dane pobiera się do pamięci (DataSet to lokalna kopia danych), po czym połączenie jest zamykane. Można na nich pracować w trybie offline, a zmiany zsynchronizować z bazą później przez DataAdapter. Zużywa więcej pamięci, ale jest elastyczny i nie trzyma otwartego połączenia.
Model połączeniowy z użyciem DataReader (połączenie otwarte przez cały odczyt):
// using gwarantuje zamknięcie połączenia po zakończeniuusing (SqlConnection conn = new SqlConnection(connectionString)){ SqlCommand cmd = new SqlCommand("SELECT Name FROM Users", conn); conn.Open(); // połączenie otwarte
// DataReader czyta strumieniowo, tylko do przodu i do odczytu using (SqlDataReader reader = cmd.ExecuteReader()) { // Read() przesuwa się do kolejnego wiersza, false gdy koniec while (reader.Read()) { Console.WriteLine(reader["Name"]); } }} // tutaj połączenie jest zamykaneModel bezpołączeniowy z użyciem DataSet i DataAdapter (połączenie tylko na czas pobrania danych):
DataSet ds = new DataSet();
using (SqlConnection conn = new SqlConnection(connectionString)){ // adapter pośredniczy między bazą a DataSet SqlDataAdapter adapter = new SqlDataAdapter("SELECT Name FROM Users", conn);
// Fill sam otwiera i zamyka połączenie, kopiując dane do pamięci adapter.Fill(ds, "Users");}// dalej pracujemy offline, bez otwartego połączeniaforeach (DataRow row in ds.Tables["Users"].Rows){ Console.WriteLine(row["Name"]);}Skrót do zapamiętania: Połączeniowy (DataReader): połączenie cały czas otwarte, odczyt do przodu, szybki, mało pamięci. Bezpołączeniowy (DataSet/DataAdapter): dane w pamięci, połączenie zamykane, praca offline, więcej pamięci.
- Połączeniowy: DataReader, otwarte połączenie, odczyt strumieniowy do przodu, szybki.
- Bezpołączeniowy: DataSet + DataAdapter, dane w pamięci, połączenie zamykane.
- Połączeniowy oszczędza pamięć, bezpołączeniowy daje elastyczność i pracę offline.
- Łącznikiem z bazą jest Connection; polecenia wykonuje Command.
Zadanie 26
Section titled “Zadanie 26”Opisać klasę Page stosowaną na poziomie aplikacji ASP.NET
Section titled “Opisać klasę Page stosowaną na poziomie aplikacji ASP.NET”Klasa Page (System.Web.UI.Page) w ASP.NET Web Forms reprezentuje pojedynczą stronę .aspx. Każda strona aplikacji jest w istocie klasą dziedziczącą z Page, generowaną na podstawie pliku .aspx i powiązanego kodu (code-behind). Obiekt tej klasy jest tworzony przy każdym żądaniu strony.
Klasa Page zarządza cyklem życia strony i udostępnia kluczowe właściwości oraz zdarzenia. Najważniejsze właściwości to IsPostBack (czy strona jest wynikiem ponownego wysłania formularza), Request, Response, Session, ViewState i kontrolki strony. Najważniejsze zdarzenia cyklu życia to Init, Load, zdarzenia kontrolek oraz PreRender i Unload.
Typowy kod code-behind: klasa strony dziedziczy z Page, a w metodach obsługi zdarzeń korzysta z jej właściwości:
// klasa strony powstaje z pliku Default.aspx i dziedziczy z Pagepublic partial class Default : System.Web.UI.Page{ // metoda obsługi zdarzenia Load (cykl życia strony) protected void Page_Load(object sender, EventArgs e) { // IsPostBack: false przy pierwszym wejściu, true po wysłaniu formularza if (!IsPostBack) { // wykonuje się tylko raz, przy pierwszym załadowaniu strony labelInfo.Text = "Witaj na stronie"; } }
// metoda obsługi kliknięcia przycisku (zdarzenie kontrolki) protected void Button_Click(object sender, EventArgs e) { // dostęp do danych żądania, sesji i odpowiedzi przez właściwości Page string user = Request.Form["userName"]; Session["lastUser"] = user; Response.Write("Zapisano: " + user); }}Skrót do zapamiętania: Page (System.Web.UI.Page) reprezentuje stronę .aspx; każda strona to klasa dziedzicząca z Page. Zarządza cyklem życia strony i udostępnia IsPostBack, Request, Response, Session oraz zdarzenia Init/Load/PreRender/Unload.
- Reprezentuje stronę .aspx; każda strona dziedziczy z System.Web.UI.Page.
- Tworzona przy każdym żądaniu, zarządza cyklem życia strony.
- Właściwości: IsPostBack, Request, Response, Session, ViewState, kontrolki.
- Zdarzenia: Init, Load, PreRender, Unload.
Zadanie 27
Section titled “Zadanie 27”Podać i omówić rodzaje transakcji
Section titled “Podać i omówić rodzaje transakcji”Transakcja to zbiór operacji wykonywanych jako jedna, niepodzielna całość, spełniający zasady ACID (atomowość, spójność, izolacja, trwałość). Albo wszystkie operacje się powiodą i zmiany zostaną zatwierdzone (commit), albo żadna i nastąpi wycofanie (rollback).
Pod względem zasięgu wyróżnia się transakcje lokalne i rozproszone. Transakcja lokalna obejmuje jeden zasób (jedną bazę danych) i jest zarządzana przez jeden menedżer transakcji. Transakcja rozproszona obejmuje wiele zasobów lub baz, koordynowanych przez wspólny menedżer (np. MSDTC), zwykle z protokołem zatwierdzania dwufazowego (two-phase commit). W .NET transakcje wygodnie obsługuje TransactionScope, który sam decyduje, czy potrzebna jest transakcja lokalna czy rozproszona.
Transakcja lokalna sterowana ręcznie przez ADO.NET (commit przy powodzeniu, rollback przy błędzie):
using (SqlConnection conn = new SqlConnection(connectionString)){ conn.Open(); // rozpoczęcie transakcji SqlTransaction tx = conn.BeginTransaction(); try { // polecenia muszą wiedzieć, do której transakcji należą SqlCommand cmd = new SqlCommand("UPDATE Accounts SET Balance = Balance - 100 WHERE Id = 1", conn, tx); cmd.ExecuteNonQuery();
cmd.CommandText = "UPDATE Accounts SET Balance = Balance + 100 WHERE Id = 2"; cmd.ExecuteNonQuery();
// wszystko się udało: zatwierdzenie zmian tx.Commit(); } catch { // błąd w którejkolwiek operacji: wycofanie całości tx.Rollback(); }}Transakcja przez TransactionScope (commit przez Complete; brak wywołania = rollback, sam dobiera lokalną lub rozproszoną):
using (TransactionScope scope = new TransactionScope()){ // operacje na jednym lub wielu zasobach... // (TransactionScope w razie potrzeby awansuje do transakcji rozproszonej)
// Complete oznacza zgodę na commit; bez niego nastąpi rollback scope.Complete();} // tutaj transakcja jest zatwierdzana lub wycofywanaSkrót do zapamiętania: Transakcja to niepodzielny zbiór operacji (ACID): commit albo rollback. Rodzaje wg zasięgu: lokalne (jeden zasób/baza, jeden menedżer) i rozproszone (wiele zasobów, koordynacja MSDTC, two-phase commit).
- Transakcja = niepodzielny zbiór operacji, zasady ACID.
- Commit (zatwierdzenie) albo rollback (wycofanie).
- Lokalna: jeden zasób/baza, jeden menedżer transakcji.
- Rozproszona: wiele zasobów, koordynator (MSDTC), zatwierdzanie dwufazowe.
- W .NET: TransactionScope.
Zadanie 28 - pewniak
Section titled “Zadanie 28 - pewniak”Wyjaśnić pojęcie plików XML i podać przykład takiego pliku
XML (Extensible Markup Language) to tekstowy, rozszerzalny format opisu danych w strukturze hierarchicznej (drzewiastej). Dane przechowuje się w znacznikach, które programista definiuje samodzielnie, dzięki czemu format jest samoopisujący i czytelny zarówno dla człowieka, jak i dla maszyny. XML służy głównie do przechowywania oraz wymiany danych między różnymi systemami niezależnie od platformy.
Plik XML ma jeden element nadrzędny (korzeń), wewnątrz którego zagnieżdżone są kolejne elementy. Elementy mogą mieć atrybuty i wartości tekstowe.
Budowa pliku
<!-- prolog: wersja XML i kodowanie znaków --><?xml version="1.0" encoding="UTF-8"?><!-- jeden element korzenia --><library> <!-- znacznik otwierający i zamykający; id, lang to atrybuty (pary nazwa="wartość") --> <book id="1" lang="pl"> <!-- element z wartością tekstową --> <title>Pan Tadeusz</title> <author>Adam Mickiewicz</author> <year>1834</year> </book> <book id="2" lang="en"> <title>Hamlet</title> <author>William Shakespeare</author> <!-- element samozamykający (bez treści) --> <year value="1603"/> </book> <!-- sekcja CDATA: tekst, którego nie chcemy interpretować (np. znaki < > &) --> <note><![CDATA[ a < b & c ]]></note></library>Poprawność. Dokument XML może być poprawny na dwa sposoby:
- well-formed (poprawny składniowo) - trzyma się reguł XML: jeden korzeń, każdy znacznik domknięty, dobre zagnieżdżenie.
- valid (poprawny strukturalnie) - well-formed plus zgodny ze schematem (DTD lub XSD), który mówi, jakie elementy i atrybuty są dozwolone.
Element a atrybut. Element to znacznik z treścią (np. <title>Hamlet</title>), a atrybut to dodatkowa informacja w znaczniku otwierającym (np. id="1").
Skrót do zapamiętania: XML to tekstowy, hierarchiczny, samoopisujący format danych z definiowanymi przez użytkownika znacznikami; służy do przechowywania i wymiany danych między systemami. Ma jeden korzeń i zagnieżdżone elementy. Bywa well-formed (poprawny składniowo) i valid (zgodny ze schematem DTD/XSD).
- XML = tekstowy, rozszerzalny, hierarchiczny format danych.
- Znaczniki definiuje użytkownik -> samoopisujący; rozróżnia wielkość liter.
- Jeden element korzenia, w nim zagnieżdżone elementy (z atrybutami i wartościami).
- Budowa: prolog (wersja, kodowanie), elementy, atrybuty, komentarze, CDATA, elementy samozamykające.
- Well-formed = poprawny składniowo; valid = zgodny ze schematem (DTD/XSD).
- Element trzyma dane, atrybut opisuje element (metadane).
- Zastosowanie: przechowywanie i wymiana danych niezależnie od platformy; w .NET pliki konfiguracyjne (.config, .csproj).
Zadanie 29 - pewniak
Section titled “Zadanie 29 - pewniak”Czym jest serializacja obiektów w języku C#?
Serializacja to zamiana obiektu (jego stanu, czyli wartości pól) na postać nadającą się do zapisu lub przesłania, np. strumień bajtów, tekst XML albo JSON. Deserializacja to proces odwrotny: odtworzenie obiektu z takiej postaci. Dzięki temu obiekt można zapisać do pliku, przesłać siecią (API) lub przechować w pamięci podręcznej, a później odtworzyć.
Rodzaje serializacji. Trzy główne formaty:
- JSON - tekstowy, obsługiwany przez System.Text.Json (klasa JsonSerializer).
- XML - tekstowy, obsługiwany przez XmlSerializer.
- Binarny - zapis obiektu jako strumień bajtów (nieczytelny dla człowieka, kompaktowy); dziś odradzany, bo bywa niebezpieczny (deserializacja niezaufanych danych umożliwiała ataki).
Przykład na JSON - JsonSerializer zamienia obiekt na tekst i z powrotem:
using System.Text.Json;
class Person{ public string Name { get; set; } public int Age { get; set; }}
Person p = new Person { Name = "Ala", Age = 20 };
// serializacja - obiekt na tekst JSON: {"Name":"Ala","Age":20}string json = JsonSerializer.Serialize(p);
// deserializacja - odtworzenie obiektu z tekstu JSONPerson restored = JsonSerializer.Deserialize<Person>(json);Atrybutami nakładanymi na właściwości można wpłynąć na to, jak obiekt jest serializowany, np. dla JSON:
[JsonPropertyName("full_name")]- inna nazwa właściwości w JSON.[JsonIgnore]- pominięcie pola (np. danych wrażliwych).[JsonPropertyOrder(1)]- kolejność właściwości w wyniku.
Skrót do zapamiętania: Serializacja to zamiana obiektu na zapisywalną/przesyłalną postać (bajty, XML, JSON), deserializacja to proces odwrotny. W C#: JsonSerializer, XmlSerializer, atrybut [Serializable].
- Serializacja: obiekt -> bajty/XML/JSON (do zapisu lub przesłania).
- Deserializacja: postać zapisana -> obiekt.
- Rodzaje: JSON (dziś najczęstszy), XML, binarny (BinaryFormatter - odradzany, bo niebezpieczny).
- Narzędzia: JsonSerializer (System.Text.Json), XmlSerializer (System.Xml.Serialization).
- Zastosowanie: zapis do pliku, przesył siecią, API, cache.
Zadanie 30
Section titled “Zadanie 30”Przedstawić sposób obsługi błędów jakie mogą pojawić się w aplikacjach ASP.NET
Section titled “Przedstawić sposób obsługi błędów jakie mogą pojawić się w aplikacjach ASP.NET”W aplikacjach ASP.NET błędy obsługuje się na kilku poziomach. Najniższy to lokalna obsługa w kodzie blokami try-catch-finally, gdy spodziewamy się konkretnego błędu i chcemy go obsłużyć od razu. Wyższy poziom to obsługa na poziomie strony przez zdarzenie Page_Error, a najwyższy to obsługa globalna na poziomie całej aplikacji przez zdarzenie Application_Error w pliku Global.asax.
Dodatkowo w pliku konfiguracyjnym web.config sekcja customErrors pozwala zdefiniować przyjazne strony błędów wyświetlane użytkownikowi zamiast technicznych komunikatów (np. osobna strona dla błędu 404). Metoda Server.GetLastError() pozwala pobrać ostatni wyjątek, a Server.ClearError() go wyczyścić. W ASP.NET Core obsługę błędów realizuje się przez middleware (np. UseExceptionHandler).
Obsługa lokalna w kodzie blokiem try-catch-finally:
try{ // kod, który może zgłosić błąd int x = int.Parse(Request.Form["age"]);}catch (FormatException ex){ // obsługa konkretnego błędu labelInfo.Text = "Niepoprawny wiek: " + ex.Message;}finally{ // wykonuje się zawsze (np. sprzątanie)}Obsługa globalna w pliku Global.asax przez zdarzenie Application_Error:
protected void Application_Error(object sender, EventArgs e){ // pobranie ostatniego wyjątku z aplikacji Exception ex = Server.GetLastError();
// tu np. zapis błędu do logu // wyczyszczenie błędu, by nie pokazać użytkownikowi szczegółów technicznych Server.ClearError();
// przekierowanie na przyjazną stronę błędu Response.Redirect("~/Error.aspx");}Przyjazne strony błędów w pliku web.config (sekcja customErrors):
<configuration> <system.web> <!-- On = pokazuj własne strony; defaultRedirect = strona ogólna --> <customErrors mode="On" defaultRedirect="~/Error.aspx"> <!-- osobna strona dla konkretnego kodu błędu --> <error statusCode="404" redirect="~/NotFound.aspx" /> </customErrors> </system.web></configuration>W ASP.NET Core odpowiednikiem obsługi globalnej jest middleware konfigurowane w potoku aplikacji:
// przekierowanie wyjątków na wskazaną ścieżkę obsługi błędówapp.UseExceptionHandler("/Error");Skrót do zapamiętania: Trzy poziomy: lokalnie try-catch, na poziomie strony Page_Error, globalnie Application_Error (Global.asax). Plus przyjazne strony błędów w web.config (customErrors) i Server.GetLastError().
- Lokalnie: try-catch-finally w kodzie.
- Na poziomie strony: zdarzenie Page_Error.
- Globalnie: Application_Error w Global.asax.
- web.config -> customErrors: przyjazne strony błędów (np. 404).
- Server.GetLastError() pobiera ostatni wyjątek; w ASP.NET Core - middleware.