BLOGas.lt
Pigūs skrydžiai
Sukurk savo BLOGą Kitas atsitiktinis BLOGas

Ypač paprastas Windows Service kūrimo būdas

Parašė Sergejus | 2013-03-03 18:41

Daugiau negu prieš 2 metus rašiau apie nuosavos bazinės Windows Service klasės kūrimą. Nuo to laiko visi mano kurti Windows servisai buvo daugiau mažiau panašūs. Nepaisant fakto, kad naujų Windows servisų kūrimas nėra ypatingai sudėtingas procesas, jis visada atrodė man per daug komplikuotas. Bet situacija pasikeitė kai atradau Topshelf. Ši biblioteka ženkliai supaprastina Windows servisų kūrimą bei jų valdymą. Tarkime, turime klasę MyService su metodais Start bei Stop, žemiau parodyta kaip ją galima apvilkti į Windows servisą Topshelf pagalba:

public class MyService
{
    public void Start() { /* service start logic */ }
    public void Stop() { /* service stop logic */ }
}

public class Program
{
    static void Main(string[] args)
    {
        var restartDelay = (int) TimeSpan.FromMinutes(1).TotalMinutes;

        HostFactory.Run(config =>
        {
            config.Service<MyService>(svc =>
            {
                svc.ConstructUsing(s => new MyService());
                svc.WhenStarted(s => s.Start());
                svc.WhenStopped(s => s.Stop());
            });
            config.SetServiceName("MyService");
            config.SetDisplayName("My service");
            config.SetDescription("My service via Topshelf");
            config.RunAsLocalService();
            config.DependsOnEventLog();
            config.StartAutomatically();
            config.EnableServiceRecovery(recovery => recovery.RestartService(restartDelay));
            config.UseNLog();
        });
    }
}

Kaip matote, mes nusakome ne tik kokį metodą iškviesti paleidžiant Windows servisą ar jį stabdant, bet ir kieno vardu jis bus vykdomas, nuo kokių kitų Windows servisų priklausomas, kaip yra paleidžiamas bei ką daryti jeigu servisas nulūš. Papildomai, jūsų programą galima bus paleisti tiek kaip Windows servisą, tiek iš konsolės.

Malonaus Windows servisų programavimo su Topshelf!

Rodyk draugams

Objektų įrašymas ir nuskaitymas iš failų

Parašė Sergejus | 2013-01-09 20:46

Ar esate kada bandę išsaugoti objektų aibę į failą ir vėliau juos nuskaityti? Egzistuoja keli būdai kaip tai padaryti, bet šiandien norėčiau parodyti mano manymu vieną patogiausių, naudojant Protocol Buffers duomenų formatą. Efektyvus darbas su failais dažnai susiveda į srautų (angl. Stream) panaudojimą, todėl mums reikalingas būdas paversti objektą į binarinį formatą ir rezultatą išsaugoti į srautą. Tam aprašysime interfeisą IStreamSerializer:

public interface IStreamSerializer
{
    T Deserialize<T>(Stream stream) where T : class;
    void Serialize<T>(T obj, Stream stream) where T : class;
}

Darbui su Protocol Buffers iš .NET rekomenduoju Marc Gravell biblioteką protobuf-net:

PM> Install-Package protobuf-net

ProtocolBuffersStreamSerializer realizacija galėtų atrodyti taip:

public class ProtocolBuffersStreamSerializer : IStreamSerializer
{
    public T Deserialize<T>(Stream stream) where T : class
    {
        if (stream == null || stream.Length == 0) return null;

        return Serializer.DeserializeWithLengthPrefix<T>(stream, PrefixStyle.Base128);
    }

    public void Serialize<T>(T obj, Stream stream) where T : class
    {
        if (obj == null || stream == null) return;

        Serializer.SerializeWithLengthPrefix(stream, obj, PrefixStyle.Base128);
    }
}

