Passar uma matriz de números inteiros para a API da Web do ASP.NET?

427

Eu tenho um serviço REST da API da Web do ASP.NET (versão 4) em que preciso passar uma matriz de números inteiros.

Aqui está o meu método de ação:

public IEnumerable<Category> GetCategories(int[] categoryIds){
// code to retrieve categories from database
}

E esta é a URL que eu tentei:

/Categories?categoryids=1,2,3,4
Hemanshu Bhojak
fonte
1
Eu estava recebendo o erro "Não é possível vincular vários parâmetros ao conteúdo da solicitação" ao usar uma string de consulta como "/ Categories? Categoryids = 1 & categoryids = 2 & categoryids = 3". Espero que isso traga pessoas aqui que estavam recebendo o mesmo erro.
Josh Noe
1
@ Josh Você usou o [FromUri]? pública IEnumerable <Categoria> GetCategories ([FromUri] int [] CategoryIDs) {...}
Anup Kattel
2
@FrankGorman Não, eu não estava, qual era o meu problema.
21715 Josh Noe

Respostas:

619

Você só precisa adicionar o [FromUri]parâmetro before, se parece com:

GetCategories([FromUri] int[] categoryIds)

E envie a solicitação:

/Categories?categoryids=1&categoryids=2&categoryids=3 
Lavel
fonte
18
E se eu não souber quantas variáveis ​​tenho na matriz? E se for como 1000? A solicitação não deve ser assim.
Sahar Ch.
7
Isso me dá um erro "Um item com a mesma chave já foi adicionado.". Isto, contudo, aceitar CategoryIDs [0] = 1 & CategoryIDs [1] = 2 & etc ...
Doctor Jones
19
Essa deve ser a resposta aceita - @Hemanshu Bhojak: não é hora de fazer a sua escolha?
precisa
12
Esse motivo deve-se à seguinte declaração do site da API da Web do ASP.NET falando sobre a ligação de parâmetro: "Se o parâmetro for do tipo" simples ", a API da Web tentará obter o valor do URI. Os tipos simples incluem o. Tipos primitivos NET (int, bool, double e assim por diante), além de TimeSpan, DateTime, Guid, decimal e string, além de qualquer tipo com um conversor de tipo que possa converter de uma string. " um int [] não é um tipo simples.
Tr1stan
3
Isto funciona bem para mim. Um ponto. No código do servidor, o parâmetro array deve vir primeiro para que ele funcione e quaisquer outros parâmetros depois. Ao alimentar os parâmetros na solicitação, o pedido não é importante.
Acionado
102

Como Filip W aponta, você pode ter que recorrer a um fichário de modelo personalizado como este (modificado para vincular ao tipo real de parâmetros):

public IEnumerable<Category> GetCategories([ModelBinder(typeof(CommaDelimitedArrayModelBinder))]long[] categoryIds) 
{
    // do your thing
}

public class CommaDelimitedArrayModelBinder : IModelBinder
{
    public bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext)
    {
        var key = bindingContext.ModelName;
        var val = bindingContext.ValueProvider.GetValue(key);
        if (val != null)
        {
            var s = val.AttemptedValue;
            if (s != null)
            {
                var elementType = bindingContext.ModelType.GetElementType();
                var converter = TypeDescriptor.GetConverter(elementType);
                var values = Array.ConvertAll(s.Split(new[] { ","},StringSplitOptions.RemoveEmptyEntries),
                    x => { return converter.ConvertFromString(x != null ? x.Trim() : x); });

                var typedValues = Array.CreateInstance(elementType, values.Length);

                values.CopyTo(typedValues, 0);

                bindingContext.Model = typedValues;
            }
            else
            {
                // change this line to null if you prefer nulls to empty arrays 
                bindingContext.Model = Array.CreateInstance(bindingContext.ModelType.GetElementType(), 0);
            }
            return true;
        }
        return false;
    }
}

E então você pode dizer:

