Sunteți pe pagina 1din 82

Lucrarea nr.

1 Medii Avansate de programare 2011

Ce este ASP MVC 2.0?


ASP MVC 2 este un framework destinat dezvoltrii aplicaiilor web propus de Microsoft i integrat n pachetul Microsoft Visual Studio 2010 care combin eficacitatea i stilul organizat de programare ce caracterizeaz arhitecturile Model-View-Controller. Cteva dintre avantajele pe care ASP MVC 2.0 le aduce n comparaie cu ASP Web Forms sunt: Permite dezvoltarea aplicaiilor web prin metode AGILE i Test-Driven Development; Furnizeaz instrumente care permit Unit Testing; Utilizarea pattern-ului MVC n aplicaiile web organizeaz foarte bine codul pe cele trei nivele ale arhitecturii: View (user interface), Controllers(user actions), Models(structuri de date); Impune un anumit ciclu de execuie a fiecarei funcionaliti: utilizatorul face o aciune iar ca i rspuns la aceast aciune, aplicaia schimb starea modelelor de date i servete un view updatat cu rezultatele cerute de utilizator; Extensibilitate uoar dac design patternul este respectat ntreinerea i dezvoltarea unei aplicaii MVC este foarte uoar i se face cu modificri minime n codul existent; Control asupra HTML i HTTP reducerea considerabil a cantitii de cod HTML prin utilizarea unor HTML Helpere (ex. Html.TextBox, Html.TextBoxFor) care n special n cadrul formularelor web fac legarea automat a cmpurilor din formular cu atributul modelului corespondent; Control uor i eficient a traficului prin HTTP la nivel de controller prin utilizarea unor directive (ex. [HttpPost], [HttpRequest]); Securitate mbuntit i sistem de autenticare la nivel de controller i la nivel de aciune bazat pe utilizatori i roluri, extensibil i customizabil; Testare i debugging uor, posibiliti de testare automat; Integrarea perfect cu CSS, Javascript, JSON, Ajax, Jquery; Manipulare uoar a request-urilor i a postback-urilor prin ajax; Sistem de rutare robust i SEO Friendly: in loc de www.domeniu.com/articole.aspx?cat=2&idart=213&pag=12 => www.domeniu.com/articole/categoria_2/articolul_213/pag_12 prin simpla specificare a unor reguli de rutare; Validarea uoar a datelor introduse de utilizator prin ajax la nivel de server validarea modelelor de date cu ajutorul unor atribute de validare care reduc foarte mult munca programatorului pentru c acesta nu mai este nevoit s scrie blocuri IF foarte multe;

Lucrarea nr. 1 Medii Avansate de programare 2011


Hello World from ASP MVC
1. Vom lucra n Microsoft Visual Web Developer 2010 ce poate fi descrcat gratuit de aici: http://www.microsoft.com/visualstudio/en-us/products/2010-editions/visual-web-developerexpress 2. Se vor parcurge aspectele eseniale legate de dezvoltarea aplicaiilor web utiliznd aceast platform prin construirea sistematic a unui magazine virtual; 3. Deschidei Microsoft Visual Web Developer 2010, File>New Project; n stnga alegei Visual C#>Web ASP.NET MVC 2 Web Application; Apoi setati numele proiectului si calea pe disc; 4. Noul proiect este creat iar in Solution Explorer este creata structura generala a unui astfel de proiect pe care o vom analiza in cele ce urmeaza.

Baze de date locale ale aplicatiei ce nu ruleaza pe un server BD Client-side content aici se localizeaza fisierele cu foile de stil CSS Clasele Controller ce descriu actiunile utilizatorului Clasele model ce descriu si furnizeaza acces la sursele de date ale aplicatiei (in general baze de date) Scripturi si librarii client-side cu cod Javascript View-urile care descriu interfata cu utilizatorul

Contine definitia regulilor de rutare si lanseaza in executie aplicatia Diverse configurari definite in limbaj XML

Figura 1 Structura generala a unui proiect MVC


Observatii: a) Observam anumite conventii de nume legate de numele de clase si fisiere ce contin aceste clase: clase controller au in denumire sufixul Controller similar si clasele model au in denumire sufixul -Models; 2

Lucrarea nr. 1 Medii Avansate de programare 2011


b) Este indicat ca modelele si controllerele sa fie definite cate una pe fisier iar fisierul sa aiba aceiasi denumire cu clasa; c) Fiecarui director din structura unei aplicatii MVC ii corespunde un namespace: Models projectname.Models, Controllers projectname.Controllers,. d) Programatorul poate sa mai adauge directoare noi in functie de nevoi creand implicit si namespace-uri noi corespunzatoare directoarelor noi create; e) Orice proiect MVC trebuie sa aiba in mod obligatoriu controllerul Home care va fi punctul de start al oricarei aplicatii web ASP MVC; f) Fiecare Controller trebuie sa aiba actiunea Index; g) Se observa si corespondenta logica dintre denumirea subdirectoarelor folderului Views si denumirea claselor Controller; Fiecarei clase controller i se creaza un director cu acelasi nume ca si al controllerului unde se vor salva toate view-urile gestionate de actiunile controllerului respectiv; Exceptie face folderul Shared care contine elemente comune tuturor View-urilor aspecte ce vor fi discutate mai tarziu; h) Controllerul Account vine o data cu template-ul proiectului si se refera la framework-ul de login al utilizatorilor;

Pentru a continua exemplul nostru Hello World: 5. Cream un nou model prin click dreapta pe directorul Models>Add>Class clasa pe care o denumim HelloModel.cs 6. In clasa HelloModel scriem urmatorul cod:

Lucrarea nr. 1 Medii Avansate de programare 2011


7. In clasa controller HomeController pe langa codul implicit adaugat de mediul de dezvoltare adaugam o actiune noua Helloworld:

8. Dam un Build la proiect (Debug>Build) pentru a inregistra modelul nou creat in proiect. 9. Urmatorul pas este acela de a crea un View pentru actiunea HelloWorld si anume View-ul cu acelasi nume ca si al actiunii HelloWorld.aspx. 10. Pentru a crea view-ul dam click dreapta oriunde in interiorul definitiei actiunii HelloWorld si alegem din meniul contextual deschis Add View. 11. Ni se va deschide o noua fereastra ca si cea de mai jos, apoi actionam butonul Add, care va adauga in directorul corespondent controllerului (Views/Home) fisierul HelloWorld.aspx care contine codul View-ului. Motivul pentru care am facut Build inainte de a crea View-ul este acela de a inregistra modelul nou creat pentru a putea fi vizibil in lista View Data class in cazul in care cream un View stronglytyped;

Lucrarea nr. 1 Medii Avansate de programare 2011

Numele View-ului
Un view strongly-typed este un view care isi incarca toatele datele dintrun anumit model. Din lista View data class se alege modelul sursa al View-ului Master page este practic sablonul general care defineste structura generala a oricarei pagini din site.Deschideti fisierul Views/Shared/Site.Master ContentPlaceHolderId este identificatorul containerului din MasterPage unde va fi incarcat View-ul nou

HelloWorld.aspx

Site.Master

Lucrarea nr. 1 Medii Avansate de programare 2011


Observatii: 1. Intr-o exprimare improprie relatia dintre un Model si un strongly-type view este similara cu cea de mostenire a claselor. Adica toate atributele modelului si metodele acestuia sunt vizibile si apelabile din interiorul unui View prin sintaxa Model.atribut sau Model.metoda(); Apelarea unei metode a unui model dintr-un View nu este recomandata deoarece nu respecta rigorile de organizare a codului impuse de pattern-ul de MVC. Accesarea membrilor unui model dintr-un view este recomandata pentru incarcarea datelor din model in View. 2. Marcatorii de cod executabil dintr-un View sunt de doua feluri: <%:-marcator care permite afisarea unor date in View (similar cu echo din PHP) si <% - marcator care nu trimite la iesire valori dar care marcheaza blocuri de cod executabil (similar cu <?PHP); 3. Relatia dintre o un master view si un View normal poate fi ilustrata astfel: Master Header Menu View3
ContentPlace Holder2 ContentPlaceHolder1

View 1

View 2 Footer

4. La crearea View-rilor avem lista View content care specifca tipul View-ului care va fi creat. In cazul nostru am creat un view Empty deoarece nu am adaugat o functionalitate foarte spectaculoasa. Astfel view-urile sunt de mai multe feluri in functie de functionalitatea pe care acestea o ofera; dintre cele mai uzuale amintim: Create specifica view-urile cu formulare care colecteaza date de la utilizator, Delete-elimina itemi din baza de date, Editcontine formulare de editare a datelor din BD, Details afiseaza in mod detaliat itemi din BD, List-afiseaza itemi sub forma de liste; 5. View-urile strongly-type fata de cele simple realizeaza legarea automata de model atat a zonelor de afisare cat si a campurilor cuprinse in formulare pe cand cele simple nu fac asta iar modelul trebuie incarcat manual de catre programator in actiunea din Controller accesand datele din variabila globala POST (aici FormCollection) si atribuindu-le atributelor modelului. 6

Lucrarea nr. 1 Medii Avansate de programare 2011


Pentru a rula proiectul nostru se actioneaza butonul Start debugging sau F5. O instanta de test IIS se va porni pe un port aleator care va afisa prima pagina a proiectului (URL: localhost:1048).De fapt se va apela controllerul Home (punctul de intrare in aplicatie) si se va apela actiunea Index a controllerului care va face rendering la view-ul Index.aspx din directorul Home. 1. Pentru a vizualiza rezultatul a ceea ce am facut pana acum vom introduce urmatorul URL in browser: http://localhost:port/Home/HelloWorld . 2. Observam ca apelul codului se face prin URL dupa principiul: server:port/Controller/Actiune/ mai putem avea si server:port/Controller caz in care se va apela actiunea Index() a controllerului; sau cel mai simplu server:port/ caz in care se va apela controllerul Home, actiunea Index(); 3. Tot acest format predefinit al URL-ului se defineste prin regulile de rutare definite in Global.asax si sunt customizabile, acesta fiind formatul implicit; 4. Un aspect important este crearea legaturilor dinte pagini care nu vor mai fi link-uri HTML href catre fisiere ci vor fi URI care vor apela actiuni ale unor controlleri dupa cum vom vedea mai tarziu. Actiuni cu parametrii 1. Adaugati in controller-ul Home urmatoarea actiune:

2. Creati un nou View de tip strongly-typed pe baza modelului HelloWorld de tip Empty pentru actiunea de mai sus la fel ca mai inainte. In folderul Home din Views ar trebui sa aveti Hello.aspx. In View-ul nou creat adaugati urmatorul cod:

3. Rulati aplicatia si introduceti URL-ul urmator in browser: http://localhost:[nrport]/Home/Hello?nume=John 7

Lucrarea nr. 1 Medii Avansate de programare 2011


Legaturi in MVC 1. In View-ul Home/Index.aspx vom crea un link catre actiunea HelloWorld a controllerului Home astfel:

Textul legaturii

Actiunea

Controllerul

Aici se apeleaza actiunea HelloWorld care nu are parametri. 2. Pentru a crea un link care sa apeleze actiunea Hello a controllerului Home care necesita parametrul nume vom adauga in View-ul Home/HelloWorld.aspx urmatorul cod: 3. Rulati

Textul legaturii

Actiunea

Controllerul

Valorile parametrilor

4. Rulati aplicatia si testati noile link-uri. Arhitectura generala a unei aplicatii ASP.MVC

Global.asax: URL Rewriting (Route Maps) Views Helpers (Optional) Controllers ViewModels(Optional) Models Repository Connection Layer BD

Lucrarea nr. 1 Medii Avansate de programare 2011


Observatii: 1. Baza de date poate fi locala sau remote pe un server de baze de date; 2. Connection Layer este cel care mentine conexiunea cu sursa de date si poate fi invizibil programatorului daca se lucreaza pe tehnologii LINQ sau ORM sau vizibil daca se lucreaza cu DataSet (acesta fiind chiar stratul de conexiune). 3. Modelele pot fi create de programator ca si mai sus sau daca se lucreaza cu LINQ to SQL clasele generate de catre DataContext vor fi modelele. Daca se lucreaza cu DataSet clasele model se vor crea de catre programator. 4. ViewModels este un strat optional si poate fi creat la optiunea utilizatorului atunci cand vrea sa obtina View-uri strongly-typed pe baza mai multor modele. Practic un ViewModel agrega mai multe modele intr-unul unitar care poate devein model sursa pentru un View. 5. Controllers aici se realizaeaza autenticarea utilizatorilor si se definesc actiunile; 6. Helpers-sunt clase care usureaza manipularea codului HTML si ASP din View-uri. Exista Helpere furnizate de catre platforma cum ar fi clasa Html de unde am folosit metoda ActionLink pentru a crea legaturi dar programatorul poate sa isi defineasca propriile Helpere daca are nevoie. Executia unei aplicatii MVC 2 Controller Action 3 7 6 View

URL

URL Rewriting

BD 5

Model

Lucrarea nr. 2 Medii Avansate de programare 2011

Crearea unui magazin virtual in ASP MVC 2


Etapele realizarii unui proiect MVC sunt: 1. Proiectarea si maparea bazei de date; 2. Crearea modelelor; 3. Stabilirea si implementarea regulilor de validare a modelelor (implicit a datelor culese de la utilizatori); 4. Stabilirea si implimentarea actiunilor si a controllerelor; 5. Crearea View-urilor; 6. Testare si optimizare; Vom realiza un magazin online de carti echipat cu tot ceea ce inseamna un magazin online: conturi pentru utilizatori, cos de cumparaturi, prezentarea produselor, comanda si plata produselor, panoul de administrare al magazinului virtual cu adaugarea de noi categorii, noi produse, vizualizarea comenzilor; Pentru aceasta vom incepe un nou proiect ASP MVC 2 (File>New Project>ASP MVC 2 Application;

Crearea Bazei de date


Pentru crearea bazei de date vom utiliza SQL ServerExpress inclus in Microsoft Visual Developer Express. 1. Click dreapta pe folderul App_Data in solution Explorer > Add> New item>Sql Server Database si introducem numele viitoarei baze de date magazine.mdf; Actionam butonul Add. 2. O noua baza de date este adaugata in folderul App_Data si daca dam click dreapta pe baza de date si alegem Open in Database Explorer se va deschide baza de date cu structura de date obisnuita unei baze de date din SQL Server. 3. Adaugarea unei noi tabele se face prin click dreapta pe sectiunea tables a BD in Database Explorer si alegerea Add new table, urmata de specificarea coloanelor tabelei si a tipurilor de date; 4. Setarea unui camp ca si cheie primara se face prin click dreapta pe respectivul camp si alegerea Set Primary Key; 5. Setarea unui camp de tip int (in general cheia primara) ca si auto increment se face in sectiunea Column Proprties, proprietatea Identity Specification setata cu valoarea Yes.

Lucrarea nr. 2 Medii Avansate de programare 2011

Propunem urmatoarea structura a bazei de date

Lucrarea nr. 2 Medii Avansate de programare 2011


6. Dupa crearea bazei de date este foarte important specificarea relatiilor dintre tabele. Astfel identificam relatiile: Tabela parinte Nume tabela Cheia primara Clienti idclient Localitati idlocalitate Judete idjudet Produse idprodus Categorii idcategorie Edituri ideditura Tabela copil Nume tabela Comenzi Clienti Localitati Comenzi Produse Produse Cheia straina idclient idlocalitate idjudet idprodus idcategorie ideditura

Specificarea relatiilor dintre tabele se face astfel: a) Se deschide fiecare tabela copil. Se face click pe butonul Relationships de pe bara cu instrumente: b) Apare fereastra de editare a relatiilor se executa Add iar in lista din dreapta se face click pe butonul de la Tables And Columns Specific:

