How can I edit child objects in an MVC4 form? - asp.net-mvc

I have the following:
#foreach (var parent in Model.Parents)
{
#foreach (var child in parent.Children)
{
#Html.TextAreaFor(c => child.name)
}
}
How can I get editing to work for child objects? I tried something like this as well:
<input type="hidden" name="children.Index" value="#child.Id" />
<textarea name="children[#child.Id]" >#child.Name</textarea>
To pass an IDictionary to the controller but I get an error:
[InvalidCastException: Specified cast is not valid.]
System.Web.Mvc.CollectionHelpers.ReplaceDictionaryImpl(IDictionary`2 dictionary, IEnumerable`1 newContents) +131
This seems like a very common task... is there a simple solution to this? What am I missing? Do I need to use an Editor Template? If so, any MVC4-compatible examples would be fantastic.

is there a simple solution to this?
Yes.
What am I missing?
Editor templates.
Do I need to use an Editor Template?
Yes.
If so, any MVC4-compatible examples would be fantastic.
ASP.NET MVC 4? Man, editor templates exist since ASP.NET MVC 2. All you need to do is to use them.
So start by getting rid of the outer foreach loop and replacing it with:
#model MyViewModel
#Html.EditorFor(x => x.Parents)
and then obviously define an editor template that will be rendered automatically for each element of the Parents collection (~/Views/Shared/EditorTemplates/Parent.cshtml):
#model Parent
#Html.EditorFor(x => x.Children)
and then an editor template for each element of the Children collection (~/Views/Shared/Editortemplates/Child.cshtml) where we will get rid of the inner foreach element:
#model Child
#Html.TextAreaFor(x => x.name)
everything works by convention in ASP.NET MVC. So in this example I assume that Parents is an IEnumerable<Parent> and Children is an IEnumerable<Child>. Adapt the names of your templates accordingly.
Conclusion: everytime you use foreach or for in an ASP.NET MVC view you are doing it wrong and you should consider getting rid of it and replacing it with an editor/display template.

Related

Is there any way to create a custom MVC Razor element like "text"?

I want to create a custom razor tag like <text></text> to decide what to do with the html code inside of it. Is there any way to create razor elements like <text></text> element and add it to the razor engine?
I don't want to create any HtmlHelpers for this.
For Examle:
<WYSYWIG>
Hello There!
</WYSYWIG>
or
<WeatherChart City="NY">
</WeatherChart>
Explanation:
Well the idea is to have server tags to be translated (Parsed) to html codes by the attributes given to them. This kind of codes helps junior developers not to be involved with the complexity of controls.
The closest thing to what you are describing is to create display or editor templates. You can then define a template for a model and use it with #Html.DisplayFor() in the view.
Here is a good blog post to get you started aspnet mvc display and editor templates and a quick overview of the structure below.
Example
Model - WeatherChartModel.cs
public class WeatherChartModel
{
}
Display template - WeatherChart.cshtml
<div class="weather-chart">
// Some other stuff here
</div>
View - Index.cshtml
#model WeatherChartModel
#Html.DisplayForModel() // This will output the template view for the model
In order to create custom element handling in razor, such as <text>, you'd need to implement a custom System.Web.Razor.dll (which is responsible for parsing the document). Specifically, the class you're looking to re-implement would be the System.Web.Razor.Parser.HtmlMarkupParser.
However, I don't believe this is necessary given how flexible the framework itself is. If you're looking to keep things modular, have a look at either using DisplayTemplates/EditorTemplates or consider writing your own extension method. For example, either of the following would be more ideal:
#* TextField is decorated with UIHint("WYSIWYG"), therefore
calling ~/Views/Shared/EditorTemplates/WYSIWYG.cshtml *#
#Html.EditorFor(x => x.TextField)
#* WeatherField is decorated with UIHint("WeatherChart"), therefore
calling ~/Views/Shared/DisplayTemplates/WeatherChart.cshtml *#
#Html.DisplayFor(x => x.WeatherField)
Alternatively:
#* Custom extension method *#
#Html.WysiwygFor(x => x.TextField)
#* Another custom extension method *#
#Html.WeatherChartFor(x => x.WeatherField)

Asp.net MVC4 #Html.EditorFor .Where() Attribute

