Parámetros múltiples de colocación / publicación de WebAPI

154

Estoy tratando de publicar múltiples parámetros en un controlador WebAPI. Un parámetro es de la URL y el otro del cuerpo. Aquí está la url: /offers/40D5E19D-0CD5-4FBD-92F8-43FDBB475333/prices/

Aquí está mi código de controlador:

public HttpResponseMessage Put(Guid offerId, OfferPriceParameters offerPriceParameters)
{
    //What!?
    var ser = new DataContractJsonSerializer(typeof(OfferPriceParameters));
    HttpContext.Current.Request.InputStream.Position = 0;
    var what = ser.ReadObject(HttpContext.Current.Request.InputStream);

    return new HttpResponseMessage(HttpStatusCode.Created);
}

El contenido del cuerpo está en JSON:

{
    "Associations":
    {
        "list": [
        {
            "FromEntityId":"276774bb-9bd9-4bbd-a7e7-6ed3d69f196f",
            "ToEntityId":"ed0d2616-f707-446b-9e40-b77b94fb7d2b",
            "Types":
            {
                "list":[
                {
                    "BillingCommitment":5,
                    "BillingCycle":5,
                    "Prices":
                    {
                        "list":[
                        {
                            "CurrencyId":"274d24c9-7d0b-40ea-a936-e800d74ead53",
                            "RecurringFee":4,
                            "SetupFee":5
                        }]
                    }
                }]
            }
        }]
    }
}

¿Alguna idea de por qué el enlace predeterminado no puede vincularse al offerPriceParametersargumento de mi controlador? Siempre se establece en nulo. Pero puedo recuperar los datos del cuerpo usando DataContractJsonSerializer.

También trato de usar el FromBodyatributo del argumento pero tampoco funciona.

Normand Bedard
fuente

Respuestas:

78
[HttpPost]
public string MyMethod([FromBody]JObject data)
{
    Customer customer = data["customerData"].ToObject<Customer>();
    Product product = data["productData"].ToObject<Product>();
    Employee employee = data["employeeData"].ToObject<Employee>();
    //... other class....
}

usando referencia

using Newtonsoft.Json.Linq;

Solicitud de uso para JQuery Ajax

var customer = {
    "Name": "jhon",
    "Id": 1,
};
var product = {
    "Name": "table",
    "CategoryId": 5,
    "Count": 100
};
var employee = {
    "Name": "Fatih",
    "Id": 4,
};

var myData = {};
myData.customerData = customer;
myData.productData = product;
myData.employeeData = employee;

$.ajax({
    type: 'POST',
    async: true,
    dataType: "json",
    url: "Your Url",
    data: myData,
    success: function (data) {
        console.log("Response Data ↓");
        console.log(data);
    },
    error: function (err) {
        console.log(err);
    }
});
Fatih GÜRDAL
fuente
3
Gran solución Si aún no está claro para otros, también puede usar .ToObject <int> (), .ToObject <decimal> (), .ToString (), etc. si está pasando parámetros simples y múltiples desde su llamada ajax.
secretwep
Gracias, probé su solución creando mi propia API y probándola a través de Postman y funciona bien; pero agregué un cuarto parámetro como var test = {"Name": "test"} y lo agregué al objeto myData y fue enviado con éxito; ¿Hay alguna forma de evitar esto y restringir solo los objetos originales?
Mlle116
@ H.Al No, Newtonsoft.Json puede tener cualquier tipo de datos json que la biblioteca conozca sobre la traducción. No puedes evitar el envío de datos. Depende de usted usar los datos entrantes
Fatih GÜRDAL
63

Nativamente WebAPI no admite el enlace de múltiples parámetros POST. Como señala Colin, hay una serie de limitaciones que se describen en la publicación de mi blog que él menciona.

Hay una solución al crear un enlazador de parámetros personalizado. El código para hacer esto es feo y complicado, pero publiqué el código junto con una explicación detallada en mi blog, listo para ser conectado a un proyecto aquí:

Pasar múltiples valores POST simples a la API web ASP.NET

Rick Strahl
fuente
1
Todo el crédito va para ti :) Simplemente estaba leyendo tu serie en WebAPI mientras comenzaba mi propia implementación cuando surgió esta pregunta.
Colin Young
¡Gracias! Muy útil.
Normand Bedard
2
A partir de 2019 lo hace ahora.
Max
@John: ¿hay una versión base desde la que se admita esta funcionalidad? No tener ningún éxito hoy.
Neil Moss
26

Si se utiliza el enrutamiento de atributos, puede utilizar los atributos [FromUri] y [FromBody].

Ejemplo:

[HttpPost()]
[Route("api/products/{id:int}")]
public HttpResponseMessage AddProduct([FromUri()] int id,  [FromBody()] Product product)
{
  // Add product
}
Bryan Rayner
fuente
1
He usado exactamente el mismo método. Necesito pasar dos modelos a la acción. He pasado uno con menos propiedades a través de la cadena de consulta y otro del cuerpo. Además, no es necesario especificar explícitamente el atributo [FromBody]
Sergey G.
1
No puedo hacer que esto funcione, ¿tienes un ejemplo más completo?
The One
No creo que esta sea la forma correcta de enviar datos a través del método POST, pero no veo otra solución si tiene que enviar 2 modelos por correo.
Alexandr
Esta respuesta es el Jam!
Leonardo Wildt
1
Estoy usando aspnetcore y debes usarlo en [FromRoute]lugar de[FromUri]
DanielV
19

Pasamos el objeto Json por el método HttpPost y lo analizamos en un objeto dinámico. funciona bien. Este es un código de muestra:

webapi:

[HttpPost]
public string DoJson2(dynamic data)
{
   //whole:
   var c = JsonConvert.DeserializeObject<YourObjectTypeHere>(data.ToString()); 

   //or 
   var c1 = JsonConvert.DeserializeObject< ComplexObject1 >(data.c1.ToString());

   var c2 = JsonConvert.DeserializeObject< ComplexObject2 >(data.c2.ToString());

   string appName = data.AppName;
   int appInstanceID = data.AppInstanceID;
   string processGUID = data.ProcessGUID;
   int userID = data.UserID;
   string userName = data.UserName;
   var performer = JsonConvert.DeserializeObject< NextActivityPerformers >(data.NextActivityPerformers.ToString());

   ...
}

El tipo de objeto complejo podría ser objeto, matriz y diccionario.

ajaxPost:
...
Content-Type: application/json,
data: {"AppName":"SamplePrice",
       "AppInstanceID":"100",
       "ProcessGUID":"072af8c3-482a-4b1c‌​-890b-685ce2fcc75d",
       "UserID":"20",
       "UserName":"Jack",
       "NextActivityPerformers":{
           "39‌​c71004-d822-4c15-9ff2-94ca1068d745":[{
                 "UserID":10,
                 "UserName":"Smith"
           }]
       }}
...
Bes Ley
fuente
1
Podemos poner múltiples parámetros formateados como un objeto json para publicar, y luego lo analizaremos en múltiples objetos en el lado del servidor. Esta podría ser otra forma de pensar.
Bes Ley
@EkoosticMartin, funciona bien, debe analizar el tipo dinámico mediante: JsonConvert.DeserializeObject <YourObjectTypeHere> (data.ToString ()); Aquí hay una muestra compleja de contenido de datos, que incluye una matriz y un objeto de diccionario. {"AppName": "SamplePrice", "AppInstanceID": "100", "ProcessGUID": "072af8c3-482a-4b1c-890b-685ce2fcc75d", "UserID": "20", "UserName": "Jack", " NextActivityPerformers ": {" 39c71004-d822-4c15-9ff2-94ca1068d745 ": [{" UserID ": 10," UserName ":" Smith "}]}}
Bes Ley
1
Bien, claro, entonces solo use un solo parámetro de cadena, no hay diferencia.
EkoostikMartin
Single no significa simple, la cadena json podría combinarse con muchos tipos diferentes de objetos. Este es el punto clave, y es otra forma de resolver preguntas.
Bes Ley
1
Excelente solucion! Gracias :)
Carl R
10

