こんにちは、トイロジックでツール業務を担当しているプログラマーのIです。主にプロジェクトで使用するGUI/CUIツールやDCCツールの作成・整備を行っています。

本記事ではASP.NET Coreというフレームワークを使用してローカルのHTTPサーバーを立てる方法をご紹介しようと思います。

皆さんも様々なカンファレンスなどでリンクからツールを起動したり、ゲームを起動して特定のレベルに入ったりなどを行っている事例を拝見されたことがあるのではないでしょうか?社内サーバーが無いから、うちでは難しいのでは?と思っている方もいらっしゃるかもしれませんが、実は社内のローカル環境内であれば社内サーバーが無くても実現することは可能です。

人によってはもう知ってるよ!って方もいらっしゃるかもしれませんが少しお付き合いください。本記事は Visual Studio 2022 を使用し、.NET 8 に含まれる「ASP.NET Core」を使用します。

 

ASP.NET Coreってなに?

ASP.NET Core(※1)は簡単に言うとWeb アプリ、Web サービスなどを構築するためのオープンソースフレームワークです。

従来であればHTTPサーバーを立てるには .NET が提供している HttpListener(※2)などを使用してサーバーを立て、リクエストを独自で処理する必要がありました。

ですが、ASP.NET Coreの登場によりかなり簡単にHTTPサーバーを立てることができるようになりました。

Visual Studio Installerから「ASP.NET と Web 開発」にチェックを入れてインストールすることで簡単にインストールが可能です。

 

HTTPサーバーを立ててみよう

一番簡単な方法は、Visual Studioの新規作成から「ASP.NET Core Web API」でプロジェクトを追加することです。追加すると以下のような構成のプロジェクトが追加されるのではないでしょうか。


 
実行してみるとこのようなWebページが表示されます。

このWebページが表示されている間はHTTPサーバーとして機能します。ブラウザで別タブを追加して「localhost:7170/WeatherForecast」を実行してみましょう。すると以下のような表示が行われるのではないでしょうか。

これは何が表示されているのか。それは「Controllers/WeatherForecastController.cs」を見てみると分かります。ASP.NET CoreはMVC(Model, View, Controller)パターンを使用したフレームワークです。

この中のControllerはユーザーの要求を処理し、適切なビューを選択して必要なモデルデータを提供します。「WeatherForecastController」はこのControllerにあたるクラスです。エンドポイントごとにControllerを作成することで、簡単にリクエストを処理することが可能となっています。

クラスの中身を見てみると「public IEnumerable<WeatherForecast> Get()」内で「WeatherForecast」というクラスのデータ一覧を作成して返しているのが分かるかと思います。

[HttpGet(Name = "GetWeatherForecast")]
public IEnumerable<WeatherForecast> Get()
{
    return Enumerable.Range(1, 5).Select(index => new WeatherForecast
    {
        Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
        TemperatureC = Random.Shared.Next(-20, 55),
        Summary = Summaries[Random.Shared.Next(Summaries.Length)]
    })
    .ToArray();
}

そう。これが先ほどのブラウザ上に表示された内容のデータですね。

HTTPサーバーを立ててリクエストを処理するために必要なクラスは

ファイル名 内容
Program.cs Webサービスの初期化
WeatherForecastController.cs リクエストを処理するコントローラ

の2つだけなんです。簡単じゃないですか?

「WeatherForecastController」の中身を少し見ていきましょう。まず、クラスに属性が付いているかと思います。

