it-swarm.asia

تمرير مجموعة من الأعداد الصحيحة إلى ASP.NET Web API؟

لديّ ASP.NET Web API (الإصدار 4) REST خدمة حيث أحتاج إلى تمرير مجموعة من الأعداد الصحيحة.

إليك طريقة عملي:

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

وهذا هو عنوان URL الذي جربته:

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

تحتاج فقط إلى إضافة[FromUri]قبل المعلمة ، مثل:

GetCategories([FromUri] int[] categoryIds)

وإرسال الطلب:

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

كما Filip W يشير ، قد تضطر إلى اللجوء إلى الموثق طراز مخصص مثل هذا (المعدلة لربط مع نوع حقيقي من المعلمة):

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

وبعد ذلك يمكنك أن تقول:

سيقوم /Categories?categoryids=1,2,3,4 و ASP.NET Web API بربط صفيف categoryIds بشكل صحيح.

91
Mrchief

لقد صادفت هذا المطلب بنفسي مؤخرًا ، وقررت تطبيق ActionFilter للتعامل مع هذا.

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

أنا أطبقها على هذا النحو (لاحظ أنني استخدمت "id" ، وليس "ids" ، لأن هذه هي الطريقة المحددة في طريقي):

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

وعنوان url العام سيكون:

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

قد تضطر إلى إعادة تشكيل هذا لتلبية الاحتياجات المحددة الخاصة بك.

36
Steve Czetty

في حالة احتياج شخص ما - لتحقيق نفس الشيء أو شيء مشابه (مثل الحذف) عبر POST بدلاً من FromUri ، استخدم FromBody وعلى تنسيق جانب العميل (JS/jQuery) param كـ $.param({ '': categoryids }, true)

ج #:

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

مسج:

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

الشيء مع $.param({ '': categoryids }, true) هو أنه .net يتوقع أن يحتوي نص المنشور على قيمة urlencoded مثل =1&=2&=3 بدون اسم المعلمة ، ودون أقواس.

21
Sofija

طريقة سهلة لإرسال مجموعة المعلمات إلى api على شبكة الإنترنت

API

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

مسج: إرسال كائن JSON كما طلب المعلمات

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

سيقوم بإنشاء عنوان URL لطلبك مثل ../api/categories/GetCategories?categoryIds=1&categoryIds=2&categoryIds=3&categoryIds=4

19
Jignesh Variya

يمكنك تجربة هذا الرمز لكي تأخذ قيمًا مفصولة بفواصل/مجموعة من القيم لاستعادة JSON من 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; }
 } 

انتاج :

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

بدلاً من استخدام ModelBinder مخصص ، يمكنك أيضًا استخدام نوع مخصص مع 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);
    }
}

الميزة هي أنه يجعل معلمات طريقة Web API بسيطة للغاية. لا تحتاج حتى إلى تحديد [FromUri].

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

هذا المثال مخصص لقائمة من السلاسل ، لكن يمكنك عمل categoryIds.Select(int.Parse) أو ببساطة كتابة IntList بدلاً من ذلك.

6
PhillipM

لقد استخدمت في الأصل الحل الذيMrchief لسنوات (إنه يعمل بشكل رائع). ولكن عندما أضفت Swagger إلى مشروعي الخاص بوثائق API ، كانت نقطة النهاية الخاصة بيلاتظهر.

استغرق الأمر مني بعض الوقت ، لكن هذا هو ما توصلت إليه. إنه يعمل مع Swagger ، وتبدو توقيعات طريقة API أكثر نظافة:

في النهاية يمكنك القيام به:

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

    }
}

إنشاء فئة جديدة: 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() };
}

إنشاء فئة جديدة: StringToIntArrayConverter.cs

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

ملاحظات:

  • https://stackoverflow.com/a/47123965/862011 أشار لي في الاتجاه الصحيح
  • فشل Swagger في اختيار نقاط النهاية المحددة بفواصل عند استخدام السمة [Route]
6
crabCRUSHERclamCOLLECTOR
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));
    }
}

الاستعمال:

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

طلب uri

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

إذا كنت ترغب في سرد ​​/ مصفوفة من الأعداد الصحيحة ، فإن أسهل طريقة للقيام بذلك هي قبول قائمة السلسلة المفصولة بفواصل (،) وتحويلها إلى قائمة الأعداد الصحيحة.

...؟ 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)
        {
        }
    }
}
6
Vaibhav

حلول ASP.NET 2.0 الأساسية (Swagger Ready)

إدخال

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

الشفرة

اكتب الموفر (كيف تعرف MVC ما الموثق الذي يجب استخدامه)

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

اكتب الموثق الفعلي (الوصول إلى جميع أنواع المعلومات حول الطلب ، والعمل ، والنماذج ، وأنواع ، وأيا كان)

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

تسجيله مع MVC

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

استخدام عينة مع وحدة تحكم موثقة جيدا ل 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);

تحرير: توصي Microsoft باستخدام TypeConverter لهؤلاء الأطفال من العمليات على هذا النهج. لذلك اتبع نصيحة الملصقات أدناه ووثق نوعك المخصص باستخدام SchemaFilter.

4
Victorio Berra

اجعل نوع الأسلوب [HttpPost] ، وقم بإنشاء نموذج له معلمة int [] واحدة ، ثم قم بنشره باستخدام 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

أو يمكنك فقط تمرير سلسلة من العناصر المحددة ووضعها في صفيف أو قائمة في الطرف المتلقي.

2
Sirentec

لقد تناولت هذه القضية بهذه الطريقة.

لقد استخدمت رسالة نشر إلى api لإرسال قائمة الأعداد الصحيحة كبيانات.

ثم عدت البيانات باعتبارها ienumerable.

رمز الإرسال على النحو التالي:

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

رمز الاستلام كالتالي:

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

أنه يعمل بشكل جيد لسجل واحد أو العديد من السجلات. التعبئة طريقة overloaded باستخدام 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;
}

يتيح لك ذلك جلب البيانات من جدول مركب (قائمة المعرف) ، ثم إرجاع السجلات التي تهتم بها حقًا من الجدول الهدف.

يمكنك أن تفعل الشيء نفسه باستخدام طريقة عرض ، لكن هذا يمنحك مزيدًا من التحكم والمرونة.

بالإضافة إلى ذلك ، لا يتم عرض تفاصيل ما تبحث عنه من قاعدة البيانات في سلسلة الاستعلام. ليس لديك أيضًا التحويل من ملف CSV.

عليك أن تضع في اعتبارك عند استخدام أي أداة مثل واجهة الويب api 2.x أن وظائف get ، put ، post ، delete ، head ، وما إلى ذلك ، لها استخدام عام ، ولكنها غير مقيدة بهذا الاستخدام.

لذلك ، بينما يتم استخدام المنشور بشكل عام في سياق إنشاء في واجهة api على الويب ، إلا أنه لا يقتصر على هذا الاستخدام. إنها مكالمة عادية html والتي يمكن استخدامها لأي غرض مسموح به من خلال ممارسة html.

بالإضافة إلى ذلك ، فإن تفاصيل ما يجري مخفي عن تلك "الأعين المتطفلين" التي نسمعها كثيرًا في هذه الأيام.

المرونة في تسمية اصطلاحات في واجهة الويب api 2.x واستخدام اتصال الويب المعتاد تعني أنك ترسل مكالمة إلى api على الويب والتي تضلل المتلصصين في تفكيرك بأنك تفعل شيئًا آخر حقًا. يمكنك استخدام "POST" لاسترجاع البيانات حقًا ، على سبيل المثال.

2
Timothy Dooling

كان الحل الخاص بي هو إنشاء سمة للتحقق من صحة السلاسل ، فهو يقوم بمجموعة من الميزات الشائعة الإضافية ، بما في ذلك التحقق من صحة regex الذي يمكنك استخدامه للتحقق من الأرقام فقط وبعد ذلك أقوم بالتحويل إلى أعداد صحيحة حسب الحاجة ...

هذه هي الطريقة التي تستخدمها:

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