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

リソースから画像を取得する際の注意点

by WebSurfer 2018年2月6日 17:02

リソースから画像を取得すると、取得するたび毎回新しい Bitmap オブジェクトが生成されるいう話を書きます。(元の話は MSDN Forum のスレッド「C#でのIF文を使ったフォーム背景画像の設定方法について」のものです)

リソースの画像

Visual Studio 2015 のテンプレートから Windows Forms アプリを生成すると、Properties フォルダ下にリソースファイル Resources.resx と厳密に型指定されたリソースクラス Resources.Designer.cs が自動生成されます。上の画像は、そのリソースに .png ファイルを追加したものです。

そうすると、厳密に型指定されたリソースクラス Resources.Designer.cs に以下のコードが自動生成されて、image プロパティでリソースの .png ファイルから Bitmap オブジェクトを取得できるようになります。

internal static System.Drawing.Bitmap image {
  get {
    object obj = 
      ResourceManager.GetObject("image", resourceCulture);
    return ((System.Drawing.Bitmap)(obj));
  }
}

image プロパティは ResourceManager.GetObject メソッドを使っているところに注目してください。

MSDN ライブラリ ResourceManager.GetObject メソッド (String, CultureInfo) の説明によると "同じ name パラメーターで GetObject メソッドを複数回呼び出した場合、戻り値が同じオブジェクトの参照になるとは限りません・・・" とありますが、画像の場合は毎回新しい Bitmap オブジェクトが生成されるようです。

リソースから画像取得

上の画像で isEqual1 が false になることがそれを裏付けていると思います。isEqual2 は当然ながら true になります。

それがどうしたって言われそうですが、自分としては新しい発見だったということで備忘録として書いておくことにしました。(笑)

Tags: ,

.NET Framework

MonthCalendar の Size

by WebSurfer 2017年11月4日 14:32

MonthCalendar

Windows Forms アプリケーション用に、カレンダーを表示してユーザーが日付を選択できる MonthCalendar コントロールがあります。

その Size プロパティから MonthCalendar のサイズを取得する際、タイミングによっては正しいサイズが取得できない、その場合でもデバッガで MonthCalendar を開いてその中身を見ると正しいサイズになるという不可解なことがありました。

具体的には、下のコードの Button_Click メソッドをデバッガでステップ実行させ、コメント (3) の行で止めて Size プロパティを見ると 178 x 155 となっているが、calendar の中身をデバッガで開いて見た後で Size プロパティを見ると 199 x 162 と正しい値になるというものです。

その理由を調べたので備忘録として書いておきます。なお、元の話は Teratail のスレッド「VisualStudioでデバッグ中にプロパティの値が変化する」です。

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace WindowsFormsApplication2
{
  public partial class Form1 : Form
  {
    public Form1()
    {
      InitializeComponent();

      Button button = new Button();
      button.Click += Button_Click;
      this.Controls.Add(button);
    }

    private void Button_Click(object sender, EventArgs e)
    {
      Form f = new Form();
      Size size1 = f.Size;    // (1) W: 300, H: 300
      f.Size = new Size(200, 200);
      size1 = f.Size;         // (2) W: 200, H: 200

      MonthCalendar calendar = new MonthCalendar();
      Size size2 = calendar.Size;   // (3) W: 178, H: 155
      calendar.Size = new Size(200, 200);
      size2 = calendar.Size;        // (4) W: 178, H: 155
      f.Controls.Add(calendar);
      size2 = calendar.Size;        // (5) W: 178, H: 155
      f.Show();
      size2 = calendar.Size;        // (6) W: 199, H: 162

      f.ClientSize = calendar.Size;
    }
  }
}

上に書いた「不可解なこと」の理由は、多少想像が入っていますが、以下のようなことだと思われます。

  1. MonthCalendar のサイズを決定するのは使用されるフォントのみ。MonthCalendar.Size プロパティの設定では変えられない・・・コメント (4) 参照。
  2. new MonthCalendar() の時点ではフォントが不明なので MonthCalendar のサイズは未定。
  3. コメント (3) の時点で Size プロパティを見ると 178 x 155 となっているが、それはデフォルト値でフォントを反映した正しいサイズではない。
  4. コメント (3) の時点で calendar.Size の calendar にマウスカーソルを当てて開くと、その時点で calendar が初期化され、使用されるフォントに応じて正しいサイズが Size プロパティに設定される。
  5. その後で calendar.Size の Size にマウスカーソルを当てると正しいサイズ 199 x 162 が取得できる。
  6. デバッガで calendar を開いて見るということをしなければ、使用されるフォントに応じて正しいサイズが Size プロパティに設定されるのは、上記のコードでは f.Show(); の時点。
  7. その後であれば、calendar.Size で正しいサイズを取得でき、それを Form の ClientSize に設定してやれば上の画像の通り calendar がフォーム内にぴったり収まる。

上のことを書いた Microsoft の公式文書などは見つからないのですが、コードで検証した結果が上記の想像は正しいことを裏付けていると思います。

Tags: ,

.NET Framework

About this blog

2010年5月にこのブログを立ち上げました。その後 ブログ2 を追加し、ここは ASP.NET 関係のトピックス、ブログ2はそれ以外のトピックスに分けました。

Calendar

<<  2018年2月  >>
28293031123
45678910
11121314151617
18192021222324
25262728123
45678910

View posts in large calendar