BLOGas.lt
Sukurk savo BLOGą Kitas atsitiktinis BLOGas

Modulių testų rekomendacijos – modulių ar integracijų testas?

Parašė Sergejus | 2011-08-07 22:11

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?

Praeitoje dalyje aš pateikiau 13 modulių testų rašymo taisyklių. Straipsnis sukėlė labai gerą diskusiją (ypatingai Facebooke) ir vėliau buvo atnaujintas atsižvelgiant į jūsų pastabas. Šioje dalyje išnagrinėsime skirtumus tarp modulių testų ir integracijų testų bei patarimus rašant pastaruosius.

Modulių ar integracijų testas?

Labai dažnai modulių testų naujokai painioja modulių testus su integracijų testais ir vadina juos vienu vardu – modulių testais. Tai paaiškinama tuo, kad integracijų testai rašomi naudojant modulių testų karkasus bei tas pačias pagalbines bibliotekas. Nepaisant šio fakto, labai svarbu suprasti ideologinį skirtumą tarp jų, nes integracijų testų rašymas bei vykdymas turi savų niuansų.

Tai kas gi yra integracijų testas? Tam, kad atskirti modulių testą nuo integracijų testo, sau aš sugalvojau tokį apibrėžimą: jeigu testas naudoja bet kokius išorinius resursus, pavyzdžiui, failų sistemą, duomenų bazę, Web servisą ir panašiai ar testuoja iš karto kelis sistemos lygius, pavyzdžiui, prieigą prie duomenų kartu su verslo logika – jis yra integracijų testas.

Integracijų testams taikytina dauguma modulių testų rašymo taisyklių apart kelių, žemiau išvardinti skirtumai tarp jų:

  1. Integracijų testų vykdymas gali būti ilgesnis nei modulių, todėl visada patartina juos atskirti į atskirą rinkinį (angl. assembly) arba kategoriją (kai kurie modulių testų karkasai turi galimybę operuoti testų kategorijomis)
  2. Integracijų testai gali testuoti tiek viena sistemos lygį (pavyzdžiui, verslo logiką), tiek iš karto kelis (pavyzdžiui, prieiga prie duomenų kartu su verslo logika)
  3. Integracijų testai turi mokėti paruošti visus testo vykdymui reikalingus duomenis bei grąžinti sistemą į pradinę būseną testui pasibaigus, tam geriausiai tinka try-finally struktūra

Aukščiau išvardintus skirtumus paaiškinsiu tokiu pavyzdžių (tikrinamas vienas sistemos lygis – prieiga prie duomenų):

