Documente Academic
Documente Profesional
Documente Cultură
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
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:
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;
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
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
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:
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
URL
URL Rewriting
BD 5
Model
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.
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:
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:
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
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.
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;
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
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
<div class="editor-label">
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
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); }
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
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
//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
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
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
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
<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
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
//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
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:
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
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
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
Screenshot-uri
Site.Master-meniu
Adauga client
22
Vizualizare clienti
Editare client
23
Delete client
24
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.
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
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); }
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:
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); }
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:
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(); }
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
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");
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
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
Observam ca verificarea credentialelor in baza de date se face prin metoda GetClientByCredentials implementata in clasa clientiRepistory. Astfel adaugam in clasa clientiRepository metoda:
10
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
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
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)
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
14
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.
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); }
= "book1", descriereprodus = = "book2", descriereprodus = = "book3", descriereprodus = = "book4", descriereprodus = = "book5", descriereprodus =
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.
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();
[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
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
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:
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
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
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>
12
Astfel obtinem:
13
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:
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
Sau:
Dac dm click dreapta pe rezultatul testului i alegem View Test Results Details ni se furnizez un Error Stack:
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; }
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; }
.
</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.