整数の配列を渡す必要がある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
整数の配列を渡す必要がある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
回答:
[FromUri]
パラメータの前に追加する必要があるだけで、次のようになります。
GetCategories([FromUri] int[] categoryIds)
そしてリクエストを送信します:
/Categories?categoryids=1&categoryids=2&categoryids=3
フィリップ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
配列を正しくバインドします。
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; }
}
私はそれを次のように適用しています(ルートで指定されている方法であるため、「id」ではなく「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({ '': categoryids }, true)
c#:
public IHttpActionResult Remove([FromBody] int[] categoryIds)
jQuery:
$.ajax({
type: 'POST',
data: $.param({ '': categoryids }, true),
url: url,
//...
});
とのこと$.param({ '': categoryids }, true)
は、それが.netはポストボディが=1&=2&=3
パラメータ名なしで、ブラケットなしのようなurlencoded値を含むことを期待するということです。
code to retrieve categories from database
、メソッドはPOSTではなくGETメソッドである必要があります。
配列パラメーターをWeb 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);
}
}
利点は、Web 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);
}
リクエストURI
http://localhost/api/v1/files/2a9937c7-8201-59b7-bc8d-11a9178895d0/tags/BBA5CD5D-F07D-47A9-8DEE-D19F5FA65F63,BBA5CD5D-F07D-47A9-8DEE-D19F5FA65F63
/
区切り文字として?次に、次のようにすることができます:dns / root / mystuff / path / to / some / resourceにマップ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
あなたの例にある1つの文字列のみを持ちます。編集を提案します。
List<Guid>
しかし、コントローラーが自動的にバインドされないことに驚きました。Asp.net Coreの注釈は[FromQuery]
であり、必要ないことに注意してください。
メソッドタイプを[HttpPost]にし、1つの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;
}
これは、1つのレコードまたは多くのレコードに対して正常に機能します。塗りつぶしは、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などの関数が一般的に使用されますが、その使用に限定されないことに注意してください。
したがって、投稿は通常、Web APIインターフェースの作成コンテキストで使用されますが、その使用に限定されません。これは、HTMLプラクティスで許可されているあらゆる目的に使用できる通常の HTML呼び出しです。
加えて、現在起こっていることの詳細は、最近よく耳にする「詮索好きな目」から隠されています。
Web API 2.xインターフェースでの命名規則の柔軟性と通常のWeb呼び出しの使用は、実際に他のことをしているとスヌーパーに誤解させるような呼び出しをWeb APIに送信することを意味します。たとえば、「POST」を使用して、実際にデータを取得できます。
コンマ区切りの値(プリミティブ、10進数、浮動小数点数、文字列のみ)を対応する配列に変換するカスタムモデルバインダーを作成しました。
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;
}
}