ผ่านอาร์เรย์ของจำนวนเต็มไปยัง ASP.NET Web API หรือไม่


427

ฉันมีบริการ REST ASP.NET Web API (รุ่น 4) ที่ฉันต้องผ่านอาร์เรย์ของจำนวนเต็ม

นี่คือวิธีการกระทำของฉัน:

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

และนี่คือ URL ที่ฉันได้ลอง:

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

1
ฉันได้รับข้อผิดพลาด "ไม่สามารถผูกพารามิเตอร์หลายตัวกับเนื้อหาของคำขอ" เมื่อใช้การสอบถามแบบ "/ หมวดหมู่? หมวดหมู่ = 1 & หมวดหมู่ = 2 & หมวดหมู่ = 3" หวังว่านี่จะทำให้คนที่นี่ซึ่งได้รับข้อผิดพลาดเดียวกันนี้
Josh Noe

1
@Josh คุณใช้ [FromUri] แล้วหรือยัง? สาธารณะ IEnumerable <หมวดหมู่> GetCategories ([FromUri] int [] categoryids) {... }
Anup Kattel

2
@ FrankGorman ไม่ฉันไม่ใช่ซึ่งเป็นปัญหาของฉัน
Josh Noe

คำตอบ:


619

คุณเพียงแค่ต้องเพิ่ม[FromUri]ก่อนพารามิเตอร์ดูเหมือนว่า:

GetCategories([FromUri] int[] categoryIds)

และส่งคำขอ:

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

18
ถ้าฉันไม่รู้ตัวแปรที่ฉันมีในอาร์เรย์? ถ้าเป็น 1,000 คำขอไม่ควรเป็นอย่างนั้น
Sahar Ch.

7
สิ่งนี้ทำให้ฉันมีข้อผิดพลาด "มีการเพิ่มรายการที่มีรหัสเดียวกันแล้ว" อย่างไรก็ตามจะยอมรับหมวดหมู่ [0] = 1 & หมวดหมู่ [1] = 2 และอื่น ๆ ...
Doctor Jones

19
นี่ควรเป็นคำตอบที่ได้รับการยอมรับ - @Hemanshu Bhojak: มันไม่เกี่ยวกับเวลาที่จะเลือกใช่ไหม?
David Rettenbacher

12
เหตุผลนี้เกิดจากคำสั่งต่อไปนี้จากเว็บไซต์ ASP.NET Web API ที่พูดถึงการโยงพารามิเตอร์: "ถ้าพารามิเตอร์เป็นประเภท" ง่าย "Web API จะพยายามรับค่าจาก URI ประเภทที่เรียบง่าย ได้แก่ ชนิดดั้งเดิมของ. NET (int, bool, double และอื่น ๆ ), รวมถึง TimeSpan, DateTime, Guid, ทศนิยมและสตริงรวมถึงประเภทใด ๆ ที่มีตัวแปลงประเภทที่สามารถแปลงจากสตริงได้ " int [] ไม่ใช่ประเภทแบบง่าย
Tr1stan

3
มันใช้งานได้ดีสำหรับฉัน จุดหนึ่ง บนรหัสเซิร์ฟเวอร์พารามิเตอร์อาร์เรย์ต้องมาก่อนเพื่อให้สามารถใช้งานได้และพารามิเตอร์อื่น ๆ หลังจากนั้น เมื่อป้อนพารามิเตอร์ในคำขอใบสั่งจะไม่สำคัญ
จุดประกาย

102

ดังที่Filip Wชี้ให้เห็นคุณอาจต้องหันไปใช้เครื่องผูกโมเดลที่กำหนดเองเช่นนี้ (แก้ไขเพื่อผูกเข้ากับประเภทของพารามิเตอร์ที่เกิดขึ้นจริง):

public IEnumerable<Category> GetCategories([ModelBinder(typeof(CommaDelimitedArrayModelBinder))]long[] categoryIds) 
{
    // do your thing
}

