ASP .Net MVC Core binds wrong value in model list - asp.net-core

I have a model with the following
- ModelData: List<ModelData>
With ModelData has the following:
- Name (string)
- LanguageId: Guid
And ViewBag has the following:
- Languages: IEnumerable<Microsoft.AspNetCore.Mvc.Rendering.SelectListItem>
And the view has the following:
#for (int i = 0; i < Model.ModelData.Count; i++)
{
<div class="row">
<div class="form-group">
<label asp-for="ModelData[i].LanguageId" class="control-label"></label>
<select asp-for="ModelData[i].LanguageId" asp-items="#ViewBag.Languages" class="form-control">
</select>
<span asp-validation-for="ModelData[i].LanguageId" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="ModelData[i].Name" class="control-label"></label>
<input asp-for="ModelData[i].Name" class="form-control" />
<span asp-validation-for="ModelData[i].Name" class="text-danger"></span>
</div>
#if (i > 0)
{
<div class="col-md-4">
<div class="form-group">
<button name="RemoveDataItem" value="#i.ToString()" class="btn btn-primary">Remove</button>
</div>
</div>
}
</div>
}
<input type="submit" name="AddDataItem" value="Add Item" class="btn btn-primary" />
<input type="submit" value="Create" class="btn btn-primary" />
And the controller as the following:
public async Task<IActionResult> CreateAsync(CreateModel model, string addDataItem, string removeDataItem)
{
if (addDataItem != null)
{
await FillViewBag();
model.ModelData.Add(new ModelData());
return View(model);
}
else if (removeDataItem != null)
{
await FillViewBag();
int itemIndex = int.Parse(removeDataItem);
model.ModelData.RemoveAt(itemIndex);
return View(model);
}
if (!ModelState.IsValid)
{
await FillViewBag();
return View(model);
}
// Save
}
And it works great, however I have a problem as the following:
Let's say i pressed the add button two times so now I have three records on ModelData and I entered a value in all textboxes and selected values in all select list, then I pressed remove next to the second row, so it goes to the controller action, the method removes the data of the correct index, and returns to the view, so Now I should find two rows, first with the data that was entered in the first row, and second with the data that was entered in the third row (because the second row is removed), however, what actually happens is that I end up with the data of the first two rows not the first and the third.
Any help is appreciated considering I did the following:
I validated that the item that is removed is the corect one (the second item), but the value is not bound correctly.
I added this attribute to the textbox value="#Model.ModelData[i].Name", and it worked correctly but I think this is not the correct way to solve this issue, also I did not find a similar attribute for the select tag.
Edit:
I also managed to add static Id for the input fields of each row, but it didn't help
Edit:
The problem is that the index is changed after the second row is removed, so the index of the third row (originally was 2) became 1 after removing the second row, and thus it now has the previous name attribute of second row "ModelData[1].Name" and not "ModelData[2].Name" and I think this is the problem which makes the browser keeps the previous value of the second row

For anyone who is concerned, I found the solution to this issue which is to add the following line before returning the view:
ModelState.Clear();

add index value to each item:
<input type="hidden" name="ModelData.index" value="#i">
update your view code like this:
#for (int i = 0; i < Model.ModelData.Count; i++)
{
<div class="row">
<input type="hidden" name="ModelData.index" value="#i">
<div class="form-group">
<label asp-for="ModelData[i].LanguageId" class="control-label"></label>
<select asp-for="ModelData[i].LanguageId" asp-items="#ViewBag.Languages" class="form-control">
</select>
<span asp-validation-for="ModelData[i].LanguageId" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="ModelData[i].Name" class="control-label"></label>
<input asp-for="ModelData[i].Name" class="form-control" />
<span asp-validation-for="ModelData[i].Name" class="text-danger"></span>
</div>
#if (i > 0)
{
<div class="col-md-4">
<div class="form-group">
<button name="RemoveDataItem" value="#i.ToString()" class="btn btn-primary">Remove</button>
</div>
</div>
}
</div>
}

