How to call one function app from another locally? - c#

In Visual Studio, I have created 2 Azure function apps f1 and f2.
I have already change the port for both function apps.
I want to call f2 from f1 but I'm getting a NotFound error.
f1 is using http://localhost:999/demo1
f2 is using http://localhost:1212/demo2

I have tried calling one Function to Another Function within the same project and different project too both works fine.
Function 1 Example:
[FunctionName("Function1")]
public static async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequest req,
ILogger log)
{
log.LogInformation("C# HTTP trigger function processed a request.");
try
{
//Extract Request Param
var content = await new StreamReader(req.Body).ReadToEndAsync();
QnAMakerQuestion objQnAMakerQuestion = JsonConvert.DeserializeObject<QnAMakerQuestion>(content);
//Global Variable for containing message
dynamic validationMessage;
// Validate param
if (string.IsNullOrEmpty(objQnAMakerQuestion.question))
{
validationMessage = new OkObjectResult("Question is required!");
return (IActionResult)validationMessage;
}
//Selialize Request Param
var json = JsonConvert.SerializeObject(objQnAMakerQuestion);
var stringContent = new StringContent(json, UnicodeEncoding.UTF8, "application/json");
// Call Function 2
HttpClient newClient = new HttpClient();
HttpResponseMessage responseFromAnotherFunction = await newClient.PostAsync("http://localhost:7073/api/Function2FromApp2", stringContent);
dynamic response = "";
if (responseFromAnotherFunction.IsSuccessStatusCode)
{
response = responseFromAnotherFunction.Content.ReadAsStringAsync().Result;
}
validationMessage = new OkObjectResult(response);
return (IActionResult)validationMessage;
}
catch (Exception ex)
{
dynamic validationMessage = new OkObjectResult(string.Format("Something went wrong, please try agian! Reason:{0}", ex.Message));
return (IActionResult)validationMessage;
}
}
Function 2 Example:
[FunctionName("Function2FromApp2")]
public static async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequest req,
ILogger log)
{
log.LogInformation("C# HTTP trigger function processed a request.");
try
{
var content = await new StreamReader(req.Body).ReadToEndAsync();
QnAMakerQuestion objQnAMakerQuestion = JsonConvert.DeserializeObject<QnAMakerQuestion>(content);
//Global Variable for containing message
dynamic validationMessage;
// Validate param
if (string.IsNullOrEmpty(objQnAMakerQuestion.question))
{
validationMessage = new OkObjectResult("Question is required!");
return (IActionResult)validationMessage;
}
validationMessage = new OkObjectResult(objQnAMakerQuestion);
return (IActionResult)validationMessage;
}
catch (Exception ex)
{
dynamic validationMessage = new OkObjectResult(string.Format("Something went wrong, please try agian! Reason:{0}", ex.Message));
return (IActionResult)validationMessage;
}
}
Class Used:
public class QnAMakerQuestion
{
public string question { get; set; }
}
Note: If you run in same project then you wouldn't encounter any problem. But if you run in different project encounter a issue
regarding port. To resolve that in local.settings.json file replace
below code :
"Host": {
"LocalHttpPort": 7073
}
And Update Project Properties -> Debug to following
host start --port 7073 --pause-on-error See the screen shot below:
Post Man Test:
I have invoked Function 1 on PostMan it Invoked Function 1 as Function 1 Invoked Function 2 and Send Response or vice-versa from function 2 to function 1. See the screen shot below:
Just plug and play, let me know if you have any additional question.

Related

C# Azure function add record but check first if it already exist cosmosdb using IMongoCollection

