Pogled Detalji
Napravi pogled Views/Katalog/Detalji.cshtml koji prikazuje naziv, cenu, količinu i ukupnu vrednost (cena × količina) proizvoda i link nazad na katalog.
Umesto da JavaScript pravi stranicu iz JSON-a, server može sam da napravi HTML – kao Django šabloni. U ASP.NET Core-u za to služe MVC kontroleri i Razor pogledi (.cshtml): C# kod i HTML u istom fajlu. Pravimo katalog proizvoda i formu za dodavanje, u istom projektu.
// Program.cs – izmene: AddControllersWithViews umesto AddControllers + podrazumevana ruta
builder.Services.AddControllersWithViews(); // API kontroleri i dalje rade
var app = builder.Build();
app.UseHttpsRedirection();
app.UseStaticFiles();
app.MapControllers(); // API: /api/proizvodi
app.MapControllerRoute( // MVC: /Katalog, /Katalog/Detalji/2
name: "default",
pattern: "{controller=Katalog}/{action=Index}/{id?}");
app.Run();
Ruta {controller=Katalog}/{action=Index}/{id?} znači: /Katalog/Detalji/2 poziva akciju Detalji kontrolera KatalogController sa id = 2; prazna adresa ide na Katalog/Index. Ako u Program.cs postoji UseDefaultFiles iz lekcije 5, ukloni ga ili premesti klijent u wwwroot/admin.html, da bi početna strana bila katalog.
// Controllers/KatalogController.cs
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Prodavnica.Data;
using Prodavnica.Models;
namespace Prodavnica.Controllers;
public class KatalogController(ProdavnicaContext db) : Controller // Controller (sa pogledima), ne ControllerBase
{
// GET /Katalog
public async Task<IActionResult> Index()
{
var proizvodi = await db.Proizvodi.OrderBy(p => p.Naziv).ToListAsync();
return View(proizvodi); // Views/Katalog/Index.cshtml
}
// GET /Katalog/Detalji/2
public async Task<IActionResult> Detalji(int id)
{
var p = await db.Proizvodi.FindAsync(id);
if (p == null) return NotFound();
return View(p);
}
// GET /Katalog/Novi – prazna forma
public IActionResult Novi() => View(new Proizvod());
// POST /Katalog/Novi – obrada forme
[HttpPost]
[ValidateAntiForgeryToken] // zaštita od CSRF napada
public async Task<IActionResult> Novi(Proizvod p)
{
if (!ModelState.IsValid) return View(p); // ista forma sa porukama o greškama
db.Proizvodi.Add(p);
await db.SaveChangesAsync();
TempData["Poruka"] = $"Dodat je proizvod „{p.Naziv}“.";
return RedirectToAction(nameof(Index)); // POST → redirect → GET
}
}
Obrazac je isti kao u Django-u: GET prikazuje formu, POST je obrađuje, posle uspeha se preusmerava. TempData čuva poruku za jedan sledeći zahtev (kao messages u Django-u).
@* Views/_ViewImports.cshtml – važi za sve poglede *@
@using Prodavnica.Models
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@* Views/Shared/_Layout.cshtml – zajednički okvir (kao base.html u Django-u) *@
<!DOCTYPE html>
<html lang="sr">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>@ViewData["Title"] – Prodavnica</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css">
</head>
<body>
<nav class="navbar bg-dark" data-bs-theme="dark">
<div class="container">
<a class="navbar-brand" asp-controller="Katalog" asp-action="Index">Prodavnica</a>
<a class="btn btn-sm btn-success" asp-controller="Katalog" asp-action="Novi">+ Novi proizvod</a>
</div>
</nav>
<main class="container py-4">
@if (TempData["Poruka"] is string poruka)
{
<div class="alert alert-success">@poruka</div>
}
@RenderBody()
</main>
</body>
</html>
@* Views/_ViewStart.cshtml *@
@{ Layout = "_Layout"; }
@* Views/Katalog/Index.cshtml *@
@model List<Proizvod>
@{ ViewData["Title"] = "Katalog"; }
<h1 class="h3">Katalog (@Model.Count)</h1>
<div class="row g-3">
@foreach (var p in Model)
{
<div class="col-12 col-sm-6 col-lg-4">
<div class="card h-100">
<div class="card-body">
<h2 class="h5">@p.Naziv</h2>
<p class="mb-1">@p.Cena din</p>
@if (p.Kolicina == 0)
{
<span class="badge bg-danger">nema na stanju</span>
}
else
{
<span class="badge bg-success">na stanju: @p.Kolicina</span>
}
</div>
<div class="card-footer">
<a asp-action="Detalji" asp-route-id="@p.Id">Detalji</a>
</div>
</div>
</div>
}
</div>
| Razor | Značenje |
|---|---|
@model List<Proizvod> | tip podataka koje pogled dobija od kontrolera (View(proizvodi)) |
@p.Naziv | ispis vrednosti – automatski „escape“-ovan (zaštita od XSS) |
@foreach, @if | C# petlje i uslovi unutar HTML-a |
asp-action, asp-route-id | tag helperi: prave ispravnu adresu (/Katalog/Detalji/2) |
@RenderBody() | mesto gde se ubacuje pogled (kao {% block %} u Django-u) |
@* Views/Katalog/Novi.cshtml *@
@model Proizvod
@{ ViewData["Title"] = "Novi proizvod"; }
<h1 class="h3">Novi proizvod</h1>
<form asp-action="Novi" method="post" style="max-width: 420px">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="mb-3">
<label asp-for="Naziv" class="form-label"></label>
<input asp-for="Naziv" class="form-control">
<span asp-validation-for="Naziv" class="text-danger small"></span>
</div>
<div class="mb-3">
<label asp-for="Cena" class="form-label"></label>
<input asp-for="Cena" class="form-control">
<span asp-validation-for="Cena" class="text-danger small"></span>
</div>
<div class="mb-3">
<label asp-for="Kolicina" class="form-label">Količina</label>
<input asp-for="Kolicina" class="form-control">
<span asp-validation-for="Kolicina" class="text-danger small"></span>
</div>
<button class="btn btn-success">Sačuvaj</button>
</form>
asp-for="Cena" pravi polje sa pravim tipom (type="number"), imenom i vrednošću; asp-validation-for prikazuje poruku iz atributa [Range]. Forma sa tag helperom automatski dobija skriveni token protiv CSRF-a, koji proverava [ValidateAntiForgeryToken].
| Web API + JS klijent (lekcija 5) | MVC + Razor (ova lekcija) | |
|---|---|---|
| ko pravi HTML | pregledač (JavaScript) | server |
| dobro za | interaktivne strane, mobilne aplikacije, više klijenata | sajtove sa sadržajem, forme, admin strane |
| pretraživači (SEO) | teže | lakše – HTML je odmah gotov |
Mnoge aplikacije kombinuju oba – kao Prodavnica sada: katalog u MVC-u, a API za mobilnu aplikaciju ili JavaScript. Postoje i Razor Pages (pogled i kod po stranici, bez kontrolera) i Blazor (interaktivne strane u C#-u umesto JavaScript-a).
Za svaki zadatak postoji primer, uputstvo i rešenje.
Napravi pogled Views/Katalog/Detalji.cshtml koji prikazuje naziv, cenu, količinu i ukupnu vrednost (cena × količina) proizvoda i link nazad na katalog.
Views/ImeKontrolera/ImeAkcije.cshtml (bez reči „Controller“): Views/Katalog/Index.cshtml.@addTagHelper u _ViewImports.cshtml atributi asp-* se ne obrađuju i linkovi su prazni.Controller, a API kontroler ControllerBase.1. Šta radi return View(proizvodi) u MVC kontroleru?
2. Čemu služi atribut asp-for="Cena" u formi?
3. Na koju akciju vodi adresa /Katalog/Detalji/5 sa podrazumevanom rutom?