Webapi Routing with Integer array and Actions

2019-09-18 19:13发布

问题:

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.

回答1:

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.