Search code examples
c#databaseasp.net-core-mvc

Issue encountered while saving data to database in ASP.NET Core


I'm encountering a problem in my ASP.NET Core MVC application when creating a recipe. The issue revolves around the inability to save recipe ingredients to the database. While the main recipe details are successfully saved, the RecipeIngredients section remains empty.

Here's an overview of the relevant parts of my code:

In the CreateModel class, I have a method OnPostAsync where I handle the saving of recipe ingredients. The Recipe.RecipeIngredient property should be populated with the selected ingredients and quantities.

In the HTML form, I have a section where users can select ingredients from a dropdown list, input quantities, and the unit is automatically selected based on the chosen ingredient. The ingredients are added dynamically using JavaScript.

I have a RecipeIngredient class representing the relationship between recipes and ingredients. This class includes an IngredientID, Quantity, and a reference to the Recipe and Ingredient entities.

I've verified that the RecipeIngredientsInput list is properly initialized in the OnGet method.

The JavaScript function addIngredientRow() appears to correctly add ingredients to the form.

The issue seems to be related to the data not being saved to the RecipeIngredient table in the database.

Debugging: I have tried debugging the application by placing breakpoints in the OnPostAsync method, and it seems that the values in RecipeIngredientsInput are not being saved to the database.

If anyone has encountered a similar issue or has insights into what might be causing the problem, I would greatly appreciate your assistance. Additionally, if you need more details or code snippets, please let me know.

Recipe Create.cshtml.cs:

using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc;
using proiect1.Models;
using System;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Authorization;

namespace proiect1.Pages.Recipes

{
    [Authorize(Roles = "Admin")]
    public class CreateModel : RecipeCategoriesPageModel
    {
        private readonly proiect1.Data.proiect1Context _context;
        private readonly IWebHostEnvironment _environment;
    
        public CreateModel(proiect1.Data.proiect1Context context, IWebHostEnvironment environment)
        {
            _context = context;
            _environment = environment;
        }
    
        [BindProperty]
        public Recipe Recipe { get; set; }
        public List<SelectListItem> AllIngredients { get; set; }
        public List<RecipeIngredientInputModel> RecipeIngredientsInput { get; set; }
    
        [BindProperty]
        public RecipePhoto RecipePhoto { get; set; }
        [BindProperty]
        public IFormFile Photo { get; set; }
    
        public IActionResult OnGet()
        {
            var recipe = new Recipe();
            recipe.RecipeCategories = new List<RecipeCategory>();
            recipe.RecipeIngredients = new List<RecipeIngredient>();

            PopulateAssignedCategoryData(_context, recipe);
            // Recipe.PublishingDate = DateTime.Now;
            PopulateIngredientsDropdown();

            RecipeIngredientsInput = new List<RecipeIngredientInputModel>();
    
            return Page();
        }
    
        private void PopulateIngredientsDropdown()
        {
            var ingredients = _context.Ingredient.Select(i => new SelectListItem
            {
                Value = i.Id.ToString(),
                Text = i.Name,
                Group = new SelectListGroup { Name = i.Unit } 
            })
            .ToList();
    
            AllIngredients = ingredients;
        }
    
        public async Task<IActionResult> OnPostAsync(string[] selectedCategories)
        {
            if (selectedCategories != null)
            {
                Recipe.RecipeCategories = selectedCategories
                    .Select(cat => new RecipeCategory { CategoryId = int.Parse(cat) })
                    .ToList();
            }
    
            if (RecipeIngredientsInput != null)
            {
                Recipe.RecipeIngredients = RecipeIngredientsInput
                    .Select(input => new RecipeIngredient
                    {
                        IngredientID = input.IngredientID,
                        Quantity = input.Quantity
                    })
                    .ToList();
            }
    
            if (Photo != null && Photo.Length > 0)
            {
                var directoryPath = Path.Combine(_environment.WebRootPath, "Recipe Photos");
    
                // Verifică dacă directorul există, altfel creează-l
                if (!Directory.Exists(directoryPath))
                {
                    Directory.CreateDirectory(directoryPath);
                }
    
                var photoPath = Path.Combine(directoryPath, Photo.FileName);

                using (var stream = new FileStream(photoPath, FileMode.Create))
                {
                    await Photo.CopyToAsync(stream);
                }
    
                RecipePhoto recipePhoto = new RecipePhoto
                {
                    Image = Photo,
                    ImagePath = Photo.FileName
                };
    
                _context.RecipePhotos.Add(recipePhoto);
                Recipe.Photo = Photo.FileName;
            }
    
            Recipe.PublishingDate = DateTime.Now;
           
            _context.Recipe.Add(Recipe);
            await _context.SaveChangesAsync();

            Recipe.Photo = Photo.FileName;

            return RedirectToPage("./Index");
        }
    }
    