/Categories?categoryids=1,2,3,4e a API da Web do ASP.NET vincularão corretamente sua categoryIdsmatriz.

Mrchief
fonte
10
Isso pode violar o SRP e / ou o SoC, mas você também pode herdar ModelBinderAttributeisso facilmente, para que possa ser usado diretamente em vez da laboriosa sintaxe usando o typeof()argumento Tudo que você tem a fazer é herdar assim: CommaDelimitedArrayModelBinder : ModelBinderAttribute, IModelBindere em seguida, fornecer um construtor padrão que empurra a definição do tipo de baixo para a classe base: public CommaDelimitedArrayModelBinder() : base(typeof(CommaDelimitedArrayModelBinder)) { }.
Sliderhouserules
Caso contrário, eu realmente gosto desta solução e a estou usando no meu projeto, então ... obrigado. :)
sliderhouserules
Aa uma nota lateral, esta solução não funciona com os genéricos como System.Collections.Generic.List<long>como bindingContext.ModelType.GetElementType()único apoio System.Arraytipos
ViRuSTriNiTy
@ViRuSTriNiTy: Esta pergunta e a resposta falam especificamente sobre matrizes. Se você precisar de uma solução genérica baseada em lista, isso é bastante trivial de implementar. Sinta-se à vontade para fazer uma pergunta separada, se você não tiver certeza de como fazer isso.
Mrchief
2
@ codeMonkey: colocar a matriz no corpo faz sentido para uma solicitação POST, mas e as solicitações GET? Estes geralmente não têm conteúdo no corpo.
stakx - não está mais contribuindo com
40

Recentemente, me deparei com esse requisito e decidi implementar um ActionFilterpara lidar com isso.

public class ArrayInputAttribute : ActionFilterAttribute
{
    private readonly string _parameterName;

    public ArrayInputAttribute(string parameterName)
    {
        _parameterName = parameterName;
        Separator = ',';
    }

    public override void OnActionExecuting(HttpActionContext actionContext)
    {
        if (actionContext.ActionArguments.ContainsKey(_parameterName))
        {
            string parameters = string.Empty;
            if (actionContext.ControllerContext.RouteData.Values.ContainsKey(_parameterName))
                parameters = (string) actionContext.ControllerContext.RouteData.Values[_parameterName];
            else if (actionContext.ControllerContext.Request.RequestUri.ParseQueryString()[_parameterName] != null)
                parameters = actionContext.ControllerContext.Request.RequestUri.ParseQueryString()[_parameterName];

            actionContext.ActionArguments[_parameterName] = parameters.Split(Separator).Select(int.Parse).ToArray();
        }
    }

    public char Separator { get; set; }
}

Estou aplicando dessa maneira (observe que usei 'id', não 'ids', pois é assim que é especificado na minha rota):

[ArrayInput("id", Separator = ';')]
public IEnumerable<Measure> Get(int[] id)
{
    return id.Select(i => GetData(i));
}

E o URL público seria:

/api/Data/1;2;3;4

Pode ser necessário refatorá-lo para atender às suas necessidades específicas.

Steve Czetty
fonte
1
O tipo int é codificado (int.Parse) na sua solução. IMHO, @ solução de Mrchief é melhor
razon
27

Caso alguém precise - para conseguir algo igual ou semelhante (como excluir) via em POSTvez de FromUri, useFromBody e no lado do cliente (JS / jQuery), o parâmetro param como$.param({ '': categoryids }, true)

c #:

public IHttpActionResult Remove([FromBody] int[] categoryIds)

jQuery:

$.ajax({
        type: 'POST',
        data: $.param({ '': categoryids }, true),
        url: url,
//...
});

O problema $.param({ '': categoryids }, true)é que ele .net espera que o corpo do post contenha valor codificado em url, como =1&=2&=3sem nome do parâmetro e sem colchetes.

