Trying to understand Azure Durable Function context - azure

Is there someone who could explain the context.GetInput<>() method of Azure Durable Functions?
Im really confused, because I get different values for same methods:
//Here I get the Url
[FunctionName(nameof(GetUrlAsync))]
public async Task<string> GetUrlAsync([ActivityTrigger] IDurableActivityContext context)
{
var url = context.GetInput<string>();
return url;
}
// Here i get the ID
[FunctionName(nameof(GetIdAsync))]
public async Task<string> GetIdAsync([ActivityTrigger] IDurableActivityContext context)
{
var id = context.GetInput<string>();
return id;
}
The Url Function returns Url and the ID Function returns the Id for the same method-call context.GetInput<string>() .
But how does this work?

The orchestrator function which would call these activites will pass through the relevant input
var url = await context.CallActivityAsync<string>("GetUrlAsync", "http://example.com");
var id = await context.CallActivityAsync<string>("GetIdAsync", "id-123")
normally you'd do something a little more interesting with the input than just echo it, but this should give you an idea.
These docs give a good example: https://learn.microsoft.com/en-us/azure/azure-functions/durable/durable-functions-sequence?tabs=csharp

Related

How to write a Azure Function and pass POST API?

I need some help in writing an Azure function with CosmosDB trigger, which will capture some values from cosmosdb like below and create a POST call and trigger an API. Is it possible in Azure function?
Cosmosdb:
The POST API which needs to be passed through Azure function is like this.
Here is the example code to for passing POST in the Azure function
[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");
}
}
Here are the few examples for Post in Azure functions
Azure function POST
Post Async Function.

How to receive response from Zoho Sign Webhook

I am able to hit my call back function from Zoho Sign webhook. But I am not able to figure out how can I receive the response that Zoho Sign sends to my callback URL. Their documentation: https://www.zoho.com/sign/api/#webhook-management
Below is my sample code that I am using to confirm that callback function is hit. It saves a sample data to DB to confirm it is being hit. But the response I am not being able to catch hold of. This is their help documentation that guides on the same, but that misses a working sample. https://help.zoho.com/portal/en/community/topic/webhooks-for-zoho-sign
[HttpPost]
public ActionResult Callback()
{
using (var context = new ZohoApiTestEntities())
{
var rowDetails = new tblWebhook();
rowDetails.PhoneNo = "7978704767";
//rowDetails.Notes1 = jsonObj.ToString();
context.tblWebhooks.Add(rowDetails);
context.SaveChanges();
}
return new HttpStatusCodeResult(HttpStatusCode.OK);
}
Finally, after a lot of hits and trials, this code worked for me. It's bad that after a lot of follow-up and calls with the Zoho team, I did not receive any help from them for many days.
[HttpPost]
public ActionResult Callback()
{
string rawBody = GetDocumentContents(Request);
dynamic eventObj = JsonConvert.DeserializeObject(rawBody);
using (var context = new ZohoApiTestEntities())
{
var rowDetails = new tblWebhook();
rowDetails.PhoneNo = "*********";
//eventObj comes in JSOn format with two keys, "requests" and "notifications" each containing a JSON object https://www.zoho.com/sign/api/#webhook-management
//you can get your required details like this
string recipientName = eventObj.notifications.performed_by_name.ToString();
rowDetails.Notes1 = recipientName;
context.tblWebhooks.Add(rowDetails);
context.SaveChanges();
}
return new HttpStatusCodeResult(HttpStatusCode.OK);
}
private string GetDocumentContents(HttpRequestBase Request)
{
string documentContents;
using (Stream receiveStream = Request.InputStream)
{
using (StreamReader readStream = new StreamReader(receiveStream, Encoding.UTF8))
{
documentContents = readStream.ReadToEnd();
}
}
return documentContents;
}

Add Optional Parameter to Azure Function

Suppose I have an Azure Function method definition that looks something like this:
[FunctionName("TestAzureFunction")]
public static async Task Run([BlobTrigger("{name}")]Stream fileBlob, string name, MyObject object = null)
I want to add an optional parameter to my azure function so other code could also call it. Unfortunately, I get an error saying that this binding is not supported. Does anyone know how to resolve this issue?
Thanks in advance!
You could implement the actual functionality in a (private) method and call that from two different Functions (or have the method be public and have it be called directly).
In pseudo code:
// Use bindings here to get the added Blob and its name
[FunctionName("TestAzureFunctionBlob")]
public static async Task Run([BlobTrigger("{name}")]Stream fileBlob, string name)
{
await DoTheMagicAsync(fileBlob, name);
}
// Use bindings here to het the MyObject instance from the post method
[FunctionName("TestAzureFunctionHttp")]
public static async Task Run([HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = null)]MyObject myObject)
{
var stream = GetStreamFromHttpRequest();
var fileName = GetNameFromHttpRequest();
// Call the generic method
await DoTheMagicAsync(stream, fileName, object);
}
private void DoTheMagicAsync(Stream stream, string name, MyObject myObject = null)
{
// Do your (async) magic here
}
Seems you have created a function with a blob trigger. If you want to be able to trigger it in other ways than creating a new file in a blob storage you should consider a http trigger or a service bus trigger.

Route or multiple function in Azure Function App

I am working with Azure Function to consume a SOAP Service and exposing the data in 5 REST endpoints.
What I did is, uses the
class ServiceFactory {
// properties
// String path, ILogger log, IConfig mappingConfig
//constructors
public IService CreateService() {
switch(path) {
case ServicePath.service1:
return new service1(log, mappingConfig);
case ServicePath.service2:
return new service2(log, mappingConfig);
case ServicePath.service3:
return new service3(log, mappingConfig);
case ServicePath.service4:
return new service4(log, mappingConfig);
}
}
}
and then, the caller method is the azure function
[FunctionName("ServiceFunction")]
public async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Function, "post", Route = "{path?}")]
HttpRequest req,
ILogger log, string? path)
{
// Validate Credential
var validatorResult = await ValidateCredential(_credential);
if (!validatorResult.IsValid)
{
var errors = validatorResult.Errors.Select(error => new
{
field = error.PropertyName,
error = error.ErrorMessage
});
return new BadRequestObjectResult(
JsonConvert.SerializeObject(
new
{
success = false,
message = errors
}
)
);
}
IService service = ServiceFactory(path, req, log, _mappingConfigurationProvider, _service, _credential).CreateService();
return await service.ServiceTask();
}
so the path is here to call different endpoints.
I am asked to implement each of the endpoint with different functions.
What will be the pros and cons here?
Pros:
Single responsibility per function, better maintainability, open closed principle
PS: Extract the common logic to a class and share it among the functions.
Cons:
I can't think any cons about this.

web api get route template from inside handler

