C# how to capture async results? - c#

I am a newbie to C# & am trying to use the Lync SDK to search for a Lync user programmatically to get their status. I am not sure how to pass the results to the webservice response when the async callback gets executed.
This is the code:
Webservice Controller GET endpoint:
// GET: api/Lync/5
public String Get(int id)
{
log.Info("[GET] Search for Lync User: " + id);
Lync lync = new Lync();
////lync.signIn();
Boolean isSignedIn = lync.isUserSignedIn();
if (isSignedIn)
{
log.Info("User is Signed In");
Console.Write("Enter search key : ");
lync.Search("medina");
//lync.Search("medina",
//(lyncContacts) =>
//{
// log.Debug("Search Results Callback fired!");
// log.Info("Results found: " + lyncContacts.Count);
// return lyncContacts.ToString();
//});
//Console.WriteLine(name);
//Console.ReadLine();
return "testUser";
}
else
{
log.Info("User is not Signed In!");
// TODO: Return status 500
return "testUser";
}
//Console.ReadLine();
//return "testUser";
}
The above method calls the business service lync.search(..) which is as follows:
public void Search(string searchKey)
{
List<LyncContact> contactList = new List<LyncContact>();
//List<ContactInformationType> ContactInformationList = new List<ContactInformationType>();
//ContactInformationList.Add(ContactInformationType.Activity);
//ContactInformationList.Add(ContactInformationType.Availability);
// ContactInformationList.Add(ContactInformationType.CapabilityString);
//ContactSubscription contactSubscription = LyncClient.GetClient().ContactManager.CreateSubscription();
Console.WriteLine("Searching for contacts on " + searchKey);
LyncClient.GetClient().ContactManager.BeginSearch(
searchKey,
(ar) =>
{
SearchResults searchResults = LyncClient.GetClient().ContactManager.EndSearch(ar);
if (searchResults.Contacts.Count > 0)
{
log.Info("Search results found: " + searchResults.Contacts.Count);
Console.WriteLine(searchResults.Contacts.Count.ToString() + " found");
foreach (Contact contact in searchResults.Contacts)
{
String displayName = contact.GetContactInformation(ContactInformationType.DisplayName).ToString();
ContactAvailability currentAvailability = (ContactAvailability)contact.GetContactInformation(ContactInformationType.Availability);
Console.WriteLine(
contact.GetContactInformation(ContactInformationType.DisplayName).ToString() + " " + contact.GetContactInformation(ContactInformationType.Availability).ToString());
log.Debug("Display Name: " + displayName);
log.Debug("Availability: " + currentAvailability);
LyncContact lyncContact = new LyncContact.Builder().DisplayName("Snehil").Availability("Busy").build();
contactList.Add(lyncContact);
//done(contactList);
}
return;
}
else
{
log.Info("No Results found!");
//done(contactList);
return;
}
},
null);
} else
{
log.Info("No Results found!");
//done(contactList);
return;
}
},
null);
}
I tried to use Task+await but I am having a hard time trying to figure out how can i return the results from the callback funtion as results from the search method. How do i capture this in the controller class?

