読者です 読者をやめる 読者になる 読者になる

かずきのBlog@hatena

日本マイクロソフトに勤めています。XAML + C#の組み合わせをメインに、たまにASP.NETやJavaなどの.NET系以外のことも書いています。掲載内容は個人の見解であり、所属する企業を代表するものではありません。

Visual Studio 2017をアップデートした直後にASP.NET CoreをAzure WebAppにデプロイしたら502エラーになった

2時間くらいはまった…。

結果としては、以下のStackoverflowにある通り、Microsoft.NETCore.Appのバージョンを明示的にしてあげればよかった。VSのアップデートともにローカルのバージョンは上がったけどAzure側はまだ追いついてないってことなのかな?

stackoverflow.com

UWP Community Toolkitを使って今風?なListViewのUIを実現する

UWP Community Toolkitが、ちょっと目をはなしてる隙に1.4.1にまでバージョンアップしてました。 色々なコントロールとかが提供されているのですが、ListViewに絡むコントロールをいくつか紹介したいと思います。

インクリメンタルローディング

最近のアプリって下の方までスクロールすると続きを読み込むっていうUI多いですよね? ということで、そういう機能を実現する方法がUWPに提供されています。

ISupportIncrementlLoadingインターフェースがそれになります。

docs.microsoft.com

ただ、こいつの実装だるいんですよね。 ということで、UWP Community Toolkitでは、いい感じに扱ってくれる機能を提供していたりします。

ということでさっそく作ってみましょう。

まず、何でもいいので表示用のデータの入れ物クラスを準備します。

public class Person
{
    public string Name { get; set; }

    public override string ToString() => this.Name;
}

このクラスのデータをインクリメンタルローディングしてみようと思います。UWP Community ToolkitにはIIncrementalSource<T>というインターフェースがあります。こいつを実装することでインクリメンタルローディングが出来るようになります。

インターフェースは単純でページ数とデータ取得件数が渡ってくるので、それの部分に対応したデータを返すだけです。

ということで、NuGetでUWP Community Toolkitで検索してパッケージを追加しましょう。Microsoft.Toolkit.Uwp.UI.Controlsが今回のお目当てのパッケージになります。

IIncrementalSource<T>インターフェースの実装は以下のような感じになります。

public class PersonSource : IIncrementalSource<Person>
{
    public Task<IEnumerable<Person>> GetPagedItemsAsync(
        int pageIndex, 
        int pageSize, 
        CancellationToken cancellationToken = default(CancellationToken))
    {
        Debug.WriteLine($"GetPagedItemsAsync: {pageIndex}, {pageSize}");
        var results = Enumerable.Range(pageIndex * pageSize, pageSize)
            .Select(x => new Person { Name = $"tanaka {x}" });
        return Task.FromResult(results);
    }
}

今回は、ログを吐いて適当にデータを生成して返しています。本当は、ここでREST APIとか叩いてデータを取ってくる感じになります。

IIncremantlSource<T>を実装したら、それを扱うためのIncrementalLoadingCollection<TSource, T>クラスを作ります。今回はMainPageにプロパティとして持たせました。

public sealed partial class MainPage : Page
{
    public IncrementalLoadingCollection<PersonSource, Person> People { get; } = new IncrementalLoadingCollection<PersonSource, Person>();

    public MainPage()
    {
        this.InitializeComponent();
    }
}

あとは、適当にXAMLでこれをバインドします。

<Page x:Class="App13.MainPage"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:local="using:App13"
      xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
      mc:Ignorable="d">

    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <ListView ItemsSource="{x:Bind People}">
        </ListView>
    </Grid>
</Page>

実行すると、以下のような結果になります。(といっても普通にデータが表示されるだけです)下までスクロールすると延々とデータが表示されるのがわかります。

f:id:okazuki:20170417111313p:plain

ひっぱって更新(PullToRefresh)

UWPのListViewに何故標準でないのか?という引っ張って更新機能ですが、UWP Community ToolkitにはPullToRefreshListViewというコントロールがあります。ListViewのかわりに使うことで、引っ張って更新が簡単に実現できます。

PullToRefreshContentプロパティで、引っ張ったときに表示されるメッセージやコンテンツを編集できます。ReleaseToRefreshContentプロパティで更新されるところまで引っ張ったときに表示されるメッセージやコンテンツを編集できます。