Atkreipkite dėmesį į metodus (Deserialize|Serialize)WithLengthPrefix. Tam, kad suprasti kam jie reikalingi, pirma riekia suprasti kaip saugomi serializuoti objektai faile. Failas yra tik baitų seka, todėl reikalingas būdas vieną objektą atskirti nuo kito. Toks skirtukas dažnai vadinamas prefiksu ir savyje saugo papildomą informaciją, pavyzdžiui, serializuoto objekto dydį:

image

Norint nuskaityti pavyzdyje pateiktą baitų seką pirma nuskaitome prefiksa, o tada tiek baitų kiek nurodyta prefikse. Operaciją kartojame kol nebus nuskaityti visi baitai. Iš kodo pusės objektų įrašymas / nuskaitymas iš failo galėtų atrodyti taip:

[ProtoContract]
public class FakeData
{
    [ProtoMember(1)]
    public int Sequence { get; set; }

    [ProtoMember(2)]
    public string[] Data { get; set; }
}

[Test]
public void BinaryDataExample()
{
    var path = "d:\\Temp.txt";
    var serializer = new ProtocolBuffersStreamSerializer();

    using (var stream = File.Create(path))
    {
        for (int i = 0; i < 10; i++)
        {
            var fakeData = GenerateFakeData(i);
            serializer.Serialize(fakeData, stream);
        }
    }

    using (var stream = File.OpenRead(path))
    {
        FakeData fakeData;
        while ((fakeData = serializer.Deserialize<FakeData>(stream)) != null)
        {
            Console.WriteLine(fakeData.Sequence);
        }
    }
}

private static FakeData GenerateFakeData(int sequence)
{
    var count = new Random().Next(1, 5);
    var fakeData = new FakeData
    {
        Sequence = sequence,
        Data = new string[count]
    };

    for (var i = 0; i < count; i++)
    {
        fakeData.Data[i] = Guid.NewGuid().ToString("N");
    }

    return fakeData;
}

Kaip matote, objektų įrašymas ir nuskaitymas iš failų nėra sudėtingas procesas, bet jis reikalauja tam tikro suvokimo apie binarinių duomenų saugojimą.

Rodyk draugams

MFMT (Set Modified Date) FTP komandos iškvietimas iš .NET

Parašė Sergejus | 2012-08-29 20:56

Prieš kelias dienas susidūriau su įdomiu uždaviniu – suvienodinti per FTP kopijuojamų failų modifikavimo datą ir laiką tarp visų klasterio serverių. Pirma paaiškinsiu problemą: klasteryje yra kelios dešimtys serverių, todėl kopijuojant failus jų modifikavimo data gali skirtis keliomis sekundėmis. Tarkim, vartotojas kreipiasi į serverį, parsisiunčia failą ir jį užkešuoja. Už sekundės jis kreipiasi vėl to paties failo, patenka į kitą klasterio serverį ir tas potencialiai gali pasakyti, kad failo modifikavimo data yra keliomis sekundėmis naujesnė. Rezultate vartotojas turės parsisiųsti failą iš naujo, nors tai yra tas pats failas. Kaip matyti, be reikalo išnaudojamas tiek procesorius, tiek tinklas.

Grįžtant prie problemos. Norint pakeisti nutolusiame FTP esančio failo datą, reikia įvykdyti komandą MFMT (Modify Fact: Modification Time) arba MFCT (Modify Fact: Creation Time). Šios komandos dar nėra oficialaus standarto dalis, bet mūsų naudojamas FileZilla FTP serveris jas palaiko (informacijai: IIS FTP serveris šių komandų nepalaiko). Kai jau galvojau, kad užteks tiesiog iškviesti MFMT komandą pasirodė, kad standartinė .NET FtpWebRequest klasė neturi nei tokio metodo, nei kito būdo ją iškviesti. Teko keisti FTP biblioteką į kitą alternatyvą – Alex FTPS Library (prieinamas ir iš NuGet galerijos). Nors minėta biblioteka irgi neturi reikalingo metodo, ji turi galimybę pačiam suformuoti ir išsiųsti norimą komandą. Taip gimė praplėtimo metodas SetModifiedTime:

internal static class FTPSClientExtensions
{
    public static void SetModifiedDate(this FTPSClient client, string path, DateTime lastModified)
    {
        var command = string.Format("MFMT {0} {1}", lastModified.ToString("yyyyMMddHHmmss"), path);
        client.SendCustomCommand(command);
    }
}

