AzureFunctionsとAzureReposを使って開発環境構築を行う

# はじめに
AzureのDevOpsを体験してみたくてひとまず今回はAzure FunctionsとAzure Reposで開発環境構築を行ってみました。
今回はC#で開発を行いたいと思います。

# 環境
* Windows11
* Visual Studio Code
* Git v2.37.1
* Azure Functions Core Tools v4.0
* Microsoft Azure Storage Explorer

# ①Azure DevOpsでプロジェクト作成を行います
Azure DevOpsにサインアップします。

https://learn.microsoft.com/ja-jp/azure/devops/user-guide/sign-up-invite-teammates?toc=%2Fazure%2Fdevops%2Fget-started%2Ftoc.json&view=azure-devops

サインアップ後プロジェクトを作成します。
プロジェクト名を入力し、【Create project】をクリックします。
ここではプロジェクト名をMSLearn-AzureFunctionsとしました。(名前長い。。。)
![DevOps.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2819390/0ddb8786-3239-e28c-2b55-c70b4a3d064f.png)

プロジェクト作成後、【Repos】の【Files】をクリックします。
その後、【Clone in VS Code】をクリックしてmasterブランチをローカルにクローンします。
![DevOps2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2819390/56aeebb6-b3d1-a6b2-04f0-fd22f8246231.png)

# ②VSCode上でAzure Functionsの環境構築を行います
クローン後VSCodeが開かれると思います。
Azureマークをクリックしその後、WorkSpaceのFunctionマークをクリックします。

![Func1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2819390/fb6125f4-e09f-a1c4-4f71-b8a964a7b252.png)

その後、以下の順にクリックしていきます。
1. C#
2. .Net6.0
2. HTTP trigger
2. HTTPTrigger(機能名、今回はデフォルトのままです)
2. Company.Function(namespace、今回はデフォルトのままです)
2. Function

その後、以下が作成されると思います。
![Func9.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2819390/57d9e1a1-39ff-3807-13da-25bfd1b17440.png)

その後、ターミナルを開き拡張機能をインストールします。
```powershell
dotnet add package Microsoft.Azure.WebJobs.Extensions
```

その後、デバッグ実行し実行したアプリにAPIをリクエストするとStatus200が返ってくると思います。
```powershell
# 参考APIリクエスト
Invoke-WebRequest -Uri "http://localhost:7071/api/機能名" -Method GET
```

※以下参考

https://learn.microsoft.com/ja-jp/azure/azure-functions/functions-develop-vs-code?tabs=csharp

※実行前にAzure Functions Core Toolsをインストールする必要があると思います。

https://learn.microsoft.com/ja-jp/azure/azure-functions/functions-run-local?tabs=v4%2Cwindows%2Ccsharp%2Cportal%2Cbash#install-the-azure-functions-core-tools

# ③Reposに向けてPushしてみる
コミット前に.gitignoreファイルに追記を行います。
(特にリモートリポジトリにpushする必要がないものは.gitignoreに追記します)
```.gitignore
# 以下を追加
# VSCode
.vscode/
```
その後コミットしVSCodeのGitの機能でPushします。
Push後にAzure Reposを見るとファイルが追加されていると思います。
![DevOps3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2819390/fcd1c9ad-f8bb-3819-f005-638052818a77.png)

【Repos】の【Branches】をクリックします。
その後、【master】をクリックして【New brench】をクリックします。
新しいブランチ名をdevelopにしてブランチを作成します。
![DevOps4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2819390/8aaee584-851b-1a41-28df-ce167772cef5.png)

![DevOps5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2819390/898cb4c6-a509-18ac-263f-a6573204f481.png)

# ④新規ブランチを作成する
VSCodeのフッター部のブランチ名masterをクリックして③で作成したdevelopブランチに切り替えます。
その後、新規ブランチを「feature/azure-blob」として作成します。
※今回は、新規ブランチでAzuriteを使ってストレージエミュレータを動かす機能を作成したいと思います。

作成後、VSCodeの拡張機能でAzuriteをインストールします。
インストール後コマンドパレットでAzurite:StartをクリックしてAzuriteを起動します。
その後、Blobコンテナを作成するクラスを作成します。
※今回はUtilityというフォルダを作成し、その配下にAzureStorageClientUtility.csというファイルを作成しました。
※今回はBlobServiceClientで使用しているアカウントキーはAzuriteでのエミュレータ共有キー資格情報を設定しています。
　Azure portalで作成したストレージと接続したい場合などは構成ファイルとかに設定を持たす等工夫が必要だと思います。
　(今度記事を書こう。)

