In my view this is what I have
#foreach (var match in Model.CommonMatches)
{
<tr>
<td>#match.StartDateTime</td>
<td>#match.EndDateTime</td>
<td>#match.AvailableAttendees.Count()</td>
<td>#Html.ActionLink("Accept", "AcceptAppointment", "Appointment", new {commonMatch = #match })</td>
</tr>
}
Model.CommonMatches is of type List<Window>
public class Window
{
public DateTime StartDateTime { get; set; }
public DateTime EndDateTime { get; set; }
public IEnumerable<DataModels.Attendee> AvailableAttendees { get; set; }
}
This is how the value is being passed from controller
[HttpGet]
public ActionResult ViewStatus(Guid appointmentId)
{
var status = new ViewStatus
{
AttendeesWhoResponded = _appointmentRepository.GetAppointmentDetails(appointmentId).Attendees.Where(a=>a.HasResponded == true).ToList(),
NotAttending = _appointmentRepository.GetAppointmentDetails(appointmentId).Attendees.Where(a=>a.HasResponded == true && a.Responses == null).ToList(),
CommonMatches = _appointmentRepository.FindCommonMatches(appointmentId)
};
return View(status);
}
ViewStatus class
public class ViewStatus
{
public ViewStatus()
{
AttendeesWhoResponded = new List<DataModels.Attendee>();
NotAttending = new List<DataModels.Attendee>();
}
public List<DataModels.Attendee> AttendeesWhoResponded { get; set; }
public List<DataModels.Attendee> NotAttending { get; set; }
public IEnumerable<Window> CommonMatches { get; set; }
}
The action in controller that ActionLink of view is calling is:
[HttpGet]
public ActionResult AcceptAppointment(Window commonMatch)
{
return Content("ac");
}
When I inspect the value of commonMatch in controller's action. I'm getting the StartDateTime and EndDateTime but i'm not getting all the value of AvailableAttendees. It is currently shown as null.
AvailableAttendees that I'm expecting is of type IEnumerable<Attendee>. Is is not possible to pass the object the way I'm passing?
What should I do, to also get all the values of AvailableAttendees in controller along with dates?
Edit 1:
<table class ="table-hover table-striped">
<thead>
<tr>
<td>Start time</td>
<td>End time</td>
<td>Number of Attendees</td>
</tr>
</thead>
#for (var count = 0; count < Model.CommonMatches.Count();count++ )
{
using (Html.BeginForm("AcceptAppointment", "Appointment", FormMethod.Post))
{
<tr>
<td>#Model.CommonMatches[count].StartDateTime</td>
<td>#Model.CommonMatches[count].EndDateTime</td>
<td>#Model.CommonMatches[count].AvailableAttendees.Count()</td>
#*<td>#Html.ActionLink("Accept", "AcceptAppointment", "Appointment", new { commonMatch = #match })</td>*#
#for(var j=0;j<Model.CommonMatches[count].AvailableAttendees.Count();j++)
{
<td>#Model.CommonMatches[count].AvailableAttendees[j].FirstName</td>//to check if the value is null or not, just a test
<td>#Html.HiddenFor(m=>Model.CommonMatches[count].AvailableAttendees[j].FirstName)</td>
<td>#Html.HiddenFor(m=>Model.CommonMatches[count].AvailableAttendees[j].LastName)</td>
<td>#Html.HiddenFor(m=>Model.CommonMatches[count].AvailableAttendees[j].Email)</td>
<td>#Html.HiddenFor(m=>Model.CommonMatches[count].AvailableAttendees[j].AttendeeId)</td>
}
<td><input type="submit" value="Accept"/></td>
</tr>
}
}
</table>
You need to post your model back, this would involve changing your controller method to this:
Controller
[HttpPost]
public ActionResult AcceptAppointment(List<Window> model)
{
return Content("ac");
}
View
You view would need a form and a submit button rather than an ActionLink. I have take the table formatting out to simplify the below.
Indexing your collections with a for loop so the model binder knows how to handle them, this is actually two loops as it is a collection within a collection. The hidden values have to be rendered too in order to be posted back (please forgive any typos).
#for(var i = 0; i < Model.CommonMatches.Count; i ++)
{
<div>
#using (Html.BeginForm("AcceptAppointment", "Appointment", FormMethod.Post)
{
#Html.HiddenFor(m => Model.CommonMatches[i].StartDateTime)
#Html.HiddenFor(m => Model.CommonMatches[i].EndDateTime)
#Model.CommonMatches[i].StartDateTime <br/>
#Model.CommonMatches[i].EndDateTime <br/>
#for(var j = 0; Model.CommonMatches[i].AvailableAttendees.Count; j++)
{
#Html.HiddenFor(m => Model.CommonMatches[i].AvailableAttendees[j].Prop1)<br/>
#Html.HiddenFor(m => Model.CommonMatches[i].AvailableAttendees[j].Prop2)<br/>
}
<input type="submit" value="Accept" />
</div>
}
}
There are plenty of things you need to taken care
<td>#Html.ActionLink("Accept", "AcceptAppointment", "Appointment", new {commonMatch = #match })</td>
Calls
[HttpGet]
public ActionResult AcceptAppointment(Window commonMatch)
{
return Content("ac");
}
Here you are navigating using a link <a href>. Basically you are issuing a get request. In get request, you can pass the data to server only via Query String. But your case, preparing a query string dynamically before navigating to url is bit more complex. But you can do it with a JavaScript like onclick=prepareHref(this);
#Html.ActionLink("Accept", "AcceptAppointment", "Appointment",
new {commonMatch = #match }, new {onclick=prepareHref(this)})
Then in Javascript
function prepareHref(obj)
{
var qsData="?StartDateTime='2014-02-25'&EndDateTime='2014-02-25'&AvailableAttendees[0].prop1=value1, etc"; // data should be obtained from other td elements
obj.href=obj.href+qsData;
}
But this is not a suggested way of doing it.
In case, if you want to open other page and show the url, better pass the id and load data again.
Option 1:
The better way could be submit the detail in the hidden field as explained by #hutchonoid.
Option 2:
or submit the details in jQuery ajax $.post method. Either way you need to use POST
#Html.ActionLink("Accept", "AcceptAppointment", "Appointment",
new {commonMatch = #match }, new {onclick=postMyData()})
function postMyData(){
var postData={};
postData.StartDateTime='';
postData.EndDateTime='';
postData.AvailableAttendees=[];
//for each AvailableAttendees prepare object
postData.AvailableAttendees[0]= {};
postData.AvailableAttendees[0].prop1=value1;
$.post('/Appointment/AcceptAppointment/',{data:postData},function(data){
});
return false;
}
[HttpPost]
public ActionResult AcceptAppointment(Window commonMatch)
{
return Content("ac");
}
Related
I am having difficulty passing an IEnumerable as a model. The data is populating a form on one page - and doing so correctly. Upon submission the model returns as null.
I've seen various posts on this and they mostly reference naming-conventions so I have attempted different methods of naming the parameters to try to avoid any confusion in the model binding.
I have also tried various models and helpers to try and pass the data and all have the same result.
Current implementation:
Models:
public class UserProfileListModel
{
public IEnumerable<UserProfileViewModel> UserProfileViewModels { get; set; }
}
public class UserProfileViewModel
{
public UserProfile UserProfile { get; set; }
public Role UserRole { get; set; }
public Team UserTeam { get; set; }
public Scope UserScope { get; set; }
}
View:
#model Project.WebUI.Models.UserPRofileListModel
SNIP
<fieldset>
<legend>Administrate Users:</legend>
<table class="adminTbl">
<thead>
<tr>
<th>UserName:</th>
<th>Role:</th>
<th>Team:</th>
<th>Scope:</th>
<th>Update:</th>
<th>Delete:</th>
</tr>
</thead>
<tbody>
#{foreach (var user in Model.UserProfileViewModels)
{
<tr>
<td>
<p>#user.UserProfile.UserName
#{if (!user.UserProfile.Membership.IsConfirmed)
{
using (Html.BeginForm("Confirm", "Account", FormMethod.Post, null)){
#Html.AntiForgeryToken()
#Html.Hidden("Token", user.UserProfile.Membership.ConfirmationToken)
#Html.Hidden("Name", user.UserProfile.UserName)
}
<input type="submit" value="Confirm" />}
}
</p>
</td>
#{using (Html.BeginForm("SaveUserChanges", "Account", FormMethod.Post, null))
{
#Html.AntiForgeryToken()
#Html.HiddenFor(u => user.UserProfile)
if (user.UserProfile.UserName != User.Identity.Name && user.UserProfile.Membership.IsConfirmed)
{
<td>
#Html.DropDownListFor(u => user.UserRole, Project.WebUI.Controllers.AccountController.RoleList, new { #class = "formdrop" })
</td>
<td>
#Html.DropDownListFor(u => user.UserTeam, Project.WebUI.Controllers.AccountController.TeamList, new { #class = "formdrop" })
</td>
<td>
#Html.DropDownListFor(u => user.UserScope, Project.WebUI.Controllers.AccountController.ScopeList, new { #class = "formdrop" })
</td>
<td>
<input type="submit" value="Save Changes" onclick="return confirm('Are you sure you wish to update this user? ')" />
</td>
}
else
{
/*If user is self or not yet confirmed these are here to buffer the delete button into the last cell*/
<td></td>
<td></td>
<td></td>
<td></td>
}
}
}
<td>
#Html.ActionLink("Delete", "Delete", new { user.UserProfile.UserId }, new
{
onclick = "return confirm('Warning: Action cannot be undone. Are you sure you wish to permanently delete this entry?')"
})
</td>
</tr>
}
}
</tbody>
</table>
</fieldset>
Controller:
Populate View:
public ActionResult AdministrateUsers()
{
populateLists();
var query = repository.UserProfiles.OrderBy(e => e.UserName);
List<UserProfileViewModel> list = new List<UserProfileViewModel>();
foreach(UserProfile up in query)
{
UserProfileViewModel vm = new UserProfileViewModel() { UserProfile = up };
list.Add(vm);
}
UserProfileListModel models = new UserProfileListModel()
{
UserProfileViewModels = list.OrderBy(up => up.UserProfile.UserName)
};
return View(models);
}
Accept Post:
public ActionResult SaveUserChanges(UserProfileListModel model)
{
foreach (UserProfileViewModel upvm in model.UserProfileViewModels)
{
UserProfile up = new UserProfile()
{
UserId = upvm.UserProfile.UserId,
UserEmail = upvm.UserProfile.UserName,
UserName = upvm.UserProfile.UserName
};
if (ModelState.IsValid)
{
repository.SaveUserProfile(up);
}
else
{
return View(model);
}
}
return RedirectToAction("Index", "Admin");
}
The code does still need a lot of work but I can't get past getting the model back to the controller on post. I have also tried returning the UserProfileViewModel instead of the entire list.
Can anyone tell what I am doing wrong?
Thanks!
You have a lot of invalid html including form elements as child elements of tr elements and duplicate id attributes. If you want to post back UserProfileListModel then you need a single form element and use an EditorTemplate or a for loop (not foreach) to render the controls so they are correctly named with indexers.
You are also trying to bind your dropdown lists to complex objects (for example UserProfile, Role etc.). <select> elements (and all form controls) only post back key/value pairs so you need to bind to a value type (for example UserProfile.UserId).
Your SaveUserChanges() post method is also trying access properties of UserProfile but you don't even have controls for properties of UserProfile in the form that post back to this method (for example UserId = upvm.UserProfile.UserId, UserEmail = upvm.UserProfile.UserName, ...) so they will always be null.
You probalby need to bind properties in POST method like here:
public ActionResult Create([Bind(Include = "Id,Subject,Text,IsImportant")] Announcment announcment) {... }
So it will be:
public ActionResult SaveUserChanges([Bind(Include = "UserProfile,Role,UserTeam,UserScope")]UserProfileListModel model)
Have you specified your action method is for HTTP Post? And change your action method to accept UserProfileViewModels instead.
[HttpPost]
public ActionResult SaveUserChanges(UserProfileViewModels model)
{
You are also only posting back one model: UserProfileViewModels.
You have your form in your foreach loop, so each UserProfileViewModels has its own form. If you want to change it to post back your UserProfileListModel, move
#{using (Html.BeginForm("SaveUserChanges", "Account", FormMethod.Post, null))
outside of your foreach.
I have the following viewModel
public class ExerciceViewModel
{
public string Code { get; set; }
public string Titre { get; set; }
public int QuestionCourante { get; set; }
}
the following view
#model MonEcoleVirtuelle.ViewModel.ExerciceViewModel
#{
ViewBag.Title = "Test";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h2>Test</h2>
#using (Html.BeginForm("test", "exercice", FormMethod.Post))
{
#Model.Code <br />
#Model.Titre<br />
#Model.QuestionCourante<br />
<br />
<br />
Model.Code = "Code Modifie";
<input type="submit" value="Post Moi Ca!" name="fini" />
}
and the following controller methods
[HttpPost]
public ActionResult Test(ViewModel.ExerciceViewModel model)
{
if (ModelState.IsValid)
{
return Content(model.Code);
}
return View(model);
}
[HttpGet]
public ActionResult Test()
{
var vm = new ViewModel.ExerciceViewModel { Code = "code1", Titre = "Mon titre", QuestionCourante = 1 };
return View(vm);
}
When I submit the form, the model passed is empty, all properties are reset, not keeping the original values. What am I missing.
thanks
well, instead of #Model.Code which just display the values, you need some inputs.
So #Html.TextBoxFor(m => m.Code) for example
To manage a collection, you can do something like that :
#for (var i = 0; i < Model.Collection.Count; i++) {
Html.TextBoxFor(m => Model.Collection[i].Property)
}
You have not included any input fields in your view.
The #Model.Code etc only output the value of the field. To be able to post back elements they need to be form elements, like inputs. Use something like
#Html.TextBoxFor(p=>p.Code)
to create input fields that can then be posted back.
For a more complete guide see MSDN at http://msdn.microsoft.com/en-us/library/dd410596(v=vs.100).aspx
I like to do one simple control for day transactions.
That need to show me how much transaction we have this day (close or open trans) and when the manager click the number of transaction I would like to send all this list of transactions into a nice table.
But I can't find the way to do it. Not even across the web.
here is my try but nothing.
I am open for suggestion =)
Here is my ViewModel
public class SIMcontrolsViewModel
{
public DTControls DTransactionControl { get; set; }
}
public class DTControls
{
public DateTime TDate { get; set; }
public int NumOfTransaction { get; set; }
public List<SIMsale> SIMsaleList { get; set; }
public DTControls()
{
SIMsaleList = new List<SIMsale>();
}
}
the Controller look like I fill all the data and I sent it to view
[AdminAuthorization]
public ActionResult DateTransactionsControl(DateTime? date)
{
SIMcontrolsViewModel vm = new SIMcontrolsViewModel();
vm.DTransactionControl = new DTControls();
if (!date.HasValue)
date = DateTime.Now;//.Today;
vm.DTransactionControl.TDate = date.Value;
try
{
using (CompanyContext db = new CompanyContext())
{
var saleLst = db.SIMsales.ToList();
foreach (var sale in saleLst)
{
if (..)
vm.DTransactionControl.SIMsaleList.Add(sale);
}
var tCount = vm.DTransactionControl.SIMsaleList.Count;
vm.DTransactionControl.NumOfTransaction = tCount;
}
}
catch (Exception ex)
{..}
return View(vm);
}
Now on my View I try to send this list from this #Html.ActionLink like we can see here.
#model oCc.IPToGo.ViewModel.SIMcontrolsViewModel
<fieldset>
<table border="0" class="display">
<thead>
<tr>
<th style="width:100px">Date</th>
<th style="width:100px">#Html.DisplayNameFor(model => model.DTransactionControl.NumOfTransaction)</th>
</tr>
</thead>
<tbody>
<tr style="text-align: center">
<td>#Model.DTransactionControl.TDate.ToShortDateString()</td>
#if (Model.DTransactionControl.NumOfTransaction != 0)
{
<td>
#Html.ActionLink(Model.DTransactionControl.NumOfTransaction.ToString(), "../SIMsale/",
new { sell = Model.DTransactionControl.SIMsaleList },
new { style = "font-weight: bold; color: Highlight" })
</td>
}
else
{
<td style="color:red">0</td>
}
</tr>
</tbody>
</table>
</fieldset>
The problem is that the view/controller who supposed to get this list is getting an empty list.
10Q =)
You can simply sent the SIMsales condition like sellerId on your ViewModel and a Hidden field on Razor to keep the data and on pthe post function (on your controller) just retrive all of those SIMsales that you count on the Get func.
(I know that was two year before but maybee that can help)
I would associate an id with the transaction count and pass that back to the controller through an ajax call
$.ajax({
url: "#(Url.Action("Action", "Controller"))",
type: "POST",
cache: false,
async: true,
data: { id: 'associatedID' },
success: function (result) {
$(".Content").html(result);
}
});
create a partial view for displaying the table and on your controller populate and return the partial view using the passed id
public PartialViewResult BuildTable(int id){
Model model = (get results from database for id)
return PartialView("_Partial", model);
}
then put a div on your view with a class of Content (or whatever you want to call it). Hopefully this helps.
I am able to display values in ListBoxFor control from controller to view. However when I move values from one listbox to another and post the view the model on controller side does not preserve the values in the listboxes.
Here is my Model
namespace MvcApplication1.Models
{
public class EmployeeClass
{
public int EmpCode { get; set; }
public string EmpName { get; set; }
}
public class EmployeeViewModel
{
public string Department { get; set; }
public list<EmployeeClass> AvailalableEmployee { get; set; }
public list<EmployeeClass> SelectedEmployee { get; set; }
public int [] AvailableEmpCodeArray { get; set; }
public int [] SelectedEmpCodeArray { get; set; }
}
}
Here is my Controller
namespace MvcApplication1.Controllers
{
public class EmployeeController : Controller
{
public ActionResult EmployeeDisplayTool()
{
EmployeeViewModel model = new EmployeeViewModel();
model.Department = "Techology"
model.AvailalableEmployee.Add(new Employee(1,"Emp1"));
model.AvailalableEmployee.Add(new Scenario(2,"Emp2"));
model.AvailalableEmployee.Add(new Scenario(3,"Emp3"));
model.SelectedEmployee.Add(new Scenario(4,"Emp4"));
model.SelectedEmployee.Add(new Scenario(5,"Emp5"));
model.SelectedEmployee.Add(new Scenario(6,"Emp6"));
return View(model);
}
[HttpPost]
public ActionResult EmployeeDisplayTool(EmployeeViewModel model)
{
//model.Department is populated as "Technology"
//All of the follownig collections are null.
//model.AvailalableEmployee is null.
//model.AvailableEmpCodeArray is null.
//model.SelectedEmployee is null.
//model.SelectedEmpCodeArray is null.
}
}
}
Here is my View and JavaScript
#using (Html.BeginForm())
{
<table id="MainTable" border="0">
<tr>
<td colspan="3">
#Html.TextBoxFor(m => Model.Department, new { id = "txtDepartment" })
</td>
</tr>
<tr>
<td>
#Html.ListBoxFor(m => Model.AvailableEmpCodeArray, new MultiSelectList(Model.AvailalableEmployee, "EmpCode", "EmpName", Model.AvailableEmpCodeArray), new { id = "lbxAvailableEmployees" })
</td>
<td>
<input type="button" id="btnSelectEmployee" value=" >> "/>
<br/>
<br/>
<input type="button" id="btnUnSelectEmployee" value=" << "/>
</td>
<td>
#Html.ListBoxFor(m => Model.SelectedEmpCodeArray, new MultiSelectList(Model.SelectedEmployee, "EmpCode", "EmpName", Model.SelectedEmpCodeArray), new { id = "lbxSelectedEmployees" })
</td>
</tr>
</table>
}
<script type="text/javascript">
$(document).ready(function()
{
$('#btnSelectEmployee').click(function(e)
{
var SelectedEmps = $('#lbxAvailableEmployees option:selected');
if (SelectedEmps.length == 0)
{
e.preventDefault();
}
$('#lbxSelectedEmployees').append($(SelectedEmps).clone());
$(SelectedEmps).remove();
e.preventDefault();
});
$('#btnUnSelectEmployee').click(function(e)
{
var SelectedEmps = $('#lbxSelectedEmployees option:selected');
if (SelectedEmps.length == 0)
{
e.preventDefault();
}
$('#lbxAvailableEmployees').append($(SelectedEmps).clone());
$(SelectedEmps).remove();
e.preventDefault();
});
});
</script>
Inside your HttpPost action you cannot expect the AvailalableEmployee and SelectedEmployee collections to be populated. That's due to the nature of how HTML works. Only the selected values are sent to the server when you submit the form. The text values of the select fields are never sent to your server when you submit the form.
So all you can hope of getting populated inside this HttpPost action is the AvailableEmpCodeArray and the SelectedEmpCodeArray collections. They will contain the values of the selected items in the corresponding select fields. Notice that I have bolded selected. This means that only if you select any items in the corresponding boxes those collections will be bound. If you don't select anything, they will remain null and that's perfectly normal.
Here's an example of the user that has selected nothing. He just used the << and >> buttons to toggle the values between the lists, but please notice how absolutely nothing is selected:
Notice how nothing is selected? So that's all you will get in your controller action - nothing.
Now please compare with the following screenshot:
Do you see the difference? Notice how the user explicitly selected the Emp2, Emp5 and Emp6 values (using the Shift key to select multiple values)? That's what you're gonna get inside your HttpPost action. The AvailableEmpCodeArray collection will contain a single element with the value of 2 and the SelectedEmpCodeArray will contain 2 elements with the values of 5 and 6.
Now to your second problem. If you intend to redisplay the same view from your HttpPost controller action the first thing you need to ensure is that you have assigned values to the AvailalableEmployee and SelectedEmployee collections. This should be done the same way you did in the HttpGet action:
model.AvailalableEmployee.Add(new Employee(1,"Emp1"));
model.AvailalableEmployee.Add(new Scenario(2,"Emp2"));
model.AvailalableEmployee.Add(new Scenario(3,"Emp3"));
model.SelectedEmployee.Add(new Scenario(4,"Emp4"));
model.SelectedEmployee.Add(new Scenario(5,"Emp5"));
model.SelectedEmployee.Add(new Scenario(6,"Emp6"));
or if those values are coming from a database you will have to re-query this database inside the HttpPost action because those values, as I already explained, will never get bound from the view.
How to I convert my simple MVC4 todo list application to AJAX?
The ideal answer would give me the steps that would lead to a successful conversion of this example to AJAX.
Note: I don't need an AJAX tutorial so much as an understanding of how the ASP.NET MVC architecture supports it.
Side Question: Why does #Html.EditorFor(model => model.TodoItemToCreate) bring back the value that was typed in even though the view model sets with this.TodoItemToCreate = null?
Model
public class TodosViewModel
{
List<string> todoItems;
public List<string> TodoItems
{
get { return this.todoItems ?? (todoItems = new List<string>()); }
}
[Display(Name="What do you need to do?")]
public string TodoItemToCreate { get; set; }
public bool AcceptTodoItem()
{
bool isThereAnItemToAccept = !string.IsNullOrWhiteSpace(this.TodoItemToCreate);
if (isThereAnItemToAccept)
{
this.TodoItems.Add(this.TodoItemToCreate);
this.TodoItemToCreate = null;
}
return isThereAnItemToAccept;
}
}
Controller
public class TodosController : Controller
{
public ActionResult Index()
{
return View(new TodosViewModel());
}
public ActionResult Create(TodosViewModel todosViewModel)
{
todosViewModel.AcceptTodoItem();
return View("Index", todosViewModel);
}
}
Index View
#model Programming.LearnWeb.Models.TodosViewModel
#{
ViewBag.Title = "Todos";
}
#using (Html.BeginForm("Create", "Todos"))
{
#Html.Partial("List")
#Html.LabelFor(model => model.TodoItemToCreate)
#Html.EditorFor(model => model.TodoItemToCreate)
<input type="submit" value="Create" />
}
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")
}
List View
#model Programming.LearnWeb.Models.TodosViewModel
#{ int i = 0; }
<table>
#foreach (var todoItem in Model.TodoItems)
{
<tr>
<td>
#Html.Hidden("TodoItems[" + i++ + "]", todoItem)
#todoItem
</td>
</tr>
}
</table>
I got this done on my own - the result is at https://github.com/gabrielgreen/Todos.Mvc if anyone is interested or has any comments.
I put a fair amount of effort in and would appreciate any feedback that indicates if I did it right.