Taigi, iš pirmo žvilgsnio paprasta užduotis (MFMT komandos iškvietimas) pasibaigė visos FTP bibliotekos keitimu…

Rodyk draugams

NSubstitute – rimtas Moq konkurentas

Parašė Sergejus | 2012-07-04 17:46

Jau kelis metus Moq yra populiariausias netikrų objektų kūrimo karkasas .NET pasaulyje. Moq labai praverčia testavime, kai norima aprašyti netikrą objektą, kuris tenkina tam tikrą interfeisą. Tarkime, mes turime klasę FileSystem, kuri yra ne kas kitas, kaip failų sistemos abstrakciją bei klasę FileFinder, skirtą failų paieškai:

public class FileFinder
{
    private readonly IFileSystem fileSystem;

    public FileFinder()
        : this(new FileSystem())
    {
    }

    public FileFinder(IFileSystem fileSystem)
    {
        this.fileSystem = fileSystem;
    }

    public IEnumerable<string> Find(string dir, string filter, bool recursively = true)
    {
        var options = recursively ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly;
        return this.fileSystem.GetDirectoryFiles(dir, filter, options);
    }
}

public interface IFileSystem
{
    IEnumerable<string> GetDirectoryFiles(string dir, string filter, SearchOption options);
}

public class FileSystem : IFileSystem
{
    public IEnumerable<string> GetDirectoryFiles(string dir, string filter, SearchOption options)
    {
        return Directory.GetFiles(dir, filter, options);
    }
}

Pagal visas modulių testų rašymo taisykles, FileFinder derėtų testuoti ne su tikra failų sistemą, bet su menama. Moq pagalba tai atrodytų taip:

[Test]
public void Moq_Example()
{
    var fileSystem = new Mock<IFileSystem>();
    fileSystem
        .Setup(fs => fs.GetDirectoryFiles(It.IsAny<string>(), "*.csv", SearchOption.AllDirectories))
        .Returns(new[] { "aaa.csv", "bbb.csv", "ccc.csv" });

    var fileFinder = new FileFinder(fileSystem.Object);

    var files = fileFinder.Find("mydir", "*.csv");

    fileSystem.Verify(
        fs => fs.GetDirectoryFiles("mydir", "*.csv", SearchOption.AllDirectories), Times.Once());

    CollectionAssert.IsNotEmpty(files);
}

Pavyzdyje pirma aprašoma kaip objektas fileSystem turi reaguoti į GetDirectoryFiles metodo iškvietimą su tam tikrais parametrais, tada iškviečiamas fileFinder metodas Find ir tikrinama, ar GetDirectoryFiles buvo iškviestas lygiai vieną kartą su teisingais argumentais.

Prieš kurį laiką .NET horizonte atsirado naujas Moq konkurentas – NSubstitute. Iš funkcionalumo pusės abu karkasai panašūs, bet NSubstitute turi vieną privalumą – daug aiškesnį API lyginant su Moq. Anksčiau pateiktas pavyzdys perrašytas su NSubstitute pateiktas žemiau:

[Test]
public void NSubstitute_Example()
{
    var fileSystem = Substitute.For<IFileSystem>();
    fileSystem
        .GetDirectoryFiles(Arg.Any<string>(), "*.csv", SearchOption.AllDirectories)
        .Returns(new[] { "aaa.csv", "bbb.csv", "ccc.csv" });

    var fileFinder = new FileFinder(fileSystem);

    var files = fileFinder.Find("mydir", "*.csv");

    fileSystem.Received(1).GetDirectoryFiles("mydir", "*.csv", SearchOption.AllDirectories);

    CollectionAssert.IsNotEmpty(files);
}

NSubstitute dar yra palyginamai jaunas projektas, tad pasitaiko ir tam tikrų klaidų. Nepaisant to, kasdieniniame darbe pastebiu save vis dažniau ir dažniau naudojant NSubstitute vietoje Moq.

P.S.