そして、RefreshRequestedイベントかRefreshCommandを処理することで引っ張って更新時の処理を実行できます。コード例は以下のような感じになります。

<Page x:Class="App13.MainPage"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:local="using:App13"
      xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
      xmlns:controls="using:Microsoft.Toolkit.Uwp.UI.Controls"
      mc:Ignorable="d">

    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <controls:PullToRefreshListView ItemsSource="{x:Bind People}"
                                        PullToRefreshContent="引っ張って更新"
                                        ReleaseToRefreshContent="離すと更新します"
                                        RefreshRequested="PullToRefreshListView_RefreshRequested">
        </controls:PullToRefreshListView>
    </Grid>
</Page>

イベントハンドラでは、今回の例ではIncrementalLoadingCollection<TSource, T>RefreshAsyncメソッドをたたいています。

public sealed partial class MainPage : Page
{
    public IncrementalLoadingCollection<PersonSource, Person> People { get; } = new IncrementalLoadingCollection<PersonSource, Person>();

    public MainPage()
    {
        this.InitializeComponent();
    }

    private async void PullToRefreshListView_RefreshRequested(object sender, EventArgs e)
    {
        await this.People.RefreshAsync();
    }
}

実行すると以下のような感じで引っ張って更新が出来るようになります。

f:id:okazuki:20170417112248p:plain

f:id:okazuki:20170417112258p:plain

左右にスワイプ可能なListView

最近よくあるUIでListViewの項目を左右にスワイプすることでアクションを実行するというものがあります。これもUWP Community Toolkitで提供されています。

SlidableListItemListViewItemTemplateに設定することで実現可能です。注意点としては、SlidableListItemListViewItemいっぱいに表示するためにHorizontalContentAlignmentVertlcalContentAlignmentStretchに設定しておくという点があります。

<Page x:Class="App13.MainPage"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:local="using:App13"
      xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
      xmlns:controls="using:Microsoft.Toolkit.Uwp.UI.Controls"
      mc:Ignorable="d">

    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <controls:PullToRefreshListView ItemsSource="{x:Bind People}"
                                        PullToRefreshContent="引っ張って更新"
                                        ReleaseToRefreshContent="離すと更新します"
                                        RefreshRequested="PullToRefreshListView_RefreshRequested"
                                        SelectionMode="None">
            <ListView.ItemTemplate>
                <DataTemplate x:DataType="local:Person">
                    <controls:SlidableListItem>
                        <TextBlock Text="{x:Bind Name}" />
                    </controls:SlidableListItem>
                </DataTemplate>
            </ListView.ItemTemplate>
            <ListView.ItemContainerStyle>
                <Style TargetType="ListViewItem">
                    <Setter Property="HorizontalContentAlignment"
                            Value="Stretch" />
                    <Setter Property="VerticalContentAlignment"
                            Value="Stretch" />
                </Style>
            </ListView.ItemContainerStyle>
        </controls:PullToRefreshListView>
    </Grid>
</Page>

これで実行すると以下のように左右にスワイプするとアイコンが表示されるようになります。

f:id:okazuki:20170417112834p:plain

アイコンのカスタマイズは、LeftIconプロパティとRightIconプロパティで行います。

<controls:SlidableListItem LeftIcon="Accept"
                           RightIcon="AddFriend">
    <TextBlock Text="{x:Bind Name}" />
</controls:SlidableListItem>

左右にスワイプされたときの処理はLeft/RightCommandRequestedイベントかLeft/RightCommandを処理することで出来ます。Left/RightCommandParameterを活用することでスワイプされた項目のデータを渡したりもできます。

ICommandインターフェースの実装がだるいのでPrism.CoreをNuGetから参照に追加して以下のようなコードを書きます。

public sealed partial class MainPage : Page
{
    public IncrementalLoadingCollection<PersonSource, Person> People { get; } = new IncrementalLoadingCollection<PersonSource, Person>();

    public DelegateCommand<Person> LeftCommand { get; }

    public DelegateCommand<Person> RightCommand { get; }

    public MainPage()
    {
        this.InitializeComponent();
        this.LeftCommand = new DelegateCommand<Person>(x => Debug.WriteLine($"LeftCommand: {x}"));
        this.RightCommand = new DelegateCommand<Person>(x => Debug.WriteLine($"RightCommand: {x}"));
    }

    private async void PullToRefreshListView_RefreshRequested(object sender, EventArgs e)
    {
        await this.People.RefreshAsync();
    }
}