If you would like to use the async/await pattern for this use Task.FromAsync https://msdn.microsoft.com/en-us/library/system.threading.tasks.taskfactory.fromasync(v=vs.110).aspx
Google for examples. Like this:
public async Task<List<LyncContact>> SearchAsync(string searchKey)
{
List<LyncContact> contactList = new List<LyncContact>();
Console.WriteLine("Searching for contacts on " + searchKey);
var cm = LyncClient.GetClient().ContactManager;
var searchResults = await Task<SearchResults>.Factory.FromAsync<String>(
cm.BeginSearch,
cm.EndSearch, searchKey, null);
if (searchResults.Contacts.Count > 0)
{
Console.WriteLine(searchResults.Contacts.Count.ToString() + " found");
foreach (Contact contact in searchResults.Contacts)
{
String displayName = contact.GetContactInformation(ContactInformationType.DisplayName).ToString();
ContactAvailability currentAvailability = (ContactAvailability)contact.GetContactInformation(ContactInformationType.Availability);
Console.WriteLine(
contact.GetContactInformation(ContactInformationType.DisplayName).ToString() + " " + contact.GetContactInformation(ContactInformationType.Availability).ToString());
LyncContact lyncContact = new LyncContact.Builder().DisplayName("Snehil").Availability("Busy").build();
contactList.Add(lyncContact);
}
}
return contactList
}
In the controller you then can do:
public async Task<String> Get(int id)
{
log.Info("[GET] Search for Lync User: " + id);
Lync lync = new Lync();
////lync.signIn();
Boolean isSignedIn = lync.isUserSignedIn();
if (isSignedIn)
{
log.Info("User is Signed In");
Console.Write("Enter search key : ");
var lyncContacts = await SearchAsync("medina");
log.Info("Results found: " + lyncContacts.Count);
return lyncContacts.ToString();
}
else
{
log.Info("User is not Signed In!");
// TODO: Return status 500
return "testUser";
}
//Console.ReadLine();
//return "testUser";
}
Or you can use a TaskCompletionSource, see http://blog.stephencleary.com/2012/07/async-interop-with-iasyncresult.html (older post but principle is still valid.
Warning
I cannot compile this since I do not have access to the libraries you use. It should work but there might be some compile errors
What does it do
Callbacks are not a nice model to work with, as you have found out. using Tasks and the async/await model are far easier to work with. Luckily for you and me they have created several methods that can act as a bridge between the old and the new model. Using Task.FromAsync you can transform IAsyncResult methods to an easier to use Task based methodology. Stephen Cleary has written some nice blogs about them. See the post I mentioned earlier.

It looks like, by looking at your commented out code, that you tried to provide some sort of callback to your Search function. That would work, it should be of type Action<Microsoft.Lync.Model.SearchResults>
public void Search(string searchKey, Action<SearchResults> callback)
{
....
LyncClient.GetClient().ContactManager.BeginSearch(
searchKey,
(ar) =>
{
SearchResults searchResults = LyncClient.GetClient().ContactManager.EndSearch(ar);
callback(searchResults);
});
....
}
Then just uncomment the code in your controller:
lync.Search("medina",
(lyncContacts) =>
{
log.Debug("Search Results Callback fired!");
log.Info("Results found: " + lyncContacts.AllResults.Count);
});

Related

ASP.NET Core 2.1 Calling Rest API in a schedule

