だるろぐ

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

WebMatrix 2 RC でサクッとWebサイトをオシャレにしてみた

f:id:daruyanagi:20120620051736p:plain

みてくれたまえ。これが昨日までの http://download.daruyanagi.net/ だ。ワイルドだろ?*1 さすがにこれを放置するのも何なので、WebMatrix 2.0 Release Candidate でキレイにしてみることにした。

なにはともあれインストール

f:id:daruyanagi:20120620052143p:plain

まず、WebMatrix 2 を
WebMatrix 2
からダウンロードしてくれたまえ。うちの場合は、なんか2・3回インストーラーを実行するハメになったけど(なんでだ?)、まぁ、すんなり入る。

Webサイト側でリモート管理を有効にする

f:id:daruyanagi:20120620052554p:plain

レンタルサーバーはもちろん、ASP.NET がお安く使える
高機能・激安 Windows レンタルサーバー ExpressWeb
を使ってるよな? Web配置で楽をしましょう。

f:id:daruyanagi:20120620052701p:plain

設定ファイルをダウンロードしておくとあとで捗る。

Webサイトのダウンロード

f:id:daruyanagi:20120620052847p:plain

WebMatrix でリモートサイトを開く。さっきダウンロードしておいた設定ファイルを読みこめば、かなり楽ちん。リモート管理のアカウント情報を入力するだけで済む。

f:id:daruyanagi:20120620052958p:plainf:id:daruyanagi:20120620053105p:plain

無事接続できた。このまま作業をしてもいいのだけど、やっぱりローカルにコピーを作っておいたほうが何かと安全なのでダウンロードしておく。

f:id:daruyanagi:20120620053200p:plainf:id:daruyanagi:20120620053206p:plainf:id:daruyanagi:20120620053211p:plainf:id:daruyanagi:20120620053217p:plainf:id:daruyanagi:20120620053223p:plainf:id:daruyanagi:20120620053229p:plain

これがやたら時間かかる。なぜか使ってもいない MySQL もインストールされるし。まぁ、細かいことは気にしない。 ASP.NET 4 のインストールにも失敗したけど、とくに問題なく動いているみたい。

WebMatrixman はほんとデキる子だな。

Git for WebMatrix のインストール

f:id:daruyanagi:20120620053912p:plain

この作業はスキップしていいのだけど、どうせならバージョン管理できるようにしておけばロールバックとか楽になるよね。というわけで、拡張機能ギャラリーから「Git for WebMatrix」をインストールしておく。

f:id:daruyanagi:20120620054028p:plainf:id:daruyanagi:20120620054031p:plain

リリース当初はハングアップしたりして大変だったけど、週明けのバージョンアップでかなり使えるようになった。

Twitter Bootstrap のインストール

f:id:daruyanagi:20120620054150p:plain

WebMatrix 2 では NuGet もよりお手軽に利用できるようになっている。どこかの誰かが作ってくれた便利ツールが自由に使えるというわけだ! 使わないなんて損、損。今回は、デザインセンスのない開発者御用達のCSSフレームワーク Twitter Bootstrap を利用してみた。 WebMatrix 2 は LESS なんかも扱えるから、今後はカスタマイズ可能な Bootstrap なんかも利用できるようになるかも。夢が広がる……

コーディング

さて、ようやくコーディングのお時間なのだけど……基本的には Bootstrap, from Twitter (サンプル)のソースコードをガバっとコピペしてチョチョイのちょいとイジるだけ。一瞬で終わってしまった。

_Layout.cshtml

f:id:daruyanagi:20120620054808p:plainf:id:daruyanagi:20120620054809p:plain

Webページのひな形。

それぞれのWebページの内容を挿入する場所に`@RenderBody()`を埋め込んでおく。ついでに、サイト名や作者名なんかは冒頭の`@{ }`セクションで変数にしておくと、あとで管理するのが楽になる。スクリプトやスタイルシートのパスを /Scripts や /Content に書き換えておくのを忘れずに。

Index.cshtml

f:id:daruyanagi:20120620055120p:plain

@{
    Layout = "~/_Layout.cshtml";
}

でレイアウトを指定し、レイアウトの `@RenderBody()` を書いていく感じ。 Bootstrap, from Twitterソースコードで言えば、 `<div class="container">` のヘッダー以外の部分をココにコピーして自分なりに書き換えていく。

あとは、同じ要領でページを増やしていけばいい。 About.cshtml を書けば download.daruyanagi.net/About になるし、 Contact.cshtml を書けば download.daruyanagi.net/Contact になる。レイアウトを指定すれば、見栄えも共通化できる。

完成!

f:id:daruyanagi:20120620055619p:plain

WebMatrix だったら、IE/Chrome/Firefoxどころか、モバイルOSシミュレーターでもテストできる! Bootstrap は MediaQuery でモバイルからも快適に閲覧できるけど、それをシミュレーターで確かめられる。

