2015-01-26 14 views
13

Przekształcałem projekt C# webapi w F # przy użyciu szablonów F # ASP.NET. Wszystko działa świetnie, z wyjątkiem opcjonalnych parametrów zapytania. Wciąż otrzymuję ten błądUżywanie opcjonalnych parametrów zapytania w projekcie F # Web Api

{ 
    "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 # Funkcja Podpis:

[<HttpGet; Route("")>] 
member x.GetVendorFiles([<Optional; DefaultParameterValue(100)>] count, [<Optional; DefaultParameterValue(null)>] start : Nullable<DateTime>) = 

C# Funkcja Podpis:

[HttpGet] 
[Route("")] 
public async Task<HttpResponseMessage> GetVendorFiles(int count = 100,DateTime? start = null) 

Czy ktoś zna jakieś obejścia?

Aktualizacja: Pomyślałem przyczynę tego problemu. Program ASP.NET wyodrębnia wartości domyślne dla działań kontrolera using ParameterInfo. Wygląda na to, że kompilator F # nie kompiluje wartości domyślnych w taki sam sposób, jak C# (nawet z DefaultParameterValueAttribute). Jest to najlepszy sposób na obejście tego problemu. Czy byłby to jakiś filtr, który muszę wstrzyknąć lub zaimplementować mój własny ParameterBinding?

+0

Czy można określić parametry domyślne na określonej trasie dla metody kontrolera? 'routes.MapRoute (name:" Default ", url:" MyController/GetVendorFiles/{count}/{start} ", domyślnie: nowy {count = 100, start = (DateTime?) null});' – DLeh

+0

@DLeh Not tak naprawdę, ponieważ istniejąca aplikacja front end używa liczby i zaczyna się jako parametry zapytania (nie parametry trasy), więc chciałbym, aby była zgodna z istniejącym kodem. Najgorszy przypadek mogę zmienić na ten – Pavel

+1

Po prostu zauważyłem, że @Pavel podniósł to jako [problem Mvc] (https://github.com/aspnet/Mvc/issues/1923). Problem zawiera dalszą dyskusję i obejście. – bentayloruk

Odpowiedz

1

Wygląda na to, że jesteś ograniczony do obejścia problemu.

Co za pomocą:

[<HttpGet; Route("")>] 
member x.GetVendorFiles(count: Nullable<int>, start : Nullable<DateTime>) = 
    let count' = 
     if obj.ReferenceEquals(count,null) 
      then 100 
      else count.Value 

    // continue with action logic, using count' instead of count 
+0

To jest poprawne i próbowałem tego, ale problem polega na tym, że jeśli nie określisz parametru kwerendy początkowej, otrzymam błąd powyżej, tj. Znajdzie trasę, ale ponieważ odbicie powoduje, że błąd nie powiedzie się, zanim dostanie się do mojej obsługi kod. – Pavel

2

Można obejść ten z niestandardowym ActionFilterAttribute realizacji. Poniższy kod obsługuje wartości DefaultParameterValue, gdy brakuje argumentu akcji i gdy argument akcji ma wartość typu System.Reflection.Missing.

Kod znajduje się również w 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) 
2

Można użyć HttpRequestMessage i analizowania słownika ciąg kwerendy.

EDYCJI ...

Aby korzystać HttpRequestMessage, powiedzmy, że masz stronę ciąg kwerendy:

[<Route("api/Applications")>] 
[<HttpGet()>] 
member x.Get (message:HttpRequestMessage) = 
    let page = GetQueryStringParameter(message, "page") 

I prawdopodobnie użyć metody pomocnika do analizowania ciąg kwerendy wyjście z HttpRequestMessage, coś w następujący sposób:

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 -> "" 
+0

Możesz zrobić przykład? – slfan