銀の光と碧い空

クラウドなインフラとC#なアプリ開発の狭間にいるエンジニアの日々

ASP.NET Core on Linux でも SignalR が使いたい

ASP.NET Core Advent Calendar一日目です。内容は直前で変えたのですが、Sessionまわりの話は空いている日のネタとして投下したいと思います。

qiita.com

SignalR もASP.NET Core対応が進んでいます。

github.com

ので現時点で動作するアプリをASP.NET Coreで作れるは作れるのですが、必要なライブラリは正式版としてnuget.orgには公開されてはいません。というところが今回のお話です。

ASP.NET Coreの開発用のライブラリはmygetで配布されているようで、今回はこのチャンネルを使います。

Gallery - MyGet - Hosting your NuGet, Npm, Bower and Vsix packages

SignalRでフィルタすると開発が続いているのがわかります。これを参照するにはproject.jsonと同じ階層にNuGet.configを配置します*1

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <packageSources>
    <!--To inherit the global NuGet package sources remove the <clear /> line below -->
    <clear />
    <add key="api.nuget.org" value="https://api.nuget.org/v3/index.json" />
    <add key="aspnetcore-dev.dotnet.myget.org" value="https://dotnet.myget.org/F/aspnetcore-dev/api/v3/index.json" />
  </packageSources>
</configuration>

project.jsonはこんな感じになります。これは.NET Core 1.1を使う場合の設定になります。

{
  "name": "SignalRDemo",
  "version": "1.1.0-*",
  "dependencies": {
    "Microsoft.AspNetCore.Diagnostics": "1.1.0-*",
    "Microsoft.AspNetCore.Server.IISIntegration": "1.1.0-*",
    "Microsoft.AspNetCore.Server.Kestrel": "1.1.0-*",
    "Microsoft.AspNetCore.StaticFiles": "1.1.0-*",
    "Microsoft.AspNetCore.SignalR.Server": "0.2.0-*",
    "Microsoft.AspNetCore.WebSockets": "0.2.0-*",
    "Microsoft.Extensions.Logging.Console": "1.1.0-*",
    "Microsoft.Extensions.Configuration.EnvironmentVariables": "1.1.0-*",
    "Microsoft.Extensions.Configuration.FileExtensions": "1.1.0-*",
    "Microsoft.Extensions.Configuration.Json": "1.1.0-*",
    "Microsoft.Extensions.Configuration.CommandLine": "1.1.0-*"
  },

  "tools": {
    "Microsoft.AspNetCore.Server.IISIntegration.Tools": "1.0.0-preview3-final"
  },

  "frameworks": {
    "netcoreapp1.1": {
      "dependencies": {
        "Microsoft.NETCore.App": {
          "version": "1.1.0-*",
          "type": "platform"
        }
      }
    }
  },

  "buildOptions": {
    "emitEntryPoint": true,
    "preserveCompilationContext": true
  },

  "runtimeOptions": {
    "configProperties": {
      "System.GC.Server": true
    }
  },

  "publishOptions": {
    "include": [
      "wwwroot",
      "web.config"
    ]
  },

  "scripts": {
    "postpublish": [ "dotnet publish-iis --publish-folder %publish:OutputPath% --framework %publish:FullTargetFramework%" ]
  },

  "tooling": {
    "defaultNamespace": "RHTE2016SignalRDemo"
  }
}

実はこのサンプルアプリは10月のおわりごろにデモしようとしたのですが、このタイミングではLinux上では.NET Core 1.0では動かなくなってしまって、.NET Core 1.1のPreviewでしか動きませんでした。その辺の経緯はこのあたりのIssueに残っています。

github.com

github.com

ですが今確認したところ、.NET Core 1.0 (netcoreapp1.0)で動きました。

  "frameworks": {
    "netcoreapp1.0": {
      "dependencies": {
        "Microsoft.NETCore.App": {
          "version": "1.0.0-*",
          "type": "platform"
        }
      }
    }
  }

というわけで、ここまで記述したらあとはSignalRアプリを書くだけです。Startupクラスはこんな感じに設定します。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;

namespace RHTE2016SignalRDemo
{
    public class Startup
    {
        // This method gets called by the runtime. Use this method to add services to the container.
        // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddSignalR(options =>
            {
                options.Hubs.EnableDetailedErrors = true;
            });
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
        {
            loggerFactory.AddConsole();

            app.UseFileServer();

            app.UseWebSockets();
            app.UseSignalR();

            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

        }
    }
}

Hubはこんな感じ。counterをSignalRでインクリメントして全クライアントにブロードキャストするだけのHub。

using System.Collections.Generic;
using Microsoft.AspNetCore.SignalR;
using Microsoft.AspNetCore.SignalR.Hubs;

namespace RHTE2016SignalRDemo.Hubs
{
    [HubName("message")]
    public class MessageHub : Hub
    {


        public static int Count { get; set; } = 0;
        public void Send()
        {
            Clients.All.broadcast(++Count);
        }

        public int GetCurrent()
        {
            return Count;
        }
    }
}

Webページは今回はシンプルに静的htmlのみで書いています。

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title>SignalR</title>  
</head>
<body>
    <h1>Hello!</h1>
    <div id="count"></div>
    <p><button type="button" id="goodbutton">GOOD!</button></p>
    <script src="/Scripts/jquery-1.8.2.js"></script>
    <script src="//ajax.aspnetcdn.com/ajax/signalr/jquery.signalr-2.0.3.js"></script>
    <script src="/signalr/js"></script>
    <script type="text/javascript">
        $(function () {
            var message = $.connection.message;
            var counter = $('#count');
            
            $.connection.hub.start(/*{ transport: activeTransport },*/ function () {
                message.server.getCurrent(function(value) {
                    counter.text(value);
                });
                $('#goodbutton').click(function () {
                    message.server.send().done(function (values) {
                    });
                });
            });
            
            message.client.broadcast = function (value) {
                counter.text(value);
            }; 
        });
    </script>
</body>
</html>

というわけで、正式版ではなく開発版のライブラリにはなりますが、ASP.NET CoreでもSignalRが使えました。Redisを使う版は改めて調査したいと思います。

*1:プロジェクト単位で参照するNuGetを追加する場合