MVC 4 model coming back null - asp.net

So I have a view that requires several different objects and lists of objects to be passed in and out that I have created a viewmodel for. My viewmodel looks like this
public class EditUserViewModel
{
public ManageUsersViewModel ManageUsersViewModel { get; set; }
public IEnumerable<StateModel> StateModel { get; set; }
}
The part I'm having trouble with is the StateModel which looks like this
public class StateModel
{
public bool IsChecked { get; set; }
public States States { get; set; }
public UsersInStates UsersInStates { get; set; }
}
and contains this
[Table("States")]
public class States
{
[Key]
public int StateId { get; set; }
public string State { get; set; }
}
[Table("UsersInStates")]
public class UsersInStates
{
[Key, Column(Order = 1)]
public int UserId { get; set; }
[Key, Column(Order = 2)]
public int StateId { get; set; }
public string LicenseNumber { get; set; }
}
In my view I'm more or less trying to loop through the states and take user input for UsersInStates. This is how I'm trying to accomplish it but my entire StateModel comes back null. Going into the view the StateModel.States has data and the UsersInStates does not. This is what it looks like in my view
#foreach (var state in Model.StateModel)
{
#Html.HiddenFor(m => state)
<tr>
<td>
#Html.CheckBoxFor(m => state.IsChecked)
</td>
<td>
#Html.Label(state.States.State)
</td>
<td>
#Html.EditorFor(m => state.UsersInStates.LicenseNumber)
</td>
</tr>
}
Any advice would be much appreciated. Everything displays as it should and the ManageUsersViewModel part works fine it's just the StateModel data coming back to the controller is null and I'm not exactly sure how to make this work the way I'd like it to.
This is what the generated html looks like for the start of the table and the first row as requested
<table style="margin-left:auto; margin-right:auto; text-align:center">
<input id="state" name="state" type="hidden" value="WebSiteNew.Models.StateModel" /> <tr>
<td>
<input data-val="true" data-val-required="The IsChecked field is required." id="state_IsChecked" name="state.IsChecked" type="checkbox" value="true" /> <input name="state.IsChecked" type="hidden" value="false" />
</td>
<td>
<label for="Alabama">Alabama</label>
</td>
<td>
<input class="text-box single-line" id="state_UsersInStates_LicenseNumber" name="state.UsersInStates.LicenseNumber" type="text" value="" />
</td>
</tr>
Answer:
Ok so to solve this I used a for loop as explained in both references listed in the answer below
#for (int i = 0; i < Model.StateModel.Count(); i++)
{
<tr>
<td>
#Html.HiddenFor(m => m.StateModel[i].States.StateId)
#Html.HiddenFor(m => m.StateModel[i].States.State)
#Html.CheckBoxFor(m => m.StateModel[i].IsChecked)
</td>
<td>
#Html.Label(Model.StateModel[i].States.State)
</td>
<td>
#Html.EditorFor(m => m.StateModel[i].UsersInStates.LicenseNumber)
</td>
</tr>
}
Also a note to anyone looking at this, I had to change IEnumerable in my EditUsersViewModel to IList to allow for indexing.

So your issue is that the model binding isn't happening correctly.
Say you have these two models:
public class ParentModel
{
public string Name {get;set;}
public ChildModel Child {get;set;}
}
public class ChildModel
{
public string ChildName {get;set;}
public int SomeNumber {get;set;}
}
Your generated HTML (for your model binding to happen correctly) needs to look like this:
<input name="Name" value="(Not relevant to this example)"/>
<input name="Child.ChildName" value="(Not relevant to this example)" />
Note how the name field is structured - this is how MVC determines what input values map to which properties in your view model. With the nested property, the property name has to go in front of it.
With collections, it gets more complicated. The model binder needs to know which values go with which instance of a property.
For example, if we assume the ChildModel is of type IEnumerable in the previous example, your HTML might look something like this in order to model bind correctly:
<input name="Name" value="(Not relevant to this example)"/>
<input name="Child[0].ChildName" value="(Not relevant to this example)" />
<input name="Child[0].SomeNumber" value="(Not relevant to this example)"/>
<input name="Child[1].ChildName" value="(Not relevant to this example)" />
<input name="Child[1].SomeNumber" value="(Not relevant to this example)"/>
Take a look at these for how to fix it:
http://seesharpdeveloper.blogspot.com/2012/05/mvc-model-binding-to-list-of-complex.html
http://haacked.com/archive/2008/10/23/model-binding-to-a-list.aspx
Edit - It's also important to note that when the Html Helpers generate the name value, it's based on the lambda value that is passed in. So
#Html.CheckBoxFor(m => state.IsChecked)
will generate the following name
name="state.IsChecked"
Since you're within a foreach, you're getting the wrong value for the name.

