Prima versiune a aplicației este destinată pe post de aplicație pilot cu cerințele de bază [307479]

Introducere

1.1 Descriere

În această lucrare este prezentată o aplicație de gestionare a [anonimizat]. Aplicația poate fi folosită de către pasionații de gătit care vor să afle rețete noi și care doresc să distribuie rețetele preferate. [anonimizat], caz în care rețeta trebuie să fie acceptată de către administratorul aplicației. Fiecare utilizator își poate edita propriile rețete și își poate vedea toate rețetele adăugate.

[anonimizat]-ul ASP.[anonimizat] a [anonimizat].

1.2 Motivație

Implementarea acestei aplicații a oferit oportunitatea de a studia o varietate de tehnologii noi apărute de la Microsoft cum ar fi: ASP.[anonimizat], Azure Storage pentru a [anonimizat].

Folosirea acestor tehnologii a permis o dezvoltare rapidă a aplicației pentru că permite să concentrarea doar pe partea de logică a aplicației. Partea de logică ce ține de infrastructura aplicației și marea majoritate a aplicațiilor, sunt cunoscute sub denumirea de „Crosscutting Concerns”[1]. [anonimizat], management-ul erorilor generate de sistem pot fi ușor gestionate cu ASP.Net Core pentru că pune la dispoziție utilități care să permită integrarea externă a unor librării dezvoltate de alte persoane sau servicii interne care se ocupă de lucrurile de bază care sunt în sistem și care nu necesită personalizarea logicii.

1.3 Sumar

În această lucrare se descrie o [anonimizat], o aplicație de gestiune a [anonimizat], fiecare putând adăuga rețetele proprii.

Prima versiune a aplicației este destinată pe post de aplicație pilot cu cerințele de bază

implementate care sunt caracteristice scopului aplicației.

În al doilea capitol, „Tehnologiile folosite”, se prezintă tehnologiile folosite în implementarea aplicației și metode de programare abordate.

În capitolul al treilea, „Implementarea aplicației”, se va prezenta arhitectura aplicației și

implementarea unui schelet de bază a unei aplicații care poate fi personalizată pentru utilizator și administrator și modul de funcționare a acestei aplicații.

În capitolul al patrulea, „Descrierea aplicației”, este prezentat modul în care pot fi accesate toate funcționalitățile aplicației de pe toate paginile.

În capitolul final al lucrării voi prezenta concluziile la care am ajuns după implementarea aplicației și ce funcționalități viitoare pot fi adăugate pentru extinderea aplicației.

Tehnologiile folosite

2.1 HTML

Tehnologia fundamentală folosită pentru structurarea unei pagini web este HTML[2][3]. [anonimizat].

HTML este un limbaj conceput pentru a [anonimizat]. [anonimizat], conectat fiind la Internet.

HTML[4] este prescurtarea de la HyperText Markup Language care înseamnă:

HyperText este metoda prin care se poate deplasa în web – făcând click pe un text special numit hyperlink care are funcționalitatea de a te redirecționa la o pagina următoare.

Markup-ul (Marcarea) este ceea ce face ca etichetele HTML sa fie în interiorul lor. Aceasta îl marchează ca un anumit tip de text (de exemplu, textul italic).

Language (limba), deoarece are cuvinte-cheie și sintaxă ca orice altă limbă.

Construcțiile HTML (imaginile, obiectele sau formularele interactive) pot fi încorporate în pagina redată. Aceasta oferă un mijloc de creare a documentelor structurate prin desemnarea structurată a semanticii pentru text, paragrafe, titluri, liste, citate, imagini și alte elemente.

Elementele HTML sunt formate din etichete. Aceste etichete sunt scrise folosind paranteze unghiulare (“<…>”). Etichetele precum <img /> sau <input /> introduc un conținut direct în pagină, pe când etichetele ca <p>…</p> sau <div>…</div> înconjoară și oferă informații, dar pot și să conțină alte etichete în interiorul lor.

În aplicația curentă, HTML a fost folosit pentru structurarea paginilor.

2.2 CSS

Cascading Style Sheet[5] este un standard folosit pentru setarea stilului vizual al paginilor web și al interfețelor utilizator scrise în HTML. Tehnologia CSS este una de temelie folosită de majoritatea site-urilor web pentru crearea de pagini web cu aspect vizual captivant, interfețe de utilizator pentru aplicații web și interfețe de utilizator pentru diverse aplicații mobile.

Defapt, CSS-ul este un limbaj de stilizare al elementelor și tagurilor HTML. Acest lucru semnifică faptul că începând de la culoarea literelor și a culorii de fundal și până la poziționarea elementelor pe o pagină web, totul este stilizat prin CSS.

Stilurile folosite pentru o pagină web pot fi definite în partea de Head (între tagurile <head> și </head>) a documentului html (vezi codul atașat mai jos). De asemenea, stilurile paginii pot fi definite și într-un fișier extern (fișier de tip style) care se referențiază tot în partea de Head a paginii sau se poate aplica un stil diferit în partea Body a fișierului html, la fiecare tag în parte.

Un exemplu de definire a stilurilor în partea Head a fișierului html ar fi următorul:

<head>

<title>Stil definit în head</title>

<style type=”text/css”>

p {

font-family:Arial;

font-size:14px;

color:#003300;

}

</style>

</head>

<body>

Paragraf la care se va aplica stilul definit mai sus!

</body>

Un exemplu de definire a stilurilor în partea Body a fișierului html ar fi următorul:

<body>

<p style=”font-family:Arial; color:#478600; font-size:14px;”>

Paragraf care a fost stilizat în interiorul tagului p! </p>

</body>

Definirea stilurilor într-un fișier extern cu extensia css (de exemplu:”stiluri.css”) o referențiem în codul html astfel:

<head>

<link rel=”stylesheet” href=”stiluri.css”

</head>

CSS-ul[6] dă o anumită libertate asupra suprascrierii unui stil deja definit, deoarece lucrează pe o anumită ierarhie, astfel: prima dată va fi luat în considerare stilul definit în fișierul extern, după care urmează stilul definit în partea head, iar ultima dată va lua în considerare css-ul inline(stilul definit în interiorul unui tag html cu ajutorul atributului style- astfel se poate suprascrie orice stil definit anterior).

Când un browser afișează un document, trebuie să combine conținutul documentului (codul html) cu informațiile legate de stil (CSS-ul). Această procesare are loc în două etape:

Browserul convertește codul HTML și CSS în DOM (Document Object Model). DOM reprezintă documentul din memoria calculatorului. De asemenea, combină conținutul documentului cu stilul acestuia.

Afișarea conținutul DOM de către browser (afișarea paginii web propriu-zise).

CSS-ul a fost folosit pentru stilizarea paginilor, mai exact, pentru stilizarea elementelor definite cu ajutorul HTML-ului.

2.3 JavaScript

JavaScript[7] (adesea prescurtat ca JS) este un limbaj ușor, interpretat, orientat pe obiect. Este cel mai bine cunoscut sub numele de limbaj de scripting pentru paginile web, dar este folosit și în multe aplicații non-browser. Este un limbaj de scripting bazat pe prototip, multiparadigmă, care este dinamic și susține astfel stiluri de programare orientate pe obiecte, imperative și funcționale.

JavaScript rulează pe partea de client a paginii web, care poate fi folosit pentru design sau pentru a programa modul în care paginile web se comportă la apariția unui eveniment. JavaScript este un limbaj de scripting ușor de învățat și puternic, utilizat pentru a controla comportamentul paginii web.

Avantajele[8] utilizarii JavaScriptului:

Mai puțină interacțiune cu serverul: se poate valida intrarea utilizatorului înainte de a trimite pagina pe server. Acest lucru economisește traficul serverului, ceea ce înseamnă o încărcare mai mică pe server.

Interactivitate sporită: se pot crea interfețe diverse care reacționează atunci când utilizatorul se deplasează cu mouse-ul peste ele sau când utilizează tastatura pentru a le activa.

Interfețe mai bogate: se poate utiliza JavaScript pentru includerea elementelor precum tragere și plasare (în engleză, aceasta acțiune poartă denumirea de drag-and-drop) sau sliders pentru oferirea unei interfețe bogate vizitatorului site-ului.

Feedback imediat către vizitatori: nu este necesară așteptarea unei reîncărcări complete a paginii pentru a vedea dacă s-a uitat să se introducă ceva sau nu.

2.3.1 jQuery

jQuery[9][10] este o librărie a JavaScriptului. Aceasta are scopul de a ușura semnificativ navigarea și manipularea documentelor HTML, crearea și gestionarea evenimentelor și a animațiilor, precum și dezvoltarea aplicațiilor. Acest lucru se realizează prin simpla utilizare a unui API care poate funcționa pe o gamă largă de browsere.

