Jak przekazać dane Json POST do metody Web API jako obiekt?

304

Aplikacja ASP.NET MVC4 Web API definiuje metodę wysyłania w celu uratowania klienta. Klient jest przekazywany w formacie json w treści żądania POST. Parametr klienta w metodzie post zawiera wartości null dla właściwości.

Jak to naprawić, aby opublikowane dane były przekazywane jako obiekt klienta?

Jeśli to możliwe, należy użyć Content-Type: application / x-www-form-urlencoded, ponieważ nie wiem, jak to zmienić w metodzie javascript, która tworzy posty.

Kontroler:

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; }
     }

Żądanie:

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"}
Andrus
źródło

Odpowiedzi:

525

EDYCJA : 31/10/2017

Ten sam kod / podejście będzie działać również w przypadku Asp.Net Core 2.0 . Główna różnica polega na tym, że w rdzeniu asp.net zarówno kontrolery interfejsu API sieci Web, jak i kontrolery Mvc są połączone w jeden model kontrolera. Więc twój typ zwracany może być IActionResultalbo jeden To realizacji (np OkObjectResult)


Posługiwać się

contentType:"application/json"

Musisz użyć JSON.stringifymetody, aby przekonwertować go na ciąg JSON podczas wysyłania,

Spoiwo modelu wiąże dane JSON z obiektem klasy.

Poniższy kod będzie działał dobrze (przetestowany)

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

Wynik

wprowadź opis zdjęcia tutaj

contentTypewłaściwość informuje serwer, że wysyłamy dane w formacie JSON. Ponieważ wysłaliśmy strukturę danych JSON, powiązanie modelu nastąpi poprawnie.

Jeśli przejrzysz nagłówki żądania ajax, zobaczysz, że Content-Typewartość jest ustawiona na application/json.

Jeśli nie określisz jawnie contentType, użyje domyślnego typu treści, którym jest application/x-www-form-urlencoded;


Edytuj w listopadzie 2015 r., Aby rozwiązać inne możliwe problemy poruszone w komentarzach

Księgowanie złożonego obiektu

Załóżmy, że masz złożoną klasę modelu widoku jako parametr metody akcji interfejsu API w ten sposób

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;}
}

a punkt końcowy interfejsu API jest podobny

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;
    }
}

W chwili pisania tego tekstu ASP.NET MVC 6 jest najnowszą stabilną wersją, aw MVC6 zarówno kontrolery interfejsu API sieci Web, jak i kontrolery MVC dziedziczą po Microsoft.AspNet.Mvc.Controllerklasie podstawowej.

Aby wysłać dane do metody od strony klienta, poniższy kod powinien działać poprawnie

//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 :)
});

Wiązanie modelu działa w przypadku niektórych właściwości, ale nie wszystkich! Dlaczego ?

Jeśli nie ozdobisz parametru metody interfejsu API WWW [FromBody]atrybutem

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

I wyślij model (surowy obiekt javascript, nie w formacie JSON) bez określania wartości właściwości contentType

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

Wiązanie modelu będzie działać dla płaskich właściwości w modelu, a nie dla właściwości, w których typ jest złożony / inny typ. W naszym przypadku, Idi Namewłaściwości zostaną właściwie związany z parametrem m, jednak Tagsnieruchomość będzie pusta lista.

Ten sam problem wystąpi, jeśli używasz krótkiej wersji, $.postktóra będzie używać domyślnego typu zawartości podczas wysyłania żądania.

$.post("../product/save", model, function (res) {
    //res contains the markup returned by the partial view
    console.log('res', res);
});
Shyju
źródło
4
Nie jestem pewien, co zrobiłem, ale wróciłem dziś rano i wróciłem tą samą łodzią. Obiekt jest pusty w kontrolerze. znowu zaczynamy lol
Grayson
1
upewnij się, że typ zawartości jest zapisany „Content-Type: application / json” podczas korzystania ze skrzypka. Twoje zdrowie!
ioWint,
1
Po prostu rozwiązałeś mi dzień pracy !!! Ta drobna funkcja „JSON.stringify (data)” to zrobiła!
Gil Allen
1
Pamiętaj, że jeśli to zrobisz (zmienisz nagłówek Content-Type) i wysyłasz żądanie CORS, jQuery rozpocznie dodawanie żądań OPTIONS inspekcji wstępnej przed Twoim testem POST, który serwer będzie musiał obsłużyć.
Arbiter
1
Ze względu na problem ze złożonymi typami myślę, że mam w zwyczaju sprecyzować „contentType:„ application / json; ” i json strify obiektu js, a następnie nie trzeba używać atrybutu [FromBody].
BornToCode,
69

