0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

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

Posted at

説明

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

問題その1: 情報が古い

この件を検索して出てきた情報がこちらだが、12年前の投稿で、いつの間にかHtmlHelperコンストラクタの引数が変更されていた1WebFormViewコンストラクタの引数にControllerContextが追加されたのだ。新しい引数に対応したコンストラクタはこちらに載っていた…と思ったら、こちらも10年前なので古い情報だった1。今度はViewContextコンストラクタの引数にTextWriterが追加された。そこで引数を調べたのだが…

問題その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の上位互換クラスを作成するのが良いのではないか。

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. 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, 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 rule3 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.

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

  2. 例えば、<table>要素に入っていない<tr>要素など。

  3. <tr> element that's not inside the <table> elements for example.

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?