Passare un array di numeri interi all'API Web ASP.NET?


427

Ho un servizio REST ASP.NET Web API (versione 4) dove devo passare un array di numeri interi.

Ecco il mio metodo di azione:

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

E questo è l'URL che ho provato:

/Categories?categoryids=1,2,3,4

1
Stavo ricevendo un errore "Impossibile associare più parametri al contenuto della richiesta" durante l'utilizzo di una stringa di query come "/ Categorie? Categoryids = 1 & categoryids = 2 & categoryids = 3". Spero che questo porti qui le persone che hanno riscontrato lo stesso errore.
Josh Noe,

1
@Josh Hai usato [FromUri] però? public IEnumerable <Category> GetCategories ([FromUri] int [] categoryids) {...}
Anup Kattel

2
@FrankGorman No, non lo ero, quale era il mio problema.
Josh Noe,

Risposte:


619

Hai solo bisogno di aggiungere [FromUri]prima del parametro, si presenta come:

GetCategories([FromUri] int[] categoryIds)

E invia la richiesta:

/Categories?categoryids=1&categoryids=2&categoryids=3 

18
Cosa succede se non so quante variabili ho nell'array? E se fosse come 1000? La richiesta non dovrebbe essere così.
Sahar Ch.

7
Questo mi dà un errore "Un elemento con la stessa chiave è già stato aggiunto.". Tuttavia accetta categorieids [0] = 1 & categoryids [1] = 2 & etc ...
Doctor Jones,

19
Questa dovrebbe essere la risposta accettata - @Hemanshu Bhojak: non è il momento di fare la tua scelta?
David Rettenbacher,

12
Questo motivo è dovuto alla seguente dichiarazione del sito Web dell'API Web ASP.NET che parla dell'associazione dei parametri: "Se il parametro è di tipo" semplice ", l'API Web tenta di ottenere il valore dall'URI. I tipi semplici includono il. Tipi primitivi NET (int, bool, double e così via), più TimeSpan, DateTime, Guid, decimale e stringa, più qualsiasi tipo con un convertitore di tipi che può convertire da una stringa. " un int [] non è un tipo semplice.
Tr1stan,

3
Questo funziona bene per me. Un punto. Sul codice del server, il parametro array deve venire prima perché funzioni e dopo ogni altro parametro. Quando si inseriscono i parametri nella richiesta, l'ordine non è importante.
Scintillato il

102

Come sottolinea Filip W , potresti dover ricorrere a un raccoglitore di modelli personalizzato come questo (modificato per legare al tipo effettivo di parametro):

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 poi puoi dire:

/Categories?categoryids=1,2,3,4e l'API Web ASP.NET assocerà correttamente l' categoryIdsarray.


10
Questo può violare SRP e / o SoC, ma puoi facilmente far sì che anche questo erediti da ModelBinderAttributecosì che possa essere usato direttamente invece della laboriosa sintassi usando l' typeof()argomento. Tutto quello che dovete fare è ereditare in questo modo: CommaDelimitedArrayModelBinder : ModelBinderAttribute, IModelBindere poi fornire un costruttore di default che spinge la definizione del tipo fino alla classe di base: public CommaDelimitedArrayModelBinder() : base(typeof(CommaDelimitedArrayModelBinder)) { }.
sliderhouserules,

Altrimenti, mi piace molto questa soluzione e la sto usando nel mio progetto, quindi ... grazie. :)
sliderhouserules,

Aa nota a margine, questa soluzione non funziona con i generici come System.Collections.Generic.List<long>come bindingContext.ModelType.GetElementType()supporto solo System.Arrayi tipi
ViRuSTriNiTy

@ViRuSTriNiTy: questa domanda e la risposta parlano specificamente di array. Se hai bisogno di una soluzione basata su elenco generico, è abbastanza banale da implementare. Sentiti libero di fare una domanda separata se non sei sicuro di come procedere.
Mrchief,

2
@codeMonkey: mettere l'array nel corpo ha un buon senso per una richiesta POST, ma per quanto riguarda le richieste GET? Questi di solito non hanno contenuto nel corpo.
stakx - non contribuisce più il

40

Recentemente mi sono imbattuto in questo requisito da solo e ho deciso di implementarlo ActionFilterper gestirlo.

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

Lo sto applicando in questo modo (nota che ho usato 'id', non 'ids', poiché è così che viene specificato nel mio percorso):

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

E l'URL pubblico sarebbe:

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

Potrebbe essere necessario effettuare il refactoring per soddisfare le esigenze specifiche.


1
digitare int sono hardcoded (int.Parse) nella soluzione. Imho, la soluzione di @ Mrchief è migliore
razon,

27

Nel caso in cui qualcuno avesse bisogno di - per ottenere la stessa cosa o qualcosa di simile (come eliminare) tramite POSTinvece di FromUri, utilizzare FromBodye sul lato client (JS / jQuery) param come$.param({ '': categoryids }, true)

C #:

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

jQuery:

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

Il fatto $.param({ '': categoryids }, true)è che .net si aspetta che post body contenga un valore urlencoded come =1&=2&=3senza nome di parametro e senza parentesi.


2
Non è necessario ricorrere a un POST. Vedi la risposta di @Lavel.
André Werlang,

3
Esiste un limite alla quantità di dati che è possibile inviare in un URI. E per standard, questa non dovrebbe essere una richiesta GET poiché sta effettivamente modificando i dati.
Degno7,

1
E dove hai visto esattamente OTTENERE qui? :)
Sofija,

3
@Sofija OP dice code to retrieve categories from database, quindi il metodo dovrebbe essere un metodo GET, non POST.
Azimuth,

22

Modo semplice per inviare parametri dell'array a API Web

API

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

Jquery: invia l'oggetto JSON come parametri di richiesta

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

Genererà il tuo URL di richiesta come ../api/categories/GetCategories?categoryIds=1&categoryIds=2&categoryIds=3&categoryIds=4


3
in che modo differisce dalla risposta accettata? ad eccezione dell'implementazione di una richiesta Ajax via jquery che non aveva nulla a che fare con il post originale.
sksallaj,

13

Puoi provare questo codice per ottenere valori separati da virgola / una matrice di valori per recuperare un 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; }
 } 

Produzione :

[
{"CategoryID":"4","CategoryName":"Category_4"}, 
{"CategoryID":"5","CategoryName":"Category_5"}, 
{"CategoryID":"3","CategoryName":"Category_3"} 
]

12

ASP.NET Core 2.0 Solution (Swagger Ready)

Ingresso

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

Codice

Scrivi al provider (come MVC sa quale legante usare)

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

Scrivi il raccoglitore effettivo (accedi a tutti i tipi di informazioni su richiesta, azione, modelli, tipi, qualunque cosa)

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

Registralo con MVC

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

Esempio di utilizzo con un controller ben documentato per 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 consiglia di utilizzare un TypeConverter per questi figli di operazioni su questo approccio. Quindi segui i consigli sui poster di seguito e documenta il tuo tipo personalizzato con uno SchemaFilter.


Penso che la raccomandazione MS di cui stai parlando sia soddisfatta da questa risposta: stackoverflow.com/a/49563970/4367683
Machado,

L'hai visto? github.com/aspnet/Mvc/pull/7967 sembra che abbiano aggiunto una correzione per iniziare l'analisi dell'elenco <whatever> nella stringa di query senza la necessità di un raccoglitore speciale. Anche il post che hai collegato non è ASPNET Core e non credo che aiuti con la mia situazione.
Victorio Berra,

La risposta migliore, non confusa.
Erik Philips,

7

Invece di utilizzare un ModelBinder personalizzato, è anche possibile utilizzare un tipo personalizzato con un 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);
    }
}

Il vantaggio è che rende i parametri del metodo API Web molto semplici. Non è nemmeno necessario specificare [FromUri].

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

Questo esempio è per un Elenco di stringhe, ma è possibile invece fare categoryIds.Select(int.Parse)o semplicemente scrivere un IntList.


Non capisco perché questa soluzione non abbia ottenuto molti voti. È bello e pulito e funziona con spavalderia senza aggiungere leganti personalizzati e roba.
Thieme

La risposta migliore / più pulita secondo me. Grazie PhillipM!
Leigh Bowers,

7

Inizialmente ho usato la soluzione che @Mrchief per anni (funziona benissimo). Ma quando ho aggiunto Swagger al mio progetto per la documentazione API, il mio punto finale NON era visibile .

Mi ci è voluto un po ', ma è quello che mi è venuto in mente. Funziona con Swagger e le firme del metodo API sembrano più pulite:

Alla fine puoi fare:

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

    }
}

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

Crea una nuova classe: StringToIntArrayConverter.cs

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

Appunti:


1
Nel caso in cui qualcun altro abbia bisogno di informazioni sulle librerie che utilizza. Ecco l'uso di "CommaDelimitedArrayParameterBinder". utilizzando System.Collections.Generic; utilizzando System.Linq; utilizzando System.Threading; utilizzando System.Threading.Tasks; utilizzando System.Web.Http.Controllers; utilizzando System.Web.Http.Metadata; utilizzando System.Web.Http.ModelBinding; utilizzando System.Web.Http.ValueProviders; utilizzando System.Web.Http.ValueProviders.Providers;
SteckDEV,

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

Richiedi uri

http://localhost/api/v1/files/2a9937c7-8201-59b7-bc8d-11a9178895d0/tags/BBA5CD5D-F07D-47A9-8DEE-D19F5FA65F63,BBA5CD5D-F07D-47A9-8DEE-D19F5FA65F63

@Elsa Potresti indicare quale pezzo non riesci a capire? Penso che il codice sia abbastanza chiaro per spiegarlo da solo. Mi dispiace spiegare tutto in inglese, scusa.
Waninlezu,

@Steve Czetty ecco la mia versione ricostruita, grazie per la tua idea
Waninlezu,

Funzionerà con /come separatore? Quindi potresti avere: dns / root / mystuff / path / to / some / resource mappato supublic string GetMyStuff(params string[] pathBits)
RoboJ1M

5

Se vuoi elencare / array di numeri interi il modo più semplice per farlo è accettare l'elenco di stringhe separato da virgola (,) e convertirlo in elenco di numeri interi. Non dimenticare di menzionare [FromUri] attriubte.il tuo URL apparirà come:

...? 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)
        {
        }
    }
}

perché usi List<string>invece di solo string? avrà solo una stringa che è 1,2,3,289,56nel tuo esempio. Suggerirò una modifica.
Daniël Tulp,

Ha funzionato per me. Sono stato sorpreso che il mio controller non si legherebbe List<Guid>automaticamente a un però. Nota in Asp.net Core l'annotazione è [FromQuery]e non è necessaria.
kitsu.eb,

2
Per una versione Linq a una riga: int [] accountIdArray = accountId.Split (','). Seleziona (i => int.Parse (i)). ToArray (); Eviterei la cattura poiché maschererebbe qualcuno che trasmette dati errati.
Steve In CO,

3

Crea il tipo di metodo [HttpPost], crea un modello con un parametro int [] e pubblica con 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);

Stai avvolgendo il tuo array in una classe, che funzionerà benissimo (nonostante MVC / WebAPI). L'OP riguardava l'associazione all'array senza una classe wrapper.
Mrchief,

1
Il problema originale non dice nulla su come farlo senza una classe wrapper, solo che volevano usare parametri di query per oggetti complessi. Se percorri quel percorso troppo lontano, arriverai a un punto in cui hai bisogno dell'API per raccogliere un oggetto js davvero complesso e i parametri della query falliranno. Potrebbe anche imparare a farlo nel modo in cui funzionerà ogni volta.
codeMonkey,

public IEnumerable<Category> GetCategories(int[] categoryIds){- Sì, potresti interpretare in diversi modi, suppongo. Ma molte volte, non voglio creare classi wrapper per il gusto di creare wrapper. Se hai oggetti complessi, allora funzionerà. Supportare questi casi più semplici è ciò che non funziona immediatamente, quindi l'OP.
Mrchief,

3
In questo modo via POSTè in realtà contro il paradigma REST. Pertanto, tale API non sarebbe un'API REST.
Azimuth,

1
@Azimuth mi dà un paradigma in una mano, ciò che funziona con .NET nell'altra
codeMonkey

3

Oppure potresti semplicemente passare una stringa di elementi delimitati e inserirla in un array o in un elenco alla fine della ricezione.


2

Ho affrontato questo problema in questo modo.

Ho usato un messaggio postale per l'API per inviare l'elenco di numeri interi come dati.

Quindi ho restituito i dati come un ienumerable.

Il codice di invio è il seguente:

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

Il codice di ricezione è il seguente:

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

Funziona bene per un record o molti record. Il riempimento è un metodo sovraccarico che utilizza 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;
}

Ciò consente di recuperare i dati da una tabella composita (l'elenco ID) e quindi restituire i record a cui si è realmente interessati dalla tabella di destinazione.

Potresti fare lo stesso con una vista, ma questo ti dà un po 'più di controllo e flessibilità.

Inoltre, i dettagli di ciò che si sta cercando dal database non vengono visualizzati nella stringa di query. Inoltre, non è necessario convertire da un file CSV.

Devi tenere presente che quando usi qualsiasi strumento come l'interfaccia web api 2.x è che le funzioni get, put, post, delete, head, ecc. Hanno un uso generale, ma non sono limitate a tale uso.

Quindi, sebbene la posta sia generalmente usata in un contesto di creazione nell'interfaccia API di web, non è limitata a quell'uso. Si tratta di una normale chiamata HTML che può essere utilizzata per qualsiasi scopo consentito dalla pratica HTML.

Inoltre, i dettagli di ciò che sta accadendo sono nascosti da quegli "occhi indiscreti" di cui sentiamo così tanto parlare in questi giorni.

La flessibilità nelle convenzioni di denominazione nell'interfaccia di api 2.x Web e nell'uso delle normali chiamate via Web significa che si invia una chiamata a api Web che induce in errore i ficcanaso nel pensare che si stia davvero facendo qualcos'altro. È possibile utilizzare "POST" per recuperare davvero i dati, ad esempio.


2

Ho creato un raccoglitore modello personalizzato che converte qualsiasi valore separato da virgola (solo primitivo, decimale, float, stringa) nei rispettivi array.

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 come utilizzare in Controller:

 public IHttpActionResult Get([ModelBinder(BinderType = typeof(CommaSeparatedToArrayBinder<int>))] int[] ids)
        {
            return Ok(ids);
        }

Grazie, l'ho portato su netcore 3.1 con poco sforzo e funziona! La risposta accettata non risolve il problema con la necessità di specificare il nome del parametro molte volte ed è la stessa dell'operazione predefinita in netcore 3.1
Bogdan Mart

0

La mia soluzione era quella di creare un attributo per convalidare le stringhe, fa un sacco di funzioni extra comuni, inclusa la convalida regex che puoi usare per controllare solo i numeri e poi converto in numeri interi secondo necessità ...

Ecco come si 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;
    }
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.