kazuk は null に触れてしまった

C# / .NET 系技術ネタ縛りでお送りしております

Custom TypeDescriptionProvider で1レイヤ下から攻略する


System.ComponentModel.DataAnnotations 便利ですよね、アノテーションしっかりしてやって入力検証とUI構築をゴニョれば ASP.NET Dynamic Dataとか、同様にASP.NET MVC のモデル検証系なんかの面倒も見ているってわけで、これからの入力検証やその他 Naked Objects への方向性へ向かう基盤ライブラリって感じですかね。(必ずしもNaked Objectsは良いとは言い切れないんだけど、Naked Object にも良い点がイッパイあるんで良いとこどりができるのであれば取り組みたい素材ですな。)

ただねー、問題もいくつか無かったりもする。

  1. 顧客用語って問題。「品番」「型番」「商品コード」、実はこれら顧客用語でシステム内部的には string ItemIdってケース。Display属性は複数指定できないし、複数指定できたとしても今度はドレをだせば良いのさ!って問題に。(これは検証エラーのエラーメッセージとかにも影響を与える)
  2. 顧客固有コード空間に対する検証。顧客用語とおんなじ例だけど、ある顧客は ItemIdのフォーマットとして [A-Z][A-Z0-9]*(-[A-Z0-9]+)? しか許さない、長さは18文字までの可変長。とある顧客は 16文字の固定長で大文字英数字、8桁目と12桁目にハイフンが必須。「おぉっと、属性で検証条件をコードにマークアップって良いのか?」

1の方にはリソース化とかを介してメッセージフォーマッターでなんとかすれば良いって意味で逃げ道があるんだけど、2には根本的には逃げ道無かったりする様に見える。

この2に対する逃げ道のお話。

 

ここで .NET Framework の(ASP.NETやWinFormsその他)各種コントロールのデータバインディング機構や DataAnnotations がどっからどのように型情報を取ってるかの順序の超おおざっぱな話が以下の通り(最底辺はちょっとあやふや)

  1. System.Runtime.Remoting.IRemotingTypeInfo とかシステムの外界、リモートオブジェクトでない場合にはアセンブリのメタデータ
  2. System.Reflection から得られる System.Type。1で得られた型情報を System.Type 型に見せているのがここ
  3. System.ComponentModel.TypeDescriptorから得られる MemberDescriptor、 TypeDescriptor.GetProperties とかで取れる PropertyDescriptorやEventDescriptor

3に対するお客は色々多岐に分かれるんだけど、主にアプリケーション層に属する人たちや、Visual Studioでのデザイン時動作等、本当にめっぽう多岐にわたる。

たとえば、 ASP.NET DynamicForms なんかも3の層を介して型情報を貰ってるわけ。証拠はこんな感じ。

TableProvider.GetTypeDescriptor メソッド (System.Web.DynamicData.ModelProviders)

要するに、この TypeDescriptorを Custom TypeDescriptionProvider を使ってカスタムすると、DataAnnotations でのモデル検証に関する属性類をランタイムに!動的に!まるっと!全部!置き換えできるって寸法。

こいつらカスタムする時はみんな派生クラスを作らないといけないんでちっと面倒なんだけど、派生クラスを作る必要がある物は以下の物。

  • TypeDescriptionProvider
    • 基底コンストラクタが2通りある、誰かに親になってもらうとbaseがそれなりに動くようになるので誰かに親になってもらうのが吉、.NET FrameworkのデフォルトのTypeDescriptionProviderは隠されて実装されているので親のなり手を探すのに困る。自分のやってる方法としてはTypeDescriptor.GetProvider(typeof (object))とすれば少なくとも object に反応してるTypeDescriptionProviderが取れるんで、親になって頂く(objectに対するTypeDescriptorを変えてるとか超絶アブストラクションしてる所ではうまくないかもしれないけど、そんな変態環境で動かないのは「仕様です」)
    • GetTypeDescriptorをoverrideする。って言っても parent になってくれている TypeDescriptionProvider にbaseに一回ICustomTypeDescriptorを作ってもらってからそれを舐めながら変換するので↓で用意する CustomTypeDescriptor の派生を new する時に base.GetTypeDescriptor で帰ってきた物を渡すだけ
  • CustomTypeDescriptor
    • 本来的には ICustomTypeDescriptor を実装するんだけど、こいつを派生すれば余計な事をしないで済むので楽
    • GetProperties をオーバライドする(のが今回の目的)、PropertyDescriptorCollection を base.GetProperties した結果を元にいじる。だけど PropertyDescriptorは根本 immutable なんで出来た物はいじれない。複製しながらいじるんだけど、PropertyDescriptor のコンストラクタは protectedなんで派生しないと駄目。複製する時にコンストラクタに渡す Attributes[] が今回入れ替えようとしている属性セットなんで、これをドッカからホゲって取ってくる様にする。
  • PropertyDescriptor
    • PropertyDescriptor のコンストラクタがprotectedだから(ケチ!)派生してるだけ、abstractなメソッドに対してはコンストラクタで貰える MemberDescriptor/PropertyDescriptorのインスタンスを呼べばよろしいわけ。実装してれば気付くと思うけど、ここの GetValue とか SetValue をどこか遠くにつれていくと各種コントロールからデータバインディングを経由して送り込まれるデータをよろしくできる。

んで、これらの派生クラスを作ったうえである型についてCustom TypeDescriptorによる属性セットの置き換えをしたければ TypeDescriptionProviderAttribute でその型をマークアップするか、TypeDescriptor.AddProvider で実行時にプロバイダを突っ込む。

 

本来的には存在しない物を PropertyDescriptor を作ってさえあげればプロパティがある様に見せかける事とか、親なんて無視でフルカスタマイズすれば全く違う構造にプロパティセットやイベントセットがある物として見せる事だって可能。やりすぎるとプログラムの構造と実行時の構造が乖離してわけわかめになるんで自殺行為なんだが DataAnnotationsでの検証がコードで固定されるのがネックならば手を出す価値ありですぞ。

 

こうして Custom したTypeDescriptorが入れ替え用の属性セットを取ってくる所が Custom VirtualPathProvider で乗っ取られてるとか乗っ取られてないとか…

「フレームワーク」と書いて「床下の魑魅魍魎」と読むのがよさげな実装を繰り返す奴にはご注意を

広告

コメントを残す

以下に詳細を記入するか、アイコンをクリックしてログインしてください。

WordPress.com ロゴ

WordPress.com アカウントを使ってコメントしています。 ログアウト / 変更 )

Twitter 画像

Twitter アカウントを使ってコメントしています。 ログアウト / 変更 )

Facebook の写真

Facebook アカウントを使ってコメントしています。 ログアウト / 変更 )

Google+ フォト

Google+ アカウントを使ってコメントしています。 ログアウト / 変更 )

%s と連携中

%d人のブロガーが「いいね」をつけました。