What are you trying to accomplish with the #Html.HiddenFor(m => state) - from the rendered HTML, that looks like your culprit right there. Would #Html.HiddenFor(m => state.StateId) be more appropriate?
Also, you could throw that into the first <td> element since it is hidden and will keep your HTML valid.

Related

Asp.net core 6 model databinding with dictionary

I have:
a) a list of days with timeslots:
timeslots
b) a list of locations:
locations
c) a link table that connects the locations with timeslots and where the users will populate the persons required in that timeslot and in that location:
Link table
I need to display a list of days with a checkbox for each timeslot in order to ask how many persons will be required in that location/timeslot (if any).
Now this is what i've done so far:
public class VolunteeringSlotViewModel
{
public VolunteeringSlotViewModel()
{
TimeSlots = new();
}
public DateOnly Date { get; set; }
public List<SelectListItem> TimeSlots { get; set; }
}
in the viewmodel:
public List<VolunteeringSlotViewModel> TimeSlots { get; set; } //lists all timeslots
public List<int> SelectedTimeSlots { get; set; } //will get selected checkboxes
public Dictionary<string,string> TimeSlotVolunteers { get; set; } // will host volunteers number required to the related (selected checkbox) time slot
In the view:
<h5>Time slots</h5>
#foreach (var v in Model.TimeSlots)
{
<div class="ml-2">
<p class="mb-n1"><u><strong>#v.Date.ToShortDateString()</strong></u></p>
#foreach (var t in v.TimeSlots)
{
<div class="form-group form-check ml-5 d-inline-flex align-items-center mb-1 ">
<input class="form-check-input" name="SelectedTimeSlots" type="checkbox"
value="#t.Value"
id="checkbox_v_#t.Value"
checked="#t.Selected" />
<label class="form-check-label mr-2" for="checkbox_t_#t.Value">#t.Text </label>
<input type="hidden" name="TimeSlotVolunteers[#t.Value].Key" value="#t.Value" />
<input class="form-control w-25" style="-moz-appearance: textfield;" type="number" name="TimeSlotVolunteers[#t.Value].Value" id="TimeSlotVolunteers[#t.Value]" value="#Model.TimeSlotVolunteers[t.Value]" />
</div>
}
</div>
}
Now the form appears like this:
rendered view
The checkboxes are working correctly but dictionary, where i want to set the [Value] with the number written in the textboxes, just populates the [key] field: the [Values] are always null.
I got it working in a dirty way, adding these lines in the controller's action but this was just to understand if data was written inside the form in some way.
for (int i = 1; i < 19; i++)
{
string key= "TimeSlotVolunteers[" + i.ToString()+"].Value";
vm.TimeSlotVolunteers[i.ToString()] = Request.Form[key];
}
I would like to make the databinding work and i thought it was related to the names of the controls but i cant figure out how to fix it.
Can you please help?
Thanks
I tried to use various textboxes code like
<input asp-for="TimeSlotVolunteers[#t.Value].Value" type="text" class="form-control" />
Adding and removing the ID= but the Dictionary values are always null.
At this point i'm wondering if this databinding is supported out of the box or a custom data binder is required

Asp.net core razor pages [BindProperty] doesnt work on collections

