정수 배열을 ASP.NET 웹 API에 전달 하시겠습니까?


427

정수 배열을 전달 해야하는 ASP.NET 웹 API (버전 4) REST 서비스가 있습니다.

내 행동 방법은 다음과 같습니다.

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

그리고 이것은 내가 시도한 URL입니다.

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

1
"/ 카테고리? categoryids = 1 & categoryids = 2 & categoryids = 3"과 같은 쿼리 문자열을 사용할 때 "요청 내용에 여러 매개 변수를 바인딩 할 수 없습니다"오류가 발생했습니다. 이것이 동일한 오류가 발생하는 사람들을 여기로 데려 오기를 바랍니다.
Josh Noe

1
@Josh [FromUri]를 사용 했습니까? public IEnumerable <Category> Get 카테고리 ([FromUri] int [] categoryids) {...}
Anup Kattel

2
@ FrankGorman 아니오, 저는 아니 었습니다.
Josh Noe

답변:


619

[FromUri]매개 변수 앞에 추가하면됩니다 .

GetCategories([FromUri] int[] categoryIds)

그리고 요청을 보내십시오 :

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

18
배열에 얼마나 많은 변수가 있는지 모른다면 어떻게합니까? 1000과 같으면 어떻게 되나요? 요청은 그렇게해서는 안됩니다.
사 하르

7
"같은 키를 가진 항목이 이미 추가되었습니다."라는 오류가 발생합니다. 그러나 categoryids [0] = 1 & categoryids [1] = 2 & etc ...를 허용합니다.
Doctor Jones

19
@Hemanshu Bhojak : 선택해야 할 시간이 아닙니까?
David Rettenbacher

12
이러한 이유는 ASP.NET 웹 API 웹 사이트의 매개 변수 바인딩에 대해 다음과 같은 설명으로 인해 발생합니다 . "매개 변수가"단순 "형식 인 경우 웹 API는 URI에서 값을 가져 오려고합니다. 단순 형식에는. NET 기본 유형 (int, bool, double 등)과 TimeSpan, DateTime, Guid, 10 진수 및 문자열과 함께 문자열에서 변환 할 수있는 유형 변환기가있는 모든 유형 " int []는 단순한 유형이 아닙니다.
Tr1stan

3
이것은 나를 위해 잘 작동합니다. 한 지점. 서버 코드에서, 배열 매개 변수가 작동하려면 다른 매개 변수가 먼저 와야합니다. 요청에서 매개 변수를 공급할 때 순서는 중요하지 않습니다.
Sparked

102

으로 필립 W는 지적, 당신은 (PARAM의 실제 타입에 바인딩 수정)이 같은 사용자 정의 모델 바인더에 의존해야 할 수도 있습니다 :

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,4ASP.NET Web API는 categoryIds배열 을 올바르게 바인딩합니다 .


10
이것은 SRP 및 / 또는 SoC를 위반 할 수 있지만 인수를 ModelBinderAttribute사용하여 힘든 구문 대신 직접 사용할 수 있도록 쉽게 상속받을 수 있습니다 typeof(). CommaDelimitedArrayModelBinder : ModelBinderAttribute, IModelBinder다음과 같이 상속하면됩니다. 그런 다음 형식 정의를 기본 클래스로 푸시하는 기본 생성자를 제공하십시오 public CommaDelimitedArrayModelBinder() : base(typeof(CommaDelimitedArrayModelBinder)) { }.
sliderhouserules

그렇지 않으면, 나는이 솔루션을 정말로 좋아하고 내 프로젝트에서 사용하고 있습니다. 감사합니다. :)
sliderhouserules

AA 보조 노트,이 솔루션은 같은 제네릭 작동하지 않습니다 System.Collections.Generic.List<long>으로 bindingContext.ModelType.GetElementType()만 지원 System.Array유형
ViRuSTriNiTy

