How to configure OpenApiRequestBody in Azure Function? - c#

I want to decorate my azure c# function with OpenApi annotations. The function accept JSON schema as parameters. How to mention that in the annotation.
Want to know how to configure below annotation
[OpenApiRequestBody(contentType: "json", bodyType: typeof(System.Text.Json.JsonDocument), Description = "Parameters",Example =typeof(Parameters))]
public class ModifyOrder
{
[FunctionName("ModifyOrder")]
[OpenApiOperation(operationId: "run", tags: new[] { "Modify Order" })]
[OpenApiSecurity("function_key", SecuritySchemeType.ApiKey, Name = "code", In = OpenApiSecurityLocationType.Query)]
[OpenApiRequestBody(contentType: "json", bodyType: typeof(System.Text.Json.JsonDocument), Description = "Parameters",Example =typeof(Parameters))]
[OpenApiResponseWithBody(statusCode: HttpStatusCode.OK, contentType: "text/plain", bodyType: typeof(string), Description = "The OK response")]
public static async Task<IActionResult> run(
[HttpTrigger(AuthorizationLevel.Function, "put", Route = null )] HttpRequest req,
ILogger log)
{
log.LogInformation($"C# HTTP trigger function processed a request.");
string ordernumber;
string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
dynamic data = JsonConvert.DeserializeObject(requestBody);
ordernumber = data?.orderno;
string responseMessage = $"Order:{ordernumber}";
return new OkObjectResult(responseMessage);
}
}
[OpenApiExample(typeof(Parameters))]
public class Parameters
{
/// <summary>The id of the customer in the context. This is also called payer, sub_account_id.</summary>
[Newtonsoft.Json.JsonProperty("customerId", Required = Newtonsoft.Json.Required.Always)]
[System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)]
public string CustomerId { get; set; }
/// <summary>The order number. Used to uniquely identify a group of order lines.</summary>
[Newtonsoft.Json.JsonProperty("orderNumber", Required = Newtonsoft.Json.Required.Always)]
[System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)]
public string OrderNumber { get; set; }
}

The main parameter of OpenApiRequestBody to focus on in your example is bodyType. Parameter Example can be omitted here and configured in a different way (explained later on). Instead of using JsonDocument rather use your Parameters class for the bodyType - this will then expose the characteristics of the Parameters class in the Swagger definition.
You can't use the Parameters class itself to represent a valid example and should instead create a dedicated class that inherits OpenApiExample<T> or in your case OpenApiExample<Parameters> then within this class override the Build method to construct your example instance of Parameters. You can then expose your example by decorating the Parameters class with the attribute [OpenApiExample<T>] (as you've done, but using an example class type!).
A couple of things worth noting about OpenApiRequestBody and this library more generally- the Description property of OpenApiRequestBody doesn't currently work as intended; the description text isn't rendered out in the Swagger requestBody definition. This leads on to my next point which is to note that (at the time of writing) this library - Microsoft.Azure.WebJobs.Extensions.OpenApi - is in a pre-release state (0.7.2) and accordingly does seem to contain some bugs, as I've discovered myself.
I've amended your ModifyOrder class below; this version I hope will help with answering your questions!
public static class ModifyOrder
{
[FunctionName("ModifyOrder")]
[OpenApiOperation(operationId: "run", tags: new[] { "Modify Order" })]
[OpenApiSecurity("function_key", SecuritySchemeType.ApiKey, Name = "code", In = OpenApiSecurityLocationType.Query)]
[OpenApiRequestBody(contentType: "application/json", bodyType: typeof(Parameters), Description = "Parameters", Required = true)]
[OpenApiResponseWithBody(statusCode: HttpStatusCode.OK, contentType: "text/plain", bodyType: typeof(string), Description = "The OK response")]
public static async Task<IActionResult> run(
[HttpTrigger(AuthorizationLevel.Function, "PUT", Route = null)] HttpRequest req,
ILogger log)
{
log.LogInformation($"C# HTTP trigger function processed a request.");
string ordernumber;
string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
dynamic data = JsonConvert.DeserializeObject(requestBody);
ordernumber = data?.orderno;
string responseMessage = $"Order:{ordernumber}";
return new OkObjectResult(responseMessage);
}
}
[OpenApiExample(typeof(ParametersExample))]
public class Parameters
{
/// <summary>The id of the customer in the context. This is also called payer, sub_account_id.</summary>
[OpenApiPropertyDescription("The id of the customer in the context. This is also called payer, sub_account_id.")]
[Newtonsoft.Json.JsonProperty("customerId", Required = Newtonsoft.Json.Required.Always)]
[System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)]
public string CustomerId { get; set; }
/// <summary>The order number. Used to uniquely identify a group of order lines.</summary>
[OpenApiPropertyDescription("The order number. Used to uniquely identify a group of order lines.")]
[Newtonsoft.Json.JsonProperty("orderNumber", Required = Newtonsoft.Json.Required.Always)]
[System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)]
public string OrderNumber { get; set; }
}
public class ParametersExample : OpenApiExample<Parameters>
{
public override IOpenApiExample<Parameters> Build(NamingStrategy namingStrategy = null)
{
this.Examples.Add(
OpenApiExampleResolver.Resolve(
"ParametersExample",
new Parameters()
{
CustomerId = "CUST12345",
OrderNumber = "ORD001"
},
namingStrategy
));
return this;
}
}