Im trying to use [BindProperty] annotation in asp.net core razor pages in order to Bind an Ilist<T> collection of one of my model classes so i can edit some of them at once, but it doesnt work at all, every time in OnPostAsync function the collection is empty, and neither the changes that i made on data nor it default values wont post back to the server, but when its a singel object [BindProperty] works fine and the values post back and can be changed, i also tried wraping a collection (i.e list<T>) in an object but it didnt work either way, so is there any way for doing so or i should lets say send a edit request for every object in that collection and edit them one by one(which cant be done in razor pages easilly and need some ajax calls)??
For binding IList between RazorPage and PageModel, you will need to use Product[i].Name to bind property.
Here are complete steps.
Model
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
}
PageModel
public class IndexModel : PageModel
{
private readonly CoreRazor.Data.ApplicationDbContext _context;
public IndexModel(CoreRazor.Data.ApplicationDbContext context)
{
_context = context;
}
[BindProperty]
public IList<Data.Product> Product { get; set; }
public async Task OnGetAsync()
{
Product = await _context.Product.ToListAsync();
}
public async Task OnPostAsync()
{
var product = Product;
}
}
View
<form method="post">
<table class="table">
<thead>
<tr>
<th>
#Html.DisplayNameFor(model => model.Product[0].Name)
</th>
<th></th>
</tr>
</thead>
<tbody>
#for (int i = 0; i < Model.Product.Count(); i++)
{
<tr>
<td>
<input hidden asp-for="Product[i].Id" class="form-control"/>
<input asp-for="Product[i].Name" class="form-control" />
</td>
<td>
<a asp-page="./Edit" asp-route-id="#Model.Product[i].Id">Edit</a> |
<a asp-page="./Details" asp-route-id="#Model.Product[i].Id">Details</a> |
<a asp-page="./Delete" asp-route-id="#Model.Product[i].Id">Delete</a>
</td>
</tr>
}
</tbody>
</table>
<div class="form-group">
<input type="submit" value="Save" class="btn btn-default" />
</div>
</form>

How to add a value to a class if the class contains LIST<class>

I have a class graf.
public class Graf
{
public List<Point> first { get; set; }
public List<Point> second { get; set; }
}
This class contains List
public class Point
{
public int x { get; set; }
public int y { get; set; }
public Point(int x, int y)
{
this.x = x;
this.y = y;
}
}
I need to add a Point into class Graf from index.cshtml:
#model WebApplication2.Models.Graf
<table>
<tr>
<td></td>
<td>
<div class="item">
<label>Y</label>
<input name="Y11" value="#Model.first" /> --------??
</div>
</td>
</tr>
</table>
<input type="submit" value="Send" />
But i dont now how i can input into Graf class Point?
How can I do it?
Ok. So let's start from a client-side code.I suppose that you have a next Index.cshtml view.
<!-- You use this code to display data from your model -->
<table>
<tr>
<td></td>
<td>
<div class="item">
<label>Y</label>
<input name="Y11" value="#Model.first" /> --------??
</div>
</td>
</tr>
</table>
Than you need a code that post new Point object from your view to controller.It could be like something like this:
<form asp-controller="Home" asp-action="InsertPoint" method="post">
X value: <input type="text" name="x"><br>
Y value: <input type="text" name="y"><br>
<input type="submit" value="Submit">
</form>
In your controller you should create action with following signature
[HttpPost]
public async Task<IActionResult> InsertPoint(Point point)
{
//Validation and insertion to list
return View();
}
NB
It's not an ideal solution. You could perform this task in many different ways. My aim, is just to show you the basic idea how it could be done. If you need more information you could start from this article
And of course, keep in mind that google is your good friend.

ASP.Net MVC Posted form values not mapping back onto viewmodel in controller

