i am facing a weird issue. Searched for multiple question but didn't really get the real fix for this. I have below default template code.
[FunctionName("OrchFunction_HttpStart")]
public async Task<HttpResponseMessage> HttpStart(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", "post")] HttpRequestMessage req,
[DurableClient] IDurableOrchestrationClient starter,
ILogger log)
{
// Function input comes from the request content.
string instanceId = await starter.StartNewAsync("OrchFunction", null);
log.LogInformation($"Started orchestration with ID = '{instanceId}'.");
return starter.CreateCheckStatusResponse(req, instanceId);
}
In Orchstrator function i have below code
[FunctionName("OrchFunction")]
public async Task<List<string>> RunOrchestrator(
[OrchestrationTrigger] IDurableOrchestrationContext context)
{
var outputs = new List<string>();
var data = context.GetInput<JobPayload>();
var inst=context.InstanceId;
// returns ["Hello Tokyo!", "Hello Seattle!", "Hello London!"]
return outputs;
}
Issue here is i am getting NULL in var data = context.GetInput<JobPayload>();. Not sure why, as its the T type i am passing in the HttpRequestMessage. i know its wrong but tried with var data = context.GetInput<HttpResponseMessage>(); , stil its null. What is wrong here? i am getting context.InstanceId value.
https://learn.microsoft.com/en-us/dotnet/api/microsoft.azure.webjobs.extensions.durabletask.idurableorchestrationclient.startnewasync?view=azure-dotnet#Microsoft_Azure_WebJobs_Extensions_DurableTask_IDurableOrchestrationClient_StartNewAsync_System_String_System_String_
Here are different overloads for StartNewAsync. The one you are using does not pass any input to the orchestrator so you wont have any input in orchestrator.
Use this as the starter
[FunctionName("OrchFunction_HttpStart")]
public async Task<HttpResponseMessage> HttpStart(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", "post")] HttpRequestMessage req,
[DurableClient] IDurableOrchestrationClient starter,
ILogger log)
{
var payload = new JobPayload()
{
//Fill with data
}
// Function input comes from the request content.
string instanceId = await starter.StartNewAsync<JobPayload>("OrchFunction", payload);
log.LogInformation($"Started orchestration with ID = '{instanceId}'.");
return starter.CreateCheckStatusResponse(req, instanceId);
}
Note: JobPayload has to be serializable
I want to have a CorrelationId to be able to follow a request from Http Trigger to Blob Trigger in Application insights.
I am creating a CorrelationId in Http Trigger function and want to track same in the blob trigger.
Here is my Http Trigger function:
[FunctionName(nameof(ReceiveEvent))]
public static async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Function, "post", Route = null)] HttpRequest req,
[Inject] ILoggingService loggingService,
[Inject] IProvideCorrelationIds correlationIds,
[Inject] IEventMapper eventMapper,
[Inject] IEventValidator eventValidator,
[Inject] IEventHandler<ResultDto, Messages.Events.Event> eventHandler)
{
var logger = new Logger(loggingService);
try
{
IActionResult actionResult = null;
correlationIds.CorrelationId = Guid.NewGuid().ToString();
string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
logger.Info($"Event request received");
var #event = eventMapper.Map(requestBody);
if (eventValidator.Validate(req, #event, logger, ref actionResult))
{
var response = await eventHandler.HandleAsync(#event, logger);
actionResult = new OkObjectResult(response);
}
return actionResult;
}
catch (Exception ex)
{
logger.Error($"Exception while processing {nameof(ReceiveEvent)}", ex,
nameof(ReceiveEvent));
throw;
}
}
CorrelationId Provider:
public class CorrelationIdProvider: IProvideCorrelationIds
{
private static readonly AsyncLocal<string> AsyncLocalCorrelationId = new AsyncLocal<string>();
public string CorrelationId
{
get => AsyncLocalCorrelationId.Value;
set => AsyncLocalCorrelationId.Value = value;
}
}
Blob Trigger Function:
[FunctionName(nameof(ProcessEvent))]
public static async Task Run([BlobTrigger(BlobStorageContainer.Name + "/{name}",
Connection = "AzureWebJobsStorage")]
Stream eventBlob, string name,
[Inject] ILoggingService loggingService,
[Inject] IEventProcessorService eventProcessor,
[Inject] IBlobClient blobClient)
{
var logger = new Logger(loggingService);
try
{
logger.Info($"Starting blob job tracker for file name {name}",
nameof(ProcessEvent));
//correlationIds.CorrelationId = correlationId;
var eventContent = eventBlob.ReadAsString();
var result = await eventProcessor.HandleProcessor(eventContent, logger);
if (result)
{
await blobClient.DeleteBlobAsync(BlobStorageContainer.Name, name);
logger.Info($"Blob deleted successfully file name: {name}");
}
else
{
logger.Warning($"Unable to process blob job for file with name: {name}");
}
}
catch (Exception ex)
{
logger.Error($"Unable to process blob job for file with name: {name}", ex,
nameof(ProcessEvent));
}
}
What are the changes I need to do to get CorrelationId in Blob Trigger?
You can put the two trigger in the same class, use below code you can get id from http trigger:
using System;
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
namespace FunctionApp86
{
public class CorrelationIdProvider
{
public string id;
public string CorrelationId
{
get { return id; }
set { id = value; }
}
}
public static class Function1
{
public static string id;
[FunctionName("Function1")]
public static async Task<IActionResult> Run1(
[HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req,
ILogger log)
{
log.LogInformation("C# HTTP trigger function processed a request.");
CorrelationIdProvider cp = new CorrelationIdProvider();
cp.CorrelationId = Guid.NewGuid().ToString();
log.LogInformation(cp.CorrelationId);
id = cp.CorrelationId;
return new OkObjectResult(cp.CorrelationId);
}
[FunctionName("Function2")]
public static async Task<IActionResult> Run2(
[HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req,
ILogger log)
{
log.LogInformation("C# HTTP trigger function processed a request.");
CorrelationIdProvider cp = new CorrelationIdProvider();
cp.CorrelationId = id;
log.LogInformation(cp.CorrelationId);
return new OkObjectResult(cp.CorrelationId);
}
}
}
My second trigger is http trigger, you can change it to blob trigger.:)
I used TelemetryClient to sink my application logs in Application Insight. Now I want to control my logging according to the environment.
I tried to set LogLevel using host.json file but it's not working. I don't see any property in TelemetryClient to pass LogLeve from AppSettings.
public class Log : ILog
{
private static TelemetryClient telemetryClient = new TelemetryClient() { InstrumentationKey = ConfigurationManager.AppSettings["APPINSIGHTS_INSTRUMENTATIONKEY"] };
public void Error(string message, Exception ex = null)
{
telemetryClient.TrackTrace(message, SeverityLevel.Error);
if (ex != null)
telemetryClient.TrackException(ex);
}
public void Info(string message)
{
telemetryClient.TrackTrace(message, SeverityLevel.Information);
}
public void Verbose(string message)
{
telemetryClient.TrackTrace(message, SeverityLevel.Verbose);
}
public void Warning(string message)
{
telemetryClient.TrackTrace(message, SeverityLevel.Warning);
}
}
host.json
{ "logger": { "categoryFilter": { "defaultLevel": "Information" }}
Function
public static HttpResponseMessage Run([HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = null)]HttpRequestMessage req,
ExecutionContext context, [Inject] ILog log)
{
log.Verbose("verbose log");
log.Info("info log");
log.Error("error log");
log.Warning("warning log");
return req.CreateResponse(HttpStatusCode.OK, "Okay");
}
ApplicationInsight
I don't want verbose log in Prod env.
I have written Azure function v1 using C# (library project) with Aspect (AOP) for Logging. I'm not getting exception in catch block.
Catch an exception thrown by an async method
I have same problem discussed above, however, Azure Function Run method is Async Task and its exception handling same as async void. Not sure where is a problem? assuming this is function SDK issue.
Azure Function
public static class PingFunction
{
[LoggerAspect]
[FunctionName("PingFunction")]
public static async Task<HttpResponseMessage> Run([HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)]HttpRequestMessage req, TraceWriter log)
{
string name = string.Empty;
log.Info("C# HTTP trigger function processed a request.");
SomeService someService = new SomeService();
await someService.DoSomething();
return req.CreateResponse(HttpStatusCode.OK, "Hello " + name);
}
}
public class SomeService
{
public async Task DoSomething()
{
await Task.Delay(1000);
throw new Exception("Exception from Service");
}
}
Logger Aspect (MrAdvise)
public class LoggerAspectAttribute : Attribute, IMethodAdvice
{
public void Advise(MethodAdviceContext context)
{
//Logger initilizer here
Console.WriteLine($"{context.TargetType.Name} started...");
try
{
context.Proceed(); // this calls the original method
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
finally
{
Console.WriteLine($"{context.TargetType.Name} completed...");
}
}
}
Workaround
When I removed Async-await from Azure function and call async method by "GetAwaiter().GetResult()", then it works.
public static HttpResponseMessage Run([HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)]HttpRequestMessage req, TraceWriter log)
{
string name = string.Empty;
log.Info("C# HTTP trigger function processed a request.");
SomeService someService = new SomeService();
someService.DoSomething().GetAwaiter().GetResult();
return req.CreateResponse(HttpStatusCode.OK, "Hello " + name);
}
Task.GetAwaiter().GetResult() methods cause the potential for deadlock issues and should be avoided in favor of async/await.
My function process millions for events per-day. Is it the right solution if this is FunctionSDK issue or something else?
you need to write an async advice, such as:
public class LoggerAspectAttribute : Attribute, IMethodAsyncAdvice
{
public async Task Advise(MethodAsyncAdviceContext context)
{
//Logger initilizer here
Console.WriteLine($"{context.TargetType.Name} started...");
try
{
await context.ProceedAsync(); // this calls the original method
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
finally
{
Console.WriteLine($"{context.TargetType.Name} completed...");
}
}
}
EDIT : and yes, it works with Mr Advice :)
I'm trying to do a simple Azure Function to learn about it. There will be 3 functions:
1 function to insert a row into a table of a database. This table will contain the current date and a string parameters typed by the user and passed by GET.
1 function similar to the previous one, but passing the parameter by POST.
1 function to read the table and show its content.
I've been able to do the first and the third ones. But I can't pass the parameter by POST. I've looked for examples but I couldn't run them with success. The client app is a Windows Forms one.
Could anyone show me an example anout how to pass parameters by POST to the function and how to read them?
Thank's in advance
EDIT:
Here's the code to pass the parameters by GET (this is working fine):
private void button2_Click(object sender, EventArgs e)
{
string cadena = lsql1.Text + "?notas=" + tNotas.Text;
try
{
HttpWebRequest req = (HttpWebRequest)WebRequest.Create(cadena);
HttpWebResponse res = (HttpWebResponse)req.GetResponse();
if (res.StatusCode == HttpStatusCode.OK)
{
MessageBox.Show("Grabado");
}
else
{
MessageBox.Show(res.StatusDescription);
}
}catch (WebException ex)
{
using (Stream s = ex.Response.GetResponseStream())
{
StreamReader sr = new StreamReader(s);
string text = sr.ReadToEnd();
text = text.Substring(1, text.Length - 2);
sr.Close();
text = text.Replace("\\", "");
text = "{" + text + "}";
Error mensajeError = JsonConvert.DeserializeObject<Error>(text);
MessageBox.Show(mensajeError.ExceptionMessage);
}
}
}
And here's the code to receive it and do the insert (this is working too):
[FunctionName("sql1")]
public static async Task<HttpResponseMessage> Run(HttpRequestMessage req, TraceWriter log)
{
try
{
log.Info("C# HTTP trigger function processed a request.");
var cnnString = "Server=SERVIDOR;Database=base_prueba;User ID =azure;Password=0000;Trusted_Connection=False;Encrypt=False;";
using (SqlConnection connection = new SqlConnection(cnnString))
{
connection.Open();
SqlCommand cmd = connection.CreateCommand();
DateTime fecha = DateTime.Today;
string notas = req.GetQueryNameValuePairs()
.FirstOrDefault(q => string.Compare(q.Key, "notas", true) == 0)
.Value;
// insert a log to the database
cmd.CommandText = "INSERT INTO Prueba_Azure (fecha, notas) VALUES ('" + fecha.ToString() + "', '" + notas + "')";
cmd.ExecuteNonQuery();
}
// Get request body
dynamic data = await req.Content.ReadAsAsync<object>();
return name == req.CreateResponse(HttpStatusCode.OK, "Done");
}
catch (Exception ex)
{
HttpResponseMessage res = req.CreateErrorResponse(HttpStatusCode.InternalServerError, ex);
return res;
}
}
What I'm looking for is to to this by POST
In case google took you here, this is how it's done in March 2019 (Azure Functions v3):
public static async void Run(
[HttpTrigger(AuthorizationLevel.Function, "post", Route = null)]
HttpRequest req,
ILogger log)
{
var content = await new StreamReader(req.Body).ReadToEndAsync();
MyClass myClass = JsonConvert.DeserializeObject<MyClass>(content);
}
To get the request content from the request body(post request), you could use req.Content.ReadAsAsync method. Here is the code sample.
Sample request body.
{
"name": "Azure"
}
Define a class to deserialize the post data.
public class PostData
{
public string name { get;set; }
}
Get the post data and display it.
PostData data = await req.Content.ReadAsAsync<PostData>();
log.Info("name:" + data.name);
Client side code to send the post request.
HttpWebRequest req = (HttpWebRequest)WebRequest.Create("function-url");
req.Method = "POST";
req.ContentType = "application/json";
Stream stream = req.GetRequestStream();
string json = "{\"name\": \"Azure\" }";
byte[] buffer = Encoding.UTF8.GetBytes(json);
stream.Write(buffer,0, buffer.Length);
HttpWebResponse res = (HttpWebResponse)req.GetResponse();
If you are using System.Text.Json, you can read the POST data in one line:
public static async Task Run(
[HttpTrigger(AuthorizationLevel.Function, "post", Route = null)]
HttpRequest req,
ILogger log)
{
MyClass myClass = await JsonSerializer.DeserializeAsync<MyClass>(req.Body);
}
If you are using Newtonsoft.Json, see the answer by Allen Zhang.
For passing parameters as POST request, you need to do following things:
Make Json model of the parameters that u need to pass,ex:
{"UserProfile":{ "UserId":"xyz1","FirstName":"Tom","LastName":"Hank" }}
Post your data model using client like POSTMAN
Now you will get the posted content in HttpRequestMessage body, sample code is as follows:
[FunctionName("TestPost")]
public static HttpResponseMessage POST([HttpTrigger(AuthorizationLevel.Function, "put", "post", Route = null)]HttpRequestMessage req, TraceWriter log)
{
try
{
//create redis connection and database
var RedisConnection = RedisConnectionFactory.GetConnection();
var serializer = new NewtonsoftSerializer();
var cacheClient = new StackExchangeRedisCacheClient(RedisConnection, serializer);
//read json object from request body
var content = req.Content;
string JsonContent = content.ReadAsStringAsync().Result;
var expirytime = DateTime.Now.AddHours(Convert.ToInt16(ConfigurationSettings.AppSettings["ExpiresAt"]));
SessionModel ObjModel = JsonConvert.DeserializeObject<SessionModel>(JsonContent);
bool added = cacheClient.Add("RedisKey", ObjModel, expirytime); //store to cache
return req.CreateResponse(HttpStatusCode.OK, "RedisKey");
}
catch (Exception ex)
{
return req.CreateErrorResponse(HttpStatusCode.InternalServerError, "an error has occured");
}
}
You can just supply your custom data class as a parameter to the HttpTrigger argument. This way you don't have to mess with the json deserialization yourself:
public async Task<IActionResult> UpdateAccount(
[HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = "api/v1/accounts/{id:guid}")]
SomeData someData, // <----- Post body ends up here automatically
HttpRequest req,
Guid id,
ILogger log)
{
log.LogInformation ("Got POST with " + someData.Foo);
}
public class SomeData
{
public string Foo { get; set; } = null!;
}
The query string (name/value pairs) is by default sent in the HTTP message body of a POST request and not as query string. The GetQueryNameValuePairs method will parse the query string and will by default not work with POST request.
For the POST request you could use something similar to this:
var content = request.Content;
string contentInString = content.ReadAsStringAsync().Result;
You need to attach data to the body of the post request and process it properly:
public static async Task<HttpResponseMessage> Run(HttpRequestMessage req, TraceWriter log) {
// This reads your post request body into variable "data"
string data = await req.Content.ReadAsStringAsync();
// Here you can process json into an object
dynamic parsed = JsonConvert.DeserializeObject(data);
return exitstring == null
? req.CreateResponse(HttpStatusCode.BadRequest, "Something went wrong, sorry")
: req.CreateResponse(HttpStatusCode.OK);
}
You can find a slightly different example here and the exact example here.
It can be done in following way with custom class
Azure Function
[FunctionName("PostParameterFunction")]
public static async Task<HttpResponseMessage> Run([HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)]HttpRequestMessage req, ILogger log)
{
log.LogInformation("C# HTTP trigger function processed a request.");
try
{
// Convert all request perameter into Json object
var content = req.Content;
string jsonContent = content.ReadAsStringAsync().Result;
dynamic requestPram = JsonConvert.DeserializeObject<RequestModel>(jsonContent);
// Validate the required param
if (string.IsNullOrEmpty(requestPram.FirstName))
{
return req.CreateResponse(HttpStatusCode.OK, "Please enter First Name!");
}
if (string.IsNullOrEmpty(requestPram.LastName))
{
return req.CreateResponse(HttpStatusCode.OK, "Please enter Last Name!");
}
//Create object for partner Model to bind the response on it
RequestModel objRequestModel = new RequestModel();
objRequestModel.FirstName = requestPram.FirstName;
objRequestModel.LastName = requestPram.LastName;
//Return Request Model
return req.CreateResponse(HttpStatusCode.OK, objRequestModel);
}
catch (Exception ex)
{
return req.CreateResponse(HttpStatusCode.OK, "Cannot Create Request! Reason: {0}", string.Format(ex.Message));
}
}
Request Class:
public class RequestModel
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
Request Input:
{
"FirstName": "Kiron",
"LastName":"Test"
}
PostMan Output Example:
I have done a very simple example to get data using POST request in Azure Function App. Please find the following example.
using System;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.Azure.WebJobs.Host;
namespace MyFunctions
{
public static class MyFunctionsOperations
{
[FunctionName("MyFunctionsOperations")]
public static async Task<HttpResponseMessage> Run([HttpTrigger(AuthorizationLevel.Function, "post", Route = null)]HttpRequestMessage req, TraceWriter log)
{
log.Info("C# HTTP trigger function processed a request.");
var headers = req.Headers;
string collection = headers.GetValues("collection").First(); //getting parameter from header
CosmosdbOperation obj = new CosmosdbOperation();
dynamic data = await req.Content.ReadAsAsync<object>(); //getting body content
Boolean response = await obj.MyFunctionExecution(data.ToString(), collection);
return (response)
? req.CreateResponse(HttpStatusCode.BadRequest, "Please pass a proper argument in the request body")
: req.CreateResponse(HttpStatusCode.OK, "Operation successfully executed..");
}
}
}
I like the WebApi approach of using [FromBody] attribute, so using IBinding I made my own. Now I can just pass in the object.
[AttributeUsage(AttributeTargets.Parameter | AttributeTargets.ReturnValue)]
[Binding]
public sealed class FromBodyAttribute : Attribute
{
}
public class FromBodyBinding : IBinding
{
private readonly ILogger logger;
public FromBodyBinding(ILogger logger)
{
this.logger = logger;
}
public Task<IValueProvider> BindAsync(BindingContext context)
{
// Get the HTTP request
var request = context.BindingData["req"] as DefaultHttpRequest;
return Task.FromResult<IValueProvider>(new FromBodyValueProvider(request, logger));
}
public bool FromAttribute => true;
public Task<IValueProvider> BindAsync(object value, ValueBindingContext context)
{
return null;
}
public ParameterDescriptor ToParameterDescriptor() => new ParameterDescriptor();
}
public class FromBodyBindingProvider : IBindingProvider
{
private readonly ILogger logger;
public FromBodyBindingProvider(ILogger logger)
{
this.logger = logger;
}
public Task<IBinding> TryCreateAsync(BindingProviderContext context)
{
IBinding binding = new FromBodyBinding(this.logger);
return Task.FromResult(binding);
}
}
public class FromBodyValueProvider : IValueProvider
{
private HttpRequest request;
private ILogger logger;
public FromBodyValueProvider(HttpRequest request, ILogger logger)
{
this.request = request;
this.logger = logger;
}
public async Task<object> GetValueAsync()
{
try
{
string requestBody = await new StreamReader(this.request.Body).ReadToEndAsync();
object result = JsonConvert.DeserializeObject(requestBody);
return result;
}
catch (System.Exception ex)
{
this.logger.LogCritical(ex, "Error deserializing object from body");
throw ex;
}
}
public Type Type => typeof(object);
public string ToInvokeString() => string.Empty;
}
public class BindingExtensionProvider : IExtensionConfigProvider
{
private readonly ILogger logger;
public BindingExtensionProvider(ILogger<Startup> logger)
{
this.logger = logger;
}
public void Initialize(ExtensionConfigContext context)
{
// Creates a rule that links the attribute to the binding
context.AddBindingRule<FromBodyAttribute>().Bind(new FromBodyBindingProvider(this.logger));
}
}
Then inside your Startup.cs file, add the binding.
public class Startup : IWebJobsStartup
{
public void Configure(IWebJobsBuilder builder)
{
JsonConvert.DefaultSettings = () =>
{
return new JsonSerializerSettings()
{
ContractResolver = new DefaultContractResolver
{
NamingStrategy = new CamelCaseNamingStrategy()
},
Formatting = Formatting.Indented
};
};
builder.Services.AddLogging();
builder.AddExtension<BindingExtensionProvider>();
}
}
Now you can just have a regular old class, just like WebApi!
[FunctionName("MyFunction")]
public async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = null)] HttpRequest req,
[Binding.FromBody] dynamic data) // or you can change 'dynamic' to some class
{
string username = data?.username;
...
}
Here is the point --> https://learn.microsoft.com/en-us/azure/azure-functions/functions-bindings-http-webhook-trigger?tabs=in-process%2Cfunctionsv2&pivots=programming-language-csharp
[FunctionName("LuckyNumber")]
public static async Task<IActionResult> Run(
[HttpTrigger(
AuthorizationLevel.Function,
"get", "post",
Route = "max/{max:int?}/min/{min:int?}")] HttpRequest req,
int? max, <-- Parameter max
int? min, <-- Parameter min
ILogger log)
{
int? maxInternal = max;
int? minInternal = min;
}
PS: I´m using .NET 6
We can do it by just one line code using System.Text.Json.
public static async void Run(
[HttpTrigger(AuthorizationLevel.Function, "post", Route = null)]
HttpRequest req,
ILogger log)
{
MyClass myClass = await JsonSerializer.DeserializeAsync<MyClass>(req.Body);
}