Parametry WebAPI Multiple Put / Post

154

Próbuję opublikować wiele parametrów na kontrolerze WebAPI. Jeden parametr pochodzi z adresu URL, a drugi z treści. Oto adres URL: /offers/40D5E19D-0CD5-4FBD-92F8-43FDBB475333/prices/

Oto mój kod kontrolera:

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

Treść treści jest w formacie 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
                        }]
                    }
                }]
            }
        }]
    }
}

Masz jakiś pomysł, dlaczego domyślne powiązanie nie może połączyć się z offerPriceParametersargumentem mojego kontrolera? Zawsze ma wartość null. Ale jestem w stanie odzyskać dane z ciała za pomocą DataContractJsonSerializer.

Próbuję też użyć FromBodyatrybutu argumentu, ale to też nie działa.

Normand Bedard
źródło

Odpowiedzi:

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

za pomocą odwołania

using Newtonsoft.Json.Linq;

Użyj żądania dla 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
źródło
3
Świetne rozwiązanie. Jeśli nie jest to jeszcze jasne dla innych, możesz również użyć .ToObject <int> (), .ToObject <decimal> (), .ToString () itp., Jeśli przekazujesz proste, wiele parametrów z wywołania ajax.
Secretwep
Dziękuję, wypróbowałem Twoje rozwiązanie, tworząc własne API i testując je za pomocą programu Postman i działa dobrze; ale dodałem czwarty parametr, taki jak var test = {"Name": "test"} i dodałem go do obiektu myData i został pomyślnie wysłany; czy w ogóle można tego uniknąć i ograniczyć tylko oryginalne obiekty?
Mlle116
@ H.Al Nie, Newtonsoft.Json może mieć dowolne dane json, które biblioteka zna o tłumaczeniu. Nie możesz zapobiec wysyłaniu danych. To zależy od Ciebie, czy wykorzystasz przychodzące dane
Fatih GÜRDAL
63

Natywnie WebAPI nie obsługuje wiązania wielu parametrów POST. Jak podkreśla Colin, istnieje szereg ograniczeń, które zostały opisane w moim wpisie na blogu, do którego się odwołuje.

Istnieje obejście problemu, tworząc spinacz parametrów niestandardowych. Kod, który to robi, jest brzydki i zawiły, ale opublikowałem kod wraz ze szczegółowym wyjaśnieniem na moim blogu, gotowy do podłączenia do projektu tutaj:

Przekazywanie wielu prostych wartości POST do interfejsu API sieci Web ASP.NET

Rick Strahl
źródło
1
Całe uznanie należy do Ciebie :) Właśnie tak się złożyło, że czytałem twoją serię w WebAPI podczas uruchamiania własnej implementacji, gdy pojawiło się to pytanie.
Colin Young,
Dziękuję Ci! Bardzo pomocne.
Normand Bedard
2
Od 2019 r. Teraz.
Max
@John - czy istnieje wersja podstawowa, od której ta funkcjonalność jest obsługiwana? Nie odnosząc dziś żadnego sukcesu.
Neil Moss
26

Jeśli używany jest routing atrybutów, możesz użyć atrybutów [FromUri] i [FromBody].

Przykład:

[HttpPost()]
[Route("api/products/{id:int}")]
public HttpResponseMessage AddProduct([FromUri()] int id,  [FromBody()] Product product)
{
  // Add product
}
Bryan Rayner
źródło
1
Użyłem dokładnie tej samej metody. Muszę przekazać do akcji dwa modele. Przekazałem jedną z mniejszą liczbą właściwości za pośrednictwem ciągu zapytania, a drugą z treści. Nie musisz też wyraźnie określać attribyte [FromBody]
Sergey G.
1
Nie mogę tego zrobić, czy masz bardziej kompletny przykład?
The One,
Nie sądzę, aby był to właściwy sposób przesyłania danych metodą POST, ale nie widzę innego rozwiązania, jeśli musisz wysłać 2 modele pocztą.
Alexandr
Ta odpowiedź to Jam!
Leonardo Wildt
1
Używam aspnetcore i musisz użyć [FromRoute]zamiast[FromUri]
DanielV
19

Przekazaliśmy obiekt Json metodą HttpPost i przeanalizowaliśmy go w obiekcie dynamicznym. to działa dobrze. to jest przykładowy kod:

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

   ...
}

Złożonym typem obiektu może być obiekt, tablica i słownik.

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
źródło
1
Do wysłania możemy umieścić wiele parametrów sformatowanych jako jeden obiekt json, a później przeanalizujemy je do wielu obiektów po stronie serwera. To mógłby być inny sposób myślenia.
Bes Ley,
@EkoosticMartin, Działa dobrze, musisz przeanalizować typ dynamiczny za pomocą: JsonConvert.DeserializeObject <YourObjectTypeHere> (data.ToString ()); Oto przykład złożonej zawartości danych, która zawiera tablicę i obiekt słownika. {"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
Okej, jasne, po prostu użyj pojedynczego parametru ciągu, nie ma różnicy.
EkoostikMartin
Pojedynczy nie oznacza prosty, łańcuch json może być łączony z wieloma różnymi typami obiektów. To jest kluczowy punkt i kolejny sposób rozwiązywania pytań.
Bes Ley,
1
Doskonałe rozwiązanie! Dzięki :)
Carl R
10