I have this ASP.NET Core 2.1 web application. The admin gets online game codes from the page below. Selecting the game (Razer Gold Pin), quantity and email to send those codes.
A Rest API call is made for the purchase. There is a limit for purchasing, a maximum of 200 purchases can be made at one time.
At first, there wasn't much to buy so there was no problem. But the demand has increased, when there are 10 thousand, 20 thousand purchases, it is necessary to purchase from this screen for hours. I wonder can we make a large number of purchases without waiting in front of the screen with scheduling?
Here is the the Javascript in the cshtml:
$("#btn_add").html(
'<span class="spinner-border spinner-border-sm" role="status" id="spinner" aria-hidden="true"></span> Loading...'
);
var selText = $("#validationCustom04").val();
var gameName = $("#validationCustom04 option:selected").text();
var quantity = $("#quantity").val();
var email = $("#email").val();
var price = $("#total").val();
var company = $("#validationCustom05").val();
if ($("#total").val() !== '') {
price = $("#total").val();
}
var serviceUrl = '/GameBanks/A101PinAsync?quantity=' + quantity + '&game=' + selText + '&email=' + email + '&prc=' + price + '&gameName=' + gameName + '&company=' + company;
$.ajax({
type: "GET",
url: serviceUrl,
dataType: 'json',
success: function (data) {
//alert(JSON.stringify(data));
ShowResult(data);
$("#spinner").remove();
$("#btn_add").html('Add');
}, error: function (xhr, status, error) {
$("#spinner").remove();
$("#btn_add").html('Add');
var errorMessage = xhr.responseText;
alert('Error - ' + errorMessage);
}
});
Here is the controller method:
[HttpGet]
public async Task<IActionResult> A101PinAsync(int quantity, string game, string email, int prc, string gameName, string company)
{
var price = 0;
try
{
string result = null;
var dList = new DemoList();
if (prc > 0)
{
price = prc;
}
var games = new Game { Product = game, Quantity = quantity };
var content = await games.CallGameAsync("Test", "12345", game, quantity, company);
var deserializedResult = JObject.Parse(content);
var root = JsonConvert.DeserializeObject<Root>(content);
if ((string)deserializedResult["coupons"]?[0]?["Serial"] == null)
{
result = result + gameName + ":" + (string)deserializedResult["Message"] + "\n";
}
else
{
foreach (var coupon in root.coupons)
{
result = result + gameName + " Serial:" + coupon.Serial + " Pin:" + coupon.Pin + "\n";
}
}
dList.gameList = result;
// NLOG
NLogPin(logger, User.Identity.Name, DateTime.Now, result, email);
return new JsonResult(dList);
}
catch (Exception e)
{
// NLOG
NLog(logger2, "OyunPalas " + e.Message, DateTime.UtcNow,0);
return StatusCode(500,e.Message);
}
}
Here is the Web API calling method:
public class Game
{
public string Username { get; set; }
public string Password { get; set; }
public string Product { get; set; }
public int Quantity { get; set; }
public string ShopNo { get; set; }
private static readonly Logger logger2 = LogManager.GetLogger("exceptionFile");
public async Task<string> CallGameAsync(string username, string password, string product, int quantity, string company)
{
try
{
HttpWebRequest request = null;
request = (HttpWebRequest)WebRequest.Create("http://111.111.111.111:1907//api/v2/web/purchase");
var svcCredentials = Convert.ToBase64String(Encoding.ASCII.GetBytes(username + ":" + password));
request.Headers.Add("Authorization", "Basic " + svcCredentials);
request.Method = "POST";
request.KeepAlive = false;
request.ContentType = "application/x-www-form-urlencoded";
var content = new FormUrlEncodedContent(new[]
{
new KeyValuePair<string, string>("productCode", product),
new KeyValuePair<string, string>("quantity", quantity.ToString()),
new KeyValuePair<string, string>("shopNo", company),
new KeyValuePair<string, string>("safeNo", company),
new KeyValuePair<string, string>("cashierNo", company)
});
var urlEncodedString = await content.ReadAsStringAsync();
using (var streamWriter = new StreamWriter(await request.GetRequestStreamAsync()))
{
await streamWriter.WriteAsync(urlEncodedString);
}
var httpResponse = (HttpWebResponse) (await request.GetResponseAsync());
var response = new HttpResponseMessage
{
StatusCode = httpResponse.StatusCode,
Content = new StreamContent(httpResponse.GetResponseStream()),
};
//Read response
var htmlResponse = await response.Content.ReadAsStringAsync();
return htmlResponse;
}
catch (Exception e)
{
//NLOG
NLog(logger2, "Test" + e.Message);
throw;
}
}
public void NLog(Logger logger, string user)
{
var sb = new StringBuilder();
sb.AppendLine("Test: " + user);
logger.Info(sb.ToString());
}
}
I have to pass product (game type) to the job. I read the https://www.quartz-scheduler.net/documentation/quartz-3.x/tutorial/more-about-jobs.html#jobdatamap documentation but not fully understand how to use in my case. And I wonder if I can stop/cancel the job from this page before its end time?
I really appreciate any help you can provide. I'm sorry if the question is too silly as I have no experience with this scheduling.
Edit:
I thought of a solution like this, but I'm not sure if it's viable.
The user will enter which game, how many they want to buy, and their e-mail address on the screen.
This data will be saved in a table (BulkRequest) containing this information and the status field in the SQL database. (game: ABC, quantity:20000, status:0)
One IHostedService will get the saved data in a schedule which status=0 from the BulkRequest table and the save requests with status information in a new table (GameRequests) in multiples of 100. (game: ABC, quantity:100, status:0)
Another IHostedService will get the records in 10 minutes schedule which status=0 from the GameRequests table and make the 3rd party API call. Write a response to another table (GameResponses) and set the status=1 if there is no error.
When all requests are completed (status = 1), the status in the BulkRequest table will also be updated to 1.
A third IHostedService will check the status of BulkRequest, if status=1 then maybe a fourth service will prepare a file and send the responses to the e-mail.
I do suggest reviewing message queuing tools such RabbitMQ. With this architecture you will do all the jobs using messaging protocols with no need for "scheduling" and any "x minutes schedule" s would be ommited. Also it is more stable and error handling could be done using best practice standards. More important, it is scalable and you can have multiple hostedServices even on different servers process items in queue:
When request is registered send a message (publish) to the queue.
The hostedService1 receives the item and process it (consume). Multiple services may subscribe to process requests.
The hostedService1 may do all the job it self or even dispatch some parts to other services using the same method
The solution you proposed is doing the same job with an ad-hoc process. In fact you are reinventing the wheel. Hope it helps.

