はじめに
Xamarin.Forms で、内部ブラウザ(WebView)で表示したサイトで Cookie をセットし、その値を画面に表示する方法を調べてみました。
サンプルアプリ
以下の画面遷移を行う iOS と Android のサンプルアプリを作ってみました。
- 初期画面
- Cookie がセットされたらこの画面で表示する
- WebView 画面
- Cookie を保存する
開発環境は、Visual Studio Community 2017 になります。
アプリ概要
内部ブラウザで設定された Cookie は、Android はAndroid.WebKit.CookieManager
、iOS はNSHttpCookieStorage.SharedStorage.Cookies
を使用することで、Cookie の値を取得することができます。
また、Android と iOS では実装が異なるので、DependencyService という仕組みを使用することで Xamarin.Forms で動作するようにしています。
実装
Web サイト
内部ブラウザで表示する Web サイトのソースは以下のようになります。
<html> <body> <h1>Cookie Test</h1> <input type="button" onclick="document.cookie='key1=value1;expires=' + (new Date('2018/12/31 00:00:00')).toString();" value="Set Cookie"> <br /><br /> <input type="button" onclick="alert(document.cookie);" value="Get Cookie"> </body> </html>
単純に「Set Cookie」ボタンで Cookie を保存し、「Get Cookie」ボタンで Cookie の値を表示するだけです。
Xamarin.Forms プロジェクト
画面遷移を行うために、App.xaml.cs
を以下のように修正します。
public App() { InitializeComponent(); //ナビゲーションするように設定 MainPage = new NavigationPage(new MainPage()); }
DependencyService は iOS と Android で共通のインターフェースを使用することで、処理を1つにまとめることができます。
参考サイトでは、Splat という DI コンテナを使用していましたが複雑になるため、サンプルアプリでは DependencyService を使用しています。
共通のインターフェースは、IPlatformCookieStore.cs
に記述します。
using System; using System.Collections.Generic; using System.Text; using System.Net; using System.Net.Http; namespace CookieAccess { public interface IPlatformCookieStore { IEnumerable<Cookie> CurrentCookies { get; } } }
MainPage.xaml
は以下のようになります。
<?xml version="1.0" encoding="utf-8" ?> <ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:local="clr-namespace:CookieAccess" x:Class="CookieAccess.MainPage"> <StackLayout HorizontalOptions="Center" VerticalOptions="Center"> <Label x:Name="label" Text="Welcome to Xamarin.Forms!" /> <Button x:Name="buttonNavi" Clicked="buttonNavi_Clicked" Text="Move to webpage" /> <Button x:Name="buttonCookie" Clicked="buttonCookie_Clicked" Text="Show cookies" /> </StackLayout> </ContentPage>
コードビハインドのMainPage.xaml.cs
は、以下のようになります。
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using Xamarin.Forms; namespace CookieAccess { public partial class MainPage : ContentPage { public MainPage() { InitializeComponent(); } private void buttonCookie_Clicked(object sender, EventArgs e) { //DependencyServiceを使用して、iOS/Androidで異なるコードを実行しCookieを取得する var cookies = DependencyService.Get<IPlatformCookieStore>().CurrentCookies; StringBuilder buf = new StringBuilder(); foreach (var cookie in cookies) { buf.Append(cookie.Name + "=" + cookie.Value + ","); } //Cookieの値を表示する label.Text = buf.ToString(); } private void buttonNavi_Clicked(object sender, EventArgs e) { //ページ遷移する Navigation.PushAsync(new WebPage()); } } }
Web サイトを表示する WebPage.xaml
は以下のようになります。www.example.com
をご自分のドメインに書き換えてください。
<?xml version="1.0" encoding="utf-8" ?> <ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="CookieAccess.WebPage"> <ContentPage.Padding> <!--iOS用にパディングを確保--> <OnPlatform x:TypeArguments="Thickness"> <On Platform="iOS" Value="0, 10, 10, 0" /> </OnPlatform> </ContentPage.Padding> <ContentPage.Content> <StackLayout> <WebView Source="https://www.example.com" VerticalOptions="FillAndExpand" HorizontalOptions="FillAndExpand"/> </StackLayout> </ContentPage.Content> </ContentPage>
Xamarin.Android プロジェクト
Xamarin.Android プロジェクトでも、IPlatformCookieStore
インターフェースを実装したクラスを作成します。
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Net; using Android.App; using Android.Content; using Android.OS; using Android.Runtime; using Android.Views; using Android.Widget; using Android.Webkit; //Dependency属性 [assembly: Xamarin.Forms.Dependency(typeof(CookieAccess.Droid.DroidCookieStore))] namespace CookieAccess.Droid { class DroidCookieStore : IPlatformCookieStore { private readonly string _url = "https://www.example.com"; private readonly object _refreshLock = new object(); public IEnumerable<Cookie> CurrentCookies { get { return RefreshCookies(); } } private IEnumerable<Cookie> RefreshCookies() { lock (_refreshLock) { // .GetCookie returns ALL cookies related to the URL as a single, long // string which we have to split and parse var allCookiesForUrl = CookieManager.Instance.GetCookie(_url); if (string.IsNullOrWhiteSpace(allCookiesForUrl)) { Console.WriteLine(string.Format("No cookies found for '{0}'. Exiting.", _url)); yield return new Cookie("none", "none"); } else { Console.WriteLine(string.Format("\r\n===== CookieHeader : '{0}'\r\n", allCookiesForUrl)); var cookiePairs = allCookiesForUrl.Split(' '); foreach (var cookiePair in cookiePairs.Where(cp => cp.Contains("="))) { // yeah, I know, but this is a quick-and-dirty, remember? ;) var cookiePieces = cookiePair.Split(new[] { '=' }, StringSplitOptions.RemoveEmptyEntries); if (cookiePieces.Length >= 2) { cookiePieces[0] = cookiePieces[0].Contains(":") ? cookiePieces[0].Substring(0, cookiePieces[0].IndexOf(":")) : cookiePieces[0]; // strip off trailing ';' if it's there (some implementations // on droid have it, some do not) cookiePieces[1] = cookiePieces[1].EndsWith(";") ? cookiePieces[1].Substring(0, cookiePieces[1].Length - 1) : cookiePieces[1]; yield return new Cookie() { Name = cookiePieces[0], Value = cookiePieces[1] }; } } } } } } }
Xamarin.iOS プロジェクト
Xamarin.iOS プロジェクトでは、IPlatformCookieStore
インターフェースを実装したクラスを作成します。
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Net; using Foundation; using UIKit; //Dependency属性 [assembly: Xamarin.Forms.Dependency(typeof(CookieAccess.iOS.IOSCookieStore))] namespace CookieAccess.iOS { class IOSCookieStore : IPlatformCookieStore { private readonly object _refreshLock = new object(); public IEnumerable<Cookie> CurrentCookies { get { return RefreshCookies(); } } private IEnumerable<Cookie> RefreshCookies() { lock (_refreshLock) { foreach (var cookie in NSHttpCookieStorage.SharedStorage.Cookies) { yield return new Cookie { Name = cookie.Name, Value = cookie.Value, }; } } } } }
動作確認
Android
初期表示の画面になります。
Web サイトで Cookie を設定します。
画面に Cookie の値が正しく表示されました。
iOS (iPhone)
初期表示の画面になります。
Web サイトで Cookie を設定します。
画面に Cookie の値が正しく表示されました。
おわりに
内部ブラウザ(WebView)で設定した Cookie の値を、Xamarin.Forms の iOS と Android で取得する方法を見てきました。
これにより、OAuth などの認証結果を Cookie に保存して、Xamarin.Forms で活用することができるようになりますね。
最初は、内部ブラウザではなく、iOS なら Safari、Android なら Chrome といった外部ブラウザで値を取得しようとしたのですが、うまくいきませんでした。
セキュリティ的には外部ブラウザの方法が勧められていますが、Cookie では値の受け渡しができないようです。
外部ブラウザとの値の受け渡しは今後の課題としたいと思います。