webentwicklung-frage-antwort-db.com.de

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

Ich verwende einen ASP.NET-Web-API-Dienst (Version 4) REST, bei dem ich ein Array von Ganzzahlen übergeben muss.

Hier ist meine Aktionsmethode:

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

Und das ist die URL, die ich ausprobiert habe:

/Categories?categoryids=1,2,3,4
379
Hemanshu Bhojak

Sie müssen nur [FromUri] hinzufügen, bevor der Parameter wie folgt aussieht:

_GetCategories([FromUri] int[] categoryIds)
_

Und Anfrage senden:

_/Categories?categoryids=1&categoryids=2&categoryids=3 
_
556
Lavel

Wie Filip W zeigt, müssen Sie möglicherweise auf einen benutzerdefinierten Modellordner wie diesen zurückgreifen (geändert, um ihn an den aktuellen Parametertyp zu binden):

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

Und dann können Sie sagen:

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

92
Mrchief

Ich bin vor kurzem selbst auf diese Anforderung gestoßen und habe beschlossen, eine ActionFilter zu implementieren, um dies zu handhaben.

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 wende es so an (beachte, dass ich 'id' und nicht 'ids' verwendet habe, da es auf meiner Route so angegeben ist):

[ArrayInput("id", Separator = ';')]
public IEnumerable<Measure> 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.

37
Steve Czetty

Falls jemand - über POST anstelle von FromUri dasselbe oder ähnliches (wie Löschen) erreichen müsste, verwenden Sie FromBody und auf der Clientseite (JS/jQuery) den Parameter 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 erwartet, dass der Post-Body einen urlencodierten Wert wie =1&=2&=3 ohne Parameternamen und ohne eckige Klammern enthält.

22
Sofija

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

API

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

Jquery: JSON-Objekt als Request-Parameter senden

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

Ihre Anfrage-URL wird wie folgt generiert: ../api/categories/GetCategories?categoryIds=1&categoryIds=2&categoryIds=3&categoryIds=4

20
Jignesh Variya

Sie können diesen Code verwenden, um durch Kommas getrennte Werte oder ein Array von Werten zu verwenden, um eine JSON von der WebAPI zurückzugewinnen

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

Ausgabe :

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

Ich habe ursprünglich die Lösung verwendet, die @Mrchief seit Jahren (es funktioniert großartig). Aber als ich Swagger zu meinem Projekt für die API-Dokumentation hinzufügte, wurde mein Endpunkt NOT angezeigt.

Es hat eine Weile gedauert, aber das ist mir eingefallen. Es funktioniert mit Swagger und Ihre API-Methodensignaturen sehen sauberer aus:

Am Ende können Sie Folgendes tun:

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

    }
}

Erstellt eine neue Klasse: 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() };
}

Erstellt eine neue Klasse: StringToIntArrayConverter.cs

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

Anmerkungen:

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

Verwendungszweck:

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

Bitte uri

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

ASP.NET Core 2.0-Lösung (Swagger Ready)

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

        return null;
    }
}

Schreiben Sie den eigentlichen Ordner (greifen Sie auf alle Arten von Informationen über die Anforderung, Aktion, Modelle, Typen usw. zu).

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

Registrieren Sie es bei MVC

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

Anwendungsbeispiel mit einem gut dokumentierten Controller für 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);

BEARBEITEN: Microsoft empfiehlt die Verwendung eines TypeConverter für diese Kinder von Operationen über diesen Ansatz. Befolgen Sie daher die nachstehenden Hinweise und dokumentieren Sie Ihren benutzerdefinierten Typ mit einem SchemaFilter.

6
Victorio Berra

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

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

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

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

Dieses Beispiel bezieht sich auf eine Liste von Zeichenfolgen, Sie können jedoch auch categoryIds.Select(int.Parse) ausführen oder stattdessen einfach eine IntList schreiben.

5
PhillipM

Wenn Sie eine Liste oder ein Array von Ganzzahlen auflisten möchten, ist es am einfachsten, die durch Kommas (,) getrennte Liste der Zeichenfolgen zu akzeptieren und in eine Liste von Ganzzahlen zu konvertieren.

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

Erstelle den Methodentyp [HttpPost], erstelle ein Modell mit einem int [] -Parameter und poste 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);
3
codeMonkey

Oder Sie übergeben einfach eine Zeichenfolge mit Trennzeichen und fügen sie auf der empfangenden Seite in ein Array oder eine Liste ein.

2
Sirentec

Ich habe dieses Problem auf diese Weise angegangen.

Ich habe eine Post-Nachricht an die API gesendet, um die Liste der Ganzzahlen als Daten zu senden.

Dann habe ich die Daten als unzählige zurückgegeben.

Der Sendecode lautet wie folgt:

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

Der Empfangscode lautet wie folgt:

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

Es funktioniert gut für einen oder mehrere Datensätze. Die Füllung ist eine überladene Methode mit 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;
}

Auf diese Weise können Sie Daten aus einer zusammengesetzten Tabelle (der ID-Liste) abrufen und anschließend die Datensätze, die Sie wirklich interessieren, aus der Zieltabelle zurückgeben.

Sie können dies auch mit einer Ansicht tun, dies gibt Ihnen jedoch ein wenig mehr Kontrolle und Flexibilität.

Darüber hinaus werden die Details zu dem, was Sie aus der Datenbank suchen, nicht in der Abfragezeichenfolge angezeigt. Sie müssen auch keine CSV-Datei konvertieren.

Wenn Sie ein Tool wie das Web API 2.x verwenden, müssen Sie berücksichtigen, dass die Funktionen get, put, post, delete, head usw. eine allgemeine Verwendung haben, jedoch nicht auf diese Verwendung beschränkt sind.

Obwohl post im Allgemeinen in einem Erstellungskontext in der Web-API-Benutzeroberfläche verwendet wird, ist es nicht auf diese Verwendung beschränkt. Es ist ein regulärer HTML-Aufruf, der für jeden Zweck verwendet werden kann, der durch HTML-Praxis erlaubt ist.

Darüber hinaus sind die Details des Geschehens vor diesen "neugierigen Blicken" verborgen, von denen wir heutzutage so viel hören.

Die Flexibilität der Namenskonventionen in der Web-API 2.x-Oberfläche und die Verwendung von regulären Webanrufen bedeuten, dass Sie einen Anruf an die Web-API senden, der Snoopers in die Irre führt, als würden Sie wirklich etwas anderes tun. Mit "POST" können Sie beispielsweise Daten wirklich abrufen.

2
Timothy Dooling

Meine Lösung bestand darin, ein Attribut zu erstellen, um Zeichenfolgen zu validieren. Es enthält eine Reihe zusätzlicher allgemeiner Funktionen, einschließlich der Regex-Validierung, mit der Sie nur nach Zahlen suchen und später nach Bedarf in Ganzzahlen konvertieren können.

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<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;
    }
}
0
Alan Cardoso