locked
DropDownListFor - How to bind to Create post parameter in controller? RRS feed

  • Question

  • User-1521109604 posted

    Edit - So if I use index 0, then I'm able to see the model in the Create post method. That just leaves me to add multiple tags, I'm guessing with the use of a 'Add' button and then you would submit/post.

    @Html.DropDownListFor(model => model.allTags[0].TagId, new SelectList(Model.allTags, "TagId", "Name"), "Select Tag", new { @class = "form-control" })
    



    When I debug and view the model parameter for the Create [HttpPost] method, data is stored in Recipe, but nothing is stored in allTags. Strangely, if I don't choose from the Drop Down List then count = 1, otherwise its 0.

    The Create form

    Extra:
    In addition to this, I would like to add multiple Tags to a Recipe, hence would need an 'Add Tag' button instead of relying on a single Submit button where it would only allow for one tag to be paired with a Recipe. How would I go about this?

    The Create View:

    @model MVCApp.ViewModels.CreateRecipe
     
    @{
        ViewBag.Title = "Create";
    }
     
    <h2>Create</h2>
     
     
    @using (Html.BeginForm())
    {
        @Html.AntiForgeryToken()
     
        <div class="form-horizontal">
            <h4>Create Recipe</h4>
            <hr />
            @Html.ValidationSummary(true""new { @class = "text-danger" })
     
            <div class="form-group">
                @Html.DropDownListFor(model => model.allTags, new SelectList(Model.allTags, "TagId""Name"), "Select Tag"new { @class = "form-control" })
            </div>
     
            <div class="form-group">
                @Html.LabelFor(model => model.Recipe.Name, htmlAttributesnew { @class = "control-label col-md-2" })
                <div class="col-md-10">
                    @Html.EditorFor(model => model.Recipe.Name, new { htmlAttributes = new { @class = "form-control" } })
                    @Html.ValidationMessageFor(model => model.Recipe.Name, ""new { @class = "text-danger" })
                </div>
            </div>
     
            <div class="form-group">
                @Html.LabelFor(model => model.Recipe.Instructions, htmlAttributesnew { @class = "control-label col-md-2" })
                <div class="col-md-10">
                    @Html.EditorFor(model => model.Recipe.Instructions, new { htmlAttributes = new { @class = "form-control" } })
                    @Html.ValidationMessageFor(model => model.Recipe.Instructions, ""new { @class = "text-danger" })
                </div>
            </div>
     
            <div class="form-group">
                <div class="col-md-offset-2 col-md-10">
                    <input type="submit" value="Create" class="btn btn-default" />
                </div>
            </div>
        </div>
    }
     
    <div>
        @Html.ActionLink("Back to List""ViewRecipes")
    </div>
     
    @section Scripts {
        @Scripts.Render("~/bundles/jqueryval")
    }
    

    The controller Create Post method:

    // POST: Recipe/Create
    [HttpPost]
    public ActionResult Create(CreateRecipe model) {
        try
        {
            // TODO: Add insert logic here
     
            return RedirectToAction("ViewRecipes");
        }
        catch
        {
            return View();
        }
    }

    The CreateRecipe ViewModel (uses Tag and Recipe Classes):

    public class CreateRecipe
    {
        // A list of all the tags available 
        public List<Tag> allTags { getset; }
        // A Recipe object
        public Recipe Recipe { getset; }
    }

    The Tag Class:

    public class Tag
    {
        public int TagId { getset; }
     
        [Required]
        [StringLength(50, ErrorMessage = "Tag cannot be longer than 50 characters.")]
        [Display(Name = "Tag Name:")]
        [RegularExpression(@"^[A-Z]+[a-zA-Z""'\s-]*$", ErrorMessage = "Must start with a capital letter, only alphabetic characters and no spaces allowed.")]
        public string Name { getset; }
    }

    The Recipe Class:

    public class Recipe
    {
        public int RecipeId { getset; }
        public string Name { getset; }
        public string Instructions { getset; }
    }



    Friday, September 18, 2020 2:55 AM

Answers

  • User-1521109604 posted

    Thanks @bruce (sqlwork.com) for the insight.

    Thanks @YihuiSun I used your checkbox solution for the customer side.

    I found out my mistakes. I didn't actually set up the SelectList properly in my Model class. I also switched to a ListboxFor. The following solution:

    The ViewModel:

    public class CreateRecipe
     {
         // A Recipe object
         public Recipe Recipe { getset; }
    
         [Required(ErrorMessage = "At least one Tag is required.")]
         [Display(Name = "Select multiple tags:")]
         public int[] SelectedTagIds { getset; }
         public IEnumerable<SelectListItem> TagList { getset; }  }

    The Controller:

    // GET: Recipe/Create
    public ActionResult Create()
    {
        ViewBag.Message = "Create Recipe";
     
        var createRecipe = new CreateRecipe
        {
            Recipe = new Recipe(),
            SelectedTagIds = new[] { 2 },
            TagList = GetAllTagTypes()
        };
     
        return View(createRecipe);
    }
    // POST: Recipe/Create
    [HttpPost]
    public ActionResult Create(CreateRecipe model) {
        try
        {
            // TODO: Add insert logic here
     
            return RedirectToAction("ViewRecipes");
        }
        catch
        {
            return View();
        }
    }
    public List<SelectListItemGetAllTagTypes()
    {
        List<SelectListItemitems = new List<SelectListItem>();
     
        var data = TagProcessor.LoadTags();
     
        foreach (var row in data)
        {
            items.Add(new SelectListItem
            {
                Text = row.Name,
                Value = row.TagId.ToString()
            });
        }
        return items; }

    The View:

    <div class="col-md-3">
        @Html.ListBoxFor(model => model.SelectedTagIds, Model.TagList, new { style = "width:200px;height:300px; padding:5px;", @onchange = "getSelectedTags(this)" })
        @Html.ValidationMessageFor(model => model.SelectedTagIds, ""new { @class = "text-danger" })
    </div>

    Resources: 

    https://www.completecsharptutorial.com/asp-net-mvc5/html-listboxfor-and-html-listboxforfor-example-in-asp-net-mvc.php

    • Marked as answer by Anonymous Thursday, October 7, 2021 12:00 AM
    Friday, September 25, 2020 11:00 PM

