Передати масив цілих чисел до веб-API ASP.NET?


427

У мене є сервіс REST ASP.NET (версія 4), де мені потрібно передати масив цілих чисел.

Ось мій спосіб дії:

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

І це я URL-адресу, яку я спробував:

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

1
Я отримував помилку "Не можу прив’язати декілька параметрів до вмісту запиту" під час використання запиту типу "/ Categories? Categoryids = 1 & categoryids = 2 & categoryids = 3". Сподіваюсь, це приводить сюди людей, які отримували цю саму помилку.
Джош Ное

1
@Josh Ти все ж використовував [FromUri]? public IEsumerable <Category> GetCategories ([FromUri] int [] categoryids) {...}
Anup Kattel

2
@FrankGorman Ні, я не був, що було моєю проблемою.
Джош Ное

Відповіді:


619

Вам просто потрібно додати [FromUri]параметр перед виглядом:

GetCategories([FromUri] int[] categoryIds)

І надішліть запит:

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

18
Що робити, якщо я не знаю, скільки змінних у мене в масиві? Що робити, як це 1000? Запит не повинен бути таким.
Сахар Ч.

7
Це дає мені помилку "Елемент з тим самим ключем уже додано." Проте він приймає категоріїids [0] = 1 & categoryids [1] = 2 та ін. ...
Doctor Jones

19
Це має бути прийнята відповідь - @Hemanshu Bhojak: чи не час приймати вибір?
Девід Реттенбахер

12
Ця причина цього пояснюється наступним твердженням на веб-сайті ASP.NET Web API, що говорить про прив'язку параметрів: "Якщо параметр є" простим "типом, Web API намагається отримати значення з URI. До простих типів належать. NET примітивні типи (int, bool, double та ін.), Плюс TimeSpan, DateTime, Guid, десяткові та рядкові, а також будь-який тип із конвертером типів, який може конвертувати з рядка. " int [] - не простий тип.
Tr1stan

3
Це добре працює для мене. Один момент. Щодо коду сервера, параметр масиву повинен прийти спочатку, щоб він працював та будь-які інші параметри, після. При подачі параметрів у запиті порядок є неважливим.
Блискав

102

Як зазначає Філіп У , вам, можливо, доведеться вдатися до в'яжучої моделі на зразок такої (модифікованої для прив’язки до фактичного типу парам):

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і веб-API ASP.NET правильно прив’яже ваш categoryIdsмасив.


10
Це може порушити SRP та / або SoC, але ви можете легко зробити це також успадкованим, ModelBinderAttributeтому його можна використовувати безпосередньо замість трудомісткого синтаксису, використовуючи typeof()аргумент. Все , що вам потрібно зробити , це успадкувати так: CommaDelimitedArrayModelBinder : ModelBinderAttribute, IModelBinderа потім надати конструктор за замовчуванням , який виштовхує визначення типу до базового класу: public CommaDelimitedArrayModelBinder() : base(typeof(CommaDelimitedArrayModelBinder)) { }.
sliderhouserules

Інакше мені дуже подобається це рішення, і я використовую його у своєму проекті, тому ... дякую. :)
sliderhouserules

Аа примітка боку, це рішення не працює з узагальненнями , як в System.Collections.Generic.List<long>якості bindingContext.ModelType.GetElementType()лише допоміжних System.Arrayтипів
ViRuSTriNiTy

@ViRuSTriNiTy: Це питання та відповідь конкретно говорять про масиви. Якщо вам потрібно загальне спискове рішення, це досить тривіально для реалізації. Не соромтеся ставити окреме питання, якщо ви не впевнені, як це зробити.
Mrchief

2
@codeMonkey: введення масиву в тіло має сенс для запиту POST, але як бути з GET-запитами? Зазвичай вони не мають вмісту в організмі.
stakx - більше не вносить дописів

40

Нещодавно я сам натрапив на цю вимогу, і вирішив застосувати інструмент 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", а не "id", тому що це вказано в моєму маршруті):

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

А публічна URL-адреса:

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

Можливо, вам доведеться переробляти це, щоб задовольнити ваші конкретні потреби.


1
type int у вашому рішенні є жорстким кодом (int.Parse). Імхо, рішення @ Mrchief краще
razon

27

У випадку, якщо комусь знадобиться - домогтися того ж чи подібного (наприклад, видалити) за допомогою, POSTа не FromUriвикористовувати, FromBodyі на стороні клієнта (JS / jQuery) параметр формату як$.param({ '': categoryids }, true)

c #:

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

jQuery:

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

Справа в $.param({ '': categoryids }, true)тому, що він .net очікує, що тіло повідомлення буде містити кодоване значення urlencoded, наприклад, =1&=2&=3без назви параметра та без дужок.


2
Не потрібно вдаватися до пошти. Дивіться відповідь @Lavel.
André Werlang

3
Існує обмеження в кількості даних, які ви можете надіслати в URI. І, як правило, це не повинно бути запитом GET, оскільки він фактично модифікує дані.
Достойний7

1
І де саме ви тут побачили GET? :)
Софія

3
@ Sofija OP каже code to retrieve categories from database, що таким чином метод повинен бути методом GET, а не POST.
Азимут

22

Простий спосіб відправити парами масиву на веб-api

API

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

Jquery: надішліть об’єкт 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


3
чим це відрізняється від прийнятої відповіді? за винятком реалізації запиту ajax через jquery, який не має нічого спільного з початковою публікацією.
sksallaj

13

Ви можете спробувати цей код, щоб ви взяли розділені комами значення / масив значень, щоб отримати 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"} 
]

12

