Architektura Aplikacji Internetowych / Programowanie .NET
Rok akademicki: 2026/27
Zadanie laboratoryjne: Portal Zarządzania Flotą i Zgłoszeniami "TransitDesk" (ASP.NET Core MVC, Potok Middleware, Tag Helpers, Walidacja)
Prowadzący: Tomasz Goluch
Wersja: 1.1
Wymagany standard: C# 12 / .NET 8+
Maksymalna ocena zadania: 100 punktów (100%)
Forma realizacji: Projekt tworzony całkowicie
od zera (Greenfield)
1. Kontekst Biznesowy i Architektoniczny Projektu
Projektujesz system centralny miejskiego przedsiębiorstwa
transportowego – TransitDesk.Web.
Aplikacja stanowi wieloplatformowy portal dyspozytorski oraz punkt kontaktu dla pasażerów, realizujący ewidencję taboru autobusowo-tramwajowego, przyjmowanie zgłoszeń awarii i incydentów drogowych oraz publikację komunikatów taryfowych.
W aplikacjach webowych o wysokim natężeniu ruchu klasyczne podejścia architektoniczne rodzą poważne wyzwania:
- Błędna kolejność komponentów w potoku middleware prowadzi do wycieku wrażliwych danych diagnostycznych lub ominięcia warstwy autoryzacji,
- Bezpośrednie wiązanie encji bazodanowych w akcjach kontrolera otwiera krytyczną podatność typu Mass Assignment (Over-Posting),
- Synchroniczny zapis przesyłanych załączników multimedialnych oraz brak weryfikacji nagłówków MIME blokuje wątki serwera Kestrel i naraża system na ataki Path Traversal oraz Denial of Service (DoS),
- Mieszanie logiki prezentacyjnej z kodem HTML (archaiczne HTML Helpers) utrudnia utrzymanie i refaktoryzację kodu.
Twoim celem jest zaprojektowanie modularnej, bezpiecznej i przetestowanej aplikacji internetowej w technologii ASP.NET Core MVC (.NET 8/9). Projekt opiera się na silnie typowanych modelach widoku (ViewModels), semantycznych Tag Helpers, deklaratywnym routingu z własnymi ograniczeniami (Route Constraints), asynchronicznym potoku I/O oraz pełnej izolacji stanu sesji.
2. Inicjalizacja Środowiska (Krok 0: Przygotowanie Solucji od Zera)
Otwórz terminal w pustym katalogu roboczym i skonfiguruj strukturę solucji z podziałem na aplikację webową, bibliotekę domenową oraz pakiet testów automatycznych:
# 1. Utworzenie pustego pliku rozwiązania
dotnet new sln -n TransitDesk
# 2. Utworzenie projektu rdzenia domeny i modeli (Class Library)
dotnet new classlib -n TransitDesk.Core -f net8.0
# 3. Utworzenie głównego projektu aplikacji internetowej (ASP.NET Core MVC)
dotnet new mvc -n TransitDesk.Web -f net8.0 --auth None
# 4. Utworzenie projektu testów jednostkowych i integracyjnych (xUnit)
dotnet new xunit -n TransitDesk.Tests -f net8.0
# 5. Dołączenie projektów do pliku rozwiązania
dotnet sln add TransitDesk.Core/TransitDesk.Core.csproj
dotnet sln add TransitDesk.Web/TransitDesk.Web.csproj
dotnet sln add TransitDesk.Tests/TransitDesk.Tests.csproj
# 6. Konfiguracja powiązań między projektami
dotnet add TransitDesk.Web/TransitDesk.Web.csproj reference TransitDesk.Core/TransitDesk.Core.csproj
dotnet add TransitDesk.Tests/TransitDesk.Tests.csproj reference TransitDesk.Core/TransitDesk.Core.csproj
dotnet add TransitDesk.Tests/TransitDesk.Tests.csproj reference TransitDesk.Web/TransitDesk.Web.csproj
# 7. Instalacja bibliotek testowych i asercji płynnych
dotnet add TransitDesk.Tests/TransitDesk.Tests.csproj package Microsoft.AspNetCore.Mvc.Testing
dotnet add TransitDesk.Tests/TransitDesk.Tests.csproj package FluentAssertions
💡 Zalecenie architektoniczne: Rola projektu TransitDesk.Core a brak bazy danych
Na tym laboratorium nie korzystamy jeszcze z Entity Framework Core ani fizycznej bazy danych. Zalecaną dobrą praktyką architektoniczną jest umieszczenie czystych encji domenowych (np.Vehicle,IncidentReport) oraz kontraktów serwisów w projekcieTransitDesk.Core, podczas gdy wTransitDesk.Webdefiniowane są wyłącznie modele formularzy (ViewModels).
Uwaga: Na tym etapie nie jest to twardo egzekwowany wymóg. Dopuszczalnym uproszczeniem jest przechowywanie danych testowych w pamięci podręcznej serwisu lub kontrolera (kolekcje in-memory), a projektTransitDesk.Coremożna potraktować jako współdzieloną bibliotekę klas.
3. Szczegółowe Wymagania i Podział Punktów (Łącznie: 100 pkt)
┌───────────────────────────────────────────────────────────────────────────────────┐
│ ARCHITEKTURA SYSTEMU TRANSITDESK (ASP.NET CORE) │
├───────────────────────────────────────────────────────────────────────────────────┤
│ [Część 1: 15 pkt] Potok Middleware ──► Minimal Hosting / Re-Execute Errors │
│ [Część 2: 15 pkt] Routing & Constraints ──► Konwencjonalny + Atrybutowy + IRoute │
│ [Część 3: 15 pkt] Formularze & Razor ──► ViewModel + Tag Helpers + Wzorzec PRG│
│ [Część 4: 15 pkt] Walidacja Hybrydowa ──► DataAnnotations + Remote AJAX Valid. │
│ [Część 5: 15 pkt] Bezpieczny Upload ──► IFormFile Async + Path Traversal G. │
│ [Część 6: 10 pkt] Zarządzanie Stanem ──► ISession JSON + Polityka Ciasteczek │
│ [Część 7: 15 pkt] Filtry & Kontenery ──► Action Filters + IViewLocalizer + DCK│
└───────────────────────────────────────────────────────────────────────────────────┘
Część 1: Potok Middleware, Minimal Hosting i Re-ekzekucja Błędów HTTP (15 pkt)
Prawidłowo skonfigurowany potok przetwarzania żądań w
Program.cs stanowi pierwszą linię obrony aplikacji. Należy
zaimplementować deterministyczny łańcuch middleware oraz mechanizm
eleganckiej obsługi kodów statusu bez utraty oryginalnego nagłówka
HTTP.
Wymagania szczegółowe:
- [4 pkt] Konfiguracja i rejestracja usług w
Program.cs:- Rejestracja MVC (
AddControllersWithViews). - Rejestracja pamięci rozproszonej
(
AddDistributedMemoryCache) i sesji z czasem wygaśnięcia 15 minut oraz flagamiCookie.HttpOnly = trueiCookie.IsEssential = true.
- Rejestracja MVC (
- [6 pkt] Architektura i kolejność potoku HTTP:
- Ścisłe uporządkowanie middleware: środowiskowa obsługa wyjątków
(
UseDeveloperExceptionPagew trybie deweloperskim,UseExceptionHandler+UseHstsw trybie produkcyjnym) →UseHttpsRedirection→UseStaticFiles→UseRouting→UseSession→UseAuthentication→UseAuthorization→MapControllerRoute.
- Ścisłe uporządkowanie middleware: środowiskowa obsługa wyjątków
(
- [5 pkt] Re-ekzekucja kodów błędów HTTP:
- Zarejestruj
UseStatusCodePagesWithReExecute("/Errors/Status", "?code={0}"). - Zaimplementuj
ErrorsControllerz akcjąStatus(int code)zwracającą dedykowany widok dla błędów 404 (Brak zasobu) i 403 (Odmowa dostępu). - Odpowiedź wysyłana do przeglądarki musi zachować oryginalny status
błędu (np. nagłówek
404 Not Found, a nie200 OKczy przekierowanie302).
- Zarejestruj
💡 Wskazówki implementacyjne: Re-ekzekucja kodów statusu (Rozwiń)
// W pliku Program.cs (przed UseRouting i UseStaticFiles):
app.UseStatusCodePagesWithReExecute("/Errors/Status", "?code={0}");
// W Controllers/ErrorsController.cs:
public class ErrorsController : Controller
{
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public IActionResult Status(int code)
{
ViewBag.StatusCode = code;
ViewBag.Message = code switch
{
404 => "Nie odnaleziono wskazanego zasobu taboru lub zgłoszenia.",
403 => "Brak uprawnień dostępu do modułu dyspozytorskiego.",
_ => "Wystąpił nieoczekiwany błąd protokołu HTTP."
};
return View();
}
}
Część
2: Routing Hybrydowy i Własne Ograniczenie Trasy
(IRouteConstraint) (15 pkt)
Aplikacja musi obsługiwać zarówno intuicyjne trasy publiczne, jak i ściśle ograniczone adresy techniczne dla personelu serwisowego.
Struktura katalogów i przestrzeń nazw dla ograniczenia trasy:
Klasa ograniczenia trasy implementuje interfejsIRouteConstraintz bibliotekiMicrosoft.AspNetCore.Routing. Jest detalem infrastruktury webowej, dlatego powinna znaleźć się w projekcieTransitDesk.Web:
Ścieżka:TransitDesk.Web/Routing/Constraints/FleetSideNumberConstraint.cs
Przestrzeń nazw:namespace TransitDesk.Web.Routing.Constraints;
(Dzięki temu projekt domenowyTransitDesk.Corepozostaje czysty i niezależny od bibliotek ASP.NET Core).
Wymagania szczegółowe:
- [4 pkt] Dedykowana trasa konwencjonalna:
- Zdefiniuj w
Program.cstrasę o nazwiedepot_fleet:Wzorzec: "Zajezdnia/{depotName}/{tractionType?}"mapujący na kontrolerVehiclesControlleri akcjęDepotFleetList.
- Zdefiniuj w
- [7 pkt] Niestandardowy Constraint numeru bocznego
(
FleetSideNumberConstraint):- Utwórz klasę
FleetSideNumberConstraintimplementującą interfejsIRouteConstraint. - Logika walidacji: numer boczny taboru musi być wartością liczbową z
zakresu
1000–9999(standard gdańskich pojazdów GAiT). - Zarejestruj ograniczenie w kontenerze pod kluczem
fleetSideNo.
- Utwórz klasę
- [4 pkt] Routing atrybutowy z ograniczeniem
złożonym:
- W
VehiclesControllerutwórz akcjęDetailsoznaczoną trasą atrybutową:[HttpGet("pojazd/{sideNumber:fleetSideNo}")]. - Wszelkie zapytania niespełniające kryteriów (np.
/pojazd/999,/pojazd/abc) muszą zostać odrzucone na etapie routingu (skutkując kodem HTTP 404 przechwyconym przez moduł z Części 1).
- W
💡 Wskazówki implementacyjne: Rejestracja IRouteConstraint (Rozwiń)
using Microsoft.AspNetCore.Routing;
using Microsoft.AspNetCore.Http;
using System.Globalization;
namespace TransitDesk.Web.Routing.Constraints;
public class FleetSideNumberConstraint : IRouteConstraint
{
public bool Match(
HttpContext? httpContext,
IRouter? route,
string routeKey,
RouteValueDictionary values,
RouteDirection routeDirection)
{
if (values.TryGetValue(routeKey, out var rawVal) && rawVal != null)
{
if (int.TryParse(rawVal.ToString(), NumberStyles.Integer, CultureInfo.InvariantCulture, out int number))
{
return number >= 1000 && number <= 9999;
}
}
return false;
}
}
// Rejestracja w Program.cs:
builder.Services.Configure<RouteOptions>(options =>
{
options.ConstraintMap.Add("fleetSideNo", typeof(FleetSideNumberConstraint));
});
Część 3: Silnie Typowane Widoki, ViewModel i Semantyczne Tag Helpers (15 pkt)
Zbuduj moduł wprowadzania nowego pojazdu do ewidencji floty w oparciu
o czystą architekturę widoków i nowoczesne Tag Helpers (całkowity zakaz
stosowania starych metod @Html.TextBoxFor i
@Html.LabelFor).
Architektura modeli (Zalecenie vs Wariant minimum):
- Zalecane: Model formularza
VehicleViewModelznajduje się wTransitDesk.Web/ViewModels, a w projekcieTransitDesk.Core/Entitiesistnieje niezależna encja domenowaVehicle. Kontroler mapuje dane przed zapisem.- Wariant dopuszczalny (minimum): Można operować bezpośrednio na
VehicleViewModeli przechowywać obiekty w statycznej kolekcji w pamięci podręcznej serwisu, pod warunkiem pełnej demonstracji poprawności Tag Helpers i wzorca PRG.
Wymagania szczegółowe:
- [4 pkt] Projekt modelu widoku
(
VehicleViewModel):- Model musi zawierać właściwości:
SideNumber(int),ModelName(string),DepotName(string),ProductionYear(int),Capacity(int),IsLowFloor(bool – niskopodłogowy) orazServiceStatus(enum).
- Model musi zawierać właściwości:
- [7 pkt] Semantyczny formularz Razor z Tag Helpers:
- Plik widoku
Views/Vehicles/Create.cshtmlwykorzystujący Tag Helpers:<form asp-action="Create" method="post">,<input asp-for="...">,<label asp-for="...">,<select asp-for="...">oraz<span asp-validation-for="...">. - Zastosowanie tag helpera
<div asp-validation-summary="ModelOnly" class="text-danger"></div>do prezentacji błędów globalnych.
- Plik widoku
- [4 pkt] Wzorzec Post-Redirect-Get (PRG):
- W przypadku błędów walidacji akcja POST natychmiast zwraca ten sam widok z przekazanym modelem.
- Po poprawnym zapisie pojazdu akcja zapisuje powiadomienie w
TempData["SuccessMessage"]i wykonujeRedirectToAction(nameof(Index)). - Widok
Index.cshtmlodczytujeTempDatai renderuje powiadomienie (np. Bootstrap alert).
💡 Wskazówki implementacyjne: Widok formularza z Tag Helpers (Rozwiń)
@model TransitDesk.Web.ViewModels.VehicleViewModel
@{
ViewData["Title"] = "Rejestracja Nowego Pojazdu";
}
<h2>@ViewData["Title"]</h2>
<form asp-controller="Vehicles" asp-action="Create" method="post">
<div asp-validation-summary="ModelOnly" class="alert alert-danger"></div>
<div class="mb-3">
<label asp-for="SideNumber" class="form-label"></label>
<input asp-for="SideNumber" class="form-control" />
<span asp-validation-for="SideNumber" class="text-danger"></span>
</div>
<div class="mb-3">
<label asp-for="ModelName" class="form-label"></label>
<input asp-for="ModelName" class="form-control" />
<span asp-validation-for="ModelName" class="text-danger"></span>
</div>
<div class="mb-3 form-check">
<input asp-for="IsLowFloor" class="form-check-input" />
<label asp-for="IsLowFloor" class="form-check-label"></label>
</div>
<button type="submit" class="btn btn-primary">Zapisz pojazd w rejestrze</button>
</form>
Część 4: Zaawansowany Model Binding, Walidacja Hybrydowa i Zdalna (Remote) (15 pkt)
Zaimplementuj moduł zgłaszania incydentów drogowych
(IncidentReportViewModel), który chroni bazę danych przed
wprowadzeniem niespójnych informacji za pomocą walidacji hybrydowej
(klient + serwer) oraz zdalnej (Remote via AJAX).
Wymagania szczegółowe:
- [5 pkt] Deklaratywne adnotacje danych
(
DataAnnotations):LineNumber: pole wymagane, format alfanumeryczny od 1 do 4 znaków (np.6,199,N1) z użyciem[RegularExpression].Description: minimalna długość 20 znaków, maksymalna 500 znaków ([StringLength]).OccurredAt: data i czas zdarzenia ([DataType(DataType.DateTime)]), nie może być datą z przyszłości.
- [6 pkt] Walidacja zdalna numeru taborowego
(
[Remote]):- Pole
VehicleSideNumberw modelu widoku musi posiadać atrybut:[Remote(action: "VerifyVehicleAvailable", controller: "Incidents", ErrorMessage = "Pojazd o podanym numerze nie istnieje lub jest wycofany.")] - W
IncidentsControllerzaimplementuj akcjęVerifyVehicleAvailable(int vehicleSideNumber)zwracającąJson(true)w przypadku sukcesu lubJson(błąd)w przypadku braku pojazdu w ewidencji.
- Pole
- [4 pkt] Walidacja po stronie klienta (Unobtrusive
Validation):
- W widoku zgłoszenia dołącz skrypty walidacyjne za pomocą sekcji
widoku:
@section Scripts { <partial name="_ValidationScriptsPartial" /> } - Formularz musi uniemożliwiać wysyłkę z błędnymi danymi bez przeładowania strony w przeglądarce.
- W widoku zgłoszenia dołącz skrypty walidacyjne za pomocą sekcji
widoku:
💡 Wskazówki implementacyjne: Walidacja zdalna w kontrolerze (Rozwiń)
[HttpGet]
public IActionResult VerifyVehicleAvailable(int vehicleSideNumber)
{
// Logika weryfikacji w kolekcji floty
bool existsAndActive = _vehicleService.IsActiveVehicle(vehicleSideNumber);
if (!existsAndActive)
{
return Json($"Pojazd #{vehicleSideNumber} nie figuruje w czynnym rejestrze floty.");
}
return Json(true);
}
Część 5: Bezpieczny Asynchroniczny Upload Plików i Ochrona Zasobów (15 pkt)
Rozszerz formularz zgłaszania incydentów o możliwość załączenia dokumentacji fotograficznej lub raportu PDF. Zaimplementuj rygorystyczne procedury bezpieczeństwa I/O.
Wymagania szczegółowe:
- [4 pkt] Konfiguracja formularza:
- Formularz HTML musi posiadać atrybut
enctype="multipart/form-data". - Akcja kontrolera przyjmuje plik za pośrednictwem interfejsu
IFormFile? attachmentFile.
- Formularz HTML musi posiadać atrybut
- [7 pkt] Ochrona przed Path Traversal i weryfikacja
zawartości:
- Ograniczenie rozmiaru pliku: maksymalnie 5 MB (weryfikacja na serwerze).
- Biała lista rozszerzeń: wyłącznie
.jpg,.jpeg,.png,.pdf. - Zakaz używania oryginalnej nazwy pliku klienta na
dysku: Zapisywany plik musi otrzymać kryptograficznie
bezpieczną, unikalną nazwę opartą o
Guid.NewGuid()z zachowaniem zweryfikowanego rozszerzenia.
- [4 pkt] Asynchroniczne strumieniowanie I/O:
- Zapis do folderu
App_Data/Uploads/(znajdującego się poza publicznym folderemwwwroot). - Zastosowanie asynchronicznego strumienia
await attachmentFile.CopyToAsync(fileStream)bez blokowania wątków puli Kestrel.
- Zapis do folderu
💡 Wskazówki implementacyjne: Bezpieczny upload plików (Rozwiń)
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> ReportIncident(IncidentReportViewModel model, IFormFile? attachmentFile)
{
if (!ModelState.IsValid) return View(model);
if (attachmentFile != null && attachmentFile.Length > 0)
{
if (attachmentFile.Length > 5 * 1024 * 1024)
{
ModelState.AddModelError("attachmentFile", "Rozmiar załącznika nie może przekraczać 5 MB.");
return View(model);
}
var allowedExt = new[] { ".jpg", ".jpeg", ".png", ".pdf" };
var ext = Path.GetExtension(attachmentFile.FileName).ToLowerInvariant();
if (!allowedExt.Contains(ext))
{
ModelState.AddModelError("attachmentFile", "Niedozwolony typ pliku.");
return View(model);
}
var secureFileName = $"{Guid.NewGuid()}{ext}";
var storageDir = Path.Combine(Directory.GetCurrentDirectory(), "App_Data", "Uploads");
Directory.CreateDirectory(storageDir);
var targetPath = Path.Combine(storageDir, secureFileName);
using (var stream = new FileStream(targetPath, FileMode.Create))
{
await attachmentFile.CopyToAsync(stream);
}
model.AttachmentPath = targetPath;
}
await _incidentService.RegisterIncidentAsync(model);
return RedirectToAction("Success");
}
Część 6: Zarządzanie Stanem: Sesja JSON, Polityka Ciasteczek i Wzorzec PRG (10 pkt)
Aplikacja musi umożliwiać dyspozytorowi tworzenie tymczasowej „listy monitorowanych pojazdów” (koszyk pojazdów) w stanie sesji bez konieczności trwałego zapisu w bazie danych.
Wymagania szczegółowe:
- [4 pkt] Metody rozszerzające sesji oparte o
System.Text.Json:- Przygotuj klasę
SessionExtensionsdostarczającą metodySetObject<T>(this ISession session, string key, T value)orazGetObject<T>(this ISession session, string key).
- Przygotuj klasę
- [4 pkt] Obsługa listy monitorowanych pojazdów:
- Akcja
AddVehicleToWatchlist(int sideNumber)pobiera obiekt z sesji, dopisuje pojazd i zapisuje stan z powrotem do sesji. - Pasek nawigacyjny w pliku
_Layout.cshtmlodczytuje z sesji liczbę aktualnie monitorowanych pojazdów i wyświetla dynamiczny licznik obok pozycji menu.
- Akcja
- [2 pkt] Zgodność z polityką ciasteczek i RODO:
- Poprawna konfiguracja
CookiePolicyOptionswProgram.csgwarantująca, że ciasteczko sesyjne jest traktowane jakoIsEssential = trueoraz posiada flagęSameSiteMode.Lax.
- Poprawna konfiguracja
💡 Wskazówki implementacyjne: Serializacja obiektów w sesji (Rozwiń)
using System.Text.Json;
using Microsoft.AspNetCore.Http;
public static class SessionExtensions
{
public static void SetObject<T>(this ISession session, string key, T value)
{
session.SetString(key, JsonSerializer.Serialize(value));
}
public static T? GetObject<T>(this ISession session, string key)
{
var rawJson = session.GetString(key);
return rawJson == null ? default : JsonSerializer.Deserialize<T>(rawJson);
}
}
Część 7: Filtry Akcji, Wielojęzyczność i Konteneryzacja w Dockerze (15 pkt)
Wdrożenie zadań przekrojowych (cross-cutting concerns), przygotowanie aplikacji do obsługi wielu języków oraz konteneryzacja środowiska wykonawczego.
Wymagania szczegółowe:
- [5 pkt] Niestandardowy filtr akcji
(
AuditLogAttribute):- Utwórz klasę dziedziczącą po
ActionFilterAttribute. - W metodzie
OnActionExecutingrejestruj: adres IP klienta (context.HttpContext.Connection.RemoteIpAddress), nazwę kontrolera, akcję oraz znacznik czasu UTC. - W metodzie
OnActionExecutedobliczaj całkowity czas realizacji żądania i dopisuj go do nagłówka odpowiedzi HTTPX-Execution-Duration-Ms. - Oznacz filtrem krytyczne akcje edycji taboru i zgłaszania awarii.
- Utwórz klasę dziedziczącą po
- [5 pkt] Wielojęzyczność widoków
(
IViewLocalizer):- Skonfiguruj obsługę kultur
pl-PLorazen-USwProgram.cs(RequestLocalizationOptions). - Utwórz pliki zasobów
.resxdla strony głównej oraz menu nawigacyjnego. - Wstrzyknij
@inject IViewLocalizer Localizerdo plikuViews/Home/Index.cshtmli przetłumacz nagłówki oraz opisy powitalne.
- Skonfiguruj obsługę kultur
- [5 pkt] Skrypt konteneryzacji Docker:
- Przygotuj produkcyjny plik
Dockerfileoparty o wieloetapowy proces budowania (multi-stage build) dla środowiska.NET 8 ASP.NET Runtime. - Aplikacja w kontenerze musi nasłuchiwać na porcie HTTP 8080.
- Przygotuj produkcyjny plik
💡 Wskazówki implementacyjne: Filtr audytu i metryk czasu (Rozwiń)
using Microsoft.AspNetCore.Mvc.Filters;
using System.Diagnostics;
public class AuditLogAttribute : ActionFilterAttribute
{
private Stopwatch? _stopwatch;
public override void OnActionExecuting(ActionExecutingContext context)
{
_stopwatch = Stopwatch.StartNew();
var ip = context.HttpContext.Connection.RemoteIpAddress?.ToString();
var action = context.ActionDescriptor.DisplayName;
Debug.WriteLine($"[AUDIT START] IP: {ip} | Akcja: {action} | UTC: {DateTime.UtcNow}");
}
public override void OnActionExecuted(ActionExecutedContext context)
{
_stopwatch?.Stop();
context.HttpContext.Response.Headers.TryAdd("X-Execution-Duration-Ms", _stopwatch?.ElapsedMilliseconds.ToString());
}
}
4. Wymagania Dotyczące Pakietu Testów Automatycznych
W projekcie TransitDesk.Tests przygotuj zestaw testów
automatycznych weryfikujących integralność systemu:
FleetSideNumberConstraintTests(Testy jednostkowe):- Weryfikacja poprawności ograniczenia trasy dla prawidłowych numerów
(
1000,2072,9999) → wyniktrue. - Weryfikacja odrzucania wartości nieprawidłowych (
999,10000,"abc",null) → wynikfalse.
- Weryfikacja poprawności ograniczenia trasy dla prawidłowych numerów
(
IncidentValidationTests(Testy jednostkowe modeli):- Weryfikacja poprawności walidacji
IncidentReportViewModelza pomocąValidator.TryValidateObject. - Sprawdzenie, czy opis krótszy niż 20 znaków lub niepoprawny numer linii zgłasza błąd walidacji.
- Weryfikacja poprawności walidacji
MiddlewareAndStatusPagesTests(Testy integracyjne zWebApplicationFactory):- Wywołanie nieistniejącego adresu (np.
/nieistnieje/strona) musi zwrócić status HTTP 404 wraz z treścią wyrenderowaną przezErrorsController.
- Wywołanie nieistniejącego adresu (np.
SessionExtensionsTests:- Test sprawdzający, czy złożony obiekt listy pojazdów jest poprawnie serializowany i deserializowany bez utraty danych.
Polecenie uruchomienia testów weryfikacyjnych:
dotnet test --logger "console;verbosity=detailed"
5. Tabela Podsumowująca Oceny (Suma: 100 pkt / 100%)
| Nr | Zakres modułu | Weryfikowane mechanizmy, technologie i standardy | Punkty |
|---|---|---|---|
| 1 | Potok Middleware i Błędy HTTP | Kolejność komponentów w
Program.cs, UseStatusCodePagesWithReExecute,
widok błędu z zachowaniem statusu 404/403. |
15 pkt |
| 2 | Routing i Ograniczenia Tras | Trasa konwencjonalna
depot_fleet, implementacja IRouteConstraint
(zakres 1000–9999), routing atrybutowy. |
15 pkt |
| 3 | Widoki, ViewModel i Tag Helpers | Silnie typowane modele widoku, semantyczne
Tag Helpers w formularzu, wzorzec Post-Redirect-Get z
TempData. |
15 pkt |
| 4 | Model Binding i Walidacja Hybrydowa | Atrybuty DataAnnotations,
zdalna walidacja AJAX [Remote], skrypty walidacji po
stronie klienta. |
15 pkt |
| 5 | Bezpieczny Upload Plików | IFormFile, asynchroniczny
zapis CopyToAsync, biała lista rozszerzeń, ochrona przed
Path Traversal (GUID). |
15 pkt |
| 6 | Zarządzanie Stanem i Sesja JSON | Metody rozszerzające ISession
z biblioteką System.Text.Json, koszyk monitorowanych
pojazdów w _Layout. |
10 pkt |
| 7 | Filtry Akcji, Lokalizacja i Docker | Filtr AuditLogAttribute z
nagłówkiem HTTP, pliki .resx z IViewLocalizer,
wieloetapowy Dockerfile. |
15 pkt |
| SUMA | 100 pkt (100%) |