I have the following function in an Azure Functions application:
[FunctionName("MyFunction")]
public async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = "x/{my-guid}/y")] HttpRequest httpRequest,
Guid myGuid,
ILogger logger
)
The problem is that I can't use the string "my-guid" as a parameter name because it contains a hyphen, and when I use "myGuid" instead I get a "cannot bind parameter" error message.
Is there a way to do this, perhaps by somehow explicitly stating the mapping from "my-guid" to "myGuid"?
I have tried using attributes such as [FromRoute(Name = "my-guid")] to decorate the myGuid parameter, but this didn't work.
So you have basically two issues:
Dashes in route
The first one is "mapping" my-guid to myGuid which I am unfortunately not being able to help with - I can not recall anything that could achieve that, besides "hiding" your functions behind some sort of API Gateway (like API Management).
Parameter binding
The second one though is your "cannot bind parameter" error, which I assume you would get even if the first issue would be resolved. Apparently functions are unable cast parameter to Guid type, change it to string and then convert to Guid inside the body of your function method:
[FunctionName("MyFunction")]
public async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = "x/{myGuid}/y")]
HttpRequest httpRequest,
string myGuid,
ILogger logger
)
{
if (Guid.TryParse(myGuid, out var guid))
{
return new OkObjectResult(guid.ToString());
}
return new BadRequestResult();
}
Side note:
It's a bit painful with this additional logic, but I think it might be simplified with use of Azure Functions in isolated process rather than in-process and benefiting from middleware that handles that logic. It would make it a bit more future-proof as well:
Source of the image
Related
I want to add Custom Properties to the RequestTelemetry generated for the Azure function(V3) written in C#.
Thanks to this StackOverflow post, I managed to achieve this for the function with HttpTrigger binding.
var requestTelemetry = req.HttpContext?.Features.Get<RequestTelemetry>();
requestTelemetry.Properties.Add("MyProp", "Some value");
However, when I try to do same thing for another function with BlobTrigger binding, it became confusing. The first challenge is:
How to get current RequestTelemetry in a function that is using
BlobTrigger binding?
In a function with HttpTrigger binding, the function is injected with HttpRequest parameter
public async Task<IActionResult> Upload(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = "upload/{name}")] HttpRequest req,
string name,
ILogger log,
ExecutionContext context)
{
...
}
So we can get current RequestTelemetry using HttpRequest. However, what about a function with BlobTrigger:
[FunctionName("Create-Thumbnail")]
public async Task CreateThumbnail([BlobTrigger("input/{name}", Source = BlobTriggerSource.EventGrid, Connection = "AzureWebJobsStorage")] Stream image,
IDictionary<string,string> metadata,
string name,
ExecutionContext context)
{
...
}
I have tried injecting HttpRequest and using same way as HttpTrigger function. But it didn't work.
After hours extensive research, I couldn't find any documentation or posts that are related to this question.
Can anyone provide some tips for this?
AFAIK there is no http request when using a blob trigger. You can still add custom properties to the telemetry generated during the execution by setting properties like this:
// The current telemetry item gets a property.
// Useful if the function is not triggered by an http request
Activity.Current.AddTag("setUsingTag", "setUsingTag");
// Subsequent telemetry gets this property attached
Activity.Current.AddBaggage("setUsingActivityBaggage", "setUsingActivityBaggage");
When using Baggage instead of a Tag the custom property is added to all telemetry generated during the execution, like dependency calls etc.
See also this github thread. It also mentions there might be a bug introduced in a later version of AI that might force you to downgrade AI for this to work.
Thanks to this GitHub issue, this is finally working after downgrading System.Diagnostics.DiagnosticSource to version 4.6
<PackageReference Include="System.Diagnostics.DiagnosticSource" Version="4.6.0" />
What types are allowed as parameters for Azure Function apps written in C# that are only callable through the admin endpoint?
I've read lots of documentation and source code and I still don't know the answer. I'm looking for definitive references and clear explanations as to what that means and why, plus examples of how to implement functions with other parameter types.
I'm wondering if the Azure team expect you to accept a string of JSON and parse it yourself into proper types, but the documentation I've found doesn't say.
Additional context
The specific function I am working on is to be called only via the admin interface so does not have any Http etc bindings.
[NoAutomaticTrigger]
[FunctionName(nameof(SomeFunctionName))]
public async Task SomeFunctionName(ParameterTypeHere someParameterName)
{
...
What can I put instead of ParameterTypeHere?
The specific use I have (this time) is that I want to pass something like List<Guid> or Guid[], I don't mind if I have to wrap it in a class or something but nothing I tried worked so I've ended up splitting a string on comma and parsing out the guids which seems like a poor solution.
Currently I've got a string parameter and am calling it with:
$ curl -v http://localhost:7071/admin/functions/SomeFunctionName \
-d '{"input": "699F3073-9BFD-4DA7-9E61-4E6564D032EC,197DA362-C281-4E0F-BB92-8759F7A5B4B4"}' \
-H "Content-Type:application/json"
Research so far
Things I've already looked at that leave me still unsure what I can use beyond string for more complex inputs:
Azure Functions: Generic type as input parameter
https://github.com/Azure/Azure-Functions/issues/735
Azure Functions error - Cannot bind parameter to type String
Cannot bind parameter, cause the parameter type is not supported by the binding (HttpTrigger of Azure Functions)
Can not bind ILogger in azure function v1
https://learn.microsoft.com/en-us/azure/azure-functions/functions-manually-run-non-http
https://github.com/Azure/azure-functions-host/blob/dev/src/WebJobs.Script/Binding/Manual/ManualTriggerAttributeBindingProvider.cs#L39
https://github.com/Azure/azure-functions-host/blob/9b2fa0f3ea69c41ce819f046213eab4f40a5db5f/src/WebJobs.Script/Binding/StreamValueBinder.cs#L24-L30
https://github.com/Azure/azure-functions-host/blob/9b2fa0f3ea69c41ce819f046213eab4f40a5db5f/src/WebJobs.Script/Utility.cs#L245
ServiceBusTrigger with enqueueTimeUtc argument fails when triggered via HTTP endpoint
The parameter name is ignored, and you have to pass it in with the name "input" regardless of the actual parameter name. Just another thing to trip over.
Even more context
If you're wondering why you'd ever want an admin-only function, this was for a one-off job to be run by someone else who has access to the admin endpoints. It appeared to be the simplest thing that could work. An HttpTrigger would have been fine, it just appeared to violate YAGNI.
Some weeks ago, I tested how to convert an API using functions with special attention to DI (not shown in below example) and validation. This may not be a direct answer to your question, but it shows that plain model classes may be used.
public class MyRequestModel
{
[Display(Name = "Message")]
[Required, MinLength(3)]
public string Message { get; set; }
}
public static class MyHttpExample
{
[FunctionName(nameof(MyHttpExample))]
public static IActionResult Run(
[HttpTrigger(AuthorizationLevel.Function, "get", Route = "test/{message}")] MyRequestModel req, ILogger log)
{
log.LogInformation("C# HTTP trigger function processed a request.");
var validationResults = new List<ValidationResult>();
if (!Validator.TryValidateObject(req, new ValidationContext(req, null, null), validationResults, true))
{
return new BadRequestObjectResult(validationResults);
}
var responseMessage = $"Hello, {req.Message}. This HTTP triggered function executed successfully.";
return new OkObjectResult(responseMessage);
}
}
More information on Azure Functions binding expression patterns
https://learn.microsoft.com/en-us/azure/azure-functions/functions-bindings-expressions-patterns
When reading the above doc, keep in mind that function.json is generated from annotations when using C#, but doc shows what's available. Generated function.json for the above example:
{
"generatedBy": "Microsoft.NET.Sdk.Functions-3.0.11",
"configurationSource": "attributes",
"bindings": [
{
"type": "httpTrigger",
"route": "test",
"methods": [
"get"
],
"authLevel": "function",
"name": "req"
}
],
"disabled": false,
"scriptFile": "../bin/MyFunctions.dll",
"entryPoint": "MyFunctions.MyHttpExample.Run"
}
See also https://stackoverflow.com/a/54489040/14072498
Every time i am debugging my Azure Functions (locally) then invoking a function, the CLI (func.exe) always shows requestId as one of the incoming request's parameters which i am highly sure it is not from my side that generate it.
My question is, is it possible to extract the value of requestId value from our code and how ? is this feature only specific for development/local debugging only ? since i can't find any documentation, article nor blog regarding this.
thank you
I believe you will find the request information in the HttpContext
req.HttpContext.Items["MS_AzureFunctionsRequestID"]
c# code example assuming you have the following signature
public static async Task<IActionResult> Run([HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequest req, ILogger log) {}
I have an OData controller with standard verbs for CRUD. Everything is working fine. Now I need to add a custom action to perform file upload. I try to add a method to my existing controller like this:
[HttpPost]
[Route("UploadFile")]
public async Task<HttpResponseMessage> UploadFile()
{
//handle uploaded content logic here...
}
But when I try to invoke it by doing a POST:
http://localhost/UploadFile
I get this error:
System.InvalidOperationException: No non-OData HTTP route registered.
What should I do for this custom action to allow file upload?
You need to declare the Action as part of the EdmModel, in the following example I am assuming that your Entity Type is Attachment, and your controller class name is AttachmentsController. By convention, your EntitySet name must then be Attachments
var attachments = builder.EntitySet<Attachment>("Attachments");
attachments.Action(nameof(AttachmentsController.UploadFile))
.Returns<System.Net.Http.HttpResponseMessage>();
The important part of the above statement is the return type, if you do not declare the return type correctly in your EdmModel then you will find your endpoints returning 406 errors - Unacceptable, even though your method executes correctly, which is really confusing the first time you run into it. This is because OData will still try to parse your response to match the Accept header from the request before completing the response.
Try to use 'nameof' when mapping functions and actions instead of 'magic strings' or constants so that the compiler can pickup basic issues like wrongly defined route.
With this approach you do not need the Route attribute on the method header and the action will be included in the metadata document and therefore swagger outputs.
When ASP.NET Core encounters ambiguously named routes, it becomes inert. That is, the application will run without exceptions thrown but, it will fail to process any requests, on any controllers. The calling client receives 500 responses.
I'll show how I got into this mess, and I'd like suggestions of how to fix it.
I have a controller that looks like this:
[Route("api/Subscribers/{id}/[controller]")]
[Route("api/Organizations/{id}/[controller]")]
public class AddressesController : Controller
{
[HttpGet("{aid}", Name = "PostalLink")]
public async Task<IActionResult> GetAddress(Guid id, Guid aid)
{
//...implementation is irrelevant for this question.
}
[HttpPost]
[SwaggerResponseRemoveDefaults]
[SwaggerResponse(HttpStatusCode.Created, Type = typeof(PostalRecord))]
public async Task<IActionResult> CreateAddress(Guid id, [FromBody] PostalAddress address)
{
address.ID = Guid.NewGuid();
await createAddress.Handle(address);
return CreatedAtRoute("PostalLink", new { id = id, aid = address.ID });
}
Why the two route prefixes on the controller? Because it fits my microservices (and Swagger documentation) strategy. Nevertheless, in this example ASP.NET Core does not know how to resolve the route name "PostalLink" because it is implicitly bound to the two prefixes:
[Route("api/Subscribers/{id}/[controller]")]
[Route("api/Organizations/{id}/[controller]")]
I can fix the problem simply by changing the HttpGet so that instead of this:
[HttpGet("{aid}", Name = "PostalLink")]
I have this:
[HttpGet("{aid}")] //the route is no longer "named"
Unfortunately, removing the route name is not a real option for me.
What is the prescribed way to fix this?
Below are some of the options I'm considering.
Possibility #1
Theoretically, ASP.NET could simply "figure it out" by itself. For example, if the current request resolved to the route containing the word "Subscribers", then the "PostalLink" name should reference that route. Seen this way, perhaps my code is exposing a bug, defect, or oversight in ASP.NET Core.
Possibility #2
I could collapse my two prefix routes into a single route like this:
[Route("api/{parent}/{id}/[controller]")]
This works, but it undermines my REST documentation strategy. I'm using Swashbuckle to publish endpoint metadata. I want a user of my API to expressly see that my "Addresses" API is serving either "Subscribers" or "Organizations". When I have two explicit route prefixes, the Swagger documentation works correctly (and I properly validate the URI used by the client).
Possibility #3
I could simply override the two prefixes like this:
[HttpGet("~/api/Subscribers/{id}/Addresses/{aid}", Name = "SubscriberLink")]
[HttpGet("~/api/Organizations/{id}/Addresses/{aid}", Name = "OrganizationLink")]
public async Task<IActionResult> GetAddress(Guid id, Guid aid)
{
//...implementation is irrelevant for this question.
}
Now my documentation and route validation works, but my implementation is forced to check which route was used to reach the endpoint. That is very doable, but very annoying.
Possibility #4
Perhaps there is a more expressive way to handle this problem without attribute-based-routing? If yes, please share!
Details
My project.json is configured as follows:
"frameworks": {
"dnx46": { }
},
I am using DNX SDK version 1.0.0-rc1-update1. Also, I posted a related SO question for those who would like more context of what I am trying to do.
If your route names are the same for all your actions, why not specify them directly on the controller ?
[Route("api/Subscribers/{id}/[controller]", Name = "SubscriberLink")]
[Route("api/Organizations/{id}/[controller]", Name = "OrganizationLink")]
public class AddressesController : Controller
{
[HttpGet("{aid}")]
public async Task<IActionResult> GetAddress(Guid id, Guid aid)
{
//...implementation is irrelevant for this question.
}
}
Have you looked into attribute routing?
E.g. Registering routes with ASP.Net 5's MVC 6 Attribute Routing
Sample from the relevant documentation:
In the following example, app.UseMvc(); is used in the Configure method and no route is passed.
public class HomeController : Controller
{
[Route("")]
[Route("Home")]
[Route("Home/Index")]
public IActionResult Index()
{
return View();
}
[Route("Home/About")]
public IActionResult About()
{
return View();
}
[Route("Home/Contact")]
public IActionResult Contact()
{
return View();
}
}
The HomeController.Index() action will be executed for any of the URL paths /, /Home, or /Home/Index.