[Test]
[Category("Integration")]
public void Given_Not_Existing_Directory_Path_New_Directory_Is_Successfully_Created_After_Calling_CreateDirectoryIfNotExists()
{
  // Arrange
  var exists = true;
  var dirPath = Path.GetTempPath();
  var fs = new FileSystem();

  try
  {
    // Act
    fs.CreateDirectoryIfNotExists(dirPath);

    // Assert
    var doesDirExist = Directory.Exists(dirPath);
    Assert.AreEqual(exists, doesDirExist);
  }
  finally
  {
    if(Directory.Exists(dirPath)
    {
      Directory.Delete(dirPath)
    }
  }
}

Atkreipkite dėmesį, kad testas po savęs grąžina sistemą į pradinę būseną, t.y. ištrina ką tik sukurtą katalogą ir tai daroma finally bloke.

Kitoje dalyje mes aptarsime kas yra kodo aprėptis ir koks aprėpties procentas yra rekomenduojamas.

Rodyk draugams

Modulių testų rekomendacijos – 13 modulių testų rašymo taisyklių

Parašė Sergejus | 2011-07-31 18:50

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ų

Praeitoje dalyje mes išnagrinėjome pagrindinius įrankius, skirtus modulių testavimui bei aptarėme aplinkos paruošimą. Šiame straipsnyje pateiksiu 13 modulių testų rašymo taisyklių, kurių pats prisilaikau jau keletą metų.

ATNAUJINTA

Atsižvelgiant į skaitytojų siūlymus (tiek tinklaraščio komentaruose, tiek Facebook), atnaujinau modulių testų rašymo taisykles.

13 modulių testų rašymo taisyklių

  1. Modulių testų rašymas tai irgi yra programavimas, todėl taikytinos visos standartinės kodo rašymo rekomendacijos
  2. Svarbiausias dalykas kuris turi būti ištestuotas – verslo logika
  3. Reikia turėti pakankamai modulių testų: nei per mažai, nei per daug
  4. Kiekvienas modulių testas turi tikrinti vieną ir tik vieną funkcionalumą
  5. Pagal galimybes reikia izoliuoti testuojamą logiką nuo išorinių resursų (naudojant netikrus objektus ir elgsenos aprašymą (angl. mocking), jeigu nepavyksta to padaryti, dažniausiai tai reiškia dizaino problemą kode ar statinių klasių panaudojimą
  6. Modulių testų vykdymas turi būti greitas
  7. Testų rezultatai tūri būti stabilūs ir determinuoti, tai yra visą laiką grąžinti tą patį rezultatą
  8. Modulių testų rašymo metu naudokite bazinę klasę bei pagalbinius metodus, bet jokių būdų nekvieskite tiesiogiai kitų testų
  9. Jeigu vienos klasės testai turi tą pačią sukūrimo / išvalymo logiką, naudokite specialiai tam skirtas konstrukcijas (Init, SetUp ir panašiai, priklausomai nuo pasirinkto modulių testų karkaso)
  10. Testuokite tik viešas (angl. public) ir vidinio naudojimo (angl. internal) klases, jeigu atsiranda poreikis testuoti privačius metodus (angl. private), dažniausiai tai reiškia šį funkcionalumą reikia iškelti į atskirą klasę
  11. Testo pradžioje apibrėžkite visus parametrus kaip kintamuosius, suteikiant jiems prasmingus pavadinimus
  12. Geriausias būdas dirbti su resursais – naudoti struktūrą try-finaly Kaip teisingai buvo pastebėta kelių skaitytojų – tai labiau liečia integracijų testus, todėl nusprendžiau išimti iš sąrašo
  13. Visi testai turi tenkinti [Arrange]-Act-Assert stilių, kur Arrange dalis nėra privaloma
  14. Testų pavadinimai turėtų pilnai nusakyti sąlygas, testuojamą objektą bei laukiamą rezultatą, pavyzdžiui galima naudoti tokį modulių testų vardų sudarymo šabloną: [Condition][Object][ExpectedResult][Action/Method]

Tikriausiai sutiksite, didžioji dalis aukščiau išvardintų taisyklių yra tiek logiškos, tiek savaime suprantamos. Paskutines 3 taisykles paaiškinsiu dviem pavyzdžiais:

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);
}
public void Given_Not_Existing_Directory_Path_Writer_Sucessfully_Creates_Directory_On_Write()
{
  // Arrange
  var exists = true;
  var doesNotExist = false;
  var dirPath = Path.GetTempPath();
  var text = "Test text";
  var fileSystem = new Mock<IFileSystem>();
  var writer = new FileWriter(fileSystem.Object);

  fileSystem.Setup(fs => fs.CreateDirectoryIfNotExists(It.IsAny<string>())

  // Act
  writer.Write(dirPath, text);

  // Assert
  fileSystem.Verify(fs => fs.CreateDirectoryIfNotExists(dirPath), Times.Once());
}

Arrange dalyje apibrėžiame visus reikalingus objektus ir parametrus bei esant poreikiui aprašome netikrus objektus ir reikalingą jų elgseną. Act dalyje iškviečiame testuojamą metodą su norimais parametrais. Assert dalyje patikriname vieną iš dviejų: arba paprastą Assert sąlygą, arba netikro objekto elgseną. Galiausiai, atkreipkite dėmesį į metodo pavadinimus – vien juos perskaičius galima tiksliai pasakyti kas daroma, esant kokioms pradinėms sąlygoms ir kokio rezultato tikimės.

Iš patirties galiu pasakyti, kad šios taisyklės ženkliai padeda programuotojams be didelės modulių testavimo patirties žengti pirmus žingsnius. Kitoje dalyje aptarsime ideologinius skirtumus tarp modulių testų ir integracijų testų.

Rodyk draugams

Modulių testų rekomendacijos – įrankiai ir aplinkos paruošimas

Parašė Sergejus | 2011-07-27 21:58

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.

Įrankiai

Pradėsime nuo pagrindinių bibliotekų (labai rekomenduoju šiems tikslams naudotis NuGet):

  • NUnit – paskutiniu metu naudoju kaip pagrindinį modulių testų karkasą, iš alternatyvų galite pasirinkti: MSTest, xUnit, MbUnit, MSpec.
  • Moq – populiariausia netikrų objektų kūrimo bei elgsenos aprašymo (angl. mocking) biblioteka, iš alternatyvų galite pasirinkti: Rhino Mocks, Typemock, JustMock.
  • AutoFixture – greitai populiarėjanti testinių duomenų generavimo biblioteka, iš alternatyvų galite pasirinkti: NBuilder.
  • ReSharper Unit Test Runner – viena patogiausių modulių testų vykdymo aplinkų (eina kartu su ReSharper), iš alternatyvų galite pasirinkti: MS Test Runner, TestDriven.NET, Gallio.
  • dotCover – naujas kodo aprėpties analizės įrankis (integruojamas su ReSharper, bet perkamas atskirai), iš alternatyvų galite pasirinkti: NCover, PartCover.

Aplinkos paruošimas

Turint po ranka visas reikalingas bibliotekas, pats laikas pereiti prie aplinkos paruošimo. Man pasiteisino tokia langų schema:

screen

Kairėje pusėje yra testuojama klasė (angl. System Under Test, SUT), o dešinėje – testai. Priklausomai nuo to, kiek monitorių turite, modulių testų vykdymo langas gali dinamiškai atsirasti lango apačioje arba gali būti užfiksuotas per pusę antro monitoriaus.

Kitas svarbus aspektas – greiti klavišai, skirti paleisti visus / vieną testą, praleisti aprėpties analizę ir pan. Kadangi dažnas testų paleidimas yra neatsiejama testų rašymo dalis, žymiai efektyviau išmokti greituosius klavišus ir naudotis jais, o ne spaudinėti grafinius mygtukus (panašiai, kaip yra su Ctrl + Shift + B :) ).

 

