ASP.NET Web API'ye bir tamsayı dizisi iletilsin mi?


427

Tamsayılar bir dizi geçmek gereken bir ASP.NET Web API (sürüm 4) REST hizmeti var.

İşte benim eylem yöntemi:

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

Ve bu denedim URL:

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

1
"/ Kategoriler? Categoryids = 1 & categoryids = 2 & categoryids = 3" gibi bir sorgu dizesi kullanırken "İsteğin içeriğine birden çok parametre bağlanamıyor" hatası alıyordum. Umarım bu da aynı hatayı alan insanları buraya getirir.
Josh Noe

1
@Josh Yine de [FromUri] kullandınız mı? public IEnumerable <Category> GetCategories ([FromUri] int [] categoryids) {...}
Anup Kattel

2
@FrankGorman Hayır, değildim, bu benim sorunumdu.
Josh Noe

Yanıtlar:


619

[FromUri]Parametreden önce eklemeniz yeterlidir , şuna benzer:

GetCategories([FromUri] int[] categoryIds)

Ve istek gönderin:

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

18
Dizide ne kadar değişkenim olduğunu bilmiyorsam? Ya 1000 gibi olursa? İstek böyle olmamalı.
Sahar Ch.

7
Bu bana "Aynı anahtara sahip bir öğe zaten eklenmiş" hatası veriyor. Ancak categoryids [0] = 1 & categoryids [1] = 2 & etc ... kabul eder
Doctor Jones

19
Kabul edilen cevap bu olmalı - @Hemanshu Bhojak: Seçiminizi yapmanın zamanı gelmedi mi?
David Rettenbacher

12
Bunun nedeni ASP.NET Web API web sitesinden parametre bağlamasıyla ilgili aşağıdaki ifadeden kaynaklanmaktadır : "Parametre" basit "bir türse, Web API değeri URI'den almaya çalışır. NET ilkel türleri (int, bool, double, vb.), Artı TimeSpan, DateTime, Guid, ondalık ve dize, artı dize dönüştürebilen bir tür dönüştürücü ile herhangi bir tür. " int [] basit bir tür değildir.
Tr1stan

3
Bu benim için iyi çalışıyor. Bir nokta. Sunucu kodunda, dizi parametresinin çalışması ve daha sonra diğer parametreler için önce gelmesi gerekir. İstekteki parametrelerde beslenirken, sipariş önemsizdir.
Kıvılcım

102

Filip W'nin işaret ettiği gibi , böyle bir özel model bağlayıcıya başvurmanız gerekebilir (gerçek param türüne bağlanmak için değiştirildi):

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

Ve sonra diyebilirsiniz:

/Categories?categoryids=1,2,3,4ve ASP.NET Web API categoryIdsdizinizi doğru şekilde bağlar .


10
Bu, SRP ve / veya SoC'yi ihlal edebilir, ancak argümanı ModelBinderAttributekullanarak zahmetli sözdizimi yerine doğrudan kullanılabilmesi için bunu kolayca miras alabilirsiniz typeof(). Tek yapmanız gereken şöyle devralır: CommaDelimitedArrayModelBinder : ModelBinderAttribute, IModelBinderve daha sonra temel sınıf için aşağı türü tanımı iter varsayılan bir kurucu sağlamaktayız: public CommaDelimitedArrayModelBinder() : base(typeof(CommaDelimitedArrayModelBinder)) { }.
49

Aksi takdirde, bu çözümü gerçekten seviyorum ve projemde kullanıyorum, yani ... teşekkürler. :)
sliderhouserules

Aa bir yan not, bu çözüm gibi jenerik çalışmaz System.Collections.Generic.List<long>olarak bindingContext.ModelType.GetElementType()tek destek System.Arraytürleri
ViRuSTriNiTy

@ViRuSTriNiTy: Bu soru ve cevabı özellikle Diziler hakkında konuşuyor. Genel bir liste tabanlı çözüme ihtiyacınız varsa, bu uygulanması oldukça önemsizdir. Bu konuda nasıl ilerleyeceğinizden emin değilseniz ayrı bir soru sormaktan çekinmeyin.
Mrchief