All replies

  • User-474980206 posted

    The select only posts  the selected value, not the option list. Your create model should have property for this. You should add a int TagId, and use this in the select.

    @Html.DropDownListFor(model => model.TagId, new SelectList(Model.allTags, "TagId""Name"), "Select Tag"new { @class = "form-control" })
    Friday, September 18, 2020 2:28 PM
  • User1686398519 posted

    Hi James_00068, 

    According to your needs, I suggest you use CheckBox. I modified it according to your code, you can refer to it.

    Tag

        public class Tag
        {
            public int TagId { get; set; }
            [Required]
            [StringLength(50, ErrorMessage = "Tag cannot be longer than 50 characters.")]
            [Display(Name = "Tag Name:")]
            [RegularExpression(@"^[A-Z]+[a-zA-Z""'\s-]*$", ErrorMessage = "Must start with a capital letter, only alphabetic characters and no spaces allowed.")]
            public string Name { get; set; }
            public bool isActive { get; set; }
        }

    RecipeController

            public ActionResult Create()
            {
                CreateRecipe model = new CreateRecipe(); 
                model.allTags = new List<Tag> ();
                for (int i = 1; i < 5; i++)
                {
                    model.allTags.Add(new Tag {TagId=i,Name="Tag"+i.ToString(),isActive=false });
                }
                return View(model);
            }
            [HttpPost]
            public ActionResult Create(CreateRecipe model)
            {
                    return View(model);
            }

    Create

            <div class="form-group">
                <ul class="list-group col-md-10 col-md-offset-1">
                    <li class="list-group-item active">
                        All tags
                    </li>
                    <li class="list-group-item">
                        @for (var i = 0; i < Model.allTags.Count(); i++)
                        {
                            <label>
                                @Html.HiddenFor(model => Model.allTags[i].TagId)@Html.HiddenFor(model => Model.allTags[i].Name)
                                @Html.CheckBoxFor(model => Model.allTags[i].isActive)@Html.DisplayFor(model => Model.allTags[i].Name)
                            </label>
                        }
                    </li>
                </ul>
            </div>

    Here is the result.

    Best Regards,

    YihuiSun

    Tuesday, September 22, 2020 5:41 AM
  • User-1521109604 posted

    Thanks @bruce (sqlwork.com) for the insight.

    Thanks @YihuiSun I used your checkbox solution for the customer side.

    I found out my mistakes. I didn't actually set up the SelectList properly in my Model class. I also switched to a ListboxFor. The following solution:

    The ViewModel:

    public class CreateRecipe
     {
         // A Recipe object
         public Recipe Recipe { getset; }
    
         [Required(ErrorMessage = "At least one Tag is required.")]
         [Display(Name = "Select multiple tags:")]
         public int[] SelectedTagIds { getset; }
         public IEnumerable<SelectListItem> TagList { getset; }  }

    The Controller:

    // GET: Recipe/Create
    public ActionResult Create()
    {
        ViewBag.Message = "Create Recipe";
     
        var createRecipe = new CreateRecipe
        {
            Recipe = new Recipe(),
            SelectedTagIds = new[] { 2 },
            TagList = GetAllTagTypes()
        };
     
        return View(createRecipe);
    }
    // POST: Recipe/Create
    [HttpPost]
    public ActionResult Create(CreateRecipe model) {
        try
        {
            // TODO: Add insert logic here
     
            return RedirectToAction("ViewRecipes");
        }
        catch
        {
            return View();
        }
    }
    public List<SelectListItemGetAllTagTypes()
    {
        List<SelectListItemitems = new List<SelectListItem>();
     
        var data = TagProcessor.LoadTags();
     
        foreach (var row in data)
        {
            items.Add(new SelectListItem
            {
                Text = row.Name,
                Value = row.TagId.ToString()
            });
        }
        return items; }

    The View:

    <div class="col-md-3">
        @Html.ListBoxFor(model => model.SelectedTagIds, Model.TagList, new { style = "width:200px;height:300px; padding:5px;", @onchange = "getSelectedTags(this)" })
        @Html.ValidationMessageFor(model => model.SelectedTagIds, ""new { @class = "text-danger" })
    </div>

    Resources: 

    https://www.completecsharptutorial.com/asp-net-mvc5/html-listboxfor-and-html-listboxforfor-example-in-asp-net-mvc.php

    • Marked as answer by Anonymous Thursday, October 7, 2021 12:00 AM
    Friday, September 25, 2020 11:00 PM