.NET Core MVC Form returning wrong model value - asp.net-mvc

I'm having a weird issue with a form in my View not returning the model's correct Id property value. You'll noticed in the code below I have the script section logging the model's Id. Doing this shows the correct Id on the console, but when the Id is passed to the Controller's action method it is always 0, which is incorrect.
Here's the view:
#model EotE_Encounter.Models.Encounter
<div>
<h4>#Model.Name</h4>
<div>
<form asp-action="CreateCharacter" asp-controller="Encounter" data-bind="" data-ajax="true" data-ajax-mode="replace" data-ajax-update="#character-container">
<input id="encounterId" type="hidden" value="#Model.Id" />
<button class="btn btn-default" type="submit">Create Character</button>
</form>
</div>
<hr />
<div>
<ul>
#{
if(Model.CharactersInEncounter != null)
{
foreach(Character character in Model.CharactersInEncounter)
{
<li>#character.Name</li>
}
}
}
</ul>
</div>
</div>
<script>
console.log(#Model.Id);
</script>
Related Action Method:
public ActionResult CreateCharacter(int encounterID)
{
return RedirectToAction("CreateCharacter", "Character", encounterID);
}
And the Encounter model:
public class Encounter
{
//these first three properties may not be used just yet.
public int Id { get; set; }
public string Name { get; set; }
public byte Round { get; set; }
public List<Character> CharactersInEncounter { get; set; }
[StringLength(2000)]
public string Notes { get; set; }
}

Only form elements with a name attribute will have their values passed when submitting a form. So, add the name attribute to your hidden element. Id and the name are not the same.
#model EotE_Encounter.Models.Encounter
<div>
<h4>#Model.Name</h4>
<div>
<form asp-action="CreateCharacter" asp-controller="Encounter" data-bind="" data-ajax="true" data-ajax-mode="replace" data-ajax-update="#character-container">
<input id="encounterId" name="encounterID" type="hidden" value="#Model.Id" />
<button class="btn btn-default" type="submit">Create Character</button>
</form>
</div>
<hr />
<div>
<ul>
#{
if(Model.CharactersInEncounter != null)
{
foreach(Character character in Modelsel.CharactersInEncounter)
{
<li>#character.Name</li>
}
}
}
</ul>
</div>
</div>
<script>
console.log(#Model.Id);
</script>
Notice the name attribute of the <input id="encounterId" name="encounterID" type="hidden" value="#Model.Id" /> element. It has to be the same name as the action parameter (int encounterID). If it's not the same then the parameter binding will not work.

Related

How to create object that contains a list of object in a single form?

public class Basket
{
public int Id { get; set; }
public string Sharp { get; set; }
public string Material { get; set; }
public List<Fruit> Fruits { get; set; }
}
public class Fruit
{
public int Id { get; set; }
public string Color { get; set; }
public string Taste { get; set; }
}
With the above example, how could I create both Basket and Fruit in the same asp-form without using any JavaScript?
<form method="post" asp-controller="Basket" asp-action="Create">
<input asp-for="Material" />
<input asp-for="Sharp" />
#*I would like to also create custom amounts of new Fruit in this form.*#
<input type="submit" value="Submit" />
</form>
If my razor form is defined as the above example, how could I create custom amounts of Fruit and create Basket at the same form? It is possible to avoid using JavaScript in this case?
It is possible to avoid using JavaScript in this case?
Based on your scenario and current architecture what you need to do is, there should be a table where you would be adding your fruit object as it's a List<Fruit> Fruit kind of. As per your given code, your output should be as below:
So, I would say, Javascript would make it easier. If you would like to avoid javascript it wouldn't be impossible but would be costly and complex.
how could I create custom amounts of Fruit and create Basket at the
same form?
You could follow the below steps to achieve what you are trying to implement.
View:
#model DotNet6MVCWebApp.Models.Basket
<form method="post" asp-controller="Yonny" asp-action="Create">
<div class="form-group">
<label asp-for="Material" class="col-md-2 form-label"></label>
<input asp-for="Material" class="col-md-6 form-control" />
<span asp-validation-for="Material" class="form-span-error"></span>
</div>
<div class="form-group" style="padding-bottom:20px">
<label asp-for="Sharp" class="col-md-2 form-label"></label>
<input asp-for="Sharp" class="col-md-6 form-control" />
<span asp-validation-for="Sharp" class="form-span-error"></span>
</div>
#*I would like to also create custom amounts of new Fruit in this form.*#
<div style="padding-bottom:20px">
<button type="button" class="btn btn-primary" onclick="AddRow()">Add Fruit</button>
</div>
<div id="dataTable">
<table>
<thead>
<tr>
<th>Id</th>
<th>Color</th>
<th>Taste</th>
</tr>
</thead>
<tbody id="FruitList" data-count="0">
</tbody>
</table>
</div>
<input type="submit" class="btn btn-success" value="Submit" />
</form>
#section Scripts {
<script>
/*
. Hidding table on load
*/
document.getElementById('dataTable').style.display ='none';
function AddRow()
{
var countVal = parseInt($('#FruitList').attr('data-count'));
var html = '';
html += '<tr>';
html += '<td><input type="text" name="Fruits[' + countVal + '].Id" class="form-control"/></td>';
html += '<td><input type="text" name="Fruits[' + countVal + '].Color" class="form-control"/></td>';
html += '<td><input type="text" name="Fruits[' + countVal + '].Taste" class="form-control"/></td>';
html += '</tr>';
$('#FruitList').append(html);
countVal += 1;
$('#FruitList').attr('data-count', countVal);
/*
. Showing table when adding item into
*/
document.getElementById('dataTable').style.display ='block';
}
</script>
}
Controller:
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create(
[Bind("Id,Material,Sharp,Fruits")] DotNet6MVCWebApp.Models.Basket basket)
{
if (ModelState.IsValid)
{
//Save Basket
_context.Add(basket);
await _context.SaveChangesAsync();
//Add Fruits List
foreach (var item in basket.Fruits)
{
_context.Add(item);
await _context.SaveChangesAsync();
}
return RedirectToAction(nameof(Create));
}
return View(basket);
}
Note:
If you somehow got null data while sending request to controller make sure your binding property that is Bind("Id,Material,Sharp,Fruits") are same as name="Fruits[' + countVal + '].Id" inside the javascript function
Output:

Asp.net core form always returns null

I'm trying to build one view that includes all (Create, Edit, Delete, and Index) in one View which is Index.
The problem is with Editing. Always returns null to the controller as shown in the gif.
I have Model and ViewModel as follows.
The Model BootstrapCategory
public class BootstrapCategory
{
[Key]
public Guid Id { get; set; }
[MaxLength(20)]
[Required]
public string Category { get; set; }
}
The ViewModel VMBPCategoris
public class VMBPCategoris
{
public List<BootstrapCategory> bootstrapCategories { get; set; }
public BootstrapCategory bootstrapCategory { get; set; }
}
The View
Note: Edit not by the usual button in the table it instead by another
button as shown in the gif
#model VMBPCategoris
#foreach (var item in Model.bootstrapCategories)
{
<tr>
<td>
<form asp-action="Edit" method="post">
<input type="hidden" asp-for="#item.Id" />
<div class="#item.Id d-none">
<div class="input-group">
<input id="btnGroupEdit" type="submit" value="Save" class="input-group-text btn btn-primary" />
<input asp-for="#item.Category" class="form-control" aria-label="Input group example" aria-describedby="btnGroupEdit">
</div>
<span asp-validation-for="#item.Category" class="text-danger"></span>
</div>
</form>
<div class="#item.Id">
#Html.DisplayFor(modelItem => item.Category)
</div>
</td>
<td>
Edit |
<a asp-action="Details" asp-route-id="#item.Id">Details</a> |
<a asp-action="Delete" asp-route-id="#item.Id">Delete</a>
</td>
</tr>
}
The Controller
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit([Bind("Id,Category")] BootstrapCategory bootstrapCategory)
{
_context.Update(bootstrapCategory);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
//return View(vMBPCategoris);
}
The view model class VMBPCategoris needs to have its members properties assigned to instances in a constructor:
public class VMBPCategoris
{
public List<BootstrapCategory> bootstrapCategories { get; set; }
public BootstrapCategory bootstrapCategory { get; set; }
public VMBPCategoris()
{
bootstrapCategories = new List<BootstrapCategory>();
bootstrapCategory = new BootstrapCategory();
}
}
You can give a name to your input tag.Change your form like below.
<form asp-action="Edit" method="post">
<input type="hidden" asp-for="#item.Id" name="Id"/>
<div class="#item.Id d-none">
<div class="input-group">
<input id="btnGroupEdit" type="submit" value="Save" class="input-group-text btn btn-primary" />
<input asp-for="#item.Category" name="Category" class="form-control" aria-label="Input group example" aria-describedby="btnGroupEdit">
</div>
<span asp-validation-for="#item.Category" class="text-danger"></span>
</div>
</form>
I made an easy solution by changing a little bit with the ViewModel and using the value attribute to get value from a model and using asp-for to set a value to another model.
It's all clarified in that Stackoverflow post

How Can I Get ViewData in PartialView in Razor Pages

I'm Using Razor Pages For my App, in one part of my app I've used a partial view here is my codes;
public class Permission
{
[Key]
public int PermissionId { get; set; }
public string PermissionTitle { get; set; }
public int? ParentID { get; set; }
}
public class IndexModel : PageModel
{
public PartialViewResult OnGetCreateRole()
{
var ListPermission = permissionService.AllPermission();
return new PartialViewResult()
{
ViewName = "_PCreateRole", // partial's name
ViewData = new ViewDataDictionary<List<Permission>>(ViewData,
ListPermission)
};
}
}
ViewData is a List of Permission class and i've sent ViewData to partial but i dont know how to get ViewData, also my partial use another model, below is my partial:
#model ToMVC.DataLayer.Entities.User.Role
<div class="row">
<div class="col-md-12">
<form asp-page="CreateRole" method="post">
<div class="form-group">
<label class="control-label">Title</label>
<input asp-for="RoleTitle" class="form-control"/>
<p><span class="text-danger" asp-validation-for="RoleTitle"></span></p>
</div>
<div class="form-group">
<input type="submit" value="submit" class="btn btn-primary" />
</div>
//this part needs ViewData
#foreach (var item in ViewData)
{
}
</form>
</div>
</div>
I want to use ViewData in Foreach loop.
A better solution than ViewData would be to simply make a new ViewModel class containing all the information you need for a view.
public class UserRoleAndPermissions{
public UserRoleAndPermissions(){
Permissions = new List<Permissions>();
}
public List<Permission> Permissions {get;set;}
public ToMVC.DataLayer.Entities.User.Role Role {get;set;}
}
And your view
//check your namespace here - this is just an example
#model ToMVC.DataLayer.UserRoleAndPermissions
<div class="row">
<div class="col-md-12">
<form asp-page="CreateRole" method="post">
<div class="form-group">
<label class="control-label">Title</label>
<input asp-for="RoleTitle" class="form-control"/>
<p><span class="text-danger" asp-validation-for="RoleTitle"></span></p>
</div>
<div class="form-group">
<input type="submit" value="submit" class="btn btn-primary" />
</div>
#foreach (var item in Model.Permissions)
{
}
</form>
</div>
</div>

MVC Url.Action causes NullReferenceException in post

I'm trying to have a button on my form page that takes me back to the previous view that requires a parameter, I'm passing a ViewModel into the view with the assigned value I'm using. If the button is un-commented the button works fine but the forms post sends a NullReferenceException, and if the button is commented the form works exactly as I want.
The button that breaks the form
<button type="button" onclick="location.href='#Url.Action("Assignments","Session", new { Model.CourseName })'">Go Back</button>
The Controller Code
public IActionResult CreateAssignment(string courseName)
{
CreateAssignmentModel assignmentModel = new CreateAssignmentModel();
assignmentModel.CourseName = courseName;
return View(assignmentModel);
}
[HttpPost]
public IActionResult CreateAssignment(CreateAssignmentModel assignment)
{
if (ModelState.IsValid)
{
ModelState.Clear();
return View(assignment.CourseName);
}
else
{
return View(assignment.CourseName);
}
}
public IActionResult Assignments(string courseName)
{
var assignments = storedProcedure.getAssignments(User.Identity.Name, courseName);
var AssignmentsView = new AssignmentsViewModel{CourseName = courseName};
foreach (var Assignment in assignments.ToList())
{
AssignmentsView.Assignments.Add(Assignment);
}
return View(AssignmentsView);
}
The Model Code
public class CreateAssignmentModel
{
public string UserName { get; set; }
public string CourseName { get; set; }
[Required]
public string AssignmentName { get; set; }
[Required]
public string AssignmentDescription { get; set; }
[Required]
public int TotalPoints { get; set; }
[Required]
public DateTime DueDate { get; set; }
}
The Form with Button
<button type="button" onclick="location.href='#Url.Action("Assignments","Session", new { Model.CourseName })'">Go Back</button>
<div class="row">
<div class="col-md-4">
<form asp-route-returnUrl="#ViewData["ReturnUrl"]" method="post">
<h4>Create an Assignment</h4>
<hr />
<div class="form-group">
<label asp-for="AssignmentName" class="control-label">Assignment Name</label>
<input asp-for="AssignmentName" class="form-control" placeholder="Assignment Name" />
<span asp-validation-for="AssignmentName" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="AssignmentDescription" class=" control-label">Assignment Description</label>
<input asp-for="AssignmentDescription" class="form-control" placeholder="Assignment Description" />
<span asp-validation-for="AssignmentDescription" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="TotalPoints" class=" control-label">Total Points</label>
<input asp-for="TotalPoints" class="form-control" placeholder="Points" />
<span asp-validation-for="TotalPoints" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="DueDate" class=" control-label">Due Date</label>
<input asp-for="DueDate" class="form-control" placeholder="Due Date" />
<span asp-validation-for="DueDate" class="text-danger"></span>
</div>
<br />
<button type="submit" class="btn btn-primary">Create</button>
</form>
</div>
</div>
Sorry for the lack of brevity, I've looked at NullReferenceException solutions for my problem but none have worked.

Stack trace error

I am having the error message:I can`t figure out what is wrong. Can someone help me please. Thanks.
Stack Trace:
[NotSupportedException: Collection is
read-only.]
System.SZArrayHelper.Clear() +56
System.Web.Mvc.CollectionHelpers.ReplaceCollectionImpl(ICollection`1
collection, IEnumerable newContents)
+125
ExecuteStep(IExecutionStep step,
Boolean& completedSynchronously) +184
My code is:
Model:
namespace TestArrays.Models
{
public class Person
{
public CategoriesRow[] Categories { get; set; }
public Person()
{
Categories = new CategoriesRow[1];
Categories[0] = new CategoriesRow();
Categories[0].Name = "Bug";
Categories[0].ID = "0";
}
}
public class CategoriesRow
{
public String Name { get; set; }
public String ID { get; set; }
}
}
Controller
public ActionResult Create()
{
return View(new Person());
}
//
// POST: /Person/Create
[HttpPost]
public ActionResult Create(Person person)
{
try
{
// TODO: Add insert logic here
return RedirectToAction("Index");
}
catch
{
return View();
}
}
Views
<form id="Form" action="<%=Url.Action("Create",new{Action="Create"}) %>" method="post" enctype="multipart/form-data">
<div id="categoriessection" >
<h3>Categories</h3>
<ul class= "list" id="categoryList">
<%if (Model.Categories!=null) {%>
<%int count = 0; %>
<%foreach (var category in Model.Categories)
{%>
<%if (!String.IsNullOrEmpty(category.Name))
{%>
<li><input type="hidden" name="Categories.Index" value="<%=count%>" />
<input type="text" value="<%=category.Name%>" name="Categories[<%=count%>].Name" style="width:280px"/>
<input type="hidden" value="<%=category.ID%>" name="Categories[<%=count++%>].ID" style="width:280px"/>
<input type="button" value = "Delete"/>
</li>
<%}
%>
<%} %>
<%} %>
<li> <input type="hidden" name="Categories.Index" value="value0" />
<input type="text" value="" name="Categories[value0].Name" style="width:280px"/>
<input type="hidden" value="" name="Categories[value0].ID" style="width:280px"/>
<input type="button" value= "Add" />
</li>
</ul>
</div>
<div>
<input type ="submit" value="Save" id="save" />
</div>
</form>
I usually use lists instead of vectors in models:
public List<CategoriesRow> Categories { get; set; }

Resources