2009年10月8日木曜日

最初の目標達成!

さっきブログに書いて気がついたんだけど、Feedの購読数がなんかキリ番。

feed

目標はそこじゃなくてですね、最近始めたstackoverflow.comへの書き込みでReputationが最初の目標の100に今朝やっと達しました。次は200?

いやいや500としよう。

rep

何気に英語の勉強もかねて翻訳サイトなんかも駆使しつつ書き込んでいこうと決めて3週間。長かった。でも、超うれしい!

日本語サイトでもASP.NET MVCのフォーラムなんかがあるなら、分かる範囲で書き込みして行きたいところデス。どこかにあるならだけど。

で、話は変わりますが、うちのボスが書いてるのでこっちでも書いておきます。

また社員募集しています

たけはらがYour Fireされたわけじゃなくて、普通に人員増強です。募集要項なんかの細かい話はボスのエントリか募集サイトを見てもらえればいいかと思います。自分新人なんでその辺の話はよくわかんないです。

株式会社クロスワープ/システムエンジニアの転職情報

ただ、これだけは言っておきたい。

たけはらのファンかどうかは採用に関係しないので明記する必要はありません!

こんな感じの職場で一緒に開発してもいいというドMな方はぜひお気軽に応募していただければと思います。

2009年10月7日水曜日

V2のFuturesにViewStateが!?

Exploring the ASP.NET MVC 2 futures assemby

小野さんに振られたので↑こちらのエントリに書かれてるViewStateについての調べてみました。こういうきっかけが無いとソースを追いかけない自分に少し反省。

まさかホントにASP.NET MVCにViewStateを持ち込むのか?と、疑いたくなるようなエントリだけど何となくサンプルとして提示されてるコードが怪しい。そのまま転載させてもらうと↓こうですよ。

<% using (Html.BeginForm()) {%>
    <%Html.Serialize("person", Model); %>
    <fieldset>
        <legend>Edit person</legend>
        <p>
            <%=Html.DisplayFor(p => Model.FirstName)%>
        </p>
        <p>
            <%=Html.DisplayFor(p => Model.LastName)%>
        </p>
        <p>
            <label for="Email">Email:</label>
            <%= Html.TextBox("Email", Model.Email) %>
            <%= Html.ValidationMessage("Email", "*") %>
        </p>
        <p>
            <input type="submit" value="Save" />
        </p>
    </fieldset>
<% } %>

Html.Serialize(“person”,Model)ってなんか怪しいですよね。おまえ、ホントにViewStateを吐いてくれるのか?と。1つのViewに何個も書いたらどうなるんだよ、とか、ポストバックしたControllerでコントロールツリーを構築するのか、とかなんやかんやデス。

で、考えててもラチがあかないので、サンプル書いて試してみました。

  [Serializable]
  public class Drink
  {
    [Required]
    [StringLength(10)]
    public string Name { get; set; }

    [Compare("Name", ErrorMessage = "一致しないよ!")]
    public string CheckName { get; set; }

    [Range(10, 50)]
    public int Size { get; set; }
  }

まずは、前回も使ったDrinkクラスにSerializable属性を追加。これつけないとそもそもシリアライズ出来ないです。どこでシリアライズしてるのかFuturesのソースを追いかけると、MvcSerializerクラスで実装してます。ちなみにシリアライズの方式としてPlaintext、Encrypted、Signed、EncryptedAndSignedの4種類があり、初期値はPlaintext。これはSystem.Web.UI.ObjectStateFormatterを使ってシリアライズしてて。って、おや?マジViewStateなのか?まぁ、いいや。

なのでSerialize属性をつけるわけですが、上記クラスに値を入れてサンプル通りにView書いて実行しても何も出力されない...。

<% Html.Serialize("drink",Model); %>

