WebResource.axd を利用したい
GDNJ の (ASP.NET)WebResource.axdについて の補足というか続き。
WebResource.axd に関してはアセンブリの埋め込みリソースを WebRequest として直接取り出す機能で、GDNJ にも書いたように、アセンブリとリソースネームスペースを示す型とリソース名を指定して、RegisterClientScriptResource を呼び出すか WebResourceAttribute で指定しておけば WebResource.axd からのアクセスが有効になり、*1その URL は GetWebResourceUrl で得ることができる。
WebControl の場合は、WebResourceAttribute で十分なのだが、aspx ページで手軽に使えると、元投稿のような状況で便利に使うことができる。そこで、id:ladybug:20051207#p3 にあるように ASP.NET Expressions を利用して URL を埋め込めるようにしてみよう。
上記の記述は、内容が大間違いのため、id:ladybug:20060316 にて修正しています。
期待した結果は、.aspx にて
<IMG SRC="<%$ ResourcesUrl: Images, Logo %>">
などと記述すると、
<IMG SRC="WebResource.axd?...">
と展開すること。(引数は Resources と同じとする)
とりあえず、音速で
// code formatted by http://manoli.net/csharpformat/ using System; using System.Data; using System.Configuration; using System.Web; using System.Web.Security; using System.Web.UI; using System.Web.Compilation; using System.CodeDom; using System.Reflection; using System.Diagnostics; /// <summary> /// ResourcesUrl の概要の説明です /// </summary> [ExpressionPrefix(ResourcesUrlExpressionBuilder.Prefix)] //[ExpressionEditor("ResourcesUrlExpressionEditor")] public class ResourcesUrlExpressionBuilder : ExpressionBuilder { internal const string Prefix = "ResourcesUrl"; public ResourcesUrlExpressionBuilder() { } /// <summary> /// コンパイルなしページをサポートするか /// </summary> public override bool SupportsEvaluate { get { return false; } } /// <summary> /// 文字列形式の式を解析し、式を表すオブジェクトを返します。 /// </summary> public override object ParseExpression(string expression, Type propertyType, ExpressionBuilderContext context) { // <%$ ResourcesUrl: resourcename, keyname %> string[] args = expression.Split(','); if (args.Length == 1) { // App_LocalResources return new ResourcesEntry(args[0].Trim()); } else if (args.Length == 2) { // App_GlobalResources return new ResourcesEntry(args[0].Trim(), args[1].Trim()); } else { throw new HttpException("引数が不正です: <%$ " + Prefix + ": グローバルリソースファイル名, キー名 %> 形式か <%$ " + Prefix + ": ローカルキー名 %> 形式である必要があります。"); } } public class ResourcesEntry { public ResourcesEntry(string resname, string key) { if (string.IsNullOrEmpty(resname)) throw new ArgumentNullException("resname", "リソースのファイル名が指定されていません。"); if (string.IsNullOrEmpty(key)) throw new ArgumentNullException("key", "リソースのキー名が指定されていません。"); this.ResourceName = resname; this.KeyName = key; } public ResourcesEntry(string key) { if (string.IsNullOrEmpty(key)) throw new ArgumentNullException("key", "リソースのキー名が指定されていません。"); this.ResourceName = null; this.KeyName = key; } public readonly string ResourceName; public readonly string KeyName; } /// <summary> /// 式を表すオブジェクトをページ実行中に使用されるコードへ変換する /// </summary> public override CodeExpression GetCodeExpression(BoundPropertyEntry entry, object parsedData, ExpressionBuilderContext context) { ResourcesEntry res = parsedData as ResourcesEntry; Debug.Assert(res != null, "aye?"); // target = this.ClientScript CodeExpression target = new CodePropertyReferenceExpression( new CodeThisReferenceExpression(), "ClientScript"); // target.GetWebResourceUrl(typeof(TemplateControl), res.KeyName) return new CodeMethodInvokeExpression( target, "GetWebResourceUrl", new CodeTypeOfExpression( /***/ ), new CodePrimitiveExpression(res.KeyName)); } }
ここまでは書いた。*2
問題は一番最後にある /***/ の部分だ。ここにはリソースをアクセスするためのネームスペースとアセンブリを参照するための型を指定しなければならない。
グローバルリソースの場合、この型は App_GlobalResources.dll*3 にある "Resources.ファイル名" というクラスを示せばいいのでなんとかなる。問題はローカルリソースの場合だ。ローカルリソースで、リソースを特定するためのネームスペースは Page クラス側がもっているので、たとえば Default.aspx であれば Default クラスを指定する必要がある。
しかし、ローカルリソースは App_LocalResources.dll*4 に保存されるため、typeof(Default) を指定しても目的のリソースをもったアセンブリを取得することはできないし、この App_LocalResources.dll には型が1つも含まれていない純粋なリソースアセンブリのため、Type 型で指定するというインターフェスでは解決できない。
困った。