Serviciul de asistență este Offline
Cristian Opariuc-Dan
30 articole
Numele meu este

​Aplicații web cu ASP.NET MVC (III). O mică librărie online. Partea a doua

ASP.NET

Acțiunea și vizualizarea „Create” 

După ce în articolul anterior am analizat acțiunea și vizualizarea „Index”, acum vom studia modul în care se poate introduce o categorie nouă, analizând acțiunea „Create" a controllerului:
public ActionResult Create()
{
    return View();
} 

​Metoda nu face decât să randeze vizualizarea „Views\Categories\Create.cshtml", ca și legătura „Create New" din pagina principală a categoriilor. Vizualizarea începe prin declararea modelului, de data aceasta nu o colecție ci un singur obiect de tip „Category" și prin stabilirea titlului paginii. 

Urmează apoi un bloc de cod mai complex ce debutează cu „using (Html.BeginForm())", metoda „BeginForm" a obiectului helper „Html" permițând generarea unui formular HTML de introducere a datelor configurat între acolade.

Generarea formularului debutează cu apelarea metodei „AntiForgeryToken", aceasta generând un token antifraudă care ne asigură că trimiterea datelor se face utilizându-se același formular și nu printr-o metodă de injectare. Pentru a fi activată, este suficient să includem atributul „[ValidateAntiForgeryToken]" variantei POST a metodei „Create" pe care o vom discuta imediat.

@model _02___Librarie.Models.Category

@{
    ViewBag.Title = "Create";
}

<h2>Create</h2>

@using (Html.BeginForm()) 
{
    @Html.AntiForgeryToken()
    
    <div class="form-horizontal">
        <h4>Category</h4>
        <hr />
        @Html.ValidationSummary(true, "", new { @class = "text-danger" })
        <div class="form-group">
            @Html.LabelFor(model => model.Name, htmlAttributes: new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.Name, new { htmlAttributes = new { @class = "form-control" } })
                @Html.ValidationMessageFor(model => model.Name, "", new { @class = "text-danger" })
            </div>
        </div>

        <div class="form-group">
            @Html.LabelFor(model => model.Description, htmlAttributes: new { @class = "con-trol-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.Description, new { htmlAttributes = new { @class = "form-control" } })
                @Html.ValidationMessageFor(model => model.Description, "", new { @class = "text-danger" })
            </div>
        </div>

        <div class="form-group">
            <div class="col-md-offset-2 col-md-10">
                <input type="submit" value="Create" class="btn btn-default" />
            </div>
        </div>
    </div>
} 

​Metoda „ValidationSummary" a obiectului helper „Html" va afișa o eroare și detaliile acesteia dacă formularul nu este valid. Primul parametru indică să se excludă de la raportare erorile interne ale codului, al doilea parametru poate include un mesaj personalizat iar cu al treilea parametru se configurează aspectul atenționării pre formatat în clasa CSS Bootstrap.

Urmează apoi un număr de 3 grupuri de elemente. Primele două permit introducerea datelor, al treilea formatează butonul de trimitere a datelor.

Folosind metoda „LabelFor", se va genera o etichetă HTML plecând de la un câmp al entității (Numele sau Descrierea) și se va formata (cu ajutorul atributului „htmlAttributes") apelând la clasele CSS Bootstrap.

Cu ajutorul metodei „EditorFor" se generează elementul HTML potrivit tipului de date al câmpului entității. Deoarece în situația de față avem câmpuri de tip șir de caractere, elementul va fi o casetă de introducere a textului.

În aceste elemente de introducere a datelor am putea scrie orice. De aceea, metoda „ValidationMessageFor" va afișa un mesaj în dreptul elementelor de editare dacă s-au încălcat regulile privind validarea lor ori s-au introdus valori incorecte, despre aceste reguli urmând să discutăm mai târziu. Deocamdată, pentru ca aceste validări să se poată efectua, aplicația va avea nevoie de o serie de fișiere JavaScript, fișiere referite la sfârșitul vizualizării

@section Scripts {
    @Scripts.Render("~/bundles/jqueryval")
}

<div>
    @Html.ActionLink("Back to List", "Index")
</div> 

​Ultimul grup de elemente conține un buton HTML de tip „submit", numit „Create", la apăsarea căruia se va apela varianta POST a acțiunii „Create" din controller, salvându-se efectiv datele. În fine, vizualizarea mai conține o legătură care direcționează la vizualizarea „Index".

Ei bine, pentru a crea categoria va trebui să introducem informațiile necesare, apoi apă-săm butonul „Create". Aplicația se va întoarce la vizualizarea „Index" a categoriilor și vom putea repeta operațiunea pentru a mai adăuga 2-3 categorii.

​Întrebarea este ce s-a întâmplat la apăsarea butonului „Create"? Fiind un buton de tip „submit" creat de vizualizarea „Create.cshtml", la apăsarea sa va fi apelată varianta HTTP POST a acțiunii „Create" din controller.

 [HttpPost]
 [ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "ID,Name,Description")] Category category)
{
    if (ModelState.IsValid)
    {
        db.Categories.Add(category);
        db.SaveChanges();
        return RedirectToAction("Index");
    }

    return View(category);
} 

