How to change method signature/properties to match given execution example? - c#

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.

Related

Stack Overflow Exception in MVcHtmlString

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)

C# MVC MvcHtmlString TModel with checking if is null

#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?

Show list of all Enums of type<T> except the one passed via lambda

In my MVC application, I've created a helper which should take an enum from the model and then display radio buttons for all of the other available enums of that type.
For example, you have the enum Satus which has Active, Inactive, Closed and the model for the page has Status = Status.Active so you want to display radio buttons for Inactive and Closed.
Going forward with this example, the MVC view calls helper RadioButtonForEnum:
#Html.RadioButtonForEnum(model => model.Status)
RadioButtonForEnum then gets the list of all Enums of that type and prints them out as radio buttons; however, I'm not sure how to get to the enum that was passed to exclude it from names
public static MvcHtmlString RadioButtonForEnum<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression)
{
var metaData = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);
var names = Enum.GetNames(metaData.ModelType);
var sb = new StringBuilder();
foreach (var name in names)
{
var id = string.Format(
"{0}_{1}_{2}",
htmlHelper.ViewData.TemplateInfo.HtmlFieldPrefix,
metaData.PropertyName,
name
);
var radio = htmlHelper.RadioButtonFor(expression, name, new { id = id }).ToHtmlString();
sb.AppendFormat("<label for=\"{1}\">{0}{2}</label>", radio, id, HttpUtility.HtmlEncode(StringHelpers.PascalCaseToSpaces(name)));
}
return MvcHtmlString.Create(sb.ToString());
}
Something like
var modelValue = expression.Compile()(htmlHelper.ViewData.Model);
...
foreach (var name in names.Where(s => s != modelValue.ToString())
...
You need the model instance to get the current enum value to avoid, you could get to it inside the HtmlHelper by using HtmlHelper.ViewData.Model as above.
I don't think you have the model object, as-is. You could add it as another parameter:
#Html.RadioButtonForEnum(Model, model => model.Status)
public static MvcHtmlString RadioButtonForEnum<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, TModel model, Expression<Func<TModel, TProperty>> expression)
{
string currentStatusName = expression.Compile()(model).ToString();
...
Quick and dirty way:
foreach (var name in names)
{
if (name == metaData.Model.ToString())
continue;
...
}
Completely untested here as I don't have my own computer, but what about changing your signature to:
public static MvcHtmlString RadioButtonForEnum<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, Enum Status)
This will allow you to pass in the Status enum and interrogate its value:
public static MvcHtmlString RadioButtonForEnum<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, Enum Status){
var metaData = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);
var names = Enum.GetNames(metaData.ModelType);
var sb = new StringBuilder();
foreach (var name in names)
{
if (!name.Equals(Status.ToString()){
var id = string.Format(
"{0}_{1}_{2}",
htmlHelper.ViewData.TemplateInfo.HtmlFieldPrefix,
metaData.PropertyName,
name
);
var radio = htmlHelper.RadioButtonFor(expression, name, new { id = id }).ToHtmlString();
sb.AppendFormat("<label for=\"{1}\">{0}{2}</label>", radio, id, HttpUtility.HtmlEncode(StringHelpers.PascalCaseToSpaces(name)));
}
}
And you would call it from your view like this:
#Html.RadioButtonForEnum(model => model.Status, Model.Status)

.Net MVC 3 Razor example of using DdUovFor workaround to fix DropDownListFor validation?

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.

How to extend the HtmlHelper with the IDictionary<string, object> overload?

I created an extension method for the HtmlHelper which works very well. Now I need to create the overload that receives an IDictionary so I can add a css class to it so I tried the following:
public static MvcHtmlString EnumDropDownListFor<TModel, TEnum>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TEnum>> expression)
{
return EnumDropDownListFor(htmlHelper, expression, null);
}
public static MvcHtmlString EnumDropDownListFor<TModel, TEnum>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TEnum>> expression, IDictionary<string, object> htmlAttributes)
{
var items = DoSomething();
return htmlHelper.DropDownListFor(expression, items, htmlAttributes);
}
When I tried to use it in my view I still got the following exception:
Compilation Error
Description: An error occurred during the compilation of a resource
required to service this request. Please review the following specific
error details and modify your source code appropriately.
Compiler Error Message: CS1928:
'System.Web.Mvc.HtmlHelper' does not
contain a definition for 'EnumDropDownListFor' and the best extension
method overload
'LIMM.Web.HtmlHelpers.HtmlDropDownExtensions.EnumDropDownListFor(System.Web.Mvc.HtmlHelper,
System.Linq.Expressions.Expression>,
System.Collections.Generic.IDictionary)' has some
invalid arguments
Obviously I'm not extending the method correctly but so google hasn't been my friend in finding a way to accomplish this. A little help will be appreciated.
Thanks.
UPDATE: As I type the code in the view, intellisense does give me both overloads. The error happens when I run the application.
Maybe you are trying use your helper using the commonest construction (pass html attributes as an anonymous object), so very probably you need an overload like this:
public static MvcHtmlString EnumDropDownListFor<TModel, TEnum>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TEnum>> expression, object htmlAttributes)
{
return EnumDropDownListFor(htmlHelper, expression, HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes));
}
public static MvcHtmlString EnumDropDownListFor<TModel, TEnum>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TEnum>> expression, IDictionary<string, object> htmlAttributes)
{
var items = DoSomething();
return htmlHelper.DropDownListFor(expression, items, htmlAttributes);
}

Categories