How can I use EditorFor with ViewData as source? - asp.net-mvc

I setup a ViewData on my Controller such as:
ViewData["Registrations_Services"] = GetServices(); // return a List<Services>
Then, I'd like to show it in my View. Before, I was iterating using the model:
#Html.EditorFor(m => m.Services)
But now how can I do the same from ViewData? Tried with:
#foreach (var item in ViewData["Registrations_Services"] as IList<MyProject.Models.Services>)
{
#Html.EditorFor(item)
}
But the type arguments are different and of course it doesn't works.
Tried also:
#Html.EditorFor(m => ViewData["Registrations_Services"] as IList<MyProject.Models.Services>)
But it says
Templates can be used only with field access, property access, single-dimension array index, or single-parameter custom indexer expressions.
I need to apply the editor template Services.cshtml for each item:
<div class="form-group">
#Html.Label(Model.Description, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-6 checkbox">
#Html.HiddenFor(m => m.Id)
#Html.HiddenFor(m => m.Name)
#Html.HiddenFor(m => m.RegistrationId)
#Html.HiddenFor(m => m.Description)
#Html.EditorFor(m => m.Enabled)
</div>
<div class="col-md-4"></div>
</div>
That's why I was using #Html.EditorFor(m => m.Services).

You can use the non-strong typed version, using the name of the ViewData key
#Html.Editor("Registrations_Services")
Note that this will generate
<input name="Registrations_Services[0].Id" ... />
<input name="Registrations_Services[0].Name" ... />
....
<input name="Registrations_Services[1].Id" ... />
<input name="Registrations_Services[1].Name" ... />
etc (i.e. prefixed with the Viewdata key), so the POST method needs to include a parameter with the same name as the ViewData key to be correctly bound
IEnumerable<Services> Registrations_Services
Having said that, you are editing data, so you should always use a view model, not data models in your view, and the view model would contain a IEnumerable<Services> Services property, making your use of ViewData unnecessary.

Related

How can I use HashSet instead of List iterating a collection on the MVC's View?

I were using a List<> iterating some collection I have in my ASP.NET MVC Model. This is the View:
#for (int i = 0; i < Model.Accessories.Count; i++)
{
#Html.EditorFor(model => model.Accessories[i])
}
Which iterate this template:
<div class="form-group">
#Html.Label(Model.Description, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-6 checkbox">
#Html.HiddenFor(m => m.Id)
#Html.HiddenFor(m => m.Name)
#Html.HiddenFor(m => m.RegistrationId)
#Html.HiddenFor(m => m.Description)
#Html.EditorFor(m => m.Enabled)
</div>
</div>
Having this m.Enabled Hint template:
#(Html.Kendo().RadioButtonFor(m => m).Label("Yes").Value(true).HtmlAttributes(new { #class = "rb-observable" }))
#(Html.Kendo().RadioButtonFor(m => m).Label("No").Value(false).HtmlAttributes(new { #class = "rb-observable" }))
The problem! For some reason, I've needed to change the Accessories collection type from:
Accessories = new List<Accessories>();
To:
Accessories = new HashSet<Accessories>();
(Due to how EntityFramework natively treat tables; it uses HashSet instead of List).
Now, I can't use #Html.EditorFor(model => model.Accessories[i]), since its not indexable.
And now the View mess the whole radio buttons/id/naming for the inputs.
How can I fix/translate it?
The EditorFor() methods accepts IEnumerable<T> as the model and correctly generates the correct html for each item it the collection based on the EditorTemplate.
Do not generate the html within a loop. It simply needs to be
#Html.EditorFor(m => m.Accessories)
Having said that, it should be irrelevant that you data model collection is HashSet. Your editing data so you should always be using view model, not your data model - refer What is ViewModel in MVC?.

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.

MVC - Editing a list of objects

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).

Why the model has no values when I delete?

