アプリケーションに手軽にWebサーバを組み込む(.Net)

※ (2022年追記) 現在では普通に ASP.NET を使った方が良いと思います

.NetアプリケーションにちょっとしたWeb APIを組み込みたい場合にお手軽に実装できたらなあと考えていましたが、EmbedIOというパッケージが良い感じでした。

unosquare/embedio: A tiny, cross-platform, module based web server for .NET

今回はコンソールアプリケーションに社員情報をJsonで登録/取得(Get/Post)する WebApi を実装します。

※ 実行環境として .Net Framework4.7.1 で動かしていますが、EmbedIO 自体は .Net Core にも対応しています。

EmbedIO のインストール

Visual Studioを管理者権限で起動しコンソールアプリケーションのプロジェクトを作成したら、パッケージマネージャから EmbedIO をインストールします。

PM> Install-Package EmbedIO

Employeeクラス作成

社員情報を表すEmployeeクラスを作成します。このクラスは社員情報の保持とApiのインターフェースに使います。

各プロパティの属性に JsonProperty を指定します。これは Jsonシリアライズ/デシリアライズ する際の項目名を指定します(これは Newtonsoft.Json と同じなので使った事があれば馴染み深い記法だと思います)。

Employee.cs

using Unosquare.Swan.Attributes;

namespace ConsoleApp1
{
    public class Employee
    {
        [JsonProperty("id")]
        public int Id { get; set; }

        [JsonProperty("name")]
        public string Name { get; set; }

        [JsonProperty("age")]
        public int Age { get; set; }

    }
}

EmployeeController クラス作成

Httpリクエストとレスポンスを処理するコントローラを作成します。MVCフレームワークを使ったことがあれば直感的に理解できるのではと思います。

EmployeeController.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
using Unosquare.Labs.EmbedIO;
using Unosquare.Labs.EmbedIO.Constants;
using Unosquare.Labs.EmbedIO.Modules;
using Unosquare.Swan;

namespace ConsoleApp1
{
    public class EmployeeController : WebApiController
    {
        public static void Setup(WebServer server)
        {
            server.RegisterModule(new WebApiModule());
            server.Module<WebApiModule>().RegisterController<EmployeeController>();
        }

        public static readonly List<Employee> Employees = new List<Employee>();

        [WebApiHandler(HttpVerbs.Get, "/api/employee/{id}")]
        public async Task<bool> GetEmployee(WebServer server, HttpListenerContext context,
            int id)
        {
            var employee = Employees.Where(v => v.Id == id);
            if (employee.Any())
            {
                context.Response.StatusCode = (int) HttpStatusCode.OK;
                return await context.JsonResponseAsync(employee); // Jsonにシリアライズして返す
            }
            else
            {
                // 対象のIdが存在しない場合は404を返す
                context.Response.StatusCode = (int) HttpStatusCode.NotFound;
                return await context.StringResponseAsync("NotFound");
            }
        }


        [WebApiHandler(HttpVerbs.Post, "/api/employee")]
        public async Task<bool> PostEmployee(WebServer server, HttpListenerContext context)
        {
            try
            {
                var employee = context.ParseJson<Employee>(); // postで受け取ったJsonのデシリアライズ
                Employees.Add(employee);

                $"『Id:{employee.Id}, name:{employee.Name}, age{employee.Age}』 が追加されました".Trace();

                context.Response.StatusCode = (int) HttpStatusCode.Created;
                return await context.StringResponseAsync(string.Empty);
            }
            catch (Exception ex)
            {
                return await context.JsonExceptionResponseAsync(ex);
            }
        }
    }
}

Webサーバの起動

エントリポイントにWebサーバの起動の記述を追記します。下記コードでは 9000 ポートを利用するように設定をしています。

Program.cs

using System.Threading.Tasks;
using Unosquare.Labs.EmbedIO;
using Unosquare.Labs.EmbedIO.Constants;

namespace ConsoleApp1
{
    internal class Program
    {
        private  static async Task Main(string[] args)
        {
            var url = $@"http://localhost:9000";    // リモートアクセスを許可する場合は http://*:9000 

            using (var server = new WebServer(url, RoutingStrategy.Regex))
            {
                EmployeeController.Setup(server);
                await server.RunAsync();
            }
        }
    }
}

これで実装は完了です。

動かしてみる

今回はコンソールアプリケーションなので、デバッグモードで実行すると次のようなコンソールが起動します(正常に起動しない場合は Visual Studio を管理者権限で起動していない可能性があるので、管理者権限で起動しなおします)。

IDを100 で登録してみます。

上記で登録したID(100)でデータを取得してみます。

無事取得できました。

Unosquare.Swan について

本題から逸れますが、コントローラの cs $"『Id:{employee.Id}, name:{employee.Name}, age{employee.Age}』 が追加されました".Trace(); (.Trace())部分について補足です。

これは EmbedIO インストール時に依存ライブラリとして一緒にインストールされる Unosquare.Swan.Lite に stringの拡張メソッドとして定義されています。 このメソッドを使い分けることでコンソールに表示させるメッセージタイプを制御できます。メッセージの種類は swan/Terminal.Enums.cs at master · unosquare/swanLogMessageType として定義されています。

コンソールに出力するメッセージタイプを指定したい場合以下のように指定します。

// エラーと警告のみ表示
Terminal.Settings.DisplayLoggingMessageType =
                | LogMessageType.Error
                | LogMessageType.Warning;