MEF(DirectoryCatalog) を使った OWIN Self Host してみた

ちょっと前に twitter .NET の軽量サーバーって無いの?


てのを見かけて、OWIN Self Host と MEF(DirectoryCatalog) で dll 置くだけで deploy みたいなのが出来るかなーと思ったので試してみました。

MEF はすっかり忘れてたのでこちらをコピペ*1
MEFでディレクトリカタログを追いかける(C# Advent Calender jp:2010 12/02) | kazuk は null に触れてしまった
OWIN Self Host はこちらを参考に
OWIN - Open Web Interface for .NET とは何か? - しばやん雑記

こんな interface を用意して

using Owin;
using System;

namespace OwinSelfHostCommon {
  public interface IOwinApp {
    string Url { get; }
    Action<IAppBuilder> Startup { get; }
  }
}

メインはこんなの。ほぼコピペ。。

using Microsoft.Owin.Hosting;
using OwinSelfHostCommon;
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
using System.IO;

namespace OwinSelfHostSample {
  class Program {
    static void Main(string[] args) {
      new Program().Run();
    }

    [ImportMany(AllowRecomposition = true)]
    private IEnumerable<Lazy<IOwinApp>> _parts;

    private CompositionContainer _container;
    private FileSystemWatcher _fsWatcher;
    private DirectoryCatalog _directoryCatalog;
    private readonly Dictionary<Type, IDisposable> _startedApps = new Dictionary<Type, IDisposable>();

    public void Run() {
      var aggregateCatalog = new AggregateCatalog();
      aggregateCatalog.Catalogs.Add(new AssemblyCatalog(typeof(Program).Assembly));
      // ReSharper disable AssignNullToNotNullAttribute
      string extentionPath = Path.Combine(
          Path.GetDirectoryName(typeof(Program).Assembly.Location),
          "../../addin");
      // ReSharper restore AssignNullToNotNullAttribute
      _fsWatcher = new FileSystemWatcher(extentionPath);
      _fsWatcher.Changed += FsWatcherChanged;
      _fsWatcher.EnableRaisingEvents = true;

      _directoryCatalog = new DirectoryCatalog(extentionPath);
      _directoryCatalog.Changed += CatalogChanged;
      aggregateCatalog.Catalogs.Add(_directoryCatalog);
      aggregateCatalog.Changed += CatalogChanged;
      _container = new CompositionContainer(aggregateCatalog);
      _container.ComposeParts(this);

      foreach (var part in _parts) {
        StartOwinApp(part.Value);
      }

      while (Console.ReadLine() != "exit") {
        foreach (var app in _startedApps.Values) {
          app.Dispose();
        }
      }
    }

    void FsWatcherChanged(object sender, FileSystemEventArgs e) {
      Console.WriteLine("extentions directory changed");
      _directoryCatalog.Refresh();
    }

    void CatalogChanged(object sender, ComposablePartCatalogChangeEventArgs e) {
      Console.WriteLine("catalog changed " + sender.GetType().Name);

      _container.ComposeParts(this);

      foreach (var part in _parts) {
        StartOwinApp(part.Value);
      }
    }

    void StartOwinApp(IOwinApp app) {
      if (!_startedApps.ContainsKey(app.GetType())) {
        var startedApp = WebApp.Start(new StartOptions(app.Url), app.Startup);
        _startedApps.Add(app.GetType(), startedApp);
      }
    }
  }
}

IOwinApp の実装クラスの型で、OWIN で実行済みか判断してます。真面目にやるなら、URL のバッティングとかも見る必要あるかな?

IOwinApp を実装したクラスはこんなの。これもコピペ。。

using Owin;
using OwinSelfHostCommon;
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.IO;
using System.Threading.Tasks;

namespace HelloOwin {
  [Export(typeof(IOwinApp))]
  public class OwinApp : IOwinApp {

    public string Url {
      get { return "http://localhost:8080/"; }
    }

    public Action<IAppBuilder> Startup {
      get { return (app => app.Use<Handler>()); }
    }
  }

  public class Handler {
    public Handler(Func<IDictionary<string, object>, Task> next) { }
    public Task Invoke(IDictionary<string, object> environment) {
      environment["owin.ResponseStatusCode"] = 200;
      using (var writer = new StreamWriter(environment["owin.ResponseBody"] as Stream)) {
        return writer.WriteAsync("Hello, OWIN");
      }
    }
  }
}

メインを実行しコンソールが起動したら、IOwinApp を実装したクラスを含んだ dll を 所定のフォルダに配置すると FileSystemWatcher で検知し、Owin でホストします。
課題は、コンソールを止めないと dll の削除や上書きが出来ない事。

という訳で、出来たけど実用するかと言われると…って感じ。

これを全部 PowerShell に出来たらもうちょい面白いかな~。
という訳で、ここらへん見てみる。
PowerShell scripting in MEF-based applications - Ryan Tremblay's Blog - Site Home - MSDN Blogs
How to run PowerShell from node.js

*1:何のアセンブリ参照するのかも忘れてた!