0

This issue is driving me nuts.

I have a get method that takes ID array with custom ModelBinder (I can call http://xckvjl.com/api/results/1,23,34,)

I want to introduce Gets on actions. (so that I can call as http://alskjdfasl.com/api/results/latest)

I have the following web api routing.

config.Routes.MapHttpRoute("DefaultApi", "{controller}/{id}", new { id = RouteParameter.Optional });

config.Routes.MapHttpRoute("ApiWithAction", "{controller}/{action}");

I have tried with (Please note here that I am using my custom model binder)

config.Routes.MapHttpRoute("DefaultApi", "{controller}/{id}", new { id = RouteParameter.Optional }, new {id = @"\d+" });

You can reproduce this error with this sample:

public class TestController: ApiController {

          [HttpGet]
           public virtual IHttpActionResult Get([ModelBinder(typeof(CommaDelimitedCollectionModelBinder))]IEnumerable<int> id = null )
        { }

          [HttpGet]
           public virtual IHttpActionResult Latest( )
        { }

}

 public class CommaDelimitedCollectionModelBinder : IModelBinder
    {
        public bool BindModel(HttpActionContext actionContext,
            ModelBindingContext bindingContext)
        {
            var key = bindingContext.ModelName;
            var val = bindingContext.ValueProvider.GetValue(key);

            if (val == null)
            {
                return false;
            }

            var s = val.AttemptedValue;
            if (s != null && s.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Length > 0)
            {
                var array = s.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select( n=>Convert.ToInt32(n)).ToArray();
                Type type = bindingContext.ModelType.GetGenericArguments().First();

                var typeValue = Array.CreateInstance(type, array.Length);
                array.CopyTo(typeValue, 0);

                bindingContext.Model = array;
            }
            else
            {
                bindingContext.Model = new[] { s };
            }

            return true;
        }
    }

If I write as:

[HttpGet]
[Route("Tests/latest")]
 public virtual IHttpActionResult Latest( )
        { }

It works. However, I want global level routing. Otherwise for every action I will have to write the same.

Please advise.

codebased
  • 6,945
  • 9
  • 50
  • 84

1 Answers1

0

So here's the dilimma:

With this route definition:

config.Routes.MapHttpRoute("DefaultApi", "{controller}/{id}", new { id = RouteParameter.Optional });

when you call http://alskjdfasl.com/api/results/latest, latest becomes the value of id, which obviously will throw error since string cannot be converted to int array.

Adding this route definition

config.Routes.MapHttpRoute("ApiWithAction", "{controller}/{action}"); 

won't help since now you either have to deal with order or define routes for your other actions which are now being masked due to this.

Without declaring routes at individual level, the only other options are to make the templates different or create your own route constraint that understands arrays.

I would give these a shot first:

config.Routes.MapHttpRoute("ApiWithAction", "{controller}/{action}/{id}", new { id = RouteParameter.Optional });

// or if all your methods are called latest

config.Routes.MapHttpRoute("ApiWithAction", "{controller}/latest");

If nothing works, I would say go with attribute routes. They are cleaner.

Mrchief
  • 75,126
  • 20
  • 142
  • 189
  • If I flip like you have suggested also it will throw me multiple route exception. local/api/users/1 here it is getting confused if 1 is action name... so can I make the constraint for ID array? How? – codebased Sep 26 '14 at 05:09
  • I'm trying to understand further - what is the purpose of `ApiWithAction` route? Is it something you're trying while troubleshooting? ALso, can you try with `int[]` instead of IEnumerable? – Mrchief Sep 26 '14 at 05:10
  • The solution I am looking for is to expose actions such as Latest(..) in the above controller. Since there are more than one GET I will have to tell to the route that when I call controller/action then call action. ApiWitAction is the route for that. If I don't any route and call Latest straight it is still saying as "Multiple actions were found that match the request: " I can understand why it is happening but I don't how to solve. – codebased Sep 26 '14 at 05:28