Azure Functions .NET 5 Isolated HttpTrigger Path Variable Input Binding - azure

I'm just trying to figure out how to do something in .NET 5 that worked in 3.1 and before.
In 3.1, the route variable binds correctly to the Guid parameter of the same name:
[FunctionName("Function1")]
public static async Task Run(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "records/{clientId:Guid}")] HttpRequest req,
Guid clientId,
ILogger log)
{
return new OkObjectResult(clientId);
}
A comparable .NET 5 version of this same function fails to bind the path variable:
[Function("Function1")]
public static HttpResponseData Run([HttpTrigger(AuthorizationLevel.Function, "get", Route = "records/{clientId:Guid}")] HttpRequestData req,
Guid clientId,
FunctionContext executionContext)
{
var response = req.CreateResponse(HttpStatusCode.OK);
response.WriteString(clientId.ToString());
return response;
}
The error that is thrown is as follows:
Exception:
Microsoft.Azure.Functions.Worker.Diagnostics.Exceptions.FunctionInputConverterException:
Error converting 1 input parameters for Function 'Function1': Cannot
convert input parameter 'clientId' to type 'System.Guid' from type
'System.String'.
I can change the type of the parameter to string and then parse it into a Guid after the fact, of course, but I'd like to know if it's still possible to do it the aforementioned way.

There are two things:
Why isn't the route constraint taken into account (i.e. why does a conversion need to occur)
I found a related git issue. That doesn't seem to be fixed, though it makes me wonder how you managed to make it work with .NET Core 3.1 :)
Why isn't the input string automatically converted to a Guid
I had a look at the code to understand where the exception is raised.
The model binding is using a list of IConverter to convert between the input type and the binding type. In your case, the input type is string and the binding type is Guid, and there's no built-in converter that can do that. You can't even create your own IConverter, because it's an internal interface.
Note: here's an example of IConverter that converts a string to a byte array: StringToByteConverter
So basically, there's nothing you can do apart from your suggestion to parse the Guid yourself.

Related

Make Azure function blob binding case-insensitive

