WebMatrix 2 RC でサクッとWebサイトをオシャレにしてみた
みてくれたまえ。これが昨日までの http://download.daruyanagi.net/ だ。ワイルドだろ?*1 さすがにこれを放置するのも何なので、WebMatrix 2.0 Release Candidate でキレイにしてみることにした。
なにはともあれインストール
まず、WebMatrix 2 を
WebMatrix 2
からダウンロードしてくれたまえ。うちの場合は、なんか2・3回インストーラーを実行するハメになったけど(なんでだ?)、まぁ、すんなり入る。
Webサイト側でリモート管理を有効にする
レンタルサーバーはもちろん、ASP.NET がお安く使える
高機能・激安 Windows レンタルサーバー ExpressWeb
を使ってるよな? Web配置で楽をしましょう。
設定ファイルをダウンロードしておくとあとで捗る。
Webサイトのダウンロード
WebMatrix でリモートサイトを開く。さっきダウンロードしておいた設定ファイルを読みこめば、かなり楽ちん。リモート管理のアカウント情報を入力するだけで済む。
無事接続できた。このまま作業をしてもいいのだけど、やっぱりローカルにコピーを作っておいたほうが何かと安全なのでダウンロードしておく。
これがやたら時間かかる。なぜか使ってもいない MySQL もインストールされるし。まぁ、細かいことは気にしない。 ASP.NET 4 のインストールにも失敗したけど、とくに問題なく動いているみたい。
@daruyanagi そういう時は、MySQLのサイトで日本のサーバーから落とすのが早いっす。
— ウェブマトリクスマンさん (@WebMatrixMan) 6月 19, 2012
WebMatrixman はほんとデキる子だな。
Git for WebMatrix のインストール
この作業はスキップしていいのだけど、どうせならバージョン管理できるようにしておけばロールバックとか楽になるよね。というわけで、拡張機能ギャラリーから「Git for WebMatrix」をインストールしておく。
リリース当初はハングアップしたりして大変だったけど、週明けのバージョンアップでかなり使えるようになった。
Twitter Bootstrap のインストール
WebMatrix 2 では NuGet もよりお手軽に利用できるようになっている。どこかの誰かが作ってくれた便利ツールが自由に使えるというわけだ! 使わないなんて損、損。今回は、デザインセンスのない開発者御用達のCSSフレームワーク Twitter Bootstrap を利用してみた。 WebMatrix 2 は LESS なんかも扱えるから、今後はカスタマイズ可能な Bootstrap なんかも利用できるようになるかも。夢が広がる……
コーディング
さて、ようやくコーディングのお時間なのだけど……基本的には Bootstrap, from Twitter (サンプル)のソースコードをガバっとコピペしてチョチョイのちょいとイジるだけ。一瞬で終わってしまった。
_Layout.cshtml
Webページのひな形。
それぞれのWebページの内容を挿入する場所に`@RenderBody()`を埋め込んでおく。ついでに、サイト名や作者名なんかは冒頭の`@{ }`セクションで変数にしておくと、あとで管理するのが楽になる。スクリプトやスタイルシートのパスを /Scripts や /Content に書き換えておくのを忘れずに。
Index.cshtml
@{
Layout = "~/_Layout.cshtml";
}
でレイアウトを指定し、レイアウトの `@RenderBody()` を書いていく感じ。 Bootstrap, from Twitter のソースコードで言えば、 `<div class="container">` のヘッダー以外の部分をココにコピーして自分なりに書き換えていく。
あとは、同じ要領でページを増やしていけばいい。 About.cshtml を書けば download.daruyanagi.net/About になるし、 Contact.cshtml を書けば download.daruyanagi.net/Contact になる。レイアウトを指定すれば、見栄えも共通化できる。
完成!
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); } } } }
できた。けど、これだと短縮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。
ビューへ適当に @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 へリダイレクトしてやればいいです。
/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 を取り出します。ちゃんとこれらが取得できていれば、クッキーをセットしてログインが終了。今回はログインの成功・失敗に関わらず、"/" へリダイレクトしています。
@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 を置いてないといちいちルーティングに引っかかってめんどくさい
パスに無効な文字が含まれています。
状況
コントローラーで View(model) を返す。model は string型 で、ローカルにあるテキストファイルを読み込んだ内容が格納されている。
ビュー側でこれを @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 を使うところから始めてみた。
準備
まず、アプリケーションの作成。
別に認証機能は要らないや。
今回は SharpBox (http://sharpbox.codeplex.com/) を使って楽をすることにした。NuGetでさくっとインストール。
あと、https://www2.dropbox.com/developers/apps でアプリの登録をしておくのも忘れずに。APIキーをここで取得しておく必要がある。
コード
コントローラー
まず、Homeコントローラーを作る。なぜ Home という名前なのかというと、Global.asax を書き換えるのが面倒くさいからですね。わかります。スキャフォールディングも使って楽をしましょう。
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 から認証周りがだいぶ変わっていて、サンプル読んでいるとダマされるので注意。
できたファイルは 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>
Thanks!
ほとんどここを参照したので、みんなも見てみればいいと思うよ。 → Unboxing Dropbox and SharpBox | Jayway Team Blog - Sharing Experience