shuhelohelo’s blog

Xamarin.Forms多めです.

ASP.NET Coreでjsファイルをminifyする

ウェブサイトではBootstrapなどのCSSフレームワークや、jQueryなどのライブラリ、自作のcssファイルやjsファイルを各ページで適宜読み込んで使用します。

これらのファイルはHTMLファイルとともにクライアント(ブラウザ)側にダウンロードされるので、そのサイズは小さければ小さいほど良いことになります。

そこでminifyといって可読性のために挿入されている改行、半角スペース、タブといった空白文字を削除してサイズを小さくするということを行ったり、そもそも配布されているライブラリではjquery.min.jsというようにminifyされたバージョンを選択することができるようになっています。

一般的なライブラリであればminifyバージョンを選択すればよいですが、自作のものは何かしらのツールやサービスを使用してminifyする必要があります。

ASP.NET Core 2.1以降には任意のcss、jsファイルを設定ファイルに基づいて自動的にminifyする仕組みが含まれています。拡張機能やNuGet経由で何かをインストールする必要はありません。

docs.microsoft.com

準備は簡単で、プロジェクトフォルダの直下にbundleconfig.jsonファイルを作成し、そこに少しばかりの記述をするだけです。

例えば、テンプレートからASP.NET Coreウェブアプリケーションを作成した場合、wwwroot/jsフォルダの中にsite.jsというファイルが用意されていると思います。

f:id:shuhelohelo:20190910215017p:plain

最初は中はコメントだけですが、それらを消して以下のようにちょっとしたJavaScriptを書いておきました。

$('#myButton').on('click', function () {
    console.log('Yey.');
});

それでは、このファイルをminifyするための設定をbundleconfig.jsonに書いてみます。

[
  {
    "outputFileName": "wwwroot/js/site.min.js",
    "inputFiles": [
      "wwwroot/js/site.js"
    ]
  }
]

シンプルで難しいことはないと思います。outputFileNameに出力ファイルのパス、inputFilesにminifyしたいファイルのパスを記述するだけです。

そして保存する、とそのタイミングで指定したファイルが自動的にminifyされます。

ソリューションエクスプローラーで見てみると、以下のようにsite.jssite.min.jsがぶら下がるように生成されていることが確認できます。

f:id:shuhelohelo:20190910215900p:plain

あとはこのminifyされたファイルを読み込むようにHTML側で指定すればよいだけです。とても便利です。

複数のファイルをそれぞれminifyしたい場合は以下のようにします。

[
  {
    "outputFileName": "wwwroot/js/site.min.js",
    "inputFiles": [
      "wwwroot/js/site.js"
    ]
  },
  {
    "outputFileName": "wwwroot/js/sidebarLinkActivate.min.js",
    "inputFiles": [
      "wwwroot/js/sidebarLinkActivate.js"
    ]
  }
]

ところで、inputFilesとあるので入力ファイルを複数指定して一つのminifyファイルにまとめることができます。

  {
    "outputFileName": "wwwroot/js/site.min.js",
    "inputFiles": [
      "wwwroot/js/site.js",
      "wwwroot/js/sidebarLinkActivate.js"
    ]
  }

はてなブログでコードを折り返さずに表示する

はてなブログで技術系の記事を書くときにコードを貼り付けますが、デフォルトのままだと表示幅で折り返してしまい、読みにくくなります。

(追記) でもこれをやると,コード以外の通常の引用も折り返し無しになるため,とても見づらくなってしまいます...しまった.

f:id:shuhelohelo:20190907110401p:plain

しかし、他の方のブログでは折り返されずに表示されてて、そういう設定があるのかな、見つからないな、と思っていたら、これははてなブログスタイルシートのカスタマイズの機能を使うということでした。↓

www.notitle-weblog.com

なるほど。

ダッシュボードのメニューからDesignを選択して、 f:id:shuhelohelo:20190907111100p:plain

Customizeを選択して、Stylesheetを開くとCSSを記述できる、と。

f:id:shuhelohelo:20190907111244p:plain