I have my values in LabelFors but when I do a postback the model has default values.
#using (Html.BeginForm("Delete", "Event", FormMethod.Post))
{
<p>
#Html.LabelFor(m => m.EventID, "Event ID")<br />
#Html.LabelFor(m => m.EventID, Model.EventID.ToString())
</p>
<p>
#Html.LabelFor(m => m.DayCode, "Type of Event")<br />
#Html.LabelFor(m => m.DayCode, Model.DayCode.ToString())
</p>
<p>
#Html.LabelFor(m => m.EventDate, "Date of Event")<br />
#Html.LabelFor(m => m.EventDate, Model.EventDate.ToShortDateString())
</p>
<p>
#Html.LabelFor(m => m.Subject, "Person or Interest")<br />
#Html.LabelFor(m => m.Subject, Model.Subject)
</p>
<p>
#Html.LabelFor(m => m.EventDesc, "Description")<br />
#Html.LabelFor(m => m.EventDesc, Model.EventDesc)
</p>
<table>
<tr>
<td>
#Html.ActionLink("Back", "Index", new { month = Model.EventDate.Month,
year = Model.EventDate.Year,
day = Model.EventDate.Day})
</td>
<td>
<input id="delete" type="submit" value="Submit Changes" />
</td>
</tr>
</table>
}
public ActionResult Delete(int eventID, int year, int month, int day)
{
//Find Event then return to Index
EventModel thisEvent = EventRepository.getDayEvent(eventID, year, month, day);
return View(thisEvent);
}
[HttpPost]
public ActionResult Delete(EventModel deletedEvent) //here Model only has 0, "", and 1/1/0001
{
EventRepository.DeleteEvent(deletedEvent);
return RedirectToAction("Index", new { year = deletedEvent.EventDate.Year, month = deletedEvent.EventDate.Month, day = deletedEvent.EventDate.Day });
}
Your model is empty because your form contains no data. Labels aren't considered to be data. They are labels for data. What you need is this:
#Html.LabelFor(m => m.Subject, "Person or Interest")<br />
#Html.DisplayFor(m => m.Subject)
Edit: To make that above part clearer, I should stress that glosrob is right in that you only need the EventId in order to process the delete. Everything else is just for displaying the correct information so the user can verify they are deleting the correct record. I just wanted to make the point that labels aren't considered to be form data, they are a visual indication.
There's a few things to note here though:
Firstly, your EventId shouldn't be open to modification, so it should be rendered with #Html.HiddenFor(m => m.EventId). This will render it as a hidden field which the user will be unable to see. That doesn't mean it can't be modified and you should eventually use an AntiForgeryToken to help against that.
Secondly, you don't need to specify the strings for the labels. You can do that on your viewmodel instead with data annotations:
// DisplayName is in System.ComponentModel and not System.ComponentModel.DataAnnotations;
using System.ComponentModel;
public class EventViewModel
{
[DisplayName("Person or Interest")]
public string Subject { get; set; }
}
Then in the view you can just use #Html.LabelFor(m => m.Subject).
Try adding a HiddenFor for each value
#Html.HiddenFor(m => m.EventID)
This will ensure the value is posted back to the server when the form is submitted.
If you need to provide a value, I like the accepted answer provided here
OK, the problem is that your form has only labels, but NO inputs. It's inputs values are sent to server and then ASP.NET MVC binds then to model class.
Instead that (you just have labels, labels are not part of post payload)
#Html.LabelFor(m => m.EventDesc, "Description")<br />
#Html.LabelFor(m => m.EventDesc, Model.EventDesc)
You should have (textbox will be rendered <input /> html tag)
#Html.LabelFor(m => m.EventDesc, "Description")<br />
#Html.TextBox(m => m.EventDesc)
I also agree with comment by #glosrob, you don't have to show/allow edit your ID. It should be up to hidden input, so it will correctly bind to model on POST
#Html.HiddenFor(m => m.EventID)
It looks to me like your postback doesn't pass the item you're trying to delete. You could use HiddenFor(m => m.EventID)to pass your model's ID, or just put it in the form route values:
#using (Html.BeginForm("Delete",
"Event",
new { idToDelete = Model.EventID },
FormMethod.Post))
Of course, to do this, you'll have to change your action method to use only the ID.

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