CTI – Dezvoltarea Aplicațiilor Web – Laborator 4
Entity Framework Core + Razor Pages – Proiect: News Portal
Obiective
În acest laborator vom construi baza aplicației care va fi folosită pe parcursul semestrului.
Aplicația va fi un portal simplu de știri (News Portal), implementat cu Razor Pages și Entity Framework Core.
Obiectivele laboratorului:
- Configurarea Entity Framework Core
- Definirea modelelor și relațiilor între entități
- Utilizarea
DbContextșiDbSet - Configurarea Dependency Injection
- Crearea bazei de date folosind migrations
- Afișarea datelor într-o pagină Razor Pages
- Introducerea conceptelor de bază pentru lucrul cu baza de date într-o aplicație web
Tehnologii utilizate
- ASP.NET Core
- Razor Pages
- Entity Framework Core
- SQL Server (LocalDB)
Crearea proiectului
Creați un proiect nou de tip ASP.NET Core Web App (Razor Pages).
Selectați .NET 8. Rulați aplicația pentru a verifica că funcționează.
Instalarea pachetelor NuGet
Instalați următoarele pachete:
Microsoft.EntityFrameworkCoreMicrosoft.EntityFrameworkCore.SqlServerMicrosoft.EntityFrameworkCore.Tools
Aceste pachete permit conectarea la baza de date, generarea migrărilor, și executarea comenzilor EF.
Structura proiectului
Adăugați următoarele foldere și fișiere:
Models/
Article.cs
Category.cs
Data/
AppDbContext.cs
Pages/
Articles/
Index.cshtml
Index.cshtml.cs
Modelele aplicației
Vom avea două tabele: Categories și Articles. O categorie poate avea mai multe articole.
Modelul Category
using System.ComponentModel.DataAnnotations;
public class Category
{
public int Id { get; set; }
[Required]
[MinLength(2)]
public string Name { get; set; } = string.Empty;
public List<Article> Articles { get; set; } = [];
}
Modelul Article
using System.ComponentModel.DataAnnotations;
public class Article
{
public int Id { get; set; }
[Required]
[MinLength(5)]
public string Title { get; set; } = string.Empty;
[Required]
[MinLength(20)]
public string Content { get; set; } = string.Empty;
public DateTime PublishedAt { get; set; } = DateTime.Now;
public int CategoryId { get; set; }
public Category Category { get; set; } = null!;
}
DbContext
DbContext reprezintă conexiunea dintre aplicație și baza de date. Este clasa principală prin care interacționăm cu baza de date în Entity Framework Core.
Adăugați fișierul Data/AppDbContext.cs:
using Microsoft.EntityFrameworkCore;
public class AppDbContext : DbContext
{
public AppDbContext(DbContextOptions<AppDbContext> options)
: base(options)
{
}
public DbSet<Article> Articles { get; set; }
public DbSet<Category> Categories { get; set; }
}
- Clasa
AppDbContextmoșteneșteDbContext— clasa de bază din EF Core care gestionează conexiunea și operațiile cu baza de date. - Constructorul primește un obiect
DbContextOptions<AppDbContext>care conține configurarea (tipul bazei de date, connection string-ul etc.) și îl pasează clasei de bază prin: base(options). - Fiecare proprietate de tip
DbSet<T>corespunde unui tabel din baza de date.DbSet<Article>→ tabelulArticles,DbSet<Category>→ tabelulCategories. Prin aceste proprietăți vom face query-uri și vom adăuga/modifica/șterge date.
Connection String
În fișierul appsettings.json adăugați un câmp pentru connection string:
"ConnectionStrings": {
"Lab04": ""
}
Deschideți SQL Server Object Explorer (View → SQL Server Object Explorer) și selectați instanța (localdb)\MSSQLLocalDB. Click dreapta → Properties și copiați valoarea Connection String.
Lipiți valoarea copiată în câmpul "Lab04" din appsettings.json. Apoi setați valoarea de la Initial Catalog la NewsDb.
Rezultatul final ar trebui să arate astfel:
"ConnectionStrings": {
"Lab04": "Server=(localdb)\\MSSQLLocalDB;Database=NewsDb;Trusted_Connection=True;"
}
Configurarea DbContext
În Program.cs adăugați, după builder.Services.AddRazorPages():
builder.Services.AddDbContext<AppDbContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("Lab04")));
Această linie înregistrează AppDbContext în containerul de Dependency Injection. Framework-ul va ști cum să creeze instanțe ale contextului oriunde este cerut.
AddDbContext<AppDbContext>(...)— înregistrează contextul ca serviciu disponibil prin DI.UseSqlServer(...)— specifică providerul de baze de date (SQL Server).GetConnectionString("Lab04")— citește connection string-ul cu cheia"Lab04"dinappsettings.json.
Migrations
Migrations sunt mecanismul prin care EF Core traduce modelele C# în structura bazei de date. La fiecare modificare a modelelor, creăm o nouă migrare care descrie schimbările.
În Package Manager Console (Tools → NuGet Package Manager → Package Manager Console):
Add-Migration InitialCreate
Update-Database
Add-Migration InitialCreate— generează un fișier cu instrucțiunile SQL necesare pentru a crea tabelele (pe bazaDbSet-urilor dinAppDbContext).Update-Database— aplică migrarea și creează efectiv baza de date și tabelele în SQL Server.
Introducerea datelor inițiale
Înainte de a continua, vom adăuga manual câteva înregistrări pentru a avea date de test.
Deschideți SQL Server Object Explorer → expandați baza de date NewsDb → Tables. Click dreapta pe tabelul Categories → View Data și adăugați câteva categorii:
| Id | Name |
|---|---|
| 1 | Tehnologie |
| 2 | Sport |
| 3 | Cultură |
Apoi faceți același lucru pentru tabelul Articles:
| Id | Title | Content | PublishedAt | CategoryId |
|---|---|---|---|---|
| 1 | Inteligența artificială în educație | Universitățile experimentează noi metode de predare. | 2026-03-10 | 1 |
| 2 | Startul sezonului de Formula 1 | Echipele prezintă noile monoposturi pentru sezon. | 2026-03-15 | 2 |
| 3 | Festival de film european | Proiecții speciale și regizori invitați. | 2026-03-09 | 3 |
| 4 | Noua generație de procesoare | Performanțe mai bune și consum redus de energie. | 2026-03-12 | 1 |
| 5 | Turneu internațional de tenis | Jucători din topul mondial participă la competiție. | 2026-03-11 | 2 |
Notă:
CategoryIdtrebuie să corespundă unuiIdexistent din tabelulCategories.
Dependency Injection
Deoarece am înregistrat AppDbContext în containerul de DI (în Program.cs), îl putem primi automat prin constructor în orice clasă gestionată de framework.
În clasa IndexModel (code-behind-ul paginii Razor) adăugăm:
private readonly AppDbContext _context;
public IndexModel(AppDbContext context)
{
_context = context;
}
Framework-ul creează automat instanța contextului și o pasează constructorului.
Razor Pages – Pagina Index
Pagina Index va afișa lista știrilor.
Index.cshtml.cs
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
public class IndexModel : PageModel
{
private readonly AppDbContext _context;
public IndexModel(AppDbContext context)
{
_context = context;
}
public List<Article> Articles { get; set; }
public void OnGet()
{
Articles = _context.Articles.ToList();
}
}
- Proprietatea
Articleseste publică — valorile setate aici vor fi accesibile din fișierul.cshtmlprinModel.Articles. - Metoda
OnGet()este apelată automat când pagina primește un request GET.
Trimiterea datelor suplimentare cu ViewData / ViewBag
Pe lângă proprietățile publice, putem trimite date către pagină folosind ViewData — un dicționar string → object:
public void OnGet()
{
ViewData["Title"] = "Lista de stiri";
ViewData["Count"] = _context.Articles.Count();
Articles = _context.Articles.ToList();
}
În .cshtml accesăm valorile astfel:
<h1>@ViewData["Title"]</h1>
<p>Total: @ViewData["Count"] articole</p>
ViewBag este un wrapper dinamic peste ViewData. Permite acces cu sintaxă de proprietate în loc de dicționar:
// in .cshtml.cs
ViewData["Title"] = "Stiri"; // ViewData - dictionar
// in .cshtml - ambele variante functioneaza:
@ViewData["Title"]
@ViewBag.Title
ViewBag și ViewData partajează aceleași date — sunt două moduri diferite de a accesa același dicționar.
Primirea parametrilor din URL (query string)
Metoda OnGet poate primi parametri direct din URL. De exemplu, pentru /Articles?category=sport:
public void OnGet(string? category)
{
// category va avea valoarea "sport" din query string
}
Framework-ul face automat legătura între numele parametrului din metodă și cel din URL (model binding).
Index.cshtml
@page
@model IndexModel
<h1>Stiri</h1>
<table>
<thead>
<tr>
<th>Title</th>
<th>Date</th>
</tr>
</thead>
<tbody>
@foreach (var article in Model.Articles)
{
<tr>
<td>@article.Title</td>
<td>@article.PublishedAt.ToShortDateString()</td>
</tr>
}
</tbody>
</table>
@page— directivă obligatorie care marchează fișierul ca Razor Page. Fără ea, fișierul nu va fi tratat ca pagină și nu va răspunde la request-uri.@model IndexModel— specifică tipul modelului asociat paginii. PrinModel(cu M mare) accesăm instanța claseiIndexModelși proprietățile ei publice (ex:Model.Articles).@foreach,@article.Title— sintaxa Razor: prefixul@permite scrierea de expresii C# direct în HTML.
Rutele în Razor Pages
În laboratorul anterior am definit rute explicit pe controllere cu [Route()] și [HttpGet()]. În Razor Pages, rutele sunt generate automat pe baza structurii de foldere din Pages/:
| Fișier | Ruta generată |
|---|---|
Pages/Index.cshtml |
/ sau /Index |
Pages/Articles/Index.cshtml |
/Articles sau /Articles/Index |
Pages/Articles/Details.cshtml |
/Articles/Details |
Nu trebuie să configurăm nimic — framework-ul mapează automat calea fișierului la URL.
Pentru a adăuga parametri în rută, folosim directiva @page cu un template:
@page "{id:int}"
Aceasta face ca pagina să răspundă la /Articles/Details/5, unde 5 este valoarea parametrului id. Parametrul va fi disponibil în metoda OnGet(int id).
Fluxul request-ului
Browser → Razor Page → DbContext → SQL Server → DbContext → Razor Page → HTML
Referință — Metode EF Core utilizate
| Metodă | Rol |
|---|---|
DbContext |
Conexiunea la baza de date |
DbSet<T> |
Reprezintă un tabel |
Include() |
Încarcă relații (eager loading) |
ToList() |
Execută query-ul și returnează o listă |
SaveChanges() |
Salvează modificările |
Referință — Metode Razor Pages utilizate
| Metodă | Rol |
|---|---|
PageModel |
Code-behind pentru pagină |
OnGet() |
Handler pentru request GET |
Page() |
Returnează pagina curentă |
RedirectToPage() |
Redirect către altă pagină |
ModelState.IsValid |
Verifică validarea modelului |
Documentație EF Core: https://learn.microsoft.com/en-us/ef/core/
Documentație Razor Pages: https://learn.microsoft.com/en-us/aspnet/core/razor-pages/
Exerciții
Pornind de la pagina Index prezentată mai sus, extindeți aplicația pas cu pas.
-
(1p) Pagina Index afișează deja titlul și data, dar nu și categoria fiecărui articol. Adăugați o coloană nouă în tabel care să afișeze numele categoriei. Asigurați-vă că folosiți
Include()pentru a încărca relația. -
(1p) Articolele ar trebui afișate cu cele mai recente primele. Modificați query-ul din
OnGet()astfel încât lista să fie ordonată descrescător dupăPublishedAt. -
(2p) Creați o pagină Details (
Pages/Articles/Details.cshtml) care afișează un singur articol cu toate informațiile (titlu, conținut, categorie, dată).- Folosiți
@page "{id:int}"pentru a primi id-ul din URL (ex:/Articles/Details/3) - În
OnGet(int id), căutați articolul cuFirstOrDefault()sauFind() - Dacă articolul nu există, returnați
NotFound()
- Folosiți
-
(1p) Reveniți la pagina Index și adăugați un link către Details pe fiecare rând din tabel. Folosiți un tag
<a>cu atributulhrefcare să includă id-ul articolului. -
(2p) Creați o pagină Create (
Pages/Articles/Create.cshtml) pentru adăugarea unui articol nou.- Pagina trebuie să conțină un formular cu câmpuri pentru titlu, conținut și categorie
- În
OnPost(), verificațiModelState.IsValidînainte de salvare - După salvarea cu
SaveChanges(), faceți redirect către pagina Index cuRedirectToPage("Index")