MVC Razor view nested foreach's model - c#

Imagine a common scenario, this is a simpler version of what I'm coming across. I actually have a couple of layers of further nesting on mine....
But this is the scenario
Theme contains List
Category contains List
Product contains List
My Controller provides a fully populated Theme, with all the Categories for that theme, the Products within this categories and the their orders.
The orders collection has a property called Quantity (amongst many others) that needs to be editable.
#model ViewModels.MyViewModels.Theme
#Html.LabelFor(Model.Theme.name)
#foreach (var category in Model.Theme)
{
#Html.LabelFor(category.name)
#foreach(var product in theme.Products)
{
#Html.LabelFor(product.name)
#foreach(var order in product.Orders)
{
#Html.TextBoxFor(order.Quantity)
#Html.TextAreaFor(order.Note)
#Html.EditorFor(order.DateRequestedDeliveryFor)
}
}
}
If I use lambda instead then then I only seem to get a reference to the top Model object, "Theme" not those within the foreach loop.
Is what I'm trying to do there even possible or have I overestimated or misunderstood what is possible?
With the above I get an error on the TextboxFor, EditorFor, etc
CS0411: The type arguments for method
'System.Web.Mvc.Html.InputExtensions.TextBoxFor(System.Web.Mvc.HtmlHelper,
System.Linq.Expressions.Expression>)'
cannot be inferred from the usage. Try specifying the type arguments
explicitly.
Thanks.

