Czy można utworzyć ogólną metodę @helper za pomocą Razor?

90

Próbuję napisać pomocnika w Razor, który wygląda następująco:

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

Niestety, parser uważa, że <Tto początek elementu HTML i kończy się błędem składni. Czy można utworzyć pomocnika z Razor, który jest metodą ogólną? Jeśli tak, jaka jest składnia?

mkedobbs
źródło
Nadal nie naprawiono w bieżącej wersji MVC 4. :(
Alex Dresko
1
Dlaczego nadal nie zostało to naprawione w VS2012?
Alex Dresko
7
Boże, nie mogę się doczekać, aż to zostanie dodane; Mam nadzieję, że jest to gdzieś na liście priorytetów „ zaimplementuj to wczoraj ”. Częściowo nie na temat, ale jednocześnie chciałbym zobaczyć, że wygenerowane klasy są static, chyba że zabraniają tego szczegóły implementacji; Powodem jest to, że można użyć ogólnych pomocników rozszerzeń :@helper Foo<T>(this T o) where T : IBar { }
Dan Lugg

Odpowiedzi:

52

Nie, obecnie nie jest to możliwe. Zamiast tego możesz napisać normalnego pomocnika HTML.

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

i wtedy:

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

lub jeśli kierujesz model jako pierwszy argument ogólny:

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

co pozwoli ci wywołać to w ten sposób (zakładając oczywiście, że twój widok jest silnie wpisany, ale jest to bezpieczne założenie, ponieważ wszystkie widoki i tak powinny być silnie wpisane :-)):

@Html.DoSomething(x => x.SomeProperty)
Darin Dimitrov
źródło
10
Miejmy nadzieję, że jest to coś, co dodają do przyszłej wersji pomocników Razora. Czytelność tradycyjnego pomocnika jest znacznie niższa niż składnia @helper.
mkedobbs
2
Tak, zgodził się. Powrót do starszej metody nie tylko jest do bani, ale także arbitralnie rozdziela twoich pomocników!
George R,
127

Jest to możliwe do osiągnięcia wewnątrz pliku pomocniczego ze @functionsskładnią, ale jeśli chcesz uzyskać czytelność w stylu maszynki do golenia, do której się odnosisz, będziesz musiał również wywołać zwykłego pomocnika, aby dopasować i zakończyć HTML.

Zwróć uwagę, że funkcje w pliku pomocnika są statyczne, więc nadal musisz przekazać wystąpienie HtmlHelper ze strony, jeśli zamierzasz użyć jej metod.

np. 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
źródło
To jest doskonałe. Dzięki.
Ken Smith
NIE MUSISZ uczynić metody statyczną, a zatem NIE musisz również przekazywać swojego Html / Url / Modelu itp.
Sheepy
12
@Sheepy, to tylko w połowie prawda. Masz rację, możesz uczynić je niestatycznymi, ale otrzymujesz tylko System.Web.WebPages.Html.HtmlHelperzamiast System.Web.Mvc.HtmlHelper. Istnieje duża szansa, że WebPageswersja nie będzie dla Ciebie odpowiednia, ponieważ większość metod rozszerzających jest napisana przeciwko System.Web.Mvc.HtmlHelper. Ponadto nie ma Urlwłaściwości i UrlHelperwymaga, RequestContextco jest niedostępne w tej WebPageswersji. W sumie prawdopodobnie będziesz musiał przejść w Mvc HtmlHelper.
Kirk Woll
1
pomocnik musi być częścią folderu App_Code?
Vishal Sharma
1
Tak, ten plik należy umieścić w {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` w App_Code. DoSomethingpowinien być statyczny, abyś mógł wywołać @MyHelper.DoSomething(..)swój widok. Jeśli ustawisz ją jako niestatyczną, musisz utworzyć instancję MyHelperfirst.
Grilse
3

We wszystkich przypadkach TModelbędzie taki sam (model zadeklarowany dla widoku), aw moim przypadku TValuemiał być taki sam, więc mogłem zadeklarować typ argumentu 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>
}

Jeśli wszystkie pola modelu są dostępne string, możesz zastąpić MyClassjestring .

Zdefiniowanie dwóch lub trzech pomocników ze TValuezdefiniowanym może nie być złe , ale jeśli masz więcej, które generują brzydki kod, nie znalazłem dobrego rozwiązania. Próbowałem zawinąć funkcję @helperz funkcji, którą umieściłem wewnątrz @functions {}bloku, ale nigdy nie udało mi się jej wykonać tej ścieżki.

bradlis7
źródło
To trochę oczywiste, kiedy się nad tym zastanowić - jeśli to TModelpewnie wiesz z góry.
ta.speot.is
0

jeśli Twoim głównym problemem jest uzyskanie wartości atrybutu nazwy dla powiązania przy użyciu wyrażenia lambda @Html.TextBoxFor(x => x.MyPoperty), a jeśli twój komponent ma bardzo złożone znaczniki HTML i powinien być zaimplementowany w pomocniku maszynki do golenia, to dlaczego nie utworzyć po prostu metody rozszerzenia w HtmlHelper<TModel>celu rozwiązania problemu nazwa oprawy:

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

Twój pomocnik maszynki do golenia powinien wyglądać jak zwykle:

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

to tutaj możesz go użyć

@TheHelper.MyComponent(Html.GetNameForBinding(x => x.MyProperty))
ktutnik
źródło
Czy to nie jest to, do czego służy @ Html.IdFor (...)?
Alex Dresko
Tak, możesz to zrobić, @Htm.IdForale potrzebujesz dodatkowego procesu, aby przekonwertować go na string ( .ToHtmlString()), w którym pomocnik wymaga ciągu
ktutnik