https://learn.microsoft.com/ja-jp/azure/storage/common/storage-configure-connection-string?source=recommendations

```csharp
using System;
using System.Threading.Tasks;
using Azure;
using Azure.Storage.Blobs;

namespace Company.Function.Utility
{
    public class AzureStorageClientUtility
    {
        /// <summary>
        /// コンテナクライアント
        /// </summary>
        BlobServiceClient blobServiceClient = new BlobServiceClient("DefaultEndpointsProtocol=http;AccountName=devstoreaccount1;AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;BlobEndpoint=http://127.0.0.1:10000/devstoreaccount1;");

        /// <summary>
        /// コンテナ作成
        /// </summary>
        /// <returns>コンテナ名</returns>
        public async Task<BlobContainerClient> CreateContainerAsync()
        {
            // Name the sample container based on new GUID to ensure uniqueness.
            // The container name must be lowercase.
            string containerName = "container-" + Guid.NewGuid();

            try
            {
                // Create the container
                BlobContainerClient container = await blobServiceClient.CreateBlobContainerAsync(containerName);

                if (await container.ExistsAsync())
                {
                    Console.WriteLine("Created container {0}", container.Name);
                    return container;
                }
            }
            catch (RequestFailedException e)
            {
                Console.WriteLine("HTTP error code {0}: {1}",
                                    e.Status, e.ErrorCode);
                Console.WriteLine(e.Message);
            }

            return null;
        }
    }
}
```

②で作成したHttpTrigger1.csというファイルを以下のようにします。
```csharp
using System;
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Company.Function.Utility;

namespace Company.Function
{
    public static class HttpTrigger1
    {
        [FunctionName("HttpTrigger1")]
        public static async Task<IActionResult> Run(
            [HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req,
            ILogger log)
        {
            log.LogInformation("C# HTTP trigger function processed a request.");

            string name = req.Query["name"];

            string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
            dynamic data = JsonConvert.DeserializeObject(requestBody);
            name = name ?? data?.name;

            string responseMessage = string.IsNullOrEmpty(name)
                ? "This HTTP triggered function executed successfully. Pass a name in the query string or in the request body for a personalized response."
                : $"Hello, {name}. This HTTP triggered function executed successfully.";

            // 以下追加
            AzureStorageClientUtility azureUtility = new AzureStorageClientUtility();
            await azureUtility.CreateContainerAsync();
            return new OkObjectResult(responseMessage);
        }
    }
}
```
作成後デバッグ実行してAPIリクエストをするとストレージエクスプローラーのBlobに新しいBlobコンテナが作成されていると思います。
![Func13png.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2819390/abc2c0c4-b73c-8eaa-2511-ce6b3ea2c99f.png)

コミット前に.gitignoreファイルに追記を行います。
```.gitignore
# 以下を追加
# Azurite
__azurite_*.json
```
コミット後Pushしてみます。
Push時に警告がかもしれませんが【OK】をクリックします。
![Func14png.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2819390/dac88945-aeb4-f858-65eb-82bcf431cdf4.png)

Pushに成功すると【Repos】の【Branches】にfeature/azure-blobブランチが作成されていると思います。
![Func15png.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2819390/eb58108a-2b1d-298b-b40f-82d81a15b900.png)

# ⑤Pull Requestしてみる
【Repos】の【Branches】の【Create a pull request】をクリックします。

![Func16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2819390/160e73f2-043b-8054-5494-16473e0baad9.png)

マージ先をdevelopにして【Create】をクリックします。
![Func18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2819390/a8f1de83-40c2-c005-dd00-37bf9963f563.png)

その後レビュー者が【approve】し、作成者が【Complete】をします。
Completeをするとdevelopブランチにソースがマージされfeature/azure-blobブランチは削除されていると思います。
![Func19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2819390/3ba6655b-014b-1ac6-8cd1-3edd65a6806b.png)

※以下参考

https://learn.microsoft.com/ja-jp/azure/devops/repos/git/pull-requests?view=azure-devops&tabs=browser

# 所感
AzureFunctionsとAzureReposを使って開発環境構築を行ってみました。
さらにBordsを使えばアジャイルぽく開発ができると思いました。
また機会があればこの開発にさらにPipelineを作成してCI/CDの環境構築にチャレンジしてみたいと思いました。
今後もいろいろチャレンジしてみたいと思います。