Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How should a C# Web API model binder provider work?

I have the following:

  • request url: 'endpoint/1,2,3?q=foo'
  • action to which the request is bound: public object Bar([ModelBinder] List< T > ids, [FromUri] string q)

I want to map the "1,2,3" fragment to the "ids" parameter, so I created a ModelBinderProvider according to this link, which should call the proper model binder.

public class MyModelBinderProvider: ModelBinderProvider
{
    public override IModelBinder GetBinder(HttpConfiguration configuration, Type modelType)
    {
        IModelBinder modelBinder = null;

        if (modelType.IsGenericType && (modelType.GetGenericTypeDefinition() == typeof(List<>)))
        {
            modelBinder = new ListModelBinder();   
        }

        return modelBinder;
    }
}

I registered the provider in Global.asax like this:

GlobalConfiguration.Configuration.Services.Insert(typeof(ModelBinderProvider), 0, new MyModelBinderProvider());

The reason: I created this provider because I want, no matter what T is ('1,2,3' or 'one,two,three'), the bind to work.

The problem: Let' say T is 'int'; everytime a request is sent, the 'modelType' paramater is always 'int' and not what I expect - 'List< int >', so the request is not properly handled.

The weird thing: Doing something like this works but T is specialized and therefor not what I want:

var simpleProvider = new SimpleModelBinderProvider(typeof(List<int>), new ListModelBinder());
GlobalConfiguration.Configuration.Services.Insert(typeof(ModelBinderProvider), 0, simpleProvider);

I cannot see what I'm doing wrong, why is the 'modelType' parameter not the expected value?

like image 737
Dan Avatar asked Jun 28 '14 19:06

Dan


1 Answers

It is a very old question but I had an similar issue here with a legacy code.

Commas are reserved and it should be avoided although they work in some cases but if you really want to use them...

I think that is more a route issue than a model binder once the "1,2,3" is path part of the url. Assuming this I wrote a small RouteHandler that does the trick (please forgive the very simple "word to integer" translator).

The CsvRouteHandler gets the id array from URL and put it on RouteData as an array of integers. If the original array has words such as one, two or three it translates each value to int.

MvcRouteHandler

protected override IHttpHandler GetHttpHandler(System.Web.Routing.RequestContext requestContext)
{
    var idArrayParameter = requestContext.RouteData.Values["idArray"] != null ? requestContext.RouteData.Values["idArray"].ToString() : null;
    if (string.IsNullOrEmpty(idArrayParameter))
    {
        return base.GetHttpHandler(requestContext);
    }

    requestContext.RouteData.Values.Remove("idArray"); // remove the old array from routedata

    // Note: it is horrible and bugged but and you probably have your own translation method :)
    string[] idArray = idArrayParameter.Split(',');
    int[] ids = new int[idArray.Length];

    for(int i = 0; i < idArray.Length; i++)
    {
        if (!int.TryParse(idArray[i], out ids[i]))
        {
            switch (idArray[i])
            {
                case "one":
                    ids[i] = 1;
                    break;
                case "two":
                    ids[i] = 2;
                    break;
                case "three":
                    ids[i] = 3;
                    break;
            }
        }
    }

    requestContext.RouteData.Values.Add("Id", ids);

    return base.GetHttpHandler(requestContext);

}
}

Route configuration:

    routes.Add(
        name: "Id Array Route",
        item: new Route(
            url: "endpoint/{idArray}",
            defaults: new RouteValueDictionary(new { controller = "Test", action = "Index" }),
            routeHandler: new CsvRouteHandler())
    );
like image 171
Douglas Gandini Avatar answered Oct 01 '22 06:10

Douglas Gandini