Is there a way to intercept the page lifecycle from an MVC controller and inject code somewhere on the page or DOM? I would like to add HTML, CSS, and JavaScript onto the page header and footer, but without adding them to the _Layout view which is embedded in an assembly somewhere. SO I would like to do this from a controller of the Global.asax / Application_Start. Is this possible?
1 Answer
I did something similar to that.
First, you need to add placeholder inside your layout files (where you want to inject the code)
Second, you need to add a global ActionFilterAttribute in your global.asax. In the FilterAttribute, override filter only the OnActionExecuted and filter only ViewResults.
Third, you need to change the Response.Filter, with a new filter that will search for the placeholders inside the html code, and replace it with your own.
I wrote exactly that, I added a way to declare Styles & Scripts anywhere in the code, and when the result is done, the resources are injected into the head tag.
public class DynamicHeaderHandler : ActionFilterAttribute
{
private static readonly byte[] _headClosingTagBuffer = Encoding.UTF8.GetBytes("</head>");
private static readonly byte[] _placeholderBuffer = Encoding.UTF8.GetBytes(DynamicHeader.SectionPlaceholder);
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
if (filterContext.Result is ViewResult)
{
filterContext.HttpContext.Response.Filter = new ResponseFilter(filterContext.HttpContext.Response.Filter);
}
base.OnActionExecuted(filterContext);
}
private class ResponseFilter : MemoryStream
{
private readonly Stream _outputStream;
public ResponseFilter(Stream aOutputStream)
{
_outputStream = aOutputStream;
}
public override void Close()
{
_outputStream.Flush();
_outputStream.Close();
}
public override void Write(byte[] buffer, int offset, int count)
{
var maxIndex = IndexOfSubArray(buffer, _headClosingTagBuffer);
var index = IndexOfSubArray(buffer, _placeholderBuffer, maxIndex);
if (index == -1)
{
_outputStream.Write(buffer, offset, count);
}
else
{
var html = DynamicHeader.GetHtml();
var injectBytes = Encoding.UTF8.GetBytes(html);
var newBuffer = buffer.Take(index).Concat(injectBytes).Concat(buffer.Skip(index + _placeholderBuffer.Length)).ToArray();
_outputStream.Write(newBuffer, 0, newBuffer.Length);
}
}
private static int IndexOfSubArray(byte[] aBuffer, byte[] aSubBuffer, int aMaxIndex = int.MaxValue)
{
if (aBuffer.Length < aSubBuffer.Length)
{
return -1;
}
for (var outerIndex = 0; outerIndex < aBuffer.Length && outerIndex < aMaxIndex; ++outerIndex)
{
var isEqual = true;
for (int subInnerIndex = 0, innerIndex = outerIndex; subInnerIndex < aSubBuffer.Length && innerIndex < aBuffer.Length; ++subInnerIndex, ++innerIndex)
{
if (aBuffer[innerIndex] != aSubBuffer[subInnerIndex])
{
isEqual = false;
break;
}
}
if (isEqual)
{
return outerIndex;
}
}
return -1;
}
}
}
I attached the ActionFilterAttribute, which is probably whats relevant to you.
If you want to see the entire code, you can visit here: Add Styles & Scripts Dynamically
2 Comments
</he and the next starting with ad>? In that case your code won't detect the end of the head. Typically the chunks passed to Write should be larger than a common HTML header, but still. You should probably buffer all input and compare the buffered input with the closing tag buffer. After the end of the head you can write everything directly to the wrapped output stream, of course.