Mengirim array bilangan bulat ke ASP.NET Web API?

427

Saya memiliki layanan REST ASP.NET Web API (versi 4) di mana saya harus melewati array bilangan bulat.

Inilah metode tindakan saya:

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

Dan ini adalah URL yang saya coba:

/Categories?categoryids=1,2,3,4
Hemanshu Bhojak
sumber
1
Saya mendapatkan kesalahan "Tidak dapat mengikat beberapa parameter ke konten permintaan" ketika menggunakan querystring seperti "/ Kategori? Semoga ini membawa orang ke sini yang mendapatkan kesalahan yang sama.
Josh Noe
1
@Josh Apakah Anda menggunakan [FromUri]? IEnumerable publik <Category> GetCategories ([FromUri] int [] categoryid) {...}
Anup Kattel
2
@ FrankGorman Tidak, saya tidak, yang merupakan masalah saya.
Josh Noe

Jawaban:

619

Anda hanya perlu menambahkan [FromUri]sebelum parameter, seperti:

GetCategories([FromUri] int[] categoryIds)

Dan kirim permintaan:

/Categories?categoryids=1&categoryids=2&categoryids=3 
Lavel
sumber
18
Bagaimana jika saya tidak tahu berapa banyak variabel yang saya miliki dalam array? Bagaimana jika itu seperti 1000? Permintaannya tidak boleh seperti itu.
Sahar Ch.
7
Ini memberi saya kesalahan "Item dengan kunci yang sama telah ditambahkan." Namun ia menerima categoryid [0] = 1 & categoryid [1] = 2 & dll ...
Dokter Jones
19
Ini seharusnya jawaban yang diterima - @Hemanshu Bhojak: bukankah sudah waktunya untuk memilih?
David Rettenbacher
12
Alasan untuk ini adalah karena pernyataan berikut dari situs web ASP.NET Web API berbicara tentang pengikatan parameter: "Jika parameternya adalah tipe" sederhana ", API Web mencoba untuk mendapatkan nilai dari URI. Tipe sederhana termasuk. Jenis primitif NET (int, bool, dobel, dan sebagainya), ditambah TimeSpan, DateTime, Guid, desimal, dan string, ditambah semua tipe dengan konverter tipe yang dapat dikonversi dari sebuah string. " int [] bukan tipe sederhana.
Tr1stan
3
Ini bekerja dengan baik untuk saya. Satu poin. Pada kode server, parameter array harus didahulukan agar dapat berfungsi dan parameter lainnya, setelah. Saat memasukkan parameter dalam permintaan, pesanan tidak penting.
Dipicu
102

Seperti yang ditunjukkan Filip W , Anda mungkin harus menggunakan pengikat model khusus seperti ini (dimodifikasi untuk mengikat ke jenis param yang sebenarnya):

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

Dan kemudian Anda bisa mengatakan:

/Categories?categoryids=1,2,3,4dan ASP.NET Web API akan mengikat categoryIdsarray Anda dengan benar .

Tuan
sumber
10
Ini mungkin melanggar SRP dan / atau SoC, tetapi Anda dapat dengan mudah membuat ini juga diwarisi ModelBinderAttributesehingga dapat digunakan secara langsung alih-alih sintaks yang sulit menggunakan typeof()argumen. Yang harus Anda lakukan adalah mewarisi seperti: CommaDelimitedArrayModelBinder : ModelBinderAttribute, IModelBinderdan kemudian menyediakan konstruktor default yang mendorong definisi jenis ke kelas dasar: public CommaDelimitedArrayModelBinder() : base(typeof(CommaDelimitedArrayModelBinder)) { }.
sliderhouserules
Kalau tidak, saya sangat suka solusi ini dan saya menggunakannya dalam proyek saya, jadi ... terima kasih. :)
sliderhouserules
Aa catatan, solusi ini tidak bekerja dengan obat generik seperti System.Collections.Generic.List<long>yang bindingContext.ModelType.GetElementType()hanya mendukung System.Arrayjenis
ViRuSTriNiTy
@ViRuSTriNiTy: Pertanyaan ini dan jawabannya secara khusus berbicara tentang Array. Jika Anda memerlukan solusi berbasis daftar generik, itu cukup sepele untuk diterapkan. Jangan ragu untuk mengajukan pertanyaan terpisah jika Anda tidak yakin bagaimana cara melakukannya.
Mrchief
2
@codeMonkey: memasukkan array ke tubuh masuk akal untuk permintaan POST, tapi bagaimana dengan MENDAPATKAN permintaan? Ini biasanya tidak memiliki konten di dalam tubuh.
stakx - tidak lagi berkontribusi
40