2
@codeMonkey: diziyi gövdeye koymak POST isteği için mantıklıdır, ancak GET isteklerine ne dersiniz? Bunlar genellikle vücutta hiçbir içeriğe sahip değildir.
stakx - artık

40

Kısa süre önce bu gereksinime kendim geldim ve bunu gerçekleştirmek için bir uygulamaya karar verdim 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; }
}

Bu şekilde uyguluyorum (rotamda belirtildiği gibi 'id' değil, 'id' kullandığımı unutmayın):

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

Ve genel URL:

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

Özel gereksinimlerinizi karşılamak için bunu yeniden düzenlemeniz gerekebilir.


1
int türü çözümünüzde sabit kodlanmıştır (int.Parse). Imho, @ Mrchief'in çözümü daha iyi
razon

27

Yoluyla (silme gibi) aynı veya benzer şeyi elde etmek için - durumda birileri gerekir POSTyerine FromUrikullanılması, FromBodyve istemci tarafında (JS / jQuery) biçiminde param olarak$.param({ '': categoryids }, true)

c #:

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

jQuery:

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

Şey, $.param({ '': categoryids }, true).net'in post gövdesinin =1&=2&=3parametre adı olmadan ve parantezler olmadan urlen kodlanmış değer içermesini beklemesi .


2
POST başvurmak gerek. Bkz. @Lavel yanıtı.
André Werlang

3
Bir URI'de ne kadar veri gönderebileceğiniz konusunda bir sınırlama vardır. Ve standart olarak, bu verileri gerçekten değiştirdiği için bir GET isteği olmamalıdır.
Worthy7

1
Ve burada tam olarak nerede bir GET gördün? :)
Sofija

3
@Sofija OP code to retrieve categories from database, bu nedenle yöntemin POST değil, bir GET yöntemi olması gerektiğini söylüyor .
Azimuth

22

Web API'sine dizi parametreleri göndermenin kolay yolu

API

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

Jquery: JSON nesnesini istek parametreleri olarak gönder

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

Gibi istek URL'nizi oluşturur ../api/categories/GetCategories?categoryIds=1&categoryIds=2&categoryIds=3&categoryIds=4


3
bu kabul edilen cevaptan nasıl farklı? orijinal yazı ile hiçbir ilgisi olmayan jquery yoluyla bir ajax isteği uygulama hariç.
sksallaj

13

WebAPI'den bir JSON'u geri almak için virgülle ayrılmış değerler / değerler dizisi almanız için bu kodu deneyebilirsiniz

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

Çıktı :

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

12

ASP.NET Core 2.0 Çözümü (Swagger Hazır)

Giriş

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

kod

Sağlayıcıyı yazın (MVC hangi bağlayıcıyı kullanacağını nasıl bilir)

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

Gerçek bağlayıcıyı yazın (istek, eylem, modeller, türler, ne olursa olsun) her türlü bilgiye erişin

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 ile kaydedin

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

Swagger için iyi belgelenmiş bir kontrolör ile örnek kullanım

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

DÜZENLEME: Microsoft bu yaklaşımlar üzerinde bu işlem çocukları için bir TypeConverter kullanmanızı önerir . Bu nedenle, aşağıdaki poster önerilerini izleyin ve özel türünüzü bir SchemaFilter ile belgeleyin.


Sanırım bahsettiğiniz MS tavsiyesi bu cevaptan memnun: stackoverflow.com/a/49563970/4367683
Machado

Bunu gördün mü? github.com/aspnet/Mvc/pull/7967 , özel bir bağlayıcıya gerek olmadan sorgu dizesindeki <whatever> Listesini ayrıştırmaya başlamak için bir düzeltme eklediler. Ayrıca bağladığınız yazı ASPNET Core değil ve durumumda yardımcı olduğunu düşünmüyorum.
Victorio Berra

En iyi, haksız cevap.
Erik Philips

7

Özel bir ModelBinder kullanmak yerine, TypeConverter ile özel bir tür de kullanabilirsiniz.

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