Sofija
fonte
2
Não há necessidade de recorrer a um POST. Consulte a resposta @Lavel.
André Werlang
3
Há um limite na quantidade de dados que você pode enviar em um URI. E, por padrão, isso não deve ser uma solicitação GET, pois na verdade está modificando dados.
precisa saber é o seguinte
1
E onde exatamente você viu um GET aqui? :)
Sofija 5/10
3
@ SOFija OP diz code to retrieve categories from database, portanto, o método deve ser um método GET, não POST.
Azimute 30/05
22

Maneira fácil de enviar parâmetros de matriz para API da Web

API

public IEnumerable<Category> GetCategories([FromUri]int[] categoryIds){
 // code to retrieve categories from database
}

Jquery: envia o objeto JSON como parâmetros de solicitação

$.get('api/categories/GetCategories',{categoryIds:[1,2,3,4]}).done(function(response){
console.log(response);
//success response
});

Ele irá gerar o URL da sua solicitação, como ../api/categories/GetCategories?categoryIds=1&categoryIds=2&categoryIds=3&categoryIds=4

Jignesh Variya
fonte
3
como isso é diferente da resposta aceita? com a exceção de implementar uma solicitação ajax via jquery, que não tinha nada a ver com a postagem original.
sksallaj
13

Você pode tentar este código para obter valores separados por vírgula / uma matriz de valores para recuperar um JSON da webAPI

 public class CategoryController : ApiController
 {
     public List<Category> Get(String categoryIDs)
     {
         List<Category> categoryRepo = new List<Category>();

         String[] idRepo = categoryIDs.Split(',');

         foreach (var id in idRepo)
         {
             categoryRepo.Add(new Category()
             {
                 CategoryID = id,
                 CategoryName = String.Format("Category_{0}", id)
             });
         }
         return categoryRepo;
     }
 }

 public class Category
 {
     public String CategoryID { get; set; }
     public String CategoryName { get; set; }
 } 

Resultado :

[
{"CategoryID":"4","CategoryName":"Category_4"}, 
{"CategoryID":"5","CategoryName":"Category_5"}, 
{"CategoryID":"3","CategoryName":"Category_3"} 
]
Naveen Vijay
fonte
12

Solução ASP.NET Core 2.0 (pronta para o Swagger)

Entrada

DELETE /api/items/1,2
DELETE /api/items/1

Código

Escreva o provedor (como o MVC sabe qual fichário usar)

public class CustomBinderProvider : IModelBinderProvider
{
    public IModelBinder GetBinder(ModelBinderProviderContext context)
    {
        if (context == null)
        {
            throw new ArgumentNullException(nameof(context));
        }

        if (context.Metadata.ModelType == typeof(int[]) || context.Metadata.ModelType == typeof(List<int>))
        {
            return new BinderTypeModelBinder(typeof(CommaDelimitedArrayParameterBinder));
        }

        return null;
    }
}

Escreva o fichário real (acesse todos os tipos de informações sobre a solicitação, ação, modelos, tipos, o que for)

public class CommaDelimitedArrayParameterBinder : IModelBinder
{

    public Task BindModelAsync(ModelBindingContext bindingContext)
    {

        var value = bindingContext.ActionContext.RouteData.Values[bindingContext.FieldName] as string;

        // Check if the argument value is null or empty
        if (string.IsNullOrEmpty(value))
        {
            return Task.CompletedTask;
        }

        var ints = value?.Split(',').Select(int.Parse).ToArray();

        bindingContext.Result = ModelBindingResult.Success(ints);

        if(bindingContext.ModelType == typeof(List<int>))
        {
            bindingContext.Result = ModelBindingResult.Success(ints.ToList());
        }

        return Task.CompletedTask;
    }
}

Registre-o no MVC

services.AddMvc(options =>
{
    // add custom binder to beginning of collection
    options.ModelBinderProviders.Insert(0, new CustomBinderProvider());
});

Exemplo de uso com um controlador bem documentado para o Swagger

