asp.netasp.net-mvclistasp.net-coredynamic-list

How to dynamically add items from different entities to lists in ASP.NET Core MVC


This is my first experience with lists in general and I have kinda complex scenario. I want to add an object of type Story that has a list of Sentences that can be added dynamically. Sentence has a one-to-one relationship with Image and another one-to-one relationship with Audio (that are optional to add). I managed to add the sentences list to the database along with the story object. But I have no idea where to start with the other two entities.

Here are the models for each entity:

public class Story
{
    public int StoryId { get; set; }
    public string Title { get; set; }
    public string Type { get; set; }
    public DateTime Date { get; set; }

    public virtual IEnumerable<Sentence> Sentences { get; set; } // one to many Story-Sentence
}

Sentence class:

public class Sentence
{
    public int Id { get; set; }
    public string SentenceText { get; set; }

    public virtual Audio Audio { get; set; } // one to one Sentence-Audio
    public virtual Image Image { get; set; } // one to one Sentence-Image
}

Image class:

public class Image
{
    [Key]
    [ForeignKey("Sentence")]
    public int Id { get; set; }
    public string ImageSelected { get; set; }
    public virtual Sentence Sentence { get; set; }
}

And the Audio class is exactly like the Image class. The view.

@model Story
<div id="editorRows">
    @foreach (var item in Model.Sentences)
    {
        <partial name="_SentenceEditor" model="item" />
    }
</div>
<a id="addItem" asp-action="BlankSentence" asp-controller="StoryTest">Add Sentence...</a> <br />
<input type="submit" value="Finished" />

The partial view

@model Sentence
<div class="editorRow">
    @using (Html.BeginCollectionItem("sentences"))
    {
        <span>Name: </span> @Html.EditorFor(m => m.SentenceText);
    }
    @using (Html.BeginCollectionItem("sentences"))
    {
        <span>Image: </span> @Html.EditorFor(m => m.Image.ImageSelected);
    }
    <a href="#" class="deleteRow">delete</a>
</div>

and I have some javascript that add and remove rows dynamically.

Finally in the Controller I'm just saving the model in the database

        [HttpPost]
    public async Task<IActionResult> AddTwo(Story model/*IEnumerable<Sentence> sentence*/)
    {
        if (ModelState.IsValid)
        {
            _db.Story.Add(model);
            await _db.SaveChangesAsync();
            return RedirectToAction("Index");
        }
        return View(model);
    }

In short, I want to add an Image and Audio along with the sentence. And also be able to access the entire row correctly for editing.


Solution

  • I made a demo based on your codes. You can store data in formData then use ajax pass it to the controller action.

    Main View:

    @model Story
    <form asp-action="AddSentence" method="post">
        <div id="editorRows">
            <input type="hidden" name="" value="@Model.StoryId" />
            @foreach (var item in Model.Sentences)
            {
                <partial name="_SentenceEditor" model="item" />
            }
        </div>
        <a id="addItem" asp-action="BlankSentence" asp-controller="StoryTest">Add Sentence...</a>
        <br />
        <input type="submit" id="submit" value="Finished" />
    </form>
    
    @section scripts {
        <script>
            $("#submit").click(function (e) {
                e.preventDefault();
                var formData = new FormData();
    
                $("input[name='Audio.AudioSelected']").each(function (i) {
                    var AudioSelected = $(this).val();
                    formData.append("Sentences[" + i + "].Audio.AudioSelected", AudioSelected);
    
                });
                $("input[name='Image.ImageSelected']").each(function (i) {
                    var ImageSelected = $(this).val();
                    formData.append("Sentences[" + i + "].Image.ImageSelected", ImageSelected);
    
                });
                $("input[name='SentenceText']").each(function (i) {
                    var SentenceText = $(this).val();
                    formData.append("Sentences[" + i + "].SentenceText", SentenceText);
    
                });
    
                $.ajax({
                    method: 'post',
                    url: "StoryTest/AddSentence",
                    data: formData,
                    processData: false,
                    contentType: false,
                    success: function () {
    
                    }
                });
    
            });
    
            $("#addItem").click(function () {
                $.ajax({
                    url: this.href,
                    cache: false,
                    success: function (html) { $("#editorRows").append(html); }
                });
                return false;
            });
    
            $("a.deleteRow").on("click", function () {
                $(this).parents("div.editorRow:first").remove();
                return false;
            });
        </script>
    }
    

    Partial View:

    @model Sentence
    <div class="editorRow">
    
        <span>Name: </span> @Html.EditorFor(m => m.SentenceText)
    
        <span>Audio: </span> @Html.EditorFor(m => m.Audio.AudioSelected)
    
        <span>Image: </span> @Html.EditorFor(m => m.Image.ImageSelected)
    
        <a href="#" class="deleteRow">delete</a>
    </div>
    

    Controller:

    public IActionResult Index()
    {
        Story story = new Story
        {
            Sentences = new List<Sentence>
            {
                new Sentence { Id = 1, SentenceText = "AAA"
                , Audio = new Audio{ AudioSelected = "True"}
                , Image = new Image{ ImageSelected = "True"} },
    
                new Sentence { Id = 2, SentenceText = "BBB"
                , Audio = new Audio{ AudioSelected = "False"}
                , Image = new Image{ ImageSelected = "False"}},
    
                new Sentence { Id = 3, SentenceText = "CCC"
                , Audio = new Audio{ AudioSelected = "True"}
                , Image = new Image{ ImageSelected = "False"}}
            }
        };
        return View(story);
    }
    
    [HttpPost]
    public async Task<IActionResult> AddSentence(Story model)
    {
        if (ModelState.IsValid)
        {
            _db.Story.Add(model);
            await _db.SaveChangesAsync();
            return RedirectToAction("Index");
        }
        return View(model);
    }
    
    public IActionResult BlankSentence()
    {
        return PartialView("_SentenceEditor", new Sentence());
    }
    

    Result:

    enter image description here