WebSurfer's Home

トップ > Blog 1   |   ログイン
APMLフィルター

HttpClient で WCF サービスを呼出

by WebSurfer 2018年2月24日 23:49

先の記事「HttpWebRequest で WCF サービスを呼出」では、JSON 文字列をデータとしてやり取りする WCF サービスのメソッドを HttpWebRequest / HttpWebResponse を利用して呼び出して JSON 文字列のデータを取得し、それを逆シリアル化して C# のオブジェクトに変換する方法を書きました。

HttpWebRequest / HttpWebResponse に代えて HttpClient を利用し、同じ WCF サービスのメソッドを呼び出してデータを取得するサンプルを書きます。 (呼び出し先の WCF サービスは「WCF と jQuery AJAX」を参照)

参考にしたのは Call a Web API From a .NET Client (C#) という記事ですので合わせて見ていただくと良いと思います。

まず、先の HttpWebRequest / HttpWebResponse を利用した記事と同様に、以下のクラス / データコントラクト定義を行います。

[DataContract]
public class RootObject
{
    // GetCarsByDoorsResult は WCF サービスメソッドに付与した
    // BodyStyle = WebMessageBodyStyle.WrappedRequest による
    // ラップの名前(ラップするのはセキュリティ対策)
    [DataMember]
    public List<Car> GetCarsByDoorsResult { get; set; }
}

[DataContract]
public class Car
{
    [DataMember]
    public string Make { get; set; }

    [DataMember]
    public string Model { get; set; }

    [DataMember]
    public int Year { get; set; }

    [DataMember]
    public int Doors { get; set; }

    [DataMember]
    public string Colour { get; set; }

    [DataMember]
    public float Price { get; set; }
}

参考にした Microsoft の記事ではデシリアライズに HttpContentExtensions.ReadAsAsync<T> メソッド (HttpContent) を使っていますが、今回の例のような入れ子になったオブジェクトはデシリアライズできないようです。

なので、先の記事と同様、DataContractJsonSerializer クラスを利用して応答ストリームに含まれる JSON 文字列を C# のオブジェクトにデシリアライズすることにしました。

HttpClient.SendAsync メソッドを利用して、JSON 文字列を WCF サービスの GetCarsByDoors(int doors) メソッドに POST 送信します。以下のコードの例では 5 ドア車を要求しています。

応答の HttpContent が HttpClient.SendAsync メソッドの戻り値として返されますので、DataContractJsonSerializer クラスを利用して応答ストリームに含まれる JSON 文字列を C# のオブジェクトにデシリアライズします。

詳しくは以下のサンプルコードとそれに付与したコメントを見てください。

using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Json;
using System.Threading.Tasks;
using System.Net.Http;
using System.IO;

namespace ConsoleAppHttpClientWCF
{
  class Program
  {
    static void Main(string[] args)
    {
      // GetCarsAsync メソッドの引数で 5 ドアを要求
      RootObject rootObject =
          GetCarsAsync(5).GetAwaiter().GetResult();
      ShowCars(rootObject);
    }

    static async Task<RootObject> GetCarsAsync(int doors)
    {
      using (HttpClient httpClient = new HttpClient())
      {
        string url = "http://.../GetCarsByDoors";

        // POST 送信を指定
        HttpRequestMessage request =
          new HttpRequestMessage(HttpMethod.Post, url);

        // POST 送信する JSON 文字列
        string postData = 
            "{\"doors\":" + doors.ToString() + "}";

        // Content-Type: application/json; charset=utf-8 が
        // 要求ヘッダに必要。それを POST 送信する JSON 文字
        // 列と共にここで設定
        request.Content = new StringContent(postData, 
                                          Encoding.UTF8, 
                                          "application/json");

        var response = await httpClient.SendAsync(request);

        // 応答のコンテンツを Stream として取得
        using (Stream responseStream = 
                await response.Content.ReadAsStreamAsync())
        {
          // JSON シリアライザの初期化
          DataContractJsonSerializer ser =
            new DataContractJsonSerializer(typeof(RootObject));

          // 応答のコンテンツを逆シリアル化して C# の
          // オブジェクトを取得
          RootObject rootObject =
              (RootObject)ser.ReadObject(responseStream);

          return rootObject;
        }                
      }
    }

    // コンソールに取得結果を書き出すためのヘルパメソッド
    static void ShowCars(RootObject rootObject)
    {
      foreach (Car car in rootObject.GetCarsByDoorsResult)
      {
        Console.WriteLine("Make:{0}, Model:{1}, Doors:{2}",
                          car.Make, car.Model, car.Doors);
      }
    }
  }

  /*
  結果は:
  Make:Audi, Model:A4, Doors:5
  Make:Ford, Model:Focus, Doors:5
  Make:Renault, Model:Laguna, Doors:5
  Make:Toyota, Model:Previa, Doors:5
  */
}

DataContractJsonSerializer クラスを利用したシリアル化 / 逆シリアル化については、MSDN ライブラリの記事「方法 : JSON データをシリアル化および逆シリアル化する」が参考になると思います。

なお、本題とは直接関係ないことですが、You're using HttpClient wrong and it is destabilizing your software によると、HttpClient の初期化と Dispose を繰り返すようなことをすると、socket が浪費されるという問題があるそうです。これは覚えておいた方がよさそうです。

Tags: ,

.NET Framework

HttpWebRequest で WCF サービスを呼出

by WebSurfer 2017年3月26日 14:36

先の記事 WCF と jQuery AJAX では、JSON 文字列をデータとしてやり取りする WCF サービスのメソッドを、jQuery.ajax を使って呼び出してデータを取得する方法を書きました。

この WCF サービスのメソッドを HttpWebRequest / HttpWebResponse を利用して呼び出して JSON 文字列のデータを取得し、それを逆シリアル化して C# のオブジェクトに変換する方法を書きます。

ここでは例として先の記事の WCF サービスの GetCarsByDoors(int doors) メソッドを POST 要求してみます。

まず、JSON 文字列が逆シリアル化された際の C# のクラス / プロパティを書き、それらに DataContract / DataMember 属性を付与してデータコントラクトを定義します。

JSON 文字列から C# のクラス / プロパティの変換は json2csharp のような変換サービスを利用すると簡単にできると思います。

先の記事のコードでは、WCF サービスの GetCarsByDoors(int doors) メソッドの応答の JSON 文字列は "GetCarsByDoorsResult" でラップされるように設定されていますので、それを考慮して以下のようなデータコントラクト定義になります。

[DataContract]
public class RootObject
{
    // GetCarsByDoorsResult は WCF サービスメソッドに付与した
    // BodyStyle = WebMessageBodyStyle.WrappedRequest による
    // ラップの名前(ラップするのはセキュリティ対策)
    [DataMember]
    public List<Car> GetCarsByDoorsResult { get; set; }
}

[DataContract]
public class Car
{
    [DataMember]
    public string Make { get; set; }

    [DataMember]
    public string Model { get; set; }

    [DataMember]
    public int Year { get; set; }

    [DataMember]
    public int Doors { get; set; }

    [DataMember]
    public string Colour { get; set; }

    [DataMember]
    public float Price { get; set; }
}

HttpWebRequest を利用して WCF サービスの GetCarsByDoors(int doors) メソッドを要求し JSON 文字列をデータとして POST 送信します。ここでは例として "{\"doors\":5}" という 5 ドア車を要求する JSON 文字列を設定しています。

HttpWebResponse を利用して応答を取得し、DataContractJsonSerializer クラスを利用して応答ストリームに含まれる JSON 文字列を C# のオブジェクトにデシリアライズします。

詳しくは以下のサンプルコードとそれに付与したコメントを見てください。

using System;
using System.Net;
using System.Text;
using System.IO;
using System.Runtime.Serialization;
using System.Collections.Generic;
using System.Runtime.Serialization.Json;

namespace ConsoleApplication7
{
  class Program
  {
    static void Main(string[] args)
    {
      // 指定した Uri を要求する HttpWebRequest を初期化
      HttpWebRequest endpointRequest =
          (HttpWebRequest)HttpWebRequest.
          Create("http://.../carservice.svc/GetCarsByDoors");

      endpointRequest.Method = "POST";
      endpointRequest.ContentType = 
          "application/json; charset=utf-8";

      // POST データの設定。とりあえず 5 ドアを要求してみる
      string postData = "{\"doors\":5}";

      Encoding encoding = Encoding.GetEncoding("utf-8");
      byte[] byte1 = encoding.GetBytes(postData);
      endpointRequest.ContentLength = byte1.Length;

      // POST データを書き込むストリームを取得
      using (Stream requestStream = 
             endpointRequest.GetRequestStream())
      {
        // POST データを要求ストリームに書き込み
        requestStream.Write(byte1, 0, byte1.Length);

        // WCF サービスメソッドからの応答を取得
        using (HttpWebResponse endpointResponse =
               (HttpWebResponse)endpointRequest.GetResponse())
        {
          // 応答のコンテンツを読むストリームを取得
          using (Stream responseStream = 
                 endpointResponse.GetResponseStream())
          {
            // JSON シリアライザの初期化
            DataContractJsonSerializer ser = 
              new DataContractJsonSerializer(typeof(RootObject));

            // 応答のコンテンツを逆シリアル化して C# の
            // オブジェクトを取得
            RootObject rootObject = 
              (RootObject)ser.ReadObject(responseStream);
                        
            foreach (Car car in rootObject.GetCarsByDoorsResult)
            {
              Console.WriteLine("Make:{0}, Model:{1}, Doors:{2}",
                                car.Make, car.Model, car.Doors);

            /*
            結果は:
            Make:Audi, Model:A4, Doors:5
            Make:Ford, Model:Focus, Doors:5
            Make:Renault, Model:Laguna, Doors:5
            Make:Toyota, Model:Previa, Doors:5
            */

            }
          }
        }
      }
    }
  }
}

DataContractJsonSerializer クラスを利用したシリアル化 / 逆シリアル化については、MSDN ライブラリの記事「方法 : JSON データをシリアル化および逆シリアル化する」が参考になると思います。

Tags: ,

.NET Framework

WCF サービスで HTTP 404.17 エラー

by WebSurfer 2017年2月5日 17:25

先日買った Windows 10 Pro 64-bit のノート PC に旧マシンから開発環境を移しています。

WCF サービスをホストしている ASP.NET Web Forms アプリをソリューションごとコピーして .aspx ページは問題なく動くようになったのですが、何故か WCF サービスだけが動きません。(汗)

ブラウザから .svc ファイルを要求すると HTTP 404.17 エラーとなります。エラーメッセージは「要求されたコンテンツはスクリプトであり、静的ファイル ハンドラーで処理されない可能性があります。」でした。

IIS のハンドラマッピングをみると .svc 用のハンドラは 3 つ存在していたのですが、何故静的ファイルハンドラーにマップされたのでしょう?

3 時間ぐらいハマってしまったのですが、ググって調べて stackoverflow の記事 What causes WCF Error 404.17 to be shown? にたどり着きました。

その記事の質問欄に "I then finally fixed it by enabling "HTTP Activation" (as suggested here)" とあります。その here のリンク先が HTTP Error 404.17 - Not Found で、その記事の回答にある画像すなわち ASP.NET 4.5 の WCF サービスの HTTP アクティブ化が正解のようです。

自分の Windows 10 Pro で該当するのは、以下の画像の「Windows の機能の有効化または無効化」で [.NET Framemwork 4.6 Advanced Services] => [WCF サービス] => [HTTP アクティブ化] のようですので、それにチェックを入れて有効化してみました。

WCF サービスの HTTP アクティブ化

結果、ブラウザから .svc ファイルを要求すると応答が返ってくるようになり、アプリから WCF サービスのメソッドを呼んだ場合も正常に応答が返ってくるようになりました。やはりこれが正解だったようです。

IIS のハンドラマッピングをみると HTTP アクティブ化前には 3 つしかなかった .svc 用のハンドラが、以下の画像のように 6 つに増えています。

ハンドラマッピング

HTTP アクティブ化の前/後でどう変わったのかはっきりしませんが、3 つ増えたのは間違いなくて、たぶん 4.0 用が追加されたのだと思います。

また、上に紹介した stackoverflow の記事に、

"The Http Activation Module (System.ServiceModel.Activation.HttpModule) is registered in the machine.config file. .NET 2.0 and .NET 4.0 have separate machine.configs. The Http Activation feature also exists for both .NET versions separately."

・・・と書いてありましたが、HTTP アクティブ化によって、ハンドラの追加だけではなく、.NET 4 用の Http Activation Module の有効化も行われたのかもしれません。

(ググってヒットした記事の中には ServiceModel 登録ツールを使って解決したという話もありました。HTTP アクティブ化もこのツールを使っているのかもしれません。ただし、確認はできていません。ご参考まで)

Tags: ,

ASP.NET

About this blog

2010年5月にこのブログを立ち上げました。主に ASP.NET Web アプリ関係の記事です。

Calendar

<<  2024年4月  >>
31123456
78910111213
14151617181920
21222324252627
2829301234
567891011

View posts in large calendar