MVC: Access display name attribute in the controller - asp.net-mvc

Is it possible to access the display name of a parameter in the controller? for example, say I defined a parameter as
public class Class1
{
[DisplayName("First Name")]
public string firstname { get; set; }
}
I now want to be able to access the display name of firstname in my controller. Something like
string name = Model.Class1.firstName.getDisplayName();
Is there a method like getDisplayName() that I can use to get the display name?

First off, you need to get a MemberInfo object that represents that property. You will need to do some form of reflection:
MemberInfo property = typeof(Class1).GetProperty("Name");
(I'm using "old-style" reflection, but you can also use an expression tree if you have access to the type at compile-time)
Then you can fetch the attribute and obtain the value of the DisplayName property:
var attribute = property.GetCustomAttributes(typeof(DisplayNameAttribute), true)
.Cast<DisplayNameAttribute>().Single();
string displayName = attribute.DisplayName;

Found the answer at this link. I created an Html helper class, added its namespace to my view web.config and used it in my controller. All described in the link

Display name for Enum is like this
Here is example
public enum Technology
{
[Display(Name = "AspNet Technology")]
AspNet,
[Display(Name = "Java Technology")]
Java,
[Display(Name = "PHP Technology")]
PHP,
}
and method like this
public static string GetDisplayName(this Enum value)
{
var type = value.GetType();
var members = type.GetMember(value.ToString());
if (members.Length == 0) throw new ArgumentException(String.Format("error '{0}' not found in type '{1}'", value, type.Name));
var member = members[0];
var attributes = member.GetCustomAttributes(typeof(DisplayAttribute), false);
if (attributes.Length == 0) throw new ArgumentException(String.Format("'{0}.{1}' doesn't have DisplayAttribute", type.Name, value));
var attribute = (DisplayAttribute)attributes[0];
return attribute.GetName();
}
And your controller like this
public ActionResult Index()
{
string DisplayName = Technology.AspNet.GetDisplayName();
return View();
}
for class property follow this step
public static string GetDisplayName2<TSource, TProperty> (Expression<Func<TSource, TProperty>> expression)
{
var attribute = Attribute.GetCustomAttribute(((MemberExpression)expression.Body).Member, typeof(DisplayAttribute)) as DisplayAttribute;
return attribute.GetName();
}
and call this method in your controller like this
// Class1 is your classname and firstname is your property of class
string localizedName = Testing.GetDisplayName2<Class1, string>(i => i.firstname);

Related

Mvc Html.DropDownList on load replace some string

I've have this Dropdown that is generated by my Enum
#Html.DropDownList("MyType",
EnumHelper.GetSelectList(typeof(C_Survey.Models.QuestionType)),
"Select My Type",
new { #class = "form-control N_Q_type" })
Enum:
public enum QuestionType {
Single_Choice,
Multiple_Choice,
Range
}
My question is, how can I replace the _ with a space ?
I don't know much details of GetSelectList method there, but I assumed it receives a System.Enum and returning a SelectList collection like this:
public static SelectList GetSelectList(this Enum enumeration)
{
var source = Enum.GetValues(enumeration);
// other stuff
...
return new SelectList(...);
}
There are 2 approaches to solve this issue:
First Approach (Using Custom Attribute)
This approach involves creating a custom attribute to define display name (set attribute target to field or others which fit to entire enum members):
public class DisplayNameAttribute : Attribute
{
public string DisplayName { get; protected set; }
public DisplayNameAttribute(string value)
{
this.DisplayName = value;
}
public string GetName()
{
return this.DisplayName;
}
}
Hence, the enum structure should be modified to this:
public enum QuestionType
{
[DisplayName("Single Choice")]
Single_Choice,
[DisplayName("Multiple Choice")]
Multiple_Choice,
[DisplayName("By Range")]
Range
}
Later, it is necessary to modify GetSelectList method to accept custom attribute created above which includes DisplayName property:
public static SelectList GetSelectList<T>(this T enumeration)
{
var source = Enum.GetValues(typeof(T));
var items = new Dictionary<Object, String>();
var displaytype = typeof(DisplayNameAttribute);
foreach (var value in source)
{
System.Reflection.FieldInfo field = value.GetType().GetField(value.ToString());
DisplayNameAttribute attr = (DisplayNameAttribute)field.GetCustomAttributes(displaytype, false).FirstOrDefault();
items.Add(value, attr != null ? attr.GetName() : value.ToString());
}
return new SelectList(items, "Key", "Value");
}
Second Approach (Using Direct Type Cast & Lambda)
Similar to first approach, GetSelectList method will return SelectList from an enum, however instead of using custom attribute this approach uses member names to build select list items as shown below (T is enum type parameter):
public static SelectList GetSelectList<T>(this T enumeration)
{
var source = Enum.GetValues(typeof(T)).Cast<T>().Select(x => new SelectListItem() {
Text = x.ToString(),
Value = x.ToString().Replace("_", " ")
});
return new SelectList(source);
}
Probably GetSelectList method contents in your side is slightly different, but the basics should be same with those approaches.
Similar issues:
How do I populate a dropdownlist with enum values?
Display enum in ComboBox with spaces
enum with space property for dropdownlist

MVC html helper to generate enum drop down list dynamiclly

My model is:
public class DynamicEnum
{
public string Name {get; set;}
public int Value {get; set;}
}
public class ViewModel
{
public DynamicEnum DynamicEnum {get; set;}
}
Public ActionResult MyAction
{
var model = new ViewModel();
model.DynamicEnum = new DynamicEnum(){ Name = "System.DayOfWeek", Value = 2};
return View(model);
}
So in the view I need a HtmlHelper to dynamically generate DropDownListFor like:
#Html.EnumDropDownListFor(m => m.DynamicEnum)
I used MVC 5.2.3, Does any one have any idea?
One way to achieve this would be to use reflection:
public static class HtmlExtensions
{
public static IHtmlString EnumDropDownListFor<TModel>(
this HtmlHelper<TModel> html,
Expression<Func<TModel, DynamicEnum>> expression)
{
var metadata = ModelMetadata.FromLambdaExpression(expression, html.ViewData);
var dynamicEnum = (DynamicEnum)metadata.Model;
var enumType = Type.GetType(dynamicEnum.Name, true);
if (!enumType.IsEnum)
{
throw new Exception(dynamicEnum.Name + " doesn't represent a valid enum type");
}
// TODO: You definetely want to cache the values here to avoid the expensive
// reflection call: a ConcurrentDictionary<Type, IList<SelectListItem>> could be used
var enumNames = Enum.GetNames(enumType);
var values = enumNames.Select(x => new SelectListItem
{
Text = x,
Value = ((int)Enum.Parse(enumType, x)).ToString(),
}).ToList();
string name = ExpressionHelper.GetExpressionText(expression) + ".Value";
return html.DropDownList(name, values);
}
}
Remark: The HtmlHelper.EnumDropDownListFor extension method already exists in ASP.NET MVC so make sure that you bring the namespace in which you declared your custom extension method into scope to avoid collisions. Or just use a different method name.

Get property name in Tag Helper

ASP.NET Core introduced custom tag helpers which can be used in views like this:
<country-select value="CountryCode" />
However, I don't understand how can I get model property name in my classes:
public class CountrySelectTagHelper : TagHelper
{
[HtmlAttributeName("value")]
public string Value { get; set; }
public override void Process(TagHelperContext context, TagHelperOutput output)
{
...
// Should return property name, which is "CountryCode" in the above example
var propertyName = ???();
base.Process(context, output);
}
}
In the previous version I was able to do this by using ModelMetadata:
var metadata = ModelMetadata.FromLambdaExpression(expression, html.ViewData);
var property = metadata.PropertyName; // return "CountryCode"
How can I do the same in the new ASP.NET tag helpers?
In order to get property name, you should use ModelExpression in your class instead:
public class CountrySelectTagHelper : TagHelper
{
public ModelExpression For { get; set; }
public override void Process(TagHelperContext context, TagHelperOutput output)
{
var propertyName = For.Metadata.PropertyName;
var value = For.Model as string;
...
base.Process(context, output);
}
}
You can pass a string via the tag helper attribute.
<country-select value="#Model.CountryCode" />
The Value property will be populated by Razor with the value of Model.CountryCode by prepending #. So you get the value directly without the need to pass the name of a model property and accessing that afterwards.
I am not sure whether you got what you wanted. If you are looking to pass the complete model from view to the custom tag helper, this is how i do it.
You can pass in your current model from the view using any custom attributes. See the example below.
Assuming your model is Country
public class Country
{
public string Name { get; set; }
public string Code { get; set; }
}
Now declare a property in your custom tag helper of the same type.
public Country CountryModel { get; set; }
Your controller would look something like this
public IActionResult Index()
{
var country= new Country
{
Name = "United States",
Code = "USA"
};
return View("Generic", country);
}
In this setup, to access your model inside the taghelper, just pass it in like any other custom attribute/property
Your view should now look like something like this
<country-select country-model="#Model"></country-select>
You can receive it in your tag helper like any other class property
public override void Process(TagHelperContext context, TagHelperOutput output)
{
...
// Should return property name, which is "CountryCode" in the above example
var propertyName = CountryModel.Name;
base.Process(context, output);
}
Happy coding!

How to create an ActionLink with Properties for the View Model

I have a ViewModel with a Filter property that has many properties that I use to filter my data
Example:
class MyViewModel : IHasFilter
{
public MyData[] Data { get; set; }
public FilterViewModel Filter { get; set; }
}
class FilterViewModel
{
public String MessageFilter { get; set; }
//etc.
}
This works fine when using my View. I can set the properties of Model.Filter and they are passed to the Controller. What I am trying to do now, is create an ActionLink that has a query string that works with the above format.
The query string generated by my View from above looks like this:
http://localhost:51050/?Filter.MessageFilter=Stuff&Filter.OtherProp=MoreStuff
I need to generate an ActionLink in a different View for each row in a grid that goes to the View above.
I have tried:
Html.ActionLink(
item.Message,
"Index",
"Home",
new { Filter = new { MessageFilter = item.Message, }, },
null);
I also tried setting the routeValues argument to:
new MyViewModel { Filter = new FilterViewModel { MessageFilter = item.Message, }, },
But these do not generate the query string like the above one.
Interesting question (+1). I'm assuming that the purpose is to use the default model binder to bind the querystring parameters to to your Action parameters.
Out of the box I do not believe that the ActionLink method will do this for you (of course there is nothing stopping you from rolling your own). Looking in reflector we can see that when the object is added to the RouteValueDictionary, only key value pairs are added. This is the code that adds the key value pairs and as you can see there is no traversing the object properties.
foreach (PropertyDescriptor descriptor in TypeDescriptor.GetProperties(values))
{
object obj2 = descriptor.GetValue(values);
this.Add(descriptor.Name, obj2);
}
So for your object
var values = new { Filter = new Filter { MessageFilter = item.Message } }
the key being added is Filter and the value is your Filter object which will evaluate to the the fully qualified name of your object type.
The result of this is Filter=Youre.Namespace.Filter.
Edit possible solution depending on your exact needs
Extension Method does the work
Note that it uses the static framework methods ExpressionHelper and ModelMetadata (which are also used by the existing helpers) to determine the appropriate names that the default model binder will understand and value of the property respectively.
public static class ExtentionMethods
{
public static MvcHtmlString ActionLink<TModel, TProperty>(
this HtmlHelper<TModel> helper,
string linkText,
string actionName,
string controllerName,
params Expression<Func<TModel, TProperty>>[] expressions)
{
var urlHelper = new UrlHelper(helper.ViewContext.HttpContext.Request.RequestContext);
var url = urlHelper.Action(actionName, controllerName);
if (expressions.Any())
{
url += "?";
foreach (var expression in expressions)
{
var result = ExpressionHelper.GetExpressionText(expression);
var metadata = ModelMetadata.FromLambdaExpression<TModel, TProperty>(expression, helper.ViewData);
url = string.Concat(url, result, "=", metadata.SimpleDisplayText, "&");
}
url = url.TrimEnd('&');
}
return new MvcHtmlString(string.Format("<a href='{0}'>{1}</a>", url, linkText));
}
}
Sample Models
public class MyViewModel
{
public string SomeProperty { get; set; }
public FilterViewModel Filter { get; set; }
}
public class FilterViewModel
{
public string MessageFilter { get; set; }
}
Action
public ActionResult YourAction(MyViewModel model)
{
return this.View(
new MyViewModel
{
SomeProperty = "property value",
Filter = new FilterViewModel
{
MessageFilter = "stuff"
}
});
}
Usage
Any number of your view model properties can be added to the querystring through that last params parameter of the method.
#this.Html.ActionLink(
"Your Link Text",
"YourAction",
"YourController",
x => x.SomeProperty,
x => x.Filter.MessageFilter)
Markup
<a href='/YourAction/YourController?SomeProperty=some property value&Filter.MessageFilter=stuff'>Your Link Text</a>
Instead of using string.Format you could use TagBuilder, the querystring should be encoded to be safely passed in a URL and this extension method would need some additional validation but I think it could be useful. Note also that, though this extension method is built for MVC 4, it could be easily modified for previous versions. I didn't realize that that one of the MVC tags was was for version 3 until now.
You could create one RouteValueDictionary from a FilterViewModel instance and then use ToDictionary on that to pass to another RouteValues with all the keys prefixed with 'Filter.'.
Taking it further, you could construct a special override of RouteValueDictionary which accepts a prefix (therefore making it more useful for other scenarios):
public class PrefixedRouteValueDictionary : RouteValueDictionary
{
public PrefixedRouteValueDictionary(string prefix, object o)
: this(prefix, new RouteValueDictionary(o))
{ }
public PrefixedRouteValueDictionary(string prefix, IDictionary<string, object> d)
: base(d.ToDictionary(kvp=>(prefix ?? "") + kvp.Key, kvp => kvp.Value))
{ }
}
With that you can now do:
Html.ActionLink(
item.Message,
"Index",
"Home",
new PrefixedRouteValueDictionary("Filter.",
new FilterViewModel() { MessageFilter = item.Message }),
null);
The caveat to this, though, is that the Add, Remove, TryGetValue and this[string key] methods aren't altered to take into account the prefix. That can be achieved by defining new versions of those methods, but because they're not virtual, they'd only work from callers that know they're talking to a PrefixedRouteValueDictionary instead of a RouteValueDictionary.

ModelMetadata for complex type in editortemplate in asp.net mvc

I have a viewmodel that includes a complex property of type TestThing which is declared as:
public class TestThing
{
[Display(Name = "String3", Prompt = "String4")]
public string Test1 { get; set; }
[Display(Name = "String5", Prompt = "String6")]
public string Test2 { get; set; }
}
I have an EditorTemplate for this type in which I would like to be able to access the meta data for each of the child properties. If the template was for a string for example, I could access the Prompt text by using #ViewData.ModelMetadata.Watermark, but because it is a complex type, I cannot use this method.
Is there an alternative?
You could fetch the metadata for each property like this:
#{
var metadata = ModelMetadata
.FromLambdaExpression<TestThing, string>(x => x.Test2, ViewData);
var watermak = metadata.Watermark;
}
1) Check this out.
#Html.TextBoxFor
(m => m.Test1 ,
new {
#placeholder =
#ModelMetadata.FromLambdaExpression
(m=>m.Test1 ,ViewData).Watermark.ToString()
}
)

Resources