3
votes

On my Index page I have a search form that allows users to pull information based on 3 search criteria. When the the search results return from the controller, they are returned as a List object that is a property of the view model and displayed in a table below the search form.

The user then has the option of selecting which records they would like to alter. They will do this by selecting a check box next to the specified records. Then, using a form that appears below the search box, will enter the new values and hit an "Update" button to save the changes. (we do this in order to allow the user to make mass updates without having to alter the table row by row)

Here is a screen shot to illustrate what I mean. This is what the page will look like after the user has hit the "Search" button:

enter image description here

When the user hit's "Search" the form is instructed to post back to the Index method of the controller. When the user hits "Clone Selected Items" the form is instructed to post back to an "Update" method in the same controller. However, the problem is the view model object that gets posted back to the "Update" method is completely null except for the values placed in the New Territory, New Description, and New Effective Date text boxes.

I'm relatively new to ASP MVC so any help/suggestions would be appreciated. Not sure why the View Model will post back from the controller with the same values it was sent there while using the "Search" button but will not do so using the "Clone Selected Items" one.

View Model (this is what gets posted back to the controller)

public class ZipCodeIndex
{
    public List<ZipCodeTerritory> zipCodeTerritory { get; set; }
    [DisplayName("Zip Code")]
    public string searchZip { get; set; }
    [DisplayName("Effective on this date")]
    public string searchDate { get; set; }
    [DisplayName("State")]
    public string searchState { get; set; }
    [DisplayName("New Territory")]
    public string newTerritory { get; set; }
    [DisplayName("New Description")]
    public string newDescription { get; set; }
    [DisplayName("New Effective Date")]
    public string newEffectiveDate { get; set; }

    public ZipCodeIndex() 
    {
        zipCodeTerritory = new List<ZipCodeTerritory>();
    }
}

Model (This populates the List object in the View Model)

[MetadataType(typeof(ZipCodeTerritoryMetaData))]
public partial class ZipCodeTerritory
{
    public bool Update { get; set; }
}

public partial class ZipCodeTerritory
{
    public string ChannelCode { get; set; }
    public string DrmTerrDesc { get; set; }
    public string IndDistrnId { get; set; }
    public string StateCode { get; set; }
    public string ZipCode { get; set; }
    public System.DateTime EndDate { get; set; }
    public System.DateTime EffectiveDate { get; set; }
    public string LastUpdateId { get; set; }
    public Nullable<System.DateTime> LastUpdateDate { get; set; }
    public int Id { get; set; }
}

View

@model Monet.ViewModel.ZipCodeIndex

    @using(Html.BeginForm("Index", "ZipCodeTerritory", FormMethod.Post))
    {
        <div id="searchBox" class="boxMe">
            <div id="zipBox">
                @Html.Raw("Zip Code")
                @Html.TextAreaFor(model => model.searchZip, new { style = "width: 300px;", placeholder = "Enter up to 35 comma separated zip codes" })
            </div>
            <div id="dateBox">
                @Html.LabelFor(model => model.searchDate)
                @Html.TextBoxFor(model => model.searchDate, new { style="width: 80px;"})
            </div>
            <div id="stateBox">
                @Html.LabelFor(model => model.searchState)
                @Html.TextBoxFor(model => model.searchState, new { style = "width: 25px;" })
                <button type="submit">Search</button>
            </div>
        </div>
        <div id="errorStatus">
            @ViewBag.ErrorMessage            
        </div>
        <div style="clear: both;"></div>
    }

<br/>
@Html.ActionLink("Create New", "Create")
<br/>