public class CommaDelimitedArrayModelBinder : IModelBinder
{
    public bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext)
    {
        var key = bindingContext.ModelName;
        var val = bindingContext.ValueProvider.GetValue(key);
        if (val != null)
        {
            var s = val.AttemptedValue;
            if (s != null)
            {
                var elementType = bindingContext.ModelType.GetElementType();
                var converter = TypeDescriptor.GetConverter(elementType);
                var values = Array.ConvertAll(s.Split(new[] { ","},StringSplitOptions.RemoveEmptyEntries),
                    x => { return converter.ConvertFromString(x != null ? x.Trim() : x); });

                var typedValues = Array.CreateInstance(elementType, values.Length);

                values.CopyTo(typedValues, 0);

                bindingContext.Model = typedValues;
            }
            else
            {
                // change this line to null if you prefer nulls to empty arrays 
                bindingContext.Model = Array.CreateInstance(bindingContext.ModelType.GetElementType(), 0);
            }
            return true;
        }
        return false;
    }
}

จากนั้นคุณสามารถพูดได้ว่า:

/Categories?categoryids=1,2,3,4และ ASP.NET Web API จะผูกcategoryIdsอาเรย์ของคุณอย่างถูกต้อง


10
สิ่งนี้อาจเป็นการละเมิด SRP และ / หรือ SoC แต่คุณสามารถทำให้สิ่งนี้สืบทอดมาได้อย่างง่ายดายModelBinderAttributeดังนั้นจึงสามารถนำมาใช้โดยตรงแทนที่จะใช้ไวยากรณ์ที่ลำบากโดยใช้typeof()อาร์กิวเมนต์ ทั้งหมดที่คุณต้องทำคือการสืบทอดเช่นดังนั้น: แล้วให้สร้างเริ่มต้นที่ผลักดันการกำหนดประเภทลงไปที่ชั้นฐาน:CommaDelimitedArrayModelBinder : ModelBinderAttribute, IModelBinder public CommaDelimitedArrayModelBinder() : base(typeof(CommaDelimitedArrayModelBinder)) { }
sliderhouserules

ไม่เช่นนั้นฉันชอบวิธีนี้มากและใช้มันในโครงการของฉันดังนั้น ... ขอบคุณ :)
sliderhouserules

โปรดทราบว่าโซลูชันนี้ใช้ไม่ได้กับ generics เช่นเดียว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' ไม่ใช่ 'รหัส' เนื่องจากเป็นวิธีที่ระบุไว้ในเส้นทางของฉัน):

[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) ในโซลูชันของคุณ 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)เป็นอยู่คือมัน. net คาดว่า post body จะมีค่า urlencoded เหมือน=1&=2&=3ไม่มีชื่อพารามิเตอร์และไม่มีเครื่องหมายวงเล็บ


2
ไม่จำเป็นต้องหันไปใช้ POST ดูที่ @Lavel คำตอบ
André Werlang

3
มีการ จำกัด ปริมาณข้อมูลที่คุณสามารถส่งใน URI ได้ และตามมาตรฐานนี่ไม่ควรเป็นคำขอ GET เนื่องจากเป็นการแก้ไขข้อมูลจริง
สมควร 7

1
และที่ไหนที่คุณเห็น GET ตรงนี้ :)
Sofija

3
@Sofija OP กล่าวว่าcode to retrieve categories from databaseดังนั้นวิธีการนี้ควรเป็นวิธีการ GET ไม่ใช่ POST
Azimuth

22

วิธีง่ายๆในการส่ง params array ไปที่ 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


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

แก้ไข: Microsoft แนะนำให้ใช้ TypeConverter สำหรับเด็ก ๆ ของการดำเนินการมากกว่าวิธีนี้ ดังนั้นทำตามคำแนะนำผู้โพสต์ด้านล่างและจัดทำเอกสารประเภทที่คุณกำหนดเองด้วย SchemaFilter