Štai mes ir esame pasiruošę rašyti modulių testus. Kitoje dalyje aš aptarsiu modulių testų vardų sudarymo rekomendacijas, kurias surinkau ir apibendrinau per keletą paskutinių metų.

Rodyk draugams

Skaitliukas per laiko vienetą

Parašė Sergejus | 2011-06-15 18:56

Šiandien darbe prisireikė galimybės apriboti kreipinių į serverį kiekį per laiko vienetą (pvz., per sekundę). Vaizdžiai, jeigu leidžiama daugiausiai 100 kreipinių į sekundę, o per tą pačią sekundę ateina 150 kreipinių – pirmi 100 bus aptarnauti, o likę 50 – ne. Taip gimė labai naudinga klasė Throttler:

public abstract class Throttler
{
    private readonly long _limit;
    private readonly TimeSpan _timePeriod;
    private DateTime _time;

    protected Throttler(long limit, TimeSpan timePeriod)
    {
        _limit = limit;
        _timePeriod = timePeriod;
        _time = DateTime.UtcNow;
        Lock = new ReaderWriterLockSlim();
    }

    protected ReaderWriterLockSlim Lock { private set; get; }

    public bool Throttle()
    {
        var now = DateTime.UtcNow;

        if (now >= Time.Add(_timePeriod))
        {
            Lock.EnterWriteLock();
            try
            {
                if (now >= _time.Add(_timePeriod))
                {
                    _time = now;
                    ResetCounter(context);
                }
            }
            finally
            {
                Lock.ExitWriteLock();
            }
        }

        var count = IncrementCounter(context);

        return count > _limit;
    }

    private DateTime Time
    {
        get
        {
            Lock.EnterReadLock();
            try
            {
                return _time;
            }
            finally
            {
                Lock.ExitReadLock();
            }
        }
    }

    protected abstract void ResetCounter(TContext context);

    protected abstract long IncrementCounter(TContext context);
}

Jos pagrindu aukščiau pateiktas uždavinys sprendžiamas ypač paprastai:

public class TotalCountThrottler : Throttler
{
    private long _count;

    public TotalCountThrottler(long limit, TimeSpan timePeriod)
        : base(limit, timePeriod)
    {
    }

    protected override void ResetCounter(HttpContextBase context)
    {
        Interlocked.Exchange(ref _count, 0);
    }

    protected override long IncrementCounter(HttpContextBase context)
    {
        return Interlocked.Increment(ref _count);
    }
}

Panaudojimas yra elementarus:

public class ThrottlingHandler : IHttpHandler
{
    private readonly TotalCountThrottler _throttler;

    public ThrottlingHandler()
    {
        _throttler = new TotalCountThrottler(100, TimeSpan.FromSeconds(1));
    }

    public bool IsReusable
    {
        get { return true; }
    }

    public void ProcessRequest(HttpContext context)
    {
        if (_throttler.Throttle())
        {
            context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
            context.Response.End();
        }
    }
}

