dialogflow simple fulfillment webhook in c# not working - c#

I am very new to dialogflow and WebAPIs, and having trouble with a simple dialogflow fulfillment webhook written in C# and hosted on Azure. I am using dialogflow V2.0 API version.
Currently my fulfillment works and returns a simple response but has no regard to the intent and parameters. I am trying to now parse the JSON get the intent do a simple select case and return the value of the parameters recevied. And this is giving me lot of trouble. The webhook link, my code and the error message returned in the "catch" block are given below
public JsonResult Post(string value)
{
try
{
dynamic obj = JsonConvert.DeserializeObject(value);
string Location = string.Empty;
switch (obj.intent.displayName)
{
case "getstock":
Location = obj.outContexts[0].parameters[0].Location;
break;
}
WebhookResponse r = new WebhookResponse();
r.fulfillmentText = string.Format("The stock at {0} is valuing Rs. 31 Lakhs \n And consists of items such as slatwall, grid and new pillar. The detailed list of the same has been email to you", Location);
r.source = "API.AI";
Response.ContentType = "application/json";
return Json(r);
} catch(Exception e)
{
WebhookResponse err = new WebhookResponse();
err.fulfillmentText = e.Message;
return Json(err);
}
}
The error message :
Value cannot be null.
Parameter name: value
The above function is called via POST, you can use POSTMAN and you will get the JSON response.
Moreover i am using ASP.Net Web Api with Visual Studio 2017 with Controllers

First install the nuget package Google.Apis.Dialogflow.v2 and its dependencies. It'll save you a lot of work later on as it has dialogflow response/request c# objects which will make navigating the object graph easier.
Second add the using for the package using Google.Apis.Dialogflow.v2.Data;
Change your method to something like
public GoogleCloudDialogflowV2WebhookResponse Post(GoogleCloudDialogflowV2WebhookRequest obj)
{
string Location = string.Empty;
switch (obj.QueryResult.Intent.DisplayName)
{
case "getstock":
Location = obj.QueryResult.Parameters["Location"].ToString();
break;
}
var response = new GoogleCloudDialogflowV2WebhookResponse()
{
FulfillmentText = $"The stock at {Location} is valuing Rs. 31 Lakhs \n And consists of items such as slatwall, grid and new pillar. The detailed list of the same has been email to you",
Source = "API.AI"
};
return response;
}
I think your main issue in your code is "obj.outContexts[0]" outContexts isn't where you'll find your parameters, and unless you've setup an output content this will be null. You need to look in queryResult for your parameters.

Related

.NET Framework 4.7 : Get particular attribute from ReadAsStringAsync

I'm sure there are some posts talking about it but so far, nothing works in my case.
I get data from an api and I read it within a console application in C# for testing purpose.
My end goal is to call the data from the API and store the attributes ("analyst" for ex) somewhere where I could pick some of them and dysplay them within a winform app' (I think mostly through a Datagridview than a webbrowser but I'm not sure). I understand I need Json.net but as I use the ReadAsStringAsync() method I'm not sure about the object type I use anymore. Here is my code:
// Get the response from the API
using (var client = new HttpClient())
{
try
{
var response = client.SendAsync(httpRequestMessage).Result;
//Console.WriteLine(response.ToString());
HttpContent responseContent = response.Content;
var responsedata = responseContent.ReadAsStringAsync();
//Console.WriteLine(responseContent);
string data = responsedata.Result;
var BS = JsonConvert.DeserializeObject<dynamic>(data);
string analyst = BS.analyst;
Console.WriteLine(analyst);
Console.ReadKey();
}
catch (HttpRequestException e)
{
Console.WriteLine("\nException Caught!");
Console.WriteLine($"Message: {e.Message}");
}
}
I got an object "data" like this :
{
"result":
{
"3_yr_proj_eps_cagr_prcntg": 47.0,
"analyst": "Jones Dow",
"analyst_email": "Dow.Jones#Theprovider.com",
"business_summary": "<p>COMPANY OVERVIEW. Dow Inc. was formed on April 1, 2019, following the spin- off of the company from DowDupont".
}
}
And I just want to display "analyst" into my datagridview when I click on a button but so far my variable "analyst" is null meaning the porgramm does not see the value "Jones Dow"...
I'm kind of lost within all the information I could read about the topic, any help would be appreciated.
Thanks

