Put Your Pages and Views on Lockdown

0 comments suggest edit

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.

Found a typo or error? Suggest an edit! If accepted, your contribution is listed automatically here.

Comments

avatar

20 responses

  1. Avatar for Vijay Santhanam
    Vijay Santhanam May 4th, 2009

    Cool! What else can we do with this? How does this make writing with the AspxViewEngine any better?

  2. Avatar for Brian Mains
    Brian Mains May 4th, 2009

    Really nice feature; never knew it was available. I'm finding ASP.NET does have a good bit of "extensibility gems."

  3. Avatar for labilbe
    labilbe May 4th, 2009

    Nice article, thanks!

  4. Avatar for JC Grubbs
    JC Grubbs May 4th, 2009

    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 ;)

  5. Avatar for Chad Moran
    Chad Moran May 4th, 2009

    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.

  6. Avatar for Kelly Stuard
    Kelly Stuard May 5th, 2009

    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.

  7. Avatar for Kamil Zadora
    Kamil Zadora May 5th, 2009

    Interesting indeed, have my vote for ASP.NET secrets book!

  8. Avatar for Mohamed Meligy
    Mohamed Meligy May 5th, 2009

    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)

  9. Avatar for almny
    almny May 5th, 2009

    Interesting post
    thanks for this useful article

  10. Avatar for peter.o
    peter.o May 5th, 2009

    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!!

  11. Avatar for David Fauber
    David Fauber May 5th, 2009

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

  12. Avatar for Juan Zamudio
    Juan Zamudio May 6th, 2009

    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.

  13. Avatar for Thanigainathan.S
    Thanigainathan.S May 6th, 2009

    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

  14. Avatar for Sean
    Sean May 6th, 2009

    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?

  15. Avatar for Haacked
    Haacked May 7th, 2009

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

  16. Avatar for yazilim
    yazilim May 9th, 2009

    nice hidden feature i didn't know of like others

  17. Avatar for Craig S.
    Craig S. May 15th, 2009

    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.

  18. Avatar for Colin Jack
    Colin Jack May 28th, 2009

    "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*

  19. Avatar for Saeed Neamati
    Saeed Neamati May 28th, 2012

    So, seems that in ASP.NET, we can replace everything with our customized code. This seems to be promising, but I can find no real-world application in which you want to "prevent a developer from injecting an script tag in a View". I mean, maybe that level of extensibility adds no extra value at all to the real-world examples.

  20. Avatar for Michael F.
    Michael F. July 16th, 2012

    Hi Phil,
    This looks very promising but when I try it, I get a System.Web.HttpException saying my master page allows a limit of 1 dependencies (direct or indirect) and that limit has been exceeded.
    I can't seem to find any documentation that will help me resolve this. Can you point me in the right direction?
    Thanks,
    Michael