studio Odyssey




スタジオ日誌

日誌的なもの

2017.11.04

ASP.net Core 2.0 で、Web API とClient Proxy

Written by
しゃちょ
Category
C#
プログラム

 ASP.net Core 2.0で、swaggerつかって、クライアント側で、REST APIを使って、Client Proxyを作って、Windows Forms でいろいろする。

 いや、まぁ、WPFでもいいですし、UWPでもいいんですけどね。あと、今回ご紹介する方法は、swagger.json のURLをAPI毎に任意にわけるとか、そんな感じの事もします。けども、swaggerの使い方とかは触れないので、swaggerの使い方を知りたい人は回れ右で、他のもっと親切なサイト様へどうぞ。

 でで。

 やりたいことしては、REST APIを使って、Client Proxyしたいので、まずは、ASP.net Core 2.0 で、Web APIサイトを作りますですます。あ、Visual Studio 2017です。15.4.2です。当然、.net Coreあたりの開発はインストールしておいてくださいね。Azure系のも必要です。(REST API Proxyを作るのに使うんだっけな)まぁ、最新にしておけって事ですよ。あ、C#です。

 さて、プロジェクト作りまーす。説明しません。新規プロジェクト-C#-Web-ASP.NET Core Webアプリケーションで、Web APIで作ります。Core 2.0 にするんだよ。名前は何でもいいよ。WebApiSampleにしたけど。

 画像、用意してたけど、張ってたらなんか、タブレットだとうまくいかない。まぁ、文字でいいだろ、文字で。こんな記事。

 で、なんだっけ。あ、新規作成したんだ。で、作ったら、ソリューションを右クリックして、NeGetパッケージの管理をクリック。NuGetでたら、参照で、Swashbuckle.AspNetCoreといれて、検索。AspNetCoreだよ。間違えるなー。

 で、Swashbuckleを使えるようにするんだけど、XMLドキュメントはけるようにしておく。あとでXMLドキュメントの設定もする。プロジェクトのプロパティから、ビルドにいって、出力の、XMLドキュメントにチェックね。

 で、Startup.cs開いて、Swagger使えるようにする。あ、ValuesControllerあるよね? ちゃんと。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Swashbuckle.AspNetCore.Swagger;
using System.IO;
using Microsoft.Extensions.PlatformAbstractions;

namespace WebApiSample
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc();

            //Swaggerを登録
            services.AddSwaggerGen(options =>
            {
                options.SwaggerDoc("v1", new Info
                {
                    Title = "WebTestAPI",
                    Version = "v1"
                });
                
                // Set the comments path for the Swagger JSON and UI.
                var basePath = PlatformServices.Default.Application.ApplicationBasePath;
                var xmlPath = Path.Combine(basePath, "WebApiSample.xml"); //ここ、出力されるxmlコメントのファイル名ね
                options.IncludeXmlComments(xmlPath);
            });
        }

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

            app.UseDefaultFiles();
            app.UseStaticFiles();
            app.UseMvc();

            // Swagger UI を使う
            app.UseSwagger();

            app.UseSwaggerUI(options =>
            {
                options.SwaggerEndpoint("/swagger/v1/swagger.json", "WebTestAPI");
            });
        }
    }
}

 まぁ、何が足してあって何が足してないかって、並べて見てみて。でで、これで実行すれば、とりあえずswagger uiが見られるんだけど、あとあと面倒なので、プロジェクトのプロパティのデバッグの、ブラウザーの起動のところ、swagger/にしておくと後で楽。

 起動した? Swagger UI見られた? OK、次に行こう。見られなかった人は、別の、もっと親切なサイトで設定しよう。

 クライアントを作ります。クライアントは、.NET Framework 4.6.2 にします。というか、ここはCoreじゃないです。Coreはできないんじゃないかな。とりあえず、SampleClientとかで適当に作ります。

 作ったら、プロジェクトを右クリック。追加-REST APIクライアントをクリックします。出てない時は、Azure開発が入っていないかも。

 出てくる画面に、Swagger URLを入れます。ここに入れるURLは、先のSwagger UIに出てきたURLです。画面の中にあるやつね。ファイル名が、swagger.jsonのやつ。swagger.josnまで、全部いれます。で、クライアント名前空間は、ルートの名前空間になっちゃうんですけど、クライアントプロキシ名にもされるので、入れます。WebTestAPIとしました。

 追加すると、なんかわしゃわしゃ、新しい物が追加されます。うまくいかなかったら、Webサイトが起動しているか、確認。IIS Expressだと思いますんで、動いているか確認。動かし方がわからんて? wwwrootを右クリックして、ブラウザーで表示ってやれば動くよ。たぶん。

 でで、なんかNuGetがRest Clientとか、Json.netとか足すんですが、バージョンが古いと思うので、NeGetで最新にしてください。最新でないと、いろいろめんどくさい。

 今回は認証機能ないので、認証しないでつなげられるように、クラスを1つ追加。普通に追加。クラス名はAnonymousCredentialで、コードは以下。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Rest;