サンプルだからなんかおかしいのかな~。気になるので更にソースを読み進めると、Html.SerializeはそもそもMvcHtmlStringクラスを返してきます。これは、あれですよね、ASP.NET 4で導入される<%: …%>出力に向けた実装ですよ。IHtmlStringってヤツですよ。でも、ASP.NET 3.5にはそんなもの無いので、Futuresの実装はインターフェースは無しバージョン。ってことは、単純に実行したらレンダリングされるわけじゃ無くて、ToString()かToHtmlString()で取得して、それをレンダリングするようにしないとちゃんと出力されないわけですね。

そうとわかれば、以下のように変更。

<% = Html.Serialize("drink",Model).ToHtmlString() %>

これでちゃんと出力されました。以下のようなModelを渡して結果を見てみます。

    public ActionResult Drinks()
    {
      var model = new Drink {Name = "Cola", CheckName = "Pepsi", Size = 30};
      return View(model);
    }

↑これが↓こうなります。

<input name="drink" type="hidden" value="/wEy7AEAAQAAAP////8BAAAAAAAAAAw
CAAAARk12Y0FwcGxpY2F0aW9uMSwgVmVyc2lvbj0xLjAuMC4wLCBDdWx0dXJlPW5ldXRyYWw
sIFB1YmxpY0tleVRva2VuPW51bGwFAQAAABxNdmNBcHBsaWNhdGlvbjEuTW9kZWxzLkRyaW5
rAwAAABU8TmFtZT5rX19CYWNraW5nRmllbGQaPENoZWNrTmFtZT5rX19CYWNraW5nRmllbGQ
VPFNpemU+a19fQmFja2luZ0ZpZWxkAQEACAIAAAAGAwAAAARDb2xhBgQAAAAFUGVwc2keAAA
ACw==" />

バリバリBase64エンコードされてViewStateっぽいです。でも、これを復元させるコードがどうなるかと言うと、↓こうなります。

    [HttpPost]
    public ActionResult Drinks([Deserialize]Drink drink)
    {
      if(ModelState.IsValid)
      {
        // ... success code
      }
      return View(drink);
    }

このDeserialize属性クラスが何をしてるか、ってことデスよね。またしてもソースを確認すると、そこには...。

        private sealed class DeserializingModelBinder : IModelBinder {

            private readonly SerializationMode _mode;

            public DeserializingModelBinder(SerializationMode mode) {
                _mode = mode;
            }

            public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) {
                if (bindingContext == null) {
                    throw new ArgumentNullException("bindingContext");
                }

                ValueProviderResult vpResult;
                bindingContext.ValueProvider.TryGetValue(bindingContext.ModelName, out vpResult);
                if (vpResult == null) {
                    // nothing found
                    return null;
                }

                MvcSerializer serializer = new MvcSerializer();
                string serializedValue = (string)vpResult.ConvertTo(typeof(string));
                return serializer.Deserialize(serializedValue, _mode);
            }

        }

単なるModelBinder...。Page.ViewStateなわけじゃないですね。単にシリアライズするためにViewStateと同じものを利用してるだけです。最初の方にも書いたけど、ViewStateと同じようにシリアライズ出来るようにするためにEncryptedやSignedが指定出来るようになってるって事です。ちなみにソースを見てみると、暗号化ViewStateを生成するためにprivate sealed class TokenPersister : PageStatePersisterっていうクラスを定義してて、その中でPageクラスのインスタンスを生成し処理させてます。なんか強引。でも、AntiForgeryDataSerializerでも同じ事してたりしてちょいビックリ。

そんなことはいいとして、これが完全に独自のModelBinderになってしまってるので、これを使う場合にはDataAnnotationsが効かない。ので、hidden書き換えを抑制したいときにはEncryptedやSigned、EncryptedAndSignedを指定しておくようにしないとね!

なぜASP.NET MVCにViewStateを持ち込むんだ~、と怒り心頭な方!ご心配なく。WebFormsでいうところのViewStateでは無かったです。ホッとした。

Futuresのソースみてて気がついたんだけど、AsyncControllerがFuturesに戻されてる。Expression系のユーティリティクラスがたんまり入ってて何やら面白そうな予感がしますが、それはまた今度ってことで。

2009年10月4日日曜日

ValidationAttributeがちょっと違う

