Passing ID from View to Controller (ASP.NET) - c#

I'm getting the following error:
System.NullReferenceException: 'Object reference not set to an instance of an object.'
Microsoft.AspNetCore.Mvc.Razor.RazorPage.Model.get returned null.
I'm trying to pass an Id from a view to a controller HttpPost action method.
Here is my code:
Controller:
public class HomeController : Controller
{
...
[Authorize]
public IActionResult List()
{
var currentUserId = User.FindFirst(ClaimTypes.NameIdentifier).Value;
var currentCars = db.Cars.Where(x => x.CreatedByUserId == currentUserId)
.Select( x => new CarsListViewModel
{
CarId = x.Id,
CreatedOn = x.CreatedOn,
CreatedByUserId = x.CreatedByUserId,
CreatedByUserName = x.CreatedByUserName,
Firstname = x.PrimaryData.Firstname,
Lastname = x.PrimaryData.Lastname
}).
ToList();
return View(currentCars);
}
[HttpPost]
[Authorize]
[ValidateAntiForgeryToken]
public IActionResult List(int carId)
{
var Car = db.Cars.FirstOrDefault(x => x.Id == carId);
db.Cars.Remove(Car);
db.SaveChanges();
return View();
}
ViewModel:
public class CarListViewModel
{
public int CarId { get; set; }
public DateTime CreatedOn { get; set; }
public string CreatedByUserId { get; set; }
public string CreatedByUserName { get; set; }
public string Firstname { get; set; }
public string Lastname { get; set; }
}
}
View (List.cshtml):
#model List<CVBuilder.ViewModels.CarListViewModel>
#{
ViewData["Title"] = "List of current cars";
}
<div class="col-md-10 offset-md-1">
<table class="table table-hover text-nowrap">
<thead>
...
</thead>
<tbody>
#for (int i = 0; i < Model.Count; i++)
{
<tr>
<td>#Model[i].CreatedOn</td>
<td>#Model[i].CreatedByUserName</td>
<td>#Model[i].Firstname</td>
<td>#Model[i].Lastname</td>
<td>
<form method="post">
<input type="hidden" name="carId" value="#Model[i].CarId" />
<button type="submit" class="btn btn-danger btn-sm">Delete</button>
</form>
</td>
</tr>
}
</tbody>
</table>
#if (Model.Count == 0)
{
<div class="text-center"><p>No cars created.</p></div>
}
</div>

try it:
public IActionResult List([FromForm]int carId){
// return View(); remove
return RedirectToAction("List")
}

You can try this to pass a particular model id from view to controller in MVC
<button href="#Url.Action("List", "ControllerName", new { carId = #Model[i].CarId},null)" button>

Related

Error passing ViewModel to View using EF Core MVC

