おでんはじめました。

required ちくわぶ and 巾着,optional はんぺん.

決済サイトが簡単に作れちゃったのでそのご報告

ハッカソンに参加してきました

Hack Azure! #1 - Azure Serverless でオンライン決済基盤をつくる というハッカソンに参加してきました。決済サイトの構築なんて未知の世界過ぎて自分には縁がないと思ってたのが、本当に短時間で作れてしまったので感動して(珍しく)ブログ書いてます。

事前にチームのメンバーに Teams や Azure や Stripe や GitHub のアカウントが割り当ててあるので、僕らは Teams でコミュニケーションを取りながら黙々とコーディングをすればよいという感じになっています(この辺の運営スタッフの事前準備が素晴らしかったです)。

msdevjp.connpass.com

Stripe って何?

Stripe とはクレジットカードの支払い方法や商品ごとの金額設定やサブスクリプション方式での支払いなど、決済に関する面倒くさいところをおまかせにできちゃう大変便利なサービスです。

しかも Stripe は API が充実しているので、今回は .NETAPI を使い倒して自前の API を作ろうという主旨のハッカソンでした(今回は時間不足と Stripe への理解不足で使い倒すところまではたどりつきませんでしたが)。

stripe.com

何を作ろう

ramen チームということでラーメンをクリックすると Stripe の決済画面に遷移して購入することができるサイトを作ることに決定。

チーム内に Stripe への理解が深い @myfinder さん主導のもと、僕らが Stripe への基本的な理解を深めるにはやっぱり見た目がある方がいいよねということで、ASP .Net Core Web アプリケーションを選択しました。

最終的に完成したのが下の画面で、各ボタンをクリックすると購入画面に遷移する感じです。

f:id:masatoru:20200627190139p:plain

商品を登録する

まずは商品としてのラーメンを揃えようということで、 Stripe のダッシュボードの商品ページからポチポチ商品とイメージを追加してきます。

f:id:masatoru:20200627200653p:plain

商品にそれぞれ価格を設定していきます。1つの商品に複数の価格を設定することや、クーポンを使ってある期間は何パーセント引きというような設定も可能です。

f:id:masatoru:20200627200803p:plain

商品ごとに価格を設定すると price_xxxx という ID が発行されるので、これを実際のサイトに紐づけて Stripe のライブラリを呼び出す(それはまあなんとなくわかる)のですが、さてどうやって JavaScript を書くんだよと。

JavaScript は書かない!?

事前に Stripe の [ダッシュボード]→[設定]→[Checkout の設定] で [ドメイン] と [Webhook] の設定をしておくと、先ほど登録した商品の価格ごとに [Checkout のコードスニペットを取得] を実行できます。

これをクリックするとコードスニペットジェネレータが <button> の HTML と Stripe への購入画面に遷移する JavaScript を生成してくれるので、OK と NG の時の URL を指定して、これをコピペすればとりあえず実装できるという JavaScript が書けない僕にとって非常にありがたい機能です。

f:id:masatoru:20200627200857p:plain

実際にコピペしたページをクリックすると Stripe 側で事前に用意されている購入画面へ遷移します。

もちろん自前の決済ページに Stripe の機能を組み込むこともできます(みたいです)。クレジットカードはテスト用のクレジットカード番号を使用してテストをおこないます。

f:id:masatoru:20200627200943p:plain

ここまで、.NET 全然使ってないよねということで、時間が足りなかったので事前に登録した画像の URL を取得するところを .NET で書きましたが、これもドキュメントが API キー付きで生成してくれるのでコピペする感じでできちゃいます。

// Stripe の API キーはダッシュボードから取得
// 今回はそれを環境変数に保存しておいて使う
StripeConfiguration.ApiKey = Environment.GetEnvironmentVariable("StripeApiKey");
var options = new ProductListOptions
{
    Limit = 10,
};
var service = new ProductService();
StripeList<Product> products = service.List(
    options
);

// 時間が最後足りなかったので登録されている先頭の画像を表示
var imageUrl = products.Data[0].Images[0];
ViewData["imageUrl"] = imageUrl;

お買い上げありがとうございますと同時に画像を表示するようにしました。

f:id:masatoru:20200627202051p:plain

ということで、本当に決済サイトが完成しちゃいました。

今回の ramen チームのリポジトリですのでよければ参照してください。

最後に

本来のハッカソンの主旨である API を実装するところまでは事前知識が足りなかったので間に合いませんでしたが、もう一回この時間があればどんな API を作ろうかというところまで知見を得られたかなと感じています。

何より知らない人とチームを組んでオンラインでハッカソンをおこなうという新しくて楽しい体験ができたことにとても感謝しています。ありがとうございました。

ハッカソンはいいぞ!!

Xamarin.Forms で使える Azure AD 認証付きの WebAPI を実装するには

