29

The problem I will be describing is very similar to ones I already found (e.g. this post with nearly identical name) but I hope that I can make it into something that is not a duplicate.

I have created a new ASP.NET MVC 5 application in Visual Studio. Then, I defined two model classes:

public class SearchCriterionModel
{
  public string Keyword { get; set; }
}

public class SearchResultModel
{
  public int Id { get; set; }
  public string FirstName { get; set; }
  public string Surname { get; set; }
}

Then I created the SearchController as follows:

public class SearchController : Controller
{
  public ActionResult Index()
  {
    return View();
  }

  public ActionResult DisplaySearchResults()
  {
    var model = new List<SearchResultModel>
    {
      new SearchResultModel { Id=1, FirstName="Peter", Surname="Pan" },
      new SearchResultModel { Id=2, FirstName="Jane", Surname="Doe" }
    };
    return PartialView("SearchResults", model);
  }
}

as well as views Index.cshtml (strongly typed with SearchCriterionModel as model and template Edit) and SearchResults.cshtml as a partial view with model of type IEnumerable<SearchResultModel> (template List).

This is the Index view:

@model WebApplication1.Models.SearchCriterionModel

@{
  ViewBag.Title = "Index";
}

@using (Html.BeginForm())
{
  @Html.AntiForgeryToken()

  <div class="form-horizontal">
    <h4>SearchCriterionModel</h4>
    <hr />
    @Html.ValidationSummary(true, "", new { @class = "text-danger" })
    <div class="form-group">
      @Html.LabelFor(model => model.Keyword, htmlAttributes: new { @class = "control-label col-md-2" })
      <div class="col-md-10">
        @Html.EditorFor(model => model.Keyword, new { htmlAttributes = new { @class = "form-control" } })
        @Html.ValidationMessageFor(model => model.Keyword, "", new { @class = "text-danger" })
      </div>
    </div>

    <div class="form-group">
      <div class="col-md-offset-2 col-md-10">
        <input type="button" id="btnDisplaySearchResults" value="Search" onclick="location.href='@Url.Action("DisplaySearchResults", "SearchController")'" />
      </div>
    </div>
  </div>
}

<div>
  @Html.ActionLink("Back to List", "Index")
</div>
<div id="searchResults">

</div>

As you can see, I added a div with id="searchResults" below the standard template and edited the button. What I want is to display the partial view SearchResults.cshtml in the div on the bottom, but only after the button is clicked. I have succeeded in showing a partial view there by using @Html.Partial("SearchResults", ViewBag.MyData), but it is rendered when the parent view is loaded for the first time and I set ViewBag.MyData in the Index() method already, which is not what I want.

Summary: On clicking the button, I will obtain some List of SearchResultModel instances (via database access) and then the partial view should be rendered, using this newly obtained data as model. How can I accomplish this? I already seem fail at the first step, that is reacting to the button click with the above code. Right now, I navigate to the URL ~/Search/DisplaySearchResults, but of course there's nothing there and no code-behind method is called. In traditional ASP.NET I'd just have added a server-side OnClick handler, set the DataSource for a grid and show the grid. But in MVC I already fail with this simple task...

Update: Changing the button to @Html.ActionLink I can finally enter the controller method. But naturally since it returns the partial view, it's displayed as the whole page content. So the question is: How do I tell the partial view to be rendered inside a specific div on the client side?

InvisiblePanda
  • 1,589
  • 2
  • 16
  • 39
  • Typically, you handle the buttons click event and use ajax to pass the search text to a controller method that returns a partial view and then update the DOM with the returned results. Look at the jquery `ajax()` or `load()` methods. –  Mar 19 '15 at 10:47
  • I would create new view for your DisplaySearchResults action with whole HTML you need and delete this serchResult div inside Index view. It should work, but after that i would create some partials and refactor these two views(index and DisplaySearchResults) because of code duplication – Disappointed Mar 19 '15 at 11:02

2 Answers2

67

Change the button to

<button id="search">Search</button>

and add the following script

var url = '@Url.Action("DisplaySearchResults", "Search")';
$('#search').click(function() {
  var keyWord = $('#Keyword').val();
  $('#searchResults').load(url, { searchText: keyWord });
})

and modify the controller method to accept the search text

public ActionResult DisplaySearchResults(string searchText)
{
  var model = // build list based on parameter searchText
   return PartialView("SearchResults", model);
}

The jQuery .load method calls your controller method, passing the value of the search text and updates the contents of the <div> with the partial view.

Side note: The use of a <form> tag and @Html.ValidationSummary() and @Html.ValidationMessageFor() are probably not necessary here. Your never returning the Index view so ValidationSummary makes no sense and I assume you want a null search text to return all results, and in any case you do not have any validation attributes for property Keyword so there is nothing to validate.

Edit

