Azure App Service に ASP.NET MVC をデプロイして PostgreSQL を使って簡易 TODO リストを作ってみた

Azure の初回登録時の無料プランで試した内容です、Azure App Service は無料で使えたようですが、Azure Database for PostgreSQL は有料(初回登録時にもらえる30000円のクレジットで余裕の範囲内)のようでした。

初心者で調べながらやっただけですのでご了承ください。

# Azure にリソースグループを作成

「リソースグループ」をクリック

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/142910/20e4c423-e134-a2ee-0720-7eb638dea882.png)


「作成」をクリック

![image-1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/142910/c5c4a7b0-b2f1-f2db-435f-600de380780c.png)

リソースグループに「SimpleTodoList」と入力して、リージョンを「Japan East」にして「確認および作成」をクリック

![image-2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/142910/a1bfbb05-7238-1c2c-34b5-51dac39c68f5.png)


「作成」をクリック

![image-3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/142910/9db217e9-343e-3883-cebf-0c2eba8c1009.png)



表示されるまで数十秒ラグがあったが、「SimpleTodoList」ができていることを確認

![image-4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/142910/1fca0024-f51d-acff-48d6-23a8e51f635b.png)



# PostgreSQL の設定

「Azure Database for PostgreSQL - フレキシブル サーバー」を選択

![image-5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/142910/5556a6be-6be5-2f98-fc16-28c44343bc9a.png)



「作成」をクリック

![image-6.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/142910/cd256fa2-b80e-f0e5-b551-55a343727e0a.png)



「基本」タブは以下のように設定しました、パスワードは任意のものを設定しました

![image-7.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/142910/25b34588-f620-09b8-e90a-9a5b60d7050c.png)

![image-8.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/142910/a5a7e328-9568-e37b-58a3-348a3c8e950c.png)


「ネットワーク」タブで「Azure 内の任意の Azure サービスにこのサーバーへのパブリック アクセスを許可する」のチェックを入れて、「確認及び作成」を押しました

![image-9.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/142910/171f5e90-1606-1cae-72e2-b560a25ec994.png)


「作成」をクリック

![image-10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/142910/a9d5c57b-084a-116b-5e77-b3ab139089c3.png)


「デプロイが進行中です」という表示になり、10分ちょっと待つと完了の表示になりました。

![image-11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/142910/71446dcb-a084-6002-29bb-06badc87a308.png)



「Azure Database for PostgreSQL - フレキシブル サーバー」の画面から、先ほど作った「posgres-todolist」をクリック

![image-12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/142910/2f20d146-1869-7257-d9c0-a56ee74f9e35.png)



「接続」から「postgres」のデータベース選択状態で「はい」をクリック

![image-13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/142910/9860d497-f448-8e3a-44d6-64de2e972fb1.png)


コンソールが開いてパスワード入力状態となるとなるので、PostgreSQLのリソース作成時に自分で設定したパスワードを入力する、ちなみにコンソールへのコピペは Shift+Ctrl+V でできる

![image-14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/142910/7d954998-5081-e7a5-7aca-5f9ecf892aeb.png)



コンソールから sql 実行してテーブルを作っておく

```sql
CREATE TABLE todos (
	id SERIAL PRIMARY KEY,
	title varchar(100) NOT NULL,
	created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
	updated_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP
);
```

![image-16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/142910/23deb74e-f696-24df-46e9-2077b56a727b.png)


「接続」をクリックすると「アプリからの接続」の欄に「ADO.NET」用の接続文字列が見れるので、後述のC#の接続処理の時に使う。

![image-15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/142910/5138a03b-b3c6-4da5-9809-1b3350db2615.png)


# C# で ASP.NET MVC のプロジェクトをデプロイするまで

基本的に↓この通りにするとできました

https://learn.microsoft.com/ja-jp/azure/app-service/quickstart-dotnetcore?tabs=net80&pivots=development-environment-vs

