空談録

http://artfulplace.net/blogs/ からひっこしつつ

Microsoft.AspNetCore.Razor.Language + Roslyn + .NET Core でファイル生成をしたい (その1)

夏休みの自由研究は4時間かからなかったようです…。
Razorが優秀すぎる。

というわけで、Macとかで使えるコード生成の仕組みがほしいなーと思ったわけですが、固定ファイルだけだとさすがに汎用性なさすぎるし、かといってテンプレートエンジンの仕組みを作るのはだるいわけですよ。
なので既存のテンプレートエンジン使ってコード生成できる仕組みを作ろう!という話です。

既存のテンプレートエンジンに乗っかってファイルを作れると
・テンプレートエンジン作るコスト、メンテナンスコストがなくなる
・構文考えなくていい
・ドキュメント作らなくていい
…最高ですね!

それでひとまず思いつくのはT4かRazorというところなんですが、T4は普通の.NET Frameworkを超えた世界にいける見込みがあんまりないです。
Mono.TextTemplatingとかあったけど.Net 4.5.1とか見えてみなかったことに。
しかし!Razorは! ASP.NET Coreに対応!している!!!
つまりMacとかでも動かせそうな雰囲気です。Razorに乗っかっていきましょう。

そもそもMicrosoft.AspNetCore.Razor.Languageって何?

AspNetCore.RazorってRazorとRazor.Runtimeしかなくない?っていうところですね。
ASP.NET Coreのバージョン1.x系では、RazorとRazor.Runtimeしかなかったわけですが、2.xでリファクタリングする際に、パーサー、コード生成部分を新しいアセンブリに分割することになっています。
(AspNetCore.Razor.Evolutionとかいう名前だったやつがRazor.Languageです。)

この辺の話は下のissueでご確認ください。
github.com

つまるところRazorEngineとかがRazor.Languageに分離された形です。
逆にAspNetCore.Razorに何が残ってるのかよくわからない感じですがまあその辺はリリースされたらきっとわかるはず…。
(今のところenumが3つ入ってるだけでよくわからない…。)

以下の話は2.0.0-preview2を用いてやっていきましょう。

プロジェクトの準備をしよう

.NET Coreのコンソールアプリケーションで作っていきます。
NuGetから次のパッケージを入れておきます。

Install-Package Microsoft.AspNetCore.Razor.Language
Install-Package Microsoft.CodeAnalysis.CSharp
Install-Package System.Runtime.Loader

こんな感じに入っていればOKです。

追記(8/14) : 気が付いたら Microsoft.AspNetCore.Razor.Language の2.0がリリースされていました。
が.NET Standard 2.0以上対応です。.NETCoreApp 1.1でやると入らないので注意してください。
(2.0.0-preview2は.NET Standard 1.6なので入る)

f:id:fantasticswallow:20170811153724p:plain

RazorでC#のコードを作成する

全体の流れとしては次の通りです。

cshtml(インテリセンス効くし…) → (Razor) → C#コード → (Roslyn) → 生成結果

ここで注意すべきはRazor単体ではファイルが生成できないという点です。
Razorはビューエンジンですが、ビューの最終的な生成はASP.NET側に任せているといえます。

とまあRazorの基本をおさえたところで、実際にC#のコードを作ってみましょう。
RazorEngineに投げてRazorCSharpDocumentを作るだけの簡単なお仕事です。

var engine = RazorEngine.Create();
var project = RazorProject.Create(@"D:\razorlanguage");

var templateEngine = new RazorTemplateEngine(engine, project);
var csDocument = templateEngine.GenerateCode("_ViewStart.cshtml");
var codeDocument = templateEngine.CreateCodeDocument("_ViewStart.cshtml");

Console.WriteLine(csDocument.GeneratedCode);

ファイル名とかは適当。
ひとまず生成できればあとは使い方次第という形なので、決め打ちでまずは動かしています。

なんか特にドキュメントっぽいものがなくて泣きそうでしたが、大半はabstruct classであること、RazorEngineとかいうそれっぽいやつがいることからまあそこそこ推測はできます。

RazorEngineとRazorProjectを作ったらRazorTemplateEngineを作成します。
RazorTemplateEngine自体はただの便利クラスでしかないのですが(RazorEngine、RazorProjectを扱いやすくしているだけ)、まあ使えるものは使いましょう。

RazorTemplateEngine作ったらあとはcshtmlを投げてコード生成するだけです。
GenerateCodeでRazorCSharpDocumentをくれるのでわーい楽ちーんという。
RazorCodeDocumentは必須ではないです。ただ元のファイルパスが手に入らないのでひとまず作成しています。

ここまで書いたらいったん次のcshtmlで動かしてみましょう。

@{ 
    var test = "hogehogefugafuga";
}


a @DateTime.Now.ToString() Eeeeeeee

@false

name = @test

Razor初心者です!よろしくお願いします!

上のcshtmlを変換すると次のC#のコードが出てきます。

#pragma checksum "D:\razorlanguage\_ViewStart.cshtml" "{ff1816xx-xxxx-...}" "d8bf95..."
namespace Razor
{
    #line hidden
    public class Template
    {
        #pragma warning disable 1998
        public async override global::System.Threading.Tasks.Task ExecuteAsync()
        {
#line 1 "D:\razorlanguage\_ViewStart.cshtml"

    var test = "hogehogefugafuga";

#line default
#line hidden
            WriteLiteral("\r\n\r\na ");
#line 6 "D:\razorlanguage\_ViewStart.cshtml"
Write(DateTime.Now.ToString());

#line default
#line hidden
            WriteLiteral(" Eeeeeeee\r\n\r\n");
#line 8 "D:\razorlanguage\_ViewStart.cshtml"
Write(false);

#line default
#line hidden
            WriteLiteral("\r\n\r\nname = ");
#line 10 "D:\razorlanguage\_ViewStart.cshtml"
  Write(test);

#line default
#line hidden
            WriteLiteral("\r\n");
        }
        #pragma warning restore 1998
    }
}

自動生成感あふれる温かみのないコードですね

このC#のコードを実行するとうまい感じにビューが作られたり、ファイル出力ができるわけです。
とはいえ結構長めになってきたのと、Razorっぽい話がここで終わるので、Roslynで実行するのは次の記事に分けます。


というわけでRazorでC#のコードを吐くところまで行いました。
Razorがどういうやつなのか分かったのはよかったです。てっきりファイル出力までやってくれると思っていたらそんなことはなかった…。

つづき:
artfulplace.hatenablog.com


この辺で。