سؤال

UPDATED POST

I am currently developing a Survey application that uses MVC4, Razor, EF5. There will be various surveys an will be utilizing one view. The Models for the application comes from an existing database. Apart from that, I created a separate Entities for the following:

SurveyDisplay - Model for elements that will appear on the survey page because it consists of different languages.

SurveyInfo - Model for information where data comes from a web service.

SurveyQuestion - Model for the questionnaires of the survey.

SurveyChoices - Model for the choices of each question in the survey.

SurveyAnswers - Model to retrieve choices of answers

UPDATE IMAGE(Added SurveyAnswers)

Kindly refer to the image below for the following fields: enter image description here

I am able to get the particular values to display in my page which is a Razor, but upon POST. I am getting ModelState.IsValid == false. All models are null except for SurveyAnswers.

enter image description here

enter image description here

Here's how my code goes so far:

SurveyRepository: For this part, I imported stored procedures to get data from the database.

    public List<SurveyQuestion> GetQuestions(int surveyid)
    {
        using (var ctx = new ICSDBContext())
        {
            return ctx.GetSurveyQuestions(surveyid).ToList<SurveyQuestion>();
        }
    }
    public List<SurveyChoice> GetChoices(int surveyid)
    {
        using (var ctx = new ICSDBContext())
        {
            return ctx.GetSurveyChoices(surveyid).ToList<SurveyChoice>();
        }
    }
    public List<SurveyDisplay> GetSurveyDisplay(int surveyid)
    {
        using (var ctx = new ICSDBContext())
        {
            return ctx.GetSurveyDisplay(surveyid).ToList<SurveyDisplay>();
        }
    }

SurveyController:

using ICS.Repositories;
using ICS.ViewModels;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;

public class SurveyController : Controller
{
    SurveyRepository surveyRepository = new SurveyRepository();
    SurveyViewModel surveyViewModel = new SurveyViewModel();

    [HttpGet]
    public ActionResult Index(int surveyid, string rowid)
    {
        var surveyDisplay = surveyRepository.GetSurveyDisplay(surveyid);
        var surveyQuestions = surveyRepository.GetQuestions(surveyid);
        var surveyChoices = surveyRepository.GetChoices(surveyid);

        string headerText = "";
        string messageBody = "";
        string buttonText = "";

        foreach (var item in surveyDisplay)
        {
            headerText = item.HeaderText;
            messageBody = item.MessageBody;
            buttonText = item.ButtonText;
        }
            surveyViewModel.HeaderText = Server.HtmlEncode(headerText);
            surveyViewModel.MessageBody = Server.HtmlEncode(messageBody);
            surveyViewModel.Buttontext = Server.HtmlEncode(buttonText);
            surveyViewModel.SurveyQuestions = surveyQuestions;
            surveyViewModel.SurveyChoices = surveyChoices;

        return View("Survey", surveyViewModel); 
    }

//Not the actual code yet. I'm trying to check in this action whether there is POSTed data or none. 
[HttpPost]

}

SurveyViewModel

public class SurveyViewModel
{
    public List<SurveyInfo> SurveyInfo { get; set; }
    public string HeaderText { get; set; }
    public string MessageBody { get; set; }
    public string Buttontext { get; set; }
    public List<SurveyQuestion> SurveyQuestions { get; set; }
    public List<SurveyChoice> SurveyChoices { get; set; }
    public List<SurveyAnser> SurveyAnsers { get; set; }
}

Razor View

<h2>@Html.DisplayFor(model => model.HeaderText)</h2>
    <div id="info">
        <p>@Html.DisplayFor(model => model.MessageBody)</p>
        <div class="margTop20">
            <ul>
                @for (var info = 0; info < Model.SurveyInfo.Count(); info++)
                {
                    <li>
                        <span>Case ID: </span>
                        <b>@Html.DisplayFor(model => model.SurveyInfo[info].SRNumber)</b>
                    </li>
                    <li>
                        <span>Description: </span>
                        <b>Others</b>
                    </li>
                    <li>
                        <span>Problem Category: </span>
                        <b>@Html.DisplayFor(model => model.SurveyInfo[info].ProblemSubCategory)</b>
                    </li>
                    <li>
                        <span>Product: </span>
                        <b>@Html.DisplayFor(model => model.SurveyInfo[info].Product)</b>
                    </li>
                    <li>
                        <span>Method of Service: </span>
                        <b>@Html.DisplayFor(model => model.SurveyInfo[info].SupportType)</b>
                    </li>
                }
            </ul>
        </div>
    </div>
    @for (var question = 0; question < Model.SurveyQuestions.Count(); question++)
    {
        <div id="@("question" + ConvertNumberToWords.Translate(question))" class="@(Convert.ToBoolean(Model.SurveyQuestions[question].Subquestion) == true ? "subquestion" : "questions")">
            <p>
                <b>
                    @Html.DisplayFor(model => model.SurveyQuestions[question].TheQuestion)
                </b>
            </p>
            @Html.HiddenFor(model => model.SurveyAnswers[question].QuestionID)
            @if (Convert.ToBoolean(Model.SurveyQuestions[question].Mandatory) == true)
            {
                <p><span id="@("errorQuestion" + ConvertNumberToWords.Translate(question))" class="errorMsg">*Please choose your answer</span></p>
            }
            @for (var choice = 0; choice < Model.SurveyChoices.Count(); choice++)
            {
                if (Model.SurveyQuestions[question].QuestionID == Model.SurveyChoices[choice].QuestionID)
                {
                    if (Model.SurveyChoices[choice].isStyleOptBox)
                    {
                        var choicesGroup = (from c in Model.SurveyChoices where c.QuestionID == Model.SurveyQuestions[question].QuestionID select new { c.ChoicesID, c.ChoicesName });
                        @Html.Raw("<ul>")
                        @Html.Raw("<li>")
                        @Html.RadioButtonForSelectList(model => model.SurveyAnswers[question].ChoiceID, new SelectList(choicesGroup, "ChoicesID", "ChoicesName"))
                        @Html.Raw("</li>")
                        @Html.Raw("</ul>")
                        break;
                    }
                    else if (Model.SurveyChoices[choice].isStyleChkBox)
                    {
                        var choicesGroup = (from c in Model.SurveyChoices where c.QuestionID == Model.SurveyQuestions[question].QuestionID select new { c.ChoicesID, c.ChoicesName });
                        @Html.Raw("<ul>")
                        @Html.Raw("<li>")
                        @Html.CheckBoxListFor(model => model.SurveyAnswers[question].ChoiceID, model => choicesGroup, model => model.ChoicesID, model => model.ChoicesName, model => false, Position.Vertical)
                        @Html.Raw("</li>")
                        @Html.Raw("</ul>")
                        break;
                    }
                    else if (Model.SurveyChoices[choice].isStyleCboBox)
                    {

                    }
                    else
                    {
                      <div class="margTop20">
                        <p>
                            @*<textarea cols="" rows="5" class="form-control"></textarea>*@
                            @Html.TextAreaFor(model => model.SurveyAnswers[question].Comment, new { rows = "5", cols = "0", @class = "form-control" })
                        </p>
                      </div>
                    }
                }
            }
        </div>
    }