Saya baru-baru ini menemukan persyaratan ini sendiri, dan saya memutuskan untuk menerapkan suatu ActionFilteruntuk menangani ini.

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

Saya menerapkannya seperti itu (perhatikan bahwa saya menggunakan 'id', bukan 'id', seperti yang ditentukan dalam rute saya):

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

Dan url publik adalah:

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

Anda mungkin harus memperbaiki ini untuk memenuhi kebutuhan spesifik Anda.

Steve Czetty
sumber
1
ketik int adalah hardcoded (int.Parse) dalam solusi Anda. Imho, solusi @ Mrchief lebih baik
razon
27

Jika seseorang perlu - untuk mencapai hal yang sama atau serupa (seperti delete) melalui POSTalih-alih FromUri, gunakan FromBodydan di sisi klien (JS / jQuery) format param sebagai$.param({ '': categoryids }, true)

c #:

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

jQuery:

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

Masalahnya $.param({ '': categoryids }, true)adalah bahwa .net akan mengharapkan post body berisi nilai urlencoded seperti =1&=2&=3tanpa nama parameter, dan tanpa tanda kurung.

Sofija
sumber
2
Tidak perlu menggunakan POST. Lihat jawaban @Lavel.
André Werlang
3
Ada batasan dalam berapa banyak data yang dapat Anda kirim dalam URI. Dan menurut standar, ini seharusnya bukan permintaan GET karena sebenarnya memodifikasi data.
Layak 7
1
Dan di mana tepatnya Anda melihat GET di sini? :)
Sofija
3
@Sofija OP mengatakan code to retrieve categories from database, jadi metode ini haruslah metode GET, bukan POST.
Azimuth
22

Cara mudah untuk mengirim parameter array ke api web

API

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

Jquery: mengirim objek JSON sebagai params permintaan

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

Ini akan menghasilkan URL permintaan Anda seperti ../api/categories/GetCategories?categoryIds=1&categoryIds=2&categoryIds=3&categoryIds=4

Jignesh Variya
sumber
3
bagaimana ini berbeda dari jawaban yang diterima? dengan pengecualian mengimplementasikan permintaan ajax via jquery yang tidak ada hubungannya dengan posting asli.
sksallaj
13

Anda dapat mencoba kode ini untuk mengambil nilai yang dipisahkan koma / array nilai untuk mendapatkan kembali JSON dari 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; }
 } 

Keluaran:

[
{"CategoryID":"4","CategoryName":"Category_4"}, 
{"CategoryID":"5","CategoryName":"Category_5"}, 
{"CategoryID":"3","CategoryName":"Category_3"} 
]
Naveen Vijay
sumber
12

Solusi ASP.NET Core 2.0 (Swagger Ready)

Memasukkan

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

Kode

Tulis penyedia (bagaimana MVC tahu binder apa yang digunakan)

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

Tulis binder yang sebenarnya (akses segala macam info tentang permintaan, tindakan, model, tipe, apa pun)

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

Daftarkan ke MVC

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

Penggunaan sampel dengan pengontrol yang terdokumentasi dengan baik untuk 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 merekomendasikan untuk menggunakan TypeConverter untuk anak-anak operasi ini melalui pendekatan ini. Jadi ikuti saran poster di bawah ini dan dokumentasikan jenis kustom Anda dengan SchemaFilter.

Victorio Berra
sumber
Saya pikir rekomendasi MS yang sedang Anda bicarakan puas dengan jawaban ini: stackoverflow.com/a/49563970/4367683
Machado
Apakah kamu melihat ini? github.com/aspnet/Mvc/pull/7967 sepertinya mereka menambahkan perbaikan untuk memulai parsing Daftar < wh Apapun> dalam string kueri tanpa perlu binder khusus. Juga pos yang Anda tautkan bukan ASPNET Core dan menurut saya tidak membantu situasi saya.
Victorio Berra
Jawaban terbaik, non-hacky.
Erik Philips
7

Alih-alih menggunakan ModelBinder khusus, Anda juga dapat menggunakan tipe khusus dengan 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);
    }
}

