¿Cómo pasar datos json POST al método API web como un objeto?

304

La aplicación API web ASP.NET MVC4 define el método de publicación para salvar al cliente. El cliente se pasa en formato json en el cuerpo de la solicitud POST. El parámetro del cliente en el método de publicación contiene valores nulos para propiedades.

¿Cómo solucionar esto para que los datos publicados pasen como objeto de cliente?

Si es posible, se debe usar Content-Type: application / x-www-form-urlencoded ya que no sé cómo cambiarlo en el método javascript que publica el formulario.

Controlador:

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

Solicitud:

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
fuente

Respuestas:

525

EDITAR : 31/10/2017

El mismo código / enfoque funcionará también para Asp.Net Core 2.0 . La principal diferencia es que, en asp.net core, tanto los controladores de API web como los controladores de Mvc se fusionan en un solo modelo de controlador. Por lo que su tipo de retorno podría ser IActionResulto uno de ellos de aplicación (Ej: OkObjectResult)


Utilizar

contentType:"application/json"

Debe usar el JSON.stringifymétodo para convertirlo a una cadena JSON cuando lo envíe,

Y la carpeta modelo enlazará los datos json a su objeto de clase.

El siguiente código funcionará bien (probado)

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

Resultado

ingrese la descripción de la imagen aquí

contentTypeLa propiedad le dice al servidor que estamos enviando los datos en formato JSON. Como enviamos una estructura de datos JSON, el enlace del modelo se realizará correctamente.

Si inspecciona los encabezados de la solicitud ajax, puede ver que el Content-Typevalor se establece como application/json.

Si no especifica contentType explícitamente, utilizará el tipo de contenido predeterminado que es application/x-www-form-urlencoded;


Edite en noviembre de 2015 para abordar otros posibles problemas planteados en los comentarios

Publicar un objeto complejo

Supongamos que tiene una clase de modelo de vista compleja como su parámetro de método de acción de API web como este

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

y su punto final de la API web es como

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

Al momento de escribir esto, ASP.NET MVC 6 es la última versión estable y en MVC6, tanto los controladores de API web como los controladores MVC heredan de la Microsoft.AspNet.Mvc.Controllerclase base.

Para enviar datos al método desde el lado del cliente, el siguiente código debería funcionar bien

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

El enlace de modelo funciona para algunas propiedades, ¡pero no para todas! Por qué ?

Si no decora el parámetro del método API web con el [FromBody]atributo

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

Y envíe el modelo (objeto javascript sin procesar, no en formato JSON) sin especificar el valor de la propiedad contentType

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

El enlace del modelo funcionará para las propiedades planas del modelo, no para las propiedades donde el tipo es complejo / otro tipo. En nuestro caso, Idy las Namepropiedades se vincularán correctamente al parámetro m, pero la Tagspropiedad será una lista vacía.

El mismo problema ocurrirá si está utilizando la versión corta, $.postque utilizará el Tipo de contenido predeterminado al enviar la solicitud.

$.post("../product/save", model, function (res) {
    //res contains the markup returned by the partial view
    console.log('res', res);
});
Shyju
fuente
44
No estoy seguro de lo que hice, pero regresé esta mañana y regresé en el mismo bote. El objeto es nulo en el controlador. aquí vamos de nuevo jajaja
Grayson
1
asegúrese de que el tipo de contenido esté escrito "Tipo de contenido: aplicación / json" mientras usa el violinista. ¡Salud!
ioWint
1
Simplemente me resolviste un día de trabajo !!! ¡Esta pequeña función "JSON.stringify (data)" lo hizo!
Gil Allen
1
Tenga en cuenta que si hace esto (cambie el encabezado Content-Type) y está haciendo una solicitud CORS, jQuery comenzará a agregar solicitudes OPTIONS de verificación previa antes de su POST que el servidor tendrá que manejar.
Árbitro
1
Debido al problema con los tipos complejos, creo que es un hábito solo especificar 'contentType:' application / json; ' y json stringifican el objeto js y luego no hay necesidad de usar el atributo [FromBody].
BornToCode
69

¡Trabajar con POST en webapi puede ser complicado! Me gustaría agregar a la respuesta ya correcta.

Se centrará específicamente en POST ya que tratar con GET es trivial. No creo que muchos estarían buscando para resolver un problema con GET con webapis. De todos modos ..

Si su pregunta es: en MVC Web Api, ¿cómo: - Usar nombres de métodos de acción personalizados distintos de los verbos HTTP genéricos? - Realizar múltiples publicaciones? - Publicar múltiples tipos simples? - Publicar tipos complejos a través de jQuery?

Entonces las siguientes soluciones pueden ayudar:

Primero, para usar métodos de acción personalizados en la API web, agregue una ruta de API web como:

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

Y luego puede crear métodos de acción como:

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

Ahora, dispara el siguiente jQuery desde la consola de tu navegador

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

En segundo lugar, para realizar múltiples publicaciones , es simple, crear múltiples métodos de acción y decorar con el atributo [HttpPost]. Use el [ActionName ("MyAction")] para asignar nombres personalizados, etc. Vendrá a jQuery en el cuarto punto a continuación

En tercer lugar, en primer lugar, no es posible publicar múltiples tipos SIMPLES en una sola acción. Además, hay un formato especial para publicar incluso un solo tipo simple (además de pasar el parámetro en la cadena de consulta o estilo REST). Este fue el punto que me hizo golpearme la cabeza con Rest Clients (como Fiddler y la extensión avanzada de cliente REST de Chrome) y buscar en la web durante casi 5 horas cuando, finalmente, la siguiente URL demostró ser de ayuda. ¡Citará el contenido relevante para el enlace que podría volverse muerto!

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

