私はまだ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
です。
FromBodyは奇妙な属性であり、入力POST値は、プリミティブ型ではない場合、パラメーターを非ヌルにするために特定の形式である必要があります。 (ここの学生)
{"name":"John Doe", "age":18, "country":"United States of America"}
を使用してリクエストを試してください。[FromBody]
属性を削除して、解決策を試してください。非プリミティブ型でも機能するはずです。 (学生)[FromBody]
属性では、他のオプションは、値を=Value
形式ではなく、key=value
形式で送信することです。これは、student
のキー値が空の文字列であることを意味します...学生クラス用のカスタムモデルバインダーを記述し、カスタムバインダーでパラメーターを属性化する他のオプションもあります。
数分間、問題の解決策を探していたので、解決策を共有します。
モデルには空の/デフォルトのコンストラクターが必要です。そうでない場合、明らかにモデルを作成できません。リファクタリング中は注意してください。 ;)
私はこの問題に数時間を費やしています... :(ゲッターとセッターは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; }
}
これは少し古いものであり、私の答えは最後の場所に行きますが、それでも私の経験を共有したいと思います。
すべての提案を試みましたが、PUT [FromBody]で同じ「null」値を保持しています。
最終的には、JSONがAngularオブジェクトのEndDateプロパティをシリアル化する間に、すべてが日付形式であることがわかりました。
エラーはスローされず、空のFromBodyオブジェクトを受け取りました。
リクエストのJSONオブジェクトの値のいずれかがサービスで予期されているものと異なる場合、[FromBody]
引数はnull
になります。
たとえば、jsonのageプロパティにfloat
値があった場合:
「年齢」:18.0
しかし、APIサービスはそれがint
であることを期待しています
「年齢」:18
student
はnull
になります。 (ヌル参照チェックがない限り、応答でエラーメッセージは送信されません)。
Postmanを使用している場合は、次のことを確認してください。
愚かにも、JSONをフォームデータとして送信しようとしていました。
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
は、false
をRequired = Required.Always
とともに使用し、パラメーターが欠落している場合、予想どおり、JsonProperty
に設定されていないようです。だからこれも役に立たない。
ただし、私の意見では、every要求ハンドラーに追加のコードを記述する必要があるソリューションは受け入れられません。強力なシリアル化機能を備えた.NETのような言語、およびASP.NET Web APIのようなフレームワークでは、要求の検証は自動で組み込みである必要があり、Microsoftは必要な組み込みを提供していなくても完全に実行可能ですツール。
また、[FromBody]を使用しようとしましたが、入力が変更され、バックエンドサービスに渡す必要があるため、文字列変数を設定しようとしましたが、これは常にnullでした。
Post([FromBody]string Input])
そこで、動的クラスを使用するようにメソッドシグネチャを変更し、それを文字列に変換しました
Post(dynamic DynamicClass)
{
string Input = JsonConvert.SerializeObject(DynamicClass);
これはうまく機能します。
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でラップする)
このスレッドに履歴を追加するだけです。私のモデル:
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
値を渡すGuid
をString
に変更しますこの問題に何度も遭遇しましたが、実際には、原因を突き止めるのは非常に簡単です。
これが今日の例です。 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!)
私の場合、問題は私が送信していたDateTime
オブジェクトでした。 "yyyy-MM-dd"でDateTime
を作成し、必要な "HH-mm-ss"にマッピングするオブジェクトに必要なDateTime
も作成しました。そのため、「00-00」を追加することで問題が解決しました(これにより、アイテム全体がヌルになりました)。
同じ問題がありました。
私の場合、問題は私が持っていたpublic int? CreditLimitBasedOn { get; set; }
プロパティにありました。
jSONには、整数を含める必要があるときに値"CreditLimitBasedOn":true
がありました。このプロパティにより、APIメソッドでオブジェクト全体が逆シリアル化されなくなりました。
誰かにとっては役立つかもしれません。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; }
}
NullになるフィールドにJsonProperty
属性が設定されているかどうかを確認します-異なるjsonプロパティ名にマップされている可能性があります。
HttpRequestMessageを使用し、多くの調査を行った後、問題が解決しました
[HttpPost]
public HttpResponseMessage PostProcedure(HttpRequestMessage req){
...
}
私のモデルはデータアノテーションの異なるバージョンを参照する.NET Standardプロジェクトに存在していたため、.NET Framework Web APIでこの問題が発生しました。
以下のReadAsAsync行を追加すると、原因が浮き彫りになりました。
public async Task<HttpResponseMessage> Register(RegistrationDetails registrationDetails)
{
var regDetails = await Request.Content.ReadAsAsync<RegistrationDetails>();
これが、データ型の不一致が原因でWeb API 2が逆シリアル化の問題に遭遇したためである場合、コンテンツストリームを検査することで、どこで失敗したかを見つけることができます。エラーが発生するまで読み込まれるため、コンテンツを文字列として読み取る場合は、投稿したデータの後半部分が必要です。
string json = await Request.Content.ReadAsStringAsync();
そのパラメーターを修正してください。そうすれば、次回さらに(または、運が良ければ成功します!)...
3日間の検索の後、上記の解決策がどれも役に立たなかったので、このリンクでこの問題に対する別のアプローチを見つけました: HttpRequestMessage
このサイトのソリューションの1つを使用しました
[HttpPost]
public async System.Threading.Tasks.Task<string> Post(HttpRequestMessage request)
{
string body = await request.Content.ReadAsStringAsync();
return body;
}
この問題にはさまざまな原因が考えられます...
モデル変数にOnDeserialized
コールバックを追加すると、パラメーターが常にnull
になることがわかりました。正確な理由は不明です。
using System.Runtime.Serialization;
// Validate request
[OnDeserialized] // TODO: Causes parameter to be null
public void DoAdditionalValidatation() {...}