ASP.NET MVC V2も既にPreview 2が出ましたね。入力検証がDataAnnotationsを前提にした設計(IDataErrorInfoじゃ物足りないしね)になってるのと、Templateベースのモデル描画がV2での重要ポイントですよね。

入力検証にxValと同じようにModelValidatorProviderとクライアントサイドバリデーションにjQuery.validateが導入されてのが興味深い。

そもそもV1にも追加コードとして公開されてるDataAnnotationsModelBinderがあって、System.ComponentModel.DataAnnotationsのValidationAttrbiuteを使えるようになてます。が、残念ながら標準アセンブリじゃなく別途配布されてるDLLを参照設定して使うようにしないと、全く機能しないというのがあります。何でかというとValidationAttributeクラスの実装が違うのと、その検証属性の実行方法が違うから。

.NET 3.5 SP1に含まれるVaridationAttributeクラスってIsValid(object value)なのに対し、配布されてるアセンブリではIsValid(object value)に加え、IsValid(object value, ValidationContext validationContext, out ValidationResult validationResult)があります。入力値を取得して検証するだけなら対象となる、プロパティ値があればそれで事足りるかも知れないけど、たとえばCompareValidatorは実装できないですよね。同じモデルインスタンス内の他のプロパティを見れないと比較できないじゃないですか。V1の場合は、特殊なアセンブリだからいいかもしれないけど、V2は標準アセンブリだからVaridationAttribute実装にValidationContextなんて無い(実はある?)。先日1.0がリリースされたxValが使用するアセンブリも標準アセンブリ参照だから当然インスタンス参照なんてない。

と、言うわけで、V1でxValを使うときの入力検証でモデルの他プロパティを参照する際のCompareAttributeクラスを書いて遊んでみました。何に使うのかは後で考える。

まずは、モデルインスタンスを渡せるIsValidを実装するためのインターフェイス定義。ValidationAttributeとそのインターフェースを実装するCompareAttributeクラスを定義。

  public interface IInstanceValidationAttribute
  {
    bool IsValid(object instance, object value);
  }

  public class CompareAttribute : ValidationAttribute, IInstanceValidationAttribute
  {
    public string PropertyName { get; set; }
    
    public CompareAttribute(string propertyName)
    {
      PropertyName = propertyName;
    }

    public override bool IsValid(object value)
    {
      throw new NotImplementedException();
    }

    public bool IsValid(object instance, object value)
    {
      var property = instance.GetType().GetProperty(PropertyName);
      if(property==null)
        throw new ArgumentException("パラメータ間違えてるよ");

      var targetValue = property.GetValue(instance, null);
      if (targetValue != null && targetValue.Equals(value))
        return true;

      return false;
    }
  }

CompareAttributeを使うモデルクラスの定義。他にも必須チェックやら付けてみる。

  public class Drink
  {
    [Required]
    [StringLength(10)]
    public string Name { get; set; }

    [Compare("Name",ErrorMessage = "一致しないよ!")]
    public string CheckName { get; set; }
    
    [Range(10, 50)]
    public int Size { get; set; }
  }

続いて、xValのサンプルを参考に、入力検証を実行するためのDataAnnotationsValidationRunnerクラスを実装。

  internal static class DataAnnotationsValidationRunner
  {
    public static IEnumerable<ErrorInfo> GetErrors(object instance)
    {
      var attrs = from prop in TypeDescriptor.GetProperties(instance)
.Cast<PropertyDescriptor>() from attribute in prop.Attributes.OfType<ValidationAttribute>() select new { Property = prop, Validator = attribute, IsInstanceValidator = attribute is IInstanceValidationAttribute }; foreach(var attr in attrs) { bool isvalid; if (!attr.IsInstanceValidator) isvalid = attr.Validator.IsValid(attr.Property.GetValue(instance)); else isvalid = (attr.Validator as IInstanceValidationAttribute)
.IsValid(instance, attr.Property.GetValue(instance)); if (!isvalid) yield return new ErrorInfo(attr.Property.Name,
attr.Validator.FormatErrorMessage(string.Empty),
instance); } } }