XAMLは以下のような感じになります。

<Page x:Class="App13.MainPage"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:local="using:App13"
      xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
      xmlns:controls="using:Microsoft.Toolkit.Uwp.UI.Controls"
      mc:Ignorable="d"
      x:Name="page">

    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <controls:PullToRefreshListView ItemsSource="{x:Bind People}"
                                        PullToRefreshContent="引っ張って更新"
                                        ReleaseToRefreshContent="離すと更新します"
                                        RefreshRequested="PullToRefreshListView_RefreshRequested"
                                        SelectionMode="None">
            <ListView.ItemTemplate>
                <DataTemplate x:DataType="local:Person">
                    <controls:SlidableListItem LeftIcon="Accept"
                                               RightIcon="AddFriend"
                                               LeftCommand="{Binding LeftCommand, ElementName=page}"
                                               LeftCommandParameter="{Binding}"
                                               RightCommand="{Binding RightCommand, ElementName=page}"
                                               RightCommandParameter="{Binding}">
                        <TextBlock Text="{x:Bind Name}" />
                    </controls:SlidableListItem>
                </DataTemplate>
            </ListView.ItemTemplate>
            <ListView.ItemContainerStyle>
                <Style TargetType="ListViewItem">
                    <Setter Property="HorizontalContentAlignment"
                            Value="Stretch" />
                    <Setter Property="VerticalContentAlignment"
                            Value="Stretch" />
                </Style>
            </ListView.ItemContainerStyle>
        </controls:PullToRefreshListView>
    </Grid>
</Page>

実行すると以下のように、左右にスワイプしたらログが出るようになります。

f:id:okazuki:20170417113611p:plain

逆方向にスクロールするとすぐ表示されるヘッダー

これ名前なんて言うんですかね。Facebookアプリとか使ってるとコンテンツを見るために下にスクロールするとヘッダー(ボタンとか操作項目が並んでる)が非表示になって、コンテンツが画面いっぱいに表示されるけど、ちょっと逆方向にスクロールすると、すぐにヘッダーが表示されるっていうやつです。

これは、ScrollHeaderコントロールをListViewHeaderに設定することで実現できます。このとき、ScrollHeaderコントロールのModeプロパティをQuickReturnにしてホストするListViewコントロールをTargetListViewBaseに設定するのがポイントです。

<Page x:Class="App13.MainPage"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:local="using:App13"
      xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
      xmlns:controls="using:Microsoft.Toolkit.Uwp.UI.Controls"
      mc:Ignorable="d"
      x:Name="page">

    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <controls:PullToRefreshListView x:Name="listView" 
                                        ItemsSource="{x:Bind People}"
                                        PullToRefreshContent="引っ張って更新"
                                        ReleaseToRefreshContent="離すと更新します"
                                        RefreshRequested="PullToRefreshListView_RefreshRequested"
                                        SelectionMode="None">
            <ListView.Header>
                <controls:ScrollHeader Mode="QuickReturn"
                                       TargetListViewBase="{x:Bind listView}">
                    <Border Background="Beige">
                        <StackPanel Padding="10"
                                    Orientation="Horizontal">
                            <Button Content="Foo"
                                    Margin="5" />
                            <Button Content="Bar"
                                    Margin="5" />
                            <Button Content="Baz"
                                    Margin="5" />
                        </StackPanel>
                    </Border>
                </controls:ScrollHeader>
            </ListView.Header>
            <ListView.ItemTemplate>
                <DataTemplate x:DataType="local:Person">
                    <controls:SlidableListItem LeftIcon="Accept"
                                               RightIcon="AddFriend"
                                               LeftCommand="{Binding LeftCommand, ElementName=page}"
                                               LeftCommandParameter="{Binding}"
                                               RightCommand="{Binding RightCommand, ElementName=page}"
                                               RightCommandParameter="{Binding}">
                        <TextBlock Text="{x:Bind Name}" />
                    </controls:SlidableListItem>
                </DataTemplate>
            </ListView.ItemTemplate>
            <ListView.ItemContainerStyle>
                <Style TargetType="ListViewItem">
                    <Setter Property="HorizontalContentAlignment"
                            Value="Stretch" />
                    <Setter Property="VerticalContentAlignment"
                            Value="Stretch" />
                </Style>
            </ListView.ItemContainerStyle>
        </controls:PullToRefreshListView>
    </Grid>