    public class RecipeIngredientInputModel
    {
        public int IngredientID { get; set; }
        public decimal Quantity { get; set; }
    }
}

Recipe.cshtml:

@page
@model proiect1.Pages.Recipes.CreateModel

@{
    ViewData["Title"] = "Create";
}

<script>
    function addIngredientRow() {
        var table = document.getElementById('ingredientTable');
        var row = table.insertRow(-1);

        // C1
        var select = document.createElement('select');
        select.name = 'RecipeIngredientsInput[' + table.rows.length + '].IngredientID';
        select.className = 'form-control';

        //data-unit
   @foreach (var ingredient in Model.AllIngredients)
{
    <text>
        var option = document.createElement('option');
        option.value = "@ingredient.Value";
        option.text = "@ingredient.Text";
        option.setAttribute('data-unit', "@ingredient.Group.Name");
        select.add(option);
    </text>
}

        row.insertCell(0).appendChild(select);

        // C2
        var quantityInput = document.createElement('input');
        quantityInput.type = 'text';
        quantityInput.name = 'RecipeIngredientsInput[' + table.rows.length + '].Quantity';
        quantityInput.className = 'form-control';
        row.insertCell(1).appendChild(quantityInput);

        // C3
        var unitText = document.createElement('span');
        unitText.className = 'form-control';
        unitText.innerHTML = '';
        row.insertCell(2).appendChild(unitText);

        select.addEventListener('change', function () {
            unitText.innerHTML = this.options[this.selectedIndex].getAttribute('data-unit');
        });
    }

    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', function () {
            addIngredientRow();
        });
    } else {
        addIngredientRow();
    }
</script>

<form method="post" enctype="multipart/form-data">
<h1>Create</h1>

<h4>Recipe</h4>
<hr />
<div class="row">
    <div class="col-md-4">
        <div class="form-group">
            <label asp-for="Recipe.Title" class="control-label"></label>
            <input asp-for="Recipe.Title" class="form-control" />
            <span asp-validation-for="Recipe.Title" class="text-danger"></span>
        </div>
        <div class="form-group">
            <label asp-for="Recipe.Description" class="control-label"></label>
            <input asp-for="Recipe.Description" class="form-control" />
            <span asp-validation-for="Recipe.Description" class="text-danger"></span>
        </div>

        <div class="form-group">
            <label asp-for="Recipe.Instructions" class="control-label"></label>
            <input asp-for="Recipe.Instructions" class="form-control" />
            <span asp-validation-for="Recipe.Instructions" class="text-danger"></span>
        </div>
         <div class="form-group">
                    <label asp-for="Recipe.RecipeIngredients" class="control-label">Ingredients</label>
                    <table id="ingredientTable">
                        <tr>
                            <th>Select Ingredient</th>
                            <th>Enter Quantity</th>
                            <th>Unit</th>
                        </tr>
                    </table>
                    <button type="button" onclick="addIngredientRow()">Add Ingredient</button>
                    <span asp-validation-for="Recipe.RecipeIngredients" class="text-danger"></span>
                </div>
        <div class="form-group">
            <label asp-for="Recipe.Photo" class="control-label"></label>
            <div class="custom-file">
                <input type="file" asp-for="Photo" class="custom-file-input" accept="image/*" />
 
            </div>
            <span asp-validation-for="Photo" class="text-danger"></span>
        </div>

        <div class="form-group">
            <label asp-for="Recipe.PublishingDate" class="control-label"></label>
           <input asp-for="Recipe.PublishingDate" type="hidden" />

            <span asp-validation-for="Recipe.PublishingDate" class="text-danger"></span>
        </div>
         <div class="form-group">
                             <div class="table">
                               <table>
                                 <tr>
                                    @{
                                         int cnt = 0;
                                         foreach (var cat in Model.AssignedCategoryDataList)
                                     {
                                             if (cnt++ % 3 == 0)
                                              {
                                                 @:</tr><tr>
                                                 }
                                                @:<td>
                                               <input type="checkbox"
                                                     name="selectedCategories"
                                                    value="@cat.CategoryID"
                                                     @(Html.Raw(cat.Assigned ?"checked=\"checked\"" : "")) />
                                             @cat.CategoryID @: @cat.Name
                                          @:</td>
                                        }
                                     @:</tr>
                                     }
                              </table>
                         </div>
                     </div>
        <div class="form-group">
            <input type="submit" value="Create" class="btn btn-primary" />
        </div>
    </div>