では折り返さないようにするCSSはどう書くのかというと、以下のとおり↓。

www.taneyats.com

これをCSS記述欄に追加するだけでOk。

pre, code {
    max-height    : 500px;
    overflow      : scroll;
    white-space   : pre !important;
    text-overflow : clip !important;
}

DIとDIコンテナについて復習

DI:

DI(依存性の注入)は、あるクラス(Hogeクラス)の中で別のクラス(Fugaクラス)のインスタンスを利用する場合、FugaクラスをHogeクラス内でインスタンス化(new)するのではなく、コンストラクタ、プロパティ、メソッド経由でHogeクラスにインスタンスを渡すこと。

依存性というとわかりにくいけれど、「クラスHogeが利用する(依存する)オブジェクト」のこと。 それを内部で生成するのではなく外部から渡す(注入:injection)するので「依存性の注入:Dependency Injection」という。

コンストラクタ経由の注入(コンストラクタインジェクション)がもっとも良いといわれる。それは、Hogeクラスのインスタンス化時にHogeクラスが必要とするオブジェクトを全部コンストラクタに渡さなければならないので、プロパティ経由、メソッド経由のようにうっかり渡し忘れということがないし、Hogeを利用する(コーディングする)側としてHogeクラスがどのクラスを使っているのか、という依存関係を把握しやすいから。

DIパターンを使うことのメリット:

  • クラス間の結合を低くできること
  • それによってテスタビリティを高くできること

DIパターン(コンストラクタインジェクション)を使ったHogeクラスの実装は以下のようになる。

public class Hoge
{
    private readonly Fuga _fuga;
    public Hoge(Fuga fuga)
    {
        _fuga = fuga;
    }

    public void DoSomething()
    {
        _fuga.SayHello();
    }
}


//利用する側

static void Main()
{
    var hoge = new Hoge(new Fuga());

    hoge.SayHello();
}

このとき、FugaのインターフェースIFugaを作って、このインターフェース経由でDIするようにすると、テスタビリティが高くてなお良い。

public interface IFuga
{
    public void SayHello();
}

public class Fuga : IFuga
{
    public void SayHello()
    {
        //実装
    }
}


public class Hoge
{
    private readonly IFuga _fuga;
    public Hoge(IFuga fuga)
    {
        _fuga = fuga;
    }

    public void DoSomething()
    {
        _fuga.SayHello();
    }
}

これでIFugaを実装するクラスならなんでもOKになるので、テスト用のクラス(例:FugaMockクラス)を作って渡すようにしても、Hogeクラスの実装に変更を加える必要がない。

DIコンテナ:

DIパターンを使うと、依存するクラスが多くなるとDI地獄(?)的なことになる。 (その場合はクラスの設計が悪い可能性が高いけれども)

var hoge = new Hoge(new Fuga(), new Bar(), new Foo()......);

これはまだいい方で、Fugaクラスが他のクラス(Misoクラス)に依存していて、MisoクラスがDaizuクラスに依存している、などとなるとDIパターンを使うと以下のようになる。

