前回はXamarin Android Playerをインストールしました。

Problem

モバイルであっても、利用する場所が野外であるとは限らなくて、社内や自宅などのLANであることも多々あります。
そのような、利用者が不特定多数ではない、利用者数が予測しやすい規模のWebサービスの場合、大規模なサーバーどころか、小さなクライアントサーバー型アプリでサーバー側を非サーバーOS上に構築したサーバーアプリで賄うこともあるでしょう。

そういうシチュエーションならWCFは簡単に構築できますし、うってつけです。
でも、XamarinでWCFがどうするのでしょうか?特にPCLの制約などで面倒な感じがします。

そのあたりのことが下記のドキュメントにまとめられています。

しかし、この記事は、PCLは使っておらず、iOS、Androidのプロジェクトに対してWCFのクライアントを追加しています。
これではコードの共通化の意味がありません。

なので、これを解決します。

今回のソースです。

Resolution

下準備

まず、いつものように、XamarinのBlankアプリをPCLで作成します。
その後、Windows 8.1Windows Phone 8.1向けのプロジェクトを除去します。
今回、PCLにWCFクライアントを実装する場合、Windows Phone 8.1は使えません。

次に、ソリューションのNugetパッケージの管理を開き、Xamarin.Formsを全てのプロジェクトからアンインストールします。

Xamarin.Formsのアンインストール
Xamarin.Formsのアンインストール

場合によっては、再起動を求められますので、ここで再起動します。
続いて、PCLのプロジェクトのプロパティから、ライブラリのタブのターゲットを設定する個所から変更ボタンを押下します。

ターゲットの変更
ターゲットの変更

そして、ターゲットの変更ダイアログからWindows Phone 8.1のチェックを外します。

Windows Phone 8.1は外す
Windows Phone 8.1は外す

Windows 8をWindows 10にしても変更できないのでそのままでOKです。

最後に、再びソリューションのNugetパッケージの管理を開き、Xamarin.Formsを全てのプロジェクトにインストールします。
場合によっては、また再起動を求められますので、ここで再起動します。

WCFサービスの追加

特にWCFサービスの制限はないです。
ここでは、Xamarin.WcfというWPFプロジェクトを追加します。
また、WCFとDataContractを使用するために、System.Runtime.SerializationSystem.ServiceModelを参照に追加します。
さらに、MVVMのために、NugetでPrism.WpfPrism.MvvmPrism.Unityを追加しています。

下記に、サービスの定義とconfigを記載します。

MessageService.cs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
using System;
using System.Runtime.Serialization;
using System.ServiceModel;