Keuntungannya adalah membuat parameter metode API Web sangat sederhana. Anda bahkan tidak perlu menentukan [FromUri].

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

Contoh ini untuk Daftar string, tetapi Anda bisa melakukan categoryIds.Select(int.Parse)atau hanya menulis IntList saja.

PhillipM
sumber
Tidak mengerti mengapa solusi ini tidak mendapat banyak suara. Ini bagus dan bersih dan bekerja dengan angkuh tanpa menambahkan pengikat kustom dan barang-barang.
Thieme
Jawaban terbaik / terbersih menurut saya. Terima kasih PhillipM!
Leigh Bowers
7

Saya awalnya menggunakan solusi yang @Mrchief selama bertahun-tahun (ini bekerja dengan baik). Tetapi ketika saya menambahkan Swagger ke proyek saya untuk dokumentasi API, titik akhir saya TIDAK muncul.

Butuh beberapa saat, tetapi inilah yang saya hasilkan. Ini bekerja dengan Swagger, dan tanda tangan metode API Anda terlihat lebih bersih:

Pada akhirnya yang bisa Anda lakukan:

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

    }
}

Buat kelas baru: 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() };
}

Buat kelas baru: StringToIntArrayConverter.cs

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

Catatan:

crabCRUSHERclamCOLLECTOR
sumber
1
Seandainya ada orang lain yang membutuhkan informasi tentang perpustakaan yang digunakan ini. Ini adalah penggunaan untuk "CommaDelimitedArrayParameterBinder". menggunakan System.Collections.Generic; menggunakan System.Linq; menggunakan System.Threading; menggunakan System.Threading.Tasks; menggunakan System.Web.Http.Controllers; menggunakan System.Web.Http.Metadata; menggunakan System.Web.Http.ModelBinding; menggunakan System.Web.Http.ValueProviders; menggunakan 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));
    }
}

Pemakaian:

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

Minta uri

http://localhost/api/v1/files/2a9937c7-8201-59b7-bc8d-11a9178895d0/tags/BBA5CD5D-F07D-47A9-8DEE-D19F5FA65F63,BBA5CD5D-F07D-47A9-8DEE-D19F5FA65F63
Waninlezu
sumber
@ Elsa Bisakah Anda tunjukkan bagian mana yang tidak Anda mengerti? Saya pikir kodenya cukup jelas untuk penjelasan itu sendiri. Sulit bagi saya untuk menjelaskan ini semua dalam bahasa Inggris, maaf.
Waninlezu
@Steve Czetty inilah versi saya yang direkonstruksi, terima kasih atas idenya
Waninlezu
Apakah akan berfungsi /sebagai pemisah? Maka Anda dapat memiliki: dns / root / mystuff / path / ke / some / resource dipetakan kepublic string GetMyStuff(params string[] pathBits)
RoboJ1M
5

Jika Anda ingin daftar / array bilangan bulat cara termudah untuk melakukan ini adalah menerima daftar string yang dipisahkan koma (,) dan mengubahnya menjadi daftar bilangan bulat. Jangan lupa untuk menyebutkan [FromUri] attriubte. Url Anda terlihat seperti:

...? 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)
        {
        }
    }
}
Vaibhav
sumber
mengapa Anda menggunakan List<string>bukan hanya string? itu hanya akan memiliki satu string di dalamnya yang ada 1,2,3,289,56dalam contoh Anda. Saya akan menyarankan hasil edit.
Daniël Tulp
Bekerja untukku. Saya terkejut controller saya tidak akan mengikat secara List<Guid>otomatis. Catatan di Asp.net Core anotasi itu [FromQuery], dan itu tidak diperlukan.
kitsu.eb
2
Untuk versi Linq satu baris: int [] accountIdArray = accountId.Split (','). Pilih (i => int.Parse (i)). ToArray (); Saya akan menghindari tangkapan karena itu akan menutupi seseorang yang mengirimkan data buruk.
Steve In CO
3