実際のコーディングはものの10分ぐらい。動的サイトも作れるし、その時、フレームワークASP.NET/PHP/Node.js のなかから自由に選べるのがナイス。 Azure へのデプロイもできるみたいなので、次は試してみたい。

*1:TVみないので、ネタに影響されるのが人よりかなり遅れております

YouTube の URL を動画タグへ変換する(oEmbed)

Flickr の URL を画像タグへ変換する(oEmbed) - だるろぐYoutube版も作ってみた。

Youtube も oEmbed に対応しているのだけれど、画像ではなく動画なので、リンクを作る場合は url ではなく html (objectタグ)を使うのが、Flickr の写真の場合と少し違うところ*1。type には、ほかに rich だの link だのがあるっぽい。

詳しくは oEmbed に全部書いてあるので参照のこと。

private static readonly string SERVICE_ENDPOINT =
	@"http://www.youtube.com/oembed";
private static readonly string FORMAT_URL =
	@"{0}?url={1}&maxwidth={2}&maxheight={3}&format={4}";

public static string FORMAT_HTML_VIDEO_TAG = @"
<blockquote class='youtube youtube-video'>
<p>{0}<p>
<p><small>{1} by <a href='{3}'>{2}</a></small><p>
</blockquote>
";
public static string FORMAT_ERROR = @"<p class='error'>{0}</p>";

public static string GetHtml(string url,
	string max_width = "500", string max_height = "500")
{
    try
    {
        return GetHtml(url,
        	int.Parse(max_width), int.Parse(max_height));
    }
    catch (Exception e)
    {
        return string.Format(FORMAT_ERROR, e.Message);
    }
}

public static string GetHtml(
    string url, int max_width, int max_height)
{
    try
    {
        if (url.StartsWith("http://youtu.be/"))
            url = url.Replace(
            	"http://youtu.be/",
        	"http://www.youtube.com/watch?v=");

        var format = "json";

        var address = string.Format(
        	FORMAT_URL, SERVICE_ENDPOINT, url,
        	max_width, max_height, format);

        using (var client = new WebClient())
        {
            var response = client.DownloadString(address);
            var info = DynamicJson.Parse(response);

            switch (info.type as string)
            {
                case "video":
                    return string.Format(FORMAT_HTML_VIDEO_TAG,
                        info.html, info.title,
                        info.author_name, info.author_url);

                default:
                    throw new Exception("Unknown media type.");
            }
        }
    }
    catch (Exception e)
    {
        return string.Format(FORMAT_ERROR, e.Message);
    }
}

YouTube の短縮URLはドメイン部分を置換しただけみたい。実装も楽だし、開発者も楽だし、多少リンクが長くなる以外はなかなかイケていると思う。

*1:Flickr も動画に対応しているのだけど、type=="video" の場合はやっぱり url ではなく html を使う

Flickr の URL を画像タグへ変換する(oEmbed)

eEmbedというのは、あるリソースのURL(例えばFlickrの特定の写真のページのURL)を
サードパーティ上で、写真自体の埋め込みに変換したいときに、
埋め込みに必要なパラメータを取得するためのプロトコルみたいです。

URLを埋め込みコンテンツに変換するoEmbedの仕様 - Codin’ In The Free World

前にやったときは API を使って実装したのだけど、こっちだと API キーや秘密鍵を取得しないで同じことができそう。

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

using Codeplex.Data;
using System.Net;

public static class FlickrHelper
{
    private static readonly string Endpoint = 
        @"http://www.flickr.com/services/oembed";
    public static string FORMAT_URL = 
        @"{0}?url={1}&maxwidth={2}&maxheight={3}&format={4}";
    public static string FORMAT_HTML_TAG = @"
<blockquote>
<p><img src='{0}' alt='{1}' /><p>
<p><small>{1} by <a href='{3}'>{2}</a></small><p>
</blockquote>
";
    public static string FORMAT_ERROR = 
        @"<p class='error'>{0}</p>";

    public static string GetHtml(string url,
        string max_width = "500", string max_height = "500")
    {
        try
        {
            return GetHtml(url, 
                int.Parse(max_width), int.Parse(max_height));
        }
        catch (Exception e)
        {
            return string.Format(FORMAT_ERROR, e.Message);
        }
    }

    public static string GetHtml(
        string url, int max_width, int max_height)
    {
        var format = "json";

        var address = string.Format(FORMAT_URL,
            Endpoint, url, max_width, max_height, format);

        using (var client = new WebClient())
        {
            try
            {
                var response = client.DownloadString(address);
                var info = DynamicJson.Parse(response);

                return string.Format(FORMAT_HTML_TAG,
                    info.url, info.title,
                    info.author_name, info.author_url);
            }
            catch (Exception e)
            {
                return string.Format(FORMAT_ERROR, e.Message);
            }
        }
    }
}


