Runnable.Run / StartCoroutine calles to Watson services from Unity - unity3d

In my ExampleStreaming.cs script, once the user utterance is recognized as final, I send it to both the Watson Assistant service and the Tone Analyzer. Because I am keeping the scripts for each service separate as they are, I have to make calls within each script to access the other service. You can see the call I make to the Tone Analyzer below (the .SendToneAnalysis method):
private void OnRecognize(SpeechRecognitionEvent result, Dictionary<string, object> customData)
{
blah blah blah . . .
/// Only send the recognized speech utterance to the
/// Assistant once we know the user has stopped talking.
if (res.final)
{
string _conversationString = alt.transcript;
Runnable.Run( StopRecording(1f) ); // Stop the microphone from listening.
/// Message.
Dictionary<string, object> input = new Dictionary<string, object>
{
["text"] = _conversationString
};
MessageRequest messageRequest = new MessageRequest()
{
Input = input,
Context = _Context
};
_exampleAssistantV1_script.SendMessageAssistant(messageRequest);
_exampleToneAnalyzer.SendToneAnalysis(_conversationString);
. . .
In my ExampleToneAnalyzer.cs script, I make a simple call to the event-handling methods that are meant to contact the service and also handle success & failure:
public void SendToneAnalysis(string conversationString)
{
_service.GetToneAnalyze(OnGetToneAnalyze, OnFail, conversationString);
}
These calls are typically made using StartCoroutines, particularly in the Watson Unity SDK that there is a specialized Runnable.Run which is essentially a helper class for running co-routines without having to inherit from MonoBehavior.
My question is whether my simple method call to the service might be problematic in certain situations or perhaps just wrong or bad programming, or whether it is perfectly OK to go for that method instead of something like the following:
public void SendToneAnalysis(string conversationString)
{
Runnable.Run( SendAssistantToneAnalysis(conversationString) );
}
private IEnumerator SendAssistantToneAnalysis(string conversationString)
{
if ( !_service.GetToneAnalyze(OnGetToneAnalyze, OnFail, conversationString) )
{
Log.Debug("ExampleToneAnalyzer.SendAssistantToneAnalysis()", "Failed to analyze!");
}
while (!_UserUtteranceToneTested)
yield return null;
}

You don't need to make any of the service calls from within a coroutine. Only authentication using iamApikey should be done using a coroutine
IEnumerator TokenExample()
{
// Create IAM token options and supply the apikey. IamUrl is the URL used to get the
// authorization token using the IamApiKey. It defaults to https://iam.bluemix.net/identity/token
TokenOptions iamTokenOptions = new TokenOptions()
{
IamApiKey = "<iam-api-key>",
IamUrl = "<iam-url>"
};
// Create credentials using the IAM token options
_credentials = new Credentials(iamTokenOptions, "<service-url>");
while (!_credentials.HasIamTokenData())
yield return null;
_assistant = new Assistant(_credentials);
_assistant.VersionDate = "2018-02-16";
_assistant.ListWorkspaces(OnListWorkspaces, OnFail);
}
The examples are only meant to show how to invoke the service call. The only reason the code is invoked from a coroutine is so we can wait for the response of one service call before running another service call (i.e. so we don't try to update or delete a workspace before the workspace is created).

It's no problem.
Runnable.Run() eventually calls StartCoroutine() as the follow.
public Routine(IEnumerator a_enumerator)
{
_enumerator = a_enumerator;
Runnable.Instance.StartCoroutine(this);
Stop = false;
ID = Runnable.Instance._nextRoutineId++;
Runnable.Instance._routines[ID] = this;
#if ENABLE_RUNNABLE_DEBUGGING
Log.Debug("Runnable.Routine()", "Coroutine {0} started.", ID );
#endif
}
Please refer to https://github.com/watson-developer-cloud/unity-sdk/blob/master/Scripts/Utilities/Runnable.cs
And the coroutine can be called from any gameobject, if it is active.

Related

Sending an async email without await from a .Net core web service

I have a webservice .Net core2 that has certain methods that send an email. I have it working fine using smtpclient.sendemailasync.
public async Task<bool> SendEmailAsync(MailMessage email)
{
try
{
if (string.IsNullOrWhiteSpace(emailFrom)) email.From = new MailAddress(emailFrom);
using (SmtpClient client = getSMTPClientInstance())
{
await client.SendMailAsync(email);
}
return true;
}
catch (Exception ex)
{
Log.Error(ex, "Error sending email in EmailService.SendEmailAsync");
return false;
}
}
The only issue is that some SMTP servers take a little too long to respond. I want to set up the email, queue it and return without waiting for the result.
Just using an unawaited async is out for 2 reasons;
It is not reliable to continue a method outside a request context in asp
I need access to the database context of my entity framework to write a log
I have to allow for external or internal SMTP (my client specifies), so a collection folder is not a possibility - at least not without a service that manages it.
How could I achieve this? Do I need to write a service that manages this? If so, how would I do that inside my .Net Core App, keeping in mind that the service also needs to access the EF context to write a log
UPDATE
There is plumbing available in .NetCore DI especially for this. Refer to my additional answer below. Use IServiceScopeFactory
You can call the RegisterAsyncTask method on the Page object. That will signal the ASP.NET runtime you want to make sure these are finished before terminating the request context:
Example:
public void Page_Load(object sender, EventArgs e)
{
RegisterAsyncTask(new PageAsyncTask(LoadSomeData));
}
public async Task LoadSomeData()
{
var clientcontacts = Client.DownloadStringTaskAsync("api/contacts");
var clienttemperature = Client.DownloadStringTaskAsync("api/temperature");
var clientlocation = Client.DownloadStringTaskAsync("api/location");
await Task.WhenAll(clientcontacts, clienttemperature, clientlocation);
var contacts = Newtonsoft.Json.JsonConvert.DeserializeObject<List<Contact>>(await clientcontacts);
var location = Newtonsoft.Json.JsonConvert.DeserializeObject<string>(await clientlocation);
var temperature = Newtonsoft.Json.JsonConvert.DeserializeObject<string>(await clienttemperature);
listcontacts.DataSource = contacts;
listcontacts.DataBind();
Temparature.Text = temperature;
Location.Text = location;
}
https://www.hanselman.com/blog/TheMagicOfUsingAsynchronousMethodsInASPNET45PlusAnImportantGotcha.aspx
So, while I have marked an answer, there are a couple of options that are better solutions for my specific example. First is the option to use a library like hangfire to schedule tasks - although that is not technically an answer to the question.
The better solution in .net core is to use IServiceScopeFactory
With IServiceScopeFactory you can rescope a task so it doesnt go out of scope when the request is complete. I did the following directly in a controller (I later moved to using the hangfire approach, but this works). As you can see, the async task is fired off in a new unawaited thread while the controller code continues.
var task = Task.Run(async () =>
{
using (var scope = _serviceScopeFactory.CreateScope())
{
var service = scope.ServiceProvider.GetRequiredService<ApprovalService>();
await service.sendResponseEmailAsync(approvalInfo.ApprovalId, userID, approvalInfo.emailTo, approvalInfo.ccTo);
}
});

Callback function issues in FB.API for Unity

I am using Unity 5.5.2f1 pro and facebook's SDK v 7.9.4
I have a script which after login (managed in a previous scene) sends an API request to FB asking for friends, name and email and sends that info as a POST to a php website.
code:
[Serializable]
public struct FBData {
public string first_name;
public string email;
public string friends;
public string id;}
public class UserManagement : MonoBehaviour {
string urlSaveUserData="some php website";
public Text testTxt;
FBData parsedData;
// Use this for initialization
void Start () {
//Check if it's the first time the user is opening the app.
if (UserInfo.FIRST_TIME) {
//update text (only used for testing, should be removed in production.)
testTxt.text = "Your user id is: " + UserInfo.ID;
//Perform FB.API call to get User Data.
getUserData ();
//Save in SQL table. (won't get here if line in getUserData() is active)
StartCoroutine ("saveUserData");
} else {
//do something else.
}
note: Since this is meant for iOS I have to test it on a device so I'm using text in the screen to display info (think of it as a badly implemented print statement).
The problem: In my callback function for FB.API I write in the text Gameobject (aka testTxt) the parsed information from the response which is saved in the Custom UserInfo clss. It display's correctly but the code gets stuck there. It doesn't continue to the next function. HOWEVER, if I delete/comment that line and don't display anything in the text field. The codes does continue to the POST function BUT the information from the API call is not passed, i.e my custom class is empty (leading me to believe the callback function is not called at all).
public void getUserData(){
string query = "me?fields=first_name,email,friends";
FB.API (query, HttpMethod.GET, Apicallback, new Dictionary<string, string> ());
}
private void Apicallback(IGraphResult result){
//Parse Graph response into a specific class created for this result.
parsedData = JsonUtility.FromJson<FBData>(result.RawResult);
//Pass each field into UserInfo class.
UserInfo.EMAIL = parsedData.email;
UserInfo.FRIENDS = parsedData.friends;
UserInfo.NAME = parsedData.first_name;
UserInfo.FACEBOOKID = parsedData.id;
/*problem area, if I comment line below, then previous information is apparently not stored. If left as is then testTxt displays correct information but code gets stuck there. */
testTxt.text = "This is the info from USerInfoInside the APICallback: " + UserInfo.EMAIL + UserInfo.FRIENDS + UserInfo.FACEBOOKID;
}
The function below is to send info to php website, is there for illustrative purposes:
code:
public IEnumerator saveUserData() {
//get user info (this information is EMPTY if line in getUserData() is commented.
parsedData.id = UserInfo.FACEBOOKID;
parsedData.friends = UserInfo.FRIENDS;
parsedData.first_name = UserInfo.NAME;
parsedData.email = UserInfo.EMAIL;
//translate data into json
string JsonBodyData = JsonUtility.ToJson (parsedData);
//Custom web request (POST method doesnt seem to work very well, documentation example sends empty form)
var w = new UnityWebRequest(urlSaveUserData, "POST");
byte[] bodyRaw = new System.Text.UTF8Encoding().GetBytes(JsonBodyData);
w.uploadHandler = (UploadHandler) new UploadHandlerRaw(bodyRaw);
w.downloadHandler = (DownloadHandler) new DownloadHandlerBuffer();
w.SetRequestHeader("Content-Type", "application/json");
yield return w.Send();
//work with received data...}
Im stuck here any help is appreciated. Thanks!
Be sure to use EscapeURL when using strings directly for JSON or HTTP POST and GET methods. The lack of this treatment tends to screw things over, particulary in iOS platforms.
From what I can see, this code
string query = "me?fields=first_name,email,friends";
should instead be escaped as
string query = WWW.EscapeURL("me?fields=first_name,email,friends");
so characters like "?" won't get encoded as an URL symbol.
I'm assuming you don't need to do that for your illustrative example, because UnityWebRequest already escapes your POST request strings internally, but I can't fully confirm that.

create token in unity to send post request to laravel controller

I wanna send a post request from a unity game to a laravel 5.4 controller.. in html form, we use {{csrf_field}} and it handles creating token. but how can I do it in unity?
I came across this thread whilst trying to acheive what it set out to do - and I got it working so I am sharing this:
It is a combination of this tutorial on YouTube: How to Use UnityWebRequest - Replacement for WWW Class - Unity Tutorial and this answer on a similar question: UnityWebRequest POST to PHP not work
The technology I am using:
Laravel Framework 7.15.0 (PHP)
Unity: 2019.3.11f1 (script is C#)
Now I am just sending basic forms not images etc - so I haven't tried anything complex but this sends a get to one URL on Laravel which returns the CSRF token.
This is then added as an additional field to the post request.
I am clicking the 2 buttons pretty quickly - I would suggest (and will implement this myself later) that you don't ask for the token until your ready to submit the form - to avoid it expiring if your form is quite long etc.
The C# Script (Unity)
Both the functions are set as ON Click() on the buttons in a simple UI Canvas in Unity.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Networking;
using UnityEngine.UI;
public class BasicWebCall : MonoBehaviour
{
public Text messageText;
public InputField scoreToSend;
private string csrf_token;
readonly string getURL = "http://mazegames.test/leaderboard/1";
readonly string postURL = "http://mazegames.test/register/1";
private void Start()
{
messageText.text = "Press buttons to interact with web server";
}
public void OnButtonGetScore()
{
messageText.text = "Getting Token";
StartCoroutine(SimpleGetRequest());
}
IEnumerator SimpleGetRequest()
{
UnityWebRequest www = UnityWebRequest.Get(getURL);
yield return www.SendWebRequest();
if(www.isNetworkError || www.isHttpError)
{
Debug.LogError(www.error);
}
else
{
messageText.text = "Token Received: " + www.downloadHandler.text;
csrf_token = www.downloadHandler.text;
}
}
public void OnButtonSendScore()
{
if (scoreToSend.text == string.Empty)
{
messageText.text = "Error: No high score to send.\nEnter a value in the input field.";
}
else
{
messageText.text = "Sending Post Request";
StartCoroutine(SimplePostRequest(scoreToSend.text));
}
}
IEnumerator SimplePostRequest(string curScore)
{
Dictionary<string, string> wwwForm = new Dictionary<string, string>();
wwwForm.Add("_token", csrf_token);
UnityWebRequest www = UnityWebRequest.Post(postURL, wwwForm);
yield return www.SendWebRequest();
if (www.isNetworkError || www.isHttpError)
{
Debug.LogError(www.error);
}
else
{
messageText.text = www.downloadHandler.text;
}
}
}
Laravel routes/web.php
Obviously you need to configure your end points to receive your data, validate and store it if necessary. In this example its just validating the token as part of the post request.
Route::post('register/{game_id}', function($game_id) {
return "Game On!";
});
Route::get('leaderboard/{game_id}', function($game_id) {
return csrf_token();
});
And that is pretty much it - hope this helps someone else.
#EDIT# - Request Token on Submission
To only get the token when your submitting the form, literally all you have to do is put this line:
StartCoroutine(SimpleGetRequest());
above the line:
StartCoroutine(SimplePostRequest(scoreToSend.text));
so that is looks like this:
StartCoroutine(SimpleGetRequest());
StartCoroutine(SimplePostRequest(scoreToSend.text));
Obviously you could then remove the function SimpleGetRequest altogether.
Laravel will generate a token each time a page is generated. The token has a lifetime and after that lifetime it cannot be used anymore (that's the whole point).
You need to get a valid token from Laravel pass it to Unity3D and then when from Unity create a WWWForm and pass it back.
How to do this it depends on the platform that Unity3D is deployed to.
If you are using WebPlayer or WebGL then you can get your hand on the Unity3D objected embedded in the browser and use SendMessage. WebGL link here.
If the game is deployed to another platform it probably makes sense to expose and API on the Laravel side and use that endpoint instead of doing a POST request.
You can use WWWForm to send in POST and call it from a coroutine:
// this will send it at start
// but you can just call SendToController in another function
string laravel_url = "http://somedomain.com/whatever";
IEnumerator Start () {
yield return StartCoroutine(SendToController());
}
IEnumerator SendToController()
{
WWWForm form = new WWWForm();
form.AddField( "csrf_field", "replace this with what you want!!!!" );
WWW download = new WWW( laravel_url, form );
yield return download;
if(!string.IsNullOrEmpty(download.error)) {
print( "Error downloading: " + download.error );
} else {
// if succesful, do what you want
Debug.Log(download.text);
}
}
"Coroutine" is your friend. It will make sending forms a lot easier. You might want to read about it in here:
https://docs.unity3d.com/ScriptReference/MonoBehaviour.StartCoroutine.html

NullReferenceException occurs during offline sync to Azure Mobile Service

I am trying to make offline sync to table from azure mobile service. My Xamarin Form version is 1.4.2.6359.
I try to test my code in OnCreate method of MainActivity. All preparation steps such as MobileServiceClient initialization, MobileServiceSQLiteStore initialization, SyncTable creation, etc are ok.
When I try to call PullAsync, I am getting NullReferenceException. I capture the package using Package Capture App from mobile. The request goes to Azure Mobile service and it returns the correct json data successfully.
When I try the same code in Xamarin Android project (not Xamarin Form), it is working fine.
To reproduce the issue.
Just create Xamarin Form (Portable) project and use my code.
My Code
private async Task Test() {
const string applicationURL = #"https://xxxx.azure-mobile.net/";
const string applicationKey = #"xxxx";
CurrentPlatform.Init();
var client = new MobileServiceClient(applicationURL, applicationKey);
string path = Path.Combine(System.Environment.GetFolderPath(System.Environment.SpecialFolder.Personal), "store.db");
if (!File.Exists(path)) {
File.Create(path).Dispose();
}
var store = new MobileServiceSQLiteStore(path);
store.DefineTable<Product>();
await client.SyncContext.InitializeAsync(store);
var productTable = client.GetSyncTable<Product>();
try {
await client.SyncContext.PushAsync();
await productTable.PullAsync("allProducts", productTable.CreateQuery());
var t = await productTable.ToListAsync();
Console.WriteLine("Product Count : " + t.Count);
}
catch (Java.Net.MalformedURLException ex) {
Console.WriteLine(ex.Message);
}
catch (Exception e) {
Console.WriteLine(e.Message);
}
}
References:
http://azure.microsoft.com/en-us/documentation/articles/mobile-services-xamarin-android-get-started-offline-data/
http://blogs.msdn.com/b/carlosfigueira/archive/2014/04/07/deep-dive-on-the-offline-support-in-the-azure-mobile-service-managed-client-sdk.aspx
I got the solution for this case.
As far as my understanding, this is what is happening. During the application is loading, I call PullAsync. It is async call and during this call, application keeps loading other components. The actual NullReferenceException is coming from OnPrepareOptionsMenu function (Xamarin.Forms.Platform.Android.AndroidActivity.OnPrepareOptionsMenu). The exception is happening on other thread and the thread simply dies. That's why I cannot get stack trace from my main thread.
This NullReferenceException issue is totally not related to Azure Mobile Service.
I override OnPrepareOptionsMenu in MainActivity and add try-catch block to base class function call. The problem is solved. Here is my code in MainActivity class.
public override bool OnPrepareOptionsMenu(IMenu menu) {
try {
// I am always getting menu.HasVisibleItems = false in my app
if (menu != null && menu.HasVisibleItems) {
// Exception is happening when the following code is executed
var result = base.OnPrepareOptionsMenu(menu);
return result;
}
}
catch{
}
return true;
}
I don't really understand why it is happening. Please point me out if you have better understanding of this case and better solution.
I think my issue is same as this : http://forums.xamarin.com/discussion/23579/exception-whilte-trying-to-open-activity-from-xamarin-forms-page

Active Directory Authentication Library (ADAL) in an MVVM Phone 8.1 App

I am currently trying to convert the phone sample app from the official ADAL github repo to a caliburn.micro MVVM app. But there are so many moving parts backed into the code-behind to get along with WebAuthenticationBroker that I don't now how to push that into viewmodels and handle navigation correctly when the app is activated again after the broker did the sign-on. Since I am totally clueless at the moment there is no code to share yet.
I've been using MvvmLight along with ADAL in my Windows Phone 8.1 App. What you need to do is as soon as you get a token, you need to send the message using the Messenger pattern. All of those view models which require the token and have already subscribed to it will receive it. Here is something I've done in my app using MvvmLight. Remember that you need to have a ContinuationManager class and IWebContinuable interface to make the app work.
private async void Login()
{
AuthenticationResult result = null;
context = AuthenticationContext.CreateAsync("https://login.windows.net/<tenant-id>").GetResults();
try
{
//try to check if you can get the token without showing pop-up
result = await context.AcquireTokenSilentAsync("https://management.core.windows.net/", "<clientid>");
if(result.Status==AuthenticationStatus.ClientError)
{
bool exists = CheckInVault();
if(exists)
{
PasswordVault vault = new PasswordVault();
var tokenvault = vault.FindAllByResource("Token");
string RefreshToken = tokenvault[0].Password;
var refresh=await context.AcquireTokenByRefreshTokenAsync(RefreshToken, clientid);
vault.Remove(tokenvault[0]);
StoreToken(refresh);
}
else
{
context.AcquireTokenAndContinue("https://management.core.windows.net/", clientid, WebAuthenticationBroker.GetCurrentApplicationCallbackUri(), StoreToken);
}
}
else if(result != null && result.Status == AuthenticationStatus.Success)
{
// A token was successfully retrieved. Post the new To Do item
bool exists = CheckInVault();
if (exists)
{
PasswordVault vault = new PasswordVault();
var tokenvault = vault.FindAllByResource("Token");
vault.Remove(tokenvault[0]);
}
StoreToken(result);
}
//this method will be called when app is opened first time and pop-up appears
result=await context.AcquireTokenSilentAsync("https://management.core.windows.net/", "<client-id>");
}
catch(Exception e)
{
MessageDialog dialog = new MessageDialog("Error");
}
}
What I am doing here is - after acquiring the access token and reference token when the user signs up first, I store the refresh token in the PasswordVault, so as to get it in the future to enable single sign-on. ADAL actually does using its caching feature, but sometimes the single sign-on failed for me, hence using the PasswordVault to store the refresh token. After the authentication completes, I have a delegate to StoreToken function, where I actually store the new refresh token and send the access token to all the subscribers using the Messenger class in MvvmLight.
private void StoreToken(AuthenticationResult result)
{
try
{
var token = result.AccessToken;
Messenger.Default.Send<string>(token); //send the access token.
PasswordVault vault = new PasswordVault();
PasswordCredential credential = new PasswordCredential();
credential.UserName = result.AccessToken;
credential.Password = result.RefreshToken;
credential.Resource = "Token";
vault.Add(credential);
}
catch(Exception e)
{
}
}
I would recommend handling the navigation in the view models. Define a helper class like NavigationService:
public class NavigationService:INavigationService
{
private Frame _frame;
public Frame Frame
{
get
{
return _frame;
}
set
{
_frame = value;
_frame.Navigated+= OnFrameNavigated;
}
}
public void NavigateTo(string type)
{
Frame.Navigate(Type.GetType(type));
}
public void GoForward()
{
if (Frame.CanGoForward)
Frame.GoForward();
}
public void GoBack()
{
if (Frame.CanGoBack)
Frame.GoBack();
}
}
To navigate to a page from the view models, you use the NavigateTo(string) method as
NavigateTo("<fully qualified class name of the page you want to navigate to>,<assembly name>")
I would also suggest using a IoC container (MvvmLight gives you a ViewModelLocator class) so that you can maintain singleton instances of your view models and helpers like NavigationService. I haven't used the CaliburnMicro framework but I would assume there would be similar features for Messaging and Dependency Injection.