Bind /action/1,2,3 to List<int>
In my API, I'd like to have routes like GET /api/v1/widgets/1,2,3
and GET /api/v1/widgets/best-widget,major-widget,bob-the-widget
public class WidgetsController : MyApiController
{
public ActionResult Show(IEnumerable<int> ids)
{
}
public ActionResult Show(IEnumerable<string> names)
{
}
}
I've got routes set up to get me to the action, but I can't figure out how to turn 1,2,3
into new List<int>(){1, 2, 3}
and so on. Of course, I could just take a string
and parse it in my action, but I'd like to avoid going that route.
One thing that came to mind was to put something in the OnActionExecuting
method, but then I wasn't sure exactly what to put in there (I could hack something together, obviously, but I'm trying to write something reusable.)
The main questions I have are how to know whether I need to do anything at all (sometimes the ValueProviders upstream will have figured everything out), and how to handle figuring out the type to cast to (e.g., how do I know that in this case I 开发者_StackOverflow社区need to go to a collection of ints, or a collection of strings, and then how do I do that?)
By the way, I had the idea of implementing a ValueProvider
as well, but got lost on similar questions.
I can't figure out how to turn 1,2,3 into new List(){1, 2, 3} and so on.
To avoid polluting each controller action that needs to receive this parameter I would recommend a custom model binder:
public class IdsModelBinder : DefaultModelBinder
{
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var result = base.BindModel(controllerContext, bindingContext);
var ids = bindingContext.ValueProvider.GetValue("ids");
if (ids != null)
{
return ids.AttemptedValue
.Split(',')
.Select(id => int.Parse(id))
.ToList();
}
return result;
}
}
and then register the model binder in Application_Start
:
ModelBinders.Binders.Add(typeof(IEnumerable<int>), new IdsModelBinder());
and finally your controller action might look like this (and from what I can see in your question it already does look like this :-)):
public ActionResult Show(IEnumerable<int> ids)
{
...
}
and the custom model binder will take care for parsing the ids
route token to the corresponding IEnumerable<int>
value.
You could do the same with the IEnumerable<string>
where you would simply remove the .Select
clause in the corresponding model binder.
if your URL was
/api/v1/widgets/Show?names=best-widget&names=major-widget&names=bob-the-widget
This would bind neatly by itself :)
No need to override modelbinders in this case.
The querystring-variable names
will bind to your Show-method_
public ActionResult Show(IEnumerable<string> names)
Hope this helps!
I'm relatively new to ASP.Net MVC and so I'm not sure if there is an easier way of doing this or not, however my approach would be to do something like the following:
public class WidgetsController : MyApiController
{
public ActionResult Show(string ids)
{
List<int> parsedIds = new List<int>();
foreach (var id in ids.Split(','))
{
parsedIds.Add(int.Parse(id));
}
return Show(parsedIds);
}
private ActionResult Show(List<int> ids);
}
You might also want to add some more sophisticated error handling for cases where the IDs entered can't be parsed, but thats the general approach I would use.
精彩评论