Incremental Sequencing DropDownListFor Binding? - asp.net-mvc

I am using incremental sequencing for a collection of objects in a form. All works fine and dandy except when I need to use DropDownListFor. Lots of questions concerning binding a dropdown and selecting the correct value, which is working fine in my case. However I am unclear on what is supposed to have on the HttpPost action in my controller. Here is my code:
Model
public class WorkRequestList
{
public WorkRequest[] Requests { get; set; }
public Vehicle[] Vehicles { get; set; }
}
View
<% using (Html.BeginForm()) {%>
<% for (var i = 0; i < Model.Requests.Count(); i++) { %>
<%=Html.DropDownListFor(x => x.Requests[i].AssignedTo,new SelectList(Model.Vehicles,"Id","Name",Model.Requests[i].AssignedTo.Id)) %>
<%}%>
<%=Html.SubmitButton("TopSubmit","Submit") %>
<%}%>
Posted Action
[HttpPost]
public ActionResult Schedule(WorkRequestList form)
{
//what goes here?
}
The dropdown lists get populated just fine, they get pre-selected just fine. But on post back form.Requests.AssignedTo is null. I'm assuming the Vehicle Id is being posted back somewhere, but how do I get to that without resorting looping through the Request magic strings:
var id = Request["Requests[" + i + "].AssignedTo"];

Here is an alternate approach, as I could not get sub objects bound either without an explicit modelbinder:
define a new class for your response:
public class WorkRequestResponse
{
public int AssignedTo { get; set; }
}
On the page change it as follows: (I changed request to WorkRequest)
<% for (var i = 0; i < Model.WorkRequest.Count(); i++)
{ %>
<%=Html.DropDownListFor(x => x.WorkRequest[i].AssignedTo, new SelectList(Model.Vehicles, "Id", "Name", Model.WorkRequest[i].AssignedTo.Id))%>
<%}%>
On your controller bind as follows:
public ActionResult Index([Bind(Prefix = "WorkRequest")]List<WorkRequestResponse> AssignedTo)
{
// AssignedTo is now populated
WorkRequestList.WorkRequests = magic_assign_function(AssignedTo);
// manual model validation etc....
}
I would be keen to see if there is a more direct route, as this has plagued me too.

Related

Checkbox adds extra "false" value per selected one

I have this piece of code in my View which belongs to a form
<div class="col-md-10">
#foreach (var l in leads)
{
#: #Html.CheckBox("cbLead", false, new { #value = #l.Id }) #Html.TextBox("worth", "") - #Html.Label(l.Name)
}
</div>
And this is the form with I handle the post:
[HttpPost]
public ActionResult Update(string[] cbLead, double[] worth)
{
// code
}
I have 24 checkboxes, but for each checkbox selected I receive 2 values in the Update method. So for example if I select 3 out of that 24 checkboxes, I receive 27 values in the string[] cblead.
Example with 24 checkboxes:
And this is what I get in the method:
So I receive the value checked and an added false after. Any tips?
That's because the Html.CheckBox helper generates an additional hidden field with the same name and the value false. The reason for that is because if the checkbox is not checked, then no value will be sent to the server and thus the model binder will fail to properly bind to a boolean property on your model. Also notice that the Html.CheckBox helper expects that you are working with boolean values on your models. Your syntax here is incorrect:
#Html.CheckBox("cbLead", false, new { #value = #l.Id })
You seem to be trying to manually set the value attribute of the checkbox (which should not be done when using helpers) to the Id property of your model which I suppose is not boolean but rather a Guid as can be seen from the screenshot.
This is by design and is expected behavior. If you do not want this behavior that you could write your own custom helper or use plain HTML instead.
I suspect that what you need to receive on the server is the list of IDs along with a boolean value corresponding to whether the element was checked or not. For this purpose I suggest you writing the following view model:
public class MyViewModel
{
public IList<LeadViewModel> Leads { get; set; }
}
public class LeadViewModel
{
public Guid Id { get; set; }
public string Name { get; set; }
public string Worth { get; set; }
public bool IsChecked { get; set; }
}
and then:
#for (var i = 0; i < Model.Leads.Count; i++)
{
Html.CheckBoxFor(x => x.Leads[i].IsChecked)
Html.HiddenFor(x => x.Leads[i].Id)
Html.TextBoxFor(x => x.Leads[i].Worth) -
Html.LabelFor(x => x.Leads[i].Name)
}