Where's your source data? What you have done is just change the model parmeter.
Also, I can reproduce your problem, when I directly return View after remove the specify record. It can be fixed by using RedirectToAction
I made a demo based on your codes, you can refer to the below codes:
Controller:
public static CreateModel createModel = new CreateModel
{
ModelDatas = new List<ModelData>
{
new ModelData{ LanguageId = 1, Name = "a"},
new ModelData{ LanguageId = 2, Name = "b"},
new ModelData{ LanguageId = 3, Name = "c"}
}
};
public IActionResult Create()
{
FillViewBag();
return View(createModel);
}
[HttpPost]
public IActionResult Create(CreateModel model, string addDataItem, string removeDataItem)
{
if (addDataItem != null)
{
FillViewBag();
createModel.ModelDatas.Add(new ModelData());
return RedirectToAction("Create");
}
else if (removeDataItem != null)
{
FillViewBag();
int itemIndex = int.Parse(removeDataItem);
createModel.ModelDatas.RemoveAt(itemIndex);
return RedirectToAction("Create");
}
if (!ModelState.IsValid)
{
FillViewBag();
return RedirectToAction("Create");
}
return View();
}
View:
#model CreateModel
<form asp-action="Create" asp-controller="Test" method="post">
#for (int i = 0; i < Model.ModelDatas.Count; i++)
{
<div class="row">
<div class="form-group">
<label asp-for="ModelDatas[i].LanguageId" class="control-label"></label>
<select asp-for="ModelDatas[i].LanguageId" asp-items="#ViewBag.Languages" class="form-control">
</select>
<span asp-validation-for="ModelDatas[i].LanguageId" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="ModelDatas[i].Name" class="control-label"></label>
<input asp-for="ModelDatas[i].Name" class="form-control" />
<span asp-validation-for="ModelDatas[i].Name" class="text-danger"></span>
</div>
#if (i >= 0)
{
<div class="col-md-4">
<div class="form-group">
<button name="RemoveDataItem" value="#i.ToString()" class="btn btn-primary">Remove</button>
</div>
</div>
}
</div>
}
<input type="submit" name="AddDataItem" value="Add Item" class="btn btn-primary" />
<input type="submit" value="Create" class="btn btn-primary" />
</form>
Result:

Related

In my creation process I want to write to another entity than only the used

