MVC 4 Model Binding returns null - c#

I'm having trouble with model binding in MVC. I have a class:
public class UserSurvey
{
public int Id { get; set; }
public virtual Survey Survey { get; set; }
}
Which is the model for a view:
#model SurveyR.Model.UserSurvey
<form id="surveyForm">
<div class="container survey">
#Html.HiddenFor(x=>x.Id)
#Html.EditorFor(x => x.Survey.Steps)
</div>
<input type="button" value="Submit" id="btnSubmit"/>
</form>
And then for the submit the controller takes a class:
public class SurveyResponseViewModel
{
public int Id { get; set; }
public Survey Survey { get; set; }
}
[HttpPost]
public ActionResult Submit(SurveyResponseViewModel surveyResponse)
{
...
}
When I debug the submit the surveyResponse.Survey object is populated as it should be but the surveyResponse.Id value is 0 when it should be 1.
I can see the Id=1 being passed back in the submit but the model binding doesn't seem to hook it up.
Any help would be greatly appreciated!
Kev
EDIT: The rendered html looks like this:
<form id="surveyForm">
<div class="container survey">
<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" />
So yes the value appears there and is also passed in the submit if I look using dev tools.
EDIT 2: The Form data in dev tools definitely contains "Id:1".

Your Code seems to be fine.Try passing the id value explicitly as another parameter like below
[HttpPost]
public ActionResult Submit(SurveyResponseViewModel surveyResponse , int Id )
{
surveyResponse.Id = Id
}

I have tested. Its working fine.
public ActionResult test1()
{
var model = new UserSurvey();
model.Id = 10;
return View(model);
}
[HttpPost]
public ActionResult test1(SurveyResponseViewModel surveyResponse)
{
var x = surveyResponse.Id; // returns 10
return View(new UserSurvey());
}
public class SurveyResponseViewModel
{
public int Id { get; set; }
public Survey Survey { get; set; }
}
public class UserSurvey
{
public int Id { get; set; }
public virtual Survey Survey { get; set; }
}
public class Survey
{
public string Steps { get; set; }
}
#model TestWeb.Controllers.UserSurvey
#using (Html.BeginForm())
{
<div class="container survey">
#Html.HiddenFor(x=>x.Id)
#Html.EditorFor(x => x.Survey.Steps)
</div>
<input type="submit" value="Submit" id="btnSubmit"/>
}

Related

How do you bind a checkbox in .net core razor pages?

How do you bind a checkbox in .net core razor pages?
I'm currently having problems where the checkbox value isn't coming back when I submit the form (using post method).
Below is my code.
domain classes:
public class Restaurant
{
public int Id { get; set; }
[Required, StringLength(80)]
public string Name { get; set; }
public Meals MealsServed { get; set; }
}
public class Meals
{
public int Id { get; set; }
public bool Breakfast { get; set; }
public bool Lunch { get; set; }
public bool Dinner { get; set; }
}
from page model:
[BindProperty]
public Restaurant Restaurant{ get; set; }
public EditModel(IRestaurantData restaurantData, IHtmlHelper htmlHelper)
{
this.restaurantData = restaurantData;
this.htmlHelper = htmlHelper;
}
public IActionResult OnGet(int? restaurantId)
{
Restaurant = restaurantData.GetById(restaurantId.Value);
Restaurant.MealsServed.Breakfast = true;
return Page();
}
public IActionResult OnPost()
{
if (!ModelState.IsValid)
{
return Page();
}
restaurantData.Update(Restaurant);
restaurantData.Commit();
TempData["Message"] = "Restaurant Saved";
return RedirectToPage("./Detail", new { restaurantId = Restaurant.Id });
}
from razor page:
<form method="post">
<input type="hidden" asp-for="Restaurant.Id" />
<div class="form-group">
<label asp-for="Restaurant.Name"></label>
<input asp-for="Restaurant.Name" class="form-control" />
<span class="text-danger" asp-validation-for="Restaurant.Name"></span>
</div>
<div class="form-group">
<input asp-for="Restaurant.MealsServed.Lunch" />
<label asp-for="Restaurant.MealsServed.Lunch"> </label>
</div>
<button type="submit" class="btn btn-primary">Save</button>
</form>
So, I figured out the problem. Everything was correct that I presented above.
After checking the checkbox for Lunch and saving the item, and then viewing the Restaurant item again, it would come back unchecked.
The issue was with the depth of the data that I was pulling from the database. It was only pulling the top level of data.
So, I had to change GetById method from:
public Restaurant GetById(int id)
{
return db.Restaurants.Find(id);
}
to:
public Restaurant GetById(int id)
{
return db.Restaurants.Where(r => r.Id == id).Include(r => r.MealsServed).FirstOrDefault();
}
explicitly telling it to pull the data for the object in the MealsServed property.