</Page>

実行すると、以下のようにヘッダーが表示されて、スクロールすると消えるけど、逆方向にスクロールすると、すぐにょきっと生えてくるようになります。

f:id:okazuki:20170417114323p:plain

f:id:okazuki:20170417114358p:plain

f:id:okazuki:20170417114428p:plain

まとめ

ということで、UWP Community ToolkitでListViewまわりの機能を紹介しました。これ以外にも色々なコントロールや機能があるので、興味のある方は見てみるといいかも?より良いUIが簡単に手に入るかもしれませんしね!

github.com

Xamarin + Visual Studio Mobile Centerをやってみよう

Visual Studio Mobile Centerは、モバイルアプリを開発する際に必要になる以下の機能を提供してくれる便利なやつです。

  • リポジトリ(GitHub, VSTS, Bitbucket)と連携しての自動ビルド
  • Xamarin.UITestなどのテストコードをプールされた様々な実機上で実行
  • アプリのテスト配布
  • クラウド上のデータストア
  • SNSやAzure ADと連携した認証機能
  • テレメトリ収集
  • クラッシュレポート

1つ1つ見て行こうと思います。

アプリの作成

まず、機能を使うためにMobile Center上でアプリを作らないといけません。Mobile Centerの画面の右上にアプリを作るためのボタンがあるので、Add New Appします。

f:id:okazuki:20170408163559p:plain

そうすると、アプリの情報を入れる画面が出てきます。

f:id:okazuki:20170408163820p:plain

名前などの必要な情報を入れて画面下部のボタンを押すだけです。 今回は、AndroidでXamarinを選択して見ました。

これでアプリができました。

f:id:okazuki:20170408164016p:plain

リポジトリと連携しての自動ビルド

自動ビルドしたいですよね? ということで簡単にできます。アプリの画面の左側からBuildを選びます。 そうすると以下のように、どのサービスと繋ぐかが画面に出てきます。

f:id:okazuki:20170408164114p:plain

ここではGitHubを選んで適当なXamarinのプロジェクトの入ったリポジトリを選択します。

リポジトリを選ぶと、どのブランチに対してビルドをするのかというのを選択する画面になります。今回選んだリポジトリは、masterブランチしかないため以下のように1つだけの選択肢になってます。

f:id:okazuki:20170408164254p:plain

ブランチを選ぶと構成してね見たいな画面になるのでSet up branchを選びます。

f:id:okazuki:20170408164423p:plain

Build on pushで、ブランチにプッシュが来るたびにビルドが走るようになります。

Sign buildsで、署名されたパッケージを作ってくれます。Androidの場合は、Xamarin StudioやVisual Studioでアーカイブ作るときに作れるkeystoreを指定します。iOSの場合は、プロビジョニングプロファイル?と証明書?を指定するみたいです。

iOSに必要なフィアルとかは、ここら辺のサイトが参考になりそうです。

macdays.hatenablog.com

Run a launch test on a deviceでは、とりあえずアプリを実機に入れて起動するかどうかだけの確認をしてくれるみたいです。

Distribute buildsは、アプリをテスト配布するかどうかが選べます。これをOnにすると、後述する、配布先のグループみたいなものに登録されてる人たちにメールを飛ばすことができるようになります。

これで自動ビルドの構成は終わりです。簡単ですね。VSTS使うよりお手軽です。(その代わりカスタマイズできない)

f:id:okazuki:20170408170452p:plain

Test

Testは、実機テストしてくれます。

Testを選んでNew test runを選択すると、デバイスを選んだり言語を選んだりする画面が出てきます。

f:id:okazuki:20170408170637p:plain

f:id:okazuki:20170408170720p:plain

色々なテストフレームワークに対応しています。ここではXamarin.UITestを選んで見ました。

そうすると、nodejsでmobile-center-cliを入れて以下のコマンドを叩けという案内が出てきます。

f:id:okazuki:20170408171639p:plain

言われた通りに叩きます。叩く場所は、書いてある通り、Xamarinのプロジェクトのpackagesフォルダになります。

こんな感じのコマンドになります。

mobile-center test run uitest --app "kaota/HelloWorld" --devices XXXXXXXX --app-path com.companyname.DemoDemoApp.apk  --test-series "master" --locale "ja_JP" --build-dir ../DemoDemoApp.UITest/bin/Release