Pažymėtina, kad minėta klasė gali veikti tiek su Web aplikacijomis, tiek su bet kokiomis kitomis aplikacijomis.

Rodyk draugams

Dapper .NET - Micro ORM karkasas iš StackOverflow

Parašė Sergejus | 2011-05-09 22:05

Taip, būtent! Visiems gerai žinomas StackOverflow sukūrė ir kaip atviro kodo projektą išleido savo Micro ORM karkasą Dapper .NET. Tai yra maža (vieno failo), greita (tik keliomis milisekundėmis lėtesnė nei SqlDataReader), užklausų materializavimo ir SQL komandų vykdymo biblioteka. Tarkime, turime tokį modelį:

class Question
{
    public int Id { get; set; }
    public int No { get; set; }
    public string Label { get; set; }
    public int SurveyId { get; set; }
    public Survey Survey { get; set; }
}

class Survey
{
    public int Id { get; set; }
    public string Code { get; set; }
    public string Title { get; set; }
    public DateTime Date { get; set; }
}

Žemiau pateiksiu kelias užklausas, kurios geriausiai pavaizduoja Dapper .NET galimybes:

  • materializavimą į tipizuotus objektus;
  • materializavimą į dinaminius objektus;
  • kelių objektų materializavimą;
  • SQL komandų vykdymą.
class Program
{
    static void Main()
    {
        var connectionString = ConfigurationManager.ConnectionStrings["Questions"].ConnectionString;
        using (var connection = new SqlConnection(connectionString))
        {
            connection.Open();

            var sql = "select * from Questions";
            var questions = connection.Query<Question>(sql);

            sql = "select No, Label from Questions";
            var custom = connection.Query(sql);

            sql = "select q.*, s.* from Questions q inner join Surveys s on q.SurveyId = s.Id where q.Id = @Id";
            var question1WithSurvey = connection.Query<Question, Survey>(
                sql, (q, s) => q.Survey = s, new { Id = 1 }).First();

            sql = "DBCC FREEPROCCACHE; DBCC DROPCLEANBUFFERS";
            connection.Execute(sql);
        }
    }
}

Paprasčiausias būdas išbandyti Dapper .NET – tiesiai iš NuGet (Install-Package Dapper). Turiu pripažinti, atrodo visai įdomiai, nors funkcionalumo prasme orientuota daugiausiai tik į SELECT tipo operacijas. Pasižiūrėsime kaip vystysis šis projektas ateityje.

Rodyk draugams

Paprasta JSON-RPC .NET biblioteka

Parašė Sergejus | 2011-05-02 21:10

JSON (JavaScript Object Notation) – vienas populiariausių duomenų formatų Internete. Daugelis pripratę naudoti JSON iš JavaScript ir Web aplikacijų, bet mažai kas žino, kad jis gali būti sėkmingai naudojamas ir serverio aplikacijose. Prieš kelias dienas teko daryti integraciją su viena serverių būsenos stebėjimo programine įranga. Tradiciškai, tokio tipo integracijos yra daromos per specifines bibliotekas (.NET, Java, COM), bet šis produktas (beje didelė pagarba kūrėjams), naudojo JSON-RPC protokolą. Kas nustebino, šio protokolo paprastumas – užtenka suformuoti paprastą JSON objektą su metodo pavadinimu ir argumentais bei padaryti HTTP POST.

Pavyzdinio metodo user.get iškvietimas (params gali būti sudėtingas JSON objektas):

{"jsonrpc": "2.0", "method": "user.get", "params": {"filter": { "userid": 42 } }, "id": 1}

Rezultato grąžinimas (result gali būti sudėtingas JSON objektas):

{"jsonrpc": "2.0", "result": { "userid": 42, "firstname": "Sergejus" }, "id": 1}

Klaidos grąžinimas (error objekto struktūra fiksuota):

{"jsonrpc": "2.0", "error": {"code": -12345, "message": "User not found."}, "id": 1}

Atkreipsiu dėmesį į tai, kad id – tai sekos numeris. Su kiekviena užklausa numeris tik didėja.