c) Se deschide fereastra de specificare a relatiei unde se alege tabela parinte si cheia primara si tabela copil si cheia primara: In exemplul de mai jos exemplificam relatia dintre tabela Comenzi si clienti. Se recomanda ca relatiile sa fie sugestiv denumite (FK_copil_parinte) pentru a putea fi usor identificate.

Lucrarea nr. 2 Medii Avansate de programare 2011

d) Se specifica tabela parinte si campul de cheie straina din tabela curenta. Se da ok. Daca tabela are mai multe chei straine se executa din nou Add si se repeat procesul. e) Popularea tabelelor cu date se face prin click dreapta pe un tabel din Database Explorer>Show table data;

Crearea modelelor
In terminologia MVC un model se refera la obiectele care reprezinta datele si structurile de date exploatate de catre aplicatie precum si logica domeniului de activitate al aplicatiei impreuna cu regulile de validare a datelor si logica de business. ASP MVC suporta o gama variata de variante de conectivitate la bazele de date. Pentru proiectul nostru vom utiliza LINQ to SQL pentru a crea usor si rapid modelele proiectului. Practic structura domeniului modelelor trebuie sa se asemene foarte mult cu structurile de date exploatate de catre aplicatie. 1. Pentru a genera clasele Linq to SQL se face click dreapta pe Models>Add>New item si se selecteaza din sectiunea Data LINQ to SQL clases; denumim noul item magazin.dbml; 2. Din Database Explorer se face drag and drop pentru fiecare tabel pe suprafata Object Relational Database Designer, generandu-se astfel clasele LINQ to SQL; 3. Daca toate relatiile intre tabele au fost corect setate in Designer-ul Object Relational ar trebui sa obtinem schema bazei de date:

Lucrarea nr. 2 Medii Avansate de programare 2011

4. In acest moment .NET a generat clase LINQ pentru obiectele din baza de date, ceea ce ne va permite sa utilizam structurile de date ca si obiecte. Practic magazin.dbml contine definitia unui DataContext si anume magazinDataContext. Tabelele devin atribute ale obiectului magazinDataContext (sunt obiecte agregate de catre magazinDataContext). Campurile devin atribute ale obiectelor ce mapeaza tabelele iar relatiile dintre tabele de tip parinte-copil devin relatii de agregare de tip tabela parinte agrega tabela copil (deci tabela copil se poate accesa astfel: Parinte.copil.camp); 5. Pentru a exploata aceste structure de date (Modele) avem nevoie de niste metode care sa introduca si extraga date din aceste structuri. Astfel vom utiliza pattern-ul de Repository si vom crea pentru fiecare obiect tabela in parte cate o clasa repository care va contine metode de interogare a structurii de date. 6. Exemplificam procedeul pentru tabela clienti creand clasa clientiRepository in Models care contine urmatorul cod:

Lucrarea nr. 2 Medii Avansate de programare 2011

Observatii: a) Este recomandat ca fiecare dintre tabelele exploatate de catre baza de date sa aiba un astfel de Repository care manipuleaza datele. b) Fiecare clasa Repository ar trebui sa fie echipata cel putin cu urmatoarele functionalitati: extragerea unei inregistrari dupa cheia primara, stergerea unei inregistrari, adaugarea unei noi inregistrari, si in functie de nevoi extragerea tuturor inregistrarilor din tabela plus alte functionalitati si interogari mai complexe pe care logica aplicatiei le impune. c) IQueryable <> este o interfata ce implementeaza un provider de interogari. Mosteneste clasa IEnumerable deci furnizeaza o interfata catre o colectie de inregistrari LINQ. Acestei colectii I se poate atasa un iterator iar colectia poate fi iterata asa cum se demonstreaza in actiunea ShowClients() din controllerul Test. d) d=>d.idclient==idclient este o expresie lambda (functie anonima care poate contine expresii si se construieste cu operatorul => care se citeste goes to). In Linq to Sql se foloseste pentru a crea predicate LINQ echivalente cu o interogare WHERE; aici expresia are semantica SELECT * FROM clienti WHERE idclient=idclient; 6

Lucrarea nr. 2 Medii Avansate de programare 2011


e) Observam metodele standard ale obiectelor tabel pentru manipularea datelor: InsertOnSubmit, DeleteOnSubmit, DeleteAllOnSubmit, SubmitChanges; In continuare pentru a testa functionalitatea Repository-ului nostru este de recomandat sa facem un controller Test care va contine actiuni ce vor testa fiecare functionalitate a clasei Repository. 1. Click dreapta pe Controllers> Add > Controller TestController; 2. Adaugam urmatorul cod pentru a testa repository-ul clienti; 3. Pentru a evita specificarea namespace-ului Models de fiecare data cand utilizam o clasa din Models vom introduce in controller using MagazinVirtual.Models; 4. Cream un View empty simplu (nu strongly-typed) pentru Controllerul Test (click dreapta in Controller>Add View) in care adaugam codul <%: ViewData[msg] %> pentru a vedea mesajul de testare. 5. Introducem urmatorul cod in clasa controllerului Test:

6. Pentru a testa rulam proiectul si introducem URL ul de apel pentru fiecare actiune de testare urmarind apoi continutul si modificarile in baza de date.

Lucrarea nr. 2 Medii Avansate de programare 2011


//URL: localhost:1046/Test/InsertClient public ActionResult InsertClient() {//introduce doi clienti noi in bd Models.clientiRepository clientirepo = new Models.clientiRepository(); //creaza instantele de clienti clienti newclient = new clienti(); newclient.nume = "Smith"; newclient.prenume = "John"; newclient.idlocalitate = 2; newclient.adresa = "Str. Test 21"; newclient.email = "johnsmith@email.com"; newclient.parola = "john"; clienti newclient1 = new clienti(); newclient1.nume = "Anne"; newclient1.prenume = "Jonson"; newclient1.idlocalitate = 2; newclient1.adresa = "Str.W21 Nr 3"; newclient1.email = "annej@email.com"; newclient1.parola = "annej"; //adauga clientii la repository clientirepo.AddClient(newclient); clientirepo.AddClient(newclient1); //salveaza schimbarile clientirepo.Save(); ViewData["msg"] = "Done!"; return View("Test"); } //URL: localhost:1046/Test/UpdateClient public ActionResult UpdateClient() {//face update la clientul cu idclient=1 clientiRepository clientirepo = new clientiRepository(); //incarca clientul cu id-ul 1 clienti cl = clientirepo.GetClientById(1); //face schimbarile cl.nume = "Jay"; cl.prenume = "Samuel"; //salveaza schimbarile clientirepo.Save(); ViewData["msg"] = "Updated!"; return View("Test"); } //URL: localhost:1046/Test/DeleteClient public ActionResult DeleteClient() {//sterge clientul cu idclient=2 clientiRepository clientirepo = new clientiRepository(); //incarca clientul clienti cl = clientirepo.GetClientById(2); //sterge clientul clientirepo.DeleteClient(cl); //salveaza schimbarile clientirepo.Save(); ViewData["msg"] = "Deleted!"; return View("Test"); }

Lucrarea nr. 2 Medii Avansate de programare 2011


Aplicatie: Continuati proiectul adaugand cate un repository class pentru fiecare dintre cele 6 tabele ramase.

Lucrarea nr. 3 Medii Avansate de programare 2011


Lucrarea curenta isi propune sa trateze crearea de formulare web, afisarea datelor in aplicatii MVC , validarea datelor introduse de utilizator precum si actiunile controllerului aferente acestora. Vom lucra pe proiectul inceput in lucrarea 2.

Introducerea datelor prin formulare


In orice aplicatie web, datele de la utilizatori se colecteaza prin intermediul formularelor web. Un aspect important il reprezinta validarea datelor. 1. Validarea datelor MVC 2 ofera mai multe modalitati de validare a datelor. Vom incerca sa cream un formular de introducere a datelor in tabela clienti. Pentru aceasta vom exploata modelul clienti si clasa clientiRepository.Cream in Models o clasa validator pentru modelul clienti :
//validator custom la nivel de model - password matching [parolaValidator(ErrorMessage = "Parola si confirmarea nu coincid")] public class clientValidator { [Required(ErrorMessage="Nume neintrodus!")]//validare pentru campuri necompletate //validare lungime string (minim=3, maxim=50) [StringLength(50,MinimumLength=3,ErrorMessage="Numele trebuie sa aiba intre 3 si 50 caractere")] public string nume { get; set; } [Required(ErrorMessage="Prenume neintrodus!")] [StringLength(50,MinimumLength=3,ErrorMessage="Prenumele trebuie sa aiba intre 3 si 50 caractere")] public string prenume { get; set; } [Required(ErrorMessage="Adresa neintrodusa!")] [StringLength(100,MinimumLength=7,ErrorMessage="Adresa trebuie sa aiba intre 7 si 100 caractere")] public string adresa { get; set; } [Required(ErrorMessage="Localitate neselectata")] [Range(1,200,ErrorMessage="Localitate invalida!")]//domeniul de valori din care isi poate lua valoarea un camp numeric public int idlocalitate { get; set; } [Required(ErrorMessage="E-mail neintrodus!")] [StringLength(50,MinimumLength=3,ErrorMessage="E-mail neintrodus")] //regular expressions [RegularExpression("^[a-z0-9_\\+-]+(\\.[a-z0-9_\\+-]+)*@[a-z0-9-]+(\\.[a-z0-9]+)*\\.([a-z]{2,4})$", ErrorMessage = "Adresa de e-mail este invalida!")] //validator custom la nivel de camp [EmailValid(ErrorMessage="E-mailul trebuie sa fie @yahoo sau @gmail!")] public string email { get; set; }

Autori: Nicolae Tomai, Alexandru Butoi

Lucrarea nr. 3 Medii Avansate de programare 2011


[Required(ErrorMessage="Parola neintrodusa!")] [StringLength(250,MinimumLength=4,ErrorMessage="Parola trebuie sa aiba intre 4 si 250 de caractere!")] public string parola { get; set; } [Required(ErrorMessage = "Confirmarea parolei neintrodusa!")] [StringLength(250, MinimumLength = 4, ErrorMessage = "Parola trebuie sa aiba intre 4 si 250 de caractere!")] public string confirmare { get; set; } } //validator custom la nivel de camp public sealed class EmailValid : ValidationAttribute { //suprascrierea IsValid public override bool IsValid(object value) { if (value != null) { if (value.ToString().Contains("yahoo") || value.ToString().Contains("gmail")) { return true; } else return false; } else return false; } //validator custom la nivel de model public sealed class parolaValidator : ValidationAttribute { public override bool IsValid(object value) { if (value != null) { PropertyDescriptorCollection atribute = TypeDescriptor.GetProperties(value); string parola = atribute["parola"].GetValue(value).ToString(); string confirmare = atribute["confirmare"].GetValue(value).ToString(); if (parola != confirmare) return false; return true; } else return false; } }

Autori: Nicolae Tomai, Alexandru Butoi

Lucrarea nr. 3 Medii Avansate de programare 2011


Explicatii: 1. O clasa validator este o clasa ce contine doar atribute. Aceste atribute mapeaza campurile formularului in care se vor introduce date. De exemplu daca avem un formular ce contine capurile nume si prenume, validatorul asociat va avea ca si atribute nume de tip string si prenume de tip string. 2. Prin modalitatea de validare a datelor descrisa mai sus se observa folosirea asa-numitelor atribute de validare care se specifica intre [] deasupra fiecarui membru al clasei validator. 3. Pentru a utiliza aceste facilitati specifice MVC se folosesc namespace-urile:
//namespace-ul pentru specificarea regulilor de validare using System.ComponentModel.DataAnnotations using System.ComponentModel; using System.Web.Mvc;

4. .NET ofera cateva astfel de atribute de validare cum ar fi: Required(verifica daca un camp este lasat gol si trimite un mesaj de eroare in caz afirmativ), StringLength(verifica lungimea minima sau maxima a unui sir introdus intr-un camp textual. Daca nu corespunde arunca mesaj de eroare), Range(validator pentru campuri numerice si verifica daca numarul introdus se incadreaza intr-un interval specificat); 5. De asemenea se pot defini atribute de validare Custom in functie regulile de validare specific domeniului si aplicatiei utilizate: a. Validatoarele custom pot fi facute la nivel de camp atunci cand se refera la datele dintr-un camp. Exemplu de aici este EmailValid care verifica daca e-mailul este pe yahoo sau pe gmail; b. Validatoarele custom mai pot fi la nivel de model atunci cand implementeaza reguli de validare care implica relatii dintre 2 sau mai multe campuri. Exemplul de aici verifica daca parola introdusa coincide cu confirmarea parolei. c. Ordinea de executie a atributelor de validare este: intai se fac validarile la nivel de camp, apoi se fac validarile la nivel de model; 6. Implementarea unui validator custom se face prin declararea unei clase public sealed (nu poate fi mostenita) care mosteneste clasa ValidationAttribute. Implementarea logicii de validare sa face prin overriding pentru metoda IsValid(object value) care trebuie sa returneza true daca modelul e valid si false daca nu e valid. a. La momentul validarii unui camp/model acesta (care este de fapt o instanta a unui obiect) se va trimite ca si argument in metoda IsValid suprascrisa (prin argumentul de tip object) si se va executa logica de validare asupra lui. b. La validatorii implementati la nivel de camp argumentul de tip object contine valoarea introdusa in camp si supusa validarii; c. La validatorii implementati la nivel de model (se mai numesc si cross-attribute-validation) este nevoie de utilizarea unor descriptori ai instantelor pentru accesarea campurilor supuse validarii. 7. ErrorMessage specifica mesajul trimis utilizatorului in cazul in care datele introduse sunt invalide. 8. ATENTIE! Pentru ca mecanismul de UpdateModel sa functioneze pentru validator toate atributele trebuie sa aiba setate {get; set;}-accesori si mutatori;

Autori: Nicolae Tomai, Alexandru Butoi

Lucrarea nr. 3 Medii Avansate de programare 2011


2. Actiunile controller-ului de incarcare a formularului si colectare a datelor Pentru incarcarea View-ului care contine formularul de colectare a datelor si colectarea datelor din formular se creaza doua actiuni in controller-ul Home.

private clientiRepository clientRepo = new clientiRepository(); //formular introducere client nou (incarcare formular nou) [HttpGet] public ActionResult CreareUtilizator() { clienti client = new clienti(); //incarca lista de localitati ViewData["localitati"]= clientRepo.GetLocalitati(); return View("CreareUtilizator",client); } //incarcare date din formular, procesare date introduse [HttpPost] public ActionResult CreareUtilizator(FormCollection form) { clienti client=new clienti(); clientValidator cValidator= new clientValidator(); //pt ca membrul confirmare parola nu exista va trebui incarcat manual in Validator cValidator.confirmare = form["confirmare"];//se ia din argumentul form care contine valorile campurilor din formular //incarca din nou lista de localitati - pt a evita erorile ViewData["localitati"] = clientRepo.GetLocalitati(); try { UpdateModel(cValidator);// incarcare date si validare(daca exista erori sare la catch) UpdateModel(client); //daca e valid incarca in Linq class clientRepo.AddClient(client);//incarcarea in repository clientRepo.Save();//salvarea pentru persistenta ViewData["msg"] = "Date salvate cu succes!"; return View(client); } catch { ViewData["msg"] = "Datele nu au fost salvate!"; return View(client); } }

Explicatii 1. Pentru a avea acces la functionalitatile implementate in clientiRepository am declarat un membru privat care sa ofere acces la clienti repository. 2. Se observa ca prima actiune a controllerului are specificat meta-atributul [HttpGet] iar a doua [HttpPost]. Acestea au rolul de a defini prin ce verb HTTP vor fi primite/trimise datele de la sau inspre client. 4 Autori: Nicolae Tomai, Alexandru Butoi

