Est-il possible de créer une méthode @helper générique avec Razor?

88

J'essaie d'écrire une aide dans Razor qui ressemble à ce qui suit:

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

Malheureusement, l'analyseur pense que <Tc'est le début d'un élément HTML et je me retrouve avec une erreur de syntaxe. Est-il possible de créer un assistant avec Razor qui est une méthode générique? Si oui, quelle est la syntaxe?

mkedobbs
la source
Toujours pas corrigé dans la version actuelle de MVC 4. :(
Alex Dresko
1
Comment ce problème n'est-il toujours pas résolu dans VS2012?
Alex Dresko
7
Bonté, j'ai hâte que cela soit ajouté; J'espère que c'est quelque part autour de " mettre en œuvre hier " sur la liste des priorités. Partiellement hors sujet, mais à côté de cela, j'aimerais voir que les classes générées le sont static, à moins que les détails d'implémentation ne l'interdisent; La raison en est, est-ce qu'on pourrait utiliser des assistants d'extension génériques :@helper Foo<T>(this T o) where T : IBar { }
Dan Lugg

Réponses:

52

Non, ce n'est pas possible. Vous pouvez plutôt écrire un assistant HTML normal.

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

puis:

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

ou si vous ciblez le modèle comme premier argument générique:

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

ce qui vous permettra de l'invoquer comme ceci (en supposant bien sûr que votre vue est fortement typée, mais c'est une hypothèse sûre car toutes les vues doivent être fortement typées de toute façon :-)):

@Html.DoSomething(x => x.SomeProperty)
Darin Dimitrov
la source
10
J'espère que c'est quelque chose qu'ils ajoutent à une future version des assistants Razor. La lisibilité d'un assistant traditionnel est bien inférieure à la syntaxe @helper.
mkedobbs
2
Ouais d'accord. Revenir à l'ancienne méthode est non seulement nul, mais divise vos assistants de manière arbitraire!
George R
126

Cela est possible à l'intérieur d'un fichier d'aide avec la @functionssyntaxe, mais si vous voulez la lisibilité de style rasoir à laquelle vous faites référence, vous devrez également appeler un assistant régulier pour faire l'ajustement et la finition HTML.

Notez que les fonctions d'un fichier Helper sont statiques, vous devrez donc toujours transmettre l'instance HtmlHelper de la page si vous aviez l'intention d'utiliser ses méthodes.

par exemple 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
la source
C'est parfait. Merci.
Ken Smith
Vous n'avez PAS à rendre la méthode statique, et donc vous n'avez pas non plus besoin de transmettre votre Html / Url / Model etc
Sheepy
12
@Sheepy, ce n'est qu'à moitié vrai. Vous avez raison, vous pouvez les rendre non statiques, mais vous obtenez seulement System.Web.WebPages.Html.HtmlHelperplutôt que System.Web.Mvc.HtmlHelper. Il y a de fortes chances que la WebPagesversion ne vous convienne pas, car la plupart des méthodes d'extension sont écrites contre System.Web.Mvc.HtmlHelper. De plus, il n'y a pas de Urlpropriété, et UrlHelpernécessite une RequestContextqui n'est pas disponible dans la WebPagesversion. Dans l'ensemble, vous devrez probablement passer dans le Mvc HtmlHelper.
Kirk Woll
1
l'assistant doit faire partie du dossier App_Code ??
Vishal Sharma
1
Oui, ce fichier doit être placé dans {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` dans App_Code. DoSomethingdoit être statique, afin que vous puissiez appeler @MyHelper.DoSomething(..)dans votre vue. Si vous le rendez non statique, vous devez créer une instance de MyHelperfirst.
Grilse
3

Dans tous les cas, le TModelsera le même (le modèle déclaré pour la vue), et dans mon cas, le TValuesera le même, j'ai donc pu déclarer le type d'argument 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>
}

Si vos champs de modèle sont tous string, vous pouvez les remplacer MyClasspar string.

Ce n'est peut-être pas mal de définir deux ou trois helpers avec le TValuedéfini, mais si vous en avez plus qui génèreraient du code laid, je n'ai pas vraiment trouvé de bonne solution. J'ai essayé d'envelopper @helperune fonction que j'ai placée dans le @functions {}bloc, mais je ne l'ai jamais fait fonctionner dans cette voie.

bradlis7
la source
C'est assez évident quand on y pense - si c'est le cas, TModelvous le savez probablement à l'avance.
ta.speot.is
0

si votre principal problème est d'obtenir la valeur d'attribut de nom pour la liaison à l'aide de l'expression lambda @Html.TextBoxFor(x => x.MyPoperty), et si votre composant a des balises html très complexes et doit être implémenté sur razor helper, alors pourquoi ne pas simplement créer une méthode d'extension HtmlHelper<TModel>pour résoudre le nom de la liaison:

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

votre aide-rasoir devrait être comme d'habitude:

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

alors ici vous pouvez l'utiliser

@TheHelper.MyComponent(Html.GetNameForBinding(x => x.MyProperty))
ktoutnik
la source
N'est-ce pas à cela que sert @ Html.IdFor (...)?
Alex Dresko
Oui, vous pouvez le faire, @Htm.IdFormais vous avez besoin d'un processus supplémentaire pour le convertir en string ( .ToHtmlString()) où l'assistant nécessite une chaîne
ktutnik