If I submit the page the first save goes without a problem
Than I assigned all values to the other entity which I want to write to create there a new record.
Why do I get an System Null Reference. I have in all fields the values which I want?
[
=== here is my c# Code ===========
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.EntityFrameworkCore;
using WorkCollaboration.Data;
using WorkCollaboration.Models;
namespace WorkCollaboration.Pages.TimeReports
{
public class CreateModel : PageModel
{
private readonly WorkCollaboration.Data.WorkCollaborationContext _context;
public CreateModel(WorkCollaboration.Data.WorkCollaborationContext context)
{
_context = context;
}
public IActionResult OnGet()
{
CusContactDropDownDisp = _context.CusContactDropDown.ToList(); // Added for DropDown
SupContactDropDownDisp = _context.SupContactDropDown.ToList(); // Added for DropDown
return Page();
}
[BindProperty]
public TimeReport TimeReport { get; set; }
public IEnumerable<Models.CusContactDropDown> CusContactDropDownDisp { get; set; }
public IEnumerable<Models.SupContactDropDown> SupContactDropDownDisp { get; set; }
public Models.PointsforSupContact PointsforSupContact { get; set; }
// To protect from overposting attacks, enable the specific properties you want to bind to, for
// more details, see https://aka.ms/RazorPagesCRUD.
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid)
{
return Page();
}
TimeReport.TimeReportSupContactPointValue = (TimeReport.TimeReportHours * 10);
_context.TimeReport.Add(TimeReport);
await _context.SaveChangesAsync();
//============================================
// Adding new Point Record to Supplier Contact
//============================================
PointsforSupContact.PointsId = TimeReport.TimeReportId;
PointsforSupContact.PointsSupContactId = TimeReport.TimeReportSupplierTalentContactId;
PointsforSupContact.PointsInternalUserId = 1; // User 1 Christof Oberholzer must be entered in Contacts and User
PointsforSupContact.PointsDate = TimeReport.TimeReportDate;
PointsforSupContact.PointsTotal = TimeReport.TimeReportSupContactPointValue;
PointsforSupContact.PointsText = TimeReport.TimeReportText;
PointsforSupContact.PointsTitle = "TimeReporting Points";
_context.PointsforSupContact.Add(PointsforSupContact);
await _context.SaveChangesAsync();
return RedirectToPage("/TimeReportOverview/Index");
}
}
}
==== My Page ======
#page
#using Microsoft.AspNetCore.Localization
#using Microsoft.AspNetCore.Mvc.Localization
#model WorkCollaboration.Pages.TimeReports.CreateModel
#{
ViewData["Title"] = "Create";
ViewData["RandomId"] = Guid.NewGuid().GetHashCode();
}
#inject IViewLocalizer Localizer
<h1>Create</h1>
<h4>TimeReport</h4>
<p>
<a asp-page="/TimeReportOverview/Index">Back to Index</a>
</p>
<hr />
<div class="row">
<div class="col-md-4">
<form method="post">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<input type="hidden" asp-for="TimeReport.TimeReportSupContactPointValue" value="0"/>
<div class="form-group">
<label asp-for="TimeReport.TimeReportId" class="control-label"></label>
<input asp-for="TimeReport.TimeReportId" value='#ViewData["RandomId"]' readonly="readonly" class="form-control" />
<span asp-validation-for="TimeReport.TimeReportId" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="TimeReport.TimeReportCustomerNeedContactId" class="control-label"></label>
</div>
<select id="CusContactId" asp-for="CusContactDropDownDisp" asp-items="#(new SelectList(Model.CusContactDropDownDisp,"CusContactId","CusFullName"))">
<option value="" selected disabled>--Choose Customer--</option>
</select>
<div class="form-group">
<input asp-for="TimeReport.TimeReportCustomerNeedContactId" readonly="readonly" class="form-control" />
<span asp-validation-for="TimeReport.TimeReportCustomerNeedContactId" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="TimeReport.TimeReportSupplierTalentContactId" class="control-label"></label>
</div>
<select id="SupContactId" asp-for="SupContactDropDownDisp" asp-items="#(new SelectList(Model.SupContactDropDownDisp,"SupContactId","SupFullName"))">
<option value="" selected disabled>--Choose Supplier--</option>
</select>
<div class="form-group">
<input asp-for="TimeReport.TimeReportSupplierTalentContactId" readonly="readonly" class="form-control" />
<span asp-validation-for="TimeReport.TimeReportSupplierTalentContactId" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="TimeReport.TimeReportDate" class="control-label"></label>
<input type="datetime-local" asp-for="TimeReport.TimeReportDate" class="form-control" />
<span asp-validation-for="TimeReport.TimeReportDate" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="TimeReport.TimeReportHours" class="control-label"></label>
<input asp-for="TimeReport.TimeReportHours" class="form-control" />
<span asp-validation-for="TimeReport.TimeReportHours" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="TimeReport.TimeReportText" class="control-label"></label>
<input asp-for="TimeReport.TimeReportText" class="form-control" />
<span asp-validation-for="TimeReport.TimeReportText" class="text-danger"></span>
</div>
<div class="form-group">
#Html.LabelFor(model => model.TimeReport.TimeReportState, htmlAttributes: new { #class = "form-group" })
<div class="form-group">
#Html.DropDownListFor(model => model.TimeReport.TimeReportState, new List<SelectListItem>
{
new SelectListItem {Text = "Gebucht", Value = "Reported", Selected = true },
new SelectListItem {Text = "Kontrolliert", Value = "Controlled" },
new SelectListItem {Text = "Verrechnet / Noch nicht bezahlt", Value = "Invoiced / Not yet payed" },
new SelectListItem {Text = "Verrechnet / Bezahlt", Value = "Invoiced / Payed" },
}, new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.TimeReport.TimeReportState, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
<input type="submit" value="Create" class="btn btn-primary" />
Back to List
</div>
</form>
</div>
</div>
<script src="~/lib/jquery/dist/jquery.min.js"></script>
<script src="~/lib/jquery-validation/dist/jquery.validate.min.js"></script>
<script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js"></script>
<script>
$("#CusContactId").on("change", function () {
$("#TimeReport_TimeReportCustomerNeedContactId").val($("#CusContactId").val());
});
$("#SupContactId").on("change", function () {
$("#TimeReport_TimeReportSupplierTalentContactId").val($("#SupContactId").val());
});
</script>
#section Scripts {
#{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}
Thank you for your help
First In your view:
ViewData["RandomId"] = Guid.NewGuid().GetHashCode();
GetHashCodemethod make the Guid to int,please make sure it matches the type in your model.
Then the right way to add new PointsforSupContact is to create a new PointsforSupContact,so you need to delete the code:
public Models.PointsforSupContact PointsforSupContact { get; set; }
and change you code to:
//...
await _context.SaveChangesAsync();
//add this line
var PointsforSupContact=new PointsforSupContact();
PointsforSupContact.PointsId = TimeReport.TimeReportId;
//...