@if (Model != null)
{
    using(Html.BeginForm("Update", "ZipCodeTerritory", FormMethod.Post))
    {
        <div id="cloneBox">
            @Html.LabelFor(model => model.newTerritory)
            @Html.TextBoxFor(model => model.newTerritory, new { style="width: 30px;padding-left:10px;"})
            @Html.LabelFor(model => model.newDescription)
            @Html.TextBoxFor(model => model.newDescription, new { style = "width: 250px;padding-left:10px;" })  
            @Html.LabelFor(model => model.newEffectiveDate)     
            @Html.TextBoxFor(model => model.newEffectiveDate, new { style = "width: 80px;padding-left:10px;" })   
            <button type="submit">Clone Selected Items</button>                      
        </div>
    }    

    <table id="thetable" class="tablesorter" >
        <thead>
            <th></th>
            <th>Channel</th>
            <th>Territory</th>
            <th>Description</th>
            <th>State</th>
            <th>Zip</th>
            <th>Effective</th>
            <th>End Date</th>
            <th>Last Update By</th>
            <th>Last Update Date</th>
            <th></th>
        </thead>
        <tbody>
            @foreach (var item in Model.zipCodeTerritory)
            {
                <tr>
                    <td>@Html.CheckBoxFor(model => item.Update)</td>
                    <td>
                        @Html.DisplayFor(model => item.ChannelCode)
                    </td>
                    <td>
                        @Html.DisplayFor(model => item.IndDistrnId)
                    </td>
                    <td>
                        @Html.DisplayFor(model => item.DrmTerrDesc)
                    </td>
                    <td>
                        @Html.DisplayFor(model => item.StateCode)
                    </td>
                    <td>
                        @Html.DisplayFor(model => item.ZipCode)
                    </td>
                    <td>
                        @Html.DisplayFor(model => item.EffectiveDate)
                    </td>
                    <td>
                        @Html.DisplayFor(model => item.EndDate)
                    </td>
                    <td>
                        @Html.DisplayFor(model => item.LastUpdateId)
                    </td>
                    <td>
                        @Html.DisplayFor(model => item.LastUpdateDate)
                    </td>
                    <td>
                        @Html.ActionLink("Edit", "Edit", new { id = item.Id })
                    </td>
                </tr>
            }
        </tbody>
    </table>    
}

Controller

    public ViewResult Index(ZipCodeIndex search)
    {
        try
        {
            //If search criteria is null page is loading for the first time so send blank view
            if (String.IsNullOrWhiteSpace(search.searchZip) &&
                String.IsNullOrWhiteSpace(search.searchDate) &&
                String.IsNullOrWhiteSpace(search.searchState))
            {
                return View();
            }

            //Determine if necessary search criteria has been sent
            if (String.IsNullOrWhiteSpace(search.searchZip) && String.IsNullOrWhiteSpace(search.searchState))
            {
                ViewBag.ErrorMessage = "Either State or Zip Code Must be Specified";
                return View(search);
            }

            DateTime effectiveDate;

            //Convert date string to DateTime type
            if (String.IsNullOrWhiteSpace(search.searchDate))
            {
                effectiveDate = DateTime.MinValue;
            }
            else
            {
                effectiveDate = Convert.ToDateTime(search.searchDate);
            }

            //Conduct search by State Code/Date alone
            if (String.IsNullOrWhiteSpace(search.searchZip))
            {
                search.zipCodeTerritory = (from z in db.ZipCodeTerritory
                                           where z.StateCode.Equals(search.searchState) &&
                                                 z.EffectiveDate >= effectiveDate
                                           select z).ToList();
                return View(search);
            }

            //Zip codes have been requested to conduct zip search
            string[] zipArray;

            //Create array and remove white spaces
            zipArray = search.searchZip.Split(',').Distinct().ToArray();
            for (int i = 0; i < zipArray.Length; i++)
            {
                zipArray[i] = zipArray[i].Trim();
            }

            //Determine if state code is being used in search
            if (String.IsNullOrWhiteSpace(search.searchState))
            {
                foreach (var zip in zipArray)
                {
                    var item = from z in db.ZipCodeTerritory
                               where z.ZipCode.Equals(zip) &&
                                      z.EffectiveDate >= effectiveDate
                               select z;
                    search.zipCodeTerritory.AddRange(item);
                }
            }
            else
            {
                foreach (var zip in zipArray)
                {
                    var item = from z in db.ZipCodeTerritory
                               where z.ZipCode.Equals(zip) &&
                                      z.EffectiveDate >= effectiveDate &&
                                      z.StateCode.Equals(search.searchState)
                               select z;
                    search.zipCodeTerritory.AddRange(item);
                }
            }
        }
        catch (DbEntityValidationException dbEx)
        {
            ViewBag.ErrorMessage = "An error has occurred, we apologize for the incovenience. IT has been notified and will resolve the issue shortly.";
            SendEmail.ErrorMail(Common.ErrorCheck.CombineDbErrors(dbEx));
        }
        catch (Exception ex)
        {
            ViewBag.ErrorMessage = ErrorCheck.FriendlyError(ex);
            SendEmail.ErrorMail(ex);
        }

        return View(search);
    }


    [HttpPost]
    public ActionResult Update(ZipCodeIndex updateZip)
    {
        foreach (var zipCode in updateZip.zipCodeTerritory)
        {
            if (zipCode.Update)
            {
                try
                {
                    if (!string.IsNullOrEmpty(updateZip.newTerritory)) zipCode.IndDistrnId = updateZip.newTerritory;
                    if (!string.IsNullOrWhiteSpace(updateZip.newDescription)) zipCode.DrmTerrDesc = updateZip.newDescription;
                    if (!string.IsNullOrWhiteSpace(updateZip.newEffectiveDate)) zipCode.EffectiveDate = Convert.ToDateTime(updateZip.newEffectiveDate);

                    db.Entry(zipCode).State = EntityState.Modified;
                    db.SaveChanges();
                }
                catch (DbEntityValidationException dbEx)
                {
                    ViewBag.ErrorMessage = "An error has occurred, we apologize for the incovenience. IT has been notified and will resolve the issue shortly.";
                    SendEmail.ErrorMail(Common.ErrorCheck.CombineDbErrors(dbEx));
                }
                catch (Exception ex)
                {
                    ViewBag.ErrorMessage = "An error has occurred, we apologize for the incovenience. IT has been notified and will resolve the issue shortly.";
                    SendEmail.ErrorMail("Zip Code not updated: " + zipCode.ToString() + " |MESSAGE| " + ex.Message);
                }
            }
        }

        return RedirectToAction("Index", updateZip);
    }

