Is it possible to create a generic #helper method with Razor? - asp.net-mvc

I am trying to write a helper in Razor that looks like the following:
#helper DoSomething<T, U>(Expression<Func<T, U>> expr) where T : class
Unfortunately, the parser thinks that <T is the beginning of an HTML element and I end up with a syntax error. Is it possible to create a helper with Razor that is a generic method? If so, what is the syntax?

This is possible to achieve inside a helper file with the #functions syntax but if you want the razor-style readability you are referring to you will also need to call a regular helper to do the HTML fit and finish.
Note that functions in a Helper file are static so you would still need to pass in the HtmlHelper instance from the page if you were intending to use its methods.
e.g.
Views\MyView.cshtml:
#MyHelper.DoSomething(Html, m=>m.Property1)
#MyHelper.DoSomething(Html, m=>m.Property2)
#MyHelper.DoSomething(Html, m=>m.Property3)
App_Code\MyHelper.cshtml:
#using System.Web.Mvc;
#using System.Web.Mvc.Html;
#using System.Linq.Expressions;
#functions
{
public static HelperResult DoSomething<TModel, TItem>(HtmlHelper<TModel> html, Expression<Func<TModel, TItem>> expr)
{
return TheThingToDo(html.LabelFor(expr), html.EditorFor(expr), html.ValidationMessageFor(expr));
}
}
#helper TheThingToDo(MvcHtmlString label, MvcHtmlString textbox, MvcHtmlString validationMessage)
{
<p>
#label
<br />
#textbox
#validationMessage
</p>
}
...

No, this is not currently possible. You could write a normal HTML helper instead.
public static MvcHtmlString DoSomething<T, U>(
this HtmlHelper htmlHelper,
Expression<Func<T, U>> expr
) where T : class
{
...
}
and then:
#(Html.DoSomething<SomeModel, string>(x => x.SomeProperty))
or if you are targeting the model as first generic argument:
public static MvcHtmlString DoSomething<TModel, TProperty>(
this HtmlHelper<TModel> htmlHelper,
Expression<Func<TModel, TProperty>> expr
) where TModel : class
{
...
}
which will allow you to invoke it like this (assuming of course that your view is strongly typed, but that's a safe assumption because all views should be strongly typed anyways :-)):
#Html.DoSomething(x => x.SomeProperty)

In all cases the TModel will be the same (the model declared for the view), and in my case, the TValue was going to be the same, so I was able to declare the Expression argument type:
#helper FormRow(Expression<Func<MyViewModel, MyClass>> expression) {
<div class="form-group">
#(Html.LabelFor(expression, new { #class = "control-label col-sm-6 text-right" }))
<div class="col-sm-6">
#Html.EnumDropDownListFor(expression, new { #class = "form-control" })
</div>
#Html.ValidationMessageFor(expression)
</div>
}
If your model fields are all string, then you can replace MyClass with string.
It might not be bad to define two or three helpers with the TValue defined, but if you have any more that would generate some ugly code, I didn't really find a good solution. I tried wrapping the #helper from a function I put inside the #functions {} block, but I never got it to work down that path.

if your main problem is to get name attribute value for binding using lambda expression seems like the #Html.TextBoxFor(x => x.MyPoperty), and if your component having very complex html tags and should be implemented on razor helper, then why don't just create an extension method of HtmlHelper<TModel> to resolve the binding name:
namespace System.Web.Mvc
{
public static class MyHelpers
{
public static string GetNameForBinding<TModel, TProperty>
(this HtmlHelper<TModel> model,
Expression<Func<TModel, TProperty>> property)
{
return ExpressionHelper.GetExpressionText(property);
}
}
}
your razor helper should be like usual:
#helper MyComponent(string name)
{
<input name="#name" type="text"/>
}
then here you can use it
#TheHelper.MyComponent(Html.GetNameForBinding(x => x.MyProperty))

Related

How can I use an #HtmlHelper inside a custom #HtmlHelper?