I have a C# Azure HTTPTrigger Function which will get a file from my blob storage.
This is a public REST method. The caller sends a string, which can be in any case.
Because the files are stored case sensitive, I'm not using a direct Blob binding but use the more generic binding, convert the input to upper-case and create a new Blob binding to get the blob stream:
public async Task<IActionResult> GetFile(
[HttpTrigger(AuthorizationLevel.Function, "get", Route = "file/{name}")] HttpRequest req,
IBinder binder, ExecutionContext context, string name)
{
// The file name is always upper-case:
var nameUpper = name.ToUpperInvariant();
// Get the file (the file has no extension), using the IBinder parameter instead of the [Blob()] parameter because we need to make name upper-case:
var blobStream = await binder.BindAsync<byte[]>(new BlobAttribute($"%BlobContainerName%/{nameUpper}", FileAccess.Read),
req.HttpContext.RequestAborted).ConfigureAwait(false);
Is this the most optimal way? Or can this be made simpler/easier?
It feels a bit hacky.

Azure Function 1.x to 2.x upgrade - Issue with GetQueryNameValuePairs

I have an HTTP Trigger Azure Function which is currently in 1.x. The code is as below:
using System.Net;
using System.Threading.Tasks;
public static async Task<HttpResponseMessage> Run(HttpRequestMessage req, TraceWriter log)
{
log.Info($"C# HTTP trigger function processed a request. RequestUri={req.RequestUri}");
// parse query parameter
string name = req.GetQueryNameValuePairs()
.FirstOrDefault(q => string.Compare(q.Key, "name", true) == 0)
.Value;
// Get request body
dynamic data = await req.Content.ReadAsAsync<object>();
// Set name to query string or body data
name = name ?? data?.name;
return name == null
? req.CreateResponse(HttpStatusCode.BadRequest, "Please pass a name on the query string or in the request body")
: req.CreateResponse(HttpStatusCode.OK, "Hello " + name);
}
While trying to upgrade it to 2.x, I am getting an issue with GetQueryNameValuePairs
I am getting error - 'HttpRequestMessage' does not contain a definition for 'GetQueryNameValuePairs'
Is there no support for this method in 2.0? How can this be accomplished in .net standard?
Function runtime 1.x is on Full .Net Framework, while 2.x runs on .NET Core env and our function code targets at .NET Standard.
For this class HttpRequestMessage, it doesn't have GetQueryNameValuePairs method in .NET Standard assembly.
Migrating from 1.x to 2.x usually needs work of code modification. Since it's just a template, I suggest you delete it and recreate a Http Trigger in 2.x runtime. You may see a different template work with .NET Standard.
Here is the sample code that looks up query string parameters in Functions V2.x
using System.Net;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Primitives;
public static IActionResult Run(HttpRequest req, TraceWriter log)
{
log.Info("C# HTTP trigger function processed a request.");
if (req.Query.TryGetValue("name", out StringValues value))
{
return new OkObjectResult($"Hello, {value.ToString()}");
}
return new BadRequestObjectResult("Please pass a name on the query string");
}
In Functions v2 this has changed to req.GetQueryParameterDictionary();

Accept x-www-form-urlencoded in Web API .NET Core

I have a .NET Core Web API that is returning a 415 Unsupported Media Error when I try to post some data to it that includes some json. Here's part of what is returned in the Chrome Debugger:
Request URL:http://localhost:51608/api/trackAllInOne/set
Request Method:POST
Status Code:415 Unsupported Media Type
Accept:text/javascript, text/html, application/xml, text/xml, */*
Content-Type:application/x-www-form-urlencoded
action:finish
currentSco:CSharp-SSLA:__How_It_Works_SCO
data:{"status":"incomplete","score":""}
activityId:13
studentId:1
timestamp:1519864867900
I think this has to do with my controller not accepting application/x-www-form-urlencoded data - but I'm not sure. I've tried decorating my controler with Consumes but that does not seem to work.
[HttpPost]
[Route("api/trackAllInOne/set")]
[Consumes("application/x-www-form-urlencoded")]
public IActionResult Post([FromBody] PlayerPackage playerPackage)
{ etc..}
Any help greatly appreciated.
The following code worked fine in .NET 4.6.1 and I am able to capture and process the posts shown above.
[ResponseType(typeof(PlayerPackage))]
public async Task<IHttpActionResult> PostLearningRecord(PlayerPackage playerPackage)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var id = Convert.ToInt32(playerPackage.ActivityId);
var learningRecord = await _context.LearningRecords.FindAsync(id);
if (learningRecord == null)
return NotFound();
etc...
Try using [FromForm] instead of [FromBody].
public IActionResult Post([FromForm] PlayerPackage playerPackage)
FromBody > Bind from JSON
FromForm > Bind from Form parameters
You can also remove [FromBody] altogether and trial it then. Because you are expecting form-urlencoded should tell it to bind to object.
For PlayerPackage, the request should send a PlayerPackage Json Object, based on your description, you could not control the request which is posted from other place.
For the request, its type is application/x-www-form-urlencoded, it will send data with {"status":"incomplete","score":""} in string Format instead of Json object. If you want to accept {"status":"incomplete","score":""}, I suggest you change the method like below, and then convert the string to Object by Newtonsoft.Json
[HttpPost]
[Route("~/api/trackAllInOne/set")]
[Consumes("application/x-www-form-urlencoded")]
public IActionResult Post([FromForm] string data)
{
PlayerPackage playerPackage = JsonConvert.DeserializeObject<PlayerPackage>(data);
return Json(data);
}
This did the trick for me:
[HttpPost]
[Consumes("application/x-www-form-urlencoded")]
public IActionResult Post([FromForm]IFormCollection value)
I had the same problem. FormDataCollection has no default constructors which is required by Formatters. Use IFormCollection instead.
Can make setting like as
[HttpPost()]/[HttpGet()]
[Consumes("application/x-www-form-urlencoded")]
public async Task<IActionResult> MethodName([FromForm] IFormCollection value)
don't forget to add [FromForm]

How to troubleshoot Flaky Azure function app behaviour?

I am trying out Azure Function Apps.
The first one following the example in a tutorial with Open Weather map, stopped working after I used log.WriteLine(), which correctly threw a compiler error. I changed to log.Info() and it kept complaining about TraceWriter not containing a definition for WriteLine.
After a lengthy troubleshooting session, I created a new function, copying all the content of the broken one, and it worked immediately.
Created a new function, as before, and began making changes to the Run() method, and running this function yields:
"The resource you are looking for has been removed, had its name
changed, or is temporarily unavailable."
Bearing in mind, the function URL is based on the default key Azure generates when the function is created: https://.azurewebsites.net/api/WeatherWhereYouAre?code=my1really2RAndom3defauLT4Key5from6Azure==
Created yet another function, with no changes from the default "Hello Azure" sample, and it yields a 500 error with:
"Exception while executing function: Functions.HttpTriggerCSharp2 ->
One or more errors occurred. -> Exception binding parameter 'req' ->
Input string was not in a correct format."
This is the content of the project.json file:
{
"frameworks": {
"net46": {
"dependencies": {
"Microsoft.IdentityModel.Clients.ActiveDirectory": "3.16.0",
"Microsoft.Azure.KeyVault": "2.3.2",
"Microsoft.AspNet.WebApi.Client": "5.2.3"
}
}
}
}
And the run.csx:
using System.Net;
public static async Task<HttpResponseMessage> Run(HttpRequestMessage req, TraceWriter log)
{
log.Info("C# HTTP trigger function processed a request.");
// parse query parameter
string name = req.GetQueryNameValuePairs()
.FirstOrDefault(q => string.Compare(q.Key, "name", true) == 0)
.Value;
// Get request body
dynamic data = await req.Content.ReadAsAsync<object>();
// Set name to query string or body data
name = name ?? data?.name;
return name == null
? req.CreateResponse(HttpStatusCode.BadRequest, "Please pass a name on the query string or in the request body")
: req.CreateResponse(HttpStatusCode.OK, "Hello " + name);
}
EDIT
In the above image, note that this is httpTriggerFSharp1, but the exception is HttpTriggerCSharp2 (which is the only one that works!)
Is there a way I can properly troubleshoot these?
For the default HttpTrigger template for C#, you could call it as follows:
Get https://brucefunapp.azurewebsites.net/api/HttpTriggerCSharp3?name=bruce&code=ItDhLMxwDYmTvMTYzVbbALtL5GEcmaL5DlzSaD4FRIuFdh17ZkY71g==
Or
Post https://brucefunapp.azurewebsites.net/api/HttpTriggerCSharp3?code=ItDhLMxwDYmTvMTYzVbbALtL5GEcmaL5DlzSaD4FRIuFdh17ZkY71g==
Content-type: application/json
{"name": "bruce"}
For more details about Azure Functions C# script, you could refer to here.
Is there a way I can properly troubleshoot these?
Per my understanding, you could leverage Precompiled functions and use Visual Studio 2017 Tools for Azure Functions for creating, local debugging, and publishing to Azure.

post parameter that passed in as #Field does not being added into RequestBody in Retrofit2?

base on Retrofit #Field doc, when making a post request
a combination of using #FormUrlEncoded and #Field will yields a request body of: paramName=paramValue&paramName=paramValue.
but what I am not getting field paramemters included in RequestBody.
my interface definition as below:
(I have no endpoint, and jake Wharton says use ./ as explicit intent that you want to use the path of the base URL and add nothing to it, but I tried #POST("./") it's not work, i got 404 not found error, so I add full url address to bypass this error temporarily)
public interface BannerService {
#FormUrlEncoded
#POST("http://10.10.20.190:6020/router")
Flowable<List<BannerBeanList.BannerBean>> getBannerData(#Field("method") String method, #Field("adspaceId") String adspaceId);
}
and this is how I make calls to interface service:
public class RemoteListDataSource implements RemoteDataSource {
#Override
public Flowable<List<BannerBeanList.BannerBean>> getBannerListData(ADFilterType adFilterType) {
BannerService bannerService = RetrofitHttpManger.getInstance().create(BannerService.class);
return bannerService.getBannerData("mz.app.ad.list", String.valueOf(adFilterType.getValue()));
}
}
below is retrofit instance in it's private constructor
retrofit = new Retrofit.Builder()
.client(httpClientBuilder.build())
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
//TODO baseurl tempororily hard code for test purpose
.baseUrl("http://10.10.20.190:6020/router/")
.build();
this is the result I got:
the current request body that I am logging is the common parameters that I added from FromBody in interceptor, only except the parameters that I passed in from #Field annoation, and server side info tells the same thing.
I have solved this issue, thanks to #iagreen's comment.
the request body was replaced by FormBody.Builder().add().build() which passed into chain.request().newBuilder().post().build() in my interceptor.
then the question turns out to be how to append paramemters in RequestBody, and the solution can refers to Retrofit2: Modifying request body in OkHttp Interceptor

Resources