0

I have a problem saving List items to database. When a field is to be saved(in HTTP POST Create) some of the Lists properties don't have to be saved so I have allowed nullabe for such. However there is one field that I retrieve from the Form and save it. Since the classes are complex I'll restrict the code I'll post here (I'll use one field since the Exception generated is the same).

StringValues is the Class that several List fields inherit from, such as TestPlanChecklist in this case.

public class TestPlanChecklist:StringValues
{
}

public class StringValues
{
    [Key]
    public int id { get; set; }

    public int ChangeRequestsID { get; set; }
    public string Value { get; set; }

    public ChangeRequests ChangeRequests { get; set; }
}

Part of my Model class

    public class ChangeRequests
{

    [Required]
    public List<TestPlanChecklist> TestPlanChecklist { get; set; }

    [Required]
    public List<PostActivityChecklist> PostActivityChecklist { get; set; }

    [Required]
    public List<CMBApproval> CMBApproval { get; set; }

    [Required]
    public List<TechnicalFeasibility> TechnicalFeasibility { get; set; }

}

In my Create view, this is the code that renders textboxes for TestPlanChecklist field

<div class="form-group">
            @Html.LabelFor(model => model.TestPlanChecklist, htmlAttributes: new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                <div>
                    <label class="numbers"> 1 </label>                        
                    <input type="text" class="TestPlanChecklist" name="TestPlan" />

                    <input type="button" value="+" class="roundButton" onclick="add('TestPlanChecklist', 'TestPlan')" />
                    <input type="button" value="-" class="roundButton" onclick="removeElement('TestPlan')" />
                </div>

                <div>
                    <label class="numbers"> 2 </label>
                    <input type="text" class="TestPlanChecklist" name="TestPlan" />
                </div>
                <div>  
                    <label class="numbers"> 3 </label>
                    <input type="text" class="TestPlanChecklist" name="TestPlan" />
                </div>

                @Html.ValidationMessageFor(model => model.TestPlanChecklist, "", new { @class = "text-danger" })
            </div>
        </div>

    </div>

And HttpPost Create method

[HttpPost]
    [ValidateAntiForgeryToken]
    public ActionResult Create([Bind(Include = "TestPlanChecklist,PostActivityChecklist,PostActivityChecklist,CMBApproval,TechnicalFeasibility")] ChangeRequests changeRequests,
      string[] TestPlan)
    {            
        changeRequests.TestPlanChecklist = new List<TestPlanChecklist>();


        foreach (var test in TestPlan)
            changeRequests.TestPlanChecklist.Add(new TestPlanChecklist { Value = test });

        //SendEmails(TechnicalFeasibility, User.Identity.Name, ChangeUrgency, Priority, DescriptionOfChange, Reason);

        //SendEmails(CMBApproval, User.Identity.Name, ChangeUrgency, Priority, DescriptionOfChange, Reason);


        if (ModelState.IsValid)
        {
            db.ChangeRequests.Add(changeRequests);
            db.SaveChanges();
            return RedirectToAction("List");
        }

        return View(changeRequests);
    }

Kindly note that am just using one field to ask this question, thats why I have removed code for initializing the other List fields.

ModelState.IsValid

returns false. I realize that all the List fields have an error, which states that its impossible to typecast from System.String to the particular class. This is funny since I assign only one field value retrieved from the form and the rest are nullable which makes sense.

Where am I going wrong?

Thanks in advance.

6
  • Very unclear what you are trying to do here. The only form controls you generate have a name attribute "TestPlan", which has no relationship to your model. Why does you POST method have the parameter ChangeRequests changeRequests? - you don't post anything related to any properties of type ChangeRequests so validation will always fail because of the [Required] attribute. You also have a LabelFor() and ValidationMessageFor() ssociated with property TestPlanChecklist which makes no sense (again you don't generate any form controls for any properties of TestPlanChecklist!) Commented May 27, 2015 at 4:16
  • @StephenMuecke The form controls have the name TestPlan so in my post constructor I can add an array of strings then from that add each to the List field. I am only using ValidationMessageFor() and LabelFor() since the form controls with the name TestPlan set one property of the TestPlanChecklist. I set the other fields on the HTTP POST method and some of them will be set (updated) later on during the applications use. Commented May 27, 2015 at 4:55
  • Your misunderstanding how to use MVC. You have no form controls relating to any of the properties in your model, so therefore your POST method should not have a parameter for ChangeRequests! But what you should have is a view model that is List<string> TestPlan and post that back if its the only thing you are editing. Commented May 27, 2015 at 4:58
  • @StephenMuecke I mentioned in the question that I'll only show code for the field causing me trouble. Since they are six such fields, I used TestPlanChecklist since each field threw the same Exception. If I had posted all the code in my view as well as my model then readadability of the question would be greatly affected Commented May 27, 2015 at 6:41
  • My previous comment still stands "Your misunderstanding how to use MVC" I have answered other questions for your where that is obvious. I strongly recommend you go to the MVC site and work through some basic tutorials. You cannot save a collection unless your form controls are correctly named with indexers and your manual attempt at creating inputs will always fail to bind to a collection Commented May 27, 2015 at 6:47

3 Answers 3

1

ModelState.IsValid check validation rules, that comes from DataAnnotations in your case [Required] Attribute in ChangeRequests ViewModel.

If you want to use this validation and make some of your List properties nullable you should delete this attribute.

Sign up to request clarification or add additional context in comments.

1 Comment

How do I still ensure that atleast one record of that List field is saved to the database without using the [Required] attribute?
1

You can check the errors of the validation.

var errores = new List<ModelError>();
foreach (ModelState modelState in ViewData.ModelState.Values)
{
    foreach (ModelError error in modelState.Errors)
    {
        errores.Add(error);
    }
}

1 Comment

As I have pointed out, the errors are unable to Type cast from System.String to now the class I have defined for the List<T> field. However, am not attempting to save the value I retrieved from the Form directly to the List, rather I add all of them to one field within my class like this foreach (var test in TestPlan) changeRequests.TestPlanChecklist.Add(new TestPlanChecklist { Value = test }); However I still get a ModelState error.
1

You get ModelState errors because ChangeRequest has [Required] attributes and gets validated by Mvc. It's unclear to me why you're using that action signature but it's a bad approach. You should rely on ViewModels and not Models directly.

public ActionResult Create(ChangeRequestViewModel viewModel)
{
    if(ModelState.IsValid == false) return View(viewModel); 
    var changeRequest = new ChangeRequest();
    foreach(var testPlan in viewModel.TestPlans) {
         changeRequest.TestPlanChecklist.Add(new TestPlanChecklist { Value = testPlan }
    }
    // ...
} 

3 Comments

The Required attribute is the cause of my errors. I had thought that once a property was nullable then I could set just one attribute for a class and still get away with it. While this solves my problem, It creates another issue, how do I get server side validation to ensure the field has at least one List item saved without using the [Required] attribute?
@Dennis assuming you follow my advise and use a viewmodel you should check out github.com/JeremySkinner/FluentValidation for serious validation
This is the answer to my question, however someone else also posted the same answer before you did. I have thus marked that one as the answer but I really appreciate the code you have provided. A little research after I saw your code pointed out that viewModels should only be used to display data(thus the 'View' in the name. Saving would require a Data Model instead. This is just to keep up with MVCs separation of concerns. +1 for your answer though.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.