GrapeCity.devlog

グレープシティ株式会社のDeveloper Tools〈開発支援ツール〉の最新情報をお届けします。製品のTIPSや発売情報、イベントのお知らせなどをいち早く発信中です。

Azure DevOps ProjectsでASP.NET Core MVCアプリをデプロイしてみる

2018年7月にAzure DevOps ProjectsというAzureのサービスがGAされました。

Azure DevOps Projects general availability – Microsoft DevOps Blog

「Azure DevOps Projects」は以下で紹介されているように、Visual Studio Team Services (VSTS)を使ったCI/CDパイプラインの構成をAzure ポータルからポチポチ設定するだけで簡単に構成できてしまう素晴らしいサービスです。

本記事では、Azure DevOps プロジェクトを利用してAzure App ServiceのWeb Appsへデプロイしてみたいと思います。対象となるASP.NET Core MVCアプリでのデータ表示にはComponentOneのコンポーネントを使用します。

Azure DevOps Projectsの作成

まずはじめにAzureポータルからAzure DevOps Projectsを参照します。[DevOps project の作成]をクリックします。

f:id:GrapeCity_dev:20180806114858p:plain:w600

デフォルトでチェックが付いている[.NET]をそのまま使います。[Next]をクリックします。

f:id:GrapeCity_dev:20180806115305p:plain:w600

[ASP.NET Core]を選択して[Next]をクリックします。

f:id:GrapeCity_dev:20180806115628p:plain:w600

[Web App]を選択して[Next]をクリックします。

f:id:GrapeCity_dev:20180806115739p:plain:w600

アプリケーションのビルドとソース管理で利用するVSTSと、アプリケーションのデプロイ先であるAzure Web Appsの情報を設定して[Done]をクリックします。

f:id:GrapeCity_dev:20180806120059p:plain:w600

作成が完了後に、成功の通知から[リソースに移動]をクリックすると以下の画面が表示されます。CI/CDの状況、ソースのリポジトリ、デプロイ先のAzure Web Appsの情報が一覧表示で確認&アクセスできます。

f:id:GrapeCity_dev:20180806133142p:plain:w600

VSTSからVisual Studio 2017でリポジトリのクローンを作成

上記の一覧表示画面からRepositoryの[Code]をクリックしてVSTSの画面に移動します。

f:id:GrapeCity_dev:20180806144741p:plain:w600

[Clone]をクリック後に[Clone in Visual Studio]をクリックするとローカルのVisual Studio 2017が起動します。以下の画面が表示されるので[複製]をクリックします。

f:id:GrapeCity_dev:20180806134701p:plain:w300

ローカルにリポジトリが複製されました。ここからはローカル環境での作業です。

f:id:GrapeCity_dev:20180806135502p:plain:w300f:id:GrapeCity_dev:20180806135800p:plain:w300

.NET Coreのバージョンを2.1に変更

このAzure DevOps Projectsで作られたASP.NET Coreプロジェクトで初期設定されている.NET Coreのバージョンですが、.NET Core 1.1と古いバージョンなので.NET Core 2.1に変更します。

f:id:GrapeCity_dev:20180806141334p:plain:w600

これに合わせてNugetパッケージも最新に更新しておきます。

f:id:GrapeCity_dev:20180806141702p:plain:w600

ここまでのローカルでの変更をVSTSへプッシュします。Visual Studioの右下のアイコンをクリック後にチーム エクスプローラーにフォーカスが移動するのでコメントを追加後に[すべてをコミット]をクリックします。

f:id:GrapeCity_dev:20180806142139p:plain:w300

[同期]をクリックします。

f:id:GrapeCity_dev:20180806142807p:plain:w300f:id:GrapeCity_dev:20180806142828p:plain:w300

同期が完了しました。

f:id:GrapeCity_dev:20180806142845p:plain:w300

ビルドが通らない問題を解決する

この時点でVSTSで新しいビルドを実行すると失敗します。

f:id:GrapeCity_dev:20180806143942p:plain:w600

上記画面の赤枠のリンクをクリックすると各ビルドの詳細が分かります。ビルドのRestoreタスクでエラーが出ています。

f:id:GrapeCity_dev:20180806145551p:plain:w600

[Edit]をクリックしてビルド構成を更新します。[.NET Core Tool Installer]のVersion1.0.4から2.1.302に更新します。[Save & queue]をクリックして新しいビルドを実行してみます。

f:id:GrapeCity_dev:20180806150231p:plain:w600

これで.NET Coreのバージョン変更による問題は解決できた…、と思ったのですがまたエラーが発生しています。これはプロジェクトファイルaspnet-core-dotnet-core.csprojで以下のエラーが発生しているのが原因です。

error NU1003: PackageTargetFallback and AssetTargetFallback cannot be used together. Remove PackageTargetFallback(deprecated) references from the project environment.

f:id:GrapeCity_dev:20180806151536p:plain:w600

そこで、ローカルのVisual Studioでaspnet-core-dotnet-core.csprojに含まれるPackageTargetFallbackをコメントアウトしてVSTSへプッシュします。

f:id:GrapeCity_dev:20180806152351p:plain:w500

この状態でビルドを実行してみると今度は成功します。.NET Coreのバージョン変更に伴う準備は完了です!

f:id:GrapeCity_dev:20180806152849p:plain:w600

ComponentOne for ASP.NET MVCのパッケージを追加

以下の記事を参考に、ComponentOne for ASP.NET MVCを使うための設定を実施します。

  • ComponentOne for ASP.NET MVCのパッケージC1.AspNetCore.Mvc.jaを追加
  • ComponentOne for ASP.NET MVCのライセンス設定
  • Nuget.configを追加します(VSTSのビルド時にComponentOne for ASP.NET MVCのパッケージを参照する際のハマりどころです)

さらにVSTSのビルド構成を編集して、追加したNuget.configを参照するように設定しておきます。

f:id:GrapeCity_dev:20180806161348p:plain:w600

VSTSの設定変更後に再度VSTSへプッシュします。VSTSでビルドが開始します。

f:id:GrapeCity_dev:20180806162139p:plain:w600

今度は成功します。これでComponentOne for ASP.NET MVCのパッケージを追加した場合のビルドも完了です!

FlexGridコントロールを使うための準備

今回はComponentOne for ASP.NET MVCのFlexGridを使ってデータを表示するページを追加します。

_ViewImport.cshtmlに以下を追加します。

@addTagHelper *, C1.AspNetCore.Mvc

_Layout.cshtml<head></head>タグの間に追加に以下を追加します。

<c1-styles />
<c1-scripts>
    <c1-basic-scripts />
</c1-scripts>

Modelsフォルダを作成してSaleクラスを追加します。

namespace aspnet_core_dotnet_core.Models
{
    public class Sale
    {
        public int ID { get; set; }
        public DateTime 開始日 { get; set; }
        public DateTime 終了日 { get; set; }
        public string 国名 { get; set; }
        public string 製品名 { get; set; }
        public string 色 { get; set; }
        public double 金額 { get; set; }
        public double 金額2 { get; set; }
        public double 割引 { get; set; }
        public bool アクティブ { get; set; }

        public MonthData[] 傾向 { get; set; }
        public int ランク { get; set; }

        /// <summary>
        /// データを取得
        /// </summary>
        /// <param name="total"></param>
        /// <returns></returns>
        public static IEnumerable<Sale> GetData(int total)
        {
            var countries = new[] { "米国", "イギリス", "カナダ", "日本", "中国",
    "フランス", "ドイツ", "イタリア", "韓国", "オーストラリア" };
            var products = new[] { "Widget", "Gadget", "Doohickey" };
            var colors = new[] { "黒色", "白色", "赤色", "緑色", "青い色" };
            var rand = new Random(0);
            var dt = DateTime.Now;
            var list = Enumerable.Range(0, total).Select(i =>
            {
                var country = countries[rand.Next(0, countries.Length - 1)];
                var product = products[rand.Next(0, products.Length - 1)];
                var color = colors[rand.Next(0, colors.Length - 1)];
                var date = new DateTime(dt.Year, i % 12 + 1, 25, i % 24, i % 60, i % 60);

                return new Sale
                {
                    ID = i + 1,
                    開始日 = date,
                    終了日 = date,
                    国名 = country,
                    製品名 = product,
                    色 = color,
                    金額 = rand.NextDouble() * 10000 - 5000,
                    金額2 = rand.NextDouble() * 10000 - 5000,
                    割引 = rand.NextDouble() / 4,
                    アクティブ = (i % 4 == 0),
                    傾向 = Enumerable.Range(0, 12).Select(x =>
                    new MonthData
                    {
                        Month = x + 1,
                        Data = rand.Next(0, 100)
                    }).ToArray(),
                    ランク = rand.Next(1, 6)
                };
            });
            return list;
        }

        internal static dynamic GetCountries()
        {
            throw new NotImplementedException();
        }

        internal static dynamic GetProducts()
        {
            throw new NotImplementedException();
        }
    }

    public class MonthData
    {
        public int Month { get; set; }
        public double Data { get; set; }
    }
}

次にControllersフォルダにFlexGridControllerコントローラーを追加します。

using Microsoft.AspNetCore.Mvc;
using aspnet_core_dotnet_core.Models;

namespace aspnet_core_dotnet_core.Controllers
{
    public class FlexGridController : Controller
    {
        // GET: /<controller>/
        public IActionResult Index()
        {
            return View(Sale.GetData(15));
        }
    }
}

最後にViewsフォルダ配下にFlexGridフォルダを作成してIndexビューを追加します。

@using C1.Web.Mvc;
@using C1.Web.Mvc.Serializition;
@using C1.Web.Mvc.Grid;
@using C1.Web.Mvc.Fluent;
@using C1.Web.Mvc.CollectionView;
@using aspnet_core_dotnet_core.Models;

@model IEnumerable<Sale>

<h2>ComponentOne for ASP.NET MVC</h2>
<h3>FlexGridでデータを表示します。</h3>

@(Html.C1().FlexGrid<Sale>()
                    .AutoGenerateColumns(false)
                    .Height(450)
                    .Width(700)
                    .AllowAddNew(true)
                    .SelectionMode(C1.Web.Mvc.Grid.SelectionMode.Cell)
                    .CssClass("grid")
                    .Bind(Model)
                     // FlexGridに列データを連結します
                     .Columns(bl =>
                       {
                           bl.Add(cb => cb.Binding("ID"));
                           bl.Add(cb => cb.Binding("開始日").Format("yyyy年/MM月/dd日"));
                           bl.Add(cb => cb.Binding("製品名"));
                           bl.Add(cb => cb.Binding("金額").Format("c"));
                           bl.Add(cb => cb.Binding("割引").Format("p0"));
                           bl.Add(cb => cb.Binding("アクティブ"));
                       })
)

ローカルでデバッグ実行してみます。FlexGridでデータを表示出来ていればOKです。

f:id:GrapeCity_dev:20180807105837p:plain:w600

最後にAzure Web Appsで実行時に例外System.InvalidOperationException(メッセージはCannot find compilation library location for package 'System.Data.SqlClient' )が発生するのを回避するために、以下の設定をaspnet-core-dotnet-core.csproj<PropertyGroup></PropertyGroup>タグの間に追加しておきます。

<MvcRazorCompileOnPublish>false</MvcRazorCompileOnPublish>

VSTSにプッシュしてAzure Web Appsへデプロイ&確認

Visual Studio 2017からプッシュするとVSTSでビルドされAzure Web Appsへデプロイされます。成功すると以下のように表示されます。

f:id:GrapeCity_dev:20180807112302p:plain:w600

Azure Web Appsからも確認してみます。ローカルで確認した時と同じく表示できていればOKです!

f:id:GrapeCity_dev:20180807144645p:plain:w600

まとめ

Azure DevOps Projectsでデフォルトで作成されるプロジェクトは、現時点では.NET Core 1.1を参照していたので、.NET Core 2.1を参照するには色々と設定の更新が必要になります。ちょっと(いや、かなりかな…)長い記事になってしまいましたが参考になれば幸いです。

また、「最初から.NET Core 2.1を設定してRazor ページも使いたい!」と思う方もいるでしょう。そこで次回はComponentOne for ASP.NET MVCで用意されているプロジェクトテンプレートを使って「.NET Core 2.1 + Razor ページ」なプロジェクトをAzure DevOps Projectsでデプロイすることにトライしてみたいと思います。

Google Compute Engineもサポートしています

運用環境のサポート対象としては、本記事で紹介したAzure Web Appsの他にGoogle Cloud PlatformのIaaSであるGoogle Compute Engineもサポートするようになりました。

対応している運用環境の詳細はこちらをご覧ください。