none
C#コンソールアプリのHttpClientでASP.NET Web APIからレスポンスが取得できない RRS feed

  • 質問

  • C#コンソールアプリ上のHttpClientでASP.NET Web APIのテストしてますが、レスポンスが取得できなくて困ってます。

    自宅PC(Windows10 64bit)では下記コードでレスポンス取得できましたが、

    別環境のPC(Windows7 32bit)単体でコンソールアプリからは、 VisualStudio開発用サーバーで実行したASP.NET Web APIのコントローラーが実行できますが、コンソールアプリ上でレスポンスが取得できません。(ステータス500) 

    自宅PCとWindowsバージョン、bitが異なるため、プロジェクトフォルダは丸ごとコピーはせず、プロジェクトを1から作成し、csファイルの必要コードのみコピーしてます。

    (別環境のPC環境)

    ・PCドメイン管理(パスワードは保存しておけば1ヶ月は聞かれない)
    ・ローカルネットワーク上でIIS構築可、別PCからアクセス可を確認済み
    ・一部のWebサイトについて閲覧制限あり(ブログ等一部閲覧禁止、localhostは制限されてない)
    ・ファイアウォールは関係ないと思いますが、一旦OFFにしてます。

    どなたかご教授お願いします。

    ■コンソールアプリ

    Visual Studio Express 2012 for Windows Desktop  .NET4.5.2

    (使用パッケージ)

      <package id="Microsoft.AspNet.WebApi.Client" version="5.2.6" targetFramework="net452" />
      <package id="Newtonsoft.Json" version="9.0.1" targetFramework="net452" />

    (コード)

    using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Net.Http; using System.Net.Http.Headers; namespace HttpTest { public class Program { static void Main(string[] args) { using (HttpClient client = new HttpClient()) { client.BaseAddress = new Uri("http://localhost:50001/"); client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); HttpResponseMessage response = client.GetAsync("api/Empolyees").Result; Console.WriteLine("・StatusCode: {0}", (int)response.StatusCode); if (response.IsSuccessStatusCode) { var r = response.Content.ReadAsAsync<IEnumerable<Empolyee>>().Result; foreach (Empolyee item in r) { Console.WriteLine(item.EmpolyeeID + ", " + item.EmpolyeeName); } } } } }   // TestとしてEmpolyeeクラス public class Empolyee { public string EmpolyeeID { get; set; } public string EmpolyeeName { get; set; }

        // 一部記載は省略 } }


    ■ ASP.NET Web API 2

    Visual Studio Express 2012 for Web (追加インストールでASP.NET Web API 2 使用)  .NET4.5.2

    (使用パッケージ)

      <package id="EntityFramework" version="6.2.0" targetFramework="net452" />
      <package id="Microsoft.AspNet.WebApi" version="5.2.6" targetFramework="net452" />
      <package id="Microsoft.AspNet.WebApi.Client" version="5.2.6" targetFramework="net452" />
      <package id="Microsoft.AspNet.WebApi.Core" version="5.2.6" targetFramework="net452" />
      <package id="Microsoft.AspNet.WebApi.WebHost" version="5.2.6" targetFramework="net452" />
      <package id="Newtonsoft.Json" version="9.0.1" targetFramework="net452" />

    (コントローラー)

    using System;
    using System.Linq;
    using System.Collections.Generic;
    using System.Threading.Tasks;
    using System.Data;
    using System.Data.Entity;
    using System.Data.Entity.Infrastructure;
    using System.Net;
    using System.Net.Http;
    using System.Web.Http;
    using System.Web.Http.Description;
    using Main.Common;
    using Main.Models;
    
    namespace Main.Controllers {
        public class EmpolyeesController : ApiController {
            private AddDbContext db = new AddDbContext();
    
            // GET: api/Empolyees
            public IQueryable<Empolyee> GetEmpolyees() {
                return db.M_Empolyees;
            }
    
            // GET: api/Empolyees/5
            [ResponseType(typeof(Empolyee))]
            public async Task<IHttpActionResult> GetEmpolyee(string id) {
                Empolyee r = await db.M_Empolyees.FindAsync(id);
                if (r == null) { return NotFound(); }
    
                return Ok(r);
            }
    
            // PUT: api/Empolyees/5
            [ResponseType(typeof(void))]
            public async Task<IHttpActionResult> PutEmpolyee(string id, Empolyee r) {
                if (!ModelState.IsValid) return BadRequest(ModelState);
                if (id != r.EmpolyeeID) return BadRequest();
                db.Entry(r).State = EntityState.Modified;
    
                try {
                    await db.SaveChangesAsync();
                } catch (DbUpdateConcurrencyException) {
                    if (!EmpolyeeExists(id)) {
                        return NotFound();
                    } else {
                        throw;
                    }
                }
    
                return StatusCode(HttpStatusCode.NoContent);
            }
    
            // POST: api/Empolyees
            [ResponseType(typeof(Empolyee))]
            public async Task<IHttpActionResult> PostEmpolyee(Empolyee r) {
                if (!ModelState.IsValid) return BadRequest(ModelState);
                db.M_Empolyees.Add(r);
                await db.SaveChangesAsync();
    
                return CreatedAtRoute("DefaultApi", new { id = r.EmpolyeeID }, r);
            }
    
            // DELETE: api/Empolyees/5
            [ResponseType(typeof(Empolyee))]
            public async Task<IHttpActionResult> DeleteEmpolyee(string id) {
                Empolyee r = await db.M_Empolyees.FindAsync(id);
                if (r == null) return NotFound();
                db.M_Empolyees.Remove(r);
                await db.SaveChangesAsync();
    
                return Ok(r);
            }
    
            protected override void Dispose(bool disposing) {
                if (disposing) db.Dispose();
                base.Dispose(disposing);
            }
    
            private bool EmpolyeeExists(string id) {
                return db.M_Empolyees.Count(e => e.EmpolyeeID == id) > 0;
            }
        }
    }

    ■ レスポンス詳細

    ・Header: Connection: Close
    Cache-Control: private
    Date: Thu, 30 Aug 2018 03:44:29 GMT
    Server: ASP.NET Development Server/11.0.0.0
    X-AspNet-Version: 4.0.30319
    
    ・RequestMessage: Method: GET, RequestUri: 'http://localhost:50001/api/Empolyees
    ', Version: 1.1, Content: <null>, Headers:
    {
      Accept: application/json
    }
    ・ReasonPhrase: Internal Server Error
    ・StatusCode: 500
    ・IsSuccessStatusCode: False
    ・Content: System.Net.Http.StreamContent
    ・Version: 1.1






    • 編集済み yuchan01 2018年8月30日 4:57
    2018年8月30日 4:13

