WebAPI Più parametri Put / Post


154

Sto cercando di pubblicare più parametri su un controller WebAPI. Un parametro proviene dall'URL e l'altro dal corpo. Ecco l'URL: /offers/40D5E19D-0CD5-4FBD-92F8-43FDBB475333/prices/

Ecco il mio codice controller:

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

Il contenuto del corpo è in 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
                        }]
                    }
                }]
            }
        }]
    }
}

Qualche idea sul perché l'associazione predefinita non sia in grado di legarsi offerPriceParametersall'argomento del mio controller? È sempre impostato su null. Ma sono in grado di recuperare i dati dal corpo usando il DataContractJsonSerializer.

Provo anche a usare l' FromBodyattributo dell'argomento ma non funziona neanche.

Risposte:


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 il riferimento

using Newtonsoft.Json.Linq;

Usa la richiesta per 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);
    }
});

3
Ottima soluzione Se non è già chiaro agli altri, puoi anche usare .ToObject <int> (), .ToObject <decimal> (), .ToString (), ecc. Se stai passando parametri semplici e multipli dalla tua chiamata ajax.
segrete il

Grazie, ho provato la tua soluzione creando la mia API e testandola tramite Postman e funziona bene; ma ho aggiunto un quarto parametro come var test = {"Name": "test"} e l'ho aggiunto all'oggetto myData ed è stato inviato con successo; esiste un modo per evitarlo e limitare solo gli oggetti originali?
Mlle116,

@ H.Al No, Newtonsoft.Json può avere qualsiasi tipo di dati json che la libreria conosce sulla traduzione. Non è possibile impedire l'invio di dati. Dipende da te utilizzare i dati in arrivo
Fatih GÜRDAL

63

Nativamente WebAPI non supporta l'associazione di più parametri POST. Come sottolinea Colin, ci sono una serie di limitazioni che sono descritte nel mio post sul blog a cui fa riferimento.

C'è una soluzione alternativa creando un raccoglitore di parametri personalizzato. Il codice per farlo è brutto e contorto, ma ho pubblicato codice insieme a una spiegazione dettagliata sul mio blog, pronto per essere inserito in un progetto qui:

Passando più valori POST semplici all'API Web ASP.NET


1
Tutto il merito va a te :) Mi è capitato di leggere le tue serie su WebAPI mentre ho iniziato la mia implementazione quando è spuntata questa domanda.
Colin Young,

Grazie! Molto utile.
Normand Bedard,

2
A partire dal 2019 lo fa ora.
Max

@John - esiste una versione base da cui è supportata questa funzionalità? Non avendo successo oggi.
Neil Moss,

26

Se viene utilizzato il routing degli attributi, è possibile utilizzare gli attributi [FromUri] e [FromBody].

Esempio:

[HttpPost()]
[Route("api/products/{id:int}")]
public HttpResponseMessage AddProduct([FromUri()] int id,  [FromBody()] Product product)
{
  // Add product
}

1
Ho usato esattamente lo stesso metodo. Devo passare due Modelli all'azione. Ne ho passato uno con meno proprietà tramite stringa di query e altro dal corpo. Inoltre, non è necessario specificare esplicitamente l'attributo [FromBody]
Sergey G.,

1
Non riesco a farlo funzionare, hai un esempio più completo?
The One

Non penso che questo sia il modo giusto per inviare i dati tramite il metodo POST, ma non vedo un'altra soluzione se devi inviare 2 modelli per posta.
Alexandr,

Questa risposta è la marmellata!
Leonardo Wildt,

1
Sto usando aspnetcore e devi usare [FromRoute]invece di[FromUri]
DanielV

19

Abbiamo passato l'oggetto Json con il metodo HttpPost e analizzato l'oggetto dinamico. funziona benissimo. questo è un codice di esempio:

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

   ...
}

Il tipo di oggetto complesso potrebbe essere oggetto, matrice e dizionario.

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"
           }]
       }}
...

1
Possiamo inserire più parametri formattati come un oggetto json da pubblicare e analizzeremo più oggetti in un secondo momento sul lato server. Questo potrebbe essere un altro modo di pensare.
Bes Ley,

