MVC - Editing a list of objects - asp.net-mvc

I have the following class layout in MVC:
public class ReportModel
{
List<SomeItem> items;
string value;
string anotherValue;
}
now I create a strongly typed view in MVC of this type and make editable text fields to edit each value as well as use a foreach loop to populate text fields to edit the items in the list of someitem.
when I submit to the httppost method the singular values come back fine in the reportmodel object but the list does not get returned in the object. How should this be done?
When I say httppost I am referring to the method that MVC is posting back to
[HttpPost]
public ActionResult EditReport(ReportModel report)
{
// Save the report in here after the update on the UI side
}
View code for posting the list of someitem
if (Model.items != null && Model.items.Count > 0)
{
for (int i = 0; i < Model.items.Count; i++)
{
<div class="editrow">
<div class="edititem">
<div class="editor-label">
#Html.LabelFor(m => m.items.ElementAt(i).propertyOne)
</div>
<div class="editor-field">
#Html.TextBoxFor(m => m.items.ElementAt(i).propertyOne)
#Html.ValidationMessageFor(m => m.items.ElementAt(i).propertyOne)
</div>
</div>
<div class="edititem">
<div class="editor-label">
#Html.LabelFor(m => m.items.ElementAt(i).propertyTwo)
</div>
<div class="editor-field">
#Html.TextBoxFor(m => m.items.ElementAt(i).propertyTwo)
#Html.ValidationMessageFor(m => m.items.ElementAt(i).propertyTwo)
</div>
</div>
<div class="edititem">
<div class="editor-label">
#Html.LabelFor(m => m.items.ElementAt(i).propertyThree)
</div>
<div class="editor-field">
#Html.TextBoxFor(m => m.items.ElementAt(i).propertyThree)
#Html.ValidationMessageFor(m => m.items.ElementAt(i).propertyThree)
</div>
</div>
</div>
}
}

Don't use ElementAt(1) in your lambda expressions => this ruins your input field names. Please read the blog post that Kirill suggested you.
So you could use indexed access:
for (int i = 0; i < Model.items.Count; i++)
{
<div class="editrow">
<div class="edititem">
<div class="editor-label">
#Html.LabelFor(m => m.items[i].propertyOne)
</div>
<div class="editor-field">
#Html.TextBoxFor(m => m.items[i].propertyOne)
#Html.ValidationMessageFor(m => m.items[i].propertyOne)
</div>
</div>
<div class="edititem">
<div class="editor-label">
#Html.LabelFor(m => m.items[i].propertyTwo)
</div>
<div class="editor-field">
#Html.TextBoxFor(m => m.items[i].propertyTwo)
#Html.ValidationMessageFor(m => m.items[i].propertyTwo)
</div>
</div>
<div class="edititem">
<div class="editor-label">
#Html.LabelFor(m => m.items[i].propertyThree)
</div>
<div class="editor-field">
#Html.TextBoxFor(m => m.items[i].propertyThree)
#Html.ValidationMessageFor(m => m.items[i].propertyThree)
</div>
</div>
</div>
}
Of course in order to have indexer access to the collection this assumes that your items property is declared as either List<SomeItem> or SomeItem[]. If it is an IEnumerable<SomeItem> it won't work. So simply change the type of this property on your view model.

Kirill's reference to Scott Hanselman's blog entry is correct, but you're reading it too narrowly. In the example shown, he passes the array to the action method, but it could just as easily be contained within the parent model as well. The same concept applies.
However, one thing to know is that the default model binder does not instantiate nested classes, so it will not create an instance of the List class, which means it will always be null. To fix this, you must instantiate the empty list class in the constructor.
This is only part of the problem, though as the data must be formatted in the correct way for the model binder to bind it. This is where Scott's blog post comes in, as it provides the format needed for the model binder to recognize the data as a list.
This is typically handled for you if you use an EditorTemplate and use Html.EditorFor(m => m.Items) and then have a SomeItem.cshtml EditorTemplate. This deals with the issues of collection item naming (so long as you also use strongly typed helpers in the template as well).

Related

radiobuttonfor grouping razor mvc5