Related

Is that possible to use multiple output in Azure Functions .Net 5?

I'm using .net 5 azure function with ServiceBus. I want to send multiple messages from trigger function.
In previous version you could usr IAsyncCollector to do something like that:
[FunctionName("HttpToServiceBusQueue")]
public static async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequest req,
[ServiceBus("testqueue",Connection ="connectionString")] IAsyncCollector<string> outputEvents,
ILogger log)
{
log.LogInformation("C# HTTP trigger function processed a request.");
// ...
await outputEvents.AddAsync("message1");
await outputEvents.AddAsync("message2");
// ...
return new OkObjectResult(responseMessage);
}
But as I read there in documentation is no support of IAsyncCollector in newer version.
Is there any alternative ways to do it or?
Example of my code:
[Function("FileTriggerFunction")]
[ServiceBusOutput("fileupload", Connection = "ServiceBusConnectionWrite")]
public string Run(
[BlobTrigger("file-storage/{name}", Connection = "ConnectionString")] string myBlob, string name,
FunctionContext context)
{
var logger = context.GetLogger("FileTriggerFunction");
var res = JsonConvert.SerializeObject(*List of messages*);
logger.LogInformation(res);
return res;
}
Actually it separates each object of list for separate message, but I don't think that it is correct to do this in such way.
Instead of IAsyncCollector we have multiple output binding in .net 5
On this function we will give the multiple output values.
public static class MultiOutput
{
[Function("MultiOutput")]
public static MyOutputType Run([HttpTrigger(AuthorizationLevel.Anonymous, "get")] HttpRequestData req,
FunctionContext context)
{
var response = req.CreateResponse(HttpStatusCode.OK);
response.WriteString("Success!");
string myQueueOutput = "some output";
return new MyOutputType()
{
Name = myQueueOutput,
HttpResponse = response
};
}
}
public class MyOutputType
{
[QueueOutput("myQueue")]
public string Name { get; set; }
public HttpResponseData HttpResponse { get; set; }
}
Refer .NET isolated process guide for .NET 5.0 in Azure Functions
Thanks # Stephen Cleary
I had tried with SeviceBus Client to send a multiple messages to a queue I am able to process it.
Refer here
Here is a sample code how to do it
[Function("MultiOutput")]
public static DispatchedMessages Run([HttpTrigger(AuthorizationLevel.Anonymous, "get")] HttpRequestData req,
FunctionContext context)
{
return new DispatchedMessages
{
Messages = new List<string> { "aaa", "bbb" } // todo: serialize
};
}
public class DispatchedMessages
{
[ServiceBusOutput(queueOrTopicName: "dest", Connection = "AzureServiceBus")]
public IEnumerable<string> Messages { get; set; }
}
}

Modifying Data before inserting it in Cosmos DB