このコマンドを叩くとmobile-center loginしろと言われるので、言われた通りmobile-center loginをします。このとき要注意なのが、MSアカウントとかと紐付けたMobile Centerのアカウントだと認証失敗するというところです。これは、パスワードを忘れた時のフローを流してパスワード再発行すると上手くいくようになります。

詳細は、ここに書いてあります。(こんなのわからんわ!)

docs.microsoft.com

テストの実行結果は以下のように閲覧することができます。

f:id:okazuki:20170408173056p:plain

f:id:okazuki:20170408173111p:plain

Distribute

これは、ビルド成果物や自分でパッケージングしたapkとかをテスト配布するための機能を提供します。

グループを作って、そこにメアドで人を招待して新しいリリースを作ることでパッケージをダウンロードするためのURLの入ったメールが飛ぶという感じです。そんなに難しくないですね。

f:id:okazuki:20170408173512p:plain

Tables / Identity

これは、Microsoft Azure Mobile Appsを作ってくれて、それのEasy tableと認証機能を使えるようにしているだけです。詳しくはMobile Appsの説明を見て見てください。

Crashes / Analytics

これは、クラッシュレポートとテレメトリの収集なんかができるようになります。 導入方法は簡単で、Mobile Center作ったときに表示される手順に従ってNuGetからパッケージを入れて初期化コードを書いて、必要に応じてトレースログのコードを入れるだけです。

入れるパッケージは以下の2つになります。

  • Microsoft.Azure.Mobile.Analytics
  • Microsoft.Azure.Mobile.Crashes

そして、Xamarin.Formsの場合は、以下のような初期化コードをApp.xaml.csのOnStartメソッドの中に書きます。 キー情報が必要になりますが、これはMobile Centerでアプリを作った時の画面に表示されています。

protected override void OnStart()
{
    base.OnStart();
    MobileCenter.Start("ios=iOS用にmobile centerでアプリを作って取得できるキー;" +
           "android=Androidようにmobile centerでアプリを作って取得できるキー",
        typeof(Analytics),
        typeof(Crashes));
}

あとは、以下のようにトレース取りたいところに以下のようなコードを差し込むだけです。

Analytics.TrackEvent(
    "ItemsPage appearing.",
    new Dictionary<string, string> { { "Category", "Trace" } });

これで、Analyticsのところで以下のようにトレースを確認できます。

f:id:okazuki:20170408174354p:plain

f:id:okazuki:20170408174408p:plain

また、アプリが例外でクラッシュしたときに自動的にレポートが上がるようになります。以下のように確認ができます。

f:id:okazuki:20170408174530p:plain

f:id:okazuki:20170408174544p:plain

まとめ

モバイルアプリを開発していると欲しくなるようなテレメトリ収集や、クラッシュレポートなんかもついてるし、自動ビルドや実機テスト、テスト配布なんかもできていい感じです。おまけにMobile Appsの機能も使えちゃう。 早く正式リリースされないかなぁ。(お値段いくらになるんだろう)

ReactiveProperty 3.6.0をリリースしました

昨日の夜にやろうと思ったんですが今日になってしまいました。

www.nuget.org

今回は、iOS向けのSetBindingメソッドが正しく動いてなかった奴の修正と、ReactiveCommandAsyncReactiveCommandクラスのSubscribeメソッドに引数無し版を追加しました。どちらもプルリクエストです。ありがたやありがたや。

Visual Studio 2017 リリース記念勉強会で発表してきました

3月11日にC#ユーザー会が開催したVisual Studio 2017 リリース記念勉強会で発表してきました。

発表資料を公開します。

30分というあわただしい感じの発表になってしまいましたが、なにか1つくらい知らない機能を持ち帰っていただけたらいいなと思って発表しました。どうだったのかなぁ。

ASP.NET CoreでAngularをする VSCodeを使おう

dotnet new angularでプロジェクト作成して、webpackしてdotnet runで実行できることはわかりました。 では、Visual Studio Codeで快適に作業するにはどうするのがいいのか?ということになりますよね!

Visual Studio Codeで開発するには以下のような感じでとりあえずやってます。

まずは、PowerShellあたりで適当なフォルダを掘ってdotnet new angularします。

> mkdir AngularVSCode
> cd AngularVSCode
> dotnet new angular

必要パッケージを入れます

> npm install
> dotnet restore

VSCodeを立ち上げましょう!

> code .