</div>
<input id="hidQuestionCount" type="hidden" value="@Model.SurveyQuestions.Count()" />

<div>
    @*<a class="btn btn-primary" href="#myModal" id="btnSubmit">@Model.Buttontext</a> @Url.Action("Submit", "SaveSurvey", Model)*@
    <input id="btnSubmit" class="btn btn-primary" type="submit" value="@Model.Buttontext" />
</div>

If you will notice, I am using a custom Html Helper to render radio button groups which is RadioButtonSelectListFor which is very handy in this scenario. I am able to bind and get the value of the selected control as you can see in the image below:

enter image description here

Secondly, I am also using the Html Helper package Hmtl Helper CheckBoxListFor to display group of checkboxes to make multiple selections and submit it. But the problem is, I am getting 1 value among all checkboxes which causes me real pain and headache. When there is 2 or more checkboxes checked, only the first item is being returned, you may refer to the image below:

enter image description here

For the comments value, I have no problem with it as I can get the values. As seen on the image:

enter image description here

I also have one problem, I need to bind QuestionID in SurveyAnswers because it is used for reporting purpose.

To sum up, below are my things to achieve:

OBJECTIVES:

  1. Bind QuestionID to model SurveyAnswers
  2. Return all checkboxes value and add it to list SurveyAnswers
  3. If possible, make ModelState.IsValid to be true. If not, I won't be validating model to get the list of SurveyAnwsers

I really want to get this thing to work. I have been doing a lot of research just to get it going, but no progress yet. Kindly help me guys! Any inputs/ suggestions will be highly appreciated.

Thank you very much!

هل كانت مفيدة؟

المحلول

The issue is with these lines in your view model:

public List<SurveyInfo> SurveyInfo { get; set; }
...
public List<SurveyQuestion> SurveyQuestions { get; set; }
public List<SurveyChoice> SurveyChoices { get; set; }

Because you're referencing full entities, presumably each with their own required properties, you need to ensure that each of those required properties is posted back with some value or it invalidates your entire model, because that individual entity is invalid. However, you're not actually creating any of these entities, but rather just displaying existing instances. As a result, you should be using a view model for each of these as well. The only thing that should be required at all on your view model or anything referenced by your view model is the actual data you want to collect from the user.

UPDATE

This is all about how the model binder works and how it determines if your model is valid or not. It looks at it as if you would want to save this entire thing to a database, as if you had a table that matched up with your view model (even though you don't actually). So, in order for the view model to be "valid", it would have to be able to save everything else on the view model as well (your SurveyInfo, SurveyQuestions and SurveyChoices properties. If there's any required properties on any of those classes, that isn't posted back (which there of course are), then they are invalid, and your view model is invalid as a result. Now, that said, there's conditions. Simply because you attached a list of SurveyQuestions doesn't mean that you have to have a list of valid SurveyQuestion instances in your POST body to allow the view model to validate. If a list or relationship is null, then it is not validated (assuming that the list or instance, itself, is not required). So, really where you're going wrong here is in posting back partial instances of these things. Because it's ending up with a list of incomplete SurveyQuestion instance, it's invalid, whereas if you simply posted nothing back at all, it would be valid.

In other words, you can keep the lists as they are, to pull information from as you need it in your view, but, you need to attach POST data somewhere else. Create another list property like:

public List<SurveyAnswerViewModel> SurveyAnswers { get; set; }

And that would be the only thing that you posted to. Then, as long the user fills out the survey correctly as defined in SurveyAnswerViewModel, you'd have a valid view model. Just be aware that since you're not posting the other lists back, you have to repopulated them should the view need to be returned back to correct an error.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top