I have been trying to call a POST action from my view. I see POST method parameter is always null. What I am doing wrong ?
My model class :
public class Survey
{
public int Id { get; set; }
public int QuestionId { get; set; }
public string QuestionText { get; set; }
public int ResponseId { get; set; }
public string ResponseText { get; set; }
public string Comments { get; set; }
}
My controller method:
[HttpPost]
public ActionResult Publish(List<Survey> surveyToSave)
{
if (ModelState.IsValid)
{
return View("Index");
}
//Save logc
return View("Index");
}
View code:
#model List<Survey>
#using (Html.BeginForm("Publish","Home",FormMethod.Post))
{
<div class="center-div">
#foreach (var item in Model)
{
<div id=#item.QuestionId class="Question">
<table>
<tr>
<td>#item.QuestionText</td>
</tr>
<tr>
<td>
<div>
<label>
#Html.RadioButtonFor(m => m.Find(i => i.QuestionId == item.QuestionId).ResponseText, "Not sure")
Not sure
</label>
</div>
<div>
<label>
#Html.RadioButtonFor(m => m.Find(i => i.QuestionId == item.QuestionId).ResponseText, "Agree")
Agree
</label>
</div>
</td>
</tr>
</table>
</div>
}
}
</div>
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Publish" class="btn btn-default" />
</div>
}
Most properties in model is supposed to be populated from view. Idea was to make the model in UI and submit that in post to save.
Your current view code is generating the input element like this, for all the items in the collection.
<input id="ResponseText" name="ResponseText" type="radio" value="Not sure" />
Model binding will work when the property structure/names matches with the form data posted to your method matches. The default model binder will look into the name attribute value of each item in the posted form data and tries to find a matching property in the class object you used as the method parameter and map the values. Since your HttpPost action methods parameter is List<Survey>, for model binding to work, your input element names should be some thing like this
<input name="[n].ResponseText" type="radio" value="Not sure" />
Where n is the index, starting from 0 to Model.Count-1
You can convert your view code to use a for loop and create input element with above name attribute value
#model List<Survey>
#using (Html.BeginForm("Publish", "Home", FormMethod.Post))
{
<div class="center-div">
#for (var i = 0; i < Model.Count; i++)
{
<div>
<label>#Model[i].QuestionText</label>
<label>#Html.RadioButtonFor(m => Model[i].ResponseText, "Not sure")No</label>
<label>#Html.RadioButtonFor(m => Model[i].ResponseText, "Agree")Agree </label>
</div>
#Html.HiddenFor(a => Model[i].QuestionId)
}
</div>
<input type="submit" value="Publish" class="btn btn-default" />
}
Related
I'm trying to create a form in asp.net core 3 MVC.
the model is a "package" class that contains different properties about the package, and a List<item> items property of the items in the package , item is a class defined that contains different properties of an "item".
how should I approach creating a form for the user to add a new "package" ?
plus inserting the items inside it, which could be any amount of items , I would like the user to insert a row for each item (with a button that adds new input row for a new item ) and submit it with the final form .
any help would be appreciated.
Here is a whole working demo:
Model:
public class Package
{
public int Id { get; set; }
public string Name { get; set; }
public List<Item> Items { get; set; }
}
public class Item
{
public int Id { get; set; }
public string ItemName { get; set; }
}
View:
#model Package
<h1>Create</h1>
<h4>Package</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="Name" class="control-label"></label>
<input asp-for="Name" class="form-control" />
<span asp-validation-for="Name" class="text-danger"></span>
</div>
<button onclick="addRow()" type="button">Add Row</button>
<table id="AddItemsTable">
<tr>
<th>#Html.DisplayNameFor(model=>model.Items[0].ItemName)</th>
</tr>
</table>
<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>
JS:
#section Scripts {
<script>
var counter = 0;
function addRow() {
var table = document.getElementById("AddItemsTable");
var row = table.insertRow(-1);
var cell1 = row.insertCell(0);
cell1.innerHTML = '<input type="text" name="Items[' + counter+'].ItemName"/>';
counter++;
}
</script>
}
Controller:
// GET: Packages/Create
public IActionResult Create()
{
return View();
}
// POST: Packages/Create
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create(Package package)
{
if (ModelState.IsValid)
{
_context.Add(package);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
return View(package);
}
Result:
This is my view
#model MyProject.ViewModel.CourseViewModel
#{
ViewData["Title"] = "AddCourseView";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h1>Add Course</h1>
<div class="row">
<div class="col-md-12">
<form method="post">
<div asp-validation-summary="All" class="text-danger"></div>
<div class="form-group">
<label asp-for="Course.CourseName"></label>
<input asp-for="Course.CourseName" class="form-control" />
<span asp-validation-for="Course.CourseName" class="text-danger"></span>
</div>
<label>Select lecturers to the course</label>
#{
var lst = Model.List;
#for (int i = 0; i<lst.Count(); i++)
{
var lecturer = lst.ElementAt(i);
<div class="checkbox">
<label><input type="checkbox" value="" id="checkbox #i" asp-for=#lecturer.IsChecked>#lecturer.LecturerId</label>
</div>
}
#if (lst.Count() == 0)
{
<div class="checkbox">
<span class="badge badge-danger" style="font-size:medium">Lecturers list is empty</span>
</div>
}
}
<p></p>
<button type="submit" class="btn btn-primary">Create</button>
</form>
</div>
</div>
The view call AddCourse
This is my controller:
[HttpGet]
public IActionResult AddCourse()
{
var lst = userService.GetAllLecturers();
CourseViewModel courseViewModel = new CourseViewModel();
courseViewModel.List = new List<IsCheckedLecturer>();
foreach(var lecture in lst)
{
courseViewModel.List.Add(new IsCheckedLecturer
{
IsChecked = false,
LecturerId = lecture.UserId
});
}
return View(courseViewModel);
}
[HttpPost]
public IActionResult AddCourse(CourseViewModel viewModel)
{
return View(viewModel);
}
HttpGet work fine, but the HttpPost method(the second method) get a null list instead of get the model from the view(by model binding)
This is the courseViewModel
public class CourseViewModel
{
public Course Course { get; set; }
public IList<IsCheckedLecturer> List { get; set; }
}
public class IsCheckedLecturer
{
public bool IsChecked { get; set; }
public string LecturerId { get; set; }
}
why doesn't model binding work? and how i fix it?
Maybe because its complex view model?
Your question isn't very clear, but I'm assuming you just mean that the List property of viewModel is null, not viewModel itself. The reason for that is that you're not binding the input(s) correctly.
var lst = Model.List;
#for (int i = 0; i<lst.Count(); i++)
{
var lecturer = lst.ElementAt(i);
<div class="checkbox">
<label><input type="checkbox" value="" id="checkbox #i" asp-for=#lecturer.IsChecked>#lecturer.LecturerId</label>
</div>
}
The code above will result in the input having a name of lecturer.IsChecked. There's nothing to bind this to when it gets back server-side (there's no lecturer param), so the model binder just drops it, leaving your List property null.
Instead, you need to change your code to something like:
#for (int i = 0; i < Model.List.Count(); i++)
{
<div class="checkbox">
<label><input type="checkbox" id="checkbox#i" asp-for="List[i].IsChecked">#Model.List[i].LecturerId</label>
</div>
}
With that, the name of the input will end up as something like List[0].IsChecked, which will then bind to the List property on your model.
I'm a student using ASP.NET Core and related technologies for the first time. I've spent hours searching for a solution to this problem with no luck, so if anyone could help or point me at a tutorial to fix this, I would be very grateful.
My group and I are trying to build a web app to interface with an existing SQL database which has a lot of tables using concatenated primary keys. We can't change that. I used this tutorial to get our CRUD edit pages to even show, but if I try to submit any changes via that Edit page, I get an error like this:
No webpage was found for the web address: https://localhost:44311/GenCollectionSamplers/Edit
After some debugging, we're pretty sure this is a problem with the POST Edit method in the Controller getting null IDs, but we cannot figure out how to pass the correct IDs into the POST Edit method. The IDs are present in the GET Edit message.
Let me know if more code is needed.
Edit GET and POST from Controller:
// GET: GenCollectionSamplers/Edit/5
public async Task<IActionResult> Edit(double? peopleID, double? colID)
{
if (peopleID == null || colID == null)
{
return NotFound();
}
var genCollectionSamplers = await _context.GenCollectionSamplers.FindAsync(peopleID, colID);
if (genCollectionSamplers == null)
{
return NotFound();
}
ViewData["FkPeopleId"] = new SelectList(_context.GenPeople, "PkPeopleId", "PkPeopleId", genCollectionSamplers.FkPeopleId);
ViewData["FkCollectionId"] = new SelectList(_context.GenCollections, "CollectionId", "CollectionId", genCollectionSamplers.FkCollectionId);
return View(genCollectionSamplers);
}
// POST: GenCollectionSamplers/Edit/5
// 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> Edit(double peopleID, double colID, [Bind("FkPeopleId,FkCollectionId")] GenCollectionSamplers genCollectionSamplers)
{
//Causes error because peopleID and colID are 0
if (peopleID != genCollectionSamplers.FkPeopleId || colID != genCollectionSamplers.FkCollectionId)
{
return NotFound();
}
if (ModelState.IsValid)
{
try
{
_context.Update(genCollectionSamplers);
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!GenCollectionSamplersExists(genCollectionSamplers.FkPeopleId) || !GenCollectionSamplersExists(genCollectionSamplers.FkCollectionId))
{
return NotFound();
}
else
{
throw;
}
}
return RedirectToAction(nameof(Index));
}
ViewData["FkPeopleId"] = new SelectList(_context.GenPeople, "PkPeopleId", "PkPeopleId", genCollectionSamplers.FkPeopleId);
ViewData["FkCollectionId"] = new SelectList(_context.GenCollections, "CollectionId", "CollectionId", genCollectionSamplers.FkCollectionId);
return View(genCollectionSamplers);
}
Model:
public partial class GenCollectionSamplers
{
[Required]
[Display(Name = "Fk People Id")]
public double FkPeopleId { get; set; }
[Required]
[Display(Name = "Fk Collection Id")]
public double FkCollectionId { get; set; }
[Required]
[Display(Name = "Fk Collection")]
public GenCollections FkCollection { get; set; }
[Required]
[Display(Name = "Fk People")]
public GenPeople FkPeople { get; set; }
}
Edit view:
#model {path removed}.Models.GenCollectionSamplers
#{
ViewData["Title"] = "Edit";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h2>Edit</h2>
<h4>GenCollectionSamplers</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form asp-action="Edit">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="FkCollectionId" class="control-label"></label>
<input asp-for="FkCollectionId" class="form-control" />
<span asp-validation-for="FkCollectionId" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="FkPeopleId" class="control-label"></label>
<input asp-for="FkPeopleId" class="form-control" />
<span asp-validation-for="FkPeopleId" class="text-danger"></span>
</div>
<input type="hidden" asp-for="FkPeopleId" />
<input type="hidden" asp-for="FkCollectionId" />
<div class="form-group">
<input type="submit" value="Save" class="btn btn-default" />
</div>
</form>
</div>
</div>
<div>
<a asp-action="Index">Back to List</a>
</div>
#section Scripts {
#{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}
Part of Index view:
<p>
<a asp-action="Create">Create New</a>
</p>
<table class="table">
<thead>
<tr>
<th>
#Html.DisplayNameFor(model => efModel.FkCollectionId)
</th>
<th>
#Html.DisplayNameFor(model => efModel.FkPeople)
</th>
<th></th>
</tr>
</thead>
<tbody>
#foreach (var item in Model)
{
<tr>
<td>
#Html.DisplayFor(modelItem => item.FkCollection.CollectionId)
</td>
<td>
#Html.DisplayFor(modelItem => item.FkPeople.PkPeopleId)
</td>
<td>
#Html.ActionLink("Edit", "Edit", new { peopleID = item.FkPeopleId, colID = item.FkCollectionId }) |
#Html.ActionLink("Details", "Details", new { peopleID = item.FkPeopleId, colID = item.FkCollectionId }) |
#Html.ActionLink("Delete", "Delete", new { peopleID = item.FkPeopleId, colID = item.FkCollectionId })
</td>
</tr>
}
</tbody>
</table>
Why do you have the hidden fields on your Edit view:?
<input type="hidden" asp-for="FkPeopleId" />
<input type="hidden" asp-for="FkCollectionId" /
It is this that gets bound to the Model when you submit the HttpPost and since they do not have any values assigned, you are getting 0. Get rid of these two hidden fields. The rendered page will create the hidden fields at run-time (inspect your rendered markup for Edit page) along with __RequestVerificationToken that knows which instance of this Model should be validated and then updated.
This question already has answers here:
what happens behind model passing in ASP MVC4
(2 answers)
Closed 4 years ago.
This question seems to be asked a lot. I went through everything I could find on StackOverflow regarding this and as far as I can tell, I'm doing everything I should be doing as far as making sure that the view iterates through the list elements properly and creates proper html for them. Yet, the model binding does not work.
I have these two view models:
public class ProjectViewModel
{
public int ProjectId { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public int? StatusId { get; set; }
public IList<CoordinateViewModel> Coordinates;
}
public class CoordinateViewModel
{
public int CoordinateId { get; set; }
public double X { get; set; }
public double Y { get; set; }
}
They are used like this in a view ( simplified for ease of reading ):
#model ProjectViewModel
<div class="container">
<form asp-action="Edit" method="post">
<div asp-validation-summary="All" class="text-danger"></div>
<div class="row">
Coordinates
<table>
<thead>
<tr>
<th></th>
<th>X</th>
<th>Y</th>
</tr>
</thead>
<tbody>
#for (int i = 0; i < Model.Coordinates.Count; i++)
{
<tr>
<td>#Html.HiddenFor(x => Model.Coordinates[i].CoordinateId)</td>
<td>#Html.TextBoxFor(x => Model.Coordinates[i].X)</td>
<td>#Html.TextBoxFor(x => Model.Coordinates[i].Y)</td>
</tr>
}
</tbody>
</table>
</div>
<div class="row">
<div class="col-md-8 form-group">
<label asp-for="Description" class="control-label"></label>
<textarea asp-for="Description" class="form-control"></textarea>
<span asp-validation-for="Description" class="text-danger"></span>
</div>
</div>
<div class="row">
<div class="form-group">
<div class="col-md-offset-2 col-md-5">
<input type="submit" class="btn btn-primary" value="Submit" />
</div>
</div>
</div>
</form>
</div>
And the controller action that gets involved is this one ( again edited down for ease of reading ):
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Edit(ProjectViewModel viewModel)
{
return View(viewModel);
}
When the form loads, the Coordinate information is populated.
When the form is submitted, the view model coming in on the action method of the controller contains a null value for the Coordinates property, and I can't figure out why.
You are doing everything right as far as the view is concerned:
Iterate over the elements in a for loop
Use HTML helpers to create elements using the for loop's indexer
However, you did something wrong in your view model:
public IList<CoordinateViewModel> Coordinates;
For model binding to work, your view model needs to expose properties for the model binder to work with. In this case, Coordinates is a field, not a property. Change this to:
public IList<CoordinateViewModel> Coordinates {get; set; }
Now your model binding will work.
I need to insert a data from a table which has a foreach loop inside. I used to work with webforms so I'm newbie to mvc. I always encountered this scenario in webforms using gridview control and just implement the findcontrol command and it works fine. But in mvc, I could hardly get the solutions how to do it. I already search online but can't find any articles that fits my needs for this type of issue. So far, what I've got is if I insert a data from a first row of the table it will insert the record but if I insert them from the second, third, and so fourth, it will no longer insert the records into my database table. How do I make this perfectly be working?
Heres my model,view and controller to help you resolve the issue. Thanks...
Model
public class CommentModel
{
public int Id { get; set; }
[Required(ErrorMessage="Don't miss to put your name.")]
public string name { get; set; }
[Required(ErrorMessage = "Don't leave your comments empty.")]
public string comment { get; set;}
}
public class ReplyModel
{
public int idrep { get; set; }
public string namerep { get; set; }
public string reply { get; set; }
}
public class CreateViewModel
{
public CommentModel CreateComment { get; set; } // this line is optional
public ReplyModel CreateReply { get; set; }
public List<CommentModel> Comments { get; set; }
public List<ReplyModel> Replies { get; set; }
}
Repository:
public class ReplyRepository
{
private ProfileDataContext3 Reprepository;
public ReplyRepository()
{
Reprepository = new ProfileDataContext3();
}
public IEnumerable<ReplyModel> GetAllComments()
{
List<ReplyModel> profiles = new List<ReplyModel>();
var prof = from profile in Reprepository.RepTabs
orderby profile.Id descending
select profile;
var user = prof.ToList();
foreach (var item in user)
{
profiles.Add(new ReplyModel()
{
idrep = item.Id,
namerep = item.Name,
reply = item.Replies
});
}
return profiles;
}
//declaring methods for inserting records
public void InsertReply(ReplyModel profile)
{
var details = new RepTab()
{
Id=profile.idrep,
Name = profile.namerep,
Replies = profile.reply
};
Reprepository.RepTabs.Add(details);
Reprepository.SaveChanges();
}
}
Controller
public ActionResult PostComment()
{
var vModel = new CreateViewModel();
vModel.Comments = comrepository.GetAllComments().ToList();
vModel.Replies = replyrepository.GetAllComments().ToList();
return View(vModel);
}
[HttpPost]
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult PostComment(CommentModel com,string butname, ReplyModel rep)
{
switch(butname)
{
case "Post Comments":
if (ModelState.IsValid)
{
comrepository.InsertComment(com); //this is to insert the records
}
//this is to display the records inserted
var vModel = new CreateViewModel();
vModel.Comments = comrepository.GetAllComments().ToList();
vModel.Replies = replyrepository.GetAllComments().ToList();
return View(vModel);
case "Post Reply":
if (ModelState.IsValid)
{
replyrepository.InsertReply(rep); //this is to insert the records
}
//this is to display the records inserted
var vModel2 = new CreateViewModel();
vModel2.Comments = comrepository.GetAllComments().ToList();
vModel2.Replies = replyrepository.GetAllComments().ToList();
return View(vModel2);
default:
return null;
}
}
View
#model MyFirstMVCApp.Models.CreateViewModel
#{
ViewBag.Title = "PostComment";
}
<h2>Post Comment</h2>
<br />
#using (Html.BeginForm("PostComment", "Profile", FormMethod.Post, new { }))
{
#Html.ValidationSummary("Unable to Post Comment. Please correct the errors and try again...")
<fieldset>
<legend>CommentModel</legend>
<div class="editor-label">
<label for="name">Name</label>
</div>
<div class="editor-field">
<input type="text" id="name" name="name" />
<span class="field-validation-valid" data-valmsg-for="name" data-valmsg-replace="true"></span>
</div>
<div class="editor-label">
<label for="comment">Post your Comment here:</label>
</div>
<div class="editor-field">
<textarea id="comment" name="comment" style="width:500px;height:100px;resize:none" ></textarea>
<span class="field-validation-valid" data-valmsg-for="comment" data-valmsg-replace="true"></span>
</div>
<p>
<input type="submit" value="Post Comments" name="butname" />
#Html.ActionLink("See Comments", "DisplayComment")
</p>
<br />
</fieldset>
}
<br />
<h2>Comments</h2>
<br />
#using (Html.BeginForm("PostComment", "Profile", FormMethod.Post, new { }))
{
<table>
#foreach (var item in Model.Comments)
{
<tr>
<td>
<div class="editor-field" style="display:none;margin-bottom:10px;margin-top:10px">
#Html.TextBoxFor(m => m.CreateComment.Id)
</div>
<div style="font-weight:bold;"> #Html.DisplayFor(modelItem => item.name) </div>
<p style ="margin-top:0px;margin-bottom:0px; border-radius: 4px 4px 4px 4px; max-width :500px; min-height :5px; display :block; background-color: #CCCCFF"> #Html.DisplayFor(modelItem => item.comment) </p>
<p style="margin-top:2px;margin-bottom:0px"> <input type="button" id="like" name="like" value="Like" style="color:blue;border:0px;background-color:inherit;cursor:pointer" /> <input type="button" id="Reply" name="Reply" value="Replie(s)" style="color:blue;border:0px;background-color:inherit;cursor:pointer" /></p>
<div id="divrep" style="position:relative;left:50px; overflow:auto;margin-top:0px">
<table>
#foreach (var item2 in Model.Replies)
{
<tr>
<td>
<p style ="margin-top:0px;margin-bottom:0px; border-radius: 4px 4px 4px 4px; max-width :445px; min-height :5px; display :block; background-color: #CCCCFF;">#Html.DisplayFor(modelItem => item2.reply) </p>
<br />
</td>
</tr>
}
</table>
</div>
<input type="text" id="idrep" name="idrep" value="#Html.DisplayFor(modelItem=>item.Id)" />
<span class="field-validation-valid" data-valmsg-for="idrep" data-valmsg-replace="true"></span>
<br />
<input type="text" id="namerep" name="namerep" />
<span class="field-validation-valid" data-valmsg-for="namerep" data-valmsg-replace="true"></span>
<br />
<textarea id="reply" name="reply" style="width:500px;height:100px;resize:none" ></textarea>
<span class="field-validation-valid" data-valmsg-for="reply" data-valmsg-replace="true"></span>
<br />
<input type="submit" value="Post Reply" name="butname" />
</td>
</tr>
}
</table>
}
<br />
<br />
<div>
#Html.ActionLink("Back to List", "Index")
</div>
#section scripts
{
<script type ="text/jscript" src="JQuery/jquery-1.4.3.min.js">
</script>
<script type="text/javascript">
$('body').keypress(function(e) {
if (e.which == 13) {
$("input[value='Post Comments']").trigger('click');
}
});
</script>
}
The problem is the naming of your controls, you have multiple controls with the same name and the DefaultModelBinder will only bind the first one (i.e the first row) and ignore the rest. I suggest a different approach using jquery to post new comments and avoid having to refresh the page each time.
View
...
#using (Html.BeginForm()) {
<div>
#Html.LabelFor(m => m.CreateComment.Name)
#Html.TextBoxFor(m => m.CreateComment.Name)
#Html.ValidationMessageFor(m => m.CreateComment.Name)
// repeat for CreateComment.Comment property
<input type="submit" id="post-comment" value="Submit Comment" />
</div>
}
...
Script
$('form').submit(function(e) {
e.preventDefault(); // prevent submission
}
$('#post-comment').click(function() {
// get values for postback
var inputs = $(this).closest('div').find('input');
var name = inputs.first().val();
var comment = inputs.last().val();
var url = '#Url.Action("PostComment", "Profile")';
$.post(url, { Name: name, Comment: comment }, function(data) {
if(data) {
inputs.val(''); // clear existing inputs
// create a new element for the comment and add to the DOM, for example
var para = $('div></div>).text(comment).appendTo(something);
} else {
// display an error message
}
}
}
and add a similar script for posting replies, except use a class name not id for the button so there are no duplicate id's
Controller
[HttpPost]
public ActionResult PostComment(CommentModel model)
{
// save the comment and if successful
return Json(true);
else
return null;
}
For posting back replies, in your foreach loop, add a hidden input for the respective comment ID and include that in the data to post back to a separate action
[HttpPost]
public ActionResult PostReply(ReplyModel model)
{
...