Avantajı, Web API yönteminin parametrelerini çok basit hale getirmesidir. [FromUri] belirtmeniz bile gerekmez.

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

Bu örnek bir dize listesi içindir, ancak categoryIds.Select(int.Parse)bunun yerine bir IntList yapabilir veya yazabilirsiniz.


Bu çözümün neden fazla oy almadığını anlamıyorum. Güzel ve temiz ve özel bağlayıcılar ve şeyler eklemeden swagger ile çalışır.
Thieme

Bence en iyi / en temiz cevap. Teşekkürler PhillipM!
Leigh Bowers

7

Başlangıçta yıllardır @Mrchief çözümünü kullandım (harika çalışıyor). Ancak Swagger'ı API dokümantasyonu için projeme eklediğimde , son nokta DEĞİLDİR gösterilmesini.

Bu beni biraz zaman aldı, ama ben de bunu buldum. Swagger ile çalışır ve API yöntemi imzalarınız daha temiz görünür:

Sonunda şunları yapabilirsiniz:

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

    }
}

Yeni bir sınıf oluşturun: 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() };
}

Yeni bir sınıf oluşturun: StringToIntArrayConverter.cs

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

Notlar:


1
Bu durumda başkalarının kullandığı kütüphaneler hakkında bilgi alması gerekir. İşte "CommaDelimitedArrayParameterBinder" kullanımı. System.Collections.Generic kullanarak; System.Linq kullanarak; System.Threading kullanarak; System.Threading.Tasks kullanarak; System.Web.Http.Controllers kullanarak; System.Web.Http.Metadata kullanarak; System.Web.Http.ModelBinding kullanarak; System.Web.Http.ValueProviders kullanarak; System.Web.Http.ValueProviders.Providers kullanarak;
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));
    }
}

Kullanımı:

    [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 iste

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

@Elsa Lütfen hangi parçayı anlayamadığına dikkat çeker misin? Kod kendini açıklamak için oldukça açık olduğunu düşünüyorum. Bütün bunları İngilizce olarak açıklamak zor, üzgünüm.
Waninlezu

@Steve Czetty İşte benim yeniden yapılandırılmış versiyonum, fikriniz için teşekkürler
Waninlezu

Ayırıcı olarak çalışacak /mı? O zaman sahip olabilirsiniz: dns / root / mystuff / path / to / some /public string GetMyStuff(params string[] pathBits)
source ile

5

Eğer tamsayıları / dizileri listelemek istiyorsanız bunu yapmanın en kolay yolu dizenin virgülle (,) ayrılmış listesini kabul etmek ve tamsayılar listesine dönüştürmektir. [FromUri] attriubte.your url'nizden bahsetmeyi unutmayın:

...? 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)
        {
        }
    }
}

neden List<string>sadece yerine kullanıyorsun string? 1,2,3,289,56içinde örneğin yalnızca bir dize olacaktır . Bir düzenleme önereceğim.
Daniël Tulp

Benim için çalıştı. Denetleyicimin List<Guid>otomatik olarak bağlanmayacağına şaşırdım . Asp.net Core'da not ekidir [FromQuery]ve gerekli değildir.
kitsu.eb

2
Tek satırlık bir Linq sürümü için: int [] accountIdArray = accountId.Split (','). (İ => int.Parse (i)). ToArray (); Yakalamaktan kaçınırdım çünkü kötü verilerden geçen birini maskeleyecek.
Steve In CO

3

[HttpPost] yöntem türünü yapın, bir int [] parametresine sahip bir model oluşturun ve json ile yayınlayın:

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

Dizinizi bir sınıfa sarıyorsunuz - bu işe yarayacak (MVC / WebAPI rağmen). OP bir sarmalayıcı sınıfı olmadan diziye bağlanmak üzereydi.
Mrchief

1
Orijinal sorun, bir sarmalayıcı sınıfı olmadan bunu yapmakla ilgili bir şey söylemez, sadece karmaşık nesneler için sorgu parametreleri kullanmak isterler. Bu yolda çok ileri giderseniz, gerçekten karmaşık bir js nesnesini almak için API'ye ihtiyacınız olan bir noktaya ulaşacaksınız ve sorgu parametreleri sizi başarısız edecektir. Her seferinde çalışacak şekilde yapmayı da öğrenebilir.
codeMonkey

