É possível criar um método @helper genérico com o Razor?

88

Estou tentando escrever um auxiliar no Razor que se pareça com o seguinte:

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

Infelizmente, o analisador pensa que <Té o início de um elemento HTML e acabo com um erro de sintaxe. É possível criar um helper com o Razor que seja um método genérico? Em caso afirmativo, qual é a sintaxe?

mkedobbs
fonte
Ainda não corrigido na versão atual do MVC 4. :(
Alex Dresko,
1
Como isso ainda não foi corrigido no VS2012?
Alex Dresko,
7
Meu Deus, mal posso esperar para que isso seja adicionado; Espero que seja algo em torno de " implemente ontem " na lista de prioridades. Parcialmente fora do tópico, mas junto com isso, eu gostaria de ver que as classes geradas são static, a menos que os detalhes de implementação o proíbam; a razão é que se poderia usar auxiliares de extensão genéricos :@helper Foo<T>(this T o) where T : IBar { }
Dan Lugg

Respostas:

52

Não, isso não é possível. Em vez disso, você poderia escrever um auxiliar HTML normal.

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

e depois:

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

ou se você está direcionando o modelo como primeiro argumento genérico:

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

que permitirá que você o invoque desta forma (assumindo, é claro, que sua visão seja fortemente tipada, mas isso é uma suposição segura porque todas as visualizações devem ser fortemente tipadas de qualquer maneira :-)):

@Html.DoSomething(x => x.SomeProperty)
Darin Dimitrov
fonte
10
Esperançosamente, isso é algo que eles adicionam a uma versão futura dos auxiliares do Razor. A legibilidade de um helper tradicional é muito menor do que a sintaxe @helper.
mkedobbs de
2
Sim, concordo. Reverter para o método antigo não apenas é uma droga, mas divide seus ajudantes arbitrariamente!
George R
126

Isso é possível fazer dentro de um arquivo auxiliar com a @functionssintaxe, mas se você quiser a legibilidade estilo navalha à qual está se referindo, também precisará chamar um auxiliar regular para fazer o ajuste e finalização do HTML.

Observe que as funções em um arquivo Helper são estáticas, portanto, você ainda precisa passar a instância HtmlHelper da página se pretende usar seus métodos.

por exemplo, 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
fonte
Isto é perfeito. Obrigado.
Ken Smith
Você NÃO precisa tornar o método estático e, portanto, NÃO precisa passar seu Html / Url / Modelo etc
Sheepy
12
@Sheepy, isso é apenas meia verdade. Você está correto, você pode torná-los não estáticos, mas você apenas obtém em System.Web.WebPages.Html.HtmlHelpervez de System.Web.Mvc.HtmlHelper. Há uma excelente chance de que a WebPagesversão não seja adequada para você, uma vez que a maioria dos métodos de extensão são escritos contra System.Web.Mvc.HtmlHelper. Além disso, não existe nenhuma Urlpropriedade e UrlHelperrequer um RequestContextque não está disponível na WebPagesversão. No geral, você provavelmente terá que passar no Mvc HtmlHelper.
Kirk Woll
1
o auxiliar deve fazer parte da pasta App_Code ??
Vishal Sharma
1
Sim, este arquivo deve ser colocado em {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` em App_Code. DoSomethingdeve ser estático, para que você possa chamar @MyHelper.DoSomething(..)em sua visualização. Se você torná-lo não estático, precisará criar uma instância de MyHelperprimeiro.
Grilse
3

Em todos os casos, o TModelserá o mesmo (o modelo declarado para a visualização) e, no meu caso, o TValueseria o mesmo, então pude declarar o tipo de argumento Expression:

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

Se os campos do seu modelo forem todos string, você poderá substituir MyClasspor string.

Pode não ser ruim definir dois ou três auxiliares com o TValuedefinido, mas se você tiver mais algum que geraria um código feio, eu realmente não encontrei uma boa solução. Eu tentei encapsular o @helperde uma função que coloquei dentro do @functions {}bloco, mas nunca consegui trabalhar nesse caminho.

Bradlis7
fonte
Meio óbvio quando você pensa sobre isso - se for, TModelvocê provavelmente sabe com antecedência.
ta.speot.is
0

se o seu principal problema é obter o valor do atributo name para vinculação usando a expressão lambda parece com o @Html.TextBoxFor(x => x.MyPoperty), e se o seu componente tem tags html muito complexas e deve ser implementado no auxiliar de barbear, então por que não criar apenas um método de extensão HtmlHelper<TModel>para resolver o nome da ligação:

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

seu auxiliar de barbear deve ser como de costume:

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

então aqui você pode usá-lo

@TheHelper.MyComponent(Html.GetNameForBinding(x => x.MyProperty))
ktutnik
fonte
Não é para isso que serve @ Html.IdFor (...)?
Alex Dresko
Sim, você pode fazer com, @Htm.IdFormas precisa de um processo extra para convertê-lo em string ( .ToHtmlString()) onde o auxiliar exige string
ktutnik