web-dev-qa-db-ja.com

web-api POST bodyオブジェクトは常にnull

私はまだWeb APIを学んでいるので、質問が愚かに聞こえたらご容赦ください。

私はStudentControllerにこれを持っています:

public HttpResponseMessage PostStudent([FromBody]Models.Student student)
        {
            if (DBManager.createStudent(student) != null)
                return Request.CreateResponse(HttpStatusCode.Created, student);
            else
                return Request.CreateResponse(HttpStatusCode.BadRequest, student);
        }

これが機能しているかどうかをテストするために、Google Chromeの拡張機能「Postman」を使用して、HTTP POSTリクエストを作成してテストします。

これは私の生のPOSTリクエストです:

POST /api/Student HTTP/1.1
Host: localhost:1118
Content-Type: application/json
Cache-Control: no-cache

{"student": [{"name":"John Doe", "age":18, "country":"United States of America"}]}

"student"はオブジェクトであるはずですが、アプリケーションをデバッグすると、APIは学生オブジェクトを受け取りますが、コンテンツは常にNULLです。

57
InnovativeDan

FromBodyは奇妙な属性であり、入力POST値は、プリミティブ型ではない場合、パラメーターを非ヌルにするために特定の形式である必要があります。 (ここの学生)

  1. Jsonとして{"name":"John Doe", "age":18, "country":"United States of America"}を使用してリクエストを試してください。
  2. [FromBody]属性を削除して、解決策を試してください。非プリミティブ型でも機能するはずです。 (学生)
  3. [FromBody]属性では、他のオプションは、値を=Value形式ではなく、key=value形式で送信することです。これは、studentのキー値が空の文字列であることを意味します...

学生クラス用のカスタムモデルバインダーを記述し、カスタムバインダーでパラメーターを属性化する他のオプションもあります。

46
Raja Nadar

数分間、問題の解決策を探していたので、解決策を共有します。

モデルには空の/デフォルトのコンストラクターが必要です。そうでない場合、明らかにモデルを作成できません。リファクタリング中は注意してください。 ;)

46
chrjs

私はこの問題に数時間を費やしています... :(ゲッターとセッターはPOSTパラメーターオブジェクト宣言で必要です。特別なデータオブジェクト(string、int、...)の使用はお勧めしませんリクエスト形式。

[HttpPost]
public HttpResponseMessage PostProcedure(EdiconLogFilter filter){
...
}

次の場合は機能しません。

public class EdiconLogFilter
{
    public string fClientName;
    public string fUserName;
    public string fMinutes;
    public string fLogDate;
}

次の場合に正常に動作します:

public class EdiconLogFilter
{
    public string fClientName { get; set; }
    public string fUserName { get; set; }
    public string fMinutes { get; set; }
    public string fLogDate { get; set; }
}
25
Milan Švec

これは少し古いものであり、私の答えは最後の場所に行きますが、それでも私の経験を共有したいと思います。

すべての提案を試みましたが、PUT [FromBody]で同じ「null」値を保持しています。

最終的には、JSONがAngularオブジェクトのEndDateプロパティをシリアル化する間に、すべてが日付形式であることがわかりました。

エラーはスローされず、空のFromBodyオブジェクトを受け取りました。

13
Josep Alacid

リクエストのJSONオブジェクトの値のいずれかがサービスで予期されているものと異なる場合、[FromBody]引数はnullになります。

たとえば、jsonのageプロパティにfloat値があった場合:

「年齢」:18.0

しかし、APIサービスはそれがintであることを期待しています

「年齢」:18

studentnullになります。 (ヌル参照チェックがない限り、応答でエラーメッセージは送信されません)。

12
Ray Vega

Postmanを使用している場合は、次のことを確認してください。

  • 「Content-Type」ヘッダーを「application/json」に設定しました
  • ボディを「生」として送信しています
  • [FromBody]を使用している場合、どこにでもパラメーター名を指定する必要はありません。

愚かにも、JSONをフォームデータとして送信しようとしていました。

7
John M

TL; DR:[FromBody]を使用しないでください。ただし、エラー処理を改善して独自のバージョンをロールバックしてください。以下の理由。

他の回答は、この問題の考えられる多くの原因を説明しています。ただし、根本的な原因は、[FromBody]がひどいエラー処理を行っているだけで、実稼働コードではほとんど役に立たないことです。

たとえば、パラメータがnullになる最も一般的な理由の1つは、リクエストの本文に無効な構文(たとえば、無効なJSON)があることです。この場合、妥当なAPIは400 BAD REQUESTを返し、妥当なWebフレームワークはこれを自動的に行います。ただし、ASP.NET Web APIはこの点で合理的ではありません。パラメーターをnullに設定するだけで、リクエストハンドラーはパラメーターがnullであるかどうかを確認するために「手動」コードを必要とします。

したがって、ここで与えられる回答の多くはエラー処理に関して不完全であり、バグのあるクライアントまたは悪意のあるクライアントは無効なリクエストを送信することでサーバー側で予期しない動作を引き起こす可能性があり、(最良の場合)NullReferenceExceptionどこかで500 INTERNAL SERVER ERRORの誤ったステータスを返すか、さらに悪いことに、予期しないことを行うか、クラッシュするか、セキュリティの脆弱性を公開します。

適切な解決策は、適切なエラー処理を行い、適切なステータスコードを返すカスタム「[FromBody]」属性を記述することです。理想的には、クライアント開発者を支援するための診断情報が含まれます。

(まだテストされていない)役立つ解決策は、次のように必要なパラメーターを作成することです。 https://stackoverflow.com/a/19322688/2279059

次の不器用なソリューションも機能します。

// BAD EXAMPLE, but may work reasonably well for "internal" APIs...

public HttpResponseMessage MyAction([FromBody] JObject json)
{
  // Check if JSON from request body has been parsed correctly
  if (json == null) {
    var response = new HttpResponseMessage(HttpStatusCode.BadRequest) {
      ReasonPhrase = "Invalid JSON"
    };
    throw new HttpResponseException(response);
  }

  MyParameterModel param;
  try {
    param = json.ToObject<MyParameterModel>();
  }
  catch (JsonException e) {
    var response = new HttpResponseMessage(HttpStatusCode.BadRequest) {
      ReasonPhrase = String.Format("Invalid parameter: {0}", e.Message)
    };
    throw new HttpResponseException(response);
  }

  // ... Request handling goes here ...

}

これは(うまくいけば)適切なエラー処理を行いますが、あまり宣言的ではありません。たとえば、Swaggerを使用してAPIを文書化する場合、パラメーターの種類がわからないため、パラメーターを文書化するには手動での回避策を見つける必要があります。これは、[FromBody]が何をすべきかを説明するためのものです。

編集:あまり面倒な解決策は、ModelStateをチェックすることです: https://stackoverflow.com/a/38515689/2279059

編集:ModelState.IsValidは、falseRequired = Required.Alwaysとともに使用し、パラメーターが欠落している場合、予想どおり、JsonPropertyに設定されていないようです。だからこれも役に立たない。

ただし、私の意見では、every要求ハンドラーに追加のコードを記述する必要があるソリューションは受け入れられません。強力なシリアル化機能を備えた.NETのような言語、およびASP.NET Web APIのようなフレームワークでは、要求の検証は自動で組み込みである必要があり、Microsoftは必要な組み込みを提供していなくても完全に実行可能ですツール。

6
Florian Winter

また、[FromBody]を使用しようとしましたが、入力が変更され、バックエンドサービスに渡す必要があるため、文字列変数を設定しようとしましたが、これは常にnullでした。

Post([FromBody]string Input]) 

