MVC.net get enum display name in view without having to refer to enum type in view - asp.net-mvc

I have the following helper method in a ViewModelBase class, which is inherited by other view Models:
public string GetEnumName<T>(Enum value)
{
Type enumType = typeof(T);
var enumValue = Enum.GetName(enumType, value);
MemberInfo member = enumType.GetMember(enumValue)[0];
var attrs = member.GetCustomAttributes(typeof(DisplayAttribute), false);
var outString = ((DisplayAttribute)attrs[0]).Name;
if (((DisplayAttribute)attrs[0]).ResourceType != null)
{
outString = ((DisplayAttribute)attrs[0]).GetName();
}
return outString;
}
I then call this from the view like this:
<p>
#{var rel = Model.GetEnumDisplayName<Enums.wheteverEnum>(Model.wheteverEnum); }
#rel
</p>
Question is - can I work this method so I don't have to tell it the type of the enum? Basically I'd like todo this for all enums:
#Model.GetEnumDisplayName(Model.wheteverEnum)
No typeof, no T, no need to add a reference to the Enums namespace in the View...
Possible?

You can simply remove the type parameter and make it an extension method.
public static string DisplayName(this Enum value)
{
Type enumType = value.GetType();
var enumValue = Enum.GetName(enumType, value);
MemberInfo member = enumType.GetMember(enumValue)[0];
var attrs = member.GetCustomAttributes(typeof(DisplayAttribute), false);
var outString = ((DisplayAttribute)attrs[0]).Name;
if (((DisplayAttribute)attrs[0]).ResourceType != null)
{
outString = ((DisplayAttribute)attrs[0]).GetName();
}
return outString;
}
#Model.wheteverEnum.DisplayName()

Could you not write this as an extension method? Something like...
public static class EnumExtensions
{
public static string ToDescription(this Enum e)
{
var attributes = (DisplayAttribute[])e.GetType().GetField(e.ToString()).GetCustomAttributes(typeof(DisplayAttribute), false);
return attributes.Length > 0 ? attributes[0].Description : string.Empty;
}
}
Usage:
#Model.WhateverEnum.ToDescription();

Nice work #jrummell!
I've added a small tweak below which captures the scenario where an enum doesn't have an associated Display attribute (currently it throws an exception)
/// <summary>
/// Gets the DataAnnotation DisplayName attribute for a given enum (for displaying enums values nicely to users)
/// </summary>
/// <param name="value">Enum value to get display for</param>
/// <returns>Pretty version of enum (if there is one)</returns>
/// <remarks>
/// Inspired by :
/// http://stackoverflow.com/questions/9328972/mvc-net-get-enum-display-name-in-view-without-having-to-refer-to-enum-type-in-vi
/// </remarks>
public static string DisplayFor(this Enum value) {
Type enumType = value.GetType();
var enumValue = Enum.GetName(enumType, value);
MemberInfo member = enumType.GetMember(enumValue)[0];
string outString = "";
var attrs = member.GetCustomAttributes(typeof(DisplayAttribute), false);
if (attrs.Any()) {
var displayAttr = ((DisplayAttribute)attrs[0]);
outString = displayAttr.Name;
if (displayAttr.ResourceType != null) {
outString = displayAttr.GetName();
}
} else {
outString = value.ToString();
}
return outString;
}

The answer of #jrummell in VB.NET for the few of us...
Module ModuleExtension
<Extension()>
Public Function DisplayName(ByVal value As System.Enum) As String
Dim enumType As Type = value.GetType()
Dim enumValue = System.Enum.GetName(enumType, value)
Dim member As MemberInfo = enumType.GetMember(enumValue)(0)
Dim attrs = member.GetCustomAttributes(GetType(DisplayAttribute), False)
Dim outString = CType(attrs(0), DisplayAttribute).Name
If (CType(attrs(0), DisplayAttribute).ResourceType IsNot Nothing) Then
outString = CType(attrs(0), DisplayAttribute).GetName()
End If
Return outString
End Function
End Module

for anyone who might reach to this question, I found this a lot easier than any thing else:
https://www.codeproject.com/articles/776908/dealing-with-enum-in-mvc
Just create a folder "DisplayTemplate" under "Views\Shared", and create an empty view (Name it "Enum") in the new folder "DisplayTemplate", and copy this code to it"
#model Enum
#if (EnumHelper.IsValidForEnumHelper(ViewData.ModelMetadata))
{
// Display Enum using same names (from [Display] attributes) as in editors
string displayName = null;
foreach (SelectListItem item in EnumHelper.GetSelectList(ViewData.ModelMetadata, (Enum)Model))
{
if (item.Selected)
{
displayName = item.Text ?? item.Value;
}
}
// Handle the unexpected case that nothing is selected
if (String.IsNullOrEmpty(displayName))
{
if (Model == null)
{
displayName = String.Empty;
}
else
{
displayName = Model.ToString();
}
}
#Html.DisplayTextFor(model => displayName)
}
else
{
// This Enum type is not supported. Fall back to the text.
#Html.DisplayTextFor(model => model)
}

Here is an extension method that I've written to do just this... it has a little extra logic in it to parse Enum names and split by capital letters. You can override any name by using the Display Attribute
public static TAttribute GetAttribute<TAttribute>(this ICustomAttributeProvider parameterInfo) where TAttribute : Attribute
{
object[] attributes = parameterInfo.GetCustomAttributes(typeof(TAttribute), false);
return attributes.Length > 0 ? (TAttribute)attributes[0] : null;
}
public static bool HasAttribute<TAttribute>(this ICustomAttributeProvider parameterInfo) where TAttribute : Attribute
{
object[] attributes = parameterInfo.GetCustomAttributes(typeof(TAttribute), false);
return attributes.Length > 0 ? true : false;
}
public static string ToFriendlyEnum(this Enum type)
{
return type.GetType().HasAttribute<DescriptionAttribute>() ? type.GetType().GetAttribute<DescriptionAttribute>().Description : type.ToString().ToFriendlyEnum();
}
public static string ToFriendlyEnum(this string value)
{
char[] chars = value.ToCharArray();
string output = string.Empty;
for (int i = 0; i < chars.Length; i++)
{
if (i <= 0 || chars[i - 1].ToString() != chars[i - 1].ToString().ToUpper() && chars[i].ToString() != chars[i].ToString().ToLower())
{
output += " ";
}
output += chars[i];
}
return output.Trim();
}
The GetAttribute extension methods could be slightly overkill, but I use them elsewhere in my projects, so they got reused when I wrote my Enum extension. You could easily combine them back into the ToFriendlyEnum(this Enum type) method

The suggested sollutions does not worked for me with MVC3: so the helper below is good.:
public static string GetEnumDescription(this Enum value)
{
Type type = value.GetType();
string name = Enum.GetName(type, value);
if (name != null)
{
FieldInfo field = type.GetField(name);
if (field != null)
{
string attr = field.GetCustomAttributesData()[0].NamedArguments[0].TypedValue.Value.ToString();
if (attr == null)
{
return name;
}
else
{
return attr;
}
}
}
return null;
}