[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
属性名 内容
ApiControllerAttrribute HTTP API 応答の処理に型とすべての派生型が使用されることを示します
RouteAttribute コントローラのルートを指定するために使用されます
※ C#の仕様としてAttribute部分は省略可能

「ApiControllerAttribute」はコントローラだと示すための属性ですね。「RouteAttribute」ではこのコントローラのルートを指定しています。「 [controller] 」が指定されていますが、これはコントローラクラスの名前(ControllerというSuffixは除く)を示します。先ほどの「public IEnumerable<WeatherForecast> Get()」メソッドにも属性が付いていますね。

属性名 内容
HttpGetAttribute HTTP GET メソッドをサポートするアクションを特定します

これが指定されていることで、GETリクエストが要求された場合にこのメソッドが呼ばれるわけですね。これらを見てみると先ほどの「localhost:7170/WeatherForecast」はGETメソッドでリクエストされているため、「WeatherForecastController」コントローラの「Get」メソッドが呼ばれることが分かるかと思います。

本当に簡単です。

 

Webページを開かないHTTPサーバーを立てるには?

さて、先ほど紹介した内容だとHTTPサーバーを立てるためにWebページを開く必要がありますね。しかし、開発で使用するためのローカルHTTPサーバーを使用するために常時Webページを開いたままにするというのは許容できない問題ですね。

心配いりません。これに関してもWebページを開かないでHTTPサーバーを立てる方法があるんです。今回は分かりやすく「コンソール アプリ」で新規プロジェクトを追加しましょう。

追加されたら、プロジェクトファイルに以下を追加しましょう。

<ItemGroup>
  <FrameworkReference Include="Microsoft.AspNetCore.App" />
</ItemGroup>
<Project Sdk="Microsoft.NET.Sdk">
 
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net8.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>
 
  <ItemGroup>
    <FrameworkReference Include="Microsoft.AspNetCore.App" />
  </ItemGroup>
 
</Project>

これは、ASP.NET Coreフレームワークを使用するために必要です。

本来、ASP.NET Core用のプロジェクトを追加した場合、プロジェクトファイルの「Sdk」が「Microsoft.NET.Sdk.Web」になります。しかし、コンソール アプリなどは「Microsoft.NET.Sdk」で通常の.NETアプリケーションとして作成されます。

そのため、上記の設定が必要になってくるわけです。

では、プロジェクトに「CommonStartup.cs」というクラスファイルを追加しておいてから、「Program.cs」ファイルでサービスの初期化を行っていきましょう。
 

 
前回のプロジェクトでは「WebApplication」というクラスを使用してビルダーを作成していましたが、今回は「Host」(※1)というクラスを使用してビルダーを作成していきましょう。

以下のコードを「Program.cs」に追加しましょう。

using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Hosting;
using WebApiConsole;
 
var builder = Host.CreateDefaultBuilder(args)
                  .ConfigureWebHostDefaults(webBuilder =>
                  {
                      webBuilder.UseStartup<CommonStartup>()
                                .UseUrls("http://localhost:8000/");
                  });
 
var host = builder.Build();
 
await host.RunAsync();

これでOKです。

UseUrls で指定しているポート番号はそれぞれのプロジェクトで適切なポート番号に変えてください。
以下などを参考に空いているポート番号を使用するのがおすすめです。
TCPやUDPにおけるポート番号の一覧 – Wikipedia

では、「CommonStartup.cs」の方へ移動し、クラスの中身を追加していきましょう。


using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
 
namespace WebApiConsole
{
    public sealed class CommonStartup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            var assembly = typeof(CommonStartup).Assembly;
 
            services.AddControllers()
                    .AddApplicationPart(assembly);
        }
 
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
 
            app.UseRouting();
            app.UseAuthorization();
            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
            });
        }
    }
}

このクラスはいわば設定を共通化しておくようなクラスです。「Program.cs」で記載したwebBuilder.UseStartup<CommonStartup>()の部分で、このクラスをスタートアップとしてサービスに登録しています。

このクラス内には以下の二つのメソッドを定義するルールがあります。

メソッド名 内容
public void ConfigureServices(IServiceCollection services) DIへのサービスの設定
public void Configure(IApplicationBuilder app, IWebHostEnvironment env) HTTPリクエストパイプラインの構成

一つ目のメソッドでは、DIへコントローラの追加を行っています。

基本的には「Microsoft.NET.Sdk.Web」プロジェクトの場合、「AddControllers()」メソッドを呼び出すことで、アセンブリに存在するコントローラをすべて列挙してくれるのですが、「Microsoft.NET.Sdk」プロジェクトではそれが機能しません。

そのため、以下のようにコントローラが存在するアセンブリを指定してあげる必要があります。