I am doing an HTTP trigger in Azure Function which add or update data in the Cosmos Db based on a condition. The data which is inserted has a modified date key(see below). I would like to update this key into date when this trigger ran and updated the record in Cosmos Db. If the update of Modified Date key is not possible, then I would prefer adding a new key here like date Updated and insert the current date.
How can I achieve this??
here is the code and structure of data inserted
public static class Function1
{
[FunctionName("Function1")]
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.");
bool upsert = bool.Parse(req.Query["upsert"]);
string cosmosDbKey = "TestDJIWEHLM+4Jw==";
string cosmosDbInstance = "https://localhost:80761";
string cosmosDbName = "TestProfiles";
string cosmosDbCollection = "Profiles";
Uri CollectionUri = UriFactory.CreateDocumentCollectionUri( cosmosDbName, cosmosDbCollection );
DocumentClient Client = new DocumentClient( new Uri( cosmosDbInstance ), cosmosDbKey );
string requestBody = await new StreamReader( req.Body ).ReadToEndAsync();
string responseMessage = upsert;
var rec = JsonConvert.DeserializeObject( requestBody );
var obj = JObject.Parse( rec.ToString() );
if( upsert )
{
await Client.CreateDocumentAsync(
CollectionUri,
obj );
}
else
{
await Client.UpsertDocumentAsync(
CollectionUri,
obj);
}
return new OkObjectResult( responseMessage );
}
}
The Obj which I am inserting/Update is of this format:
{
"id": "Test",
"Type": "tes",
"LastModified": "2020-03-29T22:22:25.6016794Z",
"Tags": `["ta` btest "," tabtest2 "]," Properties ":{}," Categories ":[]," Quality ":{" Level ":0}," System ":{" OSVersion ":{" Platform ":2," ServicePack ":" "," Version ":" 10.0.19042.0 "," VersionString ":" Microsoft Windows NT 10.0.19042.0 "}}," DataSets ":[{" Name ":" DataSet1 "," DataFiles ":[{" Name ":" Readme.txt "," LastModified ":" 2020 - 03 - 29T22: 21: 30.570373Z "," Digest ":{" Hash ":" sAxMlg == "," Length ":5}}]}]," FileStore ":{" Service ":" AZURE "}," _etag ":" \ "0500b9f2-0000-0c00-0000-5f884cba0000\"",
"Trigger": true,
"Project": "TDemo",
"ProjectId": "0mh45lfb.zqr"
}
requestBody:
"{\"id\":\"Test\",\"DocType\":\"REC\",\"LastModified\":\"2020-03-29T22:22:25.6016794Z\",\"Tags\":[\"tabtest\",\"tabtest2\"],\"Properties\":{},\"Categories\":[],\"Quality\":{\"Level\":0},\"System\":{\"OSVersion\":{\"Platform\":2,\"ServicePack\":\"\",\"Version\":\"10.0.19042.0\",\"VersionString\":\"Microsoft Windows NT 10.0.19042.0\"}},\"DataSets\":[{\"Name\":\"DataSet1\",\"DataFiles\":[{\"Name\":\"Readme.txt\",\"LastModified\":\"2020-03-29T22:21:30.570373Z\",\"Digest\":{\"Hash\":\"sAnyGxMlg==\",\"Length\":5}}]}],\"FileStore\":{\"Service\":\"AZURE\"},\"_etag\":\"\\\"0500b9f2-0000-0c00-0000-5f884cba0000\\\"\",\"Trigger\":true,\"Project\":\"Test\",\"ProjectId\":\"0mh45lfb.zqr\"}"
You can achieve it by playing a little with Newtonsoft.Json:
public static class Function1
{
[FunctionName("Function1")]
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.");
bool upsert = bool.Parse(req.Query["upsert"]);
string cosmosDbKey = "TestDJIWEHLM+4Jw==";
string cosmosDbInstance = "https://localhost:80761";
string cosmosDbName = "TestProfiles";
string cosmosDbCollection = "Profiles";
Uri CollectionUri = UriFactory.CreateDocumentCollectionUri(cosmosDbName, cosmosDbCollection);
DocumentClient Client = new DocumentClient(new Uri(cosmosDbInstance), cosmosDbKey);
string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
string responseMessage = upsert;
var rec = JsonConvert.DeserializeObject<DBObject>(requestBody);
rec.LastModified = DateTime.UtcNow;
var obj = JObject.Parse(rec.ToString());
if (upsert)
{
await Client.CreateDocumentAsync(
CollectionUri,
obj);
}
else
{
await Client.UpsertDocumentAsync(
CollectionUri,
obj);
}
return new OkObjectResult(responseMessage);
}
public class DBObject
{
public string id { get; set; }
public string Type { get; set; }
public DateTime LastModified { get; set; }
public string[] Tags { get; set; }
public string Project { get; set; }
public string ProjectId { get; set; }
}
}
Suggestion (not a part of your question's answer): Don't take LastModified value in request body of Azure Function. You should always change DateTime (CDC - Change Data Capture) values at Data Access layer.

input-binding to table storage with an http-triggered function

Is it possible to (input) bind to table storage within an http-triggered function?
I'm attempting to add an input-binding to table-storage inside of a regular http-triggered function with the following attribute:
[Table("MyTable", "MyPartition", "{httpTrigger}")] MyPoco poco
However it's returning the following error when I execute it:
[6/5/2019 5:36:38 PM] An unhandled host error has occurred. [6/5/2019
5:36:38 PM] Microsoft.Azure.WebJobs.Host:
'tableStorageInputBindingHttpTriggered' can't be invoked from Azure
WebJobs SDK. Is it missing Azure WebJobs SDK attributes?.
Additionally at startup, I get this exception:
[6/5/2019 6:17:17 PM] tableStorageInputBindingHttpTriggered: Microsoft.Azure.WebJobs.Host: Error indexing method 'tableStorageInputBindingHttpTriggered'. Microsoft.Azure.WebJobs.Host: Unable to resolve binding parameter 'httpTrigger'. Binding expressions must map to either a value provided by the trigger or a property of the value the trigger is bound to, or must be a system binding expression (e.g. sys.randguid, sys.utcnow, etc.).
Here's the full function:
public class MyPoco
{
public string PartitionKey { get; set; }
public string RowKey { get; set; }
public string Directory { get; set; }
}
public static class tableStorageInputBindingHttpTriggered
{
[FunctionName("tableStorageInputBindingHttpTriggered")]
public static async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req,
[Table("MyTable", "MyPartition", "{httpTrigger}")] MyPoco poco,
ILogger log)
{
string name = req.Query["name"];
string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
dynamic data = JsonConvert.DeserializeObject(requestBody);
name = name ?? data?.name;
return name != null
? (ActionResult)new OkObjectResult($"PK={poco.PartitionKey}, RK={poco.RowKey}, Text={poco.Directory}")
: new BadRequestObjectResult("");
}
}
What am I doing wrong? How do I bind to table storage within an http-triggered azure-function?
Issue is that http trigger returns you an object so it dont know how to extract your key.
You need to use route, which will tell Function how to get parameter and then you will be able to use that parameters
public static async Task<HttpResponseMessage> SetLatestAsync(
[HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = "release-set-latest/{program}")]
HttpRequestMessage req,
string program,
[Table(TableName, "latest", "{program}")]FlymarkLatestVersion pocos)
This inserts the request body to Table storage by binding to CloudTable
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 Microsoft.WindowsAzure.Storage.Table;
namespace AzureFunctionsSandbox
{
public class MyPoco : TableEntity
{
public string Body { get; set; }
}
public static class Function1
{
[FunctionName("Function1")]
public static async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req,
[Table("Sandbox", "StorageConnectionString")] CloudTable table,
ILogger log)
{
log.LogInformation("C# HTTP trigger function processed a request.");
string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
var poco = new MyPoco { PartitionKey = "HttpTrigger", RowKey = Guid.NewGuid().ToString(), Body = requestBody };
var insertOperation = TableOperation.Insert(poco);
await table.ExecuteAsync(insertOperation);
return new OkObjectResult($"PK={poco.PartitionKey}, RK={poco.RowKey}, Text={poco.Body}");
}
}
}
Note: MyPoco inherits from TableEntity which allows you to create the TableOperation.Insert(poco) as .Insert() takes an ITableEntity.
local.settings.json
{
"IsEncrypted": false,
"Values": {
"AzureWebJobsStorage": "UseDevelopmentStorage=true",
"FUNCTIONS_WORKER_RUNTIME": "dotnet",
"StorageConnectionString": "UseDevelopmentStorage=true"
}
}
Seems you are trying to read your Azure Table Storage from HTTP Trigger Function. Please have a look on the code snippet below:
Your POCO Class:
public class MyPoco
{
public string PartitionKey { get; set; }
public string RowKey { get; set; }
public string Directory { get; set; }
}
Table Storage Class:
public class TableStorageClass
{
public TableStorageClass()
{
}
public TableStorageClass(DynamicTableEntity entity)
{
PartitionKey = entity.PartitionKey;
RowKey = entity.RowKey;
}
public string PartitionKey { get; set; }
public string RowKey { get; set; }
}
Azure HTTP Trigger Function V2:
public static class FunctionReadFromTableStorage
{
[FunctionName("FunctionReadFromTableStorage")]
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.");
//Read Request Body
var content = await new StreamReader(req.Body).ReadToEndAsync();
//Extract Request Body and Parse To Class
MyPoco objMyPoco = JsonConvert.DeserializeObject<MyPoco>(content);
// Validate param because PartitionKey and RowKey is required to read from Table storage In this case , so I am checking here.
dynamic validationMessage;
if (string.IsNullOrEmpty(objMyPoco.PartitionKey))
{
validationMessage = new OkObjectResult("PartitionKey is required!");
return (IActionResult)validationMessage;
}
if (string.IsNullOrEmpty(objMyPoco.RowKey))
{
validationMessage = new OkObjectResult("RowKey is required!");
return (IActionResult)validationMessage;
}
// Table Storage operation with credentials
var client = new CloudTableClient(new Uri("https://YourStorageURL.table.core.windows.net/"),
new Microsoft.WindowsAzure.Storage.Auth.StorageCredentials("YourStorageName", "xtaguZokAWbfYG4QDkBjT+YourStorageKey+T/kId/Ng+cl3TfYHtg=="));
var table = client.GetTableReference("YourTableName");
//Query filter
var query = new TableQuery()
{
FilterString = string.Format("PartitionKey eq '{0}' and RowKey eq '{1}'", objMyPoco.PartitionKey, objMyPoco.RowKey)
};
//Request for storage query with query filter
var continuationToken = new TableContinuationToken();
var storageTableQueryResults = new List<TableStorageClass>();
foreach (var entity in table.ExecuteQuerySegmentedAsync(query, continuationToken).GetAwaiter().GetResult().Results)
{
var request = new TableStorageClass(entity);
storageTableQueryResults.Add(request);
}
//As we have to return IAction Type So converting to IAction Class Using OkObjectResult We Even Can Use OkResult
var result = new OkObjectResult(storageTableQueryResults);
return (IActionResult)result;
}
}
Point To Remember:
In case of Azure Portal execution just get rid of FunctionReadFromTableStorage class
You need following reference to execute above code
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;
using Microsoft.WindowsAzure.Storage.Table;
using System.Collections.Generic;
Postman Request Pattern:
Function Invoke Sample:
{
"PartitionKey": "Your Param According to Table Storage Design" ,
"RowKey": "Your Param According to Table Storage Design",
"Directory": "Your Param According to Table Storage Design"
}
See the screenshot:
Postman response:
Response is subject to my own table design
[
{
"partitionKey": "Microsoft SharePoint Server",
"rowKey": "2016"
}
]
See the screenshot below:
Note: I like to write code in simple and readable way. I just tried it for your case. If it resolved your issue my effort would be success then. This is the easiest way I know so far to read from
Azure table storage.

