SilverlihtアプリのWebサービスコール設定をXAPから外出しする(その1)

WCFなどのエンドポイントの設定はServiceReferences.ClientConfigファイルで行います。しかし、ServiceReferences.ClientConfigファイルは、XAP内に含める必要があります。このため、気軽にデプロイ環境に合わせてWebサービスの設定を変更することができません。
この問題を解決するために、Webサービスの設定をXMLファイルに記述し、このXMLファイルをXAPの外における機能を実装します。

アジェンダ

  • 機能の概要とクラス構成
  • XMLファイルで設定する項目
  • XMLファイルの仕様
  • ServiceInfoクラス
  • ServiceConfigクラス
  • ServiceClientParserクラス
  • 明日以降に続く

機能の概要とクラス構成

Webサービス設定の外出し機能はSilverlightアプリ起動時に下記のような流れで動作します。

  1. XMLファイルをダウンロードする。
  2. パーサがXMLファイルからWebサービス設定情報に変換する。
  3. サービスクライアントビルダーにWebサービス設定情報をセットする。

これで、サービスクライアントビルダーの初期化が完了します。これで、サービスクライアントビルダーがサービスクライアントを生成することができるようになります。
クラス構成を示します。

XMLファイルで設定する項目

次にXMLファイルの仕様を決定するために、XMLファイルで設定したい項目を列挙します。

  • Webサービスが配置されているURL
  • 通信形式(通常のHttp通信か、Binaryに変換した通信かの二択)
  • 受信できる最大メッセージ サイズ
  • タイムアウト時間

XMLファイルの仕様

  • 設置場所 … xapが配置されている場所
  • 名前 … Client.config.xml
タグまたはアトリビュート 概要 出現数
config 設定全般 1回
services Webサービスの設定 1回
domain Webサービスが配置されているサーバのURL指定 0回以上
domai.uri Webサービスが配置されているサーバのURLの値 1回
service Webサービスの接続設定 0回以上
service.id Webサービス設定を識別する値 1回
service.substance Webサービスの配置場所のドメインUriの差分の値 1回
service.binding Webサービスとの通信方式の値(binaryを指定するとバイナリ通信、それ以外の値だとBasicHttpBinding) 0または1回
service.timeOut サーバと通信時のタイムアウト時間(指定なしでは、デフォルト値を使用) 0または1回
service.maxReceivedMessageSize 受信できる最大メッセージ サイズの値(指定なしでは、デフォルト値を使用) 0または1回

XMLファイルの例を示す。

<config>
  <services>
    <domain uri="http://localhost:61730/">
      <service id="TestService1" 
               substance="WebService/TestService1.svc" 
               binding="binary1" 
               timeOut="6000000" 
               maxReceivedMessageSize="2147483647"/>
      <service id="TestService2" substance="WebService/TestService2.svc" binding="binary"/>
    </domain>
    <domain uri="http://www.dummy.com/">
      <service id="DummyService" substance="DummyService.svc"/>
    </domain>
  </services>
</config>

ServiceInfoクラス

ServiceInfoクラスは、Webサービスごとの接続情報を保持するデータクラスです。

public class ServiceInfo
{
    /// <summary>サービス情報の識別子</summary>
    public string ID { get; set; }

    /// <summary>domainとsubstanceを合成して作成したサービスのUri</summary>
    public EndpointAddress EndpointAddress { get; set; }

    /// <summary>サービスコール用Binding</summary>
    public Binding Binding { get; set; }
}

ServiceConfigクラス

ServiceConfigクラスは、Webサービスの接続情報を保持する機能を提供するクラスです。このクラスもヘルパーメソッド以上の機能を持っていません。

public class ServiceConfig
{
    private List<ServiceInfo> serviceInfos;

    public ServiceConfig(List<ServiceInfo> serviceInfos)
    {
        this.serviceInfos = serviceInfos;
    }

    /// <summary>サービスIDに対応したServiceInfoを取得する。</summary>
    /// <param name="serviceID"></param>
    /// <returns></returns>
    public ServiceInfo GetServiceInfo(string serviceID)
    {
        var q = from info in this.serviceInfos
                where info.ID == serviceID
                select info;
        if (q.Count() > 0)
        {
            return q.First();
        }
        else
        {
            throw new ArgumentException("指定されたserviceIDに対応するServiceDataが見つかりませんでした。");
        }
    }
}

ServiceClientParserクラス

ServiceClientParserクラスは、ダウンロードしてきたXMLファイルからServiceConfigインスタンスを生成するクラスです。かなり長いのですが、ソースを乗せます。

public class ServiceClientParser
{
    /// <summary>設定文字列からServiceConfigを生成する。</summary>
    /// <param name="configStr"></param>
    /// <returns></returns>
    public ServiceConfig Parse(string configStr)
    {
        List<ServiceInfo> serviceInfos = this.CreateServiceInfos(configStr);
        ServiceConfig config = new ServiceConfig(serviceInfos);
        return config;
    }