I am trying to create a custom Html helper with ASP.NET MVC. I have the following code:
#helper DefaultRenderer(Models.Control control)
{
<div class="form-group">
<label class="control-label" for="#control.Name">#control.Label</label>
#Html.TextBoxFor(m => control.Value, new { #class = "form-control" })
</div>
}
Apparently #Html.TextBoxFor cannot be found inside a Helper .cshtml class. I can use it in a partial view which is also a .cshtml class.
I can use #HtmlTextBox but then I will lose the strong model binding...
Why is this happening and is there a way to get it to work?
No, this is not possible. You could not write a normal HTML helper with #Html.TextBoxFor because that your view is strongly typed.
So you need something like:
public class HelperExtentions{
public static MvcHtmlString DefaultRenderer<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, Control control , object htmlAttributes)
{
var sb = new StringBuilder();
var dtp = htmlHelper.TextBoxFor(expression, htmlAttributes).ToHtmlString();
sb.AppendFormat("<div class='form-group'><label class='control-label' for='{1}'>{2}</label>{0}</div>", dtp,control.Name,control.Label);
return MvcHtmlString.Create(sb.ToString());
}
}
Then you can use :
#html.DefaultRenderer(m => m.Control.Value, Models.Control,new { #class = "form-control" }

Custom MVC template for enum to drop down list