I have the following Azure function using C# that creates/adds a student to cosmosdb and it works just fine
[FunctionName("CreateStudent")]
public static async Task<IActionResult> CreateStudent(
[HttpTrigger(AuthorizationLevel.Function, "post", Route = "student/create")] HttpRequest req,
[CosmosDB(
databaseName: "school",
collectionName: "students",
ConnectionStringSetting = "CosmosAccountEnpoint")] IAsyncCollector<Student> studentCollector,
ILogger log)
{
log.LogInformation("Creating a new student.");
string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
var student = JsonConvert.DeserializeObject<Student>(requestBody);
await studentCollector.AddAsync(student);
return new OkObjectResult(student);
}
Now the 'issue' with this approach is that if the student doesn't exist it creates it which is the expected behavior, but if the student already exists it updates the record with the latest data I'm submitting.
Did some searching and IAsyncCollector only supports the method AddAsync which it doesn't have any type of validations. I found that using IMongoCollection should work because it has methods which will let me put some validations in place.
I changed the code to the following
[FunctionName("CreateStudent")]
public static async Task<IActionResult> CreateStudent(
[HttpTrigger(AuthorizationLevel.Function, "post", Route = "student/create")] HttpRequest req,
[CosmosDB(
databaseName: "school",
collectionName: "students",
ConnectionStringSetting = "CosmosAccountEnpoint")]
IMongoCollection<Student> studentCollection,
ILogger log)
{
log.LogInformation("Creating a new student");
string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
var student = JsonConvert.DeserializeObject<Student>(requestBody);
if (student == null)
{
return new BadRequestObjectResult("Please pass a valid student in the request body");
}
var count = await studentCollection.CountDocumentsAsync(s => s.id == student.id);
if (count > 0)
{
return new BadRequestObjectResult($"Student with ID {student.id} already exists");
}
var studentexist = student.id;
await studentCollection.InsertOneAsync(student);
return new OkObjectResult(student);
}
After this code change i don't get any errors and everything seems to be ok, the problem is that when i run it i get the following error
Function 'CreateStudent' failed indexing and will be disabled.
[2023-02-13T15:14:55.796Z] The 'CreateStudent' function is in error: Microsoft.Azure.WebJobs.Host: Error indexing method 'CreateStudent'. Microsoft.Azure.WebJobs.Host: Can't bind CosmosDB to type 'MongoDB.Driver.IMongoCollection`1[School.Student]'.
what am i doing wrong?

Azure Functions Synchronous operations are disallowed Why?

I am working on two functions in Azure Functions
Timer Function and Http Trigger Function
My timer functions runs every 1 hour and it executes the http function via an Http Client.
Now I do get an error Synchronous operations are disallowed
And I know how to solve this using the article on stack overflow
But I am curious as why am I getting this error?
Whats the cause of it?
The error doesn't occur when using Postman.
My Timer Code
Azure Functions Core Tools (3.0.2245 Commit hash: 1d094e2f3ef79b9a478a1621ea7ec3f93ac1910d)
Function Runtime Version: 3.0.13139.0
Host configuration file read:
{
"version": "2.0"
}
public static class DoSomeStuffTimer
{
[FunctionName("DoSomeStuffTimer")]
public static void Run([TimerTrigger("0 0 7 * * *")]TimerInfo myTimer, ILogger log)
{
try
{
log.LogInformation($"C# DoSomeStuffTimer executing at: {DateTime.Now}");
string url = Environment.GetEnvironmentVariable(EnvironmentKey.HostKey) + "/api/DoSomeStuff";
HttpClient client = new HttpClient();
client.PostAsJsonAsync(url, new DoSomeStuffRequest());
log.LogInformation($"C# DoSomeStuffTimer executed at: {DateTime.Now}");
}
catch (Exception e)
{
log.LogInformation(e.ToString());
}
}
}
My Http Code
public class DoSomeStuffFunction
{
[FunctionName("DoSomeStuffFunction")]
public async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Function, "post", Route = "DoSomeStuff")]
HttpRequestMessage req,
ILogger log)
{
var response = new ContentResult {ContentType = "application/json", StatusCode = 200};
try
{
DoSomeStuffRequest
request = req.Content.ReadAsAsync<DoSomeStuffRequest>().Result;
}
catch (Exception e)
{
log.LogInformation(e.ToString());
}
}
}
Starting with ASP.NET Core 3.0 synchronous calls are disabled by default So any function running on 3.0 will encounter this when it tries to do synchronous calls
I looked into it and found the reason why it happens in your function is that ReadAsAsync<>() somewhere in it's operation does something synchronously. I am not sure exactly why it does this or why it doesn't break when you call the httptrigger directly. That'll require quite a bit more work to figure out.
To make your code work without FUNCTIONS_V2_COMPATIBILITY_MODE set to True you can use one of the other readers, for example ReadAsStreamAsync() instead.
Below you can find the method that works (I tested it locally). However, I would not recommend you call another function in your function app directly and instead follow the recommendations by Microsoft or create an abstraction that contains the logic that both functions can call on independently.
public class DoSomeStuffFunction
{
[FunctionName("DoSomeStuffFunction")]
public async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Function, "post", Route = "DoSomeStuff")]
HttpRequestMessage req,
ILogger log)
{
var response = new ContentResult { ContentType = "application/json", StatusCode = 200 };
try
{
var request = await req.Content.ReadAsStreamAsync();
using (StreamReader rd = new StreamReader(request))
{
var result = JsonConvert.DeserializeObject<DoSomeStuffRequest>(await rd.ReadToEndAsync()).;
}
return new OkObjectResult(response);
}
catch (Exception e)
{
log.LogInformation(e.ToString());
return new BadRequestObjectResult("It went wrong");
}
}
}
The solution you mentioned is to set the variable FUNCTIONS_V2_COMPATIBILITY_MODE to true. We can see some information about this variable in this page.
So,did you do the operation to upgraded your function from v2 to v3 ? It may cause this issue.
Update:
I test it in my side on local visual studio. When I create the function app in visual studio, I choose azure function v3(.net core). And below is my code, it works fine without any error.
namespace FunctionApp8
{
public static class Function2
{
[FunctionName("Function2")]
public static void Run([TimerTrigger("0 */1 * * * *")]TimerInfo myTimer, ILogger log)
{
log.LogInformation($"C# Timer trigger function executed at: {DateTime.Now}");
string url = "http://localhost:7071/api/triggerFunction";
HttpClient client = new HttpClient();
client.PostAsJsonAsync(url, "");
log.LogInformation($"C# DoSomeStuffTimer executed at: {DateTime.Now}");
}
}
}
namespace FunctionApp8
{
public static class Function1
{
[FunctionName("triggerFunction")]
public static async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req,
ILogger log)
{
log.LogInformation("C# HTTP trigger function processed a request.");
return (ActionResult)new OkObjectResult($"Hello");
}
}
}
I'm not sure if your function app is related to .net core 2.x. So could you please follow the steps recreate another function app(check if your visual studio has been updated to the lastest version and choose Azure Function v3 .NET core at the beginning) and test if it works fine.

ASP.NET Core 2.2 WebAPI: How to make PushStreamContent work

I'm trying to implement a simple push notification mechanism for a webpage. So I created a WebAPI controller with a method like this:
[HttpGet]
public HttpResponseMessage Subscribe()
{
var response = new HttpResponseMessage(HttpStatusCode.OK);
response.Content = new PushStreamContent(
(stream, headers, context) => OnStreamAvailable(stream, headers, context),
"text/event-stream"
);
return response;
}
But when I try to call it from the client code:
function listen() {
if (!!window.EventSource) {
const server = new EventSource('http://localhost:5000/api/Notifications/');
server.addEventListener('message', function (e) {
const json = JSON.parse(e.data);
console.log('message', json);
});
server.addEventListener('open', function (e) {
console.log('open');
});
server.addEventListener('error', function (e) {
if (e.readyState === EventSource.CLOSED) {
console.log('error');
}
});
}
}
Chrome replies me with an: EventSource's response has a MIME type ("application/json") that is not "text/event-stream". Aborting the connection.
I have to add that the code I'm writing is based on this tutorial (which uses MVC5).
My question is: How can I make the Subscribe method work? Thanks in advance.

Correct use of Azure Durable Function - Serializing Complex Objects

So I'm prototyping some Azure Durable Functions, to try and understand to see if they will fit within a proposed solution for our internal API system.
Based on examples, I've created a Orchestrator Client (HelloOrchestratorClient.cs), that responds to a HttpTrigger. This client extracts some information from the original request, then proceeds to fire off a Orchestrator Function (HelloOrchestrator.cs) passing in some of the information extracted:
Complex HelloOrchestratorClient.cs:
[FunctionName("HttpSyncStart")]
public static async Task<HttpResponseMessage> Run(
[HttpTrigger(AuthorizationLevel.Anonymous, methods: "get", Route = "orchestrators/{functionName}/wait")]
HttpRequestMessage req,
[OrchestrationClient] DurableOrchestrationClient starter,
string functionName,
ILogger log)
{
HttpReq originalRequest = new HttpReq() {
DeveloperId = GetDevKey(req,apiHeaderKey),
QueryString = req.RequestUri.Query,
APIName = GetQueryStringValue(req,APIName),
APIVersion = GetQueryStringValue(req,APIVersion)
};
string instanceId = await starter.StartNewAsync(functionName, originalRequest);
TimeSpan timeout = GetTimeSpan(req, Timeout) ?? TimeSpan.FromSeconds(30);
TimeSpan retryInterval = GetTimeSpan(req, RetryInterval) ?? TimeSpan.FromSeconds(1);
return await starter.WaitForCompletionOrCreateCheckStatusResponseAsync(
req,
instanceId,
timeout,
retryInterval);
}
The HelloOrchestrator.cs simply for now is just calling off to one of our internal API's and returning a JsonProduct payload (Simple POCO describing, you guessed it, a title), using a ActivityTigger named HelloOrchestrator.APICall to make the API call itself.
Complex HelloOrchestrator.cs:
[FunctionName("E1_JsonProduct")]
public static async Task<List<JsonProduct>> Run(
[OrchestrationTrigger] DurableOrchestrationContextBase context,
ILogger log)
{
List<JsonProduct> output = new List<JsonProduct>();
HttpReq r = context.GetInput<HttpReq>();
if(r != null)
{
if(r.DeveloperId == null)
{
return output;
}
output.Add(await context.CallActivityAsync<JsonProduct>("E1_CallAPI",r));
return output;
}
return output;
}
[FunctionName("E1_CallAPI")]
public async static Task<JsonProduct> APICall([ActivityTrigger] HttpReq req,
ILogger log)
{
JsonProduct products = null;
string u = $"{baseAddress}{req.APIVersion}/{req.APIName}{req.QueryString}";
var request = new HttpRequestMessage(HttpMethod.Get, u);
request.Headers.Accept.Add(
new MediaTypeWithQualityHeaderValue("application/json")
);
request.Headers.Add("x-apikey",req.DeveloperId);
log.LogInformation($"URL calling = '{request.RequestUri.AbsoluteUri}'.");
HttpResponseMessage response = await client.SendAsync(request);
// return await response.Content.ReadAsStringAsync();
if(response.IsSuccessStatusCode)
{
var formatter = new JsonMediaTypeFormatter
{
SerializerSettings = HelloProj.CosmosDB.Models.Products.Converter.Settings
};
products = await response.Content.ReadAsAsync<JsonProduct>(new [] {formatter});
}
return products;
}
Side Note: The plan is if I can get this to work, is to fan out a bunch of processes to different API's and fan back in again and merge the JSON payload and return it back to the originator.
Issue I'm experiencing
So, when my List<JsonProduct> is returned back from HelloOrchestrator.Run, I receive the following NullReferenceException found on this Gist (Big stack trace) and I receive a 500 response from the Orchestrator Client.
The following proves the output returned does actually have an object at runtime:
Could it be due to the complexity of JsonProduct (Again find the model classes here)? I ask, because when I swap out my Orchestrator Function for a simpler model structure, I don't receive a 500, I receive my JSON Payload.
This example shows the Simple Orchestrator Function HelloOrchestrator.cs, returning a simple TestToDo.cs (Gist for model) flat object that doesn't error:
Simple HelloOrchestrator.cs:
[FunctionName("E1_Todo")]
public static async Task<TestToDo> RunToDo(
[OrchestrationTrigger] DurableOrchestrationContextBase context,
ILogger log)
{
HttpReq r = context.GetInput<HttpReq>();
TestToDo todo = new TestToDo();
if(r != null)
{
todo = await context.CallActivityAsync<TestToDo>("E1_CallAPITodo",r);
}
return todo;
}
[FunctionName("E1_CallAPITodo")]
public async static Task<TestToDo> APITodoCall([ActivityTrigger] HttpReq req,
ILogger log)
{
var request = new HttpRequestMessage(HttpMethod.Get, "https://jsonplaceholder.typicode.com/todos/1");
request.Headers.Accept.Add(
new MediaTypeWithQualityHeaderValue("application/json")
);
log.LogInformation($"URL calling = '{request.RequestUri.AbsoluteUri}'. for {req.QueryString}");
HttpResponseMessage response = await client.SendAsync(request);
return await response.Content.ReadAsAsync<TestToDo>();
}
More Information
If you require my full prototype projects, you can find them here:
Complex Project (Throws 500 and exception)
When you run it, use the following in something like Postman (After F5ing it):
http://localhost:7071/api/orchestrators/E1_JsonProduct/wait?timeout=20&retryInterval=0.25&api=products&apiVersion=v1&filterByImprints=W%26N&N
Simple Project (No 500 or Exception thrown)
When you run it, use the following in something like Postman (after F5ing it):
http://localhost:7071/api/orchestrators/E1_Todo/wait?timeout=20&retryInterval=0.25
Looking at the callstack you posted, the NullReferenceException appears to be a bug in the DurableOrchestrationClient class. Looking at the code (which you can find here) is seems possible that if the query string you're using cannot be parsed correctly, a null-ref is possible.
You mentioned you're using the following URL for testing:
http://localhost:7071/api/orchestrators/E1_JsonProduct/wait?timeout=20&retryInterval=0.25&api=products&apiVersion=v1&filterByImprints=W%26N&N
I wonder if the last two characters (&N) are the source of the problem. Is is possible to encode the & or remove it entirely to isolate the problem?
Either way, it would be great if you could log an issue here: https://github.com/Azure/azure-functions-durable-extension/issues

Get Http response headers and content Angular 6

I have the following web server method, that returns data to our front-end applicaiton.
[FunctionName("SearchCustomerBySearchTerm")]
public static async Task<HttpResponseMessage> SearchCustomerBySearchTerm([HttpTrigger(AuthorizationLevel.Function, WebRequestMethods.Http.Get, Route = "Customer/SearchCustomerBySearchTerm/{searchTerm}/pageSize/{pageSize}")]HttpRequestMessage req, TraceWriter log, string searchTerm, int pageSize)
{
try
{
var continuationToken = req.Headers.TryGetValues("continuationToken", out IEnumerable<string> values) ? values.FirstOrDefault() : null;
PagedResponse<CustomerSearchResult> pagedResponse = await _customerComponent.FindCustomerBy(searchTerm, continuationToken, pageSize);
if (pagedResponse == null) return req.CreateResponse(HttpStatusCode.NoContent, $"Could not find any data related to {searchTerm}");
HttpResponseMessage responseMessage = req.CreateResponse(HttpStatusCode.OK, pagedResponse.Results);
responseMessage.Content.Headers.Add("continuationToken", pagedResponse.Continuation);
responseMessage.Content.Headers.Add("Access-Control-Expose-Headers", "*");
return responseMessage;
}
catch (Exception ex)
{
log.Error(ex.Message);
return req.CreateResponse(HttpStatusCode.InternalServerError, "Something went wrong. Could not search for customers");
}
}
I am allowing all headers to be exposed, by adding the Access-Control-Expose-Headers.
From my Angular application, I am doing the request as follow:
searchCustomersPaged(searchTerm: string, continuationToken: string): Observable<HttpResponse<CustomerSearchResult>> {
let customHeaders = new HttpHeaders().set("continuationToken", this.currentContinuationToken);
const url = "http://localhost:7071/api/Customer/SearchCustomerBySearchTerm/andrew/pageSize/10";
const parsedUrl = encodeURI(url);
return this.http.get<HttpResponse<CustomerSearchResult>>(parsedUrl, { headers: customHeaders });
}
As you can see above, I am expecting an HttpResponse<CustomerSearch> back.
Here is how I try and read my headers:
nextClikcedHandle(continuationToken: string): void {
this.customerService.searchCustomersPaged(this.customerService.searchTerm, this.currentContinuationToken)
.subscribe(resp => {
//add current continuation token, to previous now, as this will be used for 'previous' searching
this.previousContinuationTokens.push(this.currentContinuationToken);
//set next continuation token received by server
this.currentContinuationToken = resp.headers.get('continuationToken');
//return search results
this.customerService.searchResults.next(resp.body);
});
}
With the above code, the resp.headers and the resp.body is always undefined. Why is this happening?
If I look at the Network tab within Chrome, I can see my data is returned, as well as my header.
What am I doing wrong?
I found a useful article here:
By default the HttpClient returns the body of the response. You can
pass-in an object with an observe key set to a value of ‘response’ to
get the full response. This can be useful to inspect for certain
headers:
So I changed my code as follow, with the added observe key.
searchCustomersPaged(searchTerm: string, continuationToken: string): Observable<HttpResponse<CustomerSearchResult>> {
let customHeaders = new HttpHeaders().set("continuationToken", this.currentContinuationToken);
const url = "http://localhost:7071/api/Customer/SearchCustomerBySearchTerm/andrew/pageSize/10";
const parsedUrl = encodeURI(url);
return this.http.get<CustomerSearchResult>(parsedUrl, { headers: customHeaders, observe: 'response' });
}
After changing above method, I could query body and headers as per normal:
nextClikcedHandle(continuationToken: string): void {
this.customerService.searchCustomersPaged(this.customerService.searchTerm, this.currentContinuationToken)
.subscribe(resp => {
//add current continuation token, to previous now, as this will be used for 'previous' searching
this.previousContinuationTokens.push(this.currentContinuationToken);
//set next continuation token received by server
this.currentContinuationToken = resp.headers.get('continuationToken');
//return search results
this.customerService.searchResults.next(resp.body);
});

Categories

Resources