2009年2月28日土曜日

Base64でエンコード

Url encoded slash in URL - Stack Overflow

この投稿から始まって、以下のエントリにたどり着く。

Allowing special characters (forward slash, hash, asterisk etc) in ASP.Net MVC URL parameters

まさに、同じ問題に直面した内容。

Double/incomplete Parameter Url Encoding - Stack Overflow

↑ここへのリンクもあったから覗いてみたら、Uri.EscapeDataString メソッド (System)を使ったエンコーディングがあるということを初めて知る。なにこれ~、と思って試したけど、Server.UrlEncodeと同じだった。ガッカリ。いや、違う方が驚きか。

結局、Philさんの投稿のように"/"か他の文字に置換して区切ってしまうか、Base64エンコードで渡すのがいいのかな。Base64といえばFuturesに入ってるLinqBinaryModelBinderが使えるんだもんね。

というわけで試してみた。

登録するルートは以下の通り。

routes.MapRoute(null,
"Proxy/{*base64url}", 
new { controller = "Images", action = "Proxy" } 
); 

Controllerはシンプルに以下。LinqBinaryModelBinderを使うので、参照設定にFuturesのアセンブリを含めるのを忘れずに(単純に文字列で渡しておいて、Binary型にしなくてもConvert.FromBase64Stringでも可)。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Mvc.Ajax;
using System.Net;
using System.Text;
using System.Data.Linq;

namespace Mvc.RC.Controllers
{
 public class ImagesController : Controller
 {
   public ActionResult Proxy(Binary base64url)
   {
     var web = new WebClient();
     var url = Encoding.UTF8.GetString(base64url.ToArray());

     var bytes = web.DownloadData(url);
     return File(bytes, web.ResponseHeaders["Content-Type"]);
   }
 }
}

Viewでリンクを作る時にBase64エンコードしたパラメータを渡す。
  <% = Html.ActionLink("いぬ", "Proxy", "Images", new {
 base64url = Convert.ToBase64String(Encoding.UTF8.GetBytes(
         @"http://farm1.static.flickr.com/131/353753310_1ed04f694c_m.jpg"
         ))
 }, null)%>

これが出力されると以下のHTML。

<a href="/Proxy/aHR0cDovL2Zhcm0xLnN0YXRpYy5mbGlja3IuY29tLzEzMS8zNTM3NTMzMTBfMWVkMDRmNjk0Y19tLmpwZw==">いぬ</a>

リンクをクリックする。

base64

ちゃんと犬が表示されました。一応、ブレークポイントセットして変数の中身を確認。

base64_2

ちょと見にくいけど、引数のbase64UrlにはBase64エンコードされたBinary型の値が入ってて、変数urlには元のURLがデコードされてる。

問題はぱっと見どこのURLを参照してるのかを判断出来ないところ。フレンドリURLとは言い難し。

ところで、Windows Live Writerから投稿すると、毎回無駄な改行が入るのはなんでなんだろう。

2009年2月26日木曜日

ModelBinderでLINQ to SQLのモデルをそのまま使う

ASP.NET MVC Tip #49 - Use the LinqBinaryModelBinder in your Edit Actions

ステファン君それは無理じゃない?更新以前にバインドした時点で例外でるじゃん。

と、思ってたのは今は昔。RCでは何の問題もなくバインド出来るみたい。試してみたら、普通に出来た。ずいぶん前に試したときはデータベースコンテキストがスタティックじゃないと例外でたり(ModelBinderに気をつけねば)、そもそも直接DBO使うのはうんぬんかんぬん。綺麗な設計どうのじゃなくて、純粋に少ないコード量でどこまで出来るか、っていうのを考えたら直接使う事もあったりするのかもね。

これまでも使ってたサンプルはDBを使ってなかったので、改めてDBを用意して、LINQ to SQLのモデルを作成。

db1

これを利用するためのコントローラも定義。

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

using Mvc.RC.Models;

namespace Mvc.RC.Controllers
{
public class OnePeaceController : Controller
{
  OnePeaceDataContext _context = null;

  public OnePeaceController()
  {
    _context = new OnePeaceDataContext();
  }

  public ActionResult Index()
  {
    return List();
  }

  public ActionResult List()
  {
    var people = _context.Persons;

    return View("List", people);
  }

  public ActionResult Details(int id)
  {
    var people = _context.Persons;

    return View(people.First(p => p.Id == id));
  }

  [AcceptVerbs(HttpVerbs.Get)]
  public ActionResult Edit(int id)
  {
    var people = _context.Persons;

    return View(people.First(p => p.Id == id));
  }

