Ein Array mit Ganzzahlen an die ASP.NET-Web-API übergeben?

Ich habe einen ASP.NET Web API (Version 4) REST-Dienst, wo ich ein Array von ganzen Zahlen übergeben muss.

Hier ist meine Aktionsmethode:

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

Und das ist die URL, die ich ausprobiert habe:

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

    Sie müssen nur [FromUri] vor dem Parameter hinzufügen, sieht so aus:

     GetCategories([FromUri] int[] categoryIds) 

    Und senden Sie eine Anfrage:

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

    Wie Filip W darauf hinweist, müssen Sie möglicherweise auf ein benutzerdefiniertes Modell-Binder wie dieses zurückgreifen (modifiziert, um es an den tatsächlichen Param-Typ zu binden):

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

    Und dann kannst du sagen:

    /Categories?categoryids=1,2,3,4 und die ASP.NET-Web-API binden Ihr categoryIds Array korrekt.

    Ich bin kürzlich selbst auf diese Anforderung gestoßen und habe beschlossen, einen ActionFilter zu implementieren.

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

    Ich verwende es so (beachte, dass ich ‘id’, nicht ‘ids’ verwendet habe, so wie es auf meiner Route angegeben ist):

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

    Und die öffentliche URL wäre:

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

    Möglicherweise müssen Sie dies umgestalten, um Ihre spezifischen Anforderungen zu erfüllen.

    Einfache Möglichkeit, Array-Parameter an Web-API zu senden

    API

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

    Jquery: Sende JSON-Objekt als Anfrageparameter

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

    Es generiert Ihre Anfrage URL wie ../api/categories/GetCategories?categoryIds=1&categoryIds=2&categoryIds=3&categoryIds=4

    Falls jemand eine ähnliche oder ähnliche Sache (wie Löschen) über POST anstelle von FromUri , verwenden Sie FromBody und auf Clientseite (JS / jQuery) Format param als $.param({ '': categoryids }, true)

    c #:

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

    jQuery:

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

    Die Sache mit $.param({ '': categoryids }, true) ist, dass .net erwarten wird, dass post body urlencodierten Wert wie =1&=2&=3 ohne Parameternamen und ohne Klammern enthält.

    Sie können diesen Code für Sie verwenden, um durch Komma getrennte Werte / ein Array von Werten zu erhalten, um ein JSON von webAPI zurückzubekommen

      public class CategoryController : ApiController { public List Get(String categoryIDs) { List categoryRepo = new List(); 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; } } 

    Ausgabe :

     [ {"CategoryID":"4","CategoryName":"Category_4"}, {"CategoryID":"5","CategoryName":"Category_5"}, {"CategoryID":"3","CategoryName":"Category_3"} ] 
     public class ArrayInputAttribute : ActionFilterAttribute { private readonly string[] _ParameterNames; ///  /// ///  public string Separator { get; set; } ///  /// cons ///  ///  public ArrayInputAttribute(params string[] parameterName) { _ParameterNames = parameterName; Separator = ","; } ///  /// ///  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)); } } 

    Verwendung:

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

    Anfrage uri

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

    Wenn Sie das / array of integer am einfachsten auflisten wollen, akzeptieren Sie die durch Komma (,) getrennte String-Liste und konvertieren sie in die Liste der Ganzzahlen. Vergessen Sie nicht, [FromUri] attriube.ihr URL wie folgt zu erwähnen:

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

     public HttpResponseMessage test([FromUri]int ID, [FromUri]string accountID) { List accountIdList = new List(); string[] arrAccountId = accountId.Split(new char[] { ',' }); for (var i = 0; i < arrAccountId.Length; i++) { try { accountIdList.Add(Int32.Parse(arrAccountId[i])); } catch (Exception) { } } } 

    Machen Sie den Methodentyp [HttpPost], erstellen Sie ein Modell mit einem int [] -Parameter und posten Sie mit 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); 

    Ich benutzte ursprünglich die Lösung, die @Mrchief seit Jahren (es funktioniert super). Aber als ich Swagger zu meinem Projekt für die API-Dokumentation hinzugefügt habe, wurde mein Endpunkt NICHT angezeigt.

    Es hat eine Weile gedauert, aber das ist es, was ich mir ausgedacht habe. Es funktioniert mit Swagger, und Ihre API-Methodensignaturen sehen sauberer aus:

    Am Ende kannst du:

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

    Erstellen Sie eine neue class: CommaDelimitedArrayParameterBinder.cs

     public class CommaDelimitedArrayParameterBinder : HttpParameterBinding, IValueProviderParameterBinding { public CommaDelimitedArrayParameterBinder(HttpParameterDescriptor desc) : base(desc) { } ///  /// Handles Binding (Converts a comma delimited string into an array of integers) ///  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 ValueProviderFactories { get; } = new[] { new QueryStringValueProviderFactory() }; } 

    Erstellen Sie eine neue class: StringToIntArrayConverter.cs

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

    Anmerkungen:

    ASP.NET Core 2.0-Lösung (Swagger-fähig)

    Eingang

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

    Code

    Schreiben Sie den Anbieter (wie MVC weiß, welcher Ordner zu verwenden ist)

     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)) { return new BinderTypeModelBinder(typeof(CommaDelimitedArrayParameterBinder)); } return null; } } 

    Schreiben Sie den aktuellen Ordner (Zugriff auf alle Arten von Informationen über die Anfrage, Aktion, Modelle, Typen, was auch immer)

     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)) { bindingContext.Result = ModelBindingResult.Success(ints.ToList()); } return Task.CompletedTask; } } 

    Registrieren Sie es bei MVC

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

    Beispielverwendung mit einem gut dokumentierten Controller für Swagger

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

    Bearbeiten: Microsoft empfiehlt, einen TypeConverter für diese Kinder von Operationen über diesen Ansatz zu verwenden. Befolgen Sie daher die folgenden Hinweise und dokumentieren Sie Ihren benutzerdefinierten Typ mit einem SchemaFilter.

    Ich habe dieses Problem auf diese Weise angesprochen.

    Ich benutzte eine Post-Nachricht an die API, um die Liste der ganzen Zahlen als Daten zu senden.

    Dann gab ich die Daten als eine Anzahl zurück.

    Der Sendecode ist wie folgt:

     public override IEnumerable Fill(IEnumerable ids) { IEnumerable 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>(_endPoint, ids).Result; response.EnsureSuccessStatusCode(); if (response.IsSuccessStatusCode) { result = JsonConvert.DeserializeObject>(response.Content.ReadAsStringAsync().Result); } } } catch (Exception) { } } return result; } 

    Der empfangende Code ist wie folgt:

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

    Es funktioniert gut für einen Datensatz oder viele Datensätze. Die Füllung ist eine überladene Methode, die DapperExtensions verwendet:

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

    Dadurch können Sie Daten aus einer zusammengesetzten Tabelle (der ID-Liste) abrufen und dann die Datensätze, an denen Sie wirklich interessiert sind, aus der Zieltabelle zurückgeben.

    Sie könnten das gleiche mit einer Ansicht tun, aber das gibt Ihnen ein wenig mehr Kontrolle und Flexibilität.

    Darüber hinaus werden die Details dessen, was Sie von der database suchen, nicht in der Abfragezeichenfolge angezeigt. Sie müssen auch nicht aus einer CSV-Datei konvertieren.

    Bei der Verwendung eines Tools wie der web api 2.x-Schnittstelle ist zu beachten, dass die functionen “get”, “put”, “post”, “delete”, “head” usw. allgemein verwendet werden, aber nicht auf diese Verwendung beschränkt sind.

    Während post in der Regel in einem create-Kontext in der Web-API-Schnittstelle verwendet wird, ist es nicht auf diese Verwendung beschränkt. Es ist ein regulärer HTML-Aufruf, der für jeden durch die HTML-Praxis erlaubten Zweck verwendet werden kann.

    Darüber hinaus sind die Details dessen, was vor sich geht, vor diesen “neugierigen Augen” verborgen, von denen wir so viel über diese Tage hören.

    Die Flexibilität bei der Benennung von Konventionen in der Web-API 2.x-Schnittstelle und die Verwendung von normalen Web-Calls bedeutet, dass Sie einen Aufruf an die Web-API senden, der Schnüffler dazu verleitet, zu denken, dass Sie wirklich etwas anderes tun. Sie können beispielsweise “POST” verwenden, um Daten wirklich abzurufen.

    Oder Sie übergeben einfach eine Zeichenfolge mit begrenzten Elementen und fügen sie in ein Array oder eine Liste auf der Empfängerseite ein.

    Anstatt einen benutzerdefinierten ModelBinder zu verwenden, können Sie auch einen benutzerdefinierten Typ mit einem TypeConverter verwenden.

     [TypeConverter(typeof(StrListConverter))] public class StrList : List { public StrList(IEnumerable 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); } } 

    Der Vorteil ist, dass die Parameter der Web-API-Methode sehr einfach sind. Sie müssen nicht einmal [FromUri] angeben.

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

    Dieses Beispiel categoryIds.Select(int.Parse) sich auf eine Liste von Strings, aber Sie könnten categoryIds.Select(int.Parse) oder einfach eine IntList schreiben.

    Meine Lösung bestand darin, ein Attribut zum Überprüfen von Zeichenfolgen zu erstellen. Es bietet einige zusätzliche allgemeine functionen, einschließlich der Regex-Überprüfung, mit der Sie nur nach Zahlen suchen können. Später wandle ich sie bei Bedarf in Ganzzahlen um.

    So verwenden Sie:

     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)?[0]; if (string.IsNullOrWhiteSpace(listOfdValues)) { if (MinimumItems > 0) return new ValidationResult(this.ErrorMessage); else return null; }; var list = new List(); 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; } }