namespace SampleClient
{
    public class AnonymousCredential : ServiceClientCredentials
    {
    }
}

 Form1があるだろうから、ボタンコントロールおいて、ボタンクリックイベントに以下。

private void button1_Click(object sender, EventArgs e)
{
    var client = new WebTestAPI.WebTestAPIClient(
        new Uri("http://localhost:13418/"), //アドレスは変えろよ
        new AnonymousCredential()
        );

    //usingしてないとだめよ using WebTestAPI;
    var result = client.ApiValuesGet();

    foreach (var x in result)
    {
        MessageBox.Show(x);
    }

    var r = client.ApiValuesByIdGet(12);

    MessageBox.Show(r);
}

 コンパイルして実行しましょう。通らねーの人は、コメントにある、using WebTestAPIしたけ? まあ、つけたプロキシ名がこれじゃなかったら、その名前になるんだけど。

 これ、実態は WebTestAPIClient で、これが動作するんだけど、インターフェイスプログラミングに則って、操作のインターフェイスは、IWebTestAPIClient になってるわけ。で、このIWebTestAPIClientに対して、WebTestAPIClientExtensions から提供される拡張メソッドが付く。で、ここに同期呼び出しの拡張がついている。

 こういうテクニックは、これはこれで使えるので、知っておくと便利。

 それでも通らない時は、NuGetで最新とっているか確認して、とってきて、Proxyクラスの入っているフォルダ(今だと、WebTestAPICliet)を削除して、REST API を再作成だ。

 それでも駄目なら、あきらめろん。(右上の小窓のアヒルに聞いたら教えてくれる可能性も微レ存)

 で、実行する。と、うまくいくー! かに見えるが、ID指定の方で、おちるー! 落ちない人は、よかったなー! 修正されているか、または、解決策2を見ろ。

 でで。これなんだけど、これは、ASP.net Core 2 の問題か、REST APIの問題か、Json.NETの問題かわからないが、Bodyで帰ってくる文字列が、text/Plainなので、value になっているため、"value" でないので、Format Exception的なものが発生する問題なのね。なので、解決策は以下。

解決策1

 実際に使いそうなパターンに変更する。
 クラスにラップするだけで十分。

解決策2

 もう、そもそもjsonしか返さないようにしちゃう。
 クラスに [Produces("application/json")] を付ける方法。
 Content Negotiation 完全無視!
 でも、新規の空のAPIコントローラーを作成すると、勝手につくし! どうせ付くなら、別にいっか! 的な!

 お好みで。ここでは、別にどうでもいいので、クラスでラップします。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;

namespace WebApiSample.Controllers
{
    public class Values
    {
        public string Value { get; set; }
    }

    [Route("api/[controller]")]
    public class ValuesController : Controller
    {
        // GET api/values
        [HttpGet]
        public IEnumerable Get()
        {
            return new Values[]
            {
                new Values { Value= "value1" },
                new Values { Value= "value2" },
            };
        }

