Despre mashupuri cu date/time.

Am preluat licitatiile bancare de pe 4 site-uri : BancaRomaneasca, Raiffeisen, Leumi, AlphaBank.

Initial erau 2 : BancaRomaneasca si Raiffeisen. M-am uitat pe site-uri si am vazut structura datelor – pusesera data ( iar Raiffeisen chiar si ora!).

Asa incit in baza mea de date a aparut un cimp, numit DataLicitatiei – simplu, nu ?

Dar nu e chiar asa : la Leumi nu are data! Bun, ce sa fac ca sa nu schimb structura BD si programul(DAL) prea mult ? Pun DateTime.MaxValue  – si consider ca este Not Available.

A venit cireasa de pe tort : AlphaBank – unde au pus doar luna ! Asa incit nu am avut o solutie buna – decit sa pun tot DateTime.MaxValue pentru orice licitatie din luna > luna curenta.

Concluzie : daca preluati informatii de pe Web, si nu sunteti sigur de formatul datelor , sparge-ti-l. Ca sa nu ajungeti ca mine sa regretati ca nu ati pus de la inceput An, Luna, Zi in BD.

Cursurile de schimb de la ING Online si (Firebug +Firefox versus IE8)

Din aventurile parsarii paginii de cursuri valutare de la ING :

Mai intii, cum se obtin din browser : https://www.ingonline.com/ro/ , se apasa pe “rate de schimb” din dreapta, apare “Cursuri de schimb” – si se apasa pe el.

Am zis ca e simplu de luat pagina – dar nu e chiar asa.

Prima pagina ,https://www.ingonline.com/ro/ , are un iframe – OK, nimic deosebit. Dar pagina la care face referire iframe are un jscript care scrie menu-ul din dreapta…deci cam greu de obtinut fara browser. Nu-i nimic –sa vedem ce face “Cursuri de schimb” – face doar selChild(17).

Am vazut codul din JS  *dar cum sa vad menu-ul 17? M-am uitat in js-uri –dar erau un pic prea complicate(ma rog, mi-a placut

var sess_length = 24*60*.0104166666666666666666666666666666666667; //logoff time in minutes

) Aici firefox + firebug a fost de ajutor. M-am dus pe pagina, am selectat din menu-ul lui Firebug sa afiseze js-ul si am pus breakpoint pe functie. Si gata – am vazut valoarea

124349_Print Screen

Acelasi lucru l-am obtinut si cu IE8, developer tools(F12).Am pus start debugging, am ales scriptul cu pricina – si voila!

010017_ING Online Banking  Developer Tools_active

 

Din pacate, m-am oprit aici ., pentru ca https://www.ingonline.com/ro/directcertro/!UPR.Dispatcher?ps_redir_proc=exchangerates face redirect la home page  . De ce? Mister deocamdata – dar poate ma ajuta cineva…

ASPOSE si Documentele Word

Pentru infovalutar (mai exact, pentru mine …) am vrut sa preiau licitatiile de la banci.

Pentru rezultatul final, vezi http://infovalutar.ro/licitatie

Dar sa vedem care a fost povestea  :am inceput cu preluarea paginilor HTML . Incepusem cu  HttpWebRequest – dar am descoperit la timp HtmlAgilityPack si am ramas credincios lui.

