受欢迎的博客标签

Highlight Keywords From Response Using ASP.NET Core Middleware

Published

http://www.binaryintellect.net/articles/439edbad-fd51-4eaf-953a-5484941c7e8c.aspx Recently 

ecently 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?}");
    });
}