Truyền một mảng số nguyên cho ASP.NET Web API?


426

Tôi có một dịch vụ REST Web API (phiên bản 4) trong đó tôi cần phải vượt qua một loạt các số nguyên.

Đây là phương pháp hành động của tôi:

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

Và đây là URL mà tôi đã thử:

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

1
Tôi đã gặp lỗi "Không thể liên kết nhiều tham số với nội dung của yêu cầu" khi sử dụng chuỗi truy vấn như "/ Thể loại? Categoryids = 1 & categoryids = 2 & categoryids = 3". Hy vọng điều này mang lại cho những người ở đây đã nhận được lỗi tương tự.
Josh Noe

1
@Josh Bạn đã sử dụng [FromUri] chưa? công khai IEn Countable <Category> GetC loại ([FromUri] int [] categoryids) {...}
Anup Kattel

2
@FrankGorman Không, tôi không phải, đó là vấn đề của tôi.
Josh Noe

Câu trả lời:


619

Bạn chỉ cần thêm [FromUri]trước khi tham số, trông giống như:

GetCategories([FromUri] int[] categoryIds)

Và gửi yêu cầu:

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

18
Điều gì xảy ra nếu tôi không biết mình có bao nhiêu biến trong mảng? Nếu nó giống như 1000 thì sao? Yêu cầu không nên như thế.
Sahar Ch.

7
Điều này cho tôi một lỗi "Một mục có cùng khóa đã được thêm vào.". Tuy nhiên, nó chấp nhận thể loại [0] = 1 & thể loại [1] = 2 & vv ...
Bác sĩ Jones

19
Đây phải là câu trả lời được chấp nhận - @Hemanshu Bhojak: không phải đã đến lúc bạn chọn sao?
David Rettenbacher

12
Lý do cho điều này là do tuyên bố sau từ trang web ASP.NET Web API nói về ràng buộc tham số: "Nếu tham số là loại đơn giản, thì API Web cố gắng lấy giá trị từ URI. Các loại đơn giản bao gồm. Các kiểu nguyên thủy NET (int, bool, double, v.v.), cộng với TimeSpan, DateTime, Guid, thập phân và chuỗi, cộng với bất kỳ loại nào có trình chuyển đổi loại có thể chuyển đổi từ một chuỗi. " một int [] không phải là một loại đơn giản.
Tr1stan

3
Công việc này tốt cho tôi. Một điểm. Trên mã máy chủ, tham số mảng phải đến trước để nó hoạt động và bất kỳ tham số nào khác, sau. Khi cho ăn trong các tham số trong yêu cầu, thứ tự là không quan trọng.
Tỏa sáng

102

Như Filip W chỉ ra, bạn có thể phải dùng đến một chất kết dính mô hình tùy chỉnh như thế này (được sửa đổi để liên kết với loại param thực tế):

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

Và sau đó bạn có thể nói:

/Categories?categoryids=1,2,3,4và API Web ASP.NET sẽ liên kết chính xác categoryIdsmảng của bạn .


10
Điều này có thể vi phạm SRP và / hoặc SoC, nhưng bạn có thể dễ dàng thực hiện điều này cũng kế thừa từ ModelBinderAttributeđó để có thể sử dụng trực tiếp thay vì cú pháp tốn nhiều công sức bằng cách sử dụng typeof()đối số. Tất cả những gì bạn phải làm là kế thừa như vậy: CommaDelimitedArrayModelBinder : ModelBinderAttribute, IModelBindervà sau đó cung cấp một hàm tạo mặc định để đẩy định nghĩa kiểu xuống lớp cơ sở : public CommaDelimitedArrayModelBinder() : base(typeof(CommaDelimitedArrayModelBinder)) { }.
trượt

Mặt khác, tôi thực sự thích giải pháp này và đang sử dụng nó trong dự án của mình, vì vậy ... cảm ơn. :)
trượt

Aa một mặt lưu ý, giải pháp này không làm việc với Generics như System.Collections.Generic.List<long>như bindingContext.ModelType.GetElementType()chỉ hỗ trợ System.Arraycác loại
ViRuSTriNiTy

@ViRuSTriNiTy: Câu hỏi này và câu trả lời cụ thể nói về Mảng. Nếu bạn cần một giải pháp dựa trên danh sách chung, việc đó khá đơn giản để thực hiện. Vui lòng đưa ra một câu hỏi riêng nếu bạn không chắc chắn cách thực hiện.
Ông trùm

2
@codeMonkey: đặt mảng vào phần thân có ý nghĩa tốt cho yêu cầu POST, nhưng còn yêu cầu GET thì sao? Chúng thường không có nội dung trong cơ thể.
stakx - không còn đóng góp

40

Gần đây tôi đã bắt gặp yêu cầu này và tôi quyết định thực hiện ActionFilterđể xử lý vấn đề này.

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