Diferența dintre varianta care returnează vizualizarea și varianta care va fi executată când vizualizarea trimite date este dată de adnotarea „[HttpPost]" atașată metodei. Alături de aceasta se remarcă și adnotarea „[ValidateAntiForgeryToken]", ce se asigură că tokenul cu care a fost generată vizualizarea este identic cu tokenul vizualizării ce trimite datele, controlând astfel unele tentative de fraudă.

Parametrul „Bind" furnizat ca argument al metodei ([Bind(Include = "ID,Name,Description")]) reprezintă un alt element de securitate și comunică metodei să includă doar proprietățile ID, Name și Description atunci când se creează o nouă categorie eliminând atacurile de tip „overposting". Se creează, practic, o listă de proprietăți, care abia apoi se trimite spre actualizare, fapt care nu mai permite editarea unei proprietăți după ce a fost trimisă. Un atac de tip „overposting" poate să apară când, spre exemplu, un client vrea să comande o carte foarte scumpă. Pentru a o lua mai ieftin, acesta poate accesa codul și modifica prețul, apoi lansa comanda. Sesizând modificarea listei inițiale, aplicația va respinge o astfel de comandă.

Desigur, vizualizarea a trimis către această metodă un obiect de tip „Category" pe care acțiunea îl primește ca parametru după ce verificările de securitate au dat „unda verde”. Dacă au fost incluse clauze de verificare a datelor trimise, iar acestea sunt corecte, proprietatea „IsValid" a obiectului „ModelState" returnează adevărat, contextul de date adaugă obiectul primit de metodă în entitatea categoriilor (db.Categories.Add(category)) după care salvează entitatea în baza de date (db.SaveChanges()) și utilizează metoda „RedirectToAction" pentru a apela acțiunea „Index" din controller.

În cazul în care modelul nu este valid, se randează din nou vizualizarea „Category.cshtml" pre configurată cu datele introduse apărând, în plus, mesajele de eroare.

Acțiunea și vizualizarea „Details”

​Acțiunea „Details" este apelată atunci când se efectuează un click pe legătura „Details" din dreptul unei categorii sau când se apelează o adresă de genul: http://localhost:51262/Categories/Details/2 și are următorul cod:

public ActionResult Details(int? id)
{
    if (id == null)
    {
        return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
    }
    Category category = db.Categories.Find(id);
    if (category == null)
    {
        return HttpNotFound();
    }
    return View(category);
} 

Remarcăm, în primul rând, că acțiunea primește parametrul „id", adică cheia primară a categoriei, însă poate fi apelată și fără acest parametru. În cazul în care parametrul „id" lipsește sau nu este un număr întreg, atunci acțiunea returnează o pagină de eroare de tip „Solicitare eronată" folosind metoda „HttpStatusCodeResult".