Praca z POST w webapi może być trudna! Chciałbym dodać do już poprawnej odpowiedzi ..

Skoncentruje się szczególnie na POST, ponieważ obsługa GET jest banalna. Nie sądzę, aby wielu szukało rozwiązania problemu z GET z webapis. W każdym razie ..

Jeśli twoje pytanie brzmi - w MVC Web Api, jak: - Używać niestandardowych nazw metod akcji innych niż ogólne czasowniki HTTP? - Wykonać wiele postów? - Opublikować wiele prostych typów? - Publikować złożone typy za pomocą jQuery?

Mogą pomóc następujące rozwiązania:

Po pierwsze, aby użyć niestandardowych metod działania w interfejsie API sieci Web, dodaj trasę interfejsu API jako:

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

A potem możesz stworzyć metody akcji, takie jak:

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

Teraz uruchom następujące jQuery z konsoli przeglądarki

$.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) }
});

Po drugie, aby wykonać wiele postów , jest to proste, stwórz wiele metod akcji i udekoruj atrybutem [HttpPost]. Użyj [ActionName („MyAction”)], aby przypisać niestandardowe nazwy itp. Przejdzie do jQuery w czwartym punkcie poniżej

Po trzecie, po pierwsze, opublikowanie wielu typów SIMPLE w jednym działaniu nie jest możliwe. Ponadto istnieje specjalny format publikowania nawet jednego prostego typu (oprócz przekazywania parametru w ciągu zapytania lub stylu REST). To był punkt, w którym uderzyłem głową w Rest Clients (jak Fiddler i zaawansowane rozszerzenie klienta Chrome REST dla Chrome) i polowałem w Internecie przez prawie 5 godzin, kiedy ostatecznie następujący adres URL okazał się pomocny. Przytocz odpowiednią treść linku, który może stracić ważność!

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

PS: Zauważyłeś osobliwą składnię ?

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

Tak czy inaczej, przejdźmy tę historię. Iść dalej:

Po czwarte, publikowanie złożonych typów za pomocą jQuery, ofcourse, $ .ajax () wkrótce pojawi się w roli:

Powiedzmy, że metoda akcji akceptuje obiekt Person, który ma identyfikator i nazwę. Tak więc z 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) }
});

A akcja będzie wyglądać następująco:

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

Wszystkie powyższe działały dla mnie !! Twoje zdrowie!

Vaibhav
źródło
2
Wydaje mi się, że napotykam ten problem co kilka miesięcy, przez większość czasu w końcu go rozwiązuję, ale tym razem się poddałem. Żadna z powyższych wskazówek nie rozwiązuje mnie, więc zdecydowałem się na bin to. Jeśli tak trudno jest to naprawić, po co się tym przejmować? To i tak tylko wygoda - po prostu weź treść jako ciąg i użyj newtonsoft, aby ją przekształcić. Gotowy. Prawdopodobnie zajęło to 30 sekund, aby rozwiązać problem „trudny” po około godzinie próby rozwiązania problemu w „łatwy” sposób. Nie przepadam za tym podejściem, ale czy jest z tym podstawowy problem?
Kinetic,
PS: W WebApi2 możemy teraz używać Route Decorators. Więc ten problem jest przede wszystkim rozwiązany. asp.net/web-api/overview/web-api-routing-and-actions/…
Vaibhav
2
Chciałbym dodać obserwację. Czasami przyczyną niepowodzenia wiązania modelu (zero) po stronie WebAPI przy przekazywaniu typu złożonego (np. DTO) jest to, że co najmniej jedna właściwość w modelu będzie niezgodna (lub nie zostanie przeanalizowana). Na przykład. Właściwość Guid, której przypisano niepoprawny identyfikator GUID. W takim przypadku spróbuj użyć domyślnych / pustych wartości dla wszystkich właściwości obiektu i spróbuj ponownie.
Vaibhav
10

