だるろぐ

明日できることは、今日しない。

WebMatrix 3: @ でハマる(解決編

f:id:daruyanagi:20130929122557p:plain

WebMatrix 3: @ でハマる - だるろぐ の続き。

というアドバイスをもらった。あ、たぶんそれだ。というわけで書き直した。

旧バージョン(Logger.cshtml)

#App_Code/Logger.cshtml

@helper Write(string message)
{
    System.IO.File.AppendAllText(
        Server.MapPath("~/log.txt"),
        string.Format("{0}:\t{1}\r\n", DateTime.Now, message)
    );
}

これって実は public static HelperResult Write(string message) になるんだよね。それにしてもなぜ AppendAllText() が実行されないのかは謎だけど、HTML が含まれていない(出力がない)ならば処理を飛ばしてしまう最適化なんかがあるのかもしれない。とりあえず“Page に対してなんら出力のないヘルパー”という使い方は NG ってことかな。

新バージョン(Logger.cs)

f:id:daruyanagi:20131001075835p:plain

#App_Code/Logger.cs

using System;

public static class Logger
{
    public static void Write(string message)
    {
        System.IO.File.AppendAllText(
            System.Web.Hosting.HostingEnvironment.MapPath("~/log.txt"),
            string.Format("{0}:\t{1}\r\n", DateTime.Now, message)
        );
    }
}

CSHTML → CS にして、コードを Razor ではなく C# で書き直す。“HostingEnvironment.MapPath(String) Method (System.Web.Hosting) | Microsoft Docs”は最近覚えたのだけど、Server.MapPath() が使えない場面でも動く(のだと期待している)。

f:id:daruyanagi:20131001080145p:plain

結果は――期待通りログが出力される。けれど、Default.cshtml も少し書き直さなければならない。

旧バージョン(Default.cshtml)

@{
     @Logger.Write("冒頭のコードブロック内で記述");
}

<!DOCTYPE html>

<html lang="ja">
    <head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
        <meta charset="utf-8" />
        <title>マイ サイトのタイトル</title>
        <link href="~/favicon.ico" rel="shortcut icon" type="image/x-icon" />
    </head>
    <body>
        @Logger.Write("Body 内で記述")
    </body>
</html>

新バージョン(Default.cshtml)

@{
     Logger.Write("冒頭のコードブロック内で記述");
}

<!DOCTYPE html>

<html lang="ja">
    <head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
        <meta charset="utf-8" />
        <title>マイ サイトのタイトル</title>
        <link href="~/favicon.ico" rel="shortcut icon" type="image/x-icon" />
    </head>
    <body>
        @{ Logger.Write("Body 内で記述"); }
    </body>
</html>

Logger.Write が HelperResult を返さなくなったことにより(ヘルパーメソッドとしてシグネチャーが合わなくなったので)、@Logger.Write() という書き方はできなくなり、コードブロックで囲まなければならなくなった。けど、これが正しい。

そういえば

をあんまり理解していないことを思い出したので、これも今度解決しておくことにする。

追記

この功を表して、しばやんにはもう一枚 CD を贈ることにした。

WebMatrix 3: @ でハマる

ちょいとログを外部ファイルに吐きたくなって、テキトーにこんなコードを書いてみた。

f:id:daruyanagi:20130929121802p:plain

#App_Code/Logger.cshtml

@helper Write(string message)
{
    System.IO.File.AppendAllText(
        Server.MapPath("~/log.txt"),
        string.Format("{0}:\t{1}\r\n", DateTime.Now, message)
    );
}

これを Default.cshtml でテストしてみた。

f:id:daruyanagi:20130929122015p:plain

@{
     Logger.Write("冒頭のコードブロック内で記述");
}

<!DOCTYPE html>

<html lang="ja">
    <head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
        <meta charset="utf-8" />
        <title>マイ サイトのタイトル</title>
        <link href="~/favicon.ico" rel="shortcut icon" type="image/x-icon" />
    </head>
    <body>
        @Logger.Write("Body 内で記述")
    </body>
</html>

結果はというと――

f:id:daruyanagi:20130929122031p:plain

――冒頭のコードブロック内で記述したログは記録されない。「あれ、なんでだろう?」と思って、あちこちごちゃごちゃ弄ってみたのだけど、正解はコレだった。

@{
     @Logger.Write("冒頭のコードブロック内で記述");
}

f:id:daruyanagi:20130929122557p:plain

Logger.Write() のまえに @ を足せば実行される。

@{
     @(
         Logger.Write("冒頭のコードブロック内で記述")
     )
}

でもいいのだけど。

しっかし、これ、なぜなんだろう。自分はまだまだ Razor がわかってないな。

WebMatrix 3: Twitter でログインしてアクセストークン(秘)を取得する

WebMatrix 3: Twitter でログインする - だるろぐ でめでたく Twitter でのログインが実現できたのだけど、実はひとつ問題があった。

f:id:daruyanagi:20130905064941p:plain

AccessTokenSecret が取れない。

自分もあんまりよくわかっていないのだけど、Twitter の API を利用するには以下の情報が必要であるみたい。

f:id:daruyanagi:20130911233354p:plain

まず、これ。アプリが Twitter へアクセスするために必要。

  • ConsumerKey
  • ConsumerSecret

次に、これ。ユーザーに成り代わって Twitter の API を使うために必要。

  • AccessKey
  • AccessKeySecret

アプリの登録画面で取得できる AccessKey/AccessKeySecret はアプリを登録したユーザーのアクセスキー。このアプリにログインしたユーザーとして API を利用するには、そのユーザーに対して発行される AccessKey/AccessKeySecret が必要だ。

でも、OAuthWebSecurity では ExtraData から AccessKey をもらうことはできても、AccessKeySecret まではくれないみたい。

f:id:daruyanagi:20130912000225p:plain

通信を Fiddler でみてみた。ちゃんと authorize したあとに access_token している(ここで AccessKey がもらえる)から、ついでに AccessKeySecret もとってきてくれてもよさそうなのだけど。なにか理由があるのかもしれないが、これではちょっと困る。

これを解決するには、Twitter プロバイダーを自分で実装すればよいようだ。

// ~/App_Code/TwitterClient.cs

using DotNetOpenAuth.AspNet;
using DotNetOpenAuth.AspNet.Clients;
using DotNetOpenAuth.Messaging;
using DotNetOpenAuth.OAuth;
using DotNetOpenAuth.OAuth.ChannelElements;
using DotNetOpenAuth.OAuth.Messages;
using System.Collections.Generic;

// http://stackoverflow.com/questions/12198734/getting-twitter-access-secret-using-dotnetopenauth-in-mvc4

public class TwitterClient : OAuthClient
{
    /// <summary>
    /// The description of Twitter's OAuth protocol URIs for use with their "Sign in with Twitter" feature.
    /// </summary>
    public static readonly ServiceProviderDescription TwitterServiceDescription = new ServiceProviderDescription
    {
        RequestTokenEndpoint =
            new MessageReceivingEndpoint(
                "https://api.twitter.com/oauth/request_token",
                HttpDeliveryMethods.GetRequest | HttpDeliveryMethods.AuthorizationHeaderRequest),
        UserAuthorizationEndpoint =
            new MessageReceivingEndpoint(
                "https://api.twitter.com/oauth/authenticate",
                HttpDeliveryMethods.GetRequest | HttpDeliveryMethods.AuthorizationHeaderRequest),
        AccessTokenEndpoint =
            new MessageReceivingEndpoint(
                "https://api.twitter.com/oauth/access_token",
                HttpDeliveryMethods.GetRequest | HttpDeliveryMethods.AuthorizationHeaderRequest),
        TamperProtectionElements = new ITamperProtectionChannelBindingElement[] { new HmacSha1SigningBindingElement() },
    };

    public TwitterClient(string consumerKey, string consumerSecret) :
        base("twitter", TwitterServiceDescription, consumerKey, consumerSecret) { }
    protected override AuthenticationResult VerifyAuthenticationCore(AuthorizedTokenResponse response)
    {
        string accessToken = response.AccessToken;
        string accessSecret = (response as ITokenSecretContainingMessage).TokenSecret;
        string userId = response.ExtraData["user_id"];
        string userName = response.ExtraData["screen_name"];

        var extraData = new Dictionary<string, string>()
                            {
                                {"accesstoken", accessToken},
                                {"accesssecret", accessSecret}
                            };
        return new AuthenticationResult(
            isSuccessful: true,
            provider: ProviderName,
            providerUserId: userId,
            userName: userName,
            extraData: extraData);
    }
}

結果はこんな感じ。いつもどおり ObjectInfo.Print() で中身を見てみたよ。

f:id:daruyanagi:20130912115257p:plain

実装としてどうするのが理想的なのかはよくわからないけれど、とりあえずユーザーを管理するテーブルを拡張して、アクセスキーを保管しておくのとかどうでしょうか。

@{
    var returnUrl = Request["returnUrl"];

    // ログインの検証
    var result = OAuthWebSecurity.VerifyAuthentication(  
         Href("LogonCallBack", new { ReturnUrl = returnUrl })
    );

    if (result.IsSuccessful)
    {
        // ログインが成功すると、
        // - provider: twitter
        // - ProviderUserId: twitter の ID
        // - UserName: twitter のスクリーンネーム
        // の3つが得られる。自動補完が効かないので変数に入れとく
        var provider = result.Provider;
        var providerUserId = result.ProviderUserId;
        var userName = result.UserName;
        var accessToken = result.ExtraData["accesstoken"];
        var accessTokenSecret = result.ExtraData["accesssecret"];

        <p>@ObjectInfo.Print(result.ExtraData)</p>

        // ユーザー名が Users テーブルに存在しない場合、
        // あらかじめユーザー名を追加しておく。
        // でないと CreateOrUpdateAccount() でコケる
        using (var db = Database.Open("kenzou-memo"))
        {
            const string SELECT = "SELECT * FROM USERS WHERE Name=@0";
            const string INSERT = "INSERT INTO Users (Name, AccessToken, AccessTokenSecret) VALUES (@0, @1, @2)";
            const string UPDATE = "UPDATE Users SET AccessToken=@1, AccessTokenSecret=@2 WHERE Name=@0";

            if (db.QuerySingle(SELECT, userName) == null) // この処理を追加してみました
            {
                db.Execute(INSERT, userName, accessToken, accessTokenSecret);
            }
            else
            {
                db.Execute(UPDATE, userName, accessToken, accessTokenSecret);
            }
        }

        // CreateOrUpdate とか言ってるけど、
        // やってることは Users テーブルと内部管理テーブルの紐づけ
        OAuthWebSecurity.CreateOrUpdateAccount(
            provider,
            providerUserId,
            userName);

        // ログインチケットの発行
        OAuthWebSecurity.Login(
            provider,
            providerUserId,
            createPersistentCookie: true);

        Response.Redirect(returnUrl);        
    }
    else
    {
        // ログインに失敗したときの処理
    }
}

自分でプロバイダーを実装するのはそこはかとなくめんどくさいけれど、丸コピで動くのでまぁ、よし。プロバイダーをどうやって実装するのかも少し分かったし。練習として、ほかのサービスを実装してみるのもよいかもしれない。最近なんかだと GitHub なんかが需要ありそうだ。


WebMatrix 3: Twitter ライブラリ Tweetinvi API でツイートしてみる

f:id:daruyanagi:20130907053932p:plain

たまたま Tweetinvi a friendly Twitter C# API - Home というのをみかけたのだけど、割と便利だった。

@{
    var token = new TwitterToken.Token(
        "*****************Pj5ecAhlw3SqBPU5qHtBUSiTQDcgmUzBU", 
        "*****************RZSPVglcM0TfE7mPCFzm334rw", 
        "*****************cSizQ", 
        "*****************zws06agyxRXImPk9sfETNQeg");

    if (IsPost)
    {
        var tweet = new Tweetinvi.Tweet("てすてす");
        tweet.Publish(token);
    }
}

<!DOCTYPE html>

<html lang="ja">
    <head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
        <meta charset="utf-8" />
        <title>マイ サイトのタイトル</title>
        <link href="~/favicon.ico" rel="shortcut icon" type="image/x-icon" />
    </head>
    <body>
        <ul>
        @foreach (var tweet in new Tweetinvi.User("daruyanagi", token).GetUserTimeline())
        {
            <li>@tweet.Text</li>
        }
        </ul>

        <form action="" method="post">
            <input type="submit" />
        </form>
    </body>
</html>

f:id:daruyanagi:20130907054421p:plain

GET でタイムラインを表示(なんかゴミ(\)が入ってるけど、バグかな。あとで報告しよう)。POST でツイート。

Twitter 系のライブラリってピンキリだけど、これは UserStreams も扱えるようで、悪くない感じかな。デスクトップアプリにも組み込める、というか、PIN の認証はあるけど Web アプリの認証は今のところ未実装みたいなので、どっちかっていうと今のところデスクトップアプリ向けって感じ。

不具合、直していただきました

レスポンス、ちょっぱや! 対応の早いところも推せますね、このライブラリ。ちなみに String.CleanString() という処理が入っているのが原因でした。

If you want to store the Text in a database you can still use the extension method :
String.CleanString() before the INSERT / UPDATE call.

String.CleanString() は String の拡張メソッドで、サニタイズだかエスケープだかをするのかな? MySQL はぜんぜん知らない。

/// <summary>
/// Clean a string so that it can be used in a URL and
/// sent to Twitter
/// </summary>
/// <param name="s">String to clean</param>
/// <returns>Cleaned string</returns>
public static string CleanString(this string s)
{
    return s != null ? (s.HTMLDecode().MySQLClean().ReplaceNonPrintableCharacters('\\')) : null;
}

WebMatrix 3: Twitter でログインする

f:id:daruyanagi:20130905061149p:plain

ちょっと Twitter と連携するアプリを作ってみたかったのだけど、“スターター サイト”テンプレートを使った例以外はあまり載っていなかったので、今回はそれを使わずに、“空のサイト”テンプレートから作ってみるよ。というのも、ググってたら自分のサイトが検索に引っかかって、WebMatrix 2:OAuth でログインする(2) - だるろぐ を放置することすでに半年経つことが判明したので……さすがにこの当時のことはあまりよく思い出せないのだけど、今回の記事がフォローアップのようなものになれば幸い。

下準備

f:id:daruyanagi:20130905063705p:plain

まず NuGet で NuGet Gallery | Microsoft.AspNet.WebPages.OAuth 3.2.7 をインストール。これで“スターター サイト”テンプレートでも使われている OAuthWebSecurity Class (Microsoft.Web.WebPages.OAuth) | Microsoft Docs が使えるようになる。

f:id:daruyanagi:20130905062911p:plain

次に https://dev.twitter.com/apps/ でアプリの登録を行っておく。登録祭の必須入力事項は以下のとおり。

  • Name: アプリの名前
  • Description: title
  • Website: http://127.0.0.1:****/ (localhost は無効な URL として蹴られる)
  • Callback URL: http://127.0.0.1:****/ (空っぽだと動かないっぽい)
  • Allow this application to be used to Sign in with Twitter: 無効化

アプリを登録したら、

  • Consumer key
  • Consumer secret

を取得し、~/_AppStart.cshtml で初期化を行う。

@{
    // DB名: kenzo-memo.sdf
    WebSecurity.InitializeDatabaseConnection("kenzou-memo", "Users", "UserId", "Name", true);  

    OAuthWebSecurity.RegisterTwitterClient(
        "kFe1j**********LTcSizQ",
        "aKz6C**********Qzws06agyxRXImPk9sfETNQeg"
    );
}

これで OAuthWebSecurity の twitter プロバイダーが利用できるようになる。このプロバイダーは OAuth 認証に必要な面倒事の一切を引き受けてくれる。

~/Default.cshtml

f:id:daruyanagi:20130905061617p:plain

ログインしていない場合はログインボタンを、ログインしている場合はログオフのリンクを表示するだけの簡単なコード。

<!DOCTYPE html>

<html lang="en">
    <head>
        <meta charset="utf-8" />
        <title></title>
    </head>
    <body>
    @if (User.Identity.IsAuthenticated)
    {
        <p>@User.Identity.Name</p>
        <p><a href="Logout.cshtml">Logout</a></p>
    }
    else
    {
        <a href="~/Login.cshtml">
            <img src="Twitter ボタンを用意する" />
        </a>
    }
    </body>
</html>

資格関連の情報は User.Identity (HttpContext.User Property (System.Web) | Microsoft Docs)でとれる。

~/Login.cshtml

@{
    var returnUrl = Request.UrlReferrer.ToString();

    OAuthWebSecurity.RequestAuthentication(
        "twitter", // プロバイダー名は文字列で指定
        Href("~/LoginCallback", new { returnUrl })
    );
}

OAuthWebSecurity.RequestAuthentication で Twitter へリクエストを投げる。

f:id:daruyanagi:20130905064446p:plain

おなじみのこの画面へ飛ばされる。

~/LoginCallback

Twitter で認証処理を行うと、~/LoginCallback にリダイレクトされてくるので、OAuthWebSecurity.VerifyAuthentication で検証し、成否を得る(今回で言えば result 変数)。Twitter でのログインが成功していたら、その資格情報(OAuth なのでパスワードはないけど)をデータベースに格納し(OAuthWebSecurity.CreateOrUpdateAccount)、アプリにログインさせる(OAuthWebSecurity.Login)。

@{
    var returnUrl = Request["returnUrl"];

    // ログインの検証
    var result = OAuthWebSecurity.VerifyAuthentication(  
         Href("LogonCallBack", new { ReturnUrl = returnUrl })
    );

    if (result.IsSuccessful)
    {
        // ログインが成功すると、
        // - provider: twitter
        // - ProviderUserId: twitter の ID
        // - UserName: twitter のスクリーンネーム
        // の3つが得られる。自動補完が効かないので変数に入れとく
        var provider = result.Provider;
        var providerUserId = result.ProviderUserId;
        var userName = result.UserName;

        // ユーザー名が Users テーブルに存在しない場合、
        // あらかじめユーザー名を追加しておく。
        // でないと CreateOrUpdateAccount() でコケる
        using (var db = Database.Open("kenzou-memo"))
        {
            const string SELECT = "SELECT * FROM USERS WHERE Name=@0";
            const string INSERT = "INSERT INTO Users (Name) VALUES (@0)";

            if (db.QuerySingle(SELECT, userName) == null)
            {
                db.Execute(INSERT, userName);
            }
        }

        // CreateOrUpdate とか言ってるけど、
        // やってることは Users テーブルと内部管理テーブルの紐づけ
        OAuthWebSecurity.CreateOrUpdateAccount(
            provider,
            providerUserId,
            userName);

        // ログインチケットの発行
        OAuthWebSecurity.Login(
            provider,
            providerUserId,
            createPersistentCookie: true);

        Response.Redirect(returnUrl);        
    }
    else
    {
        // ログインに失敗したときの処理
    }
}

f:id:daruyanagi:20130905064840p:plain

f:id:daruyanagi:20130905064842p:plain

成功するとこんなかんじにユーザーがデータベースに追加される。ちなみに result の中身はこんな感じ。

f:id:daruyanagi:20130905064941p:plain

ObjectInfo.Print() はデバッグにとっても便利なヘルパーなのでぜひ覚えておいてね!

~/Logout.cshtml

最後にログアウトの処理も書いておく。

@{
    var returnUrl = Request.UrlReferrer.ToString();

    WebSecurity.Logout();

    Response.Redirect(returnUrl);
}

ログインの時はもっぱら OAuthWebSecurity Class (Microsoft.Web.WebPages.OAuth) | Microsoft Docs を使っていたけれど、ログアウトはいつもどおり WebSecurity Class (WebMatrix.WebData) | Microsoft Docs が使える。

まとめ

  1. OAuthWebSecurity.RegisterTwitterClient で twitter プロバイダーを有効化
  2. OAuthWebSecurity.RequestAuthentication で Twitter の認証画面へ飛ばす
  3. OAuthWebSecurity.VerifyAuthentication で Twitter での認証がうまく言ったか確認
  4. OAuthWebSecurity.CreateOrUpdateAccount でアプリのアカウントと紐付ける
  5. OAuthWebSecurity.Login でアプリにログインする

“スターター サイト”テンプレートは手広い実装になっていて、その分複雑になっているけれど、アプリのアカウント= Twitter アカウントという運用でよいのならば、このように比較的簡単に実装できる。

はてなグループ、作ってみました。

f:id:daruyanagi:20130902040058p:plain

本日、はてなブログを「ブログ グループ」に参加させる際に、ブログ記事のカテゴリーを指定できるようにしました。自分のブログで「写真」カテゴリーにある記事だけを「写真」グループに参加させる、といったことができます。

ブログ グループに参加する際に、記事のカテゴリーを指定できるようにしました - はてなブログ開発ブログ

グッジョブ! ずっと要望していた昨日なのでとても嬉しい。

まだ僕しかいないけど!

はてなグループには RSS もあるので、お前の日記になんぞ興味ないよ、WebMatrix や ASP.NET Web Pages のことだけ書けよ、という人がいたらそっちを購読してくだサイ。また、参加者も募集しています!

WebMatrix 3 で Wiki クローンを作る vol.1

前回(WebMatrix 3 で Wiki クローンを作る vol.0 - だるろぐ)からすでに1ヶ月たちましたが、だいたいこんなペースで、気が向いたときにやっていくと思います。すまんやで!

さて、今回はデータの読み書きです。Wiki と言えば、データはテキストとして保存するるタイプが多いんですかね? まぁ、それでもいいんですけど、WebMatrix では SQL Server Compact Edition(SQL CE と略されることが多いです)が簡単に扱えるので、それを利用したいと思います。

SQL CE というのは Microsoft SQL Server 兄弟の末弟で、SQLite みたいにポータブルに扱えるタイプのデータベースです。ちなみに、WebMatrix はそのお兄さん(SQL Server)や、お兄さんのライバル(MySQL)なんかともなかよくできるのですけれど、ああいうのはインストールとかセッティングとかメンテナンスとか面倒ですよね。その点、SQL CE はデータベースファイルをひとつポンと作るだけなので楽ちんです。ちなみに、タダ。

データベースの作成

f:id:daruyanagi:20130810152905p:plain

WebMatrix でデータを扱うには、[データベース]というワークスペースを選択します。

f:id:daruyanagi:20130810152957p:plain

では、さっそくデータベースを作りましょう。データベースのファイル名はなんでもいいです。今回はプロジェクト名そのままの“Green Tights.sdf”にしました。続いてテーブルの作成。

f:id:daruyanagi:20130810153138p:plain

今回はこんなかんじにしてみました。ついでにテーブルの定義も作っておきましょう。テーブルの名前は“Post”で、投稿を管理するテーブルです。

  • PostId:bigint(でっかい整数)型。“主キーかどうか”“ID かどうか”の両方を“はい”にしました。テーブルに行が挿入されると自動でインクリメントされるはずです。
  • Title:最大60文字の nvarchar(文字列)型。文字数は適当……。“Null(からっぽ)を許可”を“いいえ”にしておきます。
  • RawText:ntext 型。あらかじめ文字列の数を決めなくてよいテキスト型。本文をぶちこんでおくには最適かな?
  • CreatedAt:datetime(日時)型。その名の通り作成日時です。
  • UpdatedAt:datetime(日時)型。今回は使いませんが、のちのち更新日を記録しておきたくなると思うので。

これを“Post”という名前で作っておきます。

データの入力画面の作成

f:id:daruyanagi:20130810153956p:plain

[ファイル]ワークスペースへ移行、とりあえず NewPost.cshtml という名前でデータの入力画面ページをルートフォルダ―に作成します。この画面には、http://***/NewPost でアクセスできます。

f:id:daruyanagi:20130810154059p:plain

コードの方はこんな感じ。ごくごく簡単で、エラー処理っぽいことはしていません。

@{
    if (IsPost) // POST 要求だけを受け付けましょう
    {
        // Request["name"] で送られてきた名前を取得。
        // POST データなら Request.Form["name"] の方がフォーマルな書き方かな
        var title = Request["title"];
        var raw_text = Request["raw-text"];
        var now = DateTime.Now;

        // データベースを開く。拡張子はいらない
        using (var db = Database.Open("Green Tights"))
        {
            const string query = @"
                INSERT INTO Post(Title, CreatedAt, UpdatedAt, RawText)
                VALUES(@0, @1, @2, @3)
                ";
            db.Query(query, title, now, now, raw_text);
        }
    }
}

<!DOCTYPE html>

<html lang="en">
    <head>
        <meta charset="utf-8" />
        <title></title>
    </head>
    <body>
        <form method="post"> <!-- Submit すると自分を POST で呼ぶ -->
            <p><input type="text" id="title" name="title" /></p>
            <p><textarea id="raw-text" name="raw-text"></textarea></p>
            <p><input type="submit" /></p>
        </form>
    </body>
</html>

試しに実行し、[データ]ワークペースでデータを確認してみましょう。表示モードを[定義]から[データ]に切り替えてね!

f:id:daruyanagi:20130810154452p:plain

なにか……入ってますね? 成功! 次回はこれを取り出して表示して見ることにしましょう。おつかれさまです。

WebMatrix 3 で Wiki クローンを作る

Windows Store oEmbed API を NuGet にしておきました

f:id:daruyanagi:20130724064818p:plain

WebMatrix 3: Windows Store oEmbed API を利用する - だるろぐ を NuGet にして NuGet Gallery で公開しました。

前回の記事からの変更点は、System.Threading.Thread.CurrentThread.CurrentUICulture で言語を取得して URL をビルドするようにしたことのみ。

@WindowsStore.GetHtml(APP_URL)

@WindowsStore.GetHtml("8289549f-9bae-4d44-9a5c-63d9c3a79f35")
続きを読む

WebMatrix 3: Windows Store oEmbed API を利用する

Windows Store oEmbed API は、なかなかいいですね。ブログにこんなかんじでストアアプリの情報が貼り付けられます。

使い方などは以下の URL を参照してください。

またストアアプリを作りたくなりました(ぁ

WebMatrix 3 で Windows Store oEmbed API を利用する

Windows Store oEmbed API と jquery-oembed を組み合わせて使う方法 - しばやん雑記 のように JavaScript(クライアントサイド)で利用するのがよいと思いますが、C#(サーバーサイド)で使うこともできます。説明するのは面倒なので、コードだけおいておきます。APS.NET の“空のサイト”を作成し、Default.cshtml を以下のように編集してください。

@{
    const string API_ENDPOINT = @"http://wsoembed.com/oembed";
    const string APP_URL = @"http://apps.microsoft.com/windows/ja-jp/app/86b6ecdc-e810-4aa2-9bdb-bb0da5b34737";
    
    var url = string.Format("{0}?url={1}", API_ENDPOINT, APP_URL);
    var response = string.Empty;

    using (var downloader = new WebClient(){ Encoding = System.Text.Encoding.UTF8 })
    {
        response = downloader.DownloadString(url);
    }

    var json_object = Json.Decode(response);
}

<!DOCTYPE html>

<html lang="ja">
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
        <meta charset="utf-8" />
        <title>マイ サイトのタイトル</title>
        <link href="~/favicon.ico" rel="shortcut icon" type="image/x-icon" />
    </head>
    <body>
        <h1>Query</h1>
        <p>@url</p>

        <h1>JSON</h1>
        <pre>
@response
        </pre>
        
        <h1>json_object Member</h1>
        <p>var json_object = Json.Decode(response);</p>
        <ul>
        @foreach(var member in json_object.GetDynamicMemberNames())
        {
            <li>json_object.@member</li>
        }
        </ul>
        
        <h1>Html.Raw(json_object.html)</h1>
        @Html.Raw(json_object.html)
    </body>
</html>

結果はこんなかんじです。

f:id:daruyanagi:20130718032449p:plain

API_ENDPOINT を叩けば JSON が返ってくるので、JSON ヘルパーを利用してオブジェクトにしてやりましょう。このオブジェクトは dynamic なので、json_object.html などとしてやれば値がとれます。大文字小文字に注意してね ☆(ゝω・)vキャピ

Windows Store oEmbed API ヘルパー

こんなコード、毎回書くのは面倒なので、ヘルパーにしてしまいましょう。ルートフォルダーに App_Code フォルダーを作成し、WindowsStore.cshtml を作成します。中身はこんな感じ。

@helper GetHtml(string app_id) {
    // app_id が url で渡されても許容する
    app_id = app_id.Split('/').Last();

    const string API_ENDPOINT = @"http://wsoembed.com/oembed";
    const string APP_BASEURL = @"http://apps.microsoft.com/windows/ja-jp/app/";
    
    var url = string.Format("{0}?url={1}", API_ENDPOINT, APP_BASEURL + app_id);

    using (var downloader = new WebClient(){ Encoding = System.Text.Encoding.UTF8 })
    {
        var response = downloader.DownloadString(url);
        var json_object = Json.Decode(response);

        @Html.Raw(json_object.html)
    }
}

使い方はこんな感じ。Default.cshtml の最後の方にでも書き足してみてください。

        :
        :
        <h1>Helper</h1>
        @WindowsStore.GetHtml(APP_URL)

        @WindowsStore.GetHtml("86b6ecdc-e810-4aa2-9bdb-bb0da5b34737")
    </body>
</html>

結果はこんなかんじです。

f:id:daruyanagi:20130718033142p:plain

ja-jp で決め打ちになっているのはアレなので、

@helper GetHtml(string app_id, string locale = "en-us") {
    :
    :

みたいなシグネチャの方がいいのかもしれませんね。

WebMatrix 3 で Wiki クローンを作る vol.0

f:id:daruyanagi:20130717051313p:plain

Ruby on Railsには良くかけたチュートリアルがあって、最初の一歩は踏み出 しやすいようになっています。しかし、チュートリアルをひととおり読んで、 scaffoldスゲーということはわかったのだけど、次に何をしたら良いかわから ないという人が多いようです。かくいう筆者もその一人でした。

http://tam.qmix.org/wiki/Minki00.html

昔、Web アプリが作りたいなぁ、と思って手を出したのが Ruby on Rails でした。このサイトを参考にしながら、少しずつ Wiki を作って勉強したのを覚えています。当時は C# で Web アプリが書けるというのを知らなかったし、その環境も整っていなかった。なので、わざわざいちから Ruby という新しい言語を覚える必要がありました。それはそれで楽しいことだったし、今でもその経験は役に立っているのですけど、やっぱり使い慣れた C# で書けるならばそれに越したことはない。

今なら C# で Web アプリが簡単に作れる WebMatrix という優秀なツールがあります*1。でも、僕がかつてお世話になったチュートリアル的なコンテンツはまだまだ少ないのが現状です。

んなわけで、“WebMatrix 3 で Wiki クローンを作る(WebMatrix 3 で Wiki クローンを作る)”というのを、不定期にボチボチやっていこうかなと思います。でも、あんまり期待しないでください。

必要なもの

これだけです。できれば http://www.microsoft.com/visualstudio/jpn/products/visual-studio-express-for-web があるといろいろ便利。フレームワークには ASP.NET Web Pages 2 | Microsoft Docs を利用しますが、とりあえず今のところは「C# で PHP っぽく Web アプリが書けるもの」だとでも思っていただければ結構です。

プロジェクトの新規作成

f:id:daruyanagi:20130717053248p:plain

テンプレートギャラリーから……

f:id:daruyanagi:20130717053316p:plain

ASP.NET の“空のサイト”を選択。今回は @Webmatxirxman(https://twitter.com/WebMatrixMan)に敬意を表して「Green Tights」としました。“C:\Users\***\Documents\My Web Sites\Green Tights”がプロジェクトフォルダーになります。

f:id:daruyanagi:20130717053725p:plain

まず、適当に Default.cshtml を編集しましょう。今回は冒頭の @{} コードブロックで App.Name に“Green Tights”という文字列を代入し、HTML で @App.Name のように記述して埋め込んでいます(2ケ所)。

@{
    App.Name = "Green Tights";
}

<!DOCTYPE html>

<html lang="ja">
    <head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
        <meta charset="utf-8" />
        <title>@App.Name</title>
        <link href="~/favicon.ico" rel="shortcut icon" type="image/x-icon" />
    </head>
    <body>
        <p>Welcome to @App.Name!</p>
    </body>
</html>

[実行]ボタンを押し……

f:id:daruyanagi:20130717053731p:plain

ブラウザーでなにか表示されたら成功。ちゃんと動いてますね。この @ で変数を評価して出力する記法を Razor と呼びます。

今日のところはこれで終わり!――次回はデータベースを用意して、書き込みと読み込みを行います。たぶん。

ソースコード

*1:ほかにもできるけど、そっちを推すのはほかの人に任せる