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 型で指定するというインターフェスでは解決できない。
困った。

*1:当然だが、WebResource.axd にどのようなパラメータを与えても使用して許可していないリソースを取り出すことはできない

*2:ここにコピペした時点で ArgumentNullException を catch して HttpException で包んでいない不具合に気が付いた

*3:当然、DLL 名は毎回ランダム

*4:こちらも、DLL 名は毎回ランダム