\


 Monday, 24 May 2010
Omezení pro argumenty šablony (template) v C++ napodobující “where“ omezení pro generické argumenty v C#

Tento článek je hlavně reakce na stížnost,  kterou měl kolega-vývojář z firmy, kde vývojáři použivají C++ i C#. Stížnost byla zaměřena na to, že na rozdíl od C# není možné  v C++ zkontrolovat v době kompilace, zda argument předaný do šablony implementuje vyžadované rozhraní, nebo je potomkem námi vyžadované třídy.

Zdůrazním hned v úvodu, že v článku mluvím o “klasickém” C++ a ne o jeho .Net dialektu C++/CLI, který podporuje šablony i generiku.

Příkladem v C# může být následující třída Collection, která vyžaduje, aby za generický parametr T byla dosazena třída podporující rozhraní IBusinessObject. Podrobněji jsem podobnou kolekci už v éře př. lq. (čteme před Linqem) :-) popisoval třeba zde.

public class Collection<T> : where T : IBusinessObject
{
}

Šablony v C++ jsou i přes povrchní syntaktickou podobnost zcela odlišnou jazykovou konstrukcí při srovnání s generikou v C#, a protože jsou “uzavřené šablonové typy” generovány již v době kompilace, kompilátor sám zajistí, že typ předaný do šablony podporuje všechny námi vyžadované metody, vlastnosti a operátory. Přesto existují situace, kdy chceme garantovat, že do naší “šablonové” kolekce jsou vkládány jen objekty podporující stejné rozhraní, nebo vyžadujeme, aby vkládané objekty byly povinně potomky nějaké třídy. Nestačí nám tedy, když argument šablony podporuje metodu Commit, která má stejnou signaturu jako metoda Commit z třídy Transaction, ale chceme mít již v době kompilace ověřeno, že předaná instance je potomkem zřídy Transaction. Důvod? Třeba to, že metoda Commit z třídy Transaction má v sobě vysokoúrovňový scénář, na který spoléháte (vzor Template method, jehož název nemá nic společného s šablonami ani generikou :-) ).

Pro novou verzi jazyka C++0x bylo plánováno, že různá omezení parametru šablony bude možné vyjádřit pomocí tzv. “Concepts”. Bohužel Concepts ani v C++0x  nakonec nebudou a my si musíme vystačit s výrazovými  prostředky současné verze jazyka C++. S jednou výjimkou - v článku použiju i jednu elegantní konstrukci z C++0x (implementována ve Visual Studio 2010), statickou verifikaci pomocí klíčového slova static_assert, ale ukážu, jak snadno můžeme static_assert nahradit.

Mějme běžnou šablonu třídy Collection. V našem případě jde jen o dostatečný draft třídy Collection s konstruktorem, destruktorem a prázdnou implementací metod Add a Remove.

template
<typename T>
class Collection
{
public:
    
    ~Collection(void)
    {        
    }
    
    Collection(void)
    {
        
    }
    void Add(T& t)
    {

    }
    
    void Remove(T& t)
    {

    }
};

Za generický parametr T lze nyní substituovat “cokoli”, ale my chceme omezit typ T pouze na instance podporující rozhraní IBusinessObject.

class IBusinessObject
{


public:
    IBusinessObject(void);
    virtual void CommitChanges() = 0;
    virtual void RollbackChanges() = 0;
    virtual ~IBusinessObject(void);
};

V C++ samozřejmě za rozhraní považujeme třídu, jejíž všechny metody jsou abstraktní, a proto převod dvou omezení ze C# ( potomek třídy, implementor rozhraní (realizace) ) se nám v C++ redukuje na nalezení ekvivalentu omezení “parametr šablony musí být potomkem námi vyžadované třídy”.

K vyjádření takového omezení si zavedeme pomocnou šablonovou třídu IsDerivedTest:

#pragma once

template
    <typename Base, typename Derived>
class TestIsDerived
{
    private:
        struct Is_Derived_Helper
        {
            int dummy;
        };
        
        struct Not_Derived_Helper
        {
            int dummy;
            int dummy2;
        };    
    
    private:
        TestIsDerived(void);
        ~TestIsDerived(void);

        static Is_Derived_Helper Test(Base* base);                
        static Not_Derived_Helper Test(...);

    public:                
        enum TestResult
        {
            IsDerivedResult = ((sizeof(Test(static_cast<Derived*>(0))) == (sizeof(Is_Derived_Helper))))
        };
};

Co třída TestIsDerived umí?


Dva parametry šablony TestIsDerived mají výmluvné názvy – Base (bázová třída) a Derived (třída, u níž chceme zkontrolovat, zda je z Base odvozena)

Tuto třídu nechceme nikdy instanciovat, proto jsou její konstruktor a destruktor privátní.  U privátních struktur Is_Derived_Helper a Not_Derived_Helper je důležitá jen jedna věc – musí mít odlišnou velikost (sizeof(Is_Derived_Helper) != sizeof(Not_Derived_Helper ), a proto první obsahuje jednu "dummy" proměnnou typu int a druhá struktura dvě “dummy” proměnné typu int.

Při kontrole zda parametr šablony Derived je odvozen z parametru šablony Base postupujeme takto:

  1. Máme dvě privátní statické metody nazvané Test. Tyto funkce jsou jen deklarovány, jejich definici nepotřebujeme, protože nebudou nikdy zavolány. Důležité je jen to, že jedna metoda Test vrací Is_Derived_Helper  a druhá metoda Test vrací Not_Derived_Helper  - připomňme si, že jde o struktury mající různou velikost.
  2. Metoda Test, která vrací strukturu Is_Derived_Helper, přijímá jako argument pointer na parametr Base, a může tedy přijmout i pointer na Derived, pokud Derived je potomkem Base. Druhá metoda Test (Test(…)) bude vyvolána vždy, když se nepodaří Derived* převést na Base*.
  3. Do hodnoty IsDerivedResult v enumeraci TestResult uložíme výsledek výrazu ((sizeof(Test(static_cast<Derived*>(0))) == (sizeof(Is_Derived_Helper)))) - “voláme” metodu Test tak, že přes operátor static_cast přetypujeme konstantu 0 na pointer na Derived . Ač může vypadat přetypování 0 na Derived* jako “zvěrstvo” za účelem rychlého poslání programu do řiti říše binárního nebytí,  slovo voláme je v předchozí větě záměrně v uvozovkách, protože jak jsem již psal, k žádnému volání metody Test nikdy nedojde. V době kompilace ale kompilátor zjistí, jakou variantu metody Test by zavolal pro Derived* a my z velikosti “potenciální a nikdy skutečně nevrácené” návratové hodnoty zvolené metody Test jsme schopni poznat, jestli je Derived potomkem Base. Pokud je Derived potomkem Base, bude zvolena varianta Test(Base* base), která vrací Is_Derived_Helper, a podmínka ((sizeof(Test(static_cast<Derived*>(0))) == (sizeof(Is_Derived_Helper) bude pravdivá  - uložíme do IsDerivedResult hodnotu 1. Pokud Derived není potomkem Base, bude zvolena varianta metody Test(…), která vrací Not_Derived_Helper, a podmínka ((sizeof(Test(static_cast<Derived*>(0))) == (sizeof(Is_Derived_Helper) bude nepravdivá (uložíme do IsDerivedResult hodnotu 0). Znovu zdůrazňuji, že k vyhodnocení podmínky dojde již v době kompilace.

Scéna je připravena, pozveme hlavní aktéry:

Mějme dvě třídy. Třída Invoice je potomkem třídy IBusinessObject a třída NotBusinessObject (nomen omen :) ) překvapivě není.:)



class NotBusinessObject
{
public:
    NotBusinessObject(void);
    virtual ~NotBusinessObject(void);
};

#pragma once
#include "IBusinessObject.h"

class Invoice : public IBusinessObject
{
    public:
        
        virtual void CommitChanges();
        virtual void RollbackChanges();
        
        Invoice(void);
        ~Invoice(void);
};

#include "StdAfx.h"
#include <iostream>
#include "Invoice.h"

using namespace std;

Invoice::Invoice(void)
{
}


Invoice::~Invoice(void)
{
}

void Invoice::CommitChanges()
{
    cout << "Commit";
}

void Invoice::RollbackChanges()
{
    cout << "Rollback";
}
        

Naše třída Collection má přijímat jen instance potomků třídy IBusinessObject.

#pragma once
#include "TestIsDerived.h"


template
<typename T>
class Collection
{
public:
    
    ~Collection(void)
    {
     static_assert(TestIsDerived<IBusinessObject, T>::IsDerivedResult, "Invalid base class, IBusinessObject required");
    }
    
    Collection(void)
    {
        
    }
    void Add(T& t)
    {

    }
    
    void Remove(T& t)
    {

    }
};

Do destruktoru jsme vložili statickou kontrolu, kdy námi vytvořené pomocné třídě TestIsDerived substituujeme za parametr šablony Base třídu IBusinessObject a za parametr Derived přímo parametr šablony třídy Collection, který musí být vždy potomkem IBusinessObject. Do destruktoru jsme kód vložili proto, aby bylo garantováno, že při instanciaci šablony Collection ke statické kontrole vždy v době kompilace dojde – kód bychom mohli vložit i do konstruktoru, ale budeme-li mít konstruktorů více, museli bychom kontrolu duplikovat v každém konstruktoru. Statická kontrola v destruktoru spoléhá na to, že vytvořený konkrétní objekt Collection<T> je v aplikaci zlikvidován  - buď jde o likvidaci automatické (lokální) proměnné, nebo vy sami použijete operátor delete atd. Jestliže by destruktor objektu v aplikaci nikdy nebyl volán, kompilátor nebude destruktor v šablonové třídě při rozvíjení šablony instanciovat, stejně jako nikdy neinstanciuje další nepoužité metody v šabloně.

A použití:

Tento kód bez problémů projde. Invoice je potomkem IBusinessObject.

// TemplateConstraint_Console.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include "Collection.h"
#include "Invoice.h"
#include "NotBusinessObject.h"

int _tmain(int argc, _TCHAR* argv[])
{    
    Collection<Invoice> col1;
    
}

Tento kód nahlásí chybu. NotBusinessObject není potomkem třídy IBusinessObject.

// TemplateConstraint_Console.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include "Collection.h"
#include "Invoice.h"
#include "NotBusinessObject.h"

int _tmain(int argc, _TCHAR* argv[])
{    
    Collection<NotBusinessObject> col1;
        
}

Ve Visual Studiu 2010 dostanu v době kompilace tuto chybu:

Error    1    error C2338: Invalid base class, IBusinessObject required    c:\users\stein\documents\visual studio 2010\projects\templateconstraint_console\templateconstraint_console\collection.h    16    1    TemplateConstraint_Console

Tím je náš úkol splněn, ale jak jsem sliboval, ukážu nyní, i jak se můžeme obejít bez klíčového slova static_assert z C++0x.

Třída TestIsDerived zůstane beze změny, ale napíšeme další dvě šablony, ve kterých budeme reagovat na hodnotu IsDerivedResult z třídy TestIsDerived, což původně dělal přímo static_assert.

#pragma once
template
<int T>
struct Invalid_Base_Class
{
    public:
        Invalid_Base_Class(void)
        {

        }
        ~Invalid_Base_Class(void)
        {

        }
};

template<>
struct Invalid_Base_Class<0>
{
    private:
        Invalid_Base_Class(void);
        ~Invalid_Base_Class(void);
};
 

Šablona Invalid_Base_Class má jeden šablonový parametr typu int. Struktura není svým rozhaním zajímavá, podstatný je pro nás jen název struktury, abychom v době kompilace viděli chybovou hlášku “Špatná bázová třída”. První šablona Invalid_Base_Class je běžnou strukturou, která bude použita pro instanciaci všech hodnot typu int krome hodnoty 0. Pro hodnotu 0, o níž víme, že je uložena v hodnotě enumerace TestIsDerived::IsDerivedResult, jestliže DERIVED NENÍ potomkem BASE, je určena explicitní specializace Invalid_Base_Class<0>, která má ale privátní konstruktor, takže pokus o její vytvoření vždy již při kompilaci selže.

V hrubé podobě, tedy bez vlastního makra assert_is_derived apod., které skryje práci s našimi strukturami Invalid_base_Class a které by zde jen zamlžovalo průzračnost řešení, můžeme kontrolu na nutnost šablonového parametru T třídy Collection<T> dědit z třídy IBusinessObject přepsat takto:

#pragma once
#include "Invalid_Base_Class.h"


template
<typename T>
class Collection
{
public:
    
    ~Collection(void)
    {
        Invalid_Base_Class<TestIsDerived<IBusinessObject, T>::IsDerivedResult> invalid_base;
        invalid_base;
        
    }
    
    Collection(void)
    {
        
    }
    void Add(T& t)
    {

    }
    
    void Remove(T& t)
    {

    }
};

V destruktoru se pokusíme instanciovat strukturu Invalid_Base_Class, které jako šablonový argument předáme hodnotu v IsDerivedResult.

Collection<Invoice> opět bez problémů projde, ale při pokusu vytvořit Collection<NotBusinessObject> dostanu při kompilaci tuto chybovou zprávu:

Error    1    error C2248: 'Invalid_Base_Class<0>::Invalid_Base_Class' : cannot access private member declared in class 'Invalid_Base_Class<0>'    c:\users\stein\documents\visual studio 2010\projects\templateconstraint_console\templateconstraint_console\collection.h    14    1    TemplateConstraint_Console

Řešení by se dalo přisladit dalším syntaktickým cukrem, ale princip by měl být z článku zřejmý.
Triky s šablonami jsou úžasné a převod některých rysů generiky ze C# není zase tak problematický. Jen ta nonšalantní elegance, která je C# vlastní, v přihroublém, ale o to výkonnějším, C++ trochu chybí. :) Kdyby někoho triky se šablonami zaujaly, doporučuji ke studiu Alexendrescovu knihu Modern C++ Design: Generic Programming and Design Patterns Applied.



Monday, 24 May 2010 15:50:36 (Central Europe Standard Time, UTC+01:00)       
Comments [0]  C# | Nativní kód


 Wednesday, 07 April 2010
Pozvánka na kurz Objektovými principy a návrhovými vzory řízený design a vývoj kvalitních aplikací 1 – jaro 2010

Update: Kurz je zcela obsazen včetně náhradníků.

Rád bych Vás pozval na další běh kurzu Objektovými principy a návrhovými vzory řízený design a vývoj kvalitních aplikací 1. Pokud se někdo z Vás (oprávněně) diví, proč tak pozdě a proč Vás nezvu i na kurz OOP 2, níže v tomto spotu nalezne odpovědi.

Veřejný kurz Objektovými principy a návrhovými vzory řízený design a vývoj kvalitních aplikací 1

Datum konání kurzu:  14. 6. – 16. 6. 2010

Místo konání:

Školící středisko Tutor

U Půjčovny 2
110 00 Praha 1

Po celý den máme k dispozici wifi připojení a samozřejmě také teplé a studené nápoje. V ceně kurzu jsou obědy v hotelu.

Podrobné informace o kurzu a možnost přihlásit se na kurz

Program kurzu
Výběr z ohlasů na kurz

Odpovědi na některé dotazy, které jsme obdrželi od dříve přihlášených zákazníků emailem.

Otázka: Proč se kurz nekoná na předchozím místě (v hotelu Villa), kde byli naši kolegové?

Odpověď: Po vyhodnocení ohlasů na kurz jsme zjistili, že účastníci nebyli  (stejně jako já :-)) příliš spokojeni s jídlem. Také jsme při domlouvání hotelu byli ujišťováni, kolik účastníků se může na kurz přihlásit  a jak lze pro ně uspořádat pohodlně prostory, a tyto informace se v průběhu kurzu jevily jako marketingové fráze až blafy velmi optimistickými nebo nepočítaly s průměrným Evropanem, ale standardním liliputem.:-) Stručně řečeno – poměr cena/výkon nebyl u čtyřhvězdičkového hotelu příliš dobrý. Jedinou výhodou hotelu Villa bylo parkování, nyní bude lepší na ”poslední míli” použít  MHD.

Otázka: Proč se koná kurz tak pozdě (oproti přechozím rokům) a proč se nekoná kurz OOP2.

Odpověď: Byl a jsem vytížen tento rok dalšími projekty a nalézt vhodné termíny bylo problematické.  Také jsme sháněli nové prostory, kde se bude kurz konat (viz předchozí odpověď). Termín kurzu OOP2 vycházel již na červenec a v době dovolených nemá smysl kurz pořádat. Při větším zájmu vyhlásím (co nejdříve) více termínů kurzu OOP2 na podzim.

 

Těším se na setkání na kurzu!



Wednesday, 07 April 2010 12:48:36 (Central Europe Standard Time, UTC+01:00)       
Comments [0]  Analytické drobky | Kurzy UML a OOP | Návrhové vzory | UML


 Sunday, 21 March 2010
Výhody a nevýhody softwarových továren

Emailem jsem dostal zajímavou otázku, jaký je můj názor na softwarové továrny a kde vidím výhody a nevýhody softwarových továren. Odpověď nakonec publikuji i zde – už jen proto, že jsem si při jejím psaní uvědomil, že na továrnu kladu stejné nároky jako na kteroukoli další knihovnu v systému a že výběr softwarové továrny se u mě moc neliší od výběru třeba ORM Frameworku. Nejde o taxativní výčet výhod a nevýhod, ale spíš o volně nahozená témata, která mě za 20 minut psaní příspěvku napadla.

SW továrny jsou jen pokračováním trendu, že nemá smysl vynalézat znovu kolo ani v případě, že by bylo o dvě setiny procenta krásnější než to staré, které je již navržené a hotové. SW továrna říká – namísto opakování chyb svých předchůdců použijte ověřené zkušenosti, osvědčené praktiky, doporučené postupy a již otestovaný kód. Namísto obecného návrhového vzoru nebo vágně popsaného doporučovaného postupu máte k dispozici osvědčenou knihovnu, základní průvodce a odborný polštář, který by měl zabránit, že napácháte v dané oblasti velké množství hrubých bezpečnostních nebo jen s duchem technologie neslučitelných chyb.

Takže výhody:

1) Osvědčená řešení ihned po ruce. Tím se cíl SW faktory moc neliší od obecných frameworků a návrhových vzorů.

2) Není nutné psát infrastrukturní kód přímo v SW firmě (ISV). Je možné se soustředit na problémovou doménu zákazníka a ne na to, jak centrálně publikovat události nebo jak propojit různé pohledy v aplikaci tak, aby spolu dokázaly komunikovat .

3) Pokud stejnou továrnu používáte napříč všemi projekty, je zaučení nového vývojáře na dalším projektu jednodušší a rychlejší.

