Why don't DataAnnotations work on public fields? Example:
namespace Models
{
public class Product
{
[Display(Name = "Name")]
public string Title; // { get; set; }
}
}
public ActionResult Test()
{
return View(new Models.Product() { Title = "why no love?" });
}
#Html.LabelFor(m => m.Title) // will return 'Title' if field, or 'Name' if property
#Html.DisplayFor(m => m.Title)
If Title is a field, then the Display attribute seems to have no effect. If Title is changed to a property, it works as expected as displays "Name".
It would seem easy in this example to just change to a property, but I am trying to use the types from F# where they get compiled to a class with fields and not properties.
This was tested in ASP.NET 4 and MVC RC 3.
The reason why DataAnnotations do not work with fields is because the reflection-like mechanism that is used to retrieve the attributes (TypeDescriptor) only supports properties.
While it would not be easy, we could look into making this work with fields if there is enough demand.
Related
I work with asp.net mvc and devextreme
I have model with bool value and I will add form in the view
my problem is when I uncheck the checkbox I got a validation message the checkForImges field is required
I want to remove it and the red border
view code
#model ArchiveConfigManager.Models.QueryRetrieve
#using DevExtreme.AspNet.Mvc
#(Html.DevExtreme().Form().ID("form")
.ShowValidationSummary(false).ShowRequiredMark(false).
ShowOptionalMark(false).
ShowColonAfterLabel(false)
.ColCount(1)
.Items(items =>
{
items.AddGroup()
.Items(groupItems =>
{
groupItems.AddSimple().DataField("CheckForImages").
IsRequired(false).Label(l => l.Visible(false)).
Editor(e => e.CheckBox().Text("Check For Images"))
;})
;})
.FormData(Model)
)
model code
public class QueryRetrieve
{
public bool CheckForImages { set; get; }
}
the result is
The ASP.NET framework considers any non-nullable properties as required. So, to avoid the issue you can mark your CheckForImages property as nullable:
public class QueryRetrieve
{
public bool? CheckForImages { set; get; }
}
I've checked MVC post a list of complex objects as well as https://mhwelander.net/2014/03/26/asp-net-mvc-model-binding-not-occurring-when-posting-list-of-complex-types/ and a few other posts on the subject.
I would still rather ask the question, in order to understand and not simply copy paste an answer.
I have a complex type as below :
public interface ICaracteristic
{
int value { get; set; }
int max { get; set; }
string name { get; set; }
}
public class BaseAttributes : ICaracteristic
{
public int max { get; set; }
public int value { get; set; }
public string name { get; set; }
Random r = new Random();
}
And an object using multiples list of those types :
public class Character
{
// Infos
public string characterName { get; set; }
public string playerName { get; set; }
public IGame game { get; set; }
// Caracteristics
public List<ICaracteristic> baseAttr { get; set; }
public List<ICaracteristic> skills { get; set; }
public List<ICaracteristic> stats { get; set; }
public List<ICaracteristic> spendPoints { get; set; }
}
I spare you the constructors and a few other methods.
Now, for the creation, I simply ask my user to enter the names of character and player, no problem, it works wonderfully as these are simple strings.
For the edit, I get the character in "my db" (based on xml sheets but that doesn't matter here), and display it this way :
#model RPGmvc.Models.Character
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<h4>Character #Html.TextBoxFor(model => model.characterName)</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="row">
<!--SECTION BASE ATTRIBUTES-->
<div class="col-md-3">
<!-- #foreach (var item in Model.baseAttr)
{
<div>
<label>#Html.DisplayFor(model => item.name)</label>
<p>#Html.EditorFor(model => item.value, new { #class = "form-control" })</p>
</div>
}
For the display, this works perfectly fine : it shows the correct names and values of each base attribute of my character (as well as the values of the other lists).
However when I click the "save" button below the form, the model that is posted reset the values of that list... but not the characterName.
What is weird is that it seems to mix that model's different constructors : the playerName, which I don't use in my edit template, is set to "toby determined" (as in my empty basic constructor), but the characterName is the one of the current character being edited, instead of "new character" (as in said constructor)
I've tried to use a for instead of a foreach, thinking that maybe the index in the list would help to pass the correct values; but as I use an Interface that gave an error "Could not create an instance of interface".
I've tried with a custom editor, but it created the same problem as the "foreach".
(Here's the custom editor, just in case :
#model RPGmvc.Models.Caracteristic.ICaracteristic
<p>
#Html.DisplayFor(m => m.name)
#Html.DisplayFor(m => m.value)
</p>
Note that using an interface as model didn't cause any problem. )
I understand that I could apparently add an awful lot of annotations through my page to sort this, but since the custom editor didn't work and neither did the for with index, I first would like an explanation of what is happening :
What happens with the model binder here?
Could I "force" him to create an instance of an implementation of my interface, instead of the interface itself?
How come the HttpPost creates a custom object mixing my constructors ?
Thanks for your help.
Edit : Stephen Muelcke helped me by advising to remove the Interfaces from my model and using the real implementations. That almost worked :
Now my Post takes the correct values of the BaseAttributes, but the names of those are "null".
This is problematic since my datas comes from XML sheets, in which I search this way :
foreach (ICaracteristic battr in myCharac.baseAttr)
{
var currentNode = myDoc.SelectSingleNode("/character_sheet/base_attributes/" + battr.name.Replace(" ", "_").ToLower());
currentNode.InnerText = battr.value.ToString();
}
Which obviously fails, as name is "null".
I only did the change from "foreach" to "for" in the "Base Attributes" section. In that section, all names are set to null. In the other sections, no values are taken.
Any idea?
So, since Stephen Muecke doesn't care for the reputation :
--You can't model bind to an interface.
--You can't do a foreach on a list because modelbinder needs the index.
--You need a "control" sort on every value you want to use, so for complex types you need one even on the display name.
A good solution is to do a for loop, in which you include an #Html.Hiddenfor in addition to a #Html.DisplayFor, so that the display name is not editable but still has a control.
For the Interface and modelbinder lack of interaction, the only solution is to use the implementation of the interface. Sadly.
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)
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?"
I have a view model like this:
public class Event
{
public string Name { get; set; }
[DateRangeValidator]
public DateTimeSpan DateRange { get; set; }
}
And this contains another class called DateTimeSpan that looks like this:
public class DateTimeSpan
{
public DateTime Start { get; set; }
public DateTime End { get; set; }
}
I want to enable jQuery unobtrusive validation on the client side so I have written a custom data annotation validator (it inherits from ValidationAttribute and implements IClientValidatable) but it only seems to work if I apply the annotation to the Start and End properties of the DateTimeSpan class, and not if I apply it to the DateRange property of the Event class.
This is in my view:
#Html.LabelFor(x => x.DateRange.Start, "Start Date:") #Html.ValidationMessageFor(x => x.DateRange.Start)
#Html.TextBoxFor(x => x.DateRange.Start)
#Html.LabelFor(x => x.DateRange.End, "End Date:") #Html.ValidationMessageFor(x => x.DateRange.End)
#Html.TextBoxFor(x => x.DateRange.End)
ASP.NET MVC 3 will only inject the unobtrusive JavaScript data-* attributes into the HTML if the annotation is added to the Start and End properties, is there a way to make it work if the property is applied to the DateRange property instead?
I don't want my domain model class (DateRange, i.e. non view-model classes) to have to implement IClientValidatable because then I have to reference System.Web.Mvc in the domain model project.
Edit: Not sure if it's relevant but the DateRangeValidator attribute does checks to make sure the end date occurs after the start date etc.