ฉันคิดว่าการแนะนำ MS ที่คุณกำลังพูดถึงเป็นที่พึงพอใจโดยคำตอบนี้: stackoverflow.com/a/49563970/4367683
Machado

คุณเห็นสิ่งนี้ไหม github.com/aspnet/Mvc/pull/7967ดูเหมือนว่าพวกเขาได้เพิ่มการแก้ไขเพื่อเริ่มต้นการแยกวิเคราะห์รายการ <สิ่งใด> ในสตริงการสืบค้นโดยไม่จำเป็นต้องใช้เครื่องผูกพิเศษ โพสต์ที่คุณลิงก์ไม่ใช่ 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);
    }
}

ข้อดีคือทำให้พารามิเตอร์ของเมธอด Web API นั้นง่ายมาก คุณไม่จำเป็นต้องระบุ [FromUri]

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

ตัวอย่างนี้ใช้สำหรับรายการของสตริง แต่คุณสามารถทำได้categoryIds.Select(int.Parse)หรือเพียงแค่เขียน IntList แทน


ไม่เข้าใจว่าทำไมการแก้ปัญหานี้จึงไม่ได้รับคะแนนเสียงมาก มันดีและสะอาดและใช้งานได้กับกร่างโดยไม่ต้องเพิ่มตัวยึดและของที่กำหนดเอง
Thieme

คำตอบที่ดีที่สุด / สะอาดที่สุดในความคิดของฉัน ขอบคุณ PhillipM!
Leigh Bowers

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

คำขอ uri

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

มันจะทำงานร่วมกับ/ในฐานะ seperator หรือไม่? จากนั้นคุณสามารถมี: dns / root / mystuff / path / to / some / ทรัพยากรที่แมปกับpublic string GetMyStuff(params string[] pathBits)
RoboJ1M

5

