Адаптация пользовательского Html Helper для Razor (он использует HtmlTextWriter, поэтому возвращает void) - PullRequest
3 голосов
/ 18 ноября 2010

Проблема

У меня очень изящный помощник по Html меню, написанный для представлений WebFormViewEngine. Этот механизм позволяет вашим помощникам возвращать пустоту и при этом использовать:

@Html.Theseus

Это отлично подходит для моего помощника, потому что он может затем визуализировать меню, используя HtmlTextWriter, который визуализирует непосредственно в выходной поток.

Однако в представлениях Razor помощники Html должны возвращать значение (обычно MvcHtmlString), которое и добавляется в вывод. Маленькая разница, большое следствие.

Существует способ обойти это, как указал мне GvS (см. ASP.NET MVC 2 - MVC 3: пользовательские помощники HTML в Razor ) следующим образом:

Если помощник возвращает void, выполните следующие действия:

@{Html.Theseus;}

(По сути, вы просто вызываете метод, а не выполняете рендеринг в представление).

Хотя все еще аккуратно, это не совсем то же самое, что @ Html.Theseus. Итак ...

Мой код сложен, но работает очень хорошо, поэтому мне не терпится пройти через основные изменения, т. Е. Заменить HtmlTextWriter другим писателем. Фрагмент кода выглядит так:

writer.AddAttribute(HtmlTextWriterAttribute.Href, n.Url);
writer.AddAttribute(HtmlTextWriterAttribute.Title, n.Description);
writer.RenderBeginTag(HtmlTextWriterTag.A);
writer.WriteEncodedText(n.Title);
writer.RenderEndTag();

// Recursion, if any
// Snip off the recursion at this level if specified by depth
// Use a negative value for depth if you want to render the entire sitemap from the starting node

    if ((currentDepth < depth) || (depth < 0))
    {
         if (hasChildNodes)
         {
              // Recursive building starts here

              // Open new ul tag for the child nodes 
              // "<ul class='ChildNodesContainer {0} Level{1}'>"; 
              writer.AddAttribute(HtmlTextWriterAttribute.Class, "Level" + currentDepth.ToString());
              writer.RenderBeginTag(HtmlTextWriterTag.Ul);

              // BuildMenuLevel calls itself here to 
              // recursively traverse the sitemap hierarchy, 
              // building the menu as I go.
              // Note: this is where I increase the currentDepth variable!
               BuildChildMenu(currentDepth + 1, depth, n, writer);

              // Close ul tag for the child nodes
              writer.RenderEndTag();
          }
    }

Не было бы весело переписать с TagBuilders. В его нынешнем виде он отображает меню любого типа, включая «Инкрементную навигацию», как описано в моей статье 4guysfromrolla: Реализация пошаговой навигации с ASP.NET

Опции:

Полагаю, я мог бы вернуть пустую строку MvcHtmlString, но это в значительной степени определение хака ...

Единственная альтернатива - отправиться на закат и переписать помощника, используя TagBuilder для создания каждого тега, добавить его в StringBuilder, затем создать следующий тег и т. Д., А затем использовать экземпляр StringBuilder для создания MvcHtmlString. Серьезно уродливо, если я не смогу сделать что-то вроде ...

Вопрос:

Есть ли способ:

Остановить рендеринг HtmlTextWriter в поток и вместо этого использовать его как StringBuilder, который в конце процесса я использую для создания MvcHtmlString (или HtmlString)?

Звучит невероятно, даже когда я пишу ...

PS:

Самое замечательное в HtmlTextWriter заключается в том, что вы можете создавать большое количество тегов, а не создавать их один за другим, как в TagBuilder.

1 Ответ

7 голосов
/ 18 ноября 2010

Вопреки ответам, которые вы получили на ваш другой вопрос Razor не требует возврата HtmlString.Проблема с вашим кодом сейчас заключается в том, что вы пишете напрямую в поток ответов.Razor выполняет вещи наизнанку, что означает, что вы можете испортить порядок ответов (см. аналогичный вопрос ).

Так что в вашем случае вы, вероятно, могли бы сделать это (хотя я непроверил это):

public static void Theseus(this HtmlHelper html)
{
    var writer = new HtmlTextWriter(html.ViewContext.Writer);
    ...
}

Редактировать (следите за вашими комментариями):

Html Помощники вполне способны либо вернуть HtmlString напрямую, либо аннулировать и написатьк автору контекста.Например, и Html.Partial, и Html.RenderPartial отлично работают в Razor.Я думаю, что вас смущает то, что синтаксис необходим для вызова одной версии, а не другой.

Например, рассмотрим представление Aspx:

<%: Html.Partial("Name") %>
<% Html.RenderPartial("Name") %>

Вы звонитекаждый метод по-своему.Если вы перевернете вещи, они просто не будут работать.Аналогично в Razor:

@Html.Partial("Name")
@{ Html.RenderPartial("Name"); }

Теперь так уж получилось, что синтаксис использования void helper намного более многословен в Razor по сравнению с Aspx.Тем не менее, оба работают просто отлично.Если только вы не имели в виду что-то другое, «проблема в том, что html-помощник не может вернуть void».

Кстати, если вы действительно хотите вызвать своего помощника с использованием этого синтаксиса: @Html.Theseus() вы можете сделатьэто:

public static IHtmlString Theseus(this HtmlHelper html)
{
    var writer = new HtmlTextWriter(html.ViewContext.Writer);
    ...
    return new HtmlString("");
}

Но это что-то вроде хака.

...