jQuery se ocupă de multe sarcini comune care, pentru a le realiza, necesită multe linii de cod JavaScript, iar el le împachetează în metode mai scurte care se pot apela cu ajutorul unei singure linii de cod.

În aplicație a fost utilizat pentru a defini diverse interacțiuni dinamice, folosit fiin de framework-ul Bootstrap(un exemplu concret de utilizare este la adăugarea de ingrediente pe pagina de adăugare/editare a unei rețete, această acțiune realizându-se într-un mod dinamic).

2.4 Bootstrap

Bootstrap[11] este cel mai popular framework utilizat pentru crearea de pagini și site-uri responsive.

Așadar, Bootstrap[13] este un framework(schelet) JavaScript open-source dezvoltat de echipa de la Twitter.

Este o combinație de cod HTML, CSS și JavaScript proiectat pentru a ajuta la crearea de componente ale interfeței utilizatorilor. De asemenea, Bootstrap a fost proiectat să suporte atât HTML5, cât și CSS3.

Bootstrap este o colecție gratuită de instrumente folosită pentru crearea aplicațiilor sau site-urilor web. Aceasta conține șabloane de design HTML și CSS pentru navigație, butoane, formulare, alerte, simboluri, etichete, topografie și alte componente de interfață, precum și extensii JavaScript opționale.

Câteva dintre motivele pentru care programatorii aleg Bootstrap sunt următoarele:

Este ușor de utilizat.

Este un sistem de griduri fluide împărțit în 12 coloane, ușor de lucrat cu ele (sistemul flexibil de griduri se bazează pe calculul proporțiilor și se asigură că toate elementele din layout sunt redimensionate unul față de celălalt, așadar nu se mai măsoară dimensiunile în pixeli, ci în unități relative și procente).

O singură versiune de site funcționează bine pe orice dispozitiv (layout-ul se adaptează în funcție de dispozitiv și browser, eliminându-se astfel deplasarea orizontală).

Conține elementele de bază pentru majoritatea elementelor HTML (icoane, imagini, butoane, tabele, formulare, cod).

Conține o listă extinsă de componente

Câteva dintre componentele prestilizate oferite de Bootstrap ar fi: meniurile derulant (drop-down), grupurile de butoane, bara de navigare, bara de progres, diverse etichete, diverse alerte și multe altele.

2.5 SQL

Potrivit ANSI[14], SQL este un limbaj de programare standardizat, utilizat pentru

gestionarea bazelor de date relaționare și efectuarea de diverse operații asupra datelor din acestea (de exemplu, cum ar fi actualizarea sau recuperarea unor date).

SQL[15] oferă două avantaje principale; în primul rând, a introdus conceptul de accesare a mai multor înregistrări cu o singură comandă; în al doilea rând, a eliminat nevoia de a specifica cum se ajunge la o înregistrare, de exemplu cu sau fără indice.

Datorită faptului că există o standardizare a limbajul SQL, multe SGBD recunosc principalele instrucțiuni ale acestuia. Câteva dintre aceste sisteme comune de gestionare a bazelor de date care utilizează SQL sunt: Oracle, Sybase, Microsoft SQL Server, Access, Ingress și etc.

Deși majoritatea sistemelor de baze de date utilizează SQL, mare parte din ele au și propriile lor extensii adiționale. De exemplu, în standard sunt specificate 6 tipuri diferite de date pentru o bază de date SQL. În multe implementări, această listă este completată cu o diversitate de extensii. Fiecare implementare poartă denumirea de dialect, iar spre exemplu, dialectul ACCSES conține unele particularități, conceput fiind mai mult pentru crearea interogărilor de selecție.

Există 3 metode de bază privind implementarea limbajului SQL:

apelare directă (numită și Direct Invocation, din limba engleză): constă în introducerea instrucțiunilor direct de la prompter;

modular (numită și Modul Language): folosește proceduri apelate de programele aplicației;

încapsulată (numită și Embedded SQL): conține instrucțiuni încapsulate în codul de program.

În acest mediu există două moduri pentru crearea interogărilor: modul de scriere efectivă a cererilor în partea de “Queries” sau un mod grafic mult mai prietenos utilizatorului numit “Design View” (consta în desenarea efectivă de către utilizator a tabelelor și a legăturilor dintre date).

Instrucțiunile SQL pot fi grupate în următoarele categorii:

instrucțiuni de definire a datelor, care permit descrierea structurii bazei de date (“CREATE DATABASE”, “CREATE TABLE”, “ALTER TABLE”, “DROP TABLE”, “DROP DATABASE”)

instrucțiuni de manipulate a datelor: câteva dintre aceste instrucțiuni ar fi:

“Insert”: introducerea de noi date

“Delete”: ștergerea unor date deja existente în baza de date

“Update”: actualizarea datelor stocate

“Query”: interogarea bazei de date pentru regăsirea anumitor informații selectate după un criteriu ales.

instrucțiuni de selecție a datelor, care permit consultarea bazei de date:

Sintaxa unei cereri de interogare simplă:

SELECT [domeniu] listă_selecție

FROM nume_tabel1, nume_tabel2,…

[WHERE criteriu_selecție]

[ORDER BY câmpuri_criteriu [ASC|DESC]]; ”)

Sintaxa unei cerere de interogare complexă:

SELECT [domeniu] funcție_agregat(nume_câmp) AS alias [,listă_selecție]

FROM nume_tabel1, nume_tabel2,…

GROUP BY câmp_de_grupare

[HAVING criteriu_de_grupare]

[ORDER BY câmpuri_criteriu [ASC|DESC]];

instrucțiuni de procesare a tranzacțiilor

instrucțiuni de control al cursorului

instrucțiuni privind controlul accesului la date

O specificare ar fi că în limbajul SQL standardizat de ISO (Organizația Internațională de Standardizare) nu se folosesc termenii formali de relație, atribut, tuplu, ci tabel, coloană, rând.

In prezent, SQL este unul din cele mai puternice limbaje de programare structurate pentru interogarea bazelor de date relaționare.

2.6 ASP.NET

ASP.NET[18] este un framework gratuit folosit pentru construirea de site-uri și aplicatii web, folosindu-se de HTML, CSS și JavaScript. De asemenea, poate fi folosit și pentru crearea de API-uri Web sau pentru utilizarea unor tehnologii în timp real precum este Web Sockets.

ASP.NET este un model de dezvoltare web care include serviciile necesare pentru a construi aplicații web de clasă cu un minim de codificare.

ASP.NET[19] face parte din .NET Framework, iar când se scrie cod în aplicațiile ASP.NET, se pot accesa clase din .NET Framework. Se poate scrie cod în orice limbă compatibilă cu limbajul de rulare comun, inclusiv Microsoft Visual Basic și C#.

Aceste limbaje de programare permit dezvoltarea de aplicații ASP.NET care beneficiază de modul de funcționare a limbajului comun, de tipul de siguranță și asa mai departe.

ASP.NET funcționează împreună cu protocolul HTTP și utilizează comenzi și politicile HTTP pentru a stabili o comunicare și cooperare bilaterală între browser și client.

ASP.NET este folosit pentru a produce aplicații web interactive, bazate pe date, pe internet. Se compune dintr-un număr mare de comenzi, cum ar fi blocurile de text, butoanele și etichetele pentru asamblarea, configurarea și manipularea codului pentru a crea pagini HTML.

2.6.1 ASP.NET WebForms

ASP.NET Web Forms[20] face parte din cadrul aplicației web ASP.NET și este inclus în Visual Studio. Este unul dintre cele patru modele de programare care se pot utiliza pentru a crea aplicații web ASP.NET, celelalte 3 fiind: ASP.NET MVC, paginile Web ASP.NET și

aplicațiile ASP.NET Single Page.

Formularele web sunt pagini pe care utilizatorii le solicită utilizând browserul lor. Aceste pagini pot fi scrise folosind o combinație dintre cod HTML, script-uri client, controale de server și coduri server. Când utilizatorii solicită o pagină, acestea sunt compilate și executate pe server datorită framework-ului, iar apoi, tot framework-ul generează marcajul HTML pe care browserul îl poate afișa. O pagină ASP.NET Web Forms prezintă informații utilizatorului în orice browser sau dispozitiv client.

Utilizând Visual Studio, se pot crea formulare Web ASP.NET. Mediul de dezvoltare integrat Visual Studio (IDE) permite operațiunea de tragere și renunțațe (drag and drop) la controalelele serverului pentru a elabora pagina web de formular. De asemenea, se pot seta cu ușurință proprietăți, metode și evenimente pentru comenzile de pe pagină sau pentru pagina însine. Aceste proprietăți, metode și evenimente sunt folosite pentru a defini comportamentul dorit al paginii web, aspectul și așa mai departe. Pentru a gestiona logica paginii, scrierea codului serverului se poate realiza utilizând un limbaj al .NET-ului, de exemplu, cum ar fi Visual Basic sau C #.