なんかちょっとダサいけど、まぁ、その辺はセンスが無いってことで勘弁してください。 最後にControllerにアクションを追加してコードは完成。

    public ActionResult Drinks()
    {
      var model = new Drink();
      return View(model);
    }

    [AcceptVerbs(HttpVerbs.Post)]
    public ActionResult Drinks(Drink model)
    {
      var errors = DataAnnotationsValidationRunner.GetErrors(model);
      if (errors.Any())
        new RulesException(errors).AddModelStateErrors(ModelState,"");
      
      return View(model);
    }

実行したのが↓これ。

xVal xVal2 

ここまでが、V1の話で、ここからV2 P2で同じことをしてみようと思います。こんなに違うのかと思えるほどDataAnnotationsの組み込みと、ModelValidatorProviderに感動です。CompareAttributeクラスとモデルクラス(Drinkクラス)は一切いじりません。追加で作成の必要なクラスは以下の1つのみ。

  public class CompaireValidator : DataAnnotationsModelValidator<CompareAttribute>
  {
    public CompaireValidator(ModelMetadata metadata, ControllerContext context, CompareAttribute attribute) : base(metadata, context, attribute) { }
    internal static ModelValidator Create(ModelMetadata metadata, ControllerContext context, ValidationAttribute attribute)
    {
      return new CompaireValidator(metadata, context, (CompareAttribute)attribute);
    }

    public override IEnumerable<ModelValidationResult> Validate(object container)
    {
      if (!(Attribute as IInstanceValidationAttribute).IsValid(container, Metadata.Model))
        yield return (new ModelValidationResult
                        {
                          MemberName = Metadata.PropertyName, 
                  Message = Attribute.FormatErrorMessage(Metadata.GetDisplayName())
                        });
    }
  }

すごいっすね~。たったこれだけ。で、このValidatorクラスを登録するためにGlobal.asax.csのApplication_Startに1行追加。

    protected void Application_Start()
    {
      RegisterRoutes(RouteTable.Routes);
      DataAnnotationsModelValidatorProvider.RegisterAdapter(typeof(CompareAttribute), typeof(CompaireValidator));
    }

プロパティーの取出しからAttributeの取り出しまで一切合財がMVCの処理の範疇になってます。DataAnnotations関連のProvider/Validatorを使って作ってるけど、この辺も自分でベースクラスから派生させたり、AssociatedValidatorProviderからの派生で作ることも可能。もちろん簡単なのはDataAnnotations関連のクラスを派生させて作ること。

ちなみにControllerでの使用は何も考える必要も無く以下のようにしとくだけ。

    public ActionResult Drinks()
    {
      return View(new Drink());
    }

    [HttpPost]
    public ActionResult Drinks(Drink model)
    {
      if(ModelState.IsValid)
      {
        // ... success code
      }
      return View(model);
    } 

ValidationAttributeクラスは標準クラスのままなので、モデルクラスのインスタンス参照を持ってないのは今後もそうだと思うので、今回のような方法でうまいことインスタンスを渡せるオーバーロードを定義することで、対応していくのがいいんじゃないかと思う次第です。

ASP.NET MVC Reference
Brad Wilson: Enterprise Library Validation example for ASP.NET MVC 2
Shaan's Official Blog : New features in ASP.NET MVC 2 Preview

2009年9月26日土曜日

アイスホッケーのルール改定

最近アイスは試合に出てないので知らなかった(レフェリークリニックにも出てないし)んだけど、ルール改定があると語りべさんとこで知りました。戦国時代到来!<1> | 加藤じろう直営!「語りべ」通信

レフェリー2人制とアイシング後のチェンジ禁止。NHLでは既に実施されてるルールだし、これはとてもいいルール改定だと思うな~。アイスホッケーの最大の問題点は「レフェリーに見られなければ反則も反則にならない」部分。どんな競技でもそうだけど。ホッケーって使ってる道具が凶器にもなる危険なものだから悪意をもってそんなことするとすぐケガするし、感情的になって報復行為に行ったりする人をかなり見かける。