The selected values are not getting posted back when I group my radio buttons,
So, with this the SelectedQuestionId is blank
<div class="row">
<div class="col-md-4">
<div class="form-group">
#Html.HiddenFor(x => x.UserResponses[0].QuestionId)
#Html.RadioButtonFor(x => x.UserResponses[0].SelectedQuestionId, Model.UserResponses[0].QuestionId, new { Name ="selectone"})
#Html.DisplayTextFor(x => x.UserResponses[0].QuestionText)
#Html.HiddenFor(x => x.UserResponses[0].QuestionText)
#Html.HiddenFor(x => x.UserResponses[0].InputType)
#Html.HiddenFor(x => x.UserResponses[0].Points)
</div>
</div>
<div class="col-md-4">
<div class="form-group">
<span class="text-muted">#Html.DisplayTextFor(x => x.UserResponses[0].Points)</span> #Html.LabelFor(x => x.UserResponses[0].Points, new { #class = "text-muted" })
</div>
</div>
</div>
<div class="row">
<div class="col-md-4">
<div class="form-group">
#Html.HiddenFor(x => x.UserResponses[1].QuestionId)
#Html.RadioButtonFor(x => x.UserResponses[1].SelectedQuestionId, Model.UserResponses[1].QuestionId, new { Name = "selectone"})
#Html.DisplayTextFor(x => x.UserResponses[1].QuestionText)
#Html.HiddenFor(x => x.UserResponses[1].QuestionText)
#Html.HiddenFor(x => x.UserResponses[1].InputType)
#Html.HiddenFor(x => x.UserResponses[1].Points)
</div>
</div>
<div class="col-md-4">
<div class="form-group">
<span class="text-muted">#Html.DisplayTextFor(x => x.UserResponses[1].Points)</span> #Html.LabelFor(x => x.UserResponses[1].Points, new { #class = "text-muted" })
</div>
</div>
</div>
But the moment i remove the grouping from the radiobuttonfor then all works well, and the SelectedQuestionId is populated with questionid. So, replacing the RadioButtonFor above with these allow the SelectedQuestionId to be populated when the form is posted.
The code above is based on this question and though I have asked a related question here - this question is entirely separate.
#Html.RadioButtonFor(x => x.UserResponses[0].SelectedQuestionId, Model.UserResponses[0].QuestionId)
#Html.RadioButtonFor(x => x.UserResponses[1].SelectedQuestionId, Model.UserResponses[1].QuestionId)
found a solution to this rather intractable problem.
#Html.RadioButtonFor(x => x.UserResponses[0].SelectedQuestionId, Model.UserResponses[0].QuestionId)
#Html.RadioButtonFor(x => x.UserResponses[1].SelectedQuestionId, Model.UserResponses[1].QuestionId)
with the code above each radiobutton had a unique name during the deserialization process the SelectedQuestionId property would be populated for a given item if it was selected as one would expect. However, for the radiobutton to belong to a group they would need to have the same name attribute and since this was not the case in the above scenario one could select both the radio buttons. Which also meant that with multiple radiobuttons being selcted selectedId property for all the selected radio buttons would be populated with their respective QuestionId.
To get around this one could always write code as so,
#Html.RadioButtonFor(x => x.UserResponses[0].SelectedQuestionId, Model.UserResponses[0].QuestionId, new { Name = "selectone"})
#Html.RadioButtonFor(x => x.UserResponses[1].SelectedQuestionId, Model.UserResponses[1].QuestionId, new { Name = "selectone"})
And, now one gets the ability to select only one radiobutton but then during the deserialization process in the HttpPost method the SelectedQuestionId property for each item in the collection would be null. Obviously, the name of the radiobuttons were now "selectone" and so in the key/value pairing for SelectedQuestionId the value would not be getting populated, in other words the SelectedQuestionId would always be null even for the radio button that was selected.
So, the solution was the following:
#Html.RadioButtonFor(x => x.SelectedId, Model.UserResponses[i].QuestionId)
x.SelectedId lies in the ParentModel, and gets populated with the QuestionId of the question that is selected and since both the radio button share the same name they therefore belong to the same group and hence only one of the radio buttons can be selected.

Html.DisplayFor not posting values to controller in ASP.NET MVC 3

I am using ASP.NET MVC 3 with Razor, below is a sample from my view code.
The user should be able to edit all their details, except the "EmailAddress" field. For that field only I have used Html.DisplayFor(m => m.EmailAddress).
But when this form gets posted, all the model properties are filled except the EmailAddress.
How do I get the email back in the model when posting? Should I have used some helper other than DisplayFor?
#using (Html.BeginForm()) {
#Html.ValidationSummary(true, "Account update was unsuccessful. Please correct any errors and try again.")
<div>
<fieldset>
<legend>Update Account Information</legend>
<div class="editor-label">
#Html.LabelFor(m => m.EmailAddress)
</div>
<div class="editor-field">
#Html.DisplayFor(m => m.EmailAddress)
#*#Html.ValidationMessageFor(m => m.EmailAddress)*#
</div>
<div class="editor-label">
#Html.LabelFor(m => m.FirstName)
</div>
<div class="editor-field">
#Html.TextBoxFor(m => m.FirstName)
#Html.ValidationMessageFor(m => m.FirstName)
</div>
<div class="editor-label">
#Html.LabelFor(m => m.LastName)
</div>
<div class="editor-field">
#Html.TextBoxFor(m => m.LastName)
#Html.ValidationMessageFor(m => m.LastName)
</div>
<p>
<input type="submit" value="Update" />
</p>
</fieldset>
</div>
}
Please advise me on this.
you'll need to add a
#Html.HiddenFor(m => m.EmailAddress)
DisplayFor won't send anything in POST, it won't create an input...
By the way, an
#Html.HiddenFor(m => m.Id) // or anything which is the model key
would be usefull

Html.RenderAction in MVC with EntityFramework