How to fix error while trying to pass multiple models to one view in Asp.Net Core 2.1?

I need to pass some models to one view. What I've done so far is something like this:
OrderTypeModel:
namespace test.Models
{
public class OrderTypeModel
{
[Key]
public int Id { get; set; }
public string OrderType { get; set; }
}
}
OrderStatusModel:
namespace test.Models
{
public class OrderStatusModel
{
[Key]
public int Id { get; set; }
public string OrderStatus { get; set; }
}
}
OrderSizeModel:
namespace test.Models
{
public class OrderSizeModel
{
[Key]
public int Id { get; set; }
public string OrderSize { get; set; }
}
}
OrderModel:
namespace test.Models
{
public class OrdersModel
{
[Key]
public int Id { get; set; }
public string Title { get; set; }
public int OrderNumber { get; set; }
public int OrderType_Id { get; set; }
public int OrderStatus_Id { get; set; }
public int OrderSize_Id { get; set; }
public DateTime CreationDate{ get; set; }
}
}
And there's a MultipleViewsModel:
namespace test.Models
{
public class MultipleViewsModel
{
public List<OrderTypeModel> Type { get; set; }
public List<OrderSizeModel> Size { get; set; }
public List<OrderStatusModel> Status { get; set; }
public OrdersModel Orders { get; set; }
}
}
OrderType_Id, OrderStatus_Id, and OrderSize_Id are Foreign Keys in my database. Now When I need to call them all in a form, it throws an error:
My View is something like this:
#model MultipleViewsModel
#{
ViewData["Title"] = "Create";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<form asp-action="Create">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="Orders.Title" class="control-label"></label>
<input asp-for="Orders.Title" class="form-control" />
<span asp-validation-for="Orders.Title" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Orders.OrderType_Id" class="control-label"></label>
#{
foreach (var ot in ViewData["OrderType"] as Type)
{
<input asp-for="Orders.OrderType_Id" type="radio" value=#ot.Id name=#ot.OrderType. class="form-control" />
}
}
<span asp-validation-for="Orders.OrderType_Id" class="text-danger"></span>
</div>
<div class="form-group">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</form>
And my Controller is something like this:
public IActionResult Create()
{
ViewData["OrderType"] = _context.OrderType
.Any();
ViewData["OrderSize"] = _context.OrderSize.Any();
return View();
}
I need to put the models in radio forms. But an error is thrown at this line in view:
foreach (var ot in ViewData["OrderType"] as Type)
And says Type is not found in current context.
Note that I've cut a major part of what was not needed and so my form in the view is not complete just to be succinct.
Any kind of help is appreciated.
You can use view model as you have included in your page :
#model MultipleViewsModel
On server side , you should initialize the model class and set the values . For example , you can fill the Type in create action like :
MultipleViewsModel multipleViewsModel = new MultipleViewsModel();
multipleViewsModel.Type = new List<OrderTypeModel>();
multipleViewsModel.Type = db.orderTypes.ToList();
multipleViewsModel.Orders = new OrdersModel() { OrderType_Id = 2 };
return View(multipleViewsModel);
On client side , you can loop the OrderTypeModel list and set default value by which matches the OrderType_Id :
#{
foreach (var orderType in Model.Type)
{
<input asp-for="Orders.OrderType_Id" type="radio" value=#orderType.Id name=Orders.OrderType_Id />
#Html.Label("OrderType" + orderType.Id, orderType.OrderType)
}
}

ASP.NET Core - Bind IEnumerable<T> to a ViewModel field on POST