Update Values in Excel Named Ranges using Microsoft Graph API

I have an Excel file loaded into Office 365 that is accessible via the Microsoft Graph API with many named ranges, some are individual values, some are blocks of cells.
I can successfully update individual values using the API, but when I try to update multiple cells at the same time, I run into problems.
For this example, consider a single 3-cell array from F10:F12
I would like to populate as follows:
F10 = A
F11 = B
F12 = C
So, I create list of strings that ends up looking like this...
[ ["A"], ["B"], ["C"] ]
and I pass it to the Graph API using the following code...
public static async Task<WorkbookRange> UpdateRangeArray(string strItemId, string strSheetName, string strRangeName, List<string> strRangeValues, string strSessionId)
{
string[][] strValueArray = new string[strRangeValues.Count][];
try
{
int i = 0;
foreach (var val in strRangeValues)
{
strValueArray[i] = new string[1] { val };
i++;
}
}
var jsonValueArray = JsonConvert.SerializeObject(strValueArray);
var rangeUpdate = new Microsoft.Graph.WorkbookRange();
rangeUpdate.Values = jsonValueArray;
var result = await graphClient.Users[_strUserId].Drive.Items[strItemId].Workbook.Worksheets[strSheetName]
.Range(strRangeName)
.Request()
.Header("workbook-session-id", strSessionId)
.PatchAsync(rangeUpdate).ConfigureAwait(false);
return result;
}
So I am able to update the values in the range, EXCEPT instead of the expected values, what I get is this...
F10 = [ ["A"],["B"],["C"] ]
F11 = [ ["A"],["B"],["C"] ]
F12 = [ ["A"],["B"],["C"] ]
Instead of the Graph API putting the first value in the first cell, second value in the second cell, and so forth... It puts the entire array of data in each cell.
I assume this is some sort of formatting error, maybe my JSON is malformed or perhaps I'm submitting it using the wrong Graph API endpoint or something.
Any help would be greatly appreciated...
UPDATE 1:
I also tried doing this with the RANGE (ie - .Range("F10:F12")) instead of using the NAME and I get the same result.
UPDATE 2:
Cross-posted on GitHub in case this is a bug not just a user error.
https://github.com/microsoftgraph/msgraph-sdk-dotnet/issues/695
UPDATE 3:
I can successfully PATCH via the Graph API Explorer to the following URL...
https://graph.microsoft.com/v1.0/me/drive/items/{item-id}/workbook/worksheets/INPUTS/range(address='F10:F12')
with this body...
{"values":[["Hello"],["How"],["Are You?"]]}
...and it works.
but still can't get it to work via the MSGraph-SDK-dotnet
UPDATE 4:
I AM able to get it to work correctly using Postman and the resulting RestSharp code that works looks like this...
public static async Task TestUpdatePostman()
{
var client = new RestClient("https://graph.microsoft.com/v1.0/users/{USER-ID}/drive/items/{ITEM-ID}/workbook/worksheets/INPUTS/range(address='F10:F12')");
client.Timeout = -1;
var request = new RestRequest(Method.PATCH);
request.AddHeader("Authorization", "Bearer {INSERT-TOKEN}");
request.AddHeader("Content-Type", "application/json");
request.AddParameter("application/json", "{\"values\":[[\"Hello\"],[\"How\"],[\"Are You?\"]]}", ParameterType.RequestBody);
IRestResponse response = client.Execute(request);
Console.WriteLine(response.Content);
}
This again makes me believe there's an issue with the way the SDK wrapper implements the API call.
It might not be a SDK issue. When updating the range, rangeUpdate.Values should be a JToken type instead of a string.
Try using:
JArray jsonValueArray = JArray.FromObject(strValueArray);

Unable to verify Forge callback payload signature