Getting AccessDenied when enumerating BLE characteristics

I am trying to convert a working UWP app to a WPF one using .NET 6 with Target framework monikers.
I can scan BLE devices with no issues and connect to them, however I get an AccessDenied status at the next GetCharacteristicsAsync's call
I have checked characteristics properties and they all have the flag corresponding to the operation I am doing.
My OS version is 1943 and my target version is net6.0-windows10.0.19041.0
I have tried using BLuetoothCacheMode.Cached and Uncached: same result.
Edit:I have tried to call RequestAccessAsync and it does not seem to do anything. However this same method returns Allowed.
Here is a piece of code I use, it is inpired by the microsoft docs.
public async Task<byte[]> ReadAsync(string serviceUuid, string characteristicUuid)
{
using var bluetoothLEDevice = await BluetoothLEDevice.FromIdAsync(_device.DeviceId);
GattDeviceServicesResult result1 = await bluetoothLEDevice.GetGattServicesAsync(BluetoothCacheMode.Cached);
if (result1.Status == GattCommunicationStatus.Success)
{
foreach (var service in result1.Services)
{
if (service.Uuid.ToString() == serviceUuid)
{
GattCharacteristicsResult result2 = await service.GetCharacteristicsAsync(BluetoothCacheMode.Cached);
if (result2.Status == GattCommunicationStatus.Success)
{
foreach (var characteristic in result2.Characteristics)
{
if (characteristic.Uuid.ToString() == characteristicUuid)
{
var result = await characteristic.ReadValueAsync();
if (result.Status == GattCommunicationStatus.Success)
{
var reader = DataReader.FromBuffer(result.Value);
byte[] contents = new byte[reader.UnconsumedBufferLength];
reader.ReadBytes(contents);
return contents;
}
else
{
throw new Exception("Read: Cannot read characteristic: " + characteristic + " " + Enum.GetName(typeof(GattCommunicationStatus), result.Status));
}
}
}
}
else
{
// This exception is thrown
throw new Exception("Read: Cannot get characteristics for service: " + service.Uuid + " " + Enum.GetName(typeof(GattCommunicationStatus), result2.Status));
}
}
}
}
bluetoothLEDevice.Dispose();
return null;
}

Exception Logging Without using try catch - ASP.NET WEB API