My problem is when I try to render a view with two Html.RenderAction. It says: "The operation cannot be completed because the DbContext has been disposed".
I have Ninject configured in this way:
Bind<IUnitOfWork>().To<UnitOfWork>().InRequestScope;
But If I do in the default way...
Bind<IUnitOfWork>().To<UnitOfWork>()
there's no error.
I have to work with it in RequestScope (so I think), but how can I do it? It seems's that when second Html.RenderAction is called the previous DbContext it's disposed !
UPDATED:
This is the main view (summarized for brevity)
#model FoodAway.Model.Product
#Html.ValidationSummary(true)
<fieldset>
<legend>Producto</legend>
#using (Html.BeginForm())
{
<div class="editor-label">
#Html.LabelFor(model => model.Name)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Name)
#Html.ValidationMessageFor(model => model.Name)
</div>
#Html.HiddenFor(model => model.Id)
<p>
<input type="submit" value="Guardar" />
</p>
}
<fieldset>
<div class="editor-label">
#Html.LabelFor(model => model.Ingredients)
</div>
<div class="editor-field">
#{Html.RenderAction("IngredientsToRemoveList", "Ingredients");}
</div>
</fieldset>
<fieldset>
#{Html.RenderAction("IngredientsToAddList", "Ingredients");}
</fieldset>
</fieldset>
and his controller/action:
public ActionResult EditProduct(string name)
{
Product product = unitOfWork.ProductRepository.Get(i => i.Name ==name).FirstOrDefault();
if (product == null)
return HttpNotFound();
return View(product);
}
So, the error in DBContext is when I have this 2 RenderAction methods, the strange thing is if I have only 1 RenderAction there is no problem!!!!!
You need to enumerate the set before passing it to the view. This means that you query the database within the valid scope of the DbContext.
You can do this by using .ToArray() in your controller

Form not 'POST'ing with a FormMethod.Post attribute

I have a form in a view like so:
#using (Html.BeginForm("CreateDTActionBasedOnSelectedMetaAction", "TestCase", FormMethod.Post))
And an action method with the following signature:
[AcceptVerbs( new string[]{"GET","POST"})]
public void CreateDTActionBasedOnSelectedMetaAction(FormCollection fc)
However, when the 'submit' button (located in the form) is clicked, it comes to the action method, but the Request.HttpMethod property shows a "GET", and obviously the form data is then not available in the FormCollection object as it wasn't posted.
Any thoughts?
UPDATE:part of the View:
#using (Html.BeginForm("CreateDTActionBasedOnSelectedMetaAction", "TestCase", FormMethod.Post)){
<fieldset>
<legend>Test Case</legend>
<div class="editor-label">
#Html.LabelFor(model => model.Name)
</div>
<div class="editor-field">
<p>#DTContext.CurrentTestCase.Name</p>
</div>
<div class="editor-label">
#Html.LabelFor(model => model.Criteria)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Criteria)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.DTTestCaseReqSet.DTMetaReqProcessor.DTMetaActions)
</div>
<div class="editor-field">
#Html.ListBox("MyListBox", new SelectList(Model.DTTestCaseReqSet.DTMetaReqProcessor.DTMetaActions.Where(p => p.Enabled == true), "NameWithID", "NameWithID"));
</div>
<p>
<input type="submit" value="Select" />
</p>
</fieldset>
}
UPDATE2:
Okay that was silly. Turns out that the app has a custom routing system written by another dev, which expected a certain parameter in the query string to be preserved, which my code wasn't doing. This resulted in the routing system taking the POST from the form, being unable to find a suitable method, it converted it to a GET, which then found my actionmethod.
I would request this question to be deleted.
Create two action methods. One for get and one for post.
[HttpPost]
public void CreateDTActionBasedOnSelectedMetaAction(FormCollection fc)
[HttpGet]
public void CreateDTActionBasedOnSelectedMetaAction()

ASP.NET MVC: Using EditorFor() with a default template for enums

I've written an EnumDropDownFor() helper which I want to use in conjunction with EditorFor(). I've only just started using EditorFor() so am a little bit confused about how the template is chosen.
My Enum.cshtml editor template is below:
<div class="editor-label">
#Html.LabelFor(m => m)
</div>
<div class="editor-field">
#Html.EnumDropDownListFor(m => m)
#Html.ValidationMessageFor(m => m)
</div>
Short of explicitly defining the template to use, is there any way to have a default template which is used whenever an Enum is passed in to an EditorFor()?
You may checkout Brad Wilson's blog post about the default templates used in ASP.NET MVC. When you have a model property of type Enum it is the string template that is being rendered. So you could customize this string editor template like this:
~/Views/Shared/EditorTemplates/String.cshtml:
#model object
#if (Model is Enum)
{
<div class="editor-label">
#Html.LabelFor(m => m)
</div>
<div class="editor-field">
#Html.EnumDropDownListFor(m => m)
#Html.ValidationMessageFor(m => m)
</div>
}
else
{
#Html.TextBox(
"",
ViewData.TemplateInfo.FormattedModelValue,
new { #class = "text-box single-line" }
)
}
and then in your view simply:
#Html.EditorFor(x => x.SomeEnumProperty)

Resources