In my view i am using editortemplate as below:
edit.cshtml
#model NetasCrm.Models.CRM_OPP_DETAILS
<table class="table table-hover">
<thead>
<tr>
<th>Çözüm</th>
<th>Üretici</th>
<th>Tarih</th>
<th>Tutar</th>
<th>Sil</th>
</tr>
</thead>
<tbody>
#Html.EditorFor(model => model.CRM_SOLUTION_DISTRIBUTION, new { Solutions = ViewBag.Solutions, Vendors = ViewBag.Vendors })
#Html.HiddenFor(model => model.ID)
</tbody>
</table>
It is working but i am trying to do something(adding where clause.) as below to create an editor template for some of the items in my model.
#Html.EditorFor(model => model.CRM_SOLUTION_DISTRIBUTION.Where(p=>p.AMOUNT != 0), new { Solutions = ViewBag.Solutions, Vendors = ViewBag.Vendors })
I am getting the below error:
Templates can be used only with field access, property access,
single-dimension array index, or single-parameter custom indexer
expressions.
It's not how #Html.EditorFor() should be used, error message clear states about that.
To render what you want you may either use Html.Partial(), or create separate property in your model and move Where to it's getter.
Html.XxxFor expects a lambda expression that selects a property from the model. This is used to identify the property of the model which will be edited.
What you're trying to achieve is probably what you can do with an editor template.
Interesting articles on editor templates and their implementation:
Quick Tips About ASP.NET MVC – Editor Templates
Brad Wilson: ASP.NET MVC 2 Templates, Part 1: Introduction
In the second article you can see that if you have a custom class with the data you want to edit, you can create a template which is automatically used for your editor if you give it the right name and save it in the right place. You can make a class with a single field and include it in your model instead of the original property. This also allows to edit several properties at once (declaring a class with those properties)
Othe option would be a custom html helper:
Creating Custom HTML Helpers (NOTE: this technique will work perfectly with C#/Razor syntax)

EditorFor not rendering enumerable without #foreach

This is rather baffling. Imagine a Company entity with an .Employees enumerable property, for the sake of simplicity. I've seen elsewhere on SO that the trick to getting EditorTemplates to render enumerables sensibly is to use syntax like this:
In Index.cshtml:
#Html.EditorFor(company => company.Employees, "EmployeeEditorTemplate")
EmployeeEditorTemplate.cshtml expects a single employee:
#model MyNamespace.Models.Employee
//...
#Html.EditorFor(x => x.FullName)
// ...etc.
Posts like this one: Viewmodels List Is Null in Action ...indicate that the modelbinder is smart enough to figure out the binding and index the enumerable on the fly. I'm getting different results (exactly what I'd expect if the modelbinder wasn't supposed to be so mageriffic):
The model item passed into the dictionary is of type
System.Collections.Generic.List`1[MyNamespace.Models.Employee]',
but this dictionary requires a model item of type
'MyNamespace.Models.Employee'.
Enumerating it myself renders the collection just fine, but emits the same id for every row's controls (which is less than optimal, of course, but it tells me the EditorTemplate is functionally correct):
#foreach (var item in Model.Employees)
{
#Html.EditorFor(x => item, "EmployeeEditorTemplate")
}
This construct also fails to post the company.Employees collection back to my other controller action.
Any ideas on what's required to achieve the following?
Render the child collection with unique Ids per instance in the list
Post the child collection back with the model
I don't care if I have to enumerate it myself, nor whether it's inside or outside the EditorTemplate; I've just seen it said that this is unnecessary and that MVC works better if you let it handle things. Thanks.
Per #David-Tansey (from this answer - see for a much more detailed explanation):
(changed to suit this question):
When you use #Html.EditorFor(c => c.Employees) MVC convention chooses the default template for IEnumerable. This template is part of the MVC framework, and what it does is generate Html.EditorFor() for each item in the enumeration. That template then generates the appropriate editor template for each item in the list individually - in your case they're all instances of Employee, so, the Employee template is used for each item.
To sum up his explanation on when you use the named template, you are, in effect, passing the entire IEnumerable<Employee> to a template expecting a single Employee by using #Html.EditorFor(company => company.Employees, "EmployeeEditorTemplate").
note: I really only answered this because it had no answer and I stumbled here first. At least now someone doesn't close it and potentially miss an answer without having to go through the rigmarole of getting duplicate question votes, blah blah.
You can read about it here: Hanselman's article
In short, you should write in your view (index.cshtml):
int i=0;
#foreach (var item in Model.Employees)
{
#Html.TextBox(string.Format("Model[{0}].FullName", i), item.FullName)
i++;
}
foreach can be used with #HTML.EditorFor, for an example see below syntax:
#foreach(var emp in #Model)
{
#Html.EditorFor(d => emp.EmployeeName)
#Html.EditorFor(d => emp.Designation)
<br />
}

Using EditorFor template for DisplayFor in MVC4?

I'm using an Editor Template to make an Html.EditorFor(property) in my viewModel's view. There's a different .cshtml file containing "#Html.EditorFor(property)".
Now, depending on the value of a property of my viewModel, I need to display a DisplayFor instead.
I tried doing this by adding some conditional logic in my EditorTemplate but can't seem to access the properties of my viewModel from there (since the editor template is using #model.someOtherModel and not #viewModel). So if I can say something like
// Razor
if(true)
{ EditorFor(property) }
else {DisplayFor(property)}
in my viewModel's view, that would work. I just don't know how to define a "Display Template" for my object, in the same way I defined an Editor Template.
Another solution might be accessing the viewModel data from the Editor template... is this possible?
In Razor, you should be using something like the following syntax:
#if (condition) {
#Html.EditorFor(modelItem => model.property)
} else {
#Html.DisplayFor(modelItem => model.property)
}
Are you getting a specific error you can share?

MVC 3 razor TextBoxFor with ViewBag item throws an error?

Trying to print out several different forms on the webpage with the data that i have received from the ViewBag.
The first statement works but no the second:
#Html.EditorForModel(ViewBag.PI as PItem)
#Html.TextBoxFor(x => (ViewBag.PI as PItem).Text)
I also tried the following (same error message):
#Html.TextBoxFor(x => ViewBag.PI.Text)
The first one creates a model for the PItem and the second throws an error when i try to create a textbox for the Text item inside PItem. Is it possible to use the textboxfor helper to print out data from the ViewBag and not from the model?
TextBoxFor is intended to be used with strongly typed views and view models. So cut the ViewData/ViewBag c..p and use those helpers correctly:
#model MyViewModel
#Html.TextBoxFor(x => x.Text)
If you need to loop, use EditorTemplates:
#model IEnumerable<MyViewModel>
#Html.EditorForModel()
and inside the corresponding editor template:
#model MyViewModel
<div>#Html.TextBoxFor(x => x.Text)</div>
Not only that now we have IntelliSense and strong typing but in addition to this the code works.
Conclusion and my 2¢: don't use ViewBag/ViewData in ASP.NET MVC and be happy.

Resources