I have a web application for registering teams to a competition, where each team can select a number of technologies that they will use for their project. The technologies are saved in a Label class.
I am using a view model to bind the information from the form to the action.
However, when I try to submit the form, it takes all other fields, except the list of technologies.
Label.cs
public class Label
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public string ColorPalette { get; set; }
}
CreateTeamViewModel.cs
public class CreateTeamViewModel
{
[Required]
public string TeamName { get; set; }
public string ProjectName { get; set; }
public string ProjectDescription { get; set; }
[Required]
public string RepositoryLink { get; set; }
public List<Label> Labels = new List<Label>();
}
TeamsController.cs
public class TeamsController
{
private readonly ApplicationDbContext context;
public IActionResult Create()
{
ViewData["Labels"] = this.context.Labels.ToList();
return View();
}
[HttpPost]
public IActionResult Create(CreateTeamViewModel team)
{
List<Label> labels = team.Labels;
int count = labels.Count; // count = 0
return LocalRedirect("/");
}
}
Create.cshtml (the list of checkboxes)
#model Competition.Data.ViewModels.CreateTeamViewModel
#{
List<Label> labels = ViewData["Labels"] as List<Label>;
}
<form asp-action="Create">
<div class="form-check">
#for(int i = 0; i < labels.Count; i++)
{
<input asp-for="#Model.Labels[i].IsSelected" type="checkbox" />
<label asp-for="#Model.Labels[i].Name">
<span class="badge badge-#labels[i].ColorPalette">#labels[i].Name</span>
</label>
<input asp-for="#Model.Labels[i].Name" type="hidden" value="#labels[i].Name" />
<input asp-for="#Model.Labels[i].ColorPalette" type="hidden" value="#labels[i].ColorPalette" />
}
</div>
<div class="form-group">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</form>
You need to bind to a list of int instead of a list of Label on your view model. Then, you'll need to use that list of selected ids to fill your list of labels on the Team entity you're persisting:
public class CreateTeamViewModel
{
[Required]
public string TeamName { get; set; }
public string ProjectName { get; set; }
public string ProjectDescription { get; set; }
[Required]
public string RepositoryLink { get; set; }
public List<int> SelectedLabels { get; set; } = new List<int>();
}
Then, you'll need to modify your form to bind your checkboxes to this list:
#foreach (var label in labels)
{
<input asp-for="SelectedLabels" id="Label#(label.Id)" value="#label.Id" type="checkbox" />
<label id="Label#(label.Id)">
<span class="badge badge-#label.ColorPalette">#label.Name</span>
</label>
}
Notice that I removed the hidden inputs. You should never post anything that the user should not be able to modify, as even hidden inputs can be tampered with.
After posting, server-side you'll end up with a list of label ids that were selected by the user. Simply query the associated labels out of your database and then assign that to the team you're creating:
team.Labels = await _context.Set<Label>().Where(x => model.SelectedLabels.Contains(x.Id)).ToListAsync();

html.hidden for value not set in asp.net MVC core Razor view

