Web Resource Fail To Load

Over the last week or so a number of us here at [Intilecta] have been plugging away at an issue with the web version of the product we are building. The last 24hr or so has been my turn and while I haven’t found an ideal solution I did (mostly) track down the cause of our problem.  Leaving aside my belief that web apps are the source of all evil (not really but sometimes I do feel that they can be more pain than it’s worth) I’m going to try to example the issue we were seeing and why it was happening:

There are countless systems that use the .NET framework’s ability to dynamically load assemblies to provide extensibility options.  In our case the entire application is based around this concept but unlike a lot of systems we don’t rely on the assemblies existing on the local filesystem.  We use an override of the Assembly.Load method to load assemblies based on an array of bytes (which might arrive via a network connection, from a database or carrier pigeon).  This works brilliantly in the winforms world but fails miserably in the web world (sometimes!).  The reason that this issue was so difficult to track was that it only seemed to happen in certain circumstances which appeared at first to be completely random. 

In brief the process for dynamically loading web content goes:

  • Load assembly from byte array

  • Retrieve type from loaded assembly

  • Create web control from type either using LoadControl or Activator.CreateInstance

  • Loaded control is added to the Controls collection for the page and is subsequently rendered

This process works for very trivial pages but started to throw the following exception when we tried to add more complex content:

System.ArgumentException occurred
  Message=”Illegal characters in path.”
   mscorlib.dll!System.IO.Path.CheckInvalidPathChars(string path) 
   mscorlib.dll!System.IO.Path.NormalizePathFast(string path = “<Unknown>”, bool fullCheck = true)
   mscorlib.dll!System.IO.Path.GetFullPathInternal(string path)
   System.Web.dll!System.Web.StringResourceManager.ReadSafeStringResource(System.Type t = {Name = “webusercontrol1_ascx” FullName = “ASP.webusercontrol1_ascx”})

If this exception was ignored (ie hit continue) it would then bubble up as the following exception:

System.Web.HttpException occurred
  Message=”An error occurred while try to load the string resources (FindResource failed with error -2147023898).”
   System.Web.dll!System.Web.StringResourceManager.ReadSafeStringResource(System.Type t = {Name = “webusercontrol1_ascx” FullName = “ASP.webusercontrol1_ascx”})

As you can see from the first call stack the issue arises because the StringResourceManager (which is attempting to load content from a resource file) attempts to get the FullyQualifiedName of the module to which the control type belongs.  Unfortunately because the assembly is dynamically loaded from a byte array the method InternalGetFullyQualifiedName which is called just prior to the GetFullPathInternal returns “<unknown>” (not an empty string or null or something sensible) which of course the method CheckInvalidPathChars decides is invalid and throws the ArugmentException.

Lets take a back step and look at just how to replicate this issue:

  • You have three projects in a solution – a test website (the host), a web application project (contains the web control you are going to render) and a web deployment project (so you can deploy everything as dlls)

  • In the load event of the page in the test website you need to dynamically load the output assemblies from both the web app and the web deployment projects

  • Still in the load event you then create an instance of the control and add it to the controls collection of the page

  • For a simple web control (with say text saying “Hello World”) this renders fine

  • Add more than 254 characters to the web control and it will generate the exceptions as described previously.

If you want to see this in action drag down the attachment and simply run the website – it will attempt to load the control but will fail.  Go into the web control and remove the last character and rerun the site and it will load fine!

If you use Reflector to pop the hood and look at the difference you will see that with less than 255 characters the content is rendered using:

__ctrl.AddParsedSubObject(New LiteralControl(“ABCD”))

(where ABCD is the text content of the page)

__ctrl.AddParsedSubObject(Me.CreateResourceBasedLiteralControl(0, &H100, True))

So the content is now being rendered from a resource file, which as we saw from the exception earlier won’t work.  Unfortunately this only really leaves us with one fairly ugly option, which is to render the assemblies to disk prior to loading them into memory.  This of course bring issues of versioning, maintaining file currency and of course security as we now need the service to have permissions to persist files to disk.

If anyone knows of a better work around, please feel free to share as it has caused us enough heartache to get to this point!