Jeigu jau esate naudoję NSubstitute, būtų šaunu komentaruose išgirsti jūsų patirtį!

Rodyk draugams

Itin paprastas App.config konfigūracijos sekcijos kūrimas

Parašė Sergejus | 2012-06-24 22:11

Kaip žinia, .NET Framework turi pakankamai galingą mechanizmą konfigūracijų valdymui pagrįsta App.config / Web.config failais. Paprasčiausias būdas nurodyti ir paskui nuskaityti konfigūracijos nustatymus yra AppSettings skiltis. Paprasčiausias būdas nebūtinai visada reiškia geriausias, kaip dažnai jūsų aplikacijos nustatymai yra panašūs į pateiktus žemiau?

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <appSettings>
    <add key="system.exceptionThrotteling" value="00:01:00" />
    <add key="disk.freeSpaceThresholdInMB" value="10240" />
    <add key="disk.waitBeforeRetry" value="00:01:00" />
    <add key="file.writeQueueLength" value="15000" />
    <add key="file.workingDir" value="c:\temp" />
  </appSettings>
</configuration>

Kadangi AppSettigns skiltis neturi būdo logiškai atskirti nustatymų, mes sugalvojame skirtingus apėjimus, pavyzdžiui, prefiksus. Teisingesnis būdas būtų perkelti nustatymus į atskirą .NET konfigūracijos sekciją, pavyzdžiui:

<settings>
  <system exceptionThrotteling="00:01:00" />
  <disk freeSpaceThresholdInMB="10240" waitBeforeRetry="00:01:00" />
  <file writeQueueLength="15000" workingDir="c:\temp" />
</settings>

Nepaisant savo patogumo, konfigūracijos sekcijos turi vieną rimtą trūkumą – jos reikalauja nemažai pastangų aprašant reikalingas ConfigurationSection, ConfigurationElementCollection bei ConfigurationElement klases. Laimei, egzistuoja Visual Studio 2010 praplėtimas Configuration Section Designer, kuris leidžia itin paprastai kurti įvairaus sudėtingumo konfigūracijos sekcijas. Praplėtimą galima sudiegti tiesiai iš Extension Manager:

image

Sudiegus praplėtimą, naują konfigūracijos sekciją galima sukurti pridėjus Configuration Section Designer (.csd) failą:

image

Praplėtimas leidžia vizualiai apibrėžti konfigūracijos sekciją, jos elementus bei elementų atributus. Papildomai galima nustatyti tokias atributų savybes kaip duomenų tipas, privalomumo požymį, numatytąją reikšmę bei kitas:

image

Kaip matyti, praplėtimas sugeneravo kelis failus:

  • Settings.csd.config – pavyzdinis konfigūracijos failas, kuriame parodyta kaip reikia prijungti sugeneruotą konfigūracijos sekciją prie esamo App.config / Web.config failo
  • Settings.csd.cs – konfigūracijos sekciją aprašančios sugeneruotos C# klasės
  • Settings.csd.diagram – nusako diagramos elementų išdėstymą ekrane
  • Settings.csd.xsd – XML schema, kuri nusako leistinus konfigūracijos sekcijos elementus bei jų atributus

Norėdami pridėti sugeneruotą sekciją prie esamo App.config / Web.config failo, nukopijuokite sekcijos aprašą iš Settings.csd.config:

<configSections>
  <section name="settings" type="ConfigDemo.Settings, ConfigDemo"/>
</configSections>

Tam kad konfigūracijos sekcijoje veiktų Visual Studio IntelliSense (pagal sugeneruotą XSD failą), būtina prie sekcijos pavadinimo nurodyti XML vardų sritį:

<settings xmlns="urn:ConfigDemo">
  <system exceptionThrotteling="00:01:00" />
  <disk freeSpaceThresholdInMB="10240" waitBeforeRetry="00:01:00" />
  <file writeQueueLength="15000" workingDir="c:\temp" />
</settings>

Rezultate turime labai paprastą būdą nustatymų nuskaitymui iš konfigūracijos sekcijos Settings:

static void Main()
{
    var settings = Settings.Instance;

    var exceptionThrotteling = settings.System.ExceptionThrotteling;

    var freeSpaceThresholdInMB = settings.Disk.FreeSpaceThresholdInMB;
    var waitBeforeRetry = settings.Disk.WaitBeforeRetry;

    var writeQueueLength = settings.File.WriteQueueLength;
    var workingDir = settings.File.WorkingDir;
}

Taigi taip paprastai Configuration Section Designer praplėtimo pagalba galima kurti nuosavas konfigūracijos sekcijas. Tikiuosi jums tai bus naudinga!

Rodyk draugams

UrlBuilder – paprasta klasė skirta URL formavimui

Parašė Sergejus | 2012-02-08 23:15

Atnaujinta: Atviras kodas daro stebuklus, su Giedriaus pagalba turime atnaujintą klasės variantą!

Darbas su HTTP API dažnai iš programuotojų reikalauja URL formavimo. Keista, bet .NET Framework iki šiol neturi standartinės klasės šiai užduočiai spręsti. Egzistuoja artima pagal paskirtį UriBuilder klasė, bet ji nėra optimizuota darbui su URL. Bekuriant DotNetGroup 3.0 gimė labai paprasta URL formavimui skirta klasė UrlBuilder:

public class UrlBuilder
{
    private readonly Uri baseUrl;
    private readonly IList<string> relativeUrlParts;
    private readonly IList<string> queryStringParameters;

    public UrlBuilder(string baseUrl)
    {
        if (baseUrl == null)
        {
            throw new ArgumentNullException("baseUrl");
        }

        if (!Uri.IsWellFormedUriString(baseUrl, UriKind.Absolute))
        {
            throw new ArgumentException("Base URL format is not valid", "baseUrl");
        }

        this.baseUrl = new Uri(baseUrl);
        this.relativeUrlParts = new List<string>();
        this.queryStringParameters = new List<string>();
    }

    public void AddPart(object value)
    {
        this.relativeUrlParts.Add(value.ToString());
    }

    public void AddParameter(string name, object value)
    {
        this.queryStringParameters.Add(name + "=" + value);
    }

    public string Build()
    {
        var relativeUrl = new Uri(string.Join("/", this.relativeUrlParts), UriKind.Relative);
        var queryString = string.Join("&", this.queryStringParameters);

        var uriBuilder = new UriBuilder(new Uri(this.baseUrl, relativeUrl))
        {
            Query = queryString
        };

        return uriBuilder.Uri.AbsoluteUri;
    }
}

Kaip matote, rezultate gavosi visai patogi pagalbinė klasė URL formavimui:

[TestFixture]
public class UrlBuilderTests
{
    private const string BaseUrl = "http://api.dotnetgroup.dev";

    [Test]
    public void Given_No_Arguments_Base_Url_With_Trailing_Slash_Is_Returned()
    {
        var expectedUrl = "http://api.dotnetgroup.dev/";

        var urlBuilder = new UrlBuilder(BaseUrl);

        var url = urlBuilder.Build();

        Assert.AreEqual(expectedUrl, url);
    }

    [Test]
    public void Given_Type_Argument_Correct_Url_Is_Generated()
    {
        var expectedUrl = "http://api.dotnetgroup.dev/?type=rss";

        var urlBuilder = new UrlBuilder(BaseUrl);
        urlBuilder.AddParameter("type", "rss");

        var url = urlBuilder.Build();

        Assert.AreEqual(expectedUrl, url);
    }

    [Test]
    public void Given_Date_From_And_Limit_Correct_Url_Is_Generated()
    {
        var expectedUrl = "http://api.dotnetgroup.dev/?from=2012-01-01%2010:00:00&limit=10";

        var urlBuilder = new UrlBuilder(BaseUrl);
        urlBuilder.AddParameter("from", "2012-01-01 10:00:00");
        urlBuilder.AddParameter("limit", 10);

        var url = urlBuilder.Build();

        Assert.AreEqual(expectedUrl, url);
    }