4) SW továrna používá praktiky osvědčené v dané technologii. Takže odlišně jsou zakódovány původně platformně nezávislá doporučení a všeobecně zaměřené vzory v SW továrně pro ASP.Net a jinak pro Silverlight. Místo popisu obecných návrhových vzorů je již vzor adaptován na konkrétní cílové prostředí.

Nevýhody:

1) Kód nemáte pod kontrolou a musíte se spolehnout, že továrna sama neobsahuje příliš kritických chyb ani otravných chyb s nižší prioritou. To platí o jakékoli abstrakci (frameworku, knihovně), kterou na projektu používáte, ale myslím, že ani v Microsoftu stále nemají SW továrny stejnou podporu jako samotný .Net Framework. A také by měla být známa alespoň orientační roadmapa SW továrny, abyste věděli, kam autoři směřují a jestli jsou si vědomi nevýhod a omezení stávající verze SW továrny. Od továrny, která se ihned po svém uvedení honosí přídomky „experimentální, testovací, zkušební“, bych dal ruce pryč. Jen ti nejodvážnější z nás si dovolí svým zákazníkům za půl roku po zatracení SW továrny jako slepé uličky tvrdit, že musí trochu poštelovat kardiostimulátor výkonného srdce aplikace a že místo plánovaných dvou hodin za přidání dvou vlastností do objednávky zákazník zaplatí dva měsíce migrace na jinou SW továrnu. Nenechal bych se zmýlit tím, že některé firmy pro odvážlivce používající jejich experimentální SW továrny a frameworky razí lichotivý titul „early adopter“ – méně korektní a pravdě bližší překlad z marketingového slovníku totiž zní „natěšený všehoschopný blbec“.

2) Univerzální řešení jako je SW továrna může být pro jednoduché aplikace zabijákem. Více času strávíte integrací aplikace do rámce vynucovaného SW továrnou než psaním obchodní logiky specifické pro aplikaci a důležité pro zákazníka.

3) U některých složitějších aplikací zjistíte, že rámec SW továrny je příliš těsný a vy potřebujete SW továrnu pro svůj projekt upravit nebo rozšířit. Náklady na rozšíření služeb , „hackování“ a ohýbaní SW továrny pro danou problematiku mohou převážit nad výhodami SW továrny. Z velké radosti nad úsporou času v raných fázích projektu, kde byla továrna použita, můžete ještě před dodáním první verze projektu a po pořádném časovém skluzu, vynuceném třeba přepisem částí továrny, které pro výkonnostních testech  nestačí stávajícím požadavkům na projekt, přejít k jadrným kletbám nad šílenou SW továrnou, v níž si úprava jednoho modulu kaskádově vynutí úpravy všech dalších modulů. Místo používání původně slibované elegantní černé skříňky s jednoduchými službami se prohrabujete nevábnými vyhřezlými vnitřnostmi mizerně navržené SW továrny.

Z výhod a nevýhod snad vyplývá můj postoj k SW továrnám. SW továrny jsou dalším evolučním stádiem na cestě, jejímž cílem je využít pro danou problematiku osvědčená řešení a postupy, nepsat stále se opakující kód nebo v každém řešení už jen změnou typu klienta (Web, Windows Forms, Silverlight, mobilní aplikace) neprocházet znovu a znovu všechny slepé vývojářské uličky specifické pro použitou technologii. Objevování objeveného ponechme těm, kterým stačí zařvat vítězoslavně heuréka, i když jsou v pořadí stí nebo tisící, kteří stejnou infrastrukturní nebo funkční trivialitu konečně zakódovali rozumným a v životním cyklu projektu dále udržovatelným způsobem.

A samozřejmě si nemyslím, že tohle evoluční stádium je konečné. :)



Sunday, 21 March 2010 11:57:10 (Central Europe Standard Time, UTC+01:00)       
Comments [6]  .NET Framework | Analytické drobky | Návrhové vzory | UML


 Wednesday, 24 February 2010
Podivné? chování při explicitním přetypování typu dynamic ve Visual Studiu 2010 RC

Na twitteru jsem psal, že si pohraju s implementací rozhraní ve třídě přes automatickou delegaci na privátní  proměnnou s využitím nového typu dynamic v C# 4.0. Jestliže se dobře pamatuji, většinou se po nějakém takovém řešení pídí Delphisté. Z příkladu níže bude asi jasné i pro ostatni, co mám předchozími hutnými větami na mysli .

Při hraní si s typem dynamic jsem ale narazil na zvláštní chování při explicitním přetypování a chtěl bych poprosit někoho dalšího z mých čtenářů o vyzkoušení stejného chování ve Visual Studiu 2010 (nejlépe nejen na RC, ale i na starší Betě 2, kterou jsem už smazal). Příklad níže je jen jednoduchý “jednosměrný” prototyp, na kterém vynikne problém s explicitním přetypováním.

Zde je mnou zmiňovaná podivnost (problém):

Mějme rozhraní IWorker:

   public interface IWorker
    {
        void DoWork();
    }

A třídu Worker, která toto rozhraní implementuje.

 class Worker : IWorker
    {
        #region Implementation of IWorker

        public void DoWork()
        {
            Console.WriteLine(GetType().ToString());
        }

        #endregion
    }

Dále máme  třídu Order, která rozhraní IWorker neimplementuje, ale má privátní proměnnou m_worker implementující toto rozhraní, kterou předá své bázové třídě DirtyCastBase. DirtyCastBase je třída, která zajistí, že bude-li klient přetypovávat instanci Order na rozhraní IWorker, tak toto přetypování projde a klient dostane jako implementora instanci m_worker.

 public class Order : DirtyCastBase
    {
        private IWorker m_worker;
        public Order() : base()
        {
            m_worker = new Worker();
            SetImplementors(m_worker);
        }
    }

Třída DirtyCastBase je potomkem třídy DynamicObject, která nám v .Net 4.0 dovoluje reagovat na “dynamická volání” a přidat jednoduše “dynamické chování” přepsáním metod začínajících písmeny Try (TryGetMember, TrySetMember ) apod. Já jsem přepsal metodu TryConvert, která se s využitím chráněné virtuální metody TryFindImplementor pokusí nalézt objekt, který podporuje rozhraní vyžadované uživatelem. Deskriptor rozhraní je předán ve vlastnosti Type argumentu binder.

public class DirtyCastBase : DynamicObject
    {
        private IEnumerable<Object> m_implementors;
        private Dictionary<Type, object> m_castContext;

        public DirtyCastBase()
        {
            m_castContext = new Dictionary<Type, object>();
        }
        

        public override bool TryConvert(ConvertBinder binder, out object result)
        {
            Type requestedType = binder.Type;            

            Tuple<bool, Object> FindResult =  TryFindImplementor(m_implementors, m_castContext, requestedType);
            
            if (FindResult.Item1)
            {
                result = FindResult.Item2;
                return true;
            }

            return base.TryConvert(binder, out result);
        }

        
        protected virtual Tuple<bool, Object> TryFindImplementor(IEnumerable<Object> implementors, Dictionary<Type, object> currentCastContext, Type requestedType)
        {
            if (implementors == null)
            {
                throw new ArgumentNullException("implementors");
            }
            if (currentCastContext == null)
            {
                throw new ArgumentNullException("currentCastContext");
            }
            if (requestedType == null)
            {
                throw new ArgumentNullException("requestedType");
            }

            object result = null;

            bool found = m_castContext.TryGetValue(requestedType, out result);
            
            
            if (!found)
            {
                
                result = (from implementor in implementors
                          where implementor != null
                          let type = implementor.GetType()
                          where requestedType.IsAssignableFrom(type)
                          select implementor).FirstOrDefault();

                found = result != null;
            }
            
            if (found)
            {
                m_castContext.Add(requestedType, result);
            }
            return new Tuple<bool, object>(found, result);

        }

        protected void SetImplementors(params object[] implementors)
        {
            SetImplementors(implementors.AsEnumerable());
        }

        protected void SetImplementors(IEnumerable<object> implementors)
        {
            if (m_implementors != null)
            {
                throw new InvalidOperationException();
                
            }
                        
            m_implementors = implementors ?? Enumerable.Empty<Object>();
        }
    }
 

V našem případě by tedy třída Order by měla dovolit přetypování na rozhraní IWorker, i když sama toto rozhraní neimplementuje. Zanedbejme nyní, že není zachována referenční identita při konverzi i že vydáváme jako implementora privátní objekt, protože pro demonstrovanou techniku to není příliš podstatné.

Tento kód ověří, že přetypování projde. Využíváme implicitní  (“bez závorek”) konverzi. Samozřejmě že je nutné instanci Order přiřadit do proměnné typu dynamic.

    class Program
    {
        static void Main(string[] args)
        {
            dynamic order = new Order();

            IWorker worker = order;

            worker.DoWork();
            Console.ReadLine();
        }
    }

Implicitní konverze projde, a jak jsem očekával, je vyvolána naše metoda TryConvert.

Když ale projde implicitní konverze, proč explicitní konverze selže a metoda TryConvert vyvolá výjimku?

 static void Main(string[] args)
        {
            dynamic order = new Order();

            IWorker worker =  (IWorker) order;

            worker.DoWork();
            Console.ReadLine();
        }

Ihned je vyvolána výjimka InvalidcastException {"Unable to cast object of type 'DynamicCastTest.Order' to type 'DynamicCastTest.IWorker'."}