PD: ¿Notó la sintaxis peculiar ?

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

De todos modos, vamos a superar esa historia. Hacia adelante:

En cuarto lugar, publicar tipos complejos a través de jQuery, por supuesto, $ .ajax () pronto entrará en el rol:

Digamos que el método de acción acepta un objeto Persona que tiene una identificación y un nombre. Entonces, desde 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) }
});

Y la acción se verá así:

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

¡Todo lo anterior, funcionó para mí! ¡Salud!

Vaibhav
fuente
2
Parece que encuentro este problema cada pocos meses, la mayoría de las veces eventualmente lo resuelvo, pero esta vez me di por vencido. Ninguno de los consejos anteriores me lo resuelve, así que decidí descartar esto como un enfoque. Si es tan difícil hacerlo bien, ¿por qué molestarse? De todos modos, es solo una conveniencia: simplemente tome el contenido como una cadena y use newtonsoft para transformarlo. Hecho. Tomó probablemente 30 segundos para resolverlo de la manera "difícil" después de intentar durante aproximadamente una hora resolverlo de la manera "fácil". No estoy loco por el enfoque, pero ¿hay un problema fundamental con él?
Cinética
PD: En WebApi2, ahora podemos usar Route Decorators. Entonces, este problema se aborda principalmente. asp.net/web-api/overview/web-api-routing-and-actions/…
Vaibhav
2
Quisiera agregar una observación. A veces, la razón por la cual el enlace del modelo falla (nulo) en el lado de WebAPI, al pasar un tipo complejo (por ejemplo: DTO), es que una o más propiedades en el modelo serán incompatibles (o no se analizarán). P.ej. Una propiedad Guid a la que se le asigna un GUID no válido. En este caso, intente usar los valores predeterminados / vacíos para todas las propiedades del objeto e intente nuevamente.
Vaibhav
10

Acabo de jugar con esto y descubrí un resultado bastante extraño. Digamos que tiene propiedades públicas en su clase en C # como esta:

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

entonces debes hacer el truco JSON.stringify como lo sugiere Shyju y llamarlo así:

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

Sin embargo, si define getters y setters en su clase de esta manera:

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

entonces puedes llamarlo mucho más simple:

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

Esto usa el encabezado HTTP:

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

No estoy muy seguro de lo que está sucediendo aquí, pero parece un error (¿característica?) En el marco. Presumiblemente, los diferentes métodos de enlace están llamando a diferentes "adaptadores", y si bien el adaptador para la aplicación / json funciona con propiedades públicas, el de los datos codificados en forma no.

Sin embargo, no tengo idea de cuál sería considerada la mejor práctica.

Andy
fuente
66
Propiedades vs campos es por qué es diferente. Las propiedades son la mejor práctica. Lo que llama propiedades en ese primer ejemplo son, de hecho, campos. Cuando coloca un get / set en ellos, tienen un campo de respaldo creado automáticamente que los convierte en propiedades.
paqogomez
Esto es muy cierto y extraño. Las clases normales con solo campos no se unirán para formar publicaciones, pero las propiedades sí. Por cierto: ¿Todavía no explica por qué este es el caso ...? Solo puedo adivinar que la lógica interna solo unirá los datos JSON a los campos y formará los datos de la publicación en las propiedades, ¿y eso es simplemente ...?
James Wilkins el
1
Es el caso porque el código solo busca propiedades. Dado que el uso de campos públicos es que no las mejores prácticas, el equipo de MS decidió no permitir ni mejores escenarios de práctica, razón bastante buena en mi humilde opinión.
Erik Philips
1

Use JSON.stringify () para obtener la cadena en formato JSON, asegúrese de que al realizar la llamada AJAX pase los atributos mencionados a continuación:

  • contentType: 'aplicación / json'

A continuación se muestra el código de Give JQuery para hacer una llamada ajax a la API web de 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
fuente
2
no se requiere dataType.
Erik Philips
0

Asegúrese de que su servicio WebAPI espera un objeto fuertemente tipado con una estructura que coincida con el JSON que está pasando. Y asegúrese de stringificar el JSON que está PUBLICANDO.

Aquí está mi JavaScript (usando 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 ()
        {
        });

Y aquí está mi controlador WebAPI:

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

Siguiente código para devolver datos en formato json, en lugar de xml -Web API 2: -

Ponga la siguiente línea en el archivo Global.asax

GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;
        GlobalConfiguration.Configuration.Formatters.Remove(GlobalConfiguration.Configuration.Formatters.XmlFormatter);
UJS
fuente
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
fuente
0

1) En el lado del cliente, puede enviarle una solicitud http.post en cadena como a continuación

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

2) Luego, en su controlador de API web, puede deserializarlo

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

3) Su clase ApiReceivedListOfObjects debería ser como a continuación

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

    }

4) asegúrese de que su cadena serializada (IndexInfo aquí) se convierta en la siguiente estructura antes del comando JsonConvert.DeserializeObject en el paso 2

var resp = @"
    {
        ""element"": [
        {
            ""A"": ""A Jones"",
            ""B"": ""500015763""
        },
        {
            ""A"": ""B Smith"",
            ""B"": ""504986213""
        },
        {
            ""A"": ""C Brown"",
            ""B"": ""509034361""
        }
        ]
    }";
Mahdi ghafoorian
fuente