    [Test]
    public void Given_Get_And_Id_Parts_Correct_Url_Is_Generated()
    {
        var expectedUrl = "http://api.dotnetgroup.dev/get/1";

        var urlBuilder = new UrlBuilder(BaseUrl);
        urlBuilder.AddPart("get");
        urlBuilder.AddPart(1);

        var url = urlBuilder.Build();

        Assert.AreEqual(expectedUrl, url);
    }
}

P.S. Šios klasės išeities kodas taipogi prieinamas DotNetGroup 3.0 GitHub repozitorijoje.

Rodyk draugams

Asinchroninis Web komunikavimas naudojant Web soketus

Parašė Sergejus | 2012-01-07 13:46

Įvadas į Web soketus

Paskutiniu metu „ant bangos“ asinchroninis komunikavimas Web‘e. Tam egzistuoja skirtingi būdai ir vienas iš jų įeina į HTML5 standartų šeimą – WebSocket API. WebSocket – tai abipusis komunikavimo kanalas grindžiamas TCP soketais. Ryšio užmezgimui naudojamos Web naršyklės ir Web serveriai, todėl komunikavimas dažniausiai vyksta tuo pačiu 80 TCP portu.

WebSocket technologija jau kuriama kurį laiką ir egzistuoja skirtingos jos versijos:

  • Hixie-Draft-76/Hybi-00 (palaiko Safari 5, Chrome < 14, Firefox 4)
  • Hybi-07 (palaiko Firefox 6)
  • Hybi-10 (palaiko Chrome 14-16, Firefox 7)
  • Hybi-13 (palaiko Chrome 17+)

Jau yra paskelbta, kad Internet Explorer 10 irgi palaikys šią technologiją.

WebSocket technologijoja pasižymi API paprastumu, teegzistuoja 4 pagrindiniai metodai:

  • OnOpen - kai ryšis atidaromas
  • OnClose – kai ryšis uždaromas
  • OnMessage – kai gaunamas pranešimas iš kliento
  • Send – pranešimo išsiuntimas klientui

Gera žinia .NET programuotojams, Build konferencijos metu buvo anonsuota, kad Web soketų palaikymas bus įgyvendintas Internet Information Services 8 bei .NET Framework 4.5. Bet jeigu norite pradėti eksperimentuoti su WebSocket technologija jau dabar, siūlau išbandyti atvirojo kodo Web soketų serverį Fleck (prieinamas ir iš NuGet). Fleck parašytas su C# ir palaiko visas pagrindines WebSoket versijas bei turi labai paprastą programavimo API.

WebSocket serverio sukūrimas su Fleck

Žemiau pateiktas pilnai veikiančio WebSocket serverio pavyzdys:

class Server
{
    static void Main()
    {
        var allSockets = new List<IWebSocketConnection>();
        var server = new WebSocketServer("ws://localhost:8181");
        server.Start(socket =>
            {
                socket.OnOpen = () =>
                    {
                        Console.WriteLine("Open!");
                        allSockets.Add(socket);
                    };
                socket.OnClose = () =>
                    {
                        Console.WriteLine("Close!");
                        allSockets.Remove(socket);
                    };
                socket.OnMessage = message =>
                    {
                        Console.WriteLine(message);
                        allSockets.ToList().ForEach(s => s.Send("Echo: " + message));
                    };
            });

        var input = Console.ReadLine();
        while (input != "exit")
        {
            foreach (var socket in allSockets.ToList())
            {
                socket.Send(input);
            }
            input = Console.ReadLine();
        }

    }
}

WebSocket kliento sukūrimas su WebSocket API

Komunikavimas su WebSocket serveriu įmanomas arba iš kito serverio, arba tiesiai iš naršyklės JavaScript ir WebSocket API pagalba. Žemiau pateiktas pilnai veikiančio WebSocket kliento pavyzdys:

<!DOCTYPE>
<html>
<head>
    <title>websocket client</title>
    <script type="text/javascript">
        var start = function () {
            var inc = document.getElementById('incomming');
            var wsImpl = window.WebSocket || window.MozWebSocket;
            var form = document.getElementById('sendForm');
            var input = document.getElementById('sendText');

            inc.innerHTML += "connecting to server ..<br/>";

            // create a new web socket and connect
            window.ws = new wsImpl('ws://localhost:8181', 'my-protocol');

            // when data is coming from the server, this method is called
            ws.onmessage = function (evt) {
                inc.innerHTML += evt.data + '<br/>';
            };

            // when the connection is established, this method is called
            ws.onopen = function () {
                inc.innerHTML += '.. connection open<br/>';
            };

            // when the connection is closed, this method is called
            ws.onclose = function () {
                inc.innerHTML += '.. connection closed<br/>';
            }

			form.addEventListener('submit', function(e){
				e.preventDefault();
				var val = input.value;
				ws.send(val);
				input.value = "";
			});

        }
        window.onload = start;
    </script>
</head>
<body>
	<form id="sendForm">
		<input id="sendText" placeholder="Text to send" />
	</form>
    <pre id="incomming"></pre>
</body>
</html>

Apibendrinimas

Sudėtingėjant Web aplikacijoms vis dažniau atsiranda asinchroninio komunikavimo poreikis tarp klientų ir serverio. Nepaisant fakto, kad Web soketų palaikymas dar nėra masinis, viskas einama link to. Jau dabar egzistuoja pagalbiniai Flash ir Silverlight moduliai, kurie leidžia emuliuoti WebSocket panaudojimą senesnėse naršyklėse. WebSocket – tai daug žadanti ir didelį bendruomenės palaikymą turinti technologija, kuria labai rekomenduoju pradėti domėtis jau šiandien.

Rodyk draugams

Sutrumpintų nuorodų išskleidimas

Parašė Sergejus | 2011-12-06 19:43