上手なプレーヤーもそんな人がいたりして、スポーツマンシップはどこへやら。チェックに行くのとは意味が違う部分の話ね。そこに来てレフェリー2人制は希望を感じる。それを県連レベルでもちゃんとやれればの話だけど。県連レベルで実施することの最大の懸念は「判定基準の同等な2人のレフェリーを常に立たせることが出来るのか」につきる。とりあえず、県連ローカルルールでやるような社会人リーグではどこも適用しなさそうな雰囲気。そんなに沢山(ラインズマンいれて4人必要になる)レフェリー用意できないっていうのは致し方ないのかもね。

アイシングでのチェンジ禁止はクリアしかしないようなディフェンスのいるチームではかなり致命的。でも、ホッケーを楽しむ(試合の勝ち負けだけが楽しみだとあれだけど)にはいいルール。で、こちらについてはルールブックの条文にきちんと記載があって「412条の2 ラインズマンがアイシングコールをするためにハンドアップをした後...」ってなってる。ラインズマンのハンドアップが遅かったり、フロントがアイシングを取ったけど、バックがハンドアップしてないとかになると大荒れの予感。とりあえず今シーズンはAプールのみの適用で様子見みたいだね、神奈川は。

話変わって今日は先週に続けてアメージングで試合。3戦目にしてやっと勝てた。オオニョさんがいなくてマジ助かった。スガマタさんに続いて今期から参戦してくれた新ゴーリーのアオキさんの活躍がすばらしかったデス。なんか最近のS40蒲公英はゴーリーに恵まれてきてる気がする。しかも帰りに寄ったスタバで初めて黒エプロンのバリスタを見た。ちょっと感動した。

2009年9月23日水曜日

SHURE

Amazon.co.jp: Ultimate Ears MetroFi 220 MF220: 家電・カメラ

これをさ~、気に入って使ってたんだけどさ~。

断線。半年で。泣ける。

代わりになる物を探しに電器屋巡りしたけど、置いてないんだね。みんなiPhoneで音楽聞くのと通話とするときどうしてるんですかね。Bluetoothとか充電面倒じゃないっすか?面倒でくじけたんだけど...。

結局、今回もAmazonのお世話になることにしようと思うんだけど、少し学習してイヤホンとマイク部は別々に出来るようにまずは↓これ。

Amazon.co.jp: SHURE iPhoneでの高品位なハンズフリー通話を可能にするマイク付き接続コード MPA-3C: 家電・カメラ

んで、なるべく素の音がいいから、イヤホンは↓これ。

Amazon.co.jp: SHURE 高遮音性イヤホン・ブラック SE102-K-J: 家電・カメラ

写真 

連休が空ける前に届いてくれて嬉しす。明日からの通勤が楽しみだぜ~。遮音って怖い気もするけど、気配を感じ取れるように修行します。

2009年9月21日月曜日

Routeでデフォルト省略させたくない

GenerateLink does not return the control name and the view name only for the INDEX view - Stack Overflow

これってデフォルト値と同じパラメータを省略(後方に有効値パラメータ無しの場合)させずに、URLを取得出来るようになればいい、っていう問題だったり?

  routes.MapRoute(
    "Default",                                              // Route name
    "{controller}/{action}/{id}",                           // URL with parameters
    new { controller = "Home", action = "Index", id = "" }  // Parameter defaults
  );

↑こういう登録をしてる場合に、

  var url1 = Url.RouteUrl("default");
  var url2 = Url.RouteUrl("default", new { controller="Home" });
  var url3 = Url.RouteUrl("default", new { controller = "Home", action="Index" });

↑これは全部"/"と出力されるけど、これが"/Home/Index"になって欲しい、と。違うなら意味なくなっちゃうからそういう質問だと仮定。