I have a ViewModel passing to Views and am getting a strange error:
InvalidOperationException: The model item passed into the
ViewDataDictionary is of type
'System.Collections.Generic.List1[BizDevHub.Models.Intermediary]',
but this ViewDataDictionary instance requires a model item of type
'System.Collections.Generic.IEnumerable1[BizDevHub.ViewModels.IntermediaryViewModel]'.
I cannot understand why as it seems that I am passing the ViewModel to the View and not the Model.
My Models
public class Intermediary
{
public int IntermediaryID
public string RegisteredName { get; set; }
public string TradingName { get; set; }
public DateTime CreationDate { get; set; }
public string CreatedBy { get; set; }
public ICollection<Branch> Branches { get; set; }
}
public class Branch
{
public int BranchID { get; set; }
public string Name { get; set; }
[DisplayName("Creation Date")]
public DateTime CreationDate { get; set; }
[StringLength(100)]
[DisplayName("Created By")]
public string CreatedBy { get; set; }
}
My ViewModel
public class IntermediaryViewModel
{
public int IntermediaryID { get; set; }
[Required,StringLength(150),DisplayName("Registered Name")]
public string RegisteredName { get; set; }
[Required, StringLength(150), DisplayName("Registered Name")]
public string TradingName { get; set; }
public int Registration { get; set; }
public int VATNumber { get; set; }
[Required]
public int FSPNumber { get; set; }
[DisplayName("Creation Date")]
public DateTime CreationDate { get; set; }
[StringLength(100)]
[DisplayName("Created By")]
public string CreatedBy { get; set; }
public int BranchID { get; set; }
public ICollection<Branch> Branches { get; set; }
}
My Views
#model IEnumerable<BizDevHub.ViewModels.IntermediaryViewModel>
#{
ViewData["Title"] = "Index";
}
<h2>Index</h2>
<p>
<a asp-action="Create">Create New</a>
</p>
<table class="table">
<thead>
<tr>
<th>
#Html.DisplayNameFor(model => model.RegisteredName)
</th>
<th>
#Html.DisplayNameFor(model => model.TradingName)
</th>
<th>
#Html.DisplayNameFor(model => model.Registration)
</th>
<th>
#Html.DisplayNameFor(model => model.VATNumber)
</th>
<th>
#Html.DisplayNameFor(model => model.FSPNumber)
</th>
<th>
#Html.DisplayNameFor(model => model.CreationDate)
</th>
<th>
#Html.DisplayNameFor(model => model.CreatedBy)
</th>
<th>
#Html.DisplayNameFor(model => model.BranchID)
</th>
<th></th>
</tr>
</thead>
<tbody>
#foreach (var item in Model) {
<tr>
<td>
#Html.DisplayFor(modelItem => item.RegisteredName)
</td>
<td>
#Html.DisplayFor(modelItem => item.TradingName)
</td>
<td>
#Html.DisplayFor(modelItem => item.Registration)
</td>
<td>
#Html.DisplayFor(modelItem => item.VATNumber)
</td>
<td>
#Html.DisplayFor(modelItem => item.FSPNumber)
</td>
<td>
#Html.DisplayFor(modelItem => item.CreationDate)
</td>
<td>
#Html.DisplayFor(modelItem => item.CreatedBy)
</td>
<td>
#Html.DisplayFor(modelItem => item.BranchID)
</td>
<td>
<a asp-action="Edit" asp-route-id="#item.IntermediaryID">Edit</a> |
<a asp-action="Details" asp-route-id="#item.IntermediaryID">Details</a> |
<a asp-action="Delete" asp-route-id="#item.IntermediaryID">Delete</a>
</td>
</tr>
}
</tbody>
</table>
#model BizDevHub.ViewModels.IntermediaryViewModel
#{
ViewData["Title"] = "Create";
}
<h2>Create</h2>
<h4>Intermediary</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="RegisteredName" class="control-label"></label>
<input asp-for="RegisteredName" class="form-control" />
<span asp-validation-for="RegisteredName" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="TradingName" class="control-label"></label>
<input asp-for="TradingName" class="form-control" />
<span asp-validation-for="TradingName" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Registration" class="control-label"></label>
<input asp-for="Registration" class="form-control" />
<span asp-validation-for="Registration" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="VATNumber" class="control-label"></label>
<input asp-for="VATNumber" class="form-control" />
<span asp-validation-for="VATNumber" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="FSPNumber" class="control-label"></label>
<input asp-for="FSPNumber" class="form-control" />
<span asp-validation-for="FSPNumber" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="CreatedBy" class="control-label"></label>
<input asp-for="CreatedBy" class="form-control" />
<span asp-validation-for="CreatedBy" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="BranchID" class="control-label"></label>
<input asp-for="BranchID" class="form-control" />
<span asp-validation-for="BranchID" class="text-danger"></span>
</div>
<div class="form-group">
<label class="control-label col-md-2" for="DepartmentID">Branch</label>
<div class="col-md-10">
#Html.DropDownList("DepartmentID", null, new {#class="form-group"})
#Html.ValidationMessageFor(model => model.BranchID)
</div>
</div>
<div class="form-group">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</form>
</div>
</div>
<div>
<a asp-action="Index">Back to List</a>
</div>
#section Scripts {
#{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}
My Controller
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.EntityFrameworkCore;
using BizDevHub.Data;
using BizDevHub.Models;
using BizDevHub.ViewModels;
namespace BizDevHub.Controllers
{
public class IntermediariesController : Controller
{
private readonly BizDevHubContext _context;
public IntermediariesController(BizDevHubContext context)
{
_context = context;
}
// GET: Intermediaries
public async Task<IActionResult> Index()
{
return View(await _context.Intermediaries.ToListAsync());
}
// GET: Intermediaries/Details/5
public async Task<IActionResult> Details(int? id)
{
if (id == null)
{
return NotFound();
}
var intermediary = await _context.Intermediaries
.FirstOrDefaultAsync(m => m.IntermediaryID == id);
if (intermediary == null)
{
return NotFound();
}
return View(intermediary);
}
// GET: Intermediaries/Create
public IActionResult Create()
{
PopulateBranchDropDownList();
return View();
}
// POST: Intermediaries/Create
// 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, ActionName("Edit")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create([Bind("IntermediaryID,RegisteredName,TradingName,Registration,VATNumber,FSPNumber,CreationDate,CreatedBy,BranchID")] IntermediaryViewModel intermediary)
{
if (ModelState.IsValid)
{
_context.Add(intermediary);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
PopulateBranchDropDownList(intermediary.BranchID);
return View(intermediary);
}
// GET: Intermediaries/Edit/5
public async Task<IActionResult> Edit(int? id)
{
if (id == null)
{
return NotFound();
}
var intermediary = await _context.Intermediaries.FindAsync(id);
if (intermediary == null)
{
return NotFound();
}
return View(intermediary);
}
// POST: Intermediaries/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, ActionName("Edit")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id, [Bind("IntermediaryID,RegisteredName,TradingName,Registration,VATNumber,FSPNumber,CreationDate,CreatedBy,BranchID")] IntermediaryViewModel intermediary)
{
if (id != intermediary.IntermediaryID)
{
return NotFound();
}
if (ModelState.IsValid)
{
try
{
_context.Update(intermediary);
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!IntermediaryExists(intermediary.IntermediaryID))
{
return NotFound();
}
else
{
throw;
}
}
return RedirectToAction(nameof(Index));
}
PopulateBranchDropDownList(intermediary.BranchID);
return View(intermediary);
}
// GET: Intermediaries/Delete/5
public async Task<IActionResult> Delete(int? id)
{
if (id == null)
{
return NotFound();
}
var intermediary = await _context.Intermediaries
.FirstOrDefaultAsync(m => m.IntermediaryID == id);
if (intermediary == null)
{
return NotFound();
}
return View(intermediary);
}
// POST: Intermediaries/Delete/5
[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> DeleteConfirmed(int id)
{
var intermediary = await _context.Intermediaries.FindAsync(id);
_context.Intermediaries.Remove(intermediary);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
private bool IntermediaryExists(int id)
{
return _context.Intermediaries.Any(e => e.IntermediaryID == id);
}
private void PopulateBranchDropDownList(object selectedBranch = null)
{
var branchesQuery = from d in _context.Branch
orderby d.Name
select d;
ViewBag.DepartmentID = new SelectList(branchesQuery, "DepartmentID", "Name", selectedBranch);
}
}
}
Any help will be appreciated!
For:
return View(await _context.Intermediaries.ToListAsync());
and
return View(intermediary);
You seem to be passing the View objects of type Intermediary, not IntermediaryViewModel, unless your context is somehow converting them in other code. I don't see any code that's mapping the entity (Intermediary) to the ViewModel (IntermediaryViewModel). Perhaps use AutoMapper or something similar to convert your EF entities into the ViewModel.
I see couple of issues with your code.
Your views are taking ViewModel/s as input. But you are returning Model object in Index action. So, in order to fix the error that is shown in the title of this POST, please update your index action to convert colleciton of model objects to collection of viewmodel objects.
public async Task<IActionResult> Index()
{
var intermediaryViewModels = ModelsToViewModelsConversion(_context.Intermediaries.ToListAsync());
return View(intermediaryViewModels);
}
private async List<IntermediaryViewModel>() ModelsToViewModelsConversion(IEnumerable<Intermediary> models)
{
// conversion code goes here.
// return collection of IntermediaryViewModel;
}
Create GET action (// GET: Intermediaries/Create) is not returning any object, but view is expecting instance of IntermediaryViewModel. So, you need to modify your Create GET Action as below:
// GET: Intermediaries/Create
public IActionResult Create()
{
// PopulateBranchDropDownList();
var branchesQuery = from d in _context.Branch
orderby d.Name
select d;
return View(new IntermediaryViewModel{Branches = branchesQuery.AsEnumerable() });
}
And update your Create.cshtml as below to display the branches:
<div class="form-group">
<label class="control-label col-md-2" for="DepartmentID">Branch</label>
<div class="col-md-10">
#Html.DropDownList("DepartmentID", new SelectList(Model.Branches, "DepartmentID", "Name"), new { #class = "form-group" })
#Html.ValidationMessageFor(model => model.BranchID)
</div>
</div>
What I noticed is you have mixed up models and viewmodels (for example IntermediaryViewModel has Branch model instead of BranchViewModel) and in Create POST action, you are adding viewmodel to context models (entities).
All of your code is good if you have taken care of model-viewmodel conversion process via some operator or something of that sort. But if that is the case, you don't get the error that you are seeing now which making me to think there is no such conversion going on in your code.
Updated your code to include Conversion from Model to ViewModel.
public class Intermediary
{
public int IntermediaryID { get; set; }
public string RegisteredName { get; set; }
public string TradingName { get; set; }
public DateTime CreationDate { get; set; }
public string CreatedBy { get; set; }
public ICollection<Branch> Branches { get; set; }
}
public class Branch
{
public int BranchID { get; set; }
public string Name { get; set; }
// Generally Model classes should not have any idea on Display related stuff.
// [DisplayName("Creation Date")]
public DateTime CreationDate { get; set; }
[StringLength(100)]
// [DisplayName("Created By")]
public string CreatedBy { get; set; }
}
public class IntermediaryViewModel
{
// I just created common expression so it can be used from Index as well as Details actions.
// Feel free to remove this common expression and have the code inside controller actions if you prefer that way.
/// <summary>
/// Lambda expression converting Intermediary to IntermediaryViewModel
/// </summary>
public static readonly Expression<Func<Intermediary, IntermediaryViewModel>> AsIntermediaryViewModel =
i => new IntermediaryViewModel{
// Please add other required properties mapping here. I just showed couple
IntermediaryID = i.IntermediaryID,
RegisteredName = i.RegisteredName,
BranchID = i.BranchID,
// if you want you can populate Branches (by Intermediary) here like this in a single database call as we have mentioned Include in actions
Branches = i.Branches.AsQueryable().Select(b => new BranchViewModel{ BranchID = b.BranchID}).ToList()
};
public int? IntermediaryID { get; set; }
[Required,StringLength(150),DisplayName("Registered Name")]
public string RegisteredName { get; set; }
[Required, StringLength(150), DisplayName("Registered Name")]
public string TradingName { get; set; }
public int Registration { get; set; }
public int VATNumber { get; set; }
[Required]
public int FSPNumber { get; set; }
// As now you have separate ViewModel for display purposes,
// you can have string version of CreationDate which is (DateTime to string) converted as per your requirement.
[DisplayName("Creation Date")]
public DateTime CreationDate { get; set; }
[StringLength(100)]
[DisplayName("Created By")]
public string CreatedBy { get; set; }
// Since you are defining BranchId as non-nullable, you will need to default to some existing BranchId so, your (create) view will show this branch when page is loaded. Otherwise make it nullable int (int ?) so, dropdown would display your message "Select Branch" when BranchId is null
public int BranchID { get; set; }
public ICollection<BranchViewModel> Branches { get; set; }
}
// since you are using Branches to populate drop down, other audit properties are not required here.
// Does not hurt having them if you want.
public class BranchViewModel
{
public int BranchID { get; set; }
public string Name { get; set; }
}
// GET: Intermediaries
public async Task<IActionResult> Index()
{
return View(await _context.Intermediaries.Include(i => i.Branches).Select(IntermediaryViewModel.AsIntermediaryViewModel).ToListAsync());
}
// GET: Intermediaries/Details/5
public async Task<IActionResult> Details(int? id)
{
if (id == null)
{
return NotFound();
}
// I just renamed the variable to speak what it represents.
var intermediaryViewModel = await _context.Intermediaries.Include(i => i.Branches).Select(IntermediaryViewModel.AsIntermediaryViewModel)
.FirstOrDefaultAsync(m => m.IntermediaryID == id);
if (intermediaryViewModel == null)
{
return NotFound();
}
return View(intermediaryViewModel);
}
private void PopulateBranchDropDownList(object selectedBranch = null)
{
ViewBag.BranchList = from d in _context.Branch
orderby d.Name
select new BranchViewModel
{
BranchID = d.BranchID,
Name = d.Name
}.ToList();
ViewBag.BranchID = selectedBranch; //Set to some predefined BranchID selection, so when page is loaded, dropdown will be defaulted to this value.
}
Please use the same for other controller actions. No need to change anything in the views as they are already taking ViewModels except that Branch dropdown.
Please notice I have replaced DepartmentID with BranchID
#{
var Branchist = new SelectList(ViewBag.LocList, "Id", "Text");
int? BranchID = ViewBag.BranchID ?? (int ?)null; // please test this line.
}
<div class="form-group">
<label class="control-label col-md-2" for="BranchID">Branch</label>
<div class="col-md-10">
#Html.DropDownList(#BranchID, #Branchist , "Select Branch", new {#class="form-group"})
#Html.ValidationMessageFor(model => model.BranchID)
</div>
</div>

Using a viewmodel and passing a query to the same view

I am new to mvc architecture. I want to create a view with a form to store data to db and a division to show the details using a query.This view is using a viewmodel- Add_session_ViewModel.
The issue is that if I am including the viewmodel to view there is an error in display section and if I am including the list there is a error in form.
The codes are as follows:
CONTROLLER:
public ActionResult Add_session()
{
//display data
var query =( from a in db.Session_details_feedback
join b in db.Employee_Details_Feedback on a.Trainer_id equals b.Emp_id
select new
{
a.Session_date,
a.Session_name,
b.Emp_name
} ).ToList();
foreach (var item in query)
{
List<Add_session_ViewModel> sessionList = new List<Add_session_ViewModel>
{
new Add_session_ViewModel { Session_name=item.Session_name,Session_date=item.Session_date,emp_name=item.Emp_name}
};
ViewData.Model = sessionList;
return View(ViewData.Model);
}
VIEWMODEL:
public class Add_session_ViewModel : DbContext
{
public string Session_name { get; set; }
public int Trainer_id { get; set; }
public System.DateTime Session_date { get; set; }
public string emp_name { get; set; }
public IList<Add_session_ViewModel> Session_List { get; set; }
}
VIEW:
#using (Html.BeginForm("Add_session", "Home", FormMethod.Post, new { #class = "form-horizontal" }))
{
<div class="form-group">
#Html.TextBoxFor(x => x.Session_name, new { #class = "form-control", placeholder = " Enter Session name" })
</div>
<div class="form-group">
#Html.TextBoxFor(x => x.Session_date, new { #class = "form-control", placeholder = " Enter Session date" })
</div>
<div class="form-group">
#Html.DropDownListFor(x => x.Trainer_id, ViewBag.TrainerList as IEnumerable<SelectListItem>, "Select Trainer")
</div>
<div class="form-group">
<input id="add" type="submit" value="ADD" />
</div>
}
</div>
</div>
</center>
<div class="table-responsive">
<table class="table">
<thead>
<tr>
<th>Date</th>
<th>Session</th>
<th>Trainer</th>
</tr>
</thead>
#foreach (var item in Model)
{
<tr>
<td>
#Html.DisplayFor(modelItem => item.Session_date)
</td>
<td>
#Html.DisplayFor(modelItem => item.Session_name)
</td>
<td>
#Html.DisplayFor(modelItem => item.emp_name)
</td>
</tr>
}
</table>
I have been looking for a solution for days, still didnt get any.
It would be appreciative if anyone can give me a solution.
Thank you.
my viewmodel is :
public class Add_session_ViewModel
{
public string Session_name { get; set; }
public int Trainer_id { get; set; }
public System.DateTime Session_date { get; set; }
public string emp_name { get; set; }
}
the view use all these properties through a form.
At the same time I need to get the data using the below query and get it displayed on the same view:
var query =( from a in db.Session_details_feedback
join b in db.Employee_Details_Feedback on a.Trainer_id equals b.Emp_id
select new
{
a.Session_date,
a.Session_name,
b.Emp_name
} ).ToList();
I have no ides how to bind the query and viewmodel to the view at the same time
First of all,
Remove the DbContext as base class to your view model,
public class Add_session_ViewModel
{
public string Session_name { get; set; }
public int Trainer_id { get; set; }
public System.DateTime Session_date { get; set; }
public string emp_name { get; set; }
public IList<Add_session_ViewModel> Session_List { get; set; }
}
Then try to map your LINQ query result to directly list of your view model.
public ActionResult Add_session()
{
Add_session_ViewModel model = new Add_session_ViewModel();
var result =(from a in db.Session_details_feedback
join b in db.Employee_Details_Feedback on a.Trainer_id equals b.Emp_id
select new Add_session_ViewModel //<= Note here
{
Session_date = a.Session_date,
Session_name = a.Session_name,
emp_name = b.Emp_name
}).ToList();
model.Session_List = result;
return View(model); //<= Return model to view instead of "ViewData"
}
And then your view must have a view model of
#model FeedBack_Form.Models.Add_session_ViewModel
And change your foreach loop to
#foreach (var item in Model.Session_List)

Post List of lists of RadioButtons MVC

I have an Attendance program in which I want to assign Students to AttendanceTakers. I am using a table where the headers are the AttendanceTakers and the rows are Students and each cell has a RadioButton. It is basically a double array of RadioButtons. My problem is I can't get it to post.
My AttendanceTaker class
public class SessionAttendanceTaker
{
public int Id { get; set; }
[ForeignKey("Session")]
public int SessionId { get; set; }
public Session Session { get; set; }
[Display(Name="Attendance Taker")]
[ForeignKey("User")]
public string AttendanceTakerId { get; set; }
[Display(Name = "Attendance Taker")]
public User User { get; set; }
public List<Student> Students { get; set; }
}
And the Student that is in the course class
public class StudentSession
{
public int Id { get; set; }
[ForeignKey("Session")]
[DisplayName("Session")]
public int SessionId { get; set; }
public Session Session { get; set; }
[ForeignKey("Student")]
[DisplayName("Student")]
public int StudentId { get; set; }
public Student Student { get; set; }
[DisplayName("Credits Awarded")]
public int Credit { get; set; }
}
Student class
public class Student
{
public int Id { get; set; }
[ForeignKey("User")]
public string UserId { get; set; }
[DisplayName("Name")]
public virtual User user { get; set; }
public Student()
{
}
}
The View
#using (Html.BeginForm())
{
<div class="form-horizontal">
<table>
<thead>
<tr>
<th> Name </th>
#{
foreach (var attendanceTaker in Model.SessionAttendanceTakers)
{
<th>#attendanceTaker.User.LastName, #attendanceTaker.User.FirstName </th>
}
}
</tr>
</thead>
<tbody>
#{
//See https://stackoverflow.com/questions/7667495/mvc-radiobuttons-in-foreach to try and clean the foreach
foreach (var studentSession in Model.StudentSessions)
{
<tr>
<td>
#studentSession.Student.User.LastName, #studentSession.Student.User.FirstName
</td>
#foreach (var attendanceTaker in Model.SessionAttendanceTakers)
{
#Html.EditorFor(Model => Model.SessionAttendanceTakers, "StudentsToAttendanceTakersModel", "" + studentSession.StudentId, new { htmlAttributes = new { #class = "form-control" } })
}
</tr>
}
}
</tbody>
</table>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Assign" class="btn btn-default" />
</div>
</div>
</div>
}
and EditorTemplate
#model IEnumerable<SessionAttendanceTaker>
#using Attendance.Models
<td>
#Html.RadioButtonFor(model => model, new { htmlAttributes = new { #class = "form-control" } })
</td>
As an aside I would love to get rid of the foreaches as per this post but since I don't know how many attendance takers or students there will be until runtime I can't figure out how to do that besides for just moving them to the Editor and I don't see a point to that.
Also the Controller
[HttpPost]
public ActionResult Assign(StudentsToAttendanceTakersModel model)
{
return RedirectToAction("Index");
}
I have a breakpoint on the return and the attendanceTakers is null and Student sessions has a count of 0.
Additionally, using FormCollection
public ActionResult Assign(FormCollection o)
only gives me the Students who's RadioButton was clicked but not the AttendanceTaker. If more info is needed let me know. Thanks.
EDIT
Model
public class StudentsToAttendanceTakersModel
{
public IEnumerable<StudentSession> StudentSessions { get; set; }
public IEnumerable<SessionAttendanceTaker> SessionAttendanceTakers { get; set; }
public StudentsToAttendanceTakersModel() { }
}
You're creating radio buttons which do not relate to your model, and you're trying to bind them to a complex object (SessionAttendanceTaker) - a radio button posts back a simple value (and you are not even giving the radio buttons a valid value - the 2nd parameter of RadioButtonFor() is the value).
You are editing data, so you should start by creating view models which represent what you want to display in the view.
public class StudentVM
{
public int ID { get; set; }
public string Name { get; set; }
[Required(ErrorMessage = "Please select an attendance taker")]
public int? SelectedAttendanceTaker { get; set; }
}
public class AttendanceTakerVM
{
public int ID { get; set; }
public string Name { get; set; }
}
public class StudentAttendanceTakersVM
{
public List<StudentVM> Students { get; set }
public IEnumerable<AttendanceTakerVM> AttendanceTakers { get; set; }
}
So that your view will be
#model StudentAttendanceTakersVM
....
#using (Html.BeginForm())
{
<table>
<thead>
<tr>
<th>Student</th>
#foreach(var taker in Model.AttendanceTakers)
{
<th>#taker.Name</th>
}
<th></th>
</tr>
</thead>
<tbody>
#for(int i = 0; i < Model.Students.Count; i++)
{
<tr>
<td>
#Model.Students[i].Name
#Html.HiddenFor(m => m.Students[i].ID)
#Html.HiddenFor(m => m.Students[i].Name)
</td>
#foreach(var taker in Model.AttendanceTakers)
{
<td>#Html.RadioButtonFor(m => m.Students[i].SelectedAttendanceTaker, taker.ID, new { #class = "form-control" })</td>
}
<td>#Html.ValidationMessageFor(m => m.Students[i].SelectedAttendanceTaker)</td>
</tr>
}
</tbody>
</table>
<input type="submit" ... />
}
Your GET method will then initialize an instance of you view model and pass it to the view, for example, for a 'Create' method
public ActionResult Create()
{
var students = db.Students.Select(x => new StudentVM
{
ID = x.Id,
Name = x.User.FirstName + " " + x.User.LastName // adjust as required
}).ToList();
var attendanceTakers = db.SessionAttendanceTakers.Select(x => new AttendanceTakerVM
{
ID = x.Id,
Name = x.User.FirstName + " " + x.User.LastName // adjust as required
});
StudentAttendanceTakersVM model = new StudentAttendanceTakersVM
{
Students = students,
AttendanceTakers = attendanceTakers
};
return View(model);
}
And the POST method will be
public ActionResult Create(StudentAttendanceTakersVM model)
{
if (!ModelState.IsValid)
{
return View(model);
}
// loop through model.Students to get the ID of the Student and its selected AttendanceTaker
// initialize the data models and save to the database
return RedirectToAction("Index");
}

MVC3 Html.DropDownList not binding the selected value when using it inside Html.BeginCollectionItem

I have problem using #Html.DropDownList inside a Html.BeginCollectionItem block. When I create a new record the value I select is returning correctly in HttpPost Action.
But when I try to edit an existing record, the view is not showing the current selected value in the DropDownList.
The Edit view renders partial _EditorForm view, The _EditorForm view is as follows.
...
#foreach (ContactPhone item in Model.phones)
{
Html.RenderPartial("_PhoneEditorRow", item);
}
...
And the _PhoneEditorRow is as follows
#model CRM.Models.ContactPhone
#using CRM.Helpers;
#{
Layout = null;
}
<tr class="editor-table-phones-row">
#using (Html.BeginCollectionItem("phones"))
{
<td valign="top">
#Html.DropDownListFor(model => model.phoneTypeId, new SelectList(Model.phoneTypes, "id", "name"), "--SELECT--")
#Html.ValidationMessageFor(model => model.phoneTypeId)
</td>
<td valign="top">
#Html.EditorFor(model => model.phoneNumber)
#Html.ValidationMessageFor(model => model.phoneNumber)
</td>
<td>
Eliminar
</td>
}
</tr>
This is the ContactPhone class
using System.Collections.Generic;
namespace CRM.Models
{
public class ContactPhone
{
public long? id { get; set; }
public PhoneType phoneType { get; set; }
public byte? phoneTypeId
{
get { return this.phoneType.id; }
set { this.phoneType.id = value; }
}
public string phoneNumber { get; set; }
public List<PhoneType> phoneTypes { get; set; }
public ContactPhone()
{
this.phoneType= new PhoneType();
this.phoneTypes = new List<PhoneType>();
}
}
}
And the controller
...
[Authorize]
public ViewResult Edit(
int id)
{
Contact model = new Contact();
try
{
model = DataProvider.GetContactById(id);
model.parameters = DataProvider.GetContactParameters();
this.LoadContactSelectLists(ref model);
}
catch (Exception ex)
{
ModelState.AddModelError(string.Empty, ex.Message);
}
return View(model);
}
...
private void LoadContactSelectLists(
ref Contact model)
{
if (model.phones.Count > 0)
{
List<PhoneType> phoneTypes = DataProvider.GetAllPhoneTypes();
for (int i = 0; i < model.phones.Count; i++)
model.phones[i].phoneTypes = phoneTypes;
}
}
...