I am working on an asp.net MVc core application. I have a popup with a form element like this:
#using (Html.BeginForm("AddIVR", "ITPVoice", FormMethod.Post, new { role = "form" }))
{
#*#Html.HiddenFor(m =>m.m_newIVR.Account, new { #value= Model.accountID})*#
#Html.Hidden("m.m_newIVR.Account", Model.accountID)
}
I have a viewmodel like this:
public class AccountDetailsViewModel
{
public IVRS m_newIVR { get; set; }
}
and IVRS model class like this:
public class IVRS
{
[JsonProperty("_id")]
public string Id { get; set; }
[JsonProperty("name")]
public string Name { get; set; }
[JsonProperty("description")]
public string Description { get; set; }
[JsonProperty("account")]
public string Account { get; set; }
}
I am trying to populate it in my view like this:
#Html.HiddenFor(m =>m.m_newIVR.Account, new { #value= Model.accountID})
but when i see view source, Value is null
I tried using:
#Html.Hidden("m.m_newIVR.Account", Model.accountID)
and it shows m_newIVR.Account populated.
Then I am posting the form to controller this action
[HttpPost]
public ActionResult AddIVR(AccountDetailsViewModel model)
{
return RedirectToAction("AccountDetails", "mycontroller")
}
Although I see that AccountId is populated in view ( using viewsource), but in post action method value of model.m_newIVR.Account is null.
HTML output looks like this:
<div id="edit-ivrs-modal" class="modal fade" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true" style="display: none;">
<div class="modal-dialog">
<form action="/ITPVoice/AddIVR" method="post" role="form"> <div class="modal-content">
<div class="modal-header">
<h4 class="modal-title">Add IVR</h4>
<input id="m_newIVR_Account" name="m_newIVR.Account" type="hidden" value="" />
<input id="AccountId" name="AccountId" type="hidden" value="56f5e3d77ea022a042665be1" />
</div>
<div class="modal-body">
</div>
</div>
My Questions are:
Why html.hiddenfor is not setting value of the model variable?
Although html.hidden is setting value, why it is not accessible in post action method ?
Please suggest.
Now I am able to answer your question why does it works for Html.Hidden but not for Html.HiddenFor.
When you Html.HiddenFor with m =>m.m_newIVR.Account then it always try to set value for hidden field value whatever value available in property m.m_newIVR.Account not the value that you specify using #value = Model.AccountId.
If you want to use HiddenFor the set m_newIVR.Account in ViewModel just use following thing.
#Html.HiddenFor(m =>m.m_newIVR.Account)
Html.Hidden is not strongly type so it not depend on name. You can specify different name and value parameter. In this case It is your responsibility to generate proper name for HiddenField.
My Working Sample
Model
public class AccountDetailsViewModel
{
public string AccountId { get; set; }
public IVRS m_newIVR { get; set; }
}
public class IVRS
{
[JsonProperty("_id")]
public string Id { get; set; }
[JsonProperty("name")]
public string Name { get; set; }
[JsonProperty("description")]
public string Description { get; set; }
[JsonProperty("account")]
public string Account { get; set; }
}
Controller Action
[HttpGet]
public IActionResult Index1()
{
AccountDetailsViewModel model = new AccountDetailsViewModel();
//model.AccountId = "1222222";
model.m_newIVR = new IVRS();
model.m_newIVR.Account = "122222";
return View(model);
}
[HttpPost]
public IActionResult Index1(AccountDetailsViewModel model)
{
return View(model);
}
View (Index1.cshtml)
#model WebApplication2.Controllers.AccountDetailsViewModel
#using (Html.BeginForm())
{
#Html.HiddenFor(m =>m.m_newIVR.Account)
<input type="submit" />
}
// Sample Out

How do I pass this child model reference to a virtual ICollection in parent domain model in ASP.NET MVC?