Acum, dupa preluarea paginilor HTML( de ex., http://www.banca-romaneasca.ro/main.php?did=527&code=executare+silita) a fost de ajuns un XPath + expresie regulata de parsare a text-ului din interior.

Ce mi-a produs batai de cap a fost http://vanzari.leumi.ro/bunuri_imobile.html – aveau bunurile in document Word! Ori, ca sa ii ceri celui de la Hosting sa instaleze Word-ul ca sa il instantiezi tu in ASP.NET e aproape imposibil!

Solutia : ASPOSE.WORDS  – citeste documente dintre cele mai diverse si scoate un TXT superb – si asta, fara sa aiba nevoie de WORD instalat(se prea poate sa fi omorit muste cu tunul …)

Ca folosire, trebuia sa ii dau un Stream – dar cind am incercat sa ii dau stream-ul de document, mi-a zis ca nu suporta Seek. Asa incit am rezolvat cu un MemoryStream :

public string LeumiData(string URL)
        {

            byte[] buffer = new byte[1024*1024*4];

            HttpWebRequest hwr = WebRequest.Create(URL) as HttpWebRequest;
            using (WebResponse response = hwr.GetResponse())
            {
                using (Stream responseStream = response.GetResponseStream())
                {
                    using (MemoryStream memoryStream = new MemoryStream())
                    {
                        int count = 0;
                        do
                        {
                            count = responseStream.Read(buffer, 0, buffer.Length);
                            memoryStream.Write(buffer, 0, count);

                        } while (count != 0);

//ASPOSE
                        Document d = new Document(memoryStream);
                        return d.ToTxt();

                    }
                }
            }

        }

Pot sa spun ca ASPOSE, daca vreti manipulare de documente, face toti banii!

Structure Map

La ce foloseste StructureMap ? Raspunsul pe scurt  : este un tool simplu de DI

Raspunsul pe lung : in postul precedent, vroiam sa testez un export de fisiere fara sa ating BD.

Initial, codul arata cam asa :

public ActionResult ExportDate(string id)
{

DateExport fe = new DateExport();
fe.id = id;

export exp = new export(Server.MapPath("~/bin/Templates"));

FileContentResult fcr = new FileContentResult(exp.Export(fe), "application/ms-word");
fcr.FileDownloadName = fe.Number + ".doc";

return fcr;

}

unde variabila fe din

DateExport fe = new DateExport();

atingea BD in momentul in care cineva ii cerea niste date.

Vroiam sa o inlocuiesc cu o variabila ce trimite null ca date si apoi cu variabila care trimite niste date fake.

Ca sa fac acest lucru, am extras metodele care ma interesau din DateExport, am facut o interfata din ele IDateExport, am inlocuit vparametrul din functia export ce cerea un DateExport cu IDateExport si am folosit StructureMap :

In global.asax am definit cererea default :

ObjectFactory.Initialize(x =>
            {
                x.ForRequestedType<IDateExport>().TheDefault.Is.ConstructedBy(() => new DateExport(HttpContext.Current.User.Identity.Name));
            });

si apoi in metoda am inlocuit new cu ObjectFactory :

public ActionResult ExportDate(string id)
{

IDateExport fe = ObjectFactory.GetInstance<IDateExport>();
fe.id = id;

export exp = new export(Server.MapPath("~/bin/Templates"));

FileContentResult fcr = new FileContentResult(exp.Export(fe), "application/ms-word");
fcr.FileDownloadName = fe.Number + ".doc";

return fcr;

}

Acum testul automat a devenit floare la ureche – sa zicem ca vreau ca metodele din IDataExport sa nu aduca nimic –si sa testez acest lucru . Creez o clasa DataExportNotFind, implementez IDataExport ca sa nu aduca nimic si scriu in test :

ObjectFactory.Initialize(x =>
{
    x.ForRequestedType<IDataExport>().TheDefaultIsConcreteType<DataExportNotFind>();
});

 

Si asta e tot!

As vrea sa pun accent pe faptul ca , desi testez rapid pe masina proprie facind un fake la BD, totusi, la integrare, ar trebui sa aveti teste cu BLL / UI care sa testeze NEAPARAT cu BD …

Rhino Mock

Destul de tirziu mi-am dat seama de beneficiile aduse de un Mock – dar mai bine mai tirziu decit niciodata …

Ma gindeam ca niciodata nu o sa il folosesc – ca ajunge sa verific BLL cu unit test(NUNIT/VS Test) , site-ul Web cu NUnit ASP/WATI(N|R) / Selenium , Windows Forms cu NUnitForms si nu o sa am nevoie de Mock.

Adevarul este ca da, nu as avea nevoie de Mock … decit daca as vrea sa verific mai repede unele date, fara sa ating BD.De exemplu, pot sa verific controller-ele fara sa am nevoie sa instantiez HttpContext si BD. Sa zicem ca am un controller care are o actiune ce doar exporta un fisier Word– bazat pe un template. Codul din fisier  arata cam asa :

public ActionResult ExportDate(string id)
{

IDateExport fe = ObjectFactory.GetInstance<IDateExport>();
fe.id = id;

export exp = new export(Server.MapPath("~/bin/Templates"));

FileContentResult fcr = new FileContentResult(exp.Export(fe), "application/ms-word");
fcr.FileDownloadName = fe.Number + ".doc";

return fcr;

}

Daca as vrea sa verific rapid metoda aceasta ar trebui sa nu ating baza de date si sa am pun un rezultat in loc de Server.MapPath ?

Se vede clar ca deja folosesc StructureMap, deci nu ar fi o problema cu gasitul a niste date fake. Dar pentru Server.MapPath intervine stralucit Mock.

Am preluat de la Hanselmann MVC Mock Helpers ,iar codul de test arata cam asa :

 

exportController i = new exportController();
MockRepository mocks = new MockRepository();
using (mocks.Record())
{
    //MvcMockHelpers.SetFakeControllerContext(mocks, i);
    mocks.SetFakeControllerContext(i);
   SetupResult.For(i.ControllerContext.HttpContext.Server.MapPath(null)).IgnoreArguments().Return(@"c:\programs\templates");      // cod pentru chemarea Server.MapPath          
    mocks.ReplayAll();
}
using (mocks.Playback())
{
    ObjectFactory.Initialize(x =>
    {
        x.ForRequestedType<IExport>().TheDefaultIsConcreteType<FactFind>();// FactFind nu atinge BD
    });
    FileContentResult fcr = i.exportdate("865", "A") as FileContentResult;
    fcr.ShouldNotBeNull();
    fcr.FileContents.Length.ShouldBeGreaterThan(0);//TODO : Verifica si continutul
}

Tips and trick cu Entity Framework

 

1. Cum obtineti Connection String curat din Entity Framework Connection String

Sa zicem ca aveti definit connection string pentru EF in (web|app).config , ceva de genul :

<connectionStrings>
      <add name="iERPEntities" connectionString="metadata=res://*/ERP.csdl|res://*/ERP.ssdl|res://*/ERP.msl;provider=System.Data.SqlClient;provider connection string=&quot;Data Source=PC;Initial Catalog=ERP;Persist Security Info=True;User ID=x;Password=xy;MultipleActiveResultSets=True&quot;" providerName="System.Data.EntityClient" />

 

Si vreti sa preluati rezultatele de la o procedura stocata sau doar sa o executati, fara sa va mai chinuiti sa o treceti prin EF (mai mare deranjul). In consecinta va trebuie connection string-ul.

2. Cum faci Union – sau Join -  intre 2 IQueryable – ca sa iti dea macar una din ele, daca cealalta e null ?

3. Daca stii cheia unui obiect  -de obicei long ID  – cum obtii obiectul ?

4. Cum poti vedea ce genereaza, ca string sql, entitycontext – fara sa stai cu profiler-ul pe Sql Server ?

5. Daca vreti sa faceti o tabela pentru ca sa accesati/modificati continutul cu EF, ce este BestPractices de definit la ea ?

6. Cum validati un obiect in EF inainte de a salva ?

Va las sa va bateti capul! Raspunsurile mai jos…

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Rezolvarea 1.

string sqlc = "";
            using (ERPEntities e = new ERPEntities("name=iERPEntities"))
            {
                EntityConnection ec = e.Connection as EntityConnection;
                sqlc = ec.StoreConnection.ConnectionString;
            }

De ce e.Connection nu e direct EntityConnection si trebuie cast-uit, depaseste imaginatia mea …

Rezolvarea 2.

public static IQueryable<T> coalesceIntersect<T>(IQueryable<T> x, IQueryable<T> y)
           where T : class
        {

            if (x == null)
                return y;

            if (y == null)
                return x;

            return Queryable.Intersect<T>(x, y);

        }

Rezolvarea 3.

/// <summary>
        /// see also http://msdn.microsoft.com/en-us/library/bb896251.aspx
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="o"></param>
        /// <param name="ID"></param>
        /// <param name="Include"></param>
        /// <returns></returns>
        public static T FindFromPrimaryKey<T>(this ObjectQuery<T> o, long ID, params string[] Include)
            where T : EntityObject
        {
            Type t = typeof(T);
            string FullPath = t.FullName;
            if (!KeysForObject.ContainsKey(FullPath))
            {
                lock (KeysForObject)
                {
                    if (!KeysForObject.ContainsKey(FullPath))
                    {
                        foreach(PropertyInfo pi in t.GetProperties())
                        {
                            object[] attr = pi.GetCustomAttributes(typeof(EdmScalarPropertyAttribute),false);
                            if(attr != null && attr.Length ==1)
                            {
                                EdmScalarPropertyAttribute a=attr[0] as EdmScalarPropertyAttribute;
                                if(a.EntityKeyProperty)
                                {
                                    KeysForObject.Add(FullPath,pi.Name);
                                    break;
                                }
                            }
                        }
                    }
                }
            }
           

if(Include != null && Include.Length > 0)
            {
                foreach(string s in Include)
                {
                    o=o.Include(s);
                }
            }
return o.Where("it." + KeysForObject[FullPath] + " = " + ID).FirstOrDefault();

Rezolvarea 4.

http://forums.microsoft.com/msdn/ShowPost.aspx?PostID=1555711&SiteID=1

Rezolvarea 5.

Definiti neaparat Primary Key ( eu de obicei pun un bigint identity )

Rezolvarea 6.

Vedeti http://msdn.microsoft.com/en-us/library/cc716714.aspx

Adaugati o clasa partiala de tipul entitatii in care puneti partial pe OnContextCreated ca sa interceptati SavingChanges

partial class Entities
    {

partial void OnContextCreated()
        {
       

            this.SavingChanges += new EventHandler(Entities_SavingChanges);
        }

}

Eu am facut apoi o interfata IValidate de felul urmator – simplist – :

public enum ISValid
    {
        Yes,
        No,
        SendDataContext
    }
    public interface IValidate
    {
        ISValid IsValidForSaving(ObjectStateEntry ose, out string mesaj);
    }
    public interface IValidateDataContext // daca am nevoie de verificari mai ample cu alt obiect …
    {
        bool IsValidForSavingDataContext(ObjectContext oc, ObjectStateEntry ose, out string mesaj);
    }

Apoi pe SavingChanges :

void Entities_SavingChanges(object sender, EventArgs e)
        {
                     Entities context = sender as Entities;
            foreach (ObjectStateEntry ose in context.ObjectStateManager.GetObjectStateEntries(EntityState.Added | EntityState.Modified))
            {
                if (ose.Entity == null)
                    continue;
                IValidate iv = ose.Entity as IValidate;
                if (iv != null)

{

string mesaj;
                   switch (iv.IsValidForSaving(ose, out mesaj))
                   {
                       case ISValid.No:
                           throw new NotValidException(ose.Entity.GetType().Name, mesaj);
                       case ISValid.Yes:
                           break;
                       case ISValid.SendDataContext:
                           IValidateDataContext ivDC = ose.Entity as IValidateDataContext;

                           if(!ivDC.IsValidForSavingDataContext(context,ose, out mesaj))
                               throw new NotValidException(ose.Entity.GetType().Name, mesaj);
                           break;
                   }

}

Ok, se putea si mai bine, cu IDataErrorInfo – dar … sa o lasam asa deocamdata

Primii 10 pasi cu ASP.NET MVC

  1. Download asp.net mvc 1.0 from http://www.asp.net/mvc/download/
  2. Pentru IIS6 – cititi http://haacked.com/archive/2008/11/26/asp.net-mvc-on-iis-6-walkthrough.aspx . Practic mapati toate extensiile (.*) la asp.net si o sa va mearga si pe IIS6.
  3. La Project =>references => System.Web.Mvc => properties => CopyLocal la true – in eventualitatea ca providerul de hosting nu are ASP.NET MVC
  4. Descarcati un template vizual(daca nu sunteti buni la web design!) de lahttp://www.asp.net/mvc/gallery/ si suprascrieti site-ul .
    • Daca va da “The type or namespace name ‘Helpers’ could not be found (are you missing a using directive or an assembly reference?)” atunci e cazul sa dati “Show all files” si pe urma sa includeti fisierele lipsa . Recompilati. Rulati.Vedeti referintele pentru CSS.
  5. Stabiliti o clasa model pentru Master   – in care puneti cel putin o proprietate User – user-ul curent .  Schimbati inherits la master in “Inherits="System.Web.Mvc.ViewMasterPage<namespace.clasamea>" .Schimbati si in about si in home : return View() in Return view(new clasamodel{});  ; Nu uitati sa schimbati master-ul la error.aspx, pentru ca vrea derivat din System.Web.Mvc.HandleErrorInfo – si sa verificati !
  6. Modificati proiectul sa fie Web ( Project => Properties =>Web => Use Local IIS WebServer si apasati pe CreateVirtualDirectory) . Compilati si rulati. Daca va apare ciudat, refaceti referintele la Css din Views=>Shared>Site Master
  7. Adaugati routedebugger(http://haacked.com/archive/2008/03/13/url-routing-debugger.aspx) si ginditi-va / verificati-va rutele!
  8. MVC Contrib – pentru grid http://mvccontrib.codeplex.com/
  9. adaugati whitespace , output cache – pentru optimizari
  10. Daca vreti sa folositi Handlere de eroare pe Controlere, nu uitati de customErrors la RemoteOnly ( sau On).
  11. Facultativ : Nunit , NBehave, log4net, PostSharp ,log4postsharp, RhinoMocks (nu uitati ca merge doar cu metoda virtuale…), NBuilder , Structure Map , Selenium ,AutoMapper si alte frameworkuri.

Voi ce alte frameworkuri/tools-uri folositi ?

log4net si asp.net MVC – debug!

Pentru cei care se mai chinuie ca , desi au configurat log4net corect, in asp.net mvc nu logheaza nimic…

Inceput : pus un log4net.config, pus in global.asax in start Application :

System.IO.FileInfo s=new System.IO.FileInfo(Server.MapPath("log4net.config"));            
log4net.Config.XmlConfigurator.ConfigureAndWatch(s);

pus un un controller in homecontroller de tipul :

public ActionResult error()
        {
            return View("Error", new HandleErrorInfo(new ArgumentException("asd"), "home", "error" + _logger.IsErrorEnabled));
        }

 

Cind browsam http://localhost/site/home/error , imi afisa “error” FALSE

Cind faceam debug din VS ,imi afisa “error” TRUE .

Am innebunit cautind rezolvarea … si, dupa mai multe incercari nereusite, m-am hotarit sa fac debug.

In web.config sub <configuration> :

<appSettings>
        <add key="log4net.Internal.Debug" value="true"/>
    </appSettings>

<system.diagnostics>
        <trace autoflush="true">
            <listeners>
                <add
                  name="textWriterTraceListener"
                  type="System.Diagnostics.TextWriterTraceListener"
                  initializeData="<cale catre site>\log\log4net.txt" />

<!– vedeti sa aveti drept de scriere pentru user-ul de aspnet–>

            </listeners>

        </trace>

    </system.diagnostics>

<system.web>
        <trace enabled="true"/>

Si mi-a aparut :

log4net: XmlConfigurator: config file [<site>\home\log4net.config] not found.

Asa ca am schimbat  :

System.IO.FileInfo s=new System.IO.FileInfo(Server.MapPath("log4net.config"));            
cu

System.IO.FileInfo s=new System.IO.FileInfo(Server.MapPath("~/log4net.config"));  

 

SI GATA!

Issue Tracker

Aveam nevoie de un tracker de issue – pentru ca incepusem sa am prea multe cereri de modificare si nu ma mai descurcam. De fapt, la o a doua vedere, nu erau atit de multe cereri, cit detalii ale lor.

Am cerut pareri pe RONUA – dar ceea ce mi s-a oferit de acolo era pe bani (ma rog, OnTime se pare ca are oferte speciale..). Iar in criza actuala sa cer bani de la servici ca eu nu ma mai descurc suna cam prost…

Dupa ce am citit de mai multe , m-am decis pentru BugTracker.NET. Am avut doua probleme : Integrarea cu Active Directory – din fericire Web.Config este destul de clar scris – si trimiterea emailurilor. Nu vroiam sa configurez SMTP – ci doar sa isi ia setarile din Web.Config de forma:

<system.net>
        <mailSettings>
            <smtp>
                <network host="xxxx" port="25"/>
            </smtp>
        </mailSettings>
    </system.net>

Din nefericire el lucra cu System.Web.Mail – asta pentru ca ,zicea el, noul System.NET.Mail nu suporta SSL.

In fine – nu aveam nevoie de SSL –asa ca am muncit sa il transform – si am modificat pe ici, pe colo, prin partile esentiale email.cs:

/*
Copyright 2002-2008 Corey Trager
Distributed under the terms of the GNU General Public License
*/

using System;
using System.Collections;
using System.IO;
using System.Text;

// disable System.Net.Mail warnings
#pragma warning disable 618

namespace btnet
{

    public class Email {
        ///////////////////////////////////////////////////////////////////////
        public static string send_email( // 5 args
            string to,
            string from,
            string cc,
            string subject,
            string body)
        {
            return send_email(
                to,
                from,
                cc,
                subject,
                body,
                false,
                System.Net.Mail.MailPriority.Normal,
                null,
                false);
        }

        ///////////////////////////////////////////////////////////////////////
        public static string send_email( // 6 args
            string to,
            string from,
            string cc,
            string subject,
            string body,
            bool isbodyhtml)
        {
            return send_email(
                to,
                from,
                cc,
                subject,
                body,
                isbodyhtml,
                System.Net.Mail.MailPriority.Normal,
                null,
                false);
        }

        ///////////////////////////////////////////////////////////////////////
        public static string send_email(
            string to,
            string from,
            string cc,
            string subject,
            string body,
            bool isbodyhtml,
            System.Net.Mail.MailPriority priority,
            int[] attachment_bpids,
            bool return_receipt)
        {
            ArrayList files_to_delete = new ArrayList();
            ArrayList directories_to_delete = new ArrayList();
            System.Net.Mail.MailMessage msg = new System.Net.Mail.MailMessage(from, to);
            if (!string.IsNullOrEmpty(cc.Trim()))
            {
                msg.CC.Add(cc);
            }
            msg.Subject = subject;
            msg.Priority = priority;

            // This fixes a bug for a couple people, but make it configurable, just in case.
            if (Util.get_setting("BodyEncodingUTF8", "1") == "1")
            {
                msg.BodyEncoding = Encoding.UTF8;
            }

            if (return_receipt)
            {
                msg.Headers.Add("Disposition-Notification-To", from);
            }

            // workaround for a bug I don’t understand…
            if (Util.get_setting("SmtpForceReplaceOfBareLineFeeds", "0") == "1")
            {
                body = body.Replace("\n", "\r\n");
            }

            msg.Body = body;
            msg.IsBodyHtml = isbodyhtml;

            if (attachment_bpids != null && attachment_bpids.Length > 0)
            {

                string upload_folder =  btnet.Util.get_upload_folder();

                if (string.IsNullOrEmpty(upload_folder))
                {
                    upload_folder = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
                    Directory.CreateDirectory(upload_folder);
                    directories_to_delete.Add(upload_folder);
                }

                foreach (int attachment_bpid in attachment_bpids)
                {
                    byte[] buffer = new byte[16 * 1024];
                    string dest_path_and_filename;
                    Bug.BugPostAttachment bpa = Bug.get_bug_post_attachment(attachment_bpid);
                    using (bpa.content)
                    {
                        dest_path_and_filename = Path.Combine(upload_folder, bpa.file);
                        using (FileStream out_stream = new FileStream(
                            dest_path_and_filename,
                            FileMode.CreateNew,
                            FileAccess.Write,
                            FileShare.None))
                        {
                            int bytes_read =  bpa.content.Read(buffer, 0, buffer.Length);
                            while (bytes_read != 0)
                            {
                                out_stream.Write(buffer, 0, bytes_read);

                                bytes_read = bpa.content.Read(buffer, 0, buffer.Length);
                            }
                        }

                    }

                    System.Net.Mail.Attachment mail_attachment = new System.Net.Mail.Attachment(
                        dest_path_and_filename);
                    msg.Attachments.Add(mail_attachment);
                    files_to_delete.Add(dest_path_and_filename);
                }
            }

            try
            {
                // This fixes a bug for some people.  Not sure how it happens….
                msg.Body = msg.Body.Replace(Convert.ToChar(0), ‘ ‘).Trim();
                System.Net.Mail.SmtpClient s=new System.Net.Mail.SmtpClient();
                s.Send(msg);

                // We delete late here because testing showed that SmtpMail class
                // got confused when we deleted too soon.
                if (files_to_delete.Count > 0)
                {
                    foreach (string file in files_to_delete)
                    {
                        File.Delete(file);
                    }
                }

                if (directories_to_delete.Count > 0)
                {
                    foreach (string directory in directories_to_delete)
                    {
                        Directory.Delete(directory);
                    }
                }

                return "";
            }
            catch (Exception e)
            {
                Util.write_to_log("There was a problem sending email.   Check settings in Web.config.");
                Util.write_to_log("TO:" + to);
                Util.write_to_log("FROM:" + from);
                Util.write_to_log("SUBJECT:" + subject);
                Util.write_to_log(e.GetBaseException().Message.ToString());
                return (e.GetBaseException().Message);
            }

        }

    } // end Email

} // end namespace

NCover 2.0

Auzisem de NCover – dar nu avusesem posibilitatea sa il pun la lucru.

De ce vroiam sa il folosesc ? Simplu: aveam teste automate (NUNIT) pentru proiect –unele cu Baza de date, altele fara. Ceea ce vroiam sa aflu este cit de mult acopar din codul sursa – adica cit de mult testele sunt complete.

OK – citind putin a inceput sa devina evident cum sa il folosesc – desi are o curba initiala de invatat de vreo 5 minute(nu e evident)

Primul lucru, daca vreti sa il folositi cu NUnit, este sa incercati sa rulati proiectul NUnit din consola, ceva de genul

nunit-console “<fisier.nunit>" 

( daca vreti, puteti continua linia cu  /include=categorii_de_teste )

Daca a rulat OK ( adica fara erori… ) acum putem configura NCover . Porniti NCover Explorer, apasati CTRL+N si o sa vi se deschida o fereastra de comanda in care veti introduce calea catre nunit-console iar la application arguments calea catre fisierul NUnit.

071230_Run NCover_active

Rulati si o sa aveti rapid o evidenta in ce proportie codul din clase s-a executat. In plus va puteti uita direct intr-o metoda sa vedeti care cod s-a executat si care nu ( theme Underline mi se pare cea mai buna)

Generarea rapoartelor e buna – doar ca nu lanseaza fisierul htm generat si trebuie sa il gasiti singuri ( de aceea are “Explore coverage folder”)

Un tool exceptional, ce il puteti rula si din command line . Recomandat cu tarie!