VisualStudio で MVC のアプリを作成

![image-17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/142910/73ff24df-a652-b717-1d6b-7f1b83db34e5.png)



ソリューションエクスプローラーでプロジェクト名を右クリックして「発行」をクリック

![image-18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/142910/9edccd57-dea2-df8f-793e-442944dbeaa4.png)



Azure が選択されている状態で「次へ」をクリック

![image-19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/142910/0494a83b-c833-2c9f-9c6f-d045439f0764.png)



「Azure App Service(Windows)」を選択して「次へ」をクリック

![image-20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/142910/92eb892c-43df-c9c1-4b47-aa76065c80a7.png)



Azure にログインしているマイクロソフトアカウントが選択されていない場合は設定する

「+新規作成」を押す

![06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/142910/83aeb237-b226-edfa-12ca-8581a4ecd349.png)



「ホスティングプラン」の「新規作成...」を押す

![07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/142910/c1390ee2-7150-5338-7687-e040a51bf08d.png)



「場所」を「Japan West」、「サイズ」を「Free」に設定して「OK」ボタンを押しました

![08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/142910/875e623f-8d1d-c83b-8a0f-02d1cae5c2f8.png)



「作成」ボタンを押しました


![image-21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/142910/1bf292cb-67af-4685-07cd-d180b21f1cf9.png)


作成されたようなので「完了」を押しました


![image-22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/142910/612578bb-067e-e122-3df5-1e943ba0c882.png)


右上の「発行」を押すと自動でブラウザが開いて Azure App Service にデプロイされている画面を見れました

![image-23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/142910/0be1cfe1-cb60-5f7a-1a5d-eafcf39730b6.png)


![image-24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/142910/9105ca43-84a2-f4c8-bda9-73b2da882e18.png)



# TodoList を作成する

NuGet のパッケージ管理から Npgsql をインストールする

![image-25.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/142910/d8226480-03d0-3673-e4f1-082e5db71052.png)



DB操作をやりやすくするために　Npgsql　をラップしたクラスを作る

```cs:Db.cs
using Npgsql;

namespace WebApplication1
{
    internal class Db
    {
        private static NpgsqlConnection? _connection;

        private static NpgsqlConnection GetConnection()
        {
            if (_connection == null)
            {
                // Azure の画面から確認できる、接続文字列を設定する
                // パスワードはPostgreSQLのリソース作成時に自分で設定したパスワードにする
                _connection = new NpgsqlConnection("Server=postgres-todolist.postgres.database.azure.com;Database=postgres;Port=5432;User Id=postgres;Password=XXXXX;Ssl Mode=Require;");
                _connection.Open();
            }
            return _connection;
        }

        public static void Execute(string sql, object?[]? param = null)
        {
            var command = new NpgsqlCommand(sql, GetConnection());
            Array.ForEach(param ?? [], p => command.Parameters.Add(new NpgsqlParameter("", p)));
            command.ExecuteNonQuery();
        }

        public static Dictionary<string, object>[] ExecuteAndFetchAll(string sql, object?[]? param = null)
        {
            var lines = new List<Dictionary<string, object>>();

            var command = new NpgsqlCommand(sql, GetConnection());
            Array.ForEach(param ?? [], p => command.Parameters.Add(new NpgsqlParameter("", p)));

            using var reader = command.ExecuteReader();
            while (reader.Read())
            {
                var row = new Dictionary<string, object>() {};
                for (int i = 0; i < reader.FieldCount; i++)
                {
                    row.Add(reader.GetName(i), reader.GetValue(i));
                }
                lines.Add(row);
            }

            return lines.ToArray();
        }
    }
}
```

デフォルトで作られている HomeController.cs を修正する