fetch data of parent view when using paartail view to form submit MVC 4

I have created Comment box in Parent View as a partail view to add comment. below is my Comment model.
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int CMT_ID { get; set; }
private DateTime _date = DateTime.Now;
public DateTime cmd_ad
{
get { return _date; }
set { _date = value; }
}
public string cmd_content { get; set; }
public string t_email { get; set; }
public Nullable<int> SPID { get; set; }
public virtual service_provider service_provider { get; set; }
from partail View I have to submit cmd_content,t_email and SPID.Below is partail view.
#using (Html.BeginForm("AddComment", "Food")){
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
<table>
<tr><td></td> <td>#Html.TextAreaFor(model => model.cmd_content)</td></tr>
<tr><td>Email</td><td>#Html.EditorFor(model => model.t_email)</td></tr>
<tr><td></td> <td>#Html.ValidationMessageFor(model => model.t_email)</td</tr></table><p><input type="submit" value="Comment" class="btn btn-success" /></p>}
I have created action methods for submit data from partail View. Action method Details method for parent View.AddComment is Action method for _Comment partail View.Below is my Controller method.
public ActionResult Details(int id = 0)
{
ImageData details = new ImageData();
var sp_details = (from s in db.service_provider
join p in db.pictures on s.SPID equals p.SPID
join c in db.cities on s.City_ID equals c.City_ID
where s.SPID == id
select new ImageData()
{
Sp_name = s.Sp_name,
SPID = s.SPID,
pic = p.pic
});
return View(sp_details);
}
public ActionResult AddComment()
{
return PartialView("_Comment");
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult AddComment(comment cmt)
{
if (ModelState.IsValid)
{
db.comments.Add(cmt);
db.SaveChanges();
return RedirectToAction("Details", "Food");
}
return PartialView("_Comment", cmt);
}
When Someone add comment partail view should submit cmt_content,t_emil,SPID. My problem is How do I fetch SPID from parent View. It is same as parameter pass to Details Action method. Can Somebody help me to solve this problem.
Actually, the way you have this set up, there won't be anything other than the partial on post, anyways. Posting to an action that returns a partial view, should only be done via AJAX. If you're not using AJAX to post, then you should always return View, or you'll lose all your layout.
That said, you need to step back and understand the platform you're developing on: the Web. The web operates on the HTTP protocol and the TCP/IP protocols, on a lower level. Importantly, all of these are designed to be stateless. The whole idea was to create a mesh network where nodes could come online and drop off without bringing down the rest of the network. To achieve that, no individual server can have intimate knowledge of communication with a particular client, or if that server were to go down, then the client can no longer resume.
At a higher level, this translates into each request being a unique thing, uninformed by any request that proceeded it. When you post, the only thing that exists server-side is what you posted. That variable that existed before is long gone. If you need some value again in the post action, then you need to post it along with every thing else, or rerun whatever logic got you the value in the first place after the post. It's not just going to be there waiting for you.

ASP.NET MVC form handling unknown number of inputs

I'm building an internal page that allows trusted users to change a parameter setup manually through a form. The inputs to this setup are a list of setupparameters (of unknown size), each with a specific list of values. The user can then select a value for all or a subset of the parameters.
I have attempted to illustrate this with my current model for the view
public class SetupModel
{
public List<SetupParameter> Parameters { get; set; }
}
public class SetupParameter
{
public string ParameterName { get; set; }
// list with text=paramvalue, value=paramvalueid
public SelectList ParameterValueList { get; set; }
// id of the selected parametervalue if any
public int? SelectedParameterValueID { get; set; }
}
My current attempt at rendering a view for this:
<% using (Html.BeginForm("Update", "Parameters") {%>
...
<% foreach( var parameter in Model.Parameters ) { %>
<div><%: parameter.ParameterName %></div>
<div><%: Html.DropDownListFor(x => parameter.SelectedParameterValueID, parameter.ParameterValueList, "Please select") %></div>
<% } %>
...
My question is how can I render a view that allows me to submit the form and get a reasonably understandable model back to my form action that will allow me to obtain the list of selected parameter values. I'm not aware of the best practices or tricks here, so I will appreciate any feedback I get :)
You could try using a FormCollection:
public ActionResult Submit(FormCollection formCollection)
{
//Iterate form collection to get fields
return View();
}
You might find this post by Phil Haack useful: Model Binding To A List.
Also note that you'll need to post back an identifier (ParameterName, for example) for each parameter too, so you can indentify which value corresponds to a parameter back in the controller.

Error trying to populate a drop down list during a "GET" create action

I am trying to understand something a bit better with being new to C#, .NET 3.5 and MVC.
I am running through the MVC NerdDinner example and if you look at the ViewModel here: http://nerddinnerbook.s3.amazonaws.com/Part6.htm#highlighter_662935
You can see the Country list and how it gets populated, this seems to work fine but I tried to do a similar thing below using LINQ and I am having problems, with the SelectList approach even though it inherits from the IEnumerable interface.
I have got a task table with a foreign key to a status table. The below code gives me a NullReferenceException when I do a GET on a create action. I can see that an anonymous task object would not have a status set.. so I probably need to check for it, but I dont understand how this is not done for the NerdDinner example??
public class TaskViewModel {
// Properties
public Task Task { get; private set; }
public SelectList Status { get; private set; }
// Constructor
public TaskViewModel(Task task) {
TaskRepository taskRepo = new TaskRepository();
Task = task;
Status = new SelectList(taskRepo.GetStatus(), Task.Status.description);
}
}
//
// GET: /Tasks/Create
public ActionResult Create()
{
Task task = new Task();
return View(new TaskViewModel(task));
}
//Code from TaskRepository
private TaskManagerDataContext db = new TaskManagerDataContext();
public IQueryable<Status> GetStatus() {
return from status in db.Status
orderby status.description
select status;
}
I did another approach using LINQ for the type dropdown and the population of the drop down works but I am yet to test if it selects the correct value once a post is made and the details view is returned. I am also wondering whether this should some how be moved into my repository rather than have a class in my controller doing this sort of thing??
Here is the code:
//In TaskViewModel Class
public IEnumerable<SelectListItem> Types { get; private set; }
//In TaskViewModel constructor
IList<NPType> types = taskRepo.GetTypes().ToList();
Types =
from type in types
select new SelectListItem {
Selected = (type.typeId == task.typeId),
Text = type.description,
Value = type.typeId.ToString()
};
//The TaskForm partial View that is used for the Create action of the TaskController
<p>
<label for="type">type:</label>
<%= Html.DropDownList("Type", Model.Types)%>
<%= Html.ValidationMessage("type", "*") %>
</p>
<p>
<label for="status">status:</label>
<%= Html.DropDownList("Status", Model.Status)%>
<%= Html.ValidationMessage("status", "*") %>
</p>
and the TaskForm view inherits System.Web.Mvc.ViewUserControl
What's in your task constructor? What is the value of .typeId on a newly created task? Is it a null reference?
For the view model sent to your Create view, you shouldn't be trying to set the selected list item unless your task constructor (or other initialization code) sets default values for those properties. If task.typeId is null, then your code that is building the select list will get an error.
I understand that I will get a null value for the type or status if I dont add a value to the newly created task. What I dont understand and which I didnt make clear is the below.
You can see the view model has a Countries property, and its selected value is set to Dinner.Country.. now Dinner.Country is not being set in the create action.. so how come this does not give a null exception?
//viewmodel code
public DinnerFormViewModel(Dinner dinner) {
Dinner = dinner;
Countries = new SelectList(PhoneValidator.Countries, Dinner.Country);
}
//controller code
public ActionResult Create() {
Dinner dinner = new Dinner() {
EventDate = DateTime.Now.AddDays(7)
};
return View(new DinnerFormViewModel(dinner));
}
//view code
<p>
<label for="Country">Country:</label>
<%= Html.DropDownList("Country", Model.Countries) %>
<%= Html.ValidationMessage("Country", "*") %>
</p>
My attempt at trying to understand this better.
//controller code creating a select list in the viewmodel class.
//taskRepo.GetStatus() returns an IQueryable<Status>
Status = new SelectList(taskRepo.GetStatus(), Task.Status);
//MVC Framework SelectList class constructor and ToEnumerable method
public SelectList(IEnumerable items, string dataValueField, string dataTextField, object selectedValue)
: base(items, dataValueField, dataTextField, ToEnumerable(selectedValue)) {
SelectedValue = selectedValue;
}
private static IEnumerable ToEnumerable(object selectedValue) {
return (selectedValue != null) ? new object[] { selectedValue } : null;
}
I can see that SelectList uses its base class of MultiSelectList and that constructor is here:
public MultiSelectList(IEnumerable items, string dataValueField, string dataTextField, IEnumerable selectedValues) {
if (items == null) {
throw new ArgumentNullException("items");
}
Items = items;
DataValueField = dataValueField;
DataTextField = dataTextField;
SelectedValues = selectedValues;
}
When I run this project the html is given as:
<select id="Status" name="Status"><option>NPTaskManager.Models.Status</option>
<option>NPTaskManager.Models.Status</option>
<option>NPTaskManager.Models.Status</option>
<option>NPTaskManager.Models.Status</option>
</select>
Which is to be expected.
If I change the controller code to:
Status = new SelectList(taskRepo.GetStatus(), Task.Status.statusId.ToString(), Task.Status.description);
Then I get a NullReferenceException. Since this is not an ArgumentNullException It seems to me that the root of the exception is not the first SelectList argument. What I am trying to understand is how this all occurs?
Is it because Task.Status needs to be added to Task in the create action of the controller?
I will change this code to use the LINQ approach that I used for the task above, all I am trying to achieve now is some understanding of what is going on.

ASP.NET MVC : Changing model's properties on postback

I've been playing with ASP.NET MVC and ran into something I can't figure out.
Suppose I have an object like this :
public class TestObject
{
public string Name { get; set; }
public int Age { get; set; }
}
And a view page (Create.aspx) like this :
<form action="/Create" method="post">
<p>
<%=Html.TextBox("Name") %>
</p>
<p>
<%=Html.TextBox("Age")%>
</p>
</form>
And on my controller I have these actions :
[AcceptVerbs(HttpVerbs.Get)]
public ActionResult Create()
{
return View(new TestObject { Name = "DefaultName", Age = 10 } );
}
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create(TestObject o)
{
o.Name = "ChangedNameToSomethingElse";
o.Age = 15;
return View(o);
}
The Html.TextBox() method always genereates the textboxes with the default values, even after the postback, where the object is passed back with different properties on its values. Now, granted, I can't think of a real world example why I'd want to do such a thing but I still don't understand why I always end up having textboxes populated with the model's values that were set on the Create action with the AcceptVerbs(HttpVerbs.Get) attribute.
Note : I've tried Html.TextBox("Name", Model.Name) but the result is still the same. And I verified that the Create action with AcceptVerbs(HttpVerbs.Post) actually runs, by passing a value via ViewData to the View.
Also, the udated value is displayed when I output the value with <%=Model.Name %> but again, not on the textbox.
Is there something obvious I'm missing, or is there a reasoning behind this behaviour?
If you bind the result of a post request through the declaration of the method or by UpdateModel or TryUpdateModel to an object such as TestObject, a property called ModelState will get filled in with these values. The HTML helpers such as Textbox will always bind to modelstate over an explicitly passed model object.
I know this was answered a long time ago, but this is more targeted solution that works for me.
[HttpPost]
public ActionResult Create(TestObject o) {
ModelState.Remove("Name");
o.Name = "ChangedNameToSomethingElse";
ModelState.Remove("Age");
o.Age = 15;
return View(o);
}
Hope this helps someone out there.
Try this one:- (hope it will work for you)
[HttpPost]
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create(TestObject o) {
ModelState.Clear();
o.Name = "ChangedNameToSomethingElse";
o.Age = 15;
return View(o);
}

Resources