Z call stacku výjimky (at CallSite.Target(Closure , CallSite , Object ) se dá odvodit, že výjimku vyhodil C# DLR binder, který se ale ani nepokusil zavolat metodu TryConvert. Metoda TryConvert by ve vlastnosti Explicit argumentu binder měla dostat příznak, že šlo o explicitní konverzi, ale tato metoda evidentně není volána.

Přitom z dokumentace metody TryConvert se dá usoudit, že by metoda TryConvert měla být při explicitní konverzi volána.

Ještě jsem zcela do detailu nestudoval všechna pravidla pro typ dynamic ve specifikaci C# 4.0, ale tohle na mě působí jako bug. Pokud projde implicitní konverze, proč by byla zcela zakázána explicitní? To vůbec není v souladu s pravidly kompilátoru pro konverze v C#:

“The set of explicit conversions includes all implicit conversions. This means that redundant cast expressions are allowed.” (C# specification sekce 6.2 Explicit conversions)

A C# DLR binder se i za běhu snaží podle mých dosavadních zkušeností vždy co co nejvěrněji napodobit chování C# kompilátoru.

Anebo už jsem dnes utahaný, nejde o žádnou anomálii a něco triviálního ve svém kódu přehlížím? :-)

Tedy znovu. Můžete někdo spustit projekt ve svém Visual Studiu (nejlépe i v Betě 2) a podělit se o výsledek?

Zde je projekt ke stažení.

Díky!



Wednesday, 24 February 2010 19:19:03 (Central Europe Standard Time, UTC+01:00)       
Comments [4]  .NET Framework | C#


 Friday, 12 February 2010
Doplnění metod FillPie a DrawPie do objektu Graphics v Compact .Net Frameworku

Nedávno se na fóru vývojáře objevil dotaz, jak nahradit chybějící metodu FillPie v objektu Graphics na Compact .Net Frameworku, protože prý ani tradiční zuřivé googlování žádné výsledky nepřineslo. Zkusil jsem napsat implementaci metody FillPie, a protože se podobných dotazů na internetu dá najít více, dávám kód obohacený nyní i o metodu DrawPie na blog, aby nezůstal utopen jen v diskuzním fóru.

Compact .Net Framework sice nemá metodu FillPie ani DrawPie, ale má obecné metody DrawPolygon a FillPolygon, se kterými nakreslíte, co se vám zlíbí.  Zhýrale jsem kód opět trochu zlinqovatěl, asi začínám být na LINQu a extenzních metodách závislý. Inu, jak říkáme my C# vývojáři, původně odříkané extenzní metody plný zásobník volání. :-)

 

static class GraphicsExtensions
    {

        public static readonly float ANGLE_MULTIPLY = (float) Math.PI / 180;
        
        public static void FillPie(this Graphics g, SolidBrush brush, int x, int y, int width, int height, float startAngle,  float sweepAngle)
        {
            var tempPoints = GetTempPoints(sweepAngle, startAngle, x, y, width, height);

            g.FillPolygon(brush, tempPoints);
        }

        public static void DrawPie(this Graphics g, Pen pen, int x, int y, int width, int height, float startAngle, float sweepAngle)
        {
            var tempPoints = GetTempPoints(sweepAngle, startAngle, x, y, width, height);

            g.DrawPolygon(pen, tempPoints);
        }

        private static Point[] GetTempPoints(float sweepAngle, float startAngle, int x, int y, int width, int height)
        {
            const float HALF_FACTOR = 2f;
            const int TEMP_POINT_FIRST = 0;
            const int TEMP_POINT_LAST= 100;
            const int TOTAL_POINTS = TEMP_POINT_LAST - TEMP_POINT_FIRST;            
            

            float angleInc = (sweepAngle - startAngle) / TOTAL_POINTS;

            float halfWidth = width / HALF_FACTOR;

            float halfHeight= height / HALF_FACTOR;
            
            return (new[] {new Point
                               {
                                   X = x,
                                   Y = y
                               }
                          })
                                   
                .Union(
                               
                          (from i in Enumerable.Range(TEMP_POINT_FIRST, TOTAL_POINTS)
                           let angle = i == TEMP_POINT_LAST - 1? sweepAngle : startAngle + (i * angleInc)
                           select new Point
                                      {
                                          X = (int) (x + (Math.Cos(angle*(ANGLE_MULTIPLY))*(halfWidth))),
                                          Y = (int) (y + (Math.Sin(angle*(ANGLE_MULTIPLY))*(halfHeight)))
                                      })).ToArray();
        }
    }

 

Použití metod:

public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {

        }

        protected override void OnPaint(PaintEventArgs e)
        {
            base.OnPaint(e);
            using (var redBrush = new SolidBrush(Color.Red))
            using (var blueBrush = new SolidBrush(Color.Blue))
            using (var greenBrush = new SolidBrush(Color.ForestGreen))
            {
                e.Graphics.FillPie(redBrush, Width / 2, Height / 2, Width / 2, Height / 2, 0, 35f);
                e.Graphics.FillPie(blueBrush, Width / 2, Height / 2, Width / 2, Height / 2, 35f, 80f);
                e.Graphics.FillPie(greenBrush, Width / 2, Height / 2, Width / 2, Height / 2, 80f, 360f);                
            }

            using (var redPen = new Pen(Color.IndianRed))
            {
                e.Graphics.DrawPie(redPen, Width / 5, Height / 5, Width / 5, Height / 5, 0, 60f);
            }
        }
    }

 

A zde je náhled na formulář:

FillPieResult



Friday, 12 February 2010 13:17:54 (Central Europe Standard Time, UTC+01:00)       
Comments [0]  Compact .Net Framework | LINQ


 Tuesday, 02 February 2010
Hrátky s Reaktivním frameworkem (RX extenze)

V předchozím článku jsem ukazoval, jak volat asynchronně metody z C# Posterous API v Silverlightu. C# Posterous API nabízí asynchronní zpracování pomocí jednoho z doporučovaného přístupu k asynchronním operacím v .Net Frameworku – metoda s konvenčním sufixem Async (LoadPostsAsync) spustí vykonání operace v jiném vlákně a výsledky operace jsou nabídnuty v argumentech události, která je (opět) jen dle jmenné konvence spojena s asynchronní operací (událost LoadPostsCompleted). C# Posterous API nenabízí ve svém rozhraní  metody pro podporu dalšího a již od verze 1.0 .Net Frameworku přítomného asynchronního vzoru, který je spojen s dvojicí metod začínajících prefixem Begin a End. (BeginGetRequest, EndGetRequest, BeginRead, EndRead apod.)

Dále předpokládám, že oba přístupy k vytváření asynchronních opreací znáte a že jste si vědomi i toho, jak se způsob práce s asynchronními API odlišuje od práce s běžnými synchronnními metodami.

V již odkazovaném článku bylo dobře patrné, jak je řízení toku asynchronních operaci odlišné od sady volání běžných synchronních operací.

Pro připomenutí:

posterousAccount.SitesLoaded += (o, e) =>
                      {
                          throwIfAsyncEx(e.Exception);
                          posterousAccount.PrimarySite.PostsLoaded += (_, e2) =>
                                                                          {
                                                                              throwIfAsyncEx(e2.Exception);
                                                                              Posts = (from p in e2.Value
                                                                                      select new ViewPost
                                                                                                 {
                                                                                                     Title = p.Title,
                                                                                                     Body = p.Body,
                                                                                                     Url = p.Url

                                                                                                 }).ToList();                                                                                                            
                                                                              
                                                                          };
                          posterousAccount.PrimarySite.LoadAllPostsAsync();
                      };



posterousAccount.LoadSitesAsync();

Jediné, co tento kód dělá, je, že nejprve (!) nahraje všechny blogy (příkaz k asynchronnímu nahrání posterousAccount.LoadSitesAsync(); je na posledním (!) řádku. Na prvním (!) řádku máme zpracování výsledku volání metody LoadSitesAsync, ve kterém opět nejdříve (!) lambdou přihlášenou k odběru události  PostsLoaded (posterousAccount.PrimarySite.PostsLoaded += (_, e2)) řekneme, jak zpracujeme výsledek následného (!) volání další asynchronní metody (posterousAccount.LoadSitesAsync());. Tato “inverzní“ práce s asynchronními metodami a zpracováním jejich výsledku je na hony a možná ještě dále vzdálena intuitivní práci se synchronními metodami.:-)

Zkusme se nyní podívat, jak by nám s “převrácením starších asynchronních metod z hlavy zpět na synchronní nohy” mohl pomoci RX Framework. Úplné základy v tomto článku nezazní a začátečníky odkazuji na sérii přednášek na Channel 9, kde dozvíte i zajímavé podrobnosti o genezi celého RX Frameworku  a matematické dualitě rozhraní IEnumerable a IObservable (jinými slovy o společných rysech dobře známých GoF návrhových vzorů Iterátor a Observer).

Současné příklady jsou vytvořeny v aplikaci Windows Forms pro .Net 3.5. Silverlight má své zvláštnosti a a rozchození příkladů v SL si zaslouží další článek, protože teď by řešení problémů specifických pro SL zamlžovalo cíl příkladu. Aplikace je pro .Net 3.5, protože stejná aplikace pro .Net 4.0 hlásí konflikt (ambiguous reference) mezi NF typy a RX typy.

Upozornění: Nic z toho, co napíšu neberte ani jako dogmata ani, nedej bože, jako best practices. RX Framework je v Betě, zdokumentován je mizerně a z jednoho řádku u každé metody se dá jen těžko bez dalších experimetů vytušit, co přesně metoda dělá. Tento článek je výsledkem hraní si pro účely jednoho projektu, kam se RX extenze hodí  a zjednodušují (alespoň to tak prozatím vypadá :-) ) dost rutinních činností.

Zde j výsledek našeho snažení, abychom měli motivaci se RX Frameworkem zabývat.

 var resultPosts = from sites in account.GetSites()
                              from site in sites.ToObservable()
                              from posts in site.GetPosts()
                              from post in posts.ToObservable()
                              where post.Private == false
                              select post;

Získání blogů (Sites) i blogpostů (post) je stále asynchronní, ale výsledný kód vypadá jako běžný LINQ (To Enumerable) dotaz. Žádné inverzní volání a práce s výsledkem, jen prostý dotaz, jehož zvláštností je pouze to, že v některých místech voláme metodu ToObservable.

Jak jsem dosáhl tohoto výsledku?

Podíváme-li se na první řádek, vidíme, že voláme metodu account.GetSites. Metoda GetSites součástí C# Posterous API není a jedná se o extenzní metodu. Tato extenzní metoda je zvláštní tím, že její návratovou hodnotou je je jedno z klíčových rozhraní v RX Frameworku – rozhraní IObservable<T>.

        public static IObservable<IEnumerable<IPosterousSite>> GetSites(this IPosterousAccount account)

Rozhrani IObservable má v RX Frameworku podobný význam jako rozhraní IEnumerable v celém .Net Frameworku.  Zjednodušeně můžeme rozhraní IObservable popsat jako ceduli, kterou třída implementující rozhraní dává celému světu najevo: “Miluju voyery, jestliže chcete sledovat, co se ve mně děje, dejte mi sem pozorovatele a já na sebe všechno podstatné, co se od této chvíle stane, postupně  vyzvoním ”.

Rozhraní IObservable je tedy příslib, že zainteresovaný pozorovatel dostane data, která třída podporující toto rozhraní nabízí. Svůj zájem pozorovatel deklaruje tak, že předá odkaz sám na sebe do metody Subscribe.

public interface IObservable<T> 
{
 IDisposable Subscribe(IObserver<T> observer); 
}

Pozorovatel (IObserver) reaguje (proto reaktivní framework) na informace, které jsou mu poskytnuty objektem podporujícím rozhraní IObservable.

public interface IObserver<T> 
{ 
void OnCompleted(); 
void OnNext(T value); 
void OnError(Exception exn); 
}

Metoda OnNext je na IObserver volána vždy, když Observable objekt má k dispozici další data. Metodou OnError Observable objekt signalizuje chyby a metodou OnCompleted Observeru říká “jsem u konce, nic dalšího už pro tebe nemám”.

Naše metoda GetSites tedy říká – zavolejte mě a já vám nabídnu IObservable objekt, který, až budou data k dispozici, vašemu observeru (IObserver) vydá kolekci (IEnumerable) objektů IPosterousSite.

Extenzní metoda GetSites vypadá takto:

 public static IObservable<IEnumerable<IPosterousSite>> GetSites(this IPosterousAccount account)
        {
             
            checkAccountNotNull(account);            
             var sitesEvents = Observable.FromEvent<EventArgsValue<IEnumerable<IPosterousSite>>>(handler => account.SitesLoaded += handler,
                                                                                                handler => account.SitesLoaded -= handler)
                                        .Take(GlobalConstants.DEFAULT_TAKE_EVENTS_COUNT);



             return sitesEvents.GetFinalObservableEvents(account.LoadSitesAsync);
            
        }

 

Po kontrole, zda předaný IPosterousAccount není null, využijeme pomocnou metodu Observable.FromEvent z RX Frameworku, která nám vrátí IObservable objekt. Tento IObservable objekt notifikuje případného observera o každé nastalé události sites.Loaded. V našem případě Observera notifikuje o právě jedné události, protože jsme použili metodu Take (Take(GlobalConstants.DEFAULT_TAKE_EVENTS_COUNT)) a konstanta DEFAULT_TAKE_EVENTS_COUNT má hodnotu 1. Jak si můžete všimnout, metoda FromEvent nám dovoluje s událostmi, které postupně nastávají, zacházet jako (s potenciálně nekonečnou) kolekcí hodnot. Metodě FromEvent jsme pouze museli říct, jaká třída nese argumenty událost (EventArgsValue<IEnumerable<IPosterousSite>) a poskytli jsme ji dva delegáty pro registraci/deregistraci obslužných handlerů, které nám RX Framework předá  (handler => account.SitesLoaded += handler, handler => account.SitesLoaded –= handler). U našeho volání metody Take bych ještě poznamenal, že po vyvolání první události dojde automaticky RX Frameworkem k deregistraci obslužného handleru.

Proměnná sitesEvents je IObserver tohoto typu.

IObservable<IEvent<EventArgsValue<IEnumerable<IPosterousSite>>>>

Argumenty události jsou vždy zabaleny do instance třidy IEvent, která je vydána zaregistrovanému observeru v jeho metodě OnNext. Všimněte si ale, že návratovou hodnotou metody GetSites je již Observable, který observeru předá hodnoty bez IEvent (IObservable<IEnumerable<IPosterousSite>>).

Vidíme, že na sitesEvents je volána další má extenzní metoda GetFinalObservableEvents, které je předán delegát Action ukazující na asynchronní metodu account.LoadSitesAsync, a výsledek volání GetFinalObservableEvents je vrácen klientovi.

Metoda GetFinalObservableEvents:

  public static IObservable<TEventData> GetFinalObservableEvents<TEventData>(this IObservable<IEvent<EventArgsValue<TEventData>>> sourceEvents, Action runAction)
        {
            if (sourceEvents == null)
            {
                throw new ArgumentNullException("sourceEvents");
            }
            var retObservable = new DelegateObservable<TEventData>(
                observer =>
                    {
                        
                        var eventObserver = new EventObserver<TEventData>(observer);
                        var unsubScribe = sourceEvents.Subscribe(eventObserver);
                        runAction();
                        return unsubScribe;
                    });

            return retObservable;
        }

 