Lucrarea nr. 3 Medii Avansate de programare 2011


3. Cele 2 metode au acelasi identificator dar semnatura lor difera la nivelul parametrilor (supraincarcare pe argumente). Astfel prima metoda (CreareUtilizator() )este cea care incarca formularul gol de introducere a datelor prin GET. 4. A doua metoda (CreareUtilizator(FormCollection form)) este cea care primeste datele introduse (prin POST) atunci cand utilizatorul face submit si le incarca mai departe in validator si in model. Argumentul de tip FormCollection este o matrice asociativa care contine datele introduse in formular (asemanator cu $_POST din PHP); 5. In prima metoda se instantiaza modelul exploatat (clienti), si se incarca in ViewData lista localitatilor disponibile in baza de date in tabela localitati. Pentru aceasta am implementat in clientiRepository metoda GetLocalitati() care returneaza un obiect de tip SelectList care se va incarca in formular in DropDownList ul de alegere a localitatii.
//extrage lista de localitati public IEnumerable<SelectListItem> GetLocalitati() { IQueryable<localitati> loc = db.localitatis; SelectList lst = new SelectList(loc, "idlocalitate", "localitate"); return lst; }

6. In a doua metoda aceasta metoda este reinvocata deoarece formularul se reincarca si altfel s-ar arunca exceptie ca nu se gasesc datele care trebuie incarcate in lista. 7. A doua metoda functioneaza astfel: a. Primeste datele dintr-un formular strongly-type creat pe baza modelului clienti; instantiaza modelul; instantiaza validatorul asociat; b. Deoarece modelul (clienti) nu contine campul confirmare, el fiind doar la nivel de formular va trebui in mod explicit sa incarcam valoarea campului confirmare in validator (cValidator.confirmare=form[confirmare], unde cheia confirmare va reprezenta numele campului de confirmare a parolei din View); c. Incarcam din nou localitatile disponibile; Mecanismul validarii si salvarii datelor functioneaza astfel: In structura try..catch se apeleaza UpdateModel(cValidator) care incarca datele din formular in atributele validatorului; In acest moment se face si validarea specificata prin ValidationAttribute in clasa validator. Daca datele sunt valide se trece mai departe la incarcarea datelor in modelul clienti si salvarea lor in baza de date, daca datele contin erori UpdateModel(cValidator) va arunca o exceptie care va conduce firul de executie pe ramura catch unde va incarca erorile de validare in View. In cazul in care datele nu sunt valide, saltul se va produce automat iar apelurile de stocare a datelor nu vor mai fi invocate; Prin utilizarea atributelor de validare si a View-urilor strongly-typed se usureaza munca de programare pentru ca incarcarea detelor in model/validator se face in mare parte

Autori: Nicolae Tomai, Alexandru Butoi

Lucrarea nr. 3 Medii Avansate de programare 2011


automat , validarea datelor se specifica fara prea multe blocuri if iar afisarea mesajelor de eroare se face cu effort minim din partea programatorului. 3. Crearea View-ului cu formularul de adaugare 1. Click dreapta pe prima metoda a controllerului CreareClient()> Add View; 2. Bifam Create strongly-typed view, in lista ViewData Class selectam modelul MagazinVirtual.Models.clienti , View Content: Create; click pe Add. 3. Astfel in Views/Home ni se creaza View-ul CreareUtilizator.aspx, care are generat urmatorul formular:
<h2>CreareUtilizator</h2> <% Html.EnableClientValidation(); %> //(1)-afisare mesaje eroare prin AJAX (validare la nivel de camp) <% using (Html.BeginForm()) {%> <%: Html.ValidationSummary(true) %> //(2) afisare mesaje de eroare validare la nivel de model <fieldset> <legend>Fields</legend> <!--<div class="editor-label"> <%: Html.LabelFor(model => model.idclient) %> </div> <div class="editor-field"> <%: Html.TextBoxFor(model => model.idclient) %> <%: Html.ValidationMessageFor(model => model.idclient) %> </div> --> (3) eliminat (idclient e cheie primara si e gestionata de BD) <div class="editor-label"> <%: Html.LabelFor(model => model.nume) %> </div> <div class="editor-field"> <%: Html.TextBoxFor(model => model.nume) %> <%: Html.ValidationMessageFor(model => model.nume) %> </div> <div class="editor-label"> <%: Html.LabelFor(model => model.prenume) %> </div> <div class="editor-field"> <%: Html.TextBoxFor(model => model.prenume) %> <%: Html.ValidationMessageFor(model => model.prenume) %> </div> <div class="editor-label"> <%: Html.LabelFor(model => model.adresa) %> </div> <div class="editor-field"> <%: Html.TextBoxFor(model => model.adresa) %> <%: Html.ValidationMessageFor(model => model.adresa) %> </div>
<div class="editor-label"> <%: Html.LabelFor(model => model.idlocalitate) %></div> <div class="editor-field"> <%: Html.TextBoxFor(model=>model.idlocalitate) %> <%: Html.ValidationMessageFor(model => model.idlocalitate) %> </div> (4) transformat in ListBox [eliminat si introdus (5) in loc]

<div class="editor-label">

Autori: Nicolae Tomai, Alexandru Butoi

Lucrarea nr. 3 Medii Avansate de programare 2011


<%: Html.LabelFor(model => model.localitati.localitate) %> </div> <div class="editor-field"> <%: Html.DropDownListFor(model=>model.idlocalitate,ViewData["localitati"] as IEnumerable<SelectListItem>) %> <%: Html.ValidationMessageFor(model => model.idlocalitate) %> </div> //(5)-introducerea codului pentru lista cu localitati [in locul lui (4)] <div class="editor-label"> <%: Html.LabelFor(model => model.email) %> </div> <div class="editor-field"> <%: Html.TextBoxFor(model => model.email) %> <%: Html.ValidationMessageFor(model => model.email) %> </div> <div class="editor-label"> <%: Html.LabelFor(model => model.parola) %> </div> <div class="editor-field"> <%: Html.PasswordFor(model => model.parola) %> //(6)-transformare din TextBoxFor in obiect PasswordFor <%: Html.ValidationMessageFor(model => model.parola) %> </div> <div class="editor-label"> <%: Html.Label("confirmare") %> </div> <div class="editor-field"> <%: Html.Password("confirmare") %> </div> //(7)-Introducerea codului pentru casuta de confirmare parola <%: ViewData["msg"] %> //(8)-afisarea mesajul de confirmare trimis din actiunea controller-ului <p> <input type="submit" value="Create" /> </p> </fieldset> <% } %>

Explicatii: 1. La crearea unui View Create de tip strongly-type se genereaza un view care mapeaza atributele modelului (necesitatile informationale ale modelului); 2. Ca si mai sus, de cele mai multe ori, acest View trebuie putin rafinat pentru a corespunde cerintelor aplicatie noastre: (1)Am adaugat <% Html.EnableClientValidation();%> (Verde) care permite afisarea mesajelor de eroare prin AJAX; Tot in acest context observam ca fiecare obiect al formularului este insotit de catre un obiect Html.ValidationMesageFor care afiseaza mesajul de eroare aferent ficarui camp in urma validarii, daca am activat validarea pe parte de client; (2)Am setat ValidationSummary(true) care afiseaza erorile la generate de validarile la nivel de model; 7 Autori: Nicolae Tomai, Alexandru Butoi

Lucrarea nr. 3 Medii Avansate de programare 2011


(3) Am eliminat campul generat pentru idclient deoarece acesta e cheie primara si e gestionata la nivelul bazei de date; (4) Am eliminat textbox-ul generat pentru idlocalitate si am pus in locul lui pe (5) (5) Am creat un dropdownlist care permite selectarea unei localitati; (6) Am transformat textbox-ul creat pentru parola in obiect potrivit pentru introducerea de parole; (7) Pentru ca modelul clienti nu are si campul confirmare l-am adaugat manual dar nelegat de model; (8) Am afisat mesajul de confirmare transmis de catre actiunea care proceseaza datele a controllerului ; 3. Astfel observam ca intr-un strongly-typed View formularele generate sunt automat legate de model prin Helperele de tip ObjectFor, care permit utilizarea mecanismului de incarcare automata a datelor in mlodel si validator prin UpdateModel; 4. Astfel ordinea de executie a acestui formular de introducere de client nou este: Se apeleaza metoda CreareUtilizator() care incarca formularul gol de introducere a datelor, iar cand utilizatorul face submit se apeleaza metoda CreareUtilizator(FormColection form) care colecteaza datele din formular le valideaza si le introduce in BD; Un ultim pas pentru a asigura acces la aceasta functionalitate deschidem Views/Shared/Site.Master si adaugam urmatorul cod (marcat cu galben) in div-ul menucontainer:
<ul id="menu"> <li><%: Html.ActionLink("Home", "Index", "Home")%></li> <li><%: Html.ActionLink("About", "About", "Home")%></li> <li><%: Html.ActionLink("Adauga client", "CreareUtilizator", "Home")%></li> </ul>

Vizualizarea datelor
Vom crea un view care sa ne afiseze o lista cu toti clientii din tabela clienti: 1. Cream in controller-ul Home actiunea VizualizareClienti():
//vizualizare lista clienti public ActionResult VizualizareClienti() { IQueryable db_list = clientRepo.GetAllClients(); return View(db_list); }

Autori: Nicolae Tomai, Alexandru Butoi

Lucrarea nr. 3 Medii Avansate de programare 2011


Vom crea un View strongly-type de tip List care necesita ca si model un obiect de tip lista acesibil prin intermediul interfetei IEnumerable<>; IQueryable este un tip de lista compatibila; Astfel pentru crearea unui astfel de View vom trimite pe post de model lista cu inregistrarile obtinute in baza de date. 2. Click dreapta in interiorul actiunii implementate > Add view ; bifam strongly-type view; View data class: MagazinVirtual.Models.clienti; ViewContent: List; > Add; Ni se creaza un view de tip list (VizualizareClienti.aspx) in Views/Home :
<h2>VizualizareClienti</h2> <table> <tr> <th></th> <th> idclient </th> <th> nume </th> <th> prenume </th> <th> adresa </th> <th> idlocalitate </th> <th> email </th> <th> parola </th> </tr> <% foreach (var item in Model) { %> <tr> <td> <%: <%: <%: </td> <td> <%: </td> <td> <%: </td> <td> <%: </td> <td> <%: </td> Html.ActionLink("Edit", "Edit", new { id=item.idclient }) %> | Html.ActionLink("Details", "Details", new { id=item.idclient })%> | Html.ActionLink("Delete", "Delete", new { id=item.idclient })%> item.idclient %> item.nume %> item.prenume %> item.adresa %>

Autori: Nicolae Tomai, Alexandru Butoi

Lucrarea nr. 3 Medii Avansate de programare 2011


<td> <%: item.idlocalitate %> </td> <td> <%: item.email %> </td> <td> <%: item.parola %> </td> </tr> <% } %> </table> <p> <%: Html.ActionLink("Create New", "Create") %> -inlocuit cu : <%: Html.ActionLink(Adauga client,CreareUtilizator) %> </p>

Explicatii: 1. 2. 3. 4. Se genereaza automat un tabel in care se vor afisa pe fiecare linie datele din tabelul clienti; Observam ca afisarea datelor in tabel se face intr-o structura foreach; Modelul aferent View-ului este o lista de tip IQueryable care contine datele ce trebuie afisate; Pe langa datele efective observam ca se genereaza automat pentru fiecare inregistrare link-uri catre functionalitati de editare a datelor, vizualizare detaliata sau stergere a inregistrarilor; 5. Propunem eliminarea coloanelor (marcate cu verde) idlocalitate si parola deoarece acestea nu au relevanta pentru lista noastra. 6. Observam ca in josul paginii s-a generat si un link catre formularul de introducere de noi clienti; pentru a-l face functional vom inlocui codul petru link (marcat cu galben) cu:
<%: Html.ActionLink(Adauga client,CreareUtilizator) %> (daca nu se specifica si denumirea controllerului in apelul ActionLink, se va considera controller-ul curent)

Pentru a avea acces mai facil la aceasta functionalitate putem adauga un link in meniul define in Site.Master ca si mai sus:
<ul id="menu"> <li><%: Html.ActionLink("Home", "Index", "Home")%></li> <li><%: Html.ActionLink("About", "About", "Home")%></li> <li><%: Html.ActionLink("Adauga client", "CreareUtilizator", "Home")%></li> <li><%: Html.ActionLink("Lista clienti", "VizualizareClienti", "Home")%></li> </ul>

In continuare vom trece la crearea functionalitatilor de editare, stergere si vizualizare de detalii pentru a da functionalitate link-urilor generate pentru fiecare coloana din Lista de clienti. (Edit,Details,Delete);

10

Autori: Nicolae Tomai, Alexandru Butoi

Lucrarea nr. 3 Medii Avansate de programare 2011


Editarea datelor in MVC
Editarea datelor in orice aplicatie web sau desktop implica incarcarea datelor existente in formularul de editare, editarea propriu-zisa, salvarea modificarilor. In MVC 2 principiul pe care se bazeaza editarea datelor este similar cu cel utilizat in adaugarea datelor cu cateva exceptii: Exista si aici 2 actiuni in controller (una care incarca formularul, si una care proceseaza datele dupa submit) dar in actiunea care incarca formularul modelul instantiat nu va fi gol ci va contine datele extrase din baza da date spre editare; Metoda care proceseaza datele dupa submit va face validarea la fel dar pentru salvarea modificarilor in baza de date se vor utiliza metodele din repository specifice operatiei de update pe baza de date;

Exemplificam crearea unui formular de editare: 1. Adaugam in controller-ul Home urmatoarele actiuni:
//editare date client [HttpGet] public ActionResult EditClient(int id) { //incarcare date in model clienti client = new clienti(); client = clientRepo.GetClientById(id); //incarcare lista localitati IEnumerable<SelectListItem> lst = clientRepo.GetLocalitati(); //setare localitate curenta ca si seleted in lista derulanta foreach (SelectListItem item in lst) { if (item.Value == client.idclient.ToString()) { item.Selected = true; break; } } //incarcare in ViewData pentru afisare in View ViewData["localitati"] = lst; ViewData["msg"] = ""; return View("EditClient",client); } //actiune pentru procesarea datelor [HttpPost] public ActionResult EditClient(int id,FormCollection form) { clienti client = new clienti(); IEnumerable<SelectListItem> lst = clientRepo.GetLocalitati(); //setare localitate curenta ca si seleted in lista derulanta foreach (SelectListItem item in lst) { if (item.Value == client.idclient.ToString()) {

11

Autori: Nicolae Tomai, Alexandru Butoi

Lucrarea nr. 3 Medii Avansate de programare 2011


item.Selected = true; break; } } //incarca din nou lista de localitati ViewData["localitati"] = lst; pt a evita erorile

//incarca datele ce trebuie editate clientValidator cValidator = new clientValidator(); client = clientRepo.GetClientById(id); //pt ca membrul confirmare parola nu exista va trebui incarcat manual in Validator cValidator.confirmare = form["confirmare"];//se ia din argumentul form care contine valorile campurilor din formular try { UpdateModel(cValidator);// incarcare date si validare(daca exista erori sare la catch) UpdateModel(client); //daca e valid incarca in Linq class clientRepo.Save();//salvarea pentru persistenta ViewData["msg"] = "Date salvate cu succes!"; return View(client); } catch { ViewData["msg"] = "Datele nu au fost salvate!"; return View(client); } }

