28

I tried the following:

/// <summary>
/// Request the Facebook Token
/// </summary>
[FunctionName("SolicitaFacebookToken")]
[Route("SolicitaToken/?fbAppID={fbAppID}&fbCode={fbCode}&fbAppSecret={fbAppSecret}")]
public static async Task<HttpResponseMessage> SolicitaFacebookToken(
    [HttpTrigger(AuthorizationLevel.Function, methods: new string[] { "get" } )]
    HttpRequestMessage req,
    TraceWriter log,
    string fbAppID,
    string fbCode,
    string fbAppSecret
)
{ }

When I access the URL:

http://localhost:7071/api/SolicitaFacebookToken/?fbAppID=ABC&fbCode=DEF&fbAppSecret=GHI

But it gives these errors:

'SolicitaFacebookToken' can't be invoked from Azure WebJobs SDK. Is it missing Azure WebJobs SDK attributes?
System.InvalidOperationException : 'SolicitaFacebookToken' can't be invoked from Azure WebJobs SDK. Is it missing Azure WebJobs SDK
attributes?
 at Microsoft.Azure.WebJobs.JobHost.Validate(IFunctionDefinition function,Object key)
 at async Microsoft.Azure.WebJobs.JobHost.CallAsync(??)
 at async Microsoft.Azure.WebJobs.Script.ScriptHost.CallAsync(String method,Dictionary\`2 arguments,CancellationToken cancellationToken)
 at async Microsoft.Azure.WebJobs.Script.WebHost.WebScriptHostManager.HandleRequestAsync(FunctionDescriptor function,HttpRequestMessage request,CancellationToken cancellationToken)
 at async Microsoft.Azure.WebJobs.Script.Host.FunctionRequestInvoker.ProcessRequestAsync(HttpRequestMessage request,CancellationToken cancellationToken,WebScriptHostManager scriptHostManager,WebHookReceiverManager webHookReceiverManager)
 at async Microsoft.Azure.WebJobs.Script.WebHost.Controllers.FunctionsController.<>c__DisplayClass3_0.<ExecuteAsync>b__0(??)
 at async Microsoft.Azure.WebJobs.Extensions.Http.HttpRequestManager.ProcessRequestAsync(HttpRequestMessage request,Func`3 processRequestHandler,CancellationToken cancellationToken)
 at async Microsoft.Azure.WebJobs.Script.WebHost.Controllers.FunctionsController.ExecuteAsync(HttpControllerContext controllerContext,CancellationToken cancellationToken)
 at async System.Web.Http.Dispatcher.HttpControllerDispatcher.SendAsync(HttpRequestMessage request,CancellationToken cancellationToken)
 at async System.Web.Http.Dispatcher.HttpControllerDispatcher.SendAsync(HttpRequestMessage request,CancellationToken cancellationToken)
 at async Microsoft.Azure.WebJobs.Script.WebHost.Handlers.WebScriptHostHandler.SendAsync(HttpRequestMessage request,CancellationToken cancellationToken)
 at async Microsoft.Azure.WebJobs.Script.WebHost.Handlers.SystemTraceHandler.SendAsync(HttpRequestMessage request,CancellationToken cancellationToken)
 at async System.Web.Http.HttpServer.SendAsync(HttpRequestMessage request,CancellationToken cancellationToken)

If I change to this:


HttpRequestMessage req,
string fbAppID,
string fbCode,
string fbAppSecret,
TraceWriter log

The following 1 functions are in error:

SolicitaFacebookToken: Microsoft.Azure.WebJobs.Host: Error indexing method 'Function1.SolicitaFacebookToken'. Microsoft.Azure.WebJobs.Host: Cannot bind parameter 'fbAppID' to type String. Make sure the parameter Type is supported by the binding. If you're using binding extensions (e.g. ServiceBus, Timers, etc.) make sure you've called the registration method for the extension(s) in your startup code (e.g. config.UseServiceBus(), config.UseTimers(), etc.).

In Azure Functions template code, there is

string name = req.GetQueryNameValuePairs()
                 .FirstOrDefault(q => string.Compare(q.Key, "name", true) == 0)
                 .Value;