Metoda GetFnalObservableEvents vrací opět Observable, ale tentokrát jde o Observable typu IObservable<TEventData>  - jinými slovy, v našem případě IObservable<IEnumerable<IPosterousSite>>. Jak je toho dosaženo? Zdrojový IObservable objekt nazvaný sourceEvents je předán instanci třídy DelegateObservable, což je v současném scénaři již ten hledaný Observable podporující rozhraní IObservable<IEnumerable<IPosterousSite>>. DelegateObservable je tedy adaptér, který převádí události zabalené do IEvent na “rozbalené” hodnoty očekávané observerem. DelegateObservable je můj pomocný IObservable, který dostává do konstruktoru lambdu představující tělo jeho metody Subscribe, abychom nemuseli reimplementovat rozhraní IObservable v různých třídách stále dokola.

Výpis třídy DelegateObservable

 public class DelegateObservable<T> : IObservable<T>
    {
        private readonly Func<IObserver<T>, IDisposable> m_subscribeDel;

        public DelegateObservable(Func<IObserver<T>, IDisposable> subscribeDelegate)
        {
            m_subscribeDel = subscribeDelegate;
            if (m_subscribeDel == null)
            {
                throw new ArgumentNullException("subscribeDelegate");
            }
        }

        #region Implementation of IObservable<out T>

        public IDisposable Subscribe(IObserver<T> observer)
        {
            if (observer == null)
            {
                throw new ArgumentNullException("observer");
            }
            
           return m_subscribeDel(observer);
        }

        #endregion
    }

Předaná lambda v našem případě vytvoří instanci třídy eventsObserver, což je observer, který bude zpracovávat přicházející události, a do konstruktoru mu podhodí observer předaný klientským kódem – eventsObserver je tedy další adaptér, který je zdopovědný za “rozbalení” dat z instance IEvent a za předání těchto dat klientskému (“konečnému”) observeru.

Třída EventObserver:

 public class EventObserver<T> : IObserver<IEvent<EventArgsValue<T>>>                        
    {
        private readonly IObserver<T> m_innerObserver;
        private bool m_exceptionOccured;

        public EventObserver(IObserver<T> innerObserver)
        {
            if (innerObserver == null)
            {
                throw new ArgumentNullException("innerObserver");
            }
            
            m_innerObserver = innerObserver;
            m_exceptionOccured = false;
        }

        #region Implementation of IObserver<T>
        

        public virtual void OnNext(IEvent<EventArgsValue<T>> eventData)
        {
            if (eventData.EventArgs.Exception != null)
            {
                OnError(eventData.EventArgs.Exception);
                return;
            }
            //Rozbalení a předání dat Observeru
            m_innerObserver.OnNext(eventData.EventArgs.Value);
        }

        public virtual void OnError(Exception exception)
        {
            m_innerObserver.OnError(exception);
            m_exceptionOccured = true;
        }

        public virtual void OnCompleted()
        {
           //Chyba ukončí sekvenci sama o sobě
            if (!m_exceptionOccured)
            {
                m_innerObserver.OnCompleted();                
            }
        }

Třída EventObserver implementuje rozhraní IObservable s těmito generickými argumenty - IObserver<IEvent<EventArgsValue<T>>> . V C# Posterous API všechny události předávají svá data v instanci třídy EventArgsValue<T>, což znamená, že pro naše účely je EventObserver univerzálně použitelný observer pro zpracování výsledků asynchronní operace.

Pro úplnost zde je výpis třídy EventArgsValue

 public class EventArgsValue<T> : EventArgs
    {
        private readonly T m_value;
        private readonly Exception m_exception;

        internal EventArgsValue(T value, Exception exception)
        {
            m_value = value;
            m_exception = exception;
        }
            
        public T Value
        {
            get
            {
                return m_value;
            }
        }

        public Exception Exception
        {
            get
            {
                return m_exception;
            }
        }
    }

V lambdě předané do objektu DelegateObservable také musíme spustit asynchronní operaci – to je volání Action delegáta runAction, který nám předala již metoda GetSites. Každý IObservable také z metody vrací objekt implementující rozhraní IDisposable – volání metody Dispose dovoluje klientovi odpojit se objektu IObservable. Lambda  předaná do instance DelegateObservable vrátí  IDisposable objekt, který je vydán po připojení EventObservera k “streamu události“ (sourceEvents).

Přidání extenzních metod k dalším třídám je triviální – zde je extenzní metoda pro IPosterousSite, která nahraje všechny blogposty.

public static IObservable<IEnumerable<IPosterousPost>> GetPosts(this IPosterousSite site)
        {            

            throwIfSiteNull(site);
            var postsEvents = Observable.FromEvent<EventArgsValue<IEnumerable<IPosterousPost>>>(handler => site.PostsLoaded += handler,
                                                                                               handler => site.PostsLoaded -= handler)
                                      .Take(GlobalConstants.DEFAULT_TAKE_EVENTS_COUNT);
            

            return postsEvents.GetFinalObservableEvents(site.LoadAllPostsAsync);
            
            

        }
 
 

A nyní se můžeme znovu podívat, jak naše API použijeme v klientském kódu – díky alternativní implementaci Query vzoru v RX frameworku  můžeme používat staré dobré známé LINQ dotazy.

Metoda loadPosts:

private void loadPosts()
        {
            toolStripStatusLabel1.Text = TEXT_LOAD_DATA_START;

            IPosterousApplication app = PosterousApplication.Current;
            IPosterousAccount account = app.GetPosterousAccount("<Posterous user name>", "Posterous password");

            var syncContext = SynchronizationContext.Current;

            var resultPosts = from sites in account.GetSites()
                              from site in sites.ToObservable()
                              from posts in site.GetPosts()
                              from post in posts.ToObservable()
                              where post.Private == false
                              select post;

            resultPosts.Subscribe(post =>
                                      {
                                          lock (m_threadsSet)
                                          {
                                              m_threadsSet.Add(Thread.CurrentThread.ManagedThreadId);
                                          }


                                          syncContext.Post(_ =>
                                                               {
                                                                   var UCpost = new UC_Post
                                                                                    {
                                                                                        Title = post.Title,
                                                                                        Body = post.Body
                                                                                    };

                                                                   flowLayoutPanel1.Controls.Add(UCpost);
                                                               },
                                                           null

                                              );
                                      },

                                  ex => syncContext.Post(_ =>
                                                             {

                                                                 throw new ApplicationException(ASYNC_EXCEPTION_TEXT, ex);
                                                             },

                                                         null),

                                  () => syncContext.Post(_ =>
                                                             {
                                                                 lock (m_threadsSet)
                                                                 {
                                                                     m_threadsSet.Run(tId => lstThreads.Items.Add(tId));
                                                                 }

                                                                 toolStripStatusLabel1.Text = TEXT_LOAD_DATA_END;
                                                             },
                                                         null

                                            ));



        }

V proměnné resultPosts jsou uloženy všechny blogposty (IPosterousPost) ze všech blogů (IPosterousSite). Blogy i blogposty jsou nahrány asynchronně, ale v klientském kódu nevidíme žádná specialitky kvůli asynchronnímu nahrávání dat. Na proměnných sites i posts v dotazu je volána další extenzní metoda z RX Frameworku ToObservable, protože jak víme, výsledkem volání asynchronních metod byly typy IEnumerable<IPosterousPost> a IEnumerable<IPosterousSite>.

Důležité je, že zpracování dotazu je opět “lazy” – to znamená, že k získání dat dojde až poté, co k resultsPosts zaregistruju svého Observera metodou Subscribe (analogie “Lazy” vyhodnocování v LINQ To IEnumerable a procházení dotazu v cyklu foreach). Metoda Subscribe má několik variant a jedna z nich nám dovoluje pro metody OnNext, OnError a OnCompleted předat delegáty, aniž bychom byli nuceni vytvářet svou třídu implementující rozhraní IObserver.

První delegát (OnNext) vezme předaný post a vytvoří pro něj UserControl, který vloží do FlowPanelu na formuláři. Ještě předtím pro zajímavost do Hashsetu ukládám identifikátory vláken, které se na zpracování dotazu podílí. S prvky na formuláři můžeme pracovat jen z UI threadu, a proto je vložení User controlu provedeno přes SynchronizationContext uložený do proměnné syncContext před spuštěním dotazu.

Druhý delegát (OnError)  pouze přes SynchronizationContext zpropaguje výjimku, která nastala při asynchronním zpracování, do UI threadu. Všimněte si, jak je zpracování výjimek jednoduché – rozdíl vynikne při srovnání s opakovaným voláním metody throwIfAsyncEx v kódu na začátku tohoto článku.

Třetí delegát (OnCompleted) naplní listbox na formuláři ID použitých threadů a změní text ve status baru.

Zde je výsledný formulář. V listboxu nahoře si můžete všimnout, že u mě byly k vykonání dotazu použity celkem 3 thready.

 

AsyncForm

 

Tím bychom mohli skončit, ale RX Framework má pro asynchronní operace ještě další zajímavou podporu. Pomocí metody Observable.FromAsyncPattern můžeme vytvořit IObservable rychle a bezpracně ze standardního a výše již zmíněného asynchronního Begin/End vzoru. V C# Posterous API metody Begin*/End* nejsou, proto je zkusme dodat pomocí extenzních metod.

Rozhraní IPosterousAccount bude obohaceno o extenzní metody BeginLoadSites a EndLoadSite.

Metoda BeginLoadSites

public static IAsyncResult BeginLoadSites(this IPosterousAccount account, AsyncCallback callback, object context)
        {
            checkAccountNotNull(account);
            var loadSiteAction = new Action(account.LoadSites);
            
            return RXEventsHelper.GetAsyncResultEx(loadSiteAction, callback, context);
       
            
        }

Jak vidíte, přesně dle konvencí .Net vzoru metoda vrací odkaz na rozhraní IAsyncResult a přijímá callBack, což je tedy u tohoto vzoru metoda, která má být vyvolána po dokončení asynchronního zpracování, a jak také vzor vyžaduje, posledním argumentem je libovolný objekt reprezentující libovolný “stavový token” operace, který v metodě End* klient používá pro korelací mezi požadavkem a odpovědí.

Veškerá práce je přenesena na metodu GetAsyncResultEx v mém RXEventsHelperu – metoda vyžaduje, abyste ji poslali v delegátu Action metodu, která má být spuštěna asynchronně.

Metoda RXEventsHelper.GetAsyncResultEx.

 public static IAsyncResult GetAsyncResultEx(Action runAction, AsyncCallback callback, object context)
        {
            if (runAction == null)
            {
                throw new ArgumentNullException("runAction");
            }

            Exception ex = null;
            
            var proxyCallback = new AsyncCallback(ar =>
                                                      {
                                                          IAsyncResult proxyResult = new AsyncResultEx(ar, runAction);                                                                                         
                                                           callback(proxyResult);
                                                      });

            return runAction.BeginInvoke(proxyCallback, context);
                      

            
                       
        }

Hlavním trikem je využití možností delegátů – každý delegát v .Net Frameworku vždy obsahuje asynchronní metody BeginInvoke a EndInvoke, které splňují nároky asynchronního vzoru. My tedy na předaném delegátu runAction zavoláme metodu BeginInvoke, ale místo klientské callBack Funkce podhodíme svou proxy funkci (proxyCallback), která po dokončení asynchronního volání připraví pro naši End metodu vlastní IAsyncResult (AsyncResultEx).

Třída AsyncResultEx zapouzdřuje původní IAsyncResult  (argument ar předaný  do konstruktoru v předešlém výpisu) a navíc, když na její instanci zavoláme metodu EndAction, na předaném delegátovi (argument runAction v předešlém výpisu) je zavolána metoda EndInvoke, čehož využije naše metoda EndLoadSites.

Třída AsyncResultEx

 public class AsyncResultEx : IAsyncResult
    {
        
        #region private variables
        private IAsyncResult m_originalAsyncResult;
        private readonly Action m_originaldelegate;
        #endregion private variables

        public AsyncResultEx(IAsyncResult origAsyncResult, Action originaldelegate)
        {
            if (origAsyncResult == null)
            {
                throw new ArgumentNullException("origAsyncResult");
            }


            m_originalAsyncResult = origAsyncResult;
            m_originaldelegate = originaldelegate;
        }

        #region properties
        public virtual IAsyncResult OriginalAsyncResult
        {
            get
            {
                return m_originalAsyncResult;
            }

        }

        public virtual Action OriginalDelegate
        {
            get
            {
                return m_originaldelegate;
            }
        }
        
        #endregion properties

        #region methods

        public virtual void EndAction()
        {
            
            if (OriginalDelegate != null)
            {
                OriginalDelegate.EndInvoke(OriginalAsyncResult);
            }                                                

        }
            
        #endregion methods
        #region Implementation of IAsyncResult

        
        public virtual bool IsCompleted
        {
            get
            {
                return m_originalAsyncResult.IsCompleted;
            }
        }

        public virtual object AsyncState
        {
            get
            {
                return m_originalAsyncResult.AsyncState;
            }
        }

        public virtual WaitHandle AsyncWaitHandle
        {
            get
            {
                return m_originalAsyncResult.AsyncWaitHandle;
            }
        }

        public virtual bool CompletedSynchronously
        {
            get
            {
                return m_originalAsyncResult.CompletedSynchronously;
            }
        }
                
        #endregion
    }

Extenzní metoda EndLoadSites

 public static IEnumerable<IPosterousSite> EndLoadSites(this IPosterousAccount account, IAsyncResult result)
        {
            checkAccountNotNull(account);
            
            var exResult = result as AsyncResultEx;
            
            if (exResult == null)
            {
                throw new ArgumentException("result");
            }
            
            exResult.EndAction();
                                               
            return account.Sites;
        }

Zde vidíme volání metody EndAction na podhozené instanci AsyncResultE. Poté metoda EndLoadSites jen vrátí kolekci Sites objektu account, protože ta  nyní již musí být po asynchronním volání naplněna daty.

Se stávající infrastrukturou si opět si můžeme rychle připravit další Begin a End metody. Zde jsou extenzní metody BeginLoadPosts a EndLoadPosts pro IPosterousSite.

public static IAsyncResult BeginLoadPosts(this IPosterousSite site, AsyncCallback callback, object context)
        {
            throwIfSiteNull(site);
            var loadPostsAction = new Action(site.LoadAllPosts);
            
            return RXEventsHelper.GetAsyncResultEx(loadPostsAction, callback, context);
        }

        public static IEnumerable<IPosterousPost> EndLoadPosts(this IPosterousSite site, IAsyncResult result)
        {
            throwIfSiteNull(site);
            var exResult = result as AsyncResultEx;

            if (exResult == null)
            {
                throw new ArgumentException("result");
            }

            exResult.EndAction();

            return site.Posts;
        }

 

A metoda loadPosts2, která dělá to samé, co předchozí metoda loadPosts, ale používá naše nové extenzní Begin/End metody.

 private void loadPosts2()
        {
            toolStripStatusLabel1.Text = TEXT_LOAD_DATA_START;

            IPosterousApplication app = PosterousApplication.Current;
            IPosterousAccount account = app.GetPosterousAccount("posterousname", "posterouspassword");

            var syncContext = SynchronizationContext.Current;            


            var resultPosts = from sites in Observable.Defer(() => Observable.FromAsyncPattern<IEnumerable<IPosterousSite>>(account.BeginLoadSites, account.EndLoadSites)())
                              from site in sites.ToObservable()
                              from posts in Observable.Defer(() => Observable.FromAsyncPattern<IEnumerable<IPosterousPost>>(site.BeginLoadPosts, site.EndLoadPosts)())
                              from post in posts.ToObservable()
                              where post.Private == false
                              select post;

            resultPosts.Subscribe(post =>
                                      {
                                          lock (m_threadsSet)
                                          {
                                              m_threadsSet.Add(Thread.CurrentThread.ManagedThreadId);
                                          }


                                          syncContext.Post(_ =>
                                                               {
                                                                   var UCpost = new UC_Post
                                                                                    {
                                                                                        Title = post.Title,
                                                                                        Body = post.Body
                                                                                    };

                                                                   flowLayoutPanel1.Controls.Add(UCpost);
                                                               },
                                                           null

                                              );
                                      },


                                  ex => syncContext.Post(_ =>
                                                             {
                                                                 
                                                                 throw new ApplicationException(ASYNC_EXCEPTION_TEXT, ex);
                                                             },

                                                         null),

                                  () => syncContext.Post(_ =>
                                                             {
                                                                 lock (m_threadsSet)
                                                                 {
                                                                     m_threadsSet.Run(tId => lstThreads.Items.Add(tId));
                                                                 }

                                                                 toolStripStatusLabel1.Text = TEXT_LOAD_DATA_END;
                                                             },
                                                         null

                                            )


                );


        }

Upozornil bych jen na dvě specialitky či zrádná místa, která (alespoň v této BETA verzi RX) dělají kód méně intuitvním, než by bylo žádoucí:

Jedná se o tyto dva řádky:

from sites in Observable.Defer(() => Observable.FromAsyncPattern<IEnumerable<IPosterousSite>>(account.BeginLoadSites, account.EndLoadSites)())  
from posts in Observable.Defer(() => Observable.FromAsyncPattern<IEnumerable<IPosterousPost>>(site.BeginLoadPosts, site.EndLoadPosts)()) 

Metoda FromAsyncPattern přijímá delegáty na naše asynchronní metody, ale místo spolehnutí se na typovou inference jsem musel generický argument předat explicitně(Observable.FromAsyncPattern<IEnumerable<IPosterousSite>>) – pokud argument nezadáte, kompilátor hlásí “ambigous reference”.

Dále je patrné, že výsledek funkce FromAsyncPattern, kterým je další funkce vracející IObservable, je předán jako argument metodě Observable.Defer. Metoda Observable.Defer zajistí, že k vyhodnocení předané funkce dojde až poté, co je k výsledkům dotazu přihlášen observer – jinými slovy, metoda Defer nám pomáhá zachovat “lazy” vyhodnocení dotazu.

Dotaz bude fungovat i v této podobě (bez Defer):

var resultPosts = from sites in  Observable.FromAsyncPattern<IEnumerable<IPosterousSite>>(account.BeginLoadSites, account.EndLoadSites)()
                              from site in sites.ToObservable()
                              from posts in Observable.FromAsyncPattern<IEnumerable<IPosterousPost>>(site.BeginLoadPosts, site.EndLoadPosts)()
                              from post in posts.ToObservable()
                              where post.Private == false
                              select post;

Ale jeho vyhodnocení už není “lazy”. Všimněte si závorek na konci výrazu - FromAsyncPattern<IEnumerable<IPosterousSite>>(account.BeginLoadSites, account.EndLoadSites)()  - výsledné IObservable získám okamžitým zavoláním funkce vrácené z metody FromAsyncPattern. Vadit vám to začne v okamžiku, kdy zkonstruujete dotaz, ihned se odpálí asynchronní volání, dojde k chybě a vy budete mít v aplikaci neošetřenou výjimku v threadu na pozadí, protože IObserver ještě není přihlášen (není možné zavolat druhého delegáta - ex => syncContext.Post(_ => { throw new ApplicationException(ASYNC_EXCEPTION_TEXT, ex); }, null), ).

 

Snad se vám tato exkurze líbila. Já ještě na RX Framework konečný názor nemám, ale něco neodbytného ve mně říká, že by mohlo jít o další LINQ, který otřese programátorským světem. :-) Některé extenze pravděpodobně zahrnu do samostatného jmenného prostoru v C# Posterous API.



