Put Your Pages and Views on Lockdown
As I’m sure you know, we developers are very particular people and we like to have things exactly our way. How else can you explain long winded impassioned debates over curly brace placement?
So it comes as no surprise that developers really care about what goes in (and behind) their .aspx files, whether they be pages in Web Forms or views in ASP.NET MVC.
For example, some developers are adamant that a page should not include server side script blocks, while others don’t want their views to contain Web Form controls. Wouldn’t it be great if you could have your views reject such code constructs?
Fortunately, ASP.NET is full of lesser known extensibility gems which
can help in such situations such as the PageParseFilter
. MSDN
describes this class as such:
Provides an abstract base class for a page parser filter that is used by the ASP.NET parser to determine whether an item is allowed in the page at parse time.
In other words, implementing this class allows you to go along for the ride as the page parser parses the .aspx file and gives you a chance to hook into that parsing.
For example, here’s a very simple filter which blocks any script tags
with the runat="server"
attribute set within a page.
using System;
using System.Web.UI;
public class MyPageParserFilter : PageParserFilter {
public override bool ProcessCodeConstruct(CodeConstructType codeType
, string code) {
if (codeType == CodeConstructType.ScriptTag) {
throw new InvalidOperationException("Say NO to server script blocks!");
}
return base.ProcessCodeConstruct(codeType, code);
}
public override bool AllowCode {
get {
return true;
}
}
public override bool AllowControl(Type controlType, ControlBuilder builder) {
return true;
}
public override bool AllowBaseType(Type baseType) {
return true;
}
public override bool AllowServerSideInclude(string includeVirtualPath) {
return true;
}
public override bool AllowVirtualReference(string referenceVirtualPath
, VirtualReferenceType referenceType) {
return true;
}
public override int NumberOfControlsAllowed {
get {
return -1;
}
}
public override int NumberOfDirectDependenciesAllowed {
get {
return -1;
}
}
}
Notice that we had to override some defaults for other properties we’re
not interested in such as NumberOfControlsAllowed
or we’d get the
default of 0 which is not what we want in this case.
To apply this filter, just specify it in the <pages />
section of
web.config like so:
<pages
pageParserFilterType="Namespace.MyPageParserFilter, AssemblyName">
Applying a parse filter for Views in ASP.NET
MVC is a bit trickier because
it already has a parse filter registered, ViewTypeParserFilter
, which
handles part of the voodoo black magic in order to remove the need for
code-behind in views when using a generic model
type.
Remember those particular developers I was talking about?
Suppose we want to prevent developers from using server controls which
make no sense in the context of an ASP.NET MVC view. Ideally, we could
simply inherit from ViewTypeParserFilter
and make our change so we
don’t lose the existing view functionality.
That type is internal so we can’t simply inherit it. Fortunately, what we can do is simply grab the ASP.NET MVC source code for that type, rename the type and namespace, and then change it to meet our needs. Once we’re done, we can even share those changes with others. This is one of the benefits of having an open source license for ASP.NET MVC.
WARNING: The fact that we implement a ViewTypeParserFilter
is an
implementation detail. The goal is that in the future, we wouldn’t need
this filter to provide the nice generic syntax. So what I’m about to
show you might be made obsolete in the future and should be done at your
own risk. It’s definitely running with
scissors.
In my demo, I copied the following files to my project:
ViewTypeParserFilter
ViewTypeControlBuilder
ViewPageControlBuilder
ViewUserControlControlBuilder
I then created a new parser filter which inherits the
ViewTypeParserFilter and overrode the AllowControl
method like so:
public override bool AllowControl(Type controlType, ControlBuilder builder) {
return (controlType == typeof(HtmlHead)
|| controlType == typeof(HtmlTitle)
|| controlType == typeof(ContentPlaceHolder)
|| controlType == typeof(Content)
|| controlType == typeof(HtmlLink));
}
This will block adding any control except for those necessary in creating a typical view. You can imagine later adding some easy way of configuring that list in case you do later allow other controls.
Once we’ve implemented this new filter, we can edit the Web.config file within the Views directory to set the parser filter to this one.
This is a powerful tool for hooking into the parsing of a web page, so do be careful with it. As you might expect, I have a very simple demo of this feature here.
Comments
20 responses