Formularele web ASP.NET prelungesc modelul de interacțiune cu aplicațiile web. Browser-ul trimite un formular web către serverul web, iar serverul returnează o pagină completă de marcare sau o pagină HTML ca răspuns.

Toate activitățile utilizatorilor de pe partea clientului sunt redirecționate către server pentru a fi procesate. Serverul procesează rezultatele acțiunilor clientului și produce reacțiile.

2.7 ASP.NET Core

ASP.NET Core[21][22] este un framework pentru toate platformele, de înaltă performanță, open-source, utilizat pentru construirea de aplicații moderne, bazate pe cloud, conectate la internet.

Aplicațiile ASP.NET Core pot rula pe .NET Core sau pe întregul .NET Framework.

A fost proiectat pentru a oferi un cadru de dezvoltare optimizat pentru aplicațiile care sunt implementate în cloud sau care rulează local. Se compune din componente modulare cu un nivel minim de control, astfel încât să se păstreze flexibilitatea în timp ce se crează soluții. Există posibilitatea să dezvoltăm și să rulăm aplicațiile principale ASP.NET pe platforme precum Window, Mac și Linux.

Figura 2.1 Ciclul ASP.NET Core [22]

2.7 Entity Framework

Entity Framework[23] este setul de API-uri .NET pentru efectuarea accesului la date.
Entity Framework[24] este un framework ORM open-source utilizat pentru aplicațiile .NET, suportate de Microsoft. Acesta permite dezvoltatorilor să lucreze cu datele utilizând obiecte din clasele specifice domeniului, fără a se concentra pe tabelele și coloanele bazei de date care stau la baza acestora, unde sunt stocate aceste date. Cu cadrul entităților, dezvoltatorii pot lucra la un nivel mai ridicat de abstractizare atunci când se ocupă de date și pot crea și menține aplicații orientate spre date cu un număr mai mic de coduri în comparație cu aplicațiile tradiționale.
Definiția oficială ar fi: Entity Framework este un mapper obiect-relațional (O.R.M.) care permite dezvoltatorilor .NET să lucreze cu o bază de date folosind obiecte .NET."
Următoarea figură ilustrează unde se încadrează cadrul de entități în aplicație:

Figura 2.2 Integrare Entity Framework în aplicație [23]

Entity Framework se încadrează într-o categorie de tehnologie de acces la date numită ORM (Object Relation Mappers).

ORM-urile sunt concepute pentru a reduce fricțiunea dintre modul în care sunt structurate datele în baza de date relațională și modul în care se definesc clasele. Fără ORM trebuie să se scrie o mulțime de coduri pentru a transforma rezultatele bazei de date în instanțe ale claselor din software.

Un ORM permite să se exprime interogările utilizând clase și apoi ORM construiește și execută SQL-ul relavant pentru aplicatie precum și materializarea obiectelor din datele care au revenit din baza de date.

Entity Framework este diferit de multe ORM-uri care sunt deja cunoscute. În timp ce un ORM tipic deduce că, clasele și tabelele de baze de date au o structură similară, Entity Framework are un strat de legături între ele și oferă mult mai multă flexibilitate în ceea ce privește modul de obținere a obiectelor la tabele și de la proprietățile unui obiect la coloane de tabel, iar pentru aceste legături, Entity Framework începe cu unele ipoteze pe care developerul le referențiaza ca niște convenții pentru a fi sigur că într-un scenariu specific, datele sunt capabile să își găsească drumul înainte și înapoi între obiecte și baza de date.

Folosirea unui ORM poate elimina o serie de sarcini reduntante. Entity Framework poate spori cu adevărat productivitatea developerilor. De asemenea, asigură coerența în sarcina pe care o face în loc să aibă diverși membri ai echipei să își inventeze propriile mijloace de proiectare a accesului la date. Decât să se scrie SQL-ul relevant pentru a viza baza de date relațională cu care se lucrează, mai bine se utilizează Entity Framework care folosește sintaxa LINQ, care face parte din cadrul .NET.

LINQ-to-Entities permite dezvoltatorilor să utilizeze o limbă de interogare consistentă și puternică, indiferent de baza de date care se țintește a fi utilizata. În plus, LINQ-to-Objects este folosit pentru interogarea altor elemente ale .NET, chiar și obiecte în memorie, deci developerii beneficiază de cunoștințele lor despre LINQ, indiferent dacă folosesc LINQ-to-Entities, LINQ-to-Objects sau alt tip de LINQ.

Utilizarea unui ORM permite developeriilor să se concentreze pe domeniul lor, regulile lor de afaceri și obiectele lor de afaceri.

Caracteristicile Entity Framework-ului sunt:

Cross-platform: Entity Framework Core este un framework cross-platform care poate rula pe Windows, Linux și Mac;

Modelarea: EF (Entity Framework) creează un EDM (Entity Data Model) bazat pe entități POCO (obiect obișnuit CLR Object) cu proprietăți get / set de diferite tipuri de date. Utilizează acest model atunci când interoghează sau salvează datele entității în baza de date care se află subadiacentă;

