Page Controllers and Stand-alone Code Migration

Tuesday, August 2, 2005

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.

Stubs to the rescue

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.


Comments
Hung Friday, August 5, 2005
I'm still confused of how you would pass the page object into the QuoteHelper class? (I'm a asp.net newbie)

QuoteHelper qh = new QuoteHelper(??);

Thanks,

Hung-
scott Friday, August 5, 2005
Yes, Hung. The code would look something like:

QuoteHelper qh = new QuoteHelper(myUserControl);

If you wanted to pass a reference to the page, and you are inside code for the page, you can pass the "this" reference:

QuoteHelper qh = new QuoteHelper(this);
Scott Bellware Monday, August 8, 2005
Great explaination of this kind of stuff at Fowler's Model-View-Presenter article:
www.martinfowler.com/.../ModelViewPresenter.html
Coleman Thursday, September 8, 2005
Scott - One thing to watch for, which in your examples doesn't occur, is the state of the helper objects. In your example, if for some reason the implementation of the helper object stores the reference of the IQuoteInput object as an instance variable and for some reason the helper object is then stored in the session or cache, that would then (I believe) cause issue on the garbage collection of the page or user control. Memory issues would soon follow. Thoughts?
scott Thursday, September 8, 2005
Yes, the page would then stick around as long as the helper object was reachable from the Session / Cache. That would be something to watch for and I'd try to desgin so it doesn't happen. Good point, Coleman.

Hieu Wednesday, September 14, 2005
Nice article, so this is the adapter pattern that you are using here rite ?
-Mike- Wednesday, November 16, 2005
Nice writeup! BTW, the common issues white paper has been renamed so it is best to follow the link from this page.

msdn.microsoft.com/.../upgrade/

There is also a new step-by-step white paper there.
vj Wednesday, November 23, 2005
Rather than migrating an App from 1.1 t 2.0, I am myself migrating and I used your article as a base. :D

I am doing a straight forward "intended" design, based on three aspx web forms that use a utility/helper class from that is in the app_code folder.

My problem is, My partial classes dont see the utility class and as a result cannot instantiate an instance of that class. Am I missing something here. This is what you said was the striaght forward design wasnt it?

thanks
scott Wednesday, November 23, 2005
vj: That is odd. Any types in App_Code should be visible to the result of the web application.

Could it be a namespace issue? You might need to add a using / imports statement.

Are the classes marked public?
Comments are now closed.
by K. Scott Allen K.Scott Allen
My Pluralsight Courses
The Podcast!