I'm creating a commenting system for my ASP.NET MVC blog engine using a view form that triggers a basic controller action method:
FORM:
#if (User.Identity.IsAuthenticated)
{
//using (Html.BeginForm())
// {
<div class="new_comment">
<h6 id="shortcodes" class="page-header"><i class="fa fa-file-text-o"></i> Leave a comment</h6>
<div class="hline"></div>
<form class="form-horizontal" action="#Url.Action("CreateComment")" method="post" role="form">
<div class="form-group">
<div class="col-sm-4 col-md-4">
<textarea rows="7" class="form-control" name="Message" placeholder="Your Comment Here..."></textarea>
#Html.AntiForgeryToken()
<input type="hidden" name="Slug" value="#Model.Slug"/>
<input type="hidden" name="PostId" value="#Model.Id"/>
<br/>
</div>
</div>
<div class="form-group">
<input type="submit" value="Post Comment" class="btn btn-primary" style="margin-left: 12px"/>
</div>
</form>
</div>
//}
}
CONTROLLER:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult CreateComment([Bind(Include = "PostId,Message,Username,DatePosted")]Comment comment)
{
var post = db.BlogPosts.Find(comment.PostId);
if (post == null)
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
if (ModelState.IsValid)
{
comment.Username = User.Identity.GetUserId();
comment.DatePosted = DateTimeOffset.Now;
db.Comments.Add(comment);
db.SaveChanges();
}
return RedirectToAction("BlogPostDetails", new { slug = post.Slug});
}
I've set breakpoints aside each of the expressions contained within the if statement and confirmed that none of the data values being passed ("PostId, Message, Username, DatePosted") are null, and that db.SaveChances() is commiting changes to the database. Next, here isModels.BlogPosts...
MODELS:
public class BlogPosts
{
public BlogPosts()
{
this.Comments = new HashSet<Comment>();
}
public int Id { get; set; }
public DateTimeOffset Created { get; set; }
public DateTimeOffset? Updated { get; set; }
[AllowHtml]
[Required]
public string Title { get; set; }
public string Slug { get; set; }
[Required]
public string Category { get; set; }
[AllowHtml]
[Required]
public string Body { get; set; }
public string MediaURL { get; set; }
public bool Published { get; set; }
public virtual ICollection<Comment> Comments { get; set; }
}
public class Comment
{
public int Id { get; set; }
public int PostId { get; set; }
public string Username { get; set; }
[Required]
[DataType(DataType.MultilineText)]
public string Message { get; set; }
public DateTimeOffset DatePosted { get; set; }
public Nullable<System.DateTimeOffset> Edited { get; set; }
public virtual BlogPosts BlogPost { get; set; }
public virtual ApplicationUser Author { get; set; }
//public int? ParentId { get; set; }
//[ForeignKey("ParentId")]
//public virtual ICollection<Comment> Children { get; set; }
//public string ParentComment { get; internal set; }
}
And here is the view that fails to execute:
VIEW THAT DOES NOT EXECUTE
#foreach (var item in Model.Comments.OrderByDescending(c => c.DatePosted))
{
<div class="comment">
<p>
#if (item.Username == null)
{
<small>By: Anonymous</small><span>|</span>
}
else
{
<small>By: #Html.DisplayFor(modelItem => item.Username)</small><span>|</span>
}
<small>Date: #Html.DisplayFor(modelItem => item.DatePosted)</small>
#if (item.Edited != null)
{
<span>|</span><small>Updated: #Html.DisplayFor(modelItem => item.Edited)</small>
}
</p>
<div>
#Html.DisplayFor(modelItem => item.Message)
</div>
</div>
if (item.Username == User.Identity.GetUserId() || User.IsInRole("Admin") || User.IsInRole("Moderator"))
{
<div>
#Html.ActionLink("Edit", "_EditComment", new { id = item.Id }) <span>|</span>
#Html.ActionLink("Delete", "_DeleteComment", new { id = item.Id })
</div>
}
<br />
<!--<div class="hline"></div>-->
}
<div>
<input type="button" class="btn btn-primary" value="Return to Blog Roll" onclick="location.href = '#Url.Action("BlogIndex")'">
</div>
<br />
#if (User.Identity.IsAuthenticated || User.IsInRole("Admin") || User.IsInRole("Moderator"))
{
<input type="button" class="btn btn-primary" value="Modify Post" onclick="location.href = '#Url.Action("BlogAdmin")'">
<br />
<br />
}
When setting a breakpoint on the first line in the view above: #foreach (var item in Model.Comments.OrderByDescending(c => c.DatePosted)), the reference to
public virtual ICollection<Comment> Comments within the Models.BlogPosts class remains null (which obviously means the logic in my view fails to execute and no comment is posted).
I am new to ASP.NET MVC, EF Code-First, etc., and clearly do not understand how my controller is failing to pass the comment values in the child model to the public virtual ICollection<Comment> Comments in the parent... How is it that Models.Comment as referenced in my CommentCreate controller contains a value, and the very same virtual reference in my Models.BlogPosts does not?
EDIT: After great feedback from several users on both the cosmetic errors and critical errors in my code, as well as helpful ASP.NET MVC resource pointers, I determined that the null references being passed had to do with incorrect property-naming conventions in my domain models. See answer below.
Youre including PostId however the actual property name is Id. Also you need to show what model your view is receiving. If youre concerened with what youre exposing (e.g. Id, why dont you just mark it as a hidden field?).
In the controller pass in nothing but the model that you want to edit, e.g. keep a controller solely for url routing, keep your model solely for your object and your view should be only for the model youre passing in.
Finally figured out that the BlogPost property in public virtual BlogPosts BlogPost in my Models.Comment domain model needs to be renamed to match the domain model's foreign key: public int PostId. The solution was executed as follows:
Changing the property name to Post,
then manually deleting the null PostId values from the Comment table in my database,
and then running update-database -f command in NuGet.

Categories