Click here to Skip to main content
15,895,772 members
Articles / MVVM

Using MVVM in MVC Applications – Part 2

Rate me:
Please Sign up or sign in to vote.
5.00/5 (2 votes)
6 Mar 2017CPOL5 min read 7.7K   3   1
In this post, you are going to learn to search for products. You also learn how to handle all post backs through a single method in your MVC controller. You will add code to check for no rows being returned, and display a message to the user.

This blog post continues from where the last blog post left off. You are going to learn to search for products. You also learn how to handle all post backs through a single method in your MVC controller. You will add code to check for no rows being returned, and display a message to the user. Finally, you break up the single page into multiple partial pages.

Search for Products

You are now going to add a text box to allow the user to fill in a partial product name to search on. You are going to add two new buttons; one to allow the user to search on the product name they fill in, and one to clear the product name text box as shown in Figure 19.

Figure 19: Add a search area for your product page

To start out, you need a class to hold the search data. Currently, there is only the one search field, Product Name, but you might add additional ones later. This means you only need to add one property to this new class.

Go to PTC.DataLayer project and add a new folder called \EntityClasses. Add a new class called ProductSearch under the \EntityClasses folder. Write the following code in this class.

C#
public class ProductSearch
{
  public ProductSearch() : base() {
    Init();
  }

  public void Init() {
    // Initialize all search variables
    ProductName = string.Empty;
  }

  public string ProductName { get; set; }
}

Go to the PTC.ViewModelLayer project and open the ProductViewModel class. Add a using statement at the top of this class so you can use the new ProductSearch class.

C#
using PTC.DataLayer.EntityClasses;

Add a new property to your view model class that you can use to bind to the user interface.

C#
public ProductSearch SearchEntity { get; set; }

Add the following line of code to the Init() method

C#
SearchEntity = new ProductSearch();

Locate the BuildCollection method and add the following code after you set the DataCollection to the results of the db.Products.ToList().

C#
// Filter the collection
if (DataCollection != null && DataCollection.Count > 0) {
  if (!string.IsNullOrEmpty(SearchEntity.ProductName)) {
    DataCollection = DataCollection.FindAll(
      p => p.ProductName
        .StartsWith(SearchEntity.ProductName,
           StringComparison.InvariantCultureIgnoreCase));
  }
}

Hook Up the Buttons

Many MVC developers add a separate controller method for each button and hyperlink they add to a page. This leads to a lot of methods in your controller. Instead, add a string property, named EventAction, in your ProductViewModel class that tells you which button or hyperlink was pressed. This string value is going to be set into the EventAction property via a tiny bit of jQuery code. Add the following property to the ProductViewModel class.

C#
public string EventAction { get; set; }

Modify the Init() method to initialize this property.

C#
EventAction = string.Empty;

Go back to the PTC web project, open the Product.cshtml page, and add a <form> tag around your HTML using the HTML helper BeginForm() method.

C#
@using (Html.BeginForm()) {
  // HTML Code you wrote before
}

Add a hidden control to bind to the EventAction property you created.

C#
@using (Html.BeginForm()) {
  @Html.HiddenFor(m => m.EventAction, 
                  new { data_val = "false" })
  // HTML Code you wrote before
}

Build Search Input HTML

Add a panel to build the search area on the page. Add the following HTML below the hidden control and before the other HTML code you wrote earlier. Notice in this code, you are binding to the ProductName property of the ProductSearch class you built earlier.

HTML
<div class="panel panel-primary">
  <div class="panel-heading">
    <h1 class="panel-title">Search for Products</h1>
  </div>
  <div class="panel-body">
    <div class="form-group">
      @Html.LabelFor(m => m.SearchEntity.ProductName,
                     "Product Name")
      @Html.TextBoxFor(m => m.SearchEntity.ProductName, 
                       new { @class = "form-control" })
    </div>
  </div>
  <div class="panel-footer">
    <button id="btnSearch"
            class="btn btn-sm btn-primary"
            data-pdsa-action="search">
      <i class="glyphicon glyphicon-search"></i>
      &nbsp;Search
    </button>
    <button id="btnReset"
            class="btn btn-sm btn-primary"
            data-pdsa-action="resetsearch">
      <i class="glyphicon glyphicon-share-alt"></i>
      &nbsp;Reset
    </button>
  </div>
</div>

Add JavaScript File for Retrieve Action

In the HTML you just wrote, notice the data-pdsa-action attributes are filled in with two string values; search and resetsearch. Each time one of the buttons is clicked on, you want to take the associated string value and put it into the hidden field. You then submit the form to have it post back to the Product controller with the SearchEntity.ProductName value from the text box filled in, and the EventAction property filled in with either "search" or "resetsearch".

