Ok I just discovered about the EditorForModel in MVC and I want to know when I should use this instead of an EditorFor on each of my property? And why does when I add a strongly typed view it does not use this and build an EditorFor on every property?
I'm late on this... but thanks for the info!
Since the accepted answer is a link-only answer (and was removed), I thought I'd actually answer the question derived from Brad Wilson's Blog: ASP.NET MVC 2 Templates, Part 1: Introduction.
The model expressions are simple helpers which operate on the current model. The line DisplayForModel() is equivalent to DisplayFor(model => model).
TL;DR the same idea can be assumed for EditorFor(model => model) and EditorForModel(); these helper methods achieve the same thing. EditorForModel() assumes the model expression is the #model that was passed to the view.
Take the following models and view for example:
public class Person
{
public string Name {get; set;}
public Address MailingAddress {get; set;}
}
public class Address
{
public String Street {get; set;}
public String City {get; set;}
public String State {get; set;}
}
Create.cshtml:
#model MyNamespace.Models.Person
/* So, you need an Editor for the Person model? */
#Html.EditorForModel()
/*the above is equivalent to #Html.EditorFor(model => model) */
/* you need to specify the Address property that the editor accepts? */
#Html.EditorFor(model => model.MailingAddress)
You should use it when possible, but sometimes you will need the customizability of individual Html.EditorFor uses.
As for why the built-in templates don't use it, that's mainly because they are silly in general, but also because, if I recall, they need to wrap elements (like table rows etc.) around each Html.EditorFor.
#Html.EditorForModel() ?? And give up the fun of writing your own view? smile
Besides the fun, doing so as a habit is rather dicey. Consider the following common scenario - you have a bool variable say IsMale in your database in your customer table. Well obviously you don't want the default version (IsMale with a check-box) - you probably want something a bit more friendly, say a {select, Options .... , /select} tags, right? that's where the view really starts kicking in. That's the customization. Every view is a little different. You have the RAZOR engine, exploit it to the max! In your view you can override anything, or even manually type an entire chunk of HTML code of your own.
Related
.NET MVC Application EF code first, using Identity 2
Each application user belongs to a specific user_type (ApplicationUser.user_type). (the application uses Identity roles too, but they are completely independent of this user_type).
also, I am extensively using Display attribute on properties of models and viewmodels with:
[Display(Name="some literal"]
public string someProperty { get; set; }
and in the view:
#Html.LabelFor(model => model.someProperty)
or
#Html.DisplayNameFor(model => model.someProperty)
But now I am required to, in some cases, display different things if the logged-in user is of a specific user_type ("Client").
What would be the best way to implement this? I am guessing maybe its possible to customize the Display attribute so that it accepts a parameter "userType", so I could decorate the properties with something like:
[Display(Name="This will be shown to users with user_type: Client", UserType="Client"]
[Display(Name="This will be shown to everyone else"]
public int myProperty { get; set; }
or something like that... but I have no idea how to do it... any help is appreciated
To me it seems that you are trying to put too much logic/responsibility in one place.
I recon you would manage to come up with something to deal with this scenario, but, if you do, you'll risk ending up with an inter tangled property which behaviour will depend on all sorts of external parameters. The more you'll add, the more complex it will become. That can be hard to maintain.
I am not fond of "keep it simple" but I think it does apply here, by keeping it simple in maintenance.
IMO you have a couple of options to help you out:
create a complete new view and a model for this client page
add a propery to your (view)model which contains the string.
add the string to the page and handle it with razor.
use a viewbag or similar temp data container
So, to sum it: I dont think expanding the Display attribute would be the way to go here and consider one (or another) of the methods mentioned above.
Problem Description
My problem is similar to this question but instead of applying Data Annotations to the property Name via reflection (handled by ModelMetadata.DisplayName) I am applying them to the value (not handled by ModelMetadata).
Detailed Description
In the context of an ASP.NET MVC program.
Suppose I have a Model class
public class Model
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string NickName { get; set; }
public string Address { get; set; }
public int Phone { get; set; }
[Display(Name = "Start Date")]
[DataType(DataType.Date)]
[DisplayFormat(ApplyFormatInEditMode = true, DataFormatString = "{0:dd/MM/yyyy}")]
public DateTime StartDate { get; set }
[Display(Name = "End Date")]
[DataType(DataType.Date)]
[DisplayFormat(ApplyFormatInEditMode = true, DataFormatString = "{0:dd/MM/yyyy}")]
public DateTime EndDate { get; set }
}
Then suppose this Model is used in at least 5 different views where the value of every property must be displayed in full. (Sometimes for every instance, other times for a handful, other times for one).
I can manually list each Property access within it's own
<td>#Html.DisplayFor(item.<Property>)</td>
for every view.
But that won't help me if later on the definition for Model expands to include new Properties (like Description and Relation and Reliability). Then I'd need to manually update every occurrence of the complete Model listing.
I can use reflection to iterate over a list of PropertyInfo's and save having to manually list each property by using
<td>#property.GetValue(item)</td>
But DisplayFor(x) does not support an expression as complex as x=>property.GetValue(item), and this means I lose the Data Annotations that format my DateTime as
01/01/1990
instead of
01-Jan-90 12:00:00 AM
and would likely also result in the loss of all types of annotation including validation.
Problem Solutions
So far I have considered (and in some cases attempted) the following solutions:
[Failed] Manually craft an Expression which emulates the functionality of #property.GetValue(item)
[Edit]
[Failed] Pass DisplayFor a MethodInfo object representing the Property Accessor DisplayFor(x => property.GetGetMethod()) as well as .Invokeing it on x.
[/Edit]
Retrieve the value manually as normal, and
execute a method on it to manually retrieve and implement Annotation Data on it prior to insertion in the view element as this question suggests, or
Re-implement the DisplayFor handling of Data Annotations on an as-needed basis in a Display Template View and apply that directly to the value via DisplayFor as this question suggested
Refactor the Model class to contain only a list(SortedList?) of 'Prop' instances, where 'Prop' is a class representing a Property with a Name and Value element.
This last solution would turn the broken
#Html.DisplayFor(m=>property.GetValue(item)
into a theoretically working
#Html.DisplayFor(m=>item.Properties[i].Value)
Which aside from the slightly unintuitive need for getting a property called Name (Properties["Name"]) by (.Value), seems the most workable solution, at the cost of Model clarity.
[Edit]
Most recently I have created a Utility method which retrieves the DisplayFormatAttribute from the PropertyInfo and returns either the DisplayFormatString or the default of "{0}" if a format string was not annotated. I have then used it to create a collection of preformatted property values within a ViewModel.
This seems for now, to be the most elegant way I know of to decouple the View from the Model as much as possible while still retrieving the necessary data from it.
[/Edit]
The Question
This is at the moment, purely a learning exercise, but I would like to know...
Is it possible to succeed where I have failed and both have my Reflection cake and eat the Data Annotations too? Or must I seek an alternative solution?
If I must seek an alternative solution, are there routes I have missed, or am I at least on the right track?
Maybe something similar to:
#foreach (var property in Model.GetType().GetProperties())
{
<li>#property.GetValue(Model, null)</li>
}
Great Success
Revisiting my original attempt to manually craft the expression dynamically, I discovered this article which did precisely what I wanted to do, and using mostly Microsoft provided code as well!
Though the Microsoft code was difficult to find (the link in the article is broken and the example slightly outdated for the code I did find), I was able to use it to good effect to implement my own DisplayFor extension method.
Unfortunately, due to my model being a list rather than a single instance, I still needed to create a partial view to pass an instance to, so that I could access the properties via Model from within the generated expression.
My View code now looks like this:
#foreach (var thing in Model.CollectionOfThings)
{
<tr>
#foreach (var prop in typeof(Thing).GetProperties())
{
<td>
#{
Html.RenderPartial("~/Views/Shared/_DisplayForReflectedProperty.cshtml",
new Tuple<Thing, PropertyInfo>(thing, prop));
}
</td>
}
}
With _DisplayForReflectedProperty as simple as
#using WebApplication1.Models
#using System.Reflection
#using WebApplication1.Extensions
#model Tuple<Thing, PropertyInfo>
#Html.DisplayFor("Item1."+Model.Item2.Name)
And the only difference between my DisplayFor extension and the one in the article is the null object parameter in this function call (plus the obvious conversions from EditorFor to DisplayFor):
var lambda = System.Linq.Dynamic.DynamicExpression.ParseLambda(typeof(TModel),
null, expression);
Using this template I can now generate arbitrary (from code perspective, specific from business rule perspective) subsets of a model's properties to display in any manner I wish, without having to custom tailor my views to each particular subset, while retaining all the benefits of using the 'For' Helpers!
so my view starts off like this
#model ApplicationName.Models.A
and takes in the my database elements from A so if i were to use #Html.DisplayFor(model => model.Whatever) it will display that element from the database in my view.
However i have another attribute that i want to also display on this same page/view. But it is in Models.B
So i need to end up with this
#Html.DisplayFor(model => model.WhateverFromB)
all help greatly appreciated
You should use a view model which encapsulates both an A and a B. For example:
class MyViewModel
{
public ApplicationName.Models.A A {get; set;}
public ApplicationName.Models.B B {get; set;}
}
Then pass this single view model to the controller. You would then of course access your properties like:
#Html.DisplayFor(m => m.A.Whatever)
#Html.DisplayFor(m => m.B.WhateverFromB)
Similarly if you do not need every property from A and B, or the semantic difference between the two database objects is not important to the view, you might consider doing the following instead:
class MyViewModel
{
public object Whatever {get; set;}
public object WhateverFromB {get; set;}
}
Populate the individual properties of the view model and use accordingly. You can of course use a combination of the two, including a full A along with a WhateverFromB.
Realistically you should create a ViewModel which contains all the properties you need and you can populate that ViewModel from multiple Models. Then use the ViewModel as the 'model' in the MVC page.
However to answer your question. Just write an additional Html.Display entry
In your controller
ViewBag.WhateverFromB = b_obj;
and then in your view
#Html.Display(ViewBag.WhateverFromB)
You should use a ViewModel for this. When you require more than one model in your view this is a good way to go. Include what you need from the Models you are using in the ViewModel class and reference it in your view instead of the Model.
This will get you going with ViewModels
http://www.asp.net/mvc/tutorials/mvc-music-store/mvc-music-store-part-3
ViewBags and ViewData will work but there are appropriate times to use each.
http://rachelappel.com/when-to-use-viewbag-viewdata-or-tempdata-in-asp.net-mvc-3-applications
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)
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.