Pirma ką padariau, išnagrinėjau egzistuojančias .NET bibliotekas darbui su JSON-RPC. Pasirodo, egzistuoja tik viena - Jayrock. Ji turi integraciją su ASP.NET ir šiaip atrodo pakankamai solidžiai. Vienintelis dalykas kuris man kiek nepatiko, ji nesinaudoja anoniminių objektų bei dinaminių objektų galimybėmis, kas leistų gerokai supaprastinti kodą tiek skaitomumo, tiek aiškumo prasme. Kadangi integracijai man reikėjo labai minimalaus funkcionalumo, nusprendžiau greitai pasirašyti savo JSON-RPC biblioteką, paremta puikia, vieno failo biblioteka – DynamicJson. Jos pagalba JSON objektus galima konvertuoti į dinaminius C# objektus ir atvirkščiai. Ilgai negalvojęs, pasirašiau tokią JsonRpc klasę:

public class JsonRpc
{
    private int _id;

    public JsonRpc(string apiUrl)
    {
        ApiUrl = apiUrl;
    }

    public string ApiUrl { get; private set; }

    public dynamic Call(string method, object param)
    {
        var call = new
        {
            jsonrpc = "2.0",
            id = _id++,
            method = method,
            @params = param
        };

        var json = PostAndGetResponse(DynamicJson.Serialize(call));
        return DynamicJson.Parse(json);
    }

    private string PostAndGetResponse(string data)
    {
        var bytes = Encoding.UTF8.GetBytes(data);
        var wb = (HttpWebRequest)WebRequest.Create(ApiUrl);
        wb.Credentials = CredentialCache.DefaultCredentials;
        wb.ContentType = @"application/json-rpc";
        wb.Method = WebRequestMethods.Http.Post;
        wb.ContentLength = bytes.Length;

        using (var stream = wb.GetRequestStream())
        {
            stream.Write(bytes, 0, bytes.Length);
        }

        string result;
        using (var response = wb.GetResponse().GetResponseStream())
        using (var reader = new StreamReader(response))
        {
            result = reader.ReadToEnd();
        }

        return result;
    }
}

Šios klasės pagalba, darbas su JSON-RPC yra ypatingai paprastas, pvz.:

public static void Main(string[] args)
{
    var rpc = new JsonRpc("http://server/api_jsonrpc.php");
    var json = rpc.Call("user.get", new { filter = new { userid = 42 } });

    if (json.result())
    {
        var user = json.result;
    }
    else
    {
        var message = json.error.message;
    }
}

Taigi taip paprastai ir patogiai šiais laikais įmanoma integruoti aplikacijas, jeigu tik pasirinktas teisingas protokolas!

Rodyk draugams

Prieiga prie IIS7 konfigūracijos iš API

Parašė Sergejus | 2011-02-14 20:57

Kaip žinia, pradedant nuo 7 versijos IIS architektūra tapo modulinė, o visas konfigūravimas iš metaduomenų bazės buvo perkeltas į XML konfigūracijos failus. Bet nedaugelis žino, kad kartu atsirado ir .NET API, skirtas darbui su IIS. Minėtas API leidžia pasiekti daugelį IIS ir sudiegtų modulių nustatymus. Norėdami pradėti darbą su IIS7 API, užtenka pridėti nuorodą į biblioteką Microsoft.Web.Administration.dll iš katalogo %windir%\System32\inetsrv. API naudojimosi pavyzdys pateiktas žemiau:

var serverManager = new ServerManager();
foreach (var site in serverManager.Sites)
{
    Console.WriteLine("Site: {0}", site.Name);
    foreach (var app in site.Applications)
    {
        var appPool = serverManager.ApplicationPools[app.ApplicationPoolName];
        var info = new
        {
            Path = app.VirtualDirectories.First().PhysicalPath,
            Identity = appPool.ProcessModel.IdentityType == ProcessModelIdentityType.SpecificUser ?
                       appPool.ProcessModel.UserName :
                       appPool.ProcessModel.IdentityType.ToString(),
            AppPool = app.ApplicationPoolName,
            AppPoolMode = appPool.ManagedPipelineMode,
            Is32Bit = appPool.Enable32BitAppOnWin64,
            Framework = appPool.ManagedRuntimeVersion
        };

        Console.WriteLine(info);
    }
}

Aukščiau pateiktas kodas kiekvienam Web saitui išves:

  • fizinį kelią iki aplikacijos
  • vartotoją, kurio vardu veikia AppPool
  • aplikacijos naudojamą AppPool‘ą
  • požymį, kokiu režimu veikia AppPool (integruotas ar klasikinis)
  • architektūrą, kurioje veikia AppPool (x86 / x64)
  • naudojamo .NET Framework versijos numerį