My ViewModel is:
public class ObjectiveVM
{
public string DateSelected { get; set; }
public List<string> DatePeriod { get; set; }
public IList<ObList> obList { get; set; }
public class ObList
{
public int ObjectiveId { get; set; }
public int AnalystId { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public string AnalystName { get; set; }
public bool Include { get; set; }
}
}
This is passed to the view, populated as expected - and displays correctly in the view.
My problem is when it is posted back to the controller. My controller code to accept it back is:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Analyst(ObjectiveVM ovm)
ovm.obList is always showing as null:
My View html is:
#model Objectives.ViewModels.ObjectiveVM
#{
ViewBag.Title = "Analyst";
}
<h2>Copy Objectives for Analyst</h2>
#using (Html.BeginForm()) {
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
<fieldset>
<legend>Objective</legend>
#Html.DropDownListFor(model => model.DateSelected, new SelectList(Model.DatePeriod))
<table>
<tr>
<th>
#Html.DisplayNameFor(model => model.obList[0].Include)
</th>
<th>
#Html.DisplayNameFor(model => model.obList[0].AnalystName)
</th>
<th>
#Html.DisplayNameFor(model => model.obList[0].Title)
</th>
<th>
#Html.DisplayNameFor(model => model.obList[0].Description)
</th>
</tr>
#foreach (var obList in Model.obList)
{
<tr>
<td>
#Html.HiddenFor(modelItem => obList.ObjectiveId)
#Html.HiddenFor(modelItem => obList.AnalystId)
#Html.HiddenFor(modelItem => obList.Title)
#Html.HiddenFor(modelItem => obList.Description)
#Html.CheckBoxFor(modelItem => obList.Include)
</td>
<td>
#Html.DisplayFor(modelItem => obList.AnalystName)
</td>
<td>
#Html.DisplayFor(modelItem => obList.Title)
</td>
<td>
#Html.DisplayFor(modelItem => obList.Description)
</td>
</tr>
}
</table>
<p>
<input type="submit" value="Copy Selected Objectives" />
</p>
</fieldset>
}
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")
}
Looking in Developer Tools at the Posted form values, they appear to be ok:
Can anyone see any reason the posted form values, are not mapping back onto my viewmodel in the Controller HTTP post?
Thank you, Mark
You need to use a for...loop here, not a foreach....loop.
#for (int idx = 0;idx < Model.obList.Count;idx++){
#Html.HiddenFor(_ => Model.obList[idx].ObjectiveId)
// ... etc....
}
Without the indexer (idx), the model binder will not know how to bind the values back to the right collection item.
When working with collections in my views, I typically write out my markup without the use of helpers:
#for (int i = 0;i < Model.obList.Count();i++){
<input type="hidden" name="ObList[#i].ObjectiveId" id="ObList[#i].ObjectiveId" value="#ObList[i].ObjectiveId" />
<input type="hidden" name="ObList[#i].AnalystId" id="ObList[#i].AnalystId" value="#ObList[i].AnalystId" />
...
}
This will conform to the wire format the model binder expects, and will slot your values into your ViewModel: http://www.hanselman.com/blog/ASPNETWireFormatForModelBindingToArraysListsCollectionsDictionaries.aspx

Generating a dropdown list with database first

