kazuk は null に触れてしまった

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

月別アーカイブ: 3月 2011

Multi Project な Project Template を作成する


元ネタは

How to: Create Multi-Project Templates
http://msdn.microsoft.com/en-us/library/ms185308.aspx

なんだけど、一回作ってる最中にうまくビルドできなくなったので、メモとりながら「やってみる」という blog

要 Visual Studio SDK です。

プロジェクトテンプレートのプロジェクトを作成。ソリューションエクスプローラー上での構造があうように作る。

imageimage

<?xml version="1.0" encoding="utf-8"?>
<VSTemplate Version="3.0.0" 
            Type="ProjectGroup"
            xmlns="http://schemas.microsoft.com/developer/vstemplate/2005">
  <TemplateData>
    <Name>LibWithUnitTest</Name>
    <Description>&lt;No description available&gt;</Description>
    <Icon>LibWithUnitTest.ico</Icon>
    <ProjectType>CSharp</ProjectType>
    <RequiredFrameworkVersion>2.0</RequiredFrameworkVersion>
    <SortOrder>1000</SortOrder>
    <TemplateID>526730eb-c930-4614-8398-64a21c9f008a</TemplateID>
    <CreateNewFolder>true</CreateNewFolder>
    <DefaultName>LibWithUnitTest</DefaultName>
    <ProvideDefaultName>true</ProvideDefaultName>
  </TemplateData>
  <TemplateContent>
    <ProjectCollection>
      <ProjectTemplateLink ProjectName="Implementation">
        ClassLibrary\ClassLibrary.vstemplate
      </ProjectTemplateLink>
      <ProjectTemplateLink ProjectName="UnitTest">
        UnitTest\UnitTest.vstemplate
      </ProjectTemplateLink>
    </ProjectCollection>
  </TemplateContent>
</VSTemplate>