Related

How do I programmatically add records to an Umbraco v8 form?

I'm looking to add records to an Umbraco v8 form. I know I need the form guid. Is this how I'd do it? Something like this?
public void PostFormData()
{
Guid FormGuid = new Guid("8494a8f0-94da-490e-bd61-7e658c226142");
var form = _formService.Get(FormGuid);
//place for field data into fieldDic
var fieldDic = new Dictionary<Guid, RecordField>();
var firstName = form.AllFields.First(f => f.Alias == "firstName");
var firstNameRecord = new RecordField(firstName);
firstNameRecord.Values = new List<object>() { "Mad Max" };
fieldDic.Add(firstName.Id, firstNameRecord);
var record = new Record()
{
Created = DateTime.Now,
Form = form.Id,
RecordFields = fieldDic,
State = FormState.Submitted,
};
record.RecordData = record.GenerateRecordDataAsJson();
_recordStorage.InsertRecord(record, form);
}
Here's how I do it. Note, I'm hard-coding the Record.UmbracoPageId to -1 while you might want to actually pass in the correct page ID.
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using Umbraco.Core;
using Umbraco.Core.Composing;
using Umbraco.Core.Logging;
using Umbraco.Forms.Core.Data.Storage;
using Umbraco.Forms.Core.Models;
using Umbraco.Forms.Core.Persistence.Dtos;
using Umbraco.Forms.Core.Services;
namespace myProject.Services
{
public class FormServiceComposer : IUserComposer
{
public void Compose(Composition composition)
{
composition.Register<IFormService, FormService>(Lifetime.Request);
}
}
public interface IFormService
{
void InsertFormData(Guid formGuid, object formModel, string ipAddress);
}
public class FormService : IFormService
{
private readonly ILogger _logger;
private readonly Umbraco.Forms.Core.Services.IFormService _formService;
private readonly IRecordStorage _recordStorage;
private readonly IRecordFieldStorage _recordFieldStorage;
private readonly IWorkflowService _workflowService;
public FormService(ILogger logger, Umbraco.Forms.Core.Services.IFormService formService, IRecordStorage recordStorage, IRecordFieldStorage recordFieldStorage, IWorkflowService workflowService)
{
_logger = logger;
_formService = formService;
_recordStorage = recordStorage;
_recordFieldStorage = recordFieldStorage;
_workflowService = workflowService;
}
#region IFormService
public void InsertFormData(Guid formGuid, object formModel, string ipAddress)
{
try
{
Form form = _formService.GetForm(formGuid);
Record record = new Record();
foreach (Field field in form.AllFields)
{
string caption = CleanCaption(field.Caption);
if (formModel.GetType().GetProperty(caption) == null) continue;
var propertyValue = formModel.GetType().GetProperty(caption).GetValue(formModel, null);
if (propertyValue != null)
{
List<object> values = ExtractValues(propertyValue);
RecordField recordField = new RecordField
{
Alias = field.Alias,
FieldId = field.Id,
Field = field,
Key = Guid.NewGuid(),
Record = record.Id,
Values = values
};
_recordFieldStorage.InsertRecordField(recordField);
record.RecordFields.Add(recordField.Key, recordField);
}
}
record.Form = formGuid;
record.IP = ipAddress;
record.UmbracoPageId = -1;
record.State = Umbraco.Forms.Core.Enums.FormState.Approved;
record.RecordData = record.GenerateRecordDataAsJson();
_recordStorage.InsertRecord(record, form);
_recordStorage.DisposeIfDisposable();
}
catch (Exception ex)
{
_logger.Error<FormService>(ex, "Failed inserting Umbraco Forms data for {formGuid}");
}
}
#endregion IFormService
#region Private
private string CleanCaption(string caption)
{
Regex rgx = new Regex("[^a-zA-Z0-9 -]");
return rgx.Replace(caption.Trim().Replace(" ", ""), "");
}
private List<object> ExtractValues(object propertyValue)
{
List<object> result = new List<object>();
if (propertyValue is string == false && propertyValue.GetType().GetGenericTypeDefinition() == typeof(List<>))
{
IEnumerable<object> _propertyValue = (IEnumerable<object>)propertyValue;
if (_propertyValue.Any())
{
if (_propertyValue.First().GetType().GetProperties().Count() > 1)
{
JArray _properties = JArray.Parse(JsonConvert.SerializeObject(propertyValue));
foreach (JToken item in _properties)
{
string _value = string.Empty;
foreach (var _property in _propertyValue.First().GetType().GetProperties())
{
string _key = _property.Name;
_value = _value + (_value == "" ? "" : " - ") + item[_key].ToString();
}
result.Add(_value);
}
}
else
{
string _key = _propertyValue.First().GetType().GetProperties().First().Name;
JArray _properties = JArray.Parse(JsonConvert.SerializeObject(propertyValue));
foreach (JToken item in _properties)
{
result.Add(item[_key].ToString());
}
}
}
}
else
{
result.Add(propertyValue);
}
return result;
}
#endregion Private
}
}

asp.net mvc: .net core: how to map stored proc result set to class fields