f:id:daruyanagi:20120303222856p:plain

できた。けど、これだと短縮URL(flic.kr)は使えないみたい。自分で Base58 のデコード処理*1なり、URLを展開する処理なりを追加する必要がある。 Base58 のデコード処理では Photo ID しか取得できず、結局 API が必要になるので、今回は汎用の短縮URL展開処理を使った。

public static Uri ExpandUrl(this Uri input)
{
    var req = (HttpWebRequest)WebRequest.Create(input);
    WebResponse res = req.GetResponse();
    return res.ResponseUri;
}

みたいな拡張メソッドを用意して、

if (url.StartsWith("http://flic.kr/p/"))
{
    url = new Uri(url).ExpandUrl().ToString();
}

短縮URLを展開してあげる。

*1:短縮URLはPhoto IDをBase58でエンコードしてある

ASP.NET MVC 3 で Dropbox の OAuth 認証を使う

今回は Sharpbox を使って、Webサイトに Dropbox を利用したログイン機能を追加します。まず、 SessionController というコントローラを作成して、Create()、AuthorizationCallBack()、Delete() の3つのメソッドを作成しました。/Session/Create が /LogOn に、/Session/Delete が /LogOff にあたります*1

f:id:daruyanagi:20120228010839p:plain

ビューへ適当に @Request.IsAuthenticated を埋め込んでいるのでわかりにくいですけど、今は False 、つまりログインしていない状態です。では、 Create() から。

/Session/Create

//
// GET: /Session/Create -> map route /LogOn

public ActionResult Create()
{
    string app_key = "***";
    string app_secret = "***";

    // 0. load the config
    DropBoxConfiguration config = DropBoxConfiguration
        .GetStandardConfiguration();
    config.AuthorizationCallBack = new Uri(
        Request.Url, "AuthorizationCallBack");

    // 1. get the request token from dropbox
    DropBoxRequestToken requestToken = DropBoxStorageProviderTools
        .GetDropBoxRequestToken(config, app_key, app_secret);

    // 2. build the authorization url based on request token
    string url = DropBoxStorageProviderTools
        .GetDropBoxAuthorizationUrl(config, requestToken);

    // 3. Redirect to the authorization page on dropbox
    return Redirect(url);

}

面倒くさい RequestToken の作成や Callback Url の生成は DropBoxStorageProviderTools がやってくれるので簡単。あとは 生成した Callback Url へリダイレクトしてやればいいです。

f:id:daruyanagi:20120228011600p:plain

/Session/AuthorizationCallBack

Dropbox の認証画面でのログインが完了すると、0. で設定した config.AuthorizationCallBack つまり AuthorizationCallBack() にリダイレクトがかかります。

public ActionResult AuthorizationCallBack()
{
    // 4. Get oauth token and uid from Request.Form[]
    var oauth_token = Request["oauth_token"];
    var uid = Request["uid"];

    // 5. Set auth cookie
    if (oauth_token != null && uid != null)
    {
        FormsAuthentication.SetAuthCookie(uid, true);
    }

    return Redirect("/");
}

AuthorizationCallBack() では、まずリクエストに含まれる OAuth Token と UID を取り出します。ちゃんとこれらが取得できていれば、クッキーをセットしてログインが終了。今回はログインの成功・失敗に関わらず、"/" へリダイレクトしています。

f:id:daruyanagi:20120228011752p:plain

@Request.IsAuthenticated が true に。これまたわかりにくいですが @User.Identity.Name には UID がセットされました。

/Session/Delete

//
// GET: /Session/Delete -> map route /LogOff

public ActionResult Delete()
{
    FormsAuthentication.SignOut();

    return Redirect("/");
}

ついでに Delete() も実装して、ログアウトできるようにしておきましょう。

こんな感じでいいのかな?

*1:あとでルーティングを追加すればいいでしょう

favicon.ico を置いてないといちいちルーティングに引っかかってめんどくさい

f:id:daruyanagi:20120225192952p:plain

エラー:データが見つからないぜ → favicon.ico がルーティングに引っかかってました! というのがめんどくさい時は、

## Global.asax