Tôi đang áp dụng nó như vậy (lưu ý rằng tôi đã sử dụng 'id', không phải 'ids', vì đó là cách nó được chỉ định trong tuyến đường của tôi):

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

Và url công khai sẽ là:

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

Bạn có thể phải cấu trúc lại để đáp ứng nhu cầu cụ thể của bạn.


1
gõ int được mã hóa cứng (int.Pude) trong giải pháp của bạn. Giải pháp của Imho, @ Mrchief là tốt hơn
razon

27

Trong trường hợp ai đó sẽ cần - để đạt được điều tương tự hoặc tương tự (như xóa) thông qua POSTthay vì FromUri, hãy sử dụng FromBodyvà định dạng phía máy khách (JS / jQuery) như là$.param({ '': categoryids }, true)

c #:

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

jQuery:

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

Vấn đề $.param({ '': categoryids }, true)là nó .net sẽ mong muốn phần thân bài chứa giá trị urlencoding như =1&=2&=3không có tên tham số và không có dấu ngoặc.


2
Không cần phải dùng đến một POST. Xem câu trả lời @Lavel.
André Werlang

3
Có giới hạn về số lượng dữ liệu bạn có thể gửi trong URI. Và theo tiêu chuẩn, đây không phải là một yêu cầu NHẬN vì nó thực sự đang sửa đổi dữ liệu.
Worthy7

1
Và chính xác thì bạn đã thấy một GET ở đây? :)
Sofija

3
@Sofija OP nói code to retrieve categories from database, do đó phương thức này phải là phương thức GET, không phải POST.
Azimuth

22

Cách dễ dàng để gửi thông số mảng đến api web

API

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

Jquery: gửi đối tượng JSON làm thông số yêu cầu

$.get('api/categories/GetCategories',{categoryIds:[1,2,3,4]}).done(function(response){
console.log(response);
//success response
});

Nó sẽ tạo URL yêu cầu của bạn như ../api/categories/GetCategories?categoryIds=1&categoryIds=2&categoryIds=3&categoryIds=4


3
Làm thế nào khác với câu trả lời được chấp nhận? ngoại trừ việc thực hiện một yêu cầu ajax thông qua jquery không liên quan gì đến bài viết gốc.
sksallaj

13

Bạn có thể thử mã này để lấy các giá trị được phân tách bằng dấu phẩy / một mảng các giá trị để lấy lại JSON từ 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; }
 } 

Đầu ra:

[
{"CategoryID":"4","CategoryName":"Category_4"}, 
{"CategoryID":"5","CategoryName":"Category_5"}, 
{"CategoryID":"3","CategoryName":"Category_3"} 
]

12

Giải pháp ASP.NET Core 2.0 (Sẵn sàng vênh vang)

Đầu vào

DELETE /api/items/1,2
DELETE /api/items/1

Viết nhà cung cấp (làm thế nào MVC biết sử dụng chất kết dính nào)

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

Viết chất kết dính thực tế (truy cập tất cả các loại thông tin về yêu cầu, hành động, mô hình, loại, bất cứ điều gì)

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

Đăng ký nó với MVC

services.AddMvc(options =>
{
    // add custom binder to beginning of collection
    options.ModelBinderProviders.Insert(0, new CustomBinderProvider());
});

Sử dụng mẫu với bộ điều khiển được ghi chép tốt cho Swagger

/// <summary>
/// Deletes a list of items.
/// </summary>
/// <param name="itemIds">The list of unique identifiers for the  items.</param>
/// <returns>The deleted item.</returns>
/// <response code="201">The item was successfully deleted.</response>
/// <response code="400">The item is invalid.</response>
[HttpDelete("{itemIds}", Name = ItemControllerRoute.DeleteItems)]
[ProducesResponseType(typeof(void), StatusCodes.Status204NoContent)]
[ProducesResponseType(typeof(void), StatusCodes.Status404NotFound)]
public async Task Delete(List<int> itemIds)
=> await _itemAppService.RemoveRangeAsync(itemIds);

EDIT: Microsoft khuyên bạn nên sử dụng TypeConverter cho những đứa trẻ hoạt động theo phương pháp này. Vì vậy, hãy làm theo lời khuyên dưới đây và ghi lại loại tùy chỉnh của bạn với SchemaFilter.


Tôi nghĩ rằng giới thiệu MS mà bạn đang nói đến được thỏa mãn bởi câu trả lời này: stackoverflow.com/a/49563970/4367683
Machado

Bạn đã thấy điều này? github.com/aspnet/Mvc/pull/7967 có vẻ như họ đã thêm một bản sửa lỗi để bắt đầu phân tích Danh sách <anything> trong chuỗi truy vấn mà không cần một chất kết dính đặc biệt. Ngoài ra, bài đăng bạn liên kết không phải là ASPNET Core và tôi không nghĩ giúp được gì cho tình huống của tôi.
Victorio Berra