Dacă parametrul este un număr întreg, atunci acțiunea creează un nou obiect de tip „Category" ce va conține rezultatul căutării pe care o efectuează contextul de date pe baza cheii primare furnizate. Desigur, dacă aceea categorie nu este găsită în baza de date, atunci obiectul va fi nul, fapt care conduce la generarea unei pagini de eroare de tip „Nu a fost găsit" prin apelarea metodei „HttpNotFound". Dacă a fost găsită categoria, atunci se returnează vizualizarea „Views\Categories\Details.cshtml"​, ce primește ca parametru obiectul de tip „Category" identificat.

@model _02___Librarie.Models.Category

@{
    ViewBag.Title = "Details";
}
<h2>Details</h2>

<div>
    <h4>Category</h4>
    <hr />
    <dl class="dl-horizontal">
        <dt>
            @Html.DisplayNameFor(model => model.Name)
        </dt>
        <dd>
            @Html.DisplayFor(model => model.Name)
        </dd>
        <dt>
            @Html.DisplayNameFor(model => model.Description)
        </dt>
        <dd>
            @Html.DisplayFor(model => model.Description)
        </dd>
    </dl>
</div>

<p>
    @Html.ActionLink("Edit", "Edit", new { id = Model.ID }) |
    @Html.ActionLink("Back to List", "Index")
</p> 

​​Codul acestei vizualizări este destul de simplu și începe prin declararea modelului și a titlului, continuând cu extragerea și afișarea etichetelor și a conținutului categoriei. 

La final sunt create cele două legături, una care permite modificarea categoriei, apelând vizualizarea „Views\Categories\Edit.cshtml" cu cheia primară a categoriei, iar cea de-a doua randând vizualizarea „Views\Categories\Index.cshtml" și revenind la lista principală.

Acțiunile și vizualizarea „Edit”

​Editarea unei categorii se implementează similar cu crearea. Vor exista două acțiuni în controller, una HTTP GET care va crea formularul și cealaltă HTTP POST care va salva datele transmise.

Varianta HTTP GET a acțiunii necesită cheia primară a categoriei. Dacă aceasta nu există sau nu este un număr, atunci se returnează o eroare de tip „Solicitare eronată". Dacă este un număr, atunci contextul de date caută categoria și creează un nou obiect de tip „Category". Acesta devine „null" în cazul în care categoria cu „id"-ul furnizat nu există și se returnează o eroare de tip „Nu a fost găsită. Dacă totul este în regulă, va fi furnizată vizualizarea „Views\Categories\Edit.cshtml", cu obiectul de tip „Category" drept argument.

public ActionResult Edit(int? id)
{
    if (id == null)
    {
        return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
    }
    Category category = db.Categories.Find(id);
    if (category == null)
    {
        return HttpNotFound();
    }
    return View(category);
} 

​Ca și până acum, vizualizarea debutează cu precizarea modelului și stabilirea titlului. După aceea se va folosi metoda „BeginForm" de generare a formularului web pentru colectarea datelor. Codul este identic cu cel discutat la vizualizarea „Create.cshtml". Apare în plus metoda „HiddenFor" a obiectului helper „Html" prin care se precizează ascunderea câmpului „ID" a modelului, deoarece cheia primară nu trebuie modificată. Urmează cele 3 grupuri de elemente web, pentru editarea numelui categoriei și a descrierii și pentru trimiterea informațiilor:

@using (Html.BeginForm())
{
    @Html.AntiForgeryToken()
    
    <div class="form-horizontal">
        <h4>Category</h4>
        <hr />
        @Html.ValidationSummary(true, "", new { @class = "text-danger" })
        @Html.HiddenFor(model => model.ID)
        <div class="form-group">
            @Html.LabelFor(model => model.Name, htmlAttributes: new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.Name, new { htmlAttributes = new { @class = "form-control" } })
                @Html.ValidationMessageFor(model => model.Name, "", new { @class = "text-danger" })
            </div>
        </div>

        <div class="form-group">
            @Html.LabelFor(model => model.Description, htmlAttributes: new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.Description, new { htmlAttributes = new { @class = "form-control" } })
                @Html.ValidationMessageFor(model => model.Description, "", new { @class = "text-danger" })
            </div>
        </div>

        <div class="form-group">
            <div class="col-md-offset-2 col-md-10">
                <input type="submit" value="Save" class="btn btn-default" />
            </div>
        </div>
    </div>
} 

Primele două grupuri de elemente vor conține informațiile despre categoria trimisă, deoarece, spre deosebire de vizualizarea creării unei categorii noi, aici s-a primit de la controller un obiect de tip „Category", informațiile despre acesta fiind afișate cu ajutorul metodei „EditorFor".

Desigur, butonul „Save" este tot de tip „submit", prin urmare apăsarea sa va determina re-întoarcerea la controller și executarea acțiunii „Edit" în varianta HTTP POST.

 [HttpPost]
 [ValidateAntiForgeryToken]
public ActionResult Edit([Bind(Include = "ID,Name,Description")] Category category)
{
    if (ModelState.IsValid)
    {
        db.Entry(category).State = EntityState.Modified;
        db.SaveChanges();
        return RedirectToAction("Index");
    }
    return View(category);
} 
 

Nu sunt prea multe comentarii de făcut la nivelul acestei metode. Și aici metoda este ornată cu cele două adnotări și securizată împotriva atacurilor de tip „overposting", acceptând ca argument un obiect de tip „Category", adică o entitate ce corespunde tabelului categoriilor din baza de date.

Singurul element de noutate este prima linie executată dacă modelul este valid și care stabilește ca modificată starea entității (db.Entry(category).State = EntityState.Modified). Deoarece starea entității „Category" a fost stabilită ca fiind modificată, la apelarea metodei „SaveChanges" a contextului de date aceste modificări vor fi scrise în tabelele bazei de date.

Acțiunile și vizualizarea „Delete”

​ Acțiunea HTTP GET „Delete" din controller are același cod ca și acțiunea similară „Edit" și randează vizualizarea „Delete". Ea este declanșată atunci când se apasă pe legătura „Delete" corespunzătoare unei categorii și va determina ștergerea categoriei respective din baza de date:

public ActionResult Delete(int? id)
{
    if (id == null)
    {
        return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
    }
    Category category = db.Categories.Find(id);
    if (category == null)
    {
        return HttpNotFound();
    }
    return View(category);
} 

​Vizualizarea nu este altceva decât o confirmare a ștergerii deoarece există posibilitatea să se apese din greșeală pe această legătură și este conținută în fișierul „Views\Categories\Delete.cshtml". După definirea modelului furnizat de controller și afișarea titlului și a mesajului de confirmare, vizualizarea arată denumirile și conținutul câmpurilor categoriei selectate, după care generează un formular HTML:

@model _02___Librarie.Models.Category

@{
    ViewBag.Title = "Delete";
}

<h2>Delete</h2>

<h3>Are you sure you want to delete this?</h3>
<div>
    <h4>Category</h4>
    <hr />
    <dl class="dl-horizontal">
        <dt>
            @Html.DisplayNameFor(model => model.Name)
        </dt>
        <dd>
            @Html.DisplayFor(model => model.Name)
        </dd>
        <dt>
            @Html.DisplayNameFor(model => model.Description)
        </dt>
        <dd>
            @Html.DisplayFor(model => model.Description)
        </dd>
    </dl>
    @using (Html.BeginForm()) {
        @Html.AntiForgeryToken()
        <div class="form-actions no-color">
            <input type="submit" value="Delete" class="btn btn-default" /> |
            @Html.ActionLink("Back to List", "Index")
        </div>
    }