Vienas iš pageidavimų DotNetGroup 3.0 platformai buvo vietoje sutrumpintų nuorodų (pavyzdžiui, http://t.co/53rFHuMG) rodyti originalias (šiuo atveju, http://sergejus.blogas.lt/itishnikai-7-jau-online-1586.html). Vienintelis būdas išskleisti sutrumpintą nuorodą – nueiti pagal ją ir sužinoti originalaus puslapio nuorodą. Krauti visą puslapio turinį nėra efektyvu, laimei, HTTP protokole šalia GET, POST, PUT ir DELETE metodų yra mažiau žinomas metodas HEAD. Jo pagalba web serveris grąžins ne visą puslapio turinį, bet tik HTTP antraštes kartu su originalaus puslapio adresu. C# tai įgyvendinama kelių eilučių pagalba:

public class UrlResolver
{
    public string Resolve(string url)
    {
        var resolvedUrl = url;

        var request = WebRequest.Create(url);
        request.Method = WebRequestMethods.Http.Head;

        try
        {
            using (var response = request.GetResponse())
            {
                resolvedUrl = response.ResponseUri.AbsoluteUri;
            }
        }
        catch (WebException)
        {
        }

        return resolvedUrl;
    }
}

Panaudojimas yra trivialus:

[Test]
public void Given_Shorten_Url_Resolve_Successfully_Resolves_Orignial_Url()
{
    var shortenUrl = "http://t.co/53rFHuMG";
    var originalUrl = "http://sergejus.blogas.lt/itishnikai-7-jau-online-1586.html";
    var resolver = new UrlResolver();

    var resolvedUrl = resolver.Resolve(shortenUrl);

    Assert.AreEqual(originalUrl, resolvedUrl);
}

[Test]
public void Given_Full_Url_Resolve_Sucsessfully_Returns_Same_Url()
{
    var originalUrl = "http://sergejus.blogas.lt/itishnikai-7-jau-online-1586.html";
    var resolver = new UrlResolver();

    var resolvedUrl = resolver.Resolve(originalUrl);

    Assert.AreEqual(originalUrl, resolvedUrl);

}

Aukščiau pateiktas kodas kaip ir kuriamos DotNetGroup 3.0 platformos kodas laisvai prieinami GitHub.

Rodyk draugams

Savaitės video - Parallel Computing with Visual Studio 2010

Parašė Sergejus | 2011-10-12 23:09

Šios savaitės video – kolega iš Latvijos Valdis Iljuconoks pasakoja apie lygiagretų programavimą.

Rodyk draugams

Modulių testų rekomendacijos – kodo aprėptis

Parašė Sergejus | 2011-08-16 18:51

Per paskutines dvi savaites teko rašyti ypatingai daug modulių testų (angl. unit tests) ir net pavyko pasiekti „išsvajotą“ 100% kodo aprėptį (angl. code coverage). Iš to gimė mintis parašyti kelių straipsnių ciklą apie rekomendacijas rašant modulių testus.

Modulių testų rekomendacijos – įrankiai ir aplinkos paruošimas
Modulių testų rekomendacijos – 13 modulių testų rašymo taisyklių
Modulių testų rekomendacijos – modulių testas ar integracijų testas?
Modulių testų rekomendacijos – kodo aprėptis

Praeitoje dalyje aš paaiškinau skirtumus tarp modulių testų ir integracijų testų bei išnagrinėjome integracijų testų rašymo taisykles. Šioje, paskutinėje, dalyje mes aptarsime svarbią testavimo metriką – kodo aprėptį.

Kas yra kodo aprėptis?

Kodo aprėptis nusako koks funkcionalumo procentas yra padengtas testais arba kitaip tariant, kokia kodo dalis yra vykdoma leidžiant testus. Natūraliai atrodo, kuo didesnis šis procentas, tuo labiau galima pasitikėti testais. Deja, praktikoje tai ne visada tiesa. Išnagrinėkime klasę StringHelper ir jos testą:

public static class StringHelper
{
  public static string Reverse(string input)
  {
    var arr = input.ToCharArray();
    Array.Reverse(arr);
    return new String(arr);
  }
}

[Test]
public void Given_Not_Empty_String_It_Is_Correctly_Reversed()
{
  // Arrange
  var text = "Test text";
  var reversedText = "txet tseT";

  // Act  
  var result = StringHelper.Reverse(text);

  // Assert
  Assert.AreEqual(reversedText, result);
}

Pateikto testo pilnai užtenka, kad pasiekti 100% StringHelper kodo aprėptį. 100%! Ar tai reiškia, kad mūsų kodas yra visiškai be klaidų? Nebūtinai! Nors testo metu techniškai ir vykdomas visas Reverse metodo kodas, mes nežinome kas įvyks, kai bus perduota null reikšmė. Ogi gausime NullReferenceException pačioje pirmoje Reverse eilutėje. Padarykime tokį tikrinimą:

public static class StringHelper
{
  public static string Reverse(string input)
  {
    if (input == null) throw ArgumentNullException("input");
    var arr = input.ToCharArray();
    Array.Reverse(arr);
    return new String(arr);
  }
}

Toks kodas yra teisingesnis, bet mes jau nebeturime 100% kodo aprėpties. Tam reikia antro testo:

[Test]
[ExpectedException(typeof(ArgumentNullException))]
public void Given_Null_Argument_Exception_Is_Thrown()
{
  // Arrange
  var nullArgument = null;

  // Act
  StringHelper.Reverse(nullArgument);
}

Ir vėl turime 100% aprėptį, ar dabar kodas neturi klaidų? Šį kartą nežinome, ar kodas gerai veiks tu tuščia eilute. Po kelių tokių iteracijų, aš pradėjau suprasti tikrąją testais grindžiamo programavimo (angl. Test Driven Development) naudą, bet čia visai kita tema…

Koks yra rekomenduojamas kodo aprėpties procentas?

Kaip visada, atsakymas į tokius klausimas yra – „priklauso“. Kuo daugiau jūsų metodas turi apsaugų nuo blogų reikšmių, tuo daugiau testų reikia pasiekti gerą kodo aprėptį. Aš prisilaikau tokios nykščio taisyklės – ne mažiau kaip 80%.

 

Štai tiek aš norėjau papasakoti apie savo patirtį dirbant su modulių bei integracijų testais. Kai pradedi rimtai rašyti testus, paaiškėja, kad tai yra kitoks programavimas, reikalaujantis naujų žinių ir kiek kitokio mąstymo.

Rodyk draugams