そこで、動的クラスを使用するようにメソッドシグネチャを変更し、それを文字列に変換しました

Post(dynamic DynamicClass)
{
   string Input = JsonConvert.SerializeObject(DynamicClass);

これはうまく機能します。

3
Mike

JsonシリアライザーにTRACINGを追加すると、問題が発生したときに何が起きているのかを確認できます。

ITraceWriter実装を定義して、次のようなデバッグ出力を表示します。

class TraceWriter : Newtonsoft.Json.Serialization.ITraceWriter
{
    public TraceLevel LevelFilter {
        get {
            return TraceLevel.Error;
        }
    }

    public void Trace(TraceLevel level, string message, Exception ex)
    {
        Console.WriteLine("JSON {0} {1}: {2}", level, message, ex);
    }
}

次に、WebApiConfigで以下を実行します。

    config.Formatters.JsonFormatter.SerializerSettings.TraceWriter = new TraceWriter();

(おそらく#if DEBUGでラップする)

2
Phuo

このスレッドに履歴を追加するだけです。私のモデル:

public class UserProfileResource
{
    public Guid Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string Phone { get; set; }

    public UserProfileResource()
    {
    }
}

上記のオブジェクトはAPIコントローラーでシリアル化できず、常にnullを返します。 問題はタイプGuidのIdにありました:空の文字列をIdとして渡すたびに(自動的にGuid.Emptyに変換されるようになっているため)、[FromBody] parametherとしてnullオブジェクトを受け取りました。

解決策は

  • 有効なGuid値を渡す
  • または、GuidStringに変更します
1
Dariusz

この問題に何度も遭遇しましたが、実際には、原因を突き止めるのは非常に簡単です。

これが今日の例です。 AccountRequestオブジェクトを使用してPOSTサービスを呼び出していましたが、この関数の先頭にブレークポイントを置いたとき、パラメーター値は常に​​nullでした。しかし、理由?!

[ProducesResponseType(typeof(DocumentInfo[]), 201)]
[HttpPost]
public async Task<IActionResult> Post([FromBody] AccountRequest accountRequest)
{
    //  At this point...  accountRequest is null... but why ?!

    //  ... other code ... 
}

問題を特定するには、パラメータタイプをstringに変更し、JSON.Netを取得する行を追加して、オブジェクトを期待したタイプにデシリアライズし、この行にブレークポイントを設定します。

[ProducesResponseType(typeof(DocumentInfo[]), 201)]
[HttpPost]
public async Task<IActionResult> Post([FromBody] string ar)
{
    //  Put a breakpoint on the following line... what is the value of "ar" ?
    AccountRequest accountRequest = JsonConvert.DeserializeObject<AccountRequest>(ar);

    //  ... other code ...
}

これを試してみると、パラメーターがまだ空白またはnullである場合、サービスを適切に呼び出していないだけです。

ただし、文字列doesに値が含まれている場合、DeserializeObjectは問題の原因を指摘し、文字列を目的の形式に変換できません。ただし、デシリアライズしようとしている生の(string)データを使用すると、パラメーター値の何が問題なのかを確認できるようになります。

(私の場合、誤ってシリアル化されたAccountRequestオブジェクトでサービスを呼び出していましたtwice!)

1
Mike Gledhill

私の場合、問題は私が送信していたDateTimeオブジェクトでした。 "yyyy-MM-dd"でDateTimeを作成し、必要な "HH-mm-ss"にマッピングするオブジェクトに必要なDateTimeも作成しました。そのため、「00-00」を追加することで問題が解決しました(これにより、アイテム全体がヌルになりました)。

1
shocks

同じ問題がありました。

私の場合、問題は私が持っていたpublic int? CreditLimitBasedOn { get; set; }プロパティにありました。

jSONには、整数を含める必要があるときに値"CreditLimitBasedOn":trueがありました。このプロパティにより、APIメソッドでオブジェクト全体が逆シリアル化されなくなりました。

1

誰かにとっては役立つかもしれません。DTO/ Modelクラスのプロパティのアクセス修飾子を確認してください。これらはpublicでなければなりません。私の場合、ドメインオブジェクトのリファクタリング中に、内部オブジェクトは次のようにDTOに移動されました。

// Domain object
public class MyDomainObject {
    public string Name { get; internal set; }
    public string Info { get; internal set; }
}
// DTO
public class MyDomainObjectDto {
    public Name { get; internal set; } // <-- The problem is in setter access modifier (and carelessly performed refactoring).
    public string Info { get; internal set; }
}

DTOはクライアントに細かく渡されますが、オブジェクトをサーバーに戻すときが来ると、空のフィールド(null /デフォルト値)しかありませんでした。 「内部」を削除すると、物事が整頓され、逆シリアル化メカニズムがオブジェクトのプロパティを書き込めるようになります。

public class MyDomainObjectDto {
    public Name { get; set; }
    public string Info { get; set; }
}
1
ddolgushin

NullになるフィールドにJsonProperty属性が設定されているかどうかを確認します-異なるjsonプロパティ名にマップされている可能性があります。

1

HttpRequestMessageを使用し、多くの調査を行った後、問題が解決しました

[HttpPost]
public HttpResponseMessage PostProcedure(HttpRequestMessage req){
...
}
1
aejaz ahmad

私のモデルはデータアノテーションの異なるバージョンを参照する.NET Standardプロジェクトに存在していたため、.NET Framework Web APIでこの問題が発生しました。

以下のReadAsAsync行を追加すると、原因が浮き彫りになりました。

public async Task<HttpResponseMessage> Register(RegistrationDetails registrationDetails)
{
    var regDetails = await Request.Content.ReadAsAsync<RegistrationDetails>();
0
kraeg

これが、データ型の不一致が原因でWeb API 2が逆シリアル化の問題に遭遇したためである場合、コンテンツストリームを検査することで、どこで失敗したかを見つけることができます。エラーが発生するまで読み込まれるため、コンテンツを文字列として読み取る場合は、投稿したデータの後半部分が必要です。

string json = await Request.Content.ReadAsStringAsync();

そのパラメーターを修正してください。そうすれば、次回さらに(または、運が良ければ成功します!)...

0
Jereme

3日間の検索の後、上記の解決策がどれも役に立たなかったので、このリンクでこの問題に対する別のアプローチを見つけました: HttpRequestMessage

このサイトのソリューションの1つを使用しました

[HttpPost]
public async System.Threading.Tasks.Task<string> Post(HttpRequestMessage request)
{
    string body = await request.Content.ReadAsStringAsync();
    return body;
}
0
Farid Amiri

この問題にはさまざまな原因が考えられます...

モデル変数にOnDeserializedコールバックを追加すると、パラメーターが常にnullになることがわかりました。正確な理由は不明です。

using System.Runtime.Serialization;

// Validate request
[OnDeserialized]  // TODO: Causes parameter to be null
public void DoAdditionalValidatation() {...}
0
Florian Winter