I searched a lot before putting the questions here but the more I search the more confused I get.
So I have created an handler and I am trying to get the route like this:
public class ExecutionDelegatingHandler : DelegatingHandler
{
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
if (securityAuthority.VerifyPermissionToExecute(request.GetRouteData().Route.RouteTemplate, request.Headers))
{
return base.SendAsync(request, cancellationToken);
}
else
{
httpResponseMessage.StatusCode = HttpStatusCode.Unauthorized;
}
}
}
GetRouteData returns null so I can't get to the RouteTemplate property but I can
see the route there in a list deep in the stack. I found so many different ways which one can use to get the route, but those methods evaluate to null as well. I am a bit lost on how to get something so simple done. I am using self host for development but will use IIS for deployment.
UPDATE 1
I forgot to put here what else I had tried:
//NULL
request.GetRouteData();
//EMPTY
request.GetRequestContext().Configuration.Routes.GetRouteData(request).Route.RouteTemplate;
//EMPTY
request.GetConfiguration().Routes.GetRouteData(request).Route.RouteTemplate;
The route works just fine, but strangely if I try to get the controller to service that request I get a 404... if I just step over that I will get to the controller just fine.
HttpControllerDescriptor httpControllerDescriptor = request.GetRequestContext().Configuration.Services.GetHttpControllerSelector().SelectController(request);
IHttpController httpController = httpControllerDescriptor.CreateController(request);
I am using autofac to discover all the routes which I am defining just like:
[Route("queries/organization/clients")]
[HttpGet]
public ClientInitialScreenModel GetClients()
{
return OrganizationModelsBuilder.GetClientInitialScreen();
}
UPDATE 2
If I GetRouteData gets called after the line above, I am able to get the route template:
base.SendAsync(request, cancellationToken);
var routeData = request.GetRouteData();
So maybe I misunderstood the whole picture and I cant get the route template before the handler that resolves which controller to execute for the request does its work... is that the case?
For reference this is the handler I am working on:
public class ExecutionDelegatingHandler : DelegatingHandler
{
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var securityAuthority = (ISecurityAuthority) request.GetDependencyScope().GetService(typeof (ISecurityAuthority));
var configuration = (IWebApiConfiguration)request.GetDependencyScope().GetService(typeof(IWebApiConfiguration));
var tsc = new TaskCompletionSource<HttpResponseMessage>();
var httpResponseMessage = new HttpResponseMessage();
if (request.RequestUri.AbsolutePath.Equals(configuration.CommandGatewayUrl, StringComparison.InvariantCultureIgnoreCase))
{
var apiMessage = JsonConvert.DeserializeObject<ApiCommandEnvelope>(request.Content.ReadAsStringAsync().Result);
if (securityAuthority != null && !securityAuthority.VerifyPermissionToExecute(apiMessage, request.Headers))
{
httpResponseMessage.StatusCode = HttpStatusCode.Unauthorized;
}
else
{
var messageProcessor = (IWebApiMessageProcessor)request.GetDependencyScope().GetService(typeof(IWebApiMessageProcessor));
var reponse = messageProcessor.HandleRequest(apiMessage);
httpResponseMessage.StatusCode = (HttpStatusCode) reponse.StatusCode;
if (!string.IsNullOrEmpty(reponse.Content))
{
httpResponseMessage.Content = new StringContent(reponse.Content);
}
}
}
else
{
if (securityAuthority != null && !securityAuthority.VerifyPermissionToExecute(request.GetRouteData().Route.RouteTemplate, request.Headers))
{
httpResponseMessage.StatusCode = HttpStatusCode.Unauthorized;
}
else
{
return base.SendAsync(request, cancellationToken);
}
}
tsc.SetResult(httpResponseMessage);
return tsc.Task;
}
UPDATE 3
The code runs fine in a non self hosting environment, so this is more like a self host issue.
The Web Api still has a lot to improve. It was tricky to find a way to get this working and I just hope this saves other guys from spending all the time I did.
var routeTemplate = ((IHttpRouteData[]) request.GetConfiguration().Routes.GetRouteData(request).Values["MS_SubRoutes"])
.First().Route.RouteTemplate;
I had a similar issue, but was able to get the route inside the message handler by the following:
request.GetConfiguration().Routes.GetRouteData(request).Route.RouteTemplate;
The answer from Marco (shown below) is correct so long as there isn't more than one route defined with the same HttpMethod. The .First() will grab the 1st route defined in that specific ApiController, but this doesn't ensure it grabs the correct one. If you use the ControllerContext to get the Route, you can be sure you've got the exact endpoint you want.
Marco's:
var routeTemplate = ((IHttpRouteData[])request.GetConfiguration()
.Routes.GetRouteData(request).Values["MS_SubRoutes"])
.First().Route.RouteTemplate;
The code:
((IHttpRouteData[])request.GetConfiguration()
.Routes.GetRouteData(request).Values["MS_SubRoutes"])
actually returns a collection of IHttpRouteData, and it contains a record for each endpoint which has the same HttpMethod (Post, Get, etc)... The .First() doesn't guarantee you get the one you want.
Guaranteed To Grab Correct Endpoint's RouteTemplate:
public static string GetRouteTemplate(this HttpActionContext actionContext)
{
return actionContext.ControllerContext.RouteData.Route.RouteTemplate;
}
I used an extension method so to call this you'd do:
var routeTemplate = actionContext.GetRouteTemplate();
This will assure that you get the specific RouteTemplate from the endpoint making the call.
I think you can get route Data from request.Properties property and easy to unit test.
/// <summary>
/// Gets the <see cref="System.Web.Http.Routing.IHttpRouteData"/> for the given request or null if not available.
/// </summary>
/// <param name="request">The HTTP request.</param>
/// <returns>The <see cref="System.Web.Http.Routing.IHttpRouteData"/> or null.</returns>
public static IHttpRouteData GetRouteData(this HttpRequestMessage request)
{
if (request == null)
{`enter code here`
throw Error.ArgumentNull("request");
}
return request.GetProperty<IHttpRouteData>(HttpPropertyKeys.HttpRouteDataKey);
}
private static T GetProperty<T>(this HttpRequestMessage request, string key)
{
T value;
request.Properties.TryGetValue(key, out value);
return value;
}
Reference link of code

Resources