Svarbu pažymėti, nors pateiktame pavyzdyje vyksta tik skaitymo operacijos, rašymo operacijos irgi yra palaikomas (pavyzdžiui, naujo saito pridėjimas). Apibendrinant, labai šaunu kad vis daugiau pagrindinių Windows Server komponentų įgauna .NET API, kas ypatingai palengvina tam tikrus automatizavimo scenarijus!

Rodyk draugams

WCF servisų derinimas

Parašė Sergejus | 2011-01-11 21:47

Paskutiniu metu labai dažnai tenka dirbti su WCF servisais ir atitinkamai auga poreikis juos derinti (angl. debug). Kadangi jau kelis kartus iš kolegų girdėjau klausimą dėl WCF servisų derinimo, nusprendžiau tai aprašyti detaliau. Tarkime, mūsų sprendimas sudarytas iš tokių projektų:

image

Detaliau išvardinsiu kiekvieno projekto paskirtį:

  • Wcf.Client – mūsų WCF serviso klientas
  • Wcf.Contract – WCF serviso kontraktas (bendravimo interfeisas)
  • Wcf.Model – WCF serviso duomenų modelis
  • Wcf.Service – WCF serviso verslo logika (kontrakto realizacija)
  • Wcf.Service.Host – WCF serviso aplikacija

Kaip matyti, sprendimas yra minimalus, bet to pakanka parodyti kaip vienu metu derinti tiek WCF klientą, tiek WCF servisą ir/arba WCF serviso aplikaciją. Tam, kad įnešti kontekstą, žemiau pateiksiu kiekvienos klasės kodą.

Projektas Wcf.Client

class MessangerClient : ClientBase<IMessanger>, IMessanger
{
    public void Send(Message message)
    {
        base.Channel.Send(message);
    }
}
class Program
{
    static void Main()
    {
        var client = new MessangerClient();
        try
        {
            client.Send(new Message { Text = "Test" });
        }
        finally
        {
            if (client.State == CommunicationState.Faulted)
            {
                client.Abort();
            }
            else
            {
                client.Close();
            }
        }
    }
}
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.serviceModel>
    <client>
      <endpoint address="net.tcp://localhost:9000/Messanger"
                binding="netTcpBinding"
                contract="Wcf.Contract.IMessanger" />
    </client>
  </system.serviceModel>
</configuration>

Projektas Wcf.Contract

[ServiceContract]
public interface IMessanger
{
    [OperationContract]
    void Send(Message message);
}

Projektas Wcf.Model

[DataContract]
public class Message
{
    [DataMember]
    public string Text { get; set; }
}

Projektas Wcf.Service

public class Messanger : IMessanger
{
    public void Send(Message message)
    {
        throw new NotImplementedException("This method is not implemented");
    }
}

Projektas Wcf.Service.Host

class Program
{
    static void Main()
    {
        var host = new ServiceHost(typeof(Messanger));
        try
        {
            host.Open();

            Console.ReadKey();
        }
        finally
        {
            if (host.State == CommunicationState.Faulted)
            {
                host.Abort();
            }
            else
            {
                host.Close();
            }
        }
    }
}
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.serviceModel>
    <services>
      <service name="Wcf.Service.Messanger" behaviorConfiguration="ServiceBehavior">
        <endpoint binding="netTcpBinding" contract="Wcf.Contract.IMessanger" />
        <endpoint address="mex" binding="mexTcpBinding" contract="IMetadataExchange" />
        <host>
          <baseAddresses>
            <add baseAddress="net.tcp://localhost:9000/Messanger" />
          </baseAddresses>
        </host>
      </service>
    </services>
    <behaviors>
      <serviceBehaviors>
        <behavior name="ServiceBehavior">
          <serviceMetadata />
          <serviceDebug includeExceptionDetailInFaults="true"/>
        </behavior>
      </serviceBehaviors>
    </behaviors>
  </system.serviceModel>
</configuration>

Wcf.Client ir Wcf.Service.Host derinimas

Paprasčiausias atvejis – jus norite derinti pradedant WCF serviso klientu ir baigiant WCF serviso aplikacija. Tam tereikia atlikti du žingsnius:

  • Wcf.Service.Host App.config faile nurodyti <serviceDebug includeExceptionDetailInFaults="true"/> (žr. aukščiau)
  • Vienu metu paleisti derinimui Wcf.Service.Host (dešiniu pelės mygtuku ant projekto → Debug Start → new instance) ir tada tuo pačiu būdu Wcf.Client

Wcf.Client ir Wcf.Service derinimas