I need to log exceptions and bad requests in my API. Currently I am using try catch to catch the exception and add to my logs in the catch block. Is this the right way? I read about Global Error Handling in ASP.NET. How can I implement that approach for this case?
Below is my API Controller example:
[HttpPost]
[Authorize]
[ValidateModel]
[Route("CheckProgramOwner")]
public async Task<IHttpActionResult> CheckProgramOwner([FromBody] CheckProgramOwner _data)
{
try
{
using (var db = new VisualVoiceFlowEntities())
{
var Result= await db.VVF_ScriptFlow.Where(s => s.ProgramId == _data.ProgramId).OrderByDescending(s => s.ID).FirstOrDefaultAsync();
if(Result== null)
{
Log.Error("Error in CheckProgramOwner POST Request - " + "ProgramId not found");
return Content(HttpStatusCode.BadRequest, "ProgramId not found");
}
string CurrentOwner = Result.ReadBy.ToString();
return Ok(CurrentOwner);
}
}
catch (Exception ex)
{
Log.Error("Error in CheckProgramOwner POST Request - " + ex.Message, ex);
NewRelic.Api.Agent.NewRelic.NoticeError("Error in CheckProgramOwner POST Request - " + ex.Message, null);
return Content(HttpStatusCode.InternalServerError, "Internal Server Error. Please Contact Admin.");
}
}
If you read the document previously posted by Casey, you will find a link to the following document, which explains how to implement and register an exception filter globally:
https://learn.microsoft.com/en-us/aspnet/web-api/overview/error-handling/exception-handling#registering_exception_filters
You could then implement your logging logic in the filter's body thus avoiding having to repetitively log errors on each try/catch. I would suggest logging the more obvious errors using your original approach and use the filter to log any other errors (that you might not expect.)
I did it using ExceptionFilter.
I created Exception Filter Class as below -
public class MyExceptionFilter : ExceptionFilterAttribute
{
public override void OnException(HttpActionExecutedContext context)
{
log4net.ThreadContext.Properties["addr"] = HttpContext.Current.Request.UserHostAddress;
log4net.ThreadContext.Properties["Hostname"] = Dns.GetHostName().ToString();
log4net.ThreadContext.Properties["PCName"] = Dns.GetHostAddresses(Environment.MachineName)[0].ToString();
string RequestMethod = context.Request.Method.Method;
dynamic ControllerInfo = context.ActionContext.ControllerContext.Controller;
string RequestName = ControllerInfo.Url.Request.RequestUri.LocalPath.ToString().Replace("/api/", "").Replace("/VVFAPI", "");
Log.Error("Error in " + RequestName +" "+ RequestMethod+ " Request - " + context.Exception.Message, context.Exception);
NewRelic.Api.Agent.NewRelic.NoticeError("Error in " + RequestName + " " + RequestMethod + " Request - " + context.Exception.Message, null);
HttpResponseMessage msg = new HttpResponseMessage(HttpStatusCode.InternalServerError)
{
Content = new StringContent("Internal Server Error. Please Contact Admin."),
ReasonPhrase = "Critical Exception."
};
context.Response = msg;
}
}
Also, I changed my controller accordingly
[HttpPost]
[Authorize]
[ValidateModel]
[MyExceptionFilter]
[Route("CheckProgramOwner")]
public async Task<IHttpActionResult> CheckProgramOwner([FromBody] CheckProgramOwner _data)
{
Log.Info("CheckProgramOwner POST Request");
using (var db = new VisualVoiceFlowEntities())
{
var Result = await db.VVF_ScriptFlow.Where(s => s.ProgramId == _data.ProgramId).OrderByDescending(s => s.ID).FirstOrDefaultAsync();
if (Result == null)
{
Log.Error("Error in CheckProgramOwner POST Request - " + "ProgramId not found");
return Content(HttpStatusCode.BadRequest, "ProgramId not found");
}
string CurrentOwner = Result.ReadBy.ToString();
return Ok(CurrentOwner);
}
}

Null Reference Exception From Azure Notification Hubs

I have been using Azure Notification Hubs along with GCM to send notifications to the users of my app. This was all working great until I published the app to the Play Store.
Now it gives a 500 Server error whenever I try to post a notification. I have no idea why this error is happening. Perhaps the app is not picking up the RavenDB where the notifications are stored?
But it looks more like the service is not getting back users that are registered on the Hub. I really don't know... Any help would be so appreciated!
This is my stacktrace when run locally, it is the same but less detailed when published:
"Message": "An error has occurred.",
"ExceptionMessage": "Value cannot be null.\r\nParameter name: source",
"ExceptionType": "System.ArgumentNullException",
"StackTrace": " at System.Linq.Enumerable.Where[TSource](IEnumerable`1 source, Func`2 predicate)\r\n
at AcademicAssistantService.Controllers.NotificationController.<GetRecipientNamesFromNotificationHub>d__8.MoveNext()
in C:\\Users\\Kenneth\\Documents\\College\\Semester 8\\AcademicAssistantService\\AcademicAssistantService\\Controllers\\NotificationController.cs:line 105
This is the Controller action:
// POST api/notification
public async Task<IHttpActionResult> Post([FromBody]Notification notification, String key)
{
var notificationToSave = new Notification
{
NotificationGuid = Guid.NewGuid().ToString(),
TimeStamp = DateTime.UtcNow,
Message = notification.Message,
SenderName = notification.SenderName
};
var recipientNames = await GetRecipientNamesFromNotificationHub(key);
var recipientNamesString = CreateCustomRecipientNamesString(recipientNames);
string notificationJsonPayload =
"{\"data\" : " +
" {" +
" \"message\": \"" + notificationToSave.Message + "\"," +
" \"senderName\": \"" + notificationToSave.SenderName + "\"," +
" \"recipientNames\": \"" + recipientNamesString + "\"" +
" }" +
"}";
if (key == null)
{
var result = await _hubClient.SendGcmNativeNotificationAsync(notificationJsonPayload);
notificationToSave.TrackingId = result.TrackingId;
notificationToSave.Recipients = recipientNames;
}
else
{
foreach (string r in recipientNames)
{
if ((r != notification.SenderName))
{
var result = await _hubClient.SendGcmNativeNotificationAsync(notificationJsonPayload, "user:" + r);
notificationToSave.TrackingId = result.TrackingId;
notificationToSave.Recipients = recipientNames;
}
}
}
await Session.StoreAsync(notificationToSave);
return Ok(notificationToSave);
}
To get names from hub:
public async Task<List<string>> GetRecipientNamesFromNotificationHub(String key)
{
var registrationDescriptions = await _hubClient.GetAllRegistrationsAsync(Int32.MaxValue);
var recipientNames = new List<String>();
foreach (var registration in registrationDescriptions)
{
if (registration is GcmRegistrationDescription)
{
var userName = registration.Tags
.Where(t => t.StartsWith("user"))
.Select(t => t.Split(':')[1].Replace("_", " "))
.FirstOrDefault();
userName = userName ?? "Unknown User";
Conversation convo = db.Conversations.Find(key);
foreach (User u in convo.Users)
{
if (u.Email == userName && !recipientNames.Contains(userName))
{
recipientNames.Add(userName);
}
}
}
}
return recipientNames;
}
Could you use Service Bus Explorer and verify indeed you have tags starts with "user". And I also see you are using GetAllRegistrationsAsync API, which is recommend to use only for debugging purpose. This is heavily throttled API.
Thanks,
Sateesh