var assembly = typeof(CommonStartup).Assembly;
services.AddControllers()
        .AddApplicationPart(assembly);

これにより、「CommonStartup」クラスが所属するアセンブリ内のコントローラが登録されることになります。

二つ目のメソッドでは、HTTPリクエストのパイプラインを構成します。

メソッド名 内容
UseRouting() このメソッドにより、Routingを有効にします。これが呼ばれていない状態で、UseEndpoints()が呼ばれるとエラーになります。
UseAuthorization() このメソッドでは、承認機能を有効にします。詳しい説明は省きますが、とりあえず今はこれを追加しておくとだけ覚えておきましょう。
AuthorizationAppBuilderExtensions.UseAuthorization メソッド (Microsoft.AspNetCore.Builder) | Microsoft Learn
UseEndpoints() このメソッドでは、エンドポイントの実行がミドルウェアパイプラインに追加されます。内部でendpoints.MapControllers();を呼び出すことによって、属性でRoutingされたコントローラがマップされます。これがあることで、コントロールで指定したルートごとにコントローラが呼び分けられるわけですね。

さて、ここまで来たら後はコントローラを追加するだけです。以下のように「TestController.cs」を追加しましょう。


using Microsoft.AspNetCore.Mvc;
 
namespace WebApiConsole.Controllers
{
    [ApiController]
    [Route("[controller]")]
    public sealed class TestController : ControllerBase
    {
        [HttpGet]
        public IActionResult Get()
        {
            return Ok("Hello from TestController!");
        }
    }
}

このプロジェクトを状態で実行して、「http://localhost:8000/Test」をブラウザで実行してみると以下のように「Get()」メソッドで返した文字列が表示されているのが分かるかと思います。

リンクを実行することで何か処理を行いたい場合、基本的にはGETメソッドリクエストを受け取ることになると思います。ですので、「HttpGetAttribute」属性を付けたメソッド内で行いたい処理を追加しましょう。

ちなみに、メソッドはクエリを受け取ることもできます。例えば、以下のように「Get()」メソッドに引数を追加してみましょう。


[HttpGet]
public IActionResult Get([FromQuery] string? name)
{
    return Ok($"Hello \"{name}\" from TestController!");
}

 
そしてまたプロジェクトを実行して、「http://localhost:8000/Test?name=Toylo」をブラウザで実行してみると、以下のようにクエリパラメータが受け渡されていることが分かるかと思います。

リクエストにパラメータが必要な場合は、このようにしてパラメータを受け取るようにしましょう。例えば、以下のようにクエリパラメータクラスを作成して、それを引数に受け取るということも可能です。この場合、クラス内のプロパティがパラメータとして使用されます。


[ApiController]
[Route("[controller]")]
public sealed class TestController : ControllerBase
{
    [HttpGet]
    public IActionResult Get([FromQuery] QueryParam param)
    {
        return Ok($"Hello \"{param.Name}\" from TestController!");
    }
}
 
public sealed class QueryParam
{
    public string? Name { get; set; }
}

リクエストは前回と同じ「http://localhost:8000/Test?name=Toylo」です。

パラメータが複数存在する場合は、このようにクラスにしておくことで引数の数を減らすことができます。覚えておくと便利です。

 

最後に

本記事ではASP.NET Coreを使用したHTTPサーバーの立て方をご紹介させていただきました。

読んでいただければわかるかと思いますが、ローカルのHTTPサーバーを立てるだけであれば本当に簡単に作ることができます。

コンソールアプリでHTTPサーバーを実装する方法もお伝えしましたが、ローカルの常駐サーバーにする場合、WPFやWinFormsなどでコンソールアプリと同じようにASP.NET Coreを導入し、タスクトレイへ格納するようにすれば、コンソールが表示され続けるということもなくなります。

本記事が少しでも皆様の開発の助けになれば幸いです。
 

著者紹介 I
2015年にトイロジックに新卒入社。プログラマーとして複数プロジェクトでDCCツールの作成、UI、システム周りを経験し、現在はエンジンチームでツール業務を担当しています。