/// <summary>
/// Deletes a list of items.
/// </summary>
/// <param name="itemIds">The list of unique identifiers for the  items.</param>
/// <returns>The deleted item.</returns>
/// <response code="201">The item was successfully deleted.</response>
/// <response code="400">The item is invalid.</response>
[HttpDelete("{itemIds}", Name = ItemControllerRoute.DeleteItems)]
[ProducesResponseType(typeof(void), StatusCodes.Status204NoContent)]
[ProducesResponseType(typeof(void), StatusCodes.Status404NotFound)]
public async Task Delete(List<int> itemIds)
=> await _itemAppService.RemoveRangeAsync(itemIds);

EDIT: Microsoft recomenda o uso de um TypeConverter para esses filhos de operações sobre essa abordagem. Portanto, siga os conselhos dos pôsteres abaixo e documente seu tipo personalizado com um SchemaFilter.

Victorio Berra
fonte
Eu acho que os MS recomendação que você está falando é satisfeita por esta resposta: stackoverflow.com/a/49563970/4367683
Machado
Você viu isso? github.com/aspnet/Mvc/pull/7967 , parece que eles adicionaram uma correção para começar a analisar a Lista <qualquer que seja> na string de consulta sem a necessidade de um fichário especial. Além disso, a postagem que você vinculou não é o ASPNET Core e acho que não ajuda na minha situação.
Victorio Berra
A melhor resposta não hacky.
Erik Philips
7

Em vez de usar um ModelBinder personalizado, você também pode usar um tipo personalizado com um TypeConverter.

[TypeConverter(typeof(StrListConverter))]
public class StrList : List<string>
{
    public StrList(IEnumerable<string> collection) : base(collection) {}
}

public class StrListConverter : TypeConverter
{
    public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
    {
        return sourceType == typeof(string) || base.CanConvertFrom(context, sourceType);
    }

    public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
    {
        if (value == null)
            return null;

        if (value is string s)
        {
            if (string.IsNullOrEmpty(s))
                return null;
            return new StrList(s.Split(','));
        }
        return base.ConvertFrom(context, culture, value);
    }
}

A vantagem é que isso torna os parâmetros do método da API da Web muito simples. Você nem precisa especificar [FromUri].

public IEnumerable<Category> GetCategories(StrList categoryIds) {
  // code to retrieve categories from database
}

Este exemplo é para uma lista de seqüências de caracteres, mas você pode fazer categoryIds.Select(int.Parse)ou simplesmente escrever uma IntList.

PhillipM
fonte
Não entendo por que essa solução não recebeu muitos votos. É agradável e limpo e funciona com arrogância sem adicionar pastas e outras coisas personalizadas.
Thieme
A melhor / mais limpa resposta na minha opinião. Graças PhillipM!
Leigh Bowers
7

Originalmente, usei a solução que @Mrchief por anos (funciona muito bem). Mas quando adicionei o Swagger ao meu projeto para documentação da API, meu ponto final NÃO estava aparecendo.

Demorei um pouco, mas foi isso que eu criei. Funciona com o Swagger, e suas assinaturas de método de API parecem mais limpas:

No final, você pode fazer:

    // GET: /api/values/1,2,3,4 

    [Route("api/values/{ids}")]
    public IHttpActionResult GetIds(int[] ids)
    {
        return Ok(ids);
    }

WebApiConfig.cs

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        // Allow WebApi to Use a Custom Parameter Binding
        config.ParameterBindingRules.Add(descriptor => descriptor.ParameterType == typeof(int[]) && descriptor.ActionDescriptor.SupportedHttpMethods.Contains(HttpMethod.Get)
                                                           ? new CommaDelimitedArrayParameterBinder(descriptor)
                                                           : null);

        // Allow ApiExplorer to understand this type (Swagger uses ApiExplorer under the hood)
        TypeDescriptor.AddAttributes(typeof(int[]), new TypeConverterAttribute(typeof(StringToIntArrayConverter)));

        // Any existing Code ..

    }
}