やっぱりRouteクラスを派生させてGetVirtualPathのoverrideでしょ!

  public class FullRoute : Route
  {
    public FullRoute(string url, IRouteHandler routeHandler): 
base(url, routeHandler) { } public FullRoute(string url, RouteValueDictionary defaults,
IRouteHandler routeHandler):
base(url, defaults, routeHandler) { } public FullRoute(string url, RouteValueDictionary defaults,
  RouteValueDictionary constraints, IRouteHandler routeHandler):
base(url, defaults, constraints, routeHandler) { } public FullRoute(string url, RouteValueDictionary defaults,
RouteValueDictionary constraints, RouteValueDictionary dataTokens,
IRouteHandler routeHandler):
base(url, defaults, constraints, dataTokens, routeHandler) { } public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values) { var dict = new RouteValueDictionary(Defaults); values.ToList().ForEach(kv=>dict[kv.Key] = kv.Value); var url = Url; dict.ToList().ForEach(kv => url = url.Replace(string.Format("{{{0}}}", kv.Key), (string)kv.Value)); url = Regex.Replace(url, @"{([\w].+)?}", ""); url = Regex.Replace(url, @"(\/){2,}", ""); var path = base.GetVirtualPath(requestContext, values); path.VirtualPath = url; return path; } }

※途中のパラメータが空白の時は考慮してないのであしからず。なんかthrowすべきだよね~。

これを使うようにRouteTableに追加。

  routes.Add("FullDefault",
    new FullRoute(
      "{controller}/{action}/{id}/{foo}/{bar}",

      new RouteValueDictionary(new { controller = "Home", action = "Index", id = "", foo = "", bar = "" }),
      new RouteValueDictionary(),
      new MvcRouteHandler()
  ));

※idより後ろのパラメータは特に意味なしデス。

そうすると

  var full1 = Url.RouteUrl("fulldefault");
  var full2 = Url.RouteUrl("fulldefault", new { controller = "Home" });
  var full3 = Url.RouteUrl("fulldefault", new { controller = "Home", action = "Index" });

これが全部"/Home/Index"を返すようになります。

こういう事でいいんですかね?って英語でどうやって答えればいいのか分からない...。

2009年9月20日日曜日

戸塚ホッケー事情

MHLのシーズンも始まって、この土曜で既に2試合消化したけど、どっちも負けたから特に書く事もない...。切ない。とりあえず、あれだ。初戦はゴーリーいなかったからいいとして、2戦目はひどい。あれじゃ、100戦しても100回負ける。もう少し賢く守りましょう。新規加入のゴーリー、スガマタさんはとてもすばらしいゴールテンディングを見せてくれてたよね?後はディフェンスの問題だけなんだから。

で、試合の話はいいとして、今日戸塚でみんなでおしゃべりしてる時に、戸塚に誰がいるのか誰か来るのかを簡単にみんなで共有できるようにするのに、ミチアキさんが「Twitterどう?」って話になったよね。その場にいた数人には説明したけど、なかなか言葉だけだと分かりにくいと思うので、みんなでガンガン使ってみよう!

http://twitter.jp

ネット関連に疎い人も多いと思うけど、スズキ君にはサヨちゃんが上手いこと説明して登録するようにしといてね!S40組にはオレが説明するとして、クラッシャーズ組にはヤマケン選手よろしく!後は、ちょっとづつ広めていきましょう。クローバー組はタカオ?

Twitterって何?って言う人も多いと思うけど、検索すればヤマのように説明出てくるから何とかなるよ。ケータイからのアクセスは...ケータイ持ってないからよく分かんないよ!モバツイッターとか?

そのままTwitterにつぶやいても、メッセージが流れてよく分かんなくなると困るので、戸塚スケートファクトリー用のハッシュタグとして Totuka Skate Factory Hockey の頭文字を取って#tsfhとしてみる感じでどうっすか。つぶやきの最後に#tsfhって書いてつぶやくだけ。そしたらTwitter / Search - #tsfhここにアクセスすればいい、戸塚の状況がすべて見られるようになるっていう寸法。

twitter

戸塚に行く前・着いてから・誰かを誘う時、いろいろつぶやいてみたら便利なんじゃないかね~。

追記

大事なことを書き忘れてた。Twitterでたけはらをフォローするときのアカウント名はtakeparaデス。

dotnetConf2015 Japan

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