Paramètres de mise / publication multiples WebAPI

154

J'essaie de publier plusieurs paramètres sur un contrôleur WebAPI. Un paramètre provient de l'URL et l'autre du corps. Voici l'url: /offers/40D5E19D-0CD5-4FBD-92F8-43FDBB475333/prices/

Voici mon code de contrôleur:

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

Le contenu du corps 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
                        }]
                    }
                }]
            }
        }]
    }
}

Une idée pourquoi la liaison par défaut ne peut pas se lier à l' offerPriceParametersargument de mon contrôleur? Il est toujours défini sur null. Mais je suis capable de récupérer les données du corps en utilisant le DataContractJsonSerializer.

J'essaye également d'utiliser l' FromBodyattribut de l'argument mais cela ne fonctionne pas non plus.

Normand Bédard
la source

Réponses:

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

en utilisant la référence

using Newtonsoft.Json.Linq;

Utiliser la requête pour 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
la source
3
Excellente solution. Si ce n'est pas déjà clair pour les autres, vous pouvez également utiliser .ToObject <int> (), .ToObject <decimal> (), .ToString (), etc. si vous transmettez des paramètres simples et multiples à partir de votre appel ajax.
secretwep
Merci, j'ai essayé votre solution en créant ma propre API et en la testant via Postman et cela fonctionne bien; Mais j'ai ajouté un quatrième paramètre comme var test = {"Name": "test"} et l'ai ajouté à l'objet myData et il a été envoyé avec succès; existe-t-il de toute façon pour éviter cela et ne restreindre que les objets originaux?
Mlle116
@ H.Al Non, Newtonsoft.Json peut avoir n'importe quel type de données json que la bibliothèque connaît sur la traduction. Vous ne pouvez pas empêcher l'envoi de données. Cela dépend de vous d'utiliser les données entrantes
Fatih GÜRDAL
63

Nativement WebAPI ne prend pas en charge la liaison de plusieurs paramètres POST. Comme le souligne Colin, il existe un certain nombre de limitations décrites dans mon article de blog auquel il fait référence.

Il existe une solution de contournement en créant un classeur de paramètres personnalisé. Le code pour faire cela est moche et alambiqué, mais j'ai posté du code avec une explication détaillée sur mon blog, prêt à être branché dans un projet ici:

Passer plusieurs valeurs POST simples à l'API Web ASP.NET

Rick Strahl
la source
1
Tout le mérite vous revient :) Je viens de lire votre série sur WebAPI en commençant ma propre implémentation lorsque cette question est apparue.
Colin Young
Je vous remercie! Très utile.
Normand Bedard
2
À partir de 2019, c'est le cas maintenant.
Max
@John - existe-t-il une version de base à partir de laquelle cette fonctionnalité est prise en charge? N'ayant aucun succès aujourd'hui.
Neil Moss
26

Si le routage d'attributs est utilisé, vous pouvez utiliser les attributs [FromUri] et [FromBody].

Exemple:

[HttpPost()]
[Route("api/products/{id:int}")]
public HttpResponseMessage AddProduct([FromUri()] int id,  [FromBody()] Product product)
{
  // Add product
}
Bryan Rayner
la source
1
J'ai utilisé exactement la même méthode. J'ai besoin de passer deux modèles à l'action. J'en ai passé un avec le moins de propriétés via la chaîne de requête et l'autre à partir du corps. De plus, vous n'avez pas besoin de spécifier explicitement l'attribut [FromBody]
Sergey G.
1
Je ne peux pas faire ce travail, avez-vous un exemple plus complet?
The One
Je ne pense pas que ce soit la bonne façon d'envoyer des données via la méthode POST mais je ne vois pas d'autre solution si vous devez envoyer 2 modèles par courrier.
Alexandr
Cette réponse est le Jam!
Leonardo Wildt
1
J'utilise aspnetcore et vous devez utiliser à la [FromRoute]place de[FromUri]
DanielV
19

Nous avons passé l'objet Json par la méthode HttpPost et l'avons analysé en objet dynamique. ça fonctionne bien. ceci est un exemple de code:

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

   ...
}

Le type d'objet complexe peut être objet, tableau et dictionnaire.

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
la source
1
Nous pouvons mettre plusieurs paramètres formatés comme un objet json à publier, et nous l'analyserons en plusieurs objets plus tard côté serveur. Cela pourrait être une autre façon de penser.
Bes Ley
@EkoosticMartin, cela fonctionne bien, vous devez analyser le type dynamique en utilisant: JsonConvert.DeserializeObject <YourObjectTypeHere> (data.ToString ()); Un exemple de contenu de données complexe est ici, il comprend un tableau et un objet dictionnaire. {"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 sûr, alors utilisez simplement un paramètre de chaîne unique, il n'y a pas de différence.
EkoostikMartin
Simple ne signifie pas simple, la chaîne json peut être combinée avec de nombreux types d'objets différents. C'est le point clé, et c'est une autre façon de résoudre les questions.
Bes Ley
1
Excellente solution! Merci :)
Carl R
10