Câu trả lời tốt nhất, không hack.
Erik Philips

7

Ban đầu tôi đã sử dụng giải pháp mà @Mrchief trong nhiều năm (nó hoạt động rất tốt). Nhưng khi tôi thêm Swagger vào dự án của mình cho tài liệu API, điểm cuối của tôi KHÔNG hiển thị.

Phải mất một thời gian, nhưng đây là những gì tôi nghĩ ra. Nó hoạt động với Swagger và chữ ký phương thức API của bạn trông sạch hơn:

Cuối cùng, bạn có thể làm:

    // 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 ..

    }
}

Tạo một lớp mới: CommaDeliatedArrayParameterBinder.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() };
}

Tạo một lớp mới: StringToIntArrayConverter.cs

public class StringToIntArrayConverter : TypeConverter
{
    public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
    {
        return sourceType == typeof(string) || base.CanConvertFrom(context, sourceType);
    }
}

Ghi chú:

  • https://stackoverflow.com/a/47123965/862011 chỉ cho tôi đi đúng hướng
  • Swagger chỉ không chọn các điểm cuối được phân tách bằng dấu phẩy của tôi khi sử dụng thuộc tính [Tuyến đường]

1
Trong trường hợp bất cứ ai khác cần thông tin về các thư viện này sử dụng. Đây là cách sử dụng cho "CommaDeliatedArrayParameterBinder". sử dụng System.Collections.Generic; sử dụng System.Linq; sử dụng System.Threading; sử dụng System.Threading.T Nhiệm vụ; sử dụng System.Web.Http.Controllers; sử dụng System.Web.Http.Metadata; sử dụng System.Web.Http.ModelBinding; sử dụng System.Web.Http.ValueProviders; sử dụng 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));
    }
}

Sử dụng:

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

Yêu cầu uri

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

@Elsa Bạn có thể vui lòng chỉ ra phần nào bạn không thể hiểu? Tôi nghĩ rằng mã là khá rõ ràng để tự giải thích nó. Thật khó cho tôi để giải thích tất cả bằng tiếng Anh, xin lỗi.
Waninlezu

@Steve Czetty đây là phiên bản được xây dựng lại của tôi, cảm ơn ý tưởng của bạn
Waninlezu

Nó sẽ làm việc với /tư cách là người tách biệt? Sau đó, bạn có thể có: dns / root / mystuff / path / to / some / resource ánh xạ tớipublic string GetMyStuff(params string[] pathBits)
RoboJ1M

6

Thay vì sử dụng ModelBinder tùy chỉnh, bạn cũng có thể sử dụng loại tùy chỉnh với 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);
    }
}

Ưu điểm là nó làm cho các tham số của phương thức API Web rất đơn giản. Bạn thậm chí không cần chỉ định [FromUri].

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

Ví dụ này dành cho Danh sách các chuỗi, nhưng bạn có thể làm categoryIds.Select(int.Parse)hoặc chỉ đơn giản là viết một IntList thay thế.


Không hiểu tại sao giải pháp này không nhận được nhiều phiếu bầu. Nó là tốt đẹp và sạch sẽ và làm việc với swagger mà không cần thêm chất kết dính và công cụ tùy chỉnh.
Thieme

Câu trả lời tốt nhất / sạch nhất theo ý kiến ​​của tôi. Cảm ơn PhillipM!
Leigh Bowers

5

Nếu bạn muốn liệt kê / mảng các số nguyên Cách dễ nhất để thực hiện việc này là chấp nhận danh sách chuỗi được phân tách bằng dấu phẩy (,) và chuyển đổi nó thành danh sách các số nguyên. Đừng quên đề cập đến url [FromUri] attriubte.your của bạn giống như:

...? ID = 71 & tài khoảnID = 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)
        {
        }
    }
}

Tại sao bạn sử dụng List<string>thay vì chỉ string? nó sẽ chỉ có một chuỗi trong 1,2,3,289,56ví dụ của bạn. Tôi sẽ đề nghị chỉnh sửa.
Daniël Tulp

Đã làm cho tôi. Tôi đã ngạc nhiên khi bộ điều khiển của tôi sẽ không liên kết với một List<Guid>mặc dù tự động. Lưu ý trong Asp.net Core chú thích là [FromQuery], và nó không cần thiết.
Kitsu.eb

2
Đối với phiên bản Linq một dòng: int [] accountIdArray = accountId.Split (','). Chọn (i => int.Pude (i)). ToArray (); Tôi sẽ tránh bị bắt vì nó sẽ che giấu ai đó truyền dữ liệu xấu.
Steve In CO

3

Tạo kiểu phương thức [HttpPost], tạo một mô hình có một tham số int [] và đăng với 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);