The quick answer is to use a for() loop in place of your foreach() loops. Something like:
#for(var themeIndex = 0; themeIndex < Model.Theme.Count(); themeIndex++)
{
#Html.LabelFor(model => model.Theme[themeIndex])
#for(var productIndex=0; productIndex < Model.Theme[themeIndex].Products.Count(); productIndex++)
{
#Html.LabelFor(model=>model.Theme[themeIndex].Products[productIndex].name)
#for(var orderIndex=0; orderIndex < Model.Theme[themeIndex].Products[productIndex].Orders; orderIndex++)
{
#Html.TextBoxFor(model => model.Theme[themeIndex].Products[productIndex].Orders[orderIndex].Quantity)
#Html.TextAreaFor(model => model.Theme[themeIndex].Products[productIndex].Orders[orderIndex].Note)
#Html.EditorFor(model => model.Theme[themeIndex].Products[productIndex].Orders[orderIndex].DateRequestedDeliveryFor)
}
}
}
But this glosses over why this fixes the problem.
There are three things that you have at least a cursory understanding before you can resolve this issue. I have
to admit that I cargo-culted this
for a long time when I started working with the framework. And it took me quite a while
to really get what was going on.
Those three things are:
How do the LabelFor and other ...For helpers work in MVC?
What is an Expression Tree?
How does the Model Binder work?
All three of these concepts link together to get an answer.
How do the LabelFor and other ...For helpers work in MVC?
So, you've used the HtmlHelper<T> extensions for LabelFor and TextBoxFor and others, and
you probably noticed that when you invoke them, you pass them a lambda and it magically generates
some html. But how?
So the first thing to notice is the signature for these helpers. Lets look at the simplest overload for
TextBoxFor
public static MvcHtmlString TextBoxFor<TModel, TProperty>(
this HtmlHelper<TModel> htmlHelper,
Expression<Func<TModel, TProperty>> expression
)
First, this is an extension method for a strongly typed HtmlHelper, of type <TModel>. So, to simply
state what happens behind the scenes, when razor renders this view it generates a class.
Inside of this class is an instance of HtmlHelper<TModel> (as the property Html, which is why you can use #Html...),
where TModel is the type defined in your #model statement. So in your case, when you are looking at this view TModel
will always be of the type ViewModels.MyViewModels.Theme.
Now, the next argument is a bit tricky. So lets look at an invocation
#Html.TextBoxFor(model=>model.SomeProperty);
It looks like we have a little lambda, And if one were to guess the signature, one might think that the type for
this argument would simply be a Func<TModel, TProperty>, where TModel is the type of the view model and TProperty
is inferred as the type of the property.
But thats not quite right, if you look at the actual type of the argument its Expression<Func<TModel, TProperty>>.
So when you normally generate a lambda, the compiler takes the lambda and compiles it down into MSIL, just like any other
function (which is why you can use delegates, method groups, and lambdas more or less interchangeably, because they are just
code references.)
However, when the compiler sees that the type is an Expression<>, it doesn't immediately compile the lambda down to MSIL, instead it generates an
Expression Tree!
What is an Expression Tree?
So, what the heck is an expression tree. Well, it's not complicated but its not a walk in the park either. To quote ms:
| Expression trees represent code in a tree-like data structure, where each node is an expression, for example, a method call or a binary operation such as x < y.
Simply put, an expression tree is a representation of a function as a collection of "actions".
In the case of model=>model.SomeProperty, the expression tree would have a node in it that says: "Get 'Some Property' from a 'model'"
This expression tree can be compiled into a function that can be invoked, but as long as it's an expression tree, it's just a collection of nodes.
So what is that good for?
So Func<> or Action<>, once you have them, they are pretty much atomic. All you can really do is Invoke() them, aka tell them to
do the work they are supposed to do.
Expression<Func<>> on the other hand, represents a collection of actions, which can be appended, manipulated, visited, or compiled and invoked.
So why are you telling me all this?
So with that understanding of what an Expression<> is, we can go back to Html.TextBoxFor. When it renders a textbox, it needs
to generate a few things about the property that you are giving it. Things like attributes on the property for validation, and specifically
in this case it needs to figure out what to name the <input> tag.
It does this by "walking" the expression tree and building a name. So for an expression like model=>model.SomeProperty, it walks the expression
gathering the properties that you are asking for and builds <input name='SomeProperty'>.
For a more complicated example, like model=>model.Foo.Bar.Baz.FooBar, it might generate <input name="Foo.Bar.Baz.FooBar" value="[whatever FooBar is]" />
Make sense? It is not just the work that the Func<> does, but how it does its work is important here.
(Note other frameworks like LINQ to SQL do similar things by walking an expression tree and building a different grammar, that this case a SQL query)
How does the Model Binder work?
So once you get that, we have to briefly talk about the model binder. When the form gets posted, it's simply like a flat
Dictionary<string, string>, we have lost the hierarchical structure our nested view model may have had. It's the
model binder's job to take this key-value pair combo and attempt to rehydrate an object with some properties. How does it do
this? You guessed it, by using the "key" or name of the input that got posted.
So if the form post looks like
Foo.Bar.Baz.FooBar = Hello
And you are posting to a model called SomeViewModel, then it does the reverse of what the helper did in the first place. It looks for
a property called "Foo". Then it looks for a property called "Bar" off of "Foo", then it looks for "Baz"... and so on...
Finally it tries to parse the value into the type of "FooBar" and assign it to "FooBar".
PHEW!!!
And voila, you have your model. The instance the Model Binder just constructed gets handed into requested Action.
So your solution doesn't work because the Html.[Type]For() helpers need an expression. And you are just giving them a value. It has no idea
what the context is for that value, and it doesn't know what to do with it.
Now some people suggested using partials to render. Now this in theory will work, but probably not the way that you expect. When you render a partial, you are changing the type of TModel, because you are in a different view context. This means that you can describe
your property with a shorter expression. It also means when the helper generates the name for your expression, it will be shallow. It
will only generate based on the expression it's given (not the entire context).
So lets say you had a partial that just rendered "Baz" (from our example before). Inside that partial you could just say:
#Html.TextBoxFor(model=>model.FooBar)
Rather than
#Html.TextBoxFor(model=>model.Foo.Bar.Baz.FooBar)
That means that it will generate an input tag like this:
<input name="FooBar" />
Which, if you are posting this form to an action that is expecting a large deeply nested ViewModel, then it will try to hydrate a property
called FooBar off of TModel. Which at best isn't there, and at worst is something else entirely. If you were posting to a specific action that was accepting a Baz, rather than the root model, then this would work great! In fact, partials are a good way to change your view context, for example if you had a page with multiple forms that all post to different actions, then rendering a partial for each one would be a great idea.
Now once you get all of this, you can start to do really interesting things with Expression<>, by programatically extending them and doing
other neat things with them. I won't get into any of that. But, hopefully, this will
give you a better understanding of what is going on behind the scenes and why things are acting the way that they are.

You can simply use EditorTemplates to do that, you need to create a directory named "EditorTemplates" in your controller's view folder and place a seperate view for each of your nested entities (named as entity class name)
Main view :
#model ViewModels.MyViewModels.Theme
#Html.LabelFor(Model.Theme.name)
#Html.EditorFor(Model.Theme.Categories)
Category view (/MyController/EditorTemplates/Category.cshtml) :
#model ViewModels.MyViewModels.Category
#Html.LabelFor(Model.Name)
#Html.EditorFor(Model.Products)
Product view (/MyController/EditorTemplates/Product.cshtml) :
#model ViewModels.MyViewModels.Product
#Html.LabelFor(Model.Name)
#Html.EditorFor(Model.Orders)
and so on
this way Html.EditorFor helper will generate element's names in an ordered manner and therefore you won't have any further problem for retrieving the posted Theme entity as a whole

You could add a Category partial and a Product partial, each would take a smaller part of the main model as it's own model, i.e. Category's model type might be an IEnumerable, you would pass in Model.Theme to it. The Product's partial might be an IEnumerable that you pass Model.Products into (from within the Category partial).
I'm not sure if that would be the right way forward, but would be interested in knowing.
EDIT
Since posting this answer, I've used EditorTemplates and find this the easiest way to handle repeating input groups or items. It handles all your validation message problems and form submission/model binding woes automatically.

When you are using foreach loop within view for binded model ...
Your model is supposed to be in listed format.
i.e
#model IEnumerable<ViewModels.MyViewModels>
#{
if (Model.Count() > 0)
{
#Html.DisplayFor(modelItem => Model.Theme.FirstOrDefault().name)
#foreach (var theme in Model.Theme)
{
#Html.DisplayFor(modelItem => theme.name)
#foreach(var product in theme.Products)
{
#Html.DisplayFor(modelItem => product.name)
#foreach(var order in product.Orders)
{
#Html.TextBoxFor(modelItem => order.Quantity)
#Html.TextAreaFor(modelItem => order.Note)
#Html.EditorFor(modelItem => order.DateRequestedDeliveryFor)
}
}
}
}else{
<span>No Theam avaiable</span>
}
}