Tuesday, 02 February 2010 07:43:00 (Central Europe Standard Time, UTC+01:00)       
Comments [3]  .NET Framework | C# Posterous API | LINQ | Návrhové vzory | RX Extensions | Windows Forms


 Thursday, 28 January 2010
C# Posterous API pro Silverlight 4 (a .Net Framework 3.5) – verze 0.0.0.2

Stáhnout C# Posterous API pro Silverlight 4.0

Stáhnout C# Posterous API pro .Net Framework 3.5

Vše podstatné k C# Posterous API naleznete v úvodním článku.

Poznámky ke změnám v této verzi:

  • Kvůli verzi pro Silverlight přidány další asynchronní metody tak, aby bylo možné získat data z webu asynchronně, jak to Silverlight vyžaduje a jak je toto chování v aplikacích vynuceno  asynchronními metodami v rozhraní platformních tříd - příkladem budiž rozhraní tříd WebRequest a WebResponse. Pokud se pokusíte zavolat synchronní verzi metody v SL z UI vlákna, měli byste z Posterous API dostat výjimku – to je lepší varianta, než skončit v paralyzovaném stavu, kdy celá aplikace na nic nereaguje.

Ukázka uložení  změn v blogpostu – přidání komentáře. :


        var comment = post.CreateNewComment(updateText);
        post.SaveChangesCompleted += (_0, _1) =>
                                         {
                                             Assert.IsTrue(comment.Id > 0);
                                             TestComplete();
                                         };
        post.SaveChangesAsync();

  • V Silverlight verzi aplikace nedostanete v událostech rozhraní IRawRequestResponsePublisher k modifikaci objekty HttpWebRequest a HttpWebResponse, které jsou použity pro stažení obrázku autora (IAuthor.AuthorPicture) a stažení média (IPosterousMedium.Medium). Důvodem je, že pro stažení používám svého potomka třídy WebClient. Tento potomek přepisuje metody GetWebRequest a GetWebResponse, které jsou poté nabídnuty v událostech dostupných přes rozhraní IRawRequestResponsePublisher. V Silverlightu je ale třída WebClient označena jako kritický kód (Critical) a takzvaný transparentní  (Transparent) kód, pod který patří i námi psaný kód, nemá právo z takto označené infrastrukturní třídy podědit. Kromě dvou výše zmíněných omezení ale IRawRequestResponsePublisher pracuje stejně jako v desktopové verzi  a v další verzi API uvažuji, že místo WebClienta použiju na stažení obrázku autora i všech dalších médií přímo třídy WebRequest a WebResponse, které budou opět nabídnuty i v rozhraní IRawRequestResponsePublisher.
  • Silverlight má omezený přístup k souborovému systému, a proto naleznete v blogpostu (IPosterousPost) a twitter postu (ITwitterPost) další dvě metody pro přidání médií. Ve verzi 0.0.0.1 bylo možné předat pouze cestu k souboru, nyní můžete předat jakýkoli stream – např. Stream načtený z IsolatedFileStorage.
    Nové metody:
    void AppendMedium(Stream stream, string mediaName, Action<Stream> releaseStreamFunction);
    void AppendMedium(Stream stream, string mediaName);

Kromě streamu a názvu média můžete do jedné z variant funkce AppendMedium předat delegáta typu Action<Stream> , který bude vyvolán v okamžiku, kdy Posterous API  již se streamem nepotřebuje pracovat. Nejasná zodpovědnost, kdo vlastní a hlavně uvolňuje zdroje alokované objektem stream, mě právě vedla k tomu, že v první verzi API pro .Net Framework 3.5 jste mohli pouze předat název souboru a životní cyklus FileStreamu byl zcela pod mou kontrolu. Jestliže delegáta releaseStreamFunction nepředáte, máte životní cyklus streamu zcela ve svých všemocných rukou. Posterous API ani jeho autor si nepřejí být vyzváni na souboj  žádným šíleným omnipotentním vývojářem :-), a proto na předaném streamu Posterous API nikdy nevolá metodu Close ani Dispose.

Ukázka práce s metodou AppendMedium:

IPosterousPost newPost = site.CreatePost(DateTime.Now + " Media Silverlight",
                                                         "Príliš žlutoucký kun úpel dábelské ódy", true);

                IsolatedStorageFileStream stream = new IsolatedStorageFileStream("AlbumArtSmall.jpg ", FileMode.Open, IsolatedStorageFile.GetUserStoreForSite());
                
                newPost.AppendMedium(stream, "mojeSL.jpg", isoStream => isoStream.Close());
                               
                newPost.AddTag("Všechno a nic");
                newPost.AddTag("C# omnia vincit");
                newPost.SaveChangesCompleted += (_, __) =>
                                            {
                                                Assert.IsTrue(newPost.Id > 0);
                                                TestComplete();
                                            };
                newPost.SaveChangesAsync();

Jednoduchá ukázka použití API v Silverlightu. V jednoduchém boxu na stránce zobrazíme titulek spotu a url spotu. Výsledek vypadá takto:

SLBox

 

Ve Visual Studiu jsem založil projekt typu běžná navigační aplikace v Silverlightu. Zde je jednoduchý “ViewModel” pro stránku:

public class VM_Posts : INotifyPropertyChanged
    {
        public static readonly string ASYNC_EXCEPTION_TEXT = "Error while retrieving data. See inner exceptions for details";
        
        private bool m_isBusy;
        private IEnumerable<ViewPost> m_posts;

        public VM_Posts()
        {
            m_posts = null;
            IsBusy = false;
            LinkClickCommand = new DelegateCommand<String>(link => Debug.WriteLine(link) /*m_navigationServices.HandleExternalLink(link)*/,
                                                             _ => true);
            init();
        }

        
        private void init()
        {

                IsBusy = true;
                IPosterousApplication posterousApp = PosterousApplication.Current;
                IPosterousAccount posterousAccount = posterousApp.GetPosterousAccount("<PosterousUserName>", "PosterousPassword>");

                posterousAccount.SitesLoaded += (o, e) =>
                                                    {
                                                        throwIfAsyncEx(e.Exception);
                                                        posterousAccount.PrimarySite.PostsLoaded += (_, e2) =>
                                                                                                        {
                                                                                                            throwIfAsyncEx(e2.Exception);
                                                                                                            Posts = (from p in e2.Value
                                                                                                                    select new ViewPost
                                                                                                                               {
                                                                                                                                   Title = p.Title,
                                                                                                                                   Body = p.Body,
                                                                                                                                   Url = p.Url

                                                                                                                               }).ToList();                                                                                                            
                                                                                                            
                                                                                                        };
                                                        posterousAccount.PrimarySite.LoadAllPostsAsync();
                                                    };



                posterousAccount.LoadSitesAsync();
            
            
        }

       private void throwIfAsyncEx(Exception exception)
        {
            if (exception != null)
            {
                IsBusy = false;
                throw new Exception(ASYNC_EXCEPTION_TEXT,exception);
            }
        }
        
        

        public IEnumerable<ViewPost> Posts
        {
            get
            {
                return m_posts;
            }
            private set
            {
                
                m_posts = value;
                if (m_posts != null)
                {
                    IsBusy = false;
                }
                OnPropertyChanged(new PropertyChangedEventArgs("Posts"));
                
                
            }
        }

        

        public bool IsBusy
        {
            get
            {
                return  m_isBusy;
            }
            private set
            {
                m_isBusy = value;
                OnPropertyChanged(new PropertyChangedEventArgs("IsBusy"));
            }
        }

        public ICommand LinkClickCommand
        {
            get;
            private set;
        }

        #region Implementation of INotifyPropertyChanged

        public event PropertyChangedEventHandler PropertyChanged;

        protected void OnPropertyChanged(PropertyChangedEventArgs e)
        {
            PropertyChangedEventHandler handler = PropertyChanged;
            if (handler != null)
            {
                handler(this, e);
            }
        }

        #endregion
    }

    public class ViewPost
    {
        public string Title
        {
            get;
            set;
        }

        public string Body
        {
            get;
            set;
        }
        

        public string Url
        {
            get;
            set;            
        }
    }
}

V privátní metodě init, která je volána ihned po vytvoření instance třídy WM_Posts, nahrajeme blogy (Sites –> metoda LoadSitesAsync) a poté opět asynchronně z primárního blogu (PrimarySite) nahraju všechny blogposty (metoda LoadAllPostsAsync). Jestliže při některém asynchronním volání dojde k výjimce, metoda throwIfAsyncEx výjimku zpropaguje do UI vlákna.

Z nahraných blogpostů jsou vlastností Title, Url a Body přeneseny do instancí pomocné třídy ViewPost a kolekce objektů ViewPost je uložena do vlastnosti Posts. Třídu ViewPost vytvořit musíme, protože “Binding” dat, který budeme používat v XAMLu, nedokáže v Silverlightu z bezpečnostních důvodů pře reflection přistupovat ke člelnům “internal” třídy deklarované v jiné assembly. Třída PosterousPost, která v Posterous API implementuje rozhraní IPosterousPost, je označena jako internal. Ze stejného důvodu v Silverlightu nemohu použít místo explicitně definované třídy ViewPost anonymní datový typ – i anonymní datové typy jsou reprezentovány ve výsledném aplikačním dll třídami s modifikátorem viditelnosti “internal” a kód pro “binding” dat je ve zcela jiné platformní assembly.

Kromě vlastnosti Posts nabízí třída WM_Posts vlastnost IsBusy, kterou v XAMLu  využijeme k zobrazení indikátoru informujícího uživatele, že právě získáváme data. Vystaven je také LinkClickCommand, který nůže být zavolán například v okamžiku, kdy je stisknuto nějaké tlačítko reprezentující hyperlink s URL blogpostu.

