정수 배열을 전달 해야하는 ASP.NET 웹 API (버전 4) REST 서비스가 있습니다.
내 행동 방법은 다음과 같습니다.
public IEnumerable<Category> GetCategories(int[] categoryIds){
// code to retrieve categories from database
}
그리고 이것은 내가 시도한 URL입니다.
/Categories?categoryids=1,2,3,4
정수 배열을 전달 해야하는 ASP.NET 웹 API (버전 4) REST 서비스가 있습니다.
내 행동 방법은 다음과 같습니다.
public IEnumerable<Category> GetCategories(int[] categoryIds){
// code to retrieve categories from database
}
그리고 이것은 내가 시도한 URL입니다.
/Categories?categoryids=1,2,3,4
답변:
[FromUri]
매개 변수 앞에 추가하면됩니다 .
GetCategories([FromUri] int[] categoryIds)
그리고 요청을 보내십시오 :
/Categories?categoryids=1&categoryids=2&categoryids=3
으로 필립 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,4
ASP.NET Web API는 categoryIds
배열 을 올바르게 바인딩합니다 .
ModelBinderAttribute
사용하여 힘든 구문 대신 직접 사용할 수 있도록 쉽게 상속받을 수 있습니다 typeof()
. CommaDelimitedArrayModelBinder : ModelBinderAttribute, IModelBinder
다음과 같이 상속하면됩니다. 그런 다음 형식 정의를 기본 클래스로 푸시하는 기본 생성자를 제공하십시오 public CommaDelimitedArrayModelBinder() : base(typeof(CommaDelimitedArrayModelBinder)) { }
.
System.Collections.Generic.List<long>
으로 bindingContext.ModelType.GetElementType()
만 지원 System.Array
유형
나는 최근 에이 요구 사항을 직접 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
특정 요구를 충족시키기 위해 이것을 리팩토링해야 할 수도 있습니다.
를 통해 (삭제 등) 동일하거나 유사한 일을 달성하기 위해 - 경우 누군가가 필요 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
매개 변수 이름없이, 그리고 괄호없이합니다.
code to retrieve categories from database
, 따라서 메소드는 POST가 아니라 GET 메소드이어야 한다고 말합니다 .
웹 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
이 코드를 사용하여 쉼표로 구분 된 값 / 값 배열을 사용하여 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"}
]
DELETE /api/items/1,2
DELETE /api/items/1
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;
}
}
services.AddMvc(options =>
{
// add custom binder to beginning of collection
options.ModelBinderProviders.Insert(0, new CustomBinderProvider());
});
/// <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로 사용자 정의 유형을 문서화하십시오.
사용자 정의 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를 작성하거나 작성할 수 있습니다.
나는 원래 @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);
}
}
노트:
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
/
분리기로 합니까? 그럼 당신은 할 수 : DNS / 루트 / mystuff에 / 경로 /로 / 어떤 / 자원 에 매핑public string GetMyStuff(params string[] pathBits)
정수를 나열 / 배열하려면 가장 쉬운 방법은 쉼표로 구분 된 문자열 목록을 수락하고 정수 목록으로 변환하는 것입니다. [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
예제에 하나의 문자열 만 있습니다 . 편집을 제안합니다.
List<Guid>
자동으로 바인딩되지 않는 것에 놀랐습니다 . Asp.net Core에서 주석은입니다 [FromQuery]
. 필요하지 않습니다.
메소드 유형을 [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);
public IEnumerable<Category> GetCategories(int[] categoryIds){
-예, 당신은 내가 생각한 다른 방식으로 해석 할 수 있습니다. 그러나 많은 경우 래퍼를 만들기 위해 래퍼 클래스를 만들고 싶지 않습니다. 복잡한 객체가 있으면 작동합니다. 이보다 단순한 경우를 지원하는 것이 기본적으로 작동하지 않으므로 OP입니다.
POST
실제로 REST 패러다임에 위배됩니다. 따라서 그러한 API는 REST API가 아닙니다.
이 문제를 이런 식으로 해결했습니다.
정수 목록을 데이터로 보내기 위해 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"를 사용하여 실제로 데이터를 검색 할 수 있습니다.
쉼표로 구분 된 값 (기본, 소수, 부동 소수점, 문자열 만)을 해당 배열로 변환하는 사용자 정의 모델 바인더를 만들었습니다.
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);
}
내 솔루션은 문자열의 유효성을 검사하는 속성을 만드는 것이 었습니다. 정규 유효성 검사를 포함하여 숫자 만 확인하는 데 사용할 수있는 정규식 유효성 검사를 포함한 여러 가지 일반적인 기능을 수행 한 다음 필요에 따라 정수로 변환합니다 ...
이것이 당신이 사용하는 방법입니다 :
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;
}
}