azure-functionsazure-http-trigger

How to retrieve JSON string from the request body of POST method in Azure Functions?


From my JavaScript client, I send the JSON data using POST method

fetch("https://%$%$%%$.azurewebsites.net/api/JointPose?", {
                    method: "POST",
                    body: JSON.stringify({
                        JointPose1: `${Joint1Pose}`,
                        JointPose2: `${Joint2Pose}`,
                        JointPose3: `${Joint3Pose}`,
                        JointPose4: `${Joint4Pose}`,
                        JointPose5: `${Joint5Pose}`,
                        JointPose6: `${Joint6Pose}`,
                        JointPose7: `${Joint7Pose}`
                    }),
                    headers: {
                        "Content-Type": "application/json; charset=UTF-8",
                    }
                })
                    .then(response => {
                        if (!response.ok) {
                            throw new Error('Network response was not ok ' + response.statusText);
                        }
                        return response.json();
                    })
                    .then(data => {
                        console.log('Success:', data);
                    })
                    .catch(error => {
                        console.error('Error:', error);
                    });

I try to retrieve the request body using HTTPtrigger in my Azure Functions app

public static async Task<IActionResult> Run(
            [HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = "JointPose")] HttpRequest req)
        {
            MemoryStream ms = new MemoryStream();
            await req.Body.CopyToAsync(ms);
            string jointPoseData = ms.ToString();

            //using StreamReader reader = new(req.Body);
            //string bodyStr = await reader.ReadToEndAsync();
            //var jointPoseData = JsonConvert.DeserializeObject<JointPoseData>(bodyStr);

            return new OkObjectResult( new { Content = new StringContent(JsonConvert.SerializeObject(jointPoseData), Encoding.UTF8, "application/json") });
        }

public class JointPoseData
    {
        public string JointPose1 { get; set; }
        public string JointPose2 { get; set; }
        public string JointPose3 { get; set; }
        public string JointPose4 { get; set; }
        public string JointPose5 { get; set; }
        public string JointPose6 { get; set; }
        public string JointPose7 { get; set; }
    }

The response I get after opening the URL is

When opened the URL link in browser

Expected output is:

JointPose1: 0.31, JointPose2: 0.32,.......

Revised script for the function

[FunctionName("GetJointData")]
        public static async Task<IActionResult> GetJointData(
            [HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = "JointPose")] HttpRequest req)
        {
            using StreamReader reader = new(req.Body);
            string bodyStr = await reader.ReadToEndAsync();
            await serviceHubContext.Clients.All.SendAsync("jointPose", JsonConvert.SerializeObject(bodyStr));
            return new OkObjectResult(new
            {
                Content = JsonConvert.SerializeObject(bodyStr)
            });
        }

I get this response in the console of my browser

Content is null

I think the jointPoseData is null. How do I retrieve the request body with appropriate values?


Solution

  • You are on the right track but there are some clarifications that I can make.

    First, the code that you have commented to read the stream and convert it to a string is correct.

    //using StreamReader reader = new(req.Body);
    //string bodyStr = await reader.ReadToEndAsync();
    //var jointPoseData = JsonConvert.DeserializeObject<JointPoseData>(bodyStr);
    

    The code that you have uncommented for reading the stream would not work because of ms.ToString();. In C#, every class has a .ToString() method because every class is derived from Object which contains this method. Classes can override this method but by default it just outputs the name of the type. Therefore in your code the "ms" variable will just hold the value of "System.IO.MemoryStream".

    Moving onto the return of the function, we need to use OkObjectResult which you have correctly used, and we can simply pass in the string into the constructor. In case you would like to have the string under the "Content" property, we can pass an anonymous object with the "Content" property and give the string as the value. StringContent would not be applicable here as it is a class used in HttpClients. It should also be noted that if all you mean to do is output the request body, there is no need to Deserialize and then Serialize back.

    This results in the following code:

    using Microsoft.AspNetCore.Http;
    using Microsoft.AspNetCore.Mvc;
    using Microsoft.Azure.Functions.Worker;
    
    namespace Payments.PaymentInstrumentRouter
    {
        public class TestingFunction
        { 
            [Function("TestingFunction")]
            public async Task<IActionResult> Run([HttpTrigger(AuthorizationLevel.Anonymous, "get", "post")] HttpRequest req)
            {
                using StreamReader reader = new(req.Body);
                string bodyStr = await reader.ReadToEndAsync();
    
                return new OkObjectResult(new
                {
                    Content = bodyStr
                });
            }
        }
        public class JointPoseData
        {
            public string JointPose1 { get; set; }
            public string JointPose2 { get; set; }
            public string JointPose3 { get; set; }
            public string JointPose4 { get; set; }
            public string JointPose5 { get; set; }
            public string JointPose6 { get; set; }
            public string JointPose7 { get; set; }
        }
    }
    

    Now going onto calling the function. If you call the function using the script you have shared using the Chrome Dev tools or in postman, you should be able to see the output content with the request body:

    enter image description here

    enter image description here

    Hopefully this has answered your question. Please share if there are any doubts.

    EDIT:

    In the comments, a question was asked which was "How can the data sent in the POST request body be viewed in a different request?

    We can use a static variable to hold the value of the request body. Static variables are associated with the class itself rather than instances of the class, and hence they can be used to persist data across different requests. We can also add a conditional to set the value of this variable only if we are doing a POST request.

    public class TestingFunction
    {
        private static string RequestBody { get; set; }
    
        [Function("TestingFunction")]
        public async Task<IActionResult> Run([HttpTrigger(AuthorizationLevel.Anonymous, "get", "post")] HttpRequest req)
        {
            using StreamReader reader = new(req.Body);
            string bodyStr = await reader.ReadToEndAsync();
    
            if (req.Method == "POST")
            {
                RequestBody = bodyStr;
            }
    
            return new OkObjectResult(new
            {
                Content = RequestBody
            });
        }
    }