読者です 読者をやめる 読者になる 読者になる

SignalR HubProxyのTypeScript型定義を自動生成してくれるT4テンプレート 「Hubs.tt」

SignalR TypeScript ASP.NET

前回は、ASP.NET + SignalRのクライアントサイドにTypeScriptを導入してみました。

今回は、サーバー側のコードも見てみることにします。

サーバー側が呼び出すクライアント側のメソッドを静的型付けにする

サーバー側で記述するクライアント側のメソッドは、dynamicなオブジェクトに生やしています。

public class ChatHub : Hub
{
    public void Send(string name , string message)
    {
        //クライアント側のメソッド呼び出しは、実行時に解決
        Clients.All.AddNewMessageToPage(name, message);
    }
}

しかし、これではコンパイル時のチェックやインテリセンスの恩恵に預かれません。 ほとんどのケースでは、使用するメソッドの定義はコンパイル時には分かっているのですから、静的に型付けをしたいところです。

ジェネリック版Hubクラス

実は、Hubクラスにはジェネリック版が用意されています。 ジェネリックの型引数にクライアント側のインターフェースを指定することが出来ます。

class ChatHub : Hub<IChatHubClient>

クライアント側のメソッドを定義したインターフェース(IChatHubClient)を用意します。

public interface IChatHubClient
{
    void AddNewMessageToPage(string name,string message);
}

このインターフェースをHubクラスの型引数に指定します。(Hub<IChatHubClient>)
インテリセンスも効きますし、定義していないメソッドを書けばエラーになります。 f:id:pierre3:20150422221546p:plain

HubProxyを自動生成する

サーバー側のコードが固まったところで、クライアント側に視点を戻します。
前回の記事では、HubProxyの型定義を毎回書かなくてはならないのは、面倒だと書きました。

  • サーバー側とクライアント側で、同じ内容を2度定義しなければならない
  • クライアント側の型定義を記述する際に、誤りがあってもコンパイラによるチェックができない

そこで、サーバー側のコードから、HubProxyの型定義を自動生成できないだろうか?

サーバー側のこのコードから

public class ChatHub : Hub<IChatHubClient>
{
    public void Send(string name,string message)
    {
        Clients.All.AddNewMessageToPage(name,message);
    }
}

public interface IChatHubClient
{
    void AddNewMessageToPage(string name,string message);
}

クライアント側のこのコードを生成したい

//chatHubProxy.d.ts
/// <reference path="signalr/signalr.d.ts" />
/// <reference path="jquery/jquery.d.ts" />

interface SignalR {
    chatHub : ChatHub;
}

interface ChatHub {
    server : ChatHubServer;
    client : ChatHubClient;
}

interface ChatHubServer {
    send(name : string, message : string) : JQueryPromise<void>;
}

interface ChatHubClient
{
    addNewMessageToPage : (name : string, message : string) => void;
}

そう思って探したら、ありました!

Hubs.tt

Hubs.tt というT4 テキストテンプレートです。

ソースコードが Gist: https://gist.github.com/htuomola/7565357 にあります。

詳細は、以下の記事をご参照ください。

上で書いた、サンプルコードの変換はもちろん、メソッドの引数にオブジェクトを使用した場合は、その型定義もしてくれます。

class ChatHub: Hub 
{
    public void SendTo(ChatMessage message)
    {
        //...略
    }
}
public class ChatMessage
{
    public string Name { get; set; }
    public string Message { get; set; }
    public string ConnectionId { get; set; }
}

SendTo() の引数に使用している ChatMessage クラスの定義もちゃんと変換されています。

/**
  * Data contract for SignalR_TypeScript_BasicChat.hubs.ChatMessage
  */
interface ChatMessage {
    Name : string;
    Message : string;
    ConnectionId : string;
}
interface ChatHubServer {
  /** 
    * Sends a "sendTo" message to the ChatHub hub.
    * Contract Documentation: ---
    * @param message {ChatMessage} 
    * @return {JQueryPromise of void}
    */
    sendTo(message : ChatMessage) : JQueryPromise<void>;
}

しかも、コメントまで生成してくれます。
さらに、C#側でドキュメントコメントを書いて、コメントのXMLを出力するようにしておけば、そのコメントまで反映してくれるようです。素晴らしい!

Hubs.tt の導入

Web Essentials 2013 for Update 4 extension がインストールされていれば、ソリューションエクスプローラから追加できます。

f:id:pierre3:20150425135141p:plain

追加後、場合によっては、アセンブリの参照を自分の環境に合わせて書き換える必要がありますが、基本的にはこの1ファイルを追加するだけでOK!

Hubクラスを増やしたり、メソッド名を変更したりする場合でも、サーバー側のC#のコードを変更するだけで済むようになりました。