InvisiblePanda InvisiblePanda - 3 months ago 17
jQuery Question

Rendering partial view on button click in ASP.NET MVC

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?

Answer

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);
}