MVC3 Async jQuery progress bar on a insert query

So I am trying to add an async progress bar on a really slow and long query that inserts a bunch of rows to a database. My implementation is based off this example: http://blog.janjonas.net/2012-01-02/asp_net-mvc_3-async-jquery-progress-indicator-long-running-tasks
Here is the javascript code for the progress bar
function updateMonitor(taskId, status) {
$("#" + taskId).html("Task [" + taskId + "]: " + status);
}
//other code
if (doSend == true) {
$.post("/SendBatch/HandleBatchRequest", {
//other code
},
function (taskId) {
// Init monitors
//breakpoint here does not stop it, it never enters this somehow?
$("#monitors").append($("<p id='" + taskId + "'/>"));
updateMonitor(taskId, "Started");
// Periodically update monitors
var intervalId = setInterval(function () {
$.post("SendBatch/Progress", { id: taskId }, function (progress) {
if (progress >= 100) {
updateMonitor(taskId, "Completed");
clearInterval(intervalId);
} else {
updateMonitor(taskId, progress + "%");
}
});
}, 100);
}
,"html");
Then there is the DIV within the display part of the website
<div id="monitors"></div>
Here is how the controller looks
public SendBatchController
//some code
private static IDictionary<Guid, int> tasks = new Dictionary<Guid, int>();
public ActionResult HandleBatchRequest(
//some code
)
{
var taskId = Guid.NewGuid();
tasks.Add(taskId, 0);
var batchId = Guid.NewGuid().ToString("N");
var costd = cost.ToDecimal();
IEnumerable<BatchListModel> customers;
try
{
customers = new CustomerService(_customerRepository.Session).GetCustomers(
//some code
);
}
catch (Exception err)
{
return Json(err.Message);
}
if (doSend)
{
var sent = 0;
foreach (var c in customers)
{
try
{
var usr = _customerRepository.LoadByID(c.ID);
var message = new ComLog
{
//insertions to log
};
_comLogRepository.Save(message);
sent++;
//progress bar part inside here that is important comes here:
tasks[taskId] = sent;
}
catch (Exception e)
{
Log.WriteLine("ERR:" + e);
}
tasks.Remove(taskId);
}
return Json(taskId);
}
return Json(customers.Count() + " customers");
}
public ActionResult Progress(Guid id)
{
return Json(tasks.Keys.Contains(id) ? tasks[id] : 100);
}
This does not work. The process works in the background. It is only the div that never shows up and never gives any indication. I know this is a lot of code to read but I am really stuck and would love some input on how to fix this.
try change your updateMonitor function into this :
function updateMonitor(taskId, status) {
$("#monitors").html("Task [" + taskId + "]: " + status);
}

Categories

Resources