Parâmetros múltiplos de colocação / publicação de WebAPI

154

Estou tentando postar vários parâmetros em um controlador WebAPI. Um parâmetro é do URL e o outro do corpo. Aqui está o URL: /offers/40D5E19D-0CD5-4FBD-92F8-43FDBB475333/prices/

Aqui está o código do meu controlador:

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

O conteúdo do corpo está em 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
                        }]
                    }
                }]
            }
        }]
    }
}

Alguma idéia de por que a ligação padrão não é capaz de se ligar ao offerPriceParametersargumento do meu controlador? É sempre definido como nulo. Mas eu sou capaz de recuperar os dados do corpo usando o DataContractJsonSerializer.

Também tento usar o FromBodyatributo do argumento, mas ele também não funciona.

Normand Bedard
fonte

Respostas:

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 referência

using Newtonsoft.Json.Linq;

Solicitação de uso para 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
fonte
3
Ótima solução. Se ainda não estiver claro para outras pessoas, você também pode usar .ToObject <int> (), .ToObject <decimal> (), .ToString (), etc, se estiver passando parâmetros simples e múltiplos da sua chamada ajax.
secretwep
Obrigado, tentei sua solução criando minha própria API e testando-a através do Postman e está funcionando bem; mas adicionei um quarto parâmetro como var test = {"Name": "test"} e o adicionei ao objeto myData e foi enviado com sucesso; existe alguma maneira de evitar isso e restringir apenas objetos originais?
Mlle116
@ H.Al Não, o Newtonsoft.Json pode ter qualquer tipo de dados json que a biblioteca conheça sobre tradução. Você não pode impedir o envio de dados. Depende de você usar os dados recebidos
Fatih GÜRDAL
63

O WebAPI nativamente não suporta a ligação de vários parâmetros POST. Como Colin aponta, há várias limitações descritas no meu post no blog que ele faz referência.

Existe uma solução alternativa ao criar um fichário de parâmetros personalizado. O código para fazer isso é feio e complicado, mas eu publiquei o código junto com uma explicação detalhada no meu blog, pronta para ser conectada a um projeto aqui:

Passando vários valores POST simples para a API da Web do ASP.NET

Rick Strahl
fonte
1
Todo o crédito é para você :) Acabei de ler sua série na WebAPI enquanto iniciava minha própria implementação quando essa pergunta surgiu.
perfil completo de Colin Young
Obrigado! Muito útil.
Normand Bedard
2
A partir de 2019, ele faz agora.
Max
@ John - existe uma versão base a partir da qual essa funcionalidade é suportada? Não tendo nenhum sucesso hoje.
Neil Moss
26

Se o roteamento de atributo estiver sendo usado, você poderá usar os atributos [FromUri] e [FromBody].

Exemplo:

[HttpPost()]
[Route("api/products/{id:int}")]
public HttpResponseMessage AddProduct([FromUri()] int id,  [FromBody()] Product product)
{
  // Add product
}
Bryan Rayner
fonte
1
Eu usei exatamente o mesmo método. Eu preciso passar dois modelos para a ação. Eu passei um com menos propriedades via string de consulta e outro do corpo. Além disso, você não precisa explicitamente especificar o [FromBody] attribyte
Sergey G.
1
Não posso fazer isso funcionar, você tem um exemplo mais completo?
1
Eu não acho que este é o caminho certo para enviar dados via método POST, mas não vejo outra solução se você tiver que enviar 2 modelos via correio.
Alexandr #
Esta resposta é o Jam!
Leonardo Wildt
1
Eu estou usando aspnetcore e você tem que usar em [FromRoute]vez de[FromUri]
DanielV 27/11/19
19

Passamos o objeto Json pelo método HttpPost e o analisamos no objeto dinâmico. Funciona bem. este é um código de exemplo:

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

   ...
}

O tipo de objeto complexo pode ser objeto, matriz e dicionário.

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
fonte
1
Podemos colocar vários parâmetros formatados como um objeto json para postar e analisá-lo para vários objetos posteriormente no lado do servidor. Esta poderia ser outra maneira de pensar.
Bes Ley
@EkoosticMartin, ele funciona bem, você precisa analisar o tipo dinâmico usando: JsonConvert.DeserializeObject <YourObjectTypeHere> (data.ToString ()); Aqui está uma amostra complexa de conteúdo de dados, que inclui objeto de matriz e dicionário. {"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
Ok, claro, então basta usar um único parâmetro de string, não há diferenças.
EkoostikMartin
Single doesnt significa que a string json simples pode ser combinada com muitos tipos diferentes de objetos. Este é o ponto principal e é outra maneira de resolver perguntas.
Bes Ley
1
Excelente solução! Obrigado :)
Carl R
10

