WebAPI Multiple Put / Post параметры

154

Я пытаюсь опубликовать несколько параметров на контроллере WebAPI. Один параметр взят из URL, а другой из тела. Вот URL: /offers/40D5E19D-0CD5-4FBD-92F8-43FDBB475333/prices/

Вот мой код контроллера:

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

Содержимое тела в формате 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
                        }]
                    }
                }]
            }
        }]
    }
}

Любая идея, почему связывание по умолчанию не в состоянии связать с offerPriceParametersаргументом моего контроллера? Он всегда равен нулю. Но я могу восстановить данные из тела, используя DataContractJsonSerializer.

Я также пытаюсь использовать FromBodyатрибут аргумента, но он также не работает.

Норманд Бедард
источник

Ответы:

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

используя ссылку

using Newtonsoft.Json.Linq;

Запрос на использование 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
Отличное решение. Если это еще не понятно другим, вы также можете использовать .ToObject <int> (), .ToObject <decimal> (), .ToString () и т. Д., Если вы передаете простые, множественные параметры из вашего вызова ajax.
secretwep
Спасибо, я попробовал ваше решение, создав собственный API и протестировав его через Postman, и он работает нормально; но я добавил четвертый параметр, например var test = {"Name": "test"}, и добавил его в объект myData и он был успешно отправлен; Есть ли способ избежать этого и ограничить только оригинальные объекты?
Mlle116
@ H.Al Нет, Newtonsoft.Json может иметь любые данные json, которые библиотека знает о переводе. Вы не можете предотвратить отправку данных. Это зависит от вас, чтобы использовать входящие данные
Fatih GÜRDAL
63

Собственно WebAPI не поддерживает привязку нескольких параметров POST. Как указывает Колин, в моем блоге он упоминает .

Существует обходной путь, создавая пользовательскую привязку параметров. Код для этого уродлив и запутан, но я разместил код вместе с подробным объяснением в своем блоге, готовый для включения в проект здесь:

Передача нескольких простых значений POST в ASP.NET Web API

Рик Страл
источник
1
Вся заслуга идет вам :) Я только что случайно прочитал вашу серию по WebAPI, когда начал свою собственную реализацию, когда возник этот вопрос.
Колин Янг
Спасибо! Очень полезно.
Норманд Бедард
2
По состоянию на 2019 это делает сейчас.
Макс
@John - есть ли базовая версия, из которой поддерживается эта функциональность? Не имея успеха сегодня.
Нил Мосс
26

Если используется маршрутизация атрибутов, вы можете использовать атрибуты [FromUri] и [FromBody].

Пример:

[HttpPost()]
[Route("api/products/{id:int}")]
public HttpResponseMessage AddProduct([FromUri()] int id,  [FromBody()] Product product)
{
  // Add product
}
Брайан Рейнер
источник
1
Я использовал точно такой же метод. Мне нужно передать две модели к действию. Я передал один с меньшими свойствами через строку запроса, а другой из тела. Также вам не нужно явно указывать атрибут [FromBody]
Сергей Григорьевич
1
Я не могу сделать эту работу, у вас есть более полный пример?
The One
Я не думаю, что это правильный способ отправки данных методом POST, но я не вижу другого решения, если вам нужно отправить 2 модели по почте.
Александр
Этот ответ - джем!
Леонардо Вильдт
1
Я использую Aspnetcore, и вы должны использовать [FromRoute]вместо[FromUri]
DanielV
19

Мы передали объект Json методом HttpPost и проанализировали его в динамическом объекте. это работает отлично. это пример кода:

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

   ...
}

Сложный тип объекта может быть объектом, массивом и словарем.

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
Мы можем поместить несколько параметров, отформатированных как один объект json, для публикации, и мы проанализируем его для нескольких объектов позже на стороне сервера. Это может быть еще один способ думать.
Бес Лей
@EkoosticMartin, все работает нормально, нужно проанализировать динамический тип с помощью: JsonConvert.DeserializeObject <YourObjectTypeHere> (data.ToString ()); Сложный образец содержимого данных здесь, он включает в себя массив и словарь объекта. { "AppName": "SamplePrice", "AppInstanceID": "100", "ProcessGUID": "072af8c3-482a-4b1c-890b-685ce2fcc75d", "Идентификатор_пользователя": "20", "имя_пользователя": "Джек",» NextActivityPerformers ": {" 39c71004-d822-4c15-9ff2-94ca1068d745 ": [{" UserID ": 10," UserName ":" Smith "}]}}
Бес Лей
1
Хорошо, конечно, тогда просто используйте один строковый параметр, без различий.
EkoostikMartin
Одиночный не означает простой, строка JSON может быть объединена со многими различными типами объектов. Это ключевой момент и еще один способ решения вопросов.
Бес Лей
1
Отличное решение! Спасибо :)
Карл R
10

