今度はIActionHttpMethodProviderか。思ったけど、IActionHttpMethodProviderを検索すると出てくるのは↓この子達。AcceptVerbsAttributeHttpDeleteAttributeHttpGetAttributeHttpHeadAttributeHttpOptionsAttributeHttpPatchAttributeHttpPostAttributeHttpPutAttribute見慣れた属性。Providerといいつつ、Attributeそのもの。Methodだと判定ってことですね。一応HttpMethodsっていうゲッターがあるけど。大体わかってきましたね。ここでApiControllerActionSelectorのSelectActionに戻る。ここまでのコードで対象となりそうなActionが特定できたので、もう大丈夫な感じがするけど、実はここまでの流れで抽出したHttpActionDescriptorは配列だったりする。Getに対応するアクションを複数かけちゃうじゃないですか。そんな時にはちゃんとAmbiguousMatchっていうエラーにしたいからですね。APIテンプレートで作ると作成されるルーティング定義とValuesControllerは以下のようになってますね。"value";}わかりやすくGetだけ取り上げます。これ、ブラウザでそのままアクセスすると、それぞれ以下のように出てきますね。/api/values/api/values/1うん。Methodにそのまま対応するです。アクションメソッド名をいずれもFindに変更してみます。ルーティングにはまだ"action"を登録しないので、AcceptVerbsかHttpGetかいずれかの属性していが必要になるよね。"value";}これで先ほどと同じURLでアクセスする。面倒なのでスクリーンショットは乗せないけど、同じ結果です。Methodに対応するです。続いて、FindとGetそれぞれがApiControllerに定義されていた場合どうなるのか見てみる。"value";}エラーです。これがAmbiguousMatchです。Methodを解釈するアクションが複数存在する場合はエラーになるです。これには例外があって、ルーティング登録で”action”指定したものがあって、アクション名を規約や属性だけで判定する場合じゃない場合にはエラーになりません。分かりにくいので実装。アクション名を含んだルーティングを解釈できるようにRouteTableに登録。Global.asaxに以下の定義を追加。});}DefaultApiっていう名前で定義されてるのが、プロジェクトテンプレートで定義されるルーティングだけど、その前にDefaultApiActionsっていうのを定義してます。前に定義するのがミソ。ルーティングの解決順は登録順になるので。constraintsでactionは数値じゃないっていうルールを付加。これで、/api/values/1の1はactionとして解釈せず、DefaultApiの定義のほうのidとして解釈するルーティングに到達します。続いて、先ほどのApiControllerのGetとFindのうち、FindにつけたHttpGetとAcceptVerbsを削除しておきましょう。"value";}今回はアクション名をURLに含めることで、ルーティングするというのを確認するので、以下のURLにそれぞれアクセスします。/api/values/api/values/1/api/values/find/api/values/find/1わかりにくいけど、全部同じアプリケーションインスタンスに対してリクエストしてるよ。エラーにならずにそれぞれちゃんと取得できてるね!ルールその4:ルーティングの定義でactionを指定するようにすると、MVCと同じようにルーティングするです。うん!スッキリだね!あと、ApiControllerActionSelectorがIHttpActionSelectorの実装なんだけど、これどこで使われるのか検索してみると、出てくるのはSystem.Web.Http.Services.DefaultServiceResolver(IDependencyResolver実装)クラス。ここで、インターフェースと実装の紐付。ServiceLocator。classだけど、IDependencyResolverを自分で実装するなら、実装は差し替え可能ってことですね。登録してるから見てみるといいと思います。http://aspnetwebstack.codeplex.com/SourceControl/changeset/view/a0b7fe4a95fa#src%2fSystem.Web.Http%2fServices%2fDefaultServices.cs※あ。ソース変わってる?んー。最新取得してもこれとちょっと違うなー。最初のドキュメントを見てみるとなんて書いてるかなー。Site同じこと書いてるじゃないか!っていうオチ。NonActionについてまで書かれてて完敗。C#2012年3月2日金曜日ApiControllerで認証する際にログインページにリダイレクトしたくない@jsakamotoさんへそりゃそーですね!Apiなんだから。FormsAuthenticationの認証チケットを利用してWCFでもForm認証を使う、っていう話が前にありましたが、それはそれで王道なやり方じゃない、ですよね。APIとして認証するならOAuthとかなんでしょうかね。その場合の受けはHttpMessageHandler使ってやればいいのかな。いえ今回は受けじゃなくてレスポンスのですしたね。普通にAuthorize属性使うと401になるから問題無さそうだけど、MVC(WebFormsでも一緒)アプリケーションに組み込んだ場合、Form認証も入ってたりするはずなので、FormsAuthenticationModuleに途中でレスポンスコードを横取りされた結果、ログインページへのリダイレクト(302)になってしまう。なると、クライアントがブラウザならいいけど、APIを呼び出してるプログラムだったら、そんな~、ログインページとか困ります~、です。APIの標準テンプレートを使った場合に、以下のようにHomeControllerとValuesControllerを書き換えたとします。};}::}ログインページは以下のように変更。/>そうするとApiControllerにAuthorizeつけてブラウザでアクセスすると「ログインしてね!」が表示されます。ステータスコードは302のあとページ表示で200。コラー!なので、FormsAuthenticationModuleに書き換えられたステータスをカスタムモジュールで再度書き換えました。Web.configもモジュール使うようにしましょう。</system.webServer>そうすると、どーなるかっていうと。ステータスは401で中身なしになりました。用意しといたAuthorize属性のついた/home/accessdeniedにアクセスすると。Form認証と同じ挙動ですね。ログインページにリダイレクト。FormsAuthenticationModuleが仕込まれてると、これはもうHttpHandlerの実行タイミングじゃどうにもならないのtで、こういうやり方になると思います。標準で用意されてる認証モジュールって共存出来ないし。ApiControllerはHttpControllerHandlerから実行されるので、実質HttpHandlerですよね。途中でMessageをいじるっていうのはそこに対するAOP的な動作。それほどコードを書くわけでも無いので、コレでいいかなーって思いますが、いかがでしょーか?後は、素直に別サイトにしてしまう、とか...。beta面白いですねー。RESTfulなサービス実装を容易に実現するために導入されたApiControllerは強烈。APIとして生まれ変わったのがその実態です。APIなんじゃないかと勘ぐってるところです。ホントのところは本国の開発チームしか知らないんだろーし、そんな理由はDeveloperには関係なかったりもするんだけど、気になって仕方ない。その辺はこっちに書いてみました。根拠のない話ですから!フィクションですからね!で、ですね、ApiControllerですけど。これまでのMVCだとIController実装のControllerBase、それを派生させたControllerを利用してましたね。この名前が重要で、ApiControllerもControllerという名前。その実態はIControlerではなくIHttpController実装。はい~?何が違うの~?と、なりましょうね。どっちも名前はControllerってなってるし、使い方もだいたい一緒で、見た目の違いはRouteCollectionへの登録時にMapRouteなのかMapHttpRouteなのか、くらいですからね。いって、ApiControllerがIControllerの仲間だと思うのは大間違い!MapHttpRouteと、あえて違う拡張メソッドでの登録にしてるのには訳がある。今のところソースも無いし、詳細を確認するにはなかなか厳しいところです。そんな時はJustDecompile!ベータも取れて若干の不具合はあるものの、大変便利なデコンパイラ。リバースエンジニアリングで黒判定?でも、知りたいし~。Extensibility↑こちらのPDFがかなり正確な感じです。PDFをダウンロードしてみてみてください。Invokeとひと通りの流れと、Resolver使ってる部分が書かれてます。コレとMVC3のソース(公開されてるから見ましょう!)を、見つつApiControllerを使った場合の比較をしてみます。IRouteHandler))どうってこと無いですね。MvcRouteHandlerをRouteクラスに渡してます。MvcRouteHandlerにはMvcHandlerを渡します。IRouteHandler))こっちはいきなり違います。HttpWebRouteクラスを登録です。そこへはHttpControllerHandlerを渡すんです。パンチ効いてますねー。IRouteHandler.GetHttpHandlerがRouteに紐づいてるIHttpHandlerを取得するんですけど、ココからすでに別物です。APIで実装を進めていたものだしね。今のところ、双方で依存関係もないです。どこかで一本化するのかどうかは微妙ですね。System.Web.HttpとSystem.Web.Mvcはそれぞれの道を歩みそうな気もする。System.Web.RoutingやSystem.ComponentModel.DataAnnotationsなんかは共通なんだけど。それらを利用する部分はまるで別物。それを踏まえて、いま、出回ってる情報を見ると、なんでDataAnnotationsを使ったバインドやAuthorizationFilterとかのFilterが使えるんだよ、とことさら強調してるのかがわかると思います。新たに実装したんだから、自慢したい!っていうね。リクエストとレスポンスMVCRequestは基本的にASP.NETの仕組みと同じで最終的にActionResultを返す。APIRequestはHttpMessageHandlerが仲介してHttpRequestMessageになって、HttpResponseMessageを返す。普通の事書いてる感じがするけど、大違いなんです。APIではこのMessageっていうのが重要でそれに対して各種Pipelineが介入していく。この設計、まさにWCFって感じですね。できてるなー、と感心せざるを得ない。Hostへとつなげることが、あたかも自明な流れとして受け入れられる。SiteIHttpHandlerMVCControllerFactoryから対象Controllerインスタンスを取得してExecute。Controller.ExecuteCoreに入った時の流れ。IList<IResultFilter>InvokeActionResultActionResult.ExecuteResultPossiblySaveTempDataわかりにく!メモってことで。そもそも素直な実装なので、コード見たほうが早い。APIRequestからHttpRequestMessageを作成、ConfigurationとDispatcherを指定してHttpServeを作成。HttpServer.SubmitRequestAsyncにHttpRequestMessageを渡して処理開始!HttpMessageHandler、HttpControllerDispatcherが大活躍になるのがここから。ApiControllerActionInvokerActionDescriptor.Execute(ControllerContext,ActionArguments)HttpActionExecuteContext動かしながらの確認じゃないのでイイカゲン。相変わらずメモで読みにくし。Resolverがいろんな所で使われてるのと、ほとんどがTaskになってる。今のところよくわかってないのがMutableObjectModelBinderがValidationするModelBinderなんだけど、こいつが発動するのはいつなのかというところ。この中のSetPropertyでModelValidatorがワサワサ動いてるっぽいから、コレを利用したバインドにならないと検証が動かないじゃないっすか。動いてることは間違いないんだけど(DataAnnotatinosでの検証がかかってるのはサンプルで確認できてるし)なー。ここでMVCと明確に違うのがDataErrorInfoModelValidatorProviderが存在してないところ。なくてもいいと思うけど、IDataErrorInfo使ってるのを動かすのは自分でしこまないとダメってことですね。いう所ではちょっと差がある。API↑この書き方のサンプルをオンラインでよく見かけますよね。APIだとFilterにしちゃうのがいいの?ときになるところだと思いますが、これはAPIが返す結果が何なのかを考えれば妥当というかコレしかないね、と思えるところです。いうことかというとHTMLを返して、その中にエラーメッセージを含んでたりModelStateによる入力値を復元させることを前提にしてるアプリケーションとしてのMVC(どこのViewにModelを渡すのかをControllerが指定)。それに対してエラーですよ、ということを返せばいいだけのAPI(ControllerがModelを返すけど、APIから返るデータをViewというならViewはModelをFormatしたXML/JSON固定で不変)。何がどのようにエラーだったのかを判定して表示を制御するのはAPIを利用したアプリケーションの責任で表示としてのViewを制御してるのはAPIじゃない。一律Filterでエラーの時の処理を決めてレスポンスすれば良い。MVCでも同じようにFilterでModelStateをみてエラーをレスポンスすることは可能ですが、その時のViewをFilterが判断することになって、う~ん、まいった、ですね。ApiControllerのActionの中でModelStateにエラーを入れることができないの?っていうとそんなことはなくて、入れればいいです。OnActionExecutingじゃなくてOnActionExecutedでResponse変えるとか、HttpMessageHandlerで書き換えるとか、事後にエラーとしてしまう方法はイロイロです。DefaultServiceResolverちょっと脱線。最初から登録されてるインスタンスたちはコレらでした。IBuildManagerDefaultBuildManagerIHttpControllerFactoryDefaultHttpControllerFactoryIHttpControllerActivatorDefaultHttpControllerActivatorIHttpActionSelectorApiControllerActionSelectorIHttpActionInvokerApiControllerActionInvokerModelMetadataProviderEmptyModelMetadataProviderIFormatterSelectorFormatterSelectorIActionValueBinderDefaultActionValueBinderIRequestContentReadPolicyDefaultRequestContentReadPolicyIFilterProviderConfigurationFilterProviderActionDescriptorFilterProviderEnumerableEvaluatorFilterProviderQueryCompositionFilterProviderModelBinderProviderTypeMatchModelBinderProviderBinaryDataModelBinderProviderKeyValuePairModelBinderProviderComplexModelDtoModelBinderProviderArrayModelBinderProviderDictionaryModelBinderProviderCollectionModelBinderProviderTypeConverterModelBinderProviderMutableObjectModelBinderProviderCompositeModelBinderProviderModelValidatorProviderDataAnnotationsModelValidatorProviderClientDataTypeModelValidatorProviderValueProviderFactoryRouteDataValueProviderFactoryQueryStringValueProviderFactoryModelMetadataProviderCachedDataAnnotationsModelMetadataProviderILoggerDiagnosticLoggerいっぱいあるねー。System.Web.Http.Services.DefaultServiceResolver。System.Web.Http.GlobalConfiguration.Configuration.ServiceResolverらへんです。API(System.Web.Http)に対しても、目を光らせておこうと思う次第です。MVC2011年12月31日土曜日MongoDBでお気楽MapReduceMongoDBにはMapReduceの仕組みが用意されてて楽しそうですよね。Confluenceちゃんと日本語。