Value of parameters is null

The below code displays list of countries with checkbox for each. The intend is to save which checkbox was checked.
But when the submit button is clicked, in the method
ActionResult Index(UserModel newmodel) in the newmodel, the value of parameters SelectedSecurityGroup, SelectedSecurityObject and SecurityGroups is null.
Any idea what is wrong with this code?
In UserModel.cs
public class UserModel
{
public string SelectedSecurityGroup { get; set; }
public string SelectedSecurityObject { get; set; }
[DisplayName("Security Group")]
public virtual ICollection<SecurityGroup> SecurityGroups { get; set; }
}
public class SecurityGroup
{
public int Id { get; set; }
public string SecurityGroupName { get; set; }
public bool Active { get; set; }
}
In UserController.cs
[HttpGet]
public ActionResult Index()
{
UserModel objUserModel = new UserModel();
List<SecurityGroup> lstSecurityGroup = FillViewBag();
objUserModel.SecurityGroups = lstSecurityGroup;
return View(objUserModel);
}
[HttpPost]
public ActionResult Index(UserModel newmodel)
{
string strtest = "";
//Code to save data
return View(newmodel);
}
private List<SecurityGroup> FillViewBag(UserModel model = null)
{
List<SecurityGroup> lstSecurityGroup = new List<SecurityGroup>();
lstSecurityGroup.Add(new SecurityGroup { Id = 1, SecurityGroupName = "India", Active = true });
lstSecurityGroup.Add(new SecurityGroup { Id = 2, SecurityGroupName = "USA", Active = true });
lstSecurityGroup.Add(new SecurityGroup { Id = 3, SecurityGroupName = "Pakistan", Active = false });
lstSecurityGroup.Add(new SecurityGroup { Id = 4, SecurityGroupName = "Nepal", Active = false });
return lstSecurityGroup;
}
In Index.cshtml
#model Example.User.Web.Models.UserModel
<!DOCTYPE html>
<html>
<head>
<title>Dashboard</title>
<script src="~/Scripts/jquery-1.11.0.js"></script>
<script src="~/Scripts/jquery.unobtrusive-ajax.min.js"></script>
</head>
<body>
<div>
<div>
<div id="lists">
#Html.Partial("SecurityListsView", Model)
</div>
</div>
</div>
</body>
</html>
In SecurityListsView.cshtml
#model Example.User.Web.Models.UserModel
#using (Ajax.BeginForm("Index", "User", new AjaxOptions() { UpdateTargetId = "lists" }))
{
<table>
#{ int i = 0; }
#foreach (var item in Model.SecurityGroups )
{
<tr>
<td>
#Html.CheckBox("fileName", item.Active)
#Html.Hidden("fileId", item.Id)
</td>
<td>
#Html.DisplayFor(modelItem => item.SecurityGroupName)
</tr>
i++;
}
</table>
<input type="submit" name="btn1" value="Save" />
}
Finally got it working. Below is the corrected code:
In SecurityListsView.cshtml
#model Example.User.Web.Models.UserModel
#using (Html.BeginForm("Index", "User", "POST"))
{
<table>
#{ int i = 0; }
#foreach (var newitem in Model.SecurityGroups)
{
<tr>
<td>
#Html.CheckBoxFor(model => model.SecurityGroups[i].Active)
#Html.HiddenFor(model => model.SecurityGroups[i].Id, "Value")
</td>
<td>
#Html.DisplayFor(model => model.SecurityGroups[i].SecurityGroupName)
</tr>
i++;
}
</table>
<input type="submit" name="btn1" value="Save" />
}
Hope it helps someone! :)

Categories