I had a hard time coming up with the title, because as you know, markup is pretty dynamic in MVC. However, I came across an interesting limitation when it came to rendering markup. I’m not talking about rendering partial view content using Html.Partial, or using a helper method. I’m actually talking about rendering markup segments, which I’ll demonstrate with a VB.NET example (sorry, I’ve been stuck in the VB world for some time, it’s become more natural than C#):
@* SomeView.vbhtml *@ @Code Html.ScriptDefine( @ alert("ME"); ) End Code
Html.ScriptDefine is not something Microsoft created, but a custom extension I created. This was a helper method to register script segments. It is not a method defined globally or in a view, but a helper that’s code, which can be easily reused across projects, which is why I tried to utilize this technique. Somewhere in the master page, a process read all of these scripts and rendered them in the master page. This was excellent; I could define these script blocks anywhere in the view, and they would all get rendered in one place.
My helper looked like the following:
Public Sub ScriptDefine(Of TModel)(html As HtmlHelper, key as String, fn as Action(Of TModel)) Dim script = fn(html.ViewData.Model) 'Store reference to model and script somewhere, 'which the master page retrieves all of the scripts and renders End Sub
It worked, except in one scenario: Partial Views, which is a key reason why I wanted it. See, I often found myself using scripts in a partial view. I tried using an optimization technique where scripts run at the end of the page; the only problem was a partial view that used a script had it’s <script /&rt; block defined wherever the partial was, which was usually above the end of the view. The issue with partial views has to do with the rendering process, and although I wasn’t quite sure how to figure out why, I found a better solution anyway: HelperResult.
By defining the script in a helper (a small caveat) and then storing the helper result, this solved the problem much more easily. I was able to define an extension like the following:
Public Sub ScriptDefineHelper(Of TModel)(html As HtmlHelper, key As String, fn As Func(Of TModel, HelperResult)) Dim helperResult = fn(html.ViewData.Model) 'Returns the content as IHtmlString Dim list = CType(html.ViewContext.HttpContext.Items("_Scripts_"), List(Of String)) if (list Is Nothing) Then list = new List(Of String) End If list.Add(helperResult.ToHtmlString()) 'Store the scripts as a string, which is easy to render later html.ViewContext.HttpContext.Items("_Scripts_") = list End Sub
Now wherever we use our helper, we can use it like:
@Code 'Use in view or partial view Html.ScriptDefineHelper(Function(i) Scripts()) End Code @Helper Scripts() alert("Hello"); End Helper
And we can render out all the scripts with the following code (we can also use a helper method for this):
Dim items = CType(html.ViewContext.HttpContext.Items("_Scripts_"), List(Of String)) For Each item in items @Html.Raw(item) Next