Based on OP's comments that SearchCriterionModel will contain multiple properties with validation attributes, then the approach would be to include a submit button and handle the forms .submit() event

<input type="submit" value="Search" />

var url = '@Url.Action("DisplaySearchResults", "Search")';
$('form').submit(function() {
  if (!$(this).valid()) { 
    return false; // prevent the ajax call if validation errors
  }
  var form = $(this).serialize();
  $('#searchResults').load(url, form);
  return false; // prevent the default submit action
})

and the controller method would be

public ActionResult DisplaySearchResults(SearchCriterionModel criteria)
{
  var model = // build list based on the properties of criteria
  return PartialView("SearchResults", model);
}
  • Hi Stephen, and thanks for the answer. The tags and other stuff are just there because I wanted my approach to be easily copied and thus used a built-in template for the view, which inserts these things. Also, in my "real" application I will have more properties than just the `Keyword` (should have mentioned that, sorry!). Basically, the whole `SearchCriterionModel` that is the model for the main view *is* a collection of search criteria. So in the code-behind I thought I could just use the currently "bound" object to create my query and then fill the `div` with the results. – InvisiblePanda Mar 19 '15 at 11:11
  • 1
    Yes you should have mentioned that :) But the approach is the similar, except that you would use `$('#searchResults').load(url, $('form'.Serialize());` to serialize the whole `SearchCriterionModel` and then change the method to `public ActionResult DisplaySearchResults(SearchCriterionModel model)` and return a partial based on the the model values. In any case `@Html.ValidationSummary()` is not required. –  Mar 19 '15 at 11:15
  • Side question (I'll try your approach in a minute): couldn't I just use a parameterless `load` and when the code-behind method is called, I get the current model values that have been entered in the view to construct the query instead of passing them as parameter(s)? – InvisiblePanda Mar 19 '15 at 11:19
  • Not sure I understand what you mean. If you do not pass the `Keyword` value (and any other values in the model that you render in the view) to the controller, how would the `DisplaySearchResults` method know what to return? –  Mar 19 '15 at 11:22
  • Also, you could use `` and then handle the `$('form').submit()` event to trigger client side validation on the properties before calling `.load()`, but the script needs to include `return false;` to prevent the normal submit. –  Mar 19 '15 at 11:24
  • I tried the above code and I'm still having a slight problem: My code-behind method `DisplaySearchResults` isn't called, that is, I don't hit my breakpoint in there. Am I doing something wrong? Perhaps missing some attribute on my `DisplaySearchResults` method? – InvisiblePanda Mar 19 '15 at 11:39
  • Did you substitute the value of "YourControllerName" (looking at your code it should be "Search"). Check the browser console to see what errors are displayed. –  Mar 19 '15 at 11:49
  • I falsely used "SearchController" as the name, so that was one problem ;) But it's still not working with the jQuery method. I then tried direct JS `onlick` and got the correct URL and also entered the code-behind, but it didn't load into the `div` even though `load` was called with the correct parameter. Further trying suggests to me that jQuery doesn't really find any of the stuff that I search by id. I'll look into that alone, as it doesn't fit this question anymore and accept your answer because I *guess* it works once I figure this out :) Thanks! – InvisiblePanda Mar 19 '15 at 12:21
  • @InvisiblePanda, I have edited the answer with an alternative based on `SearchCriterionModel` containing multiple properties with validation attributes. –  Mar 20 '15 at 00:38
  • Great :) Btw, I figured out why it didn't work as-is: as long as I have a `using (Html.BeginForm())` or similar around my HTML, the load method is called correctly and after the 2nd try, the div is `filled` as wanted, but then immediately 'deleted' (that is, I see the site flashing and the `div` is empty again). I fixed this by using `return false` after the `load`, but I'm not 100% sure why this works. Anyway, thanks again :) – InvisiblePanda Mar 20 '15 at 08:54
  • I explained that in the edit to my answer. You need that to prevent the default submit action which would have posted and then redirected back the original view (i.e. the ajax `.load()` is called and the partial added, but then the original page is reloaded immediately after) –  Mar 20 '15 at 08:58
  • I am doing the same thing can some one guide me from here..my load is not working http://stackoverflow.com/questions/42941856/loading-search-results-to-an-html-table-mvc-razor-kendo-ajax – Samra Mar 22 '17 at 05:55
  • If the button submits the form (redirects): have to add `type="button"` attribute, as the default button behavior is to submit the form. Took me 2 hours to troubleshoot. – Raman Sinclair Jan 14 '20 at 22:32
2

So here is the controller code.

public IActionResult AddURLTest()
{
    return ViewComponent("AddURL");
}

You can load it using JQuery load method.

$(document).ready (function(){
    $("#LoadSignIn").click(function(){
        $('#UserControl').load("/Home/AddURLTest");
    });
});

source code link