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.

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

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


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
}

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

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

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.


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

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


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

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

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

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.