Простой класс параметров может использоваться для передачи нескольких параметров в сообщении:

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

[HttpPost]
public IHttpActionResult AddCustomer(AddCustomerArgs args)
{
    //use args...
    return Ok();
}
Грег Гам
источник
Есть ли шанс, что вы знаете, как должен выглядеть пример POST-запроса?
Надя Соловьева
@NadiaSolovyeva, это больше, чем строка запроса, потому что информация POSTED находится в теле, а не в строке запроса. Мне нравится использовать PostMan для выполнения тестовых запросов, и тогда вы можете точно увидеть, как это выглядит.
Грег Гам
Неважно, я уже нашел, как это сделать. Заголовок POST: Content-Type: application / json; POST body: {"First": "1", "Last": "1000"}
Надя Соловьева
9

Вы можете разрешить несколько параметров POST, используя класс MultiPostParameterBinding из https://github.com/keith5000/MultiPostParameterBinding

Чтобы использовать это:

1) Загрузите код в папку « Исходный код » и добавьте его в проект веб-API или любой другой проект решения.

2) Используйте атрибут [MultiPostParameters] для методов действия, которые должны поддерживать несколько параметров POST.

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

3) Добавьте эту строку в Global.asax.cs в метод Application_Start в любом месте перед вызовом GlobalConfiguration.Configure (WebApiConfig.Register) :

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

4) Пусть ваши клиенты передадут параметры как свойства объекта. Пример объекта JSON для DoSomething(param1, param2, param3)метода:

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

Пример JQuery:

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

Посетите ссылку для более подробной информации.

Отказ от ответственности: я напрямую связан со связанным ресурсом.

Кит
источник
7

Хороший вопрос и комментарии - узнал много из ответов здесь :)

В качестве дополнительного примера, обратите внимание, что вы также можете смешивать тело и маршруты, например

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

Звонить так:

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
Энтони Де Соуза
источник
Я хотел бы отправить 2 параметра сложного типа. Вроде как [HttpPost] публичная строка UploadFile (UploadMediaFile mediaFile, byte [] datas) как это сделать.
Башар Кая
2

Как выглядит ваш routeTemplate для этого случая?

Вы разместили этот URL:

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

Для того, чтобы это работало, я бы ожидал такую ​​маршрутизацию в вашем WebApiConfig:

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

Другие предположения: - Ваш контроллер называется OffersController. - объект JSON, который вы передаете в теле запроса, имеет тип OfferPriceParameters(не какой-либо производный тип) - у вас нет никаких других методов на контроллере, которые могли бы помешать этому (если вы это сделаете, попробуйте закомментировать их и посмотреть, что бывает)

И, как упоминал Филип, это поможет вашим вопросам, если вы начнете принимать некоторые ответы, так как «процент принятия 0%» может заставить людей думать, что они тратят свое время

Джоанна Деркс
источник
Мой маршрут - "предложения / {offerId} / цены". Это единственный метод в моем контроллере.
Норманд Бедард
2

Если вы не хотите идти по пути ModelBinding, вы можете использовать DTO для этого. Например, создайте действие POST в DataLayer, которое принимает сложный тип и отправляет данные из BusinessLayer. Вы можете сделать это в случае вызова UI-> API.

Вот образец DTO. Назначьте Учителя ученику и назначьте ученику несколько работ / предмета.

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

Тогда действие в DataLayer может быть создано как:

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

Чтобы позвонить с BusinessLayer:

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

Теперь это все еще будет работать, если я захочу отправить данные нескольких учеников одновременно. Измените MyActionкак ниже. Не нужно писать [FromBody], WebAPI2 принимает сложный тип [FromBody] по умолчанию.

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

и затем во время вызова передайте List<StudentCurriculumDTO>данные.

using (HttpResponseMessage response = await client.PostAsJsonAsync("myendpoint_MyActionName", List<dataof_StudentCurriculumDTO>)
sandiejat
источник
0

Параметры запроса, такие как

введите описание изображения здесь

Код веб-API быть как

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

Вы можете получить formdata в виде строки:

    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

Мартиен де Йонг
источник