-2

I am trying to create a view model that allows the user to create a new product by typing in the products new name and then select which parent category it belongs to, which then filters the sub category and the user selects the appropriate sub-category. I am currently just trying to have both a drop down list of all parent categories and a drop down list of all subcategories, but I am getting a null refernce for the parent category in the view.

Models

public class Product
{
    [Key]
    public int ProductID { get; set; }

    [Required]
    [Display(Name = "Item Name")]
    public string ProductName { get; set; }


    public int ProductSubcategoryID { get; set; }

    [Required]

    public ProductSubcategory ProductSubcategory { get; set; }
  }

public class ProductSubcategory
{
    [Key]
    public int ProductSubcategoryID { get; set; }
    [Required]
    public int ParentCategoryID { get; set; }
    public ParentCategory ParentCategory { get; set; }

    [Required]
    [Display(Name = "Type")]
    public string ProductSubcategoryDescription { get; set; }
}

public class ParentCategory
{
    [Key]
    public int ParentCategoryID { get; set; }

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


    public IEnumerable<ProductSubcategory> ProductSubcategories { get; set; }
}

View Model

public class CreateProductViewModel
{
    public IEnumerable<ParentCategory>  ParentCategories{ get; set; }
    public IEnumerable<ProductSubcategory> ProductSubcategories { get; set; }
    public Product Product { get; set; }
}

Controllers

// GET: Products/Create
public IActionResult Create()
{
        var parentCategories = _context.ParentCategories.ToList();
        var productSubcategories = _context.ProductSubcategories.ToList();
        var viewModel = new CreateProductViewModel
        {
            ParentCategories = parentCategories,
            ProductSubcategories = productSubcategories
        };

        return View(viewModel);
}

// POST: Products/Create
// To protect from overposting attacks, please enable the specific properties you want to bind to, for 
// more details see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create([Bind("ProductID,ProductName,VendorID,ProductSubcategoryID,ParentCategoryID,LocationID,QuantityPerUnit,UnitsInStock,UnitsInOrder,ReorderLevel,ProductComment,ProductMedia")]Product product)
{
            _context.Add(product);
            await _context.SaveChangesAsync();
            return RedirectToAction(nameof(Index));
}

view

@model PrototypeWithAuth.ViewModels.CreateProductViewModel

@{
    ViewData["Title"] = "Create";
    Layout = "~/Views/Shared/View.cshtml";
}

<h1>Create</h1>

<h4>Product</h4>
<hr />
<div class="row">
    <div class="col-md-4">
        <form asp-action="Create">
            <div asp-validation-summary="ModelOnly" class="text-danger"></div>
            <div class="form-group">
                <label asp-for="Product.ProductName" class="control-label"></label>
                <input asp-for="Product.ProductName" class="form-control" />
                <span asp-validation-for="Product.ProductName" class="text-danger"></span>
            </div>

            <div class="form-group">
                <label asp-for="Product.ProductSubcategoryID" class="control-label"></label>
                <!--<input asp-for="Product.ProductSubcategoryID" class="form-control" />-->
                @Html.DropDownListFor(s => s.Product.ProductSubcategoryID,
                    new SelectList(Model.ProductSubcategories, "ProductSubcategoryID", "ProductSubcategoryDescription"),
                    "Select Subcategory", new { @class = "form-control" })
                <span asp-validation-for="Product.ProductSubcategoryID" class="text-danger"></span>
            </div>

            <div class="form-group">
                <label asp-for="Product.ProductSubcategory.ParentCategoryID" class="control-label"></label>
                <!--<input asp-for="ProductSubcategoryID" class="form-control" />-->
                @Html.DropDownListFor(c => c.Product.ProductSubcategory.ParentCategoryID,
                new SelectList(Model.ParentCategories, "ParentCategoryID", "ProductSubcategoryDescription"),
                "Select Category", new { @class = "form-control" })
                <span asp-validation-for="Product.ProductSubcategory.ParentCategoryID" class="text-danger"></span>
            </div>
         </form>
  </div>
</div>

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

