ASP.NET Core で gRPC と MVC の両方に対応できるか試した

はじめに

ASP.NET Core で gRPC と gRPC-Web を 1 つのアプリでホストできることは確認した。

tnakamura.hatenablog.com

gRPC と MVC はどうだろうか?試してみた。

gRPC サーバーに Web API を追加

GreeterController

using Microsoft.AspNetCore.Mvc;

namespace HelloGrpcWeb.Controllers
{
    [ApiController]
    [Route("api/greeter")]
    public class GreeterController : ControllerBase
    {
        [HttpGet("hello/{name}")]
        public IActionResult Hello(string name)
        {
            return Ok(new
            {
                Message = $"Hello {name}",
            });
        }
    }
}

Startup

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

namespace HelloGrpcWeb
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddGrpc();

            // コントローラーを登録
            services.AddControllers();
        }

        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            app.UseRouting();

            app.UseGrpcWeb();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapGrpcService<GreeterService>()
                    .EnableGrpcWeb();

                // コントローラーのルーティングを登録
                endpoints.MapControllers();

                endpoints.MapGet("/", async context =>
                {
                    await context.Response.WriteAsync("Communication with gRPC endpoints must be made through a gRPC client. To learn how to create a client, visit: https://go.microsoft.com/fwlink/?linkid=2086909");
                });
            });
        }
    }
}

クライアントに Web API を呼び出すサンプルを追加

gRPC と同じアドレスで Web API もホストする場合、HTTP/2 を使う必要がある。でないと、サーバー側で「HTTP/2 over TLS was not negotiated on an HTTP/2-only endpoint.」っていうエラーになり、リクエスト送信に失敗する。

using System;
using System.Net.Http;
using System.Threading.Tasks;
using Grpc.Net.Client;
using Grpc.Net.Client.Web;

namespace HelloGrpcWeb.Client
{
    class Program
    {
        const string Address = "https://localhost:5001";

        static async Task Main(string[] args)
        {
            await Grpc();
            await GrpcWeb();
            await Mvc();

            Console.ReadLine();
        }

        static async Task Grpc()
        {
            var channel = GrpcChannel.ForAddress(Address);

            var client = new Greeter.GreeterClient(channel);

            var reply = await client.SayHelloAsync(new HelloRequest
            {
                FirstName = "Takefusa",
                LastName = "Kubo",
            });

            Console.WriteLine($"gRPC:{reply.Message}");
        }

        static async Task GrpcWeb()
        {
            var channel = GrpcChannel.ForAddress(Address, new GrpcChannelOptions
            {
                HttpHandler = new GrpcWebHandler(
                    new HttpClientHandler()),
            });

            var client = new Greeter.GreeterClient(channel);

            var reply = await client.SayHelloAsync(new HelloRequest
            {
                FirstName = "Takehiro",
                LastName = "Tomiyasu",
            });

            Console.WriteLine($"gRPC Web:{reply.Message}");
        }

        static async Task Mvc()
        {
            var client = new HttpClient
            {
                BaseAddress = new Uri(Address),
                // gRPC と同じアドレスを使う場合は
                // HTTP/2 を使う必要がある
                DefaultRequestVersion = new Version(2, 0),
            };

            var response = await client.GetAsync(
                "/api/greeter/hello/Douan");

            var body = await response.Content.ReadAsStringAsync();

            Console.WriteLine($"MVC:{body}");
        }
    }
}

実行結果

HTTP/2 ではあるが、Web API も呼び出せた。

f:id:griefworker:20210607114736p:plain

おわりに

gRPC と gRPC-Web、それに MVC を 1 つのアプリケーションでホストすることはできた。ただ、詰め込んだのはいいものの、MVC で実装した Web API を呼び出すには HTTP/2 が必要、というのが問題になるかもしれないな。