Fara sa compilati, vedeti ce output da codul acesta:
decimal? v = 100; Console.WriteLine(v); decimal? x = null; x += v.Value; Console.WriteLine(x);
Ok, e clar raspunsul avind in vedere intrebarea ...
Sunt pe cale sa termin un ebook despre ce poti face cu Visual Studio 2010 si .NET 4.0 . Ca de obicei, o sa fie free - si, ca de obicei, nu pot sa il termin singur si am nevoie de ajutorul vostru. O sa incerc sa fie gata inainte de
Mai sunt de scris capitole despre
TODO VS2010 : WPF, Workflow,Silverlight, Azure, Reporting , Sharepoint, DatabaseProject, SetupProject, Extensibility, Setup , TestProject, CodeAnalysis,Help, Windows Mobile , F#
TODO .NET 4 : CodeContracts, Parallel Extensions, (contra)variance, Tuples, SortedSet si altele
Daca vreti sa contribuiti (cod + text) , va rog sa imi spuneti (email )!
O fie in curind lansarea Visual Studio 2010 in Romania si m-am gindit sa scriu un mini-e-book despre ce poti face cu Visual Studio 2010. La ce ar fi bun ? Pai – daca te intereseaza programarea pe WindowsMobile sa stii de un de sa o apuci.
L-am rugat pe Marius Istudor sa participe la acest proiect pentru WindowsMobile 6.5. El a facut scris documentul pe care il vedeti si aplicatia in VS2008 – si , din pacate, VS2010 nu mai suporta Windows Mobile 6.5 (http://msdn.microsoft.com/en-us/library/sa69he4t%28VS.100%29.aspx)
Ii multumesc si pe aceasta cale pentru munca depusa !
-------------------------
Dezvoltarea de aplicatii cu WindowsMobile
De data asta Ion vine cu o noua cerinta :ar putea face o aplicatie pentru WindowsMobile ? Are un client un telefon cu Windows Mobile si ar fi interesat de aplicatie. Ar putea Popescu sa il ajute ?
Popescu studiaza si , pentru ca subiectul e mai lung, scrie un tutorial intreg despre acest lucru:
Programarea pentru dispozitivile mobile este diferita de programarea desktop sau web. Cand construim o aplicatie care va rula pe un smarptphone, pocket pc sau alt dispozitiv cu Windows Mobile, va trebuie sa luam in considerare mai multe lucruri: bateria (aplicatia nu trebuie sa aiba un impact mare asupra consumului bateriei), procesorul (frecvente mici, care afecteaza ritmul operatiilor sau al interogarilor in baza de date), dimensiunea dispozitivului cu rezolutii diferite ale display-ului, conexiunile la retea, memorie, securitate si altele.
In dezvoltarea aplicatiilor destinate dispozitivelor mobile s-au facut progrese, respectandu-se cerintele programatorilor de a avea framework-uri si aplicatii, acelasi IDE atat pentru dezvoltarea mobila cat si pentru cea desktop, curba de invatare usoara, cunostinte de programare ce pot fi folosite in ambele tipuri de programare.
Windows Mobile este versiunea Compact Edition, pentru dispozitivele mobile, a sistemului de operare Windows.
Windows Mobile prezinta ca avantaje, capacitatea extraordinara pentru multi-tasking, alocand automat memorie in functie de necesitatile fiecarei aplicatii, sincronizarea cu un sistem desktop/laptop, interfata intuitiva, mai ales pentru utilizatorii Windows.
O aplicatie Windows Mobile se poate dezvolta in cod nativ (Visual C++), in managed code si in mod server-site (aplicatii gazduite de browser).
Microsoft are o platforma mobila puternica, cu un runtime puternic in .Net Compact Framework, instrumente de dezvoltare puternice ca Visual Studio si un suport pe masura in ceea ce priveste comunitatea programatorilor de Windows Mobile.
Ultima versiune de .Net Compact Framework este 3.5, pe baza careia vom dezvolta aplicatia noastra. Microsoft recomdanda folosirea acestei ultimei versiuni, dar alegerea ramane la atitudinea programatorului (cui se adreseaza aplicatia, de ce particularitati are nevoie in dezvoltarea). Versiunea 2.0 a .Net Compact Framework ruleaza pe foarte multe dispozitve actuale, dar totusi ultima versiune aduce foarte multe lucruri noi in aceasta ramura a programarii, printre care Language Integrated Query, Windows Communication Foundation.
Cerinte pentru dezvoltarea in Windows Mobile
Sistemele de operare:
Windows XP/Windows Server 2003 + ActiveSync (pentru sincronizarea desktop/laptop – dispozitiv mobil)
Windows Vista/Windows 7/Windows Server 2008 + Windows Mobile Device Center (pentru sincronizarea desktop/laptop – dispozitiv mobil)
Instrumente despre dezvoltare
Pentru dezvoltarea unei aplicatii Windows Mobile 6.5, avem nevoie de urmatoarele instrumente:
1. IDE (Integrated Development Environment)
Visual Studio 2008 Professional +.Net Compact Framework 3.5
Visual Studio 2005 Standard + .Net Compact Framework 2
Se recomanda ca acestea sa aiba instalat ultimul Service Pack.
Editiile Express ale Visual Studio nu suporta dezvoltarea proiectelor pentru dispozitive mobile.
2. SDK (Software Development Kits)
Windows Mobile 6 Standard SDK Refresh – contine librarii, documentatie, exemple si coduri sursa pentru dezvoltarea aplicatiilor Windows Mobile 6.
Windows Mobile 6 Professional SDK Refresh – contine in plus,
Se recomanda dezinstalarea versiunilor anterioare de SDK.
Cele doua tipuri de SDK se pot instala simultan.
3. DTK (Developer Tool Kit)
Windows Mobile 6.5 Developer Tool Kit 6.5 - include emulatoarea, API-uri pentru dezvoltarea touchscreen, coduri sursa, disponibile pentru Windows Mobile 6.5.
4. Windows Mobile 6.1 Emulator Images (optional)
Windows Mobile 6.1 Emulator Images – consta intr-un pachet de emulatoare care poate fi folosit cu Visual Studio/sau nu, pentru testarea aplicatiilor. Avem nevoie de emulatoare pentru a suplini lipsa mai multor tipuri de dispozitive si pentru a testa aplicatia noastra in diferite cazuri.
Avand toate instrumentele pregatite si instalate, vom incepe construirea unei aplicatii mobile.
Dezvoltarea unui program Windows Mobile 6.5
Vom crea o aplicatie Windows Mobile 6.5, bazata pe Windows Mobile Standard SDK, adresata dispozitivelor fara touchscreen, care va consta in adunarea a doua numere. Simplu, nu?
Deschidem Visual Studio 2008.
File – New Project
In aceasta prima fereastra, in partea stanga, vom selecta mai intai limbajul de programare, in cazul nostru, Visual C#.
Alegem un proiect de tip SmartDevice, iar in partea dreapta a ferestrei va aparea sablonul (template) corespunzator instalat in Visual Studio 2008 – Smart Device Project.
De mentionat ca un proiect de tip SmartDevice se poate realiza si in limbajul Visual Basic sau Visual C++ (cod nativ – pentru aplicatii simple, foarte performante, care necesita in general accesarea platformei hardware).
Nota: De obicei, cand contruim aplicatii cu Visual Studio 2008, selectam versiunea de .Net Framework pe care o vom folosi. In cazul dezvoltarii unei aplicatii Windows Mobile, acest pas nu are nici o importanta, pentru ca acest tip de aplicatie va folosi .Net Compact Framework, a carei versiune o vom selecta in fereastra urmatoare. Deci, nu ne va interesa selectarea unei anumite versiuni de .Net Framework.
Apasam butonul OK pentru a continua crearea proiectului.
Fereastra urmatoare ne va permite sa finalizam crearea proiectului nostru, prin selectarea mai multor optiuni:
Platforma pe care vom construi aplicatia: Windows Mobile 6 Standard SDK
Versiunea de .Net Compact Framework: 3.5
Vom selecta sablonul pentru Device Application, pentru crearea unei aplicatii de tip forms.
In partea de jos a ferestrei, observati un link catre site-ul Microsoft, de unde puteti descarca toate instrumentele necesare dezvoltarii aplicatiilor Windows Mobile.
Apasam OK si vom termina etapa de configurare a proiectului, urmand sa construim efectiv aplicatia.
Asa va arata forma, cu “skinul” specific unei aplicatii standard:
Daca sunteti familiarizati cu mediul Visual Studio 2008, nu veti observa mari diferente. In partea stanga se afla lista controalelor disponibile (Toolbox), in partea dreapta, Solution Explorer, unde se afla fisierele proiectului. In cazul in care se doreste schimbarea platformei pentru care se dezvolta aplicatia, avem la click dreapta pe proiect optiunea Change Target Platform…
Putem “scapa” de skin, selectand optiunea de la click dreapta Show Skin.
Apelam fereastra de proprietati a formei. Modificam proprietatea Text: Compute Application si putem schimba skin-ul prin selectarea Form Factor.
Controalele disponibile sunt mai putine decat la Windows Forms, dar le putem gasi pe cele mai des folosite. Se pot deriva si crea controale “custom” in aplicatie. Clasele din System.Windows.Forms au fost optimizate pentru resursele unui dispozitiv.
Pentru aplicatia noastra, vom avea nevoie de trei label-uri si doua textbox-uri. De aceste controal, putem dispune prin simplu “drag and drop” din Toolbox.
Putem folosi aceleasi instrumente de aliniere a controalelor din bara de instrumente Layout a Visual Studio.
Primul TextBox corespunzator primului numar, il vom numi txtFirst, pe cel de-al doilea, evident, txtSecond.
Nu trebuie sa ne facem griji pentru latimea celor doua texbox-uri. Le putem seta in asa fel incat, sa fie egale cu latimea maxima a display-ului dispozitivului.
Click dreapta: View Code pentru a naviga in codul din spatele formei (code behind). In constructorul clasei, vom apela metoda ControlSettings, ale carei instructiuni, sunt prezentate mai jos:
private void ControlSettings()
{
//seteaza culoarea fundalului pentru aceasta forma
this.BackColor = Color.Lime;
//seteaza latimea, culoarea fundalului si culoarea textului pentru primul textbox
txtFirst.Width = System.Windows.Forms.Screen.PrimaryScreen.Bounds.Width;
txtFirst.BackColor = Color.Blue;
txtFirst.ForeColor = Color.White;
txtSecond.Width = System.Windows.Forms.Screen.PrimaryScreen.Bounds.Width;
txtSecond.BackColor = Color.Blue;
txtSecond.ForeColor = Color.White;
//eticheta nu va avea text
lblTotal.Text = string.Empty;
}
Pe butoanele principale ale dispozitivului, cele doua softKey-uri (left si right), le vom denumi si vom stabili ca la apasarea lor, fiecare sa apeleze o anumita functie. (le vom crea un handler)
Click pe SoftKey-ul din stanga, iar in fereastra de proprietati (care poate fi apelata cu F4) modificam proprietatea Text si vom adauga Calculeaza. Dublu click pentru crearea unui handler pentru acest buton.
private void menuItem1_Click(object sender, EventArgs e)
//daca se returneaza true
if (Validate())
//calculeaza
Compute();
//blocheaza controalele dupa calcul
LockControls();
};
In acest handler, vom apela o functie de validare a texboxurilor (ambele sa fie completate si sa contina numere). Ea va returna true daca utilizatorul este cuminte J (va introduce in ambele textbox-uri cifre), si va returna false, altfel.
private bool Validate()
//verificam daca in ambele textbox-uri au fost introduse caractere
if (txtFirst.Text.Length == 0 || txtSecond.Text.Length == 0)
MessageBox.Show("Check if the textboxes are filled in!");
return false;
else
try
first = Double.Parse(txtFirst.Text);
//prindem exceptia pentru format invalid – non-numeric
catch
MessageBox.Show("Invalid number!");
second = Double.Parse(txtSecond.Text);
MessageBox.Show("Invalid number");
//toate validarile au fost trecute cu succes
return true;
first si second sunt variabile globale, declarate la inceputul programului:
//variabile globale
private double first = 0;
private double second = 0;
Metoda Compute va returna rezultatul adunarii celor doua numere, convertit in string.
//calculeaza cele doua numere si converteste rezultatul in string
private void Compute()
lblTotal.Text = "Sum: " + (first + second);
Dupa apelul functiei Compute, se va apela si functia LockControls.
Metoda LockControls va bloca cele doua textbox-uri si butonul corespunzator softkey-ului din stanga, pentru a preveni introducerea altor numere pana la un calcul nou.
private void LockControls()
//blocheaza primul element al meniului
mainMenu1.MenuItems[0].Enabled = false;
//blocheaza textbox-urile
txtFirst.Enabled = false;
txtSecond.Enabled = false;
In momentul de fata, daca am parcurs tot acest cod, in aplicatie va fi afisata suma celor doua numere .
Trecem la softkey-ul din dreapta, caruia ii modificam proprietatea Text (“Options”) si ii mai adaugam inca doua elemente: Recompute si Exit.
In handler-ul pentru Recompute vom apela functia UnlockControls, care va debloca textbox-urile si butonul Compute pentru a putea inita un alt calcul.
private void UnlockControls()
//deblocheaza controalele
mainMenu1.MenuItems[0].Enabled = true;
txtFirst.Enabled = true;
txtSecond.Enabled = true;
//sterge textul din textbox-uri
txtFirst.Text = string.Empty;
txtSecond.Text = string.Empty;
//seteaza focusul pe primul textbox
txtFirst.Focus();
Handler-ul butonului Exit va inchide aplicatia.
private void menuItem3_Click(object sender, EventArgs e)
//inchide aplicatia
Application.Exit();
Dupa ce am terminat, apasam CTRL-SHIFT-B (pentru compilare) si, daca nu avem erori, putem apasa F5 pentru rularea aplicatiei. Imediat, va aparea o fereastra in care sunt disponibile emulatoarele catre care putem face “deploy” aplicatiei.
Selectam unul din emulatoarele unui dispozitiv 6.5. Personal, voi selecta USA Windows Mobile 6.5 Standard Landscape QVGA Emulator.
Mesajul din partea de jos a ferestrei Show me this dialog each time I deploy the application este folositor daca dorim ca aceasta fereastra sa (nu) apara la fiecare rulare a aplicatiei.
Nota: In cazul in care nu se gasesc in lista Emulatoarele pentru Windows Mobile 6.5, asigurati-va ca ati instalat Windows Mobile 6.5 Developer Tool Kit.
Vom continua apasand Deploy.
Actiunea de “deployment” va dura mai mult prima data, pentru ca acesta va include si sistemul de operare, nu doar aplicatia noastra.
Dupa terminarea actiunii de deployment, pe ecran, va rula aplicatia noastra:
Navigarea prin aplicatie se face cu ajutorul butoanelor.
Se introduc de la tastatura doua cifre si se apasa butonul Compute pentru afisarea sumei lor.
Putem testa aplicatia noastra, in diferite ipostaze., configurand emulatorul. Apasam butonul Device Options :
Apare fereastra Options, unde vom selecta emulatorul caruia ii vom modifica proprietatile:
Selectam Emulator Options.
In aceasta fereastra, navigand prin meniul cu tab-uri, putem configura emulatorul pentru situatiile in care vrem ca acesta sa aiba un folder de pe calculatorul nostru (se va comporta ca un “storage card” in emulator). De asemenea, se poate testam aplicatia pe anumite dimensiuni ale display-ului(DISPLAY), conectarea la retea(NETWORK), nivelul baterie scazut(PERIPHERALS), etc.
Windows Mobile este un sistem de operare foarte puternic, adresat unui numar mare si variat de dispozitive, cu o multitudine de aplicatii compatibile (windows market place).
Puteti downloada proiectul de la adresa
http://serviciipeweb.ro/iafblog/content/binary/tutorialWinMobile6.rar
Daca va pasioneaza ASP.NET MVC 2, atunci va invit sa veniti simbata, 24 aprilie 2010, la Cladirea City Gate, sala de conferinte Romulus si Remus, de la ora 9:30. Voi prezenta ASP.NET MVC 2.0 si citeva lucruri care o sa va faca munca mai usoara ca developer – nu neaparat de MVC ... Totul va fi cu aplicatie practica la www.scurt.ro.
Mai sunt si alte prezentari – cititi aici http://www.codecamp.ro/post/2010/04/09/Codecamp-la-Bucuresti.aspx
Va astept!
setari globale (write once, read more). De obicei stocate in
Path.GetDirectoryName( Assembly.GetEntryAssembly().GetName().CodeBase))
setari de user (write –read). De obicei stocate in
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData)
User settings ,application settings
HKCU, HKLM
Se pune problema unde se serializeaza : hard, baza de date.
http://jachman.wordpress.com/2006/09/11/how-to-access-ini-files-in-c-net/
http://www.codeproject.com/KB/cross-platform/INIFile.aspx
Puteti crea o tabela cu 3 coloane : Obiect,Nume,Valoare. Acestati cu EF, L2S , NHibernate sau orice altceva.
Downloadati codul
Tutoriale video aici
Doua noi tutoriale video despre a salva setarile cu o clasa ce poate fi serializata in fisierul de configuratie si inca unul despre salvarea in registry
Aplicatia este www.scurt.ro si o gasiti la adresa www.scurt.ro. Este o aplicatie de shortening service ( tinyurl si bit.ly fiind unele cunoscute deja).
Documentatia aplicatiei o gasiti la adresa http://www.scurt.ro/Home/About .
Mai intii , ce vreau sa fac cu aceasta aplicatie: Vreau sa arat ca programarea este doar baza piramidei. Si ca munca pentru o aplicatie simpla este destul de mare, in plus mai trebuie tot felul de persoane - testare,raportare, administrator de BD, SEO specialist, marketing, vinzari si altii…
Aplicatia mai are citeva chestii de facut ( de ex., paginile pentru utilizatorii inregistrati, logare,erori , add-on de IE si Firefox, SEO, etc- le gasiti in documentul http://www.scurt.ro/Docs/aplicatia%20scurt.docx )
Pentru cei care ma ajuta nu pot sa le promit nimic – decit ca vor fi mentionati printre autori –si vor avea link pus pe o pagina care trebuie definita.
Cine vrea sa ma ajute, va rog sa cititi documentul, downloadati sursele – si vorbim pe email!
Multumesc,
Andrei
Acesta este un Guest Post de Pascanu Alexandru si a fost inspirata de cartea de pe amazon - Professional ASP.NET 2.0
Remote debugging e foarte simplu de realizat incepand de la VS 2005 singurele probleme sunt cele de securitate in sensul ca trebuie sa ai credentialele corespunzatoare pe ambele masini (masina client pe care e VS-ul si masina server remote pe care e aplicatia la care vrem sa-i facem debugging).remote debuger se gaseste la C:\Program Files\Microsoft Visual Studio 9.0\Common7\IDE eu avand instalat vs 2008 aici .
Am facut acest folder share cu permisiuni de read pt everyone –guest veti vedea mai departe de ce !
Pentru a face remote debugging trebuie setate masina remote pentru aceasta operatiune,in loc de a face niste setari de system complicate si avansate ,tot ce trebuie facut e sa lansam aplicatia msvsmon.exe din shareul creat mai sus –asta de pe masina remote normal.
Se pot depana procese remote ce ruleaza sub contul de user care a lansat Visual studio ,asta fara a da alte permisuni in prealabil .Daca de exemplu trebuie sa se faca debug pe un process care ruleaza sub alt cont de utilizator decat contul care a initiat debuggingul remote,de exemplu un process aspnet,atunci trebuie avute dreturi de administrator pe masina remote-ma refer la contul ce initiaza debuggingul.
Cel mai important lucru cand se face remote debugging e urmatorul:Userul sub care ruleaza Visual studio trebuie mapat cumva catre un cont de utilizator de pe masina remote ce ruleaza msvsmon.exe si viceversa.Cel mai usor se poate face asta creand un cont de user local pe ambele masini cu acelasi username si password.
Daca masinile sunt in acelasi domeniu cel mai simplu e sa se lanseze aplicatiile msvsmon.exe si Visual Studio sub acelasi Domain Account,daca sunt in domenii/workgroupuri diferite solutia e cu acelasi local account.
O observatie merita facuta aici:Pentru masinile windows xp aflate intr-un workgroup politica de securitate Network Acces:Shared and security model for local Accounts afecteaza remote debugging daca e setata guest only,trebuie aleaza optiunea cealalata classic.Acest lucru nu e o problema pt masinile winxp sau win2003 logate intr-un domeniu NT
Debuggingul Remote pe o masina Windows Xp cu sp2
Trebuie sa ne asiguram ca portul TCp 80 e deschis ,asta daca dorim sa depanam o aplicatie aspnet,pentru ca iis si aspnet sa comunice cu masina remote .se recomanda sa tinem limitate aria de unde poate fi facut remote debugging adica local subnet in locul accesarii remote debugging de pe net.”Under the hood” procesul de remote debugging foloseste dcom mai exact se stabileste o conexiune dcom intre client (Visual Studio) si server adica aplicatia msvsmon.exe deci porturile tcp 135 si udp 4500 trebuie sa fie deschise ,dar dupa cum v-am zis se ocupa msvsmon de configurarea masinii,lucrurile astea trebuie avute in vedere daca facem remote debugging de pe internet printr-un vpn tunel ipsec de exemplu si atunci se complica un pic lucrurile.
Un ultim lucru de verificat este ca msvsmon.exe sa fie in lista de exceptii a firewall de xp.Inca odata subliniez nu e recomdat ca sa lasam sesiunea de debugging adica masina pt remote debugging lumii din afara internetului.remote debugging in sine e ultima solutie –in cazul in care un bug nu poate fi repprodus pe masina de test/development si trebuie sa se faca pe masina de productie de deployment.
O sa prezint mai jos urmatorul caz pe care l-am reprodus la mine:
Visual Studio ruleaza pe o masina din domeniul firmei iar msvsmon.exe masina pe care fac remote debugging ruleaza pe o masina virtuala de windows 7 cu un worckgroup al ei:
Primul pas ma loghez pe statia mea windows 7 (vm-ul pe care fac debugging) cu userul local Alex –user cu care voi rula visual studio atentie trebuie sa fie acelasi pt ca unul e in worckgroup altul domeniu.
Pas2 accesez aplicatia msvsmon.exe din shareul Remote Debugging despre care am vorbit mai sus
Pas 3Din acest share lansez aplicatia msvsmon.exe:
Pas4 Rulez Visual studio ca si userul local Alex –user sub care ruleaza msvsmon.exe remote-pt asta dau click drepta pe Vs run as si aleg useru alex.Pe vm pornesc aplicatia dot net la care se vrea depanarea:
Pas 5 Din vs2008 ma atasez remote la procesul pe care vreau sa-l depanaez in cazul meu Aplicatia Adm_client.exe in cazul in care conexiunea s-a realizat cu success in msvsmon o sa afisat userul alex-pc cu statusul connected ca in figura:
Pas 6 Nu-mi ramane decat sa pun un break point in locul in care vreau depanarea in cazul meu am pus la lina catch cand conexiunea nu se poate realize sis a incerc din aplciatia remote sa execut operatiunea de logare.Se observa ca debuggerul se lanseaza in locul in care am pus breakpoint:
Cam asta ar fi tot,sper ca m-am facut destul de explicit si ca o sa foloseasca cuiva vreodata acest articol.
Cum fac simplu citirea de pagini de pe Web ? Prima varianta este sa incercati cu WebRequest , http://msdn.microsoft.com/en-us/library/system.net.webrequest.aspx . Dar ar trebui sa parsati HTML-ul care NU este XHTML … si imediat va ginditi ca mai sunt sute altii care au aceeasi problema. Asa ca am gasit HTML Agility Pack , http://www.codeplex.com/htmlagilitypack , care stie sa transforme un HTML in XHTML.
Codul pentru incarcarea unei pagini e ridicol de simplu :
HtmlWeb hw = new HtmlWeb(); hw.AutoDetectEncoding = true; HtmlDocument doc = hw.Load(Url); HtmlNode NodeRoot = doc.DocumentNode;
Si de la NodeRoot puteti incepe XPATH cu SelectNodes
Am avut ocazia sa am pe mina Dot Trace de la Jet Brains. Si l-am incercat pe o aplicatie Windows, dar unde si-a aratat puterea a fost in aplicatii Web.
Ca sa vezi ce iti ia cel mai mult timp din aplicatie ai putea sa faci asa : dupa ce ai facut un snapshot, apesi pe plain view, dupa care apesi pe filter. Frumos din partea lui ca vine cu citeva definitii deja existente pentru tool-uri pe care nu vrei sa le vezi la inceput :
Dar poti sa mai adaugi si tu altele – daca vezi ca obtii in fata alte chestii(MS, log4net) de care vrei sa scapi si esti 99% sigur ca sunt optimizate la greu.
In fine, dupa ce dai OK, poti incepe sa inspectezi codul. Asa am vazut ca, pentru o pagina in care ar fi trebuit chemat “get_LastStep” de 9 ori , se chema de 18 ori cu un timp total de 11 ms. Concluzia : se impune un cache – macar primitiv!
Ca puncte tari : are export in XML. Dar tare m-ar fi bucurat un export in Excel – sa pot sa fac eu sortare /filtrare si alte chestii…
De avut NEAPARAT!
De folosit oricind dupa terminarea unui proiect(atentie : aici se vede cit de bune au fost testele!)
Pentru infovalutar (mai exact, pentru mine …) am vrut sa preiau licitatiile de la banci.
Pentru rezultatul final, vezi http://infovalutar.ro/licitatie
Dar sa vedem care a fost povestea :am inceput cu preluarea paginilor HTML . Incepusem cu HttpWebRequest - dar am descoperit la timp HtmlAgilityPack si am ramas credincios lui.
Acum, dupa preluarea paginilor HTML( de ex., http://www.banca-romaneasca.ro/main.php?did=527&code=executare+silita) a fost de ajuns un XPath + expresie regulata de parsare a text-ului din interior.
Ce mi-a produs batai de cap a fost http://vanzari.leumi.ro/bunuri_imobile.html – aveau bunurile in document Word! Ori, ca sa ii ceri celui de la Hosting sa instaleze Word-ul ca sa il instantiezi tu in ASP.NET e aproape imposibil!
Solutia : ASPOSE.WORDS - citeste documente dintre cele mai diverse si scoate un TXT superb – si asta, fara sa aiba nevoie de WORD instalat(se prea poate sa fi omorit muste cu tunul …)
Ca folosire, trebuia sa ii dau un Stream – dar cind am incercat sa ii dau stream-ul de document, mi-a zis ca nu suporta Seek. Asa incit am rezolvat cu un MemoryStream :
public string LeumiData(string URL) { byte[] buffer = new byte[1024*1024*4];
HttpWebRequest hwr = WebRequest.Create(URL) as HttpWebRequest; using (WebResponse response = hwr.GetResponse()) { using (Stream responseStream = response.GetResponseStream()) { using (MemoryStream memoryStream = new MemoryStream()) { int count = 0; do { count = responseStream.Read(buffer, 0, buffer.Length); memoryStream.Write(buffer, 0, count);
} while (count != 0);
//ASPOSE Document d = new Document(memoryStream); return d.ToTxt();
} } }
Pot sa spun ca ASPOSE, daca vreti manipulare de documente, face toti banii!
La ce foloseste StructureMap ? Raspunsul pe scurt : este un tool simplu de DI
Raspunsul pe lung : in postul precedent, vroiam sa testez un export de fisiere fara sa ating BD.
Initial, codul arata cam asa :
public ActionResult ExportDate(string id) {
DateExport fe = new DateExport(); fe.id = id;
export exp = new export(Server.MapPath("~/bin/Templates"));
FileContentResult fcr = new FileContentResult(exp.Export(fe), "application/ms-word"); fcr.FileDownloadName = fe.Number + ".doc";
return fcr;
unde variabila fe din
DateExport fe = new DateExport();
atingea BD in momentul in care cineva ii cerea niste date.
Vroiam sa o inlocuiesc cu o variabila ce trimite null ca date si apoi cu variabila care trimite niste date fake.
Ca sa fac acest lucru, am extras metodele care ma interesau din DateExport, am facut o interfata din ele IDateExport, am inlocuit vparametrul din functia export ce cerea un DateExport cu IDateExport si am folosit StructureMap :
In global.asax am definit cererea default :
ObjectFactory.Initialize(x => { x.ForRequestedType<IDateExport>().TheDefault.Is.ConstructedBy(() => new DateExport(HttpContext.Current.User.Identity.Name)); });
si apoi in metoda am inlocuit new cu ObjectFactory :
IDateExport fe = ObjectFactory.GetInstance<IDateExport>(); fe.id = id;
Acum testul automat a devenit floare la ureche – sa zicem ca vreau ca metodele din IDataExport sa nu aduca nimic –si sa testez acest lucru . Creez o clasa DataExportNotFind, implementez IDataExport ca sa nu aduca nimic si scriu in test :
ObjectFactory.Initialize(x => { x.ForRequestedType<IDataExport>().TheDefaultIsConcreteType<DataExportNotFind>(); });
Si asta e tot!
As vrea sa pun accent pe faptul ca , desi testez rapid pe masina proprie facind un fake la BD, totusi, la integrare, ar trebui sa aveti teste cu BLL / UI care sa testeze NEAPARAT cu BD …
Destul de tirziu mi-am dat seama de beneficiile aduse de un Mock – dar mai bine mai tirziu decit niciodata …
Ma gindeam ca niciodata nu o sa il folosesc – ca ajunge sa verific BLL cu unit test(NUNIT/VS Test) , site-ul Web cu NUnit ASP/WATI(N|R) / Selenium , Windows Forms cu NUnitForms si nu o sa am nevoie de Mock.
Adevarul este ca da, nu as avea nevoie de Mock … decit daca as vrea sa verific mai repede unele date, fara sa ating BD.De exemplu, pot sa verific controller-ele fara sa am nevoie sa instantiez HttpContext si BD. Sa zicem ca am un controller care are o actiune ce doar exporta un fisier Word– bazat pe un template. Codul din fisier arata cam asa :
Daca as vrea sa verific rapid metoda aceasta ar trebui sa nu ating baza de date si sa am pun un rezultat in loc de Server.MapPath ?
Se vede clar ca deja folosesc StructureMap, deci nu ar fi o problema cu gasitul a niste date fake. Dar pentru Server.MapPath intervine stralucit Mock.
Am preluat de la Hanselmann MVC Mock Helpers ,iar codul de test arata cam asa :
exportController i = new exportController(); MockRepository mocks = new MockRepository(); using (mocks.Record()) { //MvcMockHelpers.SetFakeControllerContext(mocks, i); mocks.SetFakeControllerContext(i); SetupResult.For(i.ControllerContext.HttpContext.Server.MapPath(null)).IgnoreArguments().Return(@"c:\programs\templates"); // cod pentru chemarea Server.MapPath mocks.ReplayAll(); } using (mocks.Playback()) { ObjectFactory.Initialize(x => { x.ForRequestedType<IExport>().TheDefaultIsConcreteType<FactFind>();// FactFind nu atinge BD }); FileContentResult fcr = i.exportdate("865", "A") as FileContentResult; fcr.ShouldNotBeNull(); fcr.FileContents.Length.ShouldBeGreaterThan(0);//TODO : Verifica si continutul }
1. Cum obtineti Connection String curat din Entity Framework Connection String
Sa zicem ca aveti definit connection string pentru EF in (web|app).config , ceva de genul :
<connectionStrings> <add name="iERPEntities" connectionString="metadata=res://*/ERP.csdl|res://*/ERP.ssdl|res://*/ERP.msl;provider=System.Data.SqlClient;provider connection string="Data Source=PC;Initial Catalog=ERP;Persist Security Info=True;User ID=x;Password=xy;MultipleActiveResultSets=True"" providerName="System.Data.EntityClient" />
Si vreti sa preluati rezultatele de la o procedura stocata sau doar sa o executati, fara sa va mai chinuiti sa o treceti prin EF (mai mare deranjul). In consecinta va trebuie connection string-ul.
2. Cum faci Union – sau Join - intre 2 IQueryable – ca sa iti dea macar una din ele, daca cealalta e null ?
3. Daca stii cheia unui obiect -de obicei long ID - cum obtii obiectul ?
4. Cum poti vedea ce genereaza, ca string sql, entitycontext – fara sa stai cu profiler-ul pe Sql Server ?
5. Daca vreti sa faceti o tabela pentru ca sa accesati/modificati continutul cu EF, ce este BestPractices de definit la ea ?
6. Cum validati un obiect in EF inainte de a salva ?
Va las sa va bateti capul! Raspunsurile mai jos…
Rezolvarea 1.
string sqlc = ""; using (ERPEntities e = new ERPEntities("name=iERPEntities")) { EntityConnection ec = e.Connection as EntityConnection; sqlc = ec.StoreConnection.ConnectionString; }
De ce e.Connection nu e direct EntityConnection si trebuie cast-uit, depaseste imaginatia mea …
Rezolvarea 2.
public static IQueryable<T> coalesceIntersect<T>(IQueryable<T> x, IQueryable<T> y) where T : class {
if (x == null) return y;
if (y == null) return x;
return Queryable.Intersect<T>(x, y);
Rezolvarea 3.
/// <summary> /// see also http://msdn.microsoft.com/en-us/library/bb896251.aspx /// </summary> /// <typeparam name="T"></typeparam> /// <param name="o"></param> /// <param name="ID"></param> /// <param name="Include"></param> /// <returns></returns> public static T FindFromPrimaryKey<T>(this ObjectQuery<T> o, long ID, params string[] Include) where T : EntityObject { Type t = typeof(T); string FullPath = t.FullName; if (!KeysForObject.ContainsKey(FullPath)) { lock (KeysForObject) { if (!KeysForObject.ContainsKey(FullPath)) { foreach(PropertyInfo pi in t.GetProperties()) { object[] attr = pi.GetCustomAttributes(typeof(EdmScalarPropertyAttribute),false); if(attr != null && attr.Length ==1) { EdmScalarPropertyAttribute a=attr[0] as EdmScalarPropertyAttribute; if(a.EntityKeyProperty) { KeysForObject.Add(FullPath,pi.Name); break; } } } } } }
if(Include != null && Include.Length > 0) { foreach(string s in Include) { o=o.Include(s); } } return o.Where("it." + KeysForObject[FullPath] + " = " + ID).FirstOrDefault();
Rezolvarea 4.
http://forums.microsoft.com/msdn/ShowPost.aspx?PostID=1555711&SiteID=1
Rezolvarea 5.
Definiti neaparat Primary Key ( eu de obicei pun un bigint identity )
Rezolvarea 6.
Vedeti http://msdn.microsoft.com/en-us/library/cc716714.aspx
Adaugati o clasa partiala de tipul entitatii in care puneti partial pe OnContextCreated ca sa interceptati SavingChanges
partial class Entities {
partial void OnContextCreated() {
this.SavingChanges += new EventHandler(Entities_SavingChanges); }
Eu am facut apoi o interfata IValidate de felul urmator – simplist – :
public enum ISValid { Yes, No, SendDataContext } public interface IValidate { ISValid IsValidForSaving(ObjectStateEntry ose, out string mesaj); } public interface IValidateDataContext // daca am nevoie de verificari mai ample cu alt obiect … { bool IsValidForSavingDataContext(ObjectContext oc, ObjectStateEntry ose, out string mesaj); }
Apoi pe SavingChanges :
void Entities_SavingChanges(object sender, EventArgs e) { Entities context = sender as Entities; foreach (ObjectStateEntry ose in context.ObjectStateManager.GetObjectStateEntries(EntityState.Added | EntityState.Modified)) { if (ose.Entity == null) continue; IValidate iv = ose.Entity as IValidate; if (iv != null)
string mesaj; switch (iv.IsValidForSaving(ose, out mesaj)) { case ISValid.No: throw new NotValidException(ose.Entity.GetType().Name, mesaj); case ISValid.Yes: break; case ISValid.SendDataContext: IValidateDataContext ivDC = ose.Entity as IValidateDataContext;
if(!ivDC.IsValidForSavingDataContext(context,ose, out mesaj)) throw new NotValidException(ose.Entity.GetType().Name, mesaj); break; }
Ok, se putea si mai bine, cu IDataErrorInfo – dar … sa o lasam asa deocamdata
Voi ce alte frameworkuri/tools-uri folositi ?
Aveam nevoie de un tracker de issue – pentru ca incepusem sa am prea multe cereri de modificare si nu ma mai descurcam. De fapt, la o a doua vedere, nu erau atit de multe cereri, cit detalii ale lor.
Am cerut pareri pe RONUA – dar ceea ce mi s-a oferit de acolo era pe bani (ma rog, OnTime se pare ca are oferte speciale..). Iar in criza actuala sa cer bani de la servici ca eu nu ma mai descurc suna cam prost…
Dupa ce am citit de mai multe , m-am decis pentru BugTracker.NET. Am avut doua probleme : Integrarea cu Active Directory – din fericire Web.Config este destul de clar scris – si trimiterea emailurilor. Nu vroiam sa configurez SMTP – ci doar sa isi ia setarile din Web.Config de forma:
<system.net> <mailSettings> <smtp> <network host="xxxx" port="25"/> </smtp> </mailSettings> </system.net>
Din nefericire el lucra cu System.Web.Mail – asta pentru ca ,zicea el, noul System.NET.Mail nu suporta SSL.
In fine – nu aveam nevoie de SSL –asa ca am muncit sa il transform – si am modificat pe ici, pe colo, prin partile esentiale email.cs:
/* Copyright 2002-2008 Corey Trager Distributed under the terms of the GNU General Public License */
using System; using System.Collections; using System.IO; using System.Text;
// disable System.Net.Mail warnings #pragma warning disable 618
namespace btnet {
public class Email { /////////////////////////////////////////////////////////////////////// public static string send_email( // 5 args string to, string from, string cc, string subject, string body) { return send_email( to, from, cc, subject, body, false, System.Net.Mail.MailPriority.Normal, null, false); }
/////////////////////////////////////////////////////////////////////// public static string send_email( // 6 args string to, string from, string cc, string subject, string body, bool isbodyhtml) { return send_email( to, from, cc, subject, body, isbodyhtml, System.Net.Mail.MailPriority.Normal, null, false); }
/////////////////////////////////////////////////////////////////////// public static string send_email( string to, string from, string cc, string subject, string body, bool isbodyhtml, System.Net.Mail.MailPriority priority, int[] attachment_bpids, bool return_receipt) { ArrayList files_to_delete = new ArrayList(); ArrayList directories_to_delete = new ArrayList(); System.Net.Mail.MailMessage msg = new System.Net.Mail.MailMessage(from, to); if (!string.IsNullOrEmpty(cc.Trim())) { msg.CC.Add(cc); } msg.Subject = subject; msg.Priority = priority;
// This fixes a bug for a couple people, but make it configurable, just in case. if (Util.get_setting("BodyEncodingUTF8", "1") == "1") { msg.BodyEncoding = Encoding.UTF8; }
if (return_receipt) { msg.Headers.Add("Disposition-Notification-To", from); }
// workaround for a bug I don't understand... if (Util.get_setting("SmtpForceReplaceOfBareLineFeeds", "0") == "1") { body = body.Replace("\n", "\r\n"); }
msg.Body = body; msg.IsBodyHtml = isbodyhtml;
if (attachment_bpids != null && attachment_bpids.Length > 0) {
string upload_folder = btnet.Util.get_upload_folder();
if (string.IsNullOrEmpty(upload_folder)) { upload_folder = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); Directory.CreateDirectory(upload_folder); directories_to_delete.Add(upload_folder); }
foreach (int attachment_bpid in attachment_bpids) { byte[] buffer = new byte[16 * 1024]; string dest_path_and_filename; Bug.BugPostAttachment bpa = Bug.get_bug_post_attachment(attachment_bpid); using (bpa.content) { dest_path_and_filename = Path.Combine(upload_folder, bpa.file); using (FileStream out_stream = new FileStream( dest_path_and_filename, FileMode.CreateNew, FileAccess.Write, FileShare.None)) { int bytes_read = bpa.content.Read(buffer, 0, buffer.Length); while (bytes_read != 0) { out_stream.Write(buffer, 0, bytes_read);
bytes_read = bpa.content.Read(buffer, 0, buffer.Length); } }
System.Net.Mail.Attachment mail_attachment = new System.Net.Mail.Attachment( dest_path_and_filename); msg.Attachments.Add(mail_attachment); files_to_delete.Add(dest_path_and_filename); } }
try { // This fixes a bug for some people. Not sure how it happens.... msg.Body = msg.Body.Replace(Convert.ToChar(0), ' ').Trim(); System.Net.Mail.SmtpClient s=new System.Net.Mail.SmtpClient(); s.Send(msg);
// We delete late here because testing showed that SmtpMail class // got confused when we deleted too soon. if (files_to_delete.Count > 0) { foreach (string file in files_to_delete) { File.Delete(file); } }
if (directories_to_delete.Count > 0) { foreach (string directory in directories_to_delete) { Directory.Delete(directory); } }
return ""; } catch (Exception e) { Util.write_to_log("There was a problem sending email. Check settings in Web.config."); Util.write_to_log("TO:" + to); Util.write_to_log("FROM:" + from); Util.write_to_log("SUBJECT:" + subject); Util.write_to_log(e.GetBaseException().Message.ToString()); return (e.GetBaseException().Message); }
} // end Email
} // end namespace
Auzisem de NCover – dar nu avusesem posibilitatea sa il pun la lucru.
De ce vroiam sa il folosesc ? Simplu: aveam teste automate (NUNIT) pentru proiect –unele cu Baza de date, altele fara. Ceea ce vroiam sa aflu este cit de mult acopar din codul sursa – adica cit de mult testele sunt complete.
OK – citind putin a inceput sa devina evident cum sa il folosesc – desi are o curba initiala de invatat de vreo 5 minute(nu e evident)
Primul lucru, daca vreti sa il folositi cu NUnit, este sa incercati sa rulati proiectul NUnit din consola, ceva de genul
nunit-console “<fisier.nunit>"
( daca vreti, puteti continua linia cu /include=categorii_de_teste )
Daca a rulat OK ( adica fara erori… ) acum putem configura NCover . Porniti NCover Explorer, apasati CTRL+N si o sa vi se deschida o fereastra de comanda in care veti introduce calea catre nunit-console iar la application arguments calea catre fisierul NUnit.
Rulati si o sa aveti rapid o evidenta in ce proportie codul din clase s-a executat. In plus va puteti uita direct intr-o metoda sa vedeti care cod s-a executat si care nu ( theme Underline mi se pare cea mai buna)
Generarea rapoartelor e buna – doar ca nu lanseaza fisierul htm generat si trebuie sa il gasiti singuri ( de aceea are “Explore coverage folder”)
Un tool exceptional, ce il puteti rula si din command line . Recomandat cu tarie!
Ce este NBuilder ? Un generator automat de obiecte – la care adauga proprietati default.
De ex:
var generator = new UniqueRandomGenerator(); var seq = new SequentialGenerator<int> { Direction = GeneratorDirection.Ascending, Increment = 1}; seq.StartingWith(1); BuilderSetup.DisablePropertyNamingFor<Linii, int>(x => x.Pret);
Factura oh = Builder<Factura>.CreateNew().Build(); var q = Builder<Linii>.CreateListOfSize(10) .WhereAll() .Have(x=>x.Qty=generator.Next(1,100)) .And(x=>x.LineNumber = seq.Generate()) .Build();
oh.Linii.AddRange(q);
E foarte bun la asta – si l-am folosit atunci cind vroiam generarea de date discrete ca sa le export in Excel…
Ce am invatat de la NBuilder :
E free – si f bun la generare. Diferenta de timp intre el si baza de date este de 1088 ms versus 3141 ms. Folositi cu incredere la teste!
Cred ca toata lumea stie la ce e bun Log4Net – este un logger . E foarte bun la log-at mesaje – in principiu cele de eroare – si trimis in cele mai diverse surse – la de fisiere text la console si la BD.
Dar problema este ca as fi vrut ceva sa imi logheze intrarea si iesirea dintr-o metoda, ceva de genul :
public string Arata(Point P, string s){
logger.Debug(“ am intrat cu Point “ + P + “ si string “ + s);
//code
logger.Debug(“ am iesit cu rezultatul “ + ….) }
La recomandarea lui Dan Bunea, am dat de PostSharp si Log4PostSharp
Dupa ce m-am chinuit sa il fac sa lucreze , iata la ce am ajuns:
1. Un folder lib , in care am pus fisierele :
lib\a.txt lib\Default.psproj lib\Log4PostSharp.dll lib\Log4PostSharp.pdb lib\Log4PostSharp.psplugin lib\Log4PostSharp.Weaver.dll lib\plugins lib\PostSharp-1.0.targets lib\PostSharp-1.0.version lib\PostSharp-AppDomain.config lib\PostSharp-Library.config lib\PostSharp-Platform.config lib\PostSharp.Core.dll lib\PostSharp.Core.XmlSerializers.dll lib\PostSharp.exe lib\PostSharp.exe.config lib\PostSharp.Laos.dll lib\PostSharp.Laos.psplugin lib\PostSharp.Laos.Weaver.dll lib\PostSharp.MSBuild.dll lib\PostSharp.Public.dll lib\PostSharp.targets lib\plugins\Log4PostSharp.dll lib\plugins\Log4PostSharp.pdb lib\plugins\Log4PostSharp.psplugin
2. Project Properties=> Reference Paths=> adaugat folderul lib
3. Un fisier .psproj numit la fel ca proiectul, in care am scris standard
<?xml version="1.0" encoding="utf-8" ?> <Project xmlns="http://schemas.postsharp.org/1.0/configuration"> <SearchPath Directory="bin/{$Configuration}"/> <SearchPath Directory="{$SearchPath}" /> <SearchPath Directory="lib" /> <Tasks> <AutoDetect /> <Compile TargetFile="{$Output}" IntermediateDirectory="{$IntermediateDirectory}" CleanIntermediate="false" /> </Tasks> </Project>
4. In csproj adaugat cu mina, linga <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<PropertyGroup> <DontImportPostSharp>True</DontImportPostSharp> <PostSharpDirectory>lib\</PostSharpDirectory> <PostSharpUseCommandLine>True</PostSharpUseCommandLine> </PropertyGroup>
<Import Project="$(PostSharpDirectory)PostSharp.targets" Condition=" Exists('$(PostSharpDirectory)PostSharp.targets') " />
5. In AssemblyInfo.cs adaugat (pentru a genera pentru fiecare metoda)
[assembly: Log(AttributeTargetTypes = "*", EntryLevel = LogLevel.Debug, EntryText = "Entering method: {signature} with parameters {paramvalues}", ExitText = "exit method {signature} with {returnvalue}", ExitLevel = LogLevel.Debug, ExceptionLevel = LogLevel.Error, AttributePriority = 1)]
Asta ca sa genereze un log pentru fiecare metoda …
6. Incepe log4net Adaugat un fisier log4net.config si pus
log4net.Config.XmlConfigurator.ConfigureAndWatch(new System.IO.FileInfo("log4net.config"));
GATA! Configurat – si apoi am uitat de el….
Output-ul este ceva de genul :
2009-07-17 22:24:08,541 [TestRunnerThread] DEBUG fisier.cs Namespace.Clasa [(null)] - Entering method: Void .cctor() with parameters 2009-07-17 22:24:08,729 [TestRunnerThread] DEBUG fisier.cs Namespace.Clasa [(null)] - exit method Void .cctor() with 2009-07-17 22:24:08,760 [TestRunnerThread] DEBUG fisier.cs Namespace.Clasa [(null)] - Entering method: Namespace.ClasaList Find(System.String) with parameters "5888" 2009-07-17 22:24:08,760 [TestRunnerThread] DEBUG Namespace.ClasaList [(null)] - Entering method: Void .ctor() with parameters 2009-07-17 22:24:08,760 [TestRunnerThread] DEBUG Namespace.ClasaList [(null)] - exit method Void .ctor() with 2009-07-17 22:24:08,760 [TestRunnerThread] DEBUG clasa2.cs namespace.DataContext [(null)] - Entering method: Void .cctor() with parameters 2009-07-17 22:24:08,760 [TestRunnerThread] DEBUG clasa2.cs namespace.DataContext [(null)] - exit method Void .cctor() with 2009-07-17 22:24:09,166 [TestRunnerThread] DEBUG clasa2.cs namespace.DataContext [(null)] - Entering method: Void .ctor() with parameters 2009-07-17 22:24:09,166 [TestRunnerThread] DEBUG clasa2.cs namespace.DataContext [(null)] - exit method Void .ctor() with 2009-07-17 22:24:09,198 [TestRunnerThread] DEBUG clasa2.cs namespace.DataContext [(null)] - Entering method: System.Data.Linq.Table`1<Namespace.clasa1> get_clasa3() with parameters 2009-07-17 22:24:09,401 [TestRunnerThread] DEBUG clasa2.cs namespace.DataContext [(null)] - exit method System.Data.Linq.Table`1<Namespace.clasa1> get_clasa3() with Table(clasa2) 2009-07-17 22:24:11,307 [TestRunnerThread] DEBUG clasa2.cs namespace.DataContext [(null)] - Entering method: System.Data.Linq.Table`1<amespace.clasa2> get_clasa4() with parameters 2009-07-17 22:24:11,417 [TestRunnerThread] DEBUG clasa2.cs namespace.DataContext [(null)] - exit method System.Data.Linq.Table`1<amespace.clasa2> get_clasa4() with Table(clasa2) 2009-07-17 22:24:11,495 [TestRunnerThread] DEBUG fisier.cs Namespace.Clasa [(null)] - exit method Namespace.ClasaList Find(System.String) with Clasa2List : 0
Nu eram deloc multumit de cum facusem predictia de curs valutar – exportam datele din tabela de SqlServer in Excel, pe aceasta analizam “in the cloud” si apoi generam fisierul html cu un macro de Excel …Naspa rau! Normal ca vroiam sa se generewze automat de la datele din tabela – si apoi sa se verse singra intr-o BD.
Asa incit am luat decizia sa studiez un pic cum se face(rezultate aici) –si iata ideile principale:
1. Nu va fie frica sa va creati sursele de date , cuburile si dimensiunile (folositi pentru aceasta VS, nu SSMS – din pacate NU merge in SSMS !). E acelasi lucru ca si cum ati defini o BD normala – si ati lucra cu ea.Dupa ce creeati, va trebui sa ii faceti “Deploy”c a sa o vedeti si in Analysis Services. O sa trec la nivelul urmator (generare automata a BI din cod) mai tirziu.
2.Chiar daca Baza de date principala isi schimba valorile,ca sa isi schimbe si Analysis Services trebuie sa ii dati “Process” din nou …Neplacut, dar asta e. In mod programatic din C#:
using(Microsoft.AnalysisServices.Server s=new Microsoft.AnalysisServices.Server()) { s.Name = "localhost"; s.Connect("localhost"); s.Refresh(); using(Database d=s.Databases["analtest1"]) { d.Refresh(); d.Process(ProcessType.ProcessFull); } }
3. Pentru a obtine valorile forecastate, sintaxa este uimitor de asemanatoare cu cea obisnuita din ADO.NET:
using (AdomdConnection con = new AdomdConnection()) { con.ConnectionString = "Data Source=localhost;Catalog=xxx"; con.Open(); using (AdomdCommand cmd = new AdomdCommand()) { cmd.CommandText = "select PredictTimeSeries(Valoare,25) AS Predictie from [Vw Eur]"; cmd.CommandType = CommandType.Text; cmd.Connection = con; using (AdomdDataReader dr = cmd.ExecuteReader()) {
while (dr.Read()) {
Totusi, te poti insela rapid:
4. Ca sa obtin tabelul de predictie, a trebuit sa vad in SSMS rezultatul si asa m-am prins ca dr are un singur rind, si acela fiind tot un AdomdDataReader
while (dr.Read()) { using (AdomdDataReader pred = dr["Predictie"] as AdomdDataReader) {
while (pred.Read()) {
4. Nu exista o modalitate standard de a obtine un DataTable dintr-un AdomdDataReader - iar GetSchemaTable nu intoarce rezultatele ca schema. Asa ca am ajuns la modalitatea obisnuita de a genera DataTable :
int fields = pred.FieldCount; DataTable dt = new DataTable("nume"); for (int i = 0; i < fields; i++) { dt.Columns.Add(pred.GetName(i), pred.GetFieldType(i)); } while (pred.Read()) { object[] p = new object[fields + 1]; for (int i = 0; i < fields; i++) { p[i] = pred[i]; } dt.Rows.Add(p); } }
De ce n-am facut-o extension method la AdomdDataReader ? Din YAGNI ( Ok, puturosenie …) Daca o sa fie nevoie , (re)fac…
5. E clar ca nu merge rapid cu BI-ul …nici macar cu ceva simplu ca un forecast amarit …Deci,la treaba!
Ok, titlul e cam naspa . Sa incerc sa o iau altfel : Orice site de informatii are un script js prin care alte site-uri preiau informatia(bineinteles, este o chestie de reclama)
Si iar revin la marota mea,www.infovalutar.ro, care avea preluare de curs prin PHP,Java, .NET, Python – dar nu avea prin Jscript (ceea ce majoritatea competitorilor aveau ) . Asa ca am fost fortat sa ma gindesc
OK, imi trebuie un js care sa fie interpretat O chestie simpla era sa fac un js care sa se interpreteze pe server – dar nu aveam chef:
E clar ca problema ar trebui inversata – si anume, avut “ceva” care se interpreteaza pe server care sa intoarca rezultatul . Clara solutia acum : un generic handler, de tipul ashx, care intoarce document.write(“text”);
Doua sfaturi:
Referitor la 2, eu am obtinut 2 clase:
clsTable – care preia niste stari de tipul cellpadding,backgroundcolor, tdbgcolor etc – astfel incit fiecare sa poa sa isi preia informatia avind culorile/fontul personalizat
clsConnect :ConfigurationSection : care intoarce datele din BD . E deriata din ConfigurationSection astfel incit sa o pun in web.config(app.config) si sa uit de ea.
Demo gasiti la http://infovalutar.ro/webmaster.aspx . Daca cineva poate crede ca este util, voi posta si sursele
Cum am spus, am trecut www.infovalutar.ro de la asp.net 1.1 la ASP.NET MVC.Trecerea nu a fost usoara, si inca sunt citeva lucruri de facut.
Totusi una din probleme care mi-a dat de furca a fost ca ChartImage , la refresh, nu se mai afisa. Foloseam asta la afisarea grafica a datelor cursurilor, de ex. http://infovalutar.ro/bnr/graphic/usd
Bun … problema era ca stergea fisierul png generat. Am pus “deleteAfterServicing “ la false * si o sa le sterg in momentul in care generez cursurile.
Totusi, mai aveam o problema . Daca accesam site-ul cu Firefox si dadeam refresh, se vedea. Dar aceeasi pagina , cu IE 7 , nu mai afisa - desi in codul HTML generat codul de la AXD era ACELASI, IMAGINEA era pe hard …
Dupa mai multe cautari am dat de pagina asta, http://blogs.msdn.com/deliant/archive/2008/12/02/managing-chart-generated-images-with-chart-image-handler.aspx care are TOATE setarile si ma dat de privateImages … L-am pus la false si gata!
Rezumat : pentru a folosi ChartImage cu succes cu IIS 7 aveti nevoie de 3 setari:
<appSettings> <add key="ChartImageHandler" value="storage=file;privateImages=false;timeout=600;dir=….;deleteAfterServicing=false;" /> </appSettings>
A doua in httphandlers
<httpHandlers><add path="ChartImg.axd" verb="GET,HEAD,POST" type="System.Web.UI.DataVisualization.Charting.ChartHttpHandler, System.Web.DataVisualization, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" validate="false" />
A treia in handlers
<add name="ChartImageHandler" preCondition="integratedMode" verb="GET,HEAD,POST" path="ChartImg.axd" type="System.Web.UI.DataVisualization.Charting.ChartHttpHandler, System.Web.DataVisualization, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
Si, bineinteles, daca hostingul nu a instalat inca controalele, sa puneti din references System.Drawing si System.Web.DataVisualization “Copy local” la true – ca sa le aveti in bin
Succes!
O sa vorbesc acum despre salvarea versiunilor fisierelor aplicatiei. De ce avem nevoie de asa ceva? Din cel putin doua motive:
Exista mai multe solutii pentru asta.Doua dintre ele sunt Microsoft Source Safe si Visual Studio Team System . Noi o sa folosim ceva gratuit, si anume subversion. O sa mergem pe calea usoara, si anume svn1clicksetup , http://svn1clicksetup.tigris.org/ . Eu am instalat vers 1.3.3 . Nu va ingrijorati de detalii ... apasati doar next si retineti folder-ele unde se instaleaza, inclusiv username si parola pe care v-o cere.
Acum sa creeam repository – acesta il putem face in c:\svnrepos\book35. Creeam aceasta cale, click dreapta pe c:\svnrepos\book35 si alegem TortoiseSVN =>Create repository here. Acum trebuie sa aducem fisierele aplicatiei: Dam click dreapta pe c:\book35, TortoiseSVN =>import, si la url punem file:///C:/svnrepos/book35 . Ok, acum, daca ne ducem pe TortoiseSVN =>RepoBrowser , si dam file:///C:/svnrepos/book35 , o sa vedem toate fisierele aplicatiei.
Bun, deci repository este file:///C:/svnrepos/book35 . Ar trebui sa luam fisierele de acolo – folosim TortoiseSVN =>SVN Checkout in ce folder dorim. Sa zicem , pentru a da un exemplu, ca modificam app.config si mai adaugam un spatiu in el. Dupa ce salvam , dam click si o sa apara „SVN Commit”. Apasam pe el si la mesaj dam „test”. Daca mergem acum pe file:///C:/svnrepos/book35 , TortoiseSVN=> RepoBrowser, si mergem pe app.config, click dreapta, show log vedem ca are 2 versiuni. Mai mult, le putem compara vizual!
Cam asta a fost despre salvarea versiunilor fisierelor
Tema pentru acasa :
Instalati ce versiune de Source Control vreti, modificati un fisier de 3 ori si salvati pe hard versiunea nr 2. Cum ati face daca ati avea mai multe fisiere si ati vrea pentru toate versiunea nr 2?
Lecturi recomandate:
Un tutorial mai lung de SVN aici, http://www.shokhirev.com/nikolai/programs/SVN/svn.html
Continuous integration - de ex., http://martinfowler.com/articles/continuousIntegration.html
Introduction to Team Build and Continuous Integration.
Aseara am inceput sa trec infovalutar pe Asp.NET MVC.Si aveam 2 probleme :
1. degeaba ii dadeam Home/Index/3 ca in controller-ul
public class HomeController : Controller
public ActionResult Index(string Banca)
ViewData.Model = new CurrencyList();
ViewData["Bank"] = Banca;
return View("Index");
Nu vroia sa imi ia id-ul(parametrul Banca era null) si gata, indiferent cum ii dadeam eu Home/Index/BNR sau Home sau orice altceva –desi trecea prin procedura.
M-am gindit sa dau vina pe Asp.NET MVC, dar , fiind un framework folosit de atitia, nu ma gindeam ca tocmai eu am un caz deosebit …Si citisem ca pe IIS integrated NU trebuie sa ii faci modificari …
2. Cind incerca sa se conecteze la Sql Server, imi dadea “login failed for user …”
Am incercat sa refac login-ul , sa schimb parola, ce nu am incercat …
Asa ca m-am dus la culcare si am revenit cu sentimente mai bune de dimineata.
Rezolvari :
1. M-am dus sa ma uit cum e inregistrat routing-ul in global.asax.Cum sa fie , obisnuit:
routes.MapRoute( "Default", // Route name "{controller}/{action}/{id}", // URL with parameters new { controller = "Home", action = "Index", id = "BNR" } // Parameter defaults );
Si mi-a cazut fisa : parametrul de la functia index nu se cheama cum vrea el, ci cum vrea MVC – adica id
public ActionResult Index(string id) fata de public ActionResult Index(string Banca) Naspa! Mi-aduce aminte de Java, cind numele clasei = numele fisierului !
2. Am incercat , in disperare , sa ma conectez si de pe Sql Server Management Console – acelasi mesaj : “Login Failed” . In disperare, ma conectez cu credentialele de Windows si ma uit in log-urile de la SQL Server – Management/Sql Server Logs - si acolo mi-a zis ca nu se poate ca nu este configurat in mixed mode …
Ce sa mai zic ? Schimb, dau restart la Sql, merge!
Sfat catre mine : Daca nu iti iese , du-te si te culca sau apuca-te de altceva. Revii cu mintea odihnita dupa aceea!
E buna si criza asta la ceva … precum si blog-ul meu pentru mine. Am primit de la Patrick Smacchia o licenta de NDepend . Si ce ar fi mai bun decit sa analizez aplicatia deja facuta?
Nu pot sa spun decit ca raportul lui, gata facut , este super impresionant (se pot defini rapoarte custom, dar ideea este ca astepti sa iasa ceva din prima)
Iata, de exemplu, imaginea cu dependentele:
Se vede dintr-o data ce nu e bine: SuffixDLL are legatura la PropertyGridUtils – cind ar trebui sa aiba doar SuffixWnd ( daca nu e clar, sa explic : Dll-ul de business nu ar trebui sa aiba de a face cu interfata grafica …)
Mai mult decit FxCop , are si multe detalii referitoare la calitatea codului in sine, de ex:
WARNING: The following CQL constraint is not satisfied. 1 types on 21 tested match the condition. --> Group {Unused Code / Dead Code}
// <Name>Potentially unused types</Name>WARN IF Count > 0 IN SELECT TOP 10 TYPES WHERE TypeCa == 0 AND // Ca=0 -> No Afferent Coupling -> The type is not used in the context of this application. !IsPublic AND // Public types might be used by client applications of your assemblies. !NameIs "Program" // Generally, types named Program contain a Main() entry-point method and this condition avoid to consider such type as unused code.
types Afferent coupling at type level (TypeCa) Full Name
Settings 0 SuffixWnd.Properties.Settings
Intr-adevar, pusesem settings la un moment dat - si apoi nu il folosisem.
Un tool de avut pentru orice team leader de .NET!
Site http://www.ndepend.com/, recomandare de la Scott Hanselman si un podcast , precum si demo-uri
Rezumat aplicatie:
1 ora si 46 de minute : Creeare
2 ore si 10 minute : cautat implementare pe asp.net, facut password invizibil
1ora si 22 minute: about box, serializare date
4 ore si 3 minute : localizare property grid
36 minute :fisier minimal de help
40 minute : localizare ASP.NET
Total : 10 ore si 37 de minute .
Concluzii :
1. Creearea efectiva este doar 20% din munca. Restul ( adica fine tuning) ramine la 80% – respecta deci principiul lui Pareto .
2. Daca esti microISV , ai nevoie de o mare rabdare in a termina o aplicatie.
3. Release early se pare ca intr-adevar functioneaza – am primit feedback imediat.
Pe de alta parte cu ce nu sunt multumit este ca nu am separat interfetele de la propertygrid utils , de a trebuit in site-ul web sa fac referinta la ceva care continea windows forms. Dar pentru o prima aproximare...merge...
Gasiti aplicatia de downloadat aici http://serviciipeweb.ro/downloads/pwdgen/ si un site minimal aici http://serviciipeweb.ro/downloads/pwdgen/index.aspx
Se ofera cineva continue in Silverlight si/sau WPF ?
Pentru referinta:
Partea 1, Geneza si creearea unei aplicatii minimale
Partea 2, Adaugare site
Partea 3, serializarea setarilor pentru Windows Forms
Partea 4, Localizare aplicatie
Partea 5, Terminat aplicatia – help si localizare ASP.NET
Partea 6, Sumar Creeare Aplicatie
Revenit la 16 : ok , help-ul. Pentru Help vom folosi fisiere HTM si HTML Help Compiler-ul (http://www.microsoft.com/downloads/details.aspx?familyid=00535334-c8a6-452f-9aa0-d597d16580cc&displaylang=en)
Scris rapid in engleza ... si acum sa il punem in aplicatie cu HelpProvider. 16:15 Pus KeyPreview la true pe forma – merge!
Ura -avem o aplicatie completa pentru ClickOnce! Sa ii facem deploy! Ii punem minor pe 1 – si vom astepta 7 zile sa isi faca update-ul
I-a pus tot versiunea 1.0 in fisierul HTML . Sa stergem default.htm si sa mai incercam 1 data. Ups – ma uitam in alta parte...pusese bine – dar deschidea pagina de pe site, nu pagina de pe loca.
OK, mi-a inclus traducerea – dar nu fisierul de help – sa il specificam sa il puna la deploy– Nu merge! La naiba, probabil fiindca e Express nu poate include alte fisiere. Sa mai citim ...Oh – se pare ca daca ii pun Build Action la Content merge... Super! 16:29
Sa le publicam pe ftp si sa facem proba . Merge - sa dezinstalam si sa mai ne racorim un pic : 16:36
Ramine modificarea aspx ca fie localizat. S-ar parea ca se termina ?
8:15 No hai si cu localizarea pe aspx... as vrea sa o fac din pagina aspx, nu sa mai scriu cod... Sa vedem daca pot ...
8:41 nu ... asa ca o sa il scriem in pagina – OK, 8:52 Deploy site, verify : 8:55 Total 40 de minute
Uff , gata ! nu mai am rabdare sa fac si Silverlight, si WPF – si poate o comunicatie prin WCF
Acum, Back to work : 7:27 . Ar trebui sa fac mai intii Localizarea si apoi fisierul de Help –din simplul fapt ca si fisierul de Help ar trebui localizat. OK, sa ii dam drumul inainte sa dam drumul la twitter sau alte prostii de Web 2.0 ...
Ce ar trebui sa fac la localizare este sa fac un fisier de resurse pe care sa il foloseasca si exe-le de Windows , si asp.net-ul .
Facut un dll de resurse, adaugat fisier de resurse, pus NameSite ,scris clasa care sa intoarca din resource , pus Nunit test:
objCI = new CultureInfo("fr-FR");
Thread.CurrentThread.CurrentUICulture = objCI;
OK – merge : 7:47 . Sa facem munca de rutina si sa traducem tot(google translate )... Sa ii dam drumul !- si sa modificam internal class ResStrings in public class ResStrings
Rulat din nou clasa de testare – ups ... greseala – metodele pe resurse sunt statice (7:59)... Revenim la hardcodare... si terminam la 8:04 . Acum ne trebuie customizat PropertyGrid – daca am fost puturosi si nu am pus textbox / label ...
Ar trebui sa incep sa mut toate chestiile legate de property grid in assembly-ul lor ... Si mi-ar place ca grid-ul sa reactioneze la tab. Sa derivam deci gridul si PropertyAttributes
La naiba! 9:32 - o ora si jumate! Dar a durat sa integrez resursele mele cu atributele – si a trebuit sa folosesc reflection pentru creere...
Dar , cel putin, merge GlobalizedDescriptionAttribute –si am facut si testul ...
Acum numele categoriilor...Si incepe sa fie stupid : CategoryAttribute : are GetLocalizedString , pe cind DescriptionAttribute NU! Alooo ... chiar 2 echipe de programatori ?? Ma rog! Bine ca macar unul are ceva sa ma ajute!
OK – modificat categoria si descrierea – acum ar trebui chiar NUMELE proprietatii...Sa ne cufundam in codul PropertyInfoDescriptor ... OK ... reusit( era vorba de override DisplayName) !-10:42 si acum sa trecem sa modificam peste tot.
Dar inainte , un pic de pauza – desi sunt acasa in zi libera, totusi mai am ceva de testat pentru servici.
11:15 - am inceput din nou ...si am terminat la 11:15 sa traduc.
Acum ar trebui sa fac kit de setup ... si sa fac help – si sa traduc site-ul asp.net. Dar plec pe afara : 11:30 inca citeva ore...
Revenit la sentimente mai bune la 11:00 PM si incercat sa termin cit mai mult din proiect.
Revenind : Parola/Salt/ Afisat sau [pus ca password text in Windows Application dupa o setare ( nu cu un enum , desi ar fi mai frumos - dar am codul http://www.codeproject.com/KB/architecture/dynamicattributes.aspx)
– salvarea acestei setari, teste pe Windows Forms : 11:19.
Acum un about box s-ar cam impune… buton, forma, testat : 11:24
Lansare de pe usb – asta inseamna sa salvez eu setarile intr-un fisier de configurare aflat fie linga aplicatie, fie undeva in alta parte…
Asa ca trebuie renuntat la application settings – si serializat clasa in XML. Scris codul de salvare(XML Serializer sa traiasca ) – intrebarea este : unde scriu unde se salveaza fisierul ??? Asa ca o sa incerc in 2 moduri : mai intii sa vad daca nu am un fisier pe care sa il citesc linga aplicatie – si apoi sa citesc din folderul de date al aplicatiei.
public string FileName
get
if (File.Exists(AppDataXML))
FileInfo fi = new FileInfo(AppDataXML);
if (HasAccess(fi.FullName))
return AppDataXML;
string Data =Path.Combine( System.Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),AppDataXML);
return Data;
private bool HasAccess(string File)
FileIOPermission f = new FileIOPermission(FileIOPermissionAccess.Append | FileIOPermissionAccess.Read | FileIOPermissionAccess.Write, File);
f.Demand();
catch (SecurityException ex)
string s = ex.Message;
Pus XMLIgnore la ceea ce nu vreau sa serializeze ( uff : mai bine faceau invers –sunt mai multe chestiile pe care vreau sa le ignor : sa nu fi facut bine clasa ? Dar nu – e vorba de afisare in grid si implementare ICustomTypeDescriptor )
Terminat si asta : ora 12:17. Inceput sa scriu un test,terminat si executat : 12:22.Asta denota ca am scris bine clasa.
Revenind : ce mai am ?
Fisierul de Help
Localizare
Of... iar am obosit :Total munca : 1 ora si 22 de minute. La culcare iarasi!
Revenind 2:41 AM :
Inca trei cerinte:
5. Cineva si-a manifestat dorinta ca sa nu se vada salt-ul in clar, ci sub forma de *** . Ar fi usor de pus [PasswordPropertyText(true)] dar as vrea sa fie dinamic
6. Sa poti alege locatia fisierului de salvare a salt-ului si a parolei - bun pentru a lua aplicatia pe usb
7. Site web.
Pentru 5, dupa ce m-am batut cu IcustomTypeDescriptor, am vazut codul de aici:
http://www.codeproject.com/KB/architecture/dynamicattributes.aspx
No, si dupa ce am copiat si m-am batut cu el, a mers cu bool, nu a mai mers cu un enum
[Flags]
public enum FormatAsPassword
None = 0,
Password=1,
Salt=2,
Site=4
Asa ca il las balta si pun doar la password …raminind pe alta data, dupa ce mai studiez nebunia… 3:48
Sa incep cu altceva mai rewarding –sa mi se para ca am facut ceva: site-ul web:
Ar fi frumos daca ar fi un property grid pentru ASP.NET ( si free , daca se poate)…Gasesc unul care pare bun, http://www.codeproject.com/KB/custom-controls/xacc_propertygrid.aspx, dar care spune ca e broken pentru IE7.
La naiba, sa vedem!3:58
Cind zicea ca arata nasol in IE7 nu se incurca… la fel de nasol arata si in Firefox
OK, deci nu… de la capat. Table, textbox, label
4:41 – terminat. Acum sa pun site-ul sus.
4:52- merge- acum, la culcare! – dupa 2 ore si 10 minute – nefacut mare lucru. Daca nu ma complicam cu cele 2( property grid pe asp.net si customtypedescriptor) , acum erau gata mai multe
Asa cum am scris si pe blog-ul propriu, sunt multe site-urile la care m-am inscris – si majoritatea cu aceeasi parola(de fapt, cam 3 parole diferite). Intre ele, si ejobs-care tocmai a fost spart.Asa ca am decis sa fac o mica aplicatie care, in functie de numele site-ului, sa genereze un sufix la o parola deja existenta.Aplicatia va trebui sa fie in doua flavors : Windows Forms si Web Form (ASP.NET) .
Cit va dura ?Probabil 2 – hai 4 ore… vom vedea! Care este estimarea voastra ?
Aceasta este relatarea cum am facut proiectul -inceput la 3:32 AM.
Citit pe net rapid de criptografie, terminat clasa : 3:57 AM.Adaugat proiect de test cu NUnit.Pentru
public void TestEncryption()
clsAddSufix c = new clsAddSufix();
c.Password = "parolagenerica";
c.NameSite = "www.ejobs.ro";
c.Salt = "onenewkey";
Console.Write(c.Generate());
am concluzionat ca e prea greu de tinut minte o parola de tipul:
parolagenerica+WdUDoNZBW3+jlUTTj+Q2b6qil0=
asa incit am decis sa o simplific – ce ar fi daca as aduna byte rezultat ?
Super! Imi da parolagenerica2377 ! Dar ar trebui sa verific ca 2 site-uri imi dau 2 parole diferite
Jale mare- Nunit imi da cu rosu – ma uit in cod –si vad ca am pus NameSite nu il folosesc- asa ca modific codul…
Super! Totul cu verde!
Acum ar trebui sa fac GUI … 4:32 – dupa ce am incercat sa pun label+textbox in table layout, i-am dat seama ca property grid ar face treaba mai usoara… <Intrerupere pina la 5:46>
Pus sa afiseze “on the fly” parola rezultat – 6:09
Verificat - si vazut ca nu se modifica daca modific site-ul – pentru ca gridul nu isi face refresh. Modificat sa implementez INotifyPropertyChanged.Modificat frumusetea
public string NameSite { get; set; }
in
public string NameSite
return _NameSite;
set
_NameSite = value;
RaisePropertyChanged("NameSite");
if (Autogenerate)
RaisePropertyChanged("Result");
Testat- nu se intimpla nimic…OK, am inteles – interceptat evenimentul PropertyChanged si facut refresh pe grid :
Modificat si celelalte proprietati, 6:34
Adaugat test – verde!
Acum ar trebui avertizat utilizatorul despre ce mai trebuie adaugat - adaugat IDataErrorInfo : 7:00
Ar trebui sa salvez setarile (salt,parola) in setarile aplicatiei.
7:04.
Modificat target framework din 3.5 in 2.0- 7:10 si 109 warnings…
Facut kit de setup pentru clickonce
Deploy si verificat :7:20
Total 1 ora + 46 minute
Totusi, au mai ramas citeva lucruri:
1. Help
2. About
3. Localizare
4. Salvare pe codeplex
Despre astea, data viitoare… Inca o data – care este estimarea voastra?
Am avut de facut urmatoarea chestie : de citit fisiere dintr-un folder si updatat text-ul din ele ( erau niste csv-uri) cu portiuni din denumire( de exemplu, daca fisierul se numeste vinzari_123 sa pun in fisier 123 la fiecare rind) . Normal ca m-am gindit sa il fac cit mai “extensibil” – pentru ca nu stiu forma sub care vin fisierele – asa ca m-am gindit sa evaluez ceea ce obtin cu expresii regulate – si sa le pun intr-un fisier de configurare( despre ConfigurationSection intr-un post viitor). Dar nu mai tineam minte sintaxa – in schimb tineam minte ca Roy Osherove a facut un Regulazy ( vedeti pagina http://weblogs.asp.net/rosherove/pages/tools-and-frameworks-by-roy-osherove.aspx ).
Cum se utilizeaza : Pui textul in casuta si apesi Regex_edit
Incepi sa subliniezi portiuni din text si dai click dreapta ( eu am facut asta cu vinzari –si am selectat “1 or more anything”. Apoi am selectat _ , am dat click dreapta si am selectat “exactly _” . In sfirsit am selectat 123 ,am dat click dreapta si am selectat “digits”.Iar click dreapta pe 123 selectat si dau “rename” - si ii pun numele “values”. Daca apas acum pe “test again input” se vede clar ca obtin in “values” ce am nevoie – si anume 123.
Ce mai am de facut este sa apas pe “generate” si a iesit codul :
public void SampleRegexUsage() { string regex=@"^\w+?_(?<values>\d+)$"; RegexOptions options = RegexOptions.Multiline; string input= @"vinzari_123"; MatchCollection matches = Regex.Matches(input,regex,options); foreach (Match match in matches) { Console.WriteLine(match.Value); Console.WriteLine("values:" + match.Groups["values"].Value); } }
( ma rog, trebuie sa scot multiline si sa scot ^ de la inceput … – dar fara asta e corect!)
Eu zic ca e un tool super rapid si usor de folosit( si free – si bun pentru cazul cind nu folosesti de multe ori expresii regulate.
Am vrut sa vad si eu citi cititori am pe blog-uri ( si sa le pun si reclame - dar asta e deja o alta discutie … ) .
Asa incit am cautat “feedburner dasblog” – si am gasit o configurare usoara aici :dasBlogAndFeedburner.aspx" href="http://mikeknowles.com/blog/2008/12/30/dasBlogAndFeedburner.aspx">http://mikeknowles.com/blog/2008/12/30/dasBlogAndFeedburner.aspx
OK - am pus in site.config , am profitat de faptul ca acest blog este deja pe feedburner , am rescris web.config (am pus un spatiu ca para ca este nou) – si merge de minune – se duce la http://feeds2.feedburner.com/AndreiIgnatBlog
Eh – acum intervine problema : pentru blog-ul propriu am facut acelasi lucru – cu mentiunea ca l-am adaugat in feedburner. Problema a venit in momentul verificarii : se ducea la http://feeds.feedburner.com/blogpropriu in loc de http://feeds2.feedburner.com/blogpropriu . Am inceput sa ma uit in cod – si vad intr-adevar:
if (siteConfig.FeedBurnerName != null && siteConfig.FeedBurnerName.Length >0) { return new Uri(new Uri("http://feeds.feedburner.com/"),siteConfig.FeedBurnerName).ToString(); }
Naspa … Problema este ca feeds.feedburner.com NU mai merge pentru noile RSS-uri, ci doar pentru cele vechi. Daca era o intrare de ex, <FeedBurnerApi>http://feeds.feedburner.com/{0}</FeedBurnerApi> mergea de minune acum configurarea.Dar asa va trebui sa recompilez / sau sa contribui la dasBlog/Release/ProjectReleases.aspx?ReleaseId=17989" href="http://www.codeplex.com/dasBlog/Release/ProjectReleases.aspx?ReleaseId=17989">http://www.codeplex.com/dasBlog/Release/ProjectReleases.aspx?ReleaseId=17989 – mai ales ca Google transfera feedburner si are un alt URL.
Am cautat pe feedburner - dar nu am gasit sa faca redirectarea .Ma rog, o sa le scriu celor de la feedburner sa vad daca imi permit sa am si URL-ul cu feeds in loc de feed2.
Concluzie : Daca lucrati la on software OpenSource si vreti ca oamenii sa il foloseasca, nu hard-codati nici un API al unui third party – tot ce folosit adaugati in fisierul de configurare…
Deocamdata o sa facem doar tabelele pentru detaliile/resursele care tin de un Angajat . Hai sa enumeram citeva :
(Daca aveti si alte sugestii,va rog sa imi scrieti)
Evident ca am putea creea o tabela prin care sa avem , ca si coloane, toate aceste proprietati - dar aplicatia nu ar fi destul de flexibila, in cazul in care cineva ar vrea sa mai adauge un detaliu/resursa ar trebui sa refacem aplicatia. Asa incit totul se rezolva cu un nivel de indirectare – o sa creez o tabela care sa contina gruparile de Proprietati (UserRelated, JobRelated , ITRelated) –, una care sa contina Proprietatile( nume, prenume, poza, telefon, laptop, email, etc) si una care sa faca legatura intre ele. De asemenea , trebuie ca tabelele sa contina date despre cine a introdus informatia si pina cind e valabila – acestea vor fi 3 cimpuri, continute tot timpul in (aproape) fiecare tabela , de tipul :
[DateModified<NumeTabela>] [datetime] NULL, --data modificarii
[NameUserModified<NumeTabela>] [nvarchar](150) NULL, - nume utilizator care a modificat
[IPModified<NumeTabela>] [nvarchar](150) NULL, -- ip-ul de la care s-a produs modificarea
Deocamdata am ajuns la urmatoarea structura :
Pentru conformitate , iata scriptul de creere al tabelelor si scriptul de creere a resurselor/detaliilor Angajatului.
http://serviciipeweb.ro/iafblog/content/binary/net35/bd/1/db.zip
Ce vreau sa fac in continuare este sa fac coloana ValueBinaryUserGroupProperty de tipul FileStream – adica sa isi pastreze datele pe hard, de fapt.
Verificam ca serverul suporta filestream – click dreapta pe server, “properties”, selectati “Advanced” si verificati ca filestream este OK:
Apoi click dreapta pe baza de date, “properties”, selectati FileGroups si acolo adaugati un filestream(bifati si “default”)
Acum trebuie sa adaugam un file ca sa putem profita de acest filestream . Trebuie ales File_Type : “filestream data”, filegroup-ul si, cel mai important, folder-ul in care sa il punem (ca alegere usoara, este acelasi folder ca fisierul primar de date)
Acum putem sa construim tabela noastra cu filestream – stergem tabela accUserGroupProperty (drop table accUserGroupProperty ) si o creeam cu suport de FileStream :
CREATE TABLE [dbo].[accUserGroupProperty](
[IDUserGroupProperty] [uniqueidentifier] NOT NULL ROWGUIDCOL PRIMARY KEY,
ToDateUserGroupProperty datetime NULL,
[ValueTextUserGroupProperty] [nvarchar](max) NULL,
[ValueBinaryUserGroupProperty] [varbinary](max) FileStream NULL,
[IDUser] [bigint] NOT NULL,
[IDProperty] [bigint] NOT NULL,
[DateModifiedUserGroupProperty] [datetime] NULL,
[NameUserModifiedUserGroupProperty] [nvarchar](150) NULL,
[IPModifiedUserGroupProperty] [nvarchar](150) NULL,
)
Ce ne trebuie neaparat pentru o coloana de tipul FileStream :
Cu ce ne afecteaza : cu aproape nimic – dar e bine sa poti vedea fisierele pe hard * de ex., poza angajatului o sa fie direct pe hard in loc sa o stocam in BD.
Nu uitati ca tot ce am facut aici prin click-uri se poate face si prin script, cu ajutorul butonului “Script” – se poate gasi usor in toate pozele precedente
Tema pentru acasa : Creeati o baza de date cu support filestream, o tabela cu o coloana filestream si inserati un text. Observati modificarile de pe folder-ul unde ati spus sa se creeze FileStream.
Lecturi Recomandate:
FileStream cu SQL Server (pe scurt)
http://blogs.microsoft.co.il/blogs/bursteg/archive/2008/05/09/sql-server-2008-filestream-part-1.aspx
Database normalization
http://en.wikipedia.org/wiki/Database_normalization
Developing Time Oriented databases in SQL
http://www.cs.arizona.edu/people/rts/tdbbook.pdf
Pentru ca la nu imi mergea Reporting Services( un IIS pe o masina cu 64 de biti, cu Sql pe 32 si cu Web Extensions de aspnet exe pe 32 de biti disabled, ca altfel nu merge alt site) a trebuit sa fac un raport cu numere. Si, pentru ca un grafic spune cit 100 de cuvinte, am zis sa le fac si citeva grafice. Si am fost bucuros sa dau o sansa la noile controale de chart. In 15 minute am reusit sa fac ceva ok –sunt super
Citeva caveats, totusi :
Download the free Microsoft Chart Controls
Download the VS 2008 Tool Support for the Chart Controls
Download the Microsoft Chart Controls Samples
Download the Microsoft Chart Controls Documentation
SI mai ales Download the Microsoft Chart Controls Samples – si rasfoiti exemplele.
<add key="ChartImageHandler" value="storage=file;timeout=20;dir=x:\inetpub\etc\log;" />
Acolo x:\inetpub\etc\log trebuie sa fie calea catre un director in care IIS sa aiba dreptul de scriere….
System.Web.DataVisualization.dll System.Web.DataVisualization.Design.dll
( folositi subst X %windir%\assembly – si copiati-le din X)
Daca vreti poze frumoase , gasiti la Scott . Oricum, sunt foarte bune – si mai bune decit vechiul Chart Control din VB6…Folositi cu incredere!
Pentru activitatea mea profesionala, mi-as dori:
Voi ce v-ati dori?
(pentru mine personal am scris aici)
PS: stiti carti bune despre Workflow Foundation ?
Aplicatia pe care m-am gindit sa o facem va realiza urmatoarele:
Va tine o evidenta (paralela !) a angajatilor dintr-o firma ( va fi integrat Active Directory si va tine datele intr-o baza de date Sql Server 2008)
Va tine o evidenta a conturilor acestora pe diverse aplicatii, precum si a diferitelor beneficii ale user-ilor ( telefon, laptop, etc)
Va face un workflow pentru un nou angajat, astfel incit sa se stie sigur cine ce are de facut( cumparat birou, laptop, etc)
Pentru toate aceste actiuni se va tine o evidenta a celor care adauga/modifica date in sistem.
Pe masura ce vom dezvolta aplicatia, ii vom adauga tot felul de noi functionalitati.Sa o numim Evidan(de la Evidenta Angajati)
Sa definim principalele roluri de actiune asupra sistemului:
Cerintele de lucru pentru versiunea 1 :
Sunt deschis la sugestii pentru orice imbunatatire
La fiecare parte a tutorialului( sau aproape ) o sa am citeva chestii pe care trebuie sa le faca cititorul singur.
De exemplu, la cel cu "aplicatii mici de test" am dat ca tema de casa realizarea aceleiasi aplicatii in Powershell. Daca cineva m-ar ajuta sa realizez temele de casa (cu poze despre cum l-a facut) , pot oferi ca recompensa :
Ce ziceti ?
Acum vom trece aplicatia minimala de consola in mai multe outputuri : Windows Forms, Asp.NET, WPF
Vom transforma aceasta aplicatie intr-una de Windows Forms
Click dreapta pe solutie, Add=> New Project, Windows Forms Application
Dublu click pe form1, luam un buton din Toolbox , dublu click pe el, si apare evenimentul de click
Copiem exemplu de la aplicatia ConsoleApplication1, modificand Console.Write in MessageBox
In figura alaturata se vede ca nu aplicatia nu stie cine este SqlConnection. Dar, imediat in stinga, exista un icon mic -daca dati click pe el, o sa vedeti ca stie - alegeti "using"
Alegeti "set as startup project"
CTRL+F5 si apasati pe "buton1" - ar trebui sa va apara
Ce am observat ? Ca am scris de 2 ori acelasi cod. Daca am avea de modificat, nu ar fi cam greu in aceasta aplicatie -dar pentru o aplicatie mare am putea intimpina probleme. Ar fi bine daca acest cod s-ar tine undeva , intr-o locatie comuna - ceea ce in Windows numim dll.
OK - sa adaugam un dll - click dreapta pe Solution, Add=>New Project , Class Library
A aparut "Class1" si adaugam codul de pe Consola, modificat putin - nu mai interceptam SqlException -ca nu am stii ce sa intoarcem ( de fapt, ar trebui sa ne facem clasa noastra de exceptii - dar despre asta mai tirziu) - si intoarcem chiar ce gasim in BD:
public string MessageFromBD()
using (SqlConnection sc = new SqlConnection())
sc.ConnectionString = @"Server=.\SqlExpress;Database=testAndrei;Trusted_Connection=True;";
sc.Open();
using (SqlCommand sco = new SqlCommand())
sco.CommandType = CommandType.Text;
sco.CommandText = "select top 1 Test from TestTable";
sco.Connection = sc;
string text = sco.ExecuteScalar().ToString();
return text;
Bun - acum sa chemam acest mesaj si in consola, si in Windows Forms.La amindoua dam click dreapta pe "References", "Add reference", "projects"
Selectam ClassLibrary1 si dam OK. Puem pe Console Application1 set as startup
Inlocuim codul din ConsoleApplication1, Program cu urmatorul:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
using System.Data.SqlClient;
namespace ConsoleApplication1
class Program
static void Main(string[] args)
ClassLibrary1.Class1 p = new ClassLibrary1.Class1();
string text = p.MessageFromBD();
Console.WriteLine("found " + text);
catch (SqlException ex)
Console.WriteLine(" Eroare conexiune:" + ex.Message);
return;
CTRL+F5,ca sa vedem rezultatul. Daca merge, haidem sa il modificam si pe cel de Windows Forms( nu uitati sa puneti referinta si set as startup)
Bun - acum vom face si o aplicatie WPF -click dreapta pe solutie, Add=>New Application, WpfApplication.
Adaugati referinta la ClassLibrary1, adaugati acelasi cod ca la Windows Forms si CTRL+F5
Ultimul pas, facem o aplicatie Web pentru aceasta aplicatie:
In carte=>testBD creeam un folder numit WebApplication1.Apasam "Start=>Run , control panel( swtich to classic view daca scrie asa ceva), administrative tools, Internet Information Services"
Expandati "local computer ", "WebSites","Default Web Site", click dreapta pe "default web site",new , "Virtual directory"
La alias scrieti "WebApplication1" si la Directory scrieti calea catre folder ( la mine, C:\carte\testBD\WebApplication1)
Asigurati-va ca la click dreapta pe "WebApplication1" , properties, ASP.NET, versiunea este 2.0
Bun - acum sa creeam aplicatia. Pornim Microsoft Visual Web Developer 2008 Express Edition si "File=>OpenWebSite, Local IIS" si expandati pina dati de WebApplication1
Acum adaugam dll-ul care facea comunicarea cu BD:
File=> Add=> Existing project si navigati pina la ClassLibrary1( la mine, C:\carte\testBD\ClassLibrary1). Adaugati csproj.Adaugam referinta la dll: La fel, click dreapta pe http://localhost/WebApplication1 , add reference, projects, classlibrary1
Acum adaugam pagina unde o sa vedem mesajul :
click dreapta pe http://localhost/WebApplication1 , add new item, Web Form ( fiti atent in ce limbak dati -C# sau VB.NET...)
Acum, gasiti jos Design - apasati pe el
Din toolbox alegeti din nou butonul , trageti pe forma si dati dublu click
Vom pune (aproape) acelasi cod ca si pentru WindowsForms/Console/WPF:
protected void Button1_Click(object sender, EventArgs e)
Response.Write("found " + text);
Response.Write(" Eroare conexiune:" + ex.Message);
Apasati CTRL+F5, apasati pe buton si ar trebui sa va scrie:
"Eroare conexiune:Cannot open database "testAndrei" requested by the login. The login failed. Login failed for user 'TEST1\ASPNET'. "
Mai tineti minte ca am folosit conexiunea trusted :
Acum, haideti sa ne identificam cum trebuie si in ASP.NET. click dreapta pe http://localhost/WebApplication1 , add new item,Web Configuration File(lasati numele de Web.Config asa cum este!), gasiti authentication si puneti linga:
<identity impersonate="true"/>
CTRL+F5 din nou si acum apare:
Eroare conexiune:Cannot open database "testAndrei" requested by the login. The login failed. Login failed for user 'TEST1\IUSR_TEST1'.
Tare, nu ? Am schimbat un user cu altul - din ASPNET, sub care ruleaza ASP.NET, am schimbat IUSR_TEST1 - sub care rulam default.
Hai sa facem ultima schimbare , ca sa ne recunoasca IIS :
Apasam "Start=>Run , control panel( switch to classic view daca scrie asa ceva), administrative tools, Internet Information Services"
Click dreapta pe "WebApplication1" , properties, Directory security, Anonymous access, Edit,scoateti "Anonymous Access"
OK si iar OK. Ne intoarcem la aplicatie si...Ura!, merge:
Observatii:
How to implement impersonation in an ASP.NET application, http://support.microsoft.com/default.aspx/kb/306158
La un moment dat aparuse stirea ca putem face debug chiar in codul sursa al librariilor .NET , vezi aici
http://weblogs.asp.net/scottgu/archive/2007/10/03/releasing-the-source-code-for-the-net-framework-libraries.aspx
Problema era de download rind pe rind...
Dupa aceea , ca sa ne fie mai usor, cineva s-a gindit sa downloadeze totul intr-o singura bucata – si asa a aparut NetMassDownloader
http://www.codeplex.com/NetMassDownloader
Si , in cele din urma, a venit normalul – aveti librariile de downloadat aici
http://referencesource.microsoft.com/netframework.aspx
Numai sa va tina banda...altfel, sunt interesant de citit * si , daca va uitati, exista si un BigInt implementat ...
Pe deasupra, puteti vedea ce greseli faceti ( sau ce e prost implementat in framework ) daca va da vreo eroare ciudata ...
PS : Pentru cei care asteapta tutorialul de .NET 3.5 : e f. greu de scris in fiecare saptamina, asa incit o sa aiba o aparitie bilunara (marti , de obicei)
Prin acest tutorial vrem sa facem o introducere in programarea cu frameworkul .NET 3.5 . Vom realiza o aplicatie de tinerea evidentelor activelor angajatilor unei intreprinderi. Intentia este sa o trecem , realizind diverse feature(raportare, integrare Active Directory,etc) cam prin tot ceea ce inseamna programarea in .NET : de la WebSite la WindowsForms, trecind prin WebService, etc.
Acesta nu va fi un tutorial de programare- nu vom incepe cu ceea ce inseamna variabile, i++ si alte lucruri de genul acesta. Pentru aceasta va rog consultati, de ex., http://www.microsoft.com/romania/educatie/cursnet/default.mspx (aveti varianta pentru elevi si pentru profesori)
Vom realiza acest lucru cu editiile Visual Studio Express ( http://www.microsoft.com/express) raminind ca, pe masura ce se poate, sa aratam diferentele intre ele si celelalte.
Sa trecem la treaba!
Acest pas se efectuează o singura date pe PC. Este vorba despre instalarea server-ului de Web , precum si a Visual Studio Express ca si unealta de dezvoltare. Este cel mai lung pas, dar, precum am spus, se efectuează o singura data.
Să începem: ai Windows (XP, de preferinţă) si ne asiguram ca ai server-ul de Web (Internet Information Services – IIS de acum înainte) pe maşină .Pentru aceasta te duci in "Control panel" => "Administrative Tools" si verificaţi ca există.
Daca nu exista, mergeţi înapoi la "Control Panel", apăsaţi pe "Add or Remove programs" si apăsaţi pe "Add/Remove Windows Components". Bifaţi IIS ca in imagine si daţi "Next"( asiguraţi-vă ca aveţi CD-ul de instalare Windows prin apropiere)
Bun – acum o sa instalam VS 2008 Express. Acesta este free si îl puteţi descărca de la adresa
http://www.microsoft.com/express/download/ . Acolo , jos de tot, gasiţi o imagine ISO (VS2008ExpressWithSP1ENUX1504728.iso) pe care o puteti downloada . O puteţi vedea cu ISObuster, Daemon Tools or Virtual CloneDrive ( căutaţi-le pe google pentru linkuri de download) sau cu un program făcut de MS, Virtual CD-ROM Control Panel for Windows XP care poate fi downloadat de la adresa:
http://download.microsoft.com/download/7/b/6/7b6abd84-7841-4978-96f5-bd58df02efa2/winxpvirtualcdcontrolpanel_21.exe
Pentru o adresa mai scurta, incercati http://tinyurl.com/winxpvirtualcdcontrolpanel
Pentru instalare incarcati CD-ul si , daca aveti autorun, va va aparea imaginea urmatoare:
Vom instala C# si WebDeveloper. Cei care vor o varianta in VB.NET , exista un traducator intre sintaxe la adresa http://www.carlosag.net/Tools/CodeTranslator/ si altul la http://www.developerfusion.com/tools/convert/csharp-to-vb/
Cind instalati , deselectati SQL Server – il vom instala mai tirziu
Daca ati terminat de instalat si C# si WebDeveloper, urmatorul pas este sa instalati Powershell de la adresa http://www.microsoft.com/powershell. Spre deosebire de VS sau SQL Server, acesta cere Microsoft Genuine validation .
Ne instalam si Windows Installer 4.5 de la http://www.microsoft.com/downloadS/details.aspx?familyid=5A58B56F-60B6-4412-95B9-54D056D6F9F4&displaylang=en ( sau http://tinyurl.com/wininst4-5 ) . Atentie sa luati pe specificul sistemului dumneavoastra( de ex., pentru Windows XP pe 32, luati WindowsXP-KB942288-v3-x86.exe)
Acum putem instala SQL Server Express Edition with Advanced Services SP2 de la adresa http://www.microsoft.com/express/sql/download/default.aspx . Luati SQL Server 2008 Express with Advanced Services - o sa facem si rapoarte in Sql Reporting si multe altele.Va duceti pe "planning" si alegeti "system configuration checker". Daca ati trecut toate, atunci putem da pe "installation"-"new sql server stand alone ….". OK-dupa o suita de"Next" ajungeti la "Feature Delection" – dati "Select all".
La "database engine" dati o parola pentru sa si nu uitati sa apasati "add current user"
De asemenea nu uitati de "Filestream", care rezolva problema veche:"sa pastrez documentele in BD sau doar un link catre calea lor?"
Acum ramine doar "Next"…si sa restartati PC-ul - de siguranta si ca sa porneasca serviciile automat, nu sa le porniti din control panel=>services.msc
In cele din urma mai avem nevoie, pentru Silverlight , de downloadat Microsoft® Silverlight™ Tools for Visual Studio 2008 SP1 (RC1) de la adresa http://www.microsoft.com/downloads/details.aspx?FamilyID=c22d6a7b-546f-4407-8ef6-d60c8ee221ed&DisplayLang=en
Acum, despre inregistrare : daca va inregistrati download-urile , atunci primiti citeva chestii free –intre care mie mi-au placut imaginile . Vedeti http://www.microsoft.com/express/registration/
Dan Fernandez , Top 15 Things to love about Visual Studio 2008 Express - http://blogs.msdn.com/danielfe/archive/2007/11/19/top-15-things-to-love-about-visual-studio-2008-express.aspx
Curs .NET Framework - http://www.microsoft.com/romania/educatie/cursnet/default.mspx
Petzold , .NET Book Zero - http://www.charlespetzold.com/dotnet/
Am avut de facut , de multe ori, site-uri care cuprindeau pagini de genul : Lista, Editare, Editare proprietati ( de ex., Lista User ( cu cautare + sortare), Editare User, Editare Proprietati UserCurent).Problema intervine cind vrei sa ii lasi si un site map – si ai urmatoarea structura : Lista User-i => Editare User => Editare proprietati user si , dupa ce user-ul este pe pagina de editare proprietati, revine la Editare User - de unde stii ce user ai editat ? De obicei pasezi id-ul in query string ...dar cum il pui in sitemap ?
Solutia rapida pe care o gasisem(nu foarte corecta, dar rapida ) a fost sa tin in Session ultimul user care este editat.Nu prea mi-a placut – mult de scris, de repetat.
Ma uitasem si la pagina aceasta, de modificat nodurile in memorie, dar iar nu mi-a placut.
Simteam ca trebuie sa fie ceva in XMLProvider ... dar nu reuseam sa ii dau de cap.
Dupa mult timp, am dat de pagina asta
http://www.csharper.net/blog/custom_sitemapprovider_incorporates_querystring_reliance.aspx
care mi-a rezolvat problema, prin mostenire de la XMLSiteMapProvider.
Ce trebuie sa faceti :
Sau de aici
public class SmartSiteMapProvider : XmlSiteMapProvider
public override void Initialize(string name, NameValueCollection attributes)
base.Initialize(name, attributes);
this.SiteMapResolve += new SiteMapResolveEventHandler(SmartSiteMapProvider_SiteMapResolve);
SiteMapNode SmartSiteMapProvider_SiteMapResolve(object sender, SiteMapResolveEventArgs e)
if(SiteMap.CurrentNode == null)
return null;
SiteMapNode temp;
temp = SiteMap.CurrentNode.Clone(true);
Uri u = new Uri(e.Context.Request.Url.ToString());
SiteMapNode tempNode = temp;
while(tempNode != null)
string qs = GetReliance(tempNode, e.Context);
if(qs != null)
if(tempNode != null)
tempNode.Url += qs;
tempNode = tempNode.ParentNode;
return temp;
private string GetReliance(SiteMapNode node, HttpContext context)
//Check to see if the node supports reliance
if(node["reliantOn"] == null)
NameValueCollection values = new NameValueCollection();
string[] vars = node["reliantOn"].Split(",".ToCharArray());
foreach(string s in vars)
string var = s.Trim();
//Make sure the var exists in the querystring
if(context.Request.QueryString[var] == null)
continue;
values.Add(var, context.Request.QueryString[var]);
if(values.Count == 0)
return NameValueCollectionToString(values);
private string NameValueCollectionToString(NameValueCollection col)
string[] parts = new string[col.Count];
string[] keys = col.AllKeys;
for(int i = 0; i < keys.Length; i++)
parts[i] = keys[i] + "=" + col[keys[i]];
string url = "?" + String.Join("&", parts);
return url;
(inca o data: nu e codul meu, ci al lui C# Shiznit,http://www.csharper.net/blog/custom_sitemapprovider_incorporates_querystring_reliance.aspx)
<siteMap defaultProvider="SmartSiteMapProvider" enabled="true"> <providers> <clear /> <add name="SmartSiteMapProvider" type="SmartSiteMapProvider" siteMapFile="web.sitemap" securityTrimmingEnabled="true" /> </providers> </siteMap>
<siteMapNode url="~/EditUser.aspx" title="Editare utilizator" reliantOn="UserID" />
Ce mai poate fi facut ? Poate sa ia nu doar din QueryString, ci si din Form.
Sa zicem ca suntem in C#,avem SQL 2005 si o tabela( de ex., utilizatori) pe care trebuie sa implementam un (fel de ) mecanism de CDC – automat.
Creeam o noua tabela, utilizatori_history,identica cu precedenta, la care adaugam 3 cimpuri ( de_la_data, la_data, si stare : insert, update, delete)
Ne jucam cu Linq 2 SQL (pentru ca e foarte rapid) – si am vrea ca sa se faca (relativ) automat – adica sa nu lasam la latitudinea celor care folosesc clasa utilizatori daca sa inregistreze sau nu istoricul.
Nici o problema – in DataContext, pentru fiecare tabela, exista un eveniment de <insert-update-delete> de tipul partial void <Metoda><NumeTabela>(<tabela> nume), in cazul nostru partial void InsertUtilizatori(Utilizatori instance)Super – e suficient sa interceptam evenimentul si sa scriem datele intr-o noua instanta de utilizatori_history , o adaugam la this.Utilizatori_history si facem submitchanges impreuna cu this.ExecuteDynamicInsert(instance); pentru a salva utilizatorii noi.
Din pacate, ne trage in piept ... si anume o eroare ca nu avem voie sa facem acest lucru in metoda...Si are (partial) dreptate.
Asa incit solutia alternativa (nu foarte buna, dar merge) este sa creeam un nou data context de la 0:
MyDataContext my=new MyDataContext(this.Connection); //pastram aceeasi conexiune
my.Transaction =this.Transaction; //si tranzactie
my. Utilizatori_history.InsertOnSubmit(history);
my.SubmitChanges();
Cam asta ar fi ... Ma rog, daca vrem si informatii aditionale( de ex., ce user a fost, etc) le putem adauga in history la creearea obiectului istoric.
Ce ar mai fi de facut, daca nu ar fi fost facut in SQL2008:
Adaugat prin Code generat , pentru fiecare tabela din DataContext, una de history – si suprascris fiecare eveniment...
In sfirsit am avut ocazia sa ma joc cu LINQ de adevaratelea(cam tirziu, avind in vedere ca a aparut Visual Studio 2008 SP1 si The .NET Framework 3.5 SP1 cu DataEntity si Astoria). Si prima chestie mare pe care a trebuit sa o fac a fost un gridview cu paginare, sortare si filtrare.
Imi generasem clasele Linq intr-un dll – si am zis ca pun un LinqDataSource. Cind citesc documentatia , gasesc TableName – si mi-am zis: "UPS... nu vreau sa ma conectez la tabela, ci la datele mele"
Asa incit imi zic – ObjectDataSource e ceea ce vreau.
Il configurez – punindu-i metodele pe care le vroia – cea cu selectcount si cea cu paginare - si mergea OK. In SQL Server Profiler aparea Ok... Si acum filtrarea pe server . Aici au inceput problemele: Nu mai voia sa afiseze paginile (1, 2 ,etc), desi selectcount-ul il facea pe server...
Ca solutie intermediara am zis sa aduca toate datele daca face filtrare... Dar nu mi-a placut prea mult...si in plus nu mai genera evenimente de rowcommand...
Asa ca intreb si eu ca incepatorul pe Ronua si am noroc :sirocco imi raspunde ca TableName e de fapt numele clasei ... Chiar asa este!Merge paginarea si sortarea rapid si fara probleme...
Iar la filtrare e suficient sa puneti un parametru la WhereParameters si sa puneti clauza Where cum trebuie .
Ma rog, am mai dat de o problema : Am filtrat dupa stringuri - merge NumeProprietate.Contains(@numeparametru).
Problema este ca ,daca nume parametru este gol, va da eroarea
No applicable method 'Contains' exists in type 'String'
Nu va speriati – eroare spune o prostie ... puneti la parametru ConvertEmptyStringToNull="false"
Incercasem sa interceptez erorile din Master page – si nu intelegeam de ce nu merge. Pina mi-am adus aminte ca Master deriva din control – si ca, de fapt, este un control instantiat de pagina, nu invers…
Totusi, o functionalitate buna a Master este cea de afisare de erori la nivel central ( ca si cum ai avea un control de afisare erori).In App_Code se adauga un fisier .cs de forma
public class ErrorAdd : IValidator
public ErrorAdd(string Message)
ErrorMessage = Message;
public string ErrorMessage { get; set; }
public bool IsValid
public void Validate()
(clar invalid...)
Apoi in Master adaugam un control ValidationSummary si urmatorul cod :
public void AddTheError(string Message)
ErrorAdd e = new ErrorAdd(Message);
this.Page.Validators.Add(e);
Cam de atit e nevoie … La orice cod de pe pagina – de pilda, pe evenimente, scrieti
{//cod
catch (Exception ex)
this.Master. AddTheError(ex.Message);
Singura problema pe care o vad este sa folosesti acelasi master pentru mai multe functionalitati ...Nu vrei un God Object, nu-i asa?
Ma tot joc cu LINQ 2 SQL de citva timp– si sunt impresionat de cit de repede te poti misca in el (chit ca viitorii programatori nu trebuie sa invete sql cu el … e suficient sa scrii sintaxa in VB.NET /C# de Where si ti-o converteste el … impresionant)
Oricum pentru testarea obiectelor ma gindisem la Linq 2 Objects – ca sa nu tot dau in Baza de date( sindromul NIH) . Din pacate, nu am reusit sa fac ceva - asa ca a doua solutie, google, a fost cu success.
De la adresa http://andrewtokeley.net/Images/andrewtokeley_net/Downloads/LinqToSQL.zip
gasiti un proiect de testare, bazat tot pe Linq2Objects - mai exact, pe List<>.
Ca sa il folositi faceti asa:
Adaugati fisierele din folder-ul Interfaces , precum si cele din Mocks ( fara ExampleMockDatabase.cs)
Adaugati DataContextWrapper.cs
Adaugati, dupa exemplul ExampleMockDatabase.cs , BD a dvoastra( sa zicem MockAndreiMemoryDatabase ) si scrieti codul pentru CreateTables si PopulateTables
Trimiteti catre programul principal prin Inversion of control un IQueryable pentru tabela TabelaMea proprietatea TabelaMea din urmatorul cod:
public class AndreiFind
MockDataContextWrapper d = new MockDataContextWrapper(new MockAndreiMemoryDatabase());
public IQueryable<Tabela> TabelaMea
return d.Table< TabelaMea>().AsQueryable<TabelaMea >();
Descrierea autorului aici http://andrewtokeley.net/archive/2008/07/06/mocking-linq-to-sql-datacontext.aspx
Am aflat de pe blogul lui Gabriel Enea, http://gabrielenea.blogspot.com/2008/07/bazele-programarii-sau-cum-sa-scrii.html , despre o carte numita Foundations of Programming - Building Better Software, by Karl Seguin .
Exceptionala si va sfatuiesc si pe voi sa o cititi. E usor de citit si scrisa cu nerv.
Alegerile pe care le face nu sunt intotdeauna pe gustul meu(ca Nhibernate, de ex.) dar in rest este o carte care iti reia/reda bunurile obiceiuri in dezvoltarea soft.
Ce mi-a placut :
Pagina 13 :
"The idea behind domain driven design is to build your system in a manner that's reflective of the actual
problem domain you are trying to solve. This is where domain experts come into play – they'll help you
understand how the system currently works (even if it's a manual paper process) and how it ought to
work. At first you'll be overwhelmed by their knowledge – they'll talk about things you've never heard
about and be surprised by your dumbfounded look. They'll use so many acronyms and special words
that'll you'll begin to question whether or not you're up to the task. Ultimately, this is the true purpose
of an enterprise developer – to understand the problem domain. You already know how to program, but
do you know how to program the specific inventory system you're being asked to do? Someone has to
learn someone else's world, and if domain experts learn to program, we're all out of jobs."
Pagina 30: despre IOC sau DI intr-un mod clar , cu Structure Map
Pagina 36 : uitasem de assembly: InternalsVisibleTo
Pagina 61 : de ce ati trece o instanta a unei clase cu ref ...
Chiar daca sunteti f. buni , citi Foundations of Programming - Building Better Software, by Karl Seguin .
. Merita!
Stiam ca a a aparut Sandcastle final. Dar, desi are GUI-ul incorporat si command line-ul, e inca destul de greu de folosit. Asa ca am cautat pe codeplex ceva care sa se integreze usor, in afara de SHFB
Asa am gasit DocProject . Acesta iti adauga un nou tip de proiect in VS, ii spui printr-un Wizard ce proiecte vrei sa iti adauge, iti spune ce lipseste pentru a compila in CHM sau HxS + 1 Site ...E clar ca e super OK si iarasi e clar ca trebuie sa il compilezi doar "on demand" la sfirsitul proiectului – adica sa nu il lasi sa se compileze la fiecare build decit daca ai masina super tare – RAM si RPM.
In alta ordine de idei, fisierele CHM le poti vedea usor, dar cele HxS nu... Am descoperit un Hxs viewer bun si free aici : http://www.helpware.net/mshelp2/h2viewer.htm
The project exports a DataTable or IEnumerable to Word / Excel / PDF / CSV / HTML / CSV
Make extensive use of
Every programmer have a DataTable or IEnumerable and wants to export to the user in an easy way.
This project enables an easy and customizable way to export data to various formats.
DataTable dta = new DataTable("andrei"); dta.Columns.Add(new DataColumn("ID", typeof(int))); dta.Columns.Add(new DataColumn("Data", typeof(string))); dta.Rows.Add(1, "test 1 & <"); dta.Rows.Add(2, "test 2 "); dta.Rows.Add(3, "test 3 >"); dta.ExportTo(ExportToFormat.Excel2007, @"C:\andrei.xlsx"); dta.ExportTo(ExportToFormat.Word2003XML, @"C:\andrei.doc"); dta.ExportTo(ExportToFormat.Word2007, @"C:\andrei.docx"); dta.ExportTo(ExportToFormat.itextSharpXML, @"C:\aitext.xml"); dta.ExportTo(ExportToFormat.Excel2003XML, @"C:\andrei.xls"); dta.ExportTo(ExportToFormat.HTML, @"C:\andrei.html"); dta.ExportTo(ExportToFormat.XML, @"C:\andrei.xml"); dta.ExportTo(ExportToFormat.PDFtextSharpXML, @"C:\andrei.pdf");
The String Template is really really great. I think that a good programmer can make a second CodeSmith from there... The itextsharp has some problems with XML transformation to PDF. Please check whitespaces after xml tags if it says something like " can not transform table into paragraph" The OfficeSDK is really great - and really easy to use.
Version 1
Imi place din ce mai mult XP - si incep sa ma rog pentru verde...Iar VS e o adevarat bomboana pentru asta ...Cind genereaza method stub( scriind , de ex., instanta.Metoda ... si geenrind codul pentru metoda inexistenta ... apasati pe coltul de la cuvintul Metoda ) scrie codul punind in corp
throw new NotImplementedException();
Este super ... apare rosu ... si pe urma apare faimosul verde ...
Va sfatuim sa cititi partile anterioare
Primul pas : instalarea software-ului free
Al doilea pas : Analiza aplicatiei
Al treilea pas : Structura Bazei de date
Sau tutorialul anterior despre .NET 2.0
http://serviciipeweb.ro/iafblog/content/binary/tutorial.pdf
Vom importa datele din fisierul Excel in Baza de date. Daca am avea SQL Server Standard( sau mai mare) am putea importa direct din Excel in SQL Server.Este suficient sa facem click dreapta pe baza noastra de date , Tasks=> ImportData – ca in figura alaturata :
Dupa ce apasam, vom selecta la surse Excel:
Iar la destinatie serverul local de SQL Server
Cam asta ar fi, daca am avea SQL Server Standard.
Dar ,pentru ca avem SQL Server Express, nu avem o astfel de facilitate incorporata - asa ca va trebui sa ne descurcam importand datele cu un program in C#.
Vom creea tabele in SQL Server asemanatoare cu structura datelor din Excel. Vom crea tabelele asa cu am facut la pasul 3. De pilda tabela cu date despre cartile de copii va arata asa:
Acum vom importa datele. Va trebui sa facem citirea datelor in Excel si apoi scrierea lor in SQL Server.
Sa le luam pe rind:
Cream un nou proiect in C# , intitulat "ImportDate" de tip Consola in folder-ul C:\book3.
Linga toate "using" mai adaugam si un "using System.Data.OleDb;" ca sa putem citi din Excel si using System.Data.SqlClient pentru conectare la SQL Server.
O sa ne folosim de faptul ca DataAdapter stie sa faca modificari de date automat. Ne facem ca citim un DataTable din SQL Server, il umplem apoi cu datele de la Excel si ii spunem lui DataAdapter sa faca insert-urile pentru noi.
Deschidem o conexiune la SQL Server si citim datele din tabela "Excel_Copii":
Pentru citirea din Excel vom folosi driverul de OLEDB.Cream o conexiune la Excel si o sa citim datele din tabela "Copii".
Deschidem o conexiune la Excel
"Provider = \"Microsoft.Jet.OLEDB.4.0\";Data Source=\"C:\\book3\\carte.xls\";Extended Properties=\"Excel 8.0;HDR=Yes;IMEX=1\"";
(daca vreti sa stiti ce ISAM aveti , vedeti cu regedit cheia
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Jet\4.0\ISAM Formats)
Deschidem o noua comanda, prin care selectam datele din Worksheetul "Copii"
oc.CommandText = "select * from [Copii$]";
(de remarcat sintaxa cu $ si paranteze drepte)
Cod complet:
using System.Data.OleDb;
namespace ImportDate
using (SqlConnection sco = new SqlConnection())
sco.ConnectionString = "Data Source=.\\sqlExpress;Integrated Security=true;Initial Catalog=Library";
sco.Open();
using (SqlDataAdapter sda = new SqlDataAdapter("select * from Excel_Copii", sco))
// construct insert
if (sda.InsertCommand == null)
SqlCommandBuilder scb = new SqlCommandBuilder(sda);
sda.InsertCommand = scb.GetInsertCommand(true);
System.Data.DataTable dtTransfer = new System.Data.DataTable();
sda.Fill(dtTransfer);
using (OleDbConnection odc = new OleDbConnection())
odc.ConnectionString = "Provider = \"Microsoft.Jet.OLEDB.4.0\";Data Source=\"C:\\book3\\carte.xls\";Extended Properties=\"Excel 8.0;HDR=Yes;IMEX=1\"";
odc.Open();
using (OleDbCommand oc = new OleDbCommand())
oc.CommandType = System.Data.CommandType.Text;
oc.Connection = odc;
//fill data table with Excel data
System.Data.DataTable dtExcel = new System.Data.DataTable();
using (OleDbDataReader sr = oc.ExecuteReader())
dtExcel.Load(sr);
// transfer rows
foreach (DataRow dr in dtExcel.Rows)
DataRow drNew = dtTransfer.NewRow();
drNew.ItemArray = dr.ItemArray;
dtTransfer.Rows.Add(drNew);
sda.Update(dtTransfer);
La fel se importa si datele din tabela de SF.
Acum este cazul sa importam datele in tabele.
Mai intii, cea de autori.
Va trebui sa luam autorii din toate tabelele .
SELECT
[Autor1] as autor
FROM [Library].[dbo].[Excel_Copii]
union
select [Autor2]
UNION
FROM [Library].[dbo].[Excel_SF]
Observam urmatoarele date:
NULL
ISPIRESCU Petre
Andersen
George Lucas
Ion Creanga
Isaac Asimov
Petre Ispirescu
Va trebui sa facem 2 lucruri:
Punctul 1 este destul de usor de facut cu un update ...
update Excel_Copii set Autor1= 'Petre Ispirescu' where Autor1 = ' ISPIRESCU Petre'
Punctul 2 il vom face inserind in tabela de persoane si pe urma in tabela de Autori
INSERT INTO [Library].[dbo].[Person]
([FirstNamePerson],[LastNamePerson]
select autor,'' from
(
) a where a.autor is not null
Acum separam nume de prenume:
UPDATE
Person
SET
FirstNamePerson = substring(FirstNamePerson,1,charindex(' ' ,FirstNamePerson)-1),
LastNamePerson = substring(FirstNamePerson,charindex(' ' ,FirstNamePerson)+1,100)
WHERE charindex(' ' ,FirstNamePerson)>0
Rezultatul este:
IDPerson FirstNamePerson LastNamePerson DateOfBirthPerson
2 Andersen NULL
3 George Lucas NULL
4 Ion Creanga NULL
5 Isaac Asimov NULL
6 Petre Ispirescu NULL
Acum le vom insera in tabela de Autori:
INSERT INTO
[Author]
([IDPerson]
SELECT IDPerson FROM Person
La fel inseram cartile si editurile..
Acum trebuie sa refacem legaturile, de pilda, intre autori si carti
INSERT INTO [Book_Author]
([IDBook]
,[IDAuthor])
select IDBOOK,IDAuthor from Book b
inner join Excel_SF excel
inner join Person p on excel.Autor1 = p.FirstNamePerson + ' ' + p.LastNamePerson
inner join Author a on p.IDPerson = a.IDPerson
on excel.Titlu = b.Title
La fel si pentru carti cu edituri :
update book
set IDPrintingHouse =
p.IDPrintingHouse from Book b
inner join PrintingHouse p on p.NamePrintingHouse = excel.Editura
Ramine la latitudinea cititorului exercitiul cu celelalte update-uri.
Backupul la BD il gasiti in folder-ul database si se numeste "lib_date_importExcel.bak" . Puteti face restore.
De citit:
Surse
Tutorial PDF
Facind deja (minima) analiza a aplicatiei, putem acum sa vorbimdespre structura bazei de date. Vom face direct o baza de date relationala in cea de a treia forma normala. Pentru aceasta pornim SQL Server Management Studio Express,ne logam la serverul local (./ sau ./sqlexpress, depinde cum ati numit instanta) prin Windows authentication (cea mai simpla metoda) si click dreapta pe Databases => New Database
Numele pe care o sa i-l dam o sa fie "Library" si o sa concepem tabelele ca fiind replica exacta a obiectelor.
Vom crea tabelele direct din "Database Diagrams" Raspundem cu "Yes" la intrebarea despre "Diagram support"
Si cream o diagrama noua, numita "Library".Click dreapta, "New Table", "Person". Adaugam coloanele ca in figura, cu mentiunea ca "IDPerson" o facem "Identity" si Primary Key
Acum vom creea tabela Author – aceasta va contine o IDPerson – evidentiere a faptului ca orice Autor este si o persoana. La fel, IDAuthor este PK si Identity
Vom"trage" IDPerson din tabela Author peste IDPerson din tabela "Person", obtinind in acest fel legatura intre Autor si Persoana.
La fel si pentru celelalte tabele.
In final vom avea urmatoare structura :
Vom face un backup al Bazei de date ca in figura:
Puteti crea BD singuri sau puteti face "restore" la ea dupa acest backup , numit lib_empty.bak
Surse aici
http://serviciipeweb.ro/iafblog/content/binary/net3/20080226.zip
Acest tutorial in intregime aici
Data viitoare vom importa datele din Excel in SQL Server.
Va sfatuim sa cititi prima parte, Primul pas : instalarea software-ului free
Sau tutorialul anterior despre .NET 2.0 http://serviciipeweb.ro/iafblog/content/binary/tutorial.pdf
Aplicatia pe care o sa o facem este una de gestionare(management, pe stil nou) a cartilor dintr-o biblioteca publica. Presupunem ca biblioteca deja isi tine o evident a cartilor intr-un Excel cu o multitudine de sheet-uri, cam de aceasta forma
Un sheet, numit "Copii" care contine carti pentru copii,cu urmatoarele date:
Titlu
Autor1
Autor2
editura
Pret
Imprumutata de
Data imprumutului
Craiasa zapezii
Teora
3
Ignat Andrei
15/01/2008
GREUCEANU SI ALTE POVESTI
All
5
Capra cu trei iezi
Polirom
2
Ursul păcălit de vulpe
Zana muntilor
Alt sheet, numit SF, cu urmatoarele date
Editura
Caverne de otel
15
Fundatia
31
Fundatia si imperiul
23
Fundatia si Pamantul
21
Inainte de fundatie
13
RAZBOIUL STELELOR
54
Pare destul de clar , nu ? Fiecare carte are cite o fisa in carte, care spuen cine a imprumutat-o si cind.
Puteti downloada fisierul Excel de la adresa http://serviciipeweb.ro/iafblog/content/binary/carte.xls
Daca nu aveti Excel (?) , puteti downloada Excel Viewer
Acum vom face o mica analiza a datelor existente , pentru ca cerintele aplicatiei, ca de obicei, sunt vagi : "sa faca o cautare intre datele existente si sa reproduca procesul existent..."
E clar ca avem de a face cu urmatoarele obiecte:
Editura – ca atribute: nume, site, email
Persoana – nume, prenume, data nasterii
Autor – este o Persoana care in plus are ca atribut – site,(una sau mai multe) Carti publicate
Bibliotecare – este o Persoana cu drepturi de modificare Carti/Edituri/Autori/Setari
Client – Este o Persoana care are dreptul sa imprumute un numar(Setare) de Carti pe o perioada data (Setare)
Carti – ca atribute : Nume, data aparitiei, (publicata de ) Editura, (unul sau mai multi) Autori, ISBN, pret
Setari – Numar de carti imprumutate, Perioada imprumutului
Nu voi lua in considerare multe alte lucruri, ca de pilda faptul ca un client pierde o carte sau ca preturile pot fi modifiacte in timp ...Ar complica in mod inutil aplicatia – care nu vrea sa fie o aplicatie completa, ci doar un demo.
Data viitoare o sa facem designul Bazei de date.
http://www.microsoft.com/express/download/offline.aspx. Acolo gasiţi o imagine ISO pe care o puteti downloada . O puteţi vedea cu ISObuster, Daemon Tools or Virtual CloneDrive ( căutaţi-le pe google pentru linkuri de download) sau cu un program făcut de MS, Virtual CD-ROM Control Panel for Windows XP care poate fi downloadat de la adresa:
( da, ştiu, e o adresa scurta)
De asemenea, instalati SQL Server Express Edition with Advanced Services SP2 de la adresa http://www.microsoft.com/express/sql/download/default.aspx
Si tool-ul de administrare grafica, numit Microsoft SQL Server 2005 Express Edition Toolkit, de la aceeasi adresa http://www.microsoft.com/express/sql/download/default.aspx
Download pdf de aici:
http://serviciipeweb.ro/iafblog/content/binary/tutorialnet3.pdf
public void Linq99() { // Sequence operators form first-class queries that // are not executed until you enumerate over them. int[] numbers = new int[] { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 }; int i = 0; var q = from n in numbers select ++i; Console.WriteLine("i = {0}", i); // Note, the local variable 'i' is not incremented // until each element is evaluated (as a side-effect): foreach (var v in q) { Console.WriteLine("v = {0}, i = {1}", v, i); } }
public void Linq100() { // Methods like ToList() cause the query to be // executed immediately, caching the results. int[] numbers = new int[] { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 }; int i = 0; var q = ( from n in numbers select ++i ) .ToList(); Console.WriteLine("i = {0}", i); // The local variable i has already been fully // incremented before we iterate the results: foreach (var v in q) { Console.WriteLine("v = {0}, i = {1}", v, i); } }
Northwind db = new Northwind(connString); db.Log = Console.Out; Console.WriteLine("before"); var query = db.Customers.Where("City == @0 and Orders.Count >= @1", "London", 10). OrderBy("CompanyName"). Select("New(CompanyName as Name, Phone)"); Console.WriteLine(query); Console.ReadLine();
foreach (var c in query) { Console.WriteLine(c.ToString()); }
private PropertyInfo[] _pi; public PropertyInfo[] Properties { get { if (_pi == null) { _pi = this.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public); } return _pi; } } public object GetValue(string PropName) { PropertyInfo pi= Properties.Single<PropertyInfo>(c => c.Name == PropName); return pi.GetValue(this, null); }
Northwind db = new Northwind(connString); db.Log = Console.Out; Console.WriteLine("before"); var query = db.Customers.Where("City == @0 and Orders.Count >= @1", "London", 10). OrderBy("CompanyName"). Select("New(CompanyName as Name, Phone)"); Console.WriteLine("after"); foreach (DynamicClass c in query) { Console.WriteLine(c.GetValue("Name")); Console.WriteLine(c.GetValue("Phone")); Console.WriteLine("-------------------"); } Console.WriteLine(query); Console.ReadLine();
Type app = Type.GetTypeFromProgID("Outlook.Application"); object oApp = Activator.CreateInstance(app); object oNameSpace = app.GetMethod("GetNamespace").Invoke(oApp, new object[] { "MAPI" }); oNameSpace.GetType().GetMethod("Logon").Invoke(oNameSpace, new object[] { Type.Missing, Type.Missing, Type.Missing, Type.Missing}); Object mail = oApp.GetType().GetMethod("CreateItem").Invoke(oApp, new object[1] { 0 }); Type t = mail.GetType(); t.GetMethod("Display").Invoke(mail, new object[] { false });
Dim app As Type = Type.GetTypeFromProgID("Outlook.Application") Dim oApp As Object = Activator.CreateInstance(app) Dim oNameSpace As Object = oApp.GetNamespace("MAPI") oNameSpace.Logon() Dim mail As Object = oApp.CreateItem(0) mail.Display(False)
Yedda.Twitter t = new Yedda.Twitter();t.UpdateAsJSON("user_email", "password", "text");
private void mnuCalc_Click( object sender, EventArgs e){ string pth; if (Environment.OSVersion.Platform == PlatformID.WinCE) { pth = @”\Windows\calc.exe”; } else { pth = @”c:\Windows\system32\calc.exe”; } Process.Start(pth, null);}totusi , cind exista cel putin aceste 2 optiuni
Haideti sa repetam ceea ce am facut in ASP.NET pentru WindowsForms
Deschideti Book.sln, adaugati o noua forma in proiect(frmPublisherPrint.cs) si trageti ReportViewer in forma.(Daca nu il gasiti, click dreapta in Toolbox, alegeti “choose items” , cautati ReportViewer din namespace-ul Microsoft.Reporting.WinForms si selectati-l
Adaugam raportul existent prin click dreapta pe BookWin
Va duceti in BookWeb, alegeti din casuta “Files of type” ultima selectie “All files” si selectati rptPublisher.rdlc.
Acum click pe el si in fereastra de proprietati alegeti la “Copy to output directory “ “Copy always”
Bun – acum au ramas 3 lucruri de facut : vizualizarea formei ca actiune, legarea controlului de raportul existent si codul de incarcare a datelor in raport.
Pentru vizualizarea formei ca actiune adaugati un buton btnPrint in frmPublisherList iar pe eveniment scrieti urmatorul cod:
private void btnPrint_Click(object sender, EventArgs e)
frmPublisherPrint p = new frmPublisherPrint();
p.ShowDialog(this);
Pentru legarea controlului vom seta la proprietati calea catre raport(presupunem ca se va afla in acelasi folder) si processing mode la local
Ultimul lucru de facut – incarcarea colectiei pe evenimentul de load :
rivate void frmPublisherPrint_Load(object sender, EventArgs e)
BookObjects.ColPublisher publishers = new BookObjects.ColPublisher();
publishers.Load();
MessageBox.Show(""+publishers.Count);
ReportDataSource rds = new ReportDataSource("DataSet1_Publisher", publishers);
rptPublisher.ProcessingMode = ProcessingMode.Local;
rptPublisher.LocalReport.DataSources.Clear();
rptPublisher.LocalReport.DataSources.Add(rds);
rptPublisher.LocalReport.Refresh();
rptPublisher.RefreshReport();
( exact acelasi cod ca la Web, in afara liniei :
Ea previne cazul( des intilnit) in care editorul IDE adauga , cu de la sine putere, un ReportDataSource .
Ceea ce se va infatisa va fi:
Ce mai e de facut
1)frmPublisherPrint sa nu mai afiseze ce vrea ea - ci sa primeasca un argument(in constructor, de exemplu) care sa spun ce lista de publisher-i are de afisat
2) Avind in vedere ca rapoartele sunt aceleasi pentru Windows si Web , ar fi interesant de facut un dll care sa intoarca raport – ul cerut
Orice aplicatie trebuie sa aiba posibilitatea de a tipari datele. Pentru aceasta in VS2005 Express se poate folosi componenta Report Viewer care e free si se poate downloada de la http://www.gotreportviewer.com.
Dupa ce o instalati, o sa aveti in toolbox urmatoarea componenta:
Acum adaugam un raport care sa fie afisat de catre aplicatie – deschidem aplicatia Web si adaugam un nou item de tipul Report si il numim rptPublisher.rdlc :
Acum adaugam un nou datasource apasand pe Add New DataSource
In ecranul urmator apasam pe NewConnection si selectam baza noastra de date:
Putem salva conexiunea in Web.Config(desi o mai avem) si apasam Next. Pe urmatorul ecran lasam selectat “Use SQL Statements” si iarasi Next. Acum scriem codul pentru SQL:
SELECT IDPublisher, NamePublisher, SitePublisher
FROM Publisher
si iarasi Next pina se termina ( sau direct Finish).
Acum in WebSite DataSource ne-a aparut dataset-ul care contine Publisher
Ne ducem pe Toolbox si tragem pe rptPublisher.rdlc un Table
Ne intoarcem pe WebSite DataSources si tragem Name Publisher pe detaliu:
Acum sa verificam
Cream o noua pagina aspx, frmPublisherReport.aspx si scriem urmatorul cod(sau tragem controlul de ReportViewer si ii spunem care e raportul)
<%@ Page Language="C#" MasterPageFile="~/Book.master" AutoEventWireup="true" CodeFile="frmPublisherReport.aspx.cs" Inherits="frmPublisherReport" Title="Report Publisher" %>
<%@ Register Assembly="Microsoft.ReportViewer.WebForms, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"
Namespace="Microsoft.Reporting.WebForms" TagPrefix="rsweb" %>
<asp:Content ID="Content1" ContentPlaceHolderID="ContentPlaceHolder1" Runat="Server">
<rsweb:ReportViewer ID="rptPublisher" runat="server">
<LocalReport ReportPath="rptPublisher.rdlc">
</LocalReport>
</rsweb:ReportViewer>
</asp:Content>
In codul C# punem urmatoarele linii :
using System.Configuration;
using System.Collections;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
using Microsoft.Reporting.WebForms;
public partial class frmPublisherReport : System.Web.UI.Page
protected void Page_Load(object sender, EventArgs e)
ReportDataSource rds = new ReportDataSource("DataSet1_Publisher",publishers);
Si modificam fisierul rdlc deschizindu-l cu notepad-ul si scriind urmatoarele:
<?xml version="1.0" encoding="utf-8"?>
<Report xmlns="http://schemas.microsoft.com/sqlserver/reporting/2005/01/reportdefinition" xmlns:rd="http://schemas.microsoft.com/SQLServer/reporting/reportdesigner">
<DataSources>
<DataSource Name="BookConnectionString">
<ConnectionProperties>
<ConnectString />
<DataProvider>SQL</DataProvider>
</ConnectionProperties>
<rd:DataSourceID>c649d533-64c3-42b6-9805-19adbfccd468</rd:DataSourceID>
</DataSource>
</DataSources>
<BottomMargin>1in</BottomMargin>
<RightMargin>1in</RightMargin>
<rd:DrawGrid>true</rd:DrawGrid>
<InteractiveWidth>8.5in</InteractiveWidth>
<rd:SnapToGrid>true</rd:SnapToGrid>
<Body>
<ReportItems>
<Table Name="table1">
<Footer>
<TableRows>
<TableRow>
<TableCells>
<TableCell>
<Textbox Name="textbox7">
<rd:DefaultName>textbox7</rd:DefaultName>
<ZIndex>1</ZIndex>
<Style>
<PaddingLeft>2pt</PaddingLeft>
<PaddingBottom>2pt</PaddingBottom>
<PaddingRight>2pt</PaddingRight>
<PaddingTop>2pt</PaddingTop>
</Style>
<CanGrow>true</CanGrow>
<Value />
</Textbox>
</ReportItems>
</TableCell>
</TableCells>
<Height>0.45833in</Height>
</TableRow>
</TableRows>
</Footer>
<Left>0.5in</Left>
<DataSetName>DataSet1_Publisher</DataSetName>
<Top>0.25in</Top>
<Width>6.5in</Width>
<Details>
<Textbox Name="txtName">
<rd:DefaultName>Name</rd:DefaultName>
<Value>=Fields!Name.Value</Value>
</Details>
<Header>
<Textbox Name="textbox1">
<rd:DefaultName>textbox1</rd:DefaultName>
<ZIndex>2</ZIndex>
<Value>Name</Value>
</Header>
<TableColumns>
<TableColumn>
</TableColumn>
</TableColumns>
<Height>1.375in</Height>
</Table>
<Height>2in</Height>
</Body>
<rd:ReportID>9f0247a8-15fe-4ef9-962e-c4c670524163</rd:ReportID>
<LeftMargin>1in</LeftMargin>
<DataSets>
<DataSet Name="DataSet1_Publisher">
<rd:DataSetInfo>
<rd:ObjectDataSourceType>Publisher</rd:ObjectDataSourceType>
<rd:TableName>Publisher</rd:TableName>
</rd:DataSetInfo>
<Query>
<rd:UseGenericDesigner>true</rd:UseGenericDesigner>
<CommandText />
<DataSourceName>BookConnectionString</DataSourceName>
</Query>
<Fields>
<Field Name="IDPublisher">
<rd:TypeName>System.Int32</rd:TypeName>
<DataField>IDPublisher</DataField>
</Field>
<Field Name="Name">
<rd:TypeName>System.String</rd:TypeName>
<DataField>Name</DataField>
<Field Name="Site">
<DataField>Site</DataField>
</Fields>
</DataSet>
</DataSets>
<Width>9.75in</Width>
<InteractiveHeight>11in</InteractiveHeight>
<Language>en-US</Language>
<TopMargin>1in</TopMargin>
</Report>
Acum , rulind pagina, observam ca se poate exporta raportul in Excel si PDF – mai mult decit sufficient:
Data viitoare vom vedea acelasi raport in WindowsForms...
Linkuri utile:
http://www.gotreportviewer.com/ - in partea dreapta aveti exemple de aplicatii care fac diverse – genereaza automat fisierele rdlc, scot automat fisierele Excel si multe altele
SQL Server Reporting Services – pentru utilizarea avansata a rapoartelor.
11 public class MockInterfaceOnObject<IInterface>
12 where IInterface:class
22 public interface IReportProgress
23 {
24 void ReportProgress(int percentProgress, object State);
25 void CancelAsync();
26 }
18 public static T MockObject<T>()
19 where T:class
25 public static IInterface MockInterface(object obj)
care construieste un obiect nou ce are un constructor ce primeste ca argument obiectul trimis si implementeaza interfata facind apel la functiile corespunzatoare obiectului trimis - se foloseste cind obiectul deja exista , are anumite proprietati setate si vreti sa il folositi.
Nu am putut folosi metoda 1 pentru a face new pe backgroundworker -nu ca nu ar fi compilat, dimpotriva... Dar design-time de la WindowsForms se incapatina sa dea eroare.
Asa ca am folosit 2, cu ceva de genul
70 MockInterfaceOnObject<IReportProgress>.MockInterface(backgroundWorker1)
using System.CodeDom.Compiler;
using System.Reflection;
using Microsoft.CSharp;
using System.IO;
namespace MockInterface
public class MockInterfaceOnObject<IInterface>
where IInterface:class
private static Type TypeInterface()
return typeof(IInterface);
public static T MockObject<T>()
where T:class
return ObjectWithInterface(typeof(T), GenerateInheritance(typeof(T)), null) as T;
public static T MockObject<T>(T obj)
where T : class
return ObjectWithInterface(typeof(T), GenerateEncapsulation(typeof(T)), obj) as T;
public static IInterface MockInterface(Type t)
return ObjectWithInterface(t, GenerateInheritance(t), null) as IInterface;
public static IInterface MockInterface(object obj)
return ObjectWithInterface(obj.GetType(), GenerateEncapsulation(obj.GetType()), obj) as IInterface;
private static string CodeBase(string pathfile)
if (pathfile == null)
if (pathfile.IndexOf("file:///") == 0)
pathfile = pathfile.Substring("file:///".Length);
return pathfile;
private static string CodeBase(Assembly a)
return CodeBase(a.CodeBase);
public static object ObjectWithInterface(object Existing)
public static object ObjectWithInterface(Type t,string Code,params object[] args)
CSharpCodeProvider cs = new CSharpCodeProvider();
CompilerParameters cp = new CompilerParameters();
cp.GenerateExecutable = false;
cp.GenerateInMemory = true;
cp.ReferencedAssemblies.AddRange(new string[] { CodeBase(t.Assembly), CodeBase(TypeInterface().Assembly) });
foreach (AssemblyName a in t.Assembly.GetReferencedAssemblies())
string pathfile = CodeBase(a.CodeBase);
if (pathfile != null && (!cp.ReferencedAssemblies.Contains(pathfile)))
cp.ReferencedAssemblies.Add(pathfile);
CompilerResults cr = cs.CompileAssemblyFromSource(cp, new string[1] { Code });
if (cr.Errors.Count > 0)
CompilerError ce = cr.Errors[0];
throw (new ArgumentException("Can not compile file : " + ce.ErrorText + " Line:" + ce.Line));
return Activator.CreateInstance(cr.CompiledAssembly.GetExportedTypes()[0],args);
private static string GenerateInheritance(Type t)
StringBuilder sb=new StringBuilder();
sb.AppendLine("namespace " + t.Namespace);
sb.AppendLine("{");
sb.AppendLine("public class My" + t.Name + " : " + t.Name + "," + TypeInterface().FullName);
sb.AppendLine("}");
return sb.ToString();
private static string GenerateEncapsulation(Type t)
StringBuilder sb = new StringBuilder();
sb.AppendLine("public class My" + t.Name + " : " + TypeInterface().FullName);
sb.AppendLine("private " + t.Name + " m_obj;");
sb.AppendLine("public My" + t.Name + "(" + t.Name + " obj){");
sb.AppendLine("m_obj = obj;");
foreach (MethodInfo mi in TypeInterface().GetMethods())
bool isvoid = (mi.ReturnType== typeof(void));
string returntype = mi.ReturnParameter.ParameterType.FullName;
if (isvoid)
returntype = "void";
sb.AppendLine("public " + returntype + " " + mi.Name + "(");
sb.AppendLine(" ");
string strParams = " ";
foreach(ParameterInfo pi in mi.GetParameters())
sb.Append(pi.ParameterType.FullName + " " + pi.Name + ",");
strParams += pi.Name +",";
strParams = strParams.Substring(0, strParams.Length - 1);//remove ,
sb.Length =sb.Length-1;//remove ,
sb.AppendLine(")");
if (!isvoid)
sb.AppendLine("return ");
sb.AppendLine(" m_obj." + mi.Name + "(" + strParams + ");");
30 string[] lines = File.ReadAllLines(@"C:\lista.txt");
31 foreach (string line in lines)
32 {
33 if (line.ToLower().IndexOf("java") > 0)
34 continue;
35
36 if (line.ToLower().IndexOf("autocad") > 0)
37 continue;
38
39 if (line.ToLower().IndexOf("cisco") > 0)
40 continue;
41
42 l.Add(line);
43 }
44 string strNewFile = @"C:\lista1.txt";
45 if (File.Exists(strNewFile))
46 File.Delete(strNewFile);
47
48 File.WriteAllLines(strNewFile, l.ToArray());
49
50 System.Diagnostics.Process.Start(strNewFile);
( de aici se vede clar ca am scos java, cisco , si altele ...)
Apoi - am inceput sa gindesc - si sa imi aduca aminte ca exista ceea ce se numeste Predicate
Asa ca am ajuns la urmatorul text :
28 static Predicate<string> NotInStringFirstTry(string x)
29 {
30
31 return new Predicate<string>(
32 delegate(string line)
33 {
34
35 if (x == null && line == null)
36 return false;
37 if (x == null)
38 return true;
39 if (line == null)
40 return true;
41 return (line.ToLower().IndexOf(x.ToLower()) < 0);
42 }
43 );
44 }
45 static void Main(string[] args)
46 {
47 string[] lines = File.ReadAllLines(@"C:\lista.txt");
48
49 List<string> l = new List<string>();
50 l.AddRange(lines);
51
52
53 l = l.FindAll(NotInStringFirstTry("java"));
54 l = l.FindAll(NotInStringFirstTry("cisco"));
55 l = l.FindAll(NotInString("autocad"));
56
57 string strNewFile = @"C:\lista1.txt";
58 if (File.Exists(strNewFile))
59 File.Delete(strNewFile);
60
61 File.WriteAllLines(strNewFile, l.ToArray());
62
63 System.Diagnostics.Process.Start(strNewFile);
64 }
Nu numai ca e mai compact - e si mai simplu de inteles ...
Iar la a 3-a gindire mi-am dat seama ca fac prea multe iteratii in array - asa ca m-ar ajuta o singura functie:
29 static Predicate<string> NotInString(params string[] x)
30 {
32 return new Predicate<string>(
33 delegate(string line)
34 {
36 if (x == null && line == null)
37 return false;
38 if (x == null)
39 return true;
40 if (x.Length == 0)
41 return true;
42 if (line == null)
43 return true;
44 foreach (string word in x)
45 {
46 bool b = (line.ToLower().IndexOf(word.ToLower()) > -1);
47 if (b)
48 return false;
49 }
50
51 return true;
52 }
53 );
54 }
55
56 static void Main(string[] args)
57 {
58 string[] lines = File.ReadAllLines(@"C:\lista.txt");
59
60 List<string> l = new List<string>();
61 l.AddRange(lines);
62 l = l.FindAll(NotInString("java", "cisco", "autocad"));
63
64 string strNewFile = @"C:\lista1.txt";
65 if (File.Exists(strNewFile))
66 File.Delete(strNewFile);
67
68 File.WriteAllLines(strNewFile, l.ToArray());
69
70 System.Diagnostics.Process.Start(strNewFile);
71 }
De ce taskuri asincrone ? In ideea ca , intr-o aplicatie Windows(si chiar ASP.NET) , operatiile lungi ar trebui sa fie executate de catre alt thread, urmind ca aplicatia sa poata sa mai afiseze ceva utilizatorulu in tot acest timp ( fie si un buton pe care scrie „apasa ca sa intrerupi operatia asta lunga ...”). De pilda, in aplicatia noastra, daca avem mai mult de 100 de Publisher-i si vrem sa ii vedem pe toti – ar trebui incarcati intr-un nou thread.
Ne ocupam mai intii de o aplicatie Windows Forms si pe urma de ASP.NET
Un thread nu e greu de pornit. Hai sa vedem un exemplu:
System.Threading.Thread t = new System.Threading.Thread(new System.Threading.ParameterizedThreadStart(StartAction));
t.Start("obiect transmis");
public void StartAction(object o)
string s = o.ToString();
System.Threading.Thread.Sleep(5000);
//executa actiunea
//trimite text
if (this.InvokeRequired)
this.Invoke((MethodInvoker)delegate()
this.Text = s;
});
//sau
//this.Invoke(new MethodInvoker(evenimentfaraparametri());
//this.Invoke(new EventHandler(btnDiscounts_Click));
//this.BeginInvoke(new EventHandler(eveniment cu parametri));
Totusi, exista o problema – dintr-un thread nu se pot accesa DIRECT controale din alt thread – si de aceea avem instructiunea this. Invoke .Diferenta intre this.Invoke si este this.BeginInvoke aceea ca prima instructiune asteapta rezultatul actiunii, pe cind a doua doar executa si se intoarce imediat sa execute codul ramas.
De aceea exista controlul numit BackgroundWorker – care asigura ca , din evenimentul propriu generat, sa accesezi orice obiect de pe forma. O sa facem acest lucru pentru salvarea in XML a colectiei de Publisher-i in format XML.
.NET are o forma usoara de a salva o colectie/clasa in format XML , salvindu-i proprietatile.
Vom utiliza modalitatea cea mai usoara de a face acest lucru
Marcam clasa Publisher si clasa colectie ColPublisher cu atributul de [Serializable] :
[Serializable]
public class ColPublisher : System.Collections.ObjectModel.KeyedCollection<string,Publisher>
public class Publisher
Acum o sa facem serializarea obiectului Publisher:
#region Serializer
/// <summary>
/// instanta pentru lazy load
/// </summary>
private static XmlSerializer m_Serializer;
/// serializator pentru obiectul publisher
private static XmlSerializer Serializer
if (m_Serializer == null)
m_Serializer = new XmlSerializer(typeof(Publisher));
return m_Serializer;
/// salveaza obiectul ca XML
[XmlIgnore]
public string XML
EncodingStringWriter sw = new EncodingStringWriter(sb, Encoding.Default);
XmlTextWriter xtw = new XmlTextWriter(sw);
Serializer.Serialize(xtw, this);
/// recreeeaza un Publisher dintr-un string XML
/// <param name="XML">string care contine tot </param>
/// <returns></returns>
public static Publisher FromXML(string XML)
StringReader sr = new StringReader(XML);
return Serializer.Deserialize(sr) as Publisher;
#endregion
Copiem apoi acelasi cod( cu citeva diferente) si pentru ColPublisher
Citeva comentarii despre cod:
De ce am pus [XmlIgnore] peste public string XML ? Pentru a nu serializa si aceasta proprietate, dind astfel nastere la o nedorita recursivitate
Ce e cu clasa EncodingStringWriter ? Este facuta pentru a putea schimba Encoding=ul- daca aveti de exemplu caractere speciale(diacritice) romanesti/franceze/etc.
De ce metoda FromXML este statica- iar XML este pe instanta? Asa mi se pare normal – transformarea dintr-un obiect in XML sa apartina obiectului, iar din XML in obiect nu poate sa apartina unui obiect( ah, daca as fi putut scrie this = Serializer.Deserialize(sr) as Publisher !) - ci doar clasei.
Nu se poate face codul mai „generic”? Ba da- una din deosebiri ar fi ca FromXML ar trebui sa fie pe instanta...
Haideti acum in proiectul Windows sa serializam o colectie de Publisher-i.Pe forma frmPublisherList adaugam un buton btnSave, cu textul Save, dublu click si scriem urmatorul cod:
private void btnSave_Click(object sender, EventArgs e)
BookObjects.ColPublisher col = colPublisherBindingSource.DataSource as BookObjects.ColPublisher;
string strSave = System.Environment.GetFolderPath(System.Environment.SpecialFolder.ApplicationData);
strSave = Path.Combine(strSave, "pub.xml");
File.WriteAllText(strSave, col.XML);
System.Diagnostics.Process.Start(strSave);
Rulati proiectul, adaugati 2 publisher-i si apasati pe save.
Este clar ca, daca sunt multi publisher-i, procesul poate deveni prea lung si blocheaza interfata.
Haideti sa folosim background worker. Il adaugam din toolbox , il redenumim bgSave, dublu click. Luam codul din btnSave_Click, il adaugam la si pe urma scriem doar bgSave.RunWorkerAsync();
bgSave.RunWorkerAsync();
private void bgSave_DoWork(object sender, DoWorkEventArgs e)
Data viitoare o sa vorbim despre modelul asincron din ASP.NET.
Am descoperit, cu uimire, virtutile Dataset si DataTable. Sunt extraordinare pentru proiecte one-shot!
Am avut de facut o integrare intre SQL SERVER, MySQL si ORACLE(date care trebuiau verificate, mapate, schimbate, vazut diferente, grupate, etc)
Dataset-ul s-a dovedit a fi unealta perfecta pentru join-uri intre tabele, pentru filtrari si altele.
Admiratia mea pentru OO a mai scazut un pic – pentru ca – chiar si cu CodeSmith – tot era destul de greu de scris asa ceva.
In orice aplicaţie este bine sa ţinem evidenta operaţiilor făcute de utilizator( ce a modificat sau chiar ce a văzut). In acest scop putem folosi fie mecanismul de trace din .net, fie o soluţie proprie, fie Logging and Instrumentation Application Block(http://msdn2.microsoft.com/en-us/library/ms998162.aspx), fie log4net(http://logging.apache.org/log4net/
Vom utiliza in acest exemplu log4net .El suporta log-area operaţiilor in felurite moduri – in fişier, baza de date, email, telnet si multe altele.
Downloadati versiunea 1.2.10 de la adresa http://logging.apache.org/log4net/downloads.html si sa începem configurarea aplicaţiei. Copiaţi conţinutul folder-ului log4net-1.2.10\bin\net\2.0\debug in C:\Book\sharedDll si sa începem modificarea proiectului Windows pentru a înregistra ce a făcut utilizatorul
Deschidem Book.sln si deschidem App.Config. Acolo scriem următoarele imediat sub configuration:
<configSections>
<section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net"/>
</configSections>
<log4net>
<appender name="RollingLogFileAppender" type="log4net.Appender.RollingFileAppender">
<File value="Log4Net.log"/>
<AppendToFile value="true"/>
<rollingStyle value="Composite"/>
<maximumFileSize value="1MB"/>
<maxSizeRollBackups value="10"/>
<datePattern value="yyyyMMdd"/>
<layout type="log4net.Layout.PatternLayout">
<param name="ConversionPattern" value="%d [%t] %-5p %c %m%n"/>
</layout>
</appender>
<root>
<level value="Debug"/>
<appender-ref ref="RollingLogFileAppender"/>
</root>
</log4net>
După cum se vede, folosim RollingLogFileAppender ( adică un fişier a cărui denumire va fi diferita in fiecare zi după modelul datePattern ) de tipul Composite(daca depaseste maximumFileSize atunci se creează un nou fişier in ziua respectiva).
Sa adăugam referinţa proiectului nostru (BookWin) dll-ul log4net.dll din sharedDll .Avem de făcut următoarele in Program.cs :
In funcţia Main scriem prima linie:
log4net.Config.XmlConfigurator.Configure();
apoi adăugam următorul membru in clasa Program:
public static readonly log4net.ILog logger = log4net.LogManager.GetLogger("RollingLogFileAppender");
Haide sa scriem in fişier de cite ori un utilizator adaugă un nou Publisher.
In frmPublisherAdd.cs, la evenimentul private void btnAdd_Click(object sender, EventArgs e) vom adăuga codul de log-are:
if (Program.logger.IsDebugEnabled)
Program.logger.Debug("Adaugat publisher cu numele:" + p.Name);
Cam atit e de făcut. Acum rulaţi proiectul, adaugaţi un Publisher, si o sa vedeţi un fişier log4Net.log in care scrie următoarele:
<data> [1] DEBUG RollingLogFileAppender Adaugat publisher cu numele:newpub
E interesant la log4Net ca puteţi adăuga mai mulţi appender-i, astfel ca , de pilda, sa trimită si email de cate ori o modificare e făcuta.
Observatie 1:
Pentru aplicatia Web, modificarile in Web.Config sunt aceleasi - iar in global.asax trebuie pusa linia urmatoare:
void Application_Start(object sender, EventArgs e) { // Code that runs on application startup log4net.Config.XmlConfigurator.Configure(); }
Observatie 2:
In loc sa punem codul in fiecare pagina de Web si Windowspe salvare, mai bine punem in fiecare cod de "salvare" al obiectelor- de pilda in public void Save()
Lectura obligatorie: documentaţia de log4net...
Documentarea - Scrierea de fişiere Help.
In .NET sunt doua tipuri mari de fişiere Help : Cele care produc Help pentru utilizatorul final si cele care sunt auto-generate din comentarii la cod.
Avem nevoie de următoarele:
1.Html Help Workshop – e free si puteţi sa îl downloadati de aici
http://www.microsoft.com/downloads/details.aspx?familyid=00535334-c8a6-452f-9aa0-d597d16580cc&displaylang=en. Adiţional puteţi downloada si fişiere css stil Office de aici
http://www.microsoft.com/downloads/details.aspx?FamilyId=A6A76073-0E0A-49BB-8E21-318B798B4CF6&displaylang=en
2. Pentru documentaţia codului exista înainte NDoc – dar din păcate dezvoltatorul nu mai face dezvoltarea pentru .Net 2.0( vezi http://johnsbraindump.blogspot.com/2006/07/ndoc-20-is-dead.html)
Alternativa este SandCastle (http://www.sandcastledocs.com) din care ultimul CTP este aici (http://www.microsoft.com/downloads/details.aspx?FamilyID=E82EA71D-DA89-42EE-A715-696E3A4873B2&displaylang=en).
3.De asemenea, pentru ca SandCastle e greu de utilizat din command line, exista mai multe GUI-uri pentru el – intre care vom lucra cu SandCastle Help File Builder de pe CodePlex (http://www.codeplex.com/Wiki/View.aspx?ProjectName=SHFB). O sa lucram cu ultimul release ,1.3.3.1 PROD aflat aici
http://www.codeplex.com/SHFB/Release/ProjectReleases.aspx?ReleaseId=1209
si as downloada chiar sursele ...
Începem cu documentarea codului pentru BookObjects, urmând sa trecem la generarea de Help pentru proiectul Windows.
Comentariile in C# se fac scriind trei slash-uri deasupra clasei/metodei/cimpului pe care vreţi sa le documentaţi:
/// Aceasta clasa tine toti publisher-ii
/// Mod de utilizare : folositi Load
/// varianta interna de generat cheie unica pentru un Publisher
/// <param name="item">publisher-ul</param>
protected override string GetKeyForItem(Publisher item)
Continuaţi cu toate metodele sau downloadati ultima varianta de proiect de aici:
http://serviciipeweb.ro/iafblog/content/binary/part12/book.zip
In plus, trebuie sa mai setaţi faptul ca trebuie generata documentaţia XML din proprietatile proiectului:
Acum in folder-ul C:\Book\BookObjects\bin\Debug aveţi generata documentaţia XML. Pornim SandCastle Help File Builder care arata cam asa:
Apăsam pe Add si ne ducem in C:\Book\BookObjects\bin\Debug. Acolo indicam dll-ul generat iar programul o sa „observe” si fişierul XML. Apăsam pe iconiţa de compilare si ... eroare...
Error: Unresolved assembly reference: System.Configuration (System.Configuration, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a) required by BookObjects
Last step completed in 00:00:02.403
După descriere vedem că ii lipseşte o referinţa la System.Configuration. Acest dll se afla in GAC (Global Assembly Cache) si o vom adăuga. In Project Properties , la Build=>Dependencies apăsaţi pe butonul cu cele 3 puncte.
Acum in ecranul următor exista un buton cu imaginea de folder si cu o icoana de o cheie care iese, iar la tooltip scrie „Add GAC dependencies”
Apăsaţi pe el si căutaţi System.Configuration, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a in lista care urmează.
Acum spune ca ii lipseşte un XSL(Error: The transform file 'C:\Program Files\Sandcastle\ProductionTransforms\AddOverloads.xsl' could not be loaded. The error is: Could not find file 'C:\Program Files\Sandcastle\ProductionTransforms\AddOverloads.xsl'.). Pentru asta fie downloadati un CTP de SandCastle mai vechi sau Visual Studio 2005 SDK Version 4.0 de la adresa http://www.microsoft.com/downloads/details.aspx?FamilyID=51a5c65b-c020-4e08-8ac0-3eb9c06996f4&DisplayLang=en si copiaţi XSL-ul cu pricina in folder-ul <Program Files>\ Sandcastle\ProductionTransforms, alături de celelalte.
Acum rulati din nou si ceea ce se va genera este un document chm , numit Documentation.chm aflat in C:\Book\BookObjects\bin\Debug\Help:
Putem personaliza ceea ce se generează destul de uşor, modificând setările de aici:
E clar acum ca aceasta documentaţie se poate regenera la cerere.
Sa generam acum documentaţia pentru Windows Forms.
Ar trebui pentru fiecare forma sa avem cate un Help – aşa ca o sa cream 3 fişiere HTML care o sa tina List, Add si Update.
Vom crea un nou folder, numit HelpWindows, in C:\book\Help si vom pune in el cele 3 fişiere : add.htm,list.htm,update.htm.
Pornim HTML Help Workshop, File=>New =>Project si daţi next. Adaugati fişierele htm si apăsati pe Contents. Acceptaţi creerea unui nou fişier si apăsând pe iconiţa din stânga cu aspect de fişier adăugat la conţinut cele 3 fisiere, dindu-le numele corespunzătoare. Acum, după compilare, s-a generat un fişier chm. Haideţi sa îl integram cu aplicaţia Windows.
Sarcina de a copia fişierul chm lângă executabil o las cititorului, având in vedere ca am mai făcut aşa ceva(Project=>Properties=>Build Events). Sa mergem la forma de list si sa adaugam din ToolBox un control HelpProvider. LA proprietati la HelpNameSpace puneti numele chm-ului. Acum pe forma, gasiti HelpKeyword on... (setati valoarea la list.htm) si HelpNavigator on...( setat la topic). Rulati, apasati F1 si iata fisierul de help!
Puteti downloada ultimele surse de aici
Lecturi utile:
GAC http://www.codeproject.com/dotnet/DemystifyGAC.asp
MSHelp 2.0 http://www.helpware.net/mshelp2/h20.htm
Din mai multe motive :
1. Pentru ca e o modalitate usoara de a releva functionalitatile mari ale aplicatiei
2. Pentru ca la orice modificare la care nu sunteti sigur daca dauneaza cumva logicii aplicatiei puteti rula testele vechi si vedeti daca ati stricat ceva sau nu(Nota : ar trebui sa adaugati un nou test pentru cei care vin dupa voi )
3. E mai usor de fixat bug-urile daca, pe deasupra, rulati testele in fiecare noapte – si a doua zi dimineata vedeti ceva stricat...
4.
Hai sa trecem la treaba:
Mai intii downloadati NUnit de la http://www.nunit.org/index.php?p=download ( eu am folosit versiunea 2.2.8 )Exista si surse si setup de instalare. Eu as sfatui sa luati sursele sa le compilati.
Apoi la solutia noastra Book.sln adaugam un nou proiect de tipul Class Library , numit BookTest , adaugam o referinta la nunit.framework.dll , aflat in NUnit-2.2.8-src\src\NUnitFramework\framework\bin\Debug2005, modificam class1.cs in TestPublisher.cs si incepem sa scriem testul.Testul cel mai simplu este unul de CRUD – create , read, update, delete.
Avem nevoie de obiectele Publisher respective, precum si de setari in fisierul App.Config pentru a recunoaste Baza de date, precum si de Baza de date.
Pentru Publisher, adaugam o referinta la BookObject in tab-ul „Projects” de la Add Reference.
Pentru App.Config, adaugam un fisier de tipul application configuration file si copiem de la BookDos partile relevante, astfel incit fisierul arata astfel :
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<appSettings>
<add key="DatabaseUsed" value="MDB"/>
<!-- possible values : MDB, SQLServer-->
</appSettings>
<connectionStrings>
<add name="MDB" connectionString="Provider=Microsoft.Jet.OLEDB.4.0;Data Source=|DataDirectory|\book.mdb;User Id=admin;Password=;"/>
<!-- TODO : add for asp.net application the connection string with SQL Server-->
</connectionStrings>
</configuration>
Pentru baza de date, e simplu : in Build Events, la Post Build Command Line adaugam
copy $(ProjectDir)..\BookData\*.mdb $(TargetDir)
Acum putem incepe sa scriem testul :
using NUnit.Framework;
using BookObjects;
namespace BookTest
[TestFixture] //arata ca e o clasa care contine teste
public class TestPublisher
[Test] //arata ca metoda care urmeaza este un test
[Category("CRUD")] //categoria -de obicei, testele de CRUD ar trebui puse impreuna
public void CRUD()
Publisher p = new Publisher();
p.Name = "Amazon";
p.Save();
//sa il gasim
ColPublisher cp = new ColPublisher();
cp.Load();
bool bFound= false;
foreach (Publisher pLoop in cp)
if (pLoop.Name == p.Name)
bFound = true;
break;
//daca acea conditie(bFound) nu e true, atunci se afiseaza mesajul de eroare
Assert.IsTrue(bFound, "Nu s-a gasit publisher cu numele " + p.Name + " dupa insert");
//sa il modificam
p.Name = "O'Reilly";
p.Update();
//sa il gasim din nou
cp = new ColPublisher();
bFound = false;
Assert.IsTrue(bFound, "Nu s-a gasit publisher cu numele " + p.Name + " dupa update");
//acum sa il stergem
p.Delete();
//si sa vedem ca nu a fost gasit
//daca acea conditie(bFound) nu e false, atunci se afiseaza mesajul de eroare
Assert.IsFalse(bFound, "S-a gasit publisher cu numele " + p.Name + " dupa delete");
Il compilam si sa rulam testul. Gasiti in folderul NUnit-2.2.8-src\src\GuiRunner\nunit-gui-exe\bin\Debug2005 un nunit-gui.exe si porniti-l.Apasati File=> Open si mergeti in C:\Book\BookTest\bin\Debug si incarcati BookTest.dll . Ar trebui sa apara figura urmatoare
Apasati pe RUN si primul lucru pe care il vedeti este:
Se vede clar ca e ceva prost... ne uitam in TestPublisher.cs si vedem ca la linia 37 este
Ceva a mers prost la update ... sa vedem linia 107 din Publisher.cs
strSQL += "'" + this.Site.Replace("'", "''") + "'";
Acum e clar ce s-a intimplat ... Cind am facut testul, nu am initializat Site-ul cu nimic... si atunci este null , ceea ce inseamna ca .Replace nu poate fi aplicat
Sa modificam codul din Publisher.cs ca sa ia in seama si acest lucru :
if (this.Site == null)
strSQL += " NULL ";
Acum apare alta eroare :
Este destul de clar ca aplicatia nu a updatat numele ...
De ce ?Ne dam seama imediat : in momentul in care aplicatia a adaugat un nou Publisher , nu a regasit ID-ul inserat ... iar cind a facut update, IDPublisher este 0 , ceea ce inseamna ca nu a putut fi facut update corect.
Cum modificam acest lucru ? Pentru access , putem sa selectam maxim de ID,iar pentru SQL Server putem crea o procedura stocata ... sau sa intoarcem @@Identity
Hai sa facem pentru Access , modificand Publisher.cs, procedura Save, adaugind la final:
if(Settings.TheDatabase == Settings.DatabaseUsed.MDB)
strSQL = "select max(IDPublisher) as nr from Publisher";
using (DbConnection dc = Settings.TheConnection)
dc.Open();
using(DbCommand dco =Settings.TheCommand)
dco.CommandType = System.Data.CommandType.Text;
dco.CommandText = strSQL;
dco.Connection = dc;
object o = dco.ExecuteScalar();
this.IDPublisher = int.Parse(o.ToString());
Acum rulam din nou testul si totul e verde , ceea ce e de bine :
E clar ca exemplu a fost mai degraba simplut, iar ceea ce conteaza, de fapt, sunt regulile de business si de validare - ca de exemplu, validarea CNP
Open the book.sln solution and add a new project of type ConsoleApplication (name it BookDos)
Add reference to the BookObjects project and add an app.config file and write to this file the same as in the corresponding app.config file from BookWin application.
The post build event on project – properties must be the same as for the BookWin
In Program.cs file please enter the following code:
namespace BookDos
ColPublisher col = new ColPublisher();
col.Load();
Console.WriteLine("Records Number:" + col.Count);
foreach (Publisher p in col)
Console.WriteLine(p.Name);
And, of course, the number of records will be 0 – as we do not have any records.
In the following we will show how to perform some automated tests with NUnit .
The site map is relatively easy:
Add a new item – find “Site Map” and accept the default name (Web.sitemap)
And put the following
<siteMap xmlns="http://schemas.microsoft.com/AspNet/SiteMap-File-1.0" >
<siteMapNode url="default.aspx" title="Main" description="First Page">
<siteMapNode url="frmPublisherList.aspx" title="All publishers" description="Publishers list" >
<siteMapNode url="frmPublisher_Insert.aspx" title="New Publisher" description="Add new"></siteMapNode>
<siteMapNode url="frmPublisher_Edit.aspx" title="Edit Publisher" description="Edit"></siteMapNode>
<siteMapNode url="frmPublisher_Delete.aspx" title="Delete Publisher" description="Delete"></siteMapNode>
</siteMapNode>
<siteMapNode url="frmBookList.aspx" title="All books" description="Book list" >
</siteMap>
( the names are pretty suggestive – url, title and description)
Now it’s time to put to work :
Open Book.master , and put a site map control ( find into the navigation tab on toolbox) before content place holder:
<asp:SiteMapPath ID="SiteMapPath1" runat="server" Font-Names="Verdana" Font-Size="0.8em" PathSeparator=" : ">
<PathSeparatorStyle Font-Bold="True" ForeColor="#990000" />
<CurrentNodeStyle ForeColor="#333333" />
<NodeStyle Font-Bold="True" ForeColor="#990000" />
<RootNodeStyle Font-Bold="True" ForeColor="#FF8000" />
</asp:SiteMapPath>
And put a tree view instead of right menu:
<asp:TreeView ID="TreeView1" runat="server" DataSourceID="SiteMapDataSource1" MaxDataBindDepth="1">
</asp:TreeView>
<asp:SiteMapDataSource ID="SiteMapDataSource1" runat="server" />
Now, if you run the project, and press new button, you will see the following
Sure that all books it is not implemented yet – but it is your task to do it.
Now we will proceed to the localization part. We want to be able that people see the content in English and French.
We will localize just one form, and we left the others as an exercise to the reader.
The setting of language will be set in a cookie on the user’s PC and will be read each time.
Add a drop down list to the master page, near Book application with the following code:
<asp:DropDownList runat="server" id="ddlLanguage" OnSelectedIndexChanged="ddlLanguage_SelectedIndexChanged" AutoPostBack="true">
<asp:ListItem Text="English" Value="en">
</asp:ListItem>
<asp:ListItem Text="French" Value="fr">
</asp:DropDownList>
On the .cs page, let’s store the actual configuration :
protected void ddlLanguage_SelectedIndexChanged(object sender, EventArgs e)
HttpCookie cookie = Request.Cookies["Language"];
cookie.Value = ddlLanguage.SelectedValue;
Response.AppendCookie(cookie);
cookie.Expires = System.DateTime.Now.AddYears(1);
Response.Redirect(Request.Url.LocalPath);
So we have saved the value ... now, let’s retrieve it:
if (!IsPostBack)
ChangeLanguage();
private void ChangeLanguage()
if (cookie == null)
//set default the cookie in web.config
string s = Thread.CurrentThread.CurrentUICulture.Name;
cookie = new HttpCookie("Language");
cookie.Value = s;
foreach (ListItem li in ddlLanguage.Items)
if (li.Value == cookie.Value)
li.Selected = true;
Now we must change the language : We can put this on each page, overriding InitializeCulture , or put in a global.asax file( that retains the application events) on Application_BeginRequest: ( new item => Global Application Class)
protected void Application_BeginRequest(object sender, EventArgs e)
string lang = System.Threading.Thread.CurrentThread.CurrentUICulture.Name;
if (cookie != null && cookie.Value != null)
lang = cookie.Value;
System.Threading.Thread.CurrentThread.CurrentUICulture = System.Globalization.CultureInfo.GetCultureInfo(lang);
System.Threading.Thread.CurrentThread.CurrentCulture = System.Globalization.CultureInfo.CreateSpecificCulture(lang);
It is time now to proceed to the localization
Add an Asp.NET folder, named “App_LocalResources”
And in this folder, add three resource files, named :
frmPublisherList.aspx.en.resx
frmPublisherList.aspx.fr.resx
frmPublisherList.aspx.resx
(The file name is compose by the name of the aspx file + (optional) language + .resx )
In these files we will add just one string for the Text property of the button that is new, like in the figure:
Now, we put meta:resourcekey="btnNew" on the button:
<asp:Button ID="btnNew" runat="server" Text="New" OnClick="btnNew_Click" meta:resourcekey="btnNew"/>
And we will see in this mode the translation by changing from English to French in the combo.
Attention: if you do not have the invariant culture file ( the one without language in the name) it does not work!
If you have several items that are invariant ( like the “save” button) you can add resources to the special folder App_GlobalResources and add there resx files ( that now can be named as you want to ) As example suppose we have now in the App_GlobalResources the files
Buttons.en.resx
Buttons.fr.resx
Buttons.resx
And one resource named
btnSaveText
We can acces as so :
<%$ Resources:Buttons,btnSaveText%>
<asp:Button ID="btnSave" Text="<%$ Resources:Buttons,btnSaveText%>" runat="server" OnClick="btnSave_Click" />
Or , programatically, by writing :
Resources.Buttons.btnSaveText
Next time we will look at making a DOS project for the same application
Items to read:
Localization: http://quickstarts.asp.net/QuickStartv20/aspnet/doc/localization/localization.aspx
Master Pages:
http://quickstarts.asp.net/QuickStartv20/aspnet/doc/masterpages/default.aspx
Now we will edit the Publisher objects .
Add a new WebForm , name it frmPublisher_Insert.aspx and make sure the “Place code in separate file” and “Select master page” are both selected by default.
Change in source view the title from “Untitled Page” to “Insert Publisher”
Now we must put controls in place to insert publishers
There must be the name and site of the publisher.
I prefer enter a table for this and the page will look like this:
<%@ Page Language="C#" MasterPageFile="~/Book.master" AutoEventWireup="true" CodeFile="frmPublisher_Insert.aspx.cs" Inherits="frmPublisher_Insert" Title="Insert Publisher" %>
<table>
<tr>
<td colspan="2">Enter values
</td>
</tr>
<td>
Name
<asp:TextBox ID="txtName" runat="server">
</asp:TextBox>
Site
<asp:TextBox ID="txtSite" runat="server">
<td><asp:Button ID="btnSave" Text="Insert" runat="server" />
<td><asp:Button ID="btnCancel" Text="Cancel" runat="server" />
</table>
Now switch to design view and double click on Insert button in order to generate Click event. Double click in solution explorer the frmPublisher_Insert.aspx and , in Design view, double click on Cancel button in order to generate Click event.
For cancel it is clear what we must do – redirect to the frmPublisherList.aspx
Response.Redirect("frmPublisherList.aspx", false);
For save button, we must create a new publisher and save
p.Name = txtName.Text;
p.Site = txtSite.Text;
p.Insert();
Please try it by setting the frmPublisher_Insert.aspx as start page and run the project (F5)
If all works well (please ensure that Insert has a call to Save()) you will see in the frmPublisherList.aspx the item you just selected.
It is clear that frmPublisherList.aspx has a need for a new button . Let’s put it
<asp:Button ID="btnNew" runat="server" Text="New" OnClick="btnNew_Click" />
And on code:
protected void btnNew_Click(object sender, EventArgs e)
Response.Redirect("frmPublisher_Insert.aspx", false);
That will be ok for adding a new publisher.
Now for editing and deleting we can make on list… but I prefer having 2 new pages.
So modify a little bit the code on the grid, in order to have the edit and delete operations : the edit will be a link , and the delete will be a button to see how different is the model on the two implementations.
The list page now looks like this:
<%@ Page Language="C#" MasterPageFile="~/Book.master" AutoEventWireup="true" CodeFile="frmPublisherList.aspx.cs" Inherits="frmPublisherList" Title="Publisher Lists" %>
<asp:GridView ID="grdPublisher" runat="server" AutoGenerateColumns="false">
<Columns>
<asp:BoundField DataField="Site" HeaderText="Site" />
<asp:BoundField DataField="Name" HeaderText="Name" />
<asp:TemplateField HeaderText="Operations">
<ItemTemplate>
<asp:Button runat="server" ID="btnDelete" CommandName="deletepub" CommandArgument='<%# Eval("IDPublisher") %>' Text="Delete" />
<asp:HyperLink runat="server" ID="hkEdit" NavigateUrl='<%# Eval("IDPublisher","~/frmPublisher_Edit.aspx?ID={0}") %>' Text="Edit"></asp:HyperLink>
</ItemTemplate>
</asp:TemplateField>
</Columns>
</asp:GridView>
<br />
The link hkEdit is self explanatory – it goes to the frmPublisher_Edit.aspx with the ID of publisher in the row.
For the button we must create the event – and the event is on the grid itself – is the RowCommand
On the .cs file:
protected void grdPublisher_RowCommand(object sender, GridViewCommandEventArgs e)
switch(e.CommandName)
case "deletepub":
int idPublisher;
if(int.TryParse(e.CommandArgument.ToString(),out idPublisher))
Response.Redirect("frmPublisher_Delete.aspx?ID="+ idPublisher, false);
Response.Write("Can not find id:" + idPublisher);
default:
Response.Write("Do not know command : " + e.CommandName);
Now create the two pages frmPublisher_Delete and frmPublisher_Edit
On both we will copy the table from the new page and the source – without the class declaration. One more thing is to retrieve from ID the editing publisher:
if(!int.TryParse(Request.QueryString["ID"],out idPublisher))
//we have id of the publisher
How can we retrieve from the ID of the publisher the object ? Remember that in Windows forms application we did pass from one form to another the publisher object. Here we have just the Id. For this, we will open again the Book.sln solution and add the method to load one single object.
I like to put the method on ColPublisher and make it static … to not apparently create a new object.
public static Publisher sLoadFromID(int ID)
DbConnection db = Settings.TheConnection;
using (db)
db.Open();
IDataReader ir = Settings.Load("select IDPublisher, NamePublisher, SitePublisher from Publisher where IDPublisher="+ ID, db);
while (ir.Read())
p.FillObject(ir);
return p;
Compile and go to Web project.We can have the publisher:
Publisher p = ColPublisher.sLoadFromID(idPublisher);
if (p == null)//maybe someone deleted
//now fill the text boxes
txtName.Text = p.Name;
txtSite.Text = p.Site;
Why we have put (!IsPostBack ) ? Simply because the textboxes must be filled only once – the first time. When the user enter new name and/or new site and after clicks on save, we must preserve his data .Other problem is that when we have to save the data, we must have the same code to load the publisher – so we put this into a function into the page:
private Publisher pub
if (!int.TryParse(Request.QueryString["ID"], out idPublisher))
return ColPublisher.sLoadFromID(idPublisher); ;
The code on PageLoad will be now shorter :
Publisher p = pub;
if (p == null)
And we must modify the code on saving also :
protected void btnSave_Click(object sender, EventArgs e)
//TODO : throw an exception that someone deleted the publisher
You can modify also the text of btnsave from “Insert” to “Save”
On the delete page we will put the same code to retrieve the Publisher .Here is the code:
public partial class frmPublisher_Delete : System.Web.UI.Page
if(p != null)
protected void btnCancel_Click(object sender, EventArgs e)
Do not forget to change the text “Insert” of btnSave to “Delete”. You also can put readonly property to true on the textboxes
<%@ Page Language="C#" MasterPageFile="~/Book.master" AutoEventWireup="true" CodeFile="frmPublisher_Delete.aspx.cs" Inherits="frmPublisher_Delete" Title="Untitled Page" %>
<asp:TextBox ID="txtName" runat="server" ReadOnly="true">
<asp:TextBox ID="txtSite" runat="server" ReadOnly="true">
<td><asp:Button ID="btnSave" Text="Delete" runat="server" OnClick="btnSave_Click" />
<td><asp:Button ID="btnCancel" Text="Cancel" runat="server" OnClick="btnCancel_Click" />
Next time we will put some modifications to the site: a site map(in order to can have indications for the user where he is) and code to change and load resources in English and French languages at run time.
Now to the performing of the setup.We suppose that we want to deploy from a CD.
Right click Book win project, choose properties, and click on “Publish” tab.
There you will find the “Publishing location” – default to http://localhost/BookWin/
If you do not have IIS, then you can select a path, like C:\Book\BookSetup\
The problem that we have is that in the files that we need to install must be also the mdb file.
If you have something like a VS.NET Standard(or more) you have a special project template that allows you to add other files to the deployment. For the moment, we have to do what we can do. So, please right click the bookwin project, click add=>existing item and search for the book.mdb file from C:\Book\BookData .
Compile the project (CTRL+Shift+B) and now you will find the book.mdb on the application files – as data file.
Now to the prerequisites : Because we are deploying a CD, it is better to include .NET 2.0 – and download from the same location (when we are deploying from internet, I think it is preferable to deploy from MS site )
The updates will not be yet available, since we do not have yet a web site. But we can customize some of Options, like publisher name, product name and others.
Now press Publish Wizard :
The first step is where to make the setup kit (C:\Book\BookSetup\ already selected ) , the next is from where (click “from cd –rom or dvd rom”).
The third allows the application to check for updates – but , for the moment, we do not have a WebSite yet, so “the application will not check for updates”
And press finish!
If an error occurs, that says that can not find package, please go to Microsoft .NET Framework Version 2.0 Redistributable Package (x86) http://www.microsoft.com/downloads/details.aspx?FamilyID=0856eacb-4362-4b0d-8edd-aab15c5e04f5&displaylang=en and download the kit. Copy this kit to <C:\Program Files>\Microsoft Visual Studio 8\SDK\v2.0\Bootstrapper\Packages\DotNetFX
For instmsia.exe , goto http://go.microsoft.com/fwlink/?LinkId=37285
Now you can write the folder C:\Book\BookSetup to a CD and test .
If you do not want a CD, you can wrote a file and load as a CD.
One way is to create a ISO file with Alex Feinman IsoRecorder
http://isorecorder.alexfeinman.com/isorecorder.htm
Download his software ( I have tested the version for XP SP2 ) and right click the folder Book Setup
You will see Create ISO image file – click and accept default setting.
Now you have a CD -and you can load this CD with Virtual CD Control Panel.
You can now test the CD you have already created.
More, if you want to test on another environment, you can download Virtual PC 2004 SP1 or VMWare and create with those a new Windows Installation (of course, you must have the Windows installation CDs).
Next time we will do a ASP.NET application with SQL Server as a backend.
Suggested readings:
Comparison between VS.NET 2005 editions: http://msdn.microsoft.com/vstudio/products/compare/default.aspx
Virtual PC 2004 SP1 (free) http://www.microsoft.com/downloads/details.aspx?familyid=6d58729d-dfa8-40bf-afaf-20bcb7f01cd1&displaylang=en
Deployment problems : http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=13937&SiteID=1
Virtual CD Control Panel – http://download.microsoft.com/download/7/b/6/7b6abd84-7841-4978-96f5-bd58df02efa2/winxpvirtualcdcontrolpanel_21.exe
Alex Feinman – Make ISO http://isorecorder.alexfeinman.com/isorecorder.htm
Theme design by Jelle Druyts
Pick a theme: BlogXP business calmBlue Candid Blue dasBlog dasblogger DirectionalRedux Discreet Blog Blue Elegante essence Just Html Mono Movable Radio Blue Movable Radio Heat orangeCream Portal Project84 Project84Grass Slate Tricoleur