json POSTデータをオブジェクトとしてWeb APIメソッドに渡す方法は?


304

ASP.NET MVC4 Web APIアプリケーションは、顧客を節約するためのpostメソッドを定義します。顧客は、POSTリクエストの本文でJSON形式で渡されます。postメソッドのCustomerパラメーターに、プロパティのnull値が含まれています。

投稿されたデータが顧客オブジェクトとして渡されるようにこれを修正する方法は?

可能であればContent-Type:application / x-www-form-urlencodedを使用する必要があります。フォームを投稿するJavaScriptメソッドで変更する方法がわからないためです。

コントローラ:

public class CustomersController : ApiController {

  public object Post([FromBody] Customer customer)
        {
            return Request.CreateResponse(HttpStatusCode.OK,
            new
            {
                customer = customer
            });
        }
    }
}

public class Customer
    {
        public string company_name { get; set; }
        public string contact_name { get; set; }
     }

リクエスト:

POST http://localhost:52216/api/customers HTTP/1.1
Accept: application/json, text/javascript, */*; q=0.01
X-Requested-With: XMLHttpRequest
Content-Type: application/x-www-form-urlencoded; charset=UTF-8

{"contact_name":"sdfsd","company_name":"ssssd"}

回答:


525

編集:31/10/2017

同じコード/アプローチがAsp.Net Core 2.0でも機能します。主な違いは、asp.netコアでは、web apiコントローラーとMvcコントローラーの両方が単一のコントローラーモデルにマージされることです。あなたの戻り値の型があるかもしれないのでIActionResult、それの実装の1か(例:OkObjectResult


使用する

contentType:"application/json"

JSON.stringify送信するときに、メソッドを使用してJSON文字列に変換する必要があります。

そして、モデルバインダーはjsonデータをクラスオブジェクトにバインドします。

以下のコードは正常に動作します(テスト済み)

$(function () {
    var customer = {contact_name :"Scott",company_name:"HP"};
    $.ajax({
        type: "POST",
        data :JSON.stringify(customer),
        url: "api/Customer",
        contentType: "application/json"
    });
});

結果

ここに画像の説明を入力してください

contentTypeプロパティは、JSON形式でデータを送信していることをサーバーに通知します。JSONデータ構造を送信したため、モデルバインディングは適切に行われます。

ajaxリクエストのヘッダーを調べると、Content-Type値がに設定されていることがわかりますapplication/json

contentTypeを明示的に指定しない場合、デフォルトのコンテンツタイプである application/x-www-form-urlencoded;


2015年11月に編集して、コメントで指摘された他の考えられる問題に対処する

複雑なオブジェクトを投稿する

次のようなWeb APIアクションメソッドのパラメーターとして、複雑なビューモデルクラスがあるとします。

public class CreateUserViewModel
{
   public int Id {set;get;}
   public string Name {set;get;}  
   public List<TagViewModel> Tags {set;get;}
}
public class TagViewModel
{
  public int Id {set;get;}
  public string Code {set;get;}
}

そしてあなたのウェブAPIのエンドポイントは

public class ProductController : Controller
{
    [HttpPost]
    public CreateUserViewModel Save([FromBody] CreateUserViewModel m)
    {
        // I am just returning the posted model as it is. 
        // You may do other stuff and return different response.
        // Ex : missileService.LaunchMissile(m);
        return m;
    }
}

この記事の執筆時点では、ASP.NET MVC 6は最新の安定バージョンであり、MVC6では、Web APIコントローラーとMVCコントローラーの両方がMicrosoft.AspNet.Mvc.Controller基本クラスから継承されています。

クライアント側からメソッドにデータを送信するには、以下のコードが正常に機能するはずです

//Build an object which matches the structure of our view model class
var model = {
    Name: "Shyju",
    Id: 123,
    Tags: [{ Id: 12, Code: "C" }, { Id: 33, Code: "Swift" }]
};

$.ajax({
    type: "POST",
    data: JSON.stringify(model),
    url: "../product/save",
    contentType: "application/json"
}).done(function(res) {       
    console.log('res', res);
    // Do something with the result :)
});

モデルバインディングは一部のプロパティでは機能しますが、すべてでは機能しません。どうして ?

[FromBody]属性でWeb APIメソッドパラメータを装飾しない場合

[HttpPost]
public CreateUserViewModel Save(CreateUserViewModel m)
{
    return m;
}

そして、contentTypeプロパティ値を指定せずにモデル(JSON形式ではなく生のjavascriptオブジェクト)を送信します

$.ajax({
    type: "POST",
    data: model,
    url: "../product/save"
}).done(function (res) {
     console.log('res', res);
});

モデルバインディングは、型が複合型/別の型であるプロパティではなく、モデルのフラットプロパティに対して機能します。私たちの場合、IdおよびNameプロパティが適切にパラメータにバインドされますm、しかし、Tagsプロパティは空のリストになります。

$.postリクエストを送信するときにデフォルトのContent-Type を使用する短いバージョンを使用している場合にも、同じ問題が発生します。

$.post("../product/save", model, function (res) {
    //res contains the markup returned by the partial view
    console.log('res', res);
});

4
私は何をしたかわからないが、私は今朝、同じ船に戻ってきた。コントローラ内のオブジェクトがnullです。ここで私たちは再び笑します
グレイソン2014年

1
バイオリン弾きを使用している間は、コンテンツタイプが「Content-Type:application / json」と書かれていることを確認してください。乾杯!
ioWint 2015年

1
あなたは単に私の一日の仕事を解決しました!!! この小さな関数「JSON.stringify(data)」はそれを作りました!
Gil Allen

1
これを行う(Content-Typeヘッダーを変更する)場合、CORSリクエストを作成すると、jQueryはサーバーが処理する必要があるPOSTの前にプリフライトOPTIONSリクエストの追加を開始します。
アービター2015年

1
複雑なタイプの問題のため、 'contentType:' application / json; 'だけを指定するのが習慣になっていると思います。jsonがjsオブジェクトを文字列化すると、[FromBody]属性を使用する必要がなくなります。
BornToCode 2016

69

webapiでPOSTを使用するのは難しい場合があります。すでに正しい答えに追加したいと思います。

GETの扱いは簡単なので、特にPOSTに焦点を当てます。webapisでのGETの問題を解決するために多くの人が探し回るとは思いません。いずれかの方法..

あなたの質問が-MVC Web APIで、どのように--汎用HTTP動詞以外のカスタムアクションメソッド名を使用するのか?-複数の投稿を実行しますか?-複数の単純なタイプを投稿しますか?-jQueryを介して複合型を投稿しますか?

次に、次の解決策が役立つことがあります。

まず、Web APIでカスタムアクションメソッドを使用するには、次のようにWeb APIルートを追加します。

public static void Register(HttpConfiguration config)
{
    config.Routes.MapHttpRoute(
        name: "ActionApi",
        routeTemplate: "api/{controller}/{action}");
}

次に、次のようなアクションメソッドを作成します。

[HttpPost]
public string TestMethod([FromBody]string value)
{
    return "Hello from http post web api controller: " + value;
}

次に、ブラウザーコンソールから次のjQueryを起動します。

$.ajax({
    type: 'POST',
    url: 'http://localhost:33649/api/TestApi/TestMethod',
    data: {'':'hello'},
    contentType: 'application/x-www-form-urlencoded',
    dataType: 'json',
    success: function(data){ console.log(data) }
});

次に、複数の投稿実行するのは簡単です。複数のアクションメソッドを作成し、[HttpPost]属性で装飾します。[ActionName( "MyAction")]を使用してカスタム名などを割り当てます。以下の4番目のポイントでjQueryに到達します

第三に、第一に、単一のアクションで複数のSIMPLEタイプを投稿することはできません。さらに、(クエリ文字列またはRESTスタイルでパラメーターを渡すことを除いて)単一の単純なタイプでも投稿するための特別な形式があります。これが、Rest Clients(FiddlerやChromeのAdvanced RESTクライアント拡張機能など)で頭を叩き、ほぼ5時間Webを探し回ったときに、次のURLが役立つことが判明した点です。リンクの関連コンテンツを引用すると死んでしまうかもしれません!

Content-Type: application/x-www-form-urlencoded
in the request header and add a = before the JSON statement:
={"Name":"Turbo Tina","Email":"na@Turbo.Tina"}

PS:奇妙な構文に気づきましたか?

http://forums.asp.net/t/1883467.aspx?The+received+value+is+null+when+I+try+to+Post+to+my+Web+Api

とにかく、その話を乗り越えましょう。次に進む:

第4に、jQuery、もちろん、$。ajax()を介して複雑な型投稿すると、すぐに次の役割になります。

アクションメソッドが、IDと名前を持つPersonオブジェクトを受け入れるとしましょう。だから、javascriptから:

var person = { PersonId:1, Name:"James" }
$.ajax({
    type: 'POST',
    url: 'http://mydomain/api/TestApi/TestMethod',
    data: JSON.stringify(person),
    contentType: 'application/json; charset=utf-8',
    dataType: 'json',
    success: function(data){ console.log(data) }
});

そして、アクションは次のようになります。

[HttpPost]
public string TestMethod(Person person)
{
    return "Hello from http post web api controller: " + person.Name;
}

上記のすべてが私のために働いた!! 乾杯!


2
私は数か月ごとにこの問題にぶつかるようですが、ほとんどの場合、最終的に解決しますが、今回はあきらめました。上記のヒントはどれも私のためにそれを解決しないので、私はこれをアプローチとしてビンにすることにしました。うまくいくのがとても難しいなら、なぜわざわざ?とにかく便利です。コンテンツを文字列として取り込み、newtonsoftを使用して変換します。できました。「簡単な」方法で1時間ほど解決した後、「難しい」方法でそれを解決するためにおそらく30秒かかりました。私はこのアプローチに熱心ではありませんが、根本的な問題はありますか?
キネティック、

PS:WebApi2では、ルートデコレータを使用できるようになりました。したがって、この問題は主に対処されます。 asp.net/web-api/overview/web-api-routing-and-actions/…–
Vaibhav

2
観察を追加したい。時々、複雑なタイプ(例:DTO)を渡すときに、WebAPI側でモデルバインディングが失敗(null)する理由は、モデルの1つ以上のプロパティが互換性がない(または解析に失敗する)ためです。例えば。無効なGUIDが割り当てられているGuidプロパティ。この場合、すべてのオブジェクトプロパティにデフォルト/空の値を使用して、再試行してください。
Vaibhav

10

私はこれで遊んでいて、かなり奇妙な結果を発見しました。次のように、C#のクラスにパブリックプロパティがあるとします。

public class Customer
{
    public string contact_name;
    public string company_name;
}

次に、Shyjuの提案に従ってJSON.stringifyトリックを実行し、次のように呼び出す必要があります。

var customer = {contact_name :"Scott",company_name:"HP"};
$.ajax({
    type: "POST",
    data :JSON.stringify(customer),
    url: "api/Customer",
    contentType: "application/json"
});

ただし、次のようにクラスでゲッターとセッターを定義すると、

public class Customer
{
    public string contact_name { get; set; }
    public string company_name { get; set; }
}

その後、より簡単に呼び出すことができます。

$.ajax({
    type: "POST",
    data :customer,
    url: "api/Customer"
});

これはHTTPヘッダーを使用します。

Content-Type:application/x-www-form-urlencoded

ここで何が起こっているのかよくわかりませんが、フレームワークのバグ(機能?)のように見えます。おそらく、異なるバインディングメソッドが異なる「アダプター」を呼び出しており、application / json用のアダプターはパブリックプロパティで機能しますが、フォームエンコードされたデータ用のアダプターは機能しません。

どれがベストプラクティスと見なされるかはわかりません。


6
プロパティとフィールドが異なる理由です。プロパティはベストプラクティスです。最初の例でプロパティと呼ぶのは、実際にはフィールドです。それらにget / setを置くと、プロパティを作成する自動作成されたバッキングフィールドがあります。
paqogomez

これは真実であり、奇妙です。フィールドのみの通常のクラスはフォームの投稿にバインドしませんが、プロパティはバインドします。ところで:なぜこれが事実なのか説明していません...?内部ロジックはJSONデータをフィールドにバインドし、投稿データをプロパティにフォームするだけだと推測できます。それだけです...?
James Wilkins

1
これは、コードがプロパティのみを検索するためです。パブリックフィールドを使用することベストプラクティスではないため、MSチームベストプラクティスではないシナリオを許可しないことを決定しました。
エリックフィリップス

1

JSON.stringify()を使用してJSON形式の文字列を取得します。AJAX呼び出しを行う際に、下記の属性を渡すようにしてください。

  • contentType: 'application / json'

以下は、asp.net Web APIへのajax post呼び出しを行うためのjqueryコードです。

var product =
    JSON.stringify({
        productGroup: "Fablet",
        productId: 1,
        productName: "Lumia 1525 64 GB",
        sellingPrice: 700
    });

$.ajax({
    URL: 'http://localhost/api/Products',
    type: 'POST',
    contentType: 'application/json',
    data: product,
    success: function (data, status, xhr) {
        alert('Success!');
    },
    error: function (xhr, status, error) {
        alert('Update Error occurred - ' + error);
    }
});


2
dataTypeは必須ではありません。
エリックフィリップス

0

WebAPIサービスが、渡すJSONと一致する構造を持つ強く型付けされたオブジェクトを期待していることを確認してください。また、POSTするJSONを文字列化するようにしてください。

これが私のJavaScript(AngluarJSを使用)です。

$scope.updateUserActivity = function (_objuserActivity) {
        $http
        ({
            method: 'post',
            url: 'your url here',
            headers: { 'Content-Type': 'application/json'},
            data: JSON.stringify(_objuserActivity)
        })
        .then(function (response)
        {
            alert("success");
        })
        .catch(function (response)
        {
            alert("failure");
        })
        .finally(function ()
        {
        });

そして、これが私のWebAPIコントローラです。

[HttpPost]
[AcceptVerbs("POST")]
public string POSTMe([FromBody]Models.UserActivity _activity)
{
    return "hello";
}

0

次のコードは、xmlの代わりにjson形式でデータを返す-Web API 2:-

Global.asaxファイルに次の行を追加します

GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;
        GlobalConfiguration.Configuration.Formatters.Remove(GlobalConfiguration.Configuration.Formatters.XmlFormatter);

0
@model MVCClient.Models.ProductDetails

@{
    ViewBag.Title = "ProductDetails";
}
<script src="~/Scripts/jquery-1.8.2.min.js"></script>
<script type="text/javascript">

    $(document).ready(function () {
        $("#Save").click(function () {
            var ProductDetails = new Object();
            ProductDetails.ProductName =  $("#txt_productName").val();
            ProductDetails.ProductDetail = $("#txt_desc").val();
            ProductDetails.Price= $("#txt_price").val();
            $.ajax({
                url: "http://localhost:24481/api/Product/addProduct",
                type: "Post",
                dataType:'JSON',
                data:ProductDetails, 

                success: function (data) {
                    alert('Updated Successfully');
                    //window.location.href = "../Index";
                },
                error: function (msg) { alert(msg); }
            });
        });
    });
    </script>
<h2>ProductDetails</h2>

<form id="form1" method="post">
    <fieldset>
        <legend>ProductDetails</legend>


        <div class="editor-label">
            @Html.LabelFor(model => model.ProductName)
        </div>
        <div class="editor-field">

            <input id="txt_productName" type="text" name="fname">
            @Html.ValidationMessageFor(model => model.ProductName)
        </div>

        <div class="editor-label">
            @Html.LabelFor(model => model.ProductDetail)
        </div>
        <div class="editor-field">

            <input id="txt_desc" type="text" name="fname">
            @Html.ValidationMessageFor(model => model.ProductDetail)
        </div>

        <div class="editor-label">
            @Html.LabelFor(model => model.Price)
        </div>
        <div class="editor-field">

            <input id="txt_price" type="text" name="fname">
            @Html.ValidationMessageFor(model => model.Price)
        </div>



        <p>
            <input id="Save" type="button" value="Create" />
        </p>
    </fieldset>

</form>
    <div>
        @Html.ActionLink("Back to List", "Index")
    </div>

</form>



@section Scripts {
    @Scripts.Render("~/bundles/jqueryval")
}

0

マイクロソフトはこれを行う良い例を示しました:

https://docs.microsoft.com/en-us/aspnet/web-api/overview/advanced/sending-html-form-data-part-1

最初にリクエストを検証します

if (ModelState.IsValid)

シリアル化されたデータを使用します。

Content = new StringContent(update.Status)

ここで、「ステータス」は複合型のフィールドです。シリアル化は.NETによって行われ、そのことを心配する必要はありません。


0

1)クライアント側では、http.postリクエストを次のような文字列で送信できます。

var IndexInfo = JSON.stringify(this.scope.IndexTree);
this.$http.post('../../../api/EvaluationProcess/InsertEvaluationProcessInputType', "'" + IndexInfo + "'" ).then((response: any) => {}

2)次に、Web APIコントローラーで逆シリアル化できます

public ApiResponce InsertEvaluationProcessInputType([FromBody]string IndexInfo)
    {
var des = (ApiReceivedListOfObjects<TempDistributedIndex>)Newtonsoft.Json.JsonConvert.DeserializeObject(DecryptedProcessInfo, typeof(ApiReceivedListOfObjects<TempDistributedIndex>));}

3)ApiReceivedListOfObjectsクラスは以下のようになります

public class ApiReceivedListOfObjects<T>
    {
        public List<T> element { get; set; }

    }

4)ステップ2のJsonConvert.DeserializeObjectコマンドの前に、シリアル化された文字列(ここではIndexInfo)が以下の構造になるようにします。

var resp = @"
    {
        ""element"": [
        {
            ""A"": ""A Jones"",
            ""B"": ""500015763""
        },
        {
            ""A"": ""B Smith"",
            ""B"": ""504986213""
        },
        {
            ""A"": ""C Brown"",
            ""B"": ""509034361""
        }
        ]
    }";
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.