public static void RegisterRoutes(RouteCollection routes)
{
        routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
        routes.IgnoreRoute("favicon.ico");
        :

と、favicon.ico を無視するルーティングを一行書き加えておくとイイ。

via Favicon Icon-MVC3 ASP.NET - Stack Overflow

パスに無効な文字が含まれています。

状況

コントローラーで View(model) を返す。model は string型 で、ローカルにあるテキストファイルを読み込んだ内容が格納されている。

f:id:daruyanagi:20120225161522p:plain

ビュー側でこれを @Html.Raw(Model) すると、「パスに無効な文字が含まれています。」というエラーが表示される。

## Controller
return View(content);

## View
@model string

@Html.Raw(Model)

解決

モデルを HtmlString型 に変更すると、エラーが表示されなくなった。

## Controller
return View(new HtmlString(content));

## View
@model HtmlString

@Model

テキストがHTMLタグ(危険な文字列)を含んでいたので、ASP.net がエラーを吐いてくれたのかな?

ASP.net MVC 3 で Dropbox を利用する

自家製の Wiki システムを ASP.net MVC 3 で作ってて、「リビジョン管理機能がほしいですなぁ」と思った。そこで試行錯誤したのだけど、だんだん面倒になってきた。そしたら思いついた。Dropbox に記事を保存すれば勝手にリビジョン管理してくれるんだから、そっちにバックアップ取ればいいじゃん」「そもそも Dropbox をデータベースとして使えばよくね?」というわけで、とりあえず Dropbox を使うところから始めてみた。

準備

まず、アプリケーションの作成。

f:id:daruyanagi:20120225080924p:plain

別に認証機能は要らないや。

f:id:daruyanagi:20120225080955p:plain

今回は SharpBox (http://sharpbox.codeplex.com/) を使って楽をすることにした。NuGetでさくっとインストール。

f:id:daruyanagi:20120225081131p:plain

あと、https://www2.dropbox.com/developers/apps でアプリの登録をしておくのも忘れずに。APIキーをここで取得しておく必要がある。

コード

コントローラー

まず、Homeコントローラーを作る。なぜ Home という名前なのかというと、Global.asax を書き換えるのが面倒くさいからですね。わかります。スキャフォールディングも使って楽をしましょう。

f:id:daruyanagi:20120225081310p:plain

Index メソッドを書く。とりあえず動かしているだけなのでごちゃごちゃしているけど、接続→ルートの取得→(ファイルアップロード)→ファイルの列挙 という操作をしているだけ。あとでモデルへ追いだそう。

public ActionResult Index()
{
    var storage = new CloudStorage();
    var config = CloudStorage.GetCloudConfigurationEasy(nSupportedCloudConfigurations.DropBox);

    // load a valid security token from file
    ICloudStorageAccessToken accessToken;
    using (var fs = System.IO.File.Open(
        Server.MapPath("~/App_Data/DropBoxToken"),
        System.IO.FileMode.Open,
        System.IO.FileAccess.Read,
        System.IO.FileShare.None))
    {
        accessToken = storage.DeserializeSecurityToken(fs);
    }

    // open the connection
    var storageToken = storage.Open(config, accessToken);

    // get the root entry of the cloud storage 
    ICloudDirectoryEntry root = storage.GetRoot();
    if (root == null)
    {
        throw new Exception("ルートあらへん。");
    }

    // Upload file
    if (root.Length == 0)
    {
        storage.UploadFile(Server.MapPath("~/App_Data/Home.md"), root);
    }

    // Enum files
    var model = new List<ICloudFileSystemEntry>();
    foreach (ICloudFileSystemEntry entry in root)
    {
        // フィルタリングとか。あ、Linq使えばよかった
        model.Add(entry);
    }

    // close the cloud storage connection
    if (storage.IsOpened)
    {
        storage.Close();
    }

    return View(model);
}

トークンファイルの作成

言い忘れていたが、 /App_Data/DropBoxToken は、SharpBox に付属のツールで作成する。(プロジェクトフォルダ)\packages\AppLimit.CloudComputing.SharpBox.1.2.0.542\lib\net40-full にあるので探してみよう。これがわからなくてだいぶググった。じゃなくてビングった。SharpBox 1.2 から認証周りがだいぶ変わっていて、サンプル読んでいるとダマされるので注意。

f:id:daruyanagi:20120225081949p:plain

できたファイルは App_Data に突っ込んでおいた。あまり自信はないけど、APS.net の作法ではこれでいい気がする。ついでにファイルが何もない場合にアップロードする初期ファイル(Home.md)も、ここに用意しておいた。

ビュー

Home/Index のビューを作成する。Index メソッドのコンテキストメニューから簡単に作成できる。

@model List<AppLimit.CloudComputing.SharpBox.ICloudFileSystemEntry>

@{
    ViewBag.Title = "Index";
}

<h2>Index</h2>
<ul>
@foreach (var i in Model)
{
    <li>@i.Name (@i.Modified)</li>
}
</ul>

[F5]!

無事動いているみたい。

f:id:daruyanagi:20120225082122p:plain

ローカルの Dropbox にもアップロードしたファイルが現れた。

f:id:daruyanagi:20120225082309p:plain

Thanks!

ほとんどここを参照したので、みんなも見てみればいいと思うよ。 → Unboxing Dropbox and SharpBox | Jayway Team Blog - Sharing Experience