あれ、ビルドできた。(ぉぃ

んでプロジェクトの追加したら普通にできた。(ぉぃ

 

企画倒れ!

…となぜ最初のは失敗したのかなと。 .cs のファイルプロパティで、ビルドアクションは「なし」にしてないと駄目。子プロジェクト側 vstemplate のビルドアクションは「なし」にした方が良いみたい。(VSTemplate ビルドアクションになってるとその分 Project Template の zipが吐かれますね)って事で、ファイルプロパティの設定間違ってるだけでした。

imageimage

っていう感じですね。

ってここまで書いてからこんなの見つけちゃった。

Multi-Project Templates with Wizard: Visual Studio 2010 Sample

http://vsix.codeplex.com/

プロジェクト作成時の Wizard まで実装してるね!コッチでも子プロジェクトの vstemplateのビルドアクションは無しになってますね。

http://vsix.codeplex.com/SourceControl/changeset/view/61761#1354286

<ItemGroup> <None Include="Children\Windows Library\ProjectTemplate.vstemplate" /> </ItemGroup>

ほら、無し(None)でしょ。

jp110311 の開発で得られた ASP.NET / 携帯 / Azureでの開発ノウハウあれこれ


サンプルをちょっと書いて乗せてみる程度では得られなかった知見という事で簡単に箇条書きしてみます。

Azure Table の continuation は少なくとも Web Frontend なアプリケーションでは無意味だし不要

今回は携帯対応という事で、どうせ表示できるのは多くても数十件です。この時に1000件単位での continuation はおおよそ無意味で、バッチで舐めるような操作でも作らない限りには不要です。

んで、ページング操作ですが、基本的には RowKey を並べたカラムを持つレコードをインデックスレコードとして作り、このインデックスに並んだ RowKey を or で並べて select します。((PartitionKey=’PK1’) and (RowKey=’RK1’)) or ((PartitionKey=’PK2’) and (RowKey=’RK2’)) を選択するのは普通には以下の形になります。

はい、いきなり式木操作です。

var qidx = context.CreateQuery< Indexのエンティティ型 >( “table” );
var indexEntity = qidx.Where( idx=>idx.PartitionKey== パーティションキー
                   && idx.RowKey== “INDEX” ).SingleOrDefault();
if( indexEntity==null ) return Enumerable.Empty< 戻りエンティティ型 >();

ParameterExpression param = Expression.Parameter( typeof( 戻りエンティティ型 ) );
MemberInfo miPartitionKey = typeof(戻りエンティティ型).GetProperty( “PartitionKey”);
MemberInfo miRowKey = typeof(戻りエンティティ型).GetProperty( “RowKey”);
Expression predBody = null;

foreach( var k = indexEntity.IndexColumn.Split( “;” ) )
{
    if( predBody==null )
    {
        predBody = Expression.And(
             Expression.Equal( Expression.MakeMemberAccess( param,miPartitionKey ),
                                           Expression.Constant( パーティションキー, typeof(string) )),
             Expression.Equal( Expression.MakeMemberAccess( param,miRowKey ),
                                           Expression.Constant( k, typeof(string ) );
    }
    else
    {
        predBody = Expression.Or(
             predBody,  
             Expression.And(
               Expression.Equal( 
                        Expression.MakeMemberAccess( param,miPartitionKey ),
                        Expression.Constant( パーティションキー, typeof(string) )),
               Expression.Equal( Expression.MakeMemberAccess( param,miRowKey ),
                                             Expression.Constant( k, typeof(string ) );
    }
}
if( predBody==null ) return Enumerable.Empty<戻りエンティティ型>();
var pred = (Expression<Func< 戻りエンティティ型,bool >>)
                 Expression.Lambda( predBody, param );
var q = context.CreateQuery< 戻りエンティティ型 >(“table”);
return q.Where( pred ).AsEnumerable();

この (PartitionKey and RowKey) or (PartitionKey and RowKey ) といように パーティションキー、 ローキーをそれぞれ評価しての形のクエリだと、10件程度をとれるクエリまでは正しく処理されるようです。それ以上になるとクエリが長すぎる的エラーで結果が返りません。(クエリの長さなので、パーティションキーやローキーのデータ項目長によって可能な件数が変わるでしょう)

これを変形して PartitionKey=X and ( RowKey=A or RowKey=B ) の様に RowKey だけを or 続きでつないでいくことができます。この形式でとる方が PartitionKey が限定されてエンティティグループトランザクションがかかりやすい、分散クエリにならないはずなので早いという事になります。都度都度 PartitionKey を指定しない分 or 項数を増やせ結果的に一発でとれる件数が増えます。

RowKeyの設計次第ですが、これでとれるのは最大でも数十件(多くて30ぐらい)という所になります。

普通の Web アプリの場合にはこれで数十件が取れれば十分よね(アプリケーションの画面でのページング単位は大抵の場合数十件)って事で、アプリケーション画面の設計に合致しやすい形になります。

50件ぐらいをとりたい場合はフェッチ密度によって戦略が解れます。フェッチ密度が十分に高い場合(各行のサイズが小さく、選択範囲内で75%以上の行をフェッチするなら)にはRowKey の Min/Max で範囲を抽出し、indexで示すレコードのみにフェッチ後にフィルタします。

var qidx = context.CreateQuery< Indexのエンティティ型 >( “table” );
var indexEntity = qidx.Where( idx=>idx.PartitionKey== パーティションキー
&& idx.RowKey== “INDEX” ).SingleOrDefault();
if( indexEntity==null ) return Enumerable.Empty< 戻りエンティティ型 >();
var rowKeys = indexEntity.KeyListColumn.Split(“;”).Skip(start).Take(max).ToArray();

string minKey = rowKeys.Min();   // Enumerable.Min/Max には string 版はありません
string maxKey = rowKeys.Max(); // 拡張メソッドで増やしてあげて下さい。
var q = context.CreateQuery< 戻りエンティティ型 >(“table”);
return q.Where( item=> item.PartitionKey==パーティションキー
                && string.Compare( item.RowKey, minKey)>=0
                && string.Compare( item.RowKey, maxKey)<=0 )
            .AsEnumerable()
            .Where( item=> rowKeys.Contains( item.RowKey ) );

フェッチ密度が低い場合は最初のアプローチでの RowKey 指定で複数列を選択するのをループする形になります。

INDEX ってレコードもってないよ!って場合には実際としてどんな RowKey でとればいいかは自然キーで推測可能という事でない限り解りません、1000件帰ってくるかもしれないクエリ打ってみるしかないになりますので、面倒でも何等かINDEXレコードを持たないと多分性能は出ません。

 

サービスクラスの Testability(テスト可能性)を確保するためのパターン

BbsEntityServicesに実装されています。

http://jp110311.codeplex.com/SourceControl/changeset/view/7674#90653

  1. 開発ストレージに振り向けるための仕組みを持ちましょう。
  2. SaveChanges の発行はメソッドを用意し集約し、SaveChanges発行前イベントを持ちましょう。

1 は開発ストレージにテストでのアクセスを振り向ける為に必要です。CloudStorageAccount.FromConfigurationSetting で通常 CloudStorageAccount を取得しますが、このメソッドは RoleEnvironment がしっかりあって、アプリケーションの初期化コードをちゃんと通らないと動作しません。(SEH例外というイヤーな物が飛んできます)

要するに普通に MsTest テストコンテナでは動くはずもない物なので単純にそれを使ってストレージアカウントにアクセスしているとテストコンテナ内では動きません。

Azure SDK 1.3 まででは開発ストレージではエンティティグループトランザクションは未サポートで、動作しないだけでなく、エンティティグループトランザクションの指定である SaveChangesOption.Batch を指定するとエラーになります。このため、開発ストレージではエンティティグループトランザクションの実際の動作テストはおろかエンティティグループトランザクションを利用するコードのテストもできません。(all or nothing にならないだけでいいのに)

このため、開発ストレージを使っている場合にはエンティティグループトランザクションをかけないようにするコード対応が必要となります。

2 はエンティティグループトランザクションの衝突等のテストをする為に必要です、発行前イベントでわざと競合するような更新をしたうえでエンティティグループトランザクションが適切に all or nothing に落ちていることを確認する必要があります。

 

UseUri でのセッションステートを使う時は POST を受ける前にセッションを保存させなければならない

jp110311 では UseUri で携帯向けにクッキーを利用しないセッションステートを設定しています。この場合にセッションに何も保存していない場合、 POST 要求を初めて受けるとASP.NETはSessionID を確定させようとし、セッションが未保存であれば保存した上でリクエストをリダイレクトします。

このリダイレクトによって POST が失われてしまい、色々な操作が失敗する事になりました。

http://jp110311.codeplex.com/SourceControl/changeset/view/7674#94558

BaseController.cs の OnActionExecuting が回避方法です。要するに新規セッションであれば無意味でもとりあえず値を書き込んで、Dirty にし Session がリクエストの終了時に保存されるようにしています。

実は jp110311 のサイト全体として、セッションはログイン維持の為にしか使っていません、ASP.NETの認証インフラがセッションを使っているだけで、アプリケーションとしては使っていないという形です。「セッションなんて使わねーよ、ばーかばーか」です。

Azure Storage を使うセッションステートプロバイダは独自実装しようと思ったんですが、first release をとにかく早く出したかったって事で出来あいの物を使ってます。

こいつはいくつか問題を抱えてます。

  1. 770行目あたり、 itemData と staticsData を reader.ReadLine で読むところ、セッション保存されてない場合にはこれらは null になりますが Convert.FromBase64String に渡してて ArgumentNullException を飛ばされます。
  2. Expire処理が全般として実装されてませんので、溜まるいっぽうになります。
  3. StorageAccount 回りのテスト可能性の所が全く考慮されてないので、テストが死ねます

 

UseUri でのセッションステートを使う場合 HtmlHelper.BeginForm() は使えない

使えないのはパラメーター無しのメソッド、HtmlHelper.BeginForm( null ) で!でないとフォームのactionがUrlセッションがついてないアドレスに飛ばされ、リダイレクトされてPOSTデータが取れない。

災害対応用サイト jp110311 Liveしました


公開URL

http://jp110311.cloudapp.net/

現状、郵便番号単位での地域掲示板機能が完成しているという事で公開します。

郵便番号単位としているのは「炊き出しの場所」「仮設トイレの場所」等、地域に密接した情報の交換その他を支援する事を目的としている為で、広域情報は報道や役所に任せて細かい情報交換に使ってもらえればという考えからです。

 

以下、iMode シミュレータでのスクリーンショット各種

サイトトップ ログオン(1)(2)、ユーザー登録画面(1),(2)、ログオン後のTOP、掲示板投稿一覧、タグ追加画面、タグ一覧画面、投稿画面

image image image image image image image image image image

 

 

謝辞

協力いただいたメンバー各位 http://jp110311.codeplex.com/team/view

災害対応用としてAZURE312を対応いただき、codeplex 等既存の物もたっぷり使わせていただいております。

 

続いてユーザー間でのメッセージ交換等を実装していきますが、とりあえず First Release にあたってお礼を申し上げます。

また、自分の Twitter TL上でさまざまな人にアドバイス頂きましたみなさんにもお礼を申し上げます。

jp110311 プロジェクト進捗報告(1)


とりあえず、プロジェクト設定直後でのディスカッション

jp110311 – View Discussion での設定によるファーストマイルストーンに向けて進行中です。

都道府県市区町村では単位が大きすぎるとの考えから郵便番号レベルで分割された情報交換、情報集積を行えるサイトとして立ち上がればいいと考えています。

 

Azure 上に上げる予定なんで、まず最初にネックになりそうな認証プロセスのASP.NET標準から Azure Table 利用版への載せ替えを行い、ユーザー登録処理を実装しました。

(現状エラーチェックとか甘々なんですけど、 developers 各位には validation ルールとか埋めていただけるとうれしいです)

ユーザーの管理は基本的に郵便番号がパーティションキーになるようにしてるので、登録人数全体量によるスループットの低下は無いと思いたいです。

 

動いてる範囲のスクリーンショットを以下

image image image image image image

携帯に合わせた HTML に画面HTMLをチコチコいじってくれる人いればドンドン修正コミット入れていただきたく。

 

自分はとりあえず、掲示板本体系のデータモデルの設計してバックエンドストレージの実装に向かいます。

参加してくれている各位、ありがとう。 被災地の皆様、頑張りましょう。

[VS Tips] WMI クラスをサーバーエクスプローラーで一括削除する方法


WMI 便利ですよね。 クラス追加して、WebManagement 配下とか一気に取り込んで必要な物をマネージクラスの生成すればすぐにそれを操作したり、情報とるプログラムが書けます。

imageimage

imageimage

これすると当然にサーバーマネージャの管理クラス配下はすさまじく大量なクラスであふれる事になりまする。

これを消すのには…「クラスの追加」のダイアログで一気に選んで「削除」です。

追加ダイアログでやるとは中々思わないので Tips って事でした。