\

Školení Návrhové vzory, OOP a UML


 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