When trying to add a new product I get a null reference for ParentCategory, but if I do not us a html helper tag for a dropdown than the form works. Thank you in advance

Panagiotis Kanavos
  • 120,703
  • 13
  • 188
  • 236
Faige
  • 13
  • 4
  • Related: https://stackoverflow.com/questions/4660142/what-is-a-nullreferenceexception-and-how-do-i-fix-it and https://stackoverflow.com/questions/25385173/what-is-a-debugger-and-how-can-it-help-me-diagnose-problems – Hans Kesting Feb 11 '20 at 11:47
  • `new SelectList(Model.ParentCategories, "ParentCategoryID", "ProductSubcategoryDescription")` > something doesn't seem quite right with that last part – AsheraH Feb 11 '20 at 15:46

1 Answers1

-1

The SelectList is used like below

public SelectList(IEnumerable items, string dataValueField, string dataTextField)

In you case,ProductSubcategoryDescription is not the field of ParentCategory leading to the error.So change it to

@Html.DropDownListFor(c => 
                     c.Product.ProductSubcategory.ParentCategoryID,
                     new SelectList(Model.ParentCategories, "ParentCategoryID", "ParentCategoryDescription"),
                     "Select Category", new { @class = "form-control" })

create a new product by typing in the products new name and then select which parent category it belongs to, which then filters the sub category and the user selects the appropriate sub-category.

If you want to get the subcategories when you choose the parent category, you could use some JavaScript, for example:

View:

@model PrototypeWithAuth.ViewModels.CreateProductViewModel

@{
   ViewData["Title"] = "Create";
   Layout = "~/Views/Shared/View.cshtml";
}

<h1>Create</h1>

<h4>Product</h4>
<hr />
<div class="row">
    <div class="col-md-4">
        <form asp-action="Create">
            <div asp-validation-summary="ModelOnly" class="text-danger"></div>
            <div class="form-group">
                <label asp-for="Product.ProductName" class="control-label"></label>
                <input asp-for="Product.ProductName" class="form-control" />
                <span asp-validation-for="Product.ProductName" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Product.ProductSubcategory.ParentCategoryID" class="control-label"></label>
                <!--<input asp-for="ProductSubcategoryID" class="form-control" />-->
                @Html.DropDownListFor(c => c.Product.ProductSubcategory.ParentCategoryID,
                new SelectList(Model.ParentCategories, "ParentCategoryID", "ParentCategoryDescription"),
                "Select Category", new { @class = "form-control",@id="parentlist" })
                <span asp-validation-for="Product.ProductSubcategory.ParentCategoryID" class="text-danger"></span>
            </div>
            <div>
                <select name="Product.ProductSubcategoryID" id="sublist" class="form-control"></select>
            </div>
            <div class="form-group">
                <input type="submit" value="Create" class="btn btn-primary" />
            </div>
        </form>
    </div>
</div>

<div>
    <a asp-action="Index">Back to List</a>
</div>
@section Scripts {
    @{await Html.RenderPartialAsync("_ValidationScriptsPartial");}

    <script type="text/javascript">
        //Insert default item "Select" in dropdownlist on load
        $(document).ready(function () {
            var items = "<option value='0'>Select</option>";
            $("#sublist").html(items);
        });


        $("#parentlist").change(function () {
            var parentCategoryId = $("#parentlist").val();
            var url = "/Products/GetSubCategoryList";

            $.getJSON(url, { ParentCategoryId: parentCategoryId }, function (data) {
                var item = "";
                $("#sublist").empty();
                $.each(data, function (i, subCategory) {
                    item += '<option value="' + subCategory.productSubcategoryID  + '">' + subCategory.productSubcategoryDescription  + '</option>'
                });
                $("#sublist").html(item);
            });
        });
    </script>
}

ProductsController:

[HttpGet]
public JsonResult GetSubCategoryList(int ParentCategoryId)
    {
        var subCategoryList = _context.ProductSubcategories.Where(c => c.ParentCategoryID == ParentCategoryId).ToList();
        return Json(subCategoryList);

    }
Ryan
  • 19,118
  • 10
  • 37
  • 53