Fixing the DynamicControlsPlaceholder control – Making the community better


11954221481914068549johnny_automatic_mister_fix_it_svg_hi In
my job as a PFE
for Microsoft, I read,
review and fix a lot of code.  A lot of code.  It’s a large part of what
I love about my job.  The code is generally written by large corporations or for
public websites
.  Every now and again I’ll get pinged on an issue and after
troubleshooting the issue, it’s pretty clear that the core issue is with some community
code.  When I say community code, in this instance, I don’t mean a CodeProject
or CodePlex project.  In this case, I am referring to a control that Denis
Bauer created and then made available to the community on his website – the “DynamicControlsPlaceholder”
control
.  This is a great little control that inherits from a PlaceHolder
and allows you to  create dynamic controls on the fly and then it will persist
the controls you add on subsequent requests – like a postback.

The Problem

The customer was experiencing a problem that could only be replicated in a web farm
when they don’t turn on sticky
sessions
.  They found that when a request went from one server to another
server in their farm they would get a FileNotFoundException with the following details:

Type Of Exception:FileNotFoundException
Message:Error on page http://blahblahblah.aspx
Exception Information:System.IO.FileNotFoundException:
Could not load file or assembly 'App_Web_myusercontrol.ascx.cc671b29.ypmqvhaw,
Version=0.0.0.0, Culture=neutral, PublicKeyToken=null' or one of its dependencies.
The system cannot find the file specified.
File name: 'App_Web_myusercontrol.ascx.cc671b29.ypmqvhaw,
Version=0.0.0.0, Culture=neutral, PublicKeyToken=null'
at System.RuntimeTypeHandle.GetTypeByName(String name,
Boolean throwOnError,
Boolean ignoreCase,
Boolean reflectionOnly,
StackCrawlMark& stackMark)
...
at DynamicControlsPlaceholder.RestoreChildStructure(Pair persistInfo,
Control parent)
at DynamicControlsPlaceholder.LoadViewState(Object savedState)
at System.Web.UI.Control.LoadViewStateRecursive(Object savedState)
...
at System.Web.UI.Control.LoadViewStateRecursive(Object savedState)
at System.Web.UI.Page.LoadAllState()
at System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint,
Boolean includeStagesAfterAsyncPoint)

So, we can gleam a few things from the error details:

  • They are using the ASP.NET website model (the “app_web_….dll” assembly is the clue).
  • The error is occurring in the RestoreChildStructure method of the DynamicControlsPlaceholder
    control.

The Research

The way that ASP.NET Websites work is that each component of your site can be compiled
into a separate assembly.  The assembly name is randomly generated.  This
also means that on two servers, the name of the assemblies can end up being different. 
So, an assumption to make is that something is trying to load an assembly by its name. 
If we look at the RestoreChildStructure method, we see the following:


Type ucType = Type.GetType(typeName[1], true, true);

try
{
MethodInfo mi = typeof(Page).GetMethod("LoadControl",
new Type[2] { typeof(Type), typeof(object[]) });
control = (Control) mi.Invoke(this.Page, new object[2] { ucType, null });
}
catch (Exception e)
{
throw new ArgumentException(String.Format("The type '{0}' …",
ucType.ToString()), e);
}

The important thing to look at here is the Type.GetType(…) call.  Since the code
for the control is in a separate assembly from everything else, the “typeName[1]”
value MUST BE A FULLY QUALIFIED ASSEMBLY NAME.  From the exception
details, we can see that it is attempting to load the type from the following string:

App_Web_myusercontrol.ascx.cc671b29.ypmqvhaw, Version=0.0.0.0, Culture=neutral,
PublicKeyToken=null

The “typeName[1]” variable is loaded from ViewState because that’s where the control
persists its child structure.  So, for some reason the fully qualified assembly
name is stored in ViewState.  If we look at the code that inserts the value into
ViewState (in the PersistChildStructure(…) method), we see:


typeName = "UC:" + control.GetType().AssemblyQualifiedName;

So, here we see the AssemblyQualifiedName is being stored into ViewState – which is
then used to persist the controls across postback using the above code.  As I
mentioned, this won’t work with an ASP.NET website hosted in a web farm because the
assembly qualified name will probably be different from server to server.  We
even have a KB article that discusses this issue somewhat

The Fix

Fortunately, the fix is pretty simple. 

First, we need to store the path to the User Control instead of the AQN in ViewState. 
To do this, you can comment out the “typeName = ….” line from directly above and replace
it with:


UserControl uc = control as UserControl;
typeName = "UC:" + uc.AppRelativeVirtualPath;

So, now we store the path to the UserControl in ViewState.  Then, we need to
fix the code that actually loads the control.  Replace the code from above in
the RestoreChildStructure(…) method with this code:


string path = typeName[1];

try
{
control = Page.LoadControl(path);
}
catch (Exception e)
{
throw new ArgumentException(String.Format(
"The type '{0}' cannot be recreated from ViewState",
path), e);
}

That’s all there is to it.  Just load the user control from where it is being
stored in the site and ASP.NET will take care of loading the appropriate assembly.

Enjoy!


Comments (0)

Skip to main content