2

I'm struggling with the following:

I have a class Questions:

public class Question
{
    public int QuestionID { get; set; }
    public string QuestionText { get; set; }
    public int CategoryID { get; set; }
    public string Explanation { get; set; }

    public virtual Category Category { get; set; }

    public virtual ICollection<Answer> Answers { get; set; }
}

and another class Answer:

public class Answer
{
    public int AnswerID { get; set; }
    public string AnswerText { get; set; }
    public string Class { get; set; }

    public int QuestionID { get; set; }
    public virtual Question Question { get; set; }
}

I want a user to be able to add a question with one or more answers from the same view. I am a newbie and not able to figure this out. At this moment I only have the possibility to create a question linked to a category in the Create view.

This is the QuestionController:

    // GET: Questions/Create
    public IActionResult Create()
    {
        ViewData["CategoryID"] = new SelectList(_context.Category, "CategoryID", "CategoryName");
        return View();
    }

Thanks for your help!

2 Answers2

2

I wirte a demo to show how to add one to many relationship tables in the same view:

model

    public class Question
    {
        public int QuestionID { get; set; }
        public string QuestionText { get; set; }
        public string Explanation { get; set; }

        public virtual ICollection<Answer> Answers { get; set; }
    }

    public class Answer
    {
        public int AnswerID { get; set; }
        public string AnswerText { get; set; }
        public string Class { get; set; }

        public int QuestionID { get; set; }
        public virtual Question Question { get; set; }
    }

    public class QA
    {
        public IList<Answer> answer { get; set; }
        public Question question { get; set; }
    }

view

@model upload111.Models.QA

<form asp-controller="Home" asp-action="Create" method="post">
    <div class="form-group">
        <label asp-for="@Model.question.QuestionText"></label>
        <input asp-for="@Model.question.QuestionText" />
    </div>
    <div class="form-group">
        <label asp-for="@Model.question.Explanation"></label>
        <input asp-for="@Model.question.Explanation" />
    </div>
    
    <br />
    

    <div class="form-group">
        <div id="inputFormRow" style="width: 35%">
            <div class="input-group mb-3">
                <br />
                <div class="input-group-append"></div>
            </div>
        </div>
        <div id="newRow">
            <input type="hidden" id="totalLans" value="0" />
        </div>
        <button id="addRow" type="button" class="btn btn-info">Add Network</button>    
    </div>
       
        <button type="submit" id="createButton">Add</button>
   
</form>

@section Scripts
{
    <script>
        
       $("#addRow").click(function ()
    {
       
        var rowCount = parseInt($("#totalLans").val());
        rowCount++;
        $("#totalLans").val(rowCount);
        var html = '';
        html += '<div id="inputFormRow" style="width: 35%">';
        html += '<div class="input-group mb-3">'; 

                //change id attribute to name attribute and modify the name
        html += '<input type="text" name="answer[' + (rowCount - 1) + '].AnswerText" class="form-control m-input" placeholder="AnswerText" autocomplete="off" style="width: 30%" required>';
        html += '<input type="text" name="answer[' + (rowCount - 1) + '].Class" class="form-control m-input" placeholder="Class" autocomplete="off" style="width: 30%" required>';
        html += '<div class="input-group-append">';
        html += '<button id="removeRow" type="button" class="btn btn-danger" style="margin-right: 5px">Remove Network</button>';
        html += '</div>';
        html += '</div>';

        $('#newRow').append(html);
        
    });    

    $(document).on('click', '#removeRow', function ()
    {
        var rowCount = parseInt($("#totalLans").val());
        rowCount--;
        $("#totalLans").val(rowCount);
        $(this).closest('#inputFormRow').remove();
    });    

    $(document).ready(function () {
        $("#createButton").click(function ()
        {
            var inputData = $('form').serializeArray();  
            $.ajax(
            {
                type: "POST", //HTTP POST Method
                url: "Home/Create", // Controller/View
                data: inputData,
                success : function(response) {
                    console.log(response)
                }
            });

        });
    });
    </script>
}

controller

   public IActionResult Create()
        {
            
          
            return View();
        }

        

        [HttpPost]
        public async Task<IActionResult> Create(QA q)
        {
            Question qs = new Question();
            qs.QuestionText = q.question.QuestionText;
            qs.Explanation = q.question.Explanation;
            qs.Answers = new List<Answer>();

            foreach (var item in q.answer) {
                var A = new Answer()
                {
                    AnswerText = item.AnswerText,
                    Class = item.Class

                };
                qs.Answers.Add(A);
                
            }
            
            _context.questions.Add(qs);
            _context.SaveChanges();
            
            return RedirectToAction("Index");
        }

enter image description here

Xinran Shen
  • 8,416
  • 2
  • 3
  • 12
0

I tried to implement the answer by Xinran Shen, and it worked extremely well apart from the fact that if multiple rows were added, and then one of them was removed from the middle, the indexes would become out of order. For example:

You may start with this:

  • 1
  • 2
  • 3
  • 4

And delete the third element, leaving the indexes like this:

  • 1
  • 2
  • 4

When this happens, the Model Binder in MVC will only take the elements up to the point where the order is broken (so in the example, only the first and second elements will be inserted into the array on the controller side).

The fix for this is quite simple, and is documented on this site https://haacked.com/archive/2008/10/23/model-binding-to-a-list.aspx/

In short, at the beginning of each of the list elements a hidden field can be added named "answer.Index" (following the original example in the question). The value of this field should contain the index for the current row.

So for the first element:

<input type="hidden" name="answer.Index" value="0" />

And for the second element:

<input type="hidden" name="answer.Index" value="1" />

This seems to solve this issue, and makes the Model Binder put all of the elements into the array, regardless of whether there are gaps or not.

It's documented here too: Binding arrays with missing elements in asp.net mvc

jttri777
  • 41
  • 1
  • 8