Insert ForEach Value in View into the Database MVC

Hi currently I am doing a shopping cart for my project
I would like to ask how can I import the values in a ForEach to the database.
For example, I have the following data in my view.
#foreach (Cart_Has_Services c in Model)
{
<div class="cart-row">
<div class="cart-items">#c.Cart_Service</div>
<div class="cart-items">#c.Additional_Notes</div>
<div class="cart-items">#c.Unit_Price</div>
<div class="cart-items">
<form asp-controller="Cart" asp-action="UpdateCart" formaction="post">
<input type="number" class="item-quantity-input" value="#c.Quantity" />
<input type="submit" class="btn btn-secondary" value="Update" />
</form>
</div>
<div class="cart-items">
<a asp-controller="Cart"
asp-action="DeleteItem"
asp-route-id="#c.Cart_Id"
onclick="return confirm('Delete Serivce #c.Cart_Service')">
Delete
</a>
</div>
</div>
}
As for now, I want to INSERT data (Cart Service, Additional Notes and Quantity) into my database (Order).
In my controller:
public IActionResult Checkout(Cart_Has_Services cart)
{
List<Cart_Has_Services> carts = DBUtl.GetList<Cart_Has_Services>("SELECT * FROM Cart");
string sql = #"INSERT INTO [Order](Order_Name,Order_Description,Order_Quantity)
VALUES('{0}','{1}',{2})";
int ord = DBUtl.ExecSQL(sql, cart.Cart_Service, cart.Additional_Notes, cart.Quantity);
if (ord == 1)
{
TempData["Message"] = "Perofrmance Successfully Created";
TempData["MsgType"] = "success";
return RedirectToAction("Success");
}
else
{
ViewData["Message"] = DBUtl.DB_Message;
ViewData["MsgType"] = "danger";
return View("ShoppingCart");
}
}
I tried the method that I have inserted but it created without inserting the data.
How can I solve this problem?
Hope can get some guidance.
Thank you
The form in the view only submit Quantity, without Cart_Service and Additional_Notes.
To submit their value, you may set hidden inputs in the form. Also you should set name attribute for the input for model binding.
#foreach (Cart_Has_Services c in Model)
{
<div class="cart-row">
<div class="cart-items">#c.Cart_Service</div>
<div class="cart-items">#c.Additional_Notes</div>
<div class="cart-items">#c.Unit_Price</div>
<div class="cart-items">
<form asp-controller="Cart" asp-action="UpdateCart" formaction="post">
<input type="hidden" name="Cart_Service" value="#c.Cart_Service" />
<input type="hidden" name="Additional_Notes" value="#c.Additional_Notes" />
<input type="number" name="Quantity" class="item-quantity-input" value="#c.Quantity" />
<input type="submit" class="btn btn-secondary" value="Update" />
</form>
</div>
<div class="cart-items">
<a asp-controller="Cart"
asp-action="DeleteItem"
asp-route-id="#c.Cart_Id"
onclick="return confirm('Delete Serivce #c.Cart_Service')">
Delete
</a>
</div>
</div>
}

