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

EditorFor での属性の付与方法

by WebSurfer 2018年2月23日 18:32

Microsoft のドキュメント What's New in ASP.NET MVC 5.1 によると、MVC 5.1 から、HTML ヘルパー EditorFor を使っても、生成される html 要素に任意の属性を付与できるようになったそうです。(実際、自分が試した限りですが、 MVC 4 では以下に述べるいずれの方法でもダメでした)

何を今頃と言われるかもしれませんが(汗)、最近そのあたりのことを調べて自分にとっていろいろ新発見があったので、備忘録としてまとめて書いておきます。

(1) TextBoxFor の場合

LabelFor, TextBoxFor などでは、第 2 引数に匿名オブジェクトを渡して html 属性を付与することができます。例えば以下のようにすると、

@Html.TextBoxFor(m => m.LastName, 
  new { @class = "coolTextBox", data_date = "12-02-2012" })

それから生成される html 要素は以下のようになります。(見やすくなるよう改行を入れてます)

<input 
  class="coolTextBox" 
  data-date="12-02-2012" 
  id="LastName" 
  name="LastName" 
  type="text" 
  value="Gee" />

本題とは関係ないですが、ついでに、匿名オブジェクトを書くときの注意点を上記の TestBoxFor の設定を例にして書いておきます。

匿名オブジェクトは、上記の場合 C# のコードですから、プロパティ名は C# としての識別子の条件を満たす必要があります。class は C# では予約語ですからそのまま使うことはできません。なので @ を付与して識別子名として使えるようにしています。(コンパイラには @ は無視されて class が識別子名と見なされます)

html 要素の属性としてよくある名前で、C# の識別子として使えないケースではハイフン '-' を含む属性名があると思います。例えば上記のように data-date="12-02-2012" という属性を設定したい場合です。その場合はハイフン '-' に代えてアンダースコア '_' を使います。ASP.NET によって html に変換されると、'_' は '-' に変換されます。

(2) EditorFor の場合

EditorFor では第 2 引数を上記の TextBoxFor と同様に設定しても無視されます。例えば以下のようにすると、

@Html.EditorFor(m => m.LastName, 
  new { @class = "coolTextBox", data_date = "12-02-2012" })

生成される html 要素は以下のようになります。

<input 
  class="text-box single-line" 
  id="LastName" 
  name="LastName" 
  type="text" 
  value="Gee" />

上記の class="text-box single-line" はデフォルトでハードコーディングされている属性だそうです。そのあたりのことは Overwriting the class on a `Html.EditorFor` とか、その記事が参照している ASP.NET MVC 2 Templates, Part 3: Default Templates に書いてありますので興味があれば見てください。

MVC 5.1 より前ではデフォルトを変更したりそれに追加することができなかったので EditorFor に代えて TextBoxFor を使うという手段を取っていたらしいです。MSDN Forum のスレッド Override CSS for textbox in MVC4 にそのような記事があります。

ところが、MVC 5.1 からは一番上に紹介した記事にあるように、EditorFor にも任意の属性が付与できるようになりました。

以下のように、匿名オブジェクトのプロパティ名を htmlAttributes とし、それに付与したい属性の匿名オブジェクトを設定するというように、入れ子で匿名オブジェクトを設定します。

@Html.EditorFor(m => m.LastName,
  new { htmlAttributes =
    new { @class = "coolTextBox", data_date = "12-02-2012" } })

その結果生成される html 要素は以下のようになります。

<input 
  class="coolTextBox text-box single-line" 
  data-date="12-02-2012" 
  id="LastName" 
  name="LastName" 
  type="text" 
  value="Gee" />

デフォルトでハードコーディングされている class="text-box single-line" と、上記コードで設定した class="coolTextBox" data-date="12-02-2012" がマージされているのが分かるでしょうか。

上に紹介した記事 What's New in ASP.NET MVC 5.1 によると Bootstrap をサポートするためとのことです。それでどのようにサポートできるのかは調べ切れてません。

TextBoxFor と EditorFor でなぜ違うのかは Html.EditorFor and htmlAttributes に詳しく書いてありますので、興味があれば見てください。

Tags: , ,

MVC

MySQL での TableAdapter 構成ウィザード不具合

by WebSurfer 2018年2月10日 20:02

先の記事「MySQL をインストールしました(その 2)」で書きましたが、MySQL Installer 5.7.17 を使って、MySQL Community Server 5.7.17, Connector/Net 6.9.9 など、その時点での最新版の MySQL 関係のコンポーネントを一式インストールしました。(何をインストールしたかは記事を見てください)

その後 1 年も経っているのに何ですが、MySQL では Visual Studio の TableAdapter 構成ウィザードを使って型付 DataSet + TableAdapter を自動生成できないことに最近やっと気が付きました。(汗)

備忘録として、自分の環境で起こった問題と解決策を書いておきます。ちなみに、自分の環境とは以下の通りです。(MySQL 関係はすべて MySQL Installer 5.7.17 でインストールしたものです)

  • Windows 10 Pro 64-bit
  • Visual Studio 2010 Pro / 2015 Community Update 3
  • MySQL Community Server 5.7.17
  • MySQL for Visual Studio 1.2.6
  • Connector/Net 6.9.9

上記の環境で TableAdapter 構成ウィザードを使って型付 DataSet + TableAdapter を生成すると、最後のステップで "[A] MySql.Data.MySqlClient.MySqlConnection は [B] MySql.Data.MySqlClient.MySqlConnection にキャストできません。" というエラーになります。

以下の画像を見てください。

Visual Studio 2010

Visual Studio 2010

Visual Studio 2015

Visual Studio 2015

エラーメッセージを見ての想像ですが、Visual Studio のウィザードが使う MySql.Data.dll のバージョン 6.9.8 と、GAC の MySql.Data.dll のバージョン 6.9.9 に不一致があって、それが原因でウィザードが期待通り動かないようです。

自分の PC のフォルダの MySql.Data.dll のバージョンを調べてみると、確かにエラーメッセージの通りのバージョンの相違がありました。

TableAdapter 構成ウィザード以外での不具合としては、ASP.NET Web Forms アプリで使用される SqlDataSource の例があります。

ウィザードの最後のステップで[クエリのテスト]を行うと、"オブジェクト参照がオブジェクト インスタンスに設定されていません。" というエラーになります。

以下の画像を見てください。

SqlDataSource の場合

Visual Studio のサーバーエクスプローラーから MySQL に接続するのは問題ないのですが、どうもウィザードが MySQL に接続に行くとダメな感じです。

ググって調べると、同様な問題が MSDN フォーラムの記事MySQL のフォーラムの記事 に報告されているのが見つかりました。

いずれの記事にも Connector/NET 6.9.9 をアンインストールしてから Connector/NET 6.9.8 をインストールして解決したと書いてあります。

Connector/NET のアンイストールはいろいろトラブルがある様子で、クリーンにアンインストールできるかどうか不安がありましたが、ウィザードが使えないと不便なので自分もやってみることにしました。

まず Connector/NET 6.9.9 のアンインストールですが、「コントロールパネル」⇒「プログラムと機能」を使って行うのではなく、MySQL Installer の Remove 機能を使うのがよさそうです。

Connector/NET 6.9.9 の Remove

上の画像で[Execute]ボタンをクリックするとエラーなく Remove は完了し、GAC, レジストリ, Program Files フォルダ, machine.config にも削除結果が反映されており、クリーンにアンインストールできました。トラブルがあるのではという心配は杞憂だったようです。

Connector/NET 6.9.8 のインストールも MySQL Installer を使ってできないかトライしたのですが、"No valid download found for product Connector/NET 6.9.8" が解消できず断念。

やむを得ないので別途ダウンロードした Connector/NET 6.9.8 の msi を起動してインストールしました。MySQL Installer に認識されなくて後で管理がやりにくいかもしれないと思いましたが、そんなことはなかったです。

Connector/NET 6.9.8

その後、Visual Studio 2015 で TableAdapter 構成ウィザードを使って型付 DataSet + TableAdapter を作ってみましたが、上に述べた問題は解消されていました。

ASP.NET Web Forms アプリ用の SqlDataSource も試してみましたが、ウィザードから MySQL に接続できないという問題も解消されていました。

Tags: ,

MySQL

About this blog

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

Calendar

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

View posts in large calendar