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?
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());
I'd like to propose an alternative solution using a custom value provider factory:
QueryStringValueProviderFactory
and override GetValueProvider
NameValuePairsValueProvider
taking in the newly modified values.QueryStringValueProviderFactory
in HttpConfiguration.Services
with your own.Roughly translates into:
public class SnakeQueryStringValueProviderFactory : QueryStringValueProviderFactory
{
public override IValueProvider GetValueProvider( HttpActionContext actionContext )
{
var pairs = actionContext.ControllerContext.Request.GetQueryNameValuePairs();
// modify query string keys accordingly
// e.g. remove '_' from query string keys
// var newPairs = ...
return new NameValuePairsValueProvider( newPairs, CultureInfo.InvariantCulture );
}
}
The following should be improved but you get the idea.
config.Services.Remove(
typeof( ValueProviderFactory ),
config.Services.GetValueProviderFactories().First( f => f is QueryStringValueProviderFactory )
);
config.Services.Insert( typeof( ValueProviderFactory ), 0, new SnakeQueryStringValueProviderFactory() ); // should be first
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With