5

I have been trying for weeks to follow a couple tutorials on how to create a dynamic form giving the ability to add another "ingredient" to the form. Here is the article I tried to follow. http://www.joe-stevens.com/2011/07/24/asp-net-mvc-2-client-side-validation-for-dynamic-fields-added-with-ajax/

Right now Im just working on adding multiple recipeIngredients using the add link, but I will need to have both the "ingredientName", and "recipeIngredient" Amount able to be added when the link is clicked. My problem is that when I run the app, the form for the recipeingredient has a 0 instead of an actual textbox. When I click add new ingredient, I am able to get a textbox to add, but when I type in an amount and click save, the model data isnt being passed to the controller..

I just dont even know where to begin with fixing this, I am not sure if I should be using a viewmodel or if Im going about this entirely wrong. Here is my database diagram http://i44.tinypic.com/xp1tog.jpg.

Here is my CreateView:

    @model ViewModels.RecipeViewModel
@using Helpers;



<h2>CreateFullRecipe</h2>

<script src="@Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>

    <script type="text/javascript">
        $().ready(function () {
            $("#add-recipeingredient").click(function () {
                $.ajax({
                    url: '@Url.Action("GetNewRecipeIngredient")',
                    success: function (data) {
                        $(".new-recipeingredients").append(data);
                        Sys.Mvc.FormContext._Application_Load();
                    }
                });
            });
        });
    </script>

  @using (Html.BeginForm())
  {
          @Html.ValidationSummary(true)
    <fieldset>
        <legend>Recipe</legend>

        <div class="editor-label">
            @Html.LabelFor(model => model.Recipe.RecipeName)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.Recipe.RecipeName)
            @Html.ValidationMessageFor(model => model.Recipe.RecipeName)
        </div>
    </fieldset>


        <fieldset>
            <legend>RecipeIngredients</legend>
            <div class="new-recipeingredients">

                @Html.EditorFor(model => model.RecipeIngredients)

            </div>
            <div style="padding: 10px 0px 10px 0px">
                <a id="add-recipeingredient" href="javascript:void(0);">Add another</a>
            </div>
        </fieldset>

        <div>
            <input type="submit" value="CreateFullRecipe" />
        </div>

  }
<div>
    @Html.ActionLink("Back to List", "Index")
</div>

My editortemplateview for recipeingredient:

@model Models.RecipeIngredient
@using Helpers;

@using (Html.BeginAjaxContentValidation("form0"))
    {
        using (Html.BeginCollectionItem("RecipeIngedients"))
        {
    <div style="padding: 5px 0px 5px 0px">
        @Html.LabelFor(model => model.Amount)
        @Html.EditorFor(model => model.Amount)
        @Html.ValidationMessageFor(model => Model.Amount)
    </div>

        }
    }

MY Controller relating methods:

[HttpGet]
    public ActionResult CreateFullRecipe()
    {
        var recipeViewModel = new RecipeViewModel();
        return View(recipeViewModel);
    }

    //
    // POST: /Recipe/Create

    [HttpPost]
    public ActionResult CreateFullRecipe(RecipeViewModel recipeViewModel)
    {
        if (ModelState.IsValid)
        {
            db.Recipes.Add(recipeViewModel.Recipe);
            db.SaveChanges();
            int recipeID = recipeViewModel.Recipe.RecipeID;
            for (int n = 0; n < recipeViewModel.RecipeIngredients.Count(); n++)
            {
                db.Ingredients.Add(recipeViewModel.Ingredients[n]);
                int ingredientID = recipeViewModel.Ingredients[n].IngredientID;

                recipeViewModel.RecipeIngredients[n].RecipeID = recipeID;
                recipeViewModel.RecipeIngredients[n].IngredientID = ingredientID;
                db.RecipeIngredients.Add(recipeViewModel.RecipeIngredients[n]);

                db.SaveChanges();
            }

            return RedirectToAction("Index");
        }

        return View(recipeViewModel);
    }

    public ActionResult GetNewIngredient()
    {
        return PartialView("~/Views/Shared/IngredientEditorRow.cshtml", new Ingredient());
    }

    public ActionResult GetNewRecipeIngredient()
    {
        return PartialView("~/Views/Shared/_RecipeIngredientEditRow.cshtml", new RecipeIngredient());
    }