A zde je pro úplnost ještě XAML. Památeční XAML, protože mi při jeho psaní nejméně a bez jakékoli nadsázky 50x spadlo Visual Studio 2010 Beta 2 (s Resharperem).:-)

<navigation:Page x:Class="SL_PosterousAPITest.Home"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:navigation="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Navigation"
    xmlns:ct="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Toolkit"
    xmlns:local="clr-namespace:SL_PosterousAPITest;assembly=SL_PosterousAPITest"
    mc:Ignorable="d" d:DesignWidth="640" d:DesignHeight="480"
    Title="Home"
    Style="{StaticResource PageStyle}">

    <Grid x:Name="LayoutRoot">
        <Grid.Resources>
            <local:VM_Posts x:Key="PostsView"></local:VM_Posts>            
            <Style x:Name="PostTitleStyle" TargetType="TextBlock">
                <Setter Property="FontWeight" Value="Bold" />
                 <Setter Property="Foreground" Value="IndianRed" />
                <Setter Property="FontSize" Value="14" />
            </Style>
            <Style x:Key="PostsRectangle" TargetType="Rectangle">
                <Setter Property="RadiusX" Value="30" />
                <Setter Property="RadiusY" Value="30" />                
                <Setter Property="Fill">
                    <Setter.Value>
                        <LinearGradientBrush>
                            <GradientStop Offset="0" Color="LightYellow" />
                            <GradientStop Offset="1" Color="LightBlue" />                            
                        </LinearGradientBrush>
                    </Setter.Value>
                </Setter>
            </Style>
            <Style TargetType="ItemsControl">
                <Setter Property="Width" Value="500" />
                <Setter Property="Height" Value="360" />
                <Setter Property="Template">
                    <Setter.Value>
                        <ControlTemplate>
                            <ScrollViewer BorderThickness="0" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
                                <Grid>                                    
                                    <Rectangle Style="{StaticResource PostsRectangle}">                                        
                                    </Rectangle>
                                    <ItemsPresenter Margin="15,15,0,0"/>
                                    <ct:BusyIndicator IsBusy="{Binding Path=IsBusy}" 
                                                      DisplayAfter="0" 
                                                      HorizontalAlignment="Center" 
                                                      VerticalAlignment="Center"                                                       
                                                      />
                                    
                                    
                                </Grid>
                            </ScrollViewer>
                        </ControlTemplate>
                    </Setter.Value>
                </Setter>
            </Style>
        </Grid.Resources>        
        <ScrollViewer x:Name="PageScrollViewer" Style="{StaticResource PageScrollViewerStyle}">

            <StackPanel x:Name="ContentStackPanel" DataContext="{StaticResource PostsView}">

                <TextBlock x:Name="HeaderText" Style="{StaticResource HeaderTextStyle}"
                                   Text="Home"/>
                <ItemsControl Name="itemsPosts" ItemsSource="{Binding Mode=OneWay, Path=Posts}">                  
                    <ItemsControl.ItemTemplate>
                        <DataTemplate>
                            <StackPanel Orientation="Vertical">
                                <StackPanel Orientation="Horizontal">
                                    <TextBlock Style="{StaticResource PostTitleStyle}" Text="{Binding Path=Title}"></TextBlock>                                
                                 </StackPanel>
                                <HyperlinkButton Content="{Binding Path=Url}" 
                                                 Command="{Binding Source={StaticResource PostsView}, Path=LinkClickCommand}"                                                  
                                                 CommandParameter="{Binding  RelativeSource={RelativeSource Self}, Path=Content}"
                                                 FontSize="12"
                                                 ></HyperlinkButton>                                
                            </StackPanel>
                        </DataTemplate>                        
                    </ItemsControl.ItemTemplate>
                </ItemsControl>
            </StackPanel>

        </ScrollViewer>
    </Grid>

</navigation:Page>


Thursday, 28 January 2010 16:33:08 (Central Europe Standard Time, UTC+01:00)       
Comments [0]  .NET Framework | C# Posterous API | Silverlight


 Wednesday, 20 January 2010
Ukázka práce s Posterous API – zálohování blogu

Stáhnout výsledné exe -RSPosterousBackup.

Po jednoduchém přehledu možností mého C# Posterous API wrapperu se nyní podíváme, jak se dá API použít k zálohování vašeho blogu. Pro účely tohoto článku předpokládám, že jste úvodní článek o API wrapperu četli.

Zálohovač blogu (RSPosterousBackup.exe) je jednoduchá konzolová aplikace, které stačí předat uživatelské jméno (parametr –u)  a heslo (parametr –p)  vašeho účtu na Posterous a také adresář vašem počítači (parametr bd), do kterého chcete blog zazálohovat.

Jednoduchá ukázka:

RSPosterousBackup.exe -u:rene@renestein.net -p:mojeheslo -bd:c:\_Archiv\PosterousBackup

Blog uživatelského účtu reneATrenestein.net s heslem mojeheslo bude zazálohován do adresáře c:\_Archiv\PosterousBackup.

Program referencuje samozřejmě knihovnu RStein.Posterous.API a pro (své, uznávám :-)) pobavení jsem také použil RX for .Net Framework 3.5 SP1. Z RX Frameworku jsou referencovány assembly System.CoreEx, System.Interactive a System.Threading. Nejzajímavější na RX Frameworku je, že pro verzi 3.5 .Net Frameworku zpřístupňuje Parallel Linq – PLINQ, který je součástí připravovaného .Net Frameworku 4.0.

Ještě upozornění – v žádném případě nechci tvrdit, že kód, který uvidíte, využívá PLINQ správným způsobem. Program je jen pískovištěm, na kterém jsem si zkoušel a ověřoval, co RX a PLINQ umí a jak vypadá výsledný kód.

Po spuštění RSPosterousBackup.exe funkce Main pouze předá argumenty z příkazové řádky metodě BackupData v třídě BackupEngine, která představuje výkonný mozek celého zálohovače Posterous blogu.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace RSPosterousBackup
{
    class Program
    {
        static void Main(string[] args)
        {
            var engine = new BackupEngine();
            engine.BackupData(args);
            Console.ReadLine();
        }
    }
}

 

Zde je třída BackupEngine

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using RStein.Posterous.API;

namespace RSPosterousBackup
{
    public class BackupEngine
    {
        #region constants
        public static readonly string POSTEROUS_USER = "-u";
        public static readonly string POSTEROUS_PASSWORD = "-p";
        public static readonly string BACKUP_DIRECTORY= "-bd";
        public static readonly string HELP_KEY = "-?";
        public static readonly string HELP_ALT_KEY = "-h";
        public static readonly string POSTEROUS_FILE_EXTENSION = ".posterous";
        public static readonly string POSTEROUS_MEDIA_FORMAT = "Media_{0}";
        public static readonly string POSTEROUS_COMMENT_FORMAT = "Comment_{0}";
        public static readonly string POSTEROUS_SITE_FORMAT = "Site_{0}";
        #endregion constants
        #region private variables        
        #endregion private variables

        #region constructors
        public BackupEngine()
        {
        }
        #endregion constructors
        
        public void BackupData(string[] commandLine)
        {
            if (commandLine == null)
            {
                throw new ArgumentNullException("commandLine");
            }

            var parser = new CommandLineParser();
            Dictionary<string, string> cmSwitches = parser.Parse(commandLine);

            Debug.Assert(cmSwitches != null);

            if (cmSwitches.Keys.Any(key => (key.Equals(HELP_KEY, StringComparison.OrdinalIgnoreCase)) ||
                                       (key.Equals(HELP_ALT_KEY,StringComparison.OrdinalIgnoreCase))))
            {
                logMessage(GlobalConstants.CMDLINE_SHOW_USAGE);
                return;
            }

            if (!checkSwitches(cmSwitches))
            {
                logMessage(GlobalConstants.CMDLINE_SHOW_USAGE);
                return;                
            }

            backupDataInner(cmSwitches);
        }

        private void backupDataInner(Dictionary<string, string> cmSwitches)
        {
            string pUser = cmSwitches[POSTEROUS_USER];
            string pPassword = cmSwitches[POSTEROUS_PASSWORD];
            string backupDir = cmSwitches[BACKUP_DIRECTORY];
            try
            {
                IPosterousAccount account = PosterousApplication.Current.GetPosterousAccount(pUser, pPassword);
                if (!Directory.Exists(backupDir))
                {
                    Directory.CreateDirectory(backupDir);
                }

                var currDirBackupName = new StringBuilder(DateTime.Now.ToLocalTime().ToString());
                currDirBackupName.ToSafeFileName();
                
                
                string currentBackupDir = Path.Combine(backupDir, currDirBackupName.ToString());
                account.Sites.Run(site =>
                                     {
                                         string sitePath = Path.Combine(currentBackupDir, String.Format(POSTEROUS_SITE_FORMAT, site.Id.ToString()));
                                         Directory.CreateDirectory(sitePath);
                                     });


                var processedPosts = (from site in account.Sites.AsParallel()
                                      from post in site.Posts.AsParallel()
                                         .Do(postPost =>
                                                  Console.WriteLine(String.Format(GlobalConstants.POST_BACKUP_MESSAGE_FORMAT,
                                                                         postPost.Title,
                                                                         Thread.CurrentThread.ManagedThreadId)))
                                        
                                         .Do(postPost =>
                                                 {                                                  
                                                     string siteDir = Path.Combine(currentBackupDir, String.Format(POSTEROUS_SITE_FORMAT, site.Id.ToString()));
                                                     
                                                     var titlBuilder = new StringBuilder(postPost.Title + postPost.Id.ToString());
                                                     titlBuilder.ToSafeFileName();

                                                     string postDirName = Path.Combine(siteDir,
                                                                                       titlBuilder.ToString()
                                                                                       );
                                                     
                                                     Directory.CreateDirectory(postDirName);
                                                     string postFileName = Path.Combine(postDirName, titlBuilder.ToString() + POSTEROUS_FILE_EXTENSION);
                                                     using (var fileStream = File.Open(postFileName, FileMode.Create))
                                                     using (var stremWriter = new StreamWriter(fileStream, Encoding.UTF8))
                                                     {
                                                         stremWriter.Write(postPost.Body);
                                                     }

                                                     postPost.Media
                                                         .AsParallel()
                                                         .Run(media =>
                                                                 {
                                                                                                             
                                                                     var mediaName = new StringBuilder(String.Format(POSTEROUS_MEDIA_FORMAT, Guid.NewGuid()));
                                                                     mediaName.ToSafeFileName();
                                                                     string mediaFile = Path.Combine(postDirName, mediaName.ToString());


                                                                     using (var fileStream = File.Open(mediaFile, FileMode.Create))
                                                                     {


                                                                         media.Content.CopyToStream(fileStream);

                                                                     }
                                                                 });


                                                     postPost.Comments
                                                         .AsParallel()                                                         
                                                         .Run(comment =>
                                                                 {
                                                                                                             
                                                                     var commentFileName = new StringBuilder(String.Format(POSTEROUS_COMMENT_FORMAT, Guid.NewGuid()));
                                                                     commentFileName.ToSafeFileName();
                                                                     string commentFile = Path.Combine(postDirName, commentFileName.ToString());


                                                                     using (var fileStream = File.Open(commentFile, FileMode.Create))
                                                                     using (var stremWriter = new StreamWriter(fileStream, Encoding.UTF8))
                                                                     {


                                                                         stremWriter.WriteLine(comment.Author.Name);
                                                                         stremWriter.Write(comment.Body);

                                                                     }
                                                                 });
                                                 })

                                     select post).ToList();





                logMessage(String.Format(GlobalConstants.FINAL_BACKUP_MESSAGE_FORMAT, processedPosts.Count));
                
                
            }
            catch (Exception e)
            {
                 Trace.WriteLine(e);
                 Console.WriteLine(e);                 
            }
           
            
        }

        private bool checkSwitches(Dictionary<string, string> cmSwitches)
        {            

            return ckeckIfSwitchNullOrEmpty(POSTEROUS_USER, cmSwitches) &&
                    ckeckIfSwitchNullOrEmpty(POSTEROUS_PASSWORD, cmSwitches) &&
                    ckeckIfSwitchNullOrEmpty(BACKUP_DIRECTORY, cmSwitches);
            
        }

        private bool ckeckIfSwitchNullOrEmpty(string switchKey, Dictionary<string, string> cmSwitches)
        {
            string val;
            cmSwitches.TryGetValue(switchKey, out val);
            if (String.IsNullOrEmpty(val))
            {
                logMessage(String.Format(GlobalConstants.INVALID_CML_SWITCH_FORMAT_STRING_EX, switchKey));
                return false;
            }
            return true;
        }

        private void logMessage(string message)
        {
            
            Console.WriteLine(message);
        }
    }
}

Třída BackupEngine nejdříve v metodě BackupData pomocí instance třídy CommandLineParser rozpársuje příkazový řádek a voláním pomocné metody checkSwitches zkontroluje, zda byly předány všechny vyžadované parametry. Jestliže nějaký parametr chybí nebo byl zadán parametr pro zobrazení nápovědy (-h, –?), program zobrazí nápovědu a k zálohování nedojde.

