Modify Response Content in ASP.NET MVC using a Custom Action Filter
Recently I came across a requirement wherein I wanted to modify the response
content being generated from an ASP.NET MVC view. What I wanted to accomplish was this - read
the HTML content that is being sent to the browser and highlight certain words
from the content. This article
explains how this can be accomplished.
Let's assume that you have a view that displays list of employees from the
Employees table of Northwind database.
Suppose that you have a separate database table that lists certain keywords.
You need to intercept response content of each view and highlight those
keywords. In the above figure USA is such a keyword. The logic to do this
highlighting can't be included in individual controller or view for the sake of
avoiding duplication. Moreover, doing so would mix two separate operations -
displaying data and highlighting keywords - causing design issues. A real world
example of this nature is advertising engines that intercept HTML content of a
page and then insert hyperlinks around certain keywords. Obviously the page and
the advertisements are separate concerns altogether and should be kept separate.
Luckily, ASP.NET MVC allows you to create custom action filters that can do the
job.
Let's create a custom action filter that will read the response stream and
then search the content for USA keyword and then highlight it as needed. We will
call our custom action filter - KeywordFilter. The following code shows how the
filter will be used:
[KeywordFilter]
public ActionResult Index()
{
NorthwindEntities db = new NorthwindEntities();
return View(db.Employees.ToList());
}
Ok. First of all add a new class to the ASP.NET MVC project as shown below:
public class KeywordStream : MemoryStream
{
private readonly Stream responseStream;
public KeywordStream(Stream stream)
{
responseStream = stream;
}
public override void Write(byte[] buffer,
int offset, int count)
{
string html = Encoding.UTF8.GetString(buffer);
html = html.Replace("USA",
"<span class='keyword'>USA</span>");
buffer = Encoding.UTF8.GetBytes(html);
responseStream.Write(buffer, offset, buffer.Length);
}
}
The KeywordStream class inherits from MemoryStream class. The constructor
accepts a Stream object and stores in a private variable responseStream. The
class then overrides Write() method. This is the place where the keyword
highlighting happens. Inside, we read the contents of the stream and replace USA
with highlighted HTML fragment. In the above code I simply wrap the keyword
inside a <span> element but you can add anything of your choice here (such as a
hyperlink). Note that for the sake of simplicity I use
Write() to do this job. If your response content is quite large you may think of
overriding Flush() or Close() instead of Write().
Now add KeywordFilterAttribute class and modify it as shown below:
public class KeywordFilterAttribute:ActionFilterAttribute
{
public override void OnActionExecuting
(ActionExecutingContext filterContext)
{
var originalFilter =
filterContext.HttpContext.Response.Filter;
filterContext.HttpContext.Response.Filter =
new KeywordStream(originalFilter);
}
}
The KeywordFilterAttribute class inherits from ActionFilterAttribute class.
It then overrides OnActionExecuting() methods of the base class. Inside, we grab
the Response.Filter property (which is basically a stream) and store it in a
private variable. We then replace the Filter property with our newly created
stream - KeywordStream - and pass the original filter stream to its constructor.
The following markup shows how the Index view looks like:
@model List<KeywordHighlightDemo.Models.Employee>
...
<style>
.keyword
{
background-color:yellow;
color:red;
font-weight:bold;
padding:2px;
}
</style>
...
<h1>List of Employees</h1>
<table border="1" cellpadding="10">
@foreach(var item in Model)
{
<tr>
<td>@item.EmployeeID</td>
<td>@item.FirstName</td>
<td>@item.LastName</td>
<td>@item.Country</td>
</tr>
}
</table>
...
As you can see, we simply run a foreach loop and output the Employee data in
a table.
That's it! Run the application and see if the USA keyword is getting
highlighted.