My View Model:

    public class RecipeViewModel
    {
        public RecipeViewModel()
        {
            RecipeIngredients = new List<RecipeIngredient>() { new RecipeIngredient() };
            Ingredients = new List<Ingredient>() { new Ingredient() };
            Recipe = new Recipe();
        }

        public Recipe Recipe { get; set; }
        public IList<Ingredient> Ingredients { get; set; }
        public IList<RecipeIngredient> RecipeIngredients { get; set; }
    }
}

If there is any other information needed to help my problem out please let me know. This is really driving me crazy so I look forward to any help I can get Thank you!

I would also like to mention that the controller post method createfullrecipe is for a pre defined list and it worked when I wasnt worried about giving the user the ability to add another ingredient, rather I just defaulted the form to have 2 ingredients and my view had this commented out code to create them. All I really want to do is get the viewmodel to pass the form data to the controller and I can handle the data like my createfullrecipe controller method does now.

@*    @for (int n = 0; n < Model.Ingredients.Count(); n++)
    {
        <div class="editor-label">
            @Html.LabelFor(model => model.Ingredients[n].IngredientName)
        </div>
                <div class="editor-field">
            @Html.EditorFor(model => model.Ingredients[n].IngredientName)
            @Html.ValidationMessageFor(model => model.Ingredients[n].IngredientName)
        </div>

        <div class="editor-label">
            @Html.LabelFor(model => model.RecipeIngredients[n].Amount)
        </div>
                <div class="editor-field">
            @Html.EditorFor(model => model.RecipeIngredients[n].Amount)
            @Html.ValidationMessageFor(model => model.RecipeIngredients[n].Amount)
        </div>
    }*@

Here are my model classes:

public class Recipe
{
    public int RecipeID { get; set; }
    public string RecipeName { get; set; }
    public string Description { get; set; }
    public int? PrepTime { get; set; }
    public int? CookTime { get; set; }
    public string ImageURL { get; set; }

    public virtual IList<RecipeTag> RecipeTags { get; set; }
    public virtual IList<Rating> Ratings { get; set; }
    public virtual IList<RecipeStep> RecipeSteps { get; set; }
    public virtual IList<RecipeIngredient> RecipeIngredients { get; set; }

}

public class RecipeIngredient
{
    public int RecipeIngredientID { get; set; }
    public string IngredientDesc { get; set; }
    public string Amount { get; set; }
    public int RecipeID { get; set; }
    public int? IngredientID { get; set; }

    public virtual Recipe Recipe { get; set; }
    public virtual Ingredient Ingredient { get; set; }
}

public class Ingredient
{

    public int IngredientID { get; set; }
    public string IngredientName { get; set; }

    public virtual ICollection<RecipeIngredient> RecipeIngredients { get; set; }
}
2
  • I talk about this issue in this blog post: jasoncavett.com/2011/03/… Commented Feb 16, 2012 at 17:01
  • Right now Im not sure if validation is my biggest issue. I cant get the dynamic form to send the data to my controller. Now the non ajax stuff for example if I type in the recipe name, that does exist in the viewmodel passed to my controller. Commented Feb 16, 2012 at 17:12

1 Answer 1

13

There are lots of issues with your code. I prefer to go step by step in order to illustrate a simplified example that you could adapt to your needs.

Models:

public class RecipeViewModel
{
    public Recipe Recipe { get; set; }
    public IList<RecipeIngredient> RecipeIngredients { get; set; }
}

public class Recipe
{
    public string RecipeName { get; set; }
}

public class RecipeIngredient
{
    public int Amount { get; set; }