</div> 

​Acest formular nu conține decât butonul de tip „submit" etichetat „Delete" și o legătură care direcționează la acțiunea „Index" din controller. Dacă se efectuează click pe „Delete", confirmându-se ștergerea, atunci se va executa varianta HTTP POST a acțiunii „Delete" din controller:

 [HttpPost, ActionName("Delete")]
 [ValidateAntiForgeryToken]
public ActionResult DeleteConfirmed(int id)
{
    Category category = db.Categories.Find(id);
    db.Categories.Remove(category);
    db.SaveChanges();
    return RedirectToAction("Index");
} 

​Metoda acestei variante poartă un alt nume, „DeleteConfirmed", și nu același nume cu varianta HTTP GET („Delete"), însă acțiunea va fi executată deoarece a fost precizată explicit ca fiind varianta HTTP POST în adnotare ([HttpPost, ActionName("Delete")]). Ea primește de la vizualizare ca parametru cheia primară a categoriei, o caută, generează un obiect de tip „Category" pe care apoi îl elimină din entitate și salvează modificările în baza de date.

Am observat un lucru. Pentru a putea afișa lista categoriilor, adică pentru a putea executa acțiunea „Index" din controllerul „Categories", am furnizat direct adresa URL (http://localhost:51262/Categories) deoarece în bara de meniuri nu avem la dispoziție o legătură, însă o putem introduce foarte ușor editând șablonul părinte (fișierul „Views\Shared\_Layout.cshtml"):

<div class="container">
    <div class="navbar-header">
        <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
            <span class="icon-bar"></span>
            <span class="icon-bar"></span>
            <span class="icon-bar"></span>
            <span class="icon-bar"></span>
        </button>
        @Html.ActionLink("Application name", "Index", "Home", new { area = "" }, new { @class = "navbar-brand" })
    </div>
    <div class="navbar-collapse collapse">
        <ul class="nav navbar-nav">
            <li>@Html.ActionLink("Home", "Index", "Home")</li>
            <li>@Html.ActionLink("Categories", "Index", "Categories")</li>
            <li>@Html.ActionLink("About", "About", "Home")</li>
            <li>@Html.ActionLink("Contact", "Contact", "Home")</li>
        </ul>
        @Html.Partial("_LoginPartial")
    </div>
</div> 

Am creat al doilea element de meniu din clasa CSS Bootstrap și am utilizat metoda „ActionLink" pentru a direcționa legătura la acțiunea „Index" a controllerului „Categories" (@Html.ActionLink("Categories", "Index", "Categories"),  introducând, imediat după „Home", noul element de meniu.

Analiza funcționării produselor, dar și alte lucruri interesante vor fi dezvăluite în articolul de data viitoare. Dacă vă place, îl puteți aprecia, îl puteți comenta sau distribui să ajungă și la alte persoane interesate.

Location (Map)

Teoria Yin Yang și medicina tradițională chineză
​Aplicații web cu ASP.NET MVC (II). O mică librări...

Articole similare

 

Comentarii

Nu există niciun comentariu. De ce nu scrieți dumneavoastră unul?
Aveși deja un cont? Autentificați-vă aici
Guest
Marți, 19 Februarie 2019
Dacă doriți să vă creați un cont, introduceți numele de utilizator, parola și numele dumneavoastră.

Imagine Captcha

Ultimele articole

Ultimele discuții

Lamurire
Cum se calculeaza consistenta interna (Alfa lui Cronbach) a unui const...
2 accesări
0 voturi
0 răspunsuri
Postat Marți, 19 Februarie 2019
  • New
  • Intrebare
    Cat de "corect" / in ce masura este in regula sa utilizam ac...
    5 accesări
    0 voturi
    1 răspunsuri
    Postat Marți, 19 Februarie 2019
  • Rezolvat
  • New
  • Comentarii

    Nu există comentarii