Right mouse click on the \scripts folder and select Add | JavaScript file. Set the name to pdsa-action.js. Write the following code within this file:

C#
$(document).ready(function () {
  // Connect to any elements that have 'data-pdsa-action'
  $("[data-pdsa-action]").on("click", function (e) {
    e.preventDefault();
    // Fill in hidden field with action to post back to model
    $("#EventAction").val($(this).data("pdsa-action"));
    // Submit form with hidden values filled in
    $("form").submit();
  });
});

Go to the bottom of your Product.cshtml page and add a reference to this script file.

C#
@section scripts {
  <script type="text/javascript"
          src="~/scripts/pdsa-action.js"></script>
}

Add Post Method in Controller

Now that you are ready to handle post backs from the user, you need to add an HttpPost method to your ProductController class. Open the ProductController class and add a new method that looks like the following:

C#
[HttpPost]
public ActionResult Product(ProductViewModel vm) {
  // Handle action by user
  vm.HandleRequest();
  // Rebind controls
  ModelState.Clear();
      
  return View(vm);
}

Modify HandleRequest Method

Previously in the HandleRequest() method, you had it calling the BuildCollection and nothing else. You now have a couple of new actions that can be performed. The user can choose to "search" or "resetsearch". You need to add a switch…case to handle these different event actions. Locate the HandleRequest() method in your ProductViewModel and modify it to look like the following:

C#
public void HandleRequest() {
  // Make sure we have a valid event command
  EventAction = (EventAction == null ? "" :
                   EventAction.ToLower());
  switch (EventAction) {
    case "search":
      break;
    case "resetsearch":
      SearchEntity = new ProductSearch();
      break;
  }
  BuildCollection();
}

Run the application, type in "b" into the Product Name search box, click the Search button, and you should see just a list of products that start with the letter "b".

Inform User of No Rows

When the user searches and no rows are returned from that search, or if there are just no rows in the Product table, you should inform the user of this fact. You can display messages to the user using the Message property you added earlier to the ProductViewModel class. Modify the HandleRequest() method and add the following after the call to the BuildCollection() method.

C#
if (DataCollection.Count == 0) {
  Message = "No Product Data Found.";
}

Open the Product.cshtml page and add the following lines of code around all the HTML that displays the table on this page. Don’t wrap it around the "search" area of the page.

HTML
@if (Model.DataCollection.Count > 0) {
  <div class="table-responsive">
  ...  // ALL THE OTHER HTML HERE
  </div>
}
else {
  <div class="row">
    <div class="col-xs-12">
      <div class="jumbotron">
        <h2>@Model.Message</h2>
      </div>
    </div>
  </div>
}

Run the Product page and enter a few random letters into the search text box. Click on the Search button and you should see the message displayed.

Use Partial Pages

Instead of putting all the HTML for this page on a single cshtml page, let’s break each area up into separate partial pages. Copy and paste the Product.cshtml page into the \Product folder. Rename the new file to _ProductList.cshtml. Leave the @model directive at the top of the file, and then just leave the logic for building the HTML table of product data.

Once again, copy and paste the Product.cshtml page into the \Product folder. Rename the new file to _ProductSearch.cshtml. Leave the @model directive at the top of the file, and leave everything that is associated with the search area. This is the HTML between the <div class="panel panel-primary"> and the </div> for that panel.

Open the Product.cshtml and modify this file to look like the following:

HTML
@using PTC.ViewModelLayer
@model ProductViewModel
@using (Html.BeginForm()) {
  @Html.HiddenFor(m => m.EventAction, 
                  new { data_val = "false" })
  @Html.Partial("_ProductSearch", Model)
  @Html.Partial("_ProductList", Model)
}
@section scripts {
  <script type="text/javascript" 
          src="~/scripts/pdsa-action.js"></script>
}

Run the application and everything should still be working.

Summary

In this blog post, you broke up the MVC page into two partial pages that are called from the main page. You learned how to handle all post backs with just a single post method in your MVC controller. You created additional code in your view model to handle searching for products. In the next blog post, you add a detail page and learn to add products to the product table.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Employed (other) PDS Consulting
United States United States
Paul has been in the IT industry over 35 years. In that time he has successfully assisted hundreds of companies architect software applications to solve their toughest business problems. Paul has been a teacher and mentor through various mediums such as video courses, blogs, articles and speaking engagements at user groups and conferences around the world. Paul has 28+ courses in the www.pluralsight.com library (https://www.pluralsight.com/author/paul-sheriff) on topics ranging from LINQ, JavaScript, Angular, MVC, WPF, XML, jQuery and Bootstrap. Contact Paul at psheriff@pdsa.com.

Comments and Discussions

 
QuestionSay what Pin
Sacha Barber6-Mar-17 11:13
Sacha Barber6-Mar-17 11:13 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.