It is clear from the error.
The HtmlHelpers appended with "For" expects lambda expression as a parameter.
If you are passing the value directly, better use Normal one.
e.g.
Instead of TextboxFor(....) use Textbox()
syntax for TextboxFor will be like Html.TextBoxFor(m=>m.Property)
In your scenario you can use basic for loop, as it will give you index to use.
#for(int i=0;i<Model.Theme.Count;i++)
{
#Html.LabelFor(m=>m.Theme[i].name)
#for(int j=0;j<Model.Theme[i].Products.Count;j++) )
{
#Html.LabelFor(m=>m.Theme[i].Products[j].name)
#for(int k=0;k<Model.Theme[i].Products[j].Orders.Count;k++)
{
#Html.TextBoxFor(m=>Model.Theme[i].Products[j].Orders[k].Quantity)
#Html.TextAreaFor(m=>Model.Theme[i].Products[j].Orders[k].Note)
#Html.EditorFor(m=>Model.Theme[i].Products[j].Orders[k].DateRequestedDeliveryFor)
}
}
}

Another much simpler possibility is that one of your property names is wrong (probably one you just changed in the class). This is what it was for me in RazorPages .NET Core 3.

Related

How create a complex EditorTemplate in ASP.NET MVC 5 with db query?

I would like to create a more complex EditorTemplate to select a customer from a list.
I'm aware of the DropDownListFor, but I would like to show cards with customer
pictures and some data not just a regular select list.
What I would like to do:
create an EditorTemplate for customer selecting, for instance... In any POCO Class
public class X{
[Key] int Id {get;set;}
[UIHint("CustomerSelector")] int Custumer_Id {get;set;}
}
And the "CustomerSelector" Editor template be able to query all clients and show them into a rich list.
What is the problem:
It's not a good idea to add querying logic from inside a view. This is against MVC pattern.
It's not very modular to query the customer list in every controller and pass it as argument to the EditorTemplate.
How can I create this EditorTemplate without mess up with the MVC pattern nor duplicate code in every controller?
Unfortunately, there is no truly good way to handle something like this. Your are correct that it's improper for database access to happen within a view. Not only does this violate MVC, but it would also require creating an additional instance of your context in the view, when you should really have just one per request.
The alternative, as you've mentioned, would be to do the query in the controller and then pass that into your view. Honestly, this is probably your best of bad options here.
Another choice I see is to use a child action. A child action allows you to confine the logic of querying the users and passing to a view in just one place. The downside is that that you would have to handle the field naming manually, because the rendering of the child actions view will be outside the scope of the form you're building. In other words, you'd have to do something like:
#Html.Action("CustomerSelect", new { fieldName = "Customer_Id" })
That's not really ideal, either, as now you've got a string that you've got to keep track of, and you'll have to be careful about actually providing the right name. For example, if this was a collection of items, then you'd actually need to pass something like "MyCollection[" + i.ToString() + "].Customer_Id". You can see that this starts to get messy quick. For this reason alone, I'd pretty much nix this as a possible solution.
One final option is to use an HtmlHelper extension. This has the same problem as an editor template would in that you're going to have to new up an instance of your context, but it's at least better in the respect that it's not being done inside a Razor view.