Crie uma nova classe: CommaDelimitedArrayParameterBinder.cs

public class CommaDelimitedArrayParameterBinder : HttpParameterBinding, IValueProviderParameterBinding
{
    public CommaDelimitedArrayParameterBinder(HttpParameterDescriptor desc)
        : base(desc)
    {
    }

    /// <summary>
    /// Handles Binding (Converts a comma delimited string into an array of integers)
    /// </summary>
    public override Task ExecuteBindingAsync(ModelMetadataProvider metadataProvider,
                                             HttpActionContext actionContext,
                                             CancellationToken cancellationToken)
    {
        var queryString = actionContext.ControllerContext.RouteData.Values[Descriptor.ParameterName] as string;

        var ints = queryString?.Split(',').Select(int.Parse).ToArray();

        SetValue(actionContext, ints);

        return Task.CompletedTask;
    }

    public IEnumerable<ValueProviderFactory> ValueProviderFactories { get; } = new[] { new QueryStringValueProviderFactory() };
}

Crie uma nova classe: StringToIntArrayConverter.cs

public class StringToIntArrayConverter : TypeConverter
{
    public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
    {
        return sourceType == typeof(string) || base.CanConvertFrom(context, sourceType);
    }
}

Notas:

crabCRUSHERclamCOLLECTOR
fonte
1
Caso outras pessoas precisem de informações sobre as bibliotecas usadas. Aqui está o uso de "CommaDelimitedArrayParameterBinder". using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; using System.Web.Http.Controllers; using System.Web.Http.Metadata; using System.Web.Http.ModelBinding; using System.Web.Http.ValueProviders; using System.Web.Http.ValueProviders.Providers;
SteckDEV 15/03/19
6
public class ArrayInputAttribute : ActionFilterAttribute
{
    private readonly string[] _ParameterNames;
    /// <summary>
    /// 
    /// </summary>
    public string Separator { get; set; }
    /// <summary>
    /// cons
    /// </summary>
    /// <param name="parameterName"></param>
    public ArrayInputAttribute(params string[] parameterName)
    {
        _ParameterNames = parameterName;
        Separator = ",";
    }

    /// <summary>
    /// 
    /// </summary>
    public void ProcessArrayInput(HttpActionContext actionContext, string parameterName)
    {
        if (actionContext.ActionArguments.ContainsKey(parameterName))
        {
            var parameterDescriptor = actionContext.ActionDescriptor.GetParameters().FirstOrDefault(p => p.ParameterName == parameterName);
            if (parameterDescriptor != null && parameterDescriptor.ParameterType.IsArray)
            {
                var type = parameterDescriptor.ParameterType.GetElementType();
                var parameters = String.Empty;
                if (actionContext.ControllerContext.RouteData.Values.ContainsKey(parameterName))
                {
                    parameters = (string)actionContext.ControllerContext.RouteData.Values[parameterName];
                }
                else
                {
                    var queryString = actionContext.ControllerContext.Request.RequestUri.ParseQueryString();
                    if (queryString[parameterName] != null)
                    {
                        parameters = queryString[parameterName];
                    }
                }

                var values = parameters.Split(new[] { Separator }, StringSplitOptions.RemoveEmptyEntries)
                    .Select(TypeDescriptor.GetConverter(type).ConvertFromString).ToArray();
                var typedValues = Array.CreateInstance(type, values.Length);
                values.CopyTo(typedValues, 0);
                actionContext.ActionArguments[parameterName] = typedValues;
            }
        }
    }

    public override void OnActionExecuting(HttpActionContext actionContext)
    {
        _ParameterNames.ForEach(parameterName => ProcessArrayInput(actionContext, parameterName));
    }
}

