Valid XHTML within .NET

Suggested Solution (contd)

Step 4: Fixing the View-State Field

Now that we've fixed our first problem lets take another look at the second error we encountered:

  1. There is no attribute called name within a form tag.
  2. The document type does not allow element input here because input is an inline tag when a block-level tag was expected.

ASP.NET writes the hidden view-state field when the HtmlForm.Render() method is called. Looking back over the rendering process we can see that the view-state field is written when the HtmlForm.RenderChildren() call the Page.OnFormRender() method. The problem here is that the Page.OnFormRender() is declared private so we can't override as before. Luckily the HtmlForm.RenderChildren() is declared as a virtual method so well start by overriding that as with code from Listing 05 .

Listing 05

protected override void RenderChildren(HtmlTextWriter output)
{
  foreach(Control c in base.Controls)
  {
    c.RenderControl(output);
  }
}

This overridden function simply gets the code between the form tags and writes it to the page. If you add this code to the CustomForm class in our Liquid.Tutorial.cs file, compile and copy the assembly files to the Web server's /bin directory then run the replacementform.aspx you'll find that it outputs valid XHTML. Only now the form doesn't actually post back properly, this is because there is an issue with all current versions of the .NET runtime that require the hidden input form field to be present, even if the value is blank.

Before we continue there is one additional point worth bringing up and that is the issue of client-side ECMAScript (JavaScript). As we are developing XHTML code for XHTML capable browsers I'm going to assume that we're also writing correct ECMAScript 1.2 code, in that we're using the document.getElementById("myId") method to access document objects. This is important because the minute we remove the "name" attribute from the page document.forms.formName will fail to work. Additionally given the problems involved with getting inline script to work correctly with XHTML valid documents I'm also going to assume all ECMAScript will be contained in a separate document. Therefore for the sake of this article I'm going to ignore the Page.OnFormPostRender() method that writes Microsoft's JScript to the page. Should you be inclined you can easily apply the same principles shown here to access these properties. I should also add that any hidden fields written to the page using the RegisterHiddenFields() method will need to be manually added from now on.

OK, back to the solution. Now we're outputting valid XHTML we need to put the view-state hidden field back onto the page. To get and set the hidden form field we need to override the Page.LoadPageStateFromPersistenceMedium() and Page.SavePageStateToPersistenceMedium() methods to get and set the hidden form field value.

Earlier you'll have noticed that we placed an asp:Literal control within the fieldset tag of our ASP.NET page. Normally the HtmlHidden field would be used to write hidden form fields but as this particular hidden field has to have a name equal to __VIEWSTATE and as ASP.NET derives the field's name from its id value we'd have to have an id called __VIEWSTATE . The problem here is that variable names beginning with a double underscore are not CLS compliant. In order to get around that we'll use the asp:Literal control to write out the populated input tag when needed. This way we can call the literal id what we like. Add the lines of code in Listing 06 to the derived CustomPage class in our Liquid.Tutorial.cs file.

Listing 06

protected override Object LoadPageStateFromPersistenceMedium()
{
  LosFormatter format = new LosFormatter();
  String viewState = HttpContext.Current.Request.Form["__VIEWSTATE"].ToString();
  return format.Deserialize(viewState);
}
protected override void SavePageStateToPersistenceMedium(Object viewState)
{
  LosFormatter format = new LosFormatter();
  StringWriter writer = new StringWriter();
  format.Serialize(writer, viewState);
  this.inputViewState.Text = "<input type=\"hidden\"
    name=\"__VIEWSTATE\" value=\"" + writer.ToString() + "\"/>";  
}

LosFormatter is a somewhat undocumented class that is used to write the view-state object to a Base64 string and back again. It's worth pointing out that in .NET runtime version 1.1 the LosFormatter can be created by passing in an MD5 checksum; this is recommended for additional security but beyond the scope of this article.

Finally, in order to prove that the form is in fact submitting and firing events correctly we add the final piece of code from Listing 07 .

Listing 07

protected void Page_Load(Object sender, EventArgs e)
{
  if(Page.IsPostBack)
  {
    outputText.Text = "Is post back";
  }
}

The code writes an output string to the final asp:Literal control when the form is submitted correctly. When the form is viewed and submitted in a browser you should see something similar to Figure 1c .

Figure 1d
Figure 1c

The final test is to view the page source code and submit it to the W3C's validating service for final validation. Hopefully you should see the following message.

This Page Is Valid XHTML 1.0 Strict!

Step 5: Adding Some Error Checking

Assuming you've been paying attention so far you'll have probably realised the one function we overwrote but didn't replace when we overrode the HtmlForm.RenderChildren() method, was the check to see if a form already exists on the page. If you're a solo developer working with simple pages this may not be any issue. You can probably remember not to add more than one form to each page. If however the project is larger or involves a team of developers you may require this helpful functionality. The problem is the functionality to perform such a check is locked away in private functions so we'll have to create our own solution. To start with, add the private field and internal accessor code found in Listing 08 to the CustomPage class.

Listing 08

private Boolean _fOnFormRenderCalled = false;
internal void OnFormRender()
{
    if(this._fOnFormRenderCalled)
    {
      throw new HttpException("A page can have only one visible
        server-side CustomForm tag");
    }
    else
    {
      this._fOnFormRenderCalled = true;
    }
}

The Boolean field will allow us to set and get a flag stating if a form already exists on a page. Add the following code to the RenderChildren() method of the CustomForm class to set and get the property via the accessor property:

((CustomPage)this.Page).OnFormRender();

This line of code simply casts the base page class as the derived page class so that it can read and write the flag field. Compile the library assembly, copy it to the /bin directory and run the code. If you try and add a second CustomForm to the replacementform.aspx page, you should see the error message shown in Figure 1d .

Figure 1d
Figure 1d

Remove the second CustomForm tag and you're done. Aside from the aforementioned JScript code inclusions we've managed to replace all of the functionality found in the HtmlForm control into a handy XHTML compliant version.

Summary

The goal of this article was to provide an insight into the way Microsoft have structured their HtmlControls and how you can go about making them more XHTML friendly. We first looked at a basic ASP.NET HtmlForm control and saw how it failed to validate to W3C standards. We then looked into one possible solution that overrides the rendering of the HtmlForm control and Page view-state handling. This was then validated using the W3C Validator.

Feel free to download the source code using the link provided, should you have any questions or comments please post them below!

You might also like...

Comments

Contribute

Why not write for us? Or you could submit an event or a user group in your area. Alternatively just tell us what you think!

Our tools

We've got automatic conversion tools to convert C# to VB.NET, VB.NET to C#. Also you can compress javascript and compress css and generate sql connection strings.

“The most exciting phrase to hear in science, the one that heralds new discoveries, is not 'Eureka!' but 'That's funny...'” - Isaac Asimov