using model in view, lambda expression explain

In the following code of the example application of VS2015:
#model LoginViewModel
<p>
#Html.DisplayNameFor(m => m.Name)
</p>
I want to know how the compiler knows that m refers to the LoginViewModel if I never said that m is an alias for LoginViewModel.
And I would like help understanding the lambda expression of DisplayNameFor. Why it requires a lambda if we could just pass the string m.Name? How is it used here?
The given Razor template will be compiled, so the Razor view compiler can do some magic here.
The compiler knows the type of your model because of the #model directive (without the #model directive the compiler falls back to dynamic).
If you look at the #Html.DisplayNameFor directive, then the Html instance is an object of the type HtmlHelper<TModel> where TModel is the type given by the #model directive. In your case is the concrete type HtmlHelper<LoginViewModel>.
Now the HtmlHelper<LoginViewModel>.DisplayNameFor method is stongly typed and the compiler can figure that 'm' (which is only a parameter name) is of type LoginViewModel and that the lamdba expression returns a value from the model.
During runtime the DisplayNameFor method is executed by providing your model object as parameter 'm' the expression returns the object of the model member (or the object the expression returns) and the MVC framework can inspect the object (Type, Validation Attributes, etc.) and produces the appropriate html based on internal or custom templates.
If you would just pass a string, then MVC would not be able to get the needed type and validation annotations (and much more information).
Your first question: It passes LogonViewModel to #Html.DislplayNameFor method since you have define it on you first line as your model (#model LoginViewModel)
Aslo as it mentioned here:
What is the #Html.DisplayFor syntax for?
Html.DisplayFor() will render the DisplayTemplate that matches the
property's type.
If it can't find any, I suppose it invokes .ToString().
DisplayNameFor is a strongly typed HTML helper. These were first introduced in ASP.NET MVC 2. The purpose of them (as per the linked article) is to...
...provide a nice way to get better type-safety within your view templates. This enables better compile-time checking of your views (allowing you to find errors at build-time instead of at runtime), and also supports richer intellisense when editing your view templates within Visual Studio.
If you want to understand the innards of how they work, the source code is available on GitHub.

How do you put a ViewBag item into a Text Box?

I have the following code and I get an error saying:
has no applicable method named 'TextBoxFor' but appears to have an extension method by that name.
My Code:
#Html.TextBoxFor(ViewBag.taglist)
Why don't you use strongly typed model in your view instead of ViewBag. This will make your life easier.
In fact, you must use a model to with TextBoxFor, otherwise it just won't work. See the definition of TextBoxFor - as a second parameter it takes a lambda expression that takes a property form a model.
If you want just a text box, two options:
#Html.TextBox("NameOfTheTextbox", (String)ViewBag.SomeValue)
or just go
<input type="text" value="#ViewBag.SomeValue" />
No complex solutions required.
I agree with other suggestions of using a strongly-typed model, because the compile-time error support is so much better than debugging exceptions. Having said that, in order to do what you want, you can use this:
#Html.TextBox("NameOfTextBox", (string)ViewBag.taglist)
Update: A Simple Example
Now that you've provided some details in your comments, I've taken a guess at what you might be doing, in order to provide a simple example.
I'm assuming you have a list of tags (like SO has per question) that you'd like to display neatly in a textbox, with each tag separated by a space. I'm going to assume your Tag domain model looks something like this:
public class Tag
{
public int Id { get; set; }
public string Description { get; set; }
}
Now, your view will need a list of the tags but will likely need some other information to be displayed as well. However, let's just focus on the tags. Below is a view model to represent all the tags, taking into account that you want to display them as a string inside a textbox:
public class SomeViewModel
{
public string Tags { get; set; }
// Other properties
}
In order to get the data you want you could grab all of the tags like this:
public ActionResult Index()
{
using (YourContext db = new YourContext())
{
var model = new SomeViewModel();
model.Tags = string.Join(" ", db.Tags.Select(t => t.Description).ToList());
return View(model);
}
}
Notice how I'm directly passing model to the view.
The view is now very simple:
#model SomeViewModel
#Html.EditorFor(m => m.Tags)
The model directive is what signifies that a view is strongly-typed. That means this view will expect to receive an instance of SomeViewModel. As you can see from my action code above, we will be providing this view the type that it wants. This now allows us to make use of the strongly-typed HtmlHelper (i.e. Html.XxxFor) methods.
In this particular case, I've used Html.EditorFor, as it will choose an appropriate input element to render the data with. (In this case, because Description is a string, it will render a textbox.)
You cannot use Html.TextBoxFor without explicitly setting a type for your model within the view. If you don't specify a type it defaults to dynamic. If you want to do model binding then you must use an explicit type rather than a dynamic type like ViewBag. To use Html.TextBoxFor you must define a model type that defines the property that you wish to bind. Otherwise you have to use Html.TextBox and set the value manually from ViewBag. As others have said, you will make your life much easier if you use a statically typed model and take advantage of the inbuilt MVC model binding.
You have to use a lambda expression to select the property, plus you will have to cast the ViewBag member to the correct type.
#Html.TextBoxFor(model => (string)ViewBag.taglist)