Uso:

    [HttpDelete]
    [ArrayInput("tagIDs")]
    [Route("api/v1/files/{fileID}/tags/{tagIDs}")]
    public HttpResponseMessage RemoveFileTags(Guid fileID, Guid[] tagIDs)
    {
        _FileRepository.RemoveFileTags(fileID, tagIDs);
        return Request.CreateResponse(HttpStatusCode.OK);
    }

Request uri

http://localhost/api/v1/files/2a9937c7-8201-59b7-bc8d-11a9178895d0/tags/BBA5CD5D-F07D-47A9-8DEE-D19F5FA65F63,BBA5CD5D-F07D-47A9-8DEE-D19F5FA65F63
Waninlezu
fonte
@Elsa Você poderia apontar qual peça não consegue entender? Eu acho que o código é bastante claro para se explicar. É difícil para mim explicar tudo isso em inglês, desculpe.
Waninlezu 17/07/2014
@ Steve Czetty aqui é a minha versão reconstruída, obrigado pela sua idéia
Waninlezu
Será que vai funcionar /como o separador? Em seguida, você poderia ter: dns / root / mystuff / path / to / some / recurso mapeado parapublic string GetMyStuff(params string[] pathBits)
RoboJ1M
5

Se você deseja listar / matriz de números inteiros, a maneira mais fácil de fazer isso é aceitar a lista de strings separada por vírgula (,) e convertê-la em lista de números inteiros. Não se esqueça de mencionar [FromUri] attriubte.

...? ID = 71 & accountID = 1,2,3,289,56

public HttpResponseMessage test([FromUri]int ID, [FromUri]string accountID)
{
    List<int> accountIdList = new List<int>();
    string[] arrAccountId = accountId.Split(new char[] { ',' });
    for (var i = 0; i < arrAccountId.Length; i++)
    {
        try
        {
           accountIdList.Add(Int32.Parse(arrAccountId[i]));
        }
        catch (Exception)
        {
        }
    }
}
Vaibhav
fonte
por que você usa em List<string>vez de apenas string? ele terá apenas uma string, que está 1,2,3,289,56no seu exemplo. Vou sugerir uma edição.
Daniël Tulp 20/05
Trabalhou para mim. Fiquei surpreso que meu controlador não se ligasse a um List<Guid>automaticamente. Observe no Asp.net Core que a anotação é [FromQuery]e não é necessária.
Kitsu.eb 28/06
2
Para uma versão Linq de uma linha: int [] accountIdArray = accountId.Split (','). Selecione (i => int.Parse (i)). ToArray (); Eu evitaria o problema, pois ele mascara alguém que passa dados ruins.
Steve Em CO
3

Crie o tipo de método [HttpPost], crie um modelo que tenha um parâmetro int [] e poste com json:

/* Model */
public class CategoryRequestModel 
{
    public int[] Categories { get; set; }
}

/* WebApi */
[HttpPost]
public HttpResponseMessage GetCategories(CategoryRequestModel model)
{
    HttpResponseMessage resp = null;

    try
    {
        var categories = //your code to get categories

        resp = Request.CreateResponse(HttpStatusCode.OK, categories);

    }
    catch(Exception ex)
    {
        resp = Request.CreateErrorResponse(HttpStatusCode.InternalServerError, ex);
    }

    return resp;
}

/* jQuery */
var ajaxSettings = {
    type: 'POST',
    url: '/Categories',
    data: JSON.serialize({Categories: [1,2,3,4]}),
    contentType: 'application/json',
    success: function(data, textStatus, jqXHR)
    {
        //get categories from data
    }
};