Derinti WCF klientą ir serviso aplikaciją yra paprasta, bet dažnai nėra galimybės tiesiogiai paleisti derinimui WCF serviso aplikaciją. Tokiais atvejais tenka derinti WCF kliento sąveiką su WCF servisu (Wcf.Service). Kadangi Wcf.Service yra biblioteka (DLL), prieš tai išvardintas derinimo būdas netinka. Laimei, kartu su Visual Studio eina labai naudinga programa WcfTestClient, kurios pagalba mes ir derinsime WCF servisą. Tam reikia atlikti keletą žingsnių:

  • Wcf.Service.Host App.config faile nurodyti <serviceDebug includeExceptionDetailInFaults="true"/> (žr. aukščiau)
  • Nukopijuoti Wcf.Service.Host App.config failą į Wcf.Service projektą (rekomenduoju kopijuoti ne fiziškai, o kaip Linked Item)
  • Nurodyti WcfTestClient kaip Wcf.Service klientą (dešiniu pelės mygtuku ant projekto → Properties → Debug → Command line arguments laukelyje įveskite /client:"WcfTestClient.exe")
  • Pakeisti Wcf.Service projekto tipą į WCF Service Library (žingsnis reikalingas tik jeigu projektas sukurtas kaip paprasta C# biblioteka) (dešiniu pelės mygtuku ant projekto → Unload Project, dešiniu pelės mygtuku ant projekto → Edit Wcf.Service.csproj, suraskite elementą ProjectGuid ir žemiau pridėkite <ProjectTypeGuids>{3D9AD99F-2412-4246-B90B-4EAA41C64699};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>)
  • Vienu metu paleisti derinimui Wcf.Service (dešiniu pelės mygtuku ant projekto → Debug Start → new instance) ir tada tuo pačiu būdu Wcf.Client

Tikiuosi, aukščiau pateikti patarimai pravers derinant WCF servisus ir sutaupys nemažai jūsų (brangaus) laiko. Efektyvaus derinimo!

Rodyk draugams

FTP over SSL konfigūravimas IIS 7 ir jo panaudojimas iš C#

Parašė Sergejus | 2010-12-27 21:24

FTP yra vienas iš seniausių failų mainų protokolų, kuriam Microsoft, mano manymu, skyrė neypatingą dėmesį. Apart tradicinio, nesaugaus FTP protokolo, egzistuoja dvi jo variacijos: FTP over SSH, dar žinomas kaip Secured FTP arba SFTP bei FTP over SSL, dar žinomas kaip FTPS. Iki šiol nei viena, nei kita saugaus FTP atmaina nebuvo oficialiai palaikoma IIS, todėl tekdavo naudoti trečios šalies FTP serverius. Laimei, atsirado galimybė parsisiųsti FTP Publishing Service, skirtą IIS 7 ir naujesnėms versijoms.

Konfigūravimas

Suinstaliavus FTP Publishing Service, IIS Manager atsiras nauji su FTP susiję punktai:

image

Prieš pradedant konfigūruoti FTP over SSL, pirma reikia sugeneruoti SSL sertifikatą, tai galima padaryti paspaudus ant Server Certificates ir pasirinkus punktą Create Self-Signed Certificate:

image

Norėdami pridėti naują FTPS saitą, užtenka paspausti dešiniu pelės mygtuku ant katalogo Sites ir pasirinkti Add Ftp Site. Atsiradusiame lange įvedame FTP pavadinimą bei fizinį adresą. Kitame vedlio žingsnyje reikalaujame SSL panaudojimą bei nurodome prieš tai sugeneruotą sertifikatą:

image

Paskutiniame dialogo lange nurodome kas galės prieiti prie mūsų FTP, mano atveju, specialiai sukurtas vartotojas ftpuser:

image

Štai ir viskas, tai yra minimaliai reikalinga FTP over SSL konfigūracija skirta IIS 7.

FTPS panaudojimas

Žemiau pateiktas kodo pavyzdys, parodantys kaip prisijungti prie FTP over SSL:

static void Main(string[] args)
{
    ServicePointManager.ServerCertificateValidationCallback =
        (sender, certificate, chain, sllPolicyErrors) => true; 

    const string url = "ftp://localhost/";
    var ftp = (FtpWebRequest)WebRequest.Create(url);
    ftp.Credentials = new NetworkCredential("ftpuser", "ftp");
    ftp.UsePassive = true;
    ftp.EnableSsl = true;
    ftp.Method = WebRequestMethods.Ftp.ListDirectory;
    var response = (FtpWebResponse)ftp.GetResponse();

    using(var reader = new StreamReader(response.GetResponseStream()))
    {
        Console.WriteLine(reader.ReadToEnd());
    }

    Console.ReadKey();
}

Atkreipkite dėmesį į delegatą ServicePointManager.ServerCertificateValidationCallback. Kadangi aš naudoju taip vadinamą Self-Signed sertifikatą, įprastas FtpWebRequest išmestų su sertifikatu susijusią klaidą. Vienas iš apėjimo variantų – nurodyti sertifikato tikrinimo delegatą, kuris tiesiog visada grąžina true.

Tikiuosi šis straipsnis padės jums greičiau sukonfigūruoti bei pasinaudoti FTP over SSL.

Rodyk draugams

Lygiagretaus programavimo supaprastinimas su .NET 4.0 ir Reactive Extensions for .NET 3.5

Parašė Sergejus | 2010-11-18 22:18

Prieš kelias savaites darbe teko susidurti su klasikine gamintojo/naudotojo užduotimi. Scenarijus buvo toks: yra viena gija-gamintoja, kuri sukuria užduotis, patalpina jas į eilę (atmintyje), iš kurios nurodytas skaičius gijų-naudotojų pasiima po užduotį ir ją įvykdo. Programuojant .NET 3.5 ar senesniu, tai įgyvendinti reikalautų nemažai lygiagretaus programavimo žinių (gijų sinchronizavimas, sinchronizavimo primityvai ir daug daugiau). Laimei, .NET 4.0 atsirado daug patobulinimų skirtų lygiagrečiam programavimui: patogesnis darbas su gijomis bei naujas gijų planuotojas iš Task Parallel Library, gijoms saugios konstrukcijos iš System.Collections.Concurrent vardų srities ir Parallel LINQ. Šių patobulinimų dėka, užduoties įgyvendinimas pasidarė kaip niekada paprastas:

public class WorkerPool
{
    private readonly BlockingCollection<Action> _queue;
    private readonly Task[] _workers;

    public WorkerPool(int numberOfWorkers, int maxQueueSize)
    {
        _queue = new BlockingCollection<Action>(maxQueueSize);
        _workers = new Task[numberOfWorkers];

        for (var i = 0; i < numberOfWorkers; i++)
        {
            _workers[i] = SpinNewWorker();
        }
    }

    public void Enqueue(Action task)
    {
        _queue.Add(task);
    }

    public bool TryEnqueue(Action task, TimeSpan timeout)
    {
        return _queue.TryAdd(task, timeout);
    }

    private Task SpinNewWorker()
    {
        return Task.Factory.StartNew(state =>
        {
            foreach (var task in _queue.GetConsumingEnumerable())
            {
                try
                {
                    task();
                }
                catch
                {
                    // failure, just ignoring
                    continue;
                }
            }
        }, TaskCreationOptions.LongRunning);
    }
}

Išnagrinėkime kodą. Iš anksto sukuriamas gijų-naudotojų masyvas, kurios ir yra naudojamos visos aplikacijos gyvavimo metu. Gijos kuriamos Task.Factory pagalba, perduodant anoniminį delegatą. Tiek gamintojas, tiek naudotojai dirba tiesiogiai su nauju kolekcijos tipu – BlockingCollection. Ji pasižymi keliomis savybėmis:

  • jos viduje pagal nutylėjimą naudojama gijoms saugi duomenų struktūra – eilę (angl. queue), kurios maksimalus dydis gali būti apribotas; pasiekus ribą – gija, bandanti pridėti naują elementą, užsiblokuos, kol eilėje neatsiras vietos naujam elementui
  • metodas GetConsumingEnumerable() leidžia ypač intuityviai vienu metu iš kelių gijų skaityti elementus iš eilės ir kai eilė patampa tuščia – gija automatiškai užmiega
  • BlockingCollection turi ne tik įprastus metodus Add() ir Take(), bet ir TryAdd() bei TryTake(), kurie pasižymi tuo, jeigu operaciją nepavyksta įvykdyti per nurodytą laiką (eilė yra perpildyta TryAdd() atveju arba eilė yra tuščia TryTake() atveju), ji bus tiesio praignoruota

O dabar svarbiausias dalykas, tam kad pasinaudoti aukščiau išvardinta galimybe, jums nebūtinas(!) .NET 4.0. Užtenka pasinaudoti System.Treading.dllReactive Extensions for .NET 3.5! Tikiuosi tai jums pravers, kaip pravertė man…

Rodyk draugams