    /// <summary>ServiceInfoのリストを生成する。</summary>
    /// <param name="configStr"></param>
    /// <returns></returns>
    private List<ServiceInfo> CreateServiceInfos(string configStr)
    {
        List<ServiceInfo> serviceInfos = new List<ServiceInfo>();

        // XML化し、services要素を取得する。
        XDocument xml = XDocument.Parse(configStr);
        XElement configElement = xml.Element("config");
        XElement services = configElement.Element("services");

        foreach (XElement domain in services.Elements("domain"))
        {
            // ドメインURLを取得する。
            string domainUri = domain.Attribute("uri").Value;
            foreach (XElement service in domain.Elements("service"))
            {
                ServiceInfo info = new ServiceInfo();

                // サービスIDをセットする。
                info.ID = service.Attribute("id").Value;

                // EndpointAddressをセットする。
                string substance = service.Attribute("substance").Value;
                info.EndpointAddress = this.GetEndpointAddress(domainUri, substance);

                // Bindingをセットする。
                info.Binding = this.GetBinding(service);

                // リストにServiceInfoインスタンスを追加する。
                serviceInfos.Add(info);
            }
        }

        return serviceInfos;
    }

    /// <summary>EndpointAddressを取得する。</summary>
    /// <param name="domainUri"></param>
    /// <param name="substance"></param>
    /// <returns></returns>
    private EndpointAddress GetEndpointAddress(string domainUri, string substance)
    {
        EndpointAddress address = new EndpointAddress(domainUri + substance);
        return address;
    }

    /// <summary>Bindingを取得する。</summary>
    /// <param name="service"></param>
    /// <returns></returns>
    private Binding GetBinding(XElement service)
    {
        // バインディングのタイプと最大受信サイズを取得する。
        string bindingType = this.GetBindingType(service);
        int? maxReceivedMessageSize = this.GetMaxReceivedMessageSize(service);

        Binding binding;
        if (bindingType == "binary")
        {
            // バイナリの場合、CustomBindingでBindingを生成する。
            BindingElementCollection elements = new BindingElementCollection();
            elements.Add(new BinaryMessageEncodingBindingElement());

            HttpTransportBindingElement htbe = new HttpTransportBindingElement();

            if (maxReceivedMessageSize.HasValue)
            {
                // 最大受信サイズを設定する。
                int size = maxReceivedMessageSize.Value;
                htbe.MaxReceivedMessageSize = size;
                htbe.MaxBufferSize = size;
            }

            elements.Add(htbe);

            binding = new CustomBinding(elements);
        }
        else
        {
            // bindingTypeがbinary以外の場合、BasicHttpBindingでBindingを生成する。
            BasicHttpBinding basicHttpBinding = new BasicHttpBinding();
            if (maxReceivedMessageSize.HasValue)
            {
                // 最大受信サイズを設定する。
                int size = maxReceivedMessageSize.Value;
                basicHttpBinding.MaxReceivedMessageSize = size;
                basicHttpBinding.MaxBufferSize = size;
            }

            binding = basicHttpBinding;
        }

        long? timeOutValue = this.GetTimeOutValue(service);
        if (timeOutValue.HasValue)
        {
            // タイムアウトを設定する。
            TimeSpan ts = TimeSpan.FromMilliseconds(timeOutValue.Value);
            binding.OpenTimeout = ts;
            binding.SendTimeout = ts;
            binding.CloseTimeout = ts;
            binding.ReceiveTimeout = ts;
        }

        return binding;
    }

    /// <summary>バインディングタイプを取得する。</summary>
    /// <param name="service"></param>
    /// <returns></returns>
    private string GetBindingType(XElement service)
    {
        XAttribute bindingAttribute = service.Attribute("binding");
        string bindingType = string.Empty;
        if (bindingAttribute != null)
        {
            bindingType = bindingAttribute.Value;
        }

        return bindingType;
    }

    /// <summary>最大受信メッセージサイズを取得する。</summary>
    /// <param name="service"></param>
    /// <returns></returns>
    private int? GetMaxReceivedMessageSize(XElement service)
    {
        string attributeName = "maxReceivedMessageSize";
        return (int?)this.GetValue(service, attributeName);
    }

    /// <summary>タイムアウト時間を取得する。</summary>
    /// <param name="service"></param>
    /// <returns></returns>
    private long? GetTimeOutValue(XElement service)
    {
        string attributeName = "timeOut";
        return this.GetValue(service, attributeName);
    }

    /// <summary>XML要素から属性の値を取得する。</summary>
    /// <param name="service"></param>
    /// <param name="attributeName"></param>
    /// <returns></returns>
    private long? GetValue(XElement service, string attributeName)
    {
        XAttribute att = service.Attribute(attributeName);
        if (att != null)
        {
            long timeOut;
            if (long.TryParse(att.Value, out timeOut))
            {
                return timeOut;
            }
            else
            {
                return null;
            }
        }
        else
        {
            return null;
        }
    }
}

明日以降に続く

長くなってしまったので、次の記事に続きます。