Gdy aplikacja webowa zaczyna rosnąć, szybko widać, że kod widoku, obsługa formularza i logika biznesowa nie powinny mieszkać w jednym miejscu. W tym artykule wyjaśniam, czym jest .NET MVC, jak żądanie przechodzi przez aplikację, jak rozpocząć projekt w ASP.NET Core oraz kiedy lepiej wybrać Razor Pages, Web API albo Blazor.
Najważniejsze informacje o budowaniu aplikacji MVC
- Model, widok i kontroler rozdzielają dane, prezentację oraz obsługę żądań.
- ASP.NET Core MVC jest właściwym wyborem dla nowych aplikacji na współczesnym .NET.
- Routing, model binding i walidacja automatyzują dużą część obsługi żądania HTTP.
- Razor Pages lepiej pasują do prostych aplikacji opartych na stronach, a MVC do rozbudowanych interfejsów i API.
- Najczęstszy błąd to przeciążony kontroler zawierający całą logikę biznesową.
Czym jest MVC w ekosystemie .NET i kiedy ma sens
Pod nazwą .NET MVC najczęściej kryje się wzorzec Model-View-Controller używany do budowania aplikacji webowych w środowisku Microsoftu. Jego główna idea jest prosta: model przechowuje dane i reguły biznesowe, widok odpowiada za interfejs, a kontroler przyjmuje żądanie i koordynuje odpowiedź.
W praktyce nie chodzi o mechaniczne rozdzielenie plików do trzech folderów. Chodzi o to, żeby zmiana wyglądu strony nie wymagała grzebania w logice płatności, a testowanie reguł biznesowych nie zależało od uruchomienia przeglądarki. Ten podział zaczyna naprawdę pomagać przy aplikacjach, które mają wiele formularzy, ról użytkowników, procesów i integracji.
Trzy elementy wzorca
| Element | Za co odpowiada | Przykład |
|---|---|---|
| Model | Dane, reguły i operacje aplikacji | Produkt, zamówienie, koszyk |
| View | Prezentacja danych użytkownikowi | Plik Razor .cshtml
|
| Controller | Obsługa żądania i wybór odpowiedzi | Akcja Index lub Create
|
W nowym projekcie wybrałbym ASP.NET Core MVC, a nie klasyczne ASP.NET MVC oparte na .NET Framework 4.x. Starsza technologia nadal występuje w utrzymywanych systemach, ale dla nowych aplikacji oznacza zwykle gorszą elastyczność, starszy model hostingu i trudniejszą modernizację.
ASP.NET Core MVCdziała na wspólnym fundamencie ASP.NET Core. Otrzymujemy między innymi wbudowane dependency injection, routing, filtry, walidację, obsługę logowania i możliwość uruchomienia aplikacji na Windowsie, Linuksie lub w kontenerze.
Jak żądanie przechodzi przez aplikację
Najłatwiej zrozumieć MVC, śledząc pojedyncze żądanie. Użytkownik otwiera adres /Products/Details/15, a aplikacja musi ustalić, która akcja ma zostać wywołana, pobrać produkt o identyfikatorze 15 i zwrócić gotowy HTML.
- Routing dopasowuje adres URL do kontrolera i akcji.
- Model binding zamienia dane z adresu, formularza lub query stringa na typy C#.
- Walidacja sprawdza, czy otrzymane dane spełniają wymagania.
- Kontroler wywołuje serwis lub model domenowy.
- Widok Razor generuje HTML, który wraca do przeglądarki.
Przykładowy kontroler może wyglądać tak:
public class ProductsController : Controller
{
private readonly IProductService _products;
public ProductsController(IProductService products)
{
_products = products;
}
public async Task Details(int id)
{
var product = await _products.GetDetailsAsync(id);
if (product is null)
return NotFound();
return View(product);
}
} Parametr id nie musi być ręcznie odczytywany z tekstu URL. Model binding pobiera wartość z routingu i konwertuje ją do typu int. To drobna funkcja, ale przy formularzach i rozbudowanych obiektach oszczędza mnóstwo powtarzalnego kodu.
Walidacja działa przed wykonaniem właściwej logiki. Dla przykładu model formularza może wymagać adresu e-mail i minimalnej długości hasła:
public class RegisterViewModel
{
[Required]
[EmailAddress]
public string Email { get; set; } = string.Empty;
[Required]
[MinLength(8)]
public string Password { get; set; } = string.Empty;
}W akcji sprawdzam potem ModelState.IsValid. Nie traktuję jednak walidacji po stronie przeglądarki jako zabezpieczenia. Użytkownik może wysłać żądanie bezpośrednio, dlatego każda reguła musi zostać zweryfikowana także na serwerze.
Jak rozpocząć projekt ASP.NET Core MVC
Najprostszy start zapewnia szablon dostępny w .NET CLI. Polecenie tworzy gotową strukturę aplikacji z kontrolerem, widokami, plikiem konfiguracyjnym i przykładowym routingiem.
dotnet new mvc -n SklepWeb
cd SklepWeb
dotnet runW typowym projekcie znajdziemy katalogi Controllers, Views i Models. W większej aplikacji nie trzymam jednak całej logiki biznesowej w Models. Częściej wydzielam serwisy aplikacyjne, modele domenowe, repozytoria i osobne ViewModele przeznaczone wyłącznie do komunikacji z widokiem.
Routing konwencjonalny
Klasyczny routing może korzystać z prostego schematu:
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");Dzięki temu adres /Products/Details/15 prowadzi do akcji Details w kontrolerze ProductsController. Przy publicznych stronach zwracam uwagę na czytelne i stabilne adresy, ponieważ pomagają użytkownikowi zrozumieć zawartość strony i ułatwiają późniejsze zarządzanie linkami.
Routing atrybutowy
Gdy potrzebuję pełnej kontroli nad endpointem, stosuję atrybuty:
[Route("produkty")]
public class ProductsController : Controller
{
[HttpGet("{id:int}")]
public IActionResult Details(int id)
{
return View(id);
}
}Ograniczenie int sprawia, że metoda nie przyjmie przypadkowego tekstu jako identyfikatora. Takie drobne reguły poprawiają czytelność i ograniczają liczbę niejednoznacznych dopasowań.
Widok i ViewModel
Widok powinien otrzymywać dokładnie te dane, których potrzebuje. Zamiast przekazywać bezpośrednio encję bazy danych, wolę użyć ViewModelu. Chroni to przed przypadkowym ujawnieniem pól i ogranicza ryzyko masowego przypisywania danych z formularza, czyli tak zwanego overpostingu.
@model ProductDetailsViewModel
@Model.Name
Cena: @Model.Price.ToString("C")
Razor pozwala łączyć HTML z kodem C#, ale logika w pliku .cshtml powinna pozostać skromna. Jeśli widok zaczyna zawierać wiele warunków, obliczeń i zapytań, zwykle jest to sygnał, że część odpowiedzialności trzeba przenieść do ViewModelu albo komponentu widoku.
MVC, Razor Pages, Web API czy Blazor
Nie każda aplikacja webowa potrzebuje kontrolerów i widoków. Wybór zależy od tego, czy buduję serwis renderujący HTML, publiczne API, czy interfejs działający bardziej jak aplikacja desktopowa.
| Technologia | Najlepsze zastosowanie | Główna zaleta | Ograniczenie |
|---|---|---|---|
| ASP.NET Core MVC | Rozbudowane aplikacje z wieloma obszarami i kontrolerami | Wyraźny podział odpowiedzialności i duża kontrola | Więcej plików i decyzji architektonicznych |
| Razor Pages | Aplikacje oparte na stronach i formularzach | Logika strony i jej widok są blisko siebie | Mniej wygodne przy bardzo rozbudowanych API |
| Web API | Backend dla aplikacji mobilnych, SPA i integracji | Jasny kontrakt HTTP i formaty JSON | Nie dostarcza klasycznego interfejsu HTML |
| Blazor | Interaktywne interfejsy tworzone w C# | Komponentowy model UI bez konieczności pisania całego frontendu w JavaScript | Inny model renderowania i dodatkowe decyzje dotyczące wydajności |
Razor Pages wybieram wtedy, gdy aplikacja składa się głównie z niezależnych stron, na przykład panelu administracyjnego z formularzami. MVC lepiej sprawdza się przy większym serwisie, w którym wiele ekranów korzysta ze wspólnych procesów, filtrów i kontrolerów.
Jeśli projekt ma udostępniać wyłącznie dane, zacząłbym od szablonu Web API. Dodawanie widoków MVC do takiej aplikacji nie daje dużej korzyści. Z kolei przy migracji starszego systemu z ASP.NET MVC przejście do ASP.NET Core MVC bywa najmniej ryzykownym etapem pośrednim.
Błędy, które szybko komplikują aplikację
Kontroler robi wszystko
Kontroler powinien koordynować żądanie, a nie zawierać cały proces biznesowy. Jeżeli jedna akcja pobiera dane, liczy rabaty, wysyła wiadomości, zapisuje logi i buduje kilka modeli, mamy już fat controller. Przeniesienie tych operacji do serwisów ułatwia testy i pozwala użyć tej samej logiki poza interfejsem webowym.
Encje bazy trafiają prosto do formularza
To wygodne na początku, ale ryzykowne przy edycji danych. Użytkownik może przesłać pola, których nie powinien zmieniać, na przykład status zamówienia albo identyfikator właściciela. Osobny ViewModel jasno określa dozwolony zakres danych wejściowych.
Walidacja istnieje tylko w JavaScripcie
Walidacja klienta poprawia wygodę, lecz nie chroni aplikacji. Żądania HTTP można zmodyfikować lub wysłać bez przeglądarki. Reguły biznesowe, uprawnienia i poprawność danych zawsze sprawdzam ponownie po stronie serwera.
Brakuje ochrony formularzy i autoryzacji
Formularze zmieniające stan aplikacji powinny korzystać z ochrony przed CSRF, a dostęp do akcji trzeba ograniczać zgodnie z rolą lub uprawnieniem użytkownika. Sam fakt, że przycisk jest ukryty w widoku, nie oznacza jeszcze, że endpoint jest zabezpieczony.
Przeczytaj również: Switch w JavaScript - składnia, przykłady i typowe błędy
Wszystko działa synchronicznie
Zapytania do bazy, wywołania usług i operacje plikowe powinny być asynchroniczne. W aplikacji z większym ruchem używanie async i await pozwala efektywniej wykorzystywać wątki serwera, choć samo dodanie tych słów nie naprawi wolnego zapytania SQL.
Jak zaprojektować MVC, które wytrzyma rozwój
Na początku wystarczy prosty podział na kontroler, model i widok. Gdy pojawiają się płatności, raporty, integracje i różne role użytkowników, dokładam kolejne granice odpowiedzialności zamiast tworzyć jeden gigantyczny projekt.
- Kontrolery powinny być krótkie i skupione na obsłudze HTTP.
- Serwisy aplikacyjne mogą koordynować przypadki użycia, takie jak utworzenie zamówienia.
- Modele domenowe powinny chronić reguły biznesowe, a nie być wyłącznie klasami z właściwościami.
- ViewModele powinny odpowiadać konkretnym ekranom i formularzom.
- Dependency injection powinna dostarczać zależności, ale nie zastępować sensownej architektury.
Przy większych systemach przydatne są także Areas, czyli logiczne obszary aplikacji, na przykład Administracja, Sprzedaż i Raporty. Nie traktuję ich jako obowiązkowego poziomu abstrakcji. Dodaję je dopiero wtedy, gdy zwykły podział katalogów przestaje być czytelny.
Testuję przede wszystkim reguły biznesowe i serwisy, a nie każdy szczegół renderowania HTML. Testy kontrolerów mają sens, gdy sprawdzają ważne zachowania, takie jak zwrócenie NotFound, wymuszenie autoryzacji albo poprawne przekazanie modelu do widoku.
Warto też od początku przewidzieć logowanie, obsługę wyjątków i konfigurację środowisk. Aplikacja działająca lokalnie może korzystać z prostego connection stringa, ale wdrożenie produkcyjne wymaga bezpiecznego przechowywania sekretów, monitoringu i kontroli migracji bazy danych.
Najkrótsza droga do dobrej decyzji architektonicznej
Jeżeli buduję rozbudowaną aplikację webową z kontrolerami, widokami, formularzami i wieloma procesami biznesowymi, ASP.NET Core MVC nadal jest bardzo rozsądnym wyborem. Daje dojrzały model pracy, przewidywalny przepływ żądania i dobre wsparcie dla testowania.
Do prostych stron rozważyłbym Razor Pages, do samego backendu Web API, a do silnie interaktywnego interfejsu Blazor. Najważniejsza decyzja nie polega jednak na wyborze modnej technologii, tylko na dopasowaniu narzędzia do sposobu, w jaki użytkownik korzysta z aplikacji.
Moje praktyczne kryterium jest proste: jeśli potrafię jasno wskazać, gdzie kończy się obsługa HTTP, gdzie zaczyna logika biznesowa i jakie dane może otrzymać widok, architektura prawdopodobnie zmierza w dobrą stronę. Gdy te granice znikają, nawet najlepszy framework nie uratuje projektu przed rosnącą złożonością.