Рішення ASP.NET Core 2.0 (готовність до переключення)

Вхідні дані

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

EDIT: Microsoft рекомендує використовувати TypeConverter для цих діток операцій над цим підходом. Тож дотримуйтесь наведених нижче порад щодо плакатів і документуйте свій власний тип за допомогою SchemaFilter.


Я думаю, що рекомендація щодо MS, про яку ви говорите, задоволена цією відповіддю: stackoverflow.com/a/49563970/4367683
Machado

Ви це бачили? github.com/aspnet/Mvc/pull/7967 виглядає так, ніби вони додали виправлення для початку розбору списку <що завгодно> у рядку запиту без необхідності спеціального в'яжучого. Крім того, посада, яку ви пов’язали, не є ASPNET Core, і я не думаю, що допомагає в моїй ситуації.
Вікторіо Берра

Найкраща відповідь.
Ерік Філіпс

7

Замість використання користувальницького 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 замість цього.


Не розумію, чому за це рішення не було багато голосів. Це приємно і чисто і працює з підробкою, не додаючи власні в'яжучі та інші речі.
Thieme

Найкраща / найчистіша відповідь на мій погляд. Дякую PhillipM!
Лей Боуерс

7

Я спочатку використовував рішення, яке @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]

1
У випадку, якщо комусь іншому потрібна інформація про бібліотеки, якими вона користується. Ось використання для "CommaDelimitedArrayParameterBinder". використовуючи System.Collections.Generic; за допомогою System.Linq; за допомогою System.Threading; використання System.Threading.Tasks; за допомогою System.Web.Http.Controllers; за допомогою System.Web.Http.Metadata; за допомогою System.Web.Http.ModelBinding; за допомогою System.Web.Http.ValueProviders; за допомогою 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));
    }
}

Використання:

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

Запити урі

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

@Elsa Ви можете, будь ласка, вказати, який твір ви не можете зрозуміти? Я думаю, що код цілком зрозумілий, щоб пояснити його самостійно. Мені важко все це пояснити англійською, вибачте.
Waninlezu

@Steve Czetty ось моя реконструйована версія, дякую за вашу ідею
Waninlezu

Чи буде це працювати з /сепаратором? Тоді ви могли мати: dns / root / mystuff / шлях / до / деякий / ресурс, відображений наpublic string GetMyStuff(params string[] pathBits)
RoboJ1M

5

Якщо ви хочете перелічити / масив цілих чисел, найпростіший спосіб зробити це - прийняти відокремлений комою (,) список рядка та перетворити його у список цілих чисел.

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

чому ви використовуєте List<string>замість просто string? у ній буде лише одна рядок, яка є 1,2,3,289,56у вашому прикладі. Я запропоную редагувати.
Даніель Тулп

Працювали для мене. Я був здивований, але контролер не прив’яжеться List<Guid>автоматично до автоматичного. Зауважте в Asp.net Core анотація є [FromQuery], і вона не потрібна.
kitsu.eb

2
Для однорядкової версії Linq: int [] accountIdArray = accountId.Split (','). Виберіть (i => int.Parse (i)). ToArray (); Я б уникнув улову, оскільки він маскує когось, хто передає погані дані.
Стів у СО

3

Створіть тип методу [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);

Ви загортаєте масив у клас - це буде добре (незважаючи на MVC / WebAPI). OP стосувався прив'язки до масиву без класу обгортки.
Mrchief

1
Первісна проблема нічого не говорить про те, щоб зробити це без класу обгортки, лише те, що вони хотіли використовувати параметри запитів для складних об'єктів. Якщо ви спуститеся по цьому шляху занадто далеко, ви дістанетесь до точки, коли вам потрібен API, щоб забрати дійсно складний js-об’єкт, і параметри запиту не зможуть вас. Можна також навчитися робити це так, як буде працювати кожен раз.
codeMonkey

public IEnumerable<Category> GetCategories(int[] categoryIds){- так, ви могли б трактувати по-різному. Але багато разів я не хочу створювати класи обгортки заради створення обгортки. Якщо у вас складні об'єкти, то це просто спрацює. Підтримка цих простіших випадків - це те, що не виходить з поля, отже, і ОП.
Mrchief

3
Зробити це через POSTфактично проти парадигми REST. Таким чином, такий API не був би REST API.
Азимут

1
@Azimuth дай мені парадигму в одній руці, що працює з .NET в іншій
codeMonkey

3

Або ви можете просто передати рядок з обмеженими елементами і помістити його в масив або список на кінці отримання.


2

Я вирішив це питання таким чином.

Я використовував повідомлення api для надсилання списку цілих чисел як даних.

Потім я повернув дані як незліченну кількість.

Код відправки такий:

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

Це добре працює для одного запису або багатьох записів. Заповнення - це перевантажений метод, використовуючи 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

Я створив спеціальну в'яжучу модель, яка перетворює будь-які значення, розділені комами (лише примітивні, десяткові, плаваючі, рядкові) у відповідні масиви.

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

І як користуватися в Controller:

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

Дякую, я переніс його на netcore 3.1 з невеликими зусиллями, і він працює! Прийнята відповідь не вирішує проблему з необхідністю вказувати ім'я парамуса багато-багато разів і є такою ж, як операція за замовчуванням у netcore 3.1
Богдан Март

0

Моє рішення полягало в створенні атрибуту для перевірки рядків, він має купу додаткових загальних функцій, включаючи перевірку регулярних виразів, які ви можете використовувати для перевірки лише чисел, а потім пізніше перетворюю на цілі числа за потребою ...

Ось як ви користуєтесь:

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;
    }
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.