MVC html helper to generate enum drop down list dynamiclly - asp.net-mvc

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.

Related

Using ModelBinder from action

I'm using a third party reporting engine (stimulsoft) that calls an action on a controller via POST. Inside of the form, many fields are sent for the mechanics of the third party. Inside of the action I need some parameters all my parameters are inside of the URL.
I want to be able to use the model binder inside of my action.
At the moment I'm getting each fields one by one using this methods
var queryString = HttpUtility.ParseQueryString(Request.UrlReferrer.Query);
var preparedBy = queryString["preparedBy"];
var preparedAt = (queryString["preparedAt"] != null) ? Convert.ToDateTime(queryString["preparedAt"]) : DateTime.Today;
I would prefer to use a model and binding using the UrlReferrer. I've created a UrlReferrerValueProvider to bind from the action. I've tried that, but I'm getting a NullReferenceException on binder.BindModel line
public class UrlReferrerValueProvider : NameValueCollectionValueProvider
{
public UrlReferrerValueProvider(ControllerContext controllerContext)
: base(HttpUtility.ParseQueryString(controllerContext.HttpContext.Request.UrlReferrer.Query), CultureInfo.InvariantCulture)
{
}
}
public ActionResultat GetReportSnapshot()
{
var bindingContext = new ModelBindingContext()
{
ValueProvider = new UrlReferrerValueProvider(ControllerContext),
ModelName = "MyReportModel",
FallbackToEmptyPrefix = true
};
var binder = new DefaultModelBinder();
var myReportModel = binder.BindModel(ControllerContext, bindingContext);
[...]
return new EmptyResult();
}
public class MyReportModel
{
public string PreparedBy {get;set;}
public DateTime PreparedAt {get;set;}
}
Edited based on comments.
public class MyReportModel
{
public string PreparedBy {get;set;}
public DateTime PreparedAt {get;set;}
}
public class UrlReferrerValueProvider : NameValueCollectionValueProvider
{
public UrlReferrerValueProvider(ControllerContext controllerContext)
: base(HttpUtility.ParseQueryString(controllerContext.HttpContext.Request.UrlReferrer.Query), CultureInfo.InvariantCulture)
{
}
}
public ActionResult GetReportSnapshot(MyReportModel model)
{
this.UpdateModel(model, new UrlReferrerValueProvider(ControllerContext));
return new EmptyResult();
}

Get [Display] attribute value from resource in MVC

I have used the [Display] attribute for one of my enums:
public enum eCommentType
{
[Display(ResourceType = typeof(FaultManagementStrings), Name = "NormalComment")]
NormalComment = 0,
[Display(ResourceType = typeof(FaultManagementStrings), Name = "OpenningComment")]
OpenningComment = 1,
[Display(ResourceType = typeof(FaultManagementStrings), Name = "StartProgressComment")]
StartProgressComment = 2,
[Display(ResourceType = typeof(FaultManagementStrings), Name = "ClouserComment")]
ClouserComment = 3,
[Display(ResourceType = typeof(FaultManagementStrings), Name = "ReopennignComment")]
ReopennignComment = 4
}
Is it possible to create an Extention method that will reuse the exsisting MVC finctionallity of getting the Display attribute value from the specified resource ?
I would what something like that...
#Html.GetEnumDisplayAttributeValue(c=> comment.CommentType)
I know i could wirte something that will implement the required reflection and find the value of the resource type and the call resource manager and so on... but i think that maybe it is possible to use the exsisting built in functionally of mvc.. after all it is already done when you call a LabelFor helper.
is is possible or should i reinvent the wheel ?
I have had the same problem and created these extension methods:
using System;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Linq.Expressions;
using System.Web.Mvc;
using System.Web.Mvc.Html;
public static class EnumHelper
{
private static readonly SelectListItem[] SingleEmptyItem = new[] { new SelectListItem { Text = string.Empty, Value = string.Empty } };
public static MvcHtmlString EnumDropDownListFor<TModel, TEnum>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TEnum>> expression)
{
return htmlHelper.EnumDropDownListFor(expression, null);
}
public static MvcHtmlString EnumDropDownListFor<TModel, TEnum>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TEnum>> expression, object htmlAttributes)
{
var metadata = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);
var enumType = GetNonNullableModelType(metadata);
var values = Enum.GetValues(enumType).Cast<TEnum>();
var items =
values.Select(value => new SelectListItem
{
Text = GetName(value),
Value = value.ToString(),
Selected = value.Equals(metadata.Model)
});
if (metadata.IsNullableValueType)
{
items = SingleEmptyItem.Concat(items);
}
return htmlHelper.DropDownListFor(expression, items, htmlAttributes);
}
private static string GetName<TEnum>(TEnum value)
{
var displayAttribute = GetDisplayAttribute(value);
return displayAttribute == null ? value.ToString() : displayAttribute.GetName();
}
private static DisplayAttribute GetDisplayAttribute<TEnum>(TEnum value)
{
return value.GetType()
.GetField(value.ToString())
.GetCustomAttributes(typeof(DisplayAttribute), false)
.Cast<DisplayAttribute>()
.FirstOrDefault();
}
private static Type GetNonNullableModelType(ModelMetadata modelMetadata)
{
var realModelType = modelMetadata.ModelType;
var underlyingType = Nullable.GetUnderlyingType(realModelType);
return underlyingType ?? realModelType;
}
}
You can use these extension methods in your views as follows:
#Html.EnumDropDownListFor(c=> comment.CommentType)
You should now see a dropdownlist containing the enum values' names according to their DisplayAttribute.
The actual retrieval of the displayed enum value is done in the GetName method, which first uses the GetDisplayAttribute method to try to retrieve the DisplayAttribute of the enum value. If the enum value was indeed decorated with a DisplayAttribute, it will use that to retrieve the name to be displayed. If there is no DisplayAttribute, we just return the name of the enum value.
As was to be expected, Microsoft included an EnumDropDownListFor HTML Helper method in MVC 5.1 (RC1).