この記事は Xamarin Advent Calendar 2019 の 9 日目の記事です。

タイトルを実装するのにこれといったベストプラクティスが見つからずになかなかハマりました。

というテーマで、昨日登壇させていただいたのでその最後のベストプラクティスを手順にしてみました。

www.slideshare.net

Azure AD にアプリケーションを登録する

Azure Portal から Azure AD のアプリケーションを登録します。

f:id:masatoru:20191201112114p:plain

  • 概要でアプリケーションID(クライアントID)とディレクトリ(テナントID)をコピペします(後で使います)

  • 認証→リダイレクトURImsal9999...://auth とあるところにチェックを入れて→保存をクリック これが Xamarin 用のリダイレクト URI になります。

f:id:masatoru:20191201113621p:plain

  • 証明書とシークレットで、新しいクライアントシークレットをクリック→任意の名前(secret など)→有効期限なし→追加 これをコピペします。

AppService に認証(EasyAuth)を設定する

AppService は作成済みとして、ポータルから AppService のサイトに移動します。

  • 認証/承認をクリック
  • App Service 認証を ON
  • Azure Active Directory をクリック
  • クライアント ID 、クライアントシークレット(上記で取得済み)をコピペ
  • 発行者の URL はテナント ID (上記で取得済み)を使って以下をコピペ https://login.microsoftonline.com/{テナントID}/v2.0/.well-known/openid-configuration
  • 許可されるトークン対象ユーザーは以下の形式でコピペ https://{Your AppService}.azurewebsites.net/.auth/login/aad/callback
  • OK をクリック
  • 要求が承認されない場合に実行するアクション→ Azure Active Directory でのログイン を選択(忘れやすいので注意!) → 保存をクリック

f:id:masatoru:20191210085138p:plain

Xamarin.Forms にログインを実装する

ログイン画面を実装して API で使用するアクセストークンを取得します。

比較的簡単に実装できる AppCenter Auth は Azure AD B2C のみ対応なので(2019年12月1日現在)使えない(ちなみにロードマップには Azure AD があるので近々対応されるはず)。

となると、Xamarin.Forms(以下 Xamarin ) の場合 MSAL(Microsoft Authentication Library)一択になるわけですが、最初はログイン画面に遷移して2回目以降はキャッシュを取得して画面遷移しないみたいな感じで書きます。この時、ログイン情報をキャッシュするとか有効期限を更新するとかはライブラリにお任せです。

MSAL は nuget から Microsoft.Identity.Client(バージョンは 4.1.0 )で取得します。

using Microsoft.Identity.Client;
// ログインしてアクセストークンを取得する
private async string SignInAsync()
{
  AuthenticationResult authResult = null;
  try
  {
    // キャッシュにあればログイン画面に遷移しない
    IAccount firstAccount = accounts.FirstOrDefault();
    authResult = await App.PCA.AcquireTokenSilent(App.Scopes, firstAccount)
                          .ExecuteAsync();
  }
  catch (MsalUiRequiredException ex)
  {
    try
    {
      // なければログイン画面に遷移する
      authResult = await App.PCA.AcquireTokenInteractive(App.Scopes)
                                  .WithParentActivityOrWindow(App.ParentWindow)
                                  .ExecuteAsync();
    }
    catch (Exception ex2)
    {
    }
  }
  return authResult?.AccessToken;
}

Xamarin で認証付き WebAPI をたたく

取得したアクセストークンを Bearer で渡して WebAPI を叩きます。