  [AcceptVerbs(HttpVerbs.Post), ValidateInput(false)]
  public ActionResult Edit(int id, Person person)
  {
    try
    {
      _context.Persons.Attach(person, true);
      _context.SubmitChanges();

      return RedirectToAction("Index");
    }
    catch (Exception ex)
    {
      return View(person);
    }
  }
}
}

List/Detail/Editの各ViewはRCで追加されたAdd Viewコマンドでサラッと作成。こういう時にとても便利ですね。Attachのところでブレークポイントをセットして、まずはこのまま動かしてみたところ、DB使っててもホントに動く。信じてなかったわけじゃないけど、こうもあっさりと動くとちょっと感動する。

bind

↑Listページ。

bind2

↑Editページ。

bind4

↑普通にバインドされてる様子。

でも、このままステップ実行すると、確かに例外が発生。

bind3

エンティティは、バージョン メンバを宣言するか、または更新チェック ポリシーを 含まない場合にのみ、元の状態なしに変更したものをアタッチできます。

System.Exception {System.InvalidOperationException}

と、言うことで、簡単に更新チェックポリシーをオフにしてしまおうかとも思ったけど、それよりちゃんと競合チェックするようにtimestamp型の列をテーブルに追加して動かしてみます。

db2

↑ChangeStampという名前のtimestamp型の列を追加したモデル。

でも、timestamp型はSystem.Data.Linq.Binary型としてクラスが生成されるので、このままだとちゃんとモデルが復元されません。なによりViewにちゃんと出力してないし。まずはEditのViewにChangeStampをhiddenで展開するコードを追加。

            <p>
              <%= Html.Hidden("ChangeStamp",
                              Convert.ToBase64String(Model.ChangeStamp.ToArray())) %>
              <input type="submit" value="Save" />
          </p>
      </fieldset>

  <% } %>

  <div>
      <%=Html.ActionLink("Back to List", "Index") %>
  </div>

</asp:Content>

submitの手前に入れてます。Base64エンコードしてHiddenフィールドに。このままだとDefaultModelBinderが復元してくれくれないので、Global.asaxのApplication_Start時にASP.NET MVC Futuresに入ってるLinqBinaryModelBinderを登録。

    protected void Application_Start()
  {
    RegisterRoutes(RouteTable.Routes);
    ModelBinders.Binders.Add(typeof(System.Data.Linq.Binary),
                              new LinqBinaryModelBinder());
  }

もう、何もかもステファンさんのいいなりです。

この状態で動かしてみて、Viewが出力するHTMLソースのChangeStamp部分を確認してみたのが↓これ。

<input id="ChangeStamp" name="ChangeStamp" type="hidden" value="AAAAAAAAB9Q=" />

ちゃんと、エンコードされて出力されてます。あたりまえだっちゅーの。

bind5

これを更新するためにsubmitして、ブレークポイントでモデルの中身を確認。ちゃんとLinqBinaryModelBinderでBinary型も復元されてます。そのまま実行を続けても例外は出なくて、テーブルも更新されてました。で、更新された後のViewのCangeStampを確認してみる。

<input id="ChangeStamp" name="ChangeStamp" type="hidden" value="AAAAAAAAB9U=" />

ちゃんと、最初とは違う値が入ってますね~。

と、いうことで、ステファンさんのやったことをそのまま試したみたわけですが、これが出来るって事はLINQ to SQL+スキャッフォールディングで凄く簡単にDBを使ったアプリケーションを作成出来る事になりますね。Repositoryは作るにしても、ViewModelを作らずシンプルなコードで開発することが出来るのでWebForms並の生産性(ViewStateとデータバインディング)を実現できてるんじゃないかと思う次第です。

※ViewStateはModelStateが各inputフィールドの値を保持しつつHTMLにレンダリングしてくれたりするので。

2009年2月25日水曜日

Bloggerをいじる

とりあえずは、コードをエントリーに書くことが多くなるとは思うので、綺麗に見せるためにGoogle Code Prettifyを導入してみる。

google-code-prettify - Google Code

ここからコードをダウンロードできるんだけど、はて、このJSファイルとCSSファイルはどこにアップロードするんでしょうね。困りますね。面倒なのでずるいとは思いつつ、SVN-Tranc(google-code-prettify - Revision 64: /trunk/src)から直接貼り付けちゃった。てへ。

テンプレートを編集する画面でheadタグ内の<title>の下にダイレクト挿入!

blogger1