Bạn đang gói mảng của mình trong một lớp - điều đó sẽ hoạt động tốt (mặc dù MVC / WebAPI). OP đã liên kết với mảng mà không có lớp bao bọc.
Ông trùm

1
Vấn đề ban đầu không nói gì về việc thực hiện nó mà không có lớp bao bọc, chỉ là họ muốn sử dụng tham số truy vấn cho các đối tượng phức tạp. Nếu bạn đi xuống con đường đó quá xa, bạn sẽ đến một điểm mà bạn cần API để chọn một đối tượng js thực sự phức tạp và truy vấn param sẽ làm bạn thất vọng. Cũng có thể học cách làm điều đó theo cách sẽ làm việc mọi lúc.
codeMonkey

public IEnumerable<Category> GetCategories(int[] categoryIds){- vâng, bạn có thể diễn giải theo những cách khác nhau mà tôi cho là. Nhưng nhiều lần, tôi không muốn tạo các lớp bao bọc vì mục đích tạo các trình bao bọc. Nếu bạn có các đối tượng phức tạp, thì điều đó sẽ chỉ hoạt động. Hỗ trợ các trường hợp đơn giản hơn là những gì không hoạt động, do đó OP.
Ông trùm

3
Làm điều này thông qua POSTthực sự là chống lại mô hình REST. Do đó, API như vậy sẽ không phải là API REST.
Azimuth

1
@Azimuth cho tôi một mô hình trong một mặt, những gì hoạt động với .NET ở mặt khác
codeMonkey

3

Hoặc bạn chỉ có thể chuyển một chuỗi các mục được phân tách và đặt nó vào một mảng hoặc danh sách ở đầu nhận.


2

Tôi đã giải quyết vấn đề này theo cách này.

Tôi đã sử dụng một thông điệp gửi đến api để gửi danh sách các số nguyên dưới dạng dữ liệu.

Sau đó, tôi trả lại dữ liệu dưới dạng vô số.

Mã gửi như sau:

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

Mã nhận như sau:

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

Nó hoạt động tốt chỉ cho một hồ sơ hoặc nhiều hồ sơ. Điền vào là một phương thức quá tải bằng 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;
}

Điều này cho phép bạn tìm nạp dữ liệu từ một bảng tổng hợp (danh sách id), sau đó trả về các bản ghi mà bạn thực sự quan tâm từ bảng mục tiêu.

Bạn có thể làm tương tự với một khung nhìn, nhưng điều này cho phép bạn kiểm soát và linh hoạt hơn một chút.

Ngoài ra, các chi tiết về những gì bạn đang tìm kiếm từ cơ sở dữ liệu không được hiển thị trong chuỗi truy vấn. Bạn cũng không phải chuyển đổi từ tệp csv.

Bạn phải lưu ý khi sử dụng bất kỳ công cụ nào như giao diện web api 2.x là các chức năng get, put, post, xóa, head, v.v., có một cách sử dụng chung, nhưng không bị hạn chế đối với việc sử dụng đó.

Vì vậy, trong khi bài viết thường được sử dụng trong ngữ cảnh tạo trong giao diện api web, nó không bị hạn chế sử dụng. Đó là một cuộc gọi html thông thường có thể được sử dụng cho bất kỳ mục đích nào được phép thực hành html cho phép.

Ngoài ra, các chi tiết về những gì đang diễn ra được ẩn giấu khỏi những "con mắt tò mò" mà chúng ta nghe rất nhiều về những ngày này.

Tính linh hoạt trong quy ước đặt tên trong giao diện web api 2.x và sử dụng cuộc gọi web thông thường có nghĩa là bạn gửi một cuộc gọi đến web api khiến những kẻ rình mò nghĩ rằng bạn đang thực sự làm việc khác. Bạn có thể sử dụng "POST" để lấy dữ liệu thực sự, ví dụ.


2

Tôi đã tạo ra một chất kết dính mô hình tùy chỉnh để chuyển đổi bất kỳ giá trị được phân tách bằng dấu phẩy (chỉ nguyên hàm, thập phân, float, chuỗi) thành các mảng tương ứng của chúng.

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

Và cách sử dụng trong Bộ điều khiển:

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

Cảm ơn, tôi đã chuyển nó sang netcore 3.1 với ít nỗ lực và nó hoạt động! Câu trả lời được chấp nhận không giải quyết vấn đề với nhu cầu chỉ định tên param nhiều lần và giống như thao tác mặc định trong netcore 3.1
Bogdan Mart

0

Giải pháp của tôi là tạo một thuộc tính để xác thực chuỗi, nó có một loạt các tính năng phổ biến khác, bao gồm xác thực regex mà bạn có thể sử dụng để kiểm tra các số và sau đó tôi chuyển đổi sang số nguyên khi cần ...

Đây là cách bạn sử dụng:

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;
    }
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.