#Html.DisplayRowForWithDisplayName(model => model.CustomerDetail.IfNotNullReturnDefault(x => x.Id))
Extension method:
public static TResult IfNotNullReturnDefault<TSource, TResult>(this TSource source, Func<TSource, TResult> function)
where TSource : class
where TResult : struct
{
if (source == null)
{
return default(TResult);
}
MVC builder
public static MvcHtmlString DisplayRowForWithDisplayName<TModel, TValue>(this HtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression)
{
var metaData = ModelMetadata.FromLambdaExpression(expression, html.ViewData);
var model = metaData.Model;
var displayName = metaData.DisplayName;
...
but I get exception here:
var metaData = ModelMetadata.FromLambdaExpression(expression, html.ViewData);
Exception:
System.InvalidOperationException: Templates can be used only with field access, property access, single-dimension array index, or single-parameter custom indexer expressions.
at System.Web.Mvc.ModelMetadata.FromLambdaExpression[TParameter,TValue](Expression`1 expression, ViewDataDictionary`1 viewData, ModelMetadataProvider metadataProvider)
at System.Web.Mvc.ModelMetadata.FromLambdaExpression[TParameter,TValue](Expression`1 expression, ViewDataDictionary`1 viewData)
at System.Web.Mvc.HtmlHelperExtensions.DisplayRowForWithDisplayName[TModel,TValue](HtmlHelper`1 html, Expression`1 expression) in ...
How to heck if is not null in MVC?
Related
For example:
public static string GetXPath<TModel, TMember>(this TModel model, Expression<Func<TModel, TMember>> expr)
{
...
}
var result = new MyObject().GetXPath(x=> x.DataList[1].Value);
Console.WriteLine(result);
And this will print out something like this (structure without attributes, just like DataContractSerializer do by default):
/DataList[1]/Value
I have created my own Html Helper which adds red asterisks to any required field.
It successfully works with both
#Html.myLabelFor(model => model.Description)
//and
#Html.myLabelFor(model => model.Description, new { /*stuff*/ })
However, some of the code lines are like following
#Html.myLabelFor(model => model.Description, "Deletion Reason", new { /*stuff*/ })
My method was not designed to handle 3 parameters, so I added a caller which would handle 3 parameters
public static MvcHtmlString myLabelFor<TModel, TValue>(this HtmlHelper<TModel> html,
Expression<Func<TModel, TValue>> expression, string labelText, Object htmlAttributes)
{
return myLabelFor(html, expression, labelText, HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes));
}
Below are other methods that are working properly (including internal, which contains all necessary code and whose structure I used as a reference)
public static MvcHtmlString myLabelFor<TModel, TValue>(this HtmlHelper<TModel> html,
Expression<Func<TModel, TValue>> expression, IDictionary<String, Object> htmlAttributes)
{
return LabelHelper(html, ModelMetadata.FromLambdaExpression(expression, html.ViewData),
ExpressionHelper.GetExpressionText(expression), null, htmlAttributes);
}
public static MvcHtmlString myLabelFor<TModel, TValue>(this HtmlHelper<TModel> html,
Expression<Func<TModel, TValue>> expression)
{
return LabelHelper(html, ModelMetadata.FromLambdaExpression(expression, html.ViewData),
ExpressionHelper.GetExpressionText(expression), null);
}
public static MvcHtmlString myLabelFor<TModel, TValue>(this HtmlHelper<TModel> html,
Expression<Func<TModel, TValue>> expression, Object htmlAttributes)
{
return myLabelFor(html, expression, HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes));
}
//USED ITS STRUCTURE AS A REFERENCE
internal static MvcHtmlString LabelHelper(HtmlHelper html, ModelMetadata metadata, String htmlFieldName,
String labelText = null, IDictionary<String, Object> htmlAttributes = null)
Logically, I was expecting that parameter labelText would take a value of "Deletion Reason" from the line of code above. However, instead it had thrown a StackOverflowException inside my 3-parameter method. Microsoft description was vague, additional explanation did not help, and additional solution was using
Expression<Func<TModel, string>> expression instead of my Expression<Func<TModel, TValue>> expression
I do not understand what I am doing wrong. At this point I can only think of "fiddle with parameters until it works", but I am hopeful there is more elegant solution to that problem.
PS: Please let me know if my code for internal helper will help to solve the problem.
You getting an exception on the first overload, because the method is recursively calling itself, and keeps doing so until the execution stack overflows. Rather than calling itself you need to change
return myLabelFor(html,
expression,
labelText,
HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes));
to
return LabelHelper(html,
ModelMetadata.FromLambdaExpression(expression, html.ViewData),
ExpressionHelper.GetExpressionText(expression),
labelText,
HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes));
From your comments, the reason your 4th overload which uses return myLabelFor(...) does not throw the exception is because it calls your 2nd overload which in turn calls return LabelHelper(...)
I recommend that you change the 4th overload to call LabelHelper() directly, and change all the public overloads to explicitly call LabelHelper(), passing all 4 parameters, which is the pattern used by the in-built `HtmlHelper extension methods (you can view the source code for LabelFor() here)
So I have this:
public static MvcHtmlString TextBoxFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, Expression<Func<TModel, ControlPermissionType>> mode)
{
MvcHtmlString value = null;
var modeIn = ModelMetadata.FromLambdaExpression(
mode, htmlHelper.ViewData
).Model;
switch ((ControlPermissionType)modeIn)
{
case ControlPermissionType.Read:
value = htmlHelper.TextBoxFor(expression, new { #readonly = "readonly" });
break;
case ControlPermissionType.Edit:
value = htmlHelper.TextBoxFor(expression);
break;
case ControlPermissionType.Deny:
value = new MvcHtmlString(string.Empty);
break;
}
return value;
}
and this is how I am calling it:
#Html.TextBoxFor(a => a.First().BirthDate, a => a.First().Mode)
but what I want is:
#Html.TextBoxFor(a => a.First().BirthDate, a.Mode)
how to do that?
EDIT:
or even
#Html.TextBoxFor(a => a.First().BirthDate) but in this way how to check if the a is implementing interface?
EDIT2:
#Html.TextBoxFor(a => a.First().BirthDate, a => a.First().Mode)
Need method signature :
public static MvcHtmlString TextBoxFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, Expression<Func<TModel, ControlPermissionType>> mode)
#Html.TextBoxFor(a => a.First().BirthDate, a.Mode)
Need method signature :
public static MvcHtmlString TextBoxFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, ControlPermissionType mode)
#Html.TextBoxFor(a => a.First().BirthDate)
Need method signature :
public static MvcHtmlString TextBoxFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression)
But in this last case, you miss the ControlPermissionType.
By the way, if you try to call your method with #Html.TextBoxFor(a => a.First().BirthDate), the compiler error should be self explanatory on which signature method is needed.
For example:
Html.TextBoxFor(x => x.ModelProperty)
If I were to get an expression like this as a method argument, how would I get the referenced property from the expression? My experience with expressions is somewhat limited and based on what I know, I don't get how this works.
You can get property name easily like this:
var metadata = ModelMetadata.FromLambdaExpression(expression, html.ViewData);
var propName = metadata.PropertyName;
Or you can get property and its attributes:
MemberExpression memberExpression = (MemberExpression) expression.Body;
var member = memberExpression.Member as PropertyInfo;
var attributes = member.GetCustomAttributes();
For example you can write a simple method that generates a input element like this:
public static MvcHtmlString TextboxForCustom<TModel, TResult>(this HtmlHelper<TModel> html,
Expression<Func<TModel, TResult>> expression)
{
var metadata = ModelMetadata.FromLambdaExpression(expression, html.ViewData);
var propName = metadata.PropertyName;
StringBuilder sb = new StringBuilder();
sb.AppendFormat("<input type=\"text\" id=\"{0}\" />", propName);
return MvcHtmlString.Create(sb.ToString());
}
Take a look at my answer here.
I once wrote my own EditorFor, it had the following definition:
public static MvcHtmlString MyHtmlEditorFor<TModel, TProperty>(this HtmlHelper<TModel> h, Expression<Func<TModel, TProperty>> expression)
{
// ...
}
To get the information of the property I used the ModelMetadata class:
ModelMetadata m = ModelMetadata.FromLambdaExpression(expression, h.ViewData);
var value = m.Model;
I came across the following code in this post which is supposed to fix validation for DropDownListFor. However, I am unsure what value to pass from the view for the parameter:
this HtmlHelper<TModel> htmlHelper
What should be passed for this value? Could you provide an example of using this in the view? This is related to this question where no example is provided.
[SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Justification = "Users cannot use anonymous methods with the LambdaExpression type")]
[SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "This is an appropriate nesting of generic types")]
public static MvcHtmlString DdUovFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, IEnumerable<SelectListItem> selectList, string optionLabel, IDictionary<string, object> htmlAttributes)
{
if (expression == null)
{
throw new ArgumentNullException("expression");
}
ModelMetadata metadata = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);
IDictionary<string, object> validationAttributes = htmlHelper
.GetUnobtrusiveValidationAttributes(ExpressionHelper.GetExpressionText(expression), metadata);
if (htmlAttributes == null)
htmlAttributes = validationAttributes;
else
htmlAttributes = htmlAttributes.Concat(validationAttributes).ToDictionary(k => k.Key, v => v.Value);
return SelectExtensions.DropDownListFor(htmlHelper, expression, selectList, optionLabel, htmlAttributes);
}
You don't need to pass anything, simply call #Html.DdUovFor ignoring that parameter.
See extension methods:
http://msdn.microsoft.com/en-us/library/bb383977.aspx
The htmlHelper is the instance of Html in #Html.. automatically that it refers to in the View.