laborator wcf : rezolvare referinte circulare in wcf cu ef.iasimin/laborator c s h/wcf si ef.pdf ·...

12
Laborator WCF : Rezolvare referinte circulare in WCF cu EF. Scenariu Se considera o baza de date ce contine doua tabele Post si Comment intre care exista relatia “one to many”. Un Post are mai multe Comment-uri. Atentia principala este indreptata asupra obtinerii pentru o inregistrare din Post a tuturor inregistrarilor corespunzatoare din Comment, adica modul cum rezolvam referintele circulare ce pot aparea in acest caz. Vom construi un serviciu WCF ce va folosi baza de date ce contine tabelele de mai sus. Pentru acces la baza de date vom folosi Entity Framework. Alegeti ce model de programare doriti: Database First, Model First Designer. Construim o aplicatie CUI sau GUI la alegerea Dv, adica cream in VS 2015 un proiect. Construire server Etapa S1. EDM-ul arata astfel: In exemplul meu PostId din Comment este in plus. Contextul va avea urmatorul cod: namespace Wcf { using System; using System.Data.Entity; using System.Data.Entity.Infrastructure; public partial class ModelPostBlogContainer : DbContext { public ModelPostBlogContainer() : base("name=ModelPostBlogContainer") { // Cod adugat manual Configuration.LazyLoadingEnabled = false;

Upload: others

Post on 06-Dec-2019

18 views

Category:

Documents


0 download

TRANSCRIPT

Page 1: Laborator WCF : Rezolvare referinte circulare in WCF cu EF.iasimin/Laborator C S H/WCF si EF.pdf · Scriem codul pentru inregistrarea serviciului. Acest cod va trebui apelat din Main

Laborator WCF : Rezolvare referinte circulare in WCF cu EF.

Scenariu

Se considera o baza de date ce contine doua tabele Post si Comment intre care exista relatia “one to

many”. Un Post are mai multe Comment-uri. Atentia principala este indreptata asupra obtinerii

pentru o inregistrare din Post a tuturor inregistrarilor corespunzatoare din Comment, adica modul

cum rezolvam referintele circulare ce pot aparea in acest caz.

Vom construi un serviciu WCF ce va folosi baza de date ce contine tabelele de mai sus. Pentru acces

la baza de date vom folosi Entity Framework.

Alegeti ce model de programare doriti: Database First, Model First Designer.

Construim o aplicatie CUI sau GUI la alegerea Dv, adica cream in VS 2015 un proiect.

Construire server

Etapa S1.

EDM-ul arata astfel:

In exemplul meu PostId din Comment este in plus.

Contextul va avea urmatorul cod:

namespace Wcf { using System; using System.Data.Entity; using System.Data.Entity.Infrastructure; public partial class ModelPostBlogContainer : DbContext { public ModelPostBlogContainer() : base("name=ModelPostBlogContainer") { // Cod adugat manual

Configuration.LazyLoadingEnabled = false;

Page 2: Laborator WCF : Rezolvare referinte circulare in WCF cu EF.iasimin/Laborator C S H/WCF si EF.pdf · Scriem codul pentru inregistrarea serviciului. Acest cod va trebui apelat din Main

Configuration.ProxyCreationEnabled = false; } protected override void OnModelCreating(DbModelBuilder modelBuilder) { throw new UnintentionalCodeFirstException(); } public virtual DbSet<Post> Posts { get; set; } public virtual DbSet<Comment> Comments { get; set; } } }

Atentie la codul adaugat manual dupa ce s-a generat contextul. In constructor inhibam “lazy loading”

si de asemenea crearea proxy-ului dinamic:

Configuration.LazyLoadingEnabled = false; Configuration.ProxyCreationEnabled = false;

Clasele POCO vor avea urmatoarea descriere:

Clasa Post

namespace Wcf { using System; using System.Collections.Generic; using System.Runtime.Serialization; [DataContract(IsReference=true)] public partial class Post { public Post() { this.Comments = new HashSet<Comment>(); } [DataMember] public int PostId { get; set; } [DataMember] public string Title { get; set; } [DataMember] public virtual ICollection<Comment> Comments { get; set; } } }

Clasa Comment

namespace Wcf { using System; using System.Collections.Generic; using System.Runtime.Serialization; [DataContract(IsReference=true)] public partial class Comment {

Page 3: Laborator WCF : Rezolvare referinte circulare in WCF cu EF.iasimin/Laborator C S H/WCF si EF.pdf · Scriem codul pentru inregistrarea serviciului. Acest cod va trebui apelat din Main

[DataMember] public int CommentId { get; set; } [DataMember] public string CommentText { get; set; } [DataMember] public int PostId { get; set; } // la mine e in plus [DataMember] public int PostPostId { get; set; } [DataMember] public virtual Post Post { get; set; } } }

Atributele [DataContract] si [DataMember] sunt adaugate manual. La o regenerare a modelului

aceste modificari se pierd.

Etapa S2.

Definim contractul pentru serviciu. Va fi o interfata definita astfel:

[ServiceContract] public interface IPost { [OperationContract] void Cleanup(); [OperationContract] Post GetPostByTitle(string title); [OperationContract] Post SubmitPost(Post post); [OperationContract] Comment SubmitComment(Comment comment); [OperationContract] void DeleteComment(Comment comment); }

Observatie: Va trebui sa adaugati referinte la System.ServiceModel , System.Data.Entity si

System.Runtime.Serialization.

In exemplul meu am construit un proiect GUI cu WPF. Pentru proiecte de tip CUI nu aveti nevoie de

MainWindow si metoda PassMainWindow.

Definim clasa ce implementeaza aceasta interfata:

[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)] public class ServicePost : IPost { MainWindow mHwnd; public ServicePost() { } /// <summary> /// Metoda apelata din server - obiect Single - pentru a pasa

/// referinta la interfata Window. /// In server exista metoda WriteMessage(string) ce va fi apelata

/// din obiectul gazduit de server.

Page 4: Laborator WCF : Rezolvare referinte circulare in WCF cu EF.iasimin/Laborator C S H/WCF si EF.pdf · Scriem codul pentru inregistrarea serviciului. Acest cod va trebui apelat din Main

/// </summary> /// <param name="mw"></param> public void PassMainWindow(MainWindow mw) { mHwnd = mw; } public void Cleanup() { using (var context = new ModelPostBlogContainer()) { context.Database.ExecuteSqlCommand("delete from wcf.comments"); context.Database.ExecuteSqlCommand("delete from wcf.posts"); } mHwnd.WriteMessage("Apel Cleanup..."); } public Post GetPostByTitle(string title) { Debug.WriteLine("GetPostByTitle ... diagnostics"); mHwnd.WriteMessage("Apel GetPostByTitle: Valoare parametru = " + title); using (var context = new ModelPostBlogContainer()) { // Cod mutat in ctor ModelPostBlogContainer //context.Configuration.LazyLoadingEnabled = false; //context.Configuration.ProxyCreationEnabled = false; // Exista o inregistrare in Post care indeplineste conditia? // Se evita o exceptie in client in cazul cand nu exista un Post. var pp = context.Posts.Where(x => x.Title == title); if (pp.Count<Post>() == 0) { return null; } var post = context.Posts.Include(p => p.Comments) .Single(p => p.Title == title); #region Cod comentat /* var post = (from x in context.Posts .Include("Comments") select x).ToList<Post>(); */ /* var post2 = from x in context.Posts where (x.Title == title) select x; var single = post2.Single(); context.Entry(single).Collection(d => d.Comments) .Query() .Load(); */ #endregion // varianta ce lucreaza fara a folosi proprietatea de navigare //var post = context.Posts.Single(p => p.Title == title); mHwnd.WriteMessage("PostId = " + post.PostId.ToString()); return post; } } public Post SubmitPost(Post post)

Page 5: Laborator WCF : Rezolvare referinte circulare in WCF cu EF.iasimin/Laborator C S H/WCF si EF.pdf · Scriem codul pentru inregistrarea serviciului. Acest cod va trebui apelat din Main

{ string temp = string.Format("Apel SubmitPost. Post = (PostId = {0},

Title = {1})", post.PostId, post.Title); mHwnd.WriteMessage(temp); using (var context = new ModelPostBlogContainer()) { context.Posts.Attach(post); // Daca Id = 0 se genereaza Insert, in caz contrar se genereaza Update if (post.PostId == 0) context.Entry(post).State = EntityState.Added; else context.Entry(post).State = EntityState.Modified; context.SaveChanges(); return post; } } public Comment SubmitComment(Comment comment) { string temp = string.Format("Apel SubmitComment. Comment =

(CommentId = {0}, Text = {1})", comment.CommentId, comment.CommentText); mHwnd.WriteMessage(temp); using (var context = new ModelPostBlogContainer()) { context.Comments.Attach(comment); if (comment.CommentId == 0) { // Acesta este Insert context.Entry(comment).State = EntityState.Added; } else { // Setam proprietatea in starea modificat ce are ca rezultat // trecerea starii entitatii in Modified, // dar se va actualiza numai proprietatea, nu intreaga entitate. context.Entry(comment).Property(

x => x.CommentText).IsModified = true; } context.SaveChanges(); return comment; } } public void DeleteComment(Comment comment) { string temp = string.Format("Apel DeleteComment. Comment =

(CommentId = {0}, Text = {1})", comment.CommentId, comment.CommentText); mHwnd.WriteMessage(temp); using (var context = new ModelPostBlogContainer()) { context.Entry(comment).State = EntityState.Deleted; context.SaveChanges(); } } }

Etapa S3.

Page 6: Laborator WCF : Rezolvare referinte circulare in WCF cu EF.iasimin/Laborator C S H/WCF si EF.pdf · Scriem codul pentru inregistrarea serviciului. Acest cod va trebui apelat din Main

Scriem codul pentru inregistrarea serviciului. Acest cod va trebui apelat din Main.

In exemplul dat codul este scris pe handler-ul ce trateaza evenimentul clic al unui buton din interfata

aplicatiei (WPF).

private void ButtonStart_Click(object sender, RoutedEventArgs e) { this.lbInfo.Items.Clear(); this.lbInfo.Items.Add("Start server. Asteptati..."); // Am folosit aceasta varianta pentru a avea acces la interfata din cadrul // metodellor definite in serviciu. // Acest lucru e numai pentru exemplificare si vizualizare

// apel metode pe partea de server

ServicePost servicePost = new ServicePost(); host = new ServiceHost(servicePost, new Uri("http://localhost:8080/post")); try { host.Open(); // Am nevoie de obiectul curent (fereastra) this pentru a-l pasa

// in obiectul de pe server. // Fiecare metoda de pe server va afisa un mesaj in ListBox in

// momentul cand va fi apelata // Management instanta serviciu (atribut plasat pe clasa

// ce implementeaza ServiceContract // [ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)] singletonInstance = (IPost)host.SingletonInstance; ServicePost svp = (ServicePost)singletonInstance; svp.PassMainWindow(this); AfisareEndpoint(); this.lbInfo.Items.Add("Server lansat in executie cu succes!

Asteptare cereri de la clienti..."); } catch(Exception ex) { this.lbInfo.Items.Add("Serverul nu poate fi lansat in executie!"); this.lbInfo.Items.Add(ex.Message); } } private void ButtonStop_Click(object sender, RoutedEventArgs e) { if (host != null) { this.lbInfo.Items.Add("Incercare oprire server..."); try { host.Close(); this.lbInfo.Items.Add("Server oprit!"); } catch { this.lbInfo.Items.Add("Probabil server nelansat.

Actiune esuata."); }

Page 7: Laborator WCF : Rezolvare referinte circulare in WCF cu EF.iasimin/Laborator C S H/WCF si EF.pdf · Scriem codul pentru inregistrarea serviciului. Acest cod va trebui apelat din Main

} } private void AfisareEndpoint() { foreach (ServiceEndpoint se in host.Description.Endpoints) { string temp = String.Format("A (address): {0}, EP name : {1}

B (binding): {2}, (Contract): {3} Uri: {4}", se.Address, se.Name, se.Binding.Name, se.Contract.Name,

se.ListenUri.AbsolutePath); this.lbInfo.Items.Add(temp); foreach(var x in se.EndpointBehaviors) { string t = x.ToString(); this.lbInfo.Items.Add(t); } } } // Metoda este apelata de operatiile din serviciu. public void WriteMessage(string msg) { lbInfo.Items.Add(msg); } Fisierul de configurare App.config trebuie sa contina si <system.serviceModel> <services> <service name="Wcf.ServicePost" behaviorConfiguration="metadataSupport"> <endpoint address ="http://localhost:8080/post" binding="basicHttpBinding" contract="Wcf.IPost" name="BasicHttpBinding_IPost" > <identity> <dns value="localhost"/> </identity> </endpoint> <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" name="mexhttp" /> </service> </services> <bindings> <netTcpBinding> <binding name="mynet" sendTimeout="00:00:05" portSharingEnabled="true"> <security mode="None" /> </binding> </netTcpBinding> </bindings> <behaviors> <serviceBehaviors> <behavior name="metadataSupport">

Page 8: Laborator WCF : Rezolvare referinte circulare in WCF cu EF.iasimin/Laborator C S H/WCF si EF.pdf · Scriem codul pentru inregistrarea serviciului. Acest cod va trebui apelat din Main

<!-- Enables the IMetadataExchange endpoint in services that --> <!-- use "metadataSupport" in their behaviorConfiguration --> <!-- attribute. --> <!-- In addition, the httpGetEnabled and httpGetUrl --> <!-- attributes publish--> <!-- Service metadata for retrieval by HTTP/GET at the address --> <!-- "http://192.168.0.102:8080/SampleService?wsdl" --> <serviceMetadata httpGetEnabled="true" httpGetUrl=""/> <!-- <serviceMetadata/>--> <serviceDebug includeExceptionDetailInFaults="true"/> </behavior> </serviceBehaviors> </behaviors> </system.serviceModel>

Build aplicatie.

Construire client.

In cadrul aceleasi solutii cream un proiect de tip CUI sau GUI.

Etapa C1.

Obtinere metadata pentru client.

Lanasam serverul in executie. Dintr-o fereastra de comanda deschisa din Visual Studio Tools scriem

linia de comanda:

Comanda este :

svcutil http://localhost:8080/post /out:e:\proxy /config:e:\app.config

Efectul este acela ca se va genera fisierul proxy.cs si fisierul app.config in partitia E:\

Etapa C2.

Oprim serverul si adaugam aceste fisiere la proiectul nostru (cel pentru client).

Page 9: Laborator WCF : Rezolvare referinte circulare in WCF cu EF.iasimin/Laborator C S H/WCF si EF.pdf · Scriem codul pentru inregistrarea serviciului. Acest cod va trebui apelat din Main

Vizualizam proxy.cs si vom vedea ceva de forma:

Aceasta clasa o vom instantia in client.

Fisierul de configurare este (cel generat):

<?xml version="1.0" encoding="utf-8"?> <configuration> <system.serviceModel> <bindings> <basicHttpBinding> <binding name="BasicHttpBinding_IPost" /> </basicHttpBinding> </bindings> <client> <endpoint address="http://localhost:8080/post" binding="basicHttpBinding" bindingConfiguration="BasicHttpBinding_IPost" contract="IPost" name="BasicHttpBinding_IPost" /> </client> </system.serviceModel> </configuration>

Etapa C3. Apele metode din serviciu

Codul din client pentru a apela metoda GetPostByTitle() este urmatorul (din nou in proiectul meu

clientul este WPF):

private void GetAllPosts(object sender, RoutedEventArgs e) { PostClient pc = new PostClient(); Wcf.Post wcfPost = pc.GetPostByTitle("T1"); AddPostInListBox(wcfPost); wcfPost = pc.GetPostByTitle("WCF - FAQ"); AddPostInListBox(wcfPost); // Nu exista un Post cu Title="a". wcfPost = pc.GetPostByTitle("a"); AddPostInListBox(wcfPost); pc.Close(); } private void AddPostInListBox(Wcf.Post wcfPost) { if (wcfPost == null) return; this.listBox.Items.Add(wcfPost.PostId.ToString() + ". " + wcfPost.Title); foreach (var x in wcfPost.Comments) {

Page 10: Laborator WCF : Rezolvare referinte circulare in WCF cu EF.iasimin/Laborator C S H/WCF si EF.pdf · Scriem codul pentru inregistrarea serviciului. Acest cod va trebui apelat din Main

this.listBox.Items.Add("\t" + x.CommentId.ToString() + " . " + x.CommentText);

} }

Observatie:

In codul prezentat mai sus nu am exemplificat inserarea in tabele.

Inserati date in tabele si apoi verificati executia acestui cod.

Lansam in executie mai intai serverul si apoi clientul.

Rezultatul este:

Dupa lansarea clientului si clic pe butonul din interfata se obtine:

Observati mesajele pe partea de server si cele pe partea de client.

Observatie:

In mod normal pe partea de server se executa o jurnalizare a operatiilor (scriere mesaje intr-un fisier

de log-uri). Varianta aleasa aici este numai in scop demonstrativ sa observam ce “face” serviciul si

care sunt rezultatele pe partea clientului.

Observatie

Fiseirul de configurare pentru client il putem obtine si in modul urmator:

1. Lansam serviciul

2. Lanasam aplicatia WcfTestClient.exe si inregistram serviciul

http://localhost:8080/post

3. Obtinem (dupa ce am facut dublu clic pe Config File):

Page 11: Laborator WCF : Rezolvare referinte circulare in WCF cu EF.iasimin/Laborator C S H/WCF si EF.pdf · Scriem codul pentru inregistrarea serviciului. Acest cod va trebui apelat din Main

4. 5. Facem dublu clic pe GetPostByTitle, completam valorile parametrilor si apoi Invoke

6. 7. Mesajele SOAP in format XML sunt (clic pe tab XML)

Page 12: Laborator WCF : Rezolvare referinte circulare in WCF cu EF.iasimin/Laborator C S H/WCF si EF.pdf · Scriem codul pentru inregistrarea serviciului. Acest cod va trebui apelat din Main

8.