Uma classe de parâmetro simples pode ser usada para passar vários parâmetros em uma postagem:

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
fonte
Alguma chance de você saber como deve ser a solicitação de amostra do POST?
Nadia Solovyeva
@NadiaSolovyeva, é mais do que uma string de consulta, porque as informações POSTED estão no corpo, não na string de consulta. Eu gosto de usar o PostMan para fazer consultas de teste e, então, você pode ver exatamente como ela é.
Greg Gum
Não importa, eu já descobri como fazê-lo. Cabeçalho POST: Tipo de conteúdo: application / json; Corpo do POST: {"Primeiro": "1", "Último": "1000"}
Nadia Solovyeva
9

Você pode permitir vários parâmetros POST usando a classe MultiPostParameterBinding em https://github.com/keith5000/MultiPostParameterBinding

Para usá-lo:

1) Faça o download do código na pasta Origem e adicione-o ao seu projeto de API da Web ou a qualquer outro projeto na solução.

2) Use o atributo [MultiPostParameters] nos métodos de ação que precisam oferecer suporte a vários parâmetros do POST.

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

3) Adicione esta linha no Global.asax.cs ao método Application_Start em qualquer lugar antes da chamada para GlobalConfiguration.Configure (WebApiConfig.Register) :

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

4) Faça com que seus clientes passem os parâmetros como propriedades de um objeto. Um exemplo de objeto JSON para o DoSomething(param1, param2, param3)método é:

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

Exemplo de JQuery:

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

Visite o link para mais detalhes.

Isenção de responsabilidade: estou diretamente associado ao recurso vinculado.

Keith
fonte
7

Boa pergunta e comentários - aprendi muito com as respostas aqui :)

Como um exemplo adicional, observe que você também pode misturar corpo e rotas, por exemplo

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

Chamando assim:

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
fonte
Eu gostaria de enviar 2 parâmetros de tipo complexo. Como por exemplo [HttpPost] public string UploadFile (UploadMediaFile mediaFile, byte [] datas) como fazê-lo.
Başar Kaya
2

Como é o seu routeTemplate para este caso?

Você postou este URL:

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

Para que isso funcione, eu esperaria um roteamento como este no seu WebApiConfig:

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

Outras suposições são: - seu controlador é chamado OffersController. - o objeto JSON que você está passando no corpo da solicitação é do tipo OfferPriceParameters(não qualquer tipo derivado) - você não possui outros métodos no controlador que possam interferir nesse (se houver, tente comentá-los e ver o que acontece)

E, como Filip mencionou, ajudaria suas perguntas se você começasse a aceitar algumas respostas, pois 'taxa de aceitação de 0%' pode fazer as pessoas pensarem que estão perdendo tempo.

Joanna Derks
fonte
Minha rota é "ofertas / {offerId} / preços". Este é o único método no meu controlador.
Normand Bedard
2

Se você não deseja seguir o caminho ModelBinding, pode usar DTOs para fazer isso por você. Por exemplo, crie uma ação POST no DataLayer que aceite um tipo complexo e envie dados do BusinessLayer. Você pode fazer isso no caso de UI-> chamada de API.

Aqui estão os exemplos de DTO. Designe um professor para um aluno e atribua vários papéis / assunto ao aluno.

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

Em seguida, a ação no DataLayer pode ser criada como:

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

Para chamá-lo do BusinessLayer:

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

Agora isso ainda funcionará se eu quiser enviar dados de vários Alunos de uma só vez. Modifique MyActioncomo abaixo. Não há necessidade de escrever [FromBody], o WebAPI2 assume o tipo complexo [FromBody] por padrão.

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

e, ao chamá-lo, passe um List<StudentCurriculumDTO>dado.

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

Solicitar parâmetros como

insira a descrição da imagem aqui

Código da API da Web seja como

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
fonte
0

Você pode obter os dados do formulário como string:

    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
fonte