```cs:Controllers/HomeController.cs
using System.Diagnostics;
using Microsoft.AspNetCore.Mvc;
using WebApplication1.Models;

namespace WebApplication1.Controllers
{
    public class HomeController : Controller
    {
        private readonly ILogger<HomeController> _logger;

        public HomeController(ILogger<HomeController> logger)
        {
            _logger = logger;
        }

        public IActionResult Index()
        {
            ViewData["Messege"] = TempData["Messege"];

            try
            {
                ViewData["TodoList"] = Db.ExecuteAndFetchAll("select * from todos order by id desc");
            }
            catch (Exception e)
            {
                ViewData["Messege"] = $"エラー : {e.Message}";
            }


            return View();
        }

        [HttpPost]
        public ActionResult Add(string title)
        {
            try
            {
                Db.Execute("insert into todos (title, created_at, updated_at) values ($1, $2, $3)", [title, DateTime.Now, DateTime.Now]);
                TempData["Messege"] = "追加しました";
            }
            catch (Exception e)
            {
                TempData["Messege"] = $"エラー : {e.Message}";
            }
            return RedirectToAction("Index", "Home");
        }

        [HttpPost]
        public ActionResult Edit(int id, string title)
        {
            try
            {
                Db.Execute("update todos set title = $1, updated_at = $2 where id = $3", [title, DateTime.Now, id]);
                TempData["Messege"] = $"id:{id} を更新しました";
            }
            catch (Exception e)
            {
                TempData["Messege"] = $"エラー : {e.Message}";
            }
            return RedirectToAction("Index", "Home");
        }

        [HttpPost]
        public ActionResult Del(int id)
        {
            try
            {
                Db.Execute("delete from todos where id = $1", [ id ]);
                TempData["Messege"] = $"id:{id} を更新しました";
            }
            catch (Exception e)
            {
                TempData["Messege"] = $"エラー : {e.Message}";
            }
            return RedirectToAction("Index", "Home");
        }

        [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
        public IActionResult Error()
        {
            return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
        }
    }
}
```

デフォルトで作成されている Index.cshtml を修正する

```html:Views/Home/Index.cshtml
@{
    ViewData["Title"] = "Home Page";
}

<h2>Todo List</h2>

<div>Messege: @ViewData["Messege"]</div>

<table class="table">
    <thead>
        <tr>
            <th>id</th>
            <th>名前</th>
            <th>作成日</th>
            <th>更新日</th>
            <th></th>
            <th></th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td></td>
            <td>
                <input type="text" class="form-control" name="title" value="" form="add" placeholder="名前を入力してください" />
            </td>
            <td></td>
            <td></td>
            <td>
                <form method="post" action="/Home/Add" id="add" onsubmit="return confirm('追加してよろしいですか？')">
                    <input type="submit" class="btn btn-success" value="追加" />
                </form>
            </td>
            <td></td>
        </tr>
        @foreach (var row in (IEnumerable<dynamic>)ViewData["TodoList"])
        {
            <tr>
                <td>@row["id"]</td>
                <td>
                    <input type="text" class="form-control" name="title" value="@row["title"]" form="edit_@row["id"]" />
                </td>
                <td>@row["created_at"]</td>
                <td>@row["updated_at"]</td>
                <td>
                <form method="post" action="/Home/Edit" id="edit_@row["id"]" onsubmit="return confirm('変更してよろしいですか？')">
                        <input type="hidden" name="id" value="@row["id"]" />
                        <input type="submit" class="btn btn-primary" value="変更" />
                    </form>
                </td>
                <td>
                    <form method="post" action="/Home/Del" onsubmit="return confirm('削除してよろしいですか？')">
                        <input type="hidden" name="id" value="@row["id"]" />
                        <input type="submit" class="btn btn-danger" value="削除" />
                    </form>
                </td>
            </tr>
        }
    </tbody>
</table>
```

再度VisualStudioから「発行」を押してデプロイすると Todo List が動きました！

![image-26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/142910/f8ad6593-7d53-d867-31d6-1f59211d9d84.png)