Search code examples
c#asp.net-web-apimodel-binding

Web Api Parameter binding: snake_case to camelCase


I'm doing some work on a new web api project, and my methods/ parameters follow the standard c# naming format; the method name is PascalCase, the parameters are camelCase.

Unfortunately I just discovered that documentation to a client has been published with all the parameters being ruby/ php style, with snake_case type parameter names.

The return objects and POST object were easy to convert; I used a version of crallen's code to replace the default Json input/ output, but on GET requests, there doesn't seem to be such an easy answer.

I'd prefer to keep my naming conventions the same. Is there a way to tell the binder to automatically change my_parameter into myParameter for all requests? Do I have to build a completely different binder?

For example, if I have this as a method signature:

[Route("~/api/Widgets")]
[ResponseType(typeof(Widget))]
public async Task<HttpResponseMessage> GetWidget(int widgetId, int groupId)
{
    . . .

I would like to be able to use this in the URL

https://myserver.com/api/Widgets?widget_id=12345&group_id=54321

Do I have to reinvent the wheel to get this to work? I've seen examples of replacing specific type model binders, but nothing at this level. Am I better off just changing my parameter names in code?


Solution

  • You can achieve this by using a custom ApiControllerActionSelector that rewrites Request.RequestUri and then calls the base selector.

    Here it goes:

    First, create the custom selector:

    public class SnakeCaseActionSelector : ApiControllerActionSelector
    {
        public override HttpActionDescriptor SelectAction(HttpControllerContext controllerContext)
        {
            var newUri = CreateNewUri(
                controllerContext.Request.RequestUri, 
                controllerContext.Request.GetQueryNameValuePairs());
    
            controllerContext.Request.RequestUri = newUri;
    
            return base.SelectAction(controllerContext);
        }
    
        private Uri CreateNewUri(Uri requestUri, IEnumerable<KeyValuePair<string, string>> queryPairs)
        {
            var currentQuery = requestUri.Query;
            var newQuery = ConvertQueryToCamelCase(queryPairs);
            return new Uri(requestUri.ToString().Replace(currentQuery, newQuery));
        }
    
        private static string ConvertQueryToCamelCase(IEnumerable<KeyValuePair<string, string>> queryPairs)
        {
            queryPairs = queryPairs
                .Select(x => new KeyValuePair<string, string>(x.Key.ToCamelCase(), x.Value));
    
            return "?" + queryPairs
                .Select(x => String.Format("{0}={1}", x.Key, x.Value))
                .Aggregate((x, y) => x + "&" + y);
        }
    }
    

    Next, create some extensions to convert to camel case and to convert to capitalized words:

    public static class StringExtensions
    {
        public static string ToCamelCase(this string source)
        {
            var parts = source
                .Split(new[] { '_' }, StringSplitOptions.RemoveEmptyEntries);
    
            return parts
                .First().ToLower() +
                String.Join("", parts.Skip(1).Select(ToCapital));
        }
    
        public static string ToCapital(this string source)
        {
            return String.Format("{0}{1}", char.ToUpper(source[0]), source.Substring(1).ToLower());
        }
    }
    

    And finally add the action selector to the WebApiConfig:

    config.Services.Replace(typeof(IHttpActionSelector), new SnakeCaseActionSelector());