$.ajax(ajaxSettings);
codeMonkey
fonte
Você está agrupando sua matriz em uma classe - que funcionará bem (apesar do MVC / WebAPI). O OP era sobre a ligação à matriz sem uma classe de wrapper.
Mrchief
1
O problema original não diz nada sobre fazer isso sem uma classe de wrapper, apenas que eles queriam usar parâmetros de consulta para objetos complexos. Se você seguir esse caminho longe demais, chegará a um ponto em que precisa da API para pegar um objeto js realmente complexo, e os parâmetros de consulta irão falhar. É melhor aprender a fazê-lo da maneira que sempre funcionará.
codemonkey
public IEnumerable<Category> GetCategories(int[] categoryIds){- Sim, você poderia interpretar de maneiras diferentes, suponho. Mas muitas vezes, não quero criar classes de wrapper para criar wrappers. Se você tiver objetos complexos, isso funcionará. Apoiar esses casos mais simples é o que não funciona imediatamente, daí o OP.
Mrchief
3
Fazer isso via POSTé realmente contra o paradigma REST. Portanto, essa API não seria uma API REST.
Azimute 30/05
1
@Azimuth me dar um paradigma em uma das mãos, o que trabalha com .NET na outra
codemonkey
3

Ou você pode simplesmente passar uma sequência de itens delimitados e colocá-la em uma matriz ou lista no lado de recebimento.

Sirentec
fonte
2

Eu resolvi esse problema dessa maneira.

Eu usei uma mensagem de postagem na API para enviar a lista de números inteiros como dados.

Depois, retornei os dados como inumeráveis.

O código de envio é o seguinte:

public override IEnumerable<Contact> Fill(IEnumerable<int> ids)
{
    IEnumerable<Contact> result = null;
    if (ids!=null&&ids.Count()>0)
    {
        try
        {
            using (var client = new HttpClient())
            {
                client.BaseAddress = new Uri("http://localhost:49520/");
                client.DefaultRequestHeaders.Accept.Clear();
                client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));

                String _endPoint = "api/" + typeof(Contact).Name + "/ListArray";

                HttpResponseMessage response = client.PostAsJsonAsync<IEnumerable<int>>(_endPoint, ids).Result;
                response.EnsureSuccessStatusCode();
                if (response.IsSuccessStatusCode)
                {
                    result = JsonConvert.DeserializeObject<IEnumerable<Contact>>(response.Content.ReadAsStringAsync().Result);
                }

            }

        }
        catch (Exception)
        {

        }
    }
    return result;
}

O código de recebimento é o seguinte:

// POST api/<controller>
[HttpPost]
[ActionName("ListArray")]
public IEnumerable<Contact> Post([FromBody]IEnumerable<int> ids)
{
    IEnumerable<Contact> result = null;
    if (ids != null && ids.Count() > 0)
    {
        return contactRepository.Fill(ids);
    }
    return result;
}

Funciona muito bem para um registro ou muitos registros. O preenchimento é um método sobrecarregado usando DapperExtensions:

public override IEnumerable<Contact> Fill(IEnumerable<int> ids)
{
    IEnumerable<Contact> result = null;
    if (ids != null && ids.Count() > 0)
    {
        using (IDbConnection dbConnection = ConnectionProvider.OpenConnection())
        {
            dbConnection.Open();
            var predicate = Predicates.Field<Contact>(f => f.id, Operator.Eq, ids);
            result = dbConnection.GetList<Contact>(predicate);
            dbConnection.Close();
        }
    }
    return result;
}

Isso permite que você busque dados de uma tabela composta (a lista de IDs) e, em seguida, retorne os registros de seu interesse na tabela de destino.

Você poderia fazer o mesmo com uma visualização, mas isso lhe dará um pouco mais de controle e flexibilidade.

Além disso, os detalhes do que você está procurando no banco de dados não são mostrados na string de consulta. Você também não precisa converter de um arquivo csv.

Você deve ter em mente que, ao usar qualquer ferramenta como a interface da API da Web 2.x, as funções get, put, post, delete, head, etc. têm um uso geral, mas não estão restritas a esse uso.

Portanto, embora a postagem geralmente seja usada em um contexto de criação na interface da API da web, ela não se restringe a esse uso. É uma chamada html regular que pode ser usada para qualquer finalidade permitida pela prática html.

Além disso, os detalhes do que está acontecendo estão ocultos daqueles "olhares indiscretos" que ouvimos muito sobre esses dias.

