integration mellem silverpop og pivotal - Danmarks Tekniske ...
integration mellem silverpop og pivotal - Danmarks Tekniske ...
integration mellem silverpop og pivotal - Danmarks Tekniske ...
You also want an ePaper? Increase the reach of your titles
YUMPU automatically turns print PDFs into web optimized ePapers that Google loves.
Christian Schoubye Hansen<br />
Kongens Lyngby, januar 2010<br />
IMM-B.Eng-2010-01<br />
INTEGRATION<br />
MELLEM<br />
SILVERPOP OG PIVOTAL
Technical University of Denmark<br />
Informatics and Mathematical Modelling<br />
DTU – Building 321, DK-2800 Kongens Lyngby, Denmark<br />
Phone +45 45253351, Fax +45 45882673<br />
reception@imm.dtu.dk<br />
www.imm.dtu.dk<br />
IMM-B.Eng-2010-01<br />
Side 2
Abstrakt<br />
Hos FOSS er man interesseret i at kunne lave nye former marketing kampagner, som kan generere<br />
mere salg.<br />
Silverpop er et amerikansk firma, som har specialiseret sig i email marketing kampagner.<br />
Ved at bruge deres produkt, kan man lave dynamiske kampagner. Hvor man leverer lige præcist<br />
den information, som er inden for kundens forretningsområde.<br />
Målet med dette projekt er at lave synkronisering begge veje <strong>mellem</strong> Silverpop <strong>og</strong> FOSS’ CRM<br />
system, Pivotal.<br />
Endvidere skal data kunne behandles i Pivotal <strong>og</strong> kunder skal have mulighed for at kunne afmelde<br />
sig marketings e-mails fra FOSS.<br />
Side 3
Forord<br />
Denne rapport er en afhandling for en Bachelor grad i Engineering. Den blev lavet ved Institut for<br />
Informatik <strong>og</strong> Matematisk Modellering, ved <strong>Danmarks</strong> <strong>Tekniske</strong> Universitet.<br />
Afhandlingen omhandler synkronisering <strong>mellem</strong> 2 computer systemer. Den er inddelt i 3<br />
hovedområder, først analyse af de forskellige platforme, derefter designe en god måde at<br />
implementere en synkronisering <strong>og</strong> til sidst selve implementationen.<br />
Afhandlingen er lavet <strong>mellem</strong> 2/11-2009 <strong>og</strong> 22/01 2010<br />
Christian Schoubye Hansen<br />
Kongens Lyngby, januar 2010<br />
Side 4
Indholdsfortegnelse<br />
Abstrakt ................................................................................................................................................ 3<br />
Forord ................................................................................................................................................... 4<br />
Indledning ............................................................................................................................................ 7<br />
FOSS ................................................................................................................................................. 7<br />
Silverpop .......................................................................................................................................... 7<br />
Krav specifikation til <strong>integration</strong>sprojektet ..................................................................................... 8<br />
Analyse ................................................................................................................................................. 9<br />
Platformens Teknol<strong>og</strong>i ..................................................................................................................... 9<br />
Pivotal Tabel design / konstruktion ............................................................................................... 11<br />
Tabel vindue ............................................................................................................................... 11<br />
Pivotal Forms .................................................................................................................................. 13<br />
Pivotal agenter ............................................................................................................................... 14<br />
Løkker i agenter ......................................................................................................................... 14<br />
Silverpop ........................................................................................................................................ 15<br />
Data der skal sendes fra Pivotal til Silverpop............................................................................ 16<br />
Data fra Silverpop til Pivotal ...................................................................................................... 16<br />
Design ................................................................................................................................................. 17<br />
Tabel opbygning i Silverpop. .......................................................................................................... 17<br />
Nye tabeller i Pivotal ...................................................................................................................... 17<br />
Synkronisering af data fra Pivotal til Silverpop .............................................................................. 18<br />
Synkronisering fra Silverpop til Pivotal .......................................................................................... 19<br />
Unsubscribe .................................................................................................................................... 19<br />
Implementering.................................................................................................................................. 20<br />
Unsubscribe agent ......................................................................................................................... 20<br />
Data fra Pivotal til Silverpop agent ................................................................................................ 22<br />
Data fra Silverpop til Pivotal agent ................................................................................................ 24<br />
Data fra Pivotal til Silverpop C# kode ............................................................................................ 25<br />
ConsoleApplication1 / SilverpopIntegration .............................................................................. 25<br />
Data fra Silverpop til Pivotal C# kode ............................................................................................ 26<br />
Return Silverpop Data ................................................................................................................ 26<br />
Side 5
Test ..................................................................................................................................................... 28<br />
Konklusion .......................................................................................................................................... 29<br />
Opfyldte krav i forhold til løsningen .............................................................................................. 29<br />
Tilfredshed fra marketing afdelingen ............................................................................................ 29<br />
Benefit for forretningen ................................................................................................................. 29<br />
Fremtidig udvikling ......................................................................................................................... 29<br />
Bilag .................................................................................................................................................... 30<br />
Pivotal Agenter ............................................................................................................................... 30<br />
Return Silverpop Data C# ............................................................................................................... 32<br />
App.config .................................................................................................................................. 32<br />
ByteConvertor.cs ........................................................................................................................ 33<br />
CsvParser.cs ................................................................................................................................ 35<br />
ErrorMailGenerator.cs ............................................................................................................... 38<br />
L<strong>og</strong>.cs .......................................................................................................................................... 39<br />
SilverpopAPI.cs ........................................................................................................................... 41<br />
SilverpopFTP.cs .......................................................................................................................... 50<br />
TransactionalDatabase.cs .......................................................................................................... 52<br />
Pr<strong>og</strong>ram.cs ................................................................................................................................. 54<br />
ConsoleApplication1 C# ................................................................................................................. 57<br />
App.config ...................................................................................................................................... 57<br />
ByteConvertor.cs ........................................................................................................................ 58<br />
CsvFileGenerator.cs ................................................................................................................... 60<br />
SilverpopAPI.cs ........................................................................................................................... 64<br />
SilverpopFTP.cs .......................................................................................................................... 72<br />
Pr<strong>og</strong>ram.cs ................................................................................................................................. 74<br />
MainList.xml ............................................................................................................................... 80<br />
Side 6
Indledning<br />
Konkurrencen om kunderne <strong>og</strong> salg skærpes væsentligt med den nuværende krise. Hos FOSS<br />
sælger man et mindre antal apparater men til en meget høj pris per styk. Dermed er ethvert salg<br />
af allerstørste vigtighed for virksomheden. Man er derfor interesseret i nye tiltag, som kan<br />
generere mere salg.<br />
Et af de nye tiltag hos FOSS er ”Silverpop Engage”. Der er tale om en platform, der vil gøre det<br />
muligt at lave dynamiske marketing kampagner. Det er nødvendigt at gøre det nemt for<br />
marketingsafdelingen at bruge Silverpop, <strong>og</strong> derfor ønsker man at integrere Silverpop med FOSS’<br />
CRM system, som indeholder alle kundeoplysninger.<br />
FOSS<br />
FOSS blev grundlagt i 1956 af Nils Foss. Målet var at lave automatiske analyse apparater til<br />
landbruget. Tidligere havde man brugt manuelle analyse metoder, hvilket var meget<br />
tidskrævende.<br />
Man opfandt et apparat, som var lille <strong>og</strong> meget robust. Landmanden kunne tage det med ud på<br />
marken <strong>og</strong> hurtigt måle indholdet af fugt i korn. Det blev en stor succes.<br />
Senere begyndte man <strong>og</strong>så at lave analyse apparater til mejeri <strong>og</strong> kød industrien. Med købet<br />
af Perstorp Analytical AB i 1997, blev konceptet udvidet til <strong>og</strong>så at omfatte produktion af analyse<br />
apparater til mel, øl, farmaceutisk <strong>og</strong> kemisk industri.<br />
FOSS er i dag en global virksomhed med salgskontorer over det meste af verdenen. Det er stadig<br />
en familieejet virksomhed, der i dag er ledet af Nils Foss’ søn Peter Foss.<br />
Silverpop<br />
Silverpop er et amerikansk firma, der har udviklet en web baseret platform, som virksomheder kan<br />
benytte til at lave marketing kampagner.<br />
Silverpop giver mulighed for at vælge <strong>mellem</strong> forskellige informationsformer f.eks ved udsendelse<br />
af e-mail eller sms.<br />
Man har <strong>og</strong>så mulighed for at få en rapport over en marketing kampagne. I den kan man f.eks. se<br />
om en e-mail er bounced, dvs. den ikke nåede frem til modtageren. Derudover kan man se om<br />
modtageren åbnede e-mailen <strong>og</strong> om modtageren klikkede på n<strong>og</strong>le links i e-mailen.<br />
Man kan <strong>og</strong>så lave dynamiske e-mails, som indeholder generel information til alle kunder <strong>og</strong><br />
specifik information til alle kunder indenfor et marked segment. Når man sender<br />
nyhedsinformationen ud, baseres den på et eller flere kriterier. Eksempel på specifik information:<br />
kødindustrien får nyheder om kødprodukter <strong>og</strong> mejerier får nyheder om mejeriprodukter.<br />
Side 7
Krav specifikation til <strong>integration</strong>sprojektet<br />
Det er et krav at kunne synkronise data fra Pivotal til Silverpop. Det drejer sig om contacts, leads<br />
<strong>og</strong> prospects. Contacts er kunder, der har købt hos FOSS før. Leads <strong>og</strong> prospects er nye potentielle<br />
kunder, der har udtrykt interesse for FOSS produkter.<br />
Forskellen på et lead <strong>og</strong> et prospect er mængden af data om kunden. Hvis der kun er et navn <strong>og</strong> en<br />
e-mail, så bliver personen oprettet som et prospect. Når man får flere data, kan man så opgradere<br />
personen til et lead.<br />
Det er et krav at kunne synkronisere data fra Silverpop tilbage til Pivotal. Her er det data om de<br />
enkelte e-mails, man er interesseret i. Om en e-mail kom frem eller om den blev bounced. Om<br />
kunden åbnede e-mailen <strong>og</strong> evt. klikkede på n<strong>og</strong>le links i den.<br />
De forskellige skærmbilleder skal opdateres i Pivotal, så man kan se, hvordan den enkelte<br />
marketing kampagne gik. Hvor mange der modt<strong>og</strong> e-mailen, <strong>og</strong> hvor mange der var interesseret i<br />
indholdet.<br />
Der skal <strong>og</strong>så laves en unsubscribe funktion, så kunder kan afmelde nyheds- <strong>og</strong> reklame e-mails.<br />
Dette er et lov krav. Det skal både kunne lade sig gøre i Pivotal <strong>og</strong> via en hjemmeside.<br />
Det skal <strong>og</strong>så være muligt at resubscribe igen, hvis en kunde ønsker det.<br />
Unsubscribe via hjemmesiden er ikke en del af dette projekt. Det vil de udviklere, der<br />
pr<strong>og</strong>rammerer FOSS’ hjemmeside sørge for kommer til at virke.<br />
Når en kunde bliver afmeldt nyheds e-mails, skal de interesseområder, som kunden i første<br />
omgang skrev sig op til, slettes, så de ikke indgår i søgeresultater fra queries.<br />
Det sidste krav er at designet af databasen <strong>og</strong> synkroniseringspr<strong>og</strong>rammet skal gøre yderligere<br />
udvikling mulig. Dette er nødvendig for både for at kunne imødekomme nye ønsker fra marketing<br />
afdelingen, <strong>og</strong> <strong>og</strong>så for at kunne tilpasse synkroniseringspr<strong>og</strong>rammet til et nyt CRM system.<br />
Side 8
Analyse<br />
Platformens Teknol<strong>og</strong>i<br />
Hos FOSS bruger man Pivotal eRelationship CRM system.<br />
CRM står for “Customer Relationship Management” system.<br />
Systemet bruges til 3 hovedområder.<br />
Marketing til at holde styr på potentielle kunder samt kunder, som har handlet før hos FOSS. Det<br />
vil sige anvendelse af disse oplysninger i forbindelse med marketing kampagner.<br />
Salg, for at kunne følge op på kunder, der har udtrykt interesse for at købe et produkt. Opfølgning<br />
på alle muligheder for et salg er vigtig for virksomheden.<br />
Service <strong>og</strong> support til de instrumenter, som kunder har købt. I den forbindelse bruges det både til<br />
at håndterer fejl hos en kunde, <strong>og</strong> <strong>og</strong>så til versionsstyring af de enkelte instrumenter. I tilfælde af<br />
fejl kontakter kunden det lokale salgsselskab, som opretter en support incident på kundens<br />
instrument i Pivotal. Service afdelingen i Danmark håndterer alle support incidents. Hvis det er en<br />
generel fejl, slår man op i Pivotal, <strong>og</strong> finder alle de kunder, som har et tilsvarende instrument.<br />
Derefter sendes en besked ud til alle salgsselskaberne, om at sende en tekniker ud <strong>og</strong> opdatere<br />
kundernes instrumenter.<br />
Side 9
Pivotal er bygget op med en master server i Danmark <strong>og</strong> en masse satellit servere rundt om i<br />
verden. Det skal medvirke til at sikre at salgsselskaber over hele verden får rimelig hurtig adgang<br />
til Pivotal, <strong>og</strong> at serverne ikke overbelastes. Data bliver synkroniseret <strong>mellem</strong> master serveren <strong>og</strong><br />
de enkelte satellit servere rundt om i verden. Man kan køre Pivotal enten direkte koblet op til<br />
master server via en Citrix klient eller med et mobilt system mod en satellit server. Når man kører<br />
med et mobilt system har man et udsnit af dataene i en lokal database på ens computer, <strong>og</strong> når<br />
man er l<strong>og</strong>get på internettet, synkroniserer ens pc data til serveren via en web service.<br />
Når man opretter en tabel i Pivotal kan man vælge, om den skal blive synkroniseret eller ej. Der er<br />
d<strong>og</strong> den begrænsning, at hvis et pr<strong>og</strong>ram udenfor Pivotal opdaterer data i en tabel, så bliver<br />
denne tabel ikke længere synkroniseret videre. Det er derfor nødvendigt at lave flere tabeller,<br />
hvor man kan opdaterer i én tabel, <strong>og</strong> derefter bruger en Pivotal agent til at opdatere dataene i en<br />
anden tabel, som så kan synkronisere videre.<br />
Side 10
Pivotal Tabel design / konstruktion<br />
Alt i Pivotal er lavet, så man skal klikke sig rundt i forskellige vinduer for at lave nye tabeller, forms,<br />
queries <strong>og</strong> agenter. Det gør, at det umiddelbart er let at lave ting i Pivotal, at komme i gang. Men<br />
det er meget svært at lave komplicerede ting, fordi Pivotal agenterne hurtigt gør det uoverskueligt<br />
<strong>og</strong> overblikket for nemt mistes.<br />
Tabel vindue<br />
Her kan man oprette nye felter <strong>og</strong> beslutte om tabellen skal synkronisere. Man angiver <strong>og</strong>så her<br />
hvilken form, der skal være tilknyttet til tabellen, <strong>og</strong> om folk skal have mulighed for at lave queries<br />
på tabellen. Endvidere kan man lave indexes <strong>og</strong> lister, som brugerne kan anvende i deres oversigt,<br />
så de ikke behøver at lave queries hver gang.<br />
Side 11
Under Add Table Field, kan man angive feltnavn, type, om det er en fremmednøgle <strong>og</strong> man kan<br />
sætte en default formular, som indsætter en default værdi i et felt, hvis brugeren ikke selv<br />
indtaster n<strong>og</strong>et.<br />
Side 12
Pivotal Forms<br />
De skærmbilleder brugerne ser i Pivotal, kaldes forms.<br />
Forms opbygges af grupper. Inden i grupperne kan man have primære <strong>og</strong> sekundære felter.<br />
Primære felter stammer fra den tabel, man laver formen over. Sekundære felter kommer fra<br />
andre tabeller <strong>og</strong> kræver en fremmednøgle relation.<br />
Man placerer henholdsvis felterne <strong>og</strong> grupperne ved at angive, hvordan de skal ligge i forhold til<br />
henholdsvis andre felter / grupper rundt om.<br />
Sekundære felter bliver altid vist som rækker, de kan ikke vises som enkelte felter.<br />
Side 13
Pivotal agenter<br />
Den eneste måde man kan kode i Pivotal er ved at arbejde med såkaldte ”agenter”. Agenter er<br />
opbygget af komponenter, <strong>og</strong> flowet vises med pile.<br />
Hver komponent kan forskellige ting <strong>og</strong> virker på forskellige niveauer. ”Form komponenten” kan<br />
kun arbejde med felter fra én form. En ”record komponent” kan arbejde med al data fra en tabel,<br />
<strong>og</strong> kan derudover baseres på queries.<br />
Generelt om komponenter. Hvis en komponent gennemløbes uden fejl, fortsætter agenten videre<br />
fra komponentens venstre ben. Hvis der er fejl, går den ud fra komponentens højre ben. Hvis et<br />
ben ikke er forbundet videre, vil agenten stoppe.<br />
Hvis man bruger en test komponent, vil den gå ud ad venstre ben, hvis kriteriet var sandt. Hvis<br />
kriteriet er falsk, går den videre ud ad højre ben.<br />
For en gennemgang af de mest brugte komponenter se bilag.<br />
Løkker i agenter<br />
Løkker er nødvendige, når man vil kalde den samme komponent flere gange. Det kan være nyttigt,<br />
hvis man for eksempel vil opdatere 1000 records i en agent. Man laver en record komponent, der<br />
udfører de ønskede opdateringer. Den kan d<strong>og</strong> kun arbejde på én record af gangen, <strong>og</strong> når den er<br />
færdig, går agenten videre til en anden komponent. Derfor bliver man nødt til at kalde den samme<br />
komponent flere gange, indtil der ikke er flere records, der skal opdateres. Konventionen i Pivotal<br />
er at lave en shortcut til record komponenten, som det kan ses i figuren ovenover.<br />
Side 14
Herefter sætter man record komponenten til at finde de records, der skal opdateres. I shortcut<br />
komponenten laver man de ønskede opdateringer. Når record komponenten ikke finder flere<br />
records, brydes løkken, <strong>og</strong> agenten kører videre ad record komponentens højre ben.<br />
Silverpop<br />
Man l<strong>og</strong>ger ind på Silverpop via deres hjemmeside <strong>og</strong> kan derfra oprette lister / tabeller <strong>og</strong><br />
relatere dem til hinanden. Der er <strong>og</strong>så der, man kan lave rapporter, queries samt lave <strong>og</strong> udsende<br />
e-mail kampagner.<br />
En lidt speciel ting i Silverpop er, at man ikke kan forbinde relational tables med hinanden. De kan<br />
kun forbindes til en liste, <strong>og</strong> ikke til andre tabeller. Listen kan forbindes med alle de tabeller, man<br />
har lyst til.<br />
Man skal være opmærksom på, at email adressen er et påkrævet felt i Silverpop, som skal være<br />
unikt for hver record, for at man kan lave email kampagner.<br />
Side 15
Man kan desuden <strong>og</strong>så oploade til Silverpop via FTP. Der skal man oploade 2 filer. En CSV fil med<br />
data <strong>og</strong> en XML fil med instruktioner. I XML filen kan man specificere om man vil oploade data <strong>og</strong><br />
man kan <strong>og</strong>så samtidig oprette tabeller, <strong>og</strong> linke tabeller. Man har <strong>og</strong>så mulighed for at angive at<br />
man vil slette de records hos Silverpop, som matcher med dem, man oploader i en CSV fil.<br />
Data der skal sendes fra Pivotal til Silverpop<br />
Marketing afdelingen vil have at følgende felter bliver synkroniseret fra Pivotal til Silverpop.<br />
Company ID, Company Name, Contact ID, First Name, Last Name, Job Title, Country, E-mail,<br />
FOSS RSM, First Name, Last Name, Job Title, Company, E-mail, Phone, fra contact tabellen.<br />
RSM står for Regional Sales Manager. Det er den salgsmanager, der er ansvarlig for det land /<br />
område, som kunden bor i.<br />
Desuden er man interesseret i interest areas for kunden. Det er de områder, som kunden har<br />
ønsket at modtage informationer om. For contacts er det <strong>og</strong>så interessant at vide hvilke<br />
produkter, kunden har købt. Derfor skal products <strong>og</strong> den product group de tilhører <strong>og</strong>så<br />
synkroniseres til Silverpop.<br />
Man er interesseret i de tilsvarende felter for leads, <strong>og</strong>så i deres interest areas.<br />
Data fra Silverpop til Pivotal<br />
Informationen fra Silverpop skal tilføjes til tabellen Rn_appointments <strong>og</strong> vises på skærmbilledet<br />
Marketing Activity.<br />
Fra marketing er man interesseret i at få følgende informationer overført fra Silverpop til Pivotal.<br />
Navnet <strong>og</strong> tidspunktet for marketing kampagnen. Om kunden åbnede e-mailen <strong>og</strong> klikkede på<br />
n<strong>og</strong>le links. Om e-mailen blev Soft bounced eller Hard bounced. Hvis e-mailen blev åbnet eller<br />
bounced skal datoen for den hændelse <strong>og</strong>så gemmes.<br />
En e-mail er Hard bounced, når det er en ugyldig e-mail adresse, man har sendt til. Soft bounced<br />
kan betyde flere ting. Det kan f.eks. være at man har sendt til en overfyldt e-mail, som derfor ikke<br />
kan modtage flere e-mails.<br />
Side 16
Design<br />
Tabel opbygning i Silverpop.<br />
Datastrukturen i Silverpop bygges op på følgende måde. En hovedliste <strong>og</strong> 3 tabeller.<br />
Listen indeholder alle oplysninger om den enkelte kunde. Det kan være både en contact eller et<br />
lead. Til listen er tilknyttet 3 tabeller. Products indeholder alle de apparater, som den pågældende<br />
kunde har købt. Interest area er de områder, kunden har udtrykt interesse for. Preferred Com<br />
Methods indeholder information om de kontaktformer, kunden ønsker. Det kan være at kunden<br />
gerne vil have nyheder på e-mail <strong>og</strong> <strong>og</strong>så gerne vil have In Focus magasinet tilsendt i papir form.<br />
En kunde kan have flere products, interest areas <strong>og</strong> preferred com methods.<br />
Nye tabeller i Pivotal<br />
For at kunne lave synkronisering indenfor <strong>pivotal</strong>, er der brug for 3 nye tabeller i Pivotal.<br />
Silverpop in Indeholder alle data, der kommer fra Silverpop.<br />
Silverpop out Indeholder data, der skal til Silverpop.<br />
Silverpop temp Indeholder contact <strong>og</strong> lead id for alle de records, der skal synkroniseres fra<br />
Pivotal til Silverpop.<br />
Side 17
Synkronisering af data fra Pivotal til Silverpop<br />
Første skridt er opsamling af data fra Pivotal. Det vil sige alle contacts <strong>og</strong> leads, der lige er blevet<br />
oprettet i Pivotal, <strong>og</strong> derfor ikke er i Silverpop endnu. Derudover alle de contacts <strong>og</strong> leads, der er<br />
blevet ændret siden sidste synkronisering.<br />
Disse records skal markeres eller gemmes i en særskilt tabel. Dermed ved man hvilke records, der<br />
skal sendes til Silverpop ved den næste synkronisering. Fra marketings side vil man gerne have at<br />
data bliver synkroniseret en gang i døgnet. På den måde forstyrrer man færrest brugere af Pivotal.<br />
Der går lidt tid <strong>mellem</strong> hver marketing kampagne, så er det rigeligt med een synkronisering i<br />
døgnet.<br />
Der skal <strong>og</strong>så holdes styr på den enkelte record. Er der tale om en kunde, der har afmeldt e-mails<br />
fra FOSS, skal kundens record slettes fra hovedlisten i Silverpop.<br />
Fra marketing vil man gerne have muligheden for at kunne genopskrive kunder til marketing emails,<br />
hvis kunden ønsker det. Man skal samtidig kunne se i <strong>pivotal</strong> om en contact eller lead har<br />
ønsket at afmelde <strong>og</strong> genopskrive til marketing e-mails.<br />
Man kunne vælge at lade Silverpop styre unsubscribes. Hvis man t<strong>og</strong> den løsning, ville der være et<br />
link i hver e-mail, hvor kunden kunne ind på en Silverpop hjemmeside <strong>og</strong> afmelde sig der. Fra<br />
Silverpops side har man valgt, at hvis en kunde unsubscriber via deres system, kommer kundens email<br />
adresse ind på en afmeldt liste, <strong>og</strong> derefter kan man aldrig sende e-mails til ham igen. Derfor<br />
ønsker marketing, at afmeldinger bliver håndteret i Pivotal.<br />
Dette betyder, at alle de kunder, som er i Silverpop, ønsker at modtage e-mails. Hvis en kunde<br />
afmelder sig, skal kundens oplysninger slettes fra Silverpop.<br />
Når der er styr på alle de records, der skal oploades, skal dataene læses fra Pivotal <strong>og</strong> sendes til<br />
Silverpop. Det er umuligt via agenter i Pivotal. Der skal et pr<strong>og</strong>ram udefra for at gøre det. Valget<br />
faldt på C#. Det er et naturligt valg, fordi det er det spr<strong>og</strong>, man bruger i FOSS i andre<br />
sammenhæng. Det bruges til hjemmesiden <strong>og</strong> er blevet brugt til andre <strong>integration</strong>sprojekter.<br />
Til sidst skal dataen fra Pivotal lægges i en CSV fil <strong>og</strong> sammen med en XML fil oploades til<br />
Silverpop.<br />
Side 18
Synkronisering fra Silverpop til Pivotal<br />
For at få data fra Silverpop, skal man sende en XML fil med en export request. Silverpop genererer<br />
derefter en CSV fil. Den indeholder alle de records, som er blevet bounced, åbnet eller andet,<br />
siden sidste gang man lavede en export fra Silverpop.<br />
Filen bliver pakket med zip, <strong>og</strong> lagt på Silverpops server under ftp mappen.<br />
Den skal downloades <strong>og</strong> pakkes ud.<br />
Dataene skal derefter lægges ind i Silverpop in tabellen i Pivotal. Alt dette skal ske i C#<br />
pr<strong>og</strong>rammet.<br />
I Pivotal skal der laves en agent, der læser dataene fra Silverpop in tabellen <strong>og</strong> gemmer dataene i<br />
tabellen Rn_appointments. Dermed kan data fra Rn_appointments blive synkroniseret igennem<br />
Pivotal til resten af verden.<br />
Data fra Rn_appointments vises på skærmbilledet Marketing Activity.<br />
Unsubscribe<br />
Der skal laves en agent, der kan afmelde contacts <strong>og</strong> leads. Den gælder i de situationer, hvor en<br />
kunde ikke ønsker at modtage n<strong>og</strong>en information fra FOSS overhovedet.<br />
Den skal køres, når en superbruger sætter hak i en tjekboks. Den skal vises på skærmbillederne for<br />
contact, lead <strong>og</strong> prospects.<br />
Når den bliver kørt, skal den i kommentarerne skrive navnet på den superbruger, der har afmeldt<br />
kunden samt tidspunkt for afmeldelsen.<br />
Den skal fjerne alle kundens Preferred com methods <strong>og</strong> sørge for, at kunden bliver fjernet fra<br />
Silverpop.<br />
Det skal <strong>og</strong>så være muligt at genopskrive kunden igen via denne agent.<br />
Side 19
Implementering<br />
Unsubscribe agent<br />
Agenten bliver kørt enten fra contact, lead eller prospects formen.<br />
Man er ikke interesseret i, at n<strong>og</strong>en kommer til at afmelde en kunde ved en fejl. Derfor har man<br />
valgt, at det kun er superbrugere, der har lov til at afmelde kunder.<br />
Det første agenten undersøger, er om brugeren har de nødvendige rettigheder, dvs. er<br />
superbruger.<br />
Hvis det er tilfældet, går agenten videre <strong>og</strong> undersøger, om kunden er blevet unsubscribed før.<br />
Det vil sige, om unsubscribed boolean er sat til sandt (check unsubscribe flag). Hvis den er sandt,<br />
bliver brugeren bedt om at bekræfte, at kunden skal kunne modtage e-mails / magasiner igen.<br />
Flaget bliver fjernet fra unsubscribe, <strong>og</strong> brugerens navn <strong>og</strong> tidspunkt gemmes under noter på<br />
kundens record (unset unsubscribe). Derefter opdateres formen, så det igen er muligt at tilføje,<br />
om kunden ønsker at modtage skrivelser.<br />
Side 20
Skal kunden afmeldes (no ved check unsubscribe flag), kommer der en meddelelse til brugeren.<br />
Når den er blevet bekræftet, sættes unsubscribe flaget til sandt. Derefter løbes alle kundens<br />
preferred com methods igennem, arkiveres, <strong>og</strong> listen slettes.<br />
Når der arkiveres en preferred com methods record betyder det at der oprettes en archive<br />
preferred com methods record. Den indeholder kunde record Id, preferred com method type,<br />
tidspunkt <strong>og</strong> om det er en tilføjelse eller sletning. Det vil sige, at det bliver arkiveret, når en kunde<br />
ønsker at modtage nyheds- <strong>og</strong> reklame e-mails <strong>og</strong> når han frabeder sig det. Dermed er der en<br />
komplet historik. Hvis en kunde klager over at modtage e-mails, kan man slå op i tabellen <strong>og</strong> se om<br />
han har tilmeldt sig <strong>og</strong> evt. afmeldt sig igen.<br />
Hvis der har været n<strong>og</strong>le opdateringer af kundens stamdata, vil der være blevet oprettet n<strong>og</strong>le<br />
records i Silverpop temp. Disse bliver fjernet fra tabellen (remove other <strong>silverpop</strong> temp records).<br />
Der bliver lavet en ny record i Silverpop temp med kundens record id, e-mail adresse <strong>og</strong> deletion<br />
sat til sand. Dermed bliver kundens oplysninger slettet fra Silverpop efter næste synkronisering, <strong>og</strong><br />
kunden vil efterfølgende ikke modtage flere e-mails.<br />
Til sidst bliver formen opdateret. Det er derefter ikke muligt at sætte kunden til at modtage<br />
skrivelser.<br />
Side 21
Data fra Pivotal til Silverpop agent<br />
Agenten kører en gang i døgnet ved midnat.<br />
Agenten læser fra Silverpop temp tabellen efter en query, som tester om agent_handled er falsk.<br />
Derefter bliver der testet, om det er en deletion. Hvis det er tilfældet, bliver der oprettet en record<br />
i Silverpop out tabellen, med contact / lead Id, E-mail, company Id <strong>og</strong> deletion sat til sandt. Hvis<br />
det ikke er en deletion, bliver der testet, om det er et lead (Is this a lead). Det efterfølgende er ens<br />
for contacts <strong>og</strong> leads. Den eneste forskel er, at det drejer sig om forskellige tabeller.<br />
Først bruges Id’et fra Silverpop temp til at hente den originale record frem. Der undersøges om<br />
den record har en e-mail adresse. Det er kun interessant at synkroniserer kunder med e-mail<br />
adresser til Silverpop.<br />
Derefter oprettes en record i Silverpop out tabellen. I den record gemmes kunde <strong>og</strong> RSM<br />
oplysninger.<br />
Side 22
Bagefter bliver Set Business area kørt. Business area er det samme som interest area. Den finder<br />
alle kundens interest areas, <strong>og</strong> gemmer dem i Silverpop out recorden. Til sidst slettes den<br />
nuværende record i Silverpop temp, <strong>og</strong> agenten læser den næste record fra Silverpop temp.<br />
Hvis der på n<strong>og</strong>et tidspunkt opstår en fejl, vil der blive sendt en mail til en Pivotal pr<strong>og</strong>rammør.<br />
Silverpop temp recorden vil blive sat til agent_handled sandt, <strong>og</strong> agenten vil springe den record<br />
over <strong>og</strong> tage den næste. Når der ikke er flere records i Silverpop temp, går agenten i sleep mode.<br />
Den vågner op igen ved midnat, <strong>og</strong> ser efter flere records i Silverpop temp.<br />
Side 23
Data fra Silverpop til Pivotal agent<br />
Agenten kører altid, men er sat til at sove i 2 minutter, når der ikke er flere records i Silverpop in.<br />
Agenten læser fra Silverpop in tabellen efter en query. Den query frasorterer records, hvor<br />
agent_handled er sat til sandt.<br />
I test komponenten tester agenten, om det er en contact. Det er enten en contact eller et lead.<br />
Derefter opdateres den pågældende record, hvis e-mailen til kunden bounced.<br />
Bagefter laves en ny record i Marketing Activity. Den bliver udfyldt med navnet på marketing email<br />
kampagnen <strong>og</strong> tidspunkt.<br />
Hvis der er blevet klikket på n<strong>og</strong>le links, eller e-mailen er blevet soft eller hard bounced, bliver<br />
dette tilføjet under noten på recorden. Derudover bliver datoen for disse events <strong>og</strong>så gemt på<br />
recorden.<br />
Til sidst slettes Silverpop in recorden, <strong>og</strong> agenten starter forfra med den næste record fra tabellen.<br />
Side 24
Hvis der opstår fejl undervejs, bliver fejl recorden gemt med agent_handled sat til sandt, <strong>og</strong> der<br />
bliver sendt en mail til en Pivotal pr<strong>og</strong>rammør. Agenten kan derefter køre videre med en anden<br />
record.<br />
Data fra Pivotal til Silverpop C# kode<br />
ConsoleApplication1 / SilverpopIntegration<br />
Denne kode læser data fra Pivotal i tabellen Siverpop out, behandler det <strong>og</strong> sender det til<br />
Silverpop.<br />
Der er App.config, som indeholder praktiske oplysninger, så som server adresse, user name,<br />
passwords <strong>og</strong> e-mail adresser. Dermed er det nemmere at finde, når man skal opdatere n<strong>og</strong>le af<br />
disse oplysninger.<br />
Selve C# pr<strong>og</strong>rammet består af en række filer. De er delt op så hver fil, er en separat funktion.<br />
Pr<strong>og</strong>ram.cs Hovedpr<strong>og</strong>rammet, styrer processen <strong>og</strong> kalder<br />
funktioner fra de andre filer.<br />
L<strong>og</strong>.cs Bruges igennem det meste af pr<strong>og</strong>rammet til at l<strong>og</strong>ge<br />
events til en l<strong>og</strong> fil.<br />
ErrorMailGenerator.cs Sender en e-mail ud i tilfælde af fejl.<br />
CsvFileGenerator.cs Funktioner til at generere csv filer.<br />
ByteConvertor.cs Bruges til at generere <strong>og</strong> håndtere record ids til Pivotal.<br />
Bemærk! Jeg har ikke lavet denne fil.<br />
SilverpopAPI.cs Bruges til at l<strong>og</strong>ge på <strong>og</strong> ud af Silverpop.<br />
SilverpopFTP.cs Bruges til at sende <strong>og</strong> modtage data fra /til Silverpop<br />
TransactionalDatabase.cs Bruges til alle database kald.<br />
Pr<strong>og</strong>ram forløb:<br />
Først hentes de records ud fra Pivotal, som skal slettes i Silverpop. Deres Id bliver konverteret ved<br />
hjælp af ByteConvertor.cs. Det er på grund af at Ids i Pivotal, er i et meget specielt format. Der<br />
Side 25
findes ingen dokumentation om det <strong>og</strong> de kunne ikke forklare det. Derfor valgte jeg at bruge den<br />
fil, de plejer at bruge, ByteConvertor.cs. Jeg har derfor ikke selv lavet denne fil.<br />
Efter konverteringen bliver XML filen oploadet til Silverpop, som sletter de pågældende records.<br />
Derefter indlæses de records fra Pivotal, som skal opdateres i Silverpop. Der bliver genereret 4<br />
XML filer: MainList.xml, pref_comm.xml, products.xml <strong>og</strong> ba.xml.<br />
XML filerne indeholder kommandoer til Silverpop. Hvor der bliver specificeret at de skal<br />
add_and_update, dvs. opdatere record, hvis den findes i forvejen ellers lav en ny. Derudover<br />
indeholder de navnet <strong>og</strong> id på den tabel eller liste, der skal opdateres i Silverpop. Efter det bliver<br />
kolonnerne listet <strong>og</strong> deres placering i CSV filen angivet.<br />
Til sidst bliver XML <strong>og</strong> CSV filerne oploadet til Silverpop. Hver opload bliver kørt i hver sin tråd.<br />
Dermed skal de ikke vente på at den foregående opload er blevet færdigbehandlet i Silverpop.<br />
Data fra Silverpop til Pivotal C# kode<br />
Return Silverpop Data<br />
Denne kode henter data fra Silverpop, behandler det <strong>og</strong> sender det til Pivotal til tabellen Silverpop<br />
in.<br />
Der er ligesom i den foregående kode en App.config fil, som indeholder praktiske oplysninger.<br />
C# pr<strong>og</strong>rammet er <strong>og</strong>så bygget op på samme måde som før.<br />
Pr<strong>og</strong>ram.cs Hovedpr<strong>og</strong>rammet, styrer processen <strong>og</strong> kalder<br />
funktioner fra de andre filer.<br />
L<strong>og</strong>.cs Bruges igennem det meste af pr<strong>og</strong>rammet til at l<strong>og</strong>ge<br />
events til en l<strong>og</strong> fil.<br />
ErrorMailGenerator.cs Sender en e-mail ud i tilfælde af fejl.<br />
CsvParser.cs Funktioner til at læse en csv file <strong>og</strong> indlæse i et<br />
DataTable.<br />
ByteConvertor.cs Bruges til at generere <strong>og</strong> håndtere record ids til Pivotal.<br />
Bemærk! Jeg har ikke lavet denne fil.<br />
SilverpopAPI.cs Bruges til at l<strong>og</strong>ge på <strong>og</strong> ud af Silverpop.<br />
SilverpopFTP.cs Bruges til at sende <strong>og</strong> modtage data fra /til Silverpop<br />
Side 26
TransactionalDatabase.cs Bruges til alle database kald.<br />
Pr<strong>og</strong>ram forløb:<br />
Først sendes en export request til Silverpop. Silverpop genererer en zip fil, der indeholder en CSV<br />
fil, med data. Filen downloades via FTP.<br />
CSV filen pakkes ud <strong>og</strong> zip filen slettes, for at begrænse mængden af filer på serveren.<br />
Herefter er det meningen at CSV filen skal indlæses i pr<strong>og</strong>rammet <strong>og</strong> bagefter skrives til Silverpop<br />
in tabellen i Pivotal.<br />
Dette virker på nuværende tidspunkt ikke. Der er en fejl, når CSV filen bliver indlæst til et<br />
DataTable. Den returnerer et tomt DataTable <strong>og</strong> der er derfor intet at indlæse til Pivotal.<br />
Side 27
Test<br />
Jeg har lavet manuel testning af de forskellige pr<strong>og</strong>ramdele. Jeg har testet ved at løbende at lave<br />
udskrifter til skærmen både i Pivotal <strong>og</strong> i C# koden. Det er ikke muligt at teste på andre måder i<br />
Pivotal. Derudover har jeg kørt pr<strong>og</strong>rammet <strong>og</strong> kontrolleret at data blev flyttet rigtigt fra tabel til<br />
tabel <strong>og</strong> fra Pivotal til Silverpop.<br />
Alle agenter er testet <strong>og</strong> virker. De stedet hvor jeg ikke har haft data fra C# koden, har jeg manuelt<br />
lagt data ind i tabellerne.<br />
Jeg har haft brugere fra marketing afdelingen til at teste at dataene blev synkroniseret fra Pivotal<br />
til Silverpop.<br />
De kunne bekræfte at dataene kom frem til Silverpop, men derudover var de meget usikre på<br />
hvordan man fandt rundt i Pivotal <strong>og</strong> hvordan man brugte Silverpop.<br />
Som følge af testen ville de gerne have at Interest Areas <strong>og</strong>så kom hen i MainList i Silverpop.<br />
Dermed kunne de bedre overskue at teste i Silverpop.<br />
Jeg har derfor ændret koden så alle interest areas, <strong>og</strong>så ligger i MainList som booleans.<br />
Jeg har undervejs mens jeg lavede pr<strong>og</strong>rammerede kun arbejdet på test servere. I <strong>og</strong> med at<br />
marketing sagde god for synkroniseringsdelen fra Pivotal til Silverpop, er denne del blevet sat i<br />
produktion.<br />
Side 28
Konklusion<br />
Opfyldte krav i forhold til løsningen<br />
Synkronisering fra Pivotal til Silverpop. Denne del er lavet <strong>og</strong> testet, at det virker korrekt. Det er i<br />
produktion i dag.<br />
Synkronisering fra Silverpop til Pivotal. Denne del virker ikke. Fejlen begrænser sig d<strong>og</strong> til<br />
Csvparseren. Alle de andre komponenter i C# koden er blevet testet. Agenten, der skal håndterer<br />
dataene i Pivotal, er <strong>og</strong>så blevet testet <strong>og</strong> virker <strong>og</strong>så.<br />
Skærmbilleder <strong>og</strong> tabel opdateringer. Disse er implementeret.<br />
Unsubscribe <strong>og</strong> resubscribe funktion. Dette er <strong>og</strong>så implementeret. Det er <strong>og</strong>så testet at agenten<br />
får unsubscribet kunden, fjernet interest areas <strong>og</strong> at kunden bliver fjernet fra Silverpop efter<br />
næste synkronisering.<br />
Fleksibelt designet. Dette punkt vil jeg mene er opnået. Jeg har brugt få agenter, <strong>og</strong> disse er<br />
forsøgt at være overskuelige. C# koden er delt op i en række filer, med hver deres<br />
funktionsområde.<br />
Tilfredshed fra marketing afdelingen<br />
Marketing afdelingen er tilfreds med at data bliver synkroniseret til Silverpop. Dermed har de data<br />
klar <strong>og</strong> kan lave n<strong>og</strong>le testkampagner. At synkroniseringen ikke virker tilbage til Pivotal, er ikke et<br />
stort problem på nuværende tidspunkt. De har travlt med at rydde op i dataene i Pivotal. Der er en<br />
masse manuelt arbejde i at finde ud af, hvem man skal sende e-mails til, hvis flere kunder har den<br />
samme e-mail adresse.<br />
Benefit for forretningen<br />
Marketing afdelingen slipper for at manuelt skulle lave filer med 5-20000 records der skal manuelt<br />
importeres. Dermed spares der meget tid.<br />
Man har mulighed for at lave dynamiske e-mail kampagner. De rammer kunderne bedre med<br />
mere specifikt information til dem. Dermed burde det genererer mere salg.<br />
Fremtidig udvikling<br />
Synkroniseringen fra Silverpop til Pivotal skal gøres færdig.<br />
Der kommer givet mere, der skal laves om, når marketing begynder at bruge Silverpop <strong>og</strong> Pivotal.<br />
Jeg forventer at de både vil have mere data til <strong>og</strong> fra Silverpop. I Pivotal vil have mere data på<br />
contact <strong>og</strong> lead formene <strong>og</strong> en række queries. Dermed kan de begynde at måle effektiviteten af<br />
deres e-mail kampagner.<br />
Side 29
Bilag<br />
Pivotal Agenter<br />
Pivotal Agent Komponenter<br />
Navn Ikon Beskrivelse<br />
Test<br />
Wait<br />
Agent<br />
Import<br />
List<br />
Tester et udtryk <strong>og</strong> returnerer enten sandt eller false.<br />
Bruges til at sætte en agent til at vente i et stykke tid.<br />
Bruges til at indlejrer en agent i en anden agent.<br />
Komponenterne i den indlejrede agent kan bruge variablerne i<br />
hovedagenten. En indlejret agent bruges ofte til at skjule en del<br />
komponenter fra hovedagenten, <strong>og</strong> dermed skabe overblik.<br />
Bruges til at importerer data ind i Pivotal. Dataene skal ligge i en<br />
CSV fil.<br />
Lister bruges til at vise data i Pivotal, når queries returnerer<br />
mere end 1 record. Komponenten kan oprette, slette <strong>og</strong><br />
manipulere lister.<br />
Side 30
Form<br />
Record<br />
Send<br />
Variable<br />
Move<br />
Call<br />
Kan læse <strong>og</strong> skrive til felter på en Pivotal Form. Kan <strong>og</strong>så<br />
manipulere de enkelte felter, f.eks. lave n<strong>og</strong>le af dem read only.<br />
Kan læse <strong>og</strong> skrive til en record i databasen. Kan baseres på en<br />
query, så man kun får en delmængde af dataene. Derudover<br />
den <strong>og</strong>så manipulere felter på den enkelte record.<br />
Sender en e-mail ud. Man kan sætte modtager, cc, cb, titel <strong>og</strong><br />
besked i komponenten.<br />
Bruges som en almindelig variable, til at gemme en værdi<br />
midlertidig.<br />
Bruges til at flytte data, f.eks. til <strong>og</strong> fra en variable.<br />
Man kan køre andre agent med denne komponent. Agenterne<br />
kan ikke dele variable, men man kan overfører argumenter når<br />
man kalder call.<br />
Side 31
Prompt<br />
Note<br />
Return Silverpop Data C#<br />
App.config<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
Bruges til at skrive tekst <strong>og</strong> evt. variabler ud på skærmen til<br />
brugeren. Bruges <strong>og</strong>så når brugeren skal tage stilling til n<strong>og</strong>et.<br />
Kan skrive en besked ud på skærmen til brugeren.<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
Side 32
ByteConvertor.cs<br />
using System;<br />
using System.Collections.Generic;<br />
using System.Text;<br />
namespace FOSS.SilverpopIntegration<br />
{<br />
public static class ByteConvertor<br />
{<br />
/// <br />
/// Converts an decimal id into an bytearray id<br />
/// <br />
/// The decimal representation of the id<br />
/// The Bytearray representation of the id.<br />
public static byte[] ConvertToFossBytes(string id)<br />
{<br />
byte[] converted = new byte[8];<br />
string binstring = "";<br />
}<br />
if (id.Contains("-"))<br />
{<br />
string[] num = id.Split('-');<br />
binstring = DecimalToBase(Convert.ToInt32(num[0]), 2).PadLeft(24, '0');<br />
binstring += DecimalToBase(Convert.ToInt32(num[1]), 2).PadLeft(40, '0');<br />
}<br />
else<br />
{<br />
binstring = DecimalToBase(Convert.ToInt32(id), 2).PadLeft(64, '0');<br />
}<br />
for (int i = 0; i < converted.Length; i++)<br />
{<br />
converted[i] = (byte)BinToDecimal(binstring.Substring(i * 8, 8));<br />
}<br />
return converted;<br />
/// <br />
/// Converts a bytearray representation of an id into a decimal representation of the id<br />
/// <br />
/// bytearray representation of the id<br />
/// the decimal representation of the id<br />
public static string ConvertFossBytes(byte[] workid)<br />
{<br />
string[] BinStringArr = new string[workid.Length];<br />
for (int i = 0; i < workid.Length; i++)<br />
{<br />
BinStringArr[i] = DecimalToBase(workid[i], 2).PadLeft(8, '0');<br />
}<br />
string Head = "";<br />
string Tail = "";<br />
Head = BinStringArr[0];<br />
Head += BinStringArr[1];<br />
Head += BinStringArr[2];<br />
Tail = BinStringArr[3];<br />
Tail += BinStringArr[4];<br />
Tail += BinStringArr[5];<br />
Tail += BinStringArr[6];<br />
Tail += BinStringArr[7];<br />
string HeadOut = BinToDecimal(Head).ToString();<br />
string TailOut = BinToDecimal(Tail).ToString();<br />
Side 33
}<br />
}<br />
}<br />
if (HeadOut == "0")<br />
return TailOut;<br />
else<br />
return HeadOut + "-" + TailOut;<br />
/// <br />
/// Converts a decimal representation of a number into the "base" representation of the number<br />
/// <br />
/// The decimal number<br />
/// The base that the the decimal should be converted into<br />
/// The converted number<br />
public static string DecimalToBase(int iDec, int numbase)<br />
{<br />
string strBin = "";<br />
int[] result = new int[32];<br />
int MaxBit = 32;<br />
for (; iDec > 0; iDec /= numbase)<br />
{<br />
int rem = iDec % numbase;<br />
result[--MaxBit] = rem;<br />
}<br />
for (int i = 0; i < result.Length; i++)<br />
{<br />
strBin += result.GetValue(i);<br />
}<br />
strBin = strBin.TrimStart(new char[] { '0' });<br />
return strBin;<br />
}<br />
/// <br />
/// Converts from binary to decimal<br />
/// <br />
/// binary representation of the number<br />
/// the decimal representation of the number <br />
public static int BinToDecimal(string sBase)<br />
{<br />
int dec = 0;<br />
int bit = 0;<br />
for (int i = sBase.Length - 1; i >= 0; i--)<br />
{<br />
if ("1" == sBase[i].ToString())<br />
{<br />
dec += (int)Math.Pow(2, bit);<br />
}<br />
bit++;<br />
}<br />
return dec;<br />
}<br />
Side 34
CsvParser.cs<br />
using System;<br />
using System.Collections;<br />
using System.Text;<br />
using System.Data;<br />
using System.IO;<br />
namespace FOSS.SilverpopIntegration<br />
{<br />
public static class CsvParser<br />
{<br />
public static DataTable Parse(string data, bool headers)<br />
{<br />
return Parse(new StringReader(data), headers);<br />
}<br />
public static DataTable Parse(string data)<br />
{<br />
return Parse(new StringReader(data));<br />
}<br />
public static DataTable Parse(TextReader stream)<br />
{<br />
return Parse(stream, false);<br />
}<br />
/// <br />
/// Parses a CSV file and returns the data in a DataTable.<br />
/// <br />
/// The Stream containing the CSV contents.<br />
/// True if the file contains column headers.<br />
/// Returns the CSV data in the form of a DataTable.<br />
public static DataTable Parse(TextReader stream, bool headers)<br />
{<br />
DataTable table = new DataTable();<br />
CsvStream csv = new CsvStream(stream);<br />
string[] row = csv.GetNextRow();<br />
if (row == null)<br />
return null;<br />
if (headers)<br />
{<br />
foreach (string header in row)<br />
{<br />
if (header != null && header.Length > 0 && !table.Columns.Contains(header))<br />
table.Columns.Add(header, typeof(string));<br />
else<br />
table.Columns.Add(GetNextColumnHeader(table), typeof(string));<br />
}<br />
row = csv.GetNextRow();<br />
}<br />
while (row != null)<br />
{<br />
while (row.Length > table.Columns.Count)<br />
table.Columns.Add(GetNextColumnHeader(table), typeof(string));<br />
table.Rows.Add(row);<br />
row = csv.GetNextRow();<br />
}<br />
return table;<br />
}<br />
private static string GetNextColumnHeader(DataTable table)<br />
{<br />
int c = 1;<br />
while (true)<br />
{<br />
string h = "Column" + c++;<br />
if (!table.Columns.Contains(h))<br />
return h;<br />
Side 35
}<br />
}<br />
private class CsvStream<br />
{<br />
private TextReader stream;<br />
public CsvStream(TextReader s)<br />
{<br />
stream = s;<br />
}<br />
public string[] GetNextRow()<br />
{<br />
ArrayList row = new ArrayList();<br />
while (true)<br />
{<br />
string item = GetNextItem();<br />
if (item == null)<br />
return row.Count == 0 ? null : (string[])row.ToArray(typeof(string));<br />
row.Add(item);<br />
}<br />
}<br />
private bool EOS = false;<br />
private bool EOL = false;<br />
private string GetNextItem()<br />
{<br />
if (EOL)<br />
{<br />
// previous item was last in line, start new line<br />
EOL = false;<br />
return null;<br />
}<br />
bool quoted = false;<br />
bool predata = true;<br />
bool postdata = false;<br />
StringBuilder item = new StringBuilder();<br />
while (true)<br />
{<br />
char c = GetNextChar(true);<br />
if (EOS)<br />
return item.Length > 0 ? item.ToString() : null;<br />
if ((postdata || !quoted) && c == ',')<br />
// end of item, return<br />
return item.ToString();<br />
if ((predata || postdata || !quoted) && (c == '\x0A' || c == '\x0D'))<br />
{<br />
EOL = true;<br />
if (c == '\x0D' && GetNextChar(false) == '\x0A')<br />
GetNextChar(true);<br />
return item.ToString();<br />
}<br />
if (predata && c == ' ')<br />
continue;<br />
if (predata && c == '"')<br />
{<br />
quoted = true;<br />
predata = false;<br />
continue;<br />
}<br />
Side 36
}<br />
}<br />
}<br />
}<br />
if (predata)<br />
{<br />
predata = false;<br />
item.Append(c);<br />
continue;<br />
}<br />
if (c == '"' && quoted)<br />
{<br />
if (GetNextChar(false) == '"')<br />
item.Append(GetNextChar(true));<br />
else<br />
postdata = true;<br />
continue;<br />
}<br />
}<br />
item.Append(c);<br />
private char[] buffer = new char[4096];<br />
private int pos = 0;<br />
private int length = 0;<br />
private char GetNextChar(bool eat)<br />
{<br />
if (pos >= length)<br />
{<br />
length = stream.ReadBlock(buffer, 0, buffer.Length);<br />
if (length == 0)<br />
{<br />
EOS = true;<br />
return '\0';<br />
}<br />
pos = 0;<br />
}<br />
if (eat)<br />
return buffer[pos++];<br />
else<br />
return buffer[pos];<br />
}<br />
Side 37
ErrorMailGenerator.cs<br />
using System;<br />
using System.Collections.Generic;<br />
using System.Text;<br />
using System.Net;<br />
using System.Net.Mail;<br />
namespace FOSS.SilverpopIntegration<br />
{<br />
public static class ErrorMailGenerator<br />
{<br />
private static string recipient = System.Configuration.ConfigurationManager.AppSettings["ErrorMailRecipient"].ToString();<br />
}<br />
}<br />
public static string Recipient<br />
{<br />
get<br />
{<br />
return (recipient);<br />
}<br />
}<br />
public static void SendErrorMail(string body)<br />
{<br />
try<br />
{<br />
MagicalL<strong>og</strong>File.MakeL<strong>og</strong>Entry("Sending Error Mail to " + recipient);<br />
}<br />
//create the mail client<br />
SmtpClient sMail = new SmtpClient("mail.foss.dk");<br />
sMail.DeliveryMethod = SmtpDeliveryMethod.Network;<br />
//send the specified message<br />
sMail.Send("<strong>silverpop</strong>@foss.dk", recipient, "Error Generated by Silverpop Integration!", body);<br />
}<br />
catch(Exception ex)<br />
{<br />
//l<strong>og</strong> the error<br />
MagicalL<strong>og</strong>File.MakeL<strong>og</strong>Entry("Sending the Email FAILED - that's a bad thing, a very very bad thing");<br />
MagicalL<strong>og</strong>File.MakeL<strong>og</strong>Entry(ex.Message);<br />
}<br />
Side 38
L<strong>og</strong>.cs<br />
using System;<br />
using System.Collections.Generic;<br />
using System.Text;<br />
using System.IO;<br />
using System.Threading;<br />
namespace FOSS<br />
{<br />
public class L<strong>og</strong>Lock<br />
{<br />
private int locked;<br />
}<br />
public Boolean status<br />
{<br />
get<br />
{<br />
if (locked == 1) return true;<br />
else return false;<br />
}<br />
}<br />
public void LockL<strong>og</strong>()<br />
{<br />
while (Interlocked.CompareExchange(ref locked, 1, 0)==1)<br />
{<br />
Thread.Sleep(0);<br />
}<br />
}<br />
public void UnlockL<strong>og</strong>()<br />
{<br />
Interlocked.Exchange(ref locked, 0);<br />
}<br />
public static class MagicalL<strong>og</strong>File<br />
{<br />
private static string default_ = System.Configuration.ConfigurationManager.AppSettings["L<strong>og</strong>gingDefault"].ToString();<br />
private static int MaxFileSize = Convert.ToInt32(System.Configuration.ConfigurationManager.AppSettings["MaxFileSize"].ToString());<br />
private static L<strong>og</strong>Lock locked = new L<strong>og</strong>Lock();<br />
public static void MakeL<strong>og</strong>Entry(string msg)<br />
{<br />
MakeL<strong>og</strong>Entry(msg, default_);<br />
}<br />
public static void MakeL<strong>og</strong>Entry(string msg, string type)<br />
{<br />
try<br />
{<br />
locked.LockL<strong>og</strong>();<br />
if (type == "file")<br />
{<br />
L<strong>og</strong>MessageToFile(msg);<br />
CheckFileSize();<br />
}<br />
else<br />
{<br />
}<br />
}<br />
finally<br />
{<br />
locked.UnlockL<strong>og</strong>();<br />
}<br />
}<br />
private static string GetTempPath()<br />
Side 39
}<br />
}<br />
{<br />
}<br />
string path = System.Environment.CurrentDirectory;//System.Environment.GetEnvironmentVariable("TEMP");<br />
if (!path.EndsWith("\\")) path += "\\";<br />
return path;<br />
private static void L<strong>og</strong>MessageToFile(string msg)<br />
{<br />
System.IO.StreamWriter sw = System.IO.File.AppendText(<br />
GetTempPath() + "L<strong>og</strong>.txt");<br />
try<br />
{<br />
string l<strong>og</strong>Line = System.String.Format(<br />
"{0:G}: {1}.", System.DateTime.Now, msg);<br />
sw.WriteLine(l<strong>og</strong>Line);<br />
}<br />
finally<br />
{<br />
sw.Close();<br />
}<br />
}<br />
private static void CheckFileSize()<br />
{<br />
FileInfo fi = new FileInfo(GetTempPath() + "L<strong>og</strong>.txt");<br />
}<br />
if (fi.Length > MaxFileSize)<br />
{<br />
File.Move(GetTempPath() + "L<strong>og</strong>.txt", GetTempPath() + "L<strong>og</strong> - " + DateTime.Now.ToFileTimeUtc() + ".txt");<br />
File.Delete(GetTempPath() + "L<strong>og</strong>.txt");<br />
}<br />
Side 40
SilverpopAPI.cs<br />
using System;<br />
using System.Collections.Generic;<br />
using System.Text;<br />
using System.Xml;<br />
using System.Configuration;<br />
using System.Web;<br />
using System.Net;<br />
using System.IO;<br />
using System.Threading;<br />
namespace FOSS.SilverpopIntegration<br />
{<br />
public sealed class SilverpopAPI<br />
{<br />
private string sessionid;<br />
private string sessionencoding;<br />
private XmlDocument request;<br />
private XmlDocument response;<br />
private string jobstatus;<br />
private string url;<br />
private string username;<br />
private string password;<br />
private string jobid;<br />
private static string MainListId = System.Configuration.ConfigurationManager.AppSettings["MainListId"].ToString();<br />
public SilverpopAPI()<br />
{<br />
url = System.Configuration.ConfigurationManager.AppSettings["ApiUrl"].ToString();<br />
username = System.Configuration.ConfigurationManager.AppSettings["Username"].ToString();<br />
password = System.Configuration.ConfigurationManager.AppSettings["Password"].ToString();<br />
}<br />
request = new XmlDocument();<br />
response = new XmlDocument();<br />
public bool ApiL<strong>og</strong>in()<br />
{<br />
if (sessionid == null)<br />
{<br />
//create xml document<br />
request = BuildSilverpopWrapper();<br />
XmlElement L<strong>og</strong>in = request.CreateElement("L<strong>og</strong>in");<br />
request.GetElementsByTagName("Body")[0].AppendChild(L<strong>og</strong>in);<br />
//add username and password from app.config<br />
XmlElement usernameXML = request.CreateElement("USERNAME");<br />
usernameXML.InnerText = username;<br />
L<strong>og</strong>in.AppendChild(usernameXML);<br />
XmlElement passwordXML = request.CreateElement("PASSWORD");<br />
passwordXML.InnerText = password;<br />
L<strong>og</strong>in.AppendChild(passwordXML);<br />
//l<strong>og</strong>ging in<br />
MagicalL<strong>og</strong>File.MakeL<strong>og</strong>Entry("L<strong>og</strong>ging Into Silverpop API...");<br />
SubmitRequest();<br />
if (response == null) return (false);<br />
//parse the response to check if l<strong>og</strong>ged in, and get the sessionid<br />
if (response.GetElementsByTagName("SUCCESS")[0].InnerText == "true")<br />
{<br />
sessionid = response.GetElementsByTagName("SESSIONID")[0].InnerText;<br />
sessionencoding = response.GetElementsByTagName("SESSION_ENCODING")[0].InnerText;<br />
Side 41
}<br />
MagicalL<strong>og</strong>File.MakeL<strong>og</strong>Entry("Successfully l<strong>og</strong>ged in with sessionid: " + sessionid);<br />
return (true);<br />
}<br />
else<br />
{<br />
//l<strong>og</strong> an error<br />
MagicalL<strong>og</strong>File.MakeL<strong>og</strong>Entry("Failed to l<strong>og</strong>in to API :: " + response.GetElementsByTagName("FaultString")[0].InnerText);<br />
}<br />
return (false);<br />
}<br />
else<br />
{<br />
MagicalL<strong>og</strong>File.MakeL<strong>og</strong>Entry("Already l<strong>og</strong>ged into Silverpop API");<br />
return (true);<br />
}<br />
public void ApiL<strong>og</strong>out()<br />
{<br />
if (sessionid == null)<br />
{<br />
//not l<strong>og</strong>ged in it seems<br />
MagicalL<strong>og</strong>File.MakeL<strong>og</strong>Entry("Already l<strong>og</strong>ged out of API");<br />
//ensure variables are reset<br />
sessionencoding = null;<br />
}<br />
else<br />
{<br />
//create l<strong>og</strong>out request<br />
request = BuildSilverpopWrapper();<br />
}<br />
}<br />
XmlElement l<strong>og</strong>out = request.CreateElement("L<strong>og</strong>out");<br />
request.GetElementsByTagName("Body")[0].AppendChild(l<strong>og</strong>out);<br />
//l<strong>og</strong>ging out<br />
MagicalL<strong>og</strong>File.MakeL<strong>og</strong>Entry("L<strong>og</strong>ging out of API");<br />
//submit request and get response<br />
SubmitRequest();<br />
if (response == null)<br />
{<br />
MagicalL<strong>og</strong>File.MakeL<strong>og</strong>Entry("Null response when attempting to l<strong>og</strong> out");<br />
}<br />
sessionencoding = null;<br />
sessionid = null;<br />
private XmlDocument BuildSilverpopWrapper()<br />
{<br />
XmlDocument xml = new XmlDocument();<br />
XmlDeclaration xmlDec = xml.CreateXmlDeclaration("1.0", "utf-8", null);<br />
XmlElement envelope = xml.CreateElement("Envelope");<br />
xml.InsertBefore(xmlDec, xml.DocumentElement);<br />
xml.AppendChild(envelope);<br />
XmlElement body = xml.CreateElement("Body");<br />
envelope.AppendChild(body);<br />
return (xml);<br />
}<br />
Side 42
public void ListImport(string list)<br />
{<br />
if (sessionid == null)<br />
{<br />
MagicalL<strong>og</strong>File.MakeL<strong>og</strong>Entry("Attempted to make an API Request without being l<strong>og</strong>ged in!");<br />
}<br />
else<br />
{<br />
//build our request<br />
request = BuildSilverpopWrapper();<br />
XmlElement ImportList_ = request.CreateElement("ImportList");<br />
request.GetElementsByTagName("Body")[0].AppendChild(ImportList_);<br />
XmlElement MAP_FILE = request.CreateElement("MAP_FILE");<br />
MAP_FILE.InnerText = list + ".xml";<br />
ImportList_.AppendChild(MAP_FILE);<br />
XmlElement SOURCE_FILE = request.CreateElement("SOURCE_FILE");<br />
SOURCE_FILE.InnerText = list + ".csv";<br />
ImportList_.AppendChild(SOURCE_FILE);<br />
//l<strong>og</strong> the request<br />
MagicalL<strong>og</strong>File.MakeL<strong>og</strong>Entry("Sending ListImport Request to Silverpop API for list: " + list);<br />
//make the request and get response<br />
SubmitRequest();<br />
//if the request was not successful record an error<br />
if (response.GetElementsByTagName("SUCCESS")[0].InnerText == "false")<br />
{<br />
MagicalL<strong>og</strong>File.MakeL<strong>og</strong>Entry("ERROR! ListImport request returned the following fault: " +<br />
response.GetElementsByTagName("FaultString")[0].InnerText);<br />
}<br />
else<br />
{<br />
//l<strong>og</strong> success<br />
MagicalL<strong>og</strong>File.MakeL<strong>og</strong>Entry(response.FirstChild.InnerText.ToString());<br />
jobid = response.GetElementsByTagName("JOB_ID")[0].InnerText;<br />
}<br />
}<br />
}<br />
public void TableImport(string list)<br />
{<br />
if (sessionid == null)<br />
{<br />
MagicalL<strong>og</strong>File.MakeL<strong>og</strong>Entry("Attempted to make an API Request without being l<strong>og</strong>ged in!");<br />
}<br />
else<br />
{<br />
//build our request<br />
request = BuildSilverpopWrapper();<br />
XmlElement ImportTable_ = request.CreateElement("ImportTable");<br />
request.GetElementsByTagName("Body")[0].AppendChild(ImportTable_);<br />
XmlElement MAP_FILE = request.CreateElement("MAP_FILE");<br />
MAP_FILE.InnerText = list + ".xml";<br />
ImportTable_.AppendChild(MAP_FILE);<br />
XmlElement SOURCE_FILE = request.CreateElement("SOURCE_FILE");<br />
SOURCE_FILE.InnerText = list + ".csv";<br />
ImportTable_.AppendChild(SOURCE_FILE);<br />
//l<strong>og</strong> the request<br />
MagicalL<strong>og</strong>File.MakeL<strong>og</strong>Entry("Sending TableImport Request to Silverpop API for list: " + list);<br />
Side 43
make the request and get response<br />
SubmitRequest();<br />
//if the request was not successful record an error<br />
if (response.GetElementsByTagName("SUCCESS")[0].InnerText == "false")<br />
{<br />
MagicalL<strong>og</strong>File.MakeL<strong>og</strong>Entry("ERROR! TableImport request returned the following fault: " +<br />
response.GetElementsByTagName("FaultString")[0].InnerText);<br />
}<br />
else<br />
{<br />
//l<strong>og</strong> success<br />
MagicalL<strong>og</strong>File.MakeL<strong>og</strong>Entry(response.FirstChild.InnerText.ToString());<br />
jobid = response.GetElementsByTagName("JOB_ID")[0].InnerText;<br />
}<br />
}<br />
}<br />
public void TrackingMetricExport()<br />
{<br />
//build our request<br />
request = BuildSilverpopWrapper();<br />
XmlElement TME = request.CreateElement("TrackingMetricExport");<br />
request.GetElementsByTagName("Body")[0].AppendChild(TME);<br />
XmlElement LIST_ID = request.CreateElement("LIST_ID");<br />
LIST_ID.InnerText = MainListId;<br />
TME.AppendChild(LIST_ID);<br />
XmlElement Move = request.CreateElement("MOVE_TO_FTP");<br />
TME.AppendChild(Move);<br />
XmlElement AAM = request.CreateElement("ALL_AGGREGATE_METRICS");<br />
TME.AppendChild(AAM);<br />
MagicalL<strong>og</strong>File.MakeL<strong>og</strong>Entry("Sending Tracking Metric Export Request");<br />
//submit the request, and get the response<br />
SubmitRequest();<br />
//if the request was not successful record an error<br />
if (response.GetElementsByTagName("SUCCESS")[0].InnerText == "false")<br />
{<br />
MagicalL<strong>og</strong>File.MakeL<strong>og</strong>Entry("ERROR! TrackingMetricExport request returned the following fault: " +<br />
response.GetElementsByTagName("FaultString")[0].InnerText);<br />
}<br />
else<br />
{<br />
//l<strong>og</strong> success<br />
MagicalL<strong>og</strong>File.MakeL<strong>og</strong>Entry(response.FirstChild.InnerText.ToString());<br />
}<br />
}<br />
public string RawRecipientDataExport()<br />
{<br />
//build our request<br />
request = BuildSilverpopWrapper();<br />
XmlElement RRD = request.CreateElement("RawRecipientDataExport");<br />
request.GetElementsByTagName("Body")[0].AppendChild(RRD);<br />
XmlElement LIST_ID = request.CreateElement("LIST_ID");<br />
LIST_ID.InnerText = MainListId;<br />
RRD.AppendChild(LIST_ID);<br />
XmlElement Move = request.CreateElement("MOVE_TO_FTP");<br />
Side 44
RRD.AppendChild(Move);<br />
XmlElement AET = request.CreateElement("ALL_EVENT_TYPES");<br />
RRD.AppendChild(AET);<br />
XmlElement Columns = request.CreateElement("COLUMNS");<br />
RRD.AppendChild(Columns);<br />
XmlElement Column1 = request.CreateElement("COLUMN");<br />
Columns.AppendChild(Column1);<br />
XmlElement ConId = request.CreateElement("NAME");<br />
ConId.InnerText = "Contact ID";<br />
Column1.AppendChild(ConId);<br />
MagicalL<strong>og</strong>File.MakeL<strong>og</strong>Entry("Sending Raw Recipient Data Export Request");<br />
//submit the request, and get the response<br />
SubmitRequest();<br />
//if the request was not successful record an error<br />
if (response.GetElementsByTagName("SUCCESS")[0].InnerText == "false")<br />
{<br />
MagicalL<strong>og</strong>File.MakeL<strong>og</strong>Entry("ERROR! RawRecipientDataExport request returned the following fault: " +<br />
response.GetElementsByTagName("FaultString")[0].InnerText);<br />
return (null);<br />
}<br />
else<br />
{<br />
//l<strong>og</strong> success<br />
MagicalL<strong>og</strong>File.MakeL<strong>og</strong>Entry(response.FirstChild.InnerText.ToString());<br />
}<br />
}<br />
string filename = response.GetElementsByTagName("FILE_PATH")[0].InnerText;<br />
//get job id, and wait for it to complete<br />
WaitOnJobStatus(response.GetElementsByTagName("JOB_ID")[0].InnerText);<br />
return (filename);<br />
private void SubmitRequest()<br />
{<br />
//build a webrequest<br />
WebRequest wrequest = WebRequest.Create(url + sessionencoding) as HttpWebRequest;<br />
//set request properties<br />
wrequest.ContentType = "text/xml;charset=UTF-8";<br />
wrequest.Method = "POST";<br />
StringWriter sw = new StringWriter();<br />
XmlTextWriter xw = new XmlTextWriter(sw);<br />
//save the xml request to a text writer<br />
request.WriteTo(xw);<br />
System.Text.ASCIIEncoding encoding = new System.Text.ASCIIEncoding();<br />
//convert to a byte array (the httputility thing requires a reference to system.web.dll)<br />
byte[] byteData = encoding.GetBytes(HttpUtility.UrlPathEncode(sw.ToString()));<br />
wrequest.ContentLength = byteData.Length;<br />
//add data to the request<br />
using (Stream requestStream = wrequest.GetRequestStream())<br />
{<br />
requestStream.Write(byteData, 0, byteData.Length);<br />
}<br />
//finish the request<br />
wrequest.GetRequestStream().Close();<br />
Side 45
}<br />
//get the response<br />
HttpWebResponse wresponse = (HttpWebResponse)wrequest.GetResponse();<br />
//load the response into the response xmlDoc<br />
response = new XmlDocument();<br />
try<br />
{<br />
response.Load(wresponse.GetResponseStream());<br />
}<br />
catch (Exception)<br />
{<br />
//l<strong>og</strong> an error<br />
MagicalL<strong>og</strong>File.MakeL<strong>og</strong>Entry("ERROR while making a request to the Silverpop API - Response could not be loaded!");<br />
}<br />
private void GetJobStatus()<br />
{<br />
if (sessionid == null)<br />
{<br />
MagicalL<strong>og</strong>File.MakeL<strong>og</strong>Entry("Can't get a job status");<br />
}<br />
else<br />
{<br />
//build our request<br />
request = BuildSilverpopWrapper();<br />
XmlElement GetStatus = request.CreateElement("GetJobStatus");<br />
request.GetElementsByTagName("Body")[0].AppendChild(GetStatus);<br />
XmlElement JOB_ID = request.CreateElement("JOB_ID");<br />
JOB_ID.InnerText = jobid;<br />
GetStatus.AppendChild(JOB_ID);<br />
//submit our request and get the response<br />
SubmitRequest();<br />
//check for a null response<br />
if (response == null)<br />
{<br />
//l<strong>og</strong> an error<br />
MagicalL<strong>og</strong>File.MakeL<strong>og</strong>Entry("Error getting Job Status for Job Id: " + jobid);<br />
}<br />
else if (response.GetElementsByTagName("SUCCESS")[0].InnerText == "FALSE") //check that we can get a status for that job<br />
{<br />
//l<strong>og</strong> an error<br />
MagicalL<strong>og</strong>File.MakeL<strong>og</strong>Entry("Could not get job status for job id: " + jobid + " Fault from Silverpop is: " +<br />
response.GetElementsByTagName("FaultString")[0].InnerText);<br />
}<br />
else<br />
{<br />
//set the job status<br />
jobstatus = response.GetElementsByTagName("JOB_STATUS")[0].InnerText;<br />
//make a l<strong>og</strong> of the status<br />
MagicalL<strong>og</strong>File.MakeL<strong>og</strong>Entry("Job status for jobid " + jobid + " is " + jobstatus);<br />
}<br />
}<br />
}<br />
public string TGetJobStatus(string id)<br />
{<br />
if (sessionid == null)<br />
{<br />
MagicalL<strong>og</strong>File.MakeL<strong>og</strong>Entry("Can't get a job status");<br />
return ("ERROR");<br />
}<br />
Side 46
else<br />
{<br />
request = null;<br />
//build our request<br />
request = BuildSilverpopWrapper();<br />
XmlElement GetStatus = request.CreateElement("GetJobStatus");<br />
XmlElement JOB_ID = request.CreateElement("JOB_ID");<br />
JOB_ID.InnerText = id;// (string)Thread.GetData(loc);<br />
GetStatus.AppendChild(request.ImportNode(JOB_ID, true));<br />
request.GetElementsByTagName("Body")[0].AppendChild(request.ImportNode(GetStatus,true));<br />
MagicalL<strong>og</strong>File.MakeL<strong>og</strong>Entry("Requesting Job Status for Job Id: " + id);<br />
//submit our request and get the response<br />
SubmitRequest();<br />
//check for a null response<br />
if (response == null)<br />
{<br />
//l<strong>og</strong> an error<br />
MagicalL<strong>og</strong>File.MakeL<strong>og</strong>Entry("Error getting Job Status for Job Id: " + id);<br />
}<br />
else if (response.GetElementsByTagName("SUCCESS")[0].InnerText == "FALSE") //check that we can get a status for that job<br />
{<br />
//l<strong>og</strong> an error<br />
MagicalL<strong>og</strong>File.MakeL<strong>og</strong>Entry("Could not get job status for job id: " + id + " Fault from Silverpop is: " +<br />
response.GetElementsByTagName("FaultString")[0].InnerText);<br />
}<br />
else<br />
{<br />
//make a l<strong>og</strong> of the status<br />
MagicalL<strong>og</strong>File.MakeL<strong>og</strong>Entry("Job status for jobid " + id + " is " + response.GetElementsByTagName("JOB_STATUS")[0].InnerText);<br />
//set the job status<br />
return(response.GetElementsByTagName("JOB_STATUS")[0].InnerText);<br />
}<br />
return ("ERROR");<br />
}<br />
}<br />
public void WaitOnJobStatus(string id)<br />
{<br />
bool keepgoing=true;<br />
string status;<br />
}<br />
while (keepgoing)<br />
{<br />
status = TGetJobStatus(id);<br />
if (status == "COMPLETE")<br />
{<br />
keepgoing=false;<br />
}<br />
if (status == "WAITING" || jobstatus=="RUNNING")<br />
{<br />
//wait 30 seconds before checking again<br />
Thread.Sleep(30000);<br />
}<br />
if (status == "ERROR")<br />
{<br />
//l<strong>og</strong> the error and end the loop<br />
MagicalL<strong>og</strong>File.MakeL<strong>og</strong>Entry(response.FirstChild.InnerText.ToString());<br />
keepgoing = false;<br />
}<br />
}<br />
public void DeleteRecipient(Recipient r)<br />
{<br />
Side 47
MagicalL<strong>og</strong>File.MakeL<strong>og</strong>Entry("Creating Delete Request for ID:" + r.Id);<br />
//build request<br />
request = BuildSilverpopWrapper();<br />
XmlElement RemoveRecipient = request.CreateElement("RemoveRecipient");<br />
request.GetElementsByTagName("Body")[0].AppendChild(RemoveRecipient);<br />
XmlElement LIST_ID = request.CreateElement("LIST_ID");<br />
LIST_ID.InnerText = MainListId;<br />
RemoveRecipient.AppendChild(request.ImportNode(LIST_ID,true));<br />
XmlElement EMAIL = request.CreateElement("EMAIL");<br />
EMAIL.InnerText = r.Email;<br />
RemoveRecipient.AppendChild(request.ImportNode(EMAIL,true));<br />
XmlElement COLUMN = request.CreateElement("COLUMN");<br />
RemoveRecipient.AppendChild(COLUMN);<br />
//columns in here must include the name and value for any unique identifiers in the list<br />
XmlElement NAME = request.CreateElement("NAME");<br />
NAME.InnerText = "NEK";<br />
COLUMN.AppendChild(NAME);<br />
XmlElement VALUE = request.CreateElement("VALUE");<br />
VALUE.InnerText = r.Id;<br />
COLUMN.AppendChild(VALUE);<br />
SubmitRequest();<br />
//if the request was not successful record an error<br />
if (response.GetElementsByTagName("SUCCESS")[0].InnerText == "false")<br />
{<br />
MagicalL<strong>og</strong>File.MakeL<strong>og</strong>Entry("ERROR! RemoveRecipient request returned the following fault: " +<br />
response.GetElementsByTagName("FaultString")[0].InnerText);<br />
}<br />
else<br />
{<br />
//l<strong>og</strong> success<br />
MagicalL<strong>og</strong>File.MakeL<strong>og</strong>Entry(response.FirstChild.InnerText.ToString());<br />
}<br />
}<br />
}<br />
public string JobId<br />
{<br />
get<br />
{<br />
return(jobid);<br />
}<br />
}<br />
public string JobStatus<br />
{<br />
get<br />
{<br />
return(jobstatus);<br />
}<br />
}<br />
public sealed class SilverpopAPIWatcher<br />
{<br />
private string jid;<br />
private SilverpopAPI api;<br />
public SilverpopAPIWatcher(string id, SilverpopAPI sapi)<br />
Side 48
}<br />
}<br />
}<br />
{<br />
}<br />
jid = id;<br />
api = sapi;<br />
public bool GetJobStatus()<br />
{<br />
bool keepgoing = true;<br />
string status;<br />
bool errors = false;<br />
}<br />
while (keepgoing)<br />
{<br />
status = api.TGetJobStatus(jid);<br />
if (status == "COMPLETE")<br />
{<br />
keepgoing = false;<br />
}<br />
if (status == "WAITING" || status == "RUNNING")<br />
{<br />
//wait 30 seconds before checking again<br />
Thread.Sleep(30000);<br />
}<br />
if (status == "ERROR" || status == "CANCELED")<br />
{<br />
//l<strong>og</strong> the error and end the loop<br />
MagicalL<strong>og</strong>File.MakeL<strong>og</strong>Entry("Error checking job status");<br />
keepgoing = false;<br />
errors = true;<br />
}<br />
}<br />
return (errors);<br />
public struct Recipient<br />
{<br />
private string id;<br />
private string email;<br />
public string Id<br />
{<br />
get<br />
{<br />
return id;<br />
}<br />
set<br />
{<br />
id = value;<br />
}<br />
}<br />
public string Email<br />
{<br />
get<br />
{<br />
return email;<br />
}<br />
set<br />
{<br />
email = value;<br />
}<br />
}<br />
Side 49
SilverpopFTP.cs<br />
using System;<br />
using System.Collections.Generic;<br />
using System.Text;<br />
using System.Web;<br />
using System.Net;<br />
using System.IO;<br />
namespace FOSS.SilverpopIntegration<br />
{<br />
public sealed class SilverpopFTP<br />
{<br />
private FtpWebResponse response;<br />
private FtpWebRequest request;<br />
private string username;<br />
private string ftpurl;<br />
private string ftppassword;<br />
public SilverpopFTP()<br />
{<br />
username = System.Configuration.ConfigurationManager.AppSettings["FtpUsername"].ToString();<br />
ftpurl = System.Configuration.ConfigurationManager.AppSettings["FtpUrl"].ToString();<br />
ftppassword = System.Configuration.ConfigurationManager.AppSettings["FtpPassword"].ToString();<br />
}<br />
public void UploadList(string filename)<br />
{<br />
try<br />
{<br />
//l<strong>og</strong> the upload<br />
MagicalL<strong>og</strong>File.MakeL<strong>og</strong>Entry("Uploading file: " + filename);<br />
//open the file to upload<br />
FileStream fs = File.OpenRead(System.Environment.CurrentDirectory + "\\" + filename);<br />
//generate a byte array from file<br />
byte[] encoded = new byte[fs.Length];<br />
fs.Read(encoded, 0, (int)fs.Length);<br />
fs.Close();<br />
//convert from Unicode to UTF-8<br />
//byte[] encoded = Encoding.Convert(Encoding.GetEncoding("Unicode"), Encoding.GetEncoding("UTF-8"), unencoded, 0,<br />
unencoded.Length);<br />
//set the ftp request properties<br />
request = (FtpWebRequest)FtpWebRequest.Create(ftpurl + filename);<br />
request.ContentLength = encoded.Length;<br />
request.Credentials = new NetworkCredential(username, ftppassword);<br />
request.KeepAlive = false;<br />
//request.EnableSsl = true;<br />
request.Method = WebRequestMethods.Ftp.UploadFile;<br />
//upload isn't supported through http proxy, so request.proxy must be null<br />
request.Proxy = null;<br />
//write the file to the request stream<br />
Stream requeststream = request.GetRequestStream();<br />
requeststream.Write(encoded, 0, encoded.Length);<br />
requeststream.Close();<br />
//get the response<br />
response = (FtpWebResponse)request.GetResponse();<br />
MagicalL<strong>og</strong>File.MakeL<strong>og</strong>Entry(response.StatusDescription);<br />
}<br />
catch(WebException ex)<br />
{<br />
MagicalL<strong>og</strong>File.MakeL<strong>og</strong>Entry("A Web Exception Occured: " + ex.ToString());<br />
throw ex;<br />
Side 50
}<br />
}<br />
}<br />
}<br />
public void DownloadReport(string fileToGet, string fileToMake)<br />
{<br />
FtpWebRequest request = (FtpWebRequest)WebRequest.Create(ftpurl + fileToGet);<br />
request.Method = WebRequestMethods.Ftp.DownloadFile;<br />
}<br />
request.Credentials = new NetworkCredential(username, ftppassword);<br />
FtpWebResponse response = (FtpWebResponse)request.GetResponse();<br />
Stream responseStream = response.GetResponseStream();<br />
StreamReader reader = new StreamReader(responseStream);<br />
FileStream fs = new FileStream(fileToMake, FileMode.Create, FileAccess.Write, FileShare.None);<br />
int length = 256;<br />
Byte[] buffer = new Byte[length];<br />
int bytesRead = responseStream.Read(buffer, 0, length);<br />
while (bytesRead > 0)<br />
{<br />
fs.Write(buffer, 0, bytesRead);<br />
bytesRead = responseStream.Read(buffer, 0, length);<br />
}<br />
responseStream.Close();<br />
fs.Close();<br />
/*StreamWriter sw = new StreamWriter(fs);<br />
byte[] buffer = new byte[responseStream.Length];<br />
sw.Write(responseStream.Read(buffer, 0, responseStream.Length));<br />
sw.Flush();<br />
sw.Close();<br />
fs.Flush();<br />
fs.Close();*/<br />
Side 51
TransactionalDatabase.cs<br />
using System;<br />
using System.Collections.Generic;<br />
using System.Text;<br />
using System.Data;<br />
using System.Data.SqlClient;<br />
using System.Data.Common;<br />
namespace FOSS.SilverpopIntegration<br />
{<br />
public sealed class TransactionalDatabase<br />
{<br />
private SqlConnection conn;<br />
private string[] sql;<br />
public TransactionalDatabase()<br />
{<br />
MagicalL<strong>og</strong>File.MakeL<strong>og</strong>Entry("Creating Database Connection");<br />
conn = new SqlConnection(System.Configuration.ConfigurationManager.AppSettings["FRConnection"].ToString());<br />
}<br />
public string[] Sql<br />
{<br />
get<br />
{<br />
return (sql);<br />
}<br />
set<br />
{<br />
sql = value;<br />
}<br />
}<br />
MagicalL<strong>og</strong>File.MakeL<strong>og</strong>Entry("SQL Commands set to:");<br />
foreach (string s in sql)<br />
{<br />
MagicalL<strong>og</strong>File.MakeL<strong>og</strong>Entry("SQL Item: " + s);<br />
}<br />
public void RunCommands()<br />
{<br />
//check for any commands to run<br />
if (sql.Length < 1)<br />
{<br />
MagicalL<strong>og</strong>File.MakeL<strong>og</strong>Entry("RunCommands() called with no SQL to run!");<br />
return;<br />
}<br />
MagicalL<strong>og</strong>File.MakeL<strong>og</strong>Entry("RunCommands() - Opening DB Connection and beginning transaction");<br />
//open the db connection, and create a transaction<br />
conn.Open();<br />
SqlTransaction trans = conn.BeginTransaction();<br />
try<br />
{<br />
//create the command<br />
SqlCommand comm = new SqlCommand();<br />
//set command properties<br />
comm.Connection = conn;<br />
comm.Transaction = trans;<br />
//loop through sql commands and execute<br />
foreach (string s in sql)<br />
{<br />
comm.CommandText = s;<br />
comm.ExecuteNonQuery();<br />
}<br />
Side 52
}<br />
}<br />
}<br />
MagicalL<strong>og</strong>File.MakeL<strong>og</strong>Entry("Committing Transaction!");<br />
//commit the transaction<br />
trans.Commit();<br />
}<br />
catch<br />
{<br />
MagicalL<strong>og</strong>File.MakeL<strong>og</strong>Entry("Transaction failed! Rolling back...");<br />
//roll back the transaction<br />
trans.Rollback();<br />
}<br />
finally<br />
{<br />
MagicalL<strong>og</strong>File.MakeL<strong>og</strong>Entry("Closing DB Connection");<br />
//close the connection<br />
conn.Close();<br />
}<br />
public DataSet GetData()<br />
{<br />
MagicalL<strong>og</strong>File.MakeL<strong>og</strong>Entry("Retrieving data from database");<br />
}<br />
//open the connection<br />
conn.Open();<br />
//create an sql data adapter<br />
using (SqlDataAdapter sda = new SqlDataAdapter(sql[0], conn))<br />
{<br />
//create the dataset<br />
DataSet ds = new DataSet();<br />
}<br />
//fill the dataset<br />
sda.Fill(ds);<br />
//close the connection<br />
conn.Close();<br />
//and return<br />
return (ds);<br />
Side 53
Pr<strong>og</strong>ram.cs<br />
using System;<br />
using System.Collections.Generic;<br />
using System.Text;<br />
using Ionic.Zip;<br />
using System.IO;<br />
using System.Data;<br />
namespace FOSS.SilverpopIntegration<br />
{<br />
class Pr<strong>og</strong>ram<br />
{<br />
static void Main(string[] args)<br />
{<br />
SilverpopAPI api = new SilverpopAPI();<br />
SilverpopFTP ftp = new SilverpopFTP();<br />
TransactionalDatabase db = new TransactionalDatabase();<br />
string[] sql = new string[1];<br />
if (api.ApiL<strong>og</strong>in() == true)<br />
{<br />
Console.WriteLine("Successfully l<strong>og</strong>ged into API");<br />
Console.Write("Running export and waiting for it to complete...");<br />
string filename = api.RawRecipientDataExport();<br />
Console.WriteLine("Completed");<br />
Console.Write("Downloading report...");<br />
ftp.DownloadReport(filename, System.Environment.CurrentDirectory + "\\" + filename);<br />
Console.WriteLine("Done");<br />
Console.WriteLine("Extracting files from zip");<br />
using(ZipFile zip = ZipFile.Read(System.Environment.CurrentDirectory + "\\" + filename))<br />
{<br />
zip.ExtractAll(System.Environment.CurrentDirectory);<br />
}<br />
Console.WriteLine("done");<br />
// Deleting Zip file<br />
File.Delete(System.Environment.CurrentDirectory + "\\" + filename);<br />
char[] ziptrim = { 'z', 'i', 'p' };<br />
filename = filename.TrimEnd(ziptrim);<br />
filename = filename + "csv";<br />
try<br />
{<br />
using (FileStream csvfile = new FileStream(System.Environment.CurrentDirectory + "\\" + filename,FileMode.Open,FileAccess.Read))<br />
{<br />
using (TextReader csvread = new StreamReader(csvfile))<br />
{<br />
DataTable CsvData = CsvParser.Parse(csvread, true);<br />
int i = 0;<br />
byte[] TableId = null;<br />
try<br />
{<br />
sql[0] = "SELECT (MAX(Silverpop_In_Id) + 1) AS nextId FROM Silverpop_In";<br />
db.Sql = sql;<br />
Console.WriteLine("Getting Data From Silverpop In");<br />
using (DataSet ds = db.GetData())<br />
{<br />
if (ds != null)<br />
{<br />
DataRow dr = ds.Tables[0].Rows[0];<br />
Side 54
}<br />
if ("" == dr[0].ToString())<br />
TableId = ByteConvertor.ConvertToFossBytes("1");<br />
else<br />
TableId = ByteConvertor.ConvertToFossBytes(dr[0].ToString());<br />
}<br />
else<br />
{<br />
MagicalL<strong>og</strong>File.MakeL<strong>og</strong>Entry("Could not get record from Silverpop_In");<br />
ErrorMailGenerator.SendErrorMail("Could not get record from Silverpop_In");<br />
Environment.Exit(0);<br />
}<br />
}<br />
catch (Exception ex)<br />
{<br />
Console.WriteLine("Failing at Getting Data");<br />
throw new Exception(ex.Message);<br />
}<br />
if (CsvData.Rows.Count == 0)<br />
Console.WriteLine("Empty dataset");<br />
foreach (DataRow row in CsvData.Rows) // Loop over the rows.<br />
{<br />
}<br />
Boolean HardBounce = false;<br />
Boolean SoftBounce = false;<br />
Boolean Opened = false;<br />
Boolean Clicked = false;<br />
Boolean OptIn = false;<br />
Boolean OptOut = false;<br />
switch (row["Event Type"].ToString())<br />
{<br />
case "Open" :<br />
Opened = true;<br />
break;<br />
case "Click Through" :<br />
Clicked = true;<br />
break;<br />
case "Soft Bounce":<br />
SoftBounce = true;<br />
break;<br />
case "Hard Bounce":<br />
HardBounce = true;<br />
break;<br />
case "Opt In":<br />
OptIn = true;<br />
break;<br />
case "Opt Out":<br />
OptOut = true;<br />
break;<br />
}<br />
sql[i] = @"INSERT INTO [Silverpop_In]<br />
([Silverpop_In_Id], [Rn_Create_Date], [Contact_Id], [Email], [Email_Opt_In], [Email_Opt_Out],<br />
[Email_Bounced_Hard], [Email_Bounced_Soft], [Email_Bounced_Date], [Email_Clicked], [Email_Clicked_Date],<br />
[Email_Clicked_Link], [Mailing_Description], [Email_Opened], [Email_Opened_Date])<br />
VALUES<br />
(" + TableId + ", " + DateTime.Now.ToString() + ", " + row["Contact Id"] + ", " + row["Email"] + ", " + OptIn<br />
+ ", " + OptOut + ", " + HardBounce + ", " + SoftBounce +", " + row["Event Timestamp"] + ", " + Clicked<br />
+ ", " + row["Event Timestamp"] + ", " + row["URL"] + ", " + row["Mailing Id"] + ", " + Opened + ", " +<br />
row["Event Timestamp"];<br />
i++;<br />
Side 55
}<br />
}<br />
}<br />
db.Sql = sql;<br />
}<br />
}<br />
}<br />
catch (IOException)<br />
{<br />
MagicalL<strong>og</strong>File.MakeL<strong>og</strong>Entry("There was an error when trying to read from csv file");<br />
Environment.Exit(0);<br />
}<br />
}<br />
else<br />
{<br />
Console.WriteLine("Could not l<strong>og</strong> in to API");<br />
MagicalL<strong>og</strong>File.MakeL<strong>og</strong>Entry("Could not l<strong>og</strong> in to API");<br />
}<br />
Console.WriteLine("Press ENTER to exit...");<br />
Console.ReadLine();<br />
Side 56
ConsoleApplication1 C#<br />
App.config<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
Side 57
ByteConvertor.cs<br />
using System;<br />
using System.Collections.Generic;<br />
using System.Text;<br />
namespace FOSS.SilverpopIntegration<br />
{<br />
public static class ByteConvertor<br />
{<br />
/// <br />
/// Converts an decimal id into an bytearray id<br />
/// <br />
/// The decimal representation of the id<br />
/// The Bytearray representation of the id.<br />
public static byte[] ConvertToFossBytes(string id)<br />
{<br />
byte[] converted = new byte[8];<br />
string binstring = "";<br />
}<br />
if (id.Contains("-"))<br />
{<br />
string[] num = id.Split('-');<br />
binstring = DecimalToBase(Convert.ToInt32(num[0]), 2).PadLeft(24, '0');<br />
binstring += DecimalToBase(Convert.ToInt32(num[1]), 2).PadLeft(40, '0');<br />
}<br />
else<br />
{<br />
binstring = DecimalToBase(Convert.ToInt32(id), 2).PadLeft(64, '0');<br />
}<br />
for (int i = 0; i < converted.Length; i++)<br />
{<br />
converted[i] = (byte)BinToDecimal(binstring.Substring(i * 8, 8));<br />
}<br />
return converted;<br />
/// <br />
/// Converts a bytearray representation of an id into a decimal representation of the id<br />
/// <br />
/// bytearray representation of the id<br />
/// the decimal representation of the id<br />
public static string ConvertFossBytes(byte[] workid)<br />
{<br />
string[] BinStringArr = new string[workid.Length];<br />
for (int i = 0; i < workid.Length; i++)<br />
{<br />
BinStringArr[i] = DecimalToBase(workid[i], 2).PadLeft(8, '0');<br />
}<br />
string Head = "";<br />
string Tail = "";<br />
Head = BinStringArr[0];<br />
Head += BinStringArr[1];<br />
Head += BinStringArr[2];<br />
Tail = BinStringArr[3];<br />
Tail += BinStringArr[4];<br />
Tail += BinStringArr[5];<br />
Tail += BinStringArr[6];<br />
Tail += BinStringArr[7];<br />
string HeadOut = BinToDecimal(Head).ToString();<br />
string TailOut = BinToDecimal(Tail).ToString();<br />
Side 58
}<br />
}<br />
}<br />
if (HeadOut == "0")<br />
return TailOut;<br />
else<br />
return HeadOut + "-" + TailOut;<br />
/// <br />
/// Converts a decimal representation of a number into the "base" representation of the number<br />
/// <br />
/// The decimal number<br />
/// The base that the the decimal should be converted into<br />
/// The converted number<br />
public static string DecimalToBase(int iDec, int numbase)<br />
{<br />
string strBin = "";<br />
int[] result = new int[32];<br />
int MaxBit = 32;<br />
for (; iDec > 0; iDec /= numbase)<br />
{<br />
int rem = iDec % numbase;<br />
result[--MaxBit] = rem;<br />
}<br />
for (int i = 0; i < result.Length; i++)<br />
{<br />
strBin += result.GetValue(i);<br />
}<br />
strBin = strBin.TrimStart(new char[] { '0' });<br />
return strBin;<br />
}<br />
/// <br />
/// Converts from binary to decimal<br />
/// <br />
/// binary representation of the number<br />
/// the decimal representation of the number <br />
public static int BinToDecimal(string sBase)<br />
{<br />
int dec = 0;<br />
int bit = 0;<br />
for (int i = sBase.Length - 1; i >= 0; i--)<br />
{<br />
if ("1" == sBase[i].ToString())<br />
{<br />
dec += (int)Math.Pow(2, bit);<br />
}<br />
bit++;<br />
}<br />
return dec;<br />
}<br />
Side 59
CsvFileGenerator.cs<br />
using System;<br />
using System.Collections.Generic;<br />
using System.Text;<br />
using System.IO;<br />
using System.Data;<br />
using System.Data.SqlClient;<br />
using System.Data.Common;<br />
using System.Text.RegularExpressions;<br />
namespace FOSS.SilverpopIntegration<br />
{<br />
public sealed class CsvFileGenerator<br />
{<br />
private string filename;<br />
public string FileName<br />
{<br />
get<br />
{<br />
return (filename);<br />
}<br />
set<br />
{<br />
filename = value;<br />
MagicalL<strong>og</strong>File.MakeL<strong>og</strong>Entry("Filename changed to: " + filename);<br />
}<br />
}<br />
public void CreateMainFile(DataRow dr)<br />
{<br />
StreamWriter sw = File.AppendText(filename);<br />
try<br />
{<br />
try<br />
{<br />
sw.Write(FormatForCSV(ByteConvertor.ConvertFossBytes((byte[])dr["Contact_Id"])));<br />
}<br />
catch<br />
{<br />
sw.Write(FormatForCSV(""));<br />
}<br />
try<br />
{<br />
sw.Write(FormatForCSV(ByteConvertor.ConvertFossBytes((byte[])dr["Lead_Id"])));<br />
}<br />
catch<br />
{<br />
sw.Write(FormatForCSV(""));<br />
}<br />
sw.Write(FormatForCSV(dr["First_Name"].ToString()));<br />
sw.Write(FormatForCSV(dr["Last_Name"].ToString()));<br />
sw.Write(FormatForCSV(dr["Job_Category"].ToString()));<br />
sw.Write(FormatForCSV(dr["Country"].ToString()));<br />
sw.Write(FormatForCSV(dr["E_Mail"].ToString()));<br />
try<br />
{<br />
sw.Write(FormatForCSV(ByteConvertor.ConvertFossBytes((byte[])dr["Company_Id"])));<br />
}<br />
catch<br />
{<br />
sw.Write(FormatForCSV(""));<br />
}<br />
sw.Write(FormatForCSV(dr["Company_Name"].ToString()));<br />
sw.Write(FormatForCSV(dr["RSM_First_Name"].ToString()));<br />
sw.Write(FormatForCSV(dr["RSM_Last_Name"].ToString()));<br />
Side 60
}<br />
sw.Write(FormatForCSV(dr["RSM_Job_Title"].ToString()));<br />
sw.Write(FormatForCSV(dr["RSM_Company_Name"].ToString()));<br />
sw.Write(FormatForCSV(dr["RSM_E_Mail"].ToString()));<br />
sw.Write(FormatForCSV(dr["RSM_Phone"].ToString()));<br />
sw.Write(FormatForCSV(dr["BA_Analytical_Labs"].ToString()));<br />
sw.Write(FormatForCSV(dr["BA_Beer"].ToString()));<br />
sw.Write(FormatForCSV(dr["BA_Biofuel"].ToString()));<br />
sw.Write(FormatForCSV(dr["BA_Chemicals"].ToString()));<br />
sw.Write(FormatForCSV(dr["BA_Confectionery"].ToString()));<br />
sw.Write(FormatForCSV(dr["BA_Dressings"].ToString()));<br />
sw.Write(FormatForCSV(dr["BA_Edible_Oils"].ToString()));<br />
sw.Write(FormatForCSV(dr["BA_Feed_and_Forage"].ToString()));<br />
sw.Write(FormatForCSV(dr["BA_Flour"].ToString()));<br />
sw.Write(FormatForCSV(dr["BA_Grain"].ToString()));<br />
sw.Write(FormatForCSV(dr["BA_Meat"].ToString()));<br />
sw.Write(FormatForCSV(dr["BA_Microbiol<strong>og</strong>y"].ToString()));<br />
sw.Write(FormatForCSV(dr["BA_Milk_and_Dairy"].ToString()));<br />
sw.Write(FormatForCSV(dr["BA_Pet_Food"].ToString()));<br />
sw.Write(FormatForCSV(dr["BA_Pharmaceuticals"].ToString()));<br />
sw.Write(FormatForCSV(dr["BA_Sugar"].ToString()));<br />
sw.Write(FormatForCSV(dr["BA_Water_and_Soil"].ToString()));<br />
sw.Write(FormatForCSV(dr["BA_Wine"].ToString()));<br />
string part1 = null;<br />
string part2 = null;<br />
try<br />
{<br />
part1 = ByteConvertor.ConvertFossBytes((byte[])dr["Contact_Id"]);<br />
}<br />
catch<br />
{<br />
part2 = ByteConvertor.ConvertFossBytes((byte[])dr["Lead_Id"]);<br />
}<br />
finally<br />
{<br />
sw.WriteLine(FormatForCSV(part1 + part2,true));<br />
}<br />
}<br />
finally<br />
{<br />
sw.Close();<br />
}<br />
public void CreateProductFile(DataRow dr)<br />
{<br />
StreamWriter sw = File.AppendText(filename);<br />
try<br />
{<br />
sw.Write(FormatForCSV(ByteConvertor.ConvertFossBytes((byte[])dr["Registration_Id"])));<br />
sw.Write(FormatForCSV(ByteConvertor.ConvertFossBytes((byte[])dr["Company_Id"])));<br />
sw.Write(FormatForCSV(dr["Product_Name"].ToString()));<br />
sw.WriteLine(FormatForCSV(dr["Product_Group_Name"].ToString(), true));<br />
}<br />
finally<br />
{<br />
sw.Close();<br />
}<br />
}<br />
public void CreateBAFile(DataRow dr)<br />
{<br />
StreamWriter sw = File.AppendText(filename);<br />
try<br />
{<br />
sw.Write(FormatForCSV(ByteConvertor.ConvertFossBytes((byte[])dr["Contact_Customer_Segment_Id"])));<br />
Side 61
}<br />
try<br />
{<br />
sw.Write(FormatForCSV(ByteConvertor.ConvertFossBytes((byte[])dr["Contact_Id"])));<br />
}<br />
catch<br />
{<br />
sw.Write(FormatForCSV(""));<br />
}<br />
try<br />
{<br />
sw.Write(FormatForCSV(ByteConvertor.ConvertFossBytes((byte[])dr["Lead_Id"])));<br />
}<br />
catch<br />
{<br />
sw.Write(FormatForCSV(""));<br />
}<br />
sw.WriteLine(FormatForCSV(dr["Segment"].ToString(), true));<br />
}<br />
finally<br />
{<br />
sw.Close();<br />
}<br />
public void CreatePrefCommFile(DataRow dr)<br />
{<br />
StreamWriter sw = File.AppendText(filename);<br />
try<br />
{<br />
sw.Write(FormatForCSV(ByteConvertor.ConvertFossBytes((byte[])dr["Preferred_Com_Methods_Id"])));<br />
try<br />
{<br />
sw.Write(FormatForCSV(ByteConvertor.ConvertFossBytes((byte[])dr["Contact_Id"])));<br />
}<br />
catch<br />
{<br />
sw.Write(FormatForCSV(""));<br />
}<br />
try<br />
{<br />
sw.Write(FormatForCSV(ByteConvertor.ConvertFossBytes((byte[])dr["Lead__Id"])));<br />
}<br />
catch<br />
{<br />
sw.Write(FormatForCSV(""));<br />
}<br />
}<br />
sw.WriteLine(FormatForCSV(dr["Type"].ToString(), true));<br />
}<br />
finally<br />
{<br />
sw.Close();<br />
}<br />
public void DeleteFile()<br />
{<br />
try<br />
{<br />
File.Delete(filename);<br />
}<br />
catch (Exception ex)<br />
{<br />
MagicalL<strong>og</strong>File.MakeL<strong>og</strong>Entry("Unable To Delete File! " + ex.ToString());<br />
}<br />
Side 62
}<br />
}<br />
}<br />
private string FormatForCSV(string s, bool last)<br />
{<br />
//escape any speech marks in the string<br />
string pattern = "\"";<br />
}<br />
Regex regex = new Regex(pattern);<br />
Match match = regex.Match(s);<br />
s = regex.Replace(s, "\"\"");<br />
//if this is the last item in a row, omit the trailing comma<br />
if (last)<br />
{<br />
return ("\"" + s + "\"");<br />
}<br />
else<br />
{<br />
return ("\"" + s + "\",");<br />
}<br />
private string FormatForCSV(string s)<br />
{<br />
return(FormatForCSV(s, false));<br />
}<br />
Side 63
SilverpopAPI.cs<br />
using System;<br />
using System.Collections.Generic;<br />
using System.Text;<br />
using System.Xml;<br />
using System.Configuration;<br />
using System.Web;<br />
using System.Net;<br />
using System.IO;<br />
using System.Threading;<br />
namespace FOSS.SilverpopIntegration<br />
{<br />
public sealed class SilverpopAPI<br />
{<br />
private string sessionid;<br />
private string sessionencoding;<br />
private XmlDocument request;<br />
private XmlDocument response;<br />
private string jobstatus;<br />
private string url;<br />
private string username;<br />
private string password;<br />
private string jobid;<br />
private static string MainListId = System.Configuration.ConfigurationManager.AppSettings["MainListId"].ToString();<br />
public SilverpopAPI()<br />
{<br />
url = System.Configuration.ConfigurationManager.AppSettings["ApiUrl"].ToString();<br />
username = System.Configuration.ConfigurationManager.AppSettings["Username"].ToString();<br />
password = System.Configuration.ConfigurationManager.AppSettings["Password"].ToString();<br />
}<br />
request = new XmlDocument();<br />
response = new XmlDocument();<br />
public bool ApiL<strong>og</strong>in()<br />
{<br />
if (sessionid == null)<br />
{<br />
//create xml document<br />
request = BuildSilverpopWrapper();<br />
XmlElement L<strong>og</strong>in = request.CreateElement("L<strong>og</strong>in");<br />
request.GetElementsByTagName("Body")[0].AppendChild(L<strong>og</strong>in);<br />
//add username and password from app.config<br />
XmlElement usernameXML = request.CreateElement("USERNAME");<br />
usernameXML.InnerText = username;<br />
L<strong>og</strong>in.AppendChild(usernameXML);<br />
XmlElement passwordXML = request.CreateElement("PASSWORD");<br />
passwordXML.InnerText = password;<br />
L<strong>og</strong>in.AppendChild(passwordXML);<br />
//l<strong>og</strong>ging in<br />
MagicalL<strong>og</strong>File.MakeL<strong>og</strong>Entry("L<strong>og</strong>ging Into Silverpop API...");<br />
SubmitRequest();<br />
if (response == null) return (false);<br />
//parse the response to check if l<strong>og</strong>ged in, and get the sessionid<br />
if (response.GetElementsByTagName("SUCCESS")[0].InnerText == "true")<br />
{<br />
sessionid = response.GetElementsByTagName("SESSIONID")[0].InnerText;<br />
sessionencoding = response.GetElementsByTagName("SESSION_ENCODING")[0].InnerText;<br />
Side 64
}<br />
MagicalL<strong>og</strong>File.MakeL<strong>og</strong>Entry("Successfully l<strong>og</strong>ged in with sessionid: " + sessionid);<br />
return (true);<br />
}<br />
else<br />
{<br />
//l<strong>og</strong> an error<br />
MagicalL<strong>og</strong>File.MakeL<strong>og</strong>Entry("Failed to l<strong>og</strong>in to API :: " + response.GetElementsByTagName("FaultString")[0].InnerText);<br />
}<br />
return (false);<br />
}<br />
else<br />
{<br />
//already l<strong>og</strong>ged in<br />
MagicalL<strong>og</strong>File.MakeL<strong>og</strong>Entry("Already l<strong>og</strong>ged into Silverpop API");<br />
return (true);<br />
}<br />
public void ApiL<strong>og</strong>out()<br />
{<br />
if (sessionid == null)<br />
{<br />
//not l<strong>og</strong>ged in it seems<br />
MagicalL<strong>og</strong>File.MakeL<strong>og</strong>Entry("Already l<strong>og</strong>ged out of API");<br />
//ensure variables are reset<br />
sessionencoding = null;<br />
}<br />
else<br />
{<br />
//create l<strong>og</strong>out request<br />
request = BuildSilverpopWrapper();<br />
}<br />
}<br />
XmlElement l<strong>og</strong>out = request.CreateElement("L<strong>og</strong>out");<br />
request.GetElementsByTagName("Body")[0].AppendChild(l<strong>og</strong>out);<br />
//l<strong>og</strong>ging out<br />
MagicalL<strong>og</strong>File.MakeL<strong>og</strong>Entry("L<strong>og</strong>ging out of API");<br />
//submit request and get response<br />
SubmitRequest();<br />
if (response == null)<br />
{<br />
MagicalL<strong>og</strong>File.MakeL<strong>og</strong>Entry("Null response when attempting to l<strong>og</strong> out");<br />
}<br />
sessionencoding = null;<br />
sessionid = null;<br />
private XmlDocument BuildSilverpopWrapper()<br />
{<br />
XmlDocument xml = new XmlDocument();<br />
XmlDeclaration xmlDec = xml.CreateXmlDeclaration("1.0", "utf-8", null);<br />
XmlElement envelope = xml.CreateElement("Envelope");<br />
xml.InsertBefore(xmlDec, xml.DocumentElement);<br />
xml.AppendChild(envelope);<br />
XmlElement body = xml.CreateElement("Body");<br />
envelope.AppendChild(body);<br />
return (xml);<br />
Side 65
}<br />
public void ListImport(string list)<br />
{<br />
if (sessionid == null)<br />
{<br />
MagicalL<strong>og</strong>File.MakeL<strong>og</strong>Entry("Attempted to make an API Request without being l<strong>og</strong>ged in!");<br />
}<br />
else<br />
{<br />
//build our request<br />
request = BuildSilverpopWrapper();<br />
XmlElement ImportList_ = request.CreateElement("ImportList");<br />
request.GetElementsByTagName("Body")[0].AppendChild(ImportList_);<br />
XmlElement MAP_FILE = request.CreateElement("MAP_FILE");<br />
MAP_FILE.InnerText = list + ".xml";<br />
ImportList_.AppendChild(MAP_FILE);<br />
XmlElement SOURCE_FILE = request.CreateElement("SOURCE_FILE");<br />
SOURCE_FILE.InnerText = list + ".csv";<br />
ImportList_.AppendChild(SOURCE_FILE);<br />
//l<strong>og</strong> the request<br />
MagicalL<strong>og</strong>File.MakeL<strong>og</strong>Entry("Sending ListImport Request to Silverpop API for list: " + list);<br />
//make the request and get response<br />
SubmitRequest();<br />
//if the request was not successful record an error<br />
if (response.GetElementsByTagName("SUCCESS")[0].InnerText == "false")<br />
{<br />
MagicalL<strong>og</strong>File.MakeL<strong>og</strong>Entry("ERROR! ListImport request returned the following fault: " +<br />
response.GetElementsByTagName("FaultString")[0].InnerText);<br />
}<br />
else<br />
{<br />
//l<strong>og</strong> success<br />
MagicalL<strong>og</strong>File.MakeL<strong>og</strong>Entry(response.FirstChild.InnerText.ToString());<br />
jobid = response.GetElementsByTagName("JOB_ID")[0].InnerText;<br />
}<br />
}<br />
}<br />
public void TableImport(string list)<br />
{<br />
if (sessionid == null)<br />
{<br />
MagicalL<strong>og</strong>File.MakeL<strong>og</strong>Entry("Attempted to make an API Request without being l<strong>og</strong>ged in!");<br />
}<br />
else<br />
{<br />
//build our request<br />
request = BuildSilverpopWrapper();<br />
XmlElement ImportTable_ = request.CreateElement("ImportTable");<br />
request.GetElementsByTagName("Body")[0].AppendChild(ImportTable_);<br />
XmlElement MAP_FILE = request.CreateElement("MAP_FILE");<br />
MAP_FILE.InnerText = list + ".xml";<br />
ImportTable_.AppendChild(MAP_FILE);<br />
XmlElement SOURCE_FILE = request.CreateElement("SOURCE_FILE");<br />
SOURCE_FILE.InnerText = list + ".csv";<br />
ImportTable_.AppendChild(SOURCE_FILE);<br />
//l<strong>og</strong> the request<br />
Side 66
MagicalL<strong>og</strong>File.MakeL<strong>og</strong>Entry("Sending TableImport Request to Silverpop API for list: " + list);<br />
//make the request and get response<br />
SubmitRequest();<br />
//if the request was not successful record an error<br />
if (response.GetElementsByTagName("SUCCESS")[0].InnerText == "false")<br />
{<br />
MagicalL<strong>og</strong>File.MakeL<strong>og</strong>Entry("ERROR! TableImport request returned the following fault: " +<br />
response.GetElementsByTagName("FaultString")[0].InnerText);<br />
}<br />
else<br />
{<br />
//l<strong>og</strong> success<br />
MagicalL<strong>og</strong>File.MakeL<strong>og</strong>Entry(response.FirstChild.InnerText.ToString());<br />
jobid = response.GetElementsByTagName("JOB_ID")[0].InnerText;<br />
}<br />
}<br />
}<br />
private void SubmitRequest()<br />
{<br />
//build a webrequest<br />
WebRequest wrequest = WebRequest.Create(url + sessionencoding) as HttpWebRequest;<br />
}<br />
//set request properties<br />
wrequest.ContentType = "text/xml;charset=UTF-8";<br />
wrequest.Method = "POST";<br />
StringWriter sw = new StringWriter();<br />
XmlTextWriter xw = new XmlTextWriter(sw);<br />
//save the xml request to a text writer<br />
request.WriteTo(xw);<br />
System.Text.ASCIIEncoding encoding = new System.Text.ASCIIEncoding();<br />
//convert to a byte array<br />
byte[] byteData = encoding.GetBytes(HttpUtility.UrlPathEncode(sw.ToString()));<br />
wrequest.ContentLength = byteData.Length;<br />
//add data to the request<br />
using (Stream requestStream = wrequest.GetRequestStream())<br />
{<br />
requestStream.Write(byteData, 0, byteData.Length);<br />
}<br />
//finish the request<br />
wrequest.GetRequestStream().Close();<br />
//get the response<br />
HttpWebResponse wresponse = (HttpWebResponse)wrequest.GetResponse();<br />
//load the response into the response xmlDoc<br />
response = new XmlDocument();<br />
try<br />
{<br />
response.Load(wresponse.GetResponseStream());<br />
}<br />
catch (Exception)<br />
{<br />
//l<strong>og</strong> an error<br />
MagicalL<strong>og</strong>File.MakeL<strong>og</strong>Entry("ERROR while making a request to the Silverpop API - Response could not be loaded!");<br />
}<br />
private void GetJobStatus()<br />
Side 67
{<br />
if (sessionid == null)<br />
{<br />
//not l<strong>og</strong>ged in<br />
MagicalL<strong>og</strong>File.MakeL<strong>og</strong>Entry("Can't get a job status, not l<strong>og</strong>ged in");<br />
}<br />
else<br />
{<br />
//build our request<br />
request = BuildSilverpopWrapper();<br />
XmlElement GetStatus = request.CreateElement("GetJobStatus");<br />
request.GetElementsByTagName("Body")[0].AppendChild(GetStatus);<br />
XmlElement JOB_ID = request.CreateElement("JOB_ID");<br />
JOB_ID.InnerText = jobid;<br />
GetStatus.AppendChild(JOB_ID);<br />
//submit our request and get the response<br />
SubmitRequest();<br />
//check for a null response<br />
if (response == null)<br />
{<br />
//l<strong>og</strong> an error<br />
MagicalL<strong>og</strong>File.MakeL<strong>og</strong>Entry("Error getting Job Status for Job Id: " + jobid);<br />
}<br />
else if (response.GetElementsByTagName("SUCCESS")[0].InnerText == "FALSE") //check that we can get a status for that job<br />
{<br />
//l<strong>og</strong> an error<br />
MagicalL<strong>og</strong>File.MakeL<strong>og</strong>Entry("Could not get job status for job id: " + jobid + " Fault from Silverpop is: " +<br />
response.GetElementsByTagName("FaultString")[0].InnerText);<br />
}<br />
else<br />
{<br />
//set the job status<br />
jobstatus = response.GetElementsByTagName("JOB_STATUS")[0].InnerText;<br />
//make a l<strong>og</strong> of the status<br />
MagicalL<strong>og</strong>File.MakeL<strong>og</strong>Entry("Job status for jobid " + jobid + " is " + jobstatus);<br />
}<br />
}<br />
}<br />
public string TGetJobStatus(string id)<br />
{<br />
if (sessionid == null)<br />
{<br />
//not l<strong>og</strong>ged in<br />
MagicalL<strong>og</strong>File.MakeL<strong>og</strong>Entry("Can't get a job status, not l<strong>og</strong>ged in");<br />
return ("ERROR");<br />
}<br />
else<br />
{<br />
request = null;<br />
//build our request<br />
request = BuildSilverpopWrapper();<br />
XmlElement GetStatus = request.CreateElement("GetJobStatus");<br />
XmlElement JOB_ID = request.CreateElement("JOB_ID");<br />
JOB_ID.InnerText = id;// (string)Thread.GetData(loc);<br />
GetStatus.AppendChild(request.ImportNode(JOB_ID, true));<br />
request.GetElementsByTagName("Body")[0].AppendChild(request.ImportNode(GetStatus,true));<br />
MagicalL<strong>og</strong>File.MakeL<strong>og</strong>Entry("Requesting Job Status for Job Id: " + id);<br />
//submit our request and get the response<br />
SubmitRequest();<br />
Side 68
check for a null response<br />
if (response == null)<br />
{<br />
//l<strong>og</strong> an error<br />
MagicalL<strong>og</strong>File.MakeL<strong>og</strong>Entry("Error getting Job Status for Job Id: " + id);<br />
}<br />
else if (response.GetElementsByTagName("SUCCESS")[0].InnerText == "FALSE") //check that we can get a status for that job<br />
{<br />
//l<strong>og</strong> an error<br />
MagicalL<strong>og</strong>File.MakeL<strong>og</strong>Entry("Could not get job status for job id: " + id + " Fault from Silverpop is: " +<br />
response.GetElementsByTagName("FaultString")[0].InnerText);<br />
}<br />
else<br />
{<br />
//make a l<strong>og</strong> of the status<br />
MagicalL<strong>og</strong>File.MakeL<strong>og</strong>Entry("Job status for jobid " + id + " is " + response.GetElementsByTagName("JOB_STATUS")[0].InnerText);<br />
//set the job status<br />
return(response.GetElementsByTagName("JOB_STATUS")[0].InnerText);<br />
}<br />
return ("ERROR");<br />
}<br />
}<br />
public void WaitOnJobStatus(string id)<br />
{<br />
bool keepgoing=true;<br />
string status;<br />
}<br />
//LocalDataStoreSlot localSlot = Thread.AllocateDataSlot();<br />
//Thread.SetData(localSlot, id);<br />
while (keepgoing)<br />
{<br />
status = TGetJobStatus(id);<br />
if (status == "COMPLETE")<br />
{<br />
keepgoing=false;<br />
}<br />
if (status == "WAITING" || jobstatus=="RUNNING")<br />
{<br />
//wait 30 seconds before checking again<br />
Thread.Sleep(30000);<br />
}<br />
if (status == "ERROR")<br />
{<br />
//l<strong>og</strong> the error and end the loop<br />
MagicalL<strong>og</strong>File.MakeL<strong>og</strong>Entry(response.FirstChild.InnerText.ToString());<br />
keepgoing = false;<br />
}<br />
}<br />
public void DeleteRecipient(Recipient r)<br />
{<br />
MagicalL<strong>og</strong>File.MakeL<strong>og</strong>Entry("Creating Delete Request for ID:" + r.Id);<br />
//build request<br />
request = BuildSilverpopWrapper();<br />
XmlElement RemoveRecipient = request.CreateElement("RemoveRecipient");<br />
request.GetElementsByTagName("Body")[0].AppendChild(RemoveRecipient);<br />
XmlElement LIST_ID = request.CreateElement("LIST_ID");<br />
LIST_ID.InnerText = MainListId;<br />
RemoveRecipient.AppendChild(request.ImportNode(LIST_ID,true));<br />
XmlElement EMAIL = request.CreateElement("EMAIL");<br />
EMAIL.InnerText = r.Email;<br />
Side 69
RemoveRecipient.AppendChild(request.ImportNode(EMAIL,true));<br />
XmlElement COLUMN = request.CreateElement("COLUMN");<br />
RemoveRecipient.AppendChild(COLUMN);<br />
//columns in here must include the name and value for any unique identifiers in the list<br />
XmlElement NAME = request.CreateElement("NAME");<br />
NAME.InnerText = "NEK";<br />
COLUMN.AppendChild(NAME);<br />
XmlElement VALUE = request.CreateElement("VALUE");<br />
VALUE.InnerText = r.Id;<br />
COLUMN.AppendChild(VALUE);<br />
SubmitRequest();<br />
//if the request was not successful record an error<br />
if (response.GetElementsByTagName("SUCCESS")[0].InnerText == "false")<br />
{<br />
MagicalL<strong>og</strong>File.MakeL<strong>og</strong>Entry("ERROR! RemoveRecipient request returned the following fault: " +<br />
response.GetElementsByTagName("FaultString")[0].InnerText);<br />
}<br />
else<br />
{<br />
//l<strong>og</strong> success<br />
MagicalL<strong>og</strong>File.MakeL<strong>og</strong>Entry(response.FirstChild.InnerText.ToString());<br />
}<br />
}<br />
}<br />
public string JobId<br />
{<br />
get<br />
{<br />
return(jobid);<br />
}<br />
}<br />
public string JobStatus<br />
{<br />
get<br />
{<br />
return(jobstatus);<br />
}<br />
}<br />
public sealed class SilverpopAPIWatcher<br />
{<br />
private string jid;<br />
private SilverpopAPI api;<br />
public SilverpopAPIWatcher(string id, SilverpopAPI sapi)<br />
{<br />
jid = id;<br />
api = sapi;<br />
}<br />
public bool GetJobStatus()<br />
{<br />
bool keepgoing = true;<br />
string status;<br />
bool errors = false;<br />
while (keepgoing)<br />
{<br />
status = api.TGetJobStatus(jid);<br />
Side 70
}<br />
}<br />
}<br />
}<br />
}<br />
if (status == "COMPLETE")<br />
{<br />
keepgoing = false;<br />
}<br />
if (status == "WAITING" || status == "RUNNING")<br />
{<br />
//wait 30 seconds before checking again<br />
Thread.Sleep(30000);<br />
}<br />
if (status == "ERROR" || status == "CANCELED")<br />
{<br />
//l<strong>og</strong> the error and end the loop<br />
MagicalL<strong>og</strong>File.MakeL<strong>og</strong>Entry("Error checking job status");<br />
keepgoing = false;<br />
errors = true;<br />
}<br />
return (errors);<br />
public struct Recipient<br />
{<br />
private string id;<br />
private string email;<br />
public string Id<br />
{<br />
get<br />
{<br />
return id;<br />
}<br />
set<br />
{<br />
id = value;<br />
}<br />
}<br />
public string Email<br />
{<br />
get<br />
{<br />
return email;<br />
}<br />
set<br />
{<br />
email = value;<br />
}<br />
}<br />
Side 71
SilverpopFTP.cs<br />
using System;<br />
using System.Collections.Generic;<br />
using System.Text;<br />
using System.Web;<br />
using System.Net;<br />
using System.IO;<br />
namespace FOSS.SilverpopIntegration<br />
{<br />
public sealed class SilverpopFTP<br />
{<br />
private FtpWebResponse response;<br />
private FtpWebRequest request;<br />
private string username;<br />
private string ftpurl;<br />
private string ftppassword;<br />
public SilverpopFTP()<br />
{<br />
username = System.Configuration.ConfigurationManager.AppSettings["FtpUsername"].ToString();<br />
ftpurl = System.Configuration.ConfigurationManager.AppSettings["FtpUrl"].ToString();<br />
ftppassword = System.Configuration.ConfigurationManager.AppSettings["FtpPassword"].ToString();<br />
}<br />
public void UploadList(string filename)<br />
{<br />
try<br />
{<br />
//l<strong>og</strong> the upload<br />
MagicalL<strong>og</strong>File.MakeL<strong>og</strong>Entry("Uploading file: " + filename);<br />
//open the file to upload<br />
FileStream fs = File.OpenRead(System.Environment.CurrentDirectory + "\\" + filename);<br />
//generate a byte array from file<br />
byte[] encoded = new byte[fs.Length];<br />
fs.Read(encoded, 0, (int)fs.Length);<br />
fs.Close();<br />
//convert from Unicode to UTF-8<br />
//byte[] encoded = Encoding.Convert(Encoding.GetEncoding("Unicode"), Encoding.GetEncoding("UTF-8"), unencoded, 0,<br />
unencoded.Length);<br />
//set the ftp request properties<br />
request = (FtpWebRequest)FtpWebRequest.Create(ftpurl + filename);<br />
request.ContentLength = encoded.Length;<br />
request.Credentials = new NetworkCredential(username, ftppassword);<br />
request.KeepAlive = false;<br />
//request.EnableSsl = true;<br />
request.Method = WebRequestMethods.Ftp.UploadFile;<br />
//upload isn't supported through http proxy, so request.proxy must be null<br />
request.Proxy = null;<br />
//write the file to the request stream<br />
Stream requeststream = request.GetRequestStream();<br />
requeststream.Write(encoded, 0, encoded.Length);<br />
requeststream.Close();<br />
//get the response<br />
response = (FtpWebResponse)request.GetResponse();<br />
MagicalL<strong>og</strong>File.MakeL<strong>og</strong>Entry(response.StatusDescription);<br />
}<br />
catch(WebException ex)<br />
{<br />
MagicalL<strong>og</strong>File.MakeL<strong>og</strong>Entry("A Web Exception Occured: " + ex.ToString());<br />
throw ex;<br />
Side 72
}<br />
}<br />
}<br />
}<br />
Side 73
Pr<strong>og</strong>ram.cs<br />
using System;<br />
using System.Collections.Generic;<br />
using System.Text;<br />
using FOSS.SilverpopIntegration;<br />
using System.Data;<br />
using System.Threading;<br />
using System.IO;<br />
namespace FOSS.SilverpopIntegration<br />
{<br />
class SilverpopSync<br />
{<br />
static void Main(string[] args)<br />
{<br />
TransactionalDatabase db = new TransactionalDatabase();<br />
CsvFileGenerator csv = new CsvFileGenerator();<br />
SilverpopFTP ftp = new SilverpopFTP();<br />
SilverpopAPI api = new SilverpopAPI();<br />
int NUMBER_OF_FILES = 4;<br />
string[] sql = new string[1];<br />
#region deletions<br />
//run through all delete requests<br />
sql[0] = "SELECT * FROM Silverpop_Out WHERE Deletion = 1";<br />
db.Sql = sql;<br />
Console.WriteLine("Getting Deletion Records");<br />
using (DataSet ds = db.GetData())<br />
{<br />
if (ds != null)<br />
{<br />
if (ds.Tables[0].Rows.Count == 0)<br />
{<br />
Console.WriteLine("There are no deletions");<br />
MagicalL<strong>og</strong>File.MakeL<strong>og</strong>Entry("There are no deletions");<br />
}<br />
else<br />
{<br />
if (api.ApiL<strong>og</strong>in())<br />
{<br />
Console.WriteLine("Starting Deletions...");<br />
List events = new List();<br />
foreach (DataRow dr in ds.Tables[0].Rows)<br />
{<br />
Recipient r = new Recipient();<br />
string part1 = null;<br />
string part2 = null;<br />
try<br />
{<br />
part1 = ByteConvertor.ConvertFossBytes((byte[])dr["Contact_Id"]);<br />
}<br />
catch<br />
{<br />
part2 = ByteConvertor.ConvertFossBytes((byte[])dr["Lead_Id"]);<br />
}<br />
finally<br />
{<br />
r.Id = part1 + part2;<br />
}<br />
r.Email = dr["E_Mail"].ToString();<br />
ManualResetEvent mre = new ManualResetEvent(false);<br />
ThreadPool.QueueUserWorkItem(<br />
delegate(object o)<br />
Side 74
{<br />
api.DeleteRecipient(r);<br />
((ManualResetEvent)o).Set();<br />
}, mre);<br />
events.Add(mre);<br />
}<br />
Console.Write("Waiting for delete requests to be submitted...");<br />
WaitHandle.WaitAll(events.ToArray());<br />
Console.WriteLine("Done!");<br />
}<br />
else<br />
{<br />
Console.ForegroundColor = ConsoleColor.Red;<br />
Console.WriteLine("ERROR LOGGING INTO API - CHECK USERNAME AND PASSWORD");<br />
//send an error mail<br />
ErrorMailGenerator.SendErrorMail("Couldn't l<strong>og</strong> into the api, check username and password");<br />
Environment.Exit(0);<br />
}<br />
}<br />
}<br />
else<br />
{<br />
Console.WriteLine("Couldn't get deletions data");<br />
MagicalL<strong>og</strong>File.MakeL<strong>og</strong>Entry("Couldn't get any deletions data...");<br />
}<br />
}<br />
#endregion<br />
#region MainList<br />
//create and upload MainList<br />
csv.FileName = System.Environment.CurrentDirectory + "\\MainList.csv";<br />
sql[0] = "SELECT * FROM Silverpop_Out WHERE Deletion=0";<br />
db.Sql = sql;<br />
Console.WriteLine("Getting Data");<br />
using (DataSet ds = db.GetData())<br />
{<br />
if (ds != null)<br />
{<br />
if (ds.Tables[0].Rows.Count == 0)<br />
{<br />
Console.WriteLine("Empty Mainlist Dataset, exiting pr<strong>og</strong>ram");<br />
MagicalL<strong>og</strong>File.MakeL<strong>og</strong>Entry("Empty Mainlist Dataset, exiting pr<strong>og</strong>ram");<br />
Environment.Exit(0);<br />
}<br />
Console.WriteLine("Making file");<br />
foreach (DataRow dr in ds.Tables["Table"].Rows)<br />
{<br />
csv.CreateMainFile(dr);<br />
}<br />
Console.WriteLine("Uploading File");<br />
ftp.UploadList("MainList.csv");<br />
ftp.UploadList("MainList.xml");<br />
csv.DeleteFile();<br />
}<br />
else<br />
{<br />
MagicalL<strong>og</strong>File.MakeL<strong>og</strong>Entry("Could not get dataset when attempting to retrieve mainlist");<br />
ErrorMailGenerator.SendErrorMail("Could not get dataset when attempting to retrieve mainlist");<br />
Environment.Exit(0);<br />
}<br />
Side 75
}<br />
#endregion<br />
#region Products<br />
//Create and upload Products<br />
csv.FileName = System.Environment.CurrentDirectory + "\\products.csv";<br />
sql[0] = @"SELECT DISTINCT Registration.Registration_Id, Registration.Company_Id, Registration.Product_Name,<br />
Product_Group.Product_Group_Name<br />
FROM Silverpop_Out, Registration, Product_Group, Product<br />
WHERE Silverpop_Out.Company_Id = Registration.Company_Id<br />
AND Registration.Product_Id = Product.Product_Id<br />
AND Product.Product_Group_Id = Product_Group.Product_Group_Id<br />
AND Registration.Formerly_Installed = 0";<br />
db.Sql = sql;<br />
Console.WriteLine("Getting Data");<br />
using (DataSet ds = db.GetData())<br />
{<br />
if (ds != null)<br />
{<br />
Console.WriteLine("Making file");<br />
foreach (DataRow dr in ds.Tables["Table"].Rows)<br />
{<br />
csv.CreateProductFile(dr);<br />
}<br />
Console.WriteLine("Uploading File");<br />
if (File.Exists(System.Environment.CurrentDirectory + "\\products.csv"))<br />
{<br />
ftp.UploadList("products.csv");<br />
ftp.UploadList("products.xml");<br />
csv.DeleteFile();<br />
}<br />
else<br />
{<br />
Console.WriteLine("Products not uploaded - file does not exist");<br />
MagicalL<strong>og</strong>File.MakeL<strong>og</strong>Entry("Products not uploaded - file does not exist");<br />
}<br />
}<br />
else<br />
{<br />
MagicalL<strong>og</strong>File.MakeL<strong>og</strong>Entry("Could not get dataset when attempting to retrieve products");<br />
ErrorMailGenerator.SendErrorMail("Could not get dataset when attempting to retrieve products");<br />
}<br />
}<br />
#endregion<br />
#region Business Areas<br />
//Create and upload BA<br />
csv.FileName = System.Environment.CurrentDirectory + "\\ba.csv";<br />
sql[0] = @"SELECT Contact_Customer_Segment.Contact_Customer_Segment_Id, Silverpop_Out.Contact_Id, Silverpop_Out.Lead_Id,<br />
Contact_Customer_Segment.Segment<br />
FROM Contact_Customer_Segment, Silverpop_Out<br />
WHERE Contact_Customer_Segment.Contact_Id = Silverpop_Out.Contact_Id<br />
OR Contact_Customer_Segment.Lead_Id = Silverpop_Out.Lead_Id";<br />
db.Sql = sql;<br />
Console.WriteLine("Getting Data");<br />
using (DataSet ds = db.GetData())<br />
{<br />
if (ds != null)<br />
{<br />
Console.WriteLine("Making file");<br />
foreach (DataRow dr in ds.Tables["Table"].Rows)<br />
Side 76
{<br />
csv.CreateBAFile(dr);<br />
}<br />
Console.WriteLine("Uploading File");<br />
if (File.Exists(System.Environment.CurrentDirectory + "\\ba.csv"))<br />
{<br />
ftp.UploadList("ba.csv");<br />
ftp.UploadList("ba.xml");<br />
csv.DeleteFile();<br />
}<br />
else<br />
{<br />
Console.WriteLine("BA not uploaded - file does not exist");<br />
MagicalL<strong>og</strong>File.MakeL<strong>og</strong>Entry("BA not uploaded - file does not exist");<br />
}<br />
}<br />
else<br />
{<br />
MagicalL<strong>og</strong>File.MakeL<strong>og</strong>Entry("Could not get dataset when attempting to retrieve business areas");<br />
ErrorMailGenerator.SendErrorMail("Could not get dataset when attempting to retrieve business areas");<br />
}<br />
}<br />
#endregion<br />
#region Pref Comm Methods<br />
csv.FileName = System.Environment.CurrentDirectory + "\\pref_comm.csv";<br />
sql[0] = @"SELECT Preferred_Com_Methods.Preferred_Com_Methods_Id, Preferred_Com_Methods.Contact_Id,<br />
Preferred_Com_Methods.Lead__Id, Preferred_Com_Methods.Type<br />
FROM Preferred_Com_Methods, Silverpop_Out<br />
WHERE Preferred_Com_Methods.Contact_Id = Silverpop_Out.Contact_Id<br />
OR Preferred_Com_Methods.Lead__Id = Silverpop_Out.Lead_Id";<br />
db.Sql = sql;<br />
Console.WriteLine("Getting Data");<br />
using (DataSet ds = db.GetData())<br />
{<br />
if (ds != null)<br />
{<br />
Console.WriteLine("Making file");<br />
foreach (DataRow dr in ds.Tables["Table"].Rows)<br />
{<br />
csv.CreatePrefCommFile(dr);<br />
}<br />
Console.WriteLine("Uploading File");<br />
if (File.Exists(System.Environment.CurrentDirectory + "\\pref_comm.csv"))<br />
{<br />
ftp.UploadList("pref_comm.csv");<br />
ftp.UploadList("pref_comm.xml");<br />
csv.DeleteFile();<br />
}<br />
else<br />
{<br />
Console.WriteLine("pref comms not uploaded - file does not exist");<br />
MagicalL<strong>og</strong>File.MakeL<strong>og</strong>Entry("pref comms not uploaded - file does not exist");<br />
}<br />
}<br />
else<br />
{<br />
MagicalL<strong>og</strong>File.MakeL<strong>og</strong>Entry("Could not get dataset when attempting to retrieve Preferred Communication Methods");<br />
ErrorMailGenerator.SendErrorMail("Could not get dataset when attempting to retrieve Preferred Communication Methods");<br />
}<br />
}<br />
#endregion<br />
Side 77
String[] jobids = new String[NUMBER_OF_FILES];<br />
if (api.ApiL<strong>og</strong>in())<br />
{<br />
Console.WriteLine("Successfully l<strong>og</strong>ged into <strong>silverpop</strong> api");<br />
#region Start Imports<br />
//start the imports and get job ids<br />
Console.WriteLine("Attempting to import: MainList");<br />
api.ListImport("MainList");<br />
jobids[0] = api.JobId;<br />
Console.WriteLine("Job ID for import is: {0}", api.JobId);<br />
Console.WriteLine("Attempting to import: products");<br />
api.TableImport("products");<br />
jobids[1] = api.JobId;<br />
Console.WriteLine("Job ID for import is: {0}", api.JobId);<br />
Console.WriteLine("Attempting to import: ba");<br />
api.TableImport("ba");<br />
jobids[2] = api.JobId;<br />
Console.WriteLine("Job ID for import is: {0}", api.JobId);<br />
Console.WriteLine("Attempting to import: pref comms");<br />
api.TableImport("pref_comm");<br />
jobids[3] = api.JobId;<br />
Console.WriteLine("Job ID for import is: {0}", api.JobId);<br />
#endregion<br />
//start a threadpool to wait for each import to complete<br />
List events = new List();<br />
bool started = false;<br />
bool errorsEncountered = false;<br />
foreach (string i in jobids)<br />
{<br />
started = false;<br />
}<br />
Console.WriteLine("Item added to threadpool for jobid: " + i);<br />
ManualResetEvent mre = new ManualResetEvent(false);<br />
ThreadPool.QueueUserWorkItem(<br />
delegate(object o)<br />
{<br />
SilverpopAPIWatcher apiw = new SilverpopAPIWatcher(i, api);<br />
started = true;<br />
if (apiw.GetJobStatus() == true) errorsEncountered = true;<br />
((ManualResetEvent)o).Set();<br />
},mre);<br />
events.Add(mre);<br />
while (!started)<br />
{<br />
Console.WriteLine("It's not ready yet");<br />
}<br />
Console.WriteLine("All Items added, waiting for imports to finish...");<br />
WaitHandle.WaitAll(events.ToArray());<br />
if (errorsEncountered)<br />
{<br />
MagicalL<strong>og</strong>File.MakeL<strong>og</strong>Entry("Errors were encountered during API Import Operations, earlier l<strong>og</strong> entries should include more<br />
information");<br />
ErrorMailGenerator.SendErrorMail("Errors were encountered during API Import Operations, earlier l<strong>og</strong> entries should include more<br />
information");<br />
Environment.Exit(0);<br />
}<br />
api.ApiL<strong>og</strong>out();<br />
Console.WriteLine("Emptying Silverpop_Out Table");<br />
Side 78
}<br />
}<br />
}<br />
sql[0] = "TRUNCATE TABLE Silverpop_Out";<br />
db.Sql = sql;<br />
db.RunCommands();<br />
//Console.WriteLine("Press Enter to exit...");<br />
//Console.ReadLine();<br />
}<br />
else<br />
{<br />
Console.ForegroundColor = ConsoleColor.Red;<br />
Console.WriteLine("ERROR LOGGING INTO API - CHECK USERNAME AND PASSWORD");<br />
}<br />
//send an error mail<br />
ErrorMailGenerator.SendErrorMail("Couldn't l<strong>og</strong> into the api, please check the username and password");<br />
Environment.Exit(0);<br />
Side 79
MainList.xml<br />
<br />
<br />
<br />
ADD_AND_UPDATE<br />
6834860<br />
1<br />
0<br />
<br />
<br />
<br />
Contact Id<br />
0<br />
<br />
<br />
Lead Id<br />
0<br />
<br />
<br />
First Name<br />
0<br />
<br />
<br />
Last Name<br />
0<br />
<br />
<br />
Job Category<br />
0<br />
<br />
<br />
Country<br />
0<br />
<br />
<br />
EMAIL<br />
9<br />
true<br />
<br />
<br />
Company Id<br />
0<br />
<br />
<br />
Company Name<br />
0<br />
<br />
<br />
RSM First Name<br />
0<br />
<br />
<br />
RSM Last Name<br />
0<br />
<br />
<br />
RSM Job Title<br />
0<br />
<br />
<br />
RSM Company<br />
0<br />
<br />
<br />
RSM EMail<br />
0<br />
<br />
<br />
Side 80
RSM Phone<br />
0<br />
<br />
<br />
BA Analytical Labs<br />
0<br />
<br />
<br />
BA Beer<br />
0<br />
<br />
<br />
BA Biofuel<br />
0<br />
<br />
<br />
BA Chemicals<br />
0<br />
<br />
<br />
BA Confectionery<br />
0<br />
<br />
<br />
BA Dressings and Condiments<br />
0<br />
<br />
<br />
BA Edible Oils and Fats<br />
0<br />
<br />
<br />
BA Feed and Forage<br />
0<br />
<br />
<br />
BA Flour<br />
0<br />
<br />
<br />
BA Grain<br />
0<br />
<br />
<br />
BA Meat<br />
0<br />
<br />
<br />
BA Microbiol<strong>og</strong>y<br />
0<br />
<br />
<br />
BA Milk and Dairy<br />
0<br />
<br />
<br />
BA Pet Food<br />
0<br />
<br />
<br />
BA Pharmaceutical<br />
0<br />
<br />
<br />
BA Sugar<br />
0<br />
<br />
<br />
Side 81
BA Water and Soil<br />
0<br />
<br />
<br />
BA Wine<br />
0<br />
<br />
<br />
NEK<br />
0<br />
true<br />
true<br />
<br />
<br />
<br />
<br />
1<br />
Contact Id<br />
true<br />
<br />
<br />
2<br />
Lead Id<br />
true<br />
<br />
<br />
3<br />
First Name<br />
true<br />
<br />
<br />
4<br />
Last Name<br />
true<br />
<br />
<br />
5<br />
Job Category<br />
true<br />
<br />
<br />
6<br />
Country<br />
true<br />
<br />
<br />
7<br />
EMAIL<br />
true<br />
<br />
<br />
8<br />
Company Id<br />
true<br />
<br />
<br />
9<br />
Company Name<br />
true<br />
<br />
<br />
10<br />
RSM First Name<br />
true<br />
<br />
<br />
11<br />
RSM Last Name<br />
Side 82
true<br />
<br />
<br />
12<br />
RSM Job Title<br />
true<br />
<br />
<br />
13<br />
RSM Company<br />
true<br />
<br />
<br />
14<br />
RSM EMail<br />
true<br />
<br />
<br />
15<br />
RSM Phone<br />
true<br />
<br />
<br />
16<br />
BA Analytical Labs<br />
true<br />
<br />
<br />
17<br />
BA Beer<br />
true<br />
<br />
<br />
18<br />
BA Biofuel<br />
true<br />
<br />
<br />
19<br />
BA Chemicals<br />
true<br />
<br />
<br />
20<br />
BA Confectionery<br />
true<br />
<br />
<br />
21<br />
BA Dressings and Condiments<br />
true<br />
<br />
<br />
22<br />
BA Edible Oils and Fats<br />
true<br />
<br />
<br />
23<br />
BA Feed and Forage<br />
true<br />
<br />
<br />
24<br />
BA Flour<br />
true<br />
<br />
<br />
Side 83
25<br />
BA Grain<br />
true<br />
<br />
<br />
26<br />
BA Meat<br />
true<br />
<br />
<br />
27<br />
BA Microbiol<strong>og</strong>y<br />
true<br />
<br />
<br />
28<br />
BA Milk and Dairy<br />
true<br />
<br />
<br />
29<br />
BA Pet Food<br />
true<br />
<br />
<br />
30<br />
BA Pharmaceutical<br />
true<br />
<br />
<br />
31<br />
BA Sugar<br />
true<br />
<br />
<br />
32<br />
BA Water and Soil<br />
true<br />
<br />
<br />
33<br />
BA Wine<br />
true<br />
<br />
<br />
34<br />
NEK<br />
true<br />
<br />
<br />
<br />
Side 84