I would like a simpler way to get the GET query parameters.

I want to have a URL like this:

http://localhost:7071/api/SolicitaFacebookToken/?fbAppID=123&fbCode=456&fbAppSecret=789

Q: How can I easily get the parameters and its values?

Ian Kemp
  • 28,293
  • 19
  • 112
  • 138
Tony
  • 16,527
  • 15
  • 80
  • 134

7 Answers7

29

For v2/beta/.NET Core runtime, you can be specific and do:

string fbAppID = req.Query["fbAppID"];

or more generic with:

using System.Collections.Generic;
...
IDictionary<string, string> queryParams = req.GetQueryParameterDictionary();
// Use queryParams["fbAppID"] to read keys from the dictionary.

For v1 function apps (.NET Full Framework):

using System.Collections.Generic;
...
IDictionary<string, string> queryParams = req.GetQueryNameValuePairs()
    .ToDictionary(x => x.Key, x => x.Value);
// Use queryParams["fbAppID"] to read keys from the dictionary.
evilSnobu
  • 24,582
  • 8
  • 41
  • 71
  • I tried req.Query["fbAppID"]; got this compile error: CS1061 'HttpRequestMessage' does not contain a definition for 'Query' and no extension method 'Query' accepting a first argument of type 'HttpRequestMessage' could be found (are you missing a using directive or an assembly reference?) – Tony Apr 14 '18 at 16:16
  • 2
    Most probably you're on the v1 runtime, use `req.GetQueryNameValuePairs()`, see above. – evilSnobu Apr 14 '18 at 16:28
  • You are right! I tought I was on v2, when I checked again now, it is on v1. I will recreate the Project in v2, Thank you! – Tony Apr 14 '18 at 16:38
28

In the past when I have multiple parameters I've added them to the route. So instead of this:

[Route("SolicitaToken/?fbAppID={fbAppID}&fbCode={fbCode}&fbAppSecret={fbAppSecret}")]

I've done something like this:

[Route("SolicitaToken/{fbAppID}/{fbCode}/{fbAppSecret}")]

Then you don't need to access the query string at all and can use the function parameters directly.

[FunctionName("Function1")]
public static HttpResponseMessage Run([HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "HttpTriggerCSharp/name/{fbAppID}/{fbCode}/{fbAppSecret}")]HttpRequestMessage req, string fbAppID, string fbCode, string fbAppSecret, TraceWriter log)
{
  log.Info("C# HTTP trigger function processed a request.");
  var msg = $"App ID: {fbAppID}, Code: {fbCode}, Secret: {fbAppSecret}";
  // Fetching the name from the path parameter in the request URL
  return req.CreateResponse(HttpStatusCode.OK, msg);
}
Sean
  • 1,359
  • 11
  • 15
  • 2
    This works. But I would like SolicitaToken/?fbAppID={fbAppID}&fbCode={fbCode}&fbAppSecret={fbAppSecret} ... Isn't possible to have GET parameters in the form of name=value ? – Tony Apr 14 '18 at 16:23
  • There is a problem the the parameter value contain ´/´, even when encoded as %2F its not parsed correctly generating a 404 instead of call to the routed function. – hultqvist Sep 03 '19 at 13:59
  • 3
    Note that it is considered bad practice to put query parameters in the route. Best practice is that path params identifies a resource, [as mentioned here](https://stackoverflow.com/questions/30967822/when-do-i-use-path-params-vs-query-params-in-a-restful-api#:~:text=8%20Answers&text=Best%20practice%20for%20RESTful%20API,to%20sort%2Ffilter%20those%20resources.). – Stian Jørgensrud Jul 26 '21 at 11:30
19

In v2 runtime, an alternative way to get query params:

var query = System.Web.HttpUtility.ParseQueryString(req.RequestUri.Query);
string result = query.Get("result");

Hope this helps.

KyleMit
  • 30,350
  • 66
  • 462
  • 664
Deepak Shaw
  • 461
  • 3
  • 6
13

To me, the most simple way to handle query parameters in a get method would be to define a PoCo object and use [FromQuery] attribute like I was used from .Net Web API.

Unfortunately, my initial approach to do that with HTTP-triggered Azure function, lead to the same exception described in the original question.

After some googling, I was about to loose all hope because all references (at least those that I found) state that it is either not possible or they simply expect you to either use route configuration or fetch the query parameter from the request object or instead of using GET use a POST request and pass what you want to be passed as part of the request body...

Since all of that was not really satisfying, I was about to implement my own custom BindingProvider for the POCO objects I wanted to use in my GET requests to the function...

Then I had a closer look at the source of azure-webjobs-sdk-extensions and found this: HttpDirectRequestBindingProvider

From the comment on that class:

// support allowing binding any non-attributed HttpRequest parameter to the incoming http request. 
//    Foo([HttpTrigger] Poco x, HttpRequest y); 

It turns out that the following is actually supported and works as expected:

Tl;Dr;

Make sure that the HttpTrigger Attribute is applied to your Poco object parameter to allow for the magic binding to kick in!

public async Task<IActionResult> Foo([HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = null)]
    PaginationFilter paginationFilter, HttpRequest req, ClaimsPrincipal principal, CancellationToken cancellationToken)