EDIT

I've added the following hidden fields to the second form (that posts to the Update method). This will send back the search criteria however the List object is still empty.

@if (Model != null)
{
    using(Html.BeginForm("Update", "ZipCodeTerritory", FormMethod.Post))
    {
        @Html.HiddenFor(model => model.searchZip)
        @Html.HiddenFor(model => model.searchDate)
        @Html.HiddenFor(model => model.searchState)
        @Html.HiddenFor(model => model.zipCodeTerritory)

        <div id="cloneBox">
6
Is it your Model.zipCodeTerritory that is not getting returned? - Ryan Schlueter
No, it is the ZipCodeIndex view model that gets returned. I'll make a note of that in the post, thx. - NealR
sorry which part of the viewmodel are you wanting to be there thats not. The List at the bottom or is it something else. - Ryan Schlueter
I want the List to be there in the Update method as well as anything in the search boxes (zip, state, date). basically everything that gets posted back to the view from the Index controller method needs to be there when the page posts back to the Update method. The only difference between what is posted back from the Index and what is sent to the Update should be whether the boolean field of each ZipCodeTerritory list object has been set to true (via the checkboxes in the table). - NealR
Well I think for search criteria them being in a different form its not gonna like. Then for the list displayfor does not repost to server, you need to put another field hiddenfor next to it to save the data. I would just try it on one or two and test it. If any of that helps ill put it in an answer. - Ryan Schlueter

6 Answers

2
votes

When we use Html Helper method Html.DisplayFor(), it just shows the data as a plain text. You can check it by View Page resource in your browser. What you have do is to use Html.HiddenFor helper method to bind what data you need after post. Or you can also bind data using JQuery.Ajax

1
votes

Well if adding the hiddenfor did not work I would try to make it something like this.

@for (int i = 0; i < Model.OrdItems.Count; i++)
{

@Html.DisplayFor(model => model.zipCodeTerritory[i].channelCode)@Html.HiddenFor(model => model.zipCodeTerritory[i].channelCode)
@Html.DisplayFor(model => model.zipCodeTerritory[i].zipCode)@Html.HiddenFor(model => model.zipCodeTerritory[i].zipCode)

}
-1
votes

The above post is an inefficient and incorrect way to fix your problem. ASP.NET MVC reserves and uses the word "model" as a keyword during the model binding process. You need to change the name "search" to "model" in your action result parameter. That should fix your problem with your search data not posting back.

-1
votes

It's Nothing but just when you return the view in action result in your controller, create the object of that model then and only pass it to view.

-1
votes

Go to RouteConfig.cs in App_start, then properly you can see url: {controller}/{action}/{id} in RegisterRoutes method. Browser will go to this route. Has your Edit Action an input argument calls id? maybe the name is different.

-2
votes

I would suggest trying to use KnockoutJS, it would be much easier to post the actual data from the server back, just updated. If you want examples - let me know i'll be happy to give some.

if that's not an option - Pay attention to what's inside the form you created (BeginForm), because most of what you want isn't inside, so it won't be submitted to the controller on post.