The recently unveiled “Common ASP.NET 2.0 Conversion Issues and Solutions” document is a must read for anyone who plans to move a 1.x web application to ASP.NET 2.0.
The document explains why one of the thornier areas will be fixing stand-alone (SA) class files that reference types in a code-behind (CB) file. In 1.x you could add stand-alone .cs or .vb files to a web application, and the code inside the files could reference any type in the web project because all the files compiled into the same assembly – the one you find in the bin subdirectory.
In 2.0 the stand-alone files will move to the App_Code directory and compile into a different assembly than the code-behind classes. Because all of the web form and user control assemblies will reference App_Code and not vice versa, you simply can’t reference web form and user control types directly from App_Code (or any class library, because there is no one “web” assembly to reference anymore).
The conversion document linked above points out how this situation causes problems with SA class files using LoadControl and requiring strong typing. The situation is also causing difficulty for people who have architected an application using Page controller patterns and pushed the responsibility for interacting with forms and controls into helper classes. The migration wizard moves the helper classes into App_Code where they cannot see any of the types they need to interact with.
The recommended solution is to create a stub class. A stub is an abstract base class from which the Page and UserControl code-behind classes will derive. The stub will be usable from a helper class since the stub will also live in App_Code. In the final, released version of Visual Studio 2005, the migration wizard will generate stub class for you, however, chances are good that a bit of hand written code will give you greater flexibility and be a better fit for your architecture. Here is one example of how to make the scenario work, even with Beta 2.
Let’s pretend you wrote a user control to let the end-user enter quotes from famous celebrities. A form with the control might look like screen shown here, with two TextBox controls and a button to click.
Now you need to interact with this control from a helper class, but the helper class lives in App_Code. The first step would be to define a stub in App_Code. My suggestion would be to derive from System.Web.UI.UserControl only if the helper class needs to interact with the entity as a control. In other words, if the helper needs to invoke the FindControl method or the touch the Attributes property, derive from a class in System.Web. For example:
using System;
public abstract class QuoteInputControlBase :
System.Web.UI.UserControl
{
public abstract string Name
{
get;
}
public abstract string Quote
{
get;
}
}
Typically a helper class isn't too interested in the presentation of data, so won't need the Attributes property. Often times a helper class doesn't need to know an object is a UI control at all. In our example we are only interested in getting at the text values inside the control. Since we don’t need to interact with the object as a user control, let’s define an interface instead.
public interface IQuoteInput
{
string Name
{
get;
}
string Quote
{
get;
}
}
Then in our user control code-behind file, we need only add the interface to our derivation list and provide a suitable implementation, i.e:
using System;
using System.Web.UI;
public partial class CelebrityQuoteInputControl :
UserControl, IQuoteInput
{
public string Name
{
get { return NameTextBox.Text; }
}
public string Quote
{
get { return QuoteTextBox.Text; }
}
protected void Page_Load(object sender, EventArgs e)
{
// typical user cotnrol load goo...
}
}
The helper class now only works on an IQuoteInput, which could be coming from anywhere. All the page controller needs to do is hand off a reference to the user control and away we go.
using System;
public class QuoteHelper
{
public void Init(IQuoteInput input)
{
if (input != null)
{
name = input.Name;
quote = input.Quote;
}
else
{
throw new ArgumentNullException("input");
}
}
public void DoSomethingInteresting()
{
// ...
}
string name = string.Empty;
string quote = string.Empty;
}
Still awake?
I think the new compilation model will force better design into some applications by requiring an approach similar to the above. I never felt comfortable seeing Page and UserControl derived types consumed outside of their own definitions.
P.S. Tom Cruise really did say that.