I am building a Twilio IVR using WebAPI and hit a bit of a snag. I am trying to loop through the recordings and at the end of each one offer the option to press 1 to delete the recording.
I can't find any examples anywhere of this in a C# WebAPI implementation and the following won't work and I am not sure how to go about this.
What I know;
Need to GATHER the digits entered
Somehow pass back the recording sid to the api to know which recording to delete.
Here is the code I've written so far:
public HttpResponseMessage Messages() {
string baseUrl = Url.Request.RequestUri.GetComponents(UriComponents.SchemeAndServer, UriFormat.Unescaped);
var twilio = new TwilioRestClient(_accountSid, _authToken);
var recordings = twilio.ListRecordings(null, DateTime.Today, null, null);
var twilioResponse = new TwilioResponse();
if (recordings != null && recordings.Recordings.Count > 0)
{
var msgCount = 1;
var msgTotal = recordings.Recordings.Count();
foreach (var recording in recordings.Recordings)
{
var caller = twilio.GetCall(recording.CallSid);
var callerNumber = Regex.Replace(caller.From, #"([0-9]{1})", "$1,");
var callDate = recording.DateCreated.ToString("dddd MMMM d");
var callTime = recording.DateCreated.ToString("h m t");
twilioResponse.Say(string.Format("Playing message {0} of {1}, from {2} on {3} at {4} M.", msgCount, msgTotal, callerNumber, callDate, callTime),
new { voice = "woman" });
var voiceFile = string.Format("{0}2010-04-01/Accounts/{1}/Recordings/{2}.mp3", twilio.BaseUrl, _accountSid, recording.Sid);
twilioResponse.Play(voiceFile);
//twilioResponse.BeginGather(new
// {
// action = baseUrl + "/api/Recording/Delete",
// numDigits = 1,
// });
//twilioResponse.Say("TO DELETE THIS MESSAGE PRESS 1");
//twilioResponse.EndGather();
msgCount++;
}
}
return Request.CreateResponse(HttpStatusCode.OK, twilioResponse.Element, Configuration.Formatters.XmlFormatter);
}
I've worked with Twilio's API before but this is a new one for me and I can't really see if this is possible. The PHP example they have is doing it though so maybe I am simply missing the mark because I should be going a different way about this.
Twilio evangelist here.
What you have looks really close. Lets look at passing back the Recording SID in scenarios where the user presses one since thats relatively easy to do by using the action URL your setting on the Gather verb to hold some state for you:
action = baseUrl + "/api/Recording/Delete?recordingSid=" + recording.Sid;
Now when the user presses one, Twilio will make a request to the Action URL which includes the recordingSid parameter.
Once you've deleted the Recording, you can just redirect back to your Messages endpoint to continue to listen to more recordings. In order to keep track of which recordings you've listened to already, you might need to pass some additional parameters in the action URL that can you can pass through your delete workflow and back into the listen workflow.
Hope that helps.
Related
I'm trying to utilize Consul .NET API to register and fire health checks via TTL. First I'm registering my service with following code:
var address = node.Address;
var id = ServiceId(address);
var registration = new AgentServiceRegistration
{
ID = id,
Name = node.ClusterName,
Address = node.Address.Host,
Port = node.Address.Port.Value,
Check = new AgentServiceCheck
{
TTL = settings.AliveInterval, // 10sec
DeregisterCriticalServiceAfter = settings.AliveTimeout, // 60sec
}
};
// first, try to deregister service, if it has been registered previously
await consul.Agent.ServiceDeregister(registration.ID);
await consul.Agent.ServiceRegister(registration);
Right afterwards, I'm trying to fire a TTL via:
await consul.Agent.PassTTL("service:" + ServiceId(addr), string.Empty);
However, what I end up with is an exception thrown during PassTTL: Consul.ConsulRequestException: Unexpected response, status code InternalServerError: CheckID "service:{service-id}" does not have associated TTL
And the related log from consul agent itself:
[ERR] http: Request PUT /v1/agent/check/pass/service:{service-id}, error: CheckID "service:{service-id}" does not have associated TTL from=127.0.0.1:25419
I'd like to know what I'm doing wrong here.
I'm using consul agent -dev (version: 1.0.1) and Nuget package Consul (version: 0.7.2.3).
Turns out AgentServiceRegistration.Check property is pretty useless. I've achieved the expected result with CheckRegister method.
Here's the code
var registration = new AgentServiceRegistration
{
ID = "serviceId",
Name = node.ClusterName,
Address = node.Address.Host,
Port = node.Address.Port.Value
};
// first, try to deregister service, if it has been registered previously
await consul.Agent.ServiceDeregister(registration.ID);
await consul.Agent.ServiceRegister(registration);
await consul.Agent.CheckRegister(new AgentCheckRegistration()
{
ID = "checkId",
Name = "Check Name",
Status = HealthStatus.Passing,
TTL = settings.AliveInterval,
ServiceID = "serviceId",
DeregisterCriticalServiceAfter = settings.AliveTimeout, // 60sec
})
Now you can pass TTL via
await consul.Agent.PassTTL("checkId", string.Empty);
Just be sure to deregister your check afterwards
It looks like my example was missing a crucial detail here: a ServiceId(address) method was constructing a service ID in form of protocol://service#host:port/ which resulted in Consul complaining about lack of TTL. Changing it to service#host:port seems to fix the error.
I guess in this case a consul error message was very misleading.
Check if id format is:
"service:{service id}:{number}"
In your case, you must pass:
"service:" + ServiceId(addr) + ":1"
as your check id.
I am currently developing a simple c# formflow bot that captures the values and sends those values off to an external api, gets the json data back from the external api and creates Card Attachments based on the results returned. I am making the call to the external api in the OnCompletion delegate as follows, To keep it simple I am not passing any values to the api (For testing purposes)
.OnCompletion(async (context, profileForm) =>
{
var reply = context.MakeMessage();
var carsFromApi = await GetCarsAsync("/api/values");
reply.AttachmentLayout = AttachmentLayoutTypes.Carousel;
reply.Attachments = GetCards(carsFromApi);
await context.PostAsync(reply);
// Tell the user that the form is complete
})
I make the call to the api and store the results in "carsFromApi" , I step into that which is the following code snippet
private static async Task<List<Car>> GetCarsAsync(string path)
{
List<Car> car = new List<Car>();
HttpResponseMessage response = await client.GetAsync(path);
if (response.IsSuccessStatusCode)
{
car = await response.Content.ReadAsAsync<List<Car>>();
}
return await response.Content.ReadAsAsync<List<Car>>();
}
Problem is when I press F10 and go to the next line which is "reply.AttachmentLayout = AttachmentLayoutTypes.Carousel;". The local variable that stored the cars "carsFromApi " is now null. This is the part where it all falls over. I cant pass this "carsFromApi" to "reply.Attachments = GetCards(carsFromApi);" I have tried to store the data in a private variable but that also seems to be null. The external api is working because it just returns a list of static text for now. Any ideas? Thanks in advance.
Based on what you are describing it sounds that your code is not existing through the path of the if (response.IsSuccessStatusCode). Check if that point is reached as I suspect an exception or something is going wrong with the request.
Alternatively, you can try doing the request in the ResumeAfter<T> method you specified when calling the Form instead of that in the OnCompletion delegate
I have an application is ASP.net core and have integrated spreedly payment gateway for processing the payments. In my log files I can see that sometimes the payment controller executes twice. I generate an ID based on the time the request was received and the ID's are sometimes apart by 1 sec or at sometimes they are at the exact same time. This is resulting in charging the card twice only for few cases when this is happening. I cant seem to figure out what could be triggering this.
Following is the code that I am using
The user fills the application form and on the pay button click I am using this code to trigger spreedly
$('#REG').click(function () {
var options = {
company_name: "abcd",
sidebar_top_description: "Fees",
sidebar_bottom_description: "Only Visa and Mastercard accepted",
amount: "#string.Format("{0:c}",Convert.ToDecimal(Model.FeeOutstanding))"
}
document.getElementById('payment').value = 'App'
SpreedlyExpress.init(environmentKey, options);
SpreedlyExpress.openView();
$('#spreedly-modal-overlay').css({ "position": "fixed", "z-index": "9999", "bottom": "0", "top": "0", "right": "0", "left": "0" });
});
This opens the spreedly payment form as a popup where the user enters all the card information and hits the pay button. Which executes the payment controller
public async Task<IActionResult> Index(DynamicViewModel model)
{
if (ModelState.IsValid)
{
try
{
if (TempData.ContainsKey("PaymentFlag") && !String.IsNullOrEmpty(TempData["PaymentFlag"].ToString()))
{
// Some code logic that calls few async methods
//generate a id based on the time of current request
"APP-" + DateTime.Now.ToString("yyyyMMddHmmss-") + model.UserID;
// ... Other code here
}
The id that I generate is logged and I can see that some times in the log file that it ran twice for a customer with the ID having either the exact same time or there was a 1 sec difference. I have tested the double click scenario and also have put in some code to prevent double clicks. But still I cant seem to understand why sometimes this happens. It is not frequent. Its like 1 case that happens in 100 payments.
I have an action attribute to handle the duplicate requests. After putting in this code it did stopped the number of duplicate requests but not completely. Still in few cases some how the controllers gets called twice.
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class NoDuplicateRequestAttribute : ActionFilterAttribute
{
public int DelayRequest = 10;
// The Error Message that will be displayed in case of
// excessive Requests
public string ErrorMessage = "Excessive Request Attempts Detected.";
// This will store the URL to Redirect errors to
public string RedirectURL;
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
// Store our HttpContext (for easier reference and code brevity)
var request = filterContext.HttpContext.Request;
// Store our HttpContext.Cache (for easier reference and code brevity)
var cache = filterContext.HttpContext.RequestServices.GetService<IMemoryCache>();
// Grab the IP Address from the originating Request (example)
var originationInfo = request.HttpContext.Connection.RemoteIpAddress.ToString() ?? request.HttpContext.Features.Get<IHttpConnectionFeature>()?.RemoteIpAddress.ToString();
// Append the User Agent
originationInfo += request.Headers["User-Agent"].ToString();
// Now we just need the target URL Information
var targetInfo = request.HttpContext.Request.GetDisplayUrl() + request.QueryString;
// Generate a hash for your strings (appends each of the bytes of
// the value into a single hashed string
var hashValue = string.Join("", MD5.Create().ComputeHash(Encoding.ASCII.GetBytes(originationInfo + targetInfo)).Select(s => s.ToString("x2")));
string cachedHash;
// Checks if the hashed value is contained in the Cache (indicating a repeat request)
if (cache.TryGetValue(hashValue,out cachedHash))
{
// Adds the Error Message to the Model and Redirect
}
else
{
// Adds an empty object to the cache using the hashValue
// to a key (This sets the expiration that will determine
// if the Request is valid or not)
var opts = new MemoryCacheEntryOptions()
{
SlidingExpiration = TimeSpan.FromSeconds(DelayRequest)
};
cache.Set(hashValue,cachedHash,opts);
}
base.OnActionExecuting(filterContext);
}
This isn't an ASP.NET Core issue. I'm 99% certain there are in fact multiple requests coming from the client and ASP.NET Core is simply handling them as it is meant to.
An option for you would be to put a guid or other identifier on the page and send it with the request. In your Controller, check your cache or session to see if that identifier already exists. If it does, throw an exception or return Ok() or log the occurrence or whatever you want to do in that case but don't charge the card.
I am developing one application in ASP.NET with office365 REST API. I need to schedule a team event in office365 but before scheduling that event i need to check the available time of all team members. If a slot is free then only i need to setup a event for the team.
Lets assume i have 3 members team like , user1#someone.com , user2#someone.com, user3#clientone.com . I need to check the available times of all members on team and needs to show only compatable times. Lets assume user1 have a schedule meeting at 9:00am - 9:30 am then i need to hide that time why because user1 is not having free time.
How can i do this? Any idea?
Find Meeting Times (=Exchange FreeBusy) is in preview https://msdn.microsoft.com/en-us/office/office365/api/calendar-rest-operations#Findmeetingtimespreview but you should be able to use it via https://outlook.office.com/api/beta
Cheers
Glen
Finally i tried to use the free/busy code . My code as follows... I am following this procedure but i don't know either it is correct or not. I have office365 account and by passing credentials silently i am creating exchange server service. After that i am passing different domain attendee information as ORGANIZER and REQUIRED as follows. But it is returning all values not skipping any scheduled meetings for those users.
Lets assume user1#domain.com is ORGANIZER and user2#anotherdomain.com is REQUIRED for meeting . User1 have meeting scheduled at 7:00-7:30pm on daily basis but when i executed the following script it shows me 7:00-7:30pm as available for meeting. It supposed to block that time. Can you suggest somechanges to code and am i proceeding in correct way??
private static void GetSuggestedMeetingTimes(ExchangeService service)
{
List<AttendeeInfo> attendees = new List<AttendeeInfo>();
attendees.Add(new AttendeeInfo()
{
SmtpAddress = "user1#mydomain.com",
AttendeeType = MeetingAttendeeType.Organizer
});
attendees.Add(new AttendeeInfo()
{
SmtpAddress = "user2#anotherdomain.com",
AttendeeType = MeetingAttendeeType.Required
});
// Specify options to request free/busy information and suggested meeting times.
AvailabilityOptions availabilityOptions = new AvailabilityOptions();
availabilityOptions.GoodSuggestionThreshold = 49;
availabilityOptions.MaximumNonWorkHoursSuggestionsPerDay = 0;
availabilityOptions.MaximumSuggestionsPerDay = 40;
// Note that 60 minutes is the default value for MeetingDuration, but setting it explicitly for demonstration purposes.
availabilityOptions.MeetingDuration = 30;
availabilityOptions.MinimumSuggestionQuality = SuggestionQuality.Good;
availabilityOptions.DetailedSuggestionsWindow = new TimeWindow(DateTime.Now.AddDays(1), DateTime.Now.AddDays(2));
availabilityOptions.RequestedFreeBusyView = FreeBusyViewType.FreeBusy;
// Return free/busy information and a set of suggested meeting times.
// This method results in a GetUserAvailabilityRequest call to EWS.
GetUserAvailabilityResults results = service.GetUserAvailability(attendees,
availabilityOptions.DetailedSuggestionsWindow,
AvailabilityData.FreeBusyAndSuggestions,
availabilityOptions);
// Display suggested meeting times.
Console.WriteLine("Availability for {0} and {1}", attendees[0].SmtpAddress, attendees[1].SmtpAddress);
Console.WriteLine();
foreach (Suggestion suggestion in results.Suggestions)
{
Console.WriteLine("Suggested date: {0}\n", suggestion.Date.ToShortDateString());
Console.WriteLine("Suggested meeting times:\n");
foreach (TimeSuggestion timeSuggestion in suggestion.TimeSuggestions)
{
Console.WriteLine("\t{0} - {1}\n",
timeSuggestion.MeetingTime.ToShortTimeString(),
timeSuggestion.MeetingTime.Add(TimeSpan.FromMinutes(availabilityOptions.MeetingDuration)).ToShortTimeString());
}
}
int i = 0;
// Display free/busy times.
foreach (AttendeeAvailability availability in results.AttendeesAvailability)
{
Console.WriteLine("Availability information for {0}:\n", attendees[i].SmtpAddress);
foreach (CalendarEvent calEvent in availability.CalendarEvents)
{
Console.WriteLine("\tBusy from {0} to {1} \n", calEvent.StartTime.ToString(), calEvent.EndTime.ToString());
}
i++;
}
I am trying to get a list of all users in our instance of Desire2Learn using a looping structure through the bookmarks however for some reason it continuously loops and doesn't return. When I debug it it is showing massive amounts of users (far more than we have in the system as shown by the User Management Tool. A portion of my code is here:
public async Task<List<UserData>> GetAllUsers(int pages = 0)
{
//List<UserData> users = new List<UserData>();
HashSet<UserData> users = new HashSet<UserData>();
int pageCount = 0;
bool getMorePages = true;
var response = await Get<PagedResultSet<UserData>>("/d2l/api/lp/1.4/users/");
var qParams = new Dictionary<string, string>();
do
{
qParams["bookmark"] = response.PagingInfo.Bookmark;
//users = users.Concat(response.Items).ToList<UserData>();
users.UnionWith(response.Items);
response = await Get<PagedResultSet<UserData>>("/d2l/api/lp/1.4/users/", qParams);
if (pages != 0)
{
pageCount++;
if (pageCount >= pages)
{
getMorePages = false;
}
}
}
while (response.PagingInfo.HasMoreItems && getMorePages);
return users.ToList();
}
I originally was using the List container that is commented out but just switched to the HashSet to see if I could notice if duplicates where being added.
It's fairly simple, but for whatever reason it's not working. The Get<PagedResultSet<UserData>>() method simply wraps the HTTP request logic. We set the bookmark each time and send it on.
The User Management Tool indicates there are 39,695 users in the system. After running for just a couple of minutes and breaking on the UnionWith in the loop I'm showing that my set has 211,800 users.
What am I missing?
It appears that you’ve encountered a defect in this API. The next course of action is for you to have your institution’s Approved Support Contact open an Incident through the Desire2Learn Helpdesk. Please make mention in the Incident report that Sarah-Beth Bianchi is aware of the issue, and I will work with our Support team to direct this issue appropriately.