Feeds:
投稿
コメント

Archive for 2012年7月

・Page 0 : ReactiveOAuthを使う前に -WP7のアプリとLINQ,Rx- (https://fantasticswallow.wordpress.com/2012/07/12/dev-twitter-client-for-wp7-page0-whats-linq-and-rx/)
・Page 1 : ReactiveOAuthを使ってみる -Rxと通信層の作成- (https://fantasticswallow.wordpress.com/2012/07/12/dev-twitter-client-for-wp7-page1-using-reactive-oauth/)
・Page 2 : ツイートしてみる -ReactiveOAuthを用いたTwitterとの通信- (https://fantasticswallow.wordpress.com/2012/07/14/dev-twitter-client-for-wp7-page2-how-to-tweet-for-twitter/)
・Page 3 : Twitterに認証する -PIN Flowを用いた認証- (この記事)
・Page 4 : タイムラインを表示する -GETリクエストとXAMLの編集- (???)
・おまけ : Server Side Flowを用いたTwitterへの認証とその罠 (???)

注意点
・この記事はプログラミング初心者に向けたものではありません。C#|VB.NETがいくらかわかる前提で進めます。解説はしません
・Silverlight|WPFの開発経験が少しでもあるとわかりやすいです
・LINQが少しでもわかるととてもよいです
・Twitter APIなどについてはほとんど解説しません。あるがままに扱われます。その辺は適当にBing先生にでも
・コンシューマーキーの取得は済んでいるものとします
・WP7.5の開発環境がすでにあるものとします
・Twitterアカウントを持っているものとします
・初心者が書いてるのでMVVMパターンなどで開発したいなどなどは対応していません

Minecraftが楽しすぎて進んでいません

さて認証です。Twitterクライアントを作るうえでは必須の動作ですね

ここではPIN Flowによる認証を扱います。Server Side Flowはまた今度

今回もC#のみです
さあさあ本題へ

・今回のxaml
Page2のMainPageがあるものとします。
MainPageにまずApplicationBarを追加します。次のxamlを追加します

    <phone:PhoneApplicationPage.ApplicationBar>
        <shell:ApplicationBar IsVisible="True" IsMenuEnabled="True">
            <!--<shell:ApplicationBarIconButton IconUri="/Images/appbar_button1.png" Text="Button 1"/>
            <shell:ApplicationBarIconButton IconUri="/Images/appbar_button2.png" Text="Button 2"/>-->
            <shell:ApplicationBar.MenuItems>
                <shell:ApplicationBarMenuItem Text="Twitterに認証" Click="ApplicationBarMenuItem_Click" />
            </shell:ApplicationBar.MenuItems>
        </shell:ApplicationBar>
    </phone:PhoneApplicationPage.ApplicationBar>

デフォルトのがあればそれをいじります。ApplicationBarIconButtonのほうは使わないのでコメントアウトします
次に認証するページのxamlを用意します。次の通り

<phone:PhoneApplicationPage 
    x:Class="TestPhoneApp1.AuthPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
    xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    FontFamily="{StaticResource PhoneFontFamilyNormal}"
    FontSize="{StaticResource PhoneFontSizeNormal}"
    Foreground="{StaticResource PhoneForegroundBrush}"
    SupportedOrientations="Portrait" Orientation="Portrait"
    mc:Ignorable="d" d:DesignHeight="768" d:DesignWidth="480"
    shell:SystemTray.IsVisible="True" Loaded="PhoneApplicationPage_Loaded">

    <!--LayoutRoot は、すべてのページ コンテンツが配置されるルート グリッドです-->
    <Grid x:Name="LayoutRoot" Background="Transparent">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>

        <!--ContentPanel - 追加コンテンツをここに入力します-->
        <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
            <TextBlock Height="Auto" HorizontalAlignment="Left" Margin="14,14,0,0" Name="textBlock1" Text="Pin Code" VerticalAlignment="Top" FontSize="32" />
            <TextBox x:Name="PinTextBox" Height="75" Margin="133,0,99,0" TextWrapping="Wrap" VerticalAlignment="Top" InputScope="Number"/>
            <Button x:Name="AuthenticateButton" Content="Go" HorizontalAlignment="Right" Margin="0,4,0,0" VerticalAlignment="Top" Width="95" Click="AuthenticateButton_Click" />
            <phone:WebBrowser x:Name="AuthNavigateBrowser" Margin="0,87,0,0" d:LayoutOverrides="Width, Height"/>
        </Grid>
    </Grid>
</phone:PhoneApplicationPage>

これでxamlの準備は完了です。認証してポストまで目指します

・認証部分のコードを書く
ここについてはReactiveOAuthのページのほうにサンプルがありますのでそのまま使って大丈夫です。今回は汎用性?をあげるために少し変更します

using System;
using System.Linq;
using Codeplex.OAuth;
using Microsoft.Phone.Reactive;

namespace TestPhoneApp1
{
    public class TwitterAuthorizer
    {
        private RequestToken reqToken;
        private AccessToken acsToken;

        private OAuthAuthorizer authorizer = new OAuthAuthorizer(TwitterInfoCS.ConsumerKey, TwitterInfoCS.ConsumerSecret);

        public delegate void GetPinCode(string navigateUri, TwitterAuthorizer author);

        public void GetRequestToken(GetPinCode getPinStr, TwitterAuthorizer  author)
        {
            var authorizer = new OAuthAuthorizer(TwitterInfoCS.ConsumerKey, TwitterInfoCS.ConsumerSecret);
            authorizer.GetRequestToken("https://twitter.com/oauth/request_token")
                .Select(res => res.Token)
                .ObserveOnDispatcher()
                .Subscribe(token =>
                {
                    reqToken = token;
                    var url = authorizer.BuildAuthorizeUrl("https://twitter.com/oauth/authorize", token);
                    getPinStr(url, this);
                });
        }

        public void GetAccessToken(string pinStr)
        {
            authorizer.GetAccessToken("https://twitter.com/oauth/access_token", reqToken, pinStr)
                .ObserveOnDispatcher()
                .Subscribe(res =>
                {
                    // Token取得時のレスポンスには、Token以外に幾つかのデータが含まれています
                    // Twitterの場合はuser_idとscreeen_nameがついてきます
                    // ILookup<string,string>なので、First()で取り出してください
                    TwitterInfoCS.OAuthUserId = UInt64.Parse(res.ExtraData["user_id"].First());
                    TwitterInfoCS.OAuthScreenName = res.ExtraData["screen_name"].First();
                    acsToken = res.Token; // AccessToken
                    TwitterInfoCS.OAuthToken = acsToken.Key;
                    TwitterInfoCS.OAuthTokenSecret = acsToken.Secret;
                    var evArgs = new TwitterAuthedEventArgs();
                    evArgs.OAuthToken = acsToken.Key; evArgs.OAuthTokenSecret = acsToken.Secret;
                    evArgs.UserId = TwitterInfoCS.OAuthUserId;
                    evArgs.UserScreenName = TwitterInfoCS.OAuthScreenName;
                    TwitterAuthed(evArgs);
                });
        }
        
        public delegate void oauthedTwitter(TwitterAuthedEventArgs e);
        public event oauthedTwitter TwitterAuthed;

        public class TwitterAuthedEventArgs
        {
            public UInt64 UserId { get; set; }
            public string UserScreenName { get; set; }
            public string OAuthToken { get; set; }
            public string OAuthTokenSecret { get; set; }
        }
    }
}

RequestTokenのほうではUriを取得したあとの処理をデリゲートに直しました。これによりwebBrowserの位置にとらわれずに認証処理ができます。ラムダ式でささっと書いてしまいましょう
AccessTokenのほうではTwitterへの認証に成功するとイベントを返すようにしてあります。このイベントをハンドルすることでメッセージを出したりできます

・AuthPageのコードビハインド
必要なのはロード時に指定したUriに移動することと認証ボタンを押してAccessTokenを取得する処理に移行するメソッドの二つです。あとはそれらに必要な参照などを追加します
こんなメソッドとかを追加します

        public static string navigateUri{get; set;}
        public static TwitterAuthorizer authRef { get; set; }

        private void PhoneApplicationPage_Loaded(object sender, RoutedEventArgs e)
        {
            AuthNavigateBrowser.Navigate(new Uri(navigateUri));
        }

        private void AuthenticateButton_Click(object sender, RoutedEventArgs e)
        {
            bool isValid = false;
            try
            {
                var tagInt = int.Parse(PinTextBox.Text);
                if (tagInt >= 0 && tagInt < 10000000 && PinTextBox.Text.Length == 7) //7桁のコード
                {
                    isValid = true;
                }
            }
            catch
            {
                MessageBox.Show("正しいPINコードが入力されていません");
            }
            if (isValid)
            {
                authRef.GetAccessToken(PinTextBox.Text);
            }
        }

TwitterAuthorizerの参照とnavigateUriをstaticで宣言してあります。これは単純に参照がめんどくさいからですはい。
認証ボタンのところではPINが正しい形かどうかをチェックしています。ただし現在の7ケタの形から変わると即動かなくなりますので注意。あと必要ないなら削ってもいいかもしれません

・MainPageからAuthPageへの遷移
さてやっとMainPageのコードビハインドです
ところで前回の時にLoadedでOAuthToken設定してる場合は設定する部分を削除してください。念のためです
ConsumerKeyとConsumerSecretは削除しないでくださいね

先ほど作ったApplicationBarMenuItemのクリックイベントのハンドラとなります。まず押されたときにRequestTokenを取得してその後AuthPageに遷移します。ページの遷移にはNavigationServiceを使います。
ここで先ほどRequestTokenの取得後の処理をデリゲートにしておいたのでラムダ式で書いてみましょう。
次にAccessToken取得後にイベントが返るのでこれにハンドラを追加します。某Kriなんとかみたいに「Hello スクリーンネーム」って出してみましょう。その後元のページに戻します
これらをコードにすると次のような感じ

        private void ApplicationBarMenuItem_Click(object sender, EventArgs e)
        {
            var author = new TwitterAuthorizer();
            author.TwitterAuthed += _ =>
            {
                Dispatcher.BeginInvoke(() =>
                    {
                        MessageBox.Show("Hello! " + _.UserScreenName);
                        NavigationService.GoBack();
                    });
            };
            author.GetRequestToken((uri,athr) =>
            {
                AuthPage.navigateUri = uri;
                AuthPage.authRef = athr;
                NavigationService.Navigate(new Uri("/AuthPage.xaml",UriKind.Relative));
            }, author);
        }

この時AuthPage.xamlがないと動けないので適宜直してください

・さあ認証だ?
ここまでできたら実際に起動してApplicationBarの中のメニューをタップしてみましょう。ネットワークにつながってればたぶんブラウザが表示されてPIN入力までできるはずです

「姫様!もうすぐアプリケーションが閉じそうです!」

というわけでまともにこの通りにやってたらまたしても落ちてしまいます。しかもReactiveOAuthの中で
というのもTwitterとかいう何かはaccess_tokenを取得するとなぜか強制GZIP化して返してくださいます、とっても親切設計ですね!実際はデータ量増えるはずですが

さてさて、こんな理不尽な不具合に対処できないようでは一人前のTwitterクライアント作者にはなれませんので(こんな不具合ばっかです)カカッと対処しちゃいましょう

とはいってもこれどうやって対処するのってなりますがもちろん今いじってるアプリケーションからじゃどうしようもありませんので、ReactiveOAuth自体をいじります
まずはCodePlexからソースコードを拾ってきます。その辺は適当に
落ちる部分はOAuthAuthorizer.csの中のGetTokenResponseのメソッドのなかのレスポンスの文字列解析部分あたりなのでそもそも文字列解析の前にGzipエンコードを処理してやれば落ちることはないのでレスポンスの文字列取得時にデコードします

さて、こんなコードが見えると思います

return req.DownloadStringAsync()
                .Select(tokenBase =>
                {
                    var splitted = tokenBase.Split('&').Select(s => s.Split('=')).ToDictionary(s => s.First(), s => s.Last());
                    var token = tokenFactory(splitted["oauth_token"], splitted["oauth_token_secret"]);
                    var extraData = splitted.Where(kvp => kvp.Key != "oauth_token" && kvp.Key != "oauth_token_secret")
                        .ToLookup(kvp => kvp.Key, kvp => kvp.Value);
                    return new TokenResponse<T>(token, extraData);
                });

さてさて、これじゃ手の出しようがありません。というのもすでに文字列になってます
だったrDownloadStringAsyncを変えればいいじゃない! というわけですばらしいReactiveOAuthにはGetResponseAsObservableがありますのでこれに書き換えてWebResponseを取得します。

次にGzip対策にSharpZipLibのSilverlight版を取得します。http://slsharpziplib.codeplex.com/からダウンロードできます
この中のSharpZipLib.WindowsPhone7.dllをReactiveOAuth.WP7の参照に追加します。そうしないと使えません。

というわけでSharpZipLibを使ってGzip対策したりすると次のコードになります。上の部分を下のコードに差し替えてください

return req.GetResponseAsObservable()
                .Select(tokenArgs =>
                {
                    string tokenBase;
#if WINDOWS_PHONE
                    if (tokenArgs.Headers[HttpRequestHeader.ContentEncoding] == "gzip")
                    {
                        using (System.IO.StreamReader xreader = new System.IO.StreamReader(new ICSharpCode.SharpZipLib.GZip.GZipInputStream(tokenArgs.GetResponseStream())))
                        {
                            tokenBase = xreader.ReadToEnd();
                        }
                    }
                    else
                    {
                        using (System.IO.StreamReader xreader = new System.IO.StreamReader(tokenArgs.GetResponseStream()))
                        {
                            tokenBase = xreader.ReadToEnd();
                        }
                    }
#else
                    using (System.IO.StreamReader xreader = new System.IO.StreamReader(tokenArgs.GetResponseStream()))
                    {
                        tokenBase = xreader.ReadToEnd();
                    }
#endif
                    var splitted = tokenBase.Split('&').Select(s => s.Split('=')).ToDictionary(s => s.First(), s => s.Last());
                    var token = tokenFactory(splitted["oauth_token"], splitted["oauth_token_secret"]);
                    var extraData = splitted.Where(kvp => kvp.Key != "oauth_token" && kvp.Key != "oauth_token_secret")
                        .ToLookup(kvp => kvp.Key, kvp => kvp.Value);
                    return new TokenResponse<T>(token, extraData);
                });

こうしてやるともれなくGzipだろうとちゃんと処理できるようになります。これでビルドして新しいdllを作ったらもとのアプリケーションのほうの参照をこちらに変えます

・認証してみよう
お疲れ様でした。たぶん私の書き方が間違ってなければこれで認証できるはずです。
認証できたら思う存分ツイートしましょう。

とりあえず言っておくとここから先に進めば進むほど今回よりめんどくさい不具合がのしかかりますので下手な覚悟だと作れません。頑張ってくださいね

次回は最終回です。タイムラインを表示してみましょう

広告

Read Full Post »

・Page 0 : ReactiveOAuthを使う前に -WP7のアプリとLINQ,Rx- (https://fantasticswallow.wordpress.com/2012/07/12/dev-twitter-client-for-wp7-page0-whats-linq-and-rx/)
・Page 1 : ReactiveOAuthを使ってみる -Rxと通信層の作成- (https://fantasticswallow.wordpress.com/2012/07/12/dev-twitter-client-for-wp7-page1-using-reactive-oauth/)
・Page 2 : ツイートしてみる -ReactiveOAuthを用いたTwitterとの通信- (この記事)
・Page 3 : Twitterに認証する -PIN Flowを用いた認証- (???)
・Page 4 : タイムラインを表示する -GETリクエストとXAMLの編集- (???)
・おまけ : Server Side Flowを用いたTwitterへの認証とその罠 (???)

注意点
・この記事はプログラミング初心者に向けたものではありません。C#|VB.NETがいくらかわかる前提で進めます。解説はしません
・Silverlight|WPFの開発経験が少しでもあるとわかりやすいです
・LINQが少しでもわかるととてもよいです
・Twitter APIなどについてはほとんど解説しません。あるがままに扱われます。その辺は適当にBing先生にでも
・コンシューマーキーの取得は済んでいるものとします
・WP7.5の開発環境がすでにあるものとします
・Twitterアカウントを持っているものとします
・初心者が書いてるのでMVVMパターンなどで開発したいなどなどは対応していません

さてやっとこツイートするところです
真面目に初心者だからこんなxaml望んでないとかなっても頑張って直してください

本題に入る前に前回の投稿のTwitterInfoCSとTwitterConnectionCSを用意してある前提で進めます。

あと前回書き忘れてたのですがSystem.Observableの参照追加しないとビルド通りません。追加してない場合は追加しましょう

ちなみにここからVBのコードがなくなります。あとで書きます

では本題へ

・今回のxaml
投稿しかしないので適当でいいかなって(
とりあえずTextBoxとButtonがあればいいです。こんな感じに用意してみました

<phone:PhoneApplicationPage 
    x:Class="TestPhoneApp1.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
    xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d" d:DesignWidth="480" d:DesignHeight="768"
    FontFamily="{StaticResource PhoneFontFamilyNormal}"
    FontSize="{StaticResource PhoneFontSizeNormal}"
    Foreground="{StaticResource PhoneForegroundBrush}"
    SupportedOrientations="Portrait" Orientation="Portrait"
    shell:SystemTray.IsVisible="True" Loaded="PhoneApplicationPage_Loaded">

    <!--LayoutRoot は、すべてのページ コンテンツが配置されるルート グリッドです-->
    <Grid x:Name="LayoutRoot" Background="Transparent">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>

        <!--TitlePanel は、アプリケーション名とページ タイトルを格納します-->
        <StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="12,17,0,28">
            <TextBlock x:Name="ApplicationTitle" Text="TestPhoneApp1" Style="{StaticResource PhoneTextNormalStyle}"/>
            <TextBlock x:Name="PageTitle" Text="ツイート!" Margin="9,-7,0,0" Style="{StaticResource PhoneTextTitle1Style}"/>
        </StackPanel>

        <!--ContentPanel - 追加コンテンツをここに入力します-->
        <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
            <TextBox Height="178" HorizontalAlignment="Left" Margin="-4,21,0,0" Name="StatusTextBox" Text="" VerticalAlignment="Top" Width="460" />
            <Button Content="ツイート" Height="72" HorizontalAlignment="Left" Margin="28,223,0,0" Name="PostButton" VerticalAlignment="Top" Width="404" Click="PostButton_Click" />
        </Grid>
    </Grid>
</phone:PhoneApplicationPage>

恒例のセンス0画面です。あきらめます

・Twitterへのリクエスト
Twitterのツイートのリクエストにはstatuses/updateを使います。api.twitter.com/1/statuses/updateですね
先ほど作ったと思うTwitterConnectionCSの中のPostRequestを使います。GETじゃないですよ

using System;
using System.Windows;
using System.Xml.Linq;
using Codeplex.OAuth;

namespace TestPhoneApp1
{
    public class TwitterRequests
    {
        TwitterConnectionCS twCon = new TwitterConnectionCS();

        public void StatusUpdate(string postData,UInt64 in_reply_to)
        {
            var param = new ParameterCollection();
            param.Add("status",postData);
            if (in_reply_to != 0) 
            {
                param.Add("in_reply_to_status_id", in_reply_to);
            }
            twCon.PostRequest("https://api.twitter.com/1/statuses/update.xml", 
                                param,
                                s => var xelmnt = XElement.Load(s.GetResponseStream()), 
                                x => MessageBox.Show(x.Message));
        }
    }
}

in_reply_toにも対応してます。が今回はツイートするだけが目的なので使いません。
すでにバグが一つありますが気にしないでください。後で解説します

・MainPageのコードビハインドその1
MVVMな方は避けて通ります
(というかxamlもあれじゃだめな気がした)

まず認証情報が必要です。認証しないと残念ながらツイートできなくて終わりません。頑張ってください
必要なのはConsumerKey、ConsumerSecret、AccessToken、AccessTokenSecretの4つです。dev.twitter.comで全部取得できますのでここらで用意しときましょう。AccessToken周りは今回だけ使います。先にPage3の内容やってからやってもいいです

認証情報は真っ先に設定する必要があるのでMainPage_Loadedメソッドに追加します。
またボタンのクリックイベントのハンドラにも先ほどのTwitterRequests.StatusUpdateを呼び出すコードを追加します。次の二つのメソッドみたいにします

private TwitterRequests twReqs = new TwitterRequests();
private void PostButton_Click(object sender, RoutedEventArgs e)
{
    twReqs.StatusUpdate(StatusTextBox.Text, 0);   
}

private void PhoneApplicationPage_Loaded(object sender, RoutedEventArgs e)
{
    TwitterInfoCS.ConsumerKey = "{Your ConsumerKey}";
    TwitterInfoCS.ConsumerSecret = "{Your ConsumerSecret}";
    TwitterInfoCS.OAuthToken = "{Your AccessToken}";
    TwitterInfoCS.OAuthTokenSecret = "{Your AccessTokenSecret}";
}

・XElementの処理
実はこの時点でちゃんと設定してあれば投稿自体はできます。できますが外では確認ができませんので通知しましょう。

今回は上の画像みたいな形を目指します。

先ほどTwitterRequestsですでにXElementを作成しましたのでこのXElementを処理します。XElementはあくまでXmlなのでノード名を入れれば要素が取得できます。なので簡単にメッセージを作ると次のような感じです

using System;
using System.Windows;
using System.Xml.Linq;

namespace TestPhoneApp1
{
    /// <summary>
    /// Twitterからのレスポンスを処理します
    /// </summary>
    public class TwitterProcessing
    {
        /// <summary>
        /// StatusUpdateのレスポンスを処理します
        /// </summary>
        public static void StatusUpdateCallback(XElement xelm)
        {
            string resData = "投稿しました:'" + xelm.Element("text").Value +"'" + Environment.NewLine;
            resData += "時刻:" + xelm.Element("created_at").Value;
            MessageBox.Show(resData);
        }
    }
}

このときTwitterRequests.StatusUpdateの中のs => var xelmnt = XElement.Load(s.GetResponseStream()), の部分を次のようにしておきます

s => TwitterProcessing.StatusUpdateCallback(XElement.Load(s.GetResponseStream())), 

では一回実行してみましょう

・非同期処理とDispatcher
「姫様!もうすぐ!アプリケーションが閉じそうです!」

というわけでまともに私と同じに書いてたら上のようなダイアログが出てクラッシュしてしまいます。これが非同期処理の難しい点でUIにはメインスレッド以外触ることができないという制約です。
つまり先ほどのMessageBox.Showは非同期スレッドから呼び出したためクロススレッドアクセスとなり実行できません
ちなみにPostRequestでObserveOnDispatcherを使うとたぶん今度はXElementが読めなくなります。どちらかというとXElementが読めないほうが厳しいです

えっじゃあ最初のTwitterRequests.StatusUpdateのMessageBox.Showもだめじゃないんです?っていうと実はそうです。

詰んだ! って思ってもまだ私たちにはDispatcherがあります!Dispatcherは特定のスレッドにアクセスできるためこれを使ってメインのスレッドにアクセスします。つまるところDispatcher.BeginInvokeでMessageBox.Showを包んでやればいいわけです。とても簡単ですね
じゃあどのスレッドかというとMainPageしかないのでMainPage.csにもうちょっと追加します

・MainPageのコードビハインドその2
またやってきました。まあやることは決まっていますのでちゃちゃっと片付けましょう

ところでMessageBox.Showは共有メソッドなので共有メソッドで呼びたいところですがするとDispatcherにアクセスできません。ですがそれを解決するのがイベントです(そんな使い方するものじゃないと思いますけど)
public static eventを定義してそれのハンドラを非共有メソッドにするということをします。別にここはMessageBoxが表示できればなんでもいいです

というわけで次のようなコードをさらに追加します

public static event NotifyDelegate MessageBoxNotify;

public delegate void NotifyDelegate(string msg);

public static void MessageNotify(string msg)
{
    MessageBoxNotify(msg);
}

引数がほしいのでデリゲートを定義してイベントにあてています。stringのみで十分かなと思います
またMainPage以外からイベントが呼べないのかわかりませんけど呼び方知らないので同じコードビハインド内でコールするメソッドを書いておきます

次にMainPage_Loadedメソッドに次の1行を追加します

MessageBoxNotify += _ => Dispatcher.BeginInvoke(() => MessageBox.Show(_));

MessageBox.Showをイベントに関連付けています。ないと動かないからです

というわけでMessageBox.ShowをMainPage.MessageNotifyに書き換えて実行しましょう。するときっと先ほどの例外もでなくなるはずです

ツイートのTextBoxのIsEnabledをいじったりとかはまたほかに書かなくてはいけないですが今回はすでに条件達成していますので(PostRequestの例外ハンドラが少し雑ですが)とりあえずここまでにしておきます。下手に書いてるとプログレスバーとかまで書くようなのでその辺は自分なりに書いてみるといいかもです

次はいよいよ認証です。ですがReactiveOAuthを使えば苦労はほとんどしないでいけるはずです

Read Full Post »

・Page 0 : ReactiveOAuthを使う前に -WP7のアプリとLINQ,Rx- (https://fantasticswallow.wordpress.com/2012/07/12/dev-twitter-client-for-wp7-page0-whats-linq-and-rx/)
・Page 1 : ReactiveOAuthを使ってみる -Rxと通信層の作成- (この記事)
・Page 2 : ツイートしてみる -ReactiveOAuthを用いたTwitterとの通信- (https://fantasticswallow.wordpress.com/2012/07/14/dev-twitter-client-for-wp7-page2-how-to-tweet-for-twitter/)
・Page 3 : Twitterに認証する -PIN Flowを用いた認証- (???)
・Page 4 : タイムラインを表示する -GETリクエストとXAMLの編集- (???)
・おまけ : Server Side Flowを用いたTwitterへの認証とその罠 (???)

注意点
・この記事はプログラミング初心者に向けたものではありません。C#|VB.NETがいくらかわかる前提で進めます。解説はしません
・Silverlight|WPFの開発経験が少しでもあるとわかりやすいです
・LINQが少しでもわかるととてもよいです
・Twitter APIなどについてはほとんど解説しません。あるがままに扱われます。その辺は適当にBing先生にでも
・コンシューマーキーの取得は済んでいるものとします
・WP7.5の開発環境がすでにあるものとします
・Twitterアカウントを持っているものとします

というわけで連投になるというより1に0を入れようとしたら長すぎて飽きてやめるという現象

さてではやっとこ本題に入りましょう

・ReactiveOAuthとは
ReactiveOAuthはneuecc大先生によるWPF(WinForms),Silverlight,WP7に対応した通信部分をRxを利用して書いたTwitterライブラリの一つです
もちろんそれ以外の通信にも使用できる素晴らしい代物だったりします
特にWP7ではライブラリが少ないのでこの手のライブラリはとても便利です

Ms-PLライセンスなのでGPL汚染などを気にせず使えます。
ダウンロードなどなどはhttp://reactiveoauth.codeplex.com/からできます。
書いてる当時は0.4.0.0が最新な気がしますのでそれを使って書いていきます。挫折しないように頑張ります

・ReactiveOAuthを使うために
ダウンロードしてきた前提で行きます
まずはプロジェクトを用意しましょう。TestPhoneApp1って今回はつけて作ります(ご自由にどうぞ)
次にこれに次の参照を追加します
・Microsoft.Phone.Reactive
・System.Observable
・(ReactiveOAuthを解凍したフォルダ/)Binary/ReactiveOAuth.WP7.dll

追加方法が分からないといわれてもどうしようもないのでそこは頑張ってください
Microsoft.Phone.Reactiveは標準で用意されています。参照の追加を押してフレームワークの部分にいますので探して追加してください

・Twitterの基本の情報を入れるクラスの作成
Twitterの基本の情報ってなんだー っていうととりあえずコンシューマーキーやアクセストークンなどがあります。
これらがないとツイートも何もできませんのでその情報を入れるだけのクラスを作りましょう
static(Shared)で宣言しちゃってもここは問題ないと思います。逆にインスタンスを複数生成できると複数アカウント対応できるかもしれませんがそこはまた別の話

Public Class TwitterInfoVB
    Public Shared ConsumerKey As String
    Public Shared ConsumerSecret As String

    Public Shared OAuthToken As String
    Public Shared OAuthTokenSecret As String
    Public Shared OAuthScreenName As String
    Public Shared OAuthUserId As ULong

    Public Shared TwitterUserAgent As String
    Public Shared ReadOnly Property IsNotAuthed As Boolean
        Get
            Return String.IsNullOrEmpty(OAuthToken) OrElse String.IsNullOrEmpty(OAuthTokenSecret)
        End Get
    End Property
End Class
public class TwitterInfoCS
{
    public static string ConsumerKey { get; set; }
    public static string ConsumerSecret { get; set; }

    public static string OAuthToken { get; set; }
    public static string OAuthTokenSecret { get; set; }
    public static string OAuthScreenName { get; set; }
    public static UInt64 OAuthUserId { get; set;}
    public static string TwitterUserAgent { get; set; }

    public static bool IsNotAuthed { get {
        return string.IsNullOrEmpty(OAuthToken) || string.IsNullOrEmpty(OAuthTokenSecret);
    }}
}

 今回の情報用のクラス(上VB.NET、下C#)
なぜかフィールドです。理由はよくわかりません
コメントは今回は省きました。名前からたぶん判断できると思います
え? 名前の付け方がひどい? それは申し訳ない

・通信する際に呼び出すメソッドの作成
続いてReactiveOAuthのメソッドを用いてレスポンスを取得するラッパーメソッドを作ります。この辺は公式にサンプルがあるのでちょっと細工してそのまま使っちゃいましょう(ぉ

Public Sub GetRequestText(url As String, param As ParameterCollection, argOperate As Action(Of String), exHandler As Action(Of Exception))
    If TwitterInfoVB.GetInstance.IsNotAuthed Then
         exHandler(New Exception("Twitterに認証されていません、認証してからもう一度やり直してください"))
    Else
         Dim _oauthClient = New OAuthClient(TwitterInfoVB.ConsumerKey, TwitterInfoVB.ConsumerSecret, TwitterInfoVB.OauthToken, TwitterInfoVB.OauthTokenSecret) With {.Url = url, .Parameters = param, .ApplyBeforeRequest = Sub(req) req.UserAgent = TwitterInfoVB.TwitterUserAgent}
        _oauthClient.GetResponseText().Subscribe(Sub(s) argOperate(s), Sub(x) exHandler(x))
    End If
End Sub
using Microsoft.Phone.Reactive;
using Codeplex.OAuth;
~~~~~
public delegate void resultTextCollection(string resTxt);
public delegate void resultResponseCollection(WebResponse res);
public delegate void exceptionHandler(Exception ex);

public void GetRequestText(string url,ParameterCollection param,resultTextCollection argOperate,exceptionHandler exHandler)
{
	if (TwitterInfo.GetInstance.IsNotAuthed)
	{
		exHandler(new Exception("Twitterに認証されていません、認証してからもう一度やり直してください"));
	}
	else
	{
		var oauthClient = new OAuthClient(TwitterInfoCS.ConsumerKey, TwitterInfoCS.ConsumerSecret, TwitterInfoCS.OAuthToken, TwitterInfoCS.OAuthTokenSecret)
		{
			Url = url,
			Parameters = param,
			ApplyBeforeRequest = req => { req.UserAgent = TwitterInfoCS.TwitterUserAgent; },
		};
		oauthClient.GetResponseText()
			.Subscribe(s => argOperate(s), x => exHandler(x));
	}
}

  GETリクエストで文字列を取得する(上VB.NET 下C#)
こんな感じです。Twitterに認証していない場合はその時点ではじくようにしています。たぶんないとは思いますが認証しないで通信したい場合は削除してください
(認証していない場合はすべて排除することで無駄なリクエストを避ける)

Subscribeとかはなんだー というのはRxについて調べるとわかります
Subscribe内でデリゲートを実行している理由は非同期でReturnする必要あるのかなとかそもそもちゃんと帰ってくるのかとかとりあえずReturnしたら非同期じゃないんじゃないかとかいろいろあったのでこのようにしました。
その辺は詳しいお方がたぶんいるので聞いてくださるといいと思いますよ

とりあえず何してるのかまったくわからない! って方はargOperateのデリゲートの引数に取得したレスポンス渡すからデリゲート内で処理してねって感じです。exHandlerは例外時に実行されるデリゲートとなります

まあなんか結局呼び出すメソッドとコールバックのメソッドが必要となってしまったのでとても手間が大きくなりますがそれでもBegin/Endペアでネスト関数しまくるよりはいいかなと思います(論点の転換)

こんな感じのを5種類用意するととてもはかどります(3種類でたぶん足りる)
・GetRequest(Text)
・PostRequest(Text)
・GetRequestStream
???ってなると思いますがとりあえずGETリクエストをするメソッド、POSTリクエストをするメソッド、ストリーミング用のメソッドの3つです。リクエストを引数で渡せば2つですがわかりやすく3つにしてみましょう。
コードはC#のみ置いときます。VBはメソッドチェーン長すぎてわけわかりません

using Microsoft.Phone.Reactive;
using Codeplex.OAuth;
~~~~~
public delegate void resultTextCollection(string resTxt);
public delegate void resultResponseCollection(WebResponse res);
public delegate void exceptionHandler(Exception ex);

/// <summary>
/// GETリクエストを行います
/// </summary>
/// <param name="url">通信先のurl</param>
/// <param name="param">ヘッダに追加する引数</param>
/// <param name="argOperate">レスポンスを処理するデリゲート</param>
/// <param name="exHandler">例外を処理するデリゲート</param>
public void GetRequest(string url, ParameterCollection param, resultResponseCollection argOperate,exceptionHandler exHandler)
{
	if (TwitterInfoCS.IsNotAuthed)
	{
		exHandler(new Exception("Twitterに認証されていません、認証してからもう一度やり直してください"));
	}
	else
	{
		var oauthClient = new OAuthClient(TwitterInfoCS.ConsumerKey, TwitterInfoCS.ConsumerSecret, TwitterInfoCS.OAuthToken, TwitterInfoCS.OAuthTokenSecret)
		{
			Url = url,
			Parameters = param,
			ApplyBeforeRequest = req => { req.UserAgent = TwitterInfoCS.TwitterUserAgent; },
		};
		oauthClient.GetResponse()
			.Subscribe(s => argOperate(s), x => exHandler(x));
	}
}

private IDisposable streamOAuthClient;

/// <summary>
/// StreamingAPIへの通信を行います
/// </summary>
/// <param name="url">通信先のurl</param>
/// <param name="param">ヘッダに追加する引数</param>
/// <param name="argOperate">レスポンスを処理するデリゲート</param>
/// <param name="exHandler">例外を処理するデリゲート</param>
public void GetRequestStream(string url, ParameterCollection param, resultTextCollection argOperate, exceptionHandler exHandler)
{
	if (TwitterInfoCS.IsNotAuthed)
	{
		exHandler(new Exception("Twitterに認証されていません、認証してからもう一度やり直してください"));
	}
	else
	{
		streamOAuthClient  = new OAuthClient(TwitterInfoCS.ConsumerKey, TwitterInfoCS.ConsumerSecret, TwitterInfoCS.OAuthToken, TwitterInfoCS.OAuthTokenSecret)
		{
			Url = url,
			Parameters = param,
			ApplyBeforeRequest = req => { req.UserAgent = TwitterInfoCS.TwitterUserAgent; },
		}.GetResponseLines()
			.Subscribe(s => argOperate(s), x => exHandler(x));
	}
}

/// <summary>
/// Streaming通信を終了します
/// </summary>
public void StreamClientDispose()
{
	streamOAuthClient.Dispose();
}

/// <summary>
/// POSTリクエストを行います
/// </summary>
/// <param name="url">通信先のurl</param>
/// <param name="param">ヘッダに追加する引数</param>
/// <param name="argOperate">レスポンスを処理するデリゲート</param>
/// <param name="exHandler">例外を処理するデリゲート</param>
public void PostRequest(string url, ParameterCollection param, resultResponseCollection argOperate, exceptionHandler exHandler)
{
	if (TwitterInfoCS.IsNotAuthed)
	{
		exHandler(new Exception("Twitterに認証されていません、認証してからもう一度やり直してください"));
	}
	else
	{
		var oauthClient = new OAuthClient(TwitterInfoCS.ConsumerKey, TwitterInfoCS.ConsumerSecret, TwitterInfoCS.OAuthToken, TwitterInfoCS.OAuthTokenSecret)
		{
			Url = url,
			MethodType = MethodType.Post,
			Parameters = param,
			ApplyBeforeRequest = req => { req.UserAgent = TwitterInfoCS.TwitterUserAgent; },
		}.GetResponse()
		.Subscribe(s => argOperate(s), x => exHandler(x));
	}
}

今回はWebResponseで扱いたいのでこんな感じにしました。WebExceptionが起きた場合にResponseをそのままデリゲートに入れることができるからです
そんなこんなで一応準備できたので次回はツイートしてみましょう。そこまで難しくはないです

Read Full Post »

・Page 0 : ReactiveOAuthを使う前に -WP7のアプリとLINQ,Rx- (この記事)
・Page 1 : ReactiveOAuthを使ってみる -Rxと通信層の作成- (https://fantasticswallow.wordpress.com/2012/07/12/dev-twitter-client-for-wp7-page1-using-reactive-oauth/)
・Page 2 : ツイートしてみる -ReactiveOAuthを用いたTwitterとの通信- (https://fantasticswallow.wordpress.com/2012/07/14/dev-twitter-client-for-wp7-page2-how-to-tweet-for-twitter/)
・Page 3 : Twitterに認証する -PIN Flowを用いた認証- (???)
・Page 4 : タイムラインを表示する -GETリクエストとXAMLの編集- (???)
・おまけ : Server Side Flowを用いたTwitterへの認証とその罠 (???)

ということですごい今更ながらWP7でTwitterクライアントアプリケーションを作ってみるという記事です。全4回くらい

Windows Phone 8がもうすぐ出るんじゃねって時期になんでWP7なんです?って思うかもしれませんが現在WP7はWP8へのアップデートはできないというように説明されています
(まあ7.0.7004.0期からの端末はあきらめてたよね)
ですが現在売れているのはWP7です、8ではありません。もちろんいち早く開発すれば最初のアプリ群に切り込めるのはあるとおもいますが、もし売るといったこと、もっとアプリを広めたいということを考えるなら明らかに対応端末が多いWP7.5向けで作成することになると思います。
(WP8のアプリはWP7.5で動かないけどWP7.5のアプリはWP8で動くため絶対数が増える)
(日本で2年縛りくらった場合もこれになるんですかね…?)
まあそんなこんなで当面はWP7.5のアプリをメンテナンスすることになっていくと思います。慎重にいくなら半年くらいは待ってもいいかもしれません。たぶんアプリがそんなにないです

前置きはこんくらいでとりあえず本題へ行きましょう

その前に注意点をいくつか
・この記事はプログラミング初心者に向けたものではありません。C#|VB.NETがいくらかわかる前提で進めます。解説はしません
・Silverlight|WPFの開発経験が少しでもあるとわかりやすいです
・LINQが少しでもわかるととてもよいです
・Twitter APIなどについてはほとんど解説しません。あるがままに扱われます。その辺は適当にBing先生にでも
・コンシューマーキーの取得は済んでいるものとします
・WP7.5の開発環境がすでにあるものとします
・Twitterアカウントを持っているものとします

ではゆっくり始めましょう
(この記事は予備知識なので作るうえで必ず理解する必要はありません)

・LINQとReactive Extensions
ライブラリを使う前にLINQとReactive Extensions(以後Rx)について少し説明します。わかる方は飛ばしてください。
また筆者もよくわかってないのでLINQだったりRxだったりでBingったりneuecc大先生のブログなりを見てください。
(LINQです。LinQじゃないです。決してWikipediaなどでLinQの項目をみてこれがLINQかーって思わないでください)

そもそもLINQって何奴 っていうと.NET 3.5の頃に追加された大宇宙の創造を可能にし… …データソースに対する操作をクエリ構文を用いて操作できるようにした技術です
さて、私的にはクエリ構文を用いてあまり書いたりすることって多くなくてえっそれじゃあ何がおいしいのって言われるかもしれませんがこのクエリ構文を書けるようにするために複数の新機能が実装されました。実はたいていの場合そっちのほうが用いられる場合がとても多いです。
(私はクエリ構文書いてハァハァいってる人間よりメソッドベースでメソッドチェーン書いてハァハァ言ってる人の方をよく見かけますがきのせいですかね)
で、何が実装されたかというと型推論、匿名型、拡張メソッド、lambda expressionsなどなどです。他にも知りたい方はVB.NET 9.0かC# 3.0の新機能を調べるといいかもしれません

型推論はコードを書くときに型が明らかであれば入れる必要がない という技術です
たとえばa = 10 と書いてあれば明らかにint32(基本はこれ)であるといえるので宣言時にint32 a = 10;と書かずにvar a = 10;と書くだけで済みます
匿名型は型の参照がないオブジェクトが生成できます。使わないので今回も使いません
拡張メソッドはすでにあるクラスに新しくメソッドを追加することができる技術です。これによりIEnumerableインターフェースに新しくメソッドを追加するということも可能になりました
lambda expressions、ラムダ式はdelegate、Actionに対し直接的な構文を使用して書くことができます
これによりネスト関数はさらに直観的にかけるようになりました。
では型推論、拡張メソッド、ラムダ式を書くとどうなるのか、を見ていきましょう

Private Function getOver1000(target As IEnumerable(Of Integer)) As List(Of Integer)
    Dim _collection As New List(Of Integer)
    For Each x As Integer In target
        If x > 1000 Then _collection.Add(x)
    Next
    Return _collection
End Function
private List<int> getOver1000(IEnumerable<int> target)
{
    List<int> _collection = new List<int>();
    foreach(int x in target)
    {
        if (x > 1000)
        {
            _collection.Add(x);
        }
    }
    return _collection;
}

1000以上の数字のListを取得するメソッドです(上VB,下C#)
(そもそもこんなメソッド使うのかといわれると非常に詰まりますが)

とても簡単なメソッドですがそれでもこれだけの行数を必要とします。そもそも_collection用意するのがとても不恰好ですね
じゃあこれを先ほどの技術を用いるとどうなるのか、試してみましょう

Private Function getOver1000(target As IEnumerable(Of Integer)) As List(Of Integer)
    Return target.Where(Function(x) x > 1000).ToList()
End Function
private List<int> getOver1000(IEnumerable<int> target)
{
    return target.Where(x => x > 1000).ToList();
}

たったの1行で済むようになりました
型推論がなければラムダ式のxはx As Intとでもいれなくてはいけませんがxだけで済みます
WhereとToListはそれぞれ拡張メソッドで提供されます
ラムダ式があることでWhereの処理を非常にきれいに書くことができます

こんなに便利な機能が無償で提供されているのです。使わない手はないですよね!

続いてRxです。RxはLINQに比べると少し新しい技術です。LINQ to Async and Eventsとでも言えるかのようにLINQを非同期とイベントに広げたものです

しかし筆者には説明ぜんぜんできないのでhttp://neue.cc/2010/07/28_269.htmlなどをお読みになってください。たぶん私が書くより1000倍くらいいいと思います

というわけで前置きはここらへんにしておきます。次から実際に作っていきます

Read Full Post »

調べ足りない系記事

Windows Phone 7ではIsolatedStorageSettings [System.IO namespace]をIsolatedStorageSettings.ApplicationSettingsで取得できます。
これのAddでは任意のObjectを追加することができますが可能なのは第1階層のObjectまでです。
つまりData1というクラスのObjectは追加可能ですがData2は不可となります

たとえば次のようなクラスを考えてみましょう

Public Class Data1
    Public Property string1 As String = "a"
    Public Property int1 As Integer = 500
End Class

この時、Data1のインスタンスをIsolatedStorageSettingsに追加することは可能です
ですが次のようなコードは不可となります

Public Class Data2
    Public Property dt As New Data1
End Class

Public Class Data1
    Public Property string1 As String = "a"
    Public Property int1 As Integer = 500
End Class

この時、Data1は追加できますがData2は追加できません。なぜならData2は基本的な型ではないからです。

ではData2のようにして保存することは絶対にできないのか というとそういうわけでもありません
実際には書けば分かりますが基本的な型でない場合IsolatedStorageSettingsはDataContractSerializer、またはそれに準ずるものを使用してシリアル化を実行しようとします。
そのためData2を保存すると出る例外はDataContractされていない といった形の内容になります

というわけでさっきのコードは次のようにしてみれば保存できるわけです

Public Class Data2
    Public Property dt As New Data1
End Class

<DataContract()>
Public Class Data1
    <DataMember(name:="string1")> Public Property string1 As String = "a"
    <DataMember(name:="int1")> Public Property int1 As Integer = 500
End Class

(Data2の方にDataContractする必要があるかはわかりません)

ところでDataContractの影響なのか不明なんですけど既定値が無視されてしまうのはどうしたらいいんでしょう。保存されていないメンバーが強制的にnullという状態になったりしてその辺も調べていきたいところ

とりあえずこんくらいで

Read Full Post »