in ASP.NET

W3C Validation and ASP.NET ViewState

The problem

I came across a very interesting point about validating XHTML pages using W3C XHTML Strict and ViewState.   By default ASP.NET ViewState adds a number of hidden fields to handle the postback of ASP.NET pages, control state and control event validation.  See example below.

<input type=”hidden” name=”__VIEWSTATE” id=”__VIEWSTATE” value=””  />

When validating web pages using the XHTML strict option, you will find that the above line is invalid:

value of attribute “ID” invalid: “_” cannot start a name .
…type=”hidden” name=”__VIEWSTATE” id=”__VIEWSTATE” value=”/wEPDwUJOTkzNjk4NjQ4

This becomes a major issue when you have clients who expect their product to be compliant with todays web standards.

The solution

Step 1 - I created a FilterModule that inherits the IHttpModule interface which will be the basis of renaming the __VIEWSTATE to VIEWSTATE.

using System;
using System.Web;

namespace Company.Utilities
{
public class IdFilterModule : IHttpModule
{
public void Dispose()
{
}
public void Init(HttpApplication app)
{
app.ReleaseRequestState += new EventHandler(InstallResponseFilter);
}
private void InstallResponseFilter(object sender, EventArgs e)
{
HttpResponse response = HttpContext.Current.Response;

if (response.ContentType == “text/html”)
{
response.Filter = new IdFilter(response.Filter);
}
}
}
}

The above code creates a event handler called InstallResponseFilter that gets fired when the ReleaseRequestState event is raised.   The ReleaseRequestState event saves all the current state data.

Step 2 - I then create a class inheriting from the Stream class that takes the current state data and does a simple search and replace on the key ViewState identifiers and adjusts the output to be what we decide is valid XHTML.

public class IdFilter : Stream
{
Stream responseStream;
StringBuilder responseHtml;
long position;

public IdFilter(Stream inputStream)
{
responseStream = inputStream;
responseHtml = new StringBuilder();
}

public override bool CanRead
{
get
{
return true;
}
}
public override bool CanSeek
{
get
{
return true;
}
}
public override bool CanWrite
{
get
{
return true;
}
}
public override void Close()
{
responseStream.Close();
}
public override void Flush()
{
responseStream.Flush();
}
public override long Length
{
get
{
return 0;
}
}
public override long Position
{
get
{
return position;
}
set
{
position = value;
}
}
public override long Seek(long offset, SeekOrigin origin)
{
return responseStream.Seek(offset, origin);
}
public override void SetLength(long length)
{
responseStream.SetLength(length);
}
public override int Read(byte[] buffer, int offset, int count)
{
return responseStream.Read(buffer, offset, count);
}
public override void Write(byte[] buffer, int offset, int count)
{
string strBuffer = System.Text.UTF8Encoding.UTF8.GetString(buffer, offset, count);

Regex eof = new Regex(“</html>”, RegexOptions.IgnoreCase);

if (!eof.IsMatch(strBuffer))
{
responseHtml.Append(strBuffer);
}
else
{
responseHtml.Append(strBuffer);

string finalHtml = responseHtml.ToString();

finalHtml = finalHtml.Replace(“id=\”__VIEWSTATE\””, “id=\”VIEWSTATE\””);
finalHtml = finalHtml.Replace(“id=\”__EVENTARGUMENT\””, “id=\”\”EVENTARGUMENT”);
finalHtml = finalHtml.Replace(“id=\”\”__EVENTTARGET”, “id=\”\”EVENTTARGET”);
finalHtml = finalHtml.Replace(”  />”, “>”);
finalHtml = finalHtml.Replace(” />”, “>”);
finalHtml = finalHtml.Replace(“/>”, “>”);

byte[] data = System.Text.UTF8Encoding.UTF8.GetBytes(finalHtml);

responseStream.Write(data, 0, data.Length);
}
}
}

The new IdFilterModule gets registered is the web.config file under httpModules.

References:

http://forum.umbraco.org/yaf_postst5965_W3C-Validation-ASPNET-ViewState-and-HTML-401.aspx

http://msdn.microsoft.com/en-us/library/system.web.httpapplication.releaserequeststate.aspx