namespace Xamarin.Wcf.Services
{

[System.ServiceModel.ServiceContract]
public interface IMessageService
{

event EventHandler ReceivedImage;

[System.ServiceModel.OperationContract]
void SendCameraUmage(MessageDto message);

}

[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
public sealed class MessageService : IMessageService
{

public event EventHandler ReceivedImage;

public void SendCameraUmage(MessageDto message)
{
this.ReceivedImage?.Invoke(this, message);
}

}

[DataContract(Name = "messageDto")]
public sealed class MessageDto
{

public MessageDto(string sender, string message)
{
this.Sender = sender;
this.Message = message;
}

[DataMember(Name = "sender")]
public string Sender
{
get;
set;
}

[DataMember(Name = "message")]
public string Message
{
get;
set;
}
}

}

App.config

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6" />
</startup>
<system.serviceModel>
<client />
<behaviors>
<serviceBehaviors>
<behavior name="BasicConfiguration">
<serviceMetadata httpGetEnabled="true" httpGetUrl="http://localhost:8080/Xamarin.Wcf/MessageService/mex" />
</behavior>
</serviceBehaviors>
</behaviors>
<bindings>
<basicHttpBinding>
<binding name="BasicHttpBinding" />
</basicHttpBinding>
</bindings>
<services>
<service behaviorConfiguration="BasicConfiguration" name="Xamarin.Wcf.Services.MessageService">
<endpoint address="http://localhost:8080/CameraViewer/MessageService"
binding="basicHttpBinding" bindingConfiguration="BasicHttpBinding"
contract="Xamarin.Wcf.Services.IMessageService" />
</service>
</services>
</system.serviceModel>
</configuration>

他にViewを定義していますが、ここでは重要ではないので無視します。詳しくはソースを参照してください。

WCFサービスのProxyクラスの生成

前項で作成したWCFサービスを実行します。実行する際、管理者権限で実行しないと例外を投げて落ちます。
無事に実行し、Webブラウザから http://localhost:8080/Xamarin.Wcf/MessageService/mex にアクセスします。
無事にアクセスできれば、下記のようにwsdlの定義が表示されるはずです。

WSDLの公開
WSDLの公開

表示を確認後、コマンドプロンプトを起動します。
カレントディレクトリはどこでも良いですが、ファイルが2つ出力されるので、わかりやすい場所に移動してください。
移動後、下記のコマンドを実行します。

"C:\Program Files (x86)\Microsoft SDKs\Silverlight\v5.0\Tools\SlSvcUtil.exe" http://localhost:8080/Xamarin.Wcf/MessageService/mex

実行が成功すれば、カレントディレクトリにMessageService.csServiceReferences.ClientConfigが出力されます。
生成後、csファイルをPCLプロジェクトに追加します。ClientConfigは無くてもよいです。

ここで注意ですが、生成する際に使用するプログラムは、SvcUtil.exeではなく、SlSvcUtil.exeを使ってください。
SvcUtil.exeでも生成には成功しますが、生成されるクラス群にSystem.Runtime.Serialization.IExtensibleDataObject等が使用されますが、このクラスは、PCLでWindows Phone 8.1が含まれていると使用できません。
そのため、最初の下準備でWindows Phone 8.1を除外したわけです。

Service Clientを呼び出す

残念ながら、configファイルを配置して、自動的にBindingを行うようなことはできません。
ですので、コードからBindingを行います。
下記はService Client生成処理の抜粋ですが、意図はつかめると思います。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
private void UpdateServiceClient()
{
if (this._MessageServiceClient != null)
{
return;
}

var binding = new BasicHttpBinding()
{
Name = "basicHttpBinding",
MaxReceivedMessageSize = 67108864,
};

var timeout = new TimeSpan(0, 1, 0);
binding.SendTimeout = timeout;
binding.OpenTimeout = timeout;
binding.ReceiveTimeout = timeout;
this._MessageServiceClient = new MessageServiceClient(
binding,
new EndpointAddress($"http://{this.IpAddress}:8080/Xamarin.Wcf/MessageService"));
}

IPAddressだけは、外部から設定できるようにしてあります。
これによって、各端末で設定として保存した値をもとにクライアントを構築できます。
応用すれば、ポート番号、タイムアウト値等も可変にできます。

最後に、WCFの呼び出し処理の抜粋です。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public ICommand SendCommand
{
get
{
if (this._SendCommand == null)
{
this._SendCommand = new DelegateCommand(
() =>
{
this.UpdateServiceClient();

var message = new messageDto();
message.message = this._Message;
message.sender = ServiceManager.Sender;
this._MessageServiceClient.SendMessageAsync(message);
},
() =>
{
return !string.IsNullOrWhiteSpace(this.IpAddress);
});
}

return this._SendCommand;
}
}

非常にシンプルです。
ServiceManager.Senderは各プロジェクトのスタートアップコードでiOS、Android、UWPという文字列を設定して、それを参照しているだけです。
つまり、どの端末からメッセージが飛んできたのかを判断するためだけの識別です。

テスト

では、実際にテストしてみましょう。
注意ですが、WCFサービスアプリを起動する端末のポート8080は解放しておいてください。面倒ならファイアーウォールを一時的に無効にしてもよいです。

iOS

メッセージ送信
メッセージ送信

メッセージ受信
メッセージ受信

Android

メッセージ送信

メッセージ送信

メッセージ受信

メッセージ受信

UWP

メッセージ送信
メッセージ送信

メッセージ受信
メッセージ受信

Conclusion

非常に簡単にクライアントサーバー型のアプリが完成しました。
一度プロジェクトを作っておけば、今後はこれをベースに拡張できそうです。

Source Code

https://github.com/takuya-takeuchi/Demo/tree/master/Xamarin/07_Xamarin.Forms.Portable7