¿Es posible crear un método @helper genérico con Razor?

90

Estoy tratando de escribir un ayudante en Razor que se parezca a lo siguiente:

@helper DoSomething<T, U>(Expression<Func<T, U>> expr) where T : class

Desafortunadamente, el analizador cree que <Tes el comienzo de un elemento HTML y termino con un error de sintaxis. ¿Es posible crear un ayudante con Razor que sea un método genérico? Si es así, ¿cuál es la sintaxis?

mkedobbs
fuente
Aún no se ha solucionado en la versión actual de MVC 4. :(
Alex Dresko
1
¿Cómo es que esto todavía no se soluciona en VS2012?
Alex Dresko
7
Dios mío, no puedo esperar a que se agregue esto; Espero que esto esté en algún lugar de " implementarlo ayer " en la lista de prioridades. Parcialmente fuera de tema, pero junto con esto, me gustaría ver que las clases generadas son static, a menos que los detalles de implementación lo prohíban; la razón es que uno podría usar ayudantes de extensión genéricos :@helper Foo<T>(this T o) where T : IBar { }
Dan Lugg

Respuestas:

52

No, esto no es posible actualmente. En su lugar, podría escribir un ayudante HTML normal.

public static MvcHtmlString DoSomething<T, U>(
    this HtmlHelper htmlHelper, 
    Expression<Func<T, U>> expr
) where T : class
{
    ...
}

y entonces:

@(Html.DoSomething<SomeModel, string>(x => x.SomeProperty))

o si está apuntando al modelo como primer argumento genérico:

public static MvcHtmlString DoSomething<TModel, TProperty>(
    this HtmlHelper<TModel> htmlHelper, 
    Expression<Func<TModel, TProperty>> expr
) where TModel : class
{
    ...
}

lo que le permitirá invocarlo así (asumiendo, por supuesto, que su vista está fuertemente tipada, pero esa es una suposición segura porque todas las vistas deben estar fuertemente tipadas de todos modos :-)):

@Html.DoSomething(x => x.SomeProperty)
Darin Dimitrov
fuente
10
Con suerte, esto es algo que agregan a una versión futura de los ayudantes de Razor. La legibilidad de un asistente tradicional es mucho menor que la sintaxis @helper.
mkedobbs
2
Sí, de acuerdo. ¡Volver al método anterior no solo apesta, sino que divide a tus ayudantes arbitrariamente!
George R
127

Esto se puede lograr dentro de un archivo de ayuda con la @functionssintaxis, pero si desea la legibilidad de estilo navaja a la que se refiere, también deberá llamar a un ayudante habitual para que ajuste y termine el HTML.

Tenga en cuenta que las funciones en un archivo Helper son estáticas, por lo que aún deberá pasar la instancia de HtmlHelper desde la página si tiene la intención de usar sus métodos.

por ejemplo, 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>
}
...
chrismilleruk
fuente
Esto es perfecto. Gracias.
Ken Smith
NO tiene que hacer que el método sea estático y, por lo tanto, NO necesita pasar su Html / Url / Model
etc.- Sheepy
12
@Sheepy, eso es solo una verdad a medias. Tiene razón, puede hacer que no sean estáticos, pero solo obtiene en System.Web.WebPages.Html.HtmlHelperlugar de System.Web.Mvc.HtmlHelper. Existe una excelente posibilidad de que la WebPagesversión no sea adecuada para usted, ya que la mayoría de los métodos de extensión están escritos en contra System.Web.Mvc.HtmlHelper. Además, no existe ninguna Urlpropiedad y UrlHelperrequiere una RequestContextque no está disponible en la WebPagesversión. Con todo, probablemente tendrá que pasar el Mvc HtmlHelper.
Kirk Woll
1
el ayudante debe ser parte de App_Code Folder ??
Vishal Sharma
1
Sí, este archivo debe colocarse en {MyMvcProject}\App_Code`. It doesn't work as advertised when you place it elsewhere. The error *Cannot access non-static method 'TheThingToDo' in static context* disappears when you move MyHelper.cshtml` en App_Code. DoSomethingdebe ser estático, de modo que pueda llamar @MyHelper.DoSomething(..)en su vista. Si lo convierte en no estático, deberá crear una instancia de MyHelperfirst.
Grilse
3

En todos los casos, el TModelserá el mismo (el modelo declarado para la vista), y en mi caso, el TValueiba a ser el mismo, por lo que pude declarar el tipo de argumento Expresión:

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

Si los campos de su modelo son todos string, puede reemplazarlos MyClasscon string.

Puede que no sea malo definir dos o tres ayudantes con el TValuedefinido, pero si tiene más que generarían un código desagradable, realmente no encontré una buena solución. Intenté envolver el @helperdesde una función que puse dentro del @functions {}bloque, pero nunca logré que funcionara en ese camino.

bradlis7
fuente
Algo obvio cuando lo piensas, si es TModelque probablemente lo sepas de antemano.
ta.speot.is
0

si su principal problema es obtener el valor del atributo de nombre para el enlace mediante la expresión lambda @Html.TextBoxFor(x => x.MyPoperty), y si su componente tiene etiquetas html muy complejas y debe implementarse en el asistente de afeitar, ¿por qué no crear un método de extensión HtmlHelper<TModel>para resolver el nombre de enlace:

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

su ayudante de afeitado debe ser como de costumbre:

@helper MyComponent(string name)
{
    <input name="@name" type="text"/>
}

entonces aquí puedes usarlo

@TheHelper.MyComponent(Html.GetNameForBinding(x => x.MyProperty))
ktutnik
fuente
¿No es esto para lo que sirve @ Html.IdFor (...)?
Alex Dresko
Sí, puede hacerlo, @Htm.IdForpero necesita un proceso adicional para convertirlo en string ( .ToHtmlString()) donde el asistente requiere string
ktutnik