Is it possible to create a generic @helper method with Razor?

asp.net MvcGenericsasp.net Mvc-3Razor

asp.net Mvc Problem Overview


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?

asp.net Mvc Solutions


Solution 1 - asp.net Mvc

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>
}
...

Solution 2 - asp.net Mvc

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)

Solution 3 - asp.net Mvc

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.

Solution 4 - asp.net Mvc

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

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionmkedobbsView Question on Stackoverflow
Solution 1 - asp.net MvcchrismillerukView Answer on Stackoverflow
Solution 2 - asp.net MvcDarin DimitrovView Answer on Stackoverflow
Solution 3 - asp.net Mvcbradlis7View Answer on Stackoverflow
Solution 4 - asp.net MvcktutnikView Answer on Stackoverflow