Wednesday, December 5, 2007

Creating an ASP.NET page from code

As a control developer I sometimes need to create a page purely from code, without any .aspx page and behind-the-scenes compilation by ASP.NET. This introduces some issues because the compilation of an .aspx file introduces some code that is not immediately obvious. In this article I've tried to compile a list of things needed to make pages work from code.

The basics
A page is an IHttpHandler so it can handle a request through it's ProcessRequest method. This is where the ASP.NET Page lifecycle starts. One of the first things a 'normal' page does is construct it's control tree. It does this through the FrameworkInitialize method. Normally the ASP.NET compiler will generate code to construct the control tree for the page. Since we're building the page from scratch, we need to do this ourselves.

Building a control tree
There are a couple of basic things every ASP.NET page has:

  • Document type:<!DOCTYPE html .... >
  • Basic HTML structure: <html> etc.
  • A header control ( HtmlHead )
  • A form control ( HtmlForm )
For an .aspx page, the compiler will take care of creating these. For a programmatic page, this has to be hand coded:
protected override void FrameworkInitialize()
  AddParsedSubObject( new LiteralControl( 
      "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 
       Transitional//EN\" \" 
       xhtml1-transitional.dtd\">\r\n" ) );
  AddParsedSubObject( new LiteralControl( 
      "<html xmlns=\"\" >\r\n" ) );  
  AddParsedSubObject( new HtmlHead() );   AddParsedSubObject( new LiteralControl( "\r\n<body>\r\n" ) );   HtmlForm form = new HtmlForm();   form.ID = "form1";   AddParsedSubObject( form );   // Add any other child controls to form.Controls here   AddParsedSubObject( new LiteralControl( 
      "\r\n</body>\r\n</html>" ) );

Note that controls on the page must be added as child controls of the Form control, just like controls in an .aspx file are declared within the form.

Page directives
Most of the @Page directives translate directly into code in one way or another. Below a list of the most commonly used directives. ResponseEncoding FrameworkInitialize is a good place to set the response encoding. If you set it here, you can be sure that nothing has been written to the response stream yet.

Response.ContentEncoding = Encoding.UTF8;
Session state To enable session state, make sure your page class implements one of the following interfaces:
Full access IRequiresSessionState
ReadOnly IReadOnlySessionState
If the Page class does not implement one of these marker interfaces no session state will be available in the page. Note that if you're not modifying session state from the page, read only access may give a performance benefit.
Theme Assign the theme name:
Theme = "myTheme";
ValidateRequest To validate the request, invoke

Note that the default value of ValidateRequest is true, so unless you explicitly do not want your requests validated, invoke ValidateInput to be on the safe side.

Adding support for Ajax
If you need Ajax support on the page, insert an Ajax ScriptManager control as the first control in the form.

// Add Ajax Script Manager
form.Controls.Add( new ScriptManager() );

Some of the internal methods of the page rely on information obtained from the compiler. Most notably functions like ResolveClientUrl. In order to make these work, set the AppRelativeTemplateSourceDirectory property.

protected override void OnInit( EventArgs e )
  if ( string.IsNullOrEmpty( AppRelativeTemplateSourceDirectory ) )
// This is required to make ResolveClientUrl function correctly
// when the page is invoked as an HttpHandler.
// Note that this assumes the url for the http handler // definition is in the application root AppRelativeTemplateSourceDirectory = "~/"; } base.OnInit( e ); }

Databinding is a process mostly facilitated by the ASP.NET compiler. So, for pages like this, you pretty much have to hand code all databinding behavior. Luckily databinding is no rocket sience; the basic way of working relies on the DataBinding event. When you create a control that needs databinding, add a handler to it's DataBinding event. Then, in the handler, do what ever you need to set the databound properties. The control itself is passed in as the sender.

Serving up a virtual URL
Finally the page needs to be hooked up to a URL. This is possible through web.config. The easiest way is to add an entry to the handlers section:

<configuration>   <system.web>
    <httpHandlers>     <add verb="*" path="mypage.ashx" type="Alanta.Example.CustomPage"/>
    </httpHandlers>   </system.web>

You can handle any URL that is mapped to ASP.NET in this way (.aspx, .ashx etc.).

What's next ?
After constructing the page and adding your controls everything works as usual. The page will go through the normal Page lifecycle.