How to change my functions behavior to not use a Model class in c# API

First, I am not sure if my question reflects my needs, please let me know if that needs to be changed.
What I am doing here is creating functions in DLL application to be called from DotNET outside c#. The issue with my function now is the Model class. Where I am going to use this DLL, I cannot see/use my model class from the outside, therefore how do I change my code to use a string instead of Product model? I still need to send my request as JSON though.
I am working with JSON
I have the following 2 classes:
In Class 1 (SetupWebAPIAsync): Function that puts a product (Model)
public static async Task<ApiResponse> PutProductAsync(string endpoint, Product p)
{
StringContent httpContent = new StringContent(JsonConvert.SerializeObject(p), Encoding.UTF8, "application/json");
string result = "";
HttpResponseMessage response = await client.PutAsync(endpoint, httpContent);
response.EnsureSuccessStatusCode();
result = await response.Content.ReadAsStringAsync();
return new ApiResponse(response.StatusCode, result);
}
In Class 2:
public static ApiResponse PutIn(string user, string password, string endpoint , Product Httpcontent)
{
User = user;
Password = password;
Endpoint = endpoint;
Content = Httpcontent;
ExecutePUTRequest().Wait();
return apiResponse;
}
private static async Task ExecutePUTRequest()
{
SetupWebAPIAsync.SetAPIAuthentication(User, Password);
apiResponse = await SetupWebAPIAsync.PutProductAsync(Endpoint,Content);
}
My Model Class:
public class Product
{
public string id { get; set; }
public string name { get; set; }
public bool inactive { get; set; }
}
ex:
{
"id" : "12",
"name" : "test",
"inactive": false,
}
Now this is how I call my function and it works this way BUT I need to replace product by a string I pass in from my test outside Dll.
Product product = new Product { name = "API_Testing" };
PutIn("user", "pass", "https://localhost/api/product", product);
Well you can't cause your PutIn() method expects an Product Httpcontent as method parameter. instead take a stringified product as JSON and convert that to product and call the main method like
public static ApiResponse PutIn(string user, string password,
string endpoint , string Httpcontent)
{
User = user;
Password = password;
Endpoint = endpoint;
Content = NewtonSoft.Json.JsonConvert.DeserializeObject<Product>(Httpcontent);
ExecutePUTRequest().Wait();
return apiResponse;
}
You can then call it like
string product = "{id : 12,name : API_Testing,inactive: false,}"
PutIn("user", "pass", "https://localhost/api/product", product);

How to pass parameters by POST to an Azure function?

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);
}

Categories

Resources