ถ้าคุณต้องการ list / array ของจำนวนเต็มวิธีที่ง่ายที่สุดคือยอมรับเครื่องหมายจุลภาค (,) ที่คั่นรายการของสตริงและแปลงเป็นรายการจำนวนเต็มอย่าลืมพูดถึง [FromUri] attriubte. 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 (',') เลือก (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 เกี่ยวกับการผูกกับอาเรย์โดยไม่มีคลาส wrapper
Mrchief

1
ปัญหาดั้งเดิมไม่ได้พูดอะไรเกี่ยวกับการทำโดยไม่มีคลาส wrapper เพียงว่าพวกเขาต้องการใช้ params แบบสอบถามสำหรับวัตถุที่ซับซ้อน ถ้าคุณลงไปที่เส้นทางนั้นมากเกินไปคุณจะไปถึงจุดที่คุณต้องการให้ API หยิบวัตถุ js ที่ซับซ้อนจริง ๆ และพารามิเตอร์ของแบบสอบถามจะทำให้คุณล้มเหลว อาจเรียนรู้ที่จะทำในแบบที่จะทำงานทุกครั้ง
codeMonkey

public IEnumerable<Category> GetCategories(int[] categoryIds){- ใช่คุณสามารถตีความในวิธีที่แตกต่างกันฉันคิดว่า แต่หลายครั้งฉันไม่ต้องการสร้างคลาส wrapper เพื่อประโยชน์ในการสร้าง wrapper หากคุณมีวัตถุที่ซับซ้อนแล้วมันก็จะทำงาน การสนับสนุนกรณีที่ง่ายกว่านี้คือสิ่งที่ไม่ได้ผลนอกกรอบดังนั้น OP
Mrchief

3
การทำสิ่งนี้ผ่านทางPOSTนั้นขัดกับกระบวนทัศน์ REST ดังนั้น API ดังกล่าวจะไม่เป็น REST API
Azimuth

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

สิ่งนี้ช่วยให้คุณสามารถดึงข้อมูลจากตารางคอมโพสิต (รายการ id) แล้วส่งกลับระเบียนที่คุณสนใจจริงๆจากตารางเป้าหมาย

คุณสามารถทำสิ่งเดียวกันได้ด้วยมุมมอง แต่สิ่งนี้จะช่วยให้คุณควบคุมและยืดหยุ่นได้มากกว่าเดิมเล็กน้อย

นอกจากนี้รายละเอียดของสิ่งที่คุณกำลังค้นหาจากฐานข้อมูลจะไม่แสดงในสตริงแบบสอบถาม คุณไม่จำเป็นต้องแปลงจากไฟล์ csv

คุณต้องจำไว้เมื่อใช้เครื่องมือใด ๆ เช่นส่วนต่อประสานเว็บ api 2.x คือฟังก์ชั่นรับ, วาง, โพสต์, ลบ, หัว ฯลฯ มีการใช้งานทั่วไป แต่ไม่ จำกัด เฉพาะการใช้งานนั้น

ดังนั้นในขณะที่โพสต์มักจะใช้ในการสร้างบริบทในเว็บอินเตอร์เฟส API มันไม่ได้ จำกัด การใช้งานที่ มันคือการเรียก html ปกติที่สามารถใช้เพื่อวัตถุประสงค์ใด ๆ ที่ได้รับอนุญาตจากการปฏิบัติ html

นอกจากนี้รายละเอียดของสิ่งที่เกิดขึ้นจะถูกซ่อนไว้จาก "prying ตา" ที่เราได้ยินเกี่ยวกับวันนี้

ความยืดหยุ่นในการตั้งชื่อข้อตกลงในส่วนต่อประสานเว็บ api 2.x และการใช้การโทรผ่านเว็บปกติหมายความว่าคุณส่งการเรียกไปยัง web 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
Bogdan Mart

0

โซลูชันของฉันคือการสร้างแอททริบิวเพื่อตรวจสอบความถูกต้องของสตริงมันมีคุณสมบัติพิเศษมากมายรวมถึงการตรวจสอบความถูกต้องของ regex ที่คุณสามารถใช้เพื่อตรวจสอบตัวเลขเท่านั้นและหลังจากนั้นฉันแปลงเป็นจำนวนเต็มตามต้องการ ...

นี่คือวิธีที่คุณใช้:

public class MustBeListAndContainAttribute : ValidationAttribute
{
    private Regex regex = null;
    public bool RemoveDuplicates { get; }
    public string Separator { get; }
    public int MinimumItems { get; }
    public int MaximumItems { get; }

    public MustBeListAndContainAttribute(string regexEachItem,
        int minimumItems = 1,
        int maximumItems = 0,
        string separator = ",",
        bool removeDuplicates = false) : base()
    {
        this.MinimumItems = minimumItems;
        this.MaximumItems = maximumItems;
        this.Separator = separator;
        this.RemoveDuplicates = removeDuplicates;

        if (!string.IsNullOrEmpty(regexEachItem))
            regex = new Regex(regexEachItem, RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.IgnoreCase);
    }

    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        var listOfdValues = (value as List<string>)?[0];

        if (string.IsNullOrWhiteSpace(listOfdValues))
        {
            if (MinimumItems > 0)
                return new ValidationResult(this.ErrorMessage);
            else
                return null;
        };

        var list = new List<string>();

        list.AddRange(listOfdValues.Split(new[] { Separator }, System.StringSplitOptions.RemoveEmptyEntries));

        if (RemoveDuplicates) list = list.Distinct().ToList();

        var prop = validationContext.ObjectType.GetProperty(validationContext.MemberName);
        prop.SetValue(validationContext.ObjectInstance, list);
        value = list;

        if (regex != null)
            if (list.Any(c => string.IsNullOrWhiteSpace(c) || !regex.IsMatch(c)))
                return new ValidationResult(this.ErrorMessage);

        return null;
    }
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.