Im new to Razor and View Models and I just want to ask if its possible to Display different string in [Display(Name = "")]
I tried adding condition in between the Display and the variable but it shows error
also tried this
public string Color {get;set;}
public String ColorDisplay
{
get
{
String name = "";
if (ColorId == 25 || ColorId == 26)
{
name = "Purple";
}
else
{
name = "Green";
}
return name;
}
}
Then in my View
#Html.LabelFor(m => m.ColorDisplay)
but seems not working as it just dispay ColorDisplay
In this issue, probably you may need a custom attribute to change text based on provided values inside attribute properties. Assumed that you want custom attribute usage like this:
[DisplayWhen("ColorId", 25, 26, "Purple", "Green")]
public String Color { get; set; }
And using HTML helper like this:
#Html.LabelFor(m => m.Color)
Then you should do these steps:
1) Create a custom attribute inherited from Attribute class.
public class DisplayWhenAttribute : Attribute
{
private string _propertyName;
private int _condition1;
private int _condition2;
private string _trueValue;
private string _falseValue;
public string PropertyName
{
get
{
return _propertyName;
}
}
public int Condition1
{
get
{
return _condition1;
}
}
public int Condition2
{
get
{
return _condition2;
}
}
public string TrueValue
{
get
{
return _trueValue;
}
}
public string FalseValue
{
get
{
return _falseValue;
}
}
public DisplayWhenAttribute(string propertyName, int condition1, int condition2, string trueValue, string falseValue)
{
_propertyName = propertyName;
_condition1 = condition1;
_condition2 = condition2;
_trueValue = trueValue;
_falseValue = falseValue;
}
}
2) Create custom metadata provider class which checks existence of custom attribute.
public class CustomModelMetadataProvider : DataAnnotationsModelMetadataProvider
{
protected override ModelMetadata CreateMetadata(IEnumerable<Attribute> attributes, Type containerType, Func<object> modelAccessor, Type modelType, string propertyName)
{
var metadata = base.CreateMetadata(attributes, containerType, modelAccessor, modelType, propertyName);
var additionalAttribute = attributes.OfType<DisplayWhenAttribute>().FirstOrDefault();
if (additionalAttribute != null)
{
metadata.AdditionalValues.Add("DisplayWhenAttribute", additionalValues);
}
return metadata;
}
}
3) Register CustomModelMetadataProvider into Application_Start() method inside Global.asax like this:
protected void Application_Start()
{
ModelMetadataProviders.Current = new CustomModelMetadataProvider();
}
4) Create your own (or override existing) LabelFor helper so that it checks against DisplayWhenAttribute, as in example below:
public static MvcHtmlString LabelFor<TModel, TProperty>(this HtmlHelper<TModel> helper, Expression<Func<TModel, TProperty>> expression, IDictionary<string, object> htmlAttributes)
{
string result = string.Empty;
var modelMetaData = expression.Compile().Invoke(helper.ViewData.Model);
string fieldName = ExpressionHelper.GetExpressionText(expression);
var containerType = typeof(TModel);
var containerProperties = containerType.GetProperties();
var propertyInfo = containerProperties.SingleOrDefault(x => x.Name == modelMetaData.PropertyName);
var attribute = propertyInfo.GetCustomAttributes(false).SingleOrDefault(x => x is DisplayWhenAttribute) as DisplayWhenAttribute;
var target = attribute.PropertyName; // target property name, e.g. ColorId
var condition1 = attribute.Condition1; // first value to check
var condition2 = attribute.Condition2; // second value to check
var targetValue = (int)containerType.GetProperty(target).GetValue(helper.ViewData.Model);
// checking provided values from attribute
if (targetValue == condition1 || targetValue == condition2)
{
result = attribute.TrueValue;
}
else
{
result = attribute.FalseValue;
}
// create <label> tag with specified true/false value
TagBuilder tag = new TagBuilder("label");
tag.MergeAttributes(htmlAttributes);
tag.Attributes.Add("for", helper.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldId(fieldName));
tag.SetInnerText(result);
return MvcHtmlString.Create(tag.ToString(TagRenderMode.Normal));
}
Some references to consider with:
Is it possible to create conditional attribute as DisplayIf?
How to extend MVC3 Label and LabelFor HTML helpers?
MVC custom display attribute
Related
I have following class
public class Device
{
[XmlElement("MobileDeviceType")]
public string DeviceType { get; set; }
}
I need extension method called "GetXElementName()" and I need to use the method like below.
string propertyDescription = (new Device()).DeviceType.GetXElementName(); // this shoud return "MobileDeviceType"
As a example
public static class ExtensionMethods
{
public static string GetXElementName<T>(this T source)
{
PropertyInfo prop = source.GetType().GetProperty(source.ToString());
string desc = prop.Name;
object[] attrs = prop.GetCustomAttributes(true);
object attr = attrs[0];
XmlElementAttribute descAttr = attr as XmlElementAttribute;
if (descAttr != null)
{
desc = descAttr.ElementName;
}
return desc;
}
}
Can I know how should I modify the method body to use the "GetXElementName()" method to use like I explained above.
You need to use Expressions to achieve that, because you need to know the member, not the value.
public static class Extensions
{
public static string GetXmlElementName<T, TProperty>(this T obj, Expression<Func<T, TProperty>> expression)
{
var memberExpression = expression.Body as MemberExpression;
if (memberExpression == null)
return string.Empty;
var xmlElementAttribute = memberExpression.Member.GetCustomAttribute<XmlElementAttribute>();
if (xmlElementAttribute == null)
return string.Empty;
return xmlElementAttribute.ElementName;
}
}
Usage:
public class MyClass
{
[XmlElement(ElementName = "Test")]
public string MyProperty { get; set; }
}
new MyClass().GetXmlElementName(x => x.MyProperty) // output "Test"
EDIT: another version, without an object instance (see Nyerguds comment)
I guess the most elegant way is make a generic class, with a generic method, so you can call it by specify only the T type parameter (TProperty is taken implicitly).
public class GetXmlElementName<T>
{
public static string From<TProperty>(Expression<Func<T, TProperty>> expression)
{
var memberExpression = expression.Body as MemberExpression;
if (memberExpression == null)
return string.Empty;
var xmlElementAttribute = memberExpression.Member.GetCustomAttribute<XmlElementAttribute>();
if (xmlElementAttribute == null)
return string.Empty;
return xmlElementAttribute.ElementName;
}
}
Usage:
GetXmlElementName<MyClass>.From(x => x.MyProperty) // output "Test"
I often have C# code like
[DisplayName("Number of Questions")]
public int NumberOfQuestions { get; set; }
Where I use the DisplayName property to add in spaces when it is displayed. Is there an option to tell MVC to add spaces by default if the DisplayName annotation is not explicitly provided?
Thanks
There are two ways.
1.Override LabelFor and creating a custom HTML helper:
Utility class for Custom HTML helper:
public class CustomHTMLHelperUtilities
{
// Method to Get the Property Name
internal static string PropertyName<T, TResult>(Expression<Func<T, TResult>> expression)
{
switch (expression.Body.NodeType)
{
case ExpressionType.MemberAccess:
var memberExpression = expression.Body as MemberExpression;
return memberExpression.Member.Name;
default:
return string.Empty;
}
}
// Method to split the camel case
internal static string SplitCamelCase(string camelCaseString)
{
string output = System.Text.RegularExpressions.Regex.Replace(
camelCaseString,
"([A-Z])",
" $1",
RegexOptions.Compiled).Trim();
return output;
}
}
Custom Helper:
public static class LabelHelpers
{
public static MvcHtmlString LabelForCamelCase<T, TResult>(this HtmlHelper<T> helper, Expression<Func<T, TResult>> expression, object htmlAttributes = null)
{
string propertyName = CustomHTMLHelperUtilities.PropertyName(expression);
string labelValue = CustomHTMLHelperUtilities.SplitCamelCase(propertyName);
#region Html attributes creation
var builder = new TagBuilder("label ");
builder.Attributes.Add("text", labelValue);
builder.Attributes.Add("for", propertyName);
#endregion
#region additional html attributes
if (htmlAttributes != null)
{
var attributes = HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes);
builder.MergeAttributes(attributes);
}
#endregion
MvcHtmlString retHtml = new MvcHtmlString(builder.ToString(TagRenderMode.SelfClosing));
return retHtml;
}
}
Use in CSHTML:
#Html.LabelForCamelCase(m=>m.YourPropertyName, new { style="color:red"})
Your label will display as 'Your Property Name'
2.Using Resource File:
[Display(Name = "PropertyKeyAsperResourceFile", ResourceType = typeof(ResourceFileName))]
public string myProperty { get; set; }
I will prefer the first solution. Because a resource file is intend to do a separate and reserved role in a project. Additionally the custom HTML helper can be reused once created.
I have this Enum (Notebook.cs):
public enum Notebook : byte
{
[Display(Name = "Notebook HP")]
NotebookHP,
[Display(Name = "Notebook Dell")]
NotebookDell
}
Also this property in my class (TIDepartment.cs):
public Notebook Notebook { get; set; }
It's working perfectly, I just have one "problem":
I created an EnumDDLFor and it's showing the name I setted in DisplayAttribute, with spaces, but the object doesn't receive that name in DisplayAttribute, receives the Enum name (what is correct), so my question is:
Is there a way to receive the name with spaces which one I configured in DisplayAttribute?
MVC doesn't make use of the Display attribute on enums (or any framework I'm aware of). You need to create a custom Enum extension class:
public static class EnumExtensions
{
public static string GetDisplayAttributeFrom(this Enum enumValue, Type enumType)
{
string displayName = "";
MemberInfo info = enumType.GetMember(enumValue.ToString()).First();
if (info != null && info.CustomAttributes.Any())
{
DisplayAttribute nameAttr = info.GetCustomAttribute<DisplayAttribute>();
displayName = nameAttr != null ? nameAttr.Name : enumValue.ToString();
}
else
{
displayName = enumValue.ToString();
}
return displayName;
}
}
Then you can use it like this:
Notebook n = Notebook.NotebookHP;
String displayName = n.GetDisplayAttributeFrom(typeof(Notebook));
EDIT: Support for localization
This may not be the most efficient way, but SHOULD work.
public static class EnumExtensions
{
public static string GetDisplayAttributeFrom(this Enum enumValue, Type enumType)
{
string displayName = "";
MemberInfo info = enumType.GetMember(enumValue.ToString()).First();
if (info != null && info.CustomAttributes.Any())
{
DisplayAttribute nameAttr = info.GetCustomAttribute<DisplayAttribute>();
if(nameAttr != null)
{
// Check for localization
if(nameAttr.ResourceType != null && nameAttr.Name != null)
{
// I recommend not newing this up every time for performance
// but rather use a global instance or pass one in
var manager = new ResourceManager(nameAttr.ResourceType);
displayName = manager.GetString(nameAttr.Name)
}
else if (nameAttr.Name != null)
{
displayName = nameAttr != null ? nameAttr.Name : enumValue.ToString();
}
}
}
else
{
displayName = enumValue.ToString();
}
return displayName;
}
}
On the enum, the key and resource type must be specified:
[Display(Name = "MyResourceKey", ResourceType = typeof(MyResourceFile)]
Here's a simplified (and working) version of akousmata's localized enum extension:
public static string DisplayName(this Enum enumValue)
{
var enumType = enumValue.GetType();
var memberInfo = enumType.GetMember(enumValue.ToString()).First();
if (memberInfo == null || !memberInfo.CustomAttributes.Any()) return enumValue.ToString();
var displayAttribute = memberInfo.GetCustomAttribute<DisplayAttribute>();
if (displayAttribute == null) return enumValue.ToString();
if (displayAttribute.ResourceType != null && displayAttribute.Name != null)
{
var manager = new ResourceManager(displayAttribute.ResourceType);
return manager.GetString(displayAttribute.Name);
}
return displayAttribute.Name ?? enumValue.ToString();
}
Note: I move enumType from a parameter to a local variable.
Example usage:
public enum IndexGroupBy
{
[Display(Name = "By Alpha")]
ByAlpha,
[Display(Name = "By Type")]
ByType
}
And
#IndexGroupBy.ByAlpha.DisplayName()
Here is a editor template that can be used with the extension method above:
#model Enum
#{
var listItems = Enum.GetValues(Model.GetType()).OfType<Enum>().Select(e =>
new SelectListItem
{
Text = e.DisplayName(),
Value = e.ToString(),
Selected = e.Equals(Model)
});
var prefix = ViewData.TemplateInfo.HtmlFieldPrefix;
var index = 0;
ViewData.TemplateInfo.HtmlFieldPrefix = string.Empty;
foreach (var li in listItems)
{
var fieldName = string.Format(CultureInfo.InvariantCulture, "{0}_{1}", prefix, index++);
<div class="editor-radio">
#Html.RadioButton(prefix, li.Value, li.Selected, new {#id = fieldName})
#Html.Label(fieldName, li.Text)
</div>
}
ViewData.TemplateInfo.HtmlFieldPrefix = prefix;
}
And here is an example usage:
#Html.EditorFor(m => m.YourEnumMember, "Enum_RadioButtonList")
Since you are worrying about visuals I would use a configurable approach:
public NotebookTypes NotebookType;
public enum NotebookTypes{
NotebookHP,
NotebookDell
}
public string NotebookTypeName{
get{
switch(NotebookType){
case NotebookTypes.NotebookHP:
return "Notebook HP"; //You may read the language dependent value from xml...
case NotebookTypes.NotebookDell:
return "Notebook Dell"; //You may read the language dependent value from xml...
default:
throw new NotImplementedException("'" + typeof(NotebookTypes).Name + "." + NotebookType.ToString() + "' is not implemented correctly.");
}
}
}
I made my first MVC helper to split long strings in a table and I also validate if string is NULL and user can send NullString ='NA' to show instead of empty string.
public static IHtmlString Split(
this HtmlHelper helper,
string source,
int size = 30,
string NullString = "")
Now I have the situation where the string is inside an object and this object can also be null.
#if (item.city == null)
{
<td>NA</td>
}
else
{
<td class="format">#item.city.name</td>
}
I want to do something generic, where I get an object and a property name. Then I can get the value from the object.
public static IHtmlString Split(
this HtmlHelper helper,
OBJECT source,
STRING property,
int size = 30,
string NullString = "")
Is there a way I can get source.property() from a generic object?
by request full code of my current function
public static IHtmlString Split(this HtmlHelper helper, string source, int size = 30, string NullString = "")
{
TagBuilder tb = new TagBuilder("td");
tb.Attributes.Add("class", "format");
if (source == null)
{
tb.InnerHtml = NullString;
}
else if (source.Length < size)
{
tb.InnerHtml = source;
}
else
{
int middle = source.Length / 2;
int before = source.LastIndexOf(' ', middle);
int after = source.IndexOf(' ', middle + 1);
if (before == -1 || (after != -1 && middle - before >= after - middle))
{
middle = after;
}
else
{
middle = before;
}
string s1 = source.Substring(0, middle);
string s2 = source.Substring(middle + 1);
tb.InnerHtml = s1 + "<br />" + s2;
}
MvcHtmlString result = new MvcHtmlString(tb.ToString(TagRenderMode.Normal));
return result;
}
One approach would be to get the type of the object and then check for the existence of a property of the given name. Your helper method would accept the following arguments:
public static IHtmlString Split(
this HtmlHelper helper,
object source,
string property = "",
int size = 30,
string NullString = "")
Then you would get the System.Type of the source object and decide whether to treat it as a string, or try to get the value of some specified property.
var stringToSplit = string.Empty;
if (source == null)
{
stringToSplit = NullString;
}
else if (string.IsNullOrEmpty(property))
{
stringToSplit = source.ToString();
}
else
{
Type type = source.GetType();
var propertyInfo = type.GetProperty(property);
if (propertyInfo != null)
{
var propertyValue = propertyInfo.GetValue(source);
stringToSplit = propertyValue != null ? propertyValue.ToString() : NullString;
}
else
{
stringToSplit = NullString;
}
}
Then you would check the length of stringToSplit and split it if necessary.
This is how I might do it.
Use Razor templates to create an extension method, that accepts an object to check and the lambda to grab the string.
public static class HtmlHelperExtensions
{
public static IHtmlString AlternateTemplates<TModel>(this HtmlHelper htmlHelper, TModel model,
Func<TModel, string> stringProperty, Func<string, HelperResult> template,
Func<object, HelperResult> nullTemplate) where TModel : class
{
HelperResult result;
if (model != null)
{
var propertyValue = stringProperty.Invoke(model);
var splitValue = YourCustomSplitFunction(propertyValue); // TODO: Impliment yout split function to return a string (in this case)
result = template(splitValue);
}
else
{
result = nullTemplate(null);
}
htmlHelper.ViewContext.Writer.Write(result);
return MvcHtmlString.Empty;
}
}
Given a model like this:
public class ViewModel
{
public Region Region { get; set; }
}
public class Region
{
public string City { get; set; }
}
My controller action as an example:
In my view I could call:
#Html.AlternateTemplates(Model.Region, x => x.City, #<div>#item</div>, #<div>N/A</div>)
So, it's checking if the object I send in (in this case it's the Region) is not null, then grab the property I specified (in this case City), then render it against my first html/razor template, otherwise use the alternate.
Easy.
Some reading: http://www.prideparrot.com/blog/archive/2012/9/simplifying_html_generation_using_razor_templates
I am creating a new web API and would like to allow the user to specify what fields get returned to them in the URL.
My current thoughts are:
For a sample model like this:
public class Value
{
public string ValueId { get; set; }
public int Number { get; set; }
public ValueInternal Internal { get; set; }
}
public class ValueInternal
{
public int Number { get; set; }
public string Something { get; set; }
}
and a URL like this
http://example.com/api/values/?_fields=Number,Internal(Something)
would return this
[
{
"Number": 0,
"Internal": {
"Number": 0
}
}
]
I have come up with the below method of achieving this, but it has some flaws. I.e. it couldn't handle if Internal was an enumerable of ValueInternal or has no support for include all or include all except, or if T and TResult are different types. Does anyone have any suggestions on how I can improve this or if there already exists a way of doing it that I am missing.
public static Expression<Func<T, TResult>> CreateSelector<T, TResult>() where TResult : new()
{
var property = "Number,Internal(Something)";
return arg => Process<T, TResult>(arg, default(TResult), property);
}
private static TResult Process<T, TResult>(T arg, TResult output, string propertyList) where TResult : new()
{
if (output == null)
{
output = new TResult();
}
if (string.IsNullOrEmpty(propertyList))
{
return output;
}
var properties = Regex.Split(propertyList, #"(?<!,[^(]+\([^)]+),");
foreach (var property in properties)
{
var propertyName = property;
var propertyInternalsMatch = Regex.Match(property, #"\(.*(?<!,[^(]+\([^)]+)\)");
var internalPropertyList = propertyInternalsMatch.Value;
if (!string.IsNullOrEmpty(internalPropertyList))
{
propertyName = property.Replace(internalPropertyList, "");
internalPropertyList = internalPropertyList.Replace("(", "");
internalPropertyList = internalPropertyList.Replace(")", "");
}
var tProperty = arg.GetType().GetProperty(propertyName);
if(tProperty == null) continue;
var tResultProperty = output.GetType().GetProperty(propertyName);
if(tResultProperty == null) continue;
if (tProperty.PropertyType.IsPrimitive || tProperty.PropertyType.IsValueType || (tProperty.PropertyType == typeof(string)))
{
tResultProperty.SetValue(output, tProperty.GetValue(arg));
}
else
{
var propertyInstance = Activator.CreateInstance(tResultProperty.PropertyType);
tResultProperty.SetValue(output, Process(tProperty.GetValue(arg), propertyInstance, internalPropertyList));
}
}
return output;
}
After a bit more reading I think I want to do something like the answer to this question LINQ : Dynamic select but that still has the same flaws my solution had
If you use OData support on your ASP .NET Web API you can jus use $select, but if you don't want to use it or your underlying system can't be easy queried using Linq, you can use a custom contract resolver, but in this case you are just reducing the serialization size, not the internal data traffic.
public class FieldsSelectContractResolver : CamelCasePropertyNamesContractResolver
{
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
var property = base.CreateProperty(member, memberSerialization);
property.GetIsSpecified = (t) =>
{
var fields = HttpContext.Current.Request["fields"];
if (fields != null)
{
return fields.IndexOf(member.Name, StringComparison.OrdinalIgnoreCase) > -1;
}
return true;
};
return property;
}
}
and in WebApiConfig.cs set the custom contract resolver:
var jsonFormatter = GlobalConfiguration.Configuration.Formatters.JsonFormatter;
jsonFormatter.SerializerSettings.ContractResolver = new FieldsSelectContractResolver();