Calling IEnumerable overload of DisplayNameFor [duplicate] - asp.net-mvc

This question already has answers here:
DisplayNameFor() From List<Object> in Model
(5 answers)
Closed 6 years ago.
This works for grabbing the headers(NOT VALUES):
#model IEnumerable<SomeModel>
...
<th>#Html.DisplayNameFor(m => m.SomeModelProperty)</th>
Which if SomeModelProperty were:
[Display(Name = "An Excellent Header")]
SomeModelProperty { get; set; }
Then it would display "An Excellent Header" in the header th element.
You would think this wouldn't work because the model is IEnumerable, which wouldn't have a m.SomeModelProperty, but it works because HtmlHelper has a HtmlHelper<IEnumerable<TModel>> such that the parameter of the lambda is TModel, not IEnumerable<TModel>. Since this just uses metadata, there is no need for an item from the collection. (Although intellisense on m. will lie to you and make you think it's a collection). I'm not sure when this cool overload was added, but is quite handy for Index.cshtml and does away with funky things like #Html.DisplayNameFor(m => #Model.FirstOrDefault().SomeModelProperty) which I want to avoid.
http://msdn.microsoft.com/en-us/library/hh833697(v=vs.108).aspx
However, I can't figure out how to get this to work when my model is not IEnumerable, but instead contains IEnumerable as a property, such as:
public class SomeList
{
public List<SomeModel> SomeModels { get; set; }
public int Page { get; set; }
public DateTime CurrentAsOf { get; set; }
}
I was hoping to be explicit with the generic type parameters, but I think the type parameters are specified by the engine that trickles down from the HtmlHelper created with the page. Can I declare a new HtmlHelper in the page, or somehow specify the type parameters explicitly?
Index.cshtml:
#model SomeList
//No idea how to do this:
#Html.DisplayNameFor<IEnumerable<SomeModel>>(m => m.SomeModelProperty)

Another similar workaround that works even if there are no rows could be:
...
#{var dummy = Model.FirstOrDefault(); }
<tr>
<th>
#Html.DisplayNameFor(model => dummy.SomeModelProperty)
</th>
...

I have exactly the same issue because I am using ViewModels so I have a ViewModel with an IEnumerable of actual objects as a property.
I did come across this post where if you check the answer the guy has created his own HTMLHelper for it to solve this issue http://forums.asp.net/t/1783733.aspx. His version is:
public static MvcHtmlString DisplayColumnNameFor<TModel, TClass, TProperty>(
this HtmlHelper<TModel> helper, IEnumerable<TClass> model,
Expression<Func<TClass, TProperty>> expression)
{
var name = ExpressionHelper.GetExpressionText(expression);
name = helper.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(name);
var metadata = ModelMetadataProviders.Current.GetMetadataForProperty(
() => Activator.CreateInstance<TClass>(), typeof(TClass), name);
return new MvcHtmlString(metadata.DisplayName);
}
You have to pass two arguments enumeration and expression rather than the normal just expression so you may prefer #franz answer. I can't see there being anyway round having to pass 2 arguments since it needs to know which property of the view model you are applying the expression to.

Related

MVC 5 and List of Complex type

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.

Html.TextBoxFor works but EditorFor throws an error - why?

I have the following code in my view:
#model MyDomain.ViewModels.EditCapacityViewModel
` ....
#Html.EditorFor<Capacity, >(m => m.Capacity.NumberOfWeeks)
This is my ViewModel - straight pass through to my model class
public class EditCapacityViewModel
{
public Capacity Capacity { get; set; }
}
My Capacity model class:
[Display(Name="Number of Weeks")]
[DataType(DataType.Date)]
public virtual int NumberOfWeeks { get; set; }
Using the above code, I get the error:
The model item passed into the dictionary is of type 'System.Int32', but this dictionary requires a model item of type 'System.String'.
If I convert the EditorFor to a TextBoxFor, it works.
I have no idea why this isn't working. What am I doing wrong?
EditorFor works only for strings without a custom template.
You'll need to create an Int32 template on path ~/Views/Shared/EditorTemplates/Int32.cshtml in order to make that work.
If you don't want to create a template for Int32 type, use TextBoxFor that will work as you already realized.

DisplayFormat for TextBoxFor in MVC

I need to round off 4 digit decimal to 2 digits and show in MVC 3 UI
Something like this 58.8964 to 58.90
Tried following this How should I use EditorFor() in MVC for a currency/money type? but not working.
As i am using TextBoxFor=> i removed ApplyFormatInEditMode here. Even
i tried with ApplyFormatInEditMode , but nothing works. Still showing
me 58.8964.
MyModelClass
[DisplayFormat(DataFormatString = "{0:F2}")]
public decimal? TotalAmount { get; set; }
#Html.TextBoxFor(m=>m.TotalAmount)
How can i achieve this round off?
I can't use EditorFor(m=>m.TotalAmount) here, as i need to pass some htmlAttributes
Edit:
After debugging with MVC source code, they internally use
string valueParameter = Convert.ToString(value, CultureInfo.CurrentCulture);
in MvcHtmlString InputHelper() method of InputExtension.cs that takes object value as parameter and converting. They are not using any display format there. How could we fix?
I managed to fix in this way. As i have a custom helper, i can able to manage with the below code
if (!string.IsNullOrEmpty(modelMetaData.DisplayFormatString))
{
string formatString = modelMetaData.DisplayFormatString;
string formattedValue = String.Format(CultureInfo.CurrentCulture, formatString, modelMetaData.Model);
string name = ExpressionHelper.GetExpressionText(expression);
string fullName = htmlHelper.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(name);
return htmlHelper.TextBox(fullName, formattedValue, htmlAttributes);
}
else
{
return htmlHelper.TextBoxFor(expression, htmlAttributes);
}
This works in MVC5
#Html.TextBoxFor(m => m.TotalAmount, "{0:0.00}")
You should use Html.EditorFor instead of Html.TextBoxFor if you want the custom format to be taken into account:
#Html.EditorFor(m => m.TotalAmount)
Also make sure that you have set ApplyFormatInEditMode to true:
[DisplayFormat(DataFormatString = "{0:F2}", ApplyFormatInEditMode = true)]
public decimal? TotalAmount { get; set; }
The DisplayFormat attribute is intended to be used only with templated helpers such as EditorFor and DisplayFor. This is the recommended approach instead of using TextBoxFor.
Try like this:
#{
var format = String.Format("{0:0.00}", Model.TotalAmount);
}
#Html.TextBoxFor(m => m.TotalAmount, format)
Hope it helps.
If you need more control of the field being displayed (vs. a built in EditorFor template) create a custom EditorFor template yourself. Inside an EditorFor template, the built in Html Helpers like #Html.TextBox() can be used with automatic client side validation and Display attributes which are usually only available to EditorFor and DisplayFor.
For example looping through a List of items. The input name has to have an unbroken index.
// Optional, if you require a custom input name for binding
String fieldName = String.Format("FieldNameForBinding[{0}].Property", index)
#Html.EditorFor(m => m.Property, "MyCustomEditorTemplate", fieldName)
Then you can setup your model
[CustomValidation(..optional..)]
[DisplayFormat(DataFormatString = "{0:F2}", ApplyFormatInEditMode = true)]
public decimal? TotalAmount { get; set; }
The EditorFor template (in e.g. ~/Views/Shared/EditorFor/MyCustomEditorTemplate.cshtml)
Note the name is left empty, it comes from the fieldName automatically. You can see it in the ViewData.TemplateInfo.HtmlFieldPrefix. Now you have complete control over the display of the field.
#model object
#{
Decimal? actualValue = (Decimal?)Model;
}
// The TextBox and ValidationMessage "names" are empty, they are filled
// from the htmlFieldName given via the #Html.EditorFor() call.
#Html.TextBox("", actualValue, new { #class = "cssClass" })
#Html.ValidationMessage("")
The idea is that you can customize the input field however you would like, and use e.g. #Html.TextBox() which outside of a custom EditorFor template would not utilize the built in client-side validation. You don't need to use the custom naming of the input field, that was simply an example of the usefulness of this solution. You can customize the way the data is presented (CSS, etc.) instead of relying on the built in EditorFor templates.
I solve that issue in this way:
#Html.TextBoxFor(model => model.dtArrivalDate, ModelMetadata.FromLambdaExpression(model => model.dtArrivalDate, ViewData).EditFormatString)
or create next extension:
public static class HtmlExtensions
{
public static MvcHtmlString TextBoxWithFormatFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, object htmlAttributes)
{
return htmlHelper.TextBoxFor(expression, ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData).EditFormatString, htmlAttributes);
}
}
But you need to set ApplyFormatInEditMode=true in DisplayFormatAttribute on your field.

MVC "Write-Once" Fields in Model and View

I want DRY/reuse as much editor code (View and Model) as possible. Some of my fields can only be set at creation, and never edited. Are there any pre-existing MVC/DataAnnotation features I should look at?
For example, maybe there is a data attribute that causes EditorFor to operate like DisplayFor if the value is non-null.
Model.cs
[Unchangeable]
string UserReferenceId { get; set; }
string Description { get; set; }
edit: to clarify my goal, I've added an answer with sample code for the approach I'm currently planning. If there's a better way/pre-existing feature for this, please let me know.
There are both the System.ComponentModel.ReadOnlyAttribute and System.ComponentModel.DataAnnotations.EditableAttribute (I think EditableAttribute is .NET 4). When model metadata is created for properties marked with either of these, you can see ModelMetadata.IsReadOnly will be set correctly.
Frustratingly, however, the built-in editor templates will still show editable fields, even if ModelMetadata.IsReadOnly is true.
You can, however, create your own shared editor template for each data type where you want this metadata property respected, and handle it specifically.
~/Views/Shared/EditorTemplates/String.cshtml
#model String
#if (ViewData.ModelMetadata.IsReadOnly)
{
#Html.Hidden(string.Empty, Model)
}
#(ViewData.ModelMetadata.IsReadOnly ? Html.DisplayText(string.Empty) : Html.TextBox(string.Empty))
View Model
[Editable(false)]
public string UserReferenceId { get; set; }
You'll note that in the event the metadata for the model indicates IsReadOnly, I draw a hidden field. This is so the value of that property is persisted across posts.
If you don't want the field displayed at all, but persisted across posts, you can use System.Web.Mvc.HiddenInputAttribute. In this case, only the hidden is drawn.
[HiddenInput(DisplayValue=false)]
public string UserReferenceId { get; set; }
Here's what I'm thinking of implementing if nothing similar is pre-existing:
EditableWhenNewModel.cs
public class EditableWhenNewModel : IIsNew
{
public bool IsNew { get { return recordId == 0; } }
string UserReferenceId { get; set; }
string Description { get; set; }
public void Save(RepositoryItem record) {
if (IsNew) { record.UserReferenceId = UserReferenceId; }
record.Description = Description;
... etc.
View.cshtml
#model EditableWhenNewModel
#Html.EditorWhenNewFor(m => m.UserReferenceId)
#Html.EditorFor(m => m.Description)
EditorWhenNewFor.cs
public static MvcHtmlString EditorWhenNewFor<TModel, TProperty>(
this HtmlHelper<TModel> htmlHelper,
Expression<Func<TModel, TProperty>> expression
) where TModel : IIsNew {
return htmlHelper.ViewData.Model.IsNew ?
htmlHelper.EditorFor(expression) :
htmlHelper.DisplayFor(expression);
}

Difference between MvcHtmlString.Create() and Html.Raw()

I am creating a MVC-Project. Using MVC 4 and Razor. After building some pages I was wondering: what is the difference between
MvcHtmlString.Create()
and
Html.Raw()
Would be nice if you could help me here to understand that.
Thanks in advance!
This is an excellent opportunity to look at the source code that's available to us for ASP.NET (https://github.com/aspnet/AspNetWebStack/).
Looking at HtmlHelper.cs, this is the code for Html.Raw():
public IHtmlString Raw(string value)
{
return new HtmlString(value);
}
public IHtmlString Raw(object value)
{
return new HtmlString(value == null ? null : value.ToString());
}
And this is the code for the MvcHtmlString class:
namespace System.Web.Mvc
{
public sealed class MvcHtmlString : HtmlString
{
[SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Justification = "MvcHtmlString is immutable")]
public static readonly MvcHtmlString Empty = Create(String.Empty);
private readonly string _value;
public MvcHtmlString(string value)
: base(value ?? String.Empty)
{
_value = value ?? String.Empty;
}
public static MvcHtmlString Create(string value)
{
return new MvcHtmlString(value);
}
public static bool IsNullOrEmpty(MvcHtmlString value)
{
return (value == null || value._value.Length == 0);
}
}
}
The most significant difference is that Html.Raw() accepts any object, while MvcHtmlString.Create() only accepts strings.
Also, Html.Raw() returns an interface, while the Create method returns an MvcHtmlString object.
Lastly, the Create deals with null differently.
There is no practical difference.
The MvcHtmlString.Create creates an instance of MvcHtmlString, while the Html.Raw method creates an instance of HtmlString, but MvcHtmlString just inherits from HtmlString, so they work the same.
The other answers focus more on the technical differences, if there are any. I think however there is another aspect: They serve different use cases / are used in different situations.
Html.Raw(...) is a method of IHtmlHelper. These are intented for use in razor views. It can be used to render raw HTML strings 'as is', without them getting encoded.
Since rendering user generated HTML content can be a security risk, it is very important to know when a string can contain HTML code, and for it to be sanitized. One of the main sources of security problems with old languages like ASP and PHP is rendering strings un-encoded per default, so you can see why, per default, ASP.NET MVC renders strings encoded. You want the (few) cases where your program renders a raw HTML string to be 'opt-in' and clear to see.
To better indicate these cases, it is good practice to store the HTML strings in a dedicated data type, like HtmlString. These objects will be rendered un-encoded, so you don't need Html.Raw. For this you can use MvcHtmlString.Create(...), or, more simply, new HtmlString(...), even if you don't have access to an IHtmlHelper (for example in a view model).
To illustrate this, consider this example of a view model for an ASP.NET MVC view with a title that does not contain HTML, and a content that does:
class MyViewModel
{
public string Title { get; set; }
public HtmlString SomeHtmlContent { get; set; }
}
This can be rendered on the page like this - notice that you don't need Html.Raw to render the HTML content:
<div>
<h1>#Model.Title</h1>
<div>
#Model.SomeHtmlContent
</div>
<div>

Resources