where the PaginationFilter type in my case is specified like that:

Please note that this is only an example. You can use any PoCo like type in your function signature as long as it is JSON serializable and has a default constructor.

public record PaginationFilter(Int32 Limit, Int32 Page)
{
    public PaginationFilter()
        : this(Constants.DefaultPageSize, 1)
    { }
}

Supported queries with this signature:

http://localhost:7071/api/Foo?Limit=50&Page=1
or
http://localhost:7071/api/Foo?Limit=50
or
http://localhost:7071/api/Foo?Page=1
or
http://localhost:7071/api/Foo

Also see my original answer to a related question on Azure-Functions GitHub Issues

Answer to the original question

So to answer the original question, if you create a transport object like for example following:

public class TokenSecrets
{
    public TokenSecrets() {}
    public string FbAppID {get; set;}
    public string FbCode {get; set;}
    public string FbAppSecret {get; set;}
}

you can adapt your Function call to this:

public static async Task<HttpResponseMessage> SolicitaFacebookToken(
    [HttpTrigger(AuthorizationLevel.Function, methods: new string[] { "get" } )]
    TokenSecrets secrets,
    HttpRequestMessage req,
    TraceWriter log
)
{ 
    // and access the query parameters like:
    var id = secrets.FbAppId;
    ...
}

without changing the way your API is being called.

Tobias Breuer
  • 825
  • 1
  • 11
  • 19
  • The `HttpDirectRequestBindingProvider` is the way to go. I decided to use classes instead to allow for more seamless inheritance. – JPortillo Jan 26 '23 at 02:22
  • These parameters can be passed in either the query args, or as part of the body. It even combines the arguments, pass ?a=1&b=2 and { "b": 3, "c": 4 }, and it will somehow combine them to get a=1, b=2 (body value is ignored) and c=4. This approach does not allow you as developer to enforce a where to pass a particular value. If this was used in an authorization API, for example, you can pass the user and password raw in the URL, leaving these exposed to network sniffing. So beware. – JPortillo Jan 26 '23 at 02:41
  • I works when using happy path, but it throws (and return 500 server error) if any of the properties are wrong (ie "foo" cannot be converted to an int). Is there any way around this? – Martin Wickman Mar 17 '23 at 15:28
4

Azure Functions Runtime v3:

 var query = System.Web.HttpUtility.ParseQueryString(req.Url.Query);
Exitare
  • 561
  • 2
  • 10
  • 30
2

you can get all query parameters like:

   foreach (var q in req.Query)
   {
     log.LogWarning($"Query param Key {q.Key} has value {q.Value}"); 
   }
Deepak Shaw
  • 461
  • 3
  • 6
1

In your function you can either use HttpRequestMessage requestMessage or HttpRequest request. I see the first one used above, but the latter supports the request.Query["myParameter"] syntax.

Cloghead
  • 19
  • 4