I am new to cosmos db as well as azure functions and I'm getting nowhere fast. I've been able to find every tutorial under the sun to create and read documents but not update and delete. No one seems to have a full CRUD tutorial using azure functions.
Can someone show me a typical .csx file in azure functions that takes a document in, updates it, and returns an OK response?
I've tried this already
#load "..\Shared\Classes.csx"
using System.Net;
public static HttpResponseMessage Run(HttpRequestMessage req,
IEnumerable<Business> businessToBeUpdated, out dynamic updatedBusiness,
TraceWriter log)
{
log.Info("C# HTTP trigger function processed a request.");
//compiler requires this assignment
updatedBusiness = null;
// Get request body
Business data = req.Content.ReadAsAsync<Business>().Result;
businessToBeUpdated = businessToBeUpdated.FirstOrDefault<Business>();
log.Info(businessToBeUpdated.Count().ToString());
if(businessToBeUpdated != null && data != null)
{
//update it
businessToBeUpdated = data;
//updatedBusiness.id = data.id;
log.Info(businessToBeUpdated.website);
}
else{
return req.CreateResponse(HttpStatusCode.BadRequest);
}
return req.CreateResponse(HttpStatusCode.OK);
}
Here is the binding associated with it.
{
"bindings": [
{
"authLevel": "anonymous",
"name": "req",
"type": "httpTrigger",
"direction": "in",
"route": "updatebiz/{id}"
},
{
"name": "$return",
"type": "http",
"direction": "out"
},
{
"type": "documentDB",
"name": "businessToBeUpdated",
"databaseName": "dbname",
"collectionName": "Businesses",
"sqlQuery": "Select * FROM c where c.id = {id}",
"connection": "connection",
"direction": "in"
},
{
"type": "documentDB",
"name": "updatedBusiness",
"databaseName": "dbname",
"collectionName": "Businesses",
"createIfNotExists": false,
"connection": "connection",
"direction": "out"
}
],
"disabled": false
}
The simplest example of a function which updates a document looks exactly the same as the function which creates a document: function will do one or the other based on whether the document with specified id already exists.
You don't mention which exact problem you face. Your code doesn't even compile, having enumerable and single objects assigned to each other. On top of that, you never assign updatedBusiness to anything other than null.
I came with a working example which does what I assume you were trying to accomplish.
csx script:
using System.Net;
public class Business
{
public string id { get; set;}
public string name { get; set;}
}
public static HttpResponseMessage Run(HttpRequestMessage req,
Business businessToBeUpdated, out Business updatedBusiness, TraceWriter log)
{
log.Info("C# HTTP trigger function processed a request.");
var data = req.Content.ReadAsAsync<Business>().Result;
if(businessToBeUpdated == null || data == null)
{
updatedBusiness = businessToBeUpdated;
return req.CreateResponse(HttpStatusCode.BadRequest);
}
updatedBusiness = data;
// or merge data and businessToBeUpdated in some desired way
return req.CreateResponse(HttpStatusCode.OK);
}
function.json:
{
"bindings": [
{
"authLevel": "anonymous",
"name": "req",
"type": "httpTrigger",
"direction": "in",
"route": "updatebiz/{id}"
},
{
"name": "$return",
"type": "http",
"direction": "out"
},
{
"type": "documentDB",
"name": "businessToBeUpdated",
"databaseName": "dbname",
"collectionName": "Businesses",
"id": "{id}",
"connection": "connection",
"direction": "in"
},
{
"type": "documentDB",
"name": "updatedBusiness",
"databaseName": "dbname",
"collectionName": "Businesses",
"id": "{id}",
"connection": "connection",
"direction": "out"
}
],
"disabled": false
}
Related
I have simple azure function which is triggered by http
module.exports = async function (context, req) {
context.log('JavaScript HTTP trigger function processed a request.');
context.res = {
// status: 200, /* Defaults to 200 */
body: "Success"
};
}
function.json
{
"bindings": [
{
"authLevel": "function",
"type": "httpTrigger",
"direction": "in",
"name": "req",
"methods": [
"get",
"post"
]
},
{
"type": "http",
"direction": "out",
"name": "res"
}
]
}
At this point I can trigger the function and see the response.
Then I try to add a service bus binding to function.json
{
"bindings": [
...
{
"type": "serviceBus",
"direction": "out",
"name": "outputSbTopic",
"topicName": "topicName",
"connection": "ServiceBusConnection"
}
]
}
When I add the binding the function returns 404 and there is nothing in log. I've even not started to use the binding.
What can be wrong? I'm struggling with the issue more than 2 hours and have no more ideas.
host.json (just in case)
{
"version": "2.0",
"extensions": {
"serviceBus": {
"prefetchCount": 100,
"messageHandlerOptions": {
"autoComplete": true,
"maxConcurrentCalls": 32,
"maxAutoRenewDuration": "00:05:00"
}
}
}
}
Runtime version ~2
Node.js Version Node.js 12 LTS
App is run in read only mode from a package file.
AppType functionAppLinux
UPDATE
I created the function with VS Code Azure Function Extension and deployed with DevOps. Later I created function in azure portal manually. Compared with App Service Editor files of both functions and found out that my first function doesn't have extensionBundle in host.json. That was the reason.
Use your host.json also get the same problem:
The problem seems comes from the host.json in your function app.
On my side those files is:
index.js
module.exports = async function (context, req) {
context.log('JavaScript HTTP trigger function processed a request.');
var message = "This is a test to output to service bus.";
context.bindings.testbowman = message;
context.done();
context.res = {
status: 200,
body: "This is a test to output to service bus topic."
};
};
function.json
{
"bindings": [
{
"authLevel": "anonymous",
"type": "httpTrigger",
"direction": "in",
"name": "req",
"methods": [
"get",
"post"
]
},
{
"type": "http",
"direction": "out",
"name": "res"
},
{
"name": "testbowman",
"type": "serviceBus",
"topicName": "testbowman",
"connection": "str",
"direction": "out"
}
]
}
host.json
{
"version": "2.0",
"logging": {
"applicationInsights": {
"samplingSettings": {
"isEnabled": true,
"excludedTypes": "Request"
}
}
},
"extensionBundle": {
"id": "Microsoft.Azure.Functions.ExtensionBundle",
"version": "[1.*, 2.0.0)"
}
}
local.settings.json
{
"IsEncrypted": false,
"Values": {
"AzureWebJobsStorage": "",
"FUNCTIONS_WORKER_RUNTIME": "node",
"str":"Endpoint=sb://testbowman.servicebus.windows.net/;SharedAccessKeyName=RootManageSharedAccessKey;SharedAccessKey=xxxxxx="
}
}
And it worked:
I am trying to insert data from azure javascript function app to cosmos DB.
This is the function.json File
{
"bindings": [
{
"type": "cosmosDBTrigger",
"name": "documents",
"direction": "in",
"leaseCollectionName": "leases",
"connectionStringSetting": "CosmosDBConnection",
"databaseName": "roi",
"collectionName": "reports",
"createLeaseCollectionIfNotExists": "true"
},
{
"type": "http",
"direction": "out",
"name": "res"
}
]
}
index.js have a code like this.
module.exports = async function (context, documents) {
if (!!documents && documents.length > 0) {
context.log('Document Id: ', documents[0].id);
}
}
Below images will have the error in the application.
Is there any function or query to insert the data ? the request data which is coming from api will look like this.(its coming in rawBody property of request)
When i am fetching the data its workign fine.
{
"name": "inputDocumentIn",
"type": "cosmosDB",
"databaseName": "roi",
"collectionName": "reports",
"sqlQuery": "SELECT * from reports r",
"connectionStringSetting": "CosmosDBConnection",
"direction": "in"
}
local.setting.json file
{
"IsEncrypted": false,
"Values": {
"AzureWebJobsStorage": "UseDevelopmentStorage=true",
"AzureWebJobsDashboard": "",
"FUNCTIONS_WORKER_RUNTIME": "node",
"CosmosDBConnection": "AccountEndpoint=.....;"
},
"Host": {
"LocalHttpPort": 7071,
"CORS": "*"
}
}
With this azure value in local.setting.ts file , i m getting following error.
Please check if the Connection String is pointing to the correct account and check if it was caused by the firewall and networks. Go to your cosmos db account on azure portal and click "Firewall and virtual networks". You can select "All networks" or "Selected networks" with your current IP filled in it.
This sounds like some some dependency conflict. Could you do a dotnet clean on the project folder and then verify that all projects (in case you have project dependencies) have the latest version of the Microsoft.Azure.WebJobs.Extensions.CosmosDB package (currently 3.0.5)?
The solution of the above problem is :
I have changed function.json file with this code.
{
"bindings": [
{
"authLevel": "anonymous",
"type": "httpTrigger",
"direction": "in",
"methods": [ "post" ],
"name": "req"
},
{
"type": "http",
"direction": "out",
"name": "res"
},
{
"type": "cosmosDB",
"name": "outputDocument",
"databaseName": "roi",
"collectionName": "reports",
"createIfNotExists": true,
"connectionStringSetting": "CosmosDBConnection",
"direction": "out",
"partitionKey": "/id"
}
],
"disabled": false
}
and the request coming from front end will be manipulate like this.
module.exports = function (context, req) {
if (req.body) {
context.bindings.outputDocument = req.body;
var responseBody = {};
responseBody.message = "Wow! data with id '" + req.body.id + "' was created!";
context.res = {
status: 201,
body:responseBody
};
}
else {
context.res = {
status: 400,
body: { "message" : "Please pass a valid object in the request body"}
};
}
context.done();
}
I have an Azure Function. I've created an out binding and data is being written to that output CosmosDB.
However I want to ask, once that has done, is it possible to hit another trigger?
Or do i have to manually write code to add to the DB, namely not using the out binding?
Thanks.
Heres the code:
The function.json
{
"bindings": [
{
"type": "cosmosDBTrigger",
"name": "documents",
"direction": "in",
"leaseCollectionName": "leases",
"connectionStringSetting": "COSMOSDB_INPUT_CONNECTION_STRING",
"databaseName": "default",
"collectionName": "metadata",
"createLeaseCollectionIfNotExists": false,
"leaseCollectionPrefix": "IngestMetadata",
"startFromBeginning": true
},
{
"type": "cosmosDB",
"name": "outputdocuments",
"direction": "out",
"connectionStringSetting": "COSMOSDB_CONNECTION_STRING",
"databaseName": "default",
"collectionName": "metadata",
"createIfNotExists": true
}
],
"scriptFile": "../dist/IngestMetadata/index.js"
}
And teh code itself:
const cosmosDBTrigger: AzureFunction = async function (context: Context, documents: any[]): Promise<void> {
if (!!documents && documents.length > 0) {
context.bindings.outputdocuments = documents;
}
context.done();
}
So after the context.done I want to hit another trigger
Yes you can use azure function triggers for that
function.json
{
"type": "cosmosDBTrigger",
"name": "documents",
"direction": "in",
"leaseCollectionName": "leases",
"connectionStringSetting": "<connection-app-setting>",
"databaseName": "Tasks",
"collectionName": "Items",
"createLeaseCollectionIfNotExists": true
}
then js code.
module.exports = function (context, documents) {
context.log('First document Id modified : ', documents[0].id);
context.done();
}
So you could have logic for instance from http trigger you write to cosmos db, and another trigger as soon as anything is written to cosmos db and so on
PS. So to sum up, since I answered your previous question as a result you will have 2 functions, first is from your other question where you write output to cosmos db and second function from this question where it will be triggered as soon as 1 function will finish execution and data available in cosmos db
I want to create the azure Function which is bind to cosmos DB.
Whenever some insertion happens in "A" collection I want to update "B" collection.
"B" collection has stored procedure which I want to call after the insertion in collection "A".
I am new to Azure and cosmos-DB.
Suggest me what need to be done to accomplish the requirement.
So far I have created Azure Function
Also Updated the function.json with below code.
{
"bindings": [
{
"type": "cosmosDBTrigger",
"name": "input",
"direction": "in",
"leaseCollectionName": "leases",
"connectionStringSetting": "cdb-swm-dev-001_DOCUMENTDB",
"databaseName": "admin",
"collectionName": "aomsorders",
"createLeaseCollectionIfNotExists": true
},
{
"type": "documentDB",
"name": "inputDocument",
"databaseName": "admin",
"collectionName": "aomsorders",
"connection": "cdb-swm-dev-001_DOCUMENTDB",
"direction": "in"
},
{
"type": "documentDB",
"name": "outputDocument",
"databaseName": "admin",
"collectionName": "test",
"createIfNotExists": true,
"connection": "cdb-swm-dev-001_DOCUMENTDB",
"direction": "out"
}
],
"disabled": false
}
Also Updated the Integrate Part as below
Any Suggestion will be appreciable.
I find your question not very specific, but broadly you have at least two options:
Insert to both collections from the same Azure Function
Insert to the collection 1 from the first Azure Function, then have a second Azure Function with Cosmos DB trigger listening to the changes of collection 1 and updating collection 2
I'm sure there are other options too.
Here is an example of an Azure Function that gets triggered by a CosmosDBTrigger and then it uses a DocumentDB Output Binding to write to a second collection:
function.json
{
"bindings": [
{
"type": "cosmosDBTrigger",
"name": "input",
"direction": "in",
"databaseName": "your-database",
"collectionName": "your-collection1",
"connectionStringSetting": "name-of-connectionstring-setting-for-collection1",
"leaseCollectionName": "your-lease-collection"
},
{
"type": "documentDB",
"direction": "out",
"name": "docsToSave",
"databaseName": "your-database2",
"collectionName": "your-collection2",
"connection": "name-of-connectionstring-setting-for-collection2",
"createIfNotExists": false
}
]
}
run.csx (C#)
#r "Microsoft.Azure.Documents.Client"
using Microsoft.Azure.Documents;
using System.Collections.Generic;
using System;
public static async Task Run(IReadOnlyList<Document> input, IAsyncCollector<Document> docsToSave)
{
foreach(var doc in input){
// Do something here, process the document or do your compute
// Here I am saving the same document to the second collection but you could send a new document created within the processing logic or send the same document modified by some logic
await docsToSave.AddAsync(doc);
}
}
index.js (NodeJS)
module.exports = function(context, input) {
if(!!input && input.length > 0){
context.bindings.docsToSave = [];
for(var i = 0, len=input.length; i<len;i++){
var doc = input[i];
// Do something here with the doc or create a new one
context.bindings.docsToSave.push(doc);
}
}
context.done();
}
So what I read from this page, is that I should be able to have:
trigger HTTP
input Http request
input DocumentDb
output DocumentDb
Numbers 1, 2 and 4 are working. But how to get a document from DocumentDB with the ID from the path?
Can I use the {path} from the proxy-route in my DocumentDb Input?
I have a proxy defined like so.
{
"$schema": "http://json.schemastore.org/proxies",
"proxies": {
"index": {
"matchCondition": {
"route": "{*path}",
"methods": [
"GET"
]
},
"backendUri": "https://%WEBSITE_SITE_NAME%.azurewebsites.net/api/Index"
},
"api": {
"matchCondition": {
"route": "api/{*path}"
},
"backendUri": "https://%WEBSITE_SITE_NAME%.azurewebsites.net/api/{path}"
},
"index existing subscription": {
"matchCondition": {
"route": "/subscription/{*path}",
"methods": [
"GET"
]
},
"backendUri": "https://%WEBSITE_SITE_NAME%.azurewebsites.net/api/IndexSubscription/{path}"
}
}
}
Here is an example of a Function with Document DB input binding.
function.json
{
"bindings": [
{
"authLevel": "function",
"name": "req",
"type": "httpTrigger",
"direction": "in",
"route": "MyDocFunc/{docid}"
},
{
"type": "documentDB",
"name": "inputDocument",
"databaseName": "MyDocDB",
"collectionName": "MyCollection",
"id": "{docid}",
"connection": "mydocdb_DOCUMENTDB",
"direction": "in"
},
{
"name": "$return",
"type": "http",
"direction": "out"
}
],
"disabled": false
}
csx
public static async Task<HttpResponseMessage> Run(
HttpRequestMessage req, string docid, string inputDocument)
{
return req.CreateResponse(HttpStatusCode.OK, inputDocument);
}
The usage of proxy doesn't affect this much... You can pass your proxy parameter to function parameter.