最近は HttpClientusing で囲うと犯罪者扱いされるみたいですが...(ry

using (var client = new HttpClient())
{
  // アクセストークンを使ってAPIをたたく
  client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
  var res = await client.GetAsync(apiUrl);

  if (!res.IsSuccessStatusCode)
  {
    throw new Exception(res.ToString());
  }
  // 結果のJSONを受け取る
  var resultText = await res.Content.ReadAsStringAsync();
...

Azure AD の認証付き WebAPI を実装する

VSCodeASP.Net Core で Azure AD 認証付きの Web API を作成します。

azure-v2-app というアプリを作る場合、コンソールから以下をたたきます。

mkdir azure-v2-app
cd  azure-v2-app
dotnet new webapi
code -r .

VSCode が起動するので、表示→ターミナルを開いて、

dotnet build
dotnet run

これで VSCode がローカルでサーバーを実行している状態になるので、ブラウザや POSTMAN などで、

https://localhost:5001/WeatherForecast

とたたくと JSON が返ってくるはずです。これを Azure の AppService にデプロイします。

  • VSCode拡張機能で Azure App Service を追加します(入れてない場合は)。
  • VSCode の左側の Azure のロゴをクリックして、デプロイする App Service を選択します
  • 右クリックで Deploy to Web App をクリックするとデプロイされます(初回はいくつか質問されますがそのまま OK をクリック)。

API を POSTMAN でテストする

デプロイした WebAPI は EasyAuth で認証されたサイトになっているので、そのままたたくと401( Unauthorized )が返ってきます。 これにトークンを設定して 200 が返ってくれば OK ということになります。

まずはブラウザを使って id_token を取得します。 - 作成したサイトが https://azuread-v2-app.azurewebsites.net であれば、https://azuread-v2-app.azurewebsites.net/WeatherForecast にブラウザでアクセスします - ログインされていなければ Azure AD のログイン画面に遷移します。ログイン済みであれば白い画面が表示されます - 同じ画面で、https://azuread-v2-app.azurewebsites.net/.auth/me とたたくとトークンが取得できるので id_token をコピーします

f:id:masatoru:20191210083212p:plain

次に POSTMAN を使って認証済みのサイトにアクセスしてみます。 - POSTMAN で URL に https://azuread-v2-app.azurewebsites.net/WeatherForecast をコピペ - Authorization → Bearer Token → id_token をコピペ - Send をクリックして ステータスが 200(OK) と JSON が返ってくればOKです。

取得したアクセストークンを調べる

取得した id_tokenaccess_tokenhttps://jwt.ms/ にコピペするとトークンの中身を確認することができます。

MSAL で Azure AD の認証を行う場合 V2 のエンドポイントである必要があるので、 isshttps://login.microsoftonline.com/{テナントID}/v2.0 であることを確認します。ちなみに、V1 のエンドポイントは https://sts.windows.net/{テナントID}/

Xamarin.Forms のソースコード

これで Xamarin.Forms から 認証付き WebAPI をたたけるようになったはずです。あとで Xamarin.Forms のサンプルを GitHub にあげます。

さいごに

401(Unauhtorized)なんて怖くない!より良い認証ライフを!!

App Center Data のオフライン同期を試してみた

App Center Data(現在プレビュー版)のオフライン同期が最近できるようになった(今回リストにも対応した)ので Xamarin.Forms で試してみました。

以下の記事を参考にしてサンプルアプリを作成しました。作成したサンプルの GitHub のリンク先も最後に掲載しておきます(現在修正中なので後日必ず)。

docs.microsoft.com

App Center Data とは

App Center Data を使用することで Cosmos DB とアプリを比較的少ないコード量で簡単につなげることができます。

docs.microsoft.com

さらに App Center Auth を組み合わせることでユーザー別のデータベースをアプリに実装することができます。App Center Data は App Center Auth 無しでも使用することはできますが、どちらかというと App Center Auth と組み合わせて使うことを前提としている感じです。

f:id:masatoru:20190803185115p:plain

オフライン同期とは

(英語ではただの offline ですが)「オフライン同期」とはデバイスがネットにつながっていない状態で編集(追加や削除)したデータをローカルにキャッシュしておき、ネットにつながった時にクラウド側のデータベース(今回の場合は Cosmos DB)と同期する機能です。App Center Data を使うとここら辺をいい感じでやってくれる(であろうことをここでは期待している)という便利機能です。

以下はデータベースの一覧を取得しているところですが、この1行だけで"オフライン同期"をいい感じでやってくれます(追加や削除などももちろん同様に)。

var result = await Data.ListAsync<TodoItem>(DefaultPartitions.UserDocuments);

オフライン同期を試す

ネットがつながっている状態で追加をおこなうと Cosmos DB と同期されます。試しに5件追加してみたところ、Azure ポータルの Data Explorer で確認をすると5件分のデータが確認できます。

f:id:masatoru:20190803225726g:plain

f:id:masatoru:20190804005110p:plain

これをデバイス側のネットを切断してデータを追加してみます。アプリ上では追加されましたが Cosmos DB の件数は5件のままです。

f:id:masatoru:20190804005654p:plain

f:id:masatoru:20190804005239p:plain

この状態からデバイスをネットをつなげると Cosmos DB 側と同期されました。

f:id:masatoru:20190804005506p:plain

正確には、

  1. OnAppearing(ページの再描画)が呼ばれる
  2. Data.ListAsync が呼ばれる
  3. (Data.ListAsync の内部で)キャッシュされたローカルのデータをクラウド(Cosmos DB)と同期する

という仕組みのようです。

ローカルの DB は何?

ローカルにキャッシュする時の DB のフォーマットは SQLite を使用しているみたいです。

The Data service supports both writing data offline with a simple store and forward model and reading information offline with a built-in SQLite cache.

devblogs.microsoft.com

最後に

わずかなコード量でオフライン同期が実装できるというのはそれだけで魅力ではあります(少なくともやってみようという気にさせてくれるという意味で)。

App Center Data に限らず Push や Auth など全般的に言えることなのですが、このようにコード量を極力少なく実装できるのが App Center SDK の特徴でもあります。このことは敷居が低いという一方で、細かいハンドリングができないとも言えますが...。

github.com

(おまけ)App Center の今後

App Center は今後多くの新機能が予定されています(以下に抜粋)。興味ある方はこの動画をぜひ見てみてください。

  • JWT Access (Auth)
  • Off-line List Support (Data) ... この記事で紹介
  • Third Party Identity Providers (Auth)
  • Enterprise Support (Auth)
  • View UserProfile Details (Auth)
  • Metrics / Browser / Free Tier (Data)
  • Notification Hubs V2 (Push)
  • Integrate Notification Hub (Push)
  • Full Offline Support (Data)
  • Realtime Updates/Syncing (Data)
  • User Collaboration (Data)
  • Partial Updates (Data)
  • File Storage (Application Services)
  • Compute (Application Services)

www.youtube.com

以上です。 App Center はいいぞ。

Xamarin.Forms で App Center のプッシュ通知を使ってみる

この記事は Xamarin Advent Calendar 2018 の 19 日目の記事です。

App Center の Push Notifications と Xamarin.Forms の Android を使用してプッシュ通知を送受信する方法を説明します。App Center の Push Notifications は Preview 版(2018.12.19 現在)ですのでご注意ください。

Xamarin.Forms でプッシュ通知を実装する方法

Xamarin.Forms でプッシュ通知を実装するには以下の方法があります。

プッシュ通知の仕組みは、プラットフォーム通知システム(以下 PNS: Platform Notification System )と呼ばれるプラットフォーム独自のインフラストラクチャを利用してプッシュ通知を配信します。 PNS は Android であれば Firebase Cloud Messaging (FCM)、iOS であれば Apple Push Notification Service (APNS) を使用することになります。

他にも Azure Notification Hubs を使用しないで直接各 PNS とプッシュ通知のやり取りをする方法や、プッシュ通知に似てるものとして Firebase In-App Messaging というのもあります。Firebase In-App Messaging はアプリに事前に URL スキームを埋め込んでおき、Firebase からその URL に対してメッセージを送信するとそのアプリがメッセージを受信するという仕組みです。

Azure Mobile Apps SDK を使用する方法は App Service の URL を指定するだけで各種の接続をまかなってくれて扱いやすいのですが、開発は止まってるぽいです。1

Azure Notification Hubs は ネイティブ側(iOS / Android)にコードを実装する必要があるのですが、App Center の Push Notifications は共通プロジェクトの App クラスにちょろちょろっと書くだけなのでずいぶん簡単になっています(それでも事前に設定することがいろいろありますが)。

Firebase Cloud Messaging の設定

今回は Android を使ってプッシュ通知をおこなうので、Firebase Cloud Messaging でのプロジェクトとアプリの登録が必要になります。

  1. Firebase にログインする
  2. 右上の「コンソールへ移動」をクリック
  3. プロジェクトを追加する
    f:id:masatoru:20181218183333p:plain
  4. 左上の設定をクリック
  5. 「プロジェクトの設定」をクリック
    f:id:masatoru:20181218183617p:plain
  6. Android アプリに Firebase を追加」をクリック f:id:masatoru:20181219122016p:plain
  7. 「アプリの登録」の「 Android パッケージ名」は Xamarin の Android プロジェクト→プロパティ→ Android マニフェスト→パッケージ名 をコピペします
    f:id:masatoru:20181219122502p:plain
  8. 「アプリを登録」をクリックすると google-services.json をダウンロードできるのでこれを保存しておきます
  9. JAVAに関するところは Xamarin には関係ないので「次へ」で無視

これでアプリ登録が完了しました。

サーバーキーの取得

設定→クラウドメッセージングの「サーバーキー」をコピペして保存しておきます。

App Center でアプリケーションを追加する

App Center にログインして All apps から右上の Add newAdd new app をクリックします。

「アプリケーション名」の入力と、OS は Android、Platform は Xamarin を指定します。

f:id:masatoru:20181219115000p:plain

作成したアプリケーションから Push → Notifications をクリックします。 ここに移行の実装手順が記載されています。

  1. Xamarin.Forms での実装手順
  2. (Firebase の設定と)Android プロジェクトへの記述
  3. Firebase のサーバーキーのコピペ

上記手順を以下進めていきます。

Xamarin.Forms にプッシュ通知を実装する

.NET Standard で Xamarin.Forms のプロジェクトを作成します。

パッケージマネージャーから Microsoft.AppCenter.Push をすべてのプロジェクトにインストールします。バージョンは 1.11.0(2018.12.15現在)です。

App.xaml.cs にコードを追加していきます。まずは using を追加します。

using Microsoft.AppCenter.Push

同様に App.xaml.csOnStart() メソッドに 以下を追加します。上記の Push → Notifications のページに記載されているのでそちらをコピペしてください。

protected override void OnStart()
{
  AppCenter.Start("xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", typeof(Push));
  Push.PushNotificationReceived += this.Push_PushNotificationReceived;
}

プッシュ通知を受信したときに Push_PushNotificationReceived イベントが呼ばれるので、受信した内容を表示するようにします。

private async void Push_PushNotificationReceived(object sender, PushNotificationReceivedEventArgs e)
{
    await this.MainPage.DisplayAlert(e.Title, e.Message, "OK");
}

Android プロジェクトへ追記する

Android プロジェクトの PropertiesAndroidManifest.xml ファイルの &lt;application>...&lt;/application> タグ内に以下を追加します。

<receiver android:name="com.google.firebase.iid.FirebaseInstanceIdInternalReceiver" android:exported="false" />
<receiver android:name="com.google.firebase.iid.FirebaseInstanceIdReceiver" android:exported="true" android:permission="com.google.android.c2dm.permission.SEND">
<intent-filter>
<action android:name="com.google.android.c2dm.intent.RECEIVE" />
<action android:name="com.google.android.c2dm.intent.REGISTRATION" />
<category android:name="${applicationId}" />
</intent-filter>
</receiver>

追加後は以下のようになります。

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionCode="1" android:versionName="1.0" package="com.company.PushAppCenterSample" android:installLocation="auto">
  <uses-sdk android:minSdkVersion="21" android:targetSdkVersion="27" />
  <application android:label="PushAppCenterSample.Android">
    <receiver android:name="com.google.firebase.iid.FirebaseInstanceIdInternalReceiver" android:exported="false" />
    <receiver android:name="com.google.firebase.iid.FirebaseInstanceIdReceiver" android:exported="true" android:permission="com.google.android.c2dm.permission.SEND">
      <intent-filter>
        <action android:name="com.google.android.c2dm.intent.RECEIVE" />
        <action android:name="com.google.android.c2dm.intent.REGISTRATION" />
        <category android:name="${applicationId}" />
      </intent-filter>
    </receiver>
  </application>
</manifest>

Fireabase でダウンロードした google-service.jsonAndroid プロジェクトにコピーして、「ビルドアクション」を GoogleServicesJson に設定します。

選択できない場合は一度プロジェクトを保存して Visual Studio を終わらせて、再度プロジェクトを開くと表示されます。

f:id:masatoru:20181219123640p:plain

Firebase のサーバーキーのコピペ

上記で Firebase の設定で取得したサーバーキーをここにコピペします。

以上で App Center の設定 と Xamarin.Forms の実装は完了です。

プッシュ通知の送信テストをする

送信テストは App Center のサイトの Notifications ページにある Send Notification ボタンからおこないます。

f:id:masatoru:20181218160145p:plain

Compose で送信するメッセージを指定します。Cmapaign Name, Message が必須で、Title, Custom Data はオプションになっています。Custom data は Key/Valueで値を送信することができます(後述)。

f:id:masatoru:20181218161842p:plain

Target でどのデバイスに送信するかを決定します。

  • All registerd devices - 登録されているすべてのデバイスに送信します。
  • Custom device list - 指定したデバイスに最大20個まで一度の送信することができます。
  • Audience - 国や言語、デバイスのバージョンなどを指定して送信することができます。

f:id:masatoru:20181218161802p:plain

最後に Review で送信する内容を確認して Send notification クリックします。

f:id:masatoru:20181218162629p:plain

うまくいけばデバイスにプッシュ通知が到達しメッセージが表示されます。これまでの経験ではいずれも1秒かからずに到達する感じです。

f:id:masatoru:20181219215210p:plain

Swagger を使ってプッシュ通知を送信する

REST API を使用してプッシュ通知を送信することもできます。使用できる API が OpenAPI(旧Swagger) で定義されています。

API を使用するためには API トークンが必要になります。API トークンは(アプリの設定ではなく) App Center の設定(右上の自分のアイコン)→ Account Settings → API Tokens → New API Token で発行できます。

f:id:masatoru:20181219173514p:plain

Swagger UI を使ってプッシュ通知のメッセージを送信してみます。プッシュ通知の API一覧 の右上にある Authorize をクリックして取得した API トークンを APIToken(apiKey) にコピペします。

f:id:masatoru:20181219180146p:plain

プッシュ通知の API一覧 から POST を使用します。

f:id:masatoru:20181219193918p:plain

[Try it out] をクリックすると値を編集できるので、ここに値を入力していきます。 すべてのデバイスに送信する場合は、"notification_target": null になります。

Example value

{
  "notification_content": {
    "name": "プッシュ通知のテスト",
    "title": "プッシュ通知のタイトル",
    "body": "プッシュ通知のメッセージ"
  },
    "notification_target" : null
}

The name of the owner にオーナー名、The name of the application にアプリ名を入力して、Execute をクリックします。成功すれば notification_id と値が JSON が返ります。

特定のデバイスにプッシュ通知をおこなう

特定のデバイスにプッシュ通知をおこなう場合は、notification_target に対象のデバイス ID を指定します。最大で同時に 20 台まで送信できます。

Example value

{
  "notification_content": {
    "name": "プッシュ通知のテスト",
    "title": "プッシュ通知のタイトル",
    "body": "プッシュ通知のメッセージ"
  },
  "notification_target":  {
    "type": "devices_target",
    "devices": ["xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"],
  }
}

バイス ID は、App Center の SDK を使用して AppCenter.GetInstallIdAsync(); で取得することができます。

Custom Data を使ってプッシュ通知をおこなう

送信する JSONcustom_data を付加すると Key/Value の値を送信することができます。

Example value

{
  "notification_content": {
    "name": "プッシュ通知のテスト",
    "title": "プッシュ通知のタイトル",
    "body": "プッシュ通知のメッセージ",
    "custom_data": {
      "dinner": "beer"
    },
  },
  "notification_target":  {
    "type": "devices_target",
    "devices": ["xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"],
  }
}

バイス側で受け取る方法は、Push_PushNotificationReceived イベントの引数の PushNotificationReceivedEventArgs クラスの CustomData プロパティに Key/Value 値が入っているので、これを使って分岐処理などをおこなうことができます。

private async void Push_PushNotificationReceived(object sender, PushNotificationReceivedEventArgs e)
{
    if (e.CustomData != null && e.CustomData.ContainsKey("dinner"))
    {
        var action = e.CustomData["dinner"];
        switch (action)
        {
            case "beer":
                await this.MainPage.DisplayAlert("Sample", "また飲むのか!", "OK");
                break;
            case "beef":
                await this.MainPage.DisplayAlert("Sample", "焼肉いいね!", "OK");
                break;
        }
    }
}

まとめ

プッシュ通知は環境の設定や実機が必要などめんどくさいイメージがありますが、App Center の Push Notifications は(これまでに比べて)実装が簡単で、かつ安定してプッシュ通知を受信してくれるので、それほどストレスなく使えると思います。ぜひ一度お試しください。

明日は @yu_ka1984 さんです。よろしくお願いします!

SkiaSharpがXamarin.MacでもWPFでも動くよという話

Xamarin その1 Advent Calendar 2017 - Qiitaの22日目です。

去年のアドベントカレンダーXamarin.Forms+SkiaSharpで縦組みの記事を書いたので、今回はこれを.NET Standard化してAndroidiOSとXamarin.MacWPFで動かしてみたよというお話です。

作成したサンプルはGitHubに置いたのでよければビルドしてみてください。

SkiaSharpとは

SkiaSharpとはC#と.Netで使用できるオープンソースの2Dグラフィックスのライブラリです。

github.com

現在はPCLと以下のプラットフォームに対応しています。

こちらに詳しく書かれています。 qiita.com

去年のプロジェクト構成

1年前はSkiaSharpがSharedプロジェクトのみに対応で、PCLプロジェクトに対応していなかったので途中で対応してたけど書き換える能力がなかったのでXAML用に専用のカスタムビュー(TategumiView)を作成し、カスタムレンダラーをiOSAndroidにそれぞれゴリゴリ書いて実装しました。

f:id:masatoru:20171220193746p:plain

今回のプロジェクト構成

現在のSkiaSharpはPCLプロジェクトにも対応しているので、専用のNativeViewを作る必要がなくなり(カスタムレンダラーが不要になり)XAMLでSkiaSharp標準のビューコントローラ(SKCanvasView)を使って描画まで実装が可能です。Xamarin.Mac / WPFも同様にSkiaSharp標準のビューコントローラが用意されています。

さらに.NET Standardに対応しているので描画ロジックなどを.Net Standard対応のライブラリ化をすることでXamarin.MacWPFからも共通のライブラリとして使用することができます。

f:id:masatoru:20171220194316p:plain

去年のSharedプロジェクトに比べてかなりシンプルな構成になっています。

.NET Standard2.0とは

.NET Standardの詳しい説明は賢者の方々のブログを見ていただくとして、僕の唯一の理解は

NET Standard2.0 は .NET Core 2.0 と .NET Framework 4.6.1 と Mono と Xamarin.iOS と Xamarin.Android と Xamarin.Mac と UWP を(ほぼ)サポートしている(ただし、OS やランタイム依存の処理がある場合を除く)

ということです。

.NET Framework4.5以降と.net standard1.xが共存できるということに関してはよくわかりません勉強中です。

要するに.NET Standardは複数のOSでソースを共有できる仕組みなので、 この仕組みのおかげで最近のXamarin.Formsや.NET Framework4.6.1以上のXamarin.MacWPFから.NET Standard2.0のライブラリを使用することができるというわけです。

「ほぼ」とあるのは.Net Standard2.0で作成したライブラリで例えば.NET Framework4.6.1のライブラリを参照すると完全な互換性がない可能性があると警告が表示されるのでそういうことです。

warning NU1701: パッケージ 'xxxxx 2.0.1' はプロジェクトのターゲット フレームワーク '.NETStandard,Version=v2.0' ではなく '.NETFramework,Version=v4.6.1' を使用して復元されました。このパッケージは、使用しているプロジェクトとの完全な互換性がない可能性があります。

僕の数少ない経験上では完全に互換性があります。

各プロジェクトのポイント

各プロジェクト作成にあたり一番特筆すべきは.NET Starndard2.0対応のために.csprojファイルを編集する必要がない!(2017.12.21時点)ということです。

参考までに現在のバージョンを記載しておきますが、少なくともこのバージョン以降であればIDEが提供するプロジェクト作成の手順だけで作成できます。

.NET Standard2.0ライブラリ

UIはXamarin.Forms / Xamarin.Mac / WPFそれぞれで作る必要がありますが、反対にそれ以外に関しては(上記条件を満たせば)共通の .NetStandard2.0ライブラリとしてまとめられるということになります。

今回のサンプルでは(フォントファイルなどリソースの取得に関する箇所を除く)描画ロジックをすべてをライブラリ化して、Xamarin.Forms / Xamarin.Mac / WPFから使用しています。

作成方法はとても簡単で、プロジェクト作成→.NET Standard→クラスライブラリ(.NET Standard)から作成できます。

f:id:masatoru:20171221111721p:plain

Xamarin.Formsプロジェクト

プロジェクト作成に関しては特筆すべきことはありません。プロジェクト作成から、Cross-Platform→Cross-Platform App(Xamarin Forms)→.NET Standardを選択で作成できます。

f:id:masatoru:20171221112156p:plain

nugetからSkiaSharpとSkiaSharp.Views.Formsを取得し、XAMLにSKCanvasViewを配置します。

<views:SKCanvasView x:Name="canvas" PaintSurface="OnPaintSample"/>

SKCanvasViewのPaintSurfaceのイベントハンドラに描画処理を記述します。

void OnPaintSample(object sender, SKPaintSurfaceEventArgs e)
{
  // SKCanvasを取得する
  var canvas = e.Surface.Canvas;
  // 描画処理
  DrawText(canvas);
}

Xamarin.Macプロジェクト

Visual Studio For Macで、Macアプリ→Cocoaアプリを選択します。

SkiaSharpを使用する場合でのはまりどころとしては、ターゲットフレームワーク(オプション→全般)をXamarin.Mac Modernを選択するということです。 .Net Framework4.6.1を選択すると、パッケージの取得でWindowsと認識してしまうのか(SkiaSharp.Mac.dllではなく)SkiaSharp.WPF.dllを持ってきてしまうので(仕組みがよくわからないので誰か教えてください)。

f:id:masatoru:20171221113820p:plain

nugetからSkiaSharpとSkiaSharp.Viewsを取得します。

Storyboardを使用してSKCanvasViewを配置してプロパティを関連付けます。

  1. Main.Storyboardをダブルクリック(XCodeが開く)
  2. ViewControllerにCustomViewを配置する
  3. CustomClassをSKCanvasViewにする
  4. 右上のAssitant editorを開く
  5. 作成したCustomViewをCtrlキーを押しながらViewController.hにドラッグ&ドロップする
  6. Nameをcanvasにする

f:id:masatoru:20171221121513p:plain

上記作業後にXCodeを保存するとコードビハインド(ViewController.designer.cs)にコントロールとプロパティが関連付けされていることがわかります。

[Register ("ViewController")]
partial class ViewController
{
    [Outlet]
    SkiaSharp.Views.Mac.SKCanvasView canvas { get; set; }
    
    void ReleaseDesignerOutlets ()
    {
        if (canvas != null) {
            canvas.Dispose ();
            canvas = null;
        }
    }
}

Xamarin.Forms同様に、ViewController.csにイベントハンドラを記述します。

// 画面が初期化される時に呼ばれる
public override void ViewWillAppear()
{
  base.ViewWillAppear();

  // SKCanvasViewにイベントハンドラを関連付ける
  canvas.PaintSurface += OnPaintCanvas;
}
private void OnPaintCanvas(object sender, SKPaintSurfaceEventArgs e)
{
  // SKCanvasを取得する
  var canvas = e.Surface.Canvas;
  // 描画処理
  DrawText(canvas);
}

Storyboardの操作方法などXamarin.Macのことが詳しく書かれているEssential Xamarinをぜひご一読ください。

WPFプロジェクト

Windowsクラッシックデスクトップ→WPFアプリ(.NET Framework)→.NET Framework4.6.1以上で作成します。

XAMLへの記述以降はほとんどXamarin.Formsと同じです(クラス名が少し違うので注意)。 nugetからSkiaSharpとSkiaSharp.Viewsを取得し、XAMLにSKElementを配置します。

<views:SKElement x:Name="canvas" PaintSurface="OnPaintSample"/>

SKCanvasViewのPaintSurfaceのイベントハンドラに描画処理を記述します。

void OnPaintSample(object sender, SKPaintSurfaceEventArgs e)
{
  // SKCanvasを取得する
  var canvas = e.Surface.Canvas;
  // 描画処理
  DrawText(canvas);
}

画面キャプチャ

最後に今回作成したサンプルの画面キャプチャをのせておきます。

f:id:masatoru:20171221103637p:plain

f:id:masatoru:20171221130955p:plain

f:id:masatoru:20171221130554p:plain

f:id:masatoru:20171221103656p:plain

最後に

.NET Standard2.0がより一般的になることでWinFormsなどのレガシーなC#の資産をXamarinやMacで再利用する流れがそろそろより一層広がればいいなと思います。

今後の課題として、(今回はすべてコードビハインドで書いていますが)WPFやXamarinでPrismやReactivePropertyなどのフレームワークを使用した場合に、MacCocoa Bindingなど別のフレームワークを利用することになるので、全体としてどういう構成(落としどころ)が望ましいのか引き続き勉強したいです。

以上です。 今宵もハッピーアドベント!(言わない?)

追伸、 あ!サンプルのボタンがどれも機能してないです(汗。随時実装していきますので。

明日は tafuji さんです。よろしくお願いします!

Xamarin.Macでアプリ起動時にコードでStoryboardを切り替える方法

Xamarin.Macの起動時に複数のストーリーボードからコードで選択する方法がわからなかったのでそのメモ。

デフォルトで起動時に表示されるインターフェイスはinfo.plistのMain Interfaceで指定されています(さらにInterface Builderの[is Initial Controller]にチェックが入っているWindow Controllerが表示対象)。

f:id:masatoru:20170828125045p:plain

この起動時に表示されるインターフェイスをコードで変更する方法です。

そもそもストーリーボードの使い方がよくわかってない*1のでストーリーボードの追加方法から。

  • ストーリーボードを追加する

    • ファイル→新しいファイル→ストーリーボードでSub.storyboardを作成する
    • 作成したSub.storyboardをInterfaceBuilderで開いてWindowControllerを追加する
    • InterfaceBuilderでWindowControllerを選択→IdentityInspectorを選択→StoryboardIDにMainWindowと入力する
    • 保存する

f:id:masatoru:20170828173725p:plain

  • AppDelegate.csでDidFinishLaunchingメソッドをオーバーライド*2して以下を追加する*3
public override void DidFinishLaunching(NSNotification notification)
{
    // 呼んじゃだめ!!
    //base.DidFinishLaunching(notification);

    // StoryBoardを呼び出し
    var storyboard = NSStoryboard.FromName("Sub", null);

    // StoryBoardからViewControllerを呼び出し
    var controller = storyboard.InstantiateControllerWithIdentifier("MainWindow") as NSWindowController;

    // 表示
    controller.ShowWindow(this);
}

以上っす。

*1:Ctrlキー押しながらdragとか無理っと指つりながら作業してます。

*2:インテリセンスで入力するとbase.DidFinishLaunching()も追加されるけどこれは呼んじゃだめ(「陰」のP24参照)

*3:コードは Working with Storyboards - XamarinのLoading from codeそのまま。

iOSのSimulatorからParallels越しのVisualStudioで起動したASP.NETを参照する方法

バックエンドのテストをしていて、Parallels越しのWindowsのVisualStudioで立ち上げたASP.NETIIS)を、XamarinStudioでビルドしたiOSアプリから参照する方法がわからなかったので調べてみました。

www.barelycompetent.co.za

VisualStudioでASP.NETを起動するとローカルの任意のポート(例:http://localhost:10897/)で実行されます。 このポートはWindowsの外側からは参照できないのでMac側のiOSのSimulator(やAndroidのEmulator)からは使用することができません。そこでSharpProxyというソフトを使ってポート番号を切り替える(すり替える?)ことで実現しています。以下その手順になります。

  • WIndows側でSharpProxyをクローンしてVisualStudioでビルド&起動します。
git clone https://github.com/jocull/SharpProxy.git
  • Windows側のVisualStudioでASP.NETを起動してポート番号を確認します(例:http://localhost:10897/)。

  • WindowsIPアドレスをipconfigで調べます(例:172.xx.xx.xx)。SharpProxyでも[Your IP Address]で表示されています。

  • SharpProxyの[InternalPort]に起動したASP.NETのポート番号(10897)を入力して[Start]をクリックします。

f:id:masatoru:20170404205444p:plain

これでiOS側からParallels越しのWindowsASP.NETを参照できます。

他にもっとスマートな方法があればぜひ教えて下さい。

追伸: Xamarinはいいぞ!!(今回はXamarinに限った話じゃないけど)