Se puede usar una clase de parámetro simple para pasar múltiples parámetros en una publicación:

public class AddCustomerArgs
{
    public string First { get; set; }
    public string Last { get; set; }
}

[HttpPost]
public IHttpActionResult AddCustomer(AddCustomerArgs args)
{
    //use args...
    return Ok();
}
Greg Gum
fuente
¿Hay alguna posibilidad de que sepa cómo debe verse la solicitud POST de muestra?
Nadia Solovyeva
@ NadiaSolovyeva, es más que una cadena de consulta, porque la información PUBLICADA está en el cuerpo, no en la cadena de consulta. Me gusta usar PostMan para hacer consultas de prueba, y luego puedes ver exactamente cómo se ve.
Greg Gum
No importa, ya he encontrado cómo hacerlo. POST header: Content-Type: application / json; POST cuerpo: {"Primero": "1", "Último": "1000"}
Nadia Solovyeva
9

Puede permitir múltiples parámetros POST utilizando la clase MultiPostParameterBinding de https://github.com/keith5000/MultiPostParameterBinding

Para usarlo:

1) Descargue el código en la carpeta Fuente y agréguelo a su proyecto de API web o cualquier otro proyecto en la solución.

2) Use el atributo [MultiPostParameters] en los métodos de acción que necesitan soportar múltiples parámetros POST.

[MultiPostParameters]
public string DoSomething(CustomType param1, CustomType param2, string param3) { ... }

3) Agregue esta línea en Global.asax.cs al método Application_Start en cualquier lugar antes de la llamada a GlobalConfiguration.Configure (WebApiConfig.Register) :

GlobalConfiguration.Configuration.ParameterBindingRules.Insert(0, MultiPostParameterBinding.CreateBindingForMarkedParameters);

4) Haga que sus clientes pasen los parámetros como propiedades de un objeto. Un objeto JSON de ejemplo para el DoSomething(param1, param2, param3)método es:

{ param1:{ Text:"" }, param2:{ Text:"" }, param3:"" }

Ejemplo JQuery:

$.ajax({
    data: JSON.stringify({ param1:{ Text:"" }, param2:{ Text:"" }, param3:"" }),
    url: '/MyService/DoSomething',
    contentType: "application/json", method: "POST", processData: false
})
.success(function (result) { ... });

Visite el enlace para más detalles.

Descargo de responsabilidad: estoy directamente asociado con el recurso vinculado.

Keith
fuente
7

Buena pregunta y comentarios: aprendí mucho de las respuestas aquí :)

Como ejemplo adicional, tenga en cuenta que también puede mezclar cuerpo y rutas, por ejemplo

[RoutePrefix("api/test")]
public class MyProtectedController 
{
    [Authorize]
    [Route("id/{id}")]
    public IEnumerable<object> Post(String id, [FromBody] JObject data)
    {
        /*
          id                                      = "123"
          data.GetValue("username").ToString()    = "user1"
          data.GetValue("password").ToString()    = "pass1"
         */
    }
}

Llamando así:

POST /api/test/id/123 HTTP/1.1
Host: localhost
Accept: application/json
Content-Type: application/x-www-form-urlencoded
Authorization: Bearer x.y.z
Cache-Control: no-cache

username=user1&password=pass1


enter code here
Anthony De Souza
fuente
Me gustaría enviar 2 parámetros de tipo complejo. Como [HttpPost] cadena pública UploadFile (UploadMediaFile mediaFile, byte [] datas) cómo hacerlo.
Başar Kaya
2