Checkbox list values empty when ModelState is not valid and ASP.NET Core does return Page()

I created an ASP.NET Core 3.1 project. I have a form in it with several checkbox lists. I can get the values into the properties in POST and they are correctly filled in (e.g. List SelectedItems). However for some custom fields I have to do a validation in OnPost() method and if the conditions are not met or a ModelState is not valid, it return Page(). Normally I would expect that every property that was filled in in the form is still filled in, but the checkboxes are always empty and not a single one is checked. The other data (radio buttons, textboxes, etc.) are still filled in.
I even tried to put the values within the Razor page, but even then neither of the checkboxes was checked.
Here is an example of one of the checkboxes:
In Razor page:
#for (var i = 1; i <= 10; i++){
<input name="AreChecked" type="checkbox" id="#i" value="#i" /> #i<br />
<input type="hidden" value="true" id="#i" name="AreChecked" />}
Behind code:
[BindProperties]
public class TestFormModel : PageModel
{
[BindProperty]
public List<int> AreChecked { get; set; }}
public IActionResult OnPost()
{
//some other form check statements here
//...
if (ModelState.IsValid)
{
//process data code...
}
return Page();
}
Can someone help me with this?
You could use JQuery to achieve as shown :
#page
#model RazorPages3_1.AddimgModelModel
<div class="row">
<div class="col-md-4">
<form enctype="multipart/form-data" method="post">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="Petimg.PetName" class="control-label"></label>
<input asp-for="Petimg.PetName" class="form-control" />
<span asp-validation-for="Petimg.PetName" class="text-danger"></span>
</div>
<div class="form-group">
<input asp-for="Uploads" class="form-control" />
</div>
#for (var i = 1; i <= 10; i++)
{
<input name="AreChecked" type="checkbox" id="#i" value="#i" /> #i<br />
<input type="hidden" value="true" id="#i" name=""AreChecked" />
}
<div class="form-group">
<input type="submit" value="Create" class="btn btn-primary" />
</div>
</form>
</div>
</div>
<div>
<a asp-page="Index">Back to List</a>
</div>
#section Scripts {
<script>
var checkedlist = #Html.Raw(Json.Serialize(Model.AreChecked));;
if (checkedlist.length > 0) {
$.each(checkedlist, function (index, value) {
$('input[type=checkbox]').each(function () {
var id = $(this).attr("id");
if (id == value) {
$(this).attr('checked', 'checked');
}
})
});
}
</script>
}
Result

Aurelia custom element access data from child to parent view model

