I was converting a C# webapi project to F# using the F# ASP.NET templates. Everything is working great except optional query parameters. I keep getting this error
{
"message": "The request is invalid.",
"messageDetail": "The parameters dictionary contains an invalid entry for parameter 'start' for method 'System.Threading.Tasks.Task`1[System.Net.Http.HttpResponseMessage] GetVendorFiles(Int32, System.Nullable`1[System.DateTime])' in 'Thor.WebApi.VendorFilesController'. The dictionary contains a value of type 'System.Reflection.Missing', but the parameter requires a value of type 'System.Nullable`1[System.DateTime]'."
}
F# function signature:
[<HttpGet; Route("")>]
member x.GetVendorFiles( [<Optional; DefaultParameterValue(100)>] count, [<Optional; DefaultParameterValue(null)>] start : Nullable<DateTime> ) =
C# function signature:
[HttpGet]
[Route("")]
public async Task<HttpResponseMessage> GetVendorFiles(int count = 100,DateTime? start = null)
Does anyone know of any workarounds?
Updated:
I figured out the cause of this issue. ASP.NET extracts default values for controller actions using ParameterInfo. Apparently the F# compiler doesn't compile default values the same way as C# does (even with the DefaultParameterValueAttribute
)
What's the best way or working around this? Would it be some filter that I need to inject or implement my own ParameterBinding
?
As query parameters are not a fixed part of a path, they can be optional and can have default values.
@QueryParam is a JAX-RS framework annotation and @RequestParam is from Spring. QueryParam is from another framework and you are mentioning Spring. @Flao wrote that @RequestParam is from Spring and that should be used in Spring MVC.
Create new map and put there parameters you need: Map<String, String> params = new HashMap<>(); params. put("param1", "value1"); params. put("param2", "value2");
You can work around this with a custom ActionFilterAttribute
implementation. The following code supports the use of DefaultParameterValue
values when an action argument is missing and when an action argument has a value of type System.Reflection.Missing
.
The code is also in the Gist ActionFilter for ASP.NET Web API DefaultParameterValue support in F#.
namespace System.Web.Http
open System.Reflection
open System.Web.Http.Filters
open System.Web.Http.Controllers
open System.Runtime.InteropServices
/// Responsible for populating missing action arguments from DefaultParameterValueAttribute values.
/// Created to handle this issue https://github.com/aspnet/Mvc/issues/1923
/// Note: This is for later version of System.Web.Http but could be back-ported.
type DefaultParameterValueFixupFilter() =
inherit ActionFilterAttribute()
/// Get list of (paramInfo, defValue) tuples for params where DefaultParameterValueAttribute is present.
let getDefParamVals (parameters:ParameterInfo array) =
[ for param in parameters do
let defParamValAttrs = param.GetCustomAttributes<DefaultParameterValueAttribute>() |> List.ofSeq
match defParamValAttrs with
// Review: we are ignoring null defaults. Is this correct?
| [x] -> if x.Value = null then () else yield param, x.Value
| [] -> ()
| _ -> failwith "Multiple DefaultParameterValueAttribute on param '%s'!" param.Name
]
/// Add action arg default values where specified in DefaultParameterValueAttribute attrs.
let addActionArgDefsFromDefParamValAttrs (context:HttpActionContext) =
match context.ActionDescriptor with
| :? ReflectedHttpActionDescriptor as ad ->
let defParamVals = getDefParamVals (ad.MethodInfo.GetParameters())
for (param, value) in defParamVals do
match context.ActionArguments.TryGetValue(param.Name) with
| true, :? System.Reflection.Missing
| false, _ ->
// Remove is null-op if key not found, so we handle both match cases OK.
let _ = context.ActionArguments.Remove(param.Name)
context.ActionArguments.Add(param.Name, value)
| _, _ -> ()
| _ -> ()
/// Override adding suport for DefaultParameterValueAttribute values.
override x.OnActionExecuting(context) =
addActionArgDefsFromDefParamValAttrs context
base.OnActionExecuting(context)
/// Override adding suport for DefaultParameterValueAttribute values.
override x.OnActionExecutingAsync(context, cancellationToken) =
addActionArgDefsFromDefParamValAttrs context
base.OnActionExecutingAsync(context, cancellationToken)
You could use HttpRequestMessage and parse the query string dictionary.
EDITED...
To use HttpRequestMessage, let's say you have a query string page:
[<Route("api/Applications")>]
[<HttpGet()>]
member x.Get (message:HttpRequestMessage) =
let page = GetQueryStringParameter(message, "page")
I would probably use a helper method to parse the query string out of the HttpRequestMessage, something like this:
let GetQueryStringParameter (message:HttpRequestMessage, value) =
let value = List.ofSeq ( message.GetQueryNameValuePairs() )
|> List.tryFind (fun item -> item.Key = value)
match value with
| Some x -> x.Value
| None -> ""
You're pretty much limited to workarounds, it seems.
What about using:
[<HttpGet; Route("")>]
member x.GetVendorFiles(count: Nullable<int>, start : Nullable<DateTime>) =
let count = count.GetValueOrDefault(100)
// or: let count = if count.HasValue then count.Value else 100
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