Explicatii: Ca si la adaugare de date avem si aici doua metode supraincarcate prin numarul de argumente, una care incarca formularul cu datele pentru editare, iar cealalta care colecteaza datele din formular si face actualizarile in baza de date; Prima actiune comunica cu View-ul prin GET, pe cand a doua actiune comunica cu View-ul prin POST; Prima actiune incarca in model datele care urmeaza a fi editate, model care apoi este trimis catre formularul de editare; observam ca si aici este necesara incarcarea listei de localitati dar pe langa aceasta trebuie sa setam si item-ul localitatii corespunzatoare ca fiind selectat. A doua actiune are urmatoarea logica: o Se instantiaza modelul; o Se incarca lista de localitati disponibile, si se determina localitatea care trebuie selectata implicit in lista din formular; Se rezolva problema campului confirmare care nu isi are corespondent in model; o Se incarca in model datele care urmeaza a fi editate. (Starea initiala a modelului); o Se face validarea datelor cu acelasi validator ca si pentru introducerea lor; ATENTIE!!! Pentru acelasi validator atat pentru creare cat si pentru editare trebuie ca in validarea datelor sa nu apara incompatibilitati. (exemplul de mai jos cu Verificarea pentru Emailul unic); 12 Autori: Nicolae Tomai, Alexandru Butoi

Lucrarea nr. 3 Medii Avansate de programare 2011


Daca nu apar erori la validare se trece la Actualizarea modelului cu datele editate prin UpdateModel (Starea finala a modelului); Daca apar erori executia va trece pe ramura catch; o Se salveaza modificarile efectuate; o Se incarca View-ul pentru editare; Exemplu de incompatibilitate a regulilor de validare la adaugare cu validarea la editare: o

Presupunem ca la validare vrem sa realizam un ValidationAttribute care sa valideze unicitatea email-ului. Adica sa impiedicam inregistrarea de emailuri duplicate in campul e-mail din tabela clienti. La adaugare regula este binevenita, dar problema intervine la editare: La editare putem sa modificam sau nu campul de e-mail; Daca il vom modifica cu o noua adresa de e-mail care nu mai exista in baza de date toate vor decurge normal; Daca la editare nu vom modifica campul de e-mail ValidationAttribute-ul nu are de unde sa stie ca noi facem editare si nu introducere si va gasi in baza de date e-mailul aruncand eroare de validare; Solutia: daca dorim sa implementam o astfel de regula cel mai bine este sa o implementam intr-un if in metoda de Add din repository sau la nivelul actiunii din controller; 2. Urmatorul pas este crearea View-ului care va contine formularul de editare a datelor: click dreapta in prima actiune; Add View; Create strongly-type view; View Data class: MagazinVirtual.Models.clienti; View content: Edit; 3. In Views/Home se genereaza view-ul EditClient.aspx:
<% Html.EnableClientValidation(); %> <% using (Html.BeginForm()) {%> <%: Html.ValidationSummary(true) %> <fieldset> <legend>Fields</legend> <div class="editor-label"> <%: Html.LabelFor(model => model.nume) %> </div> <div class="editor-field"> <%: Html.TextBoxFor(model => model.nume) %> <%: Html.ValidationMessageFor(model => model.nume) %> </div> <div class="editor-label"> <%: Html.LabelFor(model => model.prenume) %> </div> <div class="editor-field"> <%: Html.TextBoxFor(model => model.prenume) %> <%: Html.ValidationMessageFor(model => model.prenume) %> </div> <div class="editor-label"> <%: Html.LabelFor(model => model.adresa) %> </div>

13

Autori: Nicolae Tomai, Alexandru Butoi

Lucrarea nr. 3 Medii Avansate de programare 2011


<div class="editor-field"> <%: Html.TextBoxFor(model => model.adresa) %> <%: Html.ValidationMessageFor(model => model.adresa) %> </div> <div class="editor-label"> <%: Html.LabelFor(model => model.localitati.localitate) %> </div> <div class="editor-field"> <%: Html.DropDownListFor(model => model.idlocalitate,ViewData["localitati"] as IEnumerable<SelectListItem>) %> <%: Html.ValidationMessageFor(model => model.idlocalitate) %> </div> (1) la fel ca la adaugare am adaugat in locul TextBox un DropdownList pt localitati <div class="editor-label"> <%: Html.LabelFor(model => model.email) %> </div> <div class="editor-field"> <%: Html.TextBoxFor(model => model.email)%> <%: Html.ValidationMessageFor(model => model.email) %> </div> <div class="editor-label"> <%: Html.LabelFor(model => model.parola) %> </div> <div class="editor-field"> <%: Html.TextBoxFor(model => model.parola) %> <%: Html.ValidationMessageFor(model => model.parola) %> </div> <div class="editor-label"> <%: Html.Label("confirmare") %> </div> <div class="editor-field"> <%: Html.TextBox("confirmare",Model.parola) %> </div> (2) am adaugat campul de confirmare parola la fel ca la adaugare <%: ViewData["msg"] %> <p> <input type="submit" value="Save" /> </p> </fieldset> <% } %>

Am marcat cu gri modificarile efectuate in View-ul generat, care sunt similare cu cele efectuate la adaugare. 4. Integram functionalitatea de editare cu lista de clienti generata in View-ul VizualizareClienti.aspx; Astfel deschidem Views/Home/VizualizareClienti.aspx si identificam codul aferent link-ului Edit al fiecarei linii din tabel (la inceputul blocului foreach) si il modificam ca mai jos:
<%: Html.ActionLink("Edit", "EditClient", new { id=item.idclient }) %> |

(Text)

(Actiune) 14

(parametrii metodei din controller apelate) Autori: Nicolae Tomai, Alexandru Butoi

Lucrarea nr. 3 Medii Avansate de programare 2011


Observam cum se trimit parametrii dintr-un ActionLink catre o actiune a unui controller: Daca avem Function(param1, param2) -> new {param1=valoare, param2=valoare,.} In rest mecanismul esta acelasi ca si la creare in privinta afisarii erorilor si a mecanismului de validare si procesare a datelor.