ターミナルを表示して、webpack -wと打ち込みます。これでTypeScriptに変更があったら自動でwebpackが走るようになります。もう1つターミナルを立ち上げてdotnet runしましょう。

これで下準備完了です! あとは、Angular弄ってブラウザをリフレッシュすれば変更が反映された状態になります。

では、快適Angular生活をVisual Studio Codeで!!

ASP.NET CoreでAngularをするHello world

以下の記事の続きです。

blog.okazuki.jp

ひな形に自分のプログラムを追加してHello worldしてみます。

ClientAppフォルダにAngularのプログラムの本体がいます。そこを弄っていきます。

ClientApp/app/componentsフォルダにhelloworldフォルダを作りましょう。そして、その中にhelloworld.component.tsファイルとhelloworld.component.htmlを作ります。

Angularのお作法に従いコンポーネントを作っていきます。helloworld.component.tsは以下のような感じで。

import { Component, OnInit } from '@angular/core';

@Component({
    selector: 'helloworld',
    templateUrl: './helloworld.component.html'
})
export class HelloWorldComponent implements OnInit {
    constructor() { }
    
    title:string = "Hello world";

    ngOnInit() { }
}

helloworld.component.htmlは以下のような感じにします。

<h1>{{title}}</h1>

コンポーネントのプロパティを単純に表示してるだけですね。

ClientApp/app/app.module.tsに作成したモジュールを追加します。ルーティングもhelloworldという文字列で遷移するように設定しておきます。

import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { UniversalModule } from 'angular2-universal';
import { AppComponent } from './components/app/app.component'
import { NavMenuComponent } from './components/navmenu/navmenu.component';
import { HomeComponent } from './components/home/home.component';
import { FetchDataComponent } from './components/fetchdata/fetchdata.component';
import { CounterComponent } from './components/counter/counter.component';
import { HelloWorldComponent } from "./components/helloworld/helloworld.component";

@NgModule({
    bootstrap: [ AppComponent ],
    declarations: [
        AppComponent,
        NavMenuComponent,
        CounterComponent,
        FetchDataComponent,
        HomeComponent,
        HelloWorldComponent
    ],
    imports: [
        UniversalModule, // Must be first import. This automatically imports BrowserModule, HttpModule, and JsonpModule too.
        RouterModule.forRoot([
            { path: '', redirectTo: 'home', pathMatch: 'full' },
            { path: 'home', component: HomeComponent },
            { path: 'counter', component: CounterComponent },
            { path: 'fetch-data', component: FetchDataComponent },
            { path: 'helloworld', component: HelloWorldComponent },
            { path: '**', redirectTo: 'home' }
        ])
    ]
})
export class AppModule {
}

そして、ナビゲーションメニューのClientApp/app/components/navmenu/navmenu.component.htmlにメニューを追加します。

<div class='main-nav'>
    <div class='navbar navbar-inverse'>
        <div class='navbar-header'>
            <button type='button' class='navbar-toggle' data-toggle='collapse' data-target='.navbar-collapse'>
                <span class='sr-only'>Toggle navigation</span>
                <span class='icon-bar'></span>
                <span class='icon-bar'></span>
                <span class='icon-bar'></span>
            </button>
            <a class='navbar-brand' [routerLink]="['/home']">dotnet_angular_helloworld</a>
        </div>
        <div class='clearfix'></div>
        <div class='navbar-collapse collapse'>
            <ul class='nav navbar-nav'>
                <li [routerLinkActive]="['link-active']">
                    <a [routerLink]="['/home']">
                        <span class='glyphicon glyphicon-home'></span> Home
                    </a>
                </li>
                <li [routerLinkActive]="['link-active']">
                    <a [routerLink]="['/counter']">
                        <span class='glyphicon glyphicon-education'></span> Counter
                    </a>
                </li>
                <li [routerLinkActive]="['link-active']">
                    <a [routerLink]="['/fetch-data']">
                        <span class='glyphicon glyphicon-th-list'></span> Fetch data
                    </a>
                </li>
                <li [routerLinkActive]="['link-active']">
                    <a [routerLink]="['/helloworld']">
                        Hello world
                    </a>
                </li>
            </ul>
        </div>
    </div>
</div>

他のを真似て書いてるだけで何がおきてるのか私にもわかりません。

そして、アプリケーションのルートフォルダでwebpackコマンドを実行します。(webpack入れてないひとはnpm install -g webpackしてね)

