(Xamarin)Webサイトのオフライン閲覧アプリを作るときの注意

メモ書きなのでしばらく更新が続くと思いますんで悪しからず


# ローカルに落とす

- HTTPで落としてきてアプリ用ストレージに保存。
    - それぞれAPIのリファレンスを見ればわかると思いますので……

### HttpClient

- ``System.Net.Http.HttpClient``でクロスプラットフォームで書ける。URLを打てばストリームが落ちてくる。細かい加工が必要な場合``System.Net.Http.HttpClientHandler``を使う（[参考](http://blog.okazuki.jp/entry/20121225/HttpClientTips)）。
- キャッシュされるのを防ぐ場合、下記コードを追加

```
var httpClientHandler = new HttpClientHandler {  };
var httpClient = new HttpClient(httpClientHandler);
httpClient.DefaultRequestHeaders.CacheControl = new System.Net.Http.Headers.CacheControlHeaderValue();
httpClient.DefaultRequestHeaders.CacheControl.NoCache = true;
httpClient.DefaultRequestHeaders.CacheControl.NoStore = true;
```
- 例外は適当に拾うこと

### ストレージ

落としてきたHTTPストリームをファイルストリームに流す。``System.IO.IsolatedStorage``（[参考](http://qiita.com/amay077/items/672aaf058357a4ecf8a5)）を使うか[PCLStorage](https://github.com/dsplaisted/PCLStorage)を使う。個人的にはPCLStorageを使用。

# WebViewに表示する

### BaseUrl
* アプリに内包させる場合
* ``PCLStorage``や``System.IO.IsolatedStorage``を使って動的に落とす場合
    * Android: ``"file://" + Environment.GetFolderPath(Environment.SpecialFolder.Personal) + "/"``
    * Windows Device Local: ``ms-appdata:///local/``（[参考](https://docs.microsoft.com/ja-jp/windows/uwp/controls-and-patterns/web-view)）
        * ただし、このフォルダにさらにサブフォルダを置く必要がある（[参考](http://wakiama.hatenadiary.jp/entry/2016/02/20/120728)）

# その他ハマってた点

以下に列挙するのはいろいろ試してた部分で、OSによって必要かもしれないし必要ないかもしれない

## XamarinのWebViewのカスタムビューで内部用のアクセス権限を与える

VS2017で動くコードは下記の通り（任意ファイル名のクラスを追加）。どれも完全に同じではないが、ほぼ正しく書いてあるのは[公式のブログ記事](https://xamarinhelp.com/webview-rendering-engine-configuration/)と[こちら](http://mattsudev.hatenablog.com/entry/2015/07/13/183934)。

yourcode.xaml.cs側でWebViewを継承したCustomWebViewが定義されていて、yourcode.xamlでの指定もYourProject.CustomWebViewで指定する必要あり

```
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;

[assembly: ExportRenderer(typeof(YourProject.CustomWebView), typeof(YourProject.Droid.CustomWebViewRenderer))]
namespace YourProject.Droid
{
	public class CustomWebViewRenderer : WebViewRenderer
	{
		protected override void OnElementChanged(ElementChangedEventArgs<WebView> e)
		{
			base.OnElementChanged(e);
			if (Control == null) return;

			//javascriptのfileスキームへのアクセス許可
			Control.Settings.AllowUniversalAccessFromFileURLs = true;
			Control.Settings.AllowFileAccessFromFileURLs = true;

			// 後述
			Control.AddJavascriptInterface(new MyJSInterface(Context), "CSharp");

			// チェック用
			// System.Console.WriteLine("OnElementChanged CustomWebViewRenderer");
		}
	}

	// 後述
	class MyJSInterface : Java.Lang.Object
	{
		Context context;

		public MyJSInterface(Context context)
		{
			this.context = context;
		}

		[Java.Interop.Export]
		[Android.Webkit.JavascriptInterface]
		public void ShowToast(string s)
		{
			System.Console.WriteLine("ShowToast:" + s);
		}
	}
}
```

### androidでblobやmailto:が使えない

これらを使うというマニア向けの解説。[これ](http://satohu20xx.hateblo.jp/entry/2012/03/01/213550)や[これ](http://sonic.blue/it/801)や[これ](http://stackoverflow.com/questions/10069050/download-file-inside-webview)や[これ](http://stackoverflow.com/questions/24687559/how-to-pass-a-blob-object-from-javascript-to-android)を参考にしたが一向に埒が明かなかった。

→暫定的解決策：[JS側からC#のコードを呼ぶ方法](https://developer.xamarin.com/recipes/android/controls/webview/call_csharp_from_javascript/)を使うことにした。サンプルの場合、JSにwindow.CSharp.ShowToastという関数が定義される（string型の引数をつけたければつけられる）ので、その存在確認をして、存在していればそちらを使い、そうでなければ通常のブラウザ用の処理を行うという分岐をつけた。サイト側にオフライン閲覧アプリ専用のコードを組み込む必要があり用途が限られてしまうが、今回はこれで事足りる。


## ajaxのステータスコードが0である場合

ローカルリソースにfile://などでアクセスする場合、xmlhttprequestのステータスコードが成功時にも0で返ってくることがある（[参考](http://stackoverflow.com/questions/40319087/android-webview-xmlhttprequest-returns-status-of-zero-0)）。しかしdataのほうを見ると中身がしっかり返ってきているので、中身を見て判断するしかない。

## HttpListenerを使って内部に疑似HTTPサーバを組む
（本格的に組んでるわけではないですが、AndroidとiOSでは機能することは確認しました。なぜか本家のほうのUWPでは動かない。Windows Phone 8では当然ダメ）

- [C#/MacOS/簡単なWebサーバーを作る](http://qiita.com/wakaba260/items/ebd0136cfb71388dbde2)
- [.NET TIPS簡易Webサーバを実装するには？](http://www.atmarkit.co.jp/fdotnet/dotnettips/695httplistener/httplistener.html)
- [Simple-Http-Listener-PCL](https://github.com/1iveowl/Simple-Http-Listener-PCL)
- [Simple C# Web Server](https://codehosting.net/blog/BlogEngine/post/Simple-C-Web-Server)