In my mvc3 application i want to populate the dropdownlist for the data which is coming from database here am using entityframework with database first approach so please help me to do this
You did not provide any code of what you have done so far so I know nothing about the data that you have been working with. I will post some code and all that you will have to do is to modify it to fit in with your scenario.
Lets work with a simple solution of loan applications. A client needs to apply for a loan and he needs to supply banking details. He will have to select a bank from a drop down list.
Lets start with the domain model called Bank. This represents your data coming from your database table. Lets call the table Banks.
public class Bank
{
public int Id { get; set; }
public string Name { get; set; }
}
Your table called Banks will look like this:
Id | int | not null | primary key
Name | varchar(50) | not null
Depeneding on you what you need to do I normally have a service layer that calls my bank repository to bring back the data. But seeing that you only need to bring back data and nothing else we can skip the service layer.
public class BankRepository : RepositoryBase<Bank>, IBankRepository
{
public IEnumerable<Bank> FindAll()
{
return DatabaseContext.Banks;
}
}
You database context will look like this:
public class DatabaseContext : DbContext
{
public DbSet<Bank> Banks { get; set; }
}
This is how your data retrieval methods could look like. It might not be the full solution but there are many samples online. Just go and Google.
Lets move onto the web application.
Your view/page will work with a view model and not your domain model. You use view models to represent your data on the view/page. So on your create view you will pass in a create application view model with a list of your banks. An instance of IBankRepository will be supplied through a technique called dependency injection. I use Autofac for this.
public class ApplicationViewModel
{
public int BankId { get; set; }
public IEnumerable<Bank> Banks { get; set; }
}
Your controller's action method will populate the view model and send it to your view.
public class ApplicationController : Controller
{
private readonly IBankRepository bankRepository;
public ApplicationController(IBankRepository bankRepository)
{
this.bankRepository = bankRepository;
}
public ActionResult Create()
{
ApplicationViewModel viewModel = new ApplicationViewModel
{
Banks = bankRepository.FindAll()
};
return View(viewModel);
}
}
Your view will then receive this view model and do with it what it needs to do. In this case it will populate your bank drop down.
#model YourProject.ViewModels.Applications.ApplicationViewModel
#using (Html.BeginForm())
{
<tr>
<td class="edit-label">Bank: <span class="required">**</span></td>
<td>
#Html.DropDownListFor(
x => x.BankId,
new SelectList(Model.Banks, "Id", "Name", Model.BankId),
"-- Select --"
)
#Html.ValidationMessageFor(x => x.BankId)
</td>
</tr>
}
I don't what your experience is but judging from your question it seems like you still need to do a lot of research. There are tonnes of examples online. You will need to work through samples of Entity Framework code first and ASP.NET MVC. Invest some time and you will reap the rewards later.
I hope this works and best of luck. The solution might not be what you want but it can help guide you in the right direction.
Let assume you have a class like so
public class ProductBrand
{
//Eg. Nokia, Hp, Dell
/// <summary>
/// Second Level to Category
/// Has Foreign Key to category table
/// </summary>
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ProductBrandId { get; set; }
[Display(Name = "Category")]
public int ProductCategoryId { get; set; }
public virtual ProductCategory ProductCategory { get; set; }
[Required(ErrorMessage = "This field is required")]
[StringLength(300, ErrorMessage = "This field must not be greater than 300 xters long")]
[Display(Name="Name")]
public string BrandName { get; set; }
public IList<Product> Products { get; set; }
[Display(Name = "Status")]
public bool ActiveStatus { get; set; }
}
Which means this model has a foreign key name ProductCategoryId then in your create Product brand view you will need a dropdownlist containing All productcategory to choose from.
Just create an actionresult like so
public ActionResult CreateProductBrand() {
ViewBag.ProductCategoryId = new SelectList(context.ProductCategories, "ProductCategoryId", "CategoryName");
return View();
}
Then call the viewbag in you corrensponding view like so:
#using (Html.BeginForm()) {
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
<table style="width:400px" class="post-form">
<tr>
<td>Category</td>
<td>
<div class="editor-field">
#Html.DropDownList("ProductCategoryId", String.Empty)
#Html.ValidationMessageFor(model => model.ProductCategoryId)
</div>
</td>
</tr>
</table>
<table style="width:400px" class="post-form">
<tr>
<td>Brand</td>
<td>
<div class="editor-field">
#Html.EditorFor(model => model.BrandName)
#Html.ValidationMessageFor(model => model.BrandName)
</div>
</td>
</tr>
<tr>
<td>Status</td>
<td>
<div class="editor-field">
#Html.EditorFor(model => model.ActiveStatus)
#Html.ValidationMessageFor(model => model.ActiveStatus)
</div>
</td>
</tr>
<tr>
<td></td>
<td>
<input type="submit" class="button blue" value="Save" />
<a href="#Url.Action("ProductBrand", "ProductSetup")" class="button">Cancel
</a>
</td>
</tr>
</table>
}
ViewModel:
public List<Item> _Items { get; set; }
public int_newID { get; set; }
Model Item
public int_id { get; set; }
public string _name { get; set; }
Controller:
Populate _Items with data and send that list to your view
View:
#Html.DropDownListFor(c => c._newID, new SelectList(Model._Items , "_id", "_name"),--Select Item--")

Resources