Extend HiddenFor Templates in ASP.NET MVC

I thought Html.HiddenFor could use Templates like Html.DisplayFor or Html.EditorFor. Unfortunately the method doesn't accept a TemplateName like the others.
I know, the workaround would be to use a DisplayFor/EditorFor Template which has HiddenFors. But I would like to find out how to extend the Html.HiddenFor method. Anyone?
Regards
Seems like you are mislead by wrong analogy. HiddenFor corresponds exactly to the <input type="hidden"/> tag. Just like TextBoxFor, CheckBoxFor etc. These methods are not designed to use templates. DisplayFor/EditorFor on the other side are specially created to be used with templates defined in the project. Thus what you are asking for is not possible out-of-the-box.
However you can always define your own overload for HiddenFor with whatever set of parameters and whatever logic you might require.
There is an overload which accept additional parameter - htmlAttributes. And you can use it for add some attributes to the result tag.
Also the second way is to create razor partial view in one of the folders
~/Areas/AreaName/Views/ControllerName/DisplayTemplates/TemplateName.cshtml
~/Areas/AreaName/Views/Shared/DisplayTemplates/TemplateName.cshtml
~/Views/ControllerName/DisplayTemplates/TemplateName.cshtml
~/Views/Shared/DisplayTemplates/TemplateName.cshtml
with name HiddenInput.cshtml
Here's what you do, you create it as an editor template, because as Andre pointed out, HiddenFor is equivalent to the helper methods like TextBoxFor and CheckboxFor.
It's likely that you'll want to have an actual editor too, so place your real editor under ~/Shared/EditorTemplates. We're going to put our "hidden editor" under the controller you wish to use it on.
~/Views/ControllerName/EditorTemplates/ModelName.cshtml
Lets say we have a Person model.
public class Person
{
public string First { get; set; }
public string Last { get; set; }
}
We'll create a partial view.
#Model Person
#Html.HiddenFor(p => p.First);
#Html.HiddenFor(p => p.Last);
And then we'll pretend we have a model that contains a Person as a property. From our main view, we call our "hidden editor" like so.
#Model Foo
#Html.EditorFor(f => f.Person)
Easy peasy lemon squeezy. A bit hacky, but it works like a charm.

Compile-time checking for action links to controller methods

Is there a way to create a strongly typed controller action? For example:
In a Controller I use:
aClientLink = Url.Action("MethodName", "ControllerName", new { Params... });
I would like to use:
aClientLink = Url.Action(Controller.MethodName,ControllerName);
I do not want to re-invent the wheel. I am sure someone has some clever solution. This would allow me to add compile time checking to controller methods.
You could create your own HtmlHelper extension methods that uses expressions (the same way they are used to reference properties of a model).
#* An expression used to indicate which property of the model should be
examined. It may or may not actually be executed. *#
#Html.IdFor( o => o.FirstName )
#* Don't actually evaluate the expression, just parse it for the method name *#
#Url.ActionFor( o => o.ControllerMethod() )
You can look at the MVC source for examples of helper methods which take expressions as input values, and you can see my answer here for how to retrieve metadata about an object property from an expression.
However...
Used in a view, I would argue that such an approach ties your view far too tightly to the controller. I dislike magic strings but they do provide complete decoupling.
It's not clear if you want to use such methods inside a controller, in which case the separation of concerns becomes less of a problem. You would still lose certain abilities, such as the ability to alias an action name.

Categories