I got this simple model class:
public class PrvProduct
{
[Key]
public Int32 ProductId
{
get; set;
}
public Int64 ProductLineId;
public String MfgPartNumber;
public String ProductName;
public String ProductDescription;
}
I'm trying to call a stored proc, using .net core, it works fine, returns a list of PrvProduct objects. problem is: their fields are empty, unless I fill them up myself in code. ProductId is always there, not sure why (maybe because i typed there the [key] attribute?) but the rest are not.
is there a simple way to map class fields to results sets, like in ado.net (i would just do SQLDataAdapter.Fill(MyDataTable) and the MyDataTable fields will have the values by field name)... or do i have to do option 2 below every time?
Many thanks!
string sqlQuery = "EXEC Maint.GetProductList '" + sNameFilter + "'";
//option 1: this gets no value in the fields of each PrvProduct (ProductId gets value maybe because its [key], the others don't)
IQueryable results = _context.Products.FromSql(sqlQuery).AsNoTracking();
//option 2: this works, but... do i have to do this for every stored proc i call, every field, or is there a beter way to map class fields to returned results fields?
List<PrvProduct> oList = new List<PrvProduct>();
using (var command = _context.Database.GetDbConnection().CreateCommand())
{
command.CommandText = sqlQuery;
command.CommandType = CommandType.Text;
_context.Database.OpenConnection();
using (var result = command.ExecuteReader())
{
while (result.Read())
{
// Map to your entity
oList.Add(new PrvProduct
{
ProductId = result.GetInt32(0),
ProductName = result.GetString(1)
});
}
}
}
In EF Core, if you execute a stored procedure using one of your DbSet entities then it will map it automatically. The problem is that in many case you need to map a stored procedure to a DTO, for example, and the DTO is not part of your DbSet entities. In those cases you need to go back in time and map it manually which is a waste of time.
In order to avoid mapping the data reader manually, I added a bunch of extension methods that do it for you. The code is not perfect and I'm still improving it but it's good enough in most of the cases.
Once you add the extensions methods I'm gonna describe below, you can use it like this:
return dbContext.Database.SqlQuery<SalesReportDTO>("spGetSalesReport",
SqlParameterBuilder.Build("customerId", customerId),
SqlParameterBuilder.Build("dateFrom", from),
SqlParameterBuilder.Build("dateTo", to)).ToList();
DatabaseFacadeExtensions: adds extensions methods to DatabaseFacade class, allowing you to call the method SqlQuery from dbContext.Database just like we used to do with Entity Framework 6.
public static class DatabaseFacadeExtensions
{
public static List<T> SqlQuery<T>(this DatabaseFacade database, string query, params SqlParameter[] parameters)
{
return SqlQuery<T>(database, query, null, CommandType.StoredProcedure, parameters);
}
public static List<T> SqlQuery<T>(this DatabaseFacade database, string query, CommandType commandType, params SqlParameter[] parameters)
{
return SqlQuery<T>(database, query, null, commandType, parameters);
}
public static List<T> SqlQuery<T>(this DatabaseFacade database, string query, int? commandTimeout, params SqlParameter[] parameters)
{
return SqlQuery<T>(database, query, commandTimeout, CommandType.StoredProcedure, parameters);
}
public static List<T> SqlQuery<T>(this DatabaseFacade database, string query, int? commandTimeout, CommandType commandType, params SqlParameter[] parameters)
{
using (var cmd = database.GetDbConnection().CreateCommand())
{
cmd.CommandText = query;
cmd.CommandType = commandType;
if (commandTimeout.HasValue)
{
cmd.CommandTimeout = commandTimeout.Value;
}
cmd.Parameters.AddRange(parameters);
if (cmd.Connection.State == System.Data.ConnectionState.Closed)
{
cmd.Connection.Open();
}
try
{
using (var reader = cmd.ExecuteReader())
{
return reader.MapToList<T>();
}
}
finally
{
cmd.Connection.Close();
}
}
}
}
DbDataReaderExtensions: adds extensions methods to DbDataReader class so it can map the data reader to your own clases.
public static class DbDataReaderExtensions
{
public static List<T> MapToList<T>(this DbDataReader dr)
{
var objList = new List<T>();
if (dr.HasRows)
{
bool isSingleValue = typeof(T).IsPrimitive || typeof(T) == typeof(string);
IEnumerable<PropertyInfo> props = null;
Dictionary<string, DbColumn> colMapping = null;
if (!isSingleValue)
{
props = typeof(T).GetRuntimeProperties();
colMapping = dr.GetColumnSchema()
.Where(x => props.Any(y => y.Name.ToLower() == x.ColumnName.ToLower()))
.ToDictionary(key => key.ColumnName.ToLower());
}
while (dr.Read())
{
T obj;
if (isSingleValue)
{
obj = (T)dr.GetValue(0);
}
else
{
obj = Activator.CreateInstance<T>();
foreach (var prop in props)
{
string propertyName = prop.Name.ToLower();
if (!colMapping.ContainsKey(propertyName))
{
continue;
}
var val = dr.GetValue(colMapping[propertyName].ColumnOrdinal.Value);
if (val != DBNull.Value)
{
// enum property
if (prop.PropertyType.IsEnum)
{
prop.SetValue(obj, Enum.ToObject(prop.PropertyType, val));
}
// nullable enum property
if (prop.PropertyType.IsGenericType && prop.PropertyType.GetGenericTypeDefinition() == typeof(Nullable<>) && Nullable.GetUnderlyingType(prop.PropertyType).IsEnum)
{
prop.SetValue(obj, Enum.ToObject(Nullable.GetUnderlyingType(prop.PropertyType), val));
}
else
{
prop.SetValue(obj, val);
}
}
}
}
objList.Add(obj);
}
}
return objList;
}
public static T MapToObject<T>(this DbDataReader dr)
{
var props = typeof(T).GetRuntimeProperties();
if (dr.HasRows)
{
var colMapping = dr.GetColumnSchema()
.Where(x => props.Any(y => y.Name.ToLower() == x.ColumnName.ToLower()))
.ToDictionary(key => key.ColumnName.ToLower());
if (dr.Read())
{
T obj = Activator.CreateInstance<T>();
foreach (var prop in props)
{
var val = dr.GetValue(colMapping[prop.Name.ToLower()].ColumnOrdinal.Value);
prop.SetValue(obj, val == DBNull.Value ? null : val);
}
return obj;
}
}
return default(T);
}
}
The next class is optional but I use to build parameters in a simpler way and it's needed in the example I described above:
public class SqlParameterBuilder
{
public static SqlParameter Build(string name, bool? value)
{
if (value.HasValue)
{
return new SqlParameter() { ParameterName = name, Value = value.Value };
}
return new SqlParameter() { ParameterName = name, Value = DBNull.Value };
}
public static SqlParameter Build(string name, int? value)
{
if (value.HasValue)
{
return new SqlParameter() { ParameterName = name, Value = value.Value };
}
return new SqlParameter() { ParameterName = name, Value = DBNull.Value };
}
public static SqlParameter Build(string name, string value)
{
if (value != null)
{
return new SqlParameter() { ParameterName = name, Value = value };
}
return new SqlParameter() { ParameterName = name, Value = DBNull.Value };
}
public static SqlParameter Build(string name, DateTime? value)
{
if (value != null)
{
return new SqlParameter { ParameterName = name, SqlDbType = SqlDbType.DateTime, Value = value };
}
return new SqlParameter() { ParameterName = name, Value = DBNull.Value };
}
public static SqlParameter Build(string name, Guid? value)
{
if (value.HasValue)
{
return new SqlParameter { ParameterName = name, SqlDbType = SqlDbType.UniqueIdentifier, Value = value };
}
return new SqlParameter() { ParameterName = name, Value = DBNull.Value };
}
public static SqlParameter Build(string name, int[] values)
{
SqlParameter par = new SqlParameter(name, SqlDbType.Structured);
par.TypeName = "dbo.IntParameterList";
DataTable dt = new DataTable();
dt.Columns.Add("id", typeof(int));
par.Value = dt;
if (values != null)
{
foreach (int value in values.Where(p => p != 0))
{
dt.Rows.Add(value);
}
}
return par;
}
public static SqlParameter Build(string name, string[] values, VarcharParameterListEnum varcharParameterListType = VarcharParameterListEnum.Varchar50)
{
SqlParameter par = new SqlParameter(name, SqlDbType.Structured);
switch(varcharParameterListType)
{
case VarcharParameterListEnum.Varchar15:
par.TypeName = "dbo.Varchar15ParameterList";
break;
case VarcharParameterListEnum.Varchar50:
par.TypeName = "dbo.Varchar50ParameterList";
break;
case VarcharParameterListEnum.Varchar100:
par.TypeName = "dbo.Varchar100ParameterList";
break;
case VarcharParameterListEnum.Varchar255:
par.TypeName = "dbo.Varchar255ParameterList";
break;
case VarcharParameterListEnum.Varchar510:
par.TypeName = "dbo.Varchar510ParameterList";
break;
}
DataTable dt = new DataTable();
dt.Columns.Add("textValue", typeof(string));
par.Value = dt;
if (values != null)
{
foreach (var value in values.Where(p => !string.IsNullOrWhiteSpace(p)))
{
dt.Rows.Add(value);
}
}
return par;
}
}

