Put Your Pages and Views on Lockdown

lockdownAs 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.

What others have said

Requesting Gravatar... Vijay Santhanam May 05, 2009 1:42 AM
# re: Put Your Pages and Views on Lockdown
Cool! What else can we do with this? How does this make writing with the AspxViewEngine any better?

Requesting Gravatar... Brian Mains May 05, 2009 2:00 AM
# re: Put Your Pages and Views on Lockdown
Really nice feature; never knew it was available. I'm finding ASP.NET does have a good bit of "extensibility gems."
Requesting Gravatar... labilbe May 05, 2009 2:04 AM
# re: Put Your Pages and Views on Lockdown
Nice article, thanks!
Requesting Gravatar... JC Grubbs May 05, 2009 2:08 AM
# re: Put Your Pages and Views on Lockdown
This is awesome! My team is currently working an a pretty large scale ASP.NET MVC application, and I've been struggling with ways to constrain what we're doing in veiws...I can see a whole host of new opportunities using this method. Thanks ;)
Requesting Gravatar... Chad Moran May 05, 2009 2:18 AM
# re: Put Your Pages and Views on Lockdown
Great article Phil. There needs to be a ASP.NET secrets book. I've found so many hidden features that have been in since 2.0 and this is yet another one.
Requesting Gravatar... Kelly Stuard May 05, 2009 3:19 AM
# re: Put Your Pages and Views on Lockdown
Stupid question... Why can't we specify multiple 'PageParseFilter's and have them co-operate with each other on the "Least permissive" set of results? Why can't 'MyPageParserFilter' co-exist with 'ViewTypeParserFilter'? It looks like if one returs NumberOfControlsAllowed of 5 and another 4 then the obvious result would be 4. AllowControl true and false would be false. Etc.
Requesting Gravatar... Kamil Zadora May 05, 2009 3:27 AM
# re: Put Your Pages and Views on Lockdown
Interesting indeed, have my vote for ASP.NET secrets book!
Requesting Gravatar... Mohamed Meligy May 05, 2009 8:33 PM
# re: Put Your Pages and Views on Lockdown
Cool one really :).

But are you going to make the restrictions default in ASP.NET MVC vNext like you did with removing source file ??
(hint: I don't think you should)
Requesting Gravatar... almny May 05, 2009 9:29 PM
# re: Put Your Pages and Views on Lockdown
Interesting post
thanks for this useful article
Requesting Gravatar... peter.o May 05, 2009 9:50 PM
# re: Put Your Pages and Views on Lockdown
This MVC team is really some kind of 'resistance'! Everything you guys do breaks the paradigm ... the old paradigm turned many away from MSFT.

It's so refreshing to read all these demystifying stuff. Before now, it was: "We now give you this feature that will change the way you do this and that". Now, it is, "We have provided this feature to give you the option of doing this or that, AND this is how we implemented it, AND this is how you can adapt and modify it."

Awesome times!!
Requesting Gravatar... David Fauber May 05, 2009 11:09 PM
# re: Put Your Pages and Views on Lockdown
"extensibility gems."
"grab the ASP.NET MVC source code for that type, rename the type and namespace, and then change it to meet our needs."

That doesn't really seem like an extensibility point to me.
Requesting Gravatar... Juan Zamudio May 06, 2009 2:56 PM
# re: Put Your Pages and Views on Lockdown
I love this, at my job they are crazy about the DevExpress Suite and they found a video showing how to use some of the DevExpress controls in an ASP.Net MVC site, i just found a way to avoid that catastrophe.


Requesting Gravatar... Thanigainathan.S May 06, 2009 3:19 PM
# re: Put Your Pages and Views on Lockdown
Hi Phil,

Thanks for letting us know about this feature. This will be very helpfull in customizing the ASP.Net for our needs.

Thanks,
Thani
Requesting Gravatar... Sean May 06, 2009 6:03 PM
# re: Put Your Pages and Views on Lockdown
I can't help thinking when I read this that the type should never have been internal to start with. I get that you say it's an implementation detail, but it's one that takes an extensibility point from ASP.NET and breaks it.

Given that the major selling point is that ASP.NET MVC doesn't try to hide the implementation details of how the web works, why would you try to tuck these little corners out of view from the type of person who wants full control in the first place?
Requesting Gravatar... Haacked May 07, 2009 12:05 PM
# re: Put Your Pages and Views on Lockdown
@Sean yeah, you make a good point and in fact, I made a similar argument in a debate with a teammate on this point. He mentioned the implementation detail point which I think is important.

ASP.NET MVC was built out-of-band to the ASP.NET Framework. What that means is that we couldn't make changes to ASP.NET itself when writing MVC. For example, we couldn't make changes to the Page class in order to do what we want.

If we could have made changes to core ASP.NET, we would not have implemented this feature in this manner. Between you and me, it's a bit of a dirty hack, but one that provides a valuable feature.

In the future, when we can change the core ASP.NET bits (aka ASP.NET 4) AND when MVC takes a dependency on those bits, then we can completely get rid of this class.

In that case, we'd rather not have to support a class we intend to get rid of, so it's definitely easier on our part to keep it internal.

Having said that, we're still open to making it public. As my coworker said, "What's the scenario?". Now, I gave him a scenario, but my opinion doesn't count as much as our customers. And so far, not one single customer has complained that something they were trying to accomplish was broken due to this.

Of course, most people didn't even know this was here. Being the mischevious little monkey that I am, I've hopefully shed light on this and if customers start complaining that REAL scenarios that they are trying to do are painful because of this, we'll have more incentive to make it public.

However, I just hope we can fix the core framework soon enough that we can simply dump this class.
Requesting Gravatar... yazilim May 09, 2009 6:12 AM
# re: Put Your Pages and Views on Lockdown
nice hidden feature i didn't know of like others
Requesting Gravatar... Craig S. May 15, 2009 9:35 AM
# re: Put Your Pages and Views on Lockdown
I agree with Kelly Stuard above, why not have multiple parse filters work together, much as in WinForms you have the multicast delegates for events (although, with those, IIRC, there is no guarantee about the order in which the delegates are called). Regardless, I think Kelly makes a good point.

In any event, I also agree with Sean and Phil's response. Let's hope the class eventually disappears.
Requesting Gravatar... Colin Jack May 28, 2009 5:13 AM
# re: Put Your Pages and Views on Lockdown
"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"

*Sigh*

What do you have to say?

(will show your gravatar)
Please add 4 and 1 and type the answer here: