ASP.Net MVC Postback from View to Controller shows null values - c#

I've a problem with ViewModel posting back to a controller, but the ViewModel not being mapped correctly from the View to the Controller.
TopicId and Content should contain values, however, when posted back, they do not:
VS Debug:
ViewModels:
public class PostViewModel
{
public int PostId { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public string Author { get; set; }
public DateTime DateOfTopic { get; set; }
}
public class ReplyViewModel
{
public int TopicId { get; set; }
public string Content { get; set; }
}
public class PostListAndReplyVM
{
public List<PostViewModel> PostViewModel { get; set; }
public ReplyViewModel ReplyViewModel { get; set; }
}
View:
#model centreforum.Models.PostListAndReplyVM
#using (Html.BeginForm()) {
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
<fieldset>
<legend>Post</legend>
#Html.HiddenFor(model => model.ReplyViewModel.TopicId)
<div class="editor-label">
#Html.LabelFor(model => model.ReplyViewModel.Content)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.ReplyViewModel.Content)
#Html.ValidationMessageFor(model => model.ReplyViewModel.Content)
</div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
Generated HTML:
<form action="/Post/List/7/" method="post"><input name="__RequestVerificationToken" type="hidden" value="xxxxxxxxxxxxx" /> <fieldset>
<legend>Post</legend>
<input data-val="true" data-val-number="The field TopicId must be a number." data-val-required="The TopicId field is required." id="ReplyViewModel_TopicId" name="ReplyViewModel.TopicId" type="hidden" value="7" />
<div class="editor-label">
<label for="ReplyViewModel_Content">Content</label>
</div>
<div class="editor-field">
<input class="text-box single-line" id="ReplyViewModel_Content" name="ReplyViewModel.Content" type="text" value="" />
<span class="field-validation-valid" data-valmsg-for="ReplyViewModel.Content" data-valmsg-replace="true"></span>
</div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
</form>
As you can see from the generated HTML, the TopicId definitely has a value: value="7"
Can anyone see where the problem is between the form post, and the controller, which is expecting the ReplyViewModel?
Thank you,
Mark

Your input field names are prefixed with ReplyViewModel (because of the model => model.ReplyViewModel.* lambda), so you need to indicate this information to the model binder:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult List([Bind(Prefix = "ReplyViewModel")] ReplyViewModel model)
{
...
}
Alternatively have your List action take the PostListAndReplyVM model:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult List(PostListAndReplyVM model)
{
// obviously only model.ReplyViewModel will be bound here because
// those are the only input fields in your form
...
}

The problem is the fact that your view is typed to PostListAndReplyVM - so it creates names such as ReplyViewModel.Content - but, because your controller action expects a ReplyViewModel, these fields can't be bound (i.e. there is no such thing as ReplyViewModel.ReplyViewModel.Content).
Change your controller action:
public ActionResult List(PostListAndReplyVM reply)
Alternatively - if that's your whole view - just type it to ReplyViewModel instead (and update your HtmlHelper expressions accordingly).

Its null because you bound it to another model
In view
#model centreforum.Models.PostListAndReplyVM
In Action ReplyViewModel
try to bind like
public ActionResult SomeAction(PostListAndReplyVM model)
{
}

Related

How to validate several forms in one view using single view model in ASP.NET Core MVC?

I have a view with single view model called DataViewModel.
In this view I have more than one form. Now I can't set all properties to required because if I do so then if I click for example Save button which saves certain fields. There are some other fields that are not required for the save button but required for add button. How can I handle validation in this case?
Also, If I have an error in one action which is one form and I used return View(); it will show all errors of required fields that I actually don't need for this specific action. And If I used RedirectToAction(), then the page is returned with no error even if there were one.
DataViewModel
public class DataViewModel
{
[Required]
public string personName { get; set; }
[Required]
public string classChosen { get; set; }
[Required]
public string className { get; set; }
[Required]
public string ClassCode { get; set; }
[Required]
public string newPersonName { get; set; }
}
Index view
#model DataViewModel
<form asp-controller="Home" asp-action="AddPerson" method="post">
<input required="required" type="text" class="form-control scan" placeholder="New Person"
asp-for="newPersonName " />
<span asp-validation-for="newPersonName " class="text-danger"></span>
<input type="submit" class="model-close button btn btn-primary primary-btn"
style="width:auto;" value="Add" />
</form>
<form asp-controller="Home" asp-action="Index" method="post">
\\Here I'm adding person with class data
</form>
Home controller
[HttpPost]
public IActionResult AddPerson(DataViewModel model)
{
Person person = new Person();
if (model.newPersonName != null)
{
person.Name = model.newPersonName;
person.status = true;
var personName = dbContext.Person
.Where(w => w.Name == model.newPersonName)
.Select(w => w.Name)
.FirstOrDefault();
if (personName == null)
{
personRepository.AddPerson(person);
}
else
{
ModelState.AddModelError("newPersonName", "Name already exists");
}
}
ModelState.AddModelError("newPersonName", "Please enter valid value");
return View("Index");
}
[HttpPost]
public IActionResult Index(DataViewModel model)
{
// Code for adding classes for person
return View(model);
}
How can I handle such a case?
Because when I click add for adding new person with return view it shows all errors even ones that are not related to adding new person. and same for same. How can I separate the validation for several forms in one view using single view model
I think you can try to only use client side validation,If the data is not required,the form data will not be passed to the action:
public class DataViewModel
{
public string personName { get; set; }
public string classChosen { get; set; }
public string className { get; set; }
public string ClassCode { get; set; }
public string newPersonName { get; set; }
}
Index view:
<form asp-controller="Home" asp-action="AddPerson" method="post">
<input required="required" type="text" class="form-control scan" placeholder="New Person"
asp-for="newPersonName " />
<span asp-validation-for="newPersonName " class="text-danger"></span>
<input type="submit" class="model-close button btn btn-primary primary-btn"
style="width:auto;" value="Add" />
</form>
<form asp-controller="Home" asp-action="Index" method="post">
input required="required" type="text" class="form-control scan" placeholder="New Person"
asp-for="className " />
<span asp-validation-for="className " class="text-danger"></span>
<input type="submit" class="model-close button btn btn-primary primary-btn"
style="width:auto;" value="Save" />
</form>

How to save user data to List in ASP.Net Core

We need to register a new training course in the system. Save the Course Name, Course Price and Lesson List into the database. The number of lessons for each course is different, so the user will dynamically add fields to enter the name of each lesson. How to get the data entered by the user from the fields and save them to the list, and subsequently to the database?
public class RegisterCourseViewModel
{
public string Name { get; set; }
public decimal Price { get; set; }
public List<Lesson> ListOfLessons { get; set; }
}
public class Lesson
{
public int LessonId { get; set; }
public string Name { get; set; }
}
#model RegisterCourseViewModel
<div>
<h2>Registration a new course</h2>
<form asp-area="Staff" asp-controller="Course" asp-action="AddCourse" method="post">
<div asp-validation-summary="All"></div>
<div>
<label asp-for="Name"></label>
<input asp-for="Name" />
<span asp-validation-for="Name"></span>
</div>
<div>
<label asp-for="Price"></label>
<input asp-for="Price" />
<span asp-validation-for="Price"></span>
</div>
<div ID="items">
Lesson 1:
<input type="text" name="item1" size="45"><br>
<input type="button" value="Add a new lesson" onClick="AddItem();" ID="add">
<input type="button" value="Delete the lesson" onClick="DeleteItem();" ID="delete">
</div>
<div>
<input type="submit" value="Registration" />
</div>
</form>
</div>
From this code:
<div>
<label asp-for="Name"></label>
<input asp-for="Name" />
<span asp-validation-for="Name"></span>
</div>
<div>
<label asp-for="Price"></label>
<input asp-for="Price" />
<span asp-validation-for="Price"></span>
</div>
I get Name and Price in my controller method and save it to DB. But how can I get a list of user-entered lessons names?
This is controller's method:
public IActionResult AddCourse(RegisterCourseViewModel model)
{
if (ModelState.IsValid)
{
CourseModel newCourse = new CourseModel
{
Name = model.Name,
Price = model.Price,
ListOfLessons = model.ListOfLessons <---- How to get this List?
};
courseModel.SaveCourse(newCourse);
return RedirectToAction("Index", "Home", new { area = "Staff" });
}
return View(model);
}
public class CourseModel
{
public Guid Id { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
public List<Lesson> ListOfLessons { get; set; }
}
Here is a working demo:
View:
#model RegisterCourseViewModel
<div>
<h2>Registration a new course</h2>
<form asp-area="Staff" asp-controller="Course" asp-action="AddCourse" method="post">
<div asp-validation-summary="All"></div>
//...
<div ID="items">
Lesson 1:
//change the name here...
<input type="text" name="ListOfLessons[0].Name"size="45"><br>
<input type="button" value="Add a new lesson" onClick="AddItem();" ID="add">
<input type="button" value="Delete the lesson" onClick="DeleteItem();" ID="delete">
</div>
<div>
<input type="submit" value="Registration" />
</div>
</form>
</div>
#section Scripts
{
<script>
function AddItem() {
var index = $('input[name^="ListOfLessons"]').length;
$("#add").before('<input type="text" size="45" name="ListOfLessons[' + index + '].Name" /><br>')
}
</script>
}
Result:
By Converting course list into Json string, and then using one column of table in database to save it, you can get user's dynamical course list.
So use database model like:
public class RegisterCourse
{
public string Name { get; set; }
public decimal Price { get; set; }
public string ListOfLessons { get; set; }
}
Use JsonConvert.SerializeObject(courseList) to convert user inputed courses and then insert it to database.
And JsonConvert.DeserializeObject<Class>(jsonstr) to read user's course List from database model

ASP.NET Core 2.0 Razor Pages web app separate Complex BindProperty Form

I am new to ASP.NET Core 2.0 Razor Pages web app framework. The problem is the following: I need to create html form for complex [BindProperty] class and separate it's complex fields in partial views or editors (like in MVC):
public class BasicInformation
{
// complex filed
public Name Name { get; set; }
public string Email { get; set; }
// collection of objects
public IEnumerable<Address> Addresses { get; set; }
//complex field
public PhoneNumber PhoneNumber { get; set; }
}
I would like to achieve something like this:
<h4>BasicInformation</h4>
<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="Resume" />
#Html.EditorFor(m => m.BasicInformation.Name)
<div class="form-group">
<label asp-for="BasicInformation.Email" class="control-label"></label>
<input asp-for="BasicInformation.Email" class="form-control" />
<span asp-validation-for="BasicInformation.Email" class="text-danger"></span>
</div>
<div class="form-group">
#Html.EditorFor(m => m.BasicInformation.PhoneNumber)
</div>
...
<div class="form-group">
<input type="submit" value="Save" class="btn btn-default" />
</div>
</form>
</div>
</div>`
And my page model:
public class BasicInformationFormModel : PageModel
{
[BindProperty]
public BasicInformation BasicInformation { get; set; }
public IActionResult OnGet()
{
return Page();
}
public IActionResult OnPost(BasicInformation basicInformation)
{
// all data from separate views / editors need to be present in basicInformation
return Page();
}
}
How can I achieve this?
You need to create a model class to warp the BasicInformation model,
public class ProfileViewModel{
public BasicInformation BasicInformation {get;set;}
}
and than make view as strongly type with model ProfileViewModel
#model ProfileViewModel

MVC 4 Viewmodel fails to databind on postback

Hi there.
For more than a day now I have been unable to update my entity models in my MVC project with values from the post. I have read like 30 related issues here on SO and still I have no idea what is even wrong. I made a small project without Ajax and other disturbances, and now I can watch it fail reliably. I was hoping to run this by you, it is very simple, so perhaps someone more experienced can easily identify my misstake. This is what I came up with:
Models and data:
public class StoryDB : DbContext
{
public DbSet<Story> Stories { get; set; }
}
public class Story
{
[Key]
public int StoryID { get; set; }
public string zestory { get; set; }
}
public class StoryViewModel
{
public Story stry;
}
Controller:
//
// GET: /Story/Create
[HttpGet]
public ActionResult Create()
{
StoryViewModel svm = new StoryViewModel();
svm.stry = new Story();
return View(svm);
}
//
// POST: /Story/Create
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(StoryViewModel svm)
{
if (ModelState.IsValid)
{
db.Stories.Add(svm.stry);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(svm);
}
View:
#model MvcApplication7.Models.StoryViewModel
#{
ViewBag.Title = "Create";
}
<h2>Create</h2>
#using (Html.BeginForm()) {
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
<fieldset>
<legend>Story</legend>
#Html.HiddenFor(model => model.stry.StoryID)
<div class="editor-label">
#Html.LabelFor(model => model.stry.zestory)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.stry.zestory)
#Html.ValidationMessageFor(model => model.stry.zestory)
</div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")
}
This is the HTML it produces:
<form action="/Story/Create" method="post"><input name="__RequestVerificationToken" type="hidden" value="v0wlNDXbhykfe6IKOep6WMSZVteTDNPdgUVkQ8VcW4RW2oo-ufJSdVkN70Y0OHI4if56HrZiGLaGVy9UzlsoTkKXm963ZwRfJsuBZSVs9uQ1" /> <fieldset>
<legend>Story</legend>
<input data-val="true" data-val-number="The field StoryID must be a number." data-val-required="The StoryID field is required." id="stry_StoryID" name="stry.StoryID" type="hidden" value="0" />
<div class="editor-label">
<label for="stry_zestory">zestory</label>
</div>
<div class="editor-field">
<input class="text-box single-line" id="stry_zestory" name="stry.zestory" type="text" value="" />
<span class="field-validation-valid" data-valmsg-for="stry.zestory" data-valmsg-replace="true"></span>
</div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
</form>
And what happens is that there is no databinding I can notice, and it crashes on trying to save with an error due to the incomming viewmodel's Story stry being null.
I have also tried to create new viewmodel and belonging story and filling them with TryUpdateModel() with the result that empty entries were made into the database.
Possibly relevant:
Try changing the field to a property:
public class StoryViewModel
{
public Story stry { get; set; }
}

Can't get selected drop down value to bind to view model property

I'm having trouble binding the selected value of a drop down list to the correct property in my view model. I can't see what I am doing wrong here. I've put the code that should help show what I'm doing below. I've omitted some things such as the population of the 'AllFolders' property of the view model, as it's just a simple List with an object called ImageGalleryFolder.
Every time the form posts back, the ParentFolderId property is null without fail. This is driving me crazy and I've wasted a lot of time trying to work it out.
Can anyone see something I'm doing wrong?
This is the view model
public class ImageGalleryFolderViewModel
{
[Required]
public string Title { get; set; }
public int Id { get; set; }
public string CoverImageFileName { get; set; }
public HttpPostedFileBase UploadedFile { get; set; }
public string ParentFolderId { get; set; }
public IList<ImageGalleryFolder> AllFolders { get; set; }
}
Here is the view code
#using Payntbrush.Presentation.Demo.MVC3.Areas.Admin
#model Payntbrush.Presentation.Demo.MVC3.Areas.Admin.Models.ImageGalleryFolderViewModel
#{
ViewBag.Title = "Create A New Gallery Folder";
}
<h2>#ViewBag.Title</h2>
<script src="#Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
<script src="#Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>
#using (Html.BeginForm((string)ViewBag.Action + "Folder", "Portfolio", FormMethod.Post, new { Id = "CreateFolder", enctype = "multipart/form-data" }))
{
#Html.ValidationSummary(true)
if(((string)ViewBag.Action).ToLower() == FormConstants.Edit.ToLower())
{
#Html.HiddenFor(m => m.Id)
#Html.HiddenFor(m => m.CoverImageFileName)
#Html.HiddenFor(m => m.ParentFolderId)
}
<div class="editor-label">
#Html.LabelFor(model => model.Title)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Title)
#Html.ValidationMessageFor(model => model.Title)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.UploadedFile)
</div>
<div class="editor-field">
<input type="file" name="UploadedFile"/>
#Html.ValidationMessageFor(model => model.UploadedFile)
</div>
{
// Count > 1 is important here. If there is only 1 folder, then we still don't show the drop down
// as a child folder can't have itself as it's own parent.
}
if(#Model.AllFolders.Count > 1)
{
<div class="editor-label">
Choose a parent folder (optional)
</div>
<div class="editor-field">
#Html.DropDownListFor(m => m.ParentFolderId, new SelectList(Model.AllFolders, "Id", "Title"))
</div>
}
<p>
<input type="submit" value="Save" />
</p>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
I've ommitted my view, but this is what my form looks like when rendered in the browser. The form looks good from what I can see?
<form Id="CreateFolder" action="/SlapDaBass/Portfolio/EditFolder/1" enctype="multipart/form-data" method="post">
<input data-val="true" data-val-number="The field Id must be a number." data-val-required="The Id field is required." id="Id" name="Id" type="hidden" value="1" />
<input id="CoverImageFileName" name="CoverImageFileName" type="hidden" value="" />
<input id="ParentFolderId" name="ParentFolderId" type="hidden" value="" />
<div class="editor-label">
<label for="Title">Title</label>
</div>
<div class="editor-field">
<input class="text-box single-line" data-val="true" data-val-required="The Title field is required." id="Title" name="Title" type="text" value="Test" />
<span class="field-validation-valid" data-valmsg-for="Title" data-valmsg-replace="true"></span>
</div>
<div class="editor-label">
<label for="UploadedFile">UploadedFile</label>
</div>
<div class="editor-field">
<input type="file" name="UploadedFile"/>
<span class="field-validation-valid" data-valmsg-for="UploadedFile" data-valmsg-replace="true"></span>
</div>
<div class="editor-label">
Choose a parent folder (optional)
</div>
<div class="editor-field">
<select id="ParentFolderId" name="ParentFolderId">
<option value="1">Test</option>
<option value="2">Test 2</option>
</select>
</div>
<p>
<input type="submit" value="Save" />
</p>
</form>
And this is the controller action:
[HttpPost]
public ActionResult EditFolder(int id, ImageGalleryFolderViewModel model)
{
if (ModelState.IsValid)
{
Services.PortfolioService.UpdateFolder(model.MapToDomainModel(), model.UploadedFile);
return Home;
}
return View();
}
change the data type of the ParentFolderId
public class ImageGalleryFolderViewModel
{
[Required]
public string Title { get; set; }
public int Id { get; set; }
public string CoverImageFileName { get; set; }
public HttpPostedFileBase UploadedFile { get; set; }
public int ParentFolderId { get; set; }
public IList<ImageGalleryFolder> AllFolders { get; set; }
}
also use the Html helper for the dropdownlist
<%:
Html.DropDownListFor(
model => model.ParentFolderId ,
new SelectList(
new List<Object>{
new { value = 1 , text = "Test" },
new { value = 2 , text = "Test2" },
new { value = 3 , text = "Test3"}
},
"value",
"text"
)
)
%>
i hope you are strongly typing your view like
public ActionResult EditFolder()
{
return View(new ImageGalleryFolderViewModel());
}
Please refer below link for the bind drop down list. It will be very helpful to you.
ASP.NET MVC - drop down list selection - partial views and model binding
Here if you do not want to create property in model for the List of items, than you can also store it in a ViewData or ViewBag. Please find sample code below.
<%= Html.DropDownList("Category.CategoryId", new SelectList((
IEnumerable<ProductManagement.Models.Category>)ViewData["CategoryList"],
"CategoryId", "CategoryName"))%>
You're creating a Hidden input for ParentFolderId with an empty value. This is probably overriding the value that the DropDownList is trying to post. Remove this line:
#Html.HiddenFor(m => m.ParentFolderId)
you have 2 element for ParentFolderId
one of them is hidden field
#Html.HiddenFor(m => m.ParentFolderId)
second is select element
#Html.DropDownListFor(m => m.ParentFolderId, new SelectList(Model.AllFolders, "Id", "Title"))
and modelbinder bind the first matched element value to model.
You have to remove hidden field

Categories

Resources