Jon P Smith
Jon P Smith

Reputation: 2439

MVC3 - list information used for dropdownlist is null after post

I wish to show a DropDownList in a view and therfore include in my model (ExampleAddSetupDto) sent to a view a list of entries to populate the dropdownlist. That works fine, but if I have a validation error and redisplay the view with in incoming model my list is now null.

My Action is given below (note: the problem occurs if ModelState.IsValid fails). Also the Action method second parameter may look odd as I am using Autofac to inject the right service into the method).

    [HttpPost]
    [ValidateAntiForgeryToken]
    public ActionResult Add(ExampleAddSetupDto add, IServiceAddCommit<IExampleAddSetupDto, IExampleAddCommitDto> service)
    {

        if (ModelState.IsValid)
        {
            var response = service.Create(add);
            if (response.IsValid)
            {
                TempData["message"] = "You successfully added a new Example Entry";
                return View("AddSuccess", response);
            }

            //else errors, so copy the errors over to the ModelState
            response.CopyErrorsToModelState(ModelState, add);
        }

        // Some validation error, so redisplay same view
        return View(add);

    }

My model looks like this:

public class ExampleAddSetupDto : IExampleAddSetupDto
{

    [StringLength(50, MinimumLength = 2)]
    public string Name { get; set; }

    public int Option1Id { get; set; }

    public int Option2Id { get; set; }

    //-----------------------------
    //now the properties for the drop down lists 

    public IList<Option1> PosibleEntriesForOption1 { get; set; }
    public IList<Option2> PosibleEntriesForOption2 { get; set; }

}

My View is:

@model ServiceLayer.Example.DTOs.ExampleAddSetupDto

@{
    ViewBag.Title = "Add";
}

<h2>Add</h2>

<script src="@Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>

@using (Html.BeginForm())
{  
@Html.AntiForgeryToken()
@Html.ValidationSummary(true)
<fieldset>
    <legend>Add an Example item</legend>

    <div class="editor-label">
        @Html.LabelFor(model => model.Name)
    </div>
    <div class="editor-field">
        @Html.EditorFor(model => model.Name)
        @Html.ValidationMessageFor(model => model.Name)
    </div>
    <div class="editor-field">
        @Html.Label("Option1")
        @Html.DropDownListFor(model => model.Option1Id, new SelectList(Model.PosibleEntriesForOption1, "Option1Id", "OptionText"))
        @Html.ValidationMessageFor(model => model.Option1Id)
    </div>
    <div class="editor-field">
        @Html.Label("Option2")
        @Html.DropDownListFor(model => model.Option2Id, new SelectList(Model.PosibleEntriesForOption2, "Option2Id", "OptionText"))
        @Html.ValidationMessageFor(model => model.Option2Id)
    </div>
    <p>
        <input type="submit" value="Create" />
    </p>
</fieldset>

}

I understand that I need to return the Model.PosibleEntriesForOption in with the form. I tried using the Html.HiddenFor helper in the view to return the list, i.e.

@Html.HiddenFor(model => model.PosibleEntriesForOption1)

but this throws the error 'The value 'System.Collections.Generic.List`1[DataClasses.ExampleClasses.Option1]' is invalid.'.

Clearly I am missing something here and I would appreciate some advice on how to return the lists so that redisplaying the model won't cause an error.

Upvotes: 2

Views: 2506

Answers (4)

gbelzile
gbelzile

Reputation: 84

The TempData technique from Dan Nixon works once but if the validation fails again, the TempData entry is null. I guess I'll have to reload my lists too.

Upvotes: 0

SBB
SBB

Reputation: 155

On the get action for add, you will be creating the model with appropriate values for these 2 properties - PosibleEntriesForOption1 & PosibleEntriesForOption2 Since these are set properly & available on the view, the dropdown gets rendered correctly on the get.

Now on a POST, when validation fails, you have to set those properties again.

 if (ModelState.IsValid)
    {
        // Do something
    }

    // before you redisplay the same view
    // set the properties PosibleEntriesForOption1 & PosibleEntriesForOption2

    // Some validation error, so redisplay same view
    return View(add);

Upvotes: 0

Hari Gillala
Hari Gillala

Reputation: 11926

If the Validation is failing, you need to load up the the dropdownlist with the List of values. Other wise, it will fail.

I believe, when you load up your view initially, it executes HttpGet Method. In HttpGet method you must be binding the Dropdownlist

When you submit page, it executes httpPost method, if all is well, it will submit. If validation fails, it will execute, HTTPPost method, but it cannot find any binding for dropdown.

So try this : In your case

 if (ModelState.IsValid)
            {
                var response = service.Create(add);
                if (response.IsValid)
                {
                    TempData["message"] = "You successfully added a new Example Entry";
                    return View("AddSuccess", response);
                }

                //else errors, so copy the errors over to the ModelState
                response.CopyErrorsToModelState(ModelState, add);
            }
    else //if validation fails, you need to reload the dropdown and display your view.
      {
          // populate your dropdown again
              // You can add errors list into ModelState.
              ViewData.ModelState.AddModelError("What is the error", "Error Message, "What needs to be done by user, to get it work");
             return view(add)
      }

Upvotes: 0

Dan Nixon
Dan Nixon

Reputation: 101

If you are forced to persist the entire list between the two requests, for whatever reason, the best way I see to do this would be to use:

TempData["EnterUniqueKeyHere"] = PossibleEntriesForOption1;

to store it, and then:

PossibleEntriesForOption1 = TempData["EnterUniqueKeyHere"] as IList<Option1>;

to retrieve it.

Note that anything stored in TempData will be removed automatically after a single request.

Upvotes: 1

Related Questions