top button
Flag Notify
    Connect to us
      Site Registration

Site Registration

Modify Response Content In ASP.NET MVC Using A Custom Action Filter

0 votes
308 views

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.

image

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.

posted Dec 26, 2016 by Shivaranjini

  Promote This Article
Facebook Share Button Twitter Share Button LinkedIn Share Button


Related Articles

HTML5 custom data attributes (data-*) are used to store arbitrary pieces of metadata about an element. One way to store such metadata in data-* attributes is to create a separate data-* attribute for each piece of information you wish to store. This approach works well if there are only a few data-* attributes. However, at times you need to store a bunch of metadata in data-* attributes. In such cases instead of creating multiple data-* attributes you can create just one data-* attribute and store all the pieces of  metadata as an object in JSON format. To that end this article illustrates how custom data attributes can be used to store JSON data in an ASP.NET MVC application.

Consider the following view:

image

The above view shows a table that lists records from Customers table of Northwind database. Each row has Order Details button. Click on the Order Details button displays details such as OrderID and ShippingDate for the last order placed by the customer under consideration. These details are displayed in an alert dialog like this:

image

If you see the Customers table, it has only two columns - one showing CustomerID and the other showing CompanyName. From where does the order details such as OrderID and OrderDate came from? These details are stored in a custom data attribute named data-lastorder of each <tr> element. For example, consider the following markup that is revealed in the HTML source of the page in the browser:

image

As you can see the <tr> element has data-lastorder attribute that stores an object in JSON format. The object stores CustomerID, OrderID and OrderDate. The data-lastorder attribute can be accessed using jQuery code. The following jQuery code shows how this can be accomplished.

 $(document).ready(function () {
  $("input:button").click(function (evt) {
    var orderData = $(evt.target).closest("tr").data("lastorder");
    alert("Last Order : #" + orderData.OrderID + " on " + orderData.OrderDate);
  });
});

As you can see the ready() handler uses :button selector to match all the <input> elements of type button. This will return all the Order Details buttons shown in the earlier figure. The code then wires click event handler to the click event of the Order Details buttons. The click event handler finds <tr> element closest to the button being clicked. This is done using the closest() method. The data() method returns the value of a specified custom data attribute. In this case lastorder is specified (you can omit data- from the attribute name). The return value of data() in this case will be a JSON object and is stored in orderData variable. An alert dialog then displays the OrderID and OrderDate properties of the orderData object.

So far so good. But how to emit the data-lastorder attribute to the client browser? Obviously you can't hard-code it. It has to be generated on the fly using server side code. In this case you will have to write such code in the controller and the view.

Let's assume that you have Entity Framework data model for the Customers and Orders tables as shown below:

image

 

Also assume that you have HomeController with Index() method that fetches the data and makes it suitable to go inside data-lastorder attribute. The following code shows how Index() method can do this job:

public ActionResult Index()
{
  NorthwindEntities db = new NorthwindEntities();
  var customers = from c in db.Customers
                  where c.Country == "USA"
                  select c;
  List<Customer> customerList = customers.ToList();
  Dictionary<string, string> orderDict = new Dictionary<string, string>();
  foreach (Customer obj in customerList)
  {
    var order = (from o in db.Orders
                 where o.CustomerID == obj.CustomerID
                 orderby o.OrderDate descending
                 select o).FirstOrDefault();
    string jsonOrder = JsonConvert.SerializeObject(
                       new { CustomerID=order.CustomerID,
                             OrderID=order.OrderID,
                             OrderDate=order.OrderDate });
    orderDict.Add(order.CustomerID, jsonOrder);
  }
  ViewData["orderDict"] = orderDict;
  return View(customerList);
}

The above code creates a database context object (db). A LINQ to Entities query selects all the Customer objects with Country property equal to USA. Then a generic List of Customer objects is obtained by calling ToList() method of the customers variable. A dictionary is created to store order information for all the customers. The key of the dictionary is CustomerID and its value is JSON representation of the order details. Next, a foreach loop iterates through all the customer list. With every iteration the most recent Order is retrieved from the Orders DbSet for a CustomerID. This is done by sorting the results in descending order and then calling FirstOrDefault() method. Then comes the important part. The JsonConvert class of Json.NET library is used to serialize an anonymous object containing order details such as CustomerID, OrderID and OrderDate. The SerializeObject() method accepts an object and returns its JSON representation as a string. The JSON representation of the order data is stored in the dictionary created earlier. The customerList is passed to the Index view as its model whereas orderDict is passed to the view in a ViewData variable.

This completes the controller. The remaining part of the code goes inside the Index view and is shown below:

<%@ Page Language="C#" Inherits="System.Web.Mvc.ViewPage
<List<JSONObjectInCustomDataAttribute.Models.Customer>>" %>
...
<table border="1" cellpadding="6">
<% foreach(var customer in Model){ %>
<tr data-lastorder='<%= ((Dictionary<string,string>)ViewData["orderDict"])
                         [customer.CustomerID] %>'>
<td><%= customer.CustomerID %></td>
<td><%= customer.CompanyName %></td>
<td><input type="button" value="Order Details" /></td>
</tr>
<%}%>
</table>

As shown in the above code, @Page directive sets the model for the view to List<JSONObjectInCustomDataAttribute.Models.Customer>. Make sure to change the namespace of the data model class as per your setup. Then a <table> is generated by iterating through the Model. Each <tr> element emitted has data-lastorder attribute and its value is assigned from the orderDict ViewData variable. Recollect that orderDict is a dictionary where key is CustomerID and value is the order data in JSON format. Then CustomerID, CompanyName values are added to two table cells. The third table cell contains the Order Details button.

That's it! You can now run the application and see whether order data is available in data-lastorder attribute as expected.

READ MORE
...