<script src='http://google-code-prettify.googlecode.com/svn/trunk/src/prettify.js'
       type='text/javascript'/>
<link href='http://google-code-prettify.googlecode.com/svn/trunk/src/prettify.css'
     rel='stylesheet' type='text/css'/>

後は、コードを載せたい箇所を<pre class=”prettyprint”></pre>で囲むだけ! とは言ってもですね、毎回ソースを開いてそんなことやってられないじゃないか! 便利な物はないかな~、と思って行き着いたのが「Windows Live Writer用のプラグインを開発する:CodeZine」。

おっと、いきなりプラグイン開発ですか?そうですか。それもやむなしですね。どれどれ、記事を読んでみると大して難しいことしなくてもこの程度のことならサクッと作れそう。だってpreで囲むだけだし。SmartContentSourceじゃなくてContentSourceで作れるじゃんね。んで、作ったDLLをPluginフォルダに入れとけばイイみたいだし。良し作るか。と、思ってちなみに同じ事考えてる人いるんじゃないかと思って、改めて検索し直してみたらいた。Vistaのサイドバーガジェットの時の無駄作業を思い出して良かった。あのときも同じようなの作ってる人いたもんね。ちゃんと調べないで作っちゃって損した気分になったし。

Code Prettify for Windows Live Writer – Home

コード見たらビックリだね。こんなに潔いプラグインも珍しい。いや、だって、自分でもきっとこう書く。

次にデフォルトのコメントシステムはいきなりスパムが来たりするくらいなので、速攻で消してとりあえずFriendConnectにしてみたんだけど、TumblrでTypePad Connectを使ってるの思い出した。

Bloggerもサポートしてるよ!と豪語してるだけあって、設置方法もスクリーンショットでテンプレート編集箇所を書いてあったりして、親切だ。

でも、動かなかった。 だいたい、書いてあるとおりにやってみたけどダメだった。Bloggerのテンプレートがどうなってる(構造が)のか調べるのも面倒だしな~。このブログに適用してるブログにはそもそもインストール手順に書いてるような項目もないし。インストール手順がデフォルトテンプレートを基準にするのは、そりゃあたりまえだしね。違うデザインのテンプレートを使ってるんだから致し方なし。

かといって、諦めるのはダンディじゃない。 無理矢理にでも動かしてやる。大まかな手順は3つ。

1.コメントフォームとコメント一覧を表示する部分のコードを貼り付け。 2.コメント件数を表示するアンカーを貼り付け。 3.コメント件数を表示するスクリプトを貼り付け。

1と3の作業は手順書通りに出来そう。ちゃんと該当箇所も見つかるし。 2の作業は...。今度は手順書の場所が見あたらない。なんか、ちょっとテンプレートが違う。

でも、まぁ、同じようなモンだろう、ってことで、↓こんな感じで貼り付けてみた。

    <b:if cond='data:post.allowComments'>
     <h4>
       <b:if cond='data:post.numComments == 1'>
         1 <data:commentLabel/>:
       <b:else/>
         <data:post.numComments/> <data:commentLabelPlural/>:
       </b:if>
     </h4>
    <b:else/>
     <a expr:href='data:post.url + "#comments"'>Comments</a>

で、動かしてもこれがちゃんと表示してくれないんだよね~。 上のコードのリンクは表示されてるんだけど、入力フォームが出てこない。こりゃどうしたものか。 出てこないってことは、貼り付ける場所を間違えてるんだろう、と。 絶対出てくるところに貼り付ければいいじゃん。 ってことで、上記Commentsリンクの直下に貼り付けてみた。

    <b:if cond='data:post.allowComments'>
     <h4>
       <b:if cond='data:post.numComments == 1'>
         1 <data:commentLabel/>:
       <b:else/>
         <data:post.numComments/> <data:commentLabelPlural/>:
       </b:if>
     </h4>
   <b:else/>
     <a expr:href='data:post.url + "#comments"'>Comments</a>
<!-- START TypePad Connect -->
<b:else/>
<div class="comments-content">
~ここに手順書のコード~
</div>
<!-- END TypePad Connect -->

これで、どうかな~。うぬ。ちゃんと出るね。

ということで、Friend Connectは削除して、コメントシステムはTypePad Connectで一本化です! スッキリした。

LINQで今日以降今年いっぱいの日曜の日付を表示

LINQ練習 #1 「LINQ to Object 基本」 - 悠希 - builder by ZDNet Japan

ここで書かれてるコードが↓これ。