        // GET api/values/5
        [HttpGet("{id}")]
        public Values Get(int id)
        {
                return new Values { Value = $"value{id}" };
        }

        // POST api/values
        [HttpPost]
        public void Post([FromBody]string value)
        {
        }

        // PUT api/values/5
        [HttpPut("{id}")]
        public void Put(int id, [FromBody]string value)
        {
        }

        // DELETE api/values/5
        [HttpDelete("{id}")]
        public void Delete(int id)
        {
        }
    }
}

 クリックの中はこんな感じになるかな。

private void button1_Click(object sender, EventArgs e)
{
    var client = new WebTestAPI.WebTestAPIClient(
        new Uri("http://localhost:13418/"), //アドレスは変えろよ
        new AnonymousCredential()
        );

    //usingしてないとだめよ using WebTestAPI;
    var result = client.ApiValuesGet();

    foreach (var x in result)
    {
        MessageBox.Show(x.Value);
    }

    var r = client.ApiValuesByIdGet(12);

    MessageBox.Show(r.Value);
}

 はい、次。やっと本題。Web APIドキュメントを、コントローラー毎に生成する方法。コントローラー毎にWeb APIドキュメントができれば、Proxyもコントローラー毎に作れて、管理しやすい。

 とまれ、新規のコントローラーの追加。DefaultControllerでいいや。面倒くさいし。読み取りと書き込みありのパターンでいいや、めんどくさいし。

 いったん、これでサイトを実行してみましょう。ちゃんとswagger UIで、Defaultと、先に作っていたValuesがあるますか? あるます前提で続けます。

 両方でるので、これのURLを分割したいのです。でで、どういう仕組みでswaggerが、生成されるswagger.jsonのアドレスを管理しているのかというと、これは、Startup.csの、UseSwaggerUIの中の、SwaggerEndpointで管理されているのですよ。ここのアドレス、/swagger/v1/swagger.jsonのとこ。

 でね、この、v1が肝なんですよ。このv1のところがね、SwaggerDocの、nameなんですよ。つまりね、拡張すると、以下のようになるの。

//Swaggerを登録
services.AddSwaggerGen(options =>
{
    options.SwaggerDoc("v1", new Info
    {
        Title = "WebTestAPI",
        Version = "v1"
    });

    options.SwaggerDoc("DefaultControler", new Info
    {
        Title = "Default API",
        Version = "v1"
    });
    
    // Set the comments path for the Swagger JSON and UI.
    var basePath = PlatformServices.Default.Application.ApplicationBasePath;
    var xmlPath = Path.Combine(basePath, "WebApiSample.xml"); //ここ、出力されるxmlコメントのファイル名ね
    options.IncludeXmlComments(xmlPath);
});

// Swagger UI を使う
app.UseSwagger();

app.UseSwaggerUI(options =>
{
    options.SwaggerEndpoint("/swagger/v1/swagger.json", "WebTestAPI");
    options.SwaggerEndpoint("/swagger/DefaultControler/swagger.json", "Default API");
});

 で、実行してみましょ。あれ? 何も変わらない? いえ、右上のドロップダウンリストのところ、選べません? 選ぶと、左のテキストボックスのアドレス、変わりません?

 で、ここまで来たら、後はコントローラーがどっちに属するか、設定できればいいんで、それは、ApiExplorerSettings Attributeです。以下みたいな感じ。

[Produces("application/json")]
[Route("api/Default")]
[ApiExplorerSettings(GroupName = "DefaultControler")]
public class DefaultController : Controller
{

[Route("api/[controller]")]
[ApiExplorerSettings(GroupName = "v1")]
public class ValuesController : Controller
{

 はいじっこー。分割できた? できたね? できたら、もう簡単だね。アドレスが違うなら、REST APIクライアントは複数作れるね。はい、できたー!

 備忘録的なメモでもあるんだけど、なげーわ、なげーよ、アヒル。


トラックバックURL

http://blog.studio-odyssey.net/cgi-bin/mt/mt-tb.cgi/938


コメントする