@ViRuSTriNiTy :이 질문과 대답은 특히 배열에 대해 이야기합니다. 일반 목록 기반 솔루션이 필요한 경우 구현하기가 쉽지 않습니다. 어떻게 해야할지 잘 모르겠다면 별도의 질문을 제기하십시오.
Mr.

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

나는 그렇게 적용하고 있습니다 ( 'ids'가 아닌 'id'를 사용 했으므로 경로에 지정 된 방식입니다).

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

공개 URL은 다음과 같습니다.

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

특정 요구를 충족시키기 위해 이것을 리팩토링해야 할 수도 있습니다.


1
int 유형은 솔루션에 하드 코딩 (int.Parse)됩니다. Imho, @Mrchief의 솔루션이 더 좋습니다
razon

27

를 통해 (삭제 등) 동일하거나 유사한 일을 달성하기 위해 - 경우 누군가가 필요 POST대신 FromUri사용 FromBody및 클라이언트 측 (JS / jQuery를) 형식의 PARAM로$.param({ '': categoryids }, true)

씨#:

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

jQuery :

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

가진 것은 $.param({ '': categoryids }, true)그 후 몸이 좋아 urlencode되고 값을 포함 할 것으로 예상됩니다 .net의 것입니다 =1&=2&=3매개 변수 이름없이, 그리고 괄호없이합니다.


2
POST에 의존 할 필요가 없습니다. @Lavel answer를 참조하십시오.
André Werlang

3
URI로 전송할 수있는 데이터 양에는 제한이 있습니다. 그리고 기본적으로 이것은 실제로 데이터를 수정하기 때문에 GET 요청이 아니어야합니다.
Worthy7

1
그리고 여기서 정확히 GET을 보셨습니까? :)
Sofija

3
@Sofija OP는 code to retrieve categories from database, 따라서 메소드는 POST가 아니라 GET 메소드이어야 한다고 말합니다 .
방위각

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
이것이 받아 들여지는 대답과 어떻게 다릅니 까? 원래 게시물과 아무런 관련이없는 jquery를 통해 아약스 요청을 구현하는 것을 제외하고.
sksallaj

13

이 코드를 사용하여 쉼표로 구분 된 값 / 값 배열을 사용하여 webAPI에서 JSON을 다시 가져올 수 있습니다.

 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 솔루션 (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);

편집 : 이 방법을 통해 이러한 작업위해 TypeConverter를 사용하는 것이 좋습니다 . 따라서 아래의 포스터 조언을 따르고 SchemaFilter로 사용자 정의 유형을 문서화하십시오.


나는 당신이 말하는 MS 추천이이 답변에 만족한다고 생각합니다 : stackoverflow.com/a/49563970/4367683
Machado

이거 봤어? github.com/aspnet/Mvc/pull/7967 그들은 특별한 바인더가 필요없이 쿼리 문자열에서 List <whatever> 구문 분석을 시작하는 수정을 추가 한 것처럼 보입니다. 또한 귀하가 링크 한 게시물은 ASPNET Core가 아니며 내 상황에 도움이되지 않는다고 생각합니다.
Victorio Berra

해킹이 아닌 최고의 답변.
Erik Philips

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

장점은 웹 API 메소드의 매개 변수를 매우 단순하게 만든다는 것입니다. [FromUri]를 지정할 필요조차 없습니다.

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

이 예제는 문자열 목록에 대한 것이지만 categoryIds.Select(int.Parse)대신 IntList를 작성하거나 작성할 수 있습니다.


이 솔루션이 많은 표를 얻지 못한 이유를 이해하지 마십시오. 멋지고 깨끗하며 사용자 정의 바인더와 물건을 추가하지 않고 swagger와 함께 작동합니다.
Thieme

내 의견으로는 가장 좋고 깨끗한 답변. 감사합니다 PhillipM!
레이 Bowers

7

나는 원래 @Mrchief 솔루션을 수년간 사용했습니다. 그러나 API 문서를 위해 프로젝트에 Swagger 를 추가했을 때 내 엔드 포인트가 표시 되지 않았습니다 .