public IEnumerable<Category> GetCategories(int[] categoryIds){- evet sanırım farklı şekillerde yorumlayabilirsiniz. Ama birçok kez, sarmalayıcılar yaratmak için sarmalayıcı sınıfları oluşturmak istemiyorum. Karmaşık nesneleriniz varsa, bu işe yarayacaktır. Bu daha basit vakaları desteklemek, kutudan çıkmayan, dolayısıyla OP.
Mrchief

3
Bunu yapmak POSTaslında REST paradigmasına aykırıdır. Dolayısıyla böyle bir API bir REST API'sı olmaz.
Azimuth

1
@Azimuth bir yandan bana bir paradigma verdi, diğer yandan .NET ile çalışan
codeMonkey

3

Veya bir dizi sınırlandırılmış öğeyi geçirebilir ve alıcı uçtaki bir diziye veya listeye koyabilirsiniz.


2

Bu sorunu bu şekilde ele aldım.

Tamsayıların listesini veri olarak göndermek için api'ye bir mesaj gönderdim.

Sonra verileri geri dönüşümsüz olarak geri verdim.

Gönderme kodu aşağıdaki gibidir:

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

Alıcı kodu aşağıdaki gibidir:

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

Bir kayıt veya birçok kayıt için iyi çalışır. Dolgu, DapperExtensions kullanan aşırı yüklenmiş bir yöntemdir:

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

Bu, bileşik bir tablodan (kimlik listesi) veri almanıza ve ardından gerçekten ilgilendiğiniz kayıtları hedef tablodan döndürmenize olanak tanır.

Aynı şeyi bir görünümle de yapabilirsiniz, ancak bu size biraz daha fazla kontrol ve esneklik sağlar.

Ayrıca, veritabanından ne aradığınızı ayrıntıları sorgu dizesinde gösterilmez. Ayrıca bir csv dosyasından dönüştürmek zorunda değilsiniz.

Web api 2.x arayüzü gibi herhangi bir aracı kullanırken, get, put, post, delete, head, vb. Fonksiyonların genel bir kullanımı olduğu, ancak bu kullanımla sınırlı olmadığı akılda tutulmalıdır.

Bu nedenle, yazı genellikle web api arayüzünde bir oluşturma bağlamında kullanılırken, bu kullanımla sınırlı değildir. Bu bir olan normal herhangi amacı html uygulama tarafından izin için kullanılabilecek html çağrı.

Buna ek olarak, olup bitenlerin ayrıntıları bugünlerde çok duyduğumuz “meraklı gözlerden” gizlidir.

Web api 2.x arayüzündeki kuralları adlandırma ve düzenli web çağrısı kullanma esnekliği, web api'ye, başka bir şey yaptığınızı düşünmek için snoopers'ı yanlış yönlendiren bir çağrı gönderdiğiniz anlamına gelir. Örneğin, verileri gerçekten almak için "POST" kullanabilirsiniz.


2

Virgülle ayrılmış değerleri (yalnızca ilkel, ondalık, kayan nokta, kayan dize) karşılık gelen dizilere dönüştüren özel bir model bağlayıcı oluşturduk.

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

Ve Denetleyici'de nasıl kullanılır:

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

Teşekkürler, az çaba ile netcore 3.1'e taşıdım ve işe yarıyor! Kabul edilen cevap, param adını birçok kez belirtme ihtiyacı ile sorunu çözmez ve netcore 3.1'deki varsayılan işlem ile aynıdır
Bogdan Mart

0

Benim çözüm dizeleri doğrulamak için bir öznitelik oluşturmak için, sadece sayıları kontrol etmek için kullanabileceğiniz regex doğrulama da dahil olmak üzere ekstra ortak özellikler bir sürü yapar ve daha sonra gerektiği gibi tamsayılara dönüştürmek ...

Şu şekilde kullanırsınız:

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;
    }
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.