Angle Bracket Percent : Take your MVC User Controls to the next level
確かにNext Level。
普通、ascxをレンダリングするときにはRenderPartialヘルパーを使うところだけど、 T4MVCとか使ってないとパスがマジックストリングになりますね。T4MVC使ってれば、そんなことにはならないけど、そもそもascx単位の RenderPartial的なヘルパーを用意してしまうほうがいいんじゃないの?という話。
で、それを実現するためにいちいちコントロール毎にヘルパーを定義するのはバカらしいってことで、動的生成ですよ。これもまさに黒魔術。
App_Codeの動的コンパイルに介入(ControlBuilder.ProcessGeneratedCodeのoverride)して、実行時動的に拡張メソッドクラスを作り出してます。CodeSnippetTypeMemberとか初めて見た。
まずは、FileLevelUserControlBuilderを派生させたUserControlHtmlHelperControlBuilderを作成。このクラスが動的生成をおこなうクラスで、ProcessGeneratedCodeをoverrideしてCodeTypeMemberCollectionにヘルパークラスのCodeSnippetTypeMemberを追加してます。動的クラスのコーディングはサンプルと言うことでシンプルな実装ですね。
dummyClass.Members.Add(new CodeSnippetTypeMember(String.Format(@" }} }} public static class {2}Extensions {{ public static void Render{2}(this System.Web.Mvc.HtmlHelper htmlHelper{3}) {{ var uc = new {0}(); {5} uc.RenderView(htmlHelper.ViewContext); }} public static string {2}(this System.Web.Mvc.HtmlHelper htmlHelper{3}) {{ return {1}.RenderHelper(htmlHelper.ViewContext, () => Render{2}(htmlHelper{4})); ", codeGenUserControlFullTypeName, userControlHtmlHelperFullTypeName, helperMethodBaseName, paramBuilder, callParamBuilder, fieldAssignementBuilder)));
続いて、このControlBuilderを使ってくれるようにASP.NETに指示をださなきゃいけないんだけど、それってどこでやってるんでしょうね。ぱっと見、以下のクラスの属性指定?
[FileLevelControlBuilder(typeof(UserControlHtmlHelperControlBuilder))] public class UserControlHtmlHelper : ViewUserControl { // Helper method which renders the code in a temporary writer and returns it as a string public static string RenderHelper(ViewContext viewContext, Action render) { TextWriter oldWriter = viewContext.Writer; var tmpWriter = new StringWriter(CultureInfo.CurrentCulture); viewContext.Writer = tmpWriter; try { render(); } finally { viewContext.Writer = oldWriter; } return tmpWriter.ToString(); } }
ここが実行時に勝手に解釈されるようになってるのかな~。動的ヘルパーを生成したいascxのInherits="MvcUserControlHtmlHelpers.UserControlHtmlHelper"という記述が通常と違う部分なのはわかるんだけど。
FileLevelControlBuilderAttribute コンストラクタ (System.Web.UI)
実行時に初めてコードが生成されるから、コーディング時には↓こんな感じでメソッドないぜと怒られる。
実行したところで、VSはそんなコードしらないんだってさ。
そもそもMVCのViewUserControlクラスがこれと同じようにViewUserControlControlBuilderを使ってるんじゃんね。でも、BaseTypeをうわがいてるくらい(目的はよくわかってないデス)ですね。
追記:ascxなりaspxのベースクラス(ViewPage,ViewUserControl)の派生元(親)クラスを指定してるんでした。そりゃそうだ。
<%@ Control Language="C#" Inherits="MvcUserControlHtmlHelpers.UserControlHtmlHelper" ClassName="Gravatar" %> <script runat="server"> // Declare the paramaters that we need the caller the pass to us public string Email; public int Size; </script> <% // Build hash of the email address // Note: in spite of its name, this API is really just a general MD5 encoder string hash = FormsAuthentication.HashPasswordForStoringInConfigFile(Email.ToLower(), "MD5").ToLower(); // Construct Gravatar URL string imageURL = String.Format("http://www.gravatar.com/avatar/{0}.jpg?s={1}&d=wavatar", hash, Size); %> <img src="<%= imageURL %>" alt="<%= Email %>" title="<%= Email %>" />
※人さまのコードをこんなにコピペしていいのだろうか...。
上記のApp_Codeに作成されてるGravator.ascxのscript blockから正規表現でpublicなプロパティ定義を取り出して、ヘルパーのパラメータ生成に利用してるところも見逃せない。
T4MVCでの動的生成、Emitによる黒魔術に次いで、新たな黒魔術として覚えておくのもいいんではないでしょうか。
※ASP.NET MVCに限らずASP.NETならすべてに適用できるのがミソですよ!