¿Cómo se ve su routeTemplate para este caso?

Publicaste esta url:

/offers/40D5E19D-0CD5-4FBD-92F8-43FDBB475333/prices/

Para que esto funcione, esperaría una ruta como esta en su WebApiConfig:

routeTemplate: {controller}/{offerId}/prices/

Otros supuestos son: - se llama a su controlador OffersController. - el objeto JSON que está pasando en el cuerpo de la solicitud es de tipo OfferPriceParameters(no de ningún tipo derivado) - no tiene ningún otro método en el controlador que pueda interferir con este (si lo hace, intente comentarlos y vea qué sucede)

Y como Filip mencionó, ayudaría a sus preguntas si comenzara a aceptar algunas respuestas, ya que la "tasa de aceptación del 0%" podría hacer que las personas piensen que están perdiendo el tiempo.

Joanna Derks
fuente
Mi ruta es "ofertas / {offerId} / precios". Este es el único método en mi controlador.
Normand Bedard
2

Si no desea seguir el modelo, puede usar DTO para hacer esto por usted. Por ejemplo, cree una acción POST en DataLayer que acepte un tipo complejo y envíe datos desde BusinessLayer. Puede hacerlo en caso de UI-> llamada a la API.

Aquí hay una muestra de DTO. Asignar un maestro a un alumno y asignar múltiples trabajos / asignaturas al alumno.

public class StudentCurriculumDTO
 {
     public StudentTeacherMapping StudentTeacherMapping { get; set; }
     public List<Paper> Paper { get; set; }
 }    
public class StudentTeacherMapping
 {
     public Guid StudentID { get; set; }
     public Guid TeacherId { get; set; }
 }

public class Paper
 {
     public Guid PaperID { get; set; }
     public string Status { get; set; }
 }

Entonces la acción en DataLayer se puede crear como:

[HttpPost]
[ActionName("MyActionName")]
public async Task<IHttpActionResult> InternalName(StudentCurriculumDTO studentData)
  {
     //Do whatever.... insert the data if nothing else!
  }

Para llamarlo desde BusinessLayer:

using (HttpResponseMessage response = await client.PostAsJsonAsync("myendpoint_MyActionName", dataof_StudentCurriculumDTO)
  {
     //Do whatever.... get response if nothing else!
  }

Ahora, esto seguirá funcionando si quiero enviar datos de varios estudiantes a la vez. Modifica el me MyActiongusta a continuación. No es necesario escribir [FromBody], WebAPI2 toma el tipo complejo [FromBody] por defecto.

public async Task<IHttpActionResult> InternalName(List<StudentCurriculumDTO> studentData)

y luego mientras lo llama, pase un List<StudentCurriculumDTO>dato.

using (HttpResponseMessage response = await client.PostAsJsonAsync("myendpoint_MyActionName", List<dataof_StudentCurriculumDTO>)
sandiejat
fuente
0

Parámetros de solicitud como

ingrese la descripción de la imagen aquí

Código API web sea como

public class OrderItemDetailsViewModel
{
    public Order order { get; set; }
    public ItemDetails[] itemDetails { get; set; }
}

public IHttpActionResult Post(OrderItemDetailsViewModel orderInfo)
{
    Order ord = orderInfo.order;
    var ordDetails = orderInfo.itemDetails;
    return Ok();
}
Pradip Rupareliya
fuente
0

Puede obtener los datos de formulario como cadena:

    protected NameValueCollection GetFormData()
    {
        string root = HttpContext.Current.Server.MapPath("~/App_Data");
        var provider = new MultipartFormDataStreamProvider(root);

        Request.Content.ReadAsMultipartAsync(provider);

        return provider.FormData;
    }

    [HttpPost]
    public void test() 
    {
        var formData = GetFormData();
        var userId = formData["userId"];

        // todo json stuff
    }

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

Martien de Jong
fuente