VisualStudio 「サービス参照の追加」で追加した ServiceClient のバインディング情報を app.config 以外から取得したい

VisualStudio で開発しているプロジェクト内で WebService を利用する場合、
「サービス参照の追加」を利用してサービスにアクセスするためのクライアントクラスを自動生成すると便利です。

[(MSDN)方法 : Web サービスへの参照を追加する](https://msdn.microsoft.com/ja-jp/library/bb628649.aspx)

そうすると、app.config / web.config に WebService の接続に関する設定が出力され、
プログラム実行時に動的に読み込まれてサービスクライアントを初期化します。

```xml:app.config
  <system.serviceModel>
    <bindings>
      <customBinding>        
        <binding name="CustomSoapBinding" openTimeout="00:01:00" receiveTimeout="00:01:00">
          <textMessageEncoding messageVersion="Soap12" />
          <httpTransport maxReceivedMessageSize="4096000" />
        </binding>
      </customBinding>
    </bindings>

    <client>
      <endpoint address="http://hogehoge.net/Service/Endpoint1"
        binding="customBinding" bindingConfiguration="CustomSoapBinding"
        contract="Endpoint1.Endpoint1" name="Service1Port" />
    </client>
  </system.serviceModel>
```
## プロジェクト内に「サービス参照の追加」をした状態
![サービス参照の追加をしたプロジェクト](https://dl.dropboxusercontent.com/s/mxdn1yn8uj1hs5k/service01.png)


１つのプロジェクトで利用する場合はこれにて終了なのですが、
複数のプロジェクトで同じ WebService にアクセスするとき、
DLL に サービスクライアントを用意して、各プロジェクトはそのＤＬＬを参照して WebService にアクセスしたくなりました。
## やりたいことは、こんなイメージ
![複数クライアントアプリで同じサービスに接続したい](https://dl.dropboxusercontent.com/s/li8fh46xmrv9bfx/service02.png)

このとき問題になったのが、この部分です。

> app.config / web.config に WebService の接続に関する設定が出力され、
> プログラム実行時に動的に読み込まれてサービスクライアントを初期化します。

## こういう風にしないといけないみたい
DLL を参照している各プロジェクトの app.config / web.config 全てに対して
WebService に接続するための設定を用意しなければなりません。

![嫌な設定](https://dl.dropboxusercontent.com/s/50b6efrajrbd78e/service03.png)
**・・・・・いやだ！！**
サービスクライアントＤＬＬを参照するたびに app.config に設定を転記しないといけないのはとてもめんどうです。

## 私が求めた理想形
![理想形](https://dl.dropboxusercontent.com/s/5uxzratdqmatrx4/service04.png)

## これを実現するためにメソッド
「サービス参照の追加」でサービスクライアントを追加したプロジェクトのアセンブリ( DLL )と同じ名前の設定ファイルを読みに行くメソッド。 

```cs:ServiceClientFactory.cs
  public static T createServiceClient<T>()
    where T : class 
    {
      Type tt = typeof(T);
      T ret = null;
      // [このメソッドが格納されたアセンブリ].dll.config を読み込む
      var assembly = typeof(ClientFactory).Assembly.GetName().EscapedCodeBase;
      var configPath = System.IO.Path.Combine(new Uri(assembly).LocalPath);

      if (System.IO.File.Exists(configPath))
      {
        // 設定ファイルが見つかったら解析
        var config = System.Configuration.ConfigurationManager.OpenExeConfiguration(configPath);
        var smsg = System.ServiceModel.Configuration.ServiceModelSectionGroup.GetSectionGroup(config);
        ChannelEndpointElement en = null;
        foreach (ChannelEndpointElement e in smsg.Client.Endpoints)
        {
          // ジェネリックで指定されたクラスと同じ Name の Endpoint を検索する
          if (e.Name.Contains(tt.Namespace.Split('.').Last()))
          {
            en = e;
            break;
          }
        }
        // 上の検索で見つかった Endpoint のバインディング設定を取得する
        var elm = smsg.Bindings.CustomBinding.Bindings[en.BindingConfiguration];
        var binding = new System.ServiceModel.Channels.CustomBinding();
        elm.ApplyConfiguration(binding);    // カスタムバインディング定義から、このEndpoint で使用するバインディング情報を割り当てる

        // ここまで読み込んできた Endpoint の設定で、サービスクライアントオブジェクトを初期化します。
        ret = (T)tt.GetConstructor(new Type[] { typeof(System.ServiceModel.Channels.Binding),
                                                typeof(System.ServiceModel.EndpointAddress)})
                                  .Invoke(new object[] { binding,
                                          new System.ServiceModel.EndpointAddress(en.Address) });
      }
      else
      {
        // 設定ファイルが見つからない時は、既定のコンストラクタで初期化する
        // この場合、このメソッドを呼び出しているアセンブリの設定ファイル (app.config /web.config )で
        // サービスクライアントオブジェクトが初期化されます。
        ret = (T)tt.GetConstructor(Type.EmptyTypes).Invoke(null);
      }

    return ret;
  }
```
## メソッドを利用する側のコード
```cs:Driver.cs
  void Hogehoge()
  {
    var client = createServiceClient<ServiceClientClassName>();
    .
    .
    .
  }
```
引数にパスを受け取るようにして、任意のファイル名で Binding の設定を保存することもできるかと思います。