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");
}
Related
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)
I have the following entity (domain) object and model that contain an enum. The display name appears correctly and works for a EnumDropdownList but for some reason not for the DisplayFor helper, all that is shown is the actual enum name.
Not sure what I am missing, asp.net MVC 5.1 added display name support for this so I shouldn't need to create my own helper methods. See: https://aspnet.codeplex.com/SourceControl/latest#Samples/MVC/EnumSample/EnumSample/Models/Enums.cs
public class Addon
{
public int Id { get; set; }
public AddonType AddonType { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
public bool IsActive { get; set; }
}
public enum AddonType : byte
{
[Display(Name = "Cake Theme")]
CakeTheme,
[Display(Name = "Cake Flavour")]
CakeFlavour,
[Display(Name = "Cupcake Icing")]
CupcakeIcing,
[Display(Name = "Party Addon")]
AddOn
}
MODEL
public class AddonModel
{
public int Id { get; set; }
public AddonType AddonType { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
public int Quantity { get; set; }
public bool IsActive { get; set; }
}
VIEW
<h2>Index</h2>
<p>
#Html.ActionLink("Create New", "Create")
</p>
<table class="table">
<tr>
<th>Type</th>
<th>Name</th>
<th></th>
</tr>
#foreach (var item in Model) {
<tr>
<td>
#Html.DisplayFor(model => item.AddonType)
</td>
<td>
#Html.DisplayFor(model => item.Name)
</td>
<td>
#Html.DisplayFor(model => item.Price)
</td>
<td>
#Html.ActionLink("Edit", "Edit", new { id=item.Id }) |
#Html.ActionLink("Details", "Details", new { id=item.Id }) |
#Html.ActionLink("Delete", "Delete", new { id=item.Id })
</td>
</tr>
}
</table>
Create new folder Views/Shared/DisplayTemplates
Add empty Partial View named Enum, to the folder
Replace Enum View code with:
#model Enum
#if (EnumHelper.IsValidForEnumHelper(ViewData.ModelMetadata))
{
// Display Enum using same names (from [Display] attributes) as in editors
string displayName = null;
foreach (SelectListItem item in EnumHelper.GetSelectList(ViewData.ModelMetadata, (Enum)Model))
{
if (item.Selected)
{
displayName = item.Text ?? item.Value;
}
}
// Handle the unexpected case that nothing is selected
if (String.IsNullOrEmpty(displayName))
{
if (Model == null)
{
displayName = String.Empty;
}
else
{
displayName = Model.ToString();
}
}
#Html.DisplayTextFor(model => displayName)
}
else
{
// This Enum type is not supported. Fall back to the text.
#Html.DisplayTextFor(model => model)
}
Here is the link to detailed article by Shahriar Hossain
Model1:
public class Model1
{
public int Id { get; set; }
public int VariantId { get; set; }
public string Name { get; set; }
public string Language { get; set; }
public IList<Model2> ListModel2{ get; set; }
public VmSysVariantResource()
{
ListModel2=new List<Model2>();
}
}
Model2:
public class Model2
{
public int Id { get; set; }
public int ParamterId { get; set; }
public int ValueId { get; set; }
[Required]
public string SelectedValue { get; set; }
public string ParameterName { get; set; }
public IList<PossibleValue> Values { get; set; }
public IList<SelectListItem> ValuesSelectListItem
{
get
{
var list = (from item in Values
select new SelectListItem()
{
Text = item.ValueName,
Value = item.Id
}).ToList();
return list;
}
set { }
}
}
PossibleValue:
public class PossibleValue
{
public int Id { get; set; }
public string ValueName { get; set; }
}
Now explication about what I want to do:
Suppose we have one Model "Model1". This model have a list of Model2 .
Model2 contains ParameterName and a list of possibleValues.
Now I have form like this:
#using (Ajax.BeginForm("Action", "Controller", null,
new AjaxOptions
{
HttpMethod = "POST"
}))
{
<table class="sample" style="margin: 0 auto; width: 400px">
<tr>
<td>
#Resource.Name:
</td>
<td> #Html.TextBoxFor(c => c.Name)
</td>
</tr>
#foreach (var item in Model.ParamtersToValue)
{
<tr>
<td>
#item.Parameter:
</td>
<td>
#Html.DropDownList("ParamtersToValue", #item.ValuesSelectListItem)
</td>
</tr>
}
</table>
<input type="submit" value="Submit"/>
}
On post method in controller I want to get Model1 with List of ListModel2.
This is my post method:
[HttpPost]
public ActionResult MyAction(Model1 obj)
{ }
I was expected to get on post method Model1 with ListModel2 that will contain SelectedValue chosen from dropdown, but I am not shure how to construct dropdown inside foreach block to enforce model binding to work.
Any suggestion will be appreciated.Thanks..
The default model binder works with indexes. So, to have it model bind, change your foreach loop to a for loop and render the control with an index. Like this:
#for (int i = 0; i < Model.ParamtersToValue.Count; i++) {
#* .. stuff here *#
<td>
#Html.DropDownListFor(x => Model.ParametersToValue[i], Model.ParametersToValue[i].ValuesSelectListItem)
</td>
#* .. stuff here *#
}
This will result in element names such as [0].ParametersToValue, [1].ParametersToValue, etc. The model binder will bind them in order of appearance into an enumerable of your choice.
Would like to create a similar ticketing system.
Having difficulty on how the ticket creator can add new comment.
In the current code below, I am adding new record in the collection and display it as editable. However, it does not bind the whole model, only the hidden idCR.
I thought of additional member in the CRCase model for handling the new comment.e.g. public CRParticipation NewCRParticipantion {get;set;} but I received Invalid Column error because it does not exist in the table.
Any guidance is highly appreciated.
Model:
public class CRCase
{
[Key]
public int idCR { get; set; }
public string CRNo { get; set; }
public DateTime SubmittedDate { get; set; }
public string Responsible { get; set; }
public virtual ICollection<CRParticipation> CRParticipations { get; set; }
}
public class CRParticipation
{
[Key]
public int IdCRParticipation { get; set; }
public string CRStatus { get; set; }
public DateTime UpdatedDate { get; set; }
public string Comments { get; set; }
public int CR { get; set; }
[ForeignKey("CR")]
public CRCase CRCase { get; set; }
}
Controller:
public ActionResult Details(int id = 0)
{
CRCase crcase = db.CRCases.Find(id);
if (crcase == null)
{
return HttpNotFound();
}
else
{
//adding new record for new comments. might not be the proper approach.
crcase.CRParticipations.Add(new CRParticipation());
}
return View(crcase);
}
[HttpPost]
public ActionResult NewComment(CRCase crcase)
{
if (ModelState.IsValid)
{
//code here
return RedirectToAction("Details", new { id = crcase.idCR});
}
return View(crcase);
}
Display and Adding new comment View:
#model CRManagement1.Models.CRCase
#{
ViewBag.Title = "Details";
}
<h2>Details</h2>
#using (Html.BeginForm("NewComment", "CRCase")) {
#Html.ValidationSummary(true)
#Html.HiddenFor(model => model.idCR)
<fieldset>
<legend>CRCase</legend>
<div class="display-label">
#Html.DisplayNameFor(model => model.CRReferenceNo)
</div>
<!-- more fields here t display -->
<div class="display-field">
<table>
<tr>
<th>CRStatus</th>
<th>UpdatedDate</th>
<th>Comments</th>
</tr>
#foreach (var item in Model.CRParticipations)
{
<tr>
#if( item.IdCRParticipation != 0 )
{
<td>
#Html.DisplayFor(modelItem => item.CRStatus)
</td>
<td>
#Html.DisplayFor(modelItem => item.UpdatedDate)
</td>
<td>
#Html.DisplayFor(modelItem => item.Comments)
</td>
}
else
{
<td colspan="3" class="editor-field">
#Html.EditorFor(model => model.CRParticipations.Last().Comments)
#Html.ValidationMessageFor(model => model.CRParticipations.Last().Comments)
</td>
}
</tr>
}
</table>
</div>
</fieldset>
<p>
<input type="submit" value="Post" />
</p>
}
<p>
#Html.ActionLink("Back to List", "Index")
</p>
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")
}
View Output. If you can notice the new comment is inputted but it will not be bonded in the model when "Post" button is clicked.
I have a ViewModel which contains a collection of type of my Model, like so:
public class OrderConfirm
{
public ICollection<QuoteLine> SalesLines { get; set; }
public string Currency { get; set; }
public int EnquiryID { get; set; }
}
My QuoteLine Model looks like so:
public class QuoteLine
{
public int QuoteLineId { get; set; }
public int LostReasonId { get; set; }
public virtual LostReason LostReason { get; set; }
public string ItemName { get; set; }
}
In my View, I then Iterate through each of these QuoteLines, within a form, like so:
#using (Ajax.BeginForm("ConfirmLostOrder", new AjaxOptions()
{
InsertionMode = InsertionMode.Replace,
UpdateTargetId = "LostOrders",
OnBegin = "LostOrderConfirm"
}))
{
<table class="completed-enq-table">
<tr>
<th>
Item Number
</th>
<th>
Reason
</th>
</tr>
#foreach (var sales in Model.SalesLines)
{
<tr>
<td>#sales.ItemName
#Html.HiddenFor(model => sales.QuoteLineID)
</td>
<td>#Html.DropDownListFor(model => sales.LostReasonId, ((IEnumerable<myApp.Models.LostReason>)ViewBag.LostReasons).Select(option => new SelectListItem
{
Text = (option == null ? "None" : option.LostReason),
Value = option.LostReasonId.ToString(),
Selected = (Model != null) && (option.LostReasonId == sales.LostStatusId)
}))
</td>
</tr>
}
</table>
<input type="submit" style="float: right;" value="Submit Lost Order" />
}
Then my HttpPost action looks like so:
[HttpPost]
public ActionResult ConfirmLostOrder(List<QuoteLine> models)
{
// process order
return PartialView("Sales/_ConfirmLostOrder");
}
The problem is, models is null. If I use a FormCollection I can see each of the values submitted but I'd like to use my model and not a FormCollection as I'd like to process and edit each of the line submitted individually as they may have different reason's
You can't use a foreach in this instance, it needs to be a for loop so that the name attributes of the fields contain the correct index so that default model binding knows it's binding to a list.
Firstly, I'm going to move your dropdown values out of the ViewBag (they should really be in there). That'll also take out some of that nasty logic in your view :)
So your model is now:
public class OrderConfirm
{
public List<QuoteLine> SalesLines { get; set; }
public string Currency { get; set; }
public int EnquiryID { get; set; }
public SelectList LostReasons { get; set; }
}
Try this instead of your foreach:
#for (var i = 0; i < Model.SalesLines.Count; i++)
{
<tr>
<td>
#Model.SalesLines[i].ItemName
#Html.HiddenFor(m => m.SalesLines[i].QuoteLineId)
#Html.HiddenFor(m => m.SalesLines[i].ItemName) //assuming you want this
</td>
<td>
#Html.DropDownListFor(m => m.SalesLines[i].LostReasonId, Model.LostReasons)
</td>
</tr>
}
Then change your post method to take your OrderConfirm model type:
[HttpPost]
public ActionResult ConfirmLostOrder(OrderConfirm model)