    [Required]
    public string IngredientDesc { get; set; }
}

Controller:

public class HomeController : Controller
{
    public ActionResult Index()
    {
        var recipeViewModel = new RecipeViewModel();
        return View(recipeViewModel);
    }

    [HttpPost]
    public ActionResult Index(RecipeViewModel recipeViewModel)
    {
        if (!ModelState.IsValid)
        {
            // there wre validation errors => redisplay the view
            return View(recipeViewModel);
        }

        // TODO: the model is valid => you could pass it to your 
        // service layer for processing

        return RedirectToAction("Index");
    }

    public ActionResult GetNewRecipeIngredient()
    {
        return PartialView("~/Views/Shared/EditorTemplates/RecipeIngredient.cshtml", new RecipeIngredient());
    }
}

View (~/Views/Home/Index.cshtml):

@model RecipeViewModel

<script src="@Url.Content("~/Scripts/jquery.validate.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.js")" type="text/javascript"></script>
<script type="text/javascript">
    $(function() {
        $('#add-recipeingredient').click(function () {
            $.ajax({
                url: '@Url.Action("GetNewRecipeIngredient")',
                type: 'POST',
                success: function (data) {
                    $('.new-recipeingredients').append(data);
                }
            });
            return false;
        });
    });
</script>

@using (Html.BeginForm())
{
    @Html.ValidationSummary(true)

    <div>
        @Html.LabelFor(model => model.Recipe.RecipeName)
        @Html.EditorFor(model => model.Recipe.RecipeName)
        @Html.ValidationMessageFor(model => model.Recipe.RecipeName)
    </div>

    <fieldset>
        <legend>RecipeIngredients</legend>
        <div class="new-recipeingredients">
            @Html.EditorFor(model => model.RecipeIngredients)
        </div>
        <div style="padding: 10px 0px 10px 0px">
            <a id="add-recipeingredient" href="javascript:void(0);">Add another</a>
        </div>
    </fieldset>

    <div>
        <input type="submit" value="CreateFullRecipe" />
    </div>
}

Editor template (~/Views/Shared/EditorTemplates/RecipeIngredient.cshtml):

@model RecipeIngredient

@using (Html.BeginCollectionItem("RecipeIngredients"))
{
    <div>
        @Html.LabelFor(model => model.Amount)
        @Html.EditorFor(model => model.Amount)
        @Html.ValidationMessageFor(model => model.Amount)
    </div>

    <div>
        @Html.LabelFor(model => model.IngredientDesc)
        @Html.EditorFor(model => model.IngredientDesc)
        @Html.ValidationMessageFor(model => model.IngredientDesc)
    </div>
}
Sign up to request clarification or add additional context in comments.

3 Comments

Thanks for helping out I really appreciate it. I updated my post to include my already existing models which may help with answers. I could also use some help with determining if I should just combine the normalized tables Ingredient and recipeIngredient into one table/model to simplify things? I also updated my js and views with your cleaner approach, but the controller post method still doesnt have my form data in the passed in viewModel. I assume it has something to do with how my models are setup?
@papayt, did you try my code? As I said there are many issues with yours. For example you have using (Html.BeginCollectionItem("RecipeIngedients")) whereas the correct is using (Html.BeginCollectionItem("RecipeIngredients")) simply because your property is called RecipeIngredients and not RecipeIngedients. Another problem is with the name and location of your editor template. So what I would recommend you to use my code as a base for extensions.
O wow I never even realized I had a typo with (Html.BeginCollectionItem("RecipeIngedients")), thanks! I moved my renamed editor template to the folder you recommended as well. And... Awesome! I just tested my dynamic recipeIngredient portion of my code with the ability to add another recipeIngredient, and it works. The data is passed to my controller. :) Now I can continue working on cleaning up my code and figuring out how to integrate the ingredient model to my dynamic form and then handling the data in my controller! Thank you so much Darin.

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.