PS C:\Users\kaota\Documents\Visual Studio Code\Projects\dotnet-angular-helloworld> webpack
Hash: 58aabf820ca74ec21bf0a8dac7da2d1b48f06b83
Version: webpack 2.2.1
Child
    Hash: 58aabf820ca74ec21bf0
    Time: 10190ms
                 Asset     Size  Chunks             Chunk Names
        main-client.js  24.1 kB       0  [emitted]  main-client
    main-client.js.map  27.2 kB       0  [emitted]  main-client
Child
    Hash: a8dac7da2d1b48f06b83
    Time: 10155ms
             Asset    Size  Chunks             Chunk Names
    main-server.js  154 kB       0  [emitted]  main-server

そして、dotnet runをしましょう。

http://localhost:5000/ にアクセスするとHello worldメニューが増えています。そして、それをクリックすると以下のようにハローワールドが表示されます。

f:id:okazuki:20170305095150p:plain

やったね!

ASP.NET CoreでAngularをする下準備

2017/03/05 日時点の情報です

@angular/cliとか使って始めるのもいいですが、これだとサーバーサイド何でつくるの??という感じになってしまうので、C#erとしては今やるならASP.NET Coreでしょ!ということで始め方をメモっておきます。

結論としてはdotnetコマンドで作れます。ということで最新の.NET Core SDK 1.1のRC4を以下のページからダウンロードしてきました。

github.com

インストールしてdotnet --infoと打ち込むと以下のように表示されます。

PS C:\Users\kaota\Documents\Visual Studio Code\Projects> dotnet --info
.NET Command Line Tools (1.0.0-rc4-004771)

Product Information:
 Version:            1.0.0-rc4-004771
 Commit SHA-1 hash:  d881d45b75

Runtime Environment:
 OS Name:     Windows
 OS Version:  10.0.14393
 OS Platform: Windows
 RID:         win10-x64
 Base Path:   C:\Program Files\dotnet\sdk\1.0.0-rc4-004771

次にAngularとかSPA関連のプロジェクトテンプレートをダウンロードします。以下のコマンドを打ち込みます。

dotnet new --install Microsoft.AspNetCore.SpaTemplates::*

しばらく待ってるとインストールが終わります。終わったらdotnet newと打ち込んでみましょう。以下のようにAngularとかのテンプレートがインストールされてることがわかります。(Vue.jsはないのね)

PS C:\Users\kaota\Documents\Visual Studio Code\Projects> dotnet new
Template Instantiation Commands for .NET Core CLI.

Usage: dotnet new [arguments] [options]

Arguments:
  template  The template to instantiate.

Options:
  -l|--list         List templates containing the specified name.
  -lang|--language  Specifies the language of the template to create
  -n|--name         The name for the output being created. If no name is specified, the name of the current directory is
 used.
  -o|--output       Location to place the generated output.
  -h|--help         Displays help for this command.
  -all|--show-all   Shows all templates