I am currently using Forge Webhooks API to handle different events that might occur on a project. Everything works fine, except the payload signature check.
The reason why I want to check the payload is because the callback will end up on my API and I want to reject all requests that do not come from Forge's webhook service.
Steps I followed:
Add (register) secret key (token) on Forge. API Reference
Trigger an event that will eventually call my API for handling it.
Validating signature header. Followed this tutorial.
PROBLEM!!! My computedSignature is different from the signature received from Forge.
My C# code looks like this:
private const string SHA_HASH = "sha1hash";
var secretKeyBytes = Encoding.UTF8.GetBytes(ForgeAuthConfiguration.AntiForgeryToken);
using var hmac = new HMACSHA1(secretKeyBytes);
var computedHash = hmac.ComputeHash(request.Body.ReadAsBytes());
var computedSignature = $"{SHA_HASH}={computedHash.Aggregate("", (s, e) => s + $"{e:x2}", s => s)}";
For one example, Forge's request has this signature header: sha1hash=303c4e7d2a94ccfa559560dc2421cee8496d2d83
My C# code computes this signature: sha1hash=3bb8d41c3c1cb6c9652745f5996b4e7f832ca8d5
The same AntiForgeryToken was sent to Forge at step 1
Ok, I thought my C# code is broken, then I tried this online HMAC generator and for the given input, result is: 3bb8d41c3c1cb6c9652745f5996b4e7f832ca8d5 (same as C#)
Ok, maybe the online generator is broken, I tried their own code in node js and this is the result:
I have 3 ways of encrypting the SAME body using the SAME key and I get the SAME result every time. BUT those results are DIFFERENT from the signature provided by Forge, resulting in failing the check and rejecting a valid request...
Does anyone know what is happening with that signature?
Why is it different from my result if I follow their tutorial?
How are you validating your requests?
The code below is working at my side. Could you give it a try if it helps?
[HttpPost]
[Route("api/forge/callback/webhookbysig")]
public async Task<IActionResult> WebhookCallbackBySig()
{
try
{
var encoding = Encoding.UTF8;
byte[] rawBody = null;
using (StreamReader reader = new StreamReader(Request.Body, Encoding.UTF8))
{
rawBody = encoding.GetBytes(reader.ReadToEnd());
}
var requestSignature = Request.Headers["x-adsk-signature"];
string myPrivateToken = Credentials.GetAppSetting("FORGE_WEBHOOK_PRIVATE_TOKEN");
var tokenBytes = encoding.GetBytes(myPrivateToken);
var hmacSha1 = new HMACSHA1(tokenBytes);
byte[] hashmessage = hmacSha1.ComputeHash(rawBody);
var calculatedSignature = "sha1hash=" + BitConverter.ToString(hashmessage).ToLower().Replace("-", "");
if (requestSignature.Equals(calculatedSignature))
{
System.Diagnostics.Debug.Write("Same!");
}
else
{
System.Diagnostics.Debug.Write("diff!");
}
}
catch(Exception ex)
{
}
// ALWAYS return ok (200)
return Ok();
}
If this does not help, please share with your webhook ID (better send email at forge.help#autodesk.com). We will ask engineer team to check it.

How can I identify a user logged into DiallogFlow via webhook request?

I am using Dialogflow and would like to know if through the questions of a user to a bot it is possible to identify which user is asking this question.
Attached is a section of the code for reading the data already received.
I tried using the google documentation ('' https://developers.google.com/assistant/identity/google-sign-in#java "), but was unsuccessful.
WebhookRequest request;
using (var reader = new StreamReader(Request.Body))
{
request = jsonParser.Parse<WebhookRequest>(reader);
}
var pas = request.QueryResult.Parameters;
var queryText = request.QueryResult.QueryText;
var response = new WebhookResponse();
StringBuilder sb = new StringBuilder();
//interactionDAO.SaveInteration(new Interaction(Guid.NewGuid(), "google", queryText));
var intent = request.QueryResult.Intent.DisplayName;
var listaObjetos = await _service.DetectIntentAsync(new[] { queryText }, intent);
foreach (var item in listaObjetos)
{
var convertItem = JsonConvert.DeserializeObject<Fulfillment>(item.ToString());
if (!String.IsNullOrWhiteSpace(convertItem.FulfillmentText))
{
sb.Append(convertItem.FulfillmentText);
}
if (convertItem.Parameters != null && convertItem.Parameters.ContainsKey("date-time"))
{
sb.Append(convertItem.Parameters["date-time"]);
}
//sb.Append(item);
}
response.FulfillmentText = sb.ToString();
return Json(response);
Look for "session" in the JSON you receive in your webhook from DialogFlow, it is a unique identifier for the conversation.
Usually it has a format like this:
"session": "projects/${PROJECTID}/agent/sessions/${SESSIONID}"
Just extract the SESSIONID from the last part.
You can find more about DialogFlow Webhook JSON format here:
https://developers.google.com/assistant/actions/build/json/dialogflow-webhook-json
DialogFlow generally only identifies the session. Providing data to uniquely identify the user is part of the client and usually included in the payload.
For example, a signed in user from Google Assistant can be extracted like this (requires the System.IdentityModel.Tokens.Jwt package):
WebhookRequest request;
if (!request.OriginalDetectIntentRequest.Payload.Fields.ContainsKey("user"))
{
throw new ArgumentException("Payload does not contain user.");
}
string idToken = request.OriginalDetectIntentRequest.Payload.Fields["user"]
.StructValue.Fields["idToken"].StringValue;
var userInfo = new JwtSecurityTokenHandler().ReadJwtToken(idToken).Payload;
if (!userInfo["iss"].ToString().EndsWith("accounts.google.com")
|| !userInfo["aud"].ToString().Equals("((your_action_id))")
{
throw new SecurityException("Issuer or authentication token do not match expected value.");
}
string accountName = userInfo["email"].ToString();
if (string.IsNullOrEmpty(accountName))
{
throw new ArgumentException("Id token does not contain mail address.");
}
return accountName;
You need to configure the project as detailed in the article you already linked. It is then possible to mark any DialogFlow intent as "Sign-in required" via the Google Assistant integration settings or use the helper intent for optional sign-in (see this question for details on implementing the helper).

Authentication in Dialogflow API V2 using C#

I have .NET Web API Project for the fulfillment API as our webhook in my Dialogflow agent. In our Post method of the controller, after getting the request from Dialogflow, I implement the explicit authentication as shown in the Google Cloud documentation for C#.
//jsonFileName is the name of the serviceAccountKey json generated from the Google Cloud Platform that's encrypted internally
public bool AuthExplicit(string projectId, string jsonFileName)
{
try
{
string JsonCredential = DecryptHelper.Decrypt(jsonFileName);
var credential = GoogleCredential.FromJson(JsonCredential).CreateScoped(LanguageServiceClient.DefaultScopes);
var channel = new Grpc.Core.Channel(
LanguageServiceClient.DefaultEndpoint.ToString(),
credential.ToChannelCredentials());
var client = LanguageServiceClient.Create(channel);
AnalyzeSentiment(client);
if (client != null)
{
return true;
}
else
{
return false;
}
}
internal void AnalyzeSentiment(LanguageServiceClient client)
{
var response = client.AnalyzeSentiment(new Document()
{
Content = "Authenticated.",
Type = Document.Types.Type.PlainText
});
var sentiment = response.DocumentSentiment;
string score = $"Score: {sentiment.Score}";
string magnitude = $"Magnitude: {sentiment.Magnitude}";
}
The difference with the code is that after getting the client, when we call the AnalyzeSentiment() method, it doesn't do anything, and the projectId parameter is never used to authenticate. GCP docs are quite confusing, since when there is an AuthExplicit() that uses projectId, it uses it as a parameter for the buckets and only prints this on the console.
It works fine, until we test the service account key with a different agent. Expected output is that authentication would fail, but somehow it still passes.
Once the Post method goes through the AuthExplicit() method, it would only return a boolean. Is this the right way to authenticate? Or is there something else needed to invoke?
The difference with the code is that after getting the client, when we call the AnalyzeSentiment() method, it doesn't do anything,
Does client.AnalyzeSentiment() return an empty response? Does the call hang forever?
It works fine, until we test the service account key with a different agent.
What is a different agent? A different User-Agent header?
Once the Post method goes through the AuthExplicit() method, it would only return a boolean. Is this the right way to authenticate? Or is there something else needed to invoke?
What does 'the Post method' refer to? What is the 'it' that would only return a boolean?

Categories

Resources