MVC: Access display name attribute in the controller

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);

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.

Custom validation server & client side for a dropdownlist populated with enum type

Let's say you have an enum like this:
public enum ColorsEnum
{
Undefined,
Blue,
Red,
Green
}
And a model like this:
public class Foo
{
public ColorsEnum PreferedColor { get; set; }
}
With a view like this:
#model WebUI.Models.Foo
#using (Html.BeginForm())
{
#Html.LabelFor(m => m.PreferedColor)
#Html.DropDownListForEnum(m => m.PreferedColor)
<input type="submit">
}
Here is the helper for the DropdownListForEnum:
public static IHtmlString DropDownListForEnum<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper,
Expression<Func<TModel, TProperty>> expression)
{
var metaData = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);
if (metaData.ModelType.IsEnum)
{
var names = Enum.GetNames(metaData.ModelType);
var translatedNames = GetTranslatedNames(metaData.ModelType);
var query = from p in names
select new SelectListItem
{
Text = translatedNames.ContainsKey(p) ? translatedNames[p] : p,
Value = p,
Selected = false
};
return htmlHelper.DropDownList(metaData.PropertyName, query.ToList());
}
else
{
throw new ApplicationException(
"The DropDownListForEnum helper function must be used with an enum property");
}
}
My question: how do you perform validation (client & server side) to be sure a valid color is choosed by the user? 'Undefined' color should be refused by the validation process.
Thanks.
I would use a nullable enum and a Required attribute on the view model:
public class MyViewModel
{
[Required]
public ColorsEnum? PreferedColor { get; set; }
}
So you could remove the Undefined value from your enum definition:
public enum ColorsEnum
{
Blue,
Red,
Green
}
and then slightly modify your helper:
public static class HtmlExtensions
{
public static IHtmlString DropDownListForEnum<TModel, TProperty>(
this HtmlHelper<TModel> htmlHelper,
Expression<Func<TModel, TProperty>> expression
)
{
var metaData = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);
var enumType = Nullable.GetUnderlyingType(metaData.ModelType);
if (enumType == null || !enumType.IsEnum)
{
throw new ApplicationException(
"The DropDownListForEnum helper function must be used with a nullable enum property"
);
}
var names = Enum.GetNames(enumType);
var translatedNames = GetTranslatedNames(metaData.ModelType);
var query =
from p in names
select new SelectListItem
{
Text = translatedNames.ContainsKey(p) ? translatedNames[p] : p,
Value = p,
Selected = false
};
return htmlHelper.DropDownList(
metaData.PropertyName,
query.ToList(),
"-- Select a color --"
);
}
}
and then have a simple controller to test:
public class HomeController : Controller
{
public ActionResult Index()
{
return View(new MyViewModel());
}
[HttpPost]
public ActionResult Index(MyViewModel model)
{
return View(model);
}
}
and a corresponding view:
#model MyViewModel
<script src="#Url.Content("~/Scripts/jquery.validate.js")" type="text/javascript"></script>
<script src="#Url.Content("~/Scripts/jquery.validate.unobtrusive.js")" type="text/javascript"></script>
#using (Html.BeginForm())
{
#Html.LabelFor(m => m.PreferedColor)
#Html.DropDownListForEnum(m => m.PreferedColor)
<input type="submit" />
}
If the first value of every enum you plan on using with DropDownListForEnum is always a "default" option that should be rejected by the validator, you might consider making use of optionLabel parameter of DropDownList.
Pop the first element out of query and pass that string as your third parameter in DropDownList.
return htmlHelper.DropDownList(metaData.PropertyName, query.ToList(),
/* some string containing default value extracted from your enum */);
That way, all DDLs created using DropDownListForEnum will use the first value of the enum as a "default" value, and all you would have to do to validate is to add a [Required] data annotation to PreferredColor.
Alternatively, you could remove the Default option from your enum and allow your HtmlHelper (or create a new one) to take a third parameter, a string containing the optionLabel that you would pass to DropDownList.
Either way, the idea is to make use of optionLabel as the validator will do all the work for you.

Resources