Właśnie się z tym bawiłem i odkryłem dość dziwny wynik. Załóżmy, że masz właściwości publiczne w swojej klasie w języku C # w następujący sposób:

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

musisz wykonać sztuczkę JSON.stringify zgodnie z sugestią Shyju i nazwać ją tak:

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

Jeśli jednak zdefiniujesz metody pobierające i ustawiające w swojej klasie w następujący sposób:

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

możesz to nazwać znacznie prościej:

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

Używa to nagłówka HTTP:

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

Nie jestem do końca pewien, co się tutaj dzieje, ale wygląda na błąd (funkcję?) W ramach. Przypuszczalnie różne metody wiązania wywołują różne „adaptery”, i chociaż adapter dla aplikacji / json jeden działa z właściwościami publicznymi, ten dla danych zakodowanych w formacie nie.

Nie mam jednak pojęcia, co byłoby uważane za najlepszą praktykę.

Andy
źródło
6
Właściwości kontra pola są różne. Właściwości są najlepszą praktyką. To, co nazywasz właściwościami w tym pierwszym przykładzie, to w rzeczywistości pola. Po umieszczeniu na nich polecenia get / set mają one automatycznie utworzone pole kopii zapasowej, które nadaje im właściwości.
paqogomez
To takie prawdziwe i dziwne. Normalne klasy zawierające tylko pola nie będą wiązane w celu tworzenia postów, ale właściwości będą. BTW: Nadal nie wyjaśnia, dlaczego tak jest ...? Mogę tylko zgadywać, że wewnętrzna logika wiąże dane JSON tylko z polami i tworzy dane post do właściwości, a to po prostu ...?
James Wilkins
1
Dzieje się tak, ponieważ kod szuka tylko właściwości. Ponieważ stosowanie pól publicznych nie jest najlepszą praktyką, zespół MS postanowił nie zezwalać na scenariusze niezgodne z najlepszymi praktykami, całkiem dobry powód IMHO.
Erik Philips
1

Użyj JSON.stringify (), aby uzyskać ciąg w formacie JSON, upewnij się, że podczas wykonywania wywołania AJAX przekazujesz poniższe atrybuty:

  • contentType: „application / json”

Poniżej znajduje się kod podający jquery, aby wywołać wywołanie ajax do interfejsu API asp.net:

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);
    }
});

Dilip Nannaware
źródło
2
dataType nie jest wymagane.
Erik Philips
0

Upewnij się, że twoja usługa WebAPI oczekuje silnie typowanego obiektu o strukturze pasującej do przekazywanego JSON. I upewnij się, że napisałeś JSON, który publikujesz.

Oto mój JavaScript (używając 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 ()
        {
        });

A oto mój kontroler WebAPI:

[HttpPost]
[AcceptVerbs("POST")]
public string POSTMe([FromBody]Models.UserActivity _activity)
{
    return "hello";
}
szkocki
źródło
0

Poniższy kod, aby zwrócić dane w formacie json, zamiast xml -Web API 2: -

Umieść następujący wiersz w pliku Global.asax

GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;
        GlobalConfiguration.Configuration.Formatters.Remove(GlobalConfiguration.Configuration.Formatters.XmlFormatter);
UJS
źródło
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")
}
Debendra Dash
źródło
0

1) Po stronie klienta możesz wysłać zapytanie http.post w łańcuchu jak poniżej

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

2) Następnie w kontrolerze interfejsu API można go przekształcić z postaci szeregowej

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

3) Twoja klasa ApiReceivedListOfObjects powinna wyglądać jak poniżej

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

    }

4) upewnij się, że Twój szeregowany ciąg (tutaj IndexInfo) staje się podobny do poniższej struktury przed poleceniem JsonConvert.DeserializeObject w kroku 2

var resp = @"
    {
        ""element"": [
        {
            ""A"": ""A Jones"",
            ""B"": ""500015763""
        },
        {
            ""A"": ""B Smith"",
            ""B"": ""504986213""
        },
        {
            ""A"": ""C Brown"",
            ""B"": ""509034361""
        }
        ]
    }";
Mahdi Ghafoorian
źródło