MVC 3 Dynamic Form Using a ViewModel

2019-01-22 10:37发布

问题:

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; }
}

回答1:

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>
}