Highlight Keywords From Response Using ASP.NET Core Middleware
Recently I needed to search for certain keywords from the response body and
then highlight them with different color. In ASP.NET Web Forms you would have created an HTTP module to
achieve this. In ASP.NET MVC you would have written a
custom filter to accomplish this task. In ASP.NET Core you can write a
custom middleware to do the same. The remainder of this article shows how.
If you are not familiar with what ASP.NET Core middleware is, I suggest you
read
this article first and then continue with this the following example.
Ok. Let's take a scenario the closely resembles what has been mentioned above
and then build a simple example to demonstrate what we learn.
Assume that you are building a website using ASP.NET Core. The website has a
search box as shown below:

You can enter some keyword to search for and hit the Search button. The
server side code then generates the response based on the business logic and
also highlights the keyword. The following figure shows how keyword "Nancy" is
highlighted:

Why would you need such functionality? Although the above scenario is quite
straightforward and strictly speaking doesn't need middleware as such, think of
the following possibilities:
- You wish to automatically insert advertisements into the response
depending on one or more keywords.
- You may wish to add headers or footers dynamically.
- You wish to dynamically tamper with the response HTML to suit your
needs.
- You wish to do all this without having to write any specific code in the
controllers or views.
In nutshell, you wish to manipulate the response from the server to suit your
needs.
Now that you understood what we want to accomplish, let's create a simple
middleware that does what we want.
Begin by creating a new ASP.NET Core project based on the Web Application
template. Then add HomeController and Index view as usual. Place the following
HTML markup inside the Index view.
@{
ViewData["Title"] = "Home Page";
}
<div>
<form asp-action="Index" asp-controller="Home" method="post">
<input type="text" name="keyword" />
<input type="submit" value="Search" />
</form>
<p>Nancy Davolio : Her ducation includes a BA
in psychology from Colorado State University in 1970.
She also completed "The Art of the Cold Call."
Nancy is a member of Toastmasters International.</p>
</div>
The Index view consists of a form tag helper that submits to the Index action
of the HomeController. The method is set to POST. The form consists of a textbox
named keyword and a submit button. The paragraph element below the form simply
holds a bunch of text data. In a more realistic situation this text will be
generated dynamically from a database based on some server side processing. To
simplify the things, we won't go into data access code here.
Ok. Now add a middleware class to the project and name it - MyMiddleware. The
complete code of MyMiddleware is shown below:
public class MyMiddleware
{
private RequestDelegate nextMiddleware;
public MyMiddleware(RequestDelegate next)
{
this.nextMiddleware = next;
}
public async Task Invoke(HttpContext context)
{
if (context.Request.Method.ToUpper() == "POST")
{
string highlightedText = "";
Stream originalStream = context.Response.Body;
using (MemoryStream newStream = new MemoryStream())
{
context.Response.Body = newStream;
await this.nextMiddleware.Invoke(context);
context.Response.Body = originalStream;
newStream.Seek(0, SeekOrigin.Begin);
StreamReader reader = new StreamReader(newStream);
highlightedText = reader.ReadToEnd();
string keyword = context.Request.Form["keyword"];
highlightedText = highlightedText.Replace
(keyword, $"<span class='Highlight'>{keyword}</span>");
await context.Response.WriteAsync(highlightedText);
}
}
else
{
await this.nextMiddleware.Invoke(context);
}
}
}
The MyMiddleware class consists of a public constructor and asynchronous
Invoke() method.
The constructor receives RequestDelegate object that is a pointer to the next
middleware in the chain. The Invoke() method is responsible for invoking your
custom code and then call the next middleware.
The constructor first checks whether the request is POST or not. We want to
highlight a keyword upon post operation, so this check is necessary. The code
then creates a new MemoryStream object and sets it to the Response's Body
property. It then invokes the next middleware so that we can begin our highlight
operation.
The code then reads the whole response text using a StreamReader's ReadToEnd()
method. Once we have the text, we figure out the keyword entered in the textbox.
This is done using the Request's Form collection. Then the code replaces that
keyword from the highlightedText string variable by wrapping it in a <span> tag.
The <span> tag bears the Highlight CSS class that takes care of the highlight
background color and text color. Finally, the modified text is written onto the
response stream using the WriteAsync() method.
The Highlight CSS class is shown below:
.Highlight
{
background-color:brown;
color:white;
font-weight:bold;
padding:4px;
}
This completes the middleware class. Now, let's create an extension method so
that we can wire it more easily. Add MyMiddlewareExtensions class to the project
and write the following code to it:
public static class MyMiddlewareExtensions
{
public static IApplicationBuilder UseMyMiddleware
(this IApplicationBuilder app)
{
return app.UseMiddleware<MyMiddleware>();
}
}
The above code consists of an extension method named UseMyMiddleware() and
internally calls IApplicationBuilder's UseMiddleware<T>() method.
Now, you can wire your middleware by calling this extension method from the
Configure() method as shown below:
public void Configure(IApplicationBuilder app,
IHostingEnvironment env,
ILoggerFactory loggerFactory)
{
app.UseMyMiddleware();
....
....
app.UseStaticFiles();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template:
"{controller=Home}/{action=Index}/{id?}");
});
}
That's it! Run the application and see if it works as expected.