</div>

<div>
    <a asp-page="Index">Back to List</a>
</div>
</form>

Recipe.cs:

using System.ComponentModel.DataAnnotations;

namespace proiect1.Models
{
    public class Recipe
    {
        public int Id { get; set; }

        [Display(Name = "Recipe Title")]
        public string? Title { get; set; }

        [Display(Name = "Recipe Description")]
        public string? Description { get; set; }

        [Display(Name = "Recipe Instructions")]
        public string? Instructions { get; set; }

        // [Display(Name = "Recipe Photo")]
        public string? Photo { get; set; }

        [DataType(DataType.Date)]
        public DateTime PublishingDate { get; set; }

        public ICollection<RecipeIngredient>? RecipeIngredients { get; set; }

        public int? SavedRecipeId { get; set; }
        public SavedRecipe? SavedRecipe { get; set; }

        public ICollection<RecipeCategory>? RecipeCategories { get; set; }

        public int? UserId { get; set; }
    }
}

Ingredient.cs:

namespace proiect1.Models
{
    public class Ingredient
    {
        public int Id { get; set; }

        public string? Name { get; set; }
        public string? Unit { get; set; } //grame, lingurita, ml,...

        public ICollection<RecipeIngredient>? RecipeIngredients { get; set; }
    }
}

RecipeIngredient.cs:

using proiect1.Models;
using System.ComponentModel.DataAnnotations.Schema;

namespace proiect1.Models
{
    public class RecipeIngredient
    {
        public int Id { get; set; }

        [ForeignKey("Recipe")]
        public int RecipeID { get; set; }
        public Recipe? Recipe { get; set; }

        public int IngredientID { get; set; }
        public Ingredient? Ingredient { get; set; }
        [Column(TypeName = "decimal(6, 0)")]
        public decimal Quantity { get; set; }
    }
}

Solution

  • It is not database failing to store the ingredient, before that the data is not bound correctly. Two places need to be modified.

    1. Create.cshtml.cs the [bindproperty] attribute is needed
    [BindProperty]    //1.the attribute needs to be added on RecipeIngredientsInput
    public List<RecipeIngredientInputModel> RecipeIngredientsInput { get; set; }
    
    1. The row-based number in the cshtml may lead to counting error, replace it with index in your Create.cshtml.
    <script>
        var index = 0;
        function addIngredientRow() {
            var table = document.getElementById('ingredientTable');
            var row = table.insertRow(-1);
    
            // C1
            var select = document.createElement('select');
            select.name = 'RecipeIngredientsInput[' + index + '].IngredientID';
            select.className = 'form-control';
    
            //data-unit
           @foreach (var ingredient in Model.AllIngredients)
            {
                <text>
                    var option = document.createElement('option');
                    option.value = "@ingredient.Value";
                    option.text = "@ingredient.Text";
                    option.setAttribute('data-unit', "@ingredient.Group.Name");
                    select.add(option);
                </text>
            }
    
            row.insertCell(0).appendChild(select);
    
            // C2
            var quantityInput = document.createElement('input');
            quantityInput.type = 'text';
            quantityInput.name = 'RecipeIngredientsInput[' + index + '].Quantity';
            quantityInput.className = 'form-control';
            row.insertCell(1).appendChild(quantityInput);
    
            // C3
            var unitText = document.createElement('span');
            unitText.className = 'form-control';
            unitText.innerHTML = '';
            row.insertCell(2).appendChild(unitText);
    
            select.addEventListener('change', function () {
                unitText.innerHTML = this.options[this.selectedIndex].getAttribute('data-unit');
            });
            index++;
        }
    
        if (document.readyState === 'loading') {
            document.addEventListener('DOMContentLoaded', function () {
                addIngredientRow();
            });
        } else {
            addIngredientRow();
        }
    </script>
    

    enter image description here