A flexibilidade nas convenções de nomenclatura na interface da API da Web 2.x e o uso de chamadas regulares na Web significa que você envia uma chamada para a API da Web que induz os bisbilhoteiros a pensar que você está realmente fazendo outra coisa. Você pode usar "POST" para realmente recuperar dados, por exemplo.

Timothy Dooling
fonte
2

Criei um fichário de modelo personalizado que converte qualquer valor separado por vírgula (apenas primitivo, decimal, flutuante, sequência de caracteres) em suas matrizes correspondentes.

public class CommaSeparatedToArrayBinder<T> : IModelBinder
    {
        public bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext)
        {
            Type type = typeof(T);
            if (type.IsPrimitive || type == typeof(Decimal) || type == typeof(String) || type == typeof(float))
            {
                ValueProviderResult val = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
                if (val == null) return false;

                string key = val.RawValue as string;
                if (key == null) { bindingContext.ModelState.AddModelError(bindingContext.ModelName, "Wrong value type"); return false; }

                string[] values = key.Split(',');
                IEnumerable<T> result = this.ConvertToDesiredList(values).ToArray();
                bindingContext.Model = result;
                return true;
            }

            bindingContext.ModelState.AddModelError(bindingContext.ModelName, "Only primitive, decimal, string and float data types are allowed...");
            return false;
        }

        private IEnumerable<T> ConvertToDesiredArray(string[] values)
        {
            foreach (string value in values)
            {
                var val = (T)Convert.ChangeType(value, typeof(T));
                yield return val;
            }
        }
    }

E como usar no Controller:

 public IHttpActionResult Get([ModelBinder(BinderType = typeof(CommaSeparatedToArrayBinder<int>))] int[] ids)
        {
            return Ok(ids);
        }
Sulabh Singla
fonte
Obrigado, eu o transportei para o netcore 3.1 com pouco esforço e funciona! A resposta aceita não resolve o problema com a necessidade de especificar o nome do parâmetro várias vezes e é igual à operação padrão no netcore 3.1
Bogdan Mart
0

Minha solução foi criar um atributo para validar seqüências de caracteres, ele faz um monte de recursos extras comuns, incluindo validação de regex que você pode usar para verificar apenas números e depois converter para números inteiros, conforme necessário ...

É assim que você usa:

public class MustBeListAndContainAttribute : ValidationAttribute
{
    private Regex regex = null;
    public bool RemoveDuplicates { get; }
    public string Separator { get; }
    public int MinimumItems { get; }
    public int MaximumItems { get; }

    public MustBeListAndContainAttribute(string regexEachItem,
        int minimumItems = 1,
        int maximumItems = 0,
        string separator = ",",
        bool removeDuplicates = false) : base()
    {
        this.MinimumItems = minimumItems;
        this.MaximumItems = maximumItems;
        this.Separator = separator;
        this.RemoveDuplicates = removeDuplicates;

        if (!string.IsNullOrEmpty(regexEachItem))
            regex = new Regex(regexEachItem, RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.IgnoreCase);
    }

    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        var listOfdValues = (value as List<string>)?[0];

        if (string.IsNullOrWhiteSpace(listOfdValues))
        {
            if (MinimumItems > 0)
                return new ValidationResult(this.ErrorMessage);
            else
                return null;
        };

        var list = new List<string>();

        list.AddRange(listOfdValues.Split(new[] { Separator }, System.StringSplitOptions.RemoveEmptyEntries));

        if (RemoveDuplicates) list = list.Distinct().ToList();

        var prop = validationContext.ObjectType.GetProperty(validationContext.MemberName);
        prop.SetValue(validationContext.ObjectInstance, list);
        value = list;

        if (regex != null)
            if (list.Any(c => string.IsNullOrWhiteSpace(c) || !regex.IsMatch(c)))
                return new ValidationResult(this.ErrorMessage);

        return null;
    }
}
Alan Cardoso
fonte