I am new to Aurelia and need help accessing values from custom element in another view.
I have a scenario where I would like to share input file for attachment in multiple forms. One of the approach I am taking is to create custom element for input files that can be shared with multiple forms in web application.
Below is my code:
Custom element called FilePicker which will be shared between multiple forms. In my Request paged I inserted the custom element called:
<file-picker router.bind="router" accept="image/*" multiple="true" ></file-picker>
My requirement is to access property called validFiles which is an array. I would like to use values from validFiles to custom object in Request view model called formData['attachment']
TS:
import { customElement, useView, bindable, bindingMode } from 'aurelia-framework';
#customElement('file-picker')
#useView('./file-picker.html')
export class FilePicker {
#bindable accept = '';
#bindable multiple = false;
#bindable({ defaultBindingMode: bindingMode.twoWay }) files: FileList;
validFiles = [];
input: HTMLInputElement;
// Validate function for on change event to input file.
public filesChanged() {
if (!this.files) {
this.clearSelection();
}
if (this.files) {
this.processFiles();
}
}
// Trigger on button click
public triggerInputClick() {
this.input.click();
}
// Find value in array of object
private findValinArrObj(arr, key, val) {
return arr.map(function (v) { return v[key] }).indexOf(val);
}
// Process file for each new files uploaded via browser
private processFiles() {
if (this.files) {
for (let i = 0; i < this.files.length; i++) {
let findFile = this.findValinArrObj(this.validFiles, 'name', this.files.item(i).name);
if (findFile === -1) {
this.validFiles.push(this.files.item(i));
}
}
this.clearSelection();
}
}
// Remove file from fileNames and validFiles array
public removeByFileName(fileName) {
if (this.validFiles) {
for (let i = 0; i < this.validFiles.length; i++) {
if (this.validFiles[i].name === fileName) {
this.validFiles.splice(i, 1);
}
}
}
}
// Clear input file in DOM
private clearSelection() {
this.input.type = '';
this.input.type = 'file';
}
}
HTML:
<template>
<input type="file" accept.bind="accept" multiple.bind="multiple"
files.bind="files" ref="input"
style="visibility: hidden; width: 0; height: 0;">
<button class="btn btn-primary" click.delegate="triggerInputClick()">
<slot>Browse...</slot>
</button>
<ul class="list-group" if.bind="validFiles">
<li class="list-group-item" repeat.for="file of validFiles" style="padding: 0; border:none;">
${file.name} <span class="glyphicon glyphicon-remove text-danger" style="cursor: pointer;" click.delegate="removeByFileName(file.name)"></span>
</li>
</ul>
</template>
Parent View Model:
TS:
export class Request {
pageTitle: string = "Request Page";
title: string = '';
description: string = '';
businessValue: string = '';
emails: string = '';
formData: object = {};
public postData() {
this.formData['title'] = this.title;
this.formData['description'] = this.description;
this.formData['businessValue'] = this.businessValue;
this.formData['emails'] = this.emails;
this.formData['attachment'] = [];
console.log(this.formData);
}
}
HTML:
<template>
<require from="./request.css"></require>
<require from="../../resources/elements/file-picker"></require>
<div class="panel panel-primary">
<div class="panel-heading">${pageTitle}</div>
<div class="panel-body">
<form class="form-horizontal">
<div class="form-group">
<label for="title" class="col-sm-2 control-label">Title</label>
<div class="col-sm-10">
<input type="text" value.two-way="title" class="form-control" id="title" placeholder="Brief one-line summary of the request">
</div>
</div>
<div class="form-group">
<label for="description" class="col-sm-2 control-label">Description</label>
<div class="col-sm-10">
<input type="text" value.two-way="description" class="form-control" id="description" placeholder="Detailed description of the request">
</div>
</div>
<div class="form-group">
<label for="description" class="col-sm-2 control-label">Business Value</label>
<div class="col-sm-10">
<input type="text" value.two-way="businessValue" class="form-control" id="description" placeholder="Description of how this offers business value">
</div>
</div>
<div class="form-group">
<label for="emails" class="col-sm-2 control-label">Email</label>
<div class="col-sm-10">
<input type="text" value.two-way="emails" class="form-control" id="emails" placeholder="Provide email address">
</div>
</div>
<div class="form-group">
<label for="exampleInputFile" class="col-sm-2 control-label">Attachment(s)</label>
<div class="col-sm-10">
<file-picker router.bind="router" accept="image/*" multiple="true" ></file-picker>
</div>
</div>
<div class="form-group">
<div class="col-sm-12">
<button type="submit" class="btn btn-default pull-right" click.trigger="postData()">Submit</button>
</div>
</div>
</form>
</div>
</div>
</template>
Any help is really appreciated. :)
You can set up two-way-binding on your Request view, to bind the validFiles in your child view (file-picker) to a variable in your parent view (Request):
request.html:
<file-picker valid-files.two-way="validFiles"></file-picker>
request.ts:
public validFiles: File[];
file-picker.ts:
#bindable
public validFiles: File[];
This way any changes made to the validFiles object in either the child or the parent will update the object in both viewmodels.