I have created several MVC templates for the EditorFor and DisplayFor helper methods to style things the way I wanted using the Twitter Bootstrap framework. I now have a working solution for all the bits I need, but would like to generalize one part I set up to show a list of states. I have a State enum (with a list of all US states) that I display in a drop down for a users address. I used the [DataType] attribute to get MVC to use my State.cshtml template.
[Required]
[Display(Name = "State")]
[DataType("State")]
public State State { get; set; }
So it works nicely, but I would like to change it so that I can do something like DataType("Enum") or some other way to hit this template generically for all enums.
The template looks like this:
#using System
#using System.Linq
#using Beno.Web.Helpers
#using TC.Util
#model Beno.Model.Enums.State
<div class="control-group">
#Html.LabelFor(m => m, new {#class = "control-label{0}".ApplyFormat(ViewData.ModelMetadata.IsRequired ? " required" : "")})
<div class="controls">
<div class="input-append">
#Html.EnumDropDownListFor(m => m)
<span class="add-on">#(new MvcHtmlString("{0}".ApplyFormat(ViewData.ModelMetadata.IsRequired ? " <i class=\"icon-star\"></i>" : "")))</span>
</div>
#Html.ValidationMessageFor(m => m, null, new {#class = "help-inline"})
</div>
</div>
The EnumDropDownListFor is a helper method I posted about before and that works generically with any enum. What I don't know is how would I change this template to take a generic enum as the model object?
UPDATE: For completeness I include a listing of the EnumDropDownListFor method:
public static MvcHtmlString EnumDropDownListFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, object htmlAttributes = null) where TProperty : struct, IConvertible
{
if (!typeof(TProperty).IsEnum)
throw new ArgumentException("TProperty must be an enumerated type");
var selectedValue = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData).Model.ToString();
var selectList = new SelectList(from value in EnumHelper.GetValues<TProperty>()
select new SelectListItem
{
Text = value.ToDescriptionString(),
Value = value.ToString()
}, "Value", "Text", selectedValue);
return htmlHelper.DropDownListFor(expression, selectList, htmlAttributes);
}
Changing the model type to Enum produces the following error on the line with the call to the helper method:
CS0453: The type 'System.Enum' must be a non-nullable value type in order to use it as parameter 'TProperty' in the generic type or method 'Beno.Web.Helpers.ControlHelper.EnumDropDownListFor<TModel,TProperty>(System.Web.Mvc.HtmlHelper<TModel>, System.Linq.Expressions.Expression<System.Func<TModel,TProperty>>, object)'
Then if I remove the check if TProperty is an enum and the struct where constraint, I get a compile error on the line where I am trying to get the enum values of:
System.ArgumentException: Type 'Enum' is not an enum
I wonder if it's just not possible to do what I am trying here.
You could just create an EditorTemplate Enum.cshtml
All you would have to do is change this line :
#model Beno.Model.Enums.State
For this :
#model System.Enum
You will then be able to use any Enum with it.
The catch: the engine can't infer the base class of an item thus, TestEnum won't be assigned the Enum template, so you would have to call it explicitly :
#Html.EditorFor(model => model.EnumValue, "Enum")
Not sure if I understand exactly what you mean, but try this:
#Html.DropDownListFor(model => model.EnumName, new SelectList(Enum.GetValues(typeof(Namespace.Models.EnumName))))
EnumName = State in your case.
I've used the above to get an enum into a drop down list using Twitter Bootstrap.
I too have been trying to achieve this.
Is the idea that you want to be able to use one template for all Enum types in all your models.
This way you have an Enum Template in the EditorTemplates folder that allow you to display them as drop down lists.
I have been following this article. http://blogs.msdn.com/b/stuartleeks/archive/2010/05/21/asp-net-mvc-creating-a-dropdownlist-helper-for-enums.aspx
The issue you have is that your template passes the type of System.Enum in the TModel and TProperty
Expression<Func<TModel, TProperty>> expression
Then when you perform the following below TProperty is of Type System.Enum not Beno.Model.Enums.State
EnumHelper.GetValues<TProperty>()
To get around this I do not bother looking at TProperty as it does not give me the right type.
Instead I look at the metadata.ModelType.
ModelMetadata metadata = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);
This gives me the correct type but you can't use these in within the Covariance Derived class
EnumHelper.GetValue<metadata.ModelType> //This does not work.
So I rewrote the body to not use any generics.
public static MvcHtmlString EnumDropDownListFor<TModel, TEnum>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TEnum>> expression)
{
ModelMetadata metadata = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);
var values = Enum.GetValues(metadata.ModelType);
List<SelectListItem> items = new List<SelectListItem>();
foreach (var v in values)
{
items.Add(new SelectListItem
{
Text = Regex.Replace(v.ToString(), "([A-Z][a-z])", " $1").Trim(),
Value = v.ToString(),
Selected = v.Equals(metadata.Model)
});
}
return htmlHelper.DropDownListFor(expression, items);
}
You may need to change the method signature to include your htmlattributes.
As others show, writing a custom helper is the way to go. This is exactly what was done in TwitterBootstrapMVC. Among other helpers it has a helper DropDownListFromEnumFor(...), which you'd use like so:
#Html.Bootstrap().DropDownListFromEnumFor(m => m.SomeEnum)
or
#Html.Bootstrap().DropDownListFromEnum("SomeEnum")
The cool thing about BMVC is that you can customize the dropdown with extension methods some of which are for regular html and others are Bootstrap specific. Below are some of them:
#(f.ControlGroup().DropDownListFromEnumFor(m => m.SomeEnum)
.Append("something")
.AppendIcon("glyphicon glyphicon-chevron-right")
.Class("cool-dd")
.OptionLabel("-- Select --")
.Tooltip("cool tooltip"))
Oh, and yeah, the example above will generate full control-group - input, label, and validation message.
Disclaimer: I'm the author of TwitterBootstrapMVC

passing html to a function asp.net mvc

I have Html helper method, creating a check box along with some text, I pass.
#Html.CheckBoxFor(x => x.SomeProperty,#<text> <ul> <li> </li> </ul> </text>}))
public static MvcHtmlString CheckBoxFor<TModel, TProperty>(this HtmlHelper<TModel> helper, Expression<Func<TModel, bool>> expression, ?? )
{
var chkBox = helper.CheckBoxFor(expression).ToString();
return MvcHtmlString.Create(string.Format("<li>{0}</li><div>{1}</div>", chkBox, ??);
}
What would be the signature of my method then. Some lambda/expression or something.
Help will be appreciated.
Regards
Parminder
I would recommend you looking at templated razor delegates. So in your case the helper might look something along the lines of:
public static MvcHtmlString CheckBoxFor<TModel>(
this HtmlHelper<TModel> helper,
Expression<Func<TModel, bool>> expression,
Func<object, HelperResult> template)
{
var chkBox = helper.CheckBoxFor(expression).ToHtmlString();
return MvcHtmlString.Create(
string.Format("<li>{0}</li><div>{1}</div>", chkBox, template(null))
);
}
and in the view:
#model MyViewModel
#using (Html.BeginForm())
{
#Html.CheckBoxFor(
x => x.SomeProperty,
#<text><ul><li></li></ul></text>
)
<input type="submit" value="OK" />
}
Just string. It's going into string.Format, so that gives you a hint.

get the generated clientid for a form field

Is there a way to get the client id generated for a field generated with a html helper?
i have several components that sometimes are inside another forms, and i wan't to attach javascript events to them.
so, for sample, i would like to have:
#Html.TextBoxFor(model => model.client.email)
<script>
$('##getmyusercontrolid(model=>model.client.email)').val("put your mail here");
</script>
I use this helper:
public static partial class HtmlExtensions
{
public static MvcHtmlString ClientIdFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression)
{
return MvcHtmlString.Create(htmlHelper.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldId(ExpressionHelper.GetExpressionText(expression)));
}
}
Use it just as you would any other helper: #Html.ClientIdFor(model=>model.client.email)
MVC 5 has NameFor, so your example using it is:
#Html.TextBoxFor(model => model.client.email)
<script>
$('##Html.NameFor(model => model.client.email)').val("put your mail here");
</script>
EDIT
IdFor is also available, which may be more appropriate in this situation than NameFor.
Use #Html.IdFor(model => model.client.email) as this functionality already exists like so:
#Html.TextBoxFor(model => model.client.email)
<script>
$('##Html.IdFor(model => model.client.email)').val("put your mail here");
</script>
You can read more about it at the following page:
https://msdn.microsoft.com/en-us/library/hh833709%28v=vs.118%29.aspx
You could try specifying the id in the HtmlAttributes argument when you generate the input field, e.g.
#Html.TextBoxFor(model => model.client.email, new { id = "emailTextBox" })
Then you can use this in your javaScript
var email = $('#emailTextBox').val();

ASP.NET MVC html helper code used to convert model to PropertyName

This code:
#Html.HiddenFor(model => model.Country.CountryId)
gives this output:
<input type="hidden" id="Country_CountryId" name="Country.CountryId" />
Is there a way to get the id of the above code by using just the model? For eg.
#Html.TestBoxFor(model => model.Country.CountryName, new { data_HiddenId= model.Country.CountryId.GetHtmlRenderOrSomething())
which should give this:
<input type="text" id="Country_CountryName" name="Country.CountryName" data-HiddenId="Country_CountryId" />
My main concern is data-HiddenId. I want it to be equal to the id generated for the hidden input above.
You could try this:
#Html.TestBoxFor(
model => model.Country.CountryName,
new {
data_hiddenid = ExpressionHelper.GetExpressionText((Expression<Func<ModelType, int>>)(x => x.Country.CountryId))
}
)
and to reduce the ugliness you could write a custom HTML helper:
public static class HtmlExtensions
{
public static MvcHtmlString TextBoxWithIdFor<TModel, TProperty, TId>(
this HtmlHelper<TModel> htmlHelper,
Expression<Func<TModel, TProperty>> expression,
Expression<Func<TModel, TId>> idExpression
)
{
var id = ExpressionHelper.GetExpressionText(idExpression);
return htmlHelper.TextBoxFor(expression, new { data_hiddenid = id });
}
}
and then simply:
#Html.TestBoxWithIdFor(
model => model.Country.CountryName,
model => model.Country.CountryId
)

Resources