I am new to working with dialogflow and fairly new to .NET. I have been struggling for a while now to create my fulfillment webhook. I have got it to work with the node.js inline-editor but want to create my own WebhookController in .NET so I can make external API calls/db calls more easily. Here is what I have so far:
I have a really basic whats-app-like UI where a user can input some text which is appended to a chat window and then the javascript is pinged for the chatbot's response:
function userSubmit() {
var userInput = document.getElementById('user-input').value;
$.ajax({
type: "GET",
url: "/Home/CheckIntentAsync",
data: {
userInput: userInput
},
async: true,
contentType: "application/json",
success: function (data) {
var reply = data;
var botNode = document.createElement("div");
botNode.classList.add('chat');
botNode.classList.add('bot-chat');
botNode.innerHTML = reply; // <--- appends chat window with the reply from Dialogflow
chatWindow.appendChild(botNode);
chatWindow.scrollTop = chatWindow.scrollHeight;
console.log(data);
},
error: function () {
var reply = "I didn't quite catch that, can you rephrase? :/";
var botNode = document.createElement("div");
botNode.classList.add('chat');
botNode.classList.add('bot-chat');
botNode.innerHTML = reply;
chatWindow.appendChild(botNode);
}
});
The ajax call pings my HomeController class which connects to Dialogflow:
public class HomeController : Controller
{
private string sessionID = "XXX"; // my session ID
private string projectID = "XXX"; // my projectID
public ActionResult Index()
{
SetEnvironmentVariable();
return View();
}
private void SetEnvironmentVariable()
{
try
{
Environment.SetEnvironmentVariable("GOOGLE_APPLICATION_CREDENTIALS", "MY PATH TO SERVICE ACCOUNT PRIVATE KEY IS HERE");
}
catch (ArgumentNullException)
{
throw;
}
catch (ArgumentException)
{
throw;
}
}
[HttpGet]
public async Task<JsonResult> CheckIntentAsync(string userInput)
{
var sessionClient = await SessionsClient.CreateAsync();
var sessionName = new SessionName(projectID, sessionID);
QueryInput queryInput = new QueryInput();
var queryText = new TextInput();
queryText.Text = userInput;
queryText.LanguageCode = "en";
queryInput.Text = queryText;
// Make the request
DetectIntentResponse response = await sessionClient.DetectIntentAsync(sessionName, queryInput);
var reply = response.QueryResult;
return Json(reply, JsonRequestBehavior.AllowGet);
}
}
So far all of the above works a charm with the inline-editor in Dialogflow. I now am creating my webhook fulfilment in .NET and cannot get it to work. My API class looks like this:
public class WebhookController : ApiController
{
private static readonly JsonParser jsonParser =
new JsonParser(JsonParser.Settings.Default.WithIgnoreUnknownFields(true));
[HttpPost]
public async Task<HttpResponseMessage> Post()
{
WebhookRequest request;
using (var stream = await Request.Content.ReadAsStreamAsync())
{
using (var reader = new StreamReader(stream))
{
request = jsonParser.Parse<WebhookRequest>(reader);
}
}
// Simply sets the fulfillment text to equal the name of the intent detected by Dialogflow
WebhookResponse webhookResponse = new WebhookResponse
{
FulfillmentText = request.QueryResult.Intent.DisplayName
};
HttpResponseMessage httpResponse = new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new StringContent(webhookResponse.ToString())
{
Headers = { ContentType = new MediaTypeHeaderValue("text/json") }
}
};
return httpResponse;
}
}
When I run this, I get in the dialogflow console's diagnostic info a 'DEADLINE_EXCEEDED' message however the webhook is doing so little I don't understand why this is?
"webhookStatus": {
"code": 4,
"message": "Webhook call failed. Error: DEADLINE_EXCEEDED."
}
I don't know if I'm supposed to perform some sort of authentication in the webhook as well as in my HomeController?
Some help would be greatly greatly appreciated!!!
Many thanks!
I was getting this same error when i enabled the webhook call from a follow up intent that wasn't mapped (to handler) in fulfillment inline editor.
Related
I am not sure if the problem is from the front end or the back end. I know the backend at least works when using postman, whenever using react HttpContext.Current.Request.Params is always null. when I print it in my react app before sending the POST request it says there are values.
here is my Js fetch
export async function uploadFile(file, type) {
const formData = new FormData();
formData.append("file", file);
formData.append("fileType", type);
console.log(...formData); //print the correct stuff
try {
const url = `${auth.getApi()}/FileUpload`;
const resp = await fetch(url, {
method: "POST",
body: formData,
headers: new Headers({
Authorization: auth.BasicAuth(),
"Content-Type": "multipart/form-data",
}),
});
return resp;
} catch (e) {
console.log(e);
return e;
}
}
And the is my c# backend code
public class FileUploadController : ApiController
{
private static readonly ILog logger = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
private const string ALLOWED_CORS_ORIGINS = "*";
[BasicAuthenticationFilter(true)]
[HttpPost]
public async Task<IHttpActionResult> Post()
{
try
{
NameValueCollection formData = HttpContext.Current.Request.Params;
string fileType = formData.Get("fileType"); // it says it's null
if (string.IsNullOrEmpty(fileType)) //true
I have a working ASP.NET Core Web API that I'm currently refactoring to an Azure Function. An image is uploaded in the front end, sent to the Azure Function as a POST request where it is uploaded to Azure Blob Storage. Right now my Azure Function works completely fine when testing with Postman but does nothing when I actually use my client application. A postman request will hit my breakpoint in the azure function but a c# httpclient request does not.
FRONT END CODE
public partial class ImageUpload
{
[Inject]
public HttpClient HttpClient { get; set; }
public string ImgUrl { get; set; }
private async Task HandleSelected(InputFileChangeEventArgs e)
{
var imageFile = e.File;
if (imageFile == null)
return;
var resizedFile = await imageFile.RequestImageFileAsync("image/png", 300, 500);
using (var ms = resizedFile.OpenReadStream(resizedFile.Size))
{
var content = new MultipartFormDataContent();
content.Headers.ContentDisposition = new ContentDispositionHeaderValue("form-data");
content.Add(new StreamContent(ms, Convert.ToInt32(resizedFile.Size)), "image", imageFile.Name);
var response = await HttpClient.PostAsync("url/to/my/azure/function/api", content);
ImgUrl = await response.Content.ReadAsStringAsync();
}
}
}
Azure Function API
public static class Upload
{
[FunctionName("Upload")]
public static async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Function, "post", Route = null)] HttpRequest req,
ILogger log)
{
var formCollection = await req.ReadFormAsync();
var file = formCollection.Files.First();
if (file.Length > 0)
{
var container = new BlobContainerClient("connection string to blob storage", "upload-container");
var createResponse = await container.CreateIfNotExistsAsync();
if (createResponse != null && createResponse.GetRawResponse().Status == 201)
await container.SetAccessPolicyAsync(Azure.Storage.Blobs.Models.PublicAccessType.Blob);
var blob = container.GetBlobClient(file.FileName);
await blob.DeleteIfExistsAsync(DeleteSnapshotsOption.IncludeSnapshots);
using (var fileStream = file.OpenReadStream())
{
await blob.UploadAsync(fileStream, new BlobHttpHeaders { ContentType = file.ContentType });
}
return (ActionResult)new OkObjectResult(blob.Uri.ToString());
}
return new BadRequestObjectResult("Error");
}
}
Can anyone point me in the right direction of what I'm messing up?
Add this setting to the local.settings.json
"Host": {
"LocalHttpPort": 7071,
"CORS": "*"
}
I need to save the changes I make in my model through API call in my database. I have checked my API is working fine when I am running it individually on Web. But its giving me an error StatusCode: 405, ReasonPhrase: 'Method Not Allowed'. I am trying to send and object and trying to see whether the request made was completed or not. When I am trying to debug it, it is not sending hit on my API controller.
Here is my model class:
public class Customer
{
[Required]
public Guid CustomerId { get; set; }
public int Age { get; set; }
public int Phone { get; set; }
}
PUT Method in API:
[HttpPut]
[Route("api/[controller]/{customer}")]
public IActionResult EditCustomer(Customer customer)
{
var cust = _customerData.EditCustomer(customer);
if (cust == string.Empty)
{
return Ok();
}
else
{
return new StatusCodeResult(StatusCodes.Status500InternalServerError);
}
}
The method I am using in project to call API:
using (HttpClient client = new HttpClient())
{
client.BaseAddress = new Uri(apiBaseUrl);
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(
new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json")
);
var sum = await client.PutAsJsonAsync("api/Customer/", customer);
if (sum.StatusCode == System.Net.HttpStatusCode.OK)
{
return RedirectToActionPermanent(actionName: "SingIn");
}
else
{
TempData["msg"] = "There is an error";
return View();
}
where baseaddress= {https://localhost:44398/}
EditCustomer Method
public string EditCustomer(Customer customer)
{
try
{
var pro = _customerContext.Customer.Where(e => e.CustomerId == customer.CustomerId).FirstOrDefault();
pro.Age = customer.Age;
pro.Phone = customer.Phone;
pro.Name = customer.Name;
_customerContext.Entry(pro).State = EntityState.Modified;
_customerContext.SaveChanges();
}
catch(Exception e)
{
return e.Message;
}
return string.Empty;
}
You need to fix your action route by removing {Customer}, since you send customer in request body, not as a route value
[Route("~/api/Customer")]
and request
var sum = await client.PutAsJsonAsync("/api/Customer", customer);
or better fix the acttion route name to meaningfull
[Route("~/api/EditCustomer")]
and
var sum = await client.PutAsJsonAsync("/api/EditCustomer", customer);
AsJsonAsync sometimes causes problems
try this code
var json = JsonSerializer.Serialize(customer);
//or if you are using Newtonsoft
var json = JsonConvert.SerializeObject(customer);
var contentData = new StringContent(json, Encoding.UTF8, "application/json");
var response = await client.PutAsync("/api/Customer", contentData);
if (response.IsSuccessStatusCode)
return RedirectToActionPermanent("SingIn");
else
{
TempData["msg"] = "There is an error";
return View();
}
but IMHO I would prefer to use
client.PostAsync("/api/EditCustomer", contentData);
instead of Put.
and added [FromBody] to action
[HttpPost("~/api/EditCustomer")]
public IActionResult EditCustomer([FromBody] Customer customer)
I am no pro in web APIs but I suspect it could be due to the fact that the API expects customer to be in request URL.
Try and change the API route to [Route("api/[controller]")]
This could've been a comment but I don't have enough reputation :)
I have two problems. 1 is that the c# function does not get the js values. Even though when I step through the javascript, the values are in fact there.
The other problem is that my c# post returns a 404 not found error and I see that the debugger never even goes to my other service on localhost123.
Any advice?
This is my angularjs code below
var AddToGroupIds = [];
var RemoveFromGroupIds = [];
angular.forEach($scope.vm.mailingLists, function(f){
if(f.Selected == true){
AddToGroupIds.push(f.Id);
}
else{
RemoveFromGroupIds.push(f.Id);
}
});
return $http({
method: 'POST',
url: '/Contacts/UpdateSubscription',
data: { AddToGroups: AddToGroupIds, RemoveFromGroups: RemoveFromGroupIds, email: 'test#test.com' }
})
.then(function (data) { return data.data; })
this is my c# code below:
[HttpPost]
public void UpdateSubscription(List<int> AddToGroups, List<int> RemoveFromGroups, string email)
{
HttpWebRequest req = null;
var text = "";
try
{
req = HttpWebRequest.CreateHttp(string.Format("http://localhost:123/api/Api/ImmediateUpload?AddToGroupIds={0}&RemoveFromGroupIds={1}&email={2}", AddToGroups, RemoveFromGroups, email));
req.PreAuthenticate = true;
req.Credentials = new NetworkCredential("123", "123", "123");
var res = req.GetResponse();
using (var sr = new StreamReader(res.GetResponseStream()))
{
text = sr.ReadToEnd();
}
res.Close();
}
catch (Exception e)
{
text = e.ToString();
}
}
You cannot post multiple parameters that way. See this article. You can use JObject as the article mentions, or create a UpdateSubscriptionRequest model containing the parameters:
public class UpdateSubscriptionRequest
{
public List<int> AddToGroups { get; set; }
public List<int> RemoveFromGroups { get; set;}
public string Email { get; set;}
}
And change your controller action to:
public void UpdateSubscription([FromBody]UpdateSubscriptionRequest request)
I'm trying to come up with a way to post to a Web API controller with one object but have a different, processed, object return. None of the methods I've been able to find have solved the issue.
Here's my method in my MVC project that posts to my Web API project
public dynamic PostStuff<X>(string action, X request)
{
using (var client = new HttpClient())
{
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
var task = client.PostAsJsonAsync(new Uri("host/api/somecontroller/post, request);
task.Wait();
var response = task.Result;
return response;
}
}
This is my Web API controller code
public HttpResponseMessage Post([FromBody] FooObject foo)
{
var res = Request.CreateResponse(HttpStatusCode.OK,
new ValidationResponse<FooObject>
{
ID = new Random().Next(1000, 1000000),
Content =
new List<IContent>()
{
{
new Content()
{
Name = "TheContent",
Type = "SomeType",
Value = "This is some content for the page : " + foo.Bar
}
}
},
Product = new ProductFoo(),
Validated = true
});
return res;
}
}
When I put a break in my WebAPI controller code, the res variable is correctly created. Once the processing goes back to the PostStuff method, all I get is a StreamResponse with no trace of the ValidationResponse object created in the Web API controller. There are no errors but I can't use anything in the response beyond that the post succeeded. How can I extract the ValidationResponse from my posting method?
I have built myself this generic function as a helper for POSTing to Web API
Usage
var result = PostAsync<MyDataType, MyResultType>("http://...", MyData)
Function
public async Task<U> PostAsync<T, U>(string url, T model)
{
using (HttpClient httpClient = new HttpClient(httpHandler))
{
var result = await httpClient.PostAsJsonAsync<T>(url, model);
if (result.IsSuccessStatusCode == false)
{
string message = await result.Content.ReadAsStringAsync();
throw new Exception(message);
}
else
{
return await result.Content.ReadAsAsync<U>();
}
}
}
And HttpClientHandler configured for Windows Auth
protected HttpClientHandler httpHandler = new HttpClientHandler() { PreAuthenticate = true, UseDefaultCredentials = true };