var hoge = new Hoge(new Fuga(new Miso(new Daizu), new Bar(), new Foo().... );

もう意味がわからない。

このクラス生成時の依存関係を解決する仕組み(フレームワークだったりライブラリ)がDIコンテナ。

DIコンテナ(の機能を持つクラスだよ)にクラスを登録しておき、DIコンテナ経由でインスタンス化することで、注入するインスタンスの生成などを自動的にやってもらうことができる。

例:擬似コード的ななにか

var container = new Container();//ライブラリやフレームワークが提供するクラス

//まずはDIコンテナにクラスを登録する
container.Register<IFuga, Fuga>();//interface経由なら
container.Register<Miso>();//クラス直指定なら
container.Register<Daizu>();
container.Register<Bar>();
container.Register<Foo>();

//Hogeクラスのインスタンス化
var hoge = container.Resolve<Hoge>();//必要なインスタンスの準備はこの1行で全て解決!

素晴らしいですね。

C#のDIコンテナのUnityを使った例↓

shuhelohelo.hatenablog.com

Prismを使ってXamarin.Formsアプリを作るときのメモ[引越記事]

はじめに

モバイルに限らずアプリケーションの開発というと難しく感じられるかもしれませんが、シンプルなアプリケーションであれば「こうしたいときはこう書く」みたいな基本的なパターンを知っておくだけで作ることができます。

それはVisual Studioのような統合開発環境の進歩や様々なライブラリ、フレームワークの充実、言語自体の進歩のおかげだったりします。

しっかりしたアプリケーションを作るためには専門的な知識が沢山必要だったりするのでしょうが、せっかくこんなにアプリケーション開発のハードルが下がってきたのだから、要点だけ押さえてアプリケーションの開発を楽しんでみていいんじゃないかな、と思います。

この記事では「イチから」というわけにはいきませんが、Xamarin.FormsとPrismというアプリケーション開発のフレームワークを使った、「こうしたいときはこう書く」を紹介したいと思います。

モバイルアプリの典型的な動作

モバイルアプリの動作は非常に大雑把に言って、「画面をタッチしたら画面が変わる」とか「画面をタッチしたら情報が更新される」とかです。

となると、イベント処理(ボタンを押す)、画面遷移(画面が変わる)、データバインディング(情報の更新)の基本的な書き方を知っていればシンプルなアプリケーションが作れる、と思います。

そこで、この記事では以下の5つの項目について書きます。

  • データバインディング
  • イベント処理
  • 画面遷移
  • データの受け渡しを伴う画面遷移
  • 状態によるボタンの有効無効

前提

  • Windows 10
  • Visual Studio 2017
  • Prism Template Pack インストール済み
  • Prism Blank App(Xamarin.Forms)でプロジェクト作成済み

1.データバインディング

バインディングしたいプロパティはpublicにします。 そしてsetter内でSetPropertyメソッドを使います。 このメソッドがプロパティの値に変化があったときにView側に通知して表示が変化します。

        private string _title;
        public string Title
        {
            get => _title;
            set => SetProperty(ref _title, value);
        }
Title="{Binding Title}"

プロパティが持つプロパティをバインディングしたい場合

View側で目的のプロパティまでのパスを「.」でつなげていきます。 以下の例は自作のGeolocaionクラスで、このクラスはLocationプロパティを持っていて、そのLocationプロパティはLatitudeプロパティを持っています。 Geolocation型のプロパティのプロパティであるLatitudeバインディングしたい場合は、Text="{Binding Path=Geolocation.Location.Latitude}" とします。

Geolocation

        private Geolocation _geolocation;
        public Geolocation Geolocation
        {
            get => _geolocation;
            set => SetProperty(ref _geolocation, value);
        }
<Label Text="{Binding Path=Geolocation.Location.Latitude}" FontSize="15" HorizontalTextAlignment="End" HorizontalOptions="FillAndExpand"/>

配列やListなどのCollectionをバインディングするとき

ObservableCollectionを使います。 以下のようにObservableCollection型のプロパティを用意し、それをリスト形式の表示を行うUI要素(XamarinであればListView、WPFならListBoxなど)にバインディングします。

        public ObservableCollection<LocationInformation> Targets { get; set; } = new ObservableCollection<LocationInformation>();
        <ListView AbsoluteLayout.LayoutFlags="All"
                  AbsoluteLayout.LayoutBounds="0,0,1,1"
                  HasUnevenRows="True"
                  ItemsSource="{Binding Path=Targets}">

            //以下略

このObservableCollection型のプロパティに対して、AddRemoveAtClearなどのメソッドで変更を加えると、それがView側に反映されます。

2.イベント処理

コードビハインドのxaml.csファイルにイベントハンドラを書くこともできますが、ここではViewModel側にイベントハンドラを書く方法を紹介します。

Buttonが押されたとき

        <Button Text="text" 
                Command="{Binding Path=MyCommand}" />
        public Command MyCommand =>
            new Command(() =>
            {
                //ボタンが押されたときの処理を書く
            });

Buttonが押されたとき、文字列をパラメータとして渡す

CommandParameterに指定した文字列がViewModel側のCommandが呼び出されるときに引数として渡される。 以下の例ではView側で遷移先のViewを文字列で指定できるようにしている。

        <Button Text="text" 
                Command="{Binding Path=MyCommand}" 
                CommandParameter="SecondPageView"/>
        public Command<string> MyCommand =>
            new Command<string>(name =>
            {
                _navigationService.NavigateAsync(name);
            });

Button以外のUI要素がタップされた場合

Buttonの場合は押されたときにCommandプロパティにバインディングしたCommandが実行されますが、Button以外のUI要素がタップされたときはGestureRecgnaizersプロパティを使います。

Labelがタップされた場合

            <Label Text="{Binding Path=TargetInfo.Name}" FontSize="40" HorizontalOptions="FillAndExpand" HorizontalTextAlignment="Center">
                <Label.GestureRecognizers>
                    <TapGestureRecognizer Command="{Binding Path=NavigateCommand}" CommandParameter="SelectTargetPage"/>
                </Label.GestureRecognizers>
            </Label>

ListViewでアイテムがタップされた場合

EventToCommandBehaviorを使います。 そのUI要素の任意のイベントを拾いたい場合に使います。 EventNameには拾いたいイベント名を指定します。 CommandにはCommand型のプロパティを指定します。この例では選択されたアイテムのデータを引数として渡したいので、プロパティの定義ではCommand<T>クラスを使います。 EventArgsParameterPathプロパティにはListViewが持つプロパティのうち、Commandに渡したいプロパティの名前を指定します。この例では選択されたアイテムが格納されるSelectedItemを指定します。 EventNameEventArgsParameterPathも、指定した文字列のイベントやプロパティをPrismが探して使用してくれます。

            <ListView.Behaviors>
                <behaviors:EventToCommandBehavior EventName="ItemSelected"
                                                  Command="{Binding Path=ItemSelectedCommand}"
                                                  EventArgsParameterPath="SelectedItem"/>
            </ListView.Behaviors>
        public Command<LocationInformation> ItemSelectedCommand =>
            new Command<LocationInformation>(targetInfo =>
            {
                //アイテムが選択されたときの処理を書く。
                //引数targetInfo(名前は何でも良い)には選択されたアイテムのデータが入っている。
                //この例では`LocationInformation`型のコレクションをListViewにバインディングしているので、targetInfoは`LocationInformation`型のデータです。
            });

3.画面遷移

上の例で出てきたとおり。 NavigateAsyncメソッドで指定したViewに遷移できる。

_navigationService.NavigateAsync("遷移先のView名");

じゃあこの_navigationServiceはどこからきたのかというと、そのViewModelのコンストラクタの引数として渡されてくるので、それをprivateなフィールドに入れてViewModel内で使えるようにしている。 _navigationServiceをViewModelのコンストラクタに渡すのはPrismがDIコンテナを使ってやってくれる。

        private INavigationService _navigationService;
        public SelectTargetPageViewModel(INavigationService navigationService)
        {
            _navigationService = navigationService;
        }

データの受け渡しのある画面遷移

NavigateAsyncには受け渡したいデータを指定できるOverrideがあるので、それを使います。 NavigationParametersはDictionaryと同じようにキーと値のペアで渡したいデータ(任意のオブジェクト)を格納していきます。 そして、NavigateAsyncメソッドで遷移先のView名とともにNavigationParameters型のオブジェクトを渡します。

                var parameters = new NavigationParameters {
                    { "キー名", targetInfo},
                };

                _navigationService.NavigateAsync(nameof(TargetDetailPage), parameters);

受け取る側のViewModelでは以下のようにしてデータを受け取ります。 OnNavigatingTo(またはOnNavigatedTo)メソッドで受け取ります。 引数parametersに遷移先でNavigateAsyncメソッドに渡したNavigationParametersオブジェクトが入ります。 それをキー名で取り出してasで型変換して利用します。

        public void OnNavigatingTo(INavigationParameters parameters)
        {
            var targetInfo = parameters[nameof(LocationInformation)] as LocationInformation;

            if (targetInfo == null)
            {
                this.Title = "New";
                return;
            }

            this.TargetInfo = targetInfo;
            this.Title = "Edit";
        }

4.画面遷移時の処理の流れ

画面遷移するときに遷移元と遷移先で後始末や準備、遷移元からのデータの受取などをしたい場合は、遷移元と遷移先でViewModelクラスにINavigationAwareインタフェースを継承して遷移時に実行されるメソッドを実装する。

INavigationAwareは以下の3つのメソッドの実装が強制されます。 そして、この順番に実行されます。

  1. OnNavigatingTo(遷移先:描画前)
  2. OnNavigatedFrom(遷移元)
  3. OnNavigatedTo(遷移先:描画後)

データの受け渡しがある画面遷移の場合、遷移先のOnNavigatingTo``OnNavigatedToでデータを受け取る。

他に、デバイスのホームボタンや戻るボタンが押されたときの動作は以下のとおりです。

ルートページ(例えばMainPage)でデバイスの戻るボタンが押されたとき

  1. OnSleep(ページ側)
  2. App.OnSleep

復帰時

  1. MainPageコンストラク
  2. OnNavigatingTo
  3. OnNavigatedTo
  4. App.OnInitialized

バイスのホームボタンが押されたとき

  1. OnSleep(ページ側)
  2. App.OnSleep

復帰時

  1. OnResume(ページ側)
  2. App.OnResume

アプリ起動時

  1. MainPageコンストラク
  2. OnNavigatingTo
  3. OnNavigatedTo
  4. App.OnInitialized

上スワイプでアプリ切替時(アプリ選択画面)

  1. OnSleep(ページ側)
  2. App.OnSleep

復帰時

  1. OnResume(ページ側)
  2. App.OnResume

電源ボタンを押したとき

  1. OnSleep(ページ側)
  2. App.OnSleep

復帰時

  1. OnResume(ページ側)
  2. App.OnResume

ナビゲーションバーの戻るボタン

  1. OnNavigatedFrom
  2. OnNavigatedTo

GobackAsyncメソッドで戻る場合

  1. OnNavigatingTo
  2. OnNavigatedFrom
  3. OnNavigatedTo

NavigateAsyncメソッドで遷移した場合

  1. 遷移先コンストラク
  2. OnNavigatingTo
  3. OnNavigatedFrom
  4. OnNavigatedTo

5.状態によるボタンの有効、無効

DelegateCommandを使います。

        public ICommand MyCommand =>
            new DelegateCommand(() =>
            {
                //実行したい処理
            }, () =>
            {
                //Buttonの有効、無効を判定したい条件
                //例えば、何かしらかのUI要素にバインディングしている値が特定の値かどうか
            }).
            ObservesProperty(() => /*有効無効の判定に使いたいプロパティ*/);

例えば、Entry(WPFでいうTextBox)コントロールNameプロパティをバインディングしていて、そのEntryに入力がなければButtonは無効、入力があれば有効、というような動作をさせたい場合は以下のようにする。

        public ICommand MyCommand =>
            new DelegateCommand(() =>
            {
                //実行したい処理
            }, () =>
            {
                //Buttonの有効、無効を判定したい条件
                return string.IsNullOrWhiteSpace(this.Name);
            }).
            ObservesProperty(() => this.Name);

複数のプロパティを条件としたい場合、例えば2つのEntryコントロールの両方に入力がある場合だけButtonを有効にする場合は以下のようにする。

        public ICommand MyCommand =>
            new DelegateCommand(() =>
            {
                //実行したい処理
            }, () =>
            {
                //Buttonの有効、無効を判定したい条件
                return string.IsNullOrWhiteSpace(this.Name) && string.IsNullOrWhiteSpace(this.Age);
            })
            .ObservesProperty(() => this.Name)
            .ObservesProperty(() => this.Age);

おわりに

ざっくり大雑把にXamarin.FormsとPrismを使ってモバイルアプリケーションを開発するときの基本的な「こうしたいときはこう書く」を紹介してみました。

たくさんあるようなないような内容でしたが、個人的にはこれだけ知っていればそれなりのアプリケーションを作れると思います。

説明をもっとわかりやすく書けたらいいなとおもいつつ、今回のところはこのへんにしておきたいと思います。

ASP.NET Coreでファイルをアップロードする

ViewModelにIFormFile型のプロパティを用意する。

using Microsoft.AspNetCore.Http;

namespace MyApp.ViewModels.UploadFile
{
    public class UploadFileViewModel
    {
        public IFormFile SelectedFile { get; set; }
    }
}

View側でアップロードするファイルを選択するFormを用意する。

<form method="post" asp-action="upload" enctype="multipart/form-data">
    <div class="form-group row">
        <div class="col-8">
            <div class="custom-file">
                <input asp-for="SelectedFile" class="form-control custom-file-input" />
                <label class="custom-file-label">成績ファイルを選択してください</label>
            </div>
        </div>
    </div>
    <div class="form-group row">
        <div class="col-8">
            <button class="btn float-right">読み込み</button>
        </div>
    </div>
</form>

ControllerにActionメソッドを用意する。

        [HttpPost]
        public IActionResult Upload(UploadFileViewModel model)
        {
            //ここでなにかする

            return View();
        }

これでUploadアクションメソッドのパラメータのmodelにPostされたデータ(ファイル)が渡されるので、これをFileStreamでサーバー上に保存したりなんかしたり。

動作確認していないので上のコードが動くかわからない。けれど、こういう感じ。

Xamarin.FormsでPrism Template Packで作ったプロジェクトでHot Reloadを試すまで

環境

目的

目的はXamarin.FormsにPrism Template Packで作成したプロジェクトでHot Reload機能を試すことです。

結論から言うと、Prism Template Packのテンプレートから作成したプロジェクトのNuGetパッケージをすべて更新するというだけのこと。

Prism Template Packをインストールする

素のXamarin.FormsプロジェクトからPrismを適用することもできますが、Prismのプロジェクトテンプレートを使えば開発を開始しやすいのでインストールすることにします。

Visual Studio拡張機能としてインストールします。

さて、インストールするとまず最初に↓のメッセージがVisual Studio上部に表示されたりします。

f:id:shuhelohelo:20190829201601p:plain

Hot Reload機能を使うためにはバージョンが古すぎる、ということです。

確認してみると、Prism Template Packageで使用されているXamarin.Formsのバージョンは3.2ぐらいです。

Xamarin.Formsの更新

NuGetでXamarin.Forms単体をインストールしようとすると、エラーが出てAndroid側のXamarin.Formsの更新に失敗します。

片方だけ↓

f:id:shuhelohelo:20190829202454p:plain

これはAndroid側のプロジェクトで参照されているパッケージのバージョンが古いためです。

ということで、NuGetでインストール済みのパッケージの更新を適用します。

Updateタブですべて選択して更新します。

f:id:shuhelohelo:20190829202542p:plain

これでXamarin.Formsの更新が完了し、Hot Reload機能が使えるようになります。

あとはHot Reload機能を有効にすればOkです。

shuhelohelo.hatenablog.com

Xamarin.FormsでHot Reloadを試す

環境

こんなときは

こんなメッセージが出たときは、 f:id:shuhelohelo:20190815202821p:plain

Xamarin.Forms 4.2.0.673161-pre3をインストール。 f:id:shuhelohelo:20190815203417p:plain

Include prereleaseにチェックをつけること。 f:id:shuhelohelo:20190815203536p:plain

Hot Reload機能を有効に

Tools > options > Xamarin > Hot Reload とクリックして、

Enable Xamarin Hot Reload(Preview)にチェックをつける。

f:id:shuhelohelo:20190815203715p:plain

以上。