Search code examples
asp.net-mvcjqueryunobtrusive-ajax

Postback on partially loaded view brings null model


My application have page with few search boxes. When user clicks on respective search button a result grid is expected to load. That grid have few editable controls in which user can modifications and hit save button if needed to save the data.

We have implemented the functionality using AjaxForm and partial view.

  1. Search area containing search boxes and search button is created using Ajax.BeginForm, on submit of which a postback Search method is called from controller. SearchModel is passed to this method.
  2. Partial View is created to show results and Ajax form in step 1 loads it successfully from controller postback method. SearchModel.Results property is passed to view as its model.
  3. This partial view showing results have a Save button (again a Ajax form) which invokes another method in controller but gets model null in controller.

have tried lot of tricks but was unsuccessful. Any working example demonstrated anywhere or suggestion to make this working ? There are lot of examples on web where usage of AjaxForm to load data is explained but didn’t found any for multiple (or nested?)

Thanks in advance.

Edit - Feb 24

Here is the sample I created using Visual Studio default MVC template, which is similar to the actual criteria explained above and have the same problem on Submit of Partial page ..

Views:

Index.cshtml

    @using MvcApplication1.Models
    @model SearchModel

    @{
        ViewBag.Title = "Home Page";
    }

     <script src="@Url.Content("~/Scripts/jquery.unobtrusive-ajax.min.js")" ></script>
    <h2>@ViewBag.Message</h2>
    <p>
        To learn more about ASP.NET MVC visit <a href="http://asp.net/mvc" title="ASP.NET MVC Website">http://asp.net/mvc</a>.
    </p>



@Ajax.BeginForm("Search", "Home", new AjaxOptions { HttpMethod = "Post", 
InsertionMode = InsertionMode.Replace,UpdateTargetId = "SearchResults"})
{
     <table>
    <tr>
        <td>
            First Name:
        </td>
        <td>
            @Html.TextBoxFor(m => m.SearchString)
        </td>
    </tr>
    <tr>
        <td colspan="2">
            <input type="submit" value="Submit" />
        </td>
    </tr>
</table>

}
<div id="SearchResults" style="color: Green;"></div>

Partial View - _SearchResult.cshtml

    @using MvcApplication1.Models
    @model MvcApplication1.Models.SearchModel
    @{
        ViewBag.Title = "Result Partial";
    }
    <h2>
        testPartial</h2>
    @Ajax.BeginForm("SearchResult", "Home", new AjaxOptions
    {
        HttpMethod = "Post"
    })
    {
    <table>
        @foreach (ResultModel item in Model.Result)
        {
            <tr>
                <td>
                    Name:
                </td>
                <td>
                    @Html.DisplayFor(m => item.Name)
                </td>
            </tr>
            <tr>
                <td>
                    Address:
                </td>
                <td>
                    @Html.TextAreaFor(m => item.Address)
                </td>
            </tr>

        }
        <tr>
            <td colspan="2">
                <input type="submit" value="Submit" />
            </td>
        </tr>
    </table>
    }

Models:

SearchModel.cs

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;

    namespace MvcApplication1.Models
    {
        public class SearchModel
        {
            public string SearchString  { get; set; }

            public List<ResultModel> Result { get; set; }
        }
    }

ResultModel.cs

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;

    namespace MvcApplication1.Models
    {
        public class ResultModel
        {
            public string Name { get; set; }
            public string Address { get; set; }
        }
    }

Controller:

HomeController.cs

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    using System.Web.Mvc;
    using MvcApplication1.Models;

    namespace MvcApplication1.Controllers
    {
        public class HomeController : Controller
        {
            public ActionResult Index()
            {
                ViewBag.Message = "Welcome to ASP.NET MVC!";

                return View();
            }

            public ActionResult About()
            {
                return View();
            }

            [HttpPost]
            public ActionResult Search(SearchModel model)
            {
                //dummy search for result
                List<ResultModel> result = new List<ResultModel>();

                ResultModel res1 = new ResultModel();
                res1.Name = model.SearchString + " 1";
                res1.Address = "Dummy address";

                result.Add(res1);

                ResultModel res2 = new ResultModel();
                res2.Name = model.SearchString + " 2";
                res2.Address = "Rummy address";

                result.Add(res2);

                //assign seach results to model
                model.Result = result;

                return PartialView("_SearchResult", model);
            }

            [HttpPost]
            public ActionResult SearchResult(SearchModel model)
            {
                //do something with results

                List<ResultModel> res = model.Result; // null here !!

                return RedirectToAction("Index");
            }
        }
    }

SO, above sample will

  1. Show you a Search box on home page - Index.cshtml
  2. When you click on submit it will load a partial view displaying results below search
  3. When you edit search results and hit submit on result form, please have a breakpoint set up there, you will see model being returned is null.

Hope, this explain my problem.


Solution

  • Now that you have shown your actual code with a complete example your question can be answered.

    The reason you are getting null is because you are not respecting the standard naming convention for your input fields that the default model binder expects. Please read the following article from Phil Haack to familiarize yourself with those conventions.

    The problem with your code is the fact that you used a foreach loop inside your partial to render the results instead of using an editor template. So replace your code inside _SearchResult.cshtml with the following:

    @using MvcApplication1.Models
    @model SearchModel
    @{
        ViewBag.Title = "Result Partial";
    }
    <h2>testPartial</h2>
    
    @using(Ajax.BeginForm("SearchResult", "Home", new AjaxOptions()))
    {
        <table>
            @Html.EditorFor(x => x.Result)
            <tr>
                <td colspan="2">
                    <input type="submit" value="Submit" />
                </td>
            </tr>
        </table>
    }
    

    and then define a custom editor template for the ResultModel type (~/Views/Shared/EditorTemplates/ResultModel.cshtml - warning, the name and location of your editor template is important because it works by convention):

    @using MvcApplication1.Models
    @model ResultModel
    
    <tr>
        <td>
            Name:
        </td>
        <td>
            @Html.DisplayFor(m => m.Name)
            @Html.HiddenFor(m => m.Name)
        </td>
    </tr>
    <tr>
        <td>
            Address:
        </td>
        <td>
            @Html.TextAreaFor(m => m.Address)
        </td>
    </tr>
    

    Things to notice:

    • I have wrapped the Ajax.BeginForm helper in a using statement. You should do the same inside your Index.cshtml view
    • I have added a hidden field for the Name property inside the custom editor template (@Html.HiddenFor(m => m.Name)) in order to send this value to the server when the form is submitted because you only had a TextArea for the Address field meaning that the Name would never have been sent to your server.