@EkoosticMartin, Funziona bene, devi analizzare il tipo dinamico usando: JsonConvert.DeserializeObject <YourObjectTypeHere> (data.ToString ()); Un esempio di contenuto di dati complessi è qui, include array e oggetto dizionario. { "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
Va bene certo, quindi usa solo un parametro a stringa singola, non c'è differenza.
Ekoostik Martedì

Single non significa semplice, la stringa JSON potrebbe essere combinata con molti tipi diversi di oggetti. Questo è il punto chiave ed è un altro modo per risolvere le domande.
Bes Ley,

1
Ottima soluzione! Grazie :)
Carl R

10

Una semplice classe di parametri può essere utilizzata per passare più parametri in un post:

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

[HttpPost]
public IHttpActionResult AddCustomer(AddCustomerArgs args)
{
    //use args...
    return Ok();
}

Qualche possibilità di sapere come dovrebbe apparire la richiesta POST di esempio?
Nadia Solovyeva,

@NadiaSolovyeva, è più di una stringa di query, perché le informazioni POSTED sono nel corpo, non la stringa di query. Mi piace utilizzare PostMan per eseguire query di prova, quindi puoi vedere esattamente come appare.
Greg Gum,

Non importa, ho già trovato il modo di farlo. Intestazione POST: Content-Type: application / json; POST body: {"First": "1", "Last": "1000"}
Nadia Solovyeva

9

È possibile consentire più parametri POST utilizzando la classe MultiPostParameterBinding da https://github.com/keith5000/MultiPostParameterBinding

Per usarlo:

1) Scarica il codice nella cartella Origine e aggiungilo al tuo progetto API Web o qualsiasi altro progetto nella soluzione.

2) Utilizzare l'attributo [MultiPostParameters] sui metodi di azione che devono supportare più parametri POST.

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

3) Aggiungi questa riga in Global.asax.cs al metodo Application_Start ovunque prima della chiamata a GlobalConfiguration.Configure (WebApiConfig.Register) :

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

4) Chiedi ai tuoi clienti di passare i parametri come proprietà di un oggetto. Un oggetto JSON di esempio per il DoSomething(param1, param2, param3)metodo è:

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

Esempio JQuery:

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

Visita il link per maggiori dettagli.

Disclaimer: sono direttamente associato alla risorsa collegata.


7

Bella domanda e commenti - ho imparato molto dalle risposte qui :)

Come esempio aggiuntivo, tieni presente che puoi anche mescolare corpo e percorsi, ad es

[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"
         */
    }
}

Chiamando così:

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

Vorrei inviare 2 parametri di tipo complessi. Come [HttpPost] UploadFile stringa pubblica (UploadMediaFile mediaFile, byte [] datas) come farlo.
Başar Kaya

2

Che aspetto ha il tuo routeTemplate per questo caso?

Hai pubblicato questo URL:

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

Affinché ciò funzioni mi aspetterei un routing come questo nel tuo WebApiConfig:

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

Altre ipotesi sono: - viene chiamato il controller OffersController. - l'oggetto JSON che stai passando nel corpo della richiesta è di tipo OfferPriceParameters(non di alcun tipo derivato) - non hai altri metodi sul controller che potrebbero interferire con questo (se lo fai, prova a commentarli e vedi cosa accade)

E come ha detto Filip, sarebbe utile che le tue domande se iniziassi ad accettare alcune risposte come "percentuale di accettazione dello 0%" potrebbero far pensare alle persone che stanno perdendo tempo


Il mio percorso è "offerte / {offerId} / prezzi". Questo è l'unico metodo nel mio controller.
Normand Bedard,

2

Se non vuoi seguire ModelBinding, puoi utilizzare DTO per farlo. Ad esempio, creare un'azione POST in DataLayer che accetta un tipo complesso e inviare dati da BusinessLayer. Puoi farlo in caso di chiamata UI-> API.

Ecco un esempio di DTO. Assegnare un insegnante a uno studente e assegnare più documenti / materia allo studente.

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

Quindi l'azione nel DataLayer può essere creata come:

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

Per chiamarlo da BusinessLayer:

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

Ora funzionerà ancora se desidero inviare i dati di più studenti contemporaneamente. Modifica MyActioncome di seguito. Non è necessario scrivere [FromBody], WebAPI2 utilizza il tipo complesso [FromBody] per impostazione predefinita.

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

e poi mentre lo chiami, passa un List<StudentCurriculumDTO>dato.

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

0

Richiedi parametri come

inserisci qui la descrizione dell'immagine

Il codice API Web è simile

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

0

È possibile ottenere i formdata come stringa:

    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

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.