I have just refactored a large portion of the web site code, which included moving resources around and in some cases renaming them. Of course such a change can't go without a glitch the first time you fire up a portal after refactoring. As expected, I immediately started hitting a few HTTP 404 responses on WebResource.axd calls. Natural question is - which resources are incorrectly referenced? Unfortunately WebResource.axd URLs are encrypted. Looking at URL itself does no good. Unfortunately there's nothing on the web about debugging specific WebResource.axd failures so I decided to share my trick and hopefully it will save someone some time.

Embedded resource URLs look as follows:

http://localhost/MyWebSite/WebResource.axd?d=g-3JyPzayOPB4DtVZYq_txpuzlO0qVWI2XrA3kNA2oaSBABpm5T_EdHnHTSzAMaf2yYIt5qfBWfGULj2UxGzstrlrwvhgaeb4kRSDUYM5RDNB071cXkYK-LEXaDkSi9aCWv0gFCBzwkwwLWT-cB-bvVGqEuKZcpIiUb9Me5SsPI1&t=635505401990950000

This URL is generated by System.Web.Handlers.AssemblyResourceLoader class, source code for which is available here. Let's take a look at it.

/// <devdoc>
///     Performs the actual putting together of the resource reference URL.
/// </devdoc>
private static string FormatWebResourceUrl(string assemblyName, string resourceName, long assemblyDate, bool htmlEncoded) 
{ 
  string encryptedData = Page.EncryptString(assemblyName + "|" + resourceName);
  if (htmlEncoded) 
  { 
    return String.Format(CultureInfo.InvariantCulture, _webResourceUrl + "?d={0}&t={1}", 
      encryptedData,
      assemblyDate); 
  }
  else
  {
    return String.Format(CultureInfo.InvariantCulture, _webResourceUrl + "?d={0}&t={1}",
      encryptedData, 
      assemblyDate);
  } 
} 

Highlighed line indicates what's being encrypted. So d is just assembly modification timestamp. If I could reverse engineer the value of d parameter then I would know what is broken. Since I didn't care about encryption process, rather than how to reverse engineer this URL value I looked at at Page.DecryptString implementation which is available here

/// <devdoc> 
///    <para>Decrypt the string using symmetric algorithm defined in config.</para>
/// </devdoc>
internal static string DecryptString(string s) 
{
    // DevDiv Bugs 137864: Hash is the default for higher compatibility because it retains 
    // the behavior that a given input always results in the same output.
    return DecryptStringWithIV(s, IVType.Hash); 
} 

// DevDiv Bugs 137864: Add IVType overload for improved encryption semantics 
// Note: Not an actual overload because this breaks the reflection that SWE 1.0 uses to call this method.
internal static string DecryptStringWithIV(string s, IVType ivType) 
{
    if (s == null)
        return null; 

    byte[] buf = HttpServerUtility.UrlTokenDecode(s); 
    if (buf != null) 
        buf = MachineKeySection.EncryptOrDecryptData(false, buf, null, 0, buf.Length, false, false, ivType);

    if (buf == null)
        throw new HttpException(SR.GetString(SR.ViewState_InvalidViewState));

    return Encoding.UTF8.GetString(buf);
} 

If only MachineKeySection.EncryptOrDecryptData and ivType were public it would have been a bit easier to accomplish my task but since they aren't I had to write a bit of throw-away reflection code to get to them. Here's the code that will reverse-engineer the token and spit-out assembly name and resource name in referencedResources string. Their format will be the same as mentioned in FormatWebResourceUrl above, namely - [Assembly Name]|[Resource Name].

byte[] inputArray = System.Web.HttpServerUtility.UrlTokenDecode("5r59bSNcPgH41sn0n7yZmk2vV3gfQl_ywYeTKnRzb77SVSt3KyaY6wxCRYCg5aDC39z2o-utTlN62bHX1pTCxoXR8dulDCDP_8btJcrcqErJeXkRxpQCnOhVFV-f9Gfp0");

byte[] outputAarray = null;
if (inputArray != null)
{
    // Get IVType for GetMethod and Hash value for Invoke
    Type ivType = typeof(System.Web.Configuration.MachineKeySection).Assembly.GetType("System.Web.Configuration.IVType");
    object hashValue = ivType.GetEnumValues().GetValue(2);  // 2 = Hash

    // Get MachineKeySection type to call private static method on it
    Type machineKeySectionType = typeof(System.Web.Configuration.MachineKeySection);
    var encryptOrDecryptData = machineKeySectionType.GetMethod("EncryptOrDecryptData", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic, null, new Type[] { typeof(bool), typeof(byte[]), typeof(byte[]), typeof(int), typeof(int), typeof(bool), typeof(bool), ivType }, null);

    // Decrypt the token
    outputAarray = (byte[])encryptOrDecryptData.Invoke(null, new object[] { false, inputArray, null, 0, inputArray.Length, false, false, hashValue });
}

string referencedResources = Encoding.UTF8.GetString(outputAarray);

Now the job is easy. I stick this code in my Default.aspx page Page_Load method and reverse-engineer every resource that fails on the previous try. Of course, this can be wrapped-up nicely in a method and put in a better place (error handler for example) but that wasn't needed for my purpose. Feel free to do so if it works for you.