just reading about UIHint from this url What is use of UIHint attribute in MVC
If you annotate a property with UIHint attribute
and use EditorFor or DisplayFor inside your views,
ASP.NET MVC framework will look for the specified
template which you specified through UIHintAttribute.
The directories it looks for is:
For EditorFor:
~/Views/Shared/EditorTemplates
~/Views/Controller_Name/EditorTemplates
For DisplayFor:
~/Views/Shared/DisplayTemplates
~/Views/Controller_Name/DisplayTemplates
the above write up means MVC engine first search view in shared if not found then it will search view in ~/Views/Controller_Name/DisplayTemplates ?
i just got a code but it is not complete so not being able to understand it properly
public class Person {
[UIHint("Poo")]
public string Name { get; set; }
}
#model MyApp.Models.Person
<h2>My Person</h2>
#Html.DisplayFor(m => m.Name)
if i think Poo is a shared view then where is poo related view code?
when this line will execute #Html.DisplayFor(m => m.Name) then what will happen.
see this code
#Html.EditorFor(model => model.ProductViewModel, "yourTemplateName")
where MVC will find the file yourTemplateName.cshtml?
thanks
the above write up means MVC engine first search view in shared if not found then it will search view in ~/Views/Controller_Name/DisplayTemplates ?
That is backwards, the search pattern is (exactly):
(if in an area)
"~/Areas/{2}/Views/{1}/DisplayTemplates/{0}.cshtml",
"~/Areas/{2}/Views/{1}/DisplayTemplates/{0}.vbhtml",
"~/Areas/{2}/Views/Shared/DisplayTemplates/{0}.cshtml",
"~/Areas/{2}/Views/Shared/DisplayTemplates/{0}.vbhtml"
then
"~/Views/{1}/DisplayTemplates/{0}.cshtml",
"~/Views/{1}/DisplayTemplates/{0}.vbhtml",
"~/Views/Shared/DisplayTemplates/{0}.cshtml",
"~/Views/Shared/DisplayTemplates/{0}.vbhtml"
Where
0 = Template/Type name
1 = ControllerName
2 = AreaName
(In the event you do not provide a Template name hint, the razor engine default to the type (int, boolean, string and even custom class types you've defined)
if i think Poo is a shared view then where is poo related view code?
In one more more of the locations above. This allows you to create poo specific views per controller and/or a shared poo view. It's however you want to do it.
when this line will execute #Html.DisplayFor(m => m.Name) then what will happen.
The engine will search the above folders for a template. In the event one is not found it then looks for object.cshtml/vbhtml in the same folders. If that file is found it executes it, if not it executes the default internal object display for code.
where MVC will find the file yourTemplateName.cshtml?
In the same directories above. You have to understand this it does the same thing over and over, it is a convention of asp.net-mvc.
What is use of UIHint attribute in ASP.Net MVC
This allows you to override the template used for a given property.
public class Person
{
[UIHint("Age")]
public DateTime Birthday { get; set; }
}
Will attempt to look for 'age.cshtml' in the above locations. Since the UIHintAttribute is not sealed you can also derive your own attribute and create some pretty nifty templates:
public UIDateTimeAttribute : UIHintAttribute
{
public UIDateTimeAttribute(bool canShowSeconds)
: base("UIDateTime", "MVC")
{
CanShowSeconds = canShowSeconds;
}
public bool CanShowSeconds { get; private set; }
}
Then your model might looks like:
public class Person
{
[UIDateTime(false)]
public DateTime Birthday { get; set; }
}
UIDateTime.cshtml
#model DateTime
#{
var format = "dd-MM-yy hh:mm:ss";
// Get the container model (Person for example)
var attribute = ViewData.ModelMetadata.ContainerType
// Get the property we are displaying for (Birthday)
.GetProperty(ViewData.ModelMetadata.PropertyName)
// Get all attributes of type UIDateTimeAttribute
.GetCustomAttributes(typeof(UIDateTimeAttribute))
// Cast the result as UIDateTimeAttribute
.Select(a => a as UIDateTimeAttribute)
// Get the first one or null
.FirstOrDefault(a => a != null);
if (attribute != null && !attribute.CanShowTime)
{
format = "dd-MM-yy hh:mm";
}
}
#Model.ToString(format)
Related
I'm trying to render a Model in MVC that takes a list of Content objects List<Content>()
A Content is a base class and can be one of many different derived types eg: (TextBox, Schedule etc...)
In my View, while iterating through the list of contents I want to address a view that matches the type of the Content's derived class. (so that TextBox uses it's own view, Schedule it's own view and so on...)
How can I achieve this? Maybe I need to do some binding in the ViewModel?
Any help will be greatly appreciated,
thanks in advance for your answer(s).
You can iterate your list, and on each iteration call a partial view which matches the iterated object type (what you called "Content").
You can encapsulate a public property in you content class, to hold the view name. Or you can also use the object's method GetType instead.
Something like this (where your list of objects resides in Model.ContentList):
foreach (var _content in Model.ContentList) {
#Html.Partial(_content.ViewName, _content); // the "ViewName" would hold different names, such as: "TextBox", "Schedule" from your question.
}
Lets say your model -
public class Content
{
public int Weight { get; set; }
}
public class TextContent : Content
{
public string Text { get; set; }
}
Then create a controller action -
public ActionResult GetContents()
{
var contents = new List<TextContent>()
{
new TextContent() {Text = "Sample Text"},
new TextContent() {Text = "Second Sample Content"}
};
return View(contents);
}
now create a folder called EditorTemplates in Shared folder of Views folder and place following cshtml with name TextContent.cshtml (Note: name of the cshtml file should be matching with the model name, otherwise custom editor template will not be rendered).
#model myc.Models.TextContent
#Html.TextBoxFor(m => m.Text)
Now create your view with IEnumerable<myc.Models.TextContent> -
model IEnumerable<MvcApplication1.Controllers.TextContent>
#{
ViewBag.Title = "GetContents";
}
<h2>GetContents</h2>
#foreach (var item in Model) {
#Html.EditorFor(m => item)
}
When you run the application and go to the view, you will get -
I'm wondering if anyone can confirm this behavior or if I've done something wrong.
Normally when you specify the DataType(DataType.MultilineText) attribute, and do something like #Html.DisplayFor(m => m.Body) MVC will use the MultilineText.cshtml in the DisplayTemplates folder. That does not seem to work when the DataType attribute is applied to an overriden property as in the code below. Now if I move the attribute to the property in the abstract class it MVC does use the MultilineText.cshtml display template.
public abstract class PostBase
{
[Required]
public virtual string Body { get; set; }
}
public class MessagePost : PostBase
{
[StringLength(500), DataType(DataType.MultilineText)]
public override string Body
{
get { return base.Body; }
set { base.Body = value; }
}
}
What's the Model in declared in your view? The abstract or child?
It uses reflection to read the attribute based on the model declared so:
#model PostBase
#Html.DisplayFor(m => m.Body)
Will work differently to
#model MessagePost
#Html.DisplayFor(m => m.Body)
the first of these will apply the [Required] only. It's bound to a PostBase model (doesn't know or care what the child class), so when it reflects the PostBase class; this only has [Required] on that property. So it never looks for the MultilineText.cshtml, why would it? It's not got MultilineText on it.
The second one will apply [StringLength(500), DataType(DataType.MultilineText)] and [Required]. The attributes are combined for inherited classes so when it reflects the class it'll see both attributes.
This view should use the template as required. I'm guessing this doesn't work for you though as I'm presuming the inheritance is there for a reason?
I have a complex type License as a view model.
public class License
{
public string Name { get; set; }
// Other Properties
public List<Function> Functions { get; set; }
}
public class Function
{
public string Name { get; set; }
// Other Properties
public List<Unit> Units { get; set; }
}
public class Unit
{
public string Name { get; set; }
// Other Properties
}
Both the Function's view template and Unit's view template are dynamiclly rendered. So the html looks like this:
<!-- LicenseView -->
#model License
#Html.TextBoxFor(m => m.Name) // this is OK
#for(int i=0; i<Model.Functions.Count; i++)
{
#Html.Partial(Model.Functions[i].Name, Model.Functions[i])
}
and the FunctionView may look like this
#model Function
#Html.TextBoxFor(m => m.Name) // the generated html element's name is just 'Name'
#for(int i=0; i < Model.Units.Count; i++)
{
#Html.Partial(Model.Units[i].Name, Model.Units[i])
}
and this is UnitView
#model Unit
#Html.TextBoxFor(m => m.Name) // the generated html element's name is just 'Name'
So my question is, what should I do the make the Name attribute correct?
Thanks a lot
The only change you need to make in the above code is to use Editor instead of partial view.
So basically all you code will look similar to the following
#model License
#Html.TextBoxFor(m => m.Name)
// Editor will take care of the repetition and u don't need to explicitly pass in the name
// Since the model already have the attribute
#Html.EditorFor(Model.Functions)
Then create your editor template folder, "EditorTemplates", under "Shared" folder and name your view file as "Function"
Do the same for Unit class and you will get what you want.
As #Jack said... you can do this using Editors instead of PartialViews.
BUT... if you really want to use PartialViews, you can do it, but the model to pass should be the top one (License). This way is similar of what David Jessee proposed, but splitting the one view in several.
Pardon me for guessing at the problem, but are you asking for the DisplayName attribute?
It will define how the html helpers display your field lables
public class License
{
[DisplayName("License Name")]
public string Name { get; set; }
// Other Properties
public List<Function> Functions { get; set; }
}
public class Function
{
[DisplayName("Fun Name")]
public string Name { get; set; }
// Other Properties
public List<Unit> Units { get; set; }
}
public class Unit
{
[DisplayName("Unit Name")]
public string Name { get; set; }
// Other Properties
}
be sure to have
using System.ComponentModel;
in your model code.
If you want to be able to create all of the inputs for a complex object graph and have the entire graph be reconstituted by the model binder, the easiest way to approach it is to create a single view or partial view that renders the entire graph:
#for(int i=0;i<Functions.Length;i++){
#for(int j=0;j<Units.Length;j++){
#Html.EditorFor(Functions[i].Length[j].Unit)
}
}
The other option would be to find a way to pass the index of your element to the partial views for each leaf on your object graph.
Granted, a lot of people dont like the idea of rendering a complex model inside of a single view. However, your other option is to make the smaller child views for Units, etc. be dependent on having additional data either injected or provided by the context. 6 of one, half dozen of the other. Just about every time I've done the "academically correct" approach of making exactly one view or partial view for each type in an object graph, I ended up with a whole bunch of views that were not reusable to begin with and the only advantage I got was the ability to say, "Look! Lots of small files.....that are totally dependent on each other...why did I do that?"
Traditionally, I have built MVC applications using view models with Data Annotations attributes, and I dynamically render the views using editor templates. Everything works great, and it really cuts down on the time it takes me to build new views. My requirements have recently changed. Now, I can't define the view model at design time. The properties that will be rendered on the view are decided at run time based on business rules. Also, the validation rules for those properties may be decided at run time as well. (A field that is not required in my domain model, may be required in my view based on business rules). Also, the set of properties that will be rendered is not known until run time - User A may edit 6 properties from the model, while user B may edit 9 properties.
I am wondering if it is possible to create a model metadata provider that will supply my own metadata from business rules for an untyped view model like a collection of property names and values. Has anyone solved this problem?
I solved a similar problem by creating a more complex model, and using a custom editor template to make the model be rendered to look like a typical editor, but using the dynamic field information:
public class SingleRowFieldAnswerForm
{
/// <summary>
/// The fields answers to display.
/// This is a collection because we ask the MVC to bind parameters to it,
/// and it could cause issues if the underlying objects were being recreated
/// each time it got iterated over.
/// </summary>
public ICollection<IFieldAnswerModel> FieldAnswers { get; set; }
}
public interface IFieldAnswerModel
{
int FieldId { get; set; }
string FieldTitle { get; set; }
bool DisplayAsInput { get; }
bool IsRequired { get; }
bool HideSurroundingHtml { get; }
}
// sample implementation of IFieldAnswerModel
public class TextAreaFieldAnswer : FieldAnswerModelBase<TextAreaDisplayerOptions>
{
public string Answer { get; set; }
}
EditorTemplates/SingleRowFieldAnswerForm.cshtml:
#helper DisplayerOrEditor(IFieldAnswerModel answer)
{
var templateName = "FieldAnswers/" + answer.GetType().Name;
var htmlFieldName = string.Format("Answers[{0}]", answer.FieldId);
if (answer.DisplayAsInput)
{
#Html.EditorFor(m => answer, templateName, htmlFieldName)
// This will display validation messages that apply to the entire answer.
// This typically means that the input got past client-side validation and
// was caught on the server instead.
// Each answer's view must also produce a validation message for
// its individual properties if you want client-side validation to be
// enabled.
#Html.ValidationMessage(htmlFieldName)
}
else
{
#Html.DisplayFor(m => answer, templateName, htmlFieldName)
}
}
<div class="form-section">
<table class="form-table">
<tbody>
#{
foreach (var answer in Model.FieldAnswers)
{
if (answer.HideSurroundingHtml)
{
#DisplayerOrEditor(answer)
}
else
{
var labelClass = answer.IsRequired ? "form-label required" : "form-label";
<tr>
<td class="#labelClass">
#answer.FieldTitle:
</td>
<td class="form-field">
<div>
#DisplayerOrEditor(answer)
</div>
</td>
</tr>
}
}
}
</tbody>
</table>
</div>
So I populate my SingleRowFieldAnswerForm with a series of answer models. Each answer model type has its own editor template, allowing me to customize how different types of dynamic "properties" should be displayed. For example:
// EditorTemplates/FieldAnswers/TextAreaFieldAnswer.cshtml
#model TextAreaFieldAnswer
#{
var htmlAttributes = Html.GetUnobtrusiveValidationAttributes("Answer", ViewData.ModelMetadata);
// add custom classes that you want to apply to your inputs.
htmlAttributes.Add("class", "multi-line input-field");
}
#Html.TextAreaFor(m => m.Answer, Model.Options.Rows, 0, htmlAttributes)
#Html.ValidationMessage("Answer")
The next tricky part is that when you send this information to the server, it doesn't inherently know which type of IFieldAnswerModel to construct, so you can't just bind the SingleRowAnswerForm in your arguments list. Instead, you have to do something like this:
public ActionResult SaveForm(int formId)
{
SingleRowAnswerForm form = GetForm(formId);
foreach (var fieldAnswerModel in form.FieldAnswers.Where(a => a.DisplayAsInput))
{
// Updating this as a dynamic makes sure all the properties are bound regardless
// of the runtime type (since UpdateModel relies on the generic type normally).
this.TryUpdateModel((dynamic) fieldAnswerModel,
string.Format("Answers[{1}]", fieldAnswerModel.FieldId));
}
...
Since you provided MVC with each dynamic "property" value to bind to, it can bind each of the properties on each answer type without any difficulty.
Obviously I've omitted a lot of details, like how to produce the answer models in the first place, but hopefully this puts you on the right track.
You can use The ViewData Property in your ViewModel, View and Controller, it is dynamic, so it can be resolved at runtime.
I'm having a problem with display templates and dealing with interfaces and objects which implement the interface. In the example I have many objects, which I want to be rendered in a fixed way, I decided to create an interface and reference this in the view which I've decided to put into the shared display templates folder. DisplayFor doesn't seam to work for objects passed to it which implement the interface in the view, does any one know a solution to this.
Its probably easier to explain via code so I've wrote a quick example. The base interface and two classes which inherit from it:
public interface IPet
{
String Name { get; }
}
public class Dog : IPet
{
public String Name { get; set; }
}
public class Cat : IPet
{
public String Name { get; set; }
}
The example display template in shared display templates
#model IPet
<div>#Model.Name</div>
The example view model to be passed to the view
public class VM
{
public IPet Master { get; set; }
public IEnumerable<IPet> Minions { get; set; }
}
The controller (in this case to create mock information)
public ActionResult Index()
{
var viewModel = new VM();
viewModel.Master = new Cat(){Name = "Fluffy"};
var minions = new List<IPet>();
minions.Add(new Dog(){Name = "Dave"});
minions.Add(new Dog(){Name = "Pete"});
minions.Add(new Cat(){Name = "Alice"});
viewModel.Minions = minions;
return View(viewModel);
}
and finally the view which I would expect DisplayFor to work
#model ViewInheritance.Models.VM
<h2>Master</h2>
#Html.DisplayFor(x => x.Master)
<h2>Minions</h2>
#Html.DisplayFor(x => x.Minions)
Given that all the objects are are defined in the view model as the interfaces, howcome it fails to use the display template?
One solution I have found is to simply use the code
#Html.DisplayFor(x => x.Master, "IPet")
To recap, the question is:
Why does this happen?
Is there a way to make DisplayFor correctly work out that a type of Cat which implements IPet should in fact be looking at the common shared view IPet.cshtml?
Thanks
Starting a new MVC application and fixing the code to actually compile the view renders fine. It also renders fine when moving the view into the shared folder.
I Added setter to IPet:
public interface IPet
{
String Name { get; set; }
}
I updated implementation and added public accessors:
public class Dog : IPet
{
public String Name { get; set; }
}
public class Cat : IPet
{
public String Name { get; set; }
}
I left your VM alone and also did not change any code in your View.
Pressing F5, running the MVC application rendered the results as expected (See image).
Unfortunately, I don't think ASP.NET MVC currently supports automatically selecting templates based on implemented interfaces. I think this makes sense because a class could implement multiple interfaces, so if you had templates for more than one of those interfaces, which one should the framework choose?
You could use a base class instead of an interface if your design can cope with it:
Change IPet to a (possibly abstract) class.
Change IPet.cshtml to Pet.cshtml.
Otherwise I think you'll just need to explicitly tell the framework which template to use. Here are some options:
Decorate the view model properties with [UIHint].
Specify the template in your calls to your HtmlHelper methods such as DisplayFor.
Make your own ModelMetadataProvider and change the TemplateHint property of the resulting ModelMetadata.