List<DateTime> list;
list = new List<DateTime>();
for(int i=0;i<=365;i++) list.Add(DateTime.Today.AddDays(i));

int nowYear;
nowYear = DateTime.Today.Year;
var sunday = from M in list
            where M.DayOfWeek == DayOfWeek.Sunday &&
                  M.Year = nowYear select M;

foreach (var row in sunday) {
   Console.WriteLine(row.ToShortDateString());
}

※今年かどうか判定する条件の部分が代入になってるよ~。

せっかくLINQを使うっていうお題なんだから日付の生成部分もList<DateTime>に生成して入れておく、なんてことをしないで、そこもLINQに含めちゃった方がオシャレ感でると思うよ~。そう、例えば↓こんな感じでね。

var today = DateTime.Today;
var sunday = from date in
             from day in Enumerable.Range(0, 365)
             select today.AddDays(day)
            where date.DayOfWeek == DayOfWeek.Sunday &&
                  date.Year == today.Year
            select date; 

2009年2月24日火曜日

心機一転

ブログをBloggerに移行します。 FeedBurner経由でFeedを購読してる場合は、そのままのでOKです。 特に、現在がんばってるASP.NET MVCに関するエントリーは移行しました。 が、その他のエントリーは、移行せずそのままにしておきます。 今後、こちらのブログでの更新がメインになります。 あと、Tumblrはそのまま継続使用デス。

BloggerのRate Limit

まさか、自分がこの制限に引っかかるとは...。 調子に乗って、前ブログからエントリーを移行させるという行為が仇になった。 24時間で解除されるみたいだけど、それまでWindows Live Writer経由でのエントリーが出来ませぬ。 Windows Live Writerで書き込もうとすると...。
400 Bad Request Blog has exceeded rate limit or otherwise requires word verification for new posts
と、悲しいメッセージが出てくる。 50件あたりで出てきた気がする...。切ないね~。 ブラウザ経由でも、API経由でも出るみたいだから、一気にやるならImport機能のフォーマット似合わせたXMLファイルを作成して、それを取り込むのがいいんだろうね。 面倒くさいな~。特に、画像をPicasaに入れてimgタグのsrc書き換えとかゲンナリするし。 まぁ、ASP.NET MVC関係のエントリだけを移行させようと思ってるから手作業でやるさ。

ASP.NET MVCとGearsの連携

Maarten Balliauw {blog} - Creating an ASP.NET MVC application with Google Gears

面白いエントリー発見。 Gearsは最初のリリースの時に少し触ってみた程度で、名前からGoogleが消えたあたりから全く状況を追いかけてないんだけど、このサンプルがかなり面白い。

エントリーには2つソリューションがあって、1つはMVCオンリーでもう一つがGears対応版。両方見比べると分かりやすいんだけど、なにせDBファイル込みになってるから少しサイズが大きいのが難点。今時気にしないか。

どっちにも共通して存在してる(デフォルトで作成されるファイルを除く)のが、NoteControllerとそのView達(List/Detail/Edit/Create)。

で、Gears対応版では、Routeにmanifest.jsonへのリクエストに応えるためのルート登録をして(GearsController)、関連jsファイルが含まれててそれをインクルードするためにSite.Masterが少し違うだけ。

MVC部に注目してみると、GearsControllerのIndexアクションが~/Contentと~/Scriptsフォルダの全ファイルのURL、それとMVCがViewを返すURL(/Homeや/NoteList)、データ詳細のURLをJsonで返すだけに見える。

一体全体どこでデータとViewのHTMLとを分離するコードがあるんだろう。テンプレート的な物が定義されてるんだとばかり思ってたけど、そんなコードが見あたらない。

demo_offline.jsはGearsを有効にしたり、無効にしたときのローカルストアの操作と、完全オフラインになったときに編集用リンクを無効にしたり、オフラインかどうかを監視するコードだけっぽい。 gears_init.jsはいじらないコードだよね。

formやinputのname属性とかを見て自動で判断してくれてるのかな。それともシンプルに各URLに対するアクションをすべて記録してて、オンラインになったときに再生? ちゃんとGearsの仕様を確認すれば分かることなんだろうけど、まぁ、ねぇ。今はそんなに興味ないって言うか。

それにしても、Gearsはずいぶんスゴイ事になってる気がするし、MVCとの連携がこれほどシンプルに出来るのもViewStateとPostBackがないおかげだよね。

dotnetConf2015 Japan

https://github.com/takepara/MvcVpl ↑こちらにいろいろ置いときました。 参加してくださった方々の温かい対応に感謝感謝です。