Interogarea: Entity Framework ne permite să folosim interogări LINQ (C # / VB.NET) pentru a prelua date din baza de date care stau la baza. Furnizorul bazei de date va traduce aceste interogări LINQ în limba de interogare specifică bazei de date (de ex. SQL pentru o bază de date relațională). De asemenea, Entity Framework ne permite să executăm interogări SQL primite direct în baza de date;

Mecanism de urmărire al schimbărilor entităților: Entity Framework ține evidența modificărilor survenite în instanțele entităților(valori ale proprietății) care trebuie trimise la baza de date;

Salvarea: Entity Framework execută comenzile INSERT, UPDATE și DELETE către baza de date pe baza modificărilor apărute în entități atunci când se apeleaza metoda SaveChanges(). Entity Framework oferă de asemenea metoda asincronă SaveChangesAsync();

Concurenta: Entity Framework folosește Optimistic Concurrency în mod implicit pentru a proteja impotriva modificărilor efectuate de un alt utilizator de la preluarea datelor din baza de date;

Tranzacții: Entity Framework efectuează gestionarea automată a tranzacțiilor în timp ce interoghează sau salvează date. De asemenea, oferă opțiuni pentru personalizarea gestionării tranzacțiilor;

Caching: Entity Framework include primul nivel de caching. Deci, interogarea repetată va returna datele din memoria cache în loc să le acceseze pe cele din baza de date;

Convenții implicite: Entity Framework urmărește convențiile asupra modelului de configurare și include un set de reguli implicite care configurează automat modelul Entity Framework;

Configurații: Entity Framework ne permite să configuram modelul utilizând atribute de adnotare de date sau Fluent API pentru a suprascrie convențiile implicite.

Migrări: Entity Framework oferă un set de comenzi de migrare care pot fi executate în Consola managerului de pachete NuGet sau în interfața de linie de comandă pentru a crea sau gestiona schema bazei de date.

2.8 RAZOR View Engine

Un concept important de înțeles despre Razor este acela că este pur și simplu o implementare mai abstractă a conceptului de View engine în MVC.

Razor View Engine este View engine implicit al MVC-ului. Razor oferă o mulțime de

caracteristici care îl fac eficient și ușor de utilizat, cum ar fi fișierele de aspect, metode de ajutor, vederi, vederi parțiale și multe altele. Combinația dintre clasele de bază View Engine și aceste instrumente sunt ceea ce generează în cele din urmă, vederi și standardul de marcare.

Razor oferă multe caracteristici puternice de productivitate care se integrează cu celelalte straturi ale MVC-ului.

2.9 RAZOR Pages

ASP.NET Core Razer Pages[25] este un page controller framework (“framework de control al paginilor”) folosit pentru construirea de pagini web dinamice, bazate pe date.

Bazată pe cea mai recentă versiune de ASP.NET de la Microsoft – ASP.NET Core, Razor Pages suportă dezvoltarea pe diverse platforme și poate fi implementată pentru următoarele sisteme de operare: Windows, Unix și Mac.

Framework-ul Razor Pages este ușor de utilizat și foarte flexibil. Acesta oferă programatorului un control deplin asupra paginilor HTML redate. Framework-ul este construit având ca bază implicită ASP.NET Core MVC și este activat implicit când se creează o aplicație .NET Core care utilizează MVC.

Razor Pages folosește limbajul de programare C# pentru programarea pe partea de server și o sintaxă specifică, ușor de învățat, numită “Razor templating” pentru încorporarea codului C# în codul HTML pentru a genera cod dinamic pentru client pe partea de server.

Implementarea aplicației

3.1 Arhitectura

Aplicația a fost gândită și implementata sub forma unei aplicații web. Structura acestei aplicații poate fi văzută în figura de mai jos:

Figura 3.1 Structura aplicației

Pentru implementarea aplicației am ales framework-ul ASP.Net Core(v2.0.0) și ASP.Net Razor Pages și MSSQL pentru baza de date pentru partea de server, iar pentru partea de client am folosit Bootstrap 3 și jQuery.

ASP.Net Razor Pages este un nou aspect al ASP.Net MVC și permite o separare a intereselor mai mult decât ASP.NET MVC. Framework-ul elimină din ecuație structura de “controller”, iar fiecare pagină are asociat un fișier cu extensia “cs” ce conține logica din spate.

In figura atașată mai jos, “Figura 3.2 Structura proiectului” se poate vizualiza structura proiectului.

Director-ul “Pages” conține fiecare pagină a aplicației sub forma unui fișier cu extensia “cshtml” care conține cod Razor[] (combinație de cod html și cod C# care este compilată pe partea de server și trimisă către client) și pentru fiecare fișier de tip pagina este asociat un fișier cu numele “nume_fisier.cshtml.cs” care conține cod C# care definește logica asociată paginii într-o clasă care păstrează convenția “{nume_pagina}Model”.

Clasa moștenește dintr-o clasă de bază numită PageModel care este definită de framework și pune la dispoziție cod comun care este folosit de toate paginile, spre exemplu:

Context-ul apelului (accesată prin this.Request)

Informații despre utilizator dacă acesta este autentificat și autorizat în sistem (accesat prin this.User)

Metode de ajutor pentru returnarea diferitelor tipuri de răspunsuri (Forbid, File, Redirect, StatusCode)

Datele transmise de la client prin metoda POST sau GET folosesc în mod implicit sistemul de “Model Binding”, care prin convenție, leagă datele de parametrii metodei OnGet sau OnGetAsync sau folosind atributul decorativ al proprietăților [BindProperty] pentru a lega datele pasate prin POST la o proprietate complexă a clasei.

În Figura 3.3 se poate observa secvența de cod care definește acest comportament, iar datele pot fi accesate prin “this.Model” unde “Model” este o proprietate care poate avea orice nume și este un tip complex de date, care conține câmpuri care pot fi mapate de la datele din formular sau dintr-o structură JSON la proprietățile din clasa definită prin convenție.

Următoarea secvență de cod exemplifică modul de generare a codului HTML de către RAZOR folosind convenții:

Razor: <input type="text" class="addarticle-input" asp-for="Model.Title" />

Model.Title – este o proprietate definită care poate avea sau nu valoare implicita și ea va fi setată în codul final HTML în atributul value

asp-for este un atribut (TagHelper) definit de ASP.Net Core framework care permite maparea datelor din Model și informațiilor adiționale (atribute decorative pe proprietăți ex: [DisplayName] etc.) și generează la sfârșit următoarea secvență HTML: <input type="text" class="addarticle-input" id="Model_Title" name="Model.Title" >

Se observă faptul ca asp-for a generat 2 atribute name și id care au un anumit format (desigur id, spre exemplu, poate să fie suprascris de o valoare setată de programator, dar în mod implicit framework-ul setează valoarea un funcție de proprietatea pasată); folosind aceste convenții framework-ul poate face remaparea datelor când sunt transmise de către client înapoi la server

Acest framework permite o rapidă dezvoltare a aplicațiilor web pentru că pune la dispoziția programatorilor o multitudine de librării care ușureaza implementarea prin faptul că salvează timp de la scrierea codului folosit în majoritatea proiectelor:

manipulare URL: UrlHelper, HttpUtility

mecanism intern pentru injectarea dependințelor (Dependency Injection)

autentificare și autorizare: libraria ASP.Net

access la baza de date (EF Core, ADO.Net), metoda generică de validare a datelor transmise prin atribute (la un apel POST se poate verifica dacă model-ul este valid prin verificarea proprietății this.ModelState.IsValid care verifică logica de validare atribută pe fiecare proprietate prin atribute cum ar fi [Required], [MinLength] etc)

3.1.1 Structura aplicației

Aplicația este structurată în două zone:

partea de client, disponibilă atât utilizatorilor autentificați cât și celor neautentificați în sistem

partea de administrare, care este o zonă restricționată doar administratorilor site-ului pentru managementul rețetelor (aprobarea acestora) și utilizatorilor, în versiunea curentă aplicației, ștergerea de utilizatori nu este permisă.

Fiecare pagină din aplicație are asociate două fișiere care urmează următoarea convenție {nume-pagina}.cshtml.cs și {nume-pagina}.cshtml. Aceste pagini se află în folder-ul Pages. Partea generică a design-ului care este utilizată de toate paginile este definită în fișierul _Layout.cshtml, iar folosind @RenderBody() ASP.Net Core permite injectarea conținutului fiecărei pagini în _Layout.cshtml, fiind desigur excepții când anumite pagini nu folosesc același design.

Pagina de autentificare și pagina de înregistrare a utilizatorilor folosesc un design diferit și implicit ASP.Net Core va încerca să folosească fișierul _Layout.cshtml, pentru a defini un alt design. În fișierele Login.cshtml și Register.cshtml care se afla în directorul Account se setează valoarea proprietății Layout = "_Layout"; (în acest caz îi relativ la directorul curent).

Partea de design folosită în panoul de administrare este diferită de partea care este accesibilă utilizatorilor și se folosește aceeași abordare ca în cazul paginilor Register și Login. Valoarea implicită pentru care fișier _Layout.cshtml să fie folosit este definită în fișierul _ViewStart.cshtml, acest fișier poate fi definit în fiecare nivel dacă zona respectivă necesită un alt design, spre exemplu zona de administrare suprascrie designul principal printr-un alt fișier _Layout.cshtml și ca să se elimine repetiția asignării în fiecare pagină în parte, se folosește un fisier adițional _ViewStart.cshtml aflat în folderul Admin.

În mod normal, în ciclul de dezolvare a aplicației este nevoie de medii diferite care pot avea setări diferite. Setările acestea pot ajuta la depanare sau la optimizare, în cazul în care se folosește un mediu local de dezvoltare este importat să avem setări care ușurează munca de depanare, dar în cazul în care aplicația este publicată într-un mediu accesibil utilizatorilor este indicat să avem o variantă optimizată, iar ASP.Net Core pune la dispoziție utilități care să faciliteze acest lucru. La compilarea aplicației se specifică mediul de rulare, implicit acesta este definit ca fiind “Development” și este setat ca variabilă de mediu în setările proiectului cu numele ASPNETCORE_ENVIRONMENT. în fișierul _Layout se observă utilizarea acestei funcționalități:

<environment exclude="Development">

<link rel="stylesheet"

href="https://ajax.aspnetcdn.com/ajax/bootstrap/3.3.7/css/bootstrap.min.css"

asp-fallback-href="~/lib/bootstrap/dist/css/bootstrap.min.css"

asp-fallback-test-class="sr-only" asp-fallback-test-property="position"

asp-fallback-test-value="absolute" />

<link rel="stylesheet" href="~/css/site.min.css" asp-append-version="true" />

</environment>

În cazul în care mediul este diferit de “Development”, fișierele folosite pentru stilizarea aplicației sunt optimizate din punct de vedere al dimensiunii fișierelor, fapt care este benefic atât pentru încărcarea rapidă a aplicației, cât și pentru reducerea costurilor pentru utilizatorii care folosesc dispozitive mobile și nu au acces la date prin Wi-Fi.

Partea de început a aplicației care este folosită pentru încărcarea setărilor inițiale folosite de sistem, setarea serviciilor, inițializarea setărilor pentru conexiunea la baza de date, poate beneficia de setările de mediu. Acest lucru permite definirea diferitelor servicii să fie folosite pe parcursul aplicației în funcție de mediul de dezvoltare:

if (env.IsDevelopment())

{

using (IServiceScope serviceScope =

app.ApplicationServices.GetRequiredService<IServiceScopeFactory>().CreateScope())

{var context = serviceScope.ServiceProvider.GetService<ApplicationDbContext>();

if (!context.AllMigrationsApplied())

context.Database.Migrate();

context.EnsureSeeded(serviceScope);

}

}

Secvența anterioară de cod este folosită pentru a verifica dacă suntem în mediul “Development”, iar în acest caz se incearcă aplicarea modificărilor bazei de date făcute prin sistemul de migrări pus la dispozitie de EntityFramework și după aplicarea acestora să se facă inițializarea sistemului cu date de test.

Un exemplu concret pentru a pune în evidență o modalitate de depanare a aplicației în moduri diferite, în funcție de mediul în care rulează aplicația, se poate face printr-un mecanismu de logare a anumitor evenimente cheie din sistem (înregistrare de utilizatori, adăugare de rețete, apel la dependințe externe ale sistemului etc), dar în mediul de “Production” avem nevoie de un sistem diferit, care centralizează aceste informații la un serviciu extern (beneficiile ar fi: sistem de filtare al înregistrărilor, statistici și posibilitatea de a crea copii de rezervă). Pentru a face acest lucru posibil, se face o verificare de mediu if(env.IsDevelopment()) și folosind mecanismul de injectare a dependințelor furnizat de ASP.NetCore se poate specifica care mecanism de logare să fie folosit services.AddSingleton<ILogger, CustomLoger>();.

De reținut este faptul că ILogger este o interfață pus la dispoziție de ASP.Net Core, implicit se folosește un serviciu intern care afișează în consolă.

Anumite setări cum ar fi setările pentru conectarea la baza de date sau setări pentru sistemul de logare, sunt ținute în fișierul appsettings.json și, în funcție de mediu, se pot defini fișiere adiționale folosind următorul model appsettings.{Mediu}.json iar la compilarea aplicației, aceste setări vor înlocui setările din fișierul principal. Unele setări pot fi definite la nivel de calculator folosind Secret Tool Manager, pus la dispoziție de ASP.Net Core [].

În fișierul appsettings.json avem definite datele pentru conexiunea la baza de date:

"ConnectionStrings": { "DefaultConnection":”…" }

EntityFramework recunoaște implicit această setare.

3.1.2 EntityFramework și legarea la baza de date

Schema bazei de date se poate găsi la Anexa 1 a lucrării, precum și relațiile dintre tabele din care este alcătuită. Baza de date este alcătuită din urmatoarele tabele:

Recipe – conține lista de rețete; conține o coloană Id căreia i se asignează un ID unic (primary key) find folosit la legatura cu celelate tabele dependente

fiecare rețetă are o imagine principală și o listă de imagini folosite pentru galeria de imagini din pagina de detalii a rețetei

RecipeImage – conține imaginile asociate unei retețe prin coloana RecipeId care este foreign key

RecipeComments – deține lista de comentarii asociate unei retețe; foreign key în această tabelă este RecipeId

RecipeIngredients – lista de ingrediente asociate unei retețe în pagina de creare sau editare; conține foreign key la tabela Recipe și o coloană în care este specificat tipul de gramaj folosit pentru acel ingredient

În codul C# se folosește un tip enum pentru a reprezenta tipurile de gramaj folosite QuantityType care vine salvat în baza de date într-o valoare de tip întreg, iar EntityFramework face automat maparea între aceste valori (în spatele acestei mapări se află o clasă care face conversia între enum și tipul integer[26] din baza de date în momentul în care se face o interogare sau actiune de adăugare sau actualizare)

AspNetUsers – conține lista de utilizatori; această tabelă este generată de către ASP.Net Identity, care are o coloană primary key de tipul string definită implicit, dar poate fi modificată să folosească un alt tip

AspNetUserRoles – conține asocierea între utilizatori și rolurile din tabela AspNetRoles

AspNetRoles

Pentru accesul la baza de date se folosește O.R.M-ul (Object-Relational Mapper) EntityFramework, o tehnologie implementata de Microsoft și care este folosită și de libraria ASP.Net Identity (mai multe detalii pot fi gasite la capitolul 3.2 Sistemul de autentificare și autorizare). Acest O.R.M ajuta la simplificarea modului de a accesa baza de data, folosind clase C# care reprezinta tabelele și structura din baza de date, și folosind proprietati de navigare se poate mentine o legatura cu celelalte tabele. Clasele acestea sunt foarte simple, în urmatoarea figura putandu-se vizualiza clasa Recipe care se leaga de tabela dbo.Recipe care a fost creata pe baza ei automat de către EntityFramwork, folosind conventiile implicite.

În această clasă, proprietățile de tip Collection<…> sunt proprietăți de navigare la alte tabele printr-o relație de one-to-many. Fiecare are o proprietate(RecipeId) care o leagă de clasa principala Recipe, aceasta fiind un foreing key.

Tabelele, mai bine zis clasele care definesc tabelele, au următoare structură:

Accesul la aceste tabele se face prin clasa ApplicationDbContext, care este configurată la începutul aplicației în fișierul Startup.cs (vezi Figura 3.6 de mai jos), iar clasa este injectată de sistemul ASP.Net Core:

Figura 3.6 Initializare setari pentru EntityFramework

O mențiune aici ar fi ca fiecare tabela poate fi accesata prin proprietatiile clasei ApplicationDbContext.

Un exemplu de interogare a bazei de date poate fi exemplificat prin listarea de rețete folosind și paginare:

await _dbContext.Recipes

.Include(x => x.User)

.OrderByDescending(x => x.Id)

.Skip(pageNumber * pageSize)

.Take(pageSize).ToListAsync();

Aceasta interogare se leaga și de tabela User pentru a avea acces la detaliile utilizatorului pentru a fi folosite, fără această legatură, EntityFramework nu face operațiunea de JOIN ca să returneze informațiile, acest lucru fiind util pentru că nu tot timpul este nevoie de aceste informații și ajuta la performanța aplicației. Metoda de acces folosește un mecanism din .Net numit LINQ, care permite translatarea codului scris printr-un set de servicii în cod SQL (pot fi setate alte servicii pentru integrare cu MySQL, SQLLite sau sa se implementeze propriul serviciu de translatare).

În procesul de creare a entităților din baza de date se poate interveni și suprascrie convențiile prin metoda OnModelCreating din ApplicationDbContext folosind sistemul de FluentApi pus la dispoziție. Pentru tabela Recipe, a fost adăugat în cadrul proiectului o coloană IsDeleted în baza de date, dar aceasta este exclusa din clasa Recipe pentru că nu este necesară în cod, următoarea secvență de cod este folosită pentru definirea acestei noi coloane:

builder.Entity<Recipe>()

.Property<bool>("IsDeleted");

Această proprietate “IsDeleted” este folosită pentru a filtra în mod implicit în orice interogare facută entitățile cu valoarea “0” prin următoarea secvență de cod:

builder.Entity<Recipe>()

.HasQueryFilter(post => Property<bool>(post, "IsDeleted") == false);

Folosind setarea de mai sus, nu mai este nevoie să adăugam condiție adițională la fiecare selectare a datelor pentru a alege doar cele care nu sunt marcate ca și sterse.

Motivul pentru care se marchează entitățile ca și șterse prin “IsDeleted” este pentru ca să rămână un istoric al datelor și pentru a nu pierde informațiile. Un proces de ștergere a datelor este indicat să se facă manual după o anumită perioadă, dar o arhivă a bazei de date trebuie făcută implicit.

EntityFramework folosește un sistem de migrare, care permite adăugarea de modificări noi și aplicarea lor, dar și un mecanism de a reveni la versiunea anterioară. Adăugarea unei modificări noi se face prin linia de comandă ”dotnet ef migrations add MigrationName” care adaugă câteva fișiere în proiect cu codul generat care descrie structura nouă și toți pașii care modifică baza de date, cât și structura curentă a bazei de date pentru a elimina conflictele când se lucrează într-o echipă. Aplicarea migrării se face prin “dotnet ef database update” care aplică modificarea.

3.2 Sistemul de autentificare și autorizare

ASP.NET Core pune la dispoziție un sistem simplu de autentificare și înregistrare numit ASP Identity[27]. Acest sistem a fost implementat pentru înlocuirea unui sistem mai vechi ASP.NET Membership și Simple Membership.

ASP Identity oferă în mod implicit suport pentru rol-urile utilizatorilor, management de profil, schimbare de parolă, mecanism pentru autentificare prin doi pași, integrare cu servicii externe de socializare cum ar fi Facebook, Google etc.

Este posibilă extinderea sistemului prin modificarea mecanismului de stocare unde implicit este folosit EntityFramework Core, iar prin integrare cu OAuth[28], poate fi extins prin ASP.NET Core middleware[29].

La crearea șablonului pentru acest proiect, s-a optat pentru mecanismul de autentificare și înregistrare normal prin ASP Identity, iar conturile utilizatorilor vor fi stocate în aceeași bază de date folosită de restul aplicației. O altă alternativă la acest sistem este integrarea cu Azure Active Directory[26], care permite ca datele utilizatorilor să nu fie păstrate în același sistem crescând nivelul de securitate, dar în același timp și timpul de dezvoltare și complexitatea proiectului. În cazul de față a fost aleasă prima variantă deoarece aplicația nu este destul de complexă să necesite un sistem mai complex și securizat de management al conturilor.

Folosind structura MVC, aplicația creată adaugă două controllere AccountController și respectiv ManageController. AccountController este folosit pentru autentificare și înregistrarea utilizatorilor.

Pentru autentificare se apelează ruta Login în cadrul Login.cshtml.cs din formularul de autentificare. În acest pas se poate verifica dacă utilizatorul a optat pentru autentificare prin doi pași sau dacă cumva contul a fost blocat din cauza unor autentificări anterioare care au eșuat, însă această opțiune poate fi dezactivată.

În următoarea secvență de cod se apelează serviciul SignInManager pentru autentificare prin parolă.

var result = await _signInManager.PasswordSignInAsync(model.Email,

model.Password, model.RememberMe, lockoutOnFailure: false );

Această metodă nu va returna nici o excepție, dar o să returneze un obiect care va conține proprietăți necesare pentru a verifica dacă autentificarea a fost cu succes :

if ( result.Succeeded ).

Dacă se necesită autentificare prin doi pași, se poate verifica prin accesarea proprietății RequiresTwoFactor. În cazul în care sistemul necesită autentificarea prin doi pași, utilizatorul va fi redirecționat la ruta LoginWith2fa.

Înregistrarea utilizatorilor se face apelând ruta Register din Register.cshtml.cs. În cazul unei înregistrări cu succes, utilizatorul poate primi un email prin care să își poată confirma contul. Următoarea secvență de cod este folosită pentru a crea contul de utilizator:

var result = await _userManager.CreateAsync(user, model.Password);

if (result.Succeeded)

{

_logger.LogInformation("User created a new account with password.");

var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);

var callbackUrl = Url.EmailConfirmationLink(user.Id, code, Request.Scheme);

await _emailSender.SendEmailConfirmationAsync(model.Email, callbackUrl);

await _signInManager.SignInAsync(user, isPersistent: false);

_logger.LogInformation("User created a new account with password.");

return RedirectToLocal(returnUrl);

}

Schimbarea de parolă se face apelând ruta ChangePassword din ChangePassword.cshtml.cs. În cazul unei schimbări de parolă cu succes, utilizatorul va primi un mesaj pozitiv informându-l că, parola sa schimbat cu succes. Pentru trimiterea unui e-mail de verificare a contului se apelează:

SendVerificationEmail( IndexViewModel model ) .

Utilizatorul va fi informat când a fost trimis un email pentru verificare. Email-ul la care este trimis este acela care s-a setat la crearea contului sau cel care a fost actualizat în cazul în care sa optat pentru această opțiune.

Restricționarea accesului la pagini se face la inceputul aplicatiei și este diferit de mecanismul oferit de ASP.Net Core MVC unde se poate decora fiecare actiune sau controller direct cu un atribut care limiteaza accesul utilizatorilor. În aceasta aplicatie se restrictioneaza accesul la pagina de administrare, crerea de retete, editare de retete, lista proprie de retete, acest lucru se face prin urmatoarea secventa de cod:

services.AddMvc().AddRazorPagesOptions(options =>

{

options.Conventions.AuthorizePage("/Create");

options.Conventions.AuthorizePage("/Account/Logout");

});

Descrierea aplicației

4.1 Pagina principală

Pagina principală conține mai multe secțiuni care îl ajută pe vizitatorul paginii să își facă o idee despre pagină și să se familiarizeze cu informațiile (acestea sunt descrise pe scurt și au diverse animații și imagini foarte sugestive).

Figura de mai jos reprezintă exact prima pagină care se încarcă în momentul lansării aplicației:

Figura 4.1 Pagina principală

În prima parte a paginii avem un meniu cu mai multe opțiuni: Pagina principala, Despre mine, Rețete(care are și un submeniu cu diversele categorii de rețete), Contact.

În partea de header a paginii avem 2 opțiuni:

”Crează-ți un cont!”(acest link duce la o pagina de creare a unui nou cont, fiind necesare introducerea unui email, a unei parole și confirmarea acesteia)

“Intra în cont”(acest link duce la o pagina unde utilizatorul își poate introduce email-ul și parola și se poate autentifica)

Pe urmă, în partea stângă avem logo-ul paginii (Figura 4.2) care va putea fi întâlnit pe toate paginile, iar dacă utilizatorul va da click pe el, de pe orice pagină s-ar afla, va fi redirecționat la pagina principală.

Tot pe pagina principală avem o animație care prezintă 4 categorii de rețete care pot fi făcute la micul dejun, prânz, cina și desert. Acestea au o imagine foarte sugestivă și un titlu la fel de sugestiv, după cum se poate vedea și în Figura 4.1.

Sub aceste animații avem o secțiune care conține ultimele 3 rețete adăugate(vezi Figura 4.3) care au o imagine, un titlu, categoria din care face parte și o scurtă descriere, iar dacă se da click pe aceasta, utilizatorul va fi redirectat la pagina de detaliu a rețetei(care va fi prezentata mai în detaliu la Capitolul 3.8 Pagina de detaliu al unei rețete).

Figura 4.3 Pagina principală- secțiunea "Cele mai recente"

În continuare, în pagină, se poate vedea o animație care cuprinde câteva titluri și scurte descrieri, cu diverse informații actuale(scurte “știri” din domeniul culinar). Un exemplu de “știre” ar fi:“Cea mai votata rețetă a lunii- Muffins cu ciocolata și înghețată cu fructe de pădure, un deliciu al verii atât pentru cei mici, cât și pentru cei mari”).

În partea de jos a paginii se poate observa subsolul(vezi Figura 4.4) care cuprinde diverse informații și link-uri către pagini: secțiunea “Cele mai recente postări” (titlurile rețetelor proaspăt adăugate), secțiunea “Explorează” care cuprinde meniul principal paginii dar sub o altă formă și secțiunea “Contactează-ne” care conține link-uri către rețelele sociale(Facebook, Instagram și Twitter).

Figura 4.4 Pagina principală- subsol

4.2.1 Sistemul de înregistrare a unui nou utilizator

Un utilizator iși poate crea un nou cont accesând link-ul “Crează-ți un cont!” din partea dreapta sus a paginii. Acesta va fi redirectat pe o nouă pagină(pagină care este reprezentată in figura de mai jos, Figura 4.5) unde va introduce date cum ar fi adresa de email și parola pe care o dorește, iar dacă va da click pe butonul ‘Înregistrare’, va fi redirectat pe pagina principală a aplicației, în colțul din dreapta sus putandu-de vedea un mesaj corespunzator “Bine ai venit, adresa@email.com”. În capitolul 3.2 a fost prezentat sistemul de autentificare și autorizare.

Figura 4.5 Pagina de înregistrare a unui nou cont

4.2.2 Sistemul de autentificare a unui utilizator/administrator deja existent

Un utilizator sau administrator se poate autentifica accesând link-ul “Intră în cont!” din partea dreapta sus a paginii. Acesta va fi redirectat pe o nouă pagină unde va introduce date cum ar fi adresa de email și parola, iar dacă va da click pe butonul ‘Login’, va fi redirectat pe pagina principală a aplicației, în colțul din dreapta sus putându-de vedea un mesaj corespunzător “Bine ai venit, adresa@email.com”, urmat fiind de adresa de email a utilizatorului.

Această pagină este asemănătoare ca aspect cu figura atașată mai sus(Figura 4.5), însă diferă câmpurile, în pagina de autentificare având doar câmpurile email și parolă.

În capitolul 3.2 a fost prezentat sistemul de autentificare și autorizare a sistemului, în acest capitol este explicat mecanismul.

4.3 Pagina despre mine

În această pagină sunt descrise câteva lucruri despre administratorul aplicației, scopul pentru care a construit aplicația și câteva din ideile viitoare de implementare unde sunt invitați toți utilizatorii să își spună dorințele (pot trimite email cu diverse lucruri pe care le-ar dori să le conțină aplicația).

4.4 Pagina de listare a tuturor rețetelor

În această pagină sunt prezentate toate articolele(rețetele) pe scurt. Fiecare rețetă conține titlul, persoana care a adăugat rețeta, o imagine sugestivă, o descriere pe scurt și categoria din care face parte.

Această pagină poate fi accesată printr-un simplu click pe butonul “Rețete” al meniu.

Figura 4.6 Pagina de listare a rețetelor

În proiect, această pagină este definită în fișierul Recipes.cshtml, aici se va găsi design-ul pentru această pagină cât și codul folosit pentru listarea rețetelor, iar cu ajutorul RazorEngine-ului se folosește de codul C# pentru a parcurge o listă de rețete preluată din baza de date prin următoarea secvență de cod:

Recipes = await _dbContext.Recipes

.Include(x => x.User)

.Where(x=>x.Category == categoryId)

.OrderByDescending(x => x.Id)

.Skip(pageNumber * pageSize)

.Take(pageSize).ToListAsync();

Parametrii pageNumber, pageSize și categoryId sunt transmise prin link(“query parameters”), iar în clasa RecipesModel din Recipes.cshtml.cs există metoda async Task OnGetAsync(int categoryId, int pageNumber = 1, int pageSize = 5), care va fi apelată implicit de framework-ul RazorPages prin convenție. Acești parametrii sunt folosiți pentru a prelua datele paginate.

Clasa RecipesModel conține o proprietate care conține lista de rețete public List<Recipe> Recipes { get; set; }, acesta va conține lista de rețete după execuția codului anterior, iar după execuția metodei OnGetAsync, în fișierul .cshtml asociat, se va executa codul care va interpreta codul și va genera codul HTML cu tabelul final. Bucla care se execută este urmatoarea: @foreach (var recipe in Model.Recipes).

În această pagină se face o verificare în fișierul cu design-ul, unde se verifică data când utilizatorul este autentificat, în caz pozitiv se afișeaza un buton folosit pentru a redirecta utilizatorul spre pagina de adăugare de rețete: @if (User.Identity.IsAuthenticated).

4.6 Pagina de creare(adăugare) a unei noi rețete

Fișierul asociat paginii de adăugare este Create.cshtml, care conține partea de design, iar fișierul Create.cshtml.cs fișierul care conține modelul acestei pagini și logica de adăugare și încărcare de imagini. Pentru această pagină s-a folosit o librărie javascript numită SummerNote care a fost folosită pentru a adăuga conținut stilizat de către utilizator oferind capabilități minore ale unui editor în browser, în figura următoare putându-se vizualiza această librărie în actiune:

Figura 4.7 Câmpul de introducere a unor detalii(modul de preparare și descriere)

La adăugarea rețetei, există și posibilitatea de a adăuga o listă de ingrediente, pentru fiecare ingredient se poate alege și tipul de gramaj. Pentru a oferi o experiență plăcută utilizatorilor, adăugarea se face într-un mod dinamic și se permite și ștergerea elementelor, pentru impelementare s-a folosit librăria jQuery și se face prin următoarea secvență de cod:

var newRow = $jq('#ingredients');

var ingredientsRow = $jq('.ingredients-column');

$jq.get("/Create?handler=IngredientItemPartial").then(function(result) {

var final = $jq(result).append("<a href='#' class='clickremove' id='removeRow'>Sterge ingredient [x]</a>")

final.on("click","a",

function(e) {

e.preventDefault();

$jq(this).parent().remove(); })

newRow.append(final);

});

e.preventDefault();

Pe butonul aflat pe interfața de adăugare de ingrediente, se atașează un eveniment care la apăsare face un apel la server, care returnează un cod html generat pe server cu informațiile necesare pentru asociere, apelul se face la /Create?handler=IngredientItemPartial. Fișierul folosit pentru generarea codului HTML este IngredientItem.cshtml:

@model IngredientItem

@using (Html.BeginCollectionItemMVC<CreateModel>(x=>x.Model.Ingredients))

{  <div id="ingredientsRow" class="addarticle-ingredients-row">

<input type="text" asp-for="Name" placeholder="e.g. amidon alimentar"/>

<input type="text" asp-for="Quantity" placeholder="e.g. 15"/>

<select asp-for="QuantityType">

<option value="0">kg(s)</option>

<option value="1">liter(s)</option>

</select>

</div>}

Prin metoda BeginCollectionItemMVC, se creează în spate un câmp cu un id unic asociat acestei grupări de câmpuri, această metodă permite ca la generarea de nume și id-uri pe câmpurile referențiate în cod să fie asociat cu acestea. Acest lucru este necesar din cauza unei limitări din framework, care la returnarea datelor, dacă lista nu conține id-uri consecutive sau un câmp adițional care să conțină referința indicelui să ignore aceste elemente. O alternativă ar fi fost ca acest lucru să fie făcut într-un mod mai explicit, dar ar implica cod adițional care totodată să fie mai greu de menținut și să nu fie reutilizabil.

Ingredientele adăugate pe interfață pot fi și eliminate prin butonul alăturat elementului când este adăugat, iar acest lucru se face prin evenimentul atașat folosind jQuery :

$jq(this).parent().remove();

La salvarea datelor din formular, se face apel prin metoda de OnPostAsync din CreateModel.cshtml.cs, folosind sistemul de ModelBinding din framework, datele vor fi mapate în proprietatea public CreateRecipe Model { get; set; } marcată cu atributul [BindProperty]. În prima fază, se face o procesare a pozelor încărcate, se parcurge fiecare fișier și se salvează în folderul “/upload” și se creează o listă cu elemente de tipul RecipeImage, care vor conține noul link la imagine, iar ele vor fi asociate cu rețeta adăugată.

var path = Path.Combine(

Directory.GetCurrentDirectory(), "wwwroot\\upload",

formFile.FileName);

using (var stream = new FileStream(path, FileMode.Create))

{

await formFile.CopyToAsync(stream);

var x = formFile.FileName;

images.Add(new RecipeImage()

{ImageUrl = path});

}

Pentru imaginea principală se urmează aceiași pași, dar link-ul va fi salvat în coloana MainImageUrl din tabela Recipe.

Folosind următoarea secvență de cod se face salvarea datelor și asocierea imaginilor

var entity = new Recipe

{

Description = Model.Description,

CreatedOn = DateTime.Now,

IsApproved = true,

RecipeDetails = Model.Content,

Tags = "cookie choocolate stuff",

Title = Model.Title,

UserId = this.User?.Claims?.FirstOrDefault(x => x.Type == ClaimTypes.NameIdentifier)?.Value,

Images = new Collection<RecipeImage>(),

MainImageUrl = mainImage?.ImageUrl  };

await _dbContext.Recipes.AddAsync(entity);

await _dbContext.SaveChangesAsync();

foreach (RecipeImage recipeImage in images)

{

recipeImage.RecipeId = entity.Id;

await _dbContext.RecipeImages.AddAsync(recipeImage);

}

await _dbContext.SaveChangesAsync();

return RedirectToPage("Recipes");

4.7 Pagina de editare a unei rețete

Pagina de editare a unei rețete este asemănătoare cu pagina de adăugare a unei rețete (ce poate fi găsită descrisă în detaliu la subcapitolul 4.5), însă în pagina de editare apar toate detaliile unei rețete care pot fi editate.

La salvare detaliilor din formular, datele sunt transmise la metoda de OnPostAsync din fișierul Edit.cshtml.cs, care foloseste parametrul din formular Recipe.Id care este mapat cu toate informațiile la proprietatea Model din clasa folosind BindProperty. Folosind EntityFramework se selectează entitatea existentă din baza de date și se modifică proprietățile necesare cu informațiile din formular:

var entity = _dbContext.Recipes

.Include(a => a.Images)

.Where(c => c.Id == Model.Id)

.First();

entity.Description = Model.Description;

entity.IsApproved = true;

entity.RecipeDetails = Model.Content;

entity.Tags = Model.Tags;

entity.Title = Model.Title;

entity.MainImageUrl = mainImage?.ImageUrl;

_dbContext.Recipes.Update(entity);

Prin comanda de Update se marchează entitatea ca la execuția funcției

await _dbContext.SaveChangesAsync(); .

4.8 Pagina de detaliu al unei rețete

În pagina de detaliu poate fi găsită rețeta descrisă în amănunt. Această pagină conține informații despre ingredientele necesare preparării respectivei rețete, modul de preparare, imagini sugestive și o secțiune unde se poate vota rețeta respectivă(Figurile de mai jos, 4.8 și 4.9 reprezintă o asemenea pagina).

În această pagină, dacă se da click pe simbolul „+” din dreptul textului „Ingrediente” sau „Mod de preparare”, se deschide o secțiune care conține detalii legate de ingrediente (nume produs, cantitate) sau detalii legate de modul de preparare (informații adăugate/editate de pe pagina de creare sau editare a unei rețete ). Codul folosit pentru a face posibilă interacțiunea are nevoie ca aceste elemente să aibe una dintre clasele CSS “collapsible” aceasta fiind folosită pentru a marca elementele ca să fie indentificate prin Javascript:

var collap = document.getElementsByClassName("collapsible");

var i;

for (i = 0; i < collap.length; i++) {

collap[i].addEventListener("click", function() {

this.classList.toggle("activecoll");

this.classList.toggle("plusminus");

var content = this.nextElementSibling;

if (content.style.maxHeight === "600px") {

content.style.height = "0";

content.style.maxHeight = "0";

} else {

content.style.height = "100%";

content.style.maxHeight = "600px";

}

});

}

Fiecare element cu clasa respectivă este parcurs și îi este atașat un eveniment pentru mouse click.

Pentru afișarea informațiilor de pe aceasta pagina, se folosește parametrul „id” din adresa paginii (…/Details/{id} sau …/Details?id={id}) pentru condiția de selectare prin următoarea secvență de cod:

this.Details = _context.Recipes

.Include(x=> x.Comments)

.Include(x=> x.Images)

.Include(x=> x.User)

.FirstOrDefault(x => x.Id == id);

this.Ingredients = _context.RecipeIngredients.Where(x => x.RecipeId == id).ToList();

Aceste informații sunt folosite în pagina Details.cshtml.

4.9 Pagina de contact

Figura 4.10 Pagina de contact

Această pagină poate fi accesată dând click pe ultima opțiune a meniului paginii, “Contact”. Se va deschide o nouă pagină care va conține informații legate de persoana de contact și cum se poate lua legatura cu această (adresa unde poate fi găsită, numărul de telefon, adresa de email). De asemenea, în partea dreaptă a paginii se află o hartă care are setată adresa contactului. Pentru afișarea hărții și fixarea unui indicator pe hartă a locației se folosește urmatoarea secvență HTML care a fost generat de către pagina https://www.embedgooglemap.net/ unde a fost introdusă adresa exactă a locației. Harta de la figura 4.10 este rezultatul introducerii codului de mai jos în fisierul Contact.cshtml:

<div class="gmap_canvas">

<iframe width="700" height="600" id="gmap_canvas" src="https://maps.google.com/maps?q=corneliu%20coposu%20street%20number%207&t=&z=15&ie=UTF8&iwloc=&output=embed" frameborder="0" scrolling="no" marginheight="0" marginwidth="0">

</iframe>

</div>

Stilizarea hărții se face prin:

<style> .mapouter { text-align: right; height: 600px; width: 700px; }

.gmap_canvas { overflow: hidden; background: none !important;

height: 600px; width: 700px;}

</style>

4.10 Pagina de adminstrare

Această pagină poate fi accesată doar de către administratorul aplicației.

Pagina poate fi accesată din meniul vizibil doar utilizatorului cu rol de administrator, din partea dreaptă a paginii. Pagina conține informații, cum ar fi: lista de utilizatori, lista tuturor rețetelor, lista rețetelor care au fost solicitate a fi postate de către utilizatori și necesită aprobarea administratorului și diverse informații și notificări legate de cont.

Figura 4.11 Pagina de administrare

Secvența de cod care definește clasa rețetelor care urmează a fi aprobate este următoarea:

public class ApprovalListModel : PageModel

{

private readonly ApplicationDbContext _context;

public ApprovalListModel(ApplicationDbContext context)

{

_context = context;

}

public void OnGet()

{

this.Recipes = _context.Recipes

.Include(x => x.Comments)

.Include(x => x.Images)

.Include(a => a.User)

.ToList();

}

public List<Recipe> Recipes { get; set; }

}

În momentul în care administratorul bifează butonul verde din coloana “Aprobare”, rețeta respectivă va fi marcată ca aprobată în coloana IsApproved unde va primi valoarea true. Urmarea acestui fapt este că rețeta va apărea în pagina de listare a tuturor rețetelor.

Concluzii

Implementarea acestei aplicații a avut ca scop dezvoltarea unei aplicații care să ușureze căutarea de rețete pentru curioși și pentru pasionați de gătit. De asemenea a fost concepută și pentru a ajuta la centralizarea unui caiet de rețete tradițional, platforma simplificând căutarea și adăugarea rețetelor față de cum ar fi trebuit să decurgă întregul proces în mod tradițional(cu un pix și o foaie). Îmbunătățiri viitoare ale aplicației pot ajuta la extinderea aplicației pentru o gamă mai larga de rețete, pe mai multe categorii de mâncare(de exemplu supe și ciorbe, garnituri, salate etc) sau feluri de mâncare specifice anumitor culturi(spre exemplu mâncare tradițională italiană, chinezească și multe altele). Un scop adițional a fost folosirea tehnologiilor celor mai recente pentru a studia și simplifica procesul de dezvoltare. În această lucrare, aplicația a fost implementată cu funcționalitățile de bază necesare pentru o aplicație web folosită pentru gestionarea de rețete provenite atât de la administratorul aplicației, cât și de la utilizatori.

Aplicația poate fi extinsă în funcție de utilizatori pentru a include funcționalități adiționale cum ar fi: un sistem de calculare a unor posibile rețete ce pot fi realizate având ca date de intrare produsele disponibile în casa(de exemplu utilizatorul să introducă produsele de care dispune și să i se rezulte o listă de rețete pe care le poate face cu ingredientele respective), implementarea unui sistem de cumpărare online a ingredientelor, un sistem de salvare a rețetelor favorite(când un utilizator găsește vreo rețetă ce i se pare interesant, să o poată salva ca ‘favorită’ și să aibe o pagină unde să poată vizualiza toate rețetele ‘favorite’) și o aplicație mobilă având toate funcțiile aplicației web plus cele descriese mai sus, pentru a ușura “munca” utilizatorului, un telefon mobil fiind mereu mult mai la îndemână decât un laptop/calculator.

Folosirea tehnologiilor enumerate în această lucrare permit dezvoltarea mai rapidă a aplicațiilor web și asigură o securitate implicit mai ridicată pentru că framework-urile sunt implementate cu securitate și scalabilitate. Pe lângă aceste funcționalități implicite, aplicația poate fi îmbunătățită prin separarea părții de autentificare și autorizare într-un proiect independent de aplicație și implementarea protocolului OpenId Connect sau integrarea cu platformele de socializare(posibilitatea conectarii cu un cont de Facebook, Google etc. și recunoasterea ca utilizator).

Bibliografie

[1] https://docs.microsoft.com/en-us/previous-versions/msp-n-p/ee658105(v=pandp.10) consultat la 20.03.2018

[2] https://teamtreehouse.com/library/what-is-html consultat la 20.03.2018

[3] https://www.w3schools.com/html/html_intro.asp consultat la 20.03.2018

[4] http://www.yourhtmlsource.com/starthere/whatishtml.html consultat la 20.03.2018

[5] https://www.w3schools.com/css/css_intro.asp consultat la 28.03.2018

[6]https://developer.mozilla.org/en-US/docs/Learn/CSS/Introduction_to_CSS/How_CSS_

works consultat la 28.03.2018

[7]https://developer.mozilla.org/en-US/docs/Web/JavaScript/About_JavaScript

consultat la 6.04.2018

[8] https://www.tutorialspoint.com/javascript/javascript_overview.html consultat la 6.04.2018

[9] https://jquery.com/ consultat la 8.04.2018

[10] http://api.jquery.com/ consultat la 8.04.2018

[11] http://getbootstrap.com/ consultat la 12.05.2018

[12] https://ctrl-d.ro/editorial/o-scurta-introducere-in-responsive-web-design/

consultat la 12.05.2018

[13] https://stackoverflow.com/questions/14546709/what-is-bootstrap consultat la 12.05.2018

[14] http://www.sqlcourse.com/intro.html consultat la 19.05.2018

[15] https://www.w3schools.com/sql/sql_intro.asp consultat la 19.05.2018

[16] https://www.agerpres.ro/flux-documentare/2016/03/03/organizatii-internationale-organizatia-internationala-de-standardizare-16-45-49 consultat la 19.05.2018

[17] http://www.adibarbu.ro/2015/03/ce-este-un-api-interfata-de-programare.html

consultat la 9.06.2018

[18] Glenn Block, Pablo Cibraro, Pedro Felix, Howard Dierking, Darrel Miller(2014)

„Designing Evolvable Web APIs with ASP.NET”, Editura: O'Reilly Media, Inc.,

ISBN: 978-1-449-33771-1 consultat la 9.06.2018

[19] https://docs.microsoft.com/en-us/aspnet/overview consultat la 9.06.2018

[20] https://docs.microsoft.com/en-us/aspnet/web-forms/what-is-web-forms

consultat la 9.06.2018

[21] https://docs.microsoft.com/en-us/aspnet/core/ consultat la 9.06.2018

[22] https://docs.microsoft.com/en-us/aspnet/core/tutorials/index consultat la 9.06.2018

[23] http://www.entityframeworktutorial.net/what-is-entityframework.aspx

consultat la 17.06.2018

[24] Julia Lerman(2010), „Programming EntityFramework, 2nd Edition”,

Editura: O'Reilly Media Inc., ISBN: 9780596807276 consultat la 17.06.2018

[25] https://www.learnrazorpages.com/ consultat la 17.06.2018

[26] https://docs.microsoft.com/en-us/ef/core/modeling/value-conversions

[27] https://www.asp.net/identity consultat la 17.06.2018

[28] https://docs.microsoft.com/en-us/aspnet/aspnet/overview/owin-and-katana/owin-

oauth-20-authorization-server consultat la 17.06.2018

[29] https://docs.microsoft.com/en-us/aspnet/core/fundamentals/middleware?tabs=

aspnetcore2x consultat la 17.06.2018

[30] http://openid.net/connect/ consultat la 19.06.2018

Anexe

Anexa nr. 1: Diagrama bazei de date

Similar Posts