Une simple classe de paramètres peut être utilisée pour passer plusieurs paramètres dans un article:

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
la source
Avez-vous une chance de savoir à quoi devrait ressembler l'exemple de requête POST?
Nadia Solovyeva le
@NadiaSolovyeva, C'est plus qu'une chaîne de requête, car les informations POSTED sont dans le corps, pas dans la chaîne de requête. J'aime utiliser PostMan pour faire des requêtes de test, puis vous pouvez voir exactement à quoi il ressemble.
Greg Gum
Qu'à cela ne tienne, j'ai déjà trouvé comment le faire. En-tête POST: Content-Type: application / json; Corps du POST: {"First": "1", "Last": "1000"}
Nadia Solovyeva
9

Vous pouvez autoriser plusieurs paramètres POST à ​​l'aide de la classe MultiPostParameterBinding de https://github.com/keith5000/MultiPostParameterBinding

Pour l'utiliser:

1) Téléchargez le code dans le dossier Source et ajoutez-le à votre projet d'API Web ou à tout autre projet de la solution.

2) Utilisez l'attribut [MultiPostParameters] sur les méthodes d'action qui doivent prendre en charge plusieurs paramètres POST.

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

3) Ajoutez cette ligne dans Global.asax.cs à la méthode Application_Start n'importe où avant l'appel à GlobalConfiguration.Configure (WebApiConfig.Register) :

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

4) Demandez à vos clients de transmettre les paramètres en tant que propriétés d'un objet. Un exemple d'objet JSON pour la DoSomething(param1, param2, param3)méthode est:

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

Exemple JQuery:

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

Visitez le lien pour plus de détails.

Avertissement: je suis directement associé à la ressource liée.

Keith
la source
7

Belle question et commentaires - beaucoup appris des réponses ici :)

Comme exemple supplémentaire, notez que vous pouvez également mélanger le corps et les routes, par exemple

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

Appelant comme ça:

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
la source
Je voudrais envoyer 2 paramètres de type complexe. Comme [HttpPost] chaîne publique UploadFile (UploadMediaFile mediaFile, byte [] datas) comment faire.
Başar Kaya
2

À quoi ressemble votre routeTemplate pour ce cas?

Vous avez publié cette URL:

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

Pour que cela fonctionne, je m'attendrais à un routage comme celui-ci dans votre WebApiConfig:

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

D'autres hypothèses sont: - votre contrôleur est appelé OffersController. - l'objet JSON que vous passez dans le corps de la requête est de type OfferPriceParameters(pas de type dérivé) - vous n'avez aucune autre méthode sur le contrôleur qui pourrait interférer avec celle-ci (si vous le faites, essayez de les commenter et voyez ce que arrive)

Et comme Filip l'a mentionné, cela aiderait vos questions si vous commenciez à accepter certaines réponses, car `` taux d'acceptation de 0% '' pourrait faire croire aux gens qu'ils perdent leur temps.

Joanna Derks
la source
Mon itinéraire est "offres / {offerId} / prix". C'est la seule méthode de mon contrôleur.
Normand Bedard
2

Si vous ne souhaitez pas utiliser ModelBinding, vous pouvez utiliser les DTO pour le faire pour vous. Par exemple, créez une action POST dans DataLayer qui accepte un type complexe et envoie des données depuis le BusinessLayer. Vous pouvez le faire en cas d'appel UI-> API.

Voici un exemple de DTO. Attribuer un enseignant à un étudiant et attribuer plusieurs articles / sujets à l'étudiant.

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

Ensuite, l'action dans le DataLayer peut être créée comme:

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

Pour l'appeler depuis le BusinessLayer:

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

Maintenant, cela fonctionnera toujours si je souhaite envoyer les données de plusieurs étudiants à la fois. Modifiez les éléments MyActionci-dessous. Pas besoin d'écrire [FromBody], WebAPI2 prend le type complexe [FromBody] par défaut.

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

puis en l'appelant, passez un fichier List<StudentCurriculumDTO>de données.

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

Demander des paramètres comme

entrez la description de l'image ici

Le code de l'API Web ressemble à

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
la source
0

Vous pouvez obtenir les données de formulaire sous forme de chaîne:

    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
la source