Buat tipe metode [HttpPost], buat model yang memiliki satu parameter int [], dan poskan dengan 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);
codeMonkey
sumber
Anda membungkus array Anda di kelas - itu akan berfungsi dengan baik (meskipun MVC / WebAPI). OP adalah tentang mengikat ke array tanpa kelas pembungkus.
Mrchief
1
Masalah aslinya tidak mengatakan apa-apa tentang melakukannya tanpa kelas pembungkus, hanya saja mereka ingin menggunakan params kueri untuk objek yang kompleks. Jika Anda melewati jalan itu terlalu jauh, Anda akan sampai pada titik di mana Anda memerlukan API untuk mengambil objek js yang benar-benar kompleks, dan kueri param akan mengecewakan Anda. Mungkin juga belajar melakukannya dengan cara yang akan bekerja setiap saat.
codeMonkey
public IEnumerable<Category> GetCategories(int[] categoryIds){- Ya, Anda bisa menafsirkan dengan cara yang berbeda saya kira. Tetapi sering kali, saya tidak ingin membuat kelas wrapper demi menciptakan pembungkus. Jika Anda memiliki objek yang kompleks, maka itu hanya akan berfungsi. Mendukung kasus-kasus sederhana ini adalah apa yang tidak berhasil, maka OP.
Mrchief
3
Melakukan hal POSTini sebenarnya bertentangan dengan paradigma REST. Dengan demikian, API semacam itu tidak akan menjadi REST API.
Azimuth
1
@ Azzimuth memberi saya paradigma di satu sisi, yang bekerja dengan .NET di sisi lain
codeMonkey
3

Atau Anda bisa meneruskan serangkaian item terbatas dan memasukkannya ke dalam array atau daftar di ujung penerima.

Sirentec
sumber
2

Saya membahas masalah ini dengan cara ini.

Saya menggunakan pesan pos ke api untuk mengirim daftar bilangan bulat sebagai data.

Lalu saya mengembalikan data sebagai ienumerable.

Kode pengiriman adalah sebagai berikut:

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

Kode penerima adalah sebagai berikut:

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

Ini berfungsi dengan baik untuk satu catatan atau banyak catatan. Isi adalah metode kelebihan beban menggunakan 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;
}

Ini memungkinkan Anda untuk mengambil data dari tabel komposit (daftar id), dan kemudian mengembalikan catatan yang benar-benar Anda minati dari tabel target.

Anda bisa melakukan hal yang sama dengan sebuah tampilan, tetapi ini memberi Anda sedikit lebih banyak kontrol dan fleksibilitas.

Selain itu, detail apa yang Anda cari dari database tidak ditampilkan dalam string kueri. Anda juga tidak perlu mengonversi dari file csv.

Anda harus ingat ketika menggunakan alat apa pun seperti antarmuka web api 2.x adalah bahwa fungsi get, put, post, delete, head, dll. Memiliki penggunaan umum, tetapi tidak terbatas pada penggunaan itu.

Jadi, sementara posting umumnya digunakan dalam konteks buat di antarmuka api web, itu tidak terbatas pada penggunaan itu. Ini adalah panggilan html biasa yang dapat digunakan untuk tujuan apa pun yang diizinkan oleh praktik html.

Selain itu, perincian tentang apa yang sedang terjadi tersembunyi dari "mata yang mengintip" yang kita dengar banyak tentang hari-hari ini.

Fleksibilitas dalam penamaan konvensi di web api 2.x antarmuka dan penggunaan panggilan web biasa berarti Anda mengirim panggilan ke web api yang menyesatkan pengintai untuk berpikir Anda benar-benar melakukan sesuatu yang lain. Anda dapat menggunakan "POST" untuk benar-benar mengambil data, misalnya.

Timothy Dooling
sumber
2

Saya telah membuat pengikat model khusus yang mengubah nilai yang dipisahkan koma (hanya primitif, desimal, float, string) ke array yang sesuai.

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

Dan cara menggunakan di Controller:

 public IHttpActionResult Get([ModelBinder(BinderType = typeof(CommaSeparatedToArrayBinder<int>))] int[] ids)
        {
            return Ok(ids);
        }
Sulabh Singla
sumber
Terima kasih, saya telah memindahkannya ke netcore 3.1 dengan sedikit usaha dan berhasil! Jawaban yang diterima tidak menyelesaikan masalah dengan kebutuhan untuk menentukan nama param berkali-kali dan sama dengan operasi default di netcore 3.1
Bogdan Mart
0

Solusi saya adalah membuat atribut untuk memvalidasi string, ia melakukan banyak fitur umum tambahan, termasuk validasi regex yang dapat Anda gunakan untuk memeriksa angka saja dan kemudian saya mengonversi ke bilangan bulat sesuai kebutuhan ...

Ini adalah bagaimana Anda menggunakan:

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;
    }
}
Alan Cardoso
sumber