Do przekazania wielu parametrów w poście można użyć prostej klasy parametrów:

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
źródło
Czy wiesz, jak powinno wyglądać przykładowe żądanie POST?
Nadia Solovyeva
@NadiaSolovyeva, To więcej niż ciąg zapytania, ponieważ informacje POSTED znajdują się w treści, a nie w ciągu zapytania. Lubię używać PostMan do tworzenia zapytań testowych, a wtedy możesz zobaczyć dokładnie, jak to wygląda.
Greg Gum
Nieważne, już znalazłem, jak to zrobić. Nagłówek POST: Content-Type: application / json; POST body: {"First": "1", "Last": "1000"}
Nadia Solovyeva
9

Możesz zezwolić na wiele parametrów POST przy użyciu klasy MultiPostParameterBinding z https://github.com/keith5000/MultiPostParameterBinding

Aby z niego skorzystać:

1) Pobierz kod w formacie folderze źródłowym i dodaj go do projektu interfejsu API sieci Web lub dowolnego innego projektu w rozwiązaniu.

2) Użyj atrybutu [MultiPostParameters] w metodach akcji, które muszą obsługiwać wiele parametrów POST.

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

3) Dodaj ten wiersz w Global.asax.cs do metody Application_Start w dowolnym miejscu przed wywołaniem GlobalConfiguration.Configure (WebApiConfig.Register) :

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

4) Poproś klientów o przekazanie parametrów jako właściwości obiektu. Przykładowy obiekt JSON dla DoSomething(param1, param2, param3)metody to:

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

Przykład JQuery:

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

Odwiedzić łącze, aby uzyskać więcej informacji.

Zastrzeżenie: jestem bezpośrednio powiązany z połączonym zasobem.

Keith
źródło
7

Ładne pytanie i komentarze - wiele się nauczyłem z odpowiedzi tutaj :)

Jako dodatkowy przykład zwróć uwagę, że możesz również mieszać treść i trasy, np

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

Dzwonię w ten sposób:

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
źródło
Chciałbym przesłać 2 złożone parametry typu. Podobnie jak [HttpPost] publiczny ciąg UploadFile (UploadMediaFile mediaFile, byte [] dane), jak to zrobić.
Başar Kaya
2

Jak wygląda Twój szablon trasy w tym przypadku?

Opublikowałeś ten adres URL:

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

Aby to zadziałało, spodziewałbym się takiego routingu w Twoim WebApiConfig:

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

Inne założenia to: - wywoływany jest kontroler OffersController. - obiekt JSON, który przekazujesz w treści żądania, jest typu OfferPriceParameters(nie jest to żaden typ pochodny) - nie masz żadnych innych metod na kontrolerze, które mogłyby kolidować z tym (jeśli to zrobisz, spróbuj je skomentować i zobacz, co dzieje się)

I jak wspomniał Filip, pomogłoby to, gdybyś zaczął akceptować niektóre odpowiedzi, ponieważ „wskaźnik akceptacji 0%” może sprawić, że ludzie pomyślą, że marnują czas

Joanna Derks
źródło
Moja trasa to „offers / {offerId} / Prices”. To jedyna metoda w moim kontrolerze.
Normand Bedard
2

Jeśli nie chcesz iść drogą ModelBinding, możesz użyć DTO, aby zrobić to za Ciebie. Na przykład utwórz akcję POST w DataLayer, która akceptuje typ złożony i wysyła dane z BusinessLayer. Możesz to zrobić w przypadku wywołania UI-> API.

Oto przykładowe DTO. Przypisz nauczyciela do ucznia i przypisz wiele prac / przedmiotów do ucznia.

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

Następnie akcję w DataLayer można utworzyć jako:

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

Aby zadzwonić z BusinessLayer:

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

Teraz to nadal będzie działać, jeśli chcę wysłać dane wielu Studentów jednocześnie. Zmodyfikuj MyActionjak poniżej. Nie ma potrzeby pisania [FromBody], WebAPI2 przyjmuje domyślnie złożony typ [FromBody].

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

a następnie podczas wywoływania go przekaż List<StudentCurriculumDTO>dane.

using (HttpResponseMessage response = await client.PostAsJsonAsync("myendpoint_MyActionName", List<dataof_StudentCurriculumDTO>)
sandiejat
źródło
0

Parametry żądania, takie jak

wprowadź opis obrazu tutaj

Kod interfejsu API sieci Web jest podobny

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
źródło
0

Możesz pobrać dane formularza jako ciąg:

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