.NET Coreでサービスを作ってデスクトップアプリで利用してみる

[Ateam Lifestyle x cyma Advent Calendar 2018](https://qiita.com/advent-calendar/2018/ateam-lifestyle-cyma) の19日目は自転車通販サイト「cyma -サイマ-」の運営を預かる@shimura_atsushiが担当します。

普段は自転車を扱うECサイトの運営／開発を担当しておりますが今回はデスクトップアプリの試作に取り組んでみました。

## 読んでほしい方
今回は私の方で特にこういう方にとターゲットを決めておりません。
取り立てて難しいことはしていないので強いて言うならタイトルから
興味を持っていただいた全ての方になります。

## 何を作るか？
今回は.NET CoreをLinuxにインストールして自転車情報を管理するサービスを作成してみます。
用意したサービスをデスクトップアプリケーションから利用し、自転車情報を管理するという構成のシステムを作成します。

以下の環境でシステムを開発します。

|  | 利用する環境 |
| :-------: | :-------: | 
| クライアント  | Mac   |
|  サーバ    |  CentOS7| 
|  DB|MySQL|
|  言語 |C#|

## .NET Coreを使う

### .NET Coreとは？
.NET CoreはWindows/Mac/Linuxなど動作するオープンソースの開発プラットホームです。
コンソール向けのアプリ、Web向けのアプリなどがC#で開発できます。
詳しいことはMicrosoftの資料をごらんください。
[.NET Core](https://docs.microsoft.com/ja-jp/dotnet/core/about)

今回は.NET Coreによりサービスの開発を行います。
サーバ側であるLinuxに.NET Coreをインストールします。
.NET Coreのインストールは下記リンク環境別にインストール方法の紹介があります。
[.NET Core SDK](https://dotnet.microsoft.com/download)

Microsoftが公式のDockerイメージを用意してくれています。こちらを利用する方が環境構築は楽かもしれません。
[.NET Core Docker](https://hub.docker.com/r/microsoft/dotnet/)

お好きな方を選択してください。
ちなみに今回の私の環境はDocker上のLinuxに.NET Coreをインストールしたものを利用してます。
※執筆中に.NET Coreの公式Dockerの存在を知りました。

## とりあえず「Hello World」

.NET Coreのインストールが済んだら、例によって「HelloWorld」を試してみます。
新規にプロジェクトを作成して「HelloWorld」を実行してみます。

```
# dotnet new console -n HelloWorld
```

コマンドを実行すればプロジェクトに必要なファイル群が作成されます。
consoleとあるのは「コンソールアプリのテンプレートでプロジェクトを作成してね」ということです。
更に'-n'オプションで指定した内容でディレクトリを作成し、その配下にプロジェクトを展開してくれます。

```
 HelloWord/
    ┣ Program.cs
    ┣ bin
    ┣ Helloworld.csproj
    ┗ obj
```
エントリポイントは「Program.cs」です。

「Program.cs」を開いてみます。

``` Program.cs
using System;

namespace Helloworld
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Hello World!");
        }
    }
}
```
内容をみると既に「HelloWorld」を出力するプログラムが記述されているので今回はこのまま実行してみます。

```
# dotnet run 
Hello World!
```
労せずして「HelloWorld」ができました。

これらの作業と後述のサービス作成はVisualStudioを利用することでも可能です、お好みに応じて利用してみてください。
[VisualStudio](https://visualstudio.microsoft.com/ja/)

## MySQLを使う

サービスで利用するデータベースにMySQLを利用します。  
MySQLのインストールなど導入に関する情報は溢れていると思いますので今回はスキップします。
MySQLへアクセスするためにはそのためのライブラリが必要なのでNuGetを利用してMySQLにアクセス可能なライブラリを用意します。
  
[NuGet, MySQL.Data](https://www.nuget.org/packages/MySql.Data/)

プロジェクトの格納ディレクトリで以下ライブラリを追加するコマンドを実行します。

```
# dotnet add package MySql.Data --version 8.0.13
```

*.csprojファイルを開くとMySql.Dataのライブラリが追加されていることがわかります。

``` mysql.csproj
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>netcoreapp2.1</TargetFramework>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="MySql.Data" Version="8.0.13" />
  </ItemGroup>

</Project>
```
これでMySQLを利用する環境は整ったはずです、次は実際にMySQLへのアクセスを試します。

## MySQLへのアクセスを試す

簡単ですが自電車のサンプルのデータを以下のように用意します。

| id  | 名前  | 価格 |  メーカID |
| :---: | :---: | :---: | :---: |
| 1 | bike_1 | 10000 | 0 |
| 2 | bike_2 | 14000 | 0 |
| 3 | bike_3 | 20000 | 1 |

データを利用するサンプルプログラムは以下の通りです。

``` Product.cs
using System;
using MySql.Data.MySqlClient;
using System.Data;

namespace BikesAdmin
{
    class Program
    {
        static void Main(string[] args)
        {
            //MySQLへの接続文字列　
            string connstr = "userid=***; 
                              password=***; 
                              database = example; 
                              Host=127.0.0.1";
            
            //MySQLへ接続
            MySqlConnection conn = new MySqlConnection(connstr);
            conn.Open();
             
            // データを格納するテーブル作成
            DataTable ds = new DataTable();
            // SQL文と接続情報を指定し、データアダプタを作成
            string SQL = "SELECT * FROM bikes;";
            MySqlDataAdapter da = new MySqlDataAdapter(SQL, conn);
            da.Fill(ds);

            foreach(DataRow d in ds.Select()) {
                Console.WriteLine(String.Format("{0}, {1}, {2}", d[0], d[1], d[2]));
            }
            conn.Close();
        }
    }
}

//# 実行結果
//# 1, bike_001, 10000
//# 2, bike_002, 14000
//# 3, bike_003, 20000
```
これで.NET Core/C#のプログラムからMySQLを利用することができました。


## .NET Coreでサービスを作る

今度はコンソール向けのプロジェクトではなくサービス向けのテンプレートでプロジェクトを作成してみます。

```
# dotnet new webapi -n BikeAdmin
```
以上のコマンドでWebapi用のプロジェクトを作成できます。  

### 自転車情報を取得するサービスを作る

MySQLへ登録した自転車情報を取得するサービスを作成します。
自転車情報全件と特定の自転車情報を取得するサービスを作成してみます。
まず、サービスを記述するためのWebApi用コントローラのクラスファイルを作成します。

```BikesController.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using MySql.Data.MySqlClient;
using BikeAdmin.models;
using System.Data;
using Newtonsoft.Json;

namespace BikeAdmin.Controllers
{
    [Route("bikes")]
    [ApiController]
    public class BikesController : Controller
    {
        [HttpGet]
        public IEnumerable<string> GetBikeAll() //全件検索
        {
            BikeAdmin.db.db con = new BikeAdmin.db.db();
            con.Open();
            string SQL = "SELECT * FROM bikes";
            MySqlDataAdapter da = con.execute(SQL);
            DataTable dt = new DataTable();
            da.Fill(dt);

            List<string> bikeList = new List<string>();
            foreach(DataRow d in dt.Select())
            {
                Bikes bikes = new Bikes();
                bikes.Id = (int)d[0];
                bikes.Name = (string)d[1];
                bikes.Price = (int)d[2];
                bikes.MakerId = (int)d[3];
                string json = JsonConvert.SerializeObject(bikes);
                bikeList.Add(json);
            }

            return bikeList.ToArray();
        }

        [HttpGet("{id}")]
        public string GetBike(int id) //idによる絞り込み
        {
            BikeAdmin.db.db con = new BikeAdmin.db.db();
            con.Open();
            string SQL = "SELECT * FROM bikes";
            MySqlDataAdapter da = con.execute(SQL);
            DataTable dt = new DataTable();
            da.Fill(dt);

            string json = null;
            foreach (DataRow dr in dt.Select("id = " + id)) {
                Bikes bikes = new Bikes();
                bikes.Id = (int)dr["id"];
                bikes.Name = (string)dr["name"];
                bikes.Price = (int)dr["price"];
                bikes.MakerId = (int)dr["maker_id"];
                json = JsonConvert.SerializeObject(b);
            }
            return json;
        }

        以下略

```

### リクエストをする

※.NET Coreのビルドインサーバは5000番ポートを利用します、Dockerを利用する際はコンテナ起動時に5000番ポートを割り当ててください。

ブラウザから
http://localhost:5000/bikes へリクエストしてみます。  
<img width="1135" alt="getBikeAll.png" src="https://qiita-image-store.s3.amazonaws.com/0/311437/fb765edb-efa4-1cac-77bf-a925e92d0a48.png">
DBから自転車情報が全件取得しJSONで返って来ているのがわかります。

http://localhost:5000/bikes/1 へリクエストしてみます。
<img width="935" alt="getBike.png" src="https://qiita-image-store.s3.amazonaws.com/0/311437/ab60a7c6-4eb7-0733-0f0c-a35aba5565f9.png">
こちらはIDにより絞り込みされたデータで取得できています。

至極、簡素ではありますが自転車情報を取得するためのWebサービスが出来上がりました。次はこのデータを再利用するアプリを作成していきます。

## クライアントのアプリを作る
本来はWindowsFormもしくはWPFでのクライアントアプリの開発をしたいところではありましたが私の手元にWindowsがインストールされた端末がなくMacしかないという都合でCocoaを利用してWebサービスを扱うアプリの開発をします。
VisualStudio for Macを用いればC＃を利用したCocoaのアプリを開発することができます。

以下ソースコードがサービスにリクエストしている箇所です。（抜粋）
※jsonを扱うために[Json.NET](https://www.newtonsoft.com/json)というパッケージを利用しています、ここではNuGetで取得したものを使用します。

``` ViewController.cs
        public override void ViewWillAppear()  //・・・(1)
        {
            string url = "http://localhost:5000/bikes/";
            HttpWebRequest req = (HttpWebRequest)WebRequest.Create(url);
            req.Method = "GET";
            HttpWebResponse res = (HttpWebResponse)req.GetResponse();
            Stream s = res.GetResponseStream();
            StreamReader sr = new StreamReader(s);
            string content = sr.ReadToEnd();
            List<Bikes.models.Bikes> de = JsonConvert.DeserializeObject<List<Bikes.models.Bikes>>(content);
            BikesDataSource bds = new BikesDataSource();
            bds.DataSource = de;
            BikeTable.DataSource = bds;
            BikeTable.Delegate = new BikesTableDelegate(bds);
        }

        partial void ExecuteRequestBike(NSObject sender) //・・・(2)
        {
            int id = ReqId.IntValue;

            string url = "http://localhost:5000/bikes/" + id + "/";
            HttpWebRequest req = (HttpWebRequest)WebRequest.Create(url);
            req.Method = "GET";
            HttpWebResponse res = (HttpWebResponse)req.GetResponse();
            Stream s = res.GetResponseStream();
            StreamReader sr = new StreamReader(s);
            string content = sr.ReadToEnd();
            Bikes.models.Bikes de = JsonConvert.DeserializeObject<Bikes.models.Bikes>(content);
            BikesDataSource bds = new BikesDataSource(de);
            BikeTable.DataSource = bds;
            BikeTable.Delegate = new BikesTableDelegate(bds);
        }
```
※サンプルなのでソースコードが重複しているのは容赦ください。

(1)アプリの起動と同時にサービスへリクエストをして車種情報を全件取得しTableViewへ表示します。 
<img width="482" alt="getBikeAllforApp.png" src="https://qiita-image-store.s3.amazonaws.com/0/202764/fdbfc5be-52b4-5572-e34a-7a18b1d16507.png">

(2)画面上のTextFieldにIDを入力してボタンを押下するとID別の車種情報を取得しTableViewへ表示します。
<img width="484" alt="getBikeforApp.png" src="https://qiita-image-store.s3.amazonaws.com/0/202764/c89a7df5-5adf-26a8-d54b-3feeae5e1f5a.png">


## 最後に
今回はクライアント側はCocoaで作成しましたがWindowsFormもしくはWPFを用いたアプリなどのほうが開発がしやすく利便性が個人的に高いように思います。
業務系のアプリを開発する際はデザインを飾ることも少ないと思うのでデスクトップアプリの方がWebアプリのようにCSSやJavascriptを利用しなくてもよくコストが安くなる場合があるのではないでしょうか？
サービスを管理するアプリはこういった技術を利用したものを選択肢として持っておくのもいいと思いました。

---------------------------------------
Ateam Lifestyle x cyma Advent Calendar 2018、明日も「cyma -サイマ-」の@k_moriに書いてもらう予定です。お楽しみに！


エイチームグループでは、一緒に働けるチャレンジ精神旺盛な仲間を募集しています。興味を持たれた方はぜひエイチームグループ採用サイトを御覧ください。
https://www.a-tm.co.jp/recruit/