Templates                                     Short Name      Language      Tags
------------------------------------------------------------------------------------------
Console Application                           console         [C#], F#      Common/Console
Class library                                 classlib        [C#], F#      Common/Library
Unit Test Project                             mstest          [C#], F#      Test/MSTest
xUnit Test Project                            xunit           [C#], F#      Test/xUnit
Empty ASP.NET Core Web Application            web             [C#]          Web/Empty
MVC ASP.NET Core Web Application              mvc             [C#], F#      Web/MVC
MVC ASP.NET Core with Angular                 angular         [C#]          Web/MVC/SPA
MVC ASP.NET Core with Aurelia                 aurelia         [C#]          Web/MVC/SPA
MVC ASP.NET Core with Knockout.js             knockout        [C#]          Web/MVC/SPA
MVC ASP.NET Core with React.js                react           [C#]          Web/MVC/SPA
MVC ASP.NET Core with React.js and Redux      reactredux      [C#]          Web/MVC/SPA
Web API ASP.NET Core Web Application          webapi          [C#]          Web/WebAPI
Solution File                                 sln                           Solution

Examples:
    dotnet new mvc --auth None --framework netcoreapp1.0
    dotnet new classlib --framework netstandard1.4
    dotnet new --help

では、適当なフォルダ(ここではdotnet-angular-helloworldという名前にしました)を作って、そこでdotnet new angularをしてみましょう。

そして、npm installdotnet restoreして必要なパッケージをダウンロードします。

最後にdotnet runで実行してみます。そして http://localhost:5000/ にアクセスしてみましょう。

以下のように、ちょっとハローワールド…?というくらい作りこまれたページが表示されます。

f:id:okazuki:20170305091636p:plain

ふむ。とりあえず、これでAngular始めるときのとっかかりとしてC#erがやるにはちょうどいいかも?サーバーサイドはASP.NET Coreでいけるしね!

一定時間やりなおしが出来るようにする

メール削除とかしたけど、一定時間取返しがきくみたいなUIがありますよね。 あぁいうのどうやるんだろうというのを考えてみました。

UWPでやってみますが、WPFでもXamarin.Formsでも基本的に同じ感じになると思います。見た目凝るのが一番難しそう。

Modelの作成

とりあえず、ReactivePropertyとPrism.Coreを参照に追加して以下のコードを書きます。

using System;
using System.Collections.ObjectModel;
using System.Linq;
using System.Reactive;
using System.Reactive.Linq;
using System.Threading.Tasks;

namespace App2
{
    class LazyActionSampleApp
    {
        public ObservableCollection<Guid> Guids { get; } = new ObservableCollection<Guid>();

        public Task<bool> AddGuidAsync(IObservable<Unit> cancel)
        {
            var guid = Guid.NewGuid();
            this.Guids.Add(guid);

            var tcs = new TaskCompletionSource<bool>();

            Observable.Return<Func<bool>>(() => true)
                .Delay(TimeSpan.FromMilliseconds(3000))
                .Amb(cancel.Take(1).Select<Unit, Func<bool>>(_ => () => { this.Guids.Remove(guid); return false; }))
                .Subscribe(x => tcs.SetResult(x()));

            return tcs.Task;
        }
    }
}

Reactive ExtensionsのAmbを使うと先に発火したほうを後ろに流すことができます。これを使ってコミット処理(今回はtrueを返すだけ)とキャンセル処理(今回はコレクションに追加したものを削除する)といったFuncを合成してSubscribeして実行しています。一応処理が成功したのかキャンセルされたのかは戻り値で返すようにしました。

あとは、VMとVを作っておしまい。

using Prism.Mvvm;
using Reactive.Bindings;
using System;
using System.Collections.ObjectModel;
using System.Linq;
using System.Reactive;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using System.Threading.Tasks;

namespace App2
{
    public class MainPageViewModel : BindableBase
    {
        private LazyActionSampleApp Model { get; } = new LazyActionSampleApp();

        public ObservableCollection<Guid> Guids => this.Model.Guids;

        public ReactiveProperty<bool> IsBusy { get; } = new ReactiveProperty<bool>(false);

        public AsyncReactiveCommand AddCommand { get; }

        public AsyncReactiveCommand CancelAddCommand { get; }

        public MainPageViewModel()
        {
            this.AddCommand = new AsyncReactiveCommand(this.IsBusy.Select(x => !x).AsObservable());
            this.CancelAddCommand = new AsyncReactiveCommand(this.IsBusy.AsObservable());

            var cancelTrigger = new Subject<Unit>();
            this.AddCommand
                .Subscribe(async x =>
                {
                    this.IsBusy.Value = true;
                    await this.Model.AddGuidAsync(cancelTrigger);
                    this.IsBusy.Value = false;
                });

            this.CancelAddCommand.Subscribe(_ =>
            {
                cancelTrigger.OnNext(Unit.Default);
                return Task.CompletedTask;
            });
        }
    }
}
using Windows.UI.Xaml.Controls;

namespace App2
{
    public sealed partial class MainPage : Page
    {
        private MainPageViewModel ViewModel { get; } = new MainPageViewModel();

        public MainPage()
        {
            this.InitializeComponent();
        }
    }
}
<Page
    x:Class="App2.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:App2"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d">

    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition />
        </Grid.RowDefinitions>

        <Button Content="Add"
                Command="{x:Bind ViewModel.AddCommand}" 
                HorizontalAlignment="Stretch"/>
        <Button Content="Cancel"
                Command="{x:Bind ViewModel.CancelAddCommand}" 
                HorizontalAlignment="Stretch"
                Grid.Row="1" />
        <ListView ItemsSource="{x:Bind ViewModel.Guids}"
                  Grid.Row="2" />
    </Grid>
</Page>

実行結果

こんな感じになります。

www.youtube.com