MVC 5 Entity Framework 6 Execute Stored Procedure

I'm stuck. I have an existing application with an extremely large database and extensive library of stored procedures and functions. All I want to do is use a DbContext to execute a stored procedure and return a set of data or map to one of the entities in the context. Is that something magical I haven't discovered on the net somewhere? Someone, anyone, please help. Here's what I've got so far (and it doesn't return anything, the result is -1):
var contacts = db.Database.ExecuteSqlCommand("Contact_Search #LastName, #FirstName",
new SqlParameter("#LastName", GetDataValue(args.LastName)),
new SqlParameter("#FirstName", GetDataValue(args.FirstName)));
Executing that returns -1. I also tried something to the effect of this with no success:
DbRawSqlQuery<Contact> data = db.Database.SqlQuery<Contact>
("EXEC Contact_Search #LastName, #FirstName",
GetDataValue(args.LastName),
GetDataValue(args.FirstName));
I understand that I could add an edmx and map to a stored procedure that way, but that is not the preferred method. Again, our database contains nearly 450 million records and a library of almost 3,000 stored procedures and functions. It would be a nightmare to maintain. Am I even starting in the right direction? Is Entity Framework the right choice?
Wow, it seems right after I give up, I somehow stumble upon the answer. I found a FANTASTIC post about executing stored procedures and after reading up, this was my solution:
var contacts = db.Database.SqlQuery<Contact>("Contact_Search #LastName, #FirstName",
So, many thanks to Anuraj for his excellent post! The key to my solution was to first use SqlQuery instead of ExecuteSqlCommand, and also to execute the method mapping to my entity model (Contact).
This code is better than SqlQuery() because SqlQuery() doesn't recognise the [Column] attribute.
Here it is on a silver platter.
public static class StoredProcedureExtensions {
/// <summary>
/// Execute Stored Procedure and return result in an enumerable object.
/// </summary>
/// <typeparam name="TEntity">Type of enumerable object class to return.</typeparam>
/// <param name="commandText">SQL query.</param>
/// <param name="parameters">SQL parameters.</param>
/// <param name="readOnly">Determines whether to attach and track changes for saving. Defaults to true and entities will not be tracked and thus a faster call.</param>
/// <returns>IEnumerable of entity type.</returns>
public static IEnumerable<TEntity> GetStoredProcedureResults<TEntity>(this DbContext dbContext, string query, Dictionary<string, object> parameters, bool readOnly = true) where TEntity : class, new()
{
SqlParameter[] sqlParameterArray = DbContextExtensions.DictionaryToSqlParameters(parameters);
return dbContext.GetStoredProcedureResults<TEntity>(query, sqlParameterArray, readOnly);
}
/// <summary>
/// Execute Stored Procedure and return result in an enumerable object.
/// </summary>
/// <typeparam name="TEntity">Type of enumerable object class to return.</typeparam>
/// <param name="commandText">SQL query.</param>
/// <param name="parameters">SQL parameters.</param>
/// <param name="readOnly">Determines whether to attach and track changes for saving. Defaults to true and entities will not be tracked and thus a faster call.</param>
/// <returns>IEnumerable of entity type.</returns>
public static IEnumerable<TEntity> GetStoredProcedureResults<TEntity>(this DbContext dbContext, string commandText, SqlParameter[] sqlParameterArray = null, bool readOnly = true) where TEntity : class, new()
{
string infoMsg = commandText;
try
{
//---- For a better error message
if (sqlParameterArray != null)
{
foreach (SqlParameter p in sqlParameterArray)
{
infoMsg += string.Format(" {0}={1}, ", p.ParameterName, p.Value == null ? "(null)" : p.Value.ToString());
}
infoMsg = infoMsg.Trim().TrimEnd(',');
}
///////////////////////////
var reader = GetReader(dbContext, commandText, sqlParameterArray, CommandType.StoredProcedure);
///////////////////////////
///////////////////////////
List<TEntity> results = GetListFromDataReader<TEntity>(reader);
///////////////////////////
if(readOnly == false)
{
DbSet entitySet = dbContext.Set<TEntity>(); // For attaching the entities so EF can track changes
results.ForEach(n => entitySet.Attach(n)); // Add tracking to each entity
}
reader.Close();
return results.AsEnumerable();
}
catch (Exception ex)
{
throw new Exception("An error occurred while executing GetStoredProcedureResults(). " + infoMsg + ". Check the inner exception for more details.\r\n" + ex.Message, ex);
}
}
//========================================= Private methods
#region Private Methods
private static DbDataReader GetReader(DbContext dbContext, string commandText, SqlParameter[] sqlParameterArray, CommandType commandType)
{
var command = dbContext.Database.Connection.CreateCommand();
command.CommandText = commandText;
command.CommandType = commandType;
if (sqlParameterArray != null) command.Parameters.AddRange(sqlParameterArray);
dbContext.Database.Connection.Open();
var reader = command.ExecuteReader(CommandBehavior.CloseConnection);
return reader;
}
private static List<TEntity> GetListFromDataReader<TEntity>(DbDataReader reader) where TEntity : class, new()
{
PropertyInfo[] entityProperties = typeof(TEntity).GetProperties();
IEnumerable<string> readerColumnNames = (reader.GetSchemaTable().Select()).Select(r => r.ItemArray[0].ToString().ToUpper()); // uppercase reader column names.
List<MappingPropertyToColumn> propertyToColumnMappings = GetPropertyToColumnMappings<TEntity>(); // Maps the entity property names to the corresponding names of the columns in the reader
var entityList = new List<TEntity>(); // Fill this
while (reader.Read())
{
var element = Activator.CreateInstance<TEntity>();
foreach (var entityProperty in entityProperties)
{
MappingPropertyToColumn mapping = propertyToColumnMappings._Find(entityProperty.Name);
if (mapping == null) // This property has a [Not Mapped] attribute
{
continue; // Skip this one
}
var o = (object)reader[mapping.ColumnName]; // mapping must match all mapped properties to columns. If result set does not contain a column, then throw error like EF would.
bool hasValue = o.GetType() != typeof(DBNull);
if (mapping.IsEnum && hasValue) // Enum
{
entityProperty.SetValue(element, Enum.Parse(mapping.UnderlyingType, o.ToString()));
}
else
{
if (hasValue)
{
entityProperty.SetValue(element, ChangeType(o, entityProperty.PropertyType));
}
}
}
entityList.Add(element);
}
return entityList;
}
public static object ChangeType(object value, Type conversion)
{
var t = conversion;
if (t.IsGenericType && t.GetGenericTypeDefinition().Equals(typeof(Nullable<>)))
{
if (value == null)
{
return null;
}
t = Nullable.GetUnderlyingType(t);
}
return Convert.ChangeType(value, t);
}
private static List<MappingPropertyToColumn> GetPropertyToColumnMappings<TEntity>() where TEntity : new()
{
var type = typeof(TEntity);
List<MappingPropertyToColumn> databaseMappings = new List<MappingPropertyToColumn>();
foreach (var entityProperty in type.GetProperties())
{
bool isEnum = entityProperty.PropertyType.IsEnum;
// [Not Mapped] Not Mapped Attribute
var notMapped = entityProperty.GetCustomAttributes(false).FirstOrDefault(attribute => attribute is NotMappedAttribute);
if (notMapped != null) // This property has a [Not Mapped] attribute
{
continue; // Skip this property
}
// Determine if property is an enum
Type underlyingType = null;
if (entityProperty.PropertyType.IsGenericType && entityProperty.PropertyType.GetGenericTypeDefinition().Equals(typeof(Nullable<>)))
{
underlyingType = Nullable.GetUnderlyingType(entityProperty.PropertyType); ;
if (underlyingType != null && underlyingType.IsEnum)
{
isEnum = true;
}
}
// [Column("tbl_columnname")] Column Name Attribute for mapping
var columnMapping = entityProperty.GetCustomAttributes(false).FirstOrDefault(attribute => attribute is ColumnAttribute);
if (columnMapping != null)
{
databaseMappings.Add(new MappingPropertyToColumn { PropertyName = entityProperty.Name, ColumnName = ((ColumnAttribute)columnMapping).Name.ToUpper(), IsEnum = isEnum, UnderlyingType = underlyingType }); // SQL case insensitive
}
else
{
databaseMappings._AddProperty(entityProperty.Name, entityProperty.Name, isEnum); // C# case sensitive
}
}
return databaseMappings;
}
//====================================== Class for holding column mappings and other info for each property
private class MappingPropertyToColumn
{
private string _propertyName;
public string PropertyName
{
get { return _propertyName; }
set { _propertyName = value; }
}
private string _columnName;
public string ColumnName
{
get { return _columnName; }
set { _columnName = value; }
}
private bool _isNullableEnum;
public bool IsEnum
{
get { return _isNullableEnum; }
set { _isNullableEnum = value; }
}
private Type _underlyingType;
public Type UnderlyingType
{
get { return _underlyingType; }
set { _underlyingType = value; }
}
}
//======================================================= List<MappingPropertyToColumn> Extension methods
#region List<MappingPropertyToColumn> Extension methods
private static bool _ContainsKey<T>(this List<T> list, string key) where T : MappingPropertyToColumn
{
return list.Any(x => x.PropertyName == key);
}
private static MappingPropertyToColumn _Find<T>(this List<T> list, string key) where T : MappingPropertyToColumn
{
return list.Where(x => x.PropertyName == key).FirstOrDefault();
}
private static void _AddProperty<T>(this List<T> list, string propertyName, string columnName, bool isEnum, Type underlyingType = null) where T : MappingPropertyToColumn
{
list.Add((T)new MappingPropertyToColumn { PropertyName = propertyName, ColumnName = columnName, IsEnum = isEnum, UnderlyingType = underlyingType }); // C# case sensitive
}
#endregion
#endregion }

Get [DisplayName] attribute of a property in strongly-typed way

Good day!
I've such method to get [DisplayName] attribute value of a property (which is attached directly or using [MetadataType] attribute). I use it in rare cases where I need to get [DisplayName] in controller code.
public static class MetaDataHelper
{
public static string GetDisplayName(Type dataType, string fieldName)
{
// First look into attributes on a type and it's parents
DisplayNameAttribute attr;
attr = (DisplayNameAttribute)dataType.GetProperty(fieldName).GetCustomAttributes(typeof(DisplayNameAttribute), true).SingleOrDefault();
// Look for [MetadataType] attribute in type hierarchy
// http://stackoverflow.com/questions/1910532/attribute-isdefined-doesnt-see-attributes-applied-with-metadatatype-class
if (attr == null)
{
MetadataTypeAttribute metadataType = (MetadataTypeAttribute)dataType.GetCustomAttributes(typeof(MetadataTypeAttribute), true).FirstOrDefault();
if (metadataType != null)
{
var property = metadataType.MetadataClassType.GetProperty(fieldName);
if (property != null)
{
attr = (DisplayNameAttribute)property.GetCustomAttributes(typeof(DisplayNameAttribute), true).SingleOrDefault();
}
}
}
return (attr != null) ? attr.DisplayName : String.Empty;
}
}
It works, but it has two drawbacks:
It requires field name as string
It doesn't work if I want to get property of a property
Is it possible to overcome both problems using lambdas, something like we have in ASP.NET MVC:
Html.LabelFor(m => m.Property.Can.Be.Very.Complex.But.Strongly.Typed);
Update
Here is an updated and checked version from BuildStarted solution. It is modified to use DisplayName attribute (you can modify back to Display attribute if you use it). And fixed minor bugs to get attribute of nested properties.
public static string GetDisplayName<TModel>(Expression<Func<TModel, object>> expression)
{
Type type = typeof(TModel);
string propertyName = null;
string[] properties = null;
IEnumerable<string> propertyList;
//unless it's a root property the expression NodeType will always be Convert
switch (expression.Body.NodeType)
{
case ExpressionType.Convert:
case ExpressionType.ConvertChecked:
var ue = expression.Body as UnaryExpression;
propertyList = (ue != null ? ue.Operand : null).ToString().Split(".".ToCharArray()).Skip(1); //don't use the root property
break;
default:
propertyList = expression.Body.ToString().Split(".".ToCharArray()).Skip(1);
break;
}
//the propert name is what we're after
propertyName = propertyList.Last();
//list of properties - the last property name
properties = propertyList.Take(propertyList.Count() - 1).ToArray(); //grab all the parent properties
foreach (string property in properties)
{
PropertyInfo propertyInfo = type.GetProperty(property);
type = propertyInfo.PropertyType;
}
DisplayNameAttribute attr;
attr = (DisplayNameAttribute)type.GetProperty(propertyName).GetCustomAttributes(typeof(DisplayNameAttribute), true).SingleOrDefault();
// Look for [MetadataType] attribute in type hierarchy
// http://stackoverflow.com/questions/1910532/attribute-isdefined-doesnt-see-attributes-applied-with-metadatatype-class
if (attr == null)
{
MetadataTypeAttribute metadataType = (MetadataTypeAttribute)type.GetCustomAttributes(typeof(MetadataTypeAttribute), true).FirstOrDefault();
if (metadataType != null)
{
var property = metadataType.MetadataClassType.GetProperty(propertyName);
if (property != null)
{
attr = (DisplayNameAttribute)property.GetCustomAttributes(typeof(DisplayNameAttribute), true).SingleOrDefault();
}
}
}
return (attr != null) ? attr.DisplayName : String.Empty;
}
There are two ways to do this:
Models.Test test = new Models.Test();
string DisplayName = test.GetDisplayName(t => t.Name);
string DisplayName = Helpers.GetDisplayName<Models.Test>(t => t.Name);
The first one works by virtue of writing a generic extension method to any TModel (which is all types). This means it will be available on any object and not just your model. Not really recommended but nice because of it's concise syntax.
The second method requires you to pass in the Type of the model it is - which you're already doing but as a parameter instead. This method is required to define type via Generics because Func expects it.
Here are the methods for you to check out.
Static extension method to all objects
public static string GetDisplayName<TModel, TProperty>(this TModel model, Expression<Func<TModel, TProperty>> expression) {
Type type = typeof(TModel);
MemberExpression memberExpression = (MemberExpression)expression.Body;
string propertyName = ((memberExpression.Member is PropertyInfo) ? memberExpression.Member.Name : null);
// First look into attributes on a type and it's parents
DisplayAttribute attr;
attr = (DisplayAttribute)type.GetProperty(propertyName).GetCustomAttributes(typeof(DisplayAttribute), true).SingleOrDefault();
// Look for [MetadataType] attribute in type hierarchy
// http://stackoverflow.com/questions/1910532/attribute-isdefined-doesnt-see-attributes-applied-with-metadatatype-class
if (attr == null) {
MetadataTypeAttribute metadataType = (MetadataTypeAttribute)type.GetCustomAttributes(typeof(MetadataTypeAttribute), true).FirstOrDefault();
if (metadataType != null) {
var property = metadataType.MetadataClassType.GetProperty(propertyName);
if (property != null) {
attr = (DisplayAttribute)property.GetCustomAttributes(typeof(DisplayNameAttribute), true).SingleOrDefault();
}
}
}
return (attr != null) ? attr.Name : String.Empty;
}
Signature for type specific method - same code as above just different call
public static string GetDisplayName<TModel>(Expression<Func<TModel, object>> expression) { }
The reason you can't just use Something.GetDisplayName(t => t.Name) on it's own is because in the Razor engine you're actually passing an instantiated object of HtmlHelper<TModel> which is why the first method requires an instantiated object - This is only required for the compiler to infer what types belong to which generic name.
Update with recursive properties
public static string GetDisplayName<TModel>(Expression<Func<TModel, object>> expression) {
Type type = typeof(TModel);
string propertyName = null;
string[] properties = null;
IEnumerable<string> propertyList;
//unless it's a root property the expression NodeType will always be Convert
switch (expression.Body.NodeType) {
case ExpressionType.Convert:
case ExpressionType.ConvertChecked:
var ue = expression.Body as UnaryExpression;
propertyList = (ue != null ? ue.Operand : null).ToString().Split(".".ToCharArray()).Skip(1); //don't use the root property
break;
default:
propertyList = expression.Body.ToString().Split(".".ToCharArray()).Skip(1);
break;
}
//the propert name is what we're after
propertyName = propertyList.Last();
//list of properties - the last property name
properties = propertyList.Take(propertyList.Count() - 1).ToArray(); //grab all the parent properties
Expression expr = null;
foreach (string property in properties) {
PropertyInfo propertyInfo = type.GetProperty(property);
expr = Expression.Property(expr, type.GetProperty(property));
type = propertyInfo.PropertyType;
}
DisplayAttribute attr;
attr = (DisplayAttribute)type.GetProperty(propertyName).GetCustomAttributes(typeof(DisplayAttribute), true).SingleOrDefault();
// Look for [MetadataType] attribute in type hierarchy
// http://stackoverflow.com/questions/1910532/attribute-isdefined-doesnt-see-attributes-applied-with-metadatatype-class
if (attr == null) {
MetadataTypeAttribute metadataType = (MetadataTypeAttribute)type.GetCustomAttributes(typeof(MetadataTypeAttribute), true).FirstOrDefault();
if (metadataType != null) {
var property = metadataType.MetadataClassType.GetProperty(propertyName);
if (property != null) {
attr = (DisplayAttribute)property.GetCustomAttributes(typeof(DisplayNameAttribute), true).SingleOrDefault();
}
}
}
return (attr != null) ? attr.Name : String.Empty;
}
Late to the game, but...
I created a helper method using ModelMetadata like #Daniel mentioned and I thought I'd share it:
public static string GetDisplayName<TModel, TProperty>(
this TModel model
, Expression<Func<TModel, TProperty>> expression)
{
return ModelMetadata.FromLambdaExpression<TModel, TProperty>(
expression,
new ViewDataDictionary<TModel>(model)
).DisplayName;
}
Example Usage:
Models:
public class MySubObject
{
[DisplayName("Sub-Awesome!")]
public string Sub { get; set; }
}
public class MyObject
{
[DisplayName("Awesome!")]
public MySubObject Prop { get; set; }
}
Use:
HelperNamespace.GetDisplayName(Model, m => m.Prop) // "Awesome!"
HelperNamespace.GetDisplayName(Model, m => m.Prop.Sub) // "Sub-Awesome!"
Just do this:
using System.ComponentModel;
using System.Linq;
using System.Reflection;
namespace yournamespace
{
public static class ExtensionMethods
{
public static string GetDisplayName(this PropertyInfo prop)
{
if (prop.CustomAttributes == null || prop.CustomAttributes.Count() == 0)
return prop.Name;
var displayNameAttribute = prop.CustomAttributes.Where(x => x.AttributeType == typeof(DisplayNameAttribute)).FirstOrDefault();
if (displayNameAttribute == null || displayNameAttribute.ConstructorArguments == null || displayNameAttribute.ConstructorArguments.Count == 0)
return prop.Name;
return displayNameAttribute.ConstructorArguments[0].Value.ToString() ?? prop.Name;
}
}
}
Example as requested:
var props = typeof(YourType).GetProperties(BindingFlags.Public | BindingFlags.Instance).Where(p => p.CanRead);
var propFriendlyNames = props.Select(x => x.GetDisplayName());
I totally agree with the solution BuildStarted provided. The only thing i would change is that the ExtensionsMethode does not support translations. To support this is simple minor change is needed. I would have put this in the comments but i don't have enough points to do so. Look for the last line in the method.
The Extension Method
public static string GetDisplayName<TModel, TProperty>(this TModel model, Expression<Func<TModel, TProperty>> expression)
{
Type type = typeof(TModel);
IEnumerable<string> propertyList;
//unless it's a root property the expression NodeType will always be Convert
switch (expression.Body.NodeType)
{
case ExpressionType.Convert:
case ExpressionType.ConvertChecked:
var ue = expression.Body as UnaryExpression;
propertyList = (ue != null ? ue.Operand : null).ToString().Split(".".ToCharArray()).Skip(1); //don't use the root property
break;
default:
propertyList = expression.Body.ToString().Split(".".ToCharArray()).Skip(1);
break;
}
//the propert name is what we're after
string propertyName = propertyList.Last();
//list of properties - the last property name
string[] properties = propertyList.Take(propertyList.Count() - 1).ToArray();
Expression expr = null;
foreach (string property in properties)
{
PropertyInfo propertyInfo = type.GetProperty(property);
expr = Expression.Property(expr, type.GetProperty(property));
type = propertyInfo.PropertyType;
}
DisplayAttribute attr = (DisplayAttribute)type.GetProperty(propertyName).GetCustomAttributes(typeof(DisplayAttribute), true).SingleOrDefault();
// Look for [MetadataType] attribute in type hierarchy
// http://stackoverflow.com/questions/1910532/attribute-isdefined-doesnt-see-attributes-applied-with-metadatatype-class
if (attr == null)
{
MetadataTypeAttribute metadataType = (MetadataTypeAttribute)type.GetCustomAttributes(typeof(MetadataTypeAttribute), true).FirstOrDefault();
if (metadataType != null)
{
var property = metadataType.MetadataClassType.GetProperty(propertyName);
if (property != null)
{
attr = (DisplayAttribute)property.GetCustomAttributes(typeof(DisplayNameAttribute), true).SingleOrDefault();
}
}
}
//To support translations call attr.GetName() instead of attr.Name
return (attr != null) ? attr.GetName() : String.Empty;
}
I found another nice code snippet here, and I have slightly modified it for 'DisplayName' purpose
public static string GetDisplayName<TSource, TProperty>(Expression<Func<TSource, TProperty>> expression)
{
var attribute = Attribute.GetCustomAttribute(((MemberExpression)expression.Body).Member, typeof(DisplayNameAttribute)) as DisplayNameAttribute;
if (attribute == null)
{
throw new ArgumentException($"Expression '{expression}' doesn't have DisplayAttribute");
}
return attribute.DisplayName;
}
And usages
GetDisplayName<ModelName, string>(i => i.PropertyName)
I make a Little change is you are using resources to get the DisplayName
public static string GetDisplayName<TModel>(Expression<Func<TModel, object>> expression)
{
string _ReturnValue = string.Empty;
Type type = typeof(TModel);
string propertyName = null;
string[] properties = null;
IEnumerable<string> propertyList;
//unless it's a root property the expression NodeType will always be Convert
switch (expression.Body.NodeType)
{
case ExpressionType.Convert:
case ExpressionType.ConvertChecked:
var ue = expression.Body as UnaryExpression;
propertyList = (ue != null ? ue.Operand : null).ToString().Split(".".ToCharArray()).Skip(1); //don't use the root property
break;
default:
propertyList = expression.Body.ToString().Split(".".ToCharArray()).Skip(1);
break;
}
//the propert name is what we're after
propertyName = propertyList.Last();
//list of properties - the last property name
properties = propertyList.Take(propertyList.Count() - 1).ToArray(); //grab all the parent properties
Expression expr = null;
foreach (string property in properties)
{
PropertyInfo propertyInfo = type.GetProperty(property);
expr = Expression.Property(expr, type.GetProperty(property));
type = propertyInfo.PropertyType;
}
DisplayAttribute attr;
attr = (DisplayAttribute)type.GetProperty(propertyName).GetCustomAttributes(typeof(DisplayAttribute), true).SingleOrDefault();
// Look for [MetadataType] attribute in type hierarchy
// http://stackoverflow.com/questions/1910532/attribute-isdefined-doesnt-see-attributes-applied-with-metadatatype-class
if (attr == null)
{
MetadataTypeAttribute metadataType = (MetadataTypeAttribute)type.GetCustomAttributes(typeof(MetadataTypeAttribute), true).FirstOrDefault();
if (metadataType != null)
{
var property = metadataType.MetadataClassType.GetProperty(propertyName);
if (property != null)
{
attr = (DisplayAttribute)property.GetCustomAttributes(typeof(DisplayNameAttribute), true).SingleOrDefault();
}
}
}
if (attr != null && attr.ResourceType != null)
_ReturnValue = attr.ResourceType.GetProperty(attr.Name).GetValue(attr).ToString();
else if (attr != null)
_ReturnValue = attr.Name;
return _ReturnValue;
}
Happy Coding
Another code snippet with code .Net uses itself to perform this
public static class WebModelExtensions
{
public static string GetDisplayName<TModel, TProperty>(
this HtmlHelper<TModel> html,
Expression<Func<TModel, TProperty>> expression)
{
// Taken from LabelExtensions
var metadata = ModelMetadata.FromLambdaExpression(expression, html.ViewData);
string displayName = metadata.DisplayName;
if (displayName == null)
{
string propertyName = metadata.PropertyName;
if (propertyName == null)
{
var htmlFieldName = ExpressionHelper.GetExpressionText(expression);
displayName = ((IEnumerable<string>) htmlFieldName.Split('.')).Last<string>();
}
else
displayName = propertyName;
}
return displayName;
}
}
// Usage
Html.GetDisplayName(model => model.Password)
#Buildstarted Answer works but i wanted to get the DisplayName by property name rather using linq expression so i did a little change that will save your time
public static string GetDisplayNameByMemberName<TModel>(string memberName)
{
Type type = typeof(TModel);
string propertyName = memberName;
DisplayAttribute attr;
attr = (DisplayAttribute)type.GetProperty(propertyName).GetCustomAttributes(typeof(DisplayAttribute), true).SingleOrDefault();
//Look for [MetadataType] attribute in type hierarchy
//http://stackoverflow.com/questions/1910532/attribute-isdefined-doesnt-see-attributes-applied-with-metadatatype-class
if (attr == null)
{
MetadataTypeAttribute metadataType = (MetadataTypeAttribute)type.GetCustomAttributes(typeof(MetadataTypeAttribute), true).FirstOrDefault();
if (metadataType != null)
{
var property = metadataType.MetadataClassType.GetProperty(propertyName);
if (property != null)
{
attr = (DisplayAttribute)property.GetCustomAttributes(typeof(DisplayNameAttribute), true).SingleOrDefault();
}
}
}
return (attr != null) ? attr.Name : String.Empty;
}
I also wanted to get the localized value in the resources (.resx files) so :
string displayName = GeneralHelper.GetDisplayNameByMemberName<ViewModels.ProductVM>(memberName);
string displayNameTranslated = resourceManager.GetString(displayName, MultiLangHelper.CurrentCultureInfo);
This is a helper function i made to return an object of type "CultureInfo", create a culture info of some language or pass the current one
//MultiLangHelper.CurrentCultureInfo
return System.Threading.Thread.CurrentThread.CurrentCulture;

solution to the asp.net mvc drop down list selected value overrider problem

I wrote a helper method to display enums from my model in my asp.net MVC application as drop down lists in my views.
Here is my code for that:
public static List<SelectListItem> CreateSelectItemList<TEnum>(object enumObj,
string defaultItemKey,
bool sortAlphabetically,
object firstValueOverride)
where TEnum : struct
{
var values = (from v in (TEnum[])Enum.GetValues(typeof(TEnum))
select new
{
Id = Convert.ToInt32(v),
Name = ResourceHelpers.GetResourceValue(AppConstants.EnumResourceNamespace,
typeof(TEnum).Name, v.ToString())
});
return values.ToSelectList(e => e.Name,
e => e.Id.ToString(),
!string.IsNullOrEmpty(defaultItemKey) ? ResourceHelpers.GetResourceValue(AppConstants.EnumResourceNamespace, defaultItemKey) : string.Empty,
enumObj,
sortAlphabetically,
firstValueOverride);
}
This actually generates the select item list:
public static List<SelectListItem> ToSelectList<T>(
this IEnumerable<T> enumerable,
Func<T, string> text,
Func<T, string> value,
string defaultOption,
object selectedVal,
bool sortAlphabetically,
object FirstValueOverride)
{
int iSelectedVal = -1;
if(selectedVal!=null)
{
try
{
iSelectedVal = Convert.ToInt32(selectedVal);
}
catch
{
}
}
var items = enumerable.Select(f => new SelectListItem()
{
Text = text(f),
Value = value(f),
Selected = (iSelectedVal > -1)? (iSelectedVal.ToString().Equals(value(f))) : false
});
#region Sortare alfabetica
if (sortAlphabetically)
items = items.OrderBy(t => t.Text);
#endregion Sortare alfabetica
var itemsList = items.ToList();
Func<SelectListItem, bool> funct = null;
string sValue = string.Empty;
SelectListItem firstItem = null;
SelectListItem overridenItem = null;
int overridenIndex = 0;
if (FirstValueOverride != null)
{
sValue = FirstValueOverride.ToString();
funct = (t => t.Value == sValue);
overridenItem = itemsList.SingleOrDefault(funct);
overridenIndex = itemsList.IndexOf(overridenItem);
if (overridenItem != null)
{
firstItem = itemsList.ElementAt(0);
itemsList[0] = overridenItem;
itemsList[overridenIndex] = firstItem;
}
}
if(!string.IsNullOrEmpty(defaultOption))
itemsList.Insert(0, new SelectListItem()
{
Text = defaultOption,
Value = "-1"
});
return itemsList;
}
These is the method I call:
public static MvcHtmlString EnumDropDownList<TEnum>(this HtmlHelper htmlHelper,
object enumObj,
string name,
string defaultItemKey,
bool sortAlphabetically,
object firstValueOverride,
object htmlAttributes)
where TEnum : struct
{
return htmlHelper.DropDownList(name,
CreateSelectItemList<TEnum>(enumObj,
defaultItemKey,
sortAlphabetically,
firstValueOverride),
htmlAttributes);
}
Now I am having the problem described here
When I call this helper method and the input's name is the same as the property's name the selected value doesn't get selected.
The alternate solution described there doesn't work for me. The only solution that works is changing the name and not using the model binding using FormCollection instead.
I don't like this workaround because I can't use validation any more using the ViewModel pattern and I have to write some extra code for every enum.
I tried writing a custom model binder to compensate for this somehow but none of the methods I can override there gets called when I start the action.
Is there any way to do this without using FormCollection?
Can I somehow intercept ASP.NET MVC when it tries to put the value into my input field and make it select the right value?
Thank you in advance.
I have a sollution now
Here is the code for the EnumDropDownList function the other functions are listed in the question:
public static MvcHtmlString EnumDropDownList(this HtmlHelper htmlHelper,
object enumObj,
string name,
string defaultItemKey,
bool sortAlphabetically,
object firstValueOverride,
object htmlAttributes)
{
StringBuilder sbRezultat = new StringBuilder();
int selectedIndex = 0;
var selectItemList = new List<SelectListItem>();
if (enumObj != null)
{
selectItemList = CreateSelectItemList(enumObj, defaultItemKey, true, null);
var selectedItem = selectItemList.SingleOrDefault(item => item.Selected);
if (selectedItem != null)
{
selectedIndex = selectItemList.IndexOf(selectedItem);
}
}
var dict = HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes);
TagBuilder tagBuilder = new TagBuilder("select");
tagBuilder.MergeAttribute("name", name,true);
bool bReadOnly = false;
//special case for readonly
if(dict.ContainsKey("readonly"))
{
//remove this tag it won't work the way mvc renders it anyway
dict.Remove("readonly");
bReadOnly = true;
}
//in case the style element is completed if the drop down is not readonly
tagBuilder.MergeAttributes(dict, true);
if (bReadOnly)
{
//add a small javascript to make it readonly and add the lightgrey style
tagBuilder.MergeAttribute("onchange", "this.selectedIndex=" + selectedIndex + ";",true);
tagBuilder.MergeAttribute("style", "background: lightgrey", true);
}
sbRezultat.Append(tagBuilder.ToString(TagRenderMode.StartTag));
foreach (var option in selectItemList)
{
sbRezultat.Append(" <option value='");
sbRezultat.Append(option.Value);
sbRezultat.Append("' ");
if (option.Selected)
sbRezultat.Append("selected");
sbRezultat.Append(" >");
sbRezultat.Append(option.Text);
sbRezultat.Append("</option>");
}
sbRezultat.Append("</select>");
return new MvcHtmlString(sbRezultat.ToString());
}
I also wrote a generic function of type For (EnumDropDownFor):
public static MvcHtmlString EnumDropDownListFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper,
Expression<Func<TModel, TProperty>> expression,
string defaultItemKey,
bool sortAlphabetically,
object firstValueOverride,
object htmlAttributes)
where TProperty : struct
{
string inputName = GetInputName(expression);
object selectedVal = null;
try
{
selectedVal = htmlHelper.ViewData.Model == null
? default(TProperty)
: expression.Compile()(htmlHelper.ViewData.Model);
}
catch//in caz ca e ceva null sau ceva de genu'
{
}
return EnumDropDownList(htmlHelper,
selectedVal,
inputName,
defaultItemKey,
sortAlphabetically,
firstValueOverride,
htmlAttributes);
}
and some helper methods:
public static string GetInputName<TModel, TProperty>(Expression<Func<TModel, TProperty>> expression)
{
if (expression.Body.NodeType == ExpressionType.Call)
{
MethodCallExpression methodCallExpression = (MethodCallExpression)expression.Body;
string name = GetInputName(methodCallExpression);
return name.Substring(expression.Parameters[0].Name.Length + 1);
}
return expression.Body.ToString().Substring(expression.Parameters[0].Name.Length + 1);
}
private static string GetInputName(MethodCallExpression expression)
{
MethodCallExpression methodCallExpression = expression.Object as MethodCallExpression;
if (methodCallExpression != null)
{
return GetInputName(methodCallExpression);
}
return expression.Object.ToString();
}

Resources