Why is the Bind attribute seemingly breaking my model binding of nested objects?

Could someone help me resolve this issue. I'm trying to limit over posting with bind param action but it seems that it doesn't work at all. When I removed the Bind keyword, everything started to work as a charm.
Here is the code sample:
View Model:
public class ProductCreateViewModel
{
public Product Product { get; set; }
public ICollection<IFormFile> Images { get; set; }
}
Action:
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create([Bind("Product.Id,Product.CategoryId,Product.Description,Product.Title")] ProductCreateViewModel productVM)
{
if (ModelState.IsValid)
{
_context.Add(productVM.Product);
await _context.SaveChangesAsync();
return RedirectToAction("Index");
}
ViewData["CategoryId"] = new SelectList(_context.Categories.Include(c => c.Categories).Where(c => c.ParentCategoryId == null), "Id", "Name", productVM.Product.CategoryId);
return View(productVM);
}
View:
#model CatalogWebApp.Models.ProductsViewModels.ProductCreateViewModel
#{
ViewData["Title"] = "Add Product";
ViewData["BigPageTitle"] = "Products";
ViewData["PageBoxTitle"] = "Add New Product";
}
<form asp-action="Create">
<div class="form-horizontal">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="Product.CategoryId" class="col-md-2 control-label"></label>
<div class="col-md-10">
<select name="Product.CategoryId" class ="form-control">
#foreach(Category item in (ViewBag.CategoryId as SelectList).Items)
{
<option value="#item.Id">#item.Name</option>
if (item.Categories != null && item.Categories.Count > 0)
{
foreach (var subCat in item.Categories)
{
<option value="#subCat.Id">--#subCat.Name</option>
}
}
}
</select>
</div>
</div>
<div class="form-group">
<label asp-for="Product.Description" class="col-md-2 control-label"></label>
<div class="col-md-10">
<input asp-for="Product.Description" class="form-control" />
<span asp-validation-for="Product.Description" class="text-danger" />
</div>
</div>
<div class="form-group">
<label asp-for="Product.Title" class="col-md-2 control-label"></label>
<div class="col-md-10">
<input asp-for="Product.Title" class="form-control" />
<span asp-validation-for="Product.Title" 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>
</form>
<div>
<a asp-action="Index">Back to List</a>
</div>
#section Scripts {
#{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}
Could someone pelase indicate if I have a problem or it is only a known asp.net core issue?
I'm not quite sure why you using Bind for your case.
Just create sepatate ViewModel with only properties you need like ProductCreateStort.
Then use this ViewModel in your controller signature and inherit your main model from it.
This way you won't mess with Bind and limit your params on POST
While I'm fairly new to ASP.NET Core myself (and coming to this question 7 months late), I ran into this same issue. I think the key here is that you have to bind "Product" for it to be considered. Binding "Product.Id" by itself doesn't appear to be good enough. So this should work:
[Bind("Product,Product.Id,Product.CategoryId,Product.Description,Product.Title")]
Of course, Hamid Mosalla's comment is a better option if ALL of your bound properties are on the nested object (which leads to wonder why you need a view model in the first place). In my case, I have a nested object AND a local property, so using the "Prefix" solution wasn't the right thing to do.
Anyway, hope this helps someone.
You need to pass your values as params string[], not as a single string separated by commas:
[Bind("Product.Id","Product.CategoryId","Product.Description","Product.Title")]
See Source