Stergerea datelor
In View-ul vizualizare clienti, pentru fiecare linie avem si un link Delete. Vom implementa aceasta functionalitate: 1. In controller-ul Home vom adauga urmatoarele actiuni:
public ActionResult DeleteClient(int id) { //incarcare date in model clienti client = new clienti(); client = clientRepo.GetClientById(id); //incarcare View ViewData["msg"] = ""; return View(client); } [HttpPost] public ActionResult DeleteClient(int id, string submit) { //incarcare date in model clienti client = new clienti(); client = clientRepo.GetClientById(id); //stergere din BD clientRepo.DeleteClient(client); clientRepo.Save(); //incarcare view de confirmare ViewData["msg"] = "Inregistrarea a fost stearsa cu succes!"; return View("DeleteConfirmation"); }

Explicatii: Si aici avem un mechanism similar cu a celui de update; In prima actiune se incarca datele in model si se trimite modelul catre View-ul Delete; In a doua actiune se incarca modelul, se sterg datele din baza de date si se incarca un view de confirmare;

15

Autori: Nicolae Tomai, Alexandru Butoi

Lucrarea nr. 3 Medii Avansate de programare 2011


Ambele actiuni au ca si parametru id care este de fapt cheia unica a inregistrarii care urmeaza a fi eliminata; Actiunea care efectueaza stergerea trebuie sa fie invocata prin POST si nu prin GET din motive de securitate pentru a preveni stergerea accidentala a datelor prin introducerea unui URL; 2. Cream View-ul pentru stergere: click dreapta in prima actiune>Add view;Create strongly-type view; View data class: MagazinVirtual.Models.clienti; View content: Delete; Add. In Home/Views se genereaza view-ul DeleteClient.aspx:

<fieldset> <legend>Fields</legend> <div class="display-label">idclient</div> <div class="display-field"><%: Model.idclient %></div> <div class="display-label">nume</div> <div class="display-field"><%: Model.nume %></div> <div class="display-label">prenume</div> <div class="display-field"><%: Model.prenume %></div> <div class="display-label">adresa</div> <div class="display-field"><%: Model.adresa %></div> <div class="display-label">idlocalitate</div> <div class="display-field"><%: Model.idlocalitate %></div> <div class="display-label">email</div> <div class="display-field"><%: Model.email %></div> <div class="display-label">parola</div> <div class="display-field"><%: Model.parola %></div> </fieldset> <% using (Html.BeginForm()) { %> <p> <input type="submit" value="Delete" /> | <%: Html.ActionLink("Back to List", "Index") %> </p> <% } %>

3. Cream View-ul de confirmare a stergerii: click dreapta pe a doua actiune; Add View; View name: DeleteConfirmation; Create strongly-type view; View data class: MagazinVirtual.Models.clienti; View content: Empty; Add; Se creaza in Views/Home view-ul gol DeleteConfirmation.aspx, in care afisam mesajul de confirmare trimis de controller:
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server"> <h2>DeleteConfirmation</h2> <%: ViewData["msg"] %>

16

Autori: Nicolae Tomai, Alexandru Butoi

Lucrarea nr. 3 Medii Avansate de programare 2011


4. Integram functionalitatea de stergere cu lista de clienti din View-ul VizualizareClienti.aspx modificand codul aferent link-ului de Delete:
<%: Html.ActionLink("Delete", "DeleteClient", new { id=item.idclient })%>

ViewModels
In continuare dorim sa implementam o functionalitate pe care sa o atasam link-ului Details din View-ul cu lista clientilor. Presupunem ca dorim afisarea unui raport care sa contina date despre client precum si detalii legate de comenzile lui ca si in imaginea de mai jos:

Observam ca avem date din mai multe tabele aflate in relatie si anume din: clienti, comenzi, produse, categorii, edituri. Intr-o interogare SQL am fi facut diverse JOIN-uri intre aceste tabele pentru a obtine raportul dorit. In MVC lucram cu modele care preiau datele din BD. In cazul de fata avem nevoie de un ViewModel pentru a ne crea structura obiectuala de care avem nevoie pentru a obtine functionalitatea de mai sus: 1. In solution explorer facem click dreapta pe solutie> Add>New folder si adaugam un nou folder denumit ViewModels. In acest moment un nou folder se creaza dar impreuna cu el se creaza si un nou namespace denumit ViewModels. 2. Click dreapta pe folderul ViewModels> Add>Class; Adaugam clasa ClientDetailsViewModel.cs si scriem urmatorul cod: 17 Autori: Nicolae Tomai, Alexandru Butoi

Lucrarea nr. 3 Medii Avansate de programare 2011


public class ClientDetailViewModel { //view-model care afiseaza detalii despre client si comenzile sale //vom reuni date atat din tabela clienti cat si din tabela commenzi,produse, categorii, edituri public public public public public int idclient { get; set; } string nume { get; set; } string prenume { get; set; } string adresa { get; set; } string email { get; set; }

//lista comenzilor unui client public List<Comanda> comenzi = new List<Comanda>(); //acces private private private la bd magazinDataContext magazin = new magazinDataContext(); clientiRepository clientRepo = new clientiRepository(); comenziRepository comenziRepo = new comenziRepository();

//constructor care incarca datele in ViewModel public ClientDetailViewModel(int id_client) { //incarcare date client idclient = id_client; clienti client = new clienti(); client = clientRepo.GetClientById(id_client); nume = client.nume; prenume = client.prenume; adresa = client.adresa; email = client.email; //incarcare comenzi IQueryable<comenzi> lstcomenzi = comenziRepo.GetComenziByClient(id_client); foreach (comenzi cm in lstcomenzi) { //instantiere comanda Comanda cmd = new Comanda(cm.produse.denumire, //navigam pe relatie pentru a ne regasi datele de care avem nevoie cm.produse.categorii.categorie, cm.produse.edituri.editura, cm.data_comanda.ToShortDateString(), cm.suma_plata, cm.cantitate, cm.onorata); //adaugare la lista de comenzi comenzi.Add(cmd); } } } public class Comanda { //date incarcate din comenzi, edituri, categorii, produse public string denumireprodus; public string categorie;

18

Autori: Nicolae Tomai, Alexandru Butoi

Lucrarea nr. 3 Medii Avansate de programare 2011


public public public public public string editura; string data; double suma; int cantitate; bool onorata;

public Comanda(string denp, string catp, string ed, string dataC, double sumap, int cant, bool isonorata) { this.denumireprodus = denp; this.categorie = catp; this.editura = ed; this.data = dataC; this.suma = sumap; this.cantitate = cant; this.onorata = isonorata; } }

Explicatii Observam ca un ViewModel este o clasa simpla care reprezinta scheletul structurii pe care noi vrem sa o afisam in View: stocheaza date despre client si o lista a comenzilor efectuate de acesta; Ca si membri privati avem magazinDataContext si clientiRepository precum si comenziRepository, deoarece vom exploata modelele clienti si comenzi pentru a crea ViewModelul dorit; ViewModelul este echipat cu un constructor care ia ca si argument id-ul clientului pentru care se doreste incarcarea datelor, id pe baza caruia se regasesc datele de care avem nevoie; Constructorul incarca in ViewModel datele din BD; Observam ca pentru a incarca comenzile unui client apelam metoda GetComenziByClient(idclient) din comenziRepository pe care am implementat-o astfel:

public IQueryable<comenzi> GetComenziByClient(int idclient) { return db.comenzis.Where<comenzi>(d => d.idclient == idclient); }

Am definit si clasa Comanda care este o structura de date care contine detaliile legate de o comanda, iar in ClientDetailViewModel am definit o colectie de astfel de obiecte pentru a stoca toate comenzile unui client (List<Comanda>); Clasa comanda este echipata cu un constructor explicit care incarca datele unei comenzi in structura de date; Dupa ce am obtinut din BD toate comenzile aferente unui client am incarcat lista cu comenzi; Marcat cu verde am evidentiat modul in care pornind de la o instanta a clasei Linq comenzi am navigat pe relatiile dintre tabele pentru a-mi obtine datele de care am nevoie:
cm.produse.categorii.categorie cm.produse.edituri.editura

3. In controller-ul Home am adaugat actiunea care va instantia ViewModelul creat si va afisa datele intr-un View: 19 Autori: Nicolae Tomai, Alexandru Butoi

Lucrarea nr. 3 Medii Avansate de programare 2011


public ActionResult ViewDetails(int id) { //incarca view model-ul ViewModels.ClientDetailViewModel cdetail = new ViewModels.ClientDetailViewModel(id); //incarca view-ul cu viewmodelul pe post de model return View("ViewDetails",cdetail); }

Actiunea are ca si parametru id-ul clientului pentru care se doreste vizualizarea detaliilor. 4. Urmatorul pas este crearea View-ului: Click dreapta in actiunea controller-ului>Add View> Create strongly-type view; ca si View data class de aceasta data alegem viewmodelul creat de noi: MagazinVirtual.ViewModels.ClientDetailViewModel; View content: Details; Add. Ni se genereaza in Views/Home view-ul ViewDetails care este un view aprope gol. De data aceasta nu ni se genereaza aproape tot codul ca si in celelalte cazuri. 5. Va trebui ca programatorul sa specifice in View ceea ce vrea sa afiseze din ViewModel: 6. Astfel introducem in View-ul creat ViewDetails.aspx codul urmator:
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server"> <h2>ViewDetails</h2> <fieldset> <legend>Details</legend> <table> <tr><td>Idclient: </td><td><%: Model.idclient %></td></tr> <tr><td>Nume: </td><td><%: Model.nume %></td></tr> <tr><td>Prenume: </td><td><%: Model.prenume %></td></tr> <tr><td>Adresa: </td><td><%: Model.adresa %></td></tr> <tr><td>E-mail: </td><td><%: Model.email %></td></tr> </table> <h3>Comenzi</h3> <table> <tr> <th>Produs</th> <th>Categorie</th> <th>Editura</th> <th>Data</th> <th>Suma</th> <th>Cantitate</th> <th>Onorata</th> </tr> <% foreach (var item in Model.comenzi) { %> <tr> <td><%: item.denumireprodus %></td> <td><%: item.categorie %></td> <td><%: item.editura %></td> <td><%: item.data %></td> <td><%:item.suma %></td> <td><%: item.cantitate %></td>

20

Autori: Nicolae Tomai, Alexandru Butoi

Lucrarea nr. 3 Medii Avansate de programare 2011


<td> <% if (item.onorata == true) { %> Onorata <%} else { %> Neonorata <%} %> </td> </tr> <% } %> </table> </fieldset> <%: Html.ActionLink("Back","VizualizareClienti","Home") %> </asp:Content>

Explicatii: In prima parte afisam datele despre client; Apoi intr-un tabel afisam toate comenzile pe care clientul nostru le-a facut; Definim cu <th> capul de tabel iar apoi intr-un foreach afisam fiecare comanda pe o linie din tabel; Observam utilizarea tag-ului <% atunci cand vrem sa specificam cod executabil si a tag-ului <%: atunci cand vrem sa afisam date;

Pentru a testa efectul functionalitatii va trebui sa populam tabelele comenzi, categorii, produse, edituri cu date corespunzatoare:

21

Autori: Nicolae Tomai, Alexandru Butoi

Lucrarea nr. 3 Medii Avansate de programare 2011


7. Ultimul pas este integrarea functionalitatii de ViewDetails cu lista de vizualizare clienti din View-ul VizualizareCienti.aspx
<%: Html.ActionLink("Details", "ViewDetails", new { id=item.idclient })%>

Screenshot-uri
Site.Master-meniu

Adauga client

22

Autori: Nicolae Tomai, Alexandru Butoi

Lucrarea nr. 3 Medii Avansate de programare 2011

Vizualizare clienti

Editare client

23

Autori: Nicolae Tomai, Alexandru Butoi

Lucrarea nr. 3 Medii Avansate de programare 2011

Delete client

24

Autori: Nicolae Tomai, Alexandru Butoi

Lucrarea nr. 4 Medii Avansate de programare 2011

Ajax si MVC
In continuare vom lucra pe ceea ce am construit pana acum, si vom integra actiuni ajax pentru o mai buna utilizabilitate. In primul rand pentru a putea lucra cu Ajax in aplicatiile MVC trebuie ca in Site.Master sa includem, referinte catre librariile Javascript care implementeaza framework-ul pentru comunicarea Ajax: In Views/Shared/Site.Master introducem:
<link href="../../Content/Site.css" rel="stylesheet" type="text/css" /> <script src="/Scripts/MicrosoftAjax.js" type="text/javascript"></script> <script src="/Scripts/MicrosoftMvcAjax.js" type="text/javascript"></script> <script src="/Scripts/MicrosoftMvcValidation.js" type="text/javascript"></script> <script src="/Scripts/jquery-1.4.1.min.js" type="text/javascript"></script>

Aceste librarii sunt implicite in orice proiect MVC si se gasesc in folderul Scripts al proiectului. Utilizarea Ajax.ActionLink pentru stergere In lucrarea 3 am exemplificat stergerea datelor prin metoda clasica far Ajax integrat. Acolo cand mergeam pe link-ul de Delete se deschidea un View Delete pentru confirmarea stergerii datelor. Aceasta functionalitate de stergere a datelor se poate implementa si prin Ajax nemai fiind nevoie utilizarea Viewurilor de stergere. Partea de client Dorim ca in lista de vizualizualizare clienti creat n lucrarea 3 s adugm o funcionalitate de tergere prin Ajax a unui client din list. Astfel pornim de la View-ul VizualizareClienti.aspx 1. Adaugam link-ul DeleteClient(Ajax) , care apeleaza actiunea DeleteAjax a controllerului si ca si raspuns pe parte de client dupa ce apelul asincron s-a realizat se apeleaza functia javascript DeleteResponse. 2. Implementam functia javascript DeleteResponse care va fi executat pe parte de client dupa ce se primeste raspunsul asincron de la server . Functia primete ca si argument de la server stringulraspuns in format JSON, care este deserializat si transformat in instanta de obiect, dupa care se fac modificarile in pagina, in cazul nostru se elimina elementul sters din baza de date si din View si se afiseaza mesajul de confirmare in div-ul ajax_msg. 3. Adaugam div-ul ajax_msg pentru mesajul de confirmare de stergere. 4. Adaugam la fiecare linie a tabelului care se afiseaza un id de forma client-idclient pentru a putea fi identificata linia cu elementul eliminat si eliminata si din view pentru a crea interactivitate.

Autori: Nicolae Tomai, Alexandru Butoi

Lucrarea nr. 4 Medii Avansate de programare 2011


<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server"> <script type="text/javascript"> function DeleteResponse(context) {//preia raspunsul JSOn din Ajax si face update in pagina- asemanator cu XHR // Load and deserialize the returned JSON data var json = context.get_data(); //preia mesajul json var data = Sys.Serialization.JavaScriptSerializer.deserialize(json); //transfoma json intr-o instanta obiect // Update the page elements if (data.id > 0) { $('#client-' + data.id).fadeOut('slow'); } //elimina din tabel linia $('#ajax_msg').text(data.message);//afiseaza mesajul de eroare } (2) <h2>VizualizareClienti</h2> <div class="field-validation-error" id="ajax_msg">&nbsp;</div> (3) <table> <tr> <th></th> <th>idclient</th> <th>nume</th> <th>prenume </th> <th>adresa</th> <th>email</th> </tr> <% foreach (var item in Model) { %> <tr id="client-<%:item.idclient %>"> (4) <td> <%: Html.ActionLink("Edit", "EditClient", new { id=item.idclient }) %> | <%: Html.ActionLink("Details", "ViewDetails", new { id=item.idclient })%> | <%: Html.ActionLink("Delete", "DeleteClient", new { id=item.idclient })%> <%: Ajax.ActionLink("Delete(Ajax)", "DeleteAjax","Home", new { id = item.idclient }, new AjaxOptions{OnSuccess="DeleteResponse"}) %> (1) </td> <td><%: <td><%: <td><%: <td><%: <td><%: </tr> <% } %> </table> <p> <%: Html.ActionLink("Adauga client", "CreareUtilizator") %></p> </asp:Content> item.idclient %></td> item.nume %></td> item.prenume %></td> item.adresa %></td> item.email %></td>

Astfel utilizatorul cand da click pe link-ul Delete(Ajax) prin XHR se va trimite la server un apel catre controller-ul Home, actiunea DeleteAjax impreuna cu id-ul elementulu care se sterge iar actiunea din controller va sterge inregistrarea din baza de date, va instantia un obiect cu un mesaj structurat pe care il 2 Autori: Nicolae Tomai, Alexandru Butoi

Lucrarea nr. 4 Medii Avansate de programare 2011


va trimite la client in format JSON. La client functia DeleteResponse primeste prin argumentul context sirul JSON, il deserializeaza si face schimbarile in pagina clientului fara reincarcarea paginii. Partea de server 1. Implementam in ViewModels un viewmodel pentru trimiterea unui raspuns structurat in format JSON:
public class AjaxResponseViewModel { public int id { get; set; } public string message { get; set; } public AjaxResponseViewModel(int ido, string msg) { id = ido; message = msg; } }

Astfel in cazul stergerii se va instantia acest ViewModel care va stoca id-ul inregistrarii sterse si mesajul de confirmare. Dupa deserializare pe parte de client obiectul va avea aceiasi structura. 2. Implementam actiunea controllerului Home de tratare a stergerii DeleteAjax:
[HttpPost] public ActionResult DeleteAjax(int id) { clienti client = new clienti(); client = clientRepo.GetClientById(id); ViewModels.AjaxResponseViewModel ajx; try{ //stergere din BD clientRepo.DeleteClient(client); clientRepo.Save(); //instantiem AjaxReponseViewModel - stergere cu success ajx = new ViewModels.AjaxResponseViewModel(client.idclient,"Inregistrare stearsa cu succes!"); }catch{ //instantiem AjaxResponseViewModel - stergere esuata ajx = new ViewModels.AjaxResponseViewModel(-1,"Inregistrarea nu a fost stearsa!"); } //serializam obiectul AjaxResponseViewModel in JSON si returnam stringul JSOA return Json(ajx); }

Autori: Nicolae Tomai, Alexandru Butoi

Lucrarea nr. 4 Medii Avansate de programare 2011


Astfel obtinem stergerea unei inregistrari fara reincarcarea altor View-uri:

Utilizarea Ajax.ActionLink pentru afisare de detalii partea de client Vom utiliza acelasi View VizualizareClienti.aspx pentru care vrem sa afisam detalii despre comenzile fiecarui utilizator prin Ajax: 1. Adaugam la link-urile aferente fiecarei linii din lista de clienti linkul Ajax Details(Ajax):
<%: <%: <%: <%: Html.ActionLink("Edit", "EditClient", new { id=item.idclient }) %> | Html.ActionLink("Details", "ViewDetails", new { id=item.idclient })%> | Html.ActionLink("Delete", "DeleteClient", new { id=item.idclient })%> Ajax.ActionLink("Delete(Ajax)", "DeleteAjax","Home", new { id = item.idclient }, new AjaxOptions{OnSuccess="DeleteResponse"}) %> | <%: Ajax.ActionLink("Details(Ajax)", "DetailsAjax", "Home", new { id = item.idclient }, new AjaxOptions { OnSuccess = "DetailResponse" })%>(1)

Linkul apeleaza prin XHR metoda DetailAjax din Controllerul Home, se primesc si se afiseaza datele primite in format JSON prin functia javascript DetailResponse. 2. Implementam functia javascript DetailResponse(context) care se adauga in View-ul VizualizareClienti.aspx in sectiunea <script> imediat dupa functia DeleteResponse. DetailResponse() primeste o instanta de tip ClientDetailViewModel ViewModel implementat in lucrarea 3, serializat in JSON de la actiunea DetailAjax. Se transforma sirul JSON in obiect si se formeaza stringul (shtml) cu codul HTML de afisare a datelor in tabel care apoi se afiseaza in div-ul container destinat afisarii detaliilor prin ajax (aici ajax_detail). Functia DetailResponse() se adauga in sectiune <script> a View-ului VizualizareClienti.aspx:

Autori: Nicolae Tomai, Alexandru Butoi

Lucrarea nr. 4 Medii Avansate de programare 2011


function DetailResponse(context) { var json = context.get_data(); //preia mesajul json var data = Sys.Serialization.JavaScriptSerializer.deserialize(json); //transfoma json intr-o instanta obiect // Update the page elements //cod html tabel detalii var k=0; //numara cate linii are de afisat var shtml = "<table><tr><th>Produs</th><th>Categorie</th><th>Editura</th> <th>Data</th><th>Suma</th><th>Cantitate</th><th>Onorata</th></tr>"; for (i in data.comenzi) { //ia fiecare comanda din lista de comenzi si creaza html cod pentru fiecare linie din tabel shtml = shtml + "<tr><td>" + data.comenzi[i].denumireprodus + "</td>"; shtml=shtml+"<td>" + data.comenzi[i].categorie + "</td>"; shtml = shtml + "<td>" + data.comenzi[i].editura + "</td>"; shtml = shtml + "<td>" + data.comenzi[i].data + "</td>"; shtml = shtml + "<td>" + data.comenzi[i].suma + "</td>"; shtml = shtml + "<td>" + data.comenzi[i].cantitate + "</td>"; if (data.comenzi[i].onorata == true) { shtml = shtml + "<td>Onorata</td></tr>"; } else { shtml = shtml + "<td>Neonorata</td></tr>"; } k++; } shtml = shtml + "</table>"; $('#ajax_msg').text(""); $('#ajax_detail').html("&nbsp;"); if (k > 0)//daca are linii de afisat { $('#ajax_msg').text("Comenzi-"+data.nume+" "+data.prenume+":"); $('#ajax_detail').html(shtml); //$('#id_element_dom')=document.getElementById('id_element_dom') } else { $('#ajax_msg').text("Nu exista comenzi pentru "+data.nume+" "+data.prenume);} }

3. Se adauga containerul care va afisa datele:


<div class="field-validation-error" id="ajax_msg">&nbsp;</div> <div id="ajax_detail">&nbsp;</div> (3)

Partea de server In controller-ul Home se implementeaza metoda DetailAjax care incarca ViewModelul pentru afisarea detaliilor prin Ajax:
[HttpPost] public ActionResult DetailsAjax(int id) { //incarca view model-ul ViewModels.ClientDetailViewModel cdetail = new ViewModels.ClientDetailViewModel(id); //serializeaza ViewModelul in JSON return Json(cdetail); }

Autori: Nicolae Tomai, Alexandru Butoi

Lucrarea nr. 4 Medii Avansate de programare 2011

Obs: Actiunile Ajax ale controller-ului pentru partea de server comunica cu partea de client prin POST specificarea directivei [HttpPost] inaintea actiunii; Astfel obtinem incarcarea detaliilor legate de comenzile persoanei farareincarcarea altui View:

Autori: Nicolae Tomai, Alexandru Butoi

Lucrarea nr. 5 Medii Avansate de programare 2011

Autentificare i autorizare in MVC


Autentificarea procesul de verificare a identitii unui utilizator sau a unei persoane; Autorizare procesul de control i furnizare a accesului la anumite funcionaliti utilizatorilor n funcie de permisunile fiecruia sau de statutul acestuia. MVC 2 furnizeaz o platform de autorizare a utilizatorilor bazat pe roluri de utilizatori i permisiuni. Ex: un utilizator al unui site va avea doar privilegii de operare n sistem nu i de administrare a sistemului. Astfel aici avem doua roluri: Administrator i Utilizator; ASP MVC 2 asigur dou tipuri de autorizare: la nivel logic (prin roluri de utilizator) i la nivel fizic (prin reguli de acces atribuite fie unor roluri, fie unor utilizatori individuali); Regulile de acces se refer la nivelul fizic efectiv, la nivelul fiierelor i directoarelor aplicaiei; Autentificarea utilizatorului se face prin username i parola (autentificare prin cheie privat). A. Managementul utilizatorilor prin ASP.NET Configuration Orice proiect nou vine cu un Controller generat AccountController care cuprinde cod generat pentru implementarea aciunilor legate de autetificarea utilizatorilor. Acest controller utilizeaz modelul AccountModels care este i el generat automat la crearea noului proiect. Atentie! n cazul n care la crearea proiectului se alege tipul de proiect ASP.NET Empty Web Application aceste clase autogenerate (inclusiv HomeController) nu vor fi generate; Pentru a exemplifica acest mecanism de management al utilizatorilor vom crea un proiect nou de tip ASP.NET MVC 2 Web Application. 1. New project > ASP.NET MVC 2 Web Application 2. Rulam proiectul si dam click pe link-ul Log on din dreapta sus; 3. In dreapta, in Solution Explorer avem butonul ASP.NET Configuration , click pe el; Se deschide ASP.Net Web Site Administration Tool in browser; 4. Se merge pe link-ul Security care ar trebui sa afiseze 3 sectiuni: Users, Roles si Access Rules. Deoarece in aceasta varianta de management al utilizatorilor prin Asp.Net configuration Visual Studio isi genereaza o baza de date SQL Express in care isi stocheaza datele legate de utilizatori si roluri pasul doi a fost necesar tocmai pentru a genera automat acesta baza de date. (baza de date se genereaza daca nu exista in App_Data atunci cand se apeleaza pentru prima oara API-ul de autorizare-cand am efectuat click pe Logon). In aceasta varianta daca mergem in Windows Explorer in folderul proiectului in App_Data vom observa baza de date generata : ASPNETDB.dbf Daca deschidem fisierul de configurare al proiectului Web.config vom vedea ca s-a adaugat automat un string de conexiune catre baza de date generata: 1 Autori: Nicolae Tomai, Alexandru Butoi

Lucrarea nr. 5 Medii Avansate de programare 2011


connectionString="data source=.\SQLEXPRESS;Integrated Security=SSPI;AttachDBFilename=|DataDirectory|aspnetdb.mdf;User Instance=true" providerName="System.Data.SqlClient" />

Daca nu am facut pasul 2 baza de date nu va fi gasita de catre sistemul de administrare si acesta va da eroare. 5. Daca nu am creat inainte nici un rol dam Enable Roles 6. Mergem pe link-ul Create or manage Roles si de aici ne putem crea rolurile; 7. Daca mergem la pagina anterioara dupa ce am creat rolurile putem crea utilizatori carora sa le asociem aceste roluri mergand pe link-ul Create User ; Completam datele utilizatorului dupa care ii bifam rolurile pe care le poate detine acesta; Pentru exemplul nostru am definit urmatoarele roluri si utilizatori: Roluri

Utilizatori si roluri asociate Roluri asociate Admistrator Administrator User Toate aceste informatii se stocheaza in acea baza de date generata. 8. Dupa ce am facut aceste setari ne intoarcem in proiectul nostru pentru a integra logica de autorizare. 9. Presupunem ca in HomeController sunt actiunile publice care nu necesita autentificare; Astfel pentru a exemplifica introducem in actiunea Index a controllerului urmatorul cod:
public ActionResult Index() { ViewData["Message"] = "Unprotected area!"; return View(); }

Autori: Nicolae Tomai, Alexandru Butoi

Lucrarea nr. 5 Medii Avansate de programare 2011


Iar in View-ul Home/Index.aspx introducem facem afisarea mesajului:
<h2><%: ViewData["Message"] %></h2>

10. Cream un nou Controller AccHomeController care va implementa actiunile la care utilizatorul va avea acces doar dupa ce s-a autentificat prin utilizator si parola:
//[RequireHttps] - merge numai daca serverul are un certificat de SSL instalat public class AccHomeController : Controller { // // GET: /AccHome/ //se pot utiliza roluri multiple: Roles="Role1,Role2..." //autorizare la nivel de controller [Authorize(Roles="Administrator")] public ActionResult Index() { ViewData["Message"] = "Protected area!"; return View(); } //autorizare la nivel de actiune [Authorize(Users="Admin")] public ActionResult AdminAction() { ViewData["Message"] = "Admin only allowed action!"; return View("Index"); } [Authorize(Users = "Admin1")] public ActionResult Admin1Action() { ViewData["Message"] = "Admin1 only allowed action!"; return View("Index"); } //se pot specifica explicit lista de utilizatori care au permisiunea la actiunea curenta [Authorize(Users = "Admin1,Admin")] public ActionResult CommonAction() { ViewData["Message"] = "Common action!"; return View("Index"); } //nu va functiona deoarece la nivel de controller avem autorizare pentru rolul Administrator //AdmUser are rolul User si nu administrator [Authorize(Users = "AdmUser")] public ActionResult AdmUserAction() { ViewData["Message"] = "AdmUser only allowed action!"; return View("Index"); } }

Pentru a impune ca utilizatorul sa acceseze functionalitatile doar daca s-a logat in prealabil utilizam atributul Authorize din namespace-ul System.Web.Mvc (using System.Web.Mvc). 3 Autori: Nicolae Tomai, Alexandru Butoi

Lucrarea nr. 5 Medii Avansate de programare 2011


Autorizarea se poate face atat la nivel de controller cat si la nivel de actiune. De exemplu aici am dat acces la toate functionalitatile controller-ului pentru toti utilizatorii ce detin rolul de Administrator. Dar pot exista functionalitati la care doi utilizatori diferiti cu acelasi rol sa nu poata avea acces. Exemplu: la actiunea Admin are acces doar utilizatorul Admin iar la actiunea Admin1 are acces doar utilizatorul Admin1. Chiar si la nivel de controller se poate specifica o lista de utilizatori care au access la toate functionalitatile controllerului. Mai mult se pot utiliza chiar liste de utilizatori sau roluri ca de exemplu: Roles=Role1,Role2 sau Users=User1,User2... (separator ,). Observatie: Autorizarea la nivel de actiune trebuie sa se subordoneze celei de la nivel de controller. De exemplu aici AdmUser action nu va functiona deoarece la nivel de controller am autorizat doar utilizatorii cu rolul de Administrator iar AdmUser nu are rol de administrator; La fel daca se specifica la nivel de controller o lista de utilizatori, utilizatorii pentru care se face autorizarea la nivel de actiune trebuie sa se regaseasca in lista de la nivel de controller. Pentru a demonstra in continuare cum functioneaza mecanismul de autorizare am creat un View. 11. Click dreapta pe actiunea Index a controllerului AccHomeController > Add View > Add; In Views/AccHome se creaza view-ul Index la care adaugam urmatoul cod:
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server"> <h2><%: ViewData["Message"] %></h2> <%: Html.ActionLink("Account home","Index") %> <%: Html.ActionLink("Admin only action","AdminAction") %> <%: Html.ActionLink("Admin1 only action","Admin1Action") %> <%: Html.ActionLink("AdmUser only action","AdmUserAction") %> <%: Html.ActionLink("Common action","CommonAction") %> </asp:Content>

Obtinem o serie de link-uri care vor apela actiunile controllerului creat si vor afisa un mesaj care va spune daca avem acces la acea functionalitate sau nu. 12. O ultima modificare: atunci cand mergem pe Log on si vrem sa ne autetificam, daca autentificarea este reusita dorim sa se afiseze View-ul de mai sus. De acceea in controller-ul AccountController modificam (linia marcata cu galben) actiunea Log on pentru a merge la actiunea Index din AccHome in caz de autetificare reusita:
[HttpPost] public ActionResult LogOn(LogOnModel model, string returnUrl) { if (ModelState.IsValid) { if (MembershipService.ValidateUser(model.UserName, model.Password)) { FormsService.SignIn(model.UserName, model.RememberMe); if (!String.IsNullOrEmpty(returnUrl)) { return Redirect(returnUrl); } else { return RedirectToAction("Index", "AccHome");

Autori: Nicolae Tomai, Alexandru Butoi

Lucrarea nr. 5 Medii Avansate de programare 2011


} } else { ModelState.AddModelError("", "The user name or password provided is incorrect."); } } // If we got this far, something failed, redisplay form return View(model); }

Astfel obtinem:

Not allowed

Ce facem atunci cand avem baza noastra de date in care ne stocam userii si parolele si ii legam de alte tabele ca si in proiectul inceput in care clientii erau legati de comenzi? Putem sa schimbam string-ul de conexiune de mai sus in Web.config ca sa se faca conexiunea la baza noastra de date dar pentru ca toate sa functioneze bine trebuie sa ne asiguram ca baza noastra de date are structura de date (tabele, proceduri stocate, view-uri) acceptata de ASP.Net Configuration Tool. (nerecomandat) De aceea acest sistem se preteaza sistemului de administrare al unui site in care are acces doar 1-2 administratori si nu necesita crearea unei baze de date speciala. 5 Autori: Nicolae Tomai, Alexandru Butoi

Lucrarea nr. 5 Medii Avansate de programare 2011


In cazul unui site cu multi utilizatori si cu o logica de business complexa este recomandat sa implementam programatic aceasta parte de autentificare si autorizare care sa se integreze cu propria baza de date. B. Implementarea autentificarii si autorizarii in mod programatic In continuare vom lucra pe proiectul inceput in seminariile anterioare in care numele utilizatorilor si parolele erau stocate in baza de date in tabela clienti in campurile email si parola. Astfel dorim implementarea unui sistem custom de autentificare si autorizare care sa utilizeze datele din tabela clienti pentru a verifica username-ul si parola la login. Structura framework-ului pe care dorim sa il implementam:

ClientRole

Web.config
<membership></membership > <roleManager></roleManager >

MVC2 Project
[Authorization.ClientAuthorization (Roles="Client", ViewName="Error")] public class HomeController : Controller { . Models.User = new Models.User();

ClientAuthorization este mostenita din AuthorizeAttribute si reprezinta meta-atributul pentru autorizare. Astfel in implementarea Autorizarii in loc sa folosim clasa Authorize furnizata de MVC am definit propria noastra clasa pentru autorizare ClientAuthorization pe care o utilizam in controllere; 6 Autori: Nicolae Tomai, Alexandru Butoi

Lucrarea nr. 5 Medii Avansate de programare 2011


User este clasa model care implementeaza logica de validare a utilizatorului: ValidareCredentiale interogheaza baza de date si verifica daca parola si username-ul coincid; GetRolesForUser implementeaza logica de determinare a rolurilor pe care le are fiecare utilizator; ClientMembership - este mostenita din clasa abstracta MembershipProvider in care am facut override pe metoda ValidateUser() care agrega modelul User si valideaza username-ul si parola prin invocarea metodei User.ValidareCredentiale(); ClientRole este mostenita din clasa abstracta RoleProvider in care am facut override pe metoda GetRolesForUser() care agrega modelul User si determina rolurile unui utilizator prin invocarea metodei User.GetRolesForUser(); Programatorul nu prea mai utilizezaza cele doua clase ClientMembership si ClientRole dar ele sunt utilizate de framework-ul MVC pentru procesul de autorizare. Legatura intre proiectul MVC si ClientMembership si ClientRole se face in fisierul de configurare al proiectului Web.config unde in format XML se configureaza integrarea celor doua clase cu proiectul. Framework-ul va utiliza implicit clasele generate la crearea proiectului AccountController si AccountModel. Implementarea structurii de mai sus in proiectul nostru: 1. Cream in proiect un nou folder Authorization creandu-se implicit si un nou namespace Authorization; 2. In Models cream clasa User :
public class User { public bool ValidareCredentiale(string username, string pwd) { clientiRepository clientrepo = new clientiRepository(); clienti client = clientrepo.GetClientByCredentials(username, pwd); if (client != null) { if (client.email == username && client.parola == pwd) { return true; } else return false; }else return false; } public string[] GetUserRole(string username) { string[] roles = new string[1]; roles[0] = "Client"; return roles; //daca avem o logica mai complexa legata de user-roles //aici se implementeaza logica de determinare a fiecarui role in functie de user //se retuneaza un array cu toate rolurile utilizatorului } }

Observam ca verificarea credentialelor in baza de date se face prin metoda GetClientByCredentials implementata in clasa clientiRepistory. Astfel adaugam in clasa clientiRepository metoda:

Autori: Nicolae Tomai, Alexandru Butoi

Lucrarea nr. 5 Medii Avansate de programare 2011


public clienti GetClientByCredentials(string username, string password) { return db.clientis.SingleOrDefault<clienti>(d=>(d.email==username & d.parola==password)); }

3. In Authorization cream clasa ClientAuthorization:


[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = false)] public class ClientAuthorization:AuthorizeAttribute { //meta clasa ce face autorizarea si in caz de esec trimite la o pagina de access denied /// <summary> /// master page name /// </summary> public virtual string MasterName { get; set; } /// <summary> /// Error view redirect-in caz de access denied /// </summary> public virtual string ViewName { get; set; } public ClientAuthorization () : base() { this.ViewName = "Error"; } }

4. In Authorization cream clasa ClientMembership:


public class ClientMembership:MembershipProvider { //custom membership - Validate User (1) public override bool ValidateUser(string username, string password) { //supraincarea metodei de validare a utilizatorilor conform bd-ului propriu MagazinVirtual.Models.User usr = new MagazinVirtual.Models.User(); // modelul exploatat return usr.ValidareCredentiale(username, password); } //deoarece clasa MembershipProvider este o clasa abstracta si are cateva metode abstracte //acestea trebuie implementate si in clasa derivata pentru a evita erorile //noi nu evm nevoie de aceste functionalitati, doar le vom arunca un NotImplemented exception //fara prea mult efort ca imi scrie codul .net //in prima faza dam build si vedem erorile - care ne spun pe ce membri trebuie sa facem override public override MembershipUserCollection FindUsersByEmail(string emailToMatch, int pageIndex, int pageSize, out int totalRecords) { throw new NotImplementedException(); }

Autori: Nicolae Tomai, Alexandru Butoi

Lucrarea nr. 5 Medii Avansate de programare 2011


public override MembershipUserCollection FindUsersByName(string usernameToMatch, int pageIndex, int pageSize, out int totalRecords) { throw new NotImplementedException(); } public override int GetNumberOfUsersOnline() { throw new NotImplementedException(); } public override MembershipUserCollection GetAllUsers(int pageIndex, int pageSize, out int totalRecords) { throw new NotImplementedException(); } public override bool DeleteUser(string username, bool deleteAllRelatedData) { throw new NotImplementedException(); } public override MembershipUser GetUser(object providerUserKey, bool userIsOnline) { throw new NotImplementedException(); } public override string GetUserNameByEmail(string email) { throw new NotImplementedException(); } public override MembershipUser GetUser(string username, bool userIsOnline) { throw new NotImplementedException(); } public override void UpdateUser(MembershipUser user) { throw new NotImplementedException(); } public override bool UnlockUser(string userName) { throw new NotImplementedException(); } public override string ResetPassword(string username, string answer) { throw new NotImplementedException(); } public override bool ChangePassword(string username, string oldPassword, string newPassword) { throw new NotImplementedException(); } public override MembershipUser CreateUser(string username, string password, string email, string passwordQuestion, string passwordAnswer, bool isApproved, object providerUserKey, out MembershipCreateStatus status) { throw new NotImplementedException(); } public override string GetPassword(string username, string answer) { throw new NotImplementedException(); }

Autori: Nicolae Tomai, Alexandru Butoi

Lucrarea nr. 5 Medii Avansate de programare 2011


public override bool ChangePasswordQuestionAndAnswer(string username, string password, string newPasswordQuestion, string newPasswordAnswer) { throw new NotImplementedException(); } public override int MinRequiredNonAlphanumericCharacters { get { throw new NotImplementedException(); } } public override string PasswordStrengthRegularExpression { get { throw new NotImplementedException(); } } public override int MinRequiredPasswordLength { get { throw new NotImplementedException(); } } public override MembershipPasswordFormat PasswordFormat { get { throw new NotImplementedException(); } } public override bool RequiresUniqueEmail { get { throw new NotImplementedException(); } } public override int PasswordAttemptWindow { get { throw new NotImplementedException(); } } public override int MaxInvalidPasswordAttempts { get { throw new NotImplementedException(); } } public override string ApplicationName { get { throw new NotImplementedException(); } set { throw new NotImplementedException(); } } public override bool RequiresQuestionAndAnswer { get { throw new NotImplementedException(); } } public override bool EnablePasswordReset { get { throw new NotImplementedException(); } }

10

Autori: Nicolae Tomai, Alexandru Butoi

Lucrarea nr. 5 Medii Avansate de programare 2011


public override bool EnablePasswordRetrieval { get { throw new NotImplementedException(); } } }

Observatie: Pe langa ValidateUser() am fost obligati sa implementam si alte metode deoarece mostenim dintr-o clasa abstracta care ne impune acest lucru pentru ca aceste clase sunt utilizate de catre framework-ul de MVC ele trebuie sa aiba o structura binedefinita. Aici am implementat doar functionalitatea de ValidareClient dar putem customiza si mai mult framework-ul prin implementarea unei logici si in celelate metode. 5. In Authorization cream clasa ClientRole:
public class ClientRole:RoleProvider { public override string[] GetRolesForUser(string username) { MagazinVirtual.Models.User usr = new MagazinVirtual.Models.User();//modelul exploatat return usr.GetUserRole(username); } //facem override pt ceilalti membri abstracti public override string[] FindUsersInRole(string roleName, string usernameToMatch) { throw new NotImplementedException(); } public override string[] GetAllRoles() { throw new NotImplementedException(); } public override string[] GetUsersInRole(string roleName) { throw new NotImplementedException(); } public override void RemoveUsersFromRoles(string[] usernames, string[] roleNames) { throw new NotImplementedException(); } public override void AddUsersToRoles(string[] usernames, string[] roleNames) { throw new NotImplementedException(); } public override bool RoleExists(string roleName) { throw new NotImplementedException(); } public override bool DeleteRole(string roleName, bool throwOnPopulatedRole) { throw new NotImplementedException(); } public override void CreateRole(string roleName) { throw new NotImplementedException(); }

11

Autori: Nicolae Tomai, Alexandru Butoi

Lucrarea nr. 5 Medii Avansate de programare 2011


public override bool IsUserInRole(string username, string roleName) { throw new NotImplementedException(); } public override string ApplicationName { get { throw new NotImplementedException(); } set { throw new NotImplementedException(); } } }

Obs: Aceiasi problema o avem si aici, faptul ca mostenim dintr-o clasa abstracta ne impune a anumita implementare si in clasa derivata; 6. Deschidem Web.config al solutiei si eliminam orice noduri XML <membership> , <authentication> sau <roleManager> existente pentru a introduce propriile noastre configuratii:
</assemblies> </compilation> <authentication mode="Forms"> <forms loginUrl="~/Account/LogOn" timeout="2880" /> </authentication> <membership defaultProvider="ClientMembership"> (1) <providers> <clear /> <add name="ClientMembership"(1) applicationName="MagazinVirtual" Description="membership provider" passwordFormat="Clear" connectionStringName="magazinConnectionString" (2) type="MagazinVirtual.Authorization.ClientMembership" /> (3) </providers> </membership> <roleManager enabled="true" defaultProvider="ClientRole"> (4) <providers> <clear /> <add name="ClientRole"(4) applicationName="MagazinVirtual" type="MagazinVirtual.Authorization.ClientRole"(5) connectionStringName="magazinConnectionString" /> (6) </providers> </roleManager> <pages> <namespaces>

12

Autori: Nicolae Tomai, Alexandru Butoi

Lucrarea nr. 5 Medii Avansate de programare 2011


Explicatii: 1. defaultProvider = [numele clasei care implementeaza logica de membership] 2. connectionString=[numele de identifcare al stringului de conexiune catre baza de date] este specificat in acelasi fisier de configurare in sectiunea <connectionStrings>:
<connectionStrings> <add name="magazinConnectionString" connectionString="Data Source=.\SQLEXPRESS;AttachDbFilename=|DataDirectory|\magazin.mdf;Integrated Security=True;User Instance=True" providerName="System.Data.SqlClient" /> </connectionStrings>

3. 4. 5. 6.

Calea catre tipul implementat: type=[namespace_proiect.namespace.clasa_membership]; defaultProvider = [numele clasei care implementeaza logica de determinare a rolurilor] Calea catre tipul implementat: type=[namespace_proiect.namespace.clasa_roleprovider]; connectionString=[numele de identificare al stringului de conexiune catre baza de date] este specificat in acelasi fisier de configurare in sectiunea <connectionStrings> (vezi la 2)

7. Implementam autorizarea la nivelul controller-ului Home:


[HandleError] [Authorization.ClientAuthorization (Roles="Client",ViewName="Error")] public class HomeController : Controller { private clientiRepository clientRepo = new clientiRepository(); public ActionResult Index() { ViewData["Message"] = "Welcome to ASP.NET MVC!"; return View(); } ..

Folosim ca si atribut de autorizare la nivel de controller clasa ClientAuthorization din namespace-ul Authorization creat de noi. Observam ca am specificat si ViewName=Error care in caz de access denied va afisa View-ul Error din Views/Shared.

Daca rulam aplicatia ni se va cere autetificare cu utilizator si parola. Aici utilizatorul este considerat a fi adresa de email. Daca introducem un email si o parola corecta existenta in baza de date in tabela clienti se va executa actiunea Index, altfel se va afisa un mesaj de eroare la autentificare.

13

Autori: Nicolae Tomai, Alexandru Butoi

Lucrarea nr. 5 Medii Avansate de programare 2011

14

Autori: Nicolae Tomai, Alexandru Butoi

Lucrarea nr. 6 Medii Avansate de programare 2011

Servicii Web
Rest vs. SOAP

Serviciile sunt terminale, sau componente care pot exploatate mai degrab programatic i nu prin intermediul unui browser web. Cele dou mari abordri n aceast direcie sunt protocolul SOAP (Simple Object Access Protocol) i stilul arhitectural REST (Representational State Transfer):[2]
Tabel 1 SOAP vs. REST

SOAP Independent de protocol Modeleaz seturi de aciuni reunite sub acelai URI Cuprinde un set de specificaii tehnice pentru interaciunea cu serviciile web Adresabilitate limitat doar la apeluri prin verbul POST

REST Utilizeaz protocolul HTTP Modeleaz resurse fiecare resurs avnd un URI unic Ofer constrngeri i direcii de proiectare arhitectural a sistemelor Nivel crescut de generalitate i reutilizabilitate utiliznd doar verbele predefinite de HTTP: GET, POST, PUT, DELETE Ofer o descriere mai fidel a funcionaliti- Descrierea resurselor este limitat la lor prin WSDL media-type specificat n antetul HTTP prin Content-Type Vine cu conceptul de proces fiind practic un Verbele de aciune sunt predefinite, model de platform interoperabil ce are la eliminnd necesitatea de creere de noi APIbaz un sistem Remote Procedure Call (RPC) uri pentru fiecare nou serviciu

Cele dou abordri au avantajele i dezavantajele lor, dar fiecare dintre ele are rolul su principal. SOAP ofer o interfa unic standardizat de acces la funcionaliti sau procese de calcul expuse prin servicii web (cum ar fi tranzaciile bancare), pe cnd REST ofer o modalitate simpl i standardizat de acces la resurse informaionale existente pe Internet.

Autori: Nicolae Tomai, Alexandru Butoi

Lucrarea nr. 6 Medii Avansate de programare 2011


Crearea serviiciilor Web WCF WCF Windows Communication Foundation ofera o platform de programare a serviciilor web utilizand paradigma obiectuala. Pentru a dezvolta astfel de serviicii web trebuie sa cream un nou proiect de tip WCF:
1. File>New Project> Alegem din stanga tehnologia WCF si selectam tipul de proiect WCF Service Application; 2. Se creaza un nou proiect WCF cu un serviciu implicit adaugat: Service1.svc. Fiecare serviciu din proiect are un fisier svc si o interfata (ex IService.cs); In fisierul .svc se scrie codul efectiv pentru realizarea functionalitatii iar in interfata se descrie serviciul. Cream mai intai un serviciu SOAP astfel: In fisierul IService1.cs adaugat automat la crearea proiectului scriem urmatorul cod (marcat cu galben) pe langa cel automat generat:
[ServiceContract] public interface IService1 { [OperationContract] string GetData(int value); [OperationContract] CompositeType GetDataUsingDataContract(CompositeType composite); [OperationContract] string HelloWorld(string name); [OperationContract] List<Oferta> GetOferta(); // TODO: Add your service operations here } // Use a data contract as illustrated in the sample below to add composite types to service operations. [DataContract] public class CompositeType { bool boolValue = true; string stringValue = "Hello "; [DataMember] public bool BoolValue { get { return boolValue; } set { boolValue = value; } }

Autori: Nicolae Tomai, Alexandru Butoi

Lucrarea nr. 6 Medii Avansate de programare 2011


[DataMember] public string StringValue { get { return stringValue; } set { stringValue = value; } } } [DataContract] public class Oferta { [DataMember] public int codprodus { get; set; } [DataMember] public string denumireprodus { get; set; } [DataMember] public string descriereprodus { get; set; } [DataMember] public double pretprodus { get; set; } }

Observam ca in interfata serviciului se declara toate metodele expuse de catre clasa serviciu prin semnatura fiecarei metode avand deasupra meta-atributul [OperationContract]. Tot in acelasi fisier ne putem declara propriile noastre tipuri de date (clase sau structuri de date pe care le utilizam). Aici am declarat o clasa Oferta care are ca si meta-atribut [DataContract] iar fiecare membru are meta-atributul [DataMember]. Acestea in momentul consumarii servicului vor trece printr-un proces de serializare in limbaj WSDL care va contine descrierea serviciului SOAP. In continuare dupa ce am descris scheletul serviciului trebuie sa implementam si logica de programare pe care acest serviciu trebuie sa o execute. Ne propunem sa returnam o lista de produse din oferta unui oarecare furnizor si sa implementam o functionalitate de HelloWorld: In Solution Explorer deschidem fisierul Service1.svc si observam ca practic serviciul se implemnteaza sub forma unei clase care mosteneste din Interfata descrisa anterior. Pe langa codul existent deja mai adaugam cod pentru cele doua functionalitati ale noastre: HelloWorld si GetOferta:
public class Service1 : IService1 { public string GetData(int value) { return string.Format("You entered: {0}", value); }

Autori: Nicolae Tomai, Alexandru Butoi

Lucrarea nr. 6 Medii Avansate de programare 2011


public CompositeType GetDataUsingDataContract(CompositeType composite) { if (composite == null) { throw new ArgumentNullException("composite"); } if (composite.BoolValue) { composite.StringValue += "Suffix"; } return composite; } public string HelloWorld(string name) { return "Hello "+name; } public List<Oferta> GetOferta() { List<Oferta> lst = new List<Oferta>(); lst.Add(new Oferta { codprodus = 1, denumireprodus desc", pretprodus = 12.5 }); lst.Add(new Oferta { codprodus = 2, denumireprodus desc", pretprodus = 10.5 }); lst.Add(new Oferta { codprodus = 3, denumireprodus desc", pretprodus = 15 }); lst.Add(new Oferta { codprodus = 4, denumireprodus desc", pretprodus = 9 }); lst.Add(new Oferta { codprodus = 5, denumireprodus desc", pretprodus = 11}); return lst; }

= "book1", descriereprodus = = "book2", descriereprodus = = "book3", descriereprodus = = "book4", descriereprodus = = "book5", descriereprodus =

"book1 "book2 "book3 "book4 "book5 }

Clasa care implementeaza un serviciu va implementa practic Interfata care descrie functionalitatile serviciului. Pentru a lansa in executie serviciul trebuie sa avem deschisa interfata serviciului si dam run. In acest moment un server de test se va porni si serviciul va fi incarcat intr-un browser. Pentru a testa functionalitatile serviciului trebuie sa avem deschisa implementarea serviciului si dam run. In acest moment un client de test se va lansa in executie care ne va permite sa invocam serviciul:

Trebuie de mentionat ca aceasta facilitate de testare este disponibila doar pentru serviciile de tip SOAP deoarece cele de tip rest se invoca prin URL in browser.

Autori: Nicolae Tomai, Alexandru Butoi

Lucrarea nr. 6 Medii Avansate de programare 2011

Implementarea unui serviciu REST 1. In acelasi proiect WCF pentru a implementa un nou serviciu vom dam click dreapta in Solution Explorer pe numele proiectului si vom selecta Add>New Item. 2. Din fereastra de Itemi disponibili ne asiguram ca in stanga sa avem selectat Web iar in dreapta selectam WCF Service. 3. Denumim serviciul nou creat RestService.svc si dam Add. 4. In acest moment se creaza atat fisierul care va descrie interfata serviciului si anume IRestService.cs cat si RestService.svc, adica fisierul care va contine implementarea serviciului. 5. In interfata IRestService.cs a serviciului scriem urmatorul cod:
[ServiceContract] [XmlSerializerFormat]//specificam ca datele vor fi returnate in format xml serializabil public interface IRestService { [OperationContract] //se specifica parametrii de invocare prin REST a functionalitatii [WebInvoke(Method="GET", ResponseFormat=WebMessageFormat.Xml, BodyStyle=WebMessageBodyStyle.Bare, UriTemplate="getfurnizori/")] List<string> GetFurnizori();

Autori: Nicolae Tomai, Alexandru Butoi

Lucrarea nr. 6 Medii Avansate de programare 2011


[OperationContract] [WebInvoke(Method = "GET", ResponseFormat = WebMessageFormat.Xml, BodyStyle = WebMessageBodyStyle.Wrapped, UriTemplate = "getadresa/{furnizor}")] string GetAdresaFurnizor(string furnizor); [OperationContract] [WebInvoke(Method = "GET", ResponseFormat = WebMessageFormat.Xml, BodyStyle = WebMessageBodyStyle.Wrapped, UriTemplate = "getcif/{furnizor}")] string GetCifFurnizor(string furnizor); }

[DataContract] public class Furnizor { [DataMember] public string nume { get; set; } [DataMember] public string adresa { get; set; } [DataMember] public string telefon { get; set; } [DataMember] public string cif { get; set; } }

WCF lucreaza in mod predefinit cu protocolul SOAP iar pentru a dezactiva protocolul SOAP trebuie ca in interfata serviciului atunci cand specificam semnaturile expuse de serviciu sa specificam meta-atributul WebInvoke: 1. 2. 3. 4. Method modul de invocare a metodei: POST sau GET, PUT, DELETE ResponseFormat formatul de livrare a rezultatelor care poate fi XML sau Json; BodyStyle legat de formatarea datelor rezultat; UriTemplate verbul de invocare al metodei sau formatul url-ului de invocare: {numeparametru} este formatul pentru specificarea parametrilor de intrare ai serviciului Astfel GetAdresaFurnizor se va invoca prin url-ul: localhost:port/RestService.svc/getadresa/furnizor1 - parametru de intrare

Autori: Nicolae Tomai, Alexandru Butoi

Lucrarea nr. 6 Medii Avansate de programare 2011

Urmatorul pas este scrierea implementarii in RestService.svc pentru interfata definita:


public class RestService : IRestService { private List<Furnizor> LoadFurnizori() { List<Furnizor> lst = new List<Furnizor>(); lst.Add(new Furnizor { nume = "Furnizor 1", adresa = "Str. Inului Nr. 10", cif = "RO1234567", telefon = "123455" }); lst.Add(new Furnizor { nume = "Furnizor 2", adresa = "Str. Inului Nr. 10", cif = "RO1234567", telefon = "123534" }); lst.Add(new Furnizor { nume = "Furnizor 3", adresa = "Str. Inului Nr. 10", cif = "RO1234567", telefon = "123213" }); return lst; } public List<string> GetFurnizori() { List<Furnizor> lst = LoadFurnizori(); List<string> nume_f = new List<string>(); foreach (Furnizor fn in lst) { nume_f.Add(fn.nume); } return nume_f; } public string GetAdresaFurnizor(string nume) { List<Furnizor> lst = LoadFurnizori(); foreach (Furnizor fn in lst) { if (fn.nume == nume) { return fn.adresa; } } return ""; } public string GetCifFurnizor(string nume) { List<Furnizor> lst = LoadFurnizori(); foreach (Furnizor fn in lst) { if (fn.nume == nume) { return fn.cif; } } return ""; } }

Autori: Nicolae Tomai, Alexandru Butoi

Lucrarea nr. 6 Medii Avansate de programare 2011


Ultimul pas in implementarea serviciului REST este configurarea acestuia in Web.config. Astfel in Web.config se adauga: (galben)
<system.serviceModel> <services> <!-- conf service--> <service name="WCFServices.RestService" behaviorConfiguration="RestBehavior"> <endpoint binding="webHttpBinding" address="" contract="WCFServices.IRestService" behaviorConfiguration="webBehavior"> </endpoint> </service> </services> <behaviors> <serviceBehaviors> <!-- behaviours--> <behavior name="RestBehavior"> <serviceMetadata httpGetEnabled="true"/> <serviceDebug includeExceptionDetailInFaults="true"/> </behavior> <behavior> <!-- To avoid disclosing metadata information, set the value below to false and remove the metadata endpoint above before deployment --> <serviceMetadata httpGetEnabled="true"/> <!-- To receive exception details in faults for debugging purposes, set the value below to true. Set to false before deployment to avoid disclosing exception information -> <serviceDebug includeExceptionDetailInFaults="false"/> </behavior> </serviceBehaviors> <!-- end point behavior--> <endpointBehaviors> <behavior name="webBehavior"> <webHttp /> </behavior> </endpointBehaviors> </behaviors> <serviceHostingEnvironment multipleSiteBindingsEnabled="true" /> </system.serviceModel>

1. Tagul service configureaza clasa care descrie serviciului specificand calea astfel: namespace.clasa; configureaza comportamentul prin atributul behaviourConfiguration si contine si un nod copil endpoint care in principal specifica contractul atasat si descris de interfata serviciului. 2. Service behavior specifica comportamentul serviciului REST 3. EndPoint Behavior specifica comportamentul interfetei REST; Inainte de testare vom da click dreapta pe proiect in Solution explorer si vom da Rebuild. Apoi ne vom asigura ca langa ceas-ul din windows nu exista nici o instanta de server de test. Daca vreuna exista o vom inchide(click dreapta >Stop). Pentru a testa serviciul REST vom deschide interfata IRestService.cs si vom da run la proiect, actiune care va publica serviciul pe un server de test si va deschide un Directory Listing in browser. 8 Autori: Nicolae Tomai, Alexandru Butoi

Lucrarea nr. 6 Medii Avansate de programare 2011


Pentru a accesa functionalitatile vom introduce in browser urmatoarele URL-uri:

Consumarea serviciilor SOAP si REST Pentru a invoca din aplicatia client un serviciu acesta trebuie sa fie publicat pe o instanta de server de test. Ne intoarcem in proiectul nostru MVC unde adaugam un ServiceReference pentru serviciul SOAP astfel: 1. Click dreapta pe solutie in Solution Explorer > Add Service Reference; 2. Se deschide o fereastra de descoperire a serviciului (pag urmatoare) 3. In fereastra respectiva copiem din browser url-ul serviciului SOAP publicat pe serverul de test:

Autori: Nicolae Tomai, Alexandru Butoi

Lucrarea nr. 6 Medii Avansate de programare 2011

4. Redenumim Namespace-ul in SoapService; 5. Apasam pe Advanced pentru a configura serviciul: in fereastra ce se deschide la lista CollectionType alegem System.Collections.generic.List pentru a putea lucra cu liste de tipul List<> in tratarea rezultatelor venite de la serviciu. 6. Apasam Ok iar referinta catre serviciul SOAP este adaugata. Practic vom avea un nou namespace in proiect care va contine descrierea serviciului si metodele expuse de serviciu, precum si structurile de date ale serviciului. In proiectul nostru MVC in Controller adaugam controller-ul ServiceController:
public class ServiceController : Controller { public ActionResult Index() { return View(); } public ActionResult SoapInvoke() { SoapService.Service1Client client = new SoapService.Service1Client(); List<SoapService.Oferta> lst = client.GetOferta(); return View("SoapInvoke",lst); }

10

Autori: Nicolae Tomai, Alexandru Butoi

Lucrarea nr. 6 Medii Avansate de programare 2011


public ActionResult RestInvoke() { System.Net.WebRequest request = WebRequest.Create("http://localhost:2244/RestService.svc/getfurnizori/"); System.Net.WebResponse response = request.GetResponse(); XmlDocument xdoc = new XmlDocument(); xdoc.Load(response.GetResponseStream()); XmlElement root = xdoc.DocumentElement; XmlNodeList xlist = xdoc.GetElementsByTagName("string"); List<string> furnizori = new List<string>(); foreach (XmlNode xn in xlist) { furnizori.Add(xn.InnerText); } return View("RestInvoke",furnizori); } }

Atentie: Schimbati URL-ul din WebRequest.Create conform url-ului dat de catre serverul dvs. de test. (portul este generat random); Pentru invocarea serviciilor REST nu avem nevoie de referinta pentru ca acestea se invoca prin URL utilizand clasa WebRequest si WebResponse ca si mai sus. 1. Facem request-ul HTTP catre serviciul REST; 2. Obtinem raspunsul in format XML; 3. Parsam mesajul XML si extragem datele de care avem nevoie; Adaugam si view-urile corespunzatoare actiunilor definite: Views/Service/RestInvoke.aspx
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server"> <h2>RestInvoke</h2> <% foreach (string furnizor in Model) { %> <p><%: furnizor %></p> <%} %> </asp:Content>

11

Autori: Nicolae Tomai, Alexandru Butoi

Lucrarea nr. 6 Medii Avansate de programare 2011


Views/Service/SoapInvoke.aspx
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server"> <h2>SoapInvoke</h2> <% foreach (MagazinVirtual.SoapService.Oferta oferta in Model) { %> <p><%: oferta.codprodus %>|<%: oferta.denumireprodus %>|<%: oferta.descriereprodus %>|<%: oferta.pretprodus %></p> <%} %> </asp:Content>

Views/Service/Index.aspx (pentru ServiceController) pentru a le lega pe cele de mai sus


<h2>Index</h2> <%: Html.ActionLink("SoapInvoke", "SoapInvoke") %> <%:Html.ActionLink("RestInvoke","RestInvoke") %>

Integrarea controllerului ServiceController in aplicatia prin adaugarea unui Link pe View-ul Views/Home/Index.aspx
<h2><%: ViewData["Message"] %></h2> <p> To learn more about ASP.NET MVC visit <a href="http://asp.net/mvc" title="ASP.NET MVC Website">http://asp.net/mvc</a>. <%: Html.ActionLink("Invoke services","Index","Service") %> </p>

Mai multe informatii: http://blogs.msdn.com/b/pedram/archive/2008/04/21/how-to-consume-rest-services-with-wcf.aspx http://www.codeproject.com/KB/WCF/RestServiceAPI.aspx http://vishalnayan.files.wordpress.com/2011/03/create-your-first-restful-services-in-wcf.pdf

12

Autori: Nicolae Tomai, Alexandru Butoi

Lucrarea nr. 6 Medii Avansate de programare 2011

Astfel obtinem:

13

Autori: Nicolae Tomai, Alexandru Butoi

Lucrarea nr. 7 Medii Avansate de programare 2011

Unit testing in MVC


Datorit faptului c MVC este foarte bine structurat, proiectul permite ataarea unui proiect de UnitTesting in care se poate scrie cod C# de testare automat a unor funcionaliti. Din pcate versiunea Express a Visual Studio 2010 nu permite ataarea unui proiect de Unit Testing de aceea exemplele urmtoare vor funciona doar in versiunea professional a Visual Studio 2010. 1. Astfel am creat in versiunea Professional un nou proiect MVC: File>New Project>Web> Asp.NET MVC2 Web Application; 2. Suntem intrebati dac dorim crearea unui proiect de Unit Testing ataat. Dm OK

3. Se creaz noua soluie format din dou proiecte: Proiectul MVC propriu-zis i un proiect de testare Unit Testing; Pentru a avea continuitate am utilizat proiectul nceput n laboratoarele anterioare pe care l-am importat n Visual Studio Professional. (dac se import cu ajutorul opiunii Add Existing Project, acest proiect nu va fi ataat corect i nu va fi recunoscut n proiectul de testare astfel c importul s-a fcut prin Refactoring adic adaugarea fiecrei clase, fiier View sau referin de serviciu pe rnd n folderul corespunztor). 4. Click dreapta n Solution Explorer pe proiectul de testare > Add > New Item > Basic Unit Test sau acelai efect se poate obine i din meniul Test>New Test; 5. In proiectul de testare se creaz a clas de testare n care am scris urmtoarele exemple de testare:

Autori: Nicolae Tomai, Alexandru Butoi

Lucrarea nr. 7 Medii Avansate de programare 2011


using MagazinVirtual.Models; .. [TestClass] public class clientiTest { [TestMethod] public void ClientiModel_Test() { //Arrange clienti client = new clienti() { adresa = "abc", email = "abc@yahoo.com", parola = "xyz", nume = "andrei", prenume = "popescu", idlocalitate = 1 }; //Act try { client.ToString(); } catch { Assert.IsFalse(false); } //Assert Assert.IsNotNull(client); } [TestMethod] public void ClientiRepozitory_Test() { //Arrange clientiRepository clienti_repo = new clientiRepository(); //Act IQueryable<clienti> clist = clienti_repo.GetAllClients(); //Assert Assert.IsNotNull(clist); //va da fail pentru ca nu se va putea conecta la baza de date locala //calea proiectului de testare e diferita fata de calea proiectului MVC //baza de date nu va fi gasita //daca se va folosi un server remote de bd aceasta problema va fi eliminata } [TestMethod] public void CreareUtilizator_Test() { //Arrange var controller = new MagazinVirtual.Controllers.HomeController();

Autori: Nicolae Tomai, Alexandru Butoi

Lucrarea nr. 7 Medii Avansate de programare 2011


//Act var result = controller.CreareUtilizator() as ViewResult; //Assert Assert.IsInstanceOfType(result.ViewData.Model, typeof(clienti)); //va da fail pentru ca metoda utilizeaza baza de date->aceiasi pb ca mai sus } [TestMethod] public void SoapServiceInvoke_Test() { //Arrange var controller = new MagazinVirtual.Controllers.ServiceController(); //Act var result = controller.SoapInvoke(); //Assert Assert.IsInstanceOfType(result, typeof(ViewResult)); } [TestMethod] public void RestServiceInvoke_Test() { //Arrange var controller = new MagazinVirtual.Controllers.ServiceController(); //Act var result = controller.RestInvoke() as ViewResult; //Assert Assert.IsInstanceOfType(result.ViewData.Model, typeof(List<string>)); } //test de stres pentru RestInvoke [TestMethod] public void RestServiceInvoke_Test_Stress() { int limit = 10; int k = 0; try { for (int i = 1; i <= limit; i++) { //Arrange var controller = new MagazinVirtual.Controllers.ServiceController(); //Act var result = controller.RestInvoke() as ViewResult; k++; } } catch { Assert.IsTrue(k == limit); }

Autori: Nicolae Tomai, Alexandru Butoi

Lucrarea nr. 7 Medii Avansate de programare 2011


//Assert Assert.IsTrue(k == limit); } [TestMethod] public void Clienti_Data_Input_Test() { //Arrange var controller = new MagazinVirtual.Controllers.HomeController(); var formvalues = new FormCollection(){ {"nume","andrei"}, {"prenume","popescu"}, {"adresa","localitate x"}, {"localitate","1"}, {"email", "alex@gmail.com"},{"parola","parolatest"},{"confirmare","parola1"} }; controller.ValueProvider = formvalues.ToValueProvider(); //Act var result = controller.CreareUtilizator(formvalues) as ViewResult; //Assert Assert.AreEqual("CreareUtilizator", result.ViewName); Assert.IsTrue(result.ViewData.ModelState.Count > 0, "Input error expected!"); } }

Explicaii: 1. Clasa de test are ca i meta-atribut TestClass iar fiecare metod are TestMethod; 2. Fiecare metod de testare nu are tip de return i este construit dup principiul AAA: a. Arrange pregtirea testului prin instanieri i iniializri a structurilor de date; b. Act declanarea, invocarea aciunii dorite pe care vrem s o testm; c. Assert furnizarea ctre platform a condiiei care specific dac testul a trecut sau nu a trecut; Assert conine o serie de metode care evalueaz anumite conditii cnd testul poate fi trecut sau nu. De exemplu n metoda Client_Data_Input_Test() Assert.AreEqual returneaz ca rezultat pentru test Passed dac numele View-ului apelat de ctre aciunea din controller testat este CreareUtilizator, n caz contrar d rezultatul Failed. Similar funcioneaz i Assert.IsTrue, sau Assert.IsFalse sau Assert.IsNotNull.... Dac ntre timp n execuie apare orice excepie testul va fi considerat Failed. Dac se folosete o baz de date local toate testele care vor rula i vor trebui s se conecteze la baza de date local vor da Failed din cauza problemelor generate de calea pe disc a bazei de date din Connection String; Rularea unei metode de testare se face cu ajutorul click-dreapta n interiorul metodei Run Tests 4 Autori: Nicolae Tomai, Alexandru Butoi

Lucrarea nr. 7 Medii Avansate de programare 2011


Jos ni se furnizez rezultatul Testului:

Sau:

Dac dm click dreapta pe rezultatul testului i alegem View Test Results Details ni se furnizez un Error Stack:

Autori: Nicolae Tomai, Alexandru Butoi

Lucrarea nr. 7 Medii Avansate de programare 2011


Resurse suplimentare: Liste dropdown in cascada: http://weblogs.asp.net/rajbk/archive/2010/05/20/cascadingdropdown-jquery-plugin-for-asp-netmvc.aspx - project sample si tutorial ce utilizeaza un plugin jQuery pentru realizarea incarcarii in cascada a listelor Include si librariile jQuery care trebuie adaugate in Scripts si referentiate in View-ul in care se utilizeaza cascading-ul; Encriptare parole MD5: o

public string EncodeMd5(string input) { byte[] orig_buf; byte[] new_buf; System.Security.Cryptography.MD5 md5; md5 = new System.Security.Cryptography.MD5CryptoServiceProvider(); orig_buf = System.Text.ASCIIEncoding.Default.GetBytes(input); new_buf = md5.ComputeHash(orig_buf); string output = System.Text.RegularExpressions.Regex.Replace(BitConverter.ToString(new_buf), "-", "").ToLower(); return output; }

Trimitere Email prin SMTP - clasa:

public class EmailEngine { public string host; public int port; public string username; public string parola; public string from; public EmailEngine() { //preluare din Web.config setarile pentru SMTP Configuration config = WebConfigurationManager.OpenWebConfiguration(HttpContext.Current.Request.ApplicationPath); MailSettingsSectionGroup settings = (MailSettingsSectionGroup)config.GetSectionGroup("system.net/mailSettings"); host = settings.Smtp.Network.Host; port = settings.Smtp.Network.Port; username = settings.Smtp.Network.UserName; parola = settings.Smtp.Network.Password; from = settings.Smtp.From; }

Autori: Nicolae Tomai, Alexandru Butoi

Lucrarea nr. 7 Medii Avansate de programare 2011


public bool SendEmail(string to_email, string subj, string msg) { try { var client = new SmtpClient(host, port); //serverul de smtp si portul client.EnableSsl = true; //comunicare prin ssl System.Net.NetworkCredential SMTPUserInfo = new System.Net.NetworkCredential(username, parola); client.UseDefaultCredentials = false;// foloseste credentiale setate client.Credentials = SMTPUserInfo;//seteaza credentialele MailMessage message = new MailMessage(from, to_email); message.Subject = subj; message.Body = msg; client.Send(message);//trimite mesaj return true; } catch { return false; } } }

In Web.config se fac setarile (aici sunt folosite setarile pentru Gmail):


<configuration> <system.net> <mailSettings> <smtp deliveryMethod="Network" from="test@gmail.com"> <network defaultCredentials="true" host="smtp.gmail.com" port="587" userName="cont@gmail.com" password="oparola"/> </smtp> </mailSettings> </system.net>

.
</configuration>

Se configureaza practic un cont de e-mail care suporta trimitere prin SMTP prin care se va ruta e-mailurile dinspre aplicatia noastra prin serverul si contul de email specifcat ctre destinatie.

Autori: Nicolae Tomai, Alexandru Butoi

S-ar putea să vă placă și