Ihned po dokončení všech předběžných kontrol je volána privátní metoda backupDataInner, která je odpovědná za zálohování blogu. Metoda backupDataInner získá odkaz na Posterous účet (PosterousApplication.Current.GetPosterousAccount(pUser, pPassword); a poté pro každou Site (samostatný blog) založí nový podadresář v adresáři, jehož názvem je aktuální lokální datum a čas a který je vytvořen v adresáři předaném v parametru –bd uživatelem. 

Adresářová struktura pro každý zálovaný blog:

<adresář určený –bd přepínačem>\<adresář  - názvem je aktuální datum a čas>\Site_<Site Id>

Příklad adresářové struktury:

"c:\_Archiv\PosterousBackup\20.1.2010 15_57_07\Site_851694"

Zálohován tedy není jeden blog, ale všechny blogy, které jsou asociovány s daným posterous účtem.

Můžete si všimnout, že pro založení podaresáře používvám jednu z RX extenzních metod – metodu Run.

                account.Sites.Run(site =>
                                     {
                                         string sitePath = Path.Combine(currentBackupDir, String.Format(POSTEROUS_SITE_FORMAT, site.Id.ToString()));
                                         Directory.CreateDirectory(sitePath);
                                     });

Metoda Run je náhradou za extenzní metodu ForEach, kterou jste si dříve museli sami dopsat nebo jste byli nuceni použít metodu ForEach ve třídě List takto.

account.Sites.ToList().ForEach(site => {//zbytek kódu lambdy identický s kódem výše…}); 



Zdůrazním, že metoda Run vykoná nějaký vedlejší efekt nad každým elementem v IEnumerable, tedy v našem případě založí adresář, a dále již s elementy nepracuje – vrací void. Za chvíli uvidíme metodu, která nám umožní to samé, co metoda Run, ale elementy pošle po “vykonání vedlejšího efektu nad elementem” (tedy spuštění námi předané funkce jakou je např. lambda  pro založení adresáře) ke zpracování dalším LINQ extenzním metodám.

Signatura metody Run:

public static void Run<TSource>(
    this IEnumerable<TSource> source,
    Action<TSource> action
)

Dále do proměnné processedPosts uložíme pomocí speciálního a pro naši kratochvíli jediného LINQ dotazu všechny zpracované blogposty (instance podporující rozhraní IPosterousPost). Jak vidíte, stačí se přes Posterous API dotázat do kolekce Sites (blogy) a poté z každého blogu zpracovat všechny blogspoty.

from site in account.Sites.AsParallel()
                                      from post in site.Posts.AsParallel()…

Na více místech v dotazu si můžete všimnout volání extenzní metody AsParalllel, kterým dáváte najevo, že zpracování jednotlivých blogpostů a také médií (IPosterousMedium) a komentářů (IPosterousComment) může proběhnout ve více vláknech – o detaily se ale postará PLINQ, vy sami žádná nová vlákna nespouštíte ani nespravujete.

Můžete si také všimnout, že na několika místech volám extenzní metodu Do .Metoda Do pracuje podobně jako před chvílí zmiňovaná metoda Run. Na každý element v zdrojové kolekci aplikuje předanou funkci, ale poté na rozdíl od metody Run element předá k dalšímu zpracování.

Signatura metody Do:

public static IEnumerable<TSource> Do<TSource>(
	this IEnumerable<TSource> source,
	Action<TSource> action
)
 

Zde vypíšeme přes RX extenzní metodu Do titulek právě zpracovávaného blogpostu a Id vlákna, které blogspot zpracovává. Tato metoda je zde jen na ukázku, že každý element ve zdrojové kolekci je metodou Do předán dále ke zpracování

 .Do(postPost =>
                                                  Console.WriteLine(String.Format(GlobalConstants.POST_BACKUP_MESSAGE_FORMAT,
                                                                         postPost.Title,
                                                                         Thread.CurrentThread.ManagedThreadId)))

Na metodu Do navazuje další metoda Do, ve které proběhne zpracování každého blogspotu. V adresáří každé Site (blogu) je vytvořen pro každý blogspot vytvořen nový podadresář, jehož názvem je titulek (vlastnost Title) společně s  Id blogpostu. Do tohoto podadresáře je uložen text blogspotu. Soubor s textem blogspotu má příponu posterous a také jsou do podadresáře uložena média (zvukové, obrazové a video soubory) a všechny komentáře k blogspotu.

Hlavní část programu je za námi. Zde jsou ještě výpisy pomocných tříd.

Třída CommandLineParser pro pársování hodnot předaných uživatelem v příkazovém řádku.

using System;
using System.Collections.Generic;
using System.Linq;

namespace RSPosterousBackup
{
    public class CommandLineParser
    {
        #region constants

        public static readonly char COMMAND_PARTS_SEPARATOR = '-';
        public static readonly char COMMAND_VALUE_SEPARATOR = ':';
        public const int MIN_KEY_PARTS = 1;
        public const int MAX_KEY_PARTS = 2;
        #endregion constants
        
        #region constructors
        public CommandLineParser()
        {
        }
        #endregion constructors
        #region methods
        public virtual Dictionary<string, string> Parse (string[] commandLine)
        {
            if (commandLine == null)
            {
                throw new ArgumentNullException("commandLine");
            }
            
            return parseInner(commandLine);
        }

        private Dictionary<string, string> parseInner(string[] commandLine)
        {            
            var dict = (from part in commandLine
                        let keyValuePair = part.Split(new[] {COMMAND_VALUE_SEPARATOR}, MAX_KEY_PARTS)
                        select new
                                   {
                                       Key = keyValuePair.First().Trim().ToLower(),
                                       Value = keyValuePair.Length > MIN_KEY_PARTS ? keyValuePair.Last().Trim() : String.Empty
                                   }).ToDictionary(kv => kv.Key, kv => kv.Value);

            return dict;
        }

        #endregion methods
    }
}

Konstanty

namespace RSPosterousBackup
{
    public static class GlobalConstants
    {
        public static readonly string INVALID_CML_SWITCH_FORMAT_STRING_EX = "Invalid switch {0}";
        public static readonly char SAFE_FILE_PATH_CHAR = '_';        
        public static readonly string POST_BACKUP_MESSAGE_FORMAT = "Processing post: {0}  - in thread {1}";
        public static readonly string FINAL_BACKUP_MESSAGE_FORMAT = "Total posts: {0}";
        public static readonly string CMDLINE_SHOW_USAGE =
@"Usage: 
RSPosterousBackup.exe -u:<posterous user name> p:<posterous password> -bd:<backup directory>
Example:RSPosterousBackup.exe -u:GIC@Roma.com p:Rubicon -bd:c:\PosterousBackup
RSPosterousBackup.exe -?  - show this help";
                                                                 
    }
}

Třída StringBuilderExtensions s extenzní metodou ToSafeFileName, která v navrhovaném jménu souboru nahradí nepovolené znaky podtržítkem.

using System.IO;
using System.Linq;
using System.Text;

namespace RSPosterousBackup
{
    public static class StringBuilderExtensions
    {
        public static void ToSafeFileName(this StringBuilder builder)
        {
            Path.GetInvalidFileNameChars().Run(ch => builder.Replace(ch, GlobalConstants.SAFE_FILE_PATH_CHAR));
        }
    }
}

Stáhnout výsledné exe -RSPosterousBackup.



Wednesday, 20 January 2010 17:54:21 (Central Europe Standard Time, UTC+01:00)       
Comments [2]  .NET Framework | C# Posterous API | LINQ | RX Extensions


 Friday, 15 January 2010
Projekt C# Posterous API – verze 0.0.0.1 Alfa

 

Stáhnout knihovnu – download

Jak jsem avizoval minulý týden na twitteru, píšu C# wrapper webového API zajímavé služby Posterous.

Pár odkazů na začátek:

Jestliže nevíte, co je Posterous, přečtěte si článek na Živě.

Popis Posterous API. Hned na začátku zdůrazním, že autoři Posterous API nepovažují API za kompletní a za sebe dodám, že je to na mnoha místech vidět.:)

Několik důležitých informací:

  1. Projekt musí být stažen z mých stránek, jakékoli vystavování knihovny na jiném webu a serveru je zakázáno.
  2. Knihovnu jako celek v této verzi můžete používat dle libosti na komerčních i nekomerčních projektech. Zakázáno je samozřejmě vydávání knihovny za vlastní, její dekompilace a použití jen části knihovny.:) Jako autor knihovny nic negarantuji, nezodpovídám za případné přímé ani nepřímé škody vzniklé použitím knihovny a na opravu chyb knihovny není žádný nárok. Chyby lze reportovat na emailovou adresu PosterousAPI@renestein.net.
  3. Teprve dnes padlo rozhodnutí, že API v kódu musejí být komentovány v češtině. API zatím komentováno není a tento spot by vám měl pomoci se v knihovně zorientovat. Posterous API je součástí většího projektu. Posterous jsem si vymyslel a přidal do projektu sám a i když jsme s partnerem dohodnuti, že s Posterous knihovnou si mohu dělat, co chci, dokumentace musí být v češtině – stejně jako zbytek projektu. Pokusím se ale připravit i EN dokumentaci.
  4. Knihovna je zkompilována ve VS 2010 BETA 2 pro .Net Framework 3.5. Chci připravit i verze pro Compact .Net framework a Silverlight.

 

A nyní jž k samotnému API.

Branou k funkcím knihovny je třída PosterousApplication a její statická vlastnost Current.

Nejdříve se podíváme, jak pracovat s účtem Posterous. Metoda GetPosterousAccount vrací odkaz na objekt IPosterousAccount, který reprezentuje účet uživatele na službě Posterous.

using RStein.Posterous.API;
IPosterousAccount m_account = PosterousApplication.Current.GetPosterousAccount("posterousUserName", "posterousPassword");
  public interface IPosterousAccount : IExtensionInterface, IApplicationHolder
    {
        string Name { get; }
        IEnumerable<IPosterousSite> Sites { get; }
        void LoadSites();
        void LoadSitesAsync();
        event EventHandler<EventArgsValue<IEnumerable<IPosterousSite>>> SitesLoaded;
        IPosterousSite PrimarySite {get;}
        
    }

Nejzajímavější vlastností v rozhraní IPosterousAccount je vlastnost Sites, která obsahuje kolekci všech “blogů” uživatele. Kolekce Sites, stejně jako většina dalších vlastností a kolekcí i u jiných objektů, je naplněna daty až při prvním přístupu.

Jestliže chcete pracovat s výchozím blogem uživatele, můžete využít vlastnost IPosterousAccount.PrimarySite.

Rozhraní IPosterousSite

public interface IPosterousSite : IExtensionInterface
    {
        int Id { get; }
        string Name { get; }
        string Url { get; }
        bool IsPrivate { get; }
        bool IsPrimary{ get;}
        bool AreCommentsEnabled{ get; }
        IPosterousAccount PosterousAccount { get; set; }

        IEnumerable<IPosterousPost> Posts { get; }
        int TotalPosts { get; }
        int LoadedPosts { get; }
        void LoadAllPosts();
        void LoadAllPostsAsync();
        event EventHandler<EventArgsValue<IEnumerable<IPosterousPost>>> PostsLoaded;        

        IEnumerable<string> Tags { get; }
        bool IsTagsLoaded { get; }        
        void LoadTags();
        void LoadTagsAsync();
        event EventHandler<EventArgsValue<IEnumerable<string>>> TagsLoaded;
        
        IEnumerable<IPosterousPost> GetPostsByTag(string tag);
        void GetPostsByTagAsync(string tag);
        event EventHandler<EventArgsValue<IEnumerable<IPosterousPost>>> PostsByTagLoaded;

        IEnumerable<IPosterousPost> GetPostsInPage(int page, int recordsCount);
        void GetPostsInPageAsync(int page, int recordsCount);
        event EventHandler<EventArgsValue<IEnumerable<IPosterousPost>>> PostsInPageLoaded;

        IPosterousPost CreatePost(string title, string body, bool autopostAll);

        
    }

Každý blog (IPosterousSite) obsahuje blogspoty  - objekty podporující rozhraní IPosterousPost.

Assert.IsTrue(m_account.PrimarySite.Posts.Count() > 0); 
 public interface IPosterousPost : IEntityWithClientState
    {
        string Link { get; }
        string Title{ get; set; }        
        string Url { get; }
        int Id { get;}
        string Body {get; set;}                    
        DateTime PostDate { get; }
        int Views { get; }
        bool Private { get; }
        IAuthor Author { get; }
        bool AreCommentsEnabled { get; }                
        IPosterousComment CreateNewComment(string commentBody);
        void AppendMedium(string filePath);        
        IEnumerable<IPosterousComment> Comments { get; }
        IEnumerable<IPosterousMedium> Media { get; }
        IEnumerable<String> Tags{ get; }
        void AddTag(string tag);
        IPosterousSite Site { get; }
        void Refresh();
        
    }

Při přístupu k vlastnosti Posts jsou staženy všechny blogspoty v dávkách po 50 položkách. 50 položek najendou je interní omezení Posterous API. Jestliže nechcete nahrávat všechny blogspoty, můžete sami “stránkovat” a nahrávat blogspoty pomocí metody GetPostsIn Page.

//Nahraje z první stránky dva blogspoty
var posts = m_account.Sites.First().GetPostsInPage(1, 2);

Můžete také nahrát pouze blogspoty označené vybraným tagem. Seznam dostupných tagů zjistíte ve vlastnosti IPosterousSite.Tags. Dle mých zkušeností ale vrácení blogpostů nefunguje v Posterous API zcela správně a občas blogposty vráceny nejsou.

//Vrátí se blogposty označené tagem "Všechno a nic"
var posts = m_account.Sites.First().GetPostsByTag("Všechno a nic");

 

Kromě dalších zajímavých a samopopisných informací v každém blogspotu naleznete i kolekci komentářů k blogspotu (rozhraní IPosterousComment) a informaci o přiložených souborech (audio, foto, mp3… – rozhraní IPosterousMedium ) .

public interface IPosterousComment : IExtensionInterface 
    {
        int Id {get;}
        IAuthor Author {get;}
        DateTime CreateDate {get;}        
        string Body {get;}        
        IPosterousPost Post{get;}
        
        
    }
 public interface IPosterousMedium : IExtensionInterface
    {
        MediumType Type { get;}
        string Url { get;  }
        Stream Content { get; }
        int FileSize { get; }
        IDictionary<string, object> ExtendedInfo { get; }
        bool IsContentLoaded { get;}
        void LoadContent();
        void LoadContentAsync();   
        event EventHandler<EventArgs> ContentLoaded;
    }
 

U médií se vlastnost Content opět naplní až při přístupu a jakékoli další informace o médiích stažené z Posterous naleznete v kolekci ExtendedInfo – např. informace o náhledu obrázku.

Nové blogspoty je samozřejmě možné vytvářet i s médii.

//Nový post, první argument – titulek blogspotu, druhý argument tělo blogspotu, 
//třetí argument - pokud je true dojde automaticky k rozeslání postu na všechny další registrované služby -//(Twitter, FB...)
IPosterousPost newPost = m_account.PrimarySite.CreatePost("Obrázek HUDBA TeST",
                                                "Příliš žluťoučký kůň úpěl ďábelské ódy", true);

//Přidání obrázku
            newPost.AppendMedium(@"c:\Users\STEIN\Documents\Hudba\Once\AlbumArtSmall.jpg");
//Přidání mp3
            newPost.AppendMedium(@"c:\Users\STEIN\Documents\Hudba\Once\01_falling_slowly.mp3");
//Uložení postu na server
            newPost.SaveChanges();

Posterous bohužel nevrací po uložení automaticky veškeré informace o novém spotu (informace o médiích apod.), takže jsem zvolil mechanismus, kdy po volání SaveChanges je vždy ještě volána metoda Refresh, která přes další (Bit.Ly) API dotáhne podrobnosti, aby programátor nemusel na získání dodatečných údajů myslet a volat metodu Refresh sám.

Metodu Refresh ale samozřejmě sami volat můžete a získate tak vždy aktuální data ze serveru.

Uložený blogspot můžete editovat – ne všechny údaje lze nyní uložit na server, podívejte se na současný stav web API.

string updateText = "Updated " + DateTime.Now.ToString();
            post.AppendMedium(@"c:\Users\STEIN\Documents\Hudba\Dylan Bob - Time Out Of Mind\AlbumArt_{6DF0A444-4F68-489B-AFCF-A985B02166BB}_Large.jpg" );
            post.Body = updateText;
            post.Title = updateText;
            post.SaveChanges();

K uloženému blogspotu můžete přidávat nové komentáře.

var comment = post.CreateNewComment(updateText);            
            post.SaveChanges();
  

Posterous API dovoluje vytvořit zjednodušený nový blogpost, aniž byste museli mít na Posterous účet. K publikaci vám stačí předat jméno a heslo stávajícího twitter účtu.  Url nového blogspotu automaticky publikuje na twitter. Jestliže máte Posterous účet svázaný s twitter účtem, blogspot se uloží na vašem primárním blogu (Site). Toto API se hodí hlavně pro rychlou publikaci obrázků na twitter a  Posterous toto API považuje za alternativu ke službě TwitPic.

Nejprve opět přes vstupní objekt PosterousApplication získáte twitter účet (rozhraní ITwitterAccount) .

m_twitterAccount = PosterousApplication.Current.GetTwitterAccount("twitter_name", "twitter_password");
    public interface ITwitterAccount : IApplicationHolder
    {
        string UserName{ get; }
        ITwitterPost CreatePost(string title, string body, bool postToTwitter);        
    }
 

A takto vypadá rychlá publikace obrázku

//Nový post, první argument – titulek blogspotu, druhý argument - tělo blogspotu, třetí argument – pokud je true, automaticky dojde k publikaci url obrázku (blogspotu) na twitter.
ITwitterPost newPost = m_twitterAccount.CreatePost(null,
                                                               null, true); 

            newPost.AppendMedium(“c:\pic.jpg”);
            newPost.SaveChanges();
 

Rozhraní ITwitterPost.

public interface ITwitterPost : IEntityWithClientState
    {
        string Url { get; }
        string Title{ get; }
        string Body { get; }
        string MediaId { get; }
        void AppendMedium(string filePath);
        bool AutopostToTwitter { get; }
        ITwitterAccount TwitterAccount { get; }
        IEnumerable<String> MediaNames { get; }
        ISinglePostInfo GetPostInfo();
    }

Další API vám dovoluje získat informace o libovolném blogpostu, u kterého znáte Bit.ly  adresu – Bit.Ly adresa je každému blogpostu přiřazena při vytvoření  - vlastnost IPosterousPost.Url.

Tento blogpost nemusí pocházet z vašeho blogu (Site).

Opět přes objekt PosterousApplication získáte odkaz na IBitLyService.

 public interface IBitLyService : IApplicationHolder
    {
        ISinglePostInfo GetSinglePost(string bitLySuffix);
        void GetSinglePostAsync(string bitLySuffix);
        event EventHandler<EventArgsValue<ISinglePostInfo>>  SinglePostLoaded;
    }
 
Ukázka získání jednoduchého blogspotu z této služby.
Uri uri = new Uri(Url);
//Extenzní metoda GetBitLySuffix pro snadné získání suffixu
ISinglePostInfo post = bitLyService.GetSinglePost(uri.GetBitLySuffix())
 

Rozhraní ISinglePostInfo

 public interface ISinglePostInfo : IExtensionInterface
    {
        string Link { get; }
        string Title{get;}        
        string Url { get; }
        int Id { get;}
        string Body{get;}                    
        DateTime PostDate {get;}
        int Views { get; }
        bool Private { get; }
        IAuthor Author { get; }
        bool AreCommentsEnabled { get; }                        
        IEnumerable<IPosterousComment> Comments { get;}
        IEnumerable<IPosterousMedium> Media { get;}
        IEnumerable<String> Tags{ get;}        
    }
 

Pokročilejší nastavení, která by se vám mohla hodit.

Blogspoty mohou být označeny jménem aplikace, která je vytvořila, a odkazem na aplikaci.

PosterousApplication.Current.ApplicationName = "Moje cool aplikace";
PosterousApplication.Current.ApplicationUrl = http://renestein.net;

Chcete pracovat přímo s objekty HttpWebRequest a HttpWebResponse? Potřebujete doplnit autentizaci k proxy, nebo chcete změnit maximální dobu, po kterou bude trvat požadavek? S pomocí rozhraní IRawRequestResponsePublisher  je to jednoduché.

    public interface IRawRequestResponsePublisher : IExtensionInterface
    {
        event EventHandler<EventArgsValue<WebRequest>> WebRequestCreated;
        event EventHandler<EventArgsValue<WebResponse>> WebResponseCreated;
    }
 

Stačí zaregistrovat obslužné metody pro události a poté všechny objekty HttpWebRequest a HttpWebResponse, které interně knihovna používá,  můžete upravit dle libosti.

Ukázka změny vlastnosti Timeout.

IRawRequestResponsePublisher publisher =
                PosterousApplication.Current.GetInterface<IRawRequestResponsePublisher>();

            Debug.Assert(publisher != null);
            publisher.WebRequestCreated += ((_, e) => e.Value.Timeout = WEB_TIMEOUT);

C# Posterous API toho zvládne ještě více, ale myslím, že pro dnešek už bylo kódu dost. Užijte si to. :-)



Friday, 15 January 2010 17:49:51 (Central Europe Standard Time, UTC+01:00)       
Comments [1]  .NET Framework | C# Posterous API | Compact .Net Framework | Silverlight


 Wednesday, 18 November 2009
Vynucení si překreslení celé obrazovky v Compact .Net Frameworku

Při snaze kreslit grafické objekty na celou obrazovku PDA, a ne pouze na vlastní formulář, se můžete velmi často setkat s dotazem, jak zajistím, že předchozí nakreslené dílko, rozprostřené většinou přes vícero formulářů a mimo naši přímou kontrolu, smažu před vykreslením dalšího dílka. Následující příklad je reakcí na takový dotaz. Za “celou obrazovkou” budeme v článku považovat grafický kontext vrácený voláním metody GetDC s argumentem NULL. Po celé obrazovce PDA je postupně vykreslován kruh (s velkou fantazií míč) pohybující se z levé strany displeje na pravou. Před vykreslením “míče” na další pozici musí být míč vykreslený v předcházejícím kroku smazán. Následující kód je ukázkou použití “brutální síly”, protože žádné slečinkovské, sexy ani elegantní konstrukce s překreslováním pouze části obrazovky k výsledku nevedly. Hlavní trik, ke kterému jsem dospěl po sundání bílých vývojářských rukaviček, spočívá v rekurzivním vynucení si překreslení všech oken v metodě DoWork s využitím přímého volání mnoha nativních API funkcí, které mají paradoxně v aplikacích cílených na Microsoftem macešsky spravovaný a rozvíjený Compact .Net Framework (i ve verzi. 3.5) stále privilegovanou pozici.

 using System;
 using System.ComponentModel;
 using System.Drawing;
 using System.Threading;
 using System.Windows.Forms;

namespace RedrawScreenTest
{
    

    partial class Form1 : Form
    {

        #region constants
        
        public const int POSITION_INCREMENT = 10;
        public const int MAX_POSITION = 200;
        public const int MAX_THREAD_WAIT = 5000;
        public const int CIRCLE_RADIUS = 30;

        #endregion constants
        
        #region delegates
        public delegate void InvokeDelegate();
        #endregion delegates

        #region Properties
            public IntPtr WindowsHDC { get; set; }        
            public Thread Worker { get; set; }        
            public bool ShouldWork { get; set; }        
            public bool IsWorking { get; set; }
        #endregion Properties


         #region Constructors
            public Form1()
        {
            InitializeComponent();  
            WindowsHDC = IntPtr.Zero;
            ShouldWork = false;
            IsWorking = false;
        }
       #endregion Constructors

            #region methods
            public void DoWork()
        {
            IsWorking = true;
            int x = 0;
            int y = 0;
            

            bool firstDraw = true;


            while (ShouldWork)
            {
                WindowsHDC = ApiWrapper.GetDC(IntPtr.Zero);

                if (WindowsHDC == IntPtr.Zero)
                {
                    break;
                }

                


                if (!firstDraw)
                {

                    Invoke((InvokeDelegate) (() =>
                                                 {

                                                     IntPtr hwnd = ApiWrapper.GetForegroundWindow();

                                                     hwnd = ApiWrapper.GetWindow(hwnd, (int)ApiWrapper.GWConstants.GW_HWNDFIRST);


                                                     while (hwnd != IntPtr.Zero)
                                                     {

                                                         ApiWrapper.RedrawWindow(hwnd, IntPtr.Zero, IntPtr.Zero,
                                                                      ApiWrapper.RDW_ERASE | ApiWrapper.RDW_INVALIDATE | ApiWrapper.RDW_ALLCHILDREN);

                                                         
                                                         EnumChild(hwnd, 0);

                                                         hwnd = ApiWrapper.GetWindow(hwnd, (int)ApiWrapper.GWConstants.GW_HWNDNEXT);

                                                     }
                                                 }));
                }
                else
                {
                      
                       firstDraw = false;       

                }



                using (Graphics g = Graphics.FromHdc(WindowsHDC))
                using (Brush b = new SolidBrush(Color.Yellow))
                {
                    g.FillEllipse(b, new Rectangle(x, y, CIRCLE_RADIUS, CIRCLE_RADIUS));                    
                    
                }
                    
                
                
                    ApiWrapper.ReleaseDC(IntPtr.Zero, WindowsHDC);
                    

                    Thread.Sleep(1000);

                    x += POSITION_INCREMENT;
                    y += POSITION_INCREMENT;

                    if (x > MAX_POSITION || y > MAX_POSITION)
                    {
                        x = 0;
                        y = 0;
                    }
                }

                IsWorking = false;
            }
        
        private void EnumChild(IntPtr hwnd, int level)
        {

            if ((hwnd == IntPtr.Zero))
            {
                return;
            }

            IntPtr childHwnd = ApiWrapper.GetWindow(hwnd, (int)ApiWrapper.GWConstants.GW_CHILD);            

            while (childHwnd != IntPtr.Zero)
            {
                EnumChild(childHwnd, level + 1);
                ApiWrapper.RedrawWindow(childHwnd, IntPtr.Zero, IntPtr.Zero,
                          ApiWrapper.RDW_ERASE | ApiWrapper.RDW_INVALIDATE | ApiWrapper.RDW_ALLCHILDREN);

                childHwnd = ApiWrapper.GetWindow(childHwnd, (int)ApiWrapper.GWConstants.GW_HWNDNEXT);

            }
            

            

        }
        private void Form1_Load(object sender, EventArgs e)
        {
            
        }

        
        private void Form1_Closing(object sender, CancelEventArgs e)
        {
            buttonStop_Click(this, new EventArgs());


            if (WindowsHDC != IntPtr.Zero)
            {
                ApiWrapper.ReleaseDC(IntPtr.Zero, WindowsHDC);
            }
                
        }

        private void buttonStart_Click(object sender, EventArgs e)
        {
            if (IsWorking)
            {
                return;
            }
            
            ShouldWork = true;
            Worker = new Thread(DoWork);
            Worker.Start();
        }

        private void buttonStop_Click(object sender, EventArgs e)
        {
            if (!IsWorking)
            {
                return;
            }
            
            ShouldWork = false;
            Worker.Join(MAX_THREAD_WAIT);
        }

           

        private void Form1_Paint(object sender, PaintEventArgs e)
        {

        }

        #endregion methods
    }   
  }  

Zde jsou potřebné deklarace API funkcí, konstant a struktur


using System;
using System.Drawing;
using System.Runtime.InteropServices;

namespace RedrawScreenTest
{
    class ApiWrapper
    {
        [DllImport("coredll.dll")]
        [return: MarshalAs(UnmanagedType.Bool)]
        public static extern bool RedrawWindow(IntPtr hWnd, IntPtr lprcUpdate,
                IntPtr hrgnUpdate, uint flags);

        public const int RDW_INVALIDATE = 0x0001;
        const int RDW_INTERNALPAINT = 0x0002;
        public const int RDW_ERASE = 0x0004;

        const int RDW_VALIDATE = 0x0008;
        const int RDW_NOINTERNALPAINT = 0x0010;
        const int RDW_NOERASE = 0x0020;

        const int RDW_NOCHILDREN = 0x0040;
        public const int RDW_ALLCHILDREN = 0x0080;

        const int RDW_UPDATENOW = 0x0100;
        const int RDW_ERASENOW = 0x0200;

        const int RDW_FRAME = 0x0400;
        const int RDW_NOFRAME = 0x0800;



        [DllImport("coredll.dll", SetLastError = true)]
        public static extern IntPtr GetWindow(IntPtr hWnd, int uCmd);

        public enum GWConstants
        {
            GW_HWNDFIRST = 0,
            GW_HWNDLAST = 1,
            GW_HWNDNEXT = 2,
            GW_HWNDPREV = 3,
            GW_OWNER = 4,
            GW_CHILD = 5,
            GW_ENABLEDPOPUP = 6
        }



        [DllImport("coredll.dll")]
        public static extern IntPtr GetForegroundWindow();

        [DllImport("coredll.dll", EntryPoint = "GetWindowDC")]
        public static extern IntPtr GetDC(IntPtr ptr);
        
        [DllImport("coredll.dll", EntryPoint = "ReleaseDC")]
        public static extern int ReleaseDC(IntPtr hWnd, IntPtr hDc);
        
        [DllImport("coredll.dll", EntryPoint = "GetDesktopWindow")]
        public static extern IntPtr GetDesktopWindow();
        
        [DllImport("coredll.dll", EntryPoint = "UpdateWindow")]
        public static extern bool UpdateWindow(IntPtr hWnd);
        
        [DllImport("coredll.dll", EntryPoint = "UpdateWindow")]
        public static extern int SendMessage(IntPtr hWnd, uint msg, int wparam, int lparam);
        
        [DllImport("coredll.dll")]
        public extern static void InvalidateRect(IntPtr handle, Rectangle dummy, bool erase);

        [DllImport("coredll.dll", EntryPoint = "FindWindowW", SetLastError = true)]
        public static extern IntPtr FindWindow(string lpClassName, string lpWindowName);



    }
}


Wednesday, 18 November 2009 15:02:51 (Central Europe Standard Time, UTC+01:00)       
Comments [0]  Compact .Net Framework