Architektura Aplikacji Internetowych / Programowanie .NET
Rok akademicki: 2026/27
Instrukcja laboratoryjna: Architektura ASP.NET Core MVC (.NET 8/9), Tag Helpers, Potok Żądań HTTP i Walidacja
Prowadzący: Tomasz Goluch
Wersja: 1.0
1. Wprowadzenie i ewolucja platformy
Cel: Zrozumienie założeń wzorca Model-View-Controller (MVC), ewolucji od klasycznego ASP.NET do wieloplatformowego ASP.NET Core (.NET 8 LTS / .NET 9), mechanizmów kompilacji widoków oraz architektury potoku żądań.
Wzorzec architektoniczny MVC (Model-View-Controller) realizuje zasadę separacji odpowiedzialności (Separation of Concerns) poprzez podział aplikacji na trzy autonomiczne warstwy:
- Model (oraz ViewModel): Reprezentuje strukturę danych, logikę biznesową oraz reguły walidacji. Komunikuje się ze źródłami danych i pozostaje całkowicie niezależny od interfejsu użytkownika.
- Widok (View): Odpowiada wyłącznie za prezentację
danych przekazanych przez kontroler za pomocą szablonów silnika Razor
(
.cshtml). - Kontroler (Controller): Przechwytuje akcje i zapytania użytkownika, komunikuje się z modelami oraz decyduje o wyborze widoku lub rezultatu HTTP zwracanego do klienta.
Żądanie HTTP (GET / POST)
│
▼
[ Potok Middleware ] (Obsługa błędów, HTTPS, Pliki statyczne, Sesja)
│
▼
[ Silnik Routingu ] ──► Dopasowanie URL do Kontrolera i Akcji
│
▼
[ Kontroler (Controller) ]
│ ▲
Pobiera / │ │ Zwraca dane /
Modyfikuje│ │ Obiekt ViewModel
▼ │
[ Model / Baza Danych ]
│
▼ Kontroler przekazuje Model do Widoku
[ Widok Razor (View .cshtml) ] ──► Kompilacja do HTML ──► Odpowiedź HTTP
🧠 Zrozumieć różnicę pokoleniową: ASP.NET (.NET Framework) vs ASP.NET Core (.NET 8/9+)
W klasycznym ASP.NET MVC aplikacja była nierozerwalnie powiązana z systemem Windows, bibliotekąSystem.Web.dlloraz serwerem IIS/IIS Express. Środowiska alternatywne, takie jak Mono pod Linuksem z serwerem Apache i modułemmod_monolub serwerem XSP, wymagały złożonej konfiguracji i nie gwarantowały pełnej zgodności.
ASP.NET Core (.NET 8/9) to platforma modularna, otwartoźródłowa i natywnie wieloplatformowa. Domyślnie wykorzystuje wbudowany, ultrawydajny serwer Kestrel (aplikacja jest w pełni samohostująca – self-hosted), eliminując konieczność instalacji zewnętrznych serwerów aplikacyjnych do uruchomienia procesu produkcyjnego.
2. Środowisko uruchomieniowe, narzędzia CLI i konteneryzacja
Cel: Konfiguracja środowiska Visual Studio 2022 oraz .NET CLI, opanowanie wieloplatformowego uruchamiania aplikacji i konteneryzacji w środowisku Docker.
1. Konfiguracja środowiska
- Zestaw narzędzi: Visual Studio 2022 (od wersji 17.8+) z zainstalowanym pakietem obciążeń „Opracowywanie zawartości dla platformy ASP.NET i sieci Web” (ASP.NET and web development) lub edytor Visual Studio Code z rozszerzeniem C# Dev Kit.
- Środowisko uruchomieniowe: SDK .NET 8.0 (wsparcie długoterminowe LTS) lub .NET 9.0.
- Telemetria CLI: W przypadku pracy w terminalu
zaleca się ustawienie zmiennej środowiskowej wyłączającej zbieranie
danych diagnostycznych:
lub w powłoce PowerShell:set DOTNET_CLI_TELEMETRY_OPTOUT=1$env:DOTNET_CLI_TELEMETRY_OPTOUT = "1"
2. Inicjalizacja i uruchomienie z wiersza poleceń (.NET CLI)
Tworzenie nowego projektu typu ASP.NET Core MVC:
dotnet new mvc -n MvcLabApp -f net8.0 --auth None
cd MvcLabApp
dotnet run
Aplikacja uruchomi serwer Kestrel i wyświetli adresy nasłuchiwania w
konsoli (np. https://localhost:7001 oraz
http://localhost:5001).
3. Konteneryzacja aplikacji w Dockerze
Aplikacje ASP.NET Core mogą być w prosty sposób uruchamiane w odizolowanych kontenerach linuksowych. Wykorzystujemy wieloetapowy proces budowania (multi-stage build), oddzielający środowisko kompilacji SDK od odchudzonego obrazu wykonawczego:
# ETAP 1: Kompilacja i publikacja aplikacji (Build Stage)
FROM [mcr.microsoft.com/dotnet/sdk:8.0](https://mcr.microsoft.com/dotnet/sdk:8.0) AS build
WORKDIR /src
COPY ["MvcLabApp.csproj", "./"]
RUN dotnet restore "MvcLabApp.csproj"
COPY . .
RUN dotnet publish "MvcLabApp.csproj" -c Release -o /app/publish /p:UseAppHost=false
# ETAP 2: Minimalistyczne środowisko uruchomieniowe (Runtime Stage)
FROM [mcr.microsoft.com/dotnet/aspnet:8.0](https://mcr.microsoft.com/dotnet/aspnet:8.0) AS final
WORKDIR /app
EXPOSE 8080
EXPOSE 8081
COPY --from=build /app/publish .
ENTRYPOINT ["dotnet", "MvcLabApp.dll"]
3. Struktura projektu i potok przetwarzania żądań (Middleware Pipeline)
Cel: Poznanie modułowej struktury aplikacji,
rejestracji usług w kontenerze Dependency Injection oraz konfiguracji
potoku HTTP w modelu Minimal Hosting (Program.cs).
1. Organizacja katalogów projektu
Controllers/– zawiera klasy kontrolerów obsługujących przychodzące żądania (np.HomeController.cs).Models/– klasy reprezentujące logikę dziedzinową, encje oraz modele widoków (ViewModels).Views/– szablony widoków Razor pogrupowane w podfolderach odpowiadających nazwom kontrolerów.Views/Shared/– widoki współdzielone (szablon główny_Layout.cshtml, widok błędówError.cshtml, skrypty walidacji_ValidationScriptsPartial.cshtml).Views/_ViewStart.cshtml– plik inicjujący domyślny układ graficzny dla każdego widoku.Views/_ViewImports.cshtml– globalne importy przestrzeni nazw oraz dyrektywy rejestracji Tag Helperów.
wwwroot/– katalog zasobów statycznych (CSS, pliki JavaScript, obrazy) serwowanych bezpośrednio przez Kestrel.Program.cs– plik konfiguracyjny aplikacji, w którym rejestrowane są usługi (IoC/DI) oraz definiowany jest potok middleware.
2. Rejestracja
usług i middleware w Program.cs
W ASP.NET Core konfiguracja odbywa się z wykorzystaniem
zunifikowanego pliku Program.cs:
var builder = WebApplication.CreateBuilder(args);
// 1. Rejestracja usług w kontenerze Dependency Injection
builder.Services.AddControllersWithViews();
// Rejestracja pamięci podręcznej i stanu sesji
builder.Services.AddDistributedMemoryCache();
builder.Services.AddSession(options =>
{
options.IdleTimeout = TimeSpan.FromMinutes(15);
options.Cookie.HttpOnly = true;
options.Cookie.IsEssential = true;
});
// Konfiguracja polityki ciasteczek
builder.Services.Configure<CookiePolicyOptions>(options =>
{
options.CheckConsentNeeded = context => false;
options.MinimumSameSitePolicy = SameSiteMode.Lax;
});
var app = builder.Build();
// 2. Konfiguracja potoku przetwarzania żądań HTTP (Kolejność ma znaczenie!)
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Home/Error");
app.UseHsts();
}
else
{
app.UseDeveloperExceptionPage();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseSession(); // Sesja musi być umieszczona po UseRouting(), a przed UseAuthorization()
app.UseAuthentication();
app.UseAuthorization();
// Domyślna trasa konwencjonalna
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
app.Run();
💡 Wyjaśnienie: Dlaczego kolejność komponentów Middleware w potoku jest krytyczna? (Kliknij, aby rozwinąć/zwinąć)
Potok żądań w ASP.NET Core działa dwukierunkowo – żądanie przepływa przez kolejne komponenty middleware w głąb potoku, a wygenerowana odpowiedź wraca przez nie w kolejności odwrotnej:
UseExceptionHandler/UseDeveloperExceptionPage: Musi znajdować się na samym początku, aby przechwytywać wszelkie wyjątki zgłoszone przez komponenty umieszczone dalej w potoku.UseStaticFiles: Umieszczone wcześnie, aby pliki statyczne (.css,.js, obrazy zwwwroot) były natychmiast zwracane bez niepotrzebnego uruchamiania mechanizmów routingu czy sprawdzania uprawnień.UseRouting: Dopasowuje adres URL do właściwego punktu końcowego (kontrolera i akcji).UseSession: Ładuje stan sesji powiązany z ciasteczkiem użytkownika przed wywołaniem logiki biznesowej.UseAuthenticationiUseAuthorization: Sprawdzają tożsamość i uprawnienia użytkownika dla wskazanego punktu końcowego.MapControllerRoute: Ostatecznie uruchamia wskazaną akcję kontrolera.
4. Routing: Konwencjonalny vs Atrybutowy oraz Ograniczenia Tras (Constraints)
Cel: Opanowanie reguł dopasowywania ścieżek URL, tworzenia szablonów parametrów oraz implementacji własnych ograniczeń tras.
1. Routing konwencjonalny (Conventional Routing)
Trasy konwencjonalne rejestrowane są centralnie w pliku
Program.cs przy pomocy metody
MapControllerRoute. Trasa składa się z unikalnej nazwy
(name), szablonu adresu (pattern) oraz
opcjonalnych wartości domyślnych (defaults):
app.MapControllerRoute(
name: "users_route",
pattern: "Uzytkownicy/{action}/{id?}",
defaults: new { controller = "Users", action = "Index" });
- Parametry w szablonie: Segmenty objęte nawiasami
klamrowymi (
{controller},{action},{id}) są traktowane jako parametry trasy. - Wartości domyślne i opcjonalne: Znak równości
określa wartość domyślną (
controller=Home), a znak zapytania oznacza parametr opcjonalny (id?). - Parametr wieloznaczny (Catch-all): Prefiks
w postaci gwiazdki (
{*slug}) sprawia, że parametr przechwytuje całą pozostałą część ścieżki (np.dokumenty/{*sciezka}). - Zasada pierwszeństwa: Trasy dopasowywane są ściśle w kolejności ich rejestracji w kodzie. Po pierwszym udanym dopasowaniu przetwarzanie kolejnych reguł zostaje przerwane.
2. Wbudowane ograniczenia tras (Route Constraints)
Ograniczenia pozwalają odrzucić dopasowanie trasy na poziomie routingu, jeśli przekazany parametr nie spełnia kryteriów technicznych:
| Ograniczenie | Składnia w szablonie | Opis działania |
|---|---|---|
int /
long |
{id:int} |
Wymaga poprawnej liczby całkowitej. |
alpha |
{kod:alpha} |
Dopuszcza wyłącznie litery alfabetu łacińskiego [a-zA-Z]. |
range(min, max) |
{wiek:range(18, 100)} |
Wartość liczbowa mieszcząca się w zadanym przedziale. |
minlength /
maxlength |
{login:minlength(3):maxlength(20)} |
Ograniczenie minimalnej i maksymalnej długości tekstu. |
regex(wzorzec) |
{data:regex(^\\d{{4}}-\\d{{2}}-\\d{{2}}$)} |
Weryfikacja zgodności z wyrażeniem regularnym. |
guid |
{id:guid} |
Weryfikacja poprawności formatu GUID. |
Wiele ograniczeń można łączyć kolejnymi dwukropkami, np.
{id:int:min(1)}.
3.
Własne ograniczenie trasy: implementacja
IRouteConstraint
W przypadku niestandardowych reguł dopasowania tworzymy klasę
implementującą interfejs IRouteConstraint:
using Microsoft.AspNetCore.Routing;
using Microsoft.AspNetCore.Http;
using System.Globalization;
public class DivisibleBy10Constraint : IRouteConstraint
{
public bool Match(
HttpContext? httpContext,
IRouter? route,
string routeKey,
RouteValueDictionary values,
RouteDirection routeDirection)
{
if (values.TryGetValue(routeKey, out var value) && value != null)
{
if (int.TryParse(value.ToString(), NumberStyles.Integer, CultureInfo.InvariantCulture, out int number))
{
return number % 10 == 0;
}
}
return false;
}
}
Rejestracja w Program.cs i użycie w trasie:
builder.Services.Configure<RouteOptions>(options =>
{
options.ConstraintMap.Add("div10", typeof(DivisibleBy10Constraint));
});
app.MapControllerRoute(
name: "calculator",
pattern: "Obliczenia/Dziesiatki/{liczba:div10}",
defaults: new { controller = "Calculator", action = "Process" });
4. Routing atrybutowy (Attribute Routing)
Routing atrybutowy definiowany jest bezpośrednio nad klasami
kontrolerów i metodami akcji za pomocą atrybutu [Route]
oraz atrybutów czasowników HTTP ([HttpGet],
[HttpPost]):
[Route("produkty")]
public class ProductsController : Controller
{
// Adres: /produkty/szczegoly/15
[HttpGet("szczegoly/{id:int:min(1)}", Name = "ProductDetails")]
public IActionResult Details(int id)
{
return View(id);
}
// Prefiks '/' na początku trasy akcji odcina trasę nadrzędną kontrolera: /cennik
[HttpGet("/cennik")]
public IActionResult PriceList()
{
return View();
}
}
5. Kontrolery, Akcje i Przepływ Danych do Widoku
Cel: Zrozumienie cyklu życia kontrolera,
obsługiwanych typów rezultatów (IActionResult) oraz
bezpiecznych mechanizmów przekazywania danych do widoku.
1. Zasady budowy kontrolerów
- Kontroler dziedziczy po klasie bazowej
Microsoft.AspNetCore.Mvc.Controller. - Klasa tworzona jest na nowo dla każdego przychodzącego żądania HTTP (jest obiektem bezstanowym).
- Każda publiczna metoda niestatyczna jest traktowana jako akcja.
- Metody, które nie mają być punktami końcowymi HTTP, muszą zostać
oznaczone atrybutem
[NonAction]. - Akcje nie mogą posiadać parametrów modyfikowanych przez słowa
kluczowe
reflubout.
2. Typy zwracane
przez akcje (IActionResult)
Współczesne aplikacje zwracają implementacje interfejsu
IActionResult za pośrednictwem metod pomocniczych klasy
bazowej:
View()– zwraca widok o nazwie odpowiadającej bieżącej akcji.View("InnaNazwa", model)– zwraca wskazany widok z przekazanym obiektem modelu.RedirectToAction("Index", "Home")– wykonuje przekierowanie HTTP (kod 302) do innej akcji.Redirect(url)– wykonuje bezpośrednie przekierowanie pod podany adres URL.Content("tekst")– zwraca surowy tekst jako treść odpowiedzi.File(bajt, "application/pdf", "plik.pdf")– zwraca strumień lub tablicę bajtów jako plik binarny.Json(obiekt)– zwraca obiekt zserializowany do formatu JSON.NotFound(),BadRequest(),Unauthorized()– zwraca standardowe kody odpowiedzi HTTP (404, 400, 401).
3. Zestawienie mechanizmów przekazywania danych do widoku
| Mechanizm | Typowanie | Czas życia | Zastosowanie |
|---|---|---|---|
Model /
ViewModel |
Silne (Strongly-typed) | Bieżące żądanie | Zalecany standard. Bezpieczeństwo typów w czasie kompilacji. |
ViewData |
Słownik string →
object |
Bieżące żądanie | Drobne dane pomocnicze (np. tytuł strony w
nagłówku ViewData["Title"]). |
ViewBag |
Obiekt dynamiczny
(dynamic) |
Bieżące żądanie | Dynamiczna powłoka na
ViewData (brak kontroli błędów kompilatora). |
TempData |
Magazyn klucz-wartość | Przetrwa jedno przekierowanie | Komunikaty po akcjach POST (wzorzec Post-Redirect-Get). |
Przykład zastosowania wzorca Post-Redirect-Get (PRG):
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Create(ProductViewModel model)
{
if (!ModelState.IsValid)
{
return View(model); // Zwrot formularza z błędami w tym samym żądaniu
}
_repository.Add(model);
TempData["SuccessMessage"] = "Produkt został pomyślnie dodany do katalogu.";
return RedirectToAction(nameof(Index)); // Przekierowanie GET po zapisie POST
}
6. Silnik Widoków Razor i Nowoczesne Tag Helpers
Cel: Opanowanie składni szablonów Razor, mechanizmów zabezpieczeń przed atakami XSS oraz Tag Helperów.
1. Podstawy składni Razor
- Kod po stronie serwera rozpoczyna się od znaku
@. - Wyrażenia proste:
<span>Zalogowany: @Model.UserName</span> - Wyrażenia jawne:
<span>Suma: @(Model.Price + Model.Tax)</span> - Instrukcje wieloliniowe i pętle:
@for (int i = 0; i < Model.Items.Count; i++) { <p>Pozycja #@i: @Model.Items[i].Name</p> } - Bezpieczeństwo XSS: Razor domyślnie koduje wszystkie znaczniki HTML.
Aby renderować niesformatowany kod HTML, należy jawnie wywołać
@Html.Raw(Model.HtmlContent).
2.
Szablony nadrzędne i sekcje (RenderBody /
RenderSectionAsync)
Główny układ aplikacji (_Layout.cshtml) definiuje
wspólną strukturę wizualną. Treść widoku wstrzykiwana jest za pomocą
metody RenderBody(), a nazwane bloki za pośrednictwem
RenderSectionAsync:
<!DOCTYPE html>
<html lang="pl">
<head>
<meta charset="utf-8" />
<title>@ViewData["Title"] - System Centralny</title>
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css" />
</head>
<body>
<header>
<nav class="navbar navbar-expand-sm navbar-dark bg-dark">
<div class="container-fluid">
<a class="navbar-brand" asp-controller="Home" asp-action="Index">System</a>
</div>
</nav>
</header>
<div class="container mt-4">
@RenderBody()
</div>
<script src="~/lib/jquery/dist/jquery.min.js"></script>
@await RenderSectionAsync("Scripts", required: false)
</body>
</html>
3. Tag Helpers w formularzach
Tag Helpers zastąpiły dawne metody HTML Helpers
(@Html.TextBoxFor), oferując składnię zgodną ze standardem
HTML5 i pełne wsparcie edytora:
Rejestracja w Views/_ViewImports.cshtml:
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
Kluczowe Tag Helpers:
- Formularze:
<form asp-controller="Account" asp-action="Login" method="post">– automatycznie dodaje ukryty token ochrony przed atakiem CSRF (__RequestVerificationToken). - Etykiety:
<label asp-for="Login" class="form-label"></label>– generuje treść na podstawie atrybutu[Display(Name = "...")]z modelu. - Pola wejściowe:
<input asp-for="Login" class="form-control" />– tworzy atrybutyid,name,value, dopasowuje typ pola (text,password,number) oraz dodaje atrybuty walidacyjnedata-val-*. - Komunikaty błędów:
<span asp-validation-for="Login" class="text-danger"></span>– renderuje komunikat błędu powiązany z danym polem. - Podsumowanie walidacji:
<div asp-validation-summary="ModelOnly" class="text-danger"></div>– wyświetla błędy globalne niezwiązane z konkretnym polem. - Linki nawigacyjne:
<a asp-controller="Product" asp-action="Details" asp-route-id="15">Szczegóły</a>.
7. Model Binding, Walidacja Hybrydowa i Zdalna (Remote Validation)
Cel: Opanowanie wiązania parametrów HTTP do modeli C#, ochrona przed podatnością Mass Assignment oraz konfiguracja walidacji wielopoziomowej.
1. Mechanizm Model Bindingu i bezpieczeństwo Mass Assignment
Silnik ASP.NET Core automatycznie odczytuje wartości parametrów z pól
formularza (Form), danych trasy (RouteData)
oraz ciągu zapytania (QueryString), konwertując je na
odpowiednie typy w C#.
⚠️ Podatność Mass Assignment (Over-Posting)
Przekazywanie bezpośrednich encji bazy danych jako parametrów akcji kontrolera stwarza lukę bezpieczeństwa – użytkownik może przesłać dodatkowe pola w żądaniu HTTP (np.IsAdmin=true), które zostaną automatycznie powiązane z encją.
Dobra praktyka: Formularze powinny zawsze operować na dedykowanych modelach widoku (ViewModels / DTO), lub korzystać z atrybutu[Bind]określającego dozwoloną listę pól:public IActionResult UpdateProfile([Bind("FirstName,LastName,Email")] UserProfileViewModel model)
2.
Walidacja po stronie serwera i adnotacje
DataAnnotations
Właściwości modelu opisujemy atrybutami z przestrzeni
System.ComponentModel.DataAnnotations:
using System.ComponentModel.DataAnnotations;
using Microsoft.AspNetCore.Mvc;
public class RegisterViewModel
{
[Required(ErrorMessage = "Pole {0} jest wymagane.")]
[StringLength(20, MinimumLength = 3, ErrorMessage = "{0} musi zawierać od {2} do {1} znaków.")]
[Display(Name = "Nazwa użytkownika")]
[Remote(action: "VerifyUsername", controller: "Account", ErrorMessage = "Podana nazwa jest już zajęta.")]
public string Username { get; set; } = string.Empty;
[Required(ErrorMessage = "Adres e-mail jest wymagany.")]
[EmailAddress(ErrorMessage = "Nieprawidłowy format adresu e-mail.")]
[Display(Name = "Adres e-mail")]
public string Email { get; set; } = string.Empty;
[Required]
[Range(18, 100, ErrorMessage = "Wymagany wiek: od {1} do {2} lat.")]
[Display(Name = "Wiek")]
public int Age { get; set; }
[Required]
[DataType(DataType.Password)]
[StringLength(100, MinimumLength = 8, ErrorMessage = "Hasło musi mieć co najmniej 8 znaków.")]
[Display(Name = "Hasło")]
public string Password { get; set; } = string.Empty;
}
Weryfikacja w kontrolerze z wykorzystaniem obiektu
ModelState:
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Register(RegisterViewModel model)
{
if (model.Username.Equals("administrator", StringComparison.OrdinalIgnoreCase))
{
ModelState.AddModelError(nameof(model.Username), "Nazwa 'administrator' jest zastrzeżona.");
}
if (!ModelState.IsValid)
{
return View(model); // Powrót do widoku formularza z prezentacją błędów
}
return RedirectToAction("Success");
}
3. Walidacja po stronie klienta (Client-Side Unobtrusive Validation)
Zapobiega wysyłaniu formularza z błędami do serwera, sprawdzając reguły w przeglądarce przy pomocy biblioteki JavaScript. Wymaga dołączenia skryptów w sekcji widoku:
@section Scripts {
@{ await Html.RenderPartialAsync("_ValidationScriptsPartial"); }
}
4. Walidacja zdalna (Remote Validation przez AJAX)
Pozwala asynchronicznie zweryfikować unikalność lub poprawność danych po stronie serwera bez przeładowywania strony (np. sprawdzenie dostępności loginu):
[HttpGet]
public IActionResult VerifyUsername(string username)
{
bool exists = _userService.CheckIfUserExists(username);
if (exists)
{
return Json($"Użytkownik o loginie '{username}' już istnieje.");
}
return Json(true); // Zwrócenie wartości true oznacza walidację pomyślną
}
8. Asynchroniczny Upload Plików i Bezpieczeństwo
Cel: Prawidłowa obsługa przesyłania plików na serwer, asynchroniczny zapis strumieniowy oraz zabezpieczenie przed atakami Path Traversal.
Formularz przesyłający pliki musi posiadać atrybut
enctype="multipart/form-data":
<form asp-action="UploadAttachment" method="post" enctype="multipart/form-data">
<div class="mb-3">
<label class="form-label">Załącznik (PDF, JPG, PNG - max 5 MB):</label>
<input type="file" name="fileUpload" class="form-control" />
</div>
<button type="submit" class="btn btn-primary">Prześlij plik</button>
</form>
Bezpieczna implementacja akcji przyjmującej
IFormFile:
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> UploadAttachment(IFormFile? fileUpload)
{
if (fileUpload == null || fileUpload.Length == 0)
{
ModelState.AddModelError(string.Empty, "Nie wybrano pliku do przesłania.");
return View();
}
// 1. Walidacja rozmiaru pliku (np. max 5 MB)
if (fileUpload.Length > 5 * 1024 * 1024)
{
ModelState.AddModelError(string.Empty, "Plik przekracza dopuszczalny limit 5 MB.");
return View();
}
// 2. Walidacja rozszerzenia (Biała lista formatów)
var allowedExtensions = new[] { ".pdf", ".jpg", ".png" };
var extension = Path.GetExtension(fileUpload.FileName).ToLowerInvariant();
if (!allowedExtensions.Contains(extension))
{
ModelState.AddModelError(string.Empty, "Niedozwolony format pliku.");
return View();
}
// 3. Generowanie unikalnej nazwy pliku (Ochrona przed Path Traversal)
var trustedFileName = $"{Guid.NewGuid()}{extension}";
var targetFolder = Path.Combine(Directory.GetCurrentDirectory(), "App_Data", "Uploads");
Directory.CreateDirectory(targetFolder);
var targetPath = Path.Combine(targetFolder, trustedFileName);
// 4. Asynchroniczne strumieniowanie pliku na dysk
using (var stream = new FileStream(targetPath, FileMode.Create))
{
await fileUpload.CopyToAsync(stream);
}
TempData["Status"] = "Plik został pomyślnie przesłany.";
return RedirectToAction(nameof(Index));
}
9. Zarządzanie Stanem: Sesja JSON i Polityka Ciasteczek
Cel: Wykorzystanie stanu sesji HTTP, bezpieczna
serializacja obiektów z biblioteką System.Text.Json oraz
konfiguracja ciasteczek sesyjnych.
Obiekt ISession w ASP.NET Core obsługuje natywnie
jedynie operacje na typach int, string oraz
tablicach bajtów. Aby przechowywać złożone obiekty biznesowe, tworzymy
metody rozszerzające oparte na serializacji JSON:
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 json = session.GetString(key);
return json == null ? default : JsonSerializer.Deserialize<T>(json);
}
}
Użycie w kontrolerze:
// Zapis obiektu do sesji
var userPreferences = new UserSettings { Theme = "Dark", PageSize = 25 };
HttpContext.Session.SetObject("Preferences", userPreferences);
// Odczyt obiektu z sesji
var currentSettings = HttpContext.Session.GetObject<UserSettings>("Preferences") ?? new UserSettings();
10. Filtry Akcji (Action Filters) i Globalna Obsługa Błędów
Cel: Opanowanie potoku filtrów MVC do obsługi zadań przekrojowych (Cross-Cutting Concerns) oraz konfiguracja stron błędów HTTP.
1.
Implementacja filtru akcji (ActionFilterAttribute)
Filtry akcji pozwalają na uruchomienie wspólnego kodu przed
(OnActionExecuting) oraz po (OnActionExecuted)
wykonaniu metody akcji:
using Microsoft.AspNetCore.Mvc.Filters;
using System.Diagnostics;
using Microsoft.AspNetCore.Mvc;
public class ExecutionBenchmarkAttribute : ActionFilterAttribute
{
private Stopwatch? _stopwatch;
public override void OnActionExecuting(ActionExecutingContext context)
{
_stopwatch = Stopwatch.StartNew();
}
public override void OnActionExecuted(ActionExecutedContext context)
{
_stopwatch?.Stop();
if (context.Controller is Controller controller)
{
controller.ViewData["ExecutionTimeMs"] = _stopwatch?.ElapsedMilliseconds;
}
}
}
Użycie filtru nad akcją lub całym kontrolerem:
[ExecutionBenchmark]
public IActionResult Report()
{
return View();
}
2.
Re-ekzekucja kodów błędów HTTP
(UseStatusCodePagesWithReExecute)
Domyślnie serwer Kestrel zwraca pustą stronę w przypadku błędu HTTP
404 lub 403. Użycie middleware
UseStatusCodePagesWithReExecute zachowuje poprawny kod
odpowiedzi HTTP w nagłówkach, a jednocześnie przekazuje wykonanie do
dedykowanego widoku błędu:
// W Program.cs:
app.UseStatusCodePagesWithReExecute("/Errors/Status", "?code={0}");
Kontroler obsługujący błędy:
public class ErrorsController : Controller
{
public IActionResult Status(int code)
{
ViewBag.Code = code;
ViewBag.Description = code switch
{
404 => "Nie znaleziono żądanego zasobu.",
403 => "Brak uprawnień do przeglądania tej strony.",
_ => "Wystąpił nieoczekiwany błąd protokołu HTTP."
};
return View();
}
}
11. Wielojęzyczność i Lokalizacja (Localization)
Cel: Przygotowanie aplikacji do obsługi wielu wersji
językowych za pomocą plików zasobów (.resx) oraz
wstrzykiwania serwisu IViewLocalizer.
1. Rejestracja
usług lokalizacji w Program.cs
using Microsoft.AspNetCore.Localization;
using System.Globalization;
builder.Services.AddLocalization(options => options.ResourcesPath = "Resources");
builder.Services.AddControllersWithViews()
.AddViewLocalization()
.AddDataAnnotationsLocalization();
var app = builder.Build();
var supportedCultures = new[]
{
new CultureInfo("pl-PL"),
new CultureInfo("pl"),
new CultureInfo("en-US"),
new CultureInfo("en")
};
app.UseRequestLocalization(new RequestLocalizationOptions
{
DefaultRequestCulture = new RequestCulture("pl"),
SupportedCultures = supportedCultures,
SupportedUICultures = supportedCultures
});
2. Tłumaczenie treści w widoku Razor
Pliki zasobów umieszczamy w katalogu
Resources/Views/{Kontroler}/{Akcja}.{kultura}.resx. W
widoku wstrzykujemy interfejs IViewLocalizer:
@using Microsoft.AspNetCore.Mvc.Localization
@inject IViewLocalizer Localizer
<h1>@Localizer["NaglowekGlowny"]</h1>
<p>@Localizer["OpisStrony"]</p>