Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to send an array via a URI using Attribute Routing in Web API?

I'm following the article on Attribute Routing in Web API 2 to try to send an array via URI:

[HttpPost("api/set/copy/{ids}")]
public HttpResponseMessage CopySet([FromUri]int[] ids)

This was working when using convention-based routing:

http://localhost:24144/api/set/copy/?ids=1&ids=2&ids=3

But with attribute routing it is no longer working - I get 404 not found.

If I try this:

http://localhost:24144/api/set/copy/1

Then it works - I get an array with one element.

How do I use attribute routing in this manner?

like image 648
SB2055 Avatar asked Jul 22 '13 22:07

SB2055


1 Answers

The behavior you are noticing is more related to Action selection & Model binding rather than Attribute Routing.

If you are expecting 'ids' to come from query string, then modify your route template like below(because the way you have defined it makes 'ids' mandatory in the uri path):

[HttpPost("api/set/copy")]

Looking at your second question, are you looking to accept a list of ids within the uri itself, like api/set/copy/[1,2,3]? if yes, I do not think web api has in-built support for this kind of model binding.

You could implement a custom parameter binding like below to achieve it though(I am guessing there are other better ways to achieve this like via modelbinders and value providers, but i am not much aware of them...so you could probably need to explore those options too):

[HttpPost("api/set/copy/{ids}")]
public HttpResponseMessage CopySet([CustomParamBinding]int[] ids)

Example:

[AttributeUsage(AttributeTargets.Parameter, Inherited = false, AllowMultiple = false)]
public class CustomParamBindingAttribute : ParameterBindingAttribute
{
    public override HttpParameterBinding GetBinding(HttpParameterDescriptor paramDesc)
    {
        return new CustomParamBinding(paramDesc);
    }
}

public class CustomParamBinding : HttpParameterBinding
{
    public CustomParamBinding(HttpParameterDescriptor paramDesc) : base(paramDesc) { }

    public override bool WillReadBody
    {
        get
        {
            return false;
        }
    }

    public override Task ExecuteBindingAsync(ModelMetadataProvider metadataProvider, HttpActionContext actionContext, 
                                                    CancellationToken cancellationToken)
    {
        //TODO: VALIDATION & ERROR CHECKS
        string idsAsString = actionContext.Request.GetRouteData().Values["ids"].ToString();

        idsAsString = idsAsString.Trim('[', ']');

        IEnumerable<string> ids = idsAsString.Split(',');
        ids = ids.Where(str => !string.IsNullOrEmpty(str));

        IEnumerable<int> idList = ids.Select(strId =>
            {
                if (string.IsNullOrEmpty(strId)) return -1;

                return Convert.ToInt32(strId);

            }).ToArray();

        SetValue(actionContext, idList);

        TaskCompletionSource<object> tcs = new TaskCompletionSource<object>();
        tcs.SetResult(null);
        return tcs.Task;
    }
}
like image 185
Kiran Avatar answered Sep 30 '22 13:09

Kiran