시간이 걸렸지 만 이것이 내가 생각 해낸 것입니다. 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);
    }
}

노트:


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 / 루트 / mystuff에 / 경로 /로 / 어떤 / 자원 에 매핑public string GetMyStuff(params string[] pathBits)
RoboJ1M

5

정수를 나열 / 배열하려면 가장 쉬운 방법은 쉼표로 구분 된 문자열 목록을 수락하고 정수 목록으로 변환하는 것입니다. [FromUri] attriubte.your url을 언급하는 것을 잊지 마십시오.

...? 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예제에 하나의 문자열 만 있습니다 . 편집을 제안합니다.
Daniël Tulp

나를 위해 일했다. 컨트롤러가 List<Guid>자동으로 바인딩되지 않는 것에 놀랐습니다 . Asp.net Core에서 주석은입니다 [FromQuery]. 필요하지 않습니다.
kitsu.eb

2
한 줄 Linq 버전의 경우 : int [] accountIdArray = accountId.Split ( ','). Select (i => int.Parse (i)). ToArray (); 나쁜 데이터를 전달하는 누군가를 가릴 것이므로 캐치를 피할 수 있습니다.
Steve In CO

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는 래퍼 클래스없이 배열에 바인딩하는 것에 관한 것입니다.
Mr.

1
원래 문제는 래퍼 클래스없이 수행하는 것에 대해 아무 말도하지 않고 단지 복잡한 객체에 대해 쿼리 매개 변수를 사용하려고한다는 것입니다. 그 길을 너무 멀리 내려 가면 정말 복잡한 js 객체를 선택하기 위해 API가 필요한 시점에 도달하게되며 쿼리 매개 변수가 실패합니다. 매번 작동하는 방식으로 배우는 것도 배울 수 있습니다.
codeMonkey

public IEnumerable<Category> GetCategories(int[] categoryIds){-예, 당신은 내가 생각한 다른 방식으로 해석 할 수 있습니다. 그러나 많은 경우 래퍼를 만들기 위해 래퍼 클래스를 만들고 싶지 않습니다. 복잡한 객체가 있으면 작동합니다. 이보다 단순한 경우를 지원하는 것이 기본적으로 작동하지 않으므로 OP입니다.
Mr.

3
이를 통해 POST실제로 REST 패러다임에 위배됩니다. 따라서 그러한 API는 REST API가 아닙니다.
방위각

1
@Azimuth는 한 손으로 패러다임을 제공합니다. 다른 한편으로는 .NET과 작동하는 코드
codeMonkey

3

또는 구분 된 항목의 문자열을 전달하여 수신 측의 배열 또는 목록에 넣을 수 있습니다.


2

이 문제를 이런 식으로 해결했습니다.

정수 목록을 데이터로 보내기 위해 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;
}

하나의 레코드 또는 많은 레코드에 적합합니다. 채우기는 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;
}

이를 통해 복합 테이블 (id 목록)에서 데이터를 가져온 다음 목표 테이블에서 실제로 관심있는 레코드를 리턴 할 수 있습니다.

보기를 사용하여 동일한 작업을 수행 할 수 있지만 이로 인해 제어 및 유연성이 약간 향상됩니다.

또한 데이터베이스에서 찾고자하는 세부 정보가 쿼리 문자열에 표시되지 않습니다. 또한 CSV 파일에서 변환 할 필요가 없습니다.

web api 2.x 인터페이스와 같은 도구를 사용할 때 get, put, post, delete, head 등의 기능은 일반적으로 사용되지만 그 용도로 제한되지 않는다는 점을 명심해야합니다.

따라서 post는 일반적으로 웹 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;
            }
        }
    }

컨트롤러에서 사용하는 방법 :

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

고마워, 약간의 노력으로 netcore 3.1에 포팅했으며 작동합니다! 허용되는 답변은 여러 번 매개 변수 이름을 지정 해야하는 문제를 해결하지 못하며 netcore 3.1의 기본 작업과 동일합니다.
Bogdan Mart

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;
    }
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.