ASP.NET MVC コントローラー内で`HtmlHelper`を使いたい！ How to use `HtmlHelper` in controller.

#説明

筆者はASP.NET MVCで、コントローラ内で`ContentResult`を利用したHTMLスニペットの生成アクションを作成していた。
一からHTMLを起こすのは大変なので、Viewで使われている`HtmlHelper`クラスが使えればうれしいのだが、使えるように設定するのは至難の業だった。

##問題その1: 情報が古い

この件を検索して出てきた情報が[こちら](https://stackoverflow.com/questions/621235/using-htmlhelper-in-a-controller/621261)だが、12年前の投稿で、いつの間にか`HtmlHelper`コンストラクタの引数が変更されていた[^1]。`WebFormView`コンストラクタの引数に`ControllerContext`が追加されたのだ。新しい引数に対応したコンストラクタは[こちら](https://stackoverflow.com/questions/6887697/htmlhelper-in-controller)に載っていた…と思ったら、こちらも10年前なので古い情報だった[^1]。今度は`ViewContext`コンストラクタの引数に`TextWriter`が追加された。そこで引数を調べたのだが…

[^1]: このHtmlHelperは非ジェネリック版なので、`EditorFor()`などのモデルの型情報が必要な関数には対応していない。

##問題その2: 情報が少ない

各引数の型（TextWriter除く）はASP.NET内部で使用されることが前提のため、公式でもそれほどドキュメントが準備されておらず、かと言ってネットを検索してもそれらに関する情報がほとんど無かった。

#正解

そんな四苦八苦の末、たどり着いた結論は、2番目のリンクの解決策を少し修正したものとなった。

``` DeclareHtmlController.cs
public ContentResult Baz() {
    var helper = new HtmlHelper<FooModel>(new ViewContext(ControllerContext, new WebFormView(ControllerContext, "Nemo"), new ViewDataDictionary(), new TempDataDictionary(), new StringWriter()), new ViewPage()); //この行!
    var textFor = helper.TextFor(m => m.Bar);
    var htmlCode = GenerateHtml(textFor) // textForをhtmlコードに挿入する。詳細は割愛する。
    return Content(htmlCode , "text/html");
}
```

`TextWriter`は抽象クラスのため、実装の1つである`StringWriter`を利用する。`WebFormView`のコンストラクタの2つ目の引数は識別子みたいなものらしく、名前は適当でよい（ただし空文字はNG）。

#最後に―`ContentResult`の戻り値について―

少なくともChromeの場合（FirefoxやSafariは環境に未インストールのため検証不可）、`ContentResult`でHTMLスニペットをそのまま返してしまうと、受け取り側（ブラウザのJavaScript）で不正なHTMLとみなされてしまうのか、HTMLの書式に合わない部分は省略されてしまう[^2]。この辺りは`<template>`要素を使用した場合と事情が異なっている。
解決はやや手間がかかるが簡単で、HTMLスニペットを妥当なHTML文書に格納し、そこから必要な要素を読みだせばよい。先ほどのコード中の`GenerateHtml`はそれをするためのメソッドだが、実装の仕方は人それぞれだろうから中身は省略した。後々のことを考えると、Compositeパターンを取り入れて、タグを入れ子にできる`TagBuilder`の上位互換クラスを作成するのが良いのではないか。

[^2]: 例えば、`<table>`要素に入っていない`<tr>`要素など。

#English Version Here

I was making `Action` inside the controller for generating HTML snippets as `ContentResult` using ASP.NET MVC.
I'd like to use `HtmlHelper` since writing HTML from the ground up is tedious, but how to use it was a trek.

##Probrem 1: Information is old

The information I came across when I searched the issue is [here](https://stackoverflow.com/questions/621235/using-htmlhelper-in-a-controller/621261). However, this info was published 12 years ago and `HtmlHelper` constructor has changed; `ControllerContext` was added to the arguments of the `WebFormView` constructor. I found [newer solution](https://stackoverflow.com/questions/6887697/htmlhelper-in-controller), though that was also 10 years old one and it lacks one of the `ViewContext`'s argument that is `TextWriter`. Also, they are both aren't generic, so they can't use methods such as `EditorFor()`, which needs the information of type the model uses.

##Probrem 2: Information is scarce

Most of the arguments' type are supporsed to be used internally, so there are not much documents even officially, and there are few piecies of information around the web.

#The solution

I managed to implement the helper. The idea was basically from the second link.

``` DeclareHtmlController.cs
public ContentResult Baz() {
    var helper = new HtmlHelper<FooModel>(new ViewContext(ControllerContext, new WebFormView(ControllerContext, "Nemo"), new ViewDataDictionary(), new TempDataDictionary(), new StringWriter()), new ViewPage()); // This line!
    var textFor = helper.TextFor(m => m.Bar);
    var htmlCode = GenerateHtml(textFor) // Inserts textFor to html code. Details are omitted here.
    return Content(htmlCode , "text/html");
}
```

Because the `TextWriter` is a abstruct class, I needed to use the `StringWriter` class, which is one of the extension of it. The second argument of the `WebFormView` constructor seems to be an identifier, thus the name can be anything but empty.

#At last - the return value of `ContentResult`

At reast in Chrome (I can't verify Firefox or Safari now), if the `ContentResult` returns raw HTML snippets, receiver (the JavaScript of the browser) appears to interpret the HTML as malformed; The parts that's not corresponding HTML rule[^3] is omitted. This behavior is different from using the `<template>` elements. The answer is simple yet needs some work - Put the snippet into a valid HTML and JS reads the necessary part. `GenerateHtml` in the previous code example is for the purpose, but how to implement it is your problem. I suggest making the upper conversion of the `TagBuilder` class that can insert tags inside using the Composite Pattern. It would be useful in the future.

[^3]: `<tr>` element that's not inside the `<table>` elements for example.