回答

  • HTTP 500 はサーバーエラーなので、HttpClient からの要求はサーバーまで届いて、サーバーで要求を処置しているときに例外がスローされたということだと思われます。

    コンテンツに例外の詳細情報が含まれてないか調べてください。Fiddler を使って要求・応答をキャプチャすると調べられます。

    まずは質問者さんの方でそれを調べて問題を切り分けてください。
    • 回答としてマーク yuchan01 2018年8月31日 3:19
    2018年8月30日 5:35
  • > また、例外の詳細情報を確認をして下さいとご指摘がありましたので、何かないか探していたところ、

    HTTP 500 エラーになった場合でもコンテンツは返ってきますので、それを見ると例外がスローされたファイルの名前、どの行か、スタックトレースなどの情報が分かります。それで大体原因の見当がつくと思います。Fiddler で応答の TextView を見てください。

    > EntityFramework用のデータ定義モデルのクラスで、外部キー関係で例外が発生していることを見つけました。

    それが原因なのに、環境によって例外の出る出ないがあるということですか? 最初に質問では以下のように書いてありましたよね?

    > VisualStudio開発用サーバーで実行したASP.NET Web APIのコントローラーが実行できますが、コンソールアプリ上でレスポンスが取得できません。

    不思議です。上記の環境の違いが「外部キー関係で例外」にどう影響していたのか、後学のために教えていただけると幸いです。


    > virtualの必要性についてまだ詳しく理解できてはいませんが、修正したところクライアント側でレスポンスが取得できるようになりました。

    EF Code First の機能を利用して DB を生成しているのだと思いますが、であれば virtual を使う理由、その際 DB にどのように外部キーが設定されるかはきちんと把握しておくべきと思います。

    以下の記事が参考になると思いますので紹介しておきます。

    新しいデータベースの code First
    https://docs.microsoft.com/ja-jp/ef/ef6/modeling/code-first/workflows/new-database

    Code First Conventions(コードファーストの規約)
    https://docs.microsoft.com/en-us/ef/ef6/modeling/code-first/conventions/built-in

    > テストのためHttpClientをusingで使用してますが、usingは使わずにstaticプロパティで使用したほうが良いと、他のサイトに記載がありました。

    HttpClient の初期化と Dispose を繰り返すようなことをすると、socket が浪費されるという問題があるそうです。詳しくは以下の記事を見てください。

    You're using HttpClient wrong and it is destabilizing your software
    https://aspnetmonsters.com/2016/08/2016-08-27-httpclientwrong/


    • 編集済み SurferOnWww 2018年8月31日 5:30 参考記事追加
    • 回答としてマーク yuchan01 2018年9月2日 16:14
    2018年8月31日 5:20
  • いつもご回答ありがとうございます。

    Fiddlerというツールがあるんですね。ネットワーク状況を確認するツールがあることを知りませんでした。
    一度、自宅PCでFiddlerを試してみたところ、GETでURL指定して実行すると、
    ASP.NET WEB APIからレスポンスで帰ってくるJSONの中を確認することができました。


    また、例外の詳細情報を確認をして下さいとご指摘がありましたので、何かないか探していたところ、

    Nugetで ASP.NET WEB API 2.2 Tracing というものを見つけ、
    Visual Studioの出力ウィンドウで例外の詳細をトレースをすることができるとわかりましたので、

    → https://www.nuget.org/packages/Microsoft.AspNet.WebApi.Tracing


    今回は、ASP.NET WEB API 2.2 Tracingを使用して、問題がおこっている別環境PCにて、
    ASP.NET WEB API側の例外確認をしたころと、EntityFramework用のデータ定義モデルのクラスで、
    外部キー関係で例外が発生していることを見つけました。


    Empolyee.cs

    // 外部キーをこのように記載していた。
    [ForeignKey("DepartmentID")]
    public virtual Department Department { get; set; }
    
    ↓
    
    // 修正
    [ForeignKey("DepartmentID")]
    public Department Department { get; set; }

    virtualの必要性についてまだ詳しく理解できてはいませんが、修正したところクライアント側でレスポンスが取得できるようになりました。

    EntityFramework用のデータ定義モデルのクラスの書き方が間違っていた場合、
    クライアント側単独で仮に動いていたとしても、ASP.NET WEB API側に移行した場合に動かない場合があるので、
    データ定義モデルは正しく書く必要があることを認識しました。

    ありがとうございました。

    • 回答としてマーク yuchan01 2018年8月31日 3:47
    • 編集済み yuchan01 2018年8月31日 3:54
    2018年8月31日 3:45

すべての返信

  • HTTP 500 はサーバーエラーなので、HttpClient からの要求はサーバーまで届いて、サーバーで要求を処置しているときに例外がスローされたということだと思われます。

    コンテンツに例外の詳細情報が含まれてないか調べてください。Fiddler を使って要求・応答をキャプチャすると調べられます。

    まずは質問者さんの方でそれを調べて問題を切り分けてください。
    • 回答としてマーク yuchan01 2018年8月31日 3:19
    2018年8月30日 5:35
  • いつもご回答ありがとうございます。

    Fiddlerというツールがあるんですね。ネットワーク状況を確認するツールがあることを知りませんでした。
    一度、自宅PCでFiddlerを試してみたところ、GETでURL指定して実行すると、
    ASP.NET WEB APIからレスポンスで帰ってくるJSONの中を確認することができました。


    また、例外の詳細情報を確認をして下さいとご指摘がありましたので、何かないか探していたところ、

    Nugetで ASP.NET WEB API 2.2 Tracing というものを見つけ、
    Visual Studioの出力ウィンドウで例外の詳細をトレースをすることができるとわかりましたので、

    → https://www.nuget.org/packages/Microsoft.AspNet.WebApi.Tracing


    今回は、ASP.NET WEB API 2.2 Tracingを使用して、問題がおこっている別環境PCにて、
    ASP.NET WEB API側の例外確認をしたころと、EntityFramework用のデータ定義モデルのクラスで、
    外部キー関係で例外が発生していることを見つけました。


    Empolyee.cs

    // 外部キーをこのように記載していた。
    [ForeignKey("DepartmentID")]
    public virtual Department Department { get; set; }
    
    ↓
    
    // 修正
    [ForeignKey("DepartmentID")]
    public Department Department { get; set; }

    virtualの必要性についてまだ詳しく理解できてはいませんが、修正したところクライアント側でレスポンスが取得できるようになりました。

    EntityFramework用のデータ定義モデルのクラスの書き方が間違っていた場合、
    クライアント側単独で仮に動いていたとしても、ASP.NET WEB API側に移行した場合に動かない場合があるので、
    データ定義モデルは正しく書く必要があることを認識しました。

    ありがとうございました。

    • 回答としてマーク yuchan01 2018年8月31日 3:47
    • 編集済み yuchan01 2018年8月31日 3:54
    2018年8月31日 3:45
  • また、上記のコンソールアプリのコードについて補足ですが、

    テストのためHttpClientをusingで使用してますが、usingは使わずにstaticプロパティで使用したほうが良いと、

    他のサイトに記載がありました。

    2018年8月31日 3:55
  • > また、例外の詳細情報を確認をして下さいとご指摘がありましたので、何かないか探していたところ、

    HTTP 500 エラーになった場合でもコンテンツは返ってきますので、それを見ると例外がスローされたファイルの名前、どの行か、スタックトレースなどの情報が分かります。それで大体原因の見当がつくと思います。Fiddler で応答の TextView を見てください。

    > EntityFramework用のデータ定義モデルのクラスで、外部キー関係で例外が発生していることを見つけました。

    それが原因なのに、環境によって例外の出る出ないがあるということですか? 最初に質問では以下のように書いてありましたよね?

    > VisualStudio開発用サーバーで実行したASP.NET Web APIのコントローラーが実行できますが、コンソールアプリ上でレスポンスが取得できません。

    不思議です。上記の環境の違いが「外部キー関係で例外」にどう影響していたのか、後学のために教えていただけると幸いです。


    > virtualの必要性についてまだ詳しく理解できてはいませんが、修正したところクライアント側でレスポンスが取得できるようになりました。

    EF Code First の機能を利用して DB を生成しているのだと思いますが、であれば virtual を使う理由、その際 DB にどのように外部キーが設定されるかはきちんと把握しておくべきと思います。

    以下の記事が参考になると思いますので紹介しておきます。

    新しいデータベースの code First
    https://docs.microsoft.com/ja-jp/ef/ef6/modeling/code-first/workflows/new-database

    Code First Conventions(コードファーストの規約)
    https://docs.microsoft.com/en-us/ef/ef6/modeling/code-first/conventions/built-in

    > テストのためHttpClientをusingで使用してますが、usingは使わずにstaticプロパティで使用したほうが良いと、他のサイトに記載がありました。

    HttpClient の初期化と Dispose を繰り返すようなことをすると、socket が浪費されるという問題があるそうです。詳しくは以下の記事を見てください。

    You're using HttpClient wrong and it is destabilizing your software
    https://aspnetmonsters.com/2016/08/2016-08-27-httpclientwrong/


    • 編集済み SurferOnWww 2018年8月31日 5:30 参考記事追加
    • 回答としてマーク yuchan01 2018年9月2日 16:14
    2018年8月31日 5:20
  • すみませんが私の説明・確認不足です。

    環境によって出る出ないのがあったのではなく、
    自宅PC環境でモデルの外部キー設定をしていなかったことが、
    エラーが発生していなかった理由です。(コピーして同じ内容だと思い込んでました)

    Fiddlerで、応答のTextViewで例外詳細を確認することができました。

    virtual含めてEntity Framework関係は、
    ご紹介頂いたcode First関係のドキュメントを読んでよく理解しておこうと思います。
    2018年9月2日 16:15