Have you ever created a WebControl in MVC and though "Gee, it sure would be nice if I could add a stylesheet/script to the header of my page." It's not as easy as it used to be. Lets just pretend you ran this code right here.
It would compile and run just fine but you wouldn't see your script! That's because the page life-cycle has come and gone and you're in the middle of the page render! We're too late!
Inline code doesn't make it easy to communicate with other parts of the page. As far as I can tell, inline code is rendered in the same order that it appears on the page (or at least, parsed), so once you finally get to the end of the page, the code at the start has already been executed.
A Workable Solution
I've included some code at the end of this post that simplifies the whole process and for the remaining of this post I explain how it works. In an effort to solve this problem I ended up coming up with two extension methods that are attached to the HtmlHelper.
InsertMarker(id) : Creates a point on the page that will render any content that is added to it. Uses the id provided (either an enum or a string)
AppendToMarker(id, content) : Appends the string content to the matching marker id (either an enum or a string)
These methods allow marker points to be added to the page inline and then content appended to each marker, regardless of where they are used. Let's look at an example of the code. If this code doesn't make sense just yet, don't worry -- I'll explain @html.smile()
A Simple Example
Lets use this code to add a script to the header of our page -- but from within a WebControl.
Index.aspx (the View)
<%@ Page Language="C#" Inherits="System.Web.Mvc.ViewPage" %> <html> <head runat="server"> <title>Just A Test</title> <% this.Html.InsertMarker(Document.Head); %> </head> <body> <% this.Html.RenderPartial("SomeControl"); %> </body> </html>
SomeControl.ascx (the WebControl)
As you can see, we can pick the name of the marker that we want to append content and it is added to the page -- all from within our WebControl! You can define your own custom markers and append content to really anywhere you want on the page. If the marker isn't found, then the content is never rendered.
How It Works (The Short Version)
Once we're in the middle of our render event for our page it makes it quite difficult for us to make many changes to the rest of the document. This is where the
HttpContext.Response.Filter stream comes in handy. With this stream we're able to intercept all of the content before it is really sent out.
Each time we set a marker onto the page we place a bit of text to mark it as a point we need to replace before we push the content out. I had preferred the idea of remembering the position of the output stream, but apparently the output stream is written to all at once which took that out of the picture.
Once we're done its as simple as using a Regular Expression and replacing all of the markers with the correct content.
MVC-ish - Kinda Sorta...
This may not have been what the guys who designed MVC were thinking of when they created it, but the ability to work with multiple areas of the page was definitely a feature that I felt was missing. How could something like this help you with your projects?
July 28, 2009
Include Stylesheets and Scripts In MVC
An idea how to include external resources from MVC views.