In my web application I want to load all data to client side from the server on power up.
After that I want all communication be managed through Signalr - meaning that each update the server will send notification to all clients and they will ask for the updated data.
However, I don't know what to do when the SingalR connection is corrupted and then goes back. I don't want to load all the data all over again. What I want to do is to implement some sort of notifications management on the server side for each disconnected client and whenever the SignalR connection is made again - push to that specific client all the notifications that he has missed.
Our signalR listeners on client side are made on singleton listeners instead of short living controllers, that so we can prevent GET request on each view change and make the application be faster and more user friendly. Because of that approach, new notifications in the background also get handled and processed even when it isn't relevant to the current view the end user is on, like so:
// This service is initialized once only
public class Service1 {
static inject = ['$rootScope']
array : Item[];
// This is a singleton!
public constructor ($rootScope){
// Get all items from the server
GetAllItemsFromServer();
// Listener for signalR updates
var listener = $rootScope.$on("ItemsNotificationFromServer", UpdateItems);
$rootScope.$on('destroy', {
// Stop the listener
listener();
})
}
// Getting all the items from the server on each controller creation
GetAllItemsFromServer(){
// Getting the items
}
// Handle the notification from the server
public UpdateItems(event, result) : void
//..
}
}
At the moment what happens for example is that when an end user refreshes the browser (F5) I can not know what SignalR notifications this client has missed during the connection problems and so I load all the data from the server all over again (it sucks).
In order to prevent it I thought of implementing something like this -
namespace MapUsersSample
{
public class UserContext : DbContext
{
// All those are cleaned when server is powered up
public DbSet<Connection> Connections { get; set; }
public DbSet<Notification> Notifications {get; set;}
}
public class Connection
{
[Key]
[DatabaseGenerationOptions.None]
public string ConnectionID { get; set; }
public bool Connected { get; set; }
// I fill this when disconnected
public List<Notification> MissedNotifications {get; set;}
public Connection(string id)
{
this.ConnectionID = id;
this.Connected = true;
this.MissedNotifications = new List<Notification>();
}
}
public abstract class Notification()
{
public int Id {get; set;}
public DateTime CreationTime {get; set;}
}
.. // Many notifications implement this
}
public class MyHub : Hub
{
private readonly DbContext _db;
public class MyHub(DbContext db)
{
this._db = db;
}
// Adding a new connection or updating status to true
public override Task OnConnected()
{
var connection = GetConnection(Context.ConnectionId);
if (connection == null)
_db.Connections.Add(new Connection(Context.ConnectionId));
else
connection.Connected = true;
return base.OnConnected()
}
// Changing connection status to false
public override Task OnDisconnected()
{
var connection = GetConnection(Context.ConnectionId);
if (connection == null)
{
Log("Disconnect error: failed to find a connection with id : " + Context.ConnectionId);
return;
}
else {
connection.Connected = false;
}
return base.OnDisconnected();
}
public override Task OnReconnected()
{
var connection = GetConnection(Context.ConnectionId);
if (connection == null)
{
Log("Reconnect error - failed to find a connection with id : " + Context.ConnectionId);
return;
}
else {
connection.Connected = true;
}
// On reconnect, trying to send to the client all the notifications that he has missed
foreach (var notification in connection.MissedNotifications){
Clients.Client(connection.ConnectionID).handleNotification(notification);
}
return base.OnReconnected();
}
// This method is called from clients that receive a notification
public clientNotified(int connectionId, int notificationId)
{
// Getting the connection
var connection = GetConnection(connectionId);
if (connection == null){
Log("clientNotified error - failed to find a connection with id : " + Context.ConnectionId);
return;
}
// Getting the notification that the client was notified about
var notificationToRemove = _dbConnection.Notifications.FirstOrDefault(n => n.Id == notificationId);
if (notificationToRemove == null)
{
Log("clientNotified error - failed to find notification with id : " + notificationId);
return;
}
// Removing from the missed notifications
connection.MissedNotifications.Remove(notificationToRemove);
}
private Connection GetConnection(int connectionId)
{
return _db.Connections.find(connectionId);
}
}
// Notifications outside of the hub
public class Broadcaster
{
DbContext _db;
public Broadcaster(DbContext db)
{
_hubContext = GlobalHost.ConnectionManager.GetHubContext<MoveShapeHub>();
_dbConnection = db;
}
public void NotifyClients(Notification notification)
{
var openConnections = _db.Connections.Where(x => x.Connected);
var closedConnections = _db.Connections.Where(x => !x.Connected);
// Adding all notifications to be sent when those connections are back
foreach (var connection in closedConnections){
connection.MissedNotifications.add(notification);
}
// Notifying all open connections
foreach (var connection in openConnections){
_hubContext.Clients.Client(connection.ConnectionID).handleNotification(notification);
}
}
}
client side java script:
handleNotification(notification){
hubProxy.Server.clientNotified(hub.connection.id, notification.Id)
// Keep handling the notification here..
}
I haven't got to test it yet, but before I present this idea to my team, is this approach popular? haven't seen people taking this approach and I wondered why? Are there any risks here?
At the moment what happens for example is that when an end user refreshes the browser (F5) I can not know what SignalR notifications this client has missed during the connection problems and so I load all the data from the server all over again (it sucks).
Pressing F5 to refresh browser is a hard reset, all existing SignalR connection would be lost. New connections would be made to get data. Connection problems occur in scenarios when SignalR notices problems with the http connection for e.g. due to temporary network issues. Browser refresh isn't a connection problem, it's an act of a user knowingly recreating a new connection.
So, your code of managing missed notifications would work only for signalR connection issues. I don't think it'll work for browser refresh, but then it's a new connection so you haven't missed anything.
You should check if the data is actual.
It can be Hash or datetime of last change.
When client reconnect you should send actual data hash or datetime of last change to the client.
for example
{
clients: '2016-05-05T09:05:05',
orders: '2016-09-20T10:11:11'
}
And the client application will decide what data it needs to update.
On client you can save data to LocalStorage or SessionStorage.
Related
I have been trying to read the official docs and guides about how to send message from one device to another. I have saved registration token of both devices in the Real Time Database, thus I have the registration token of another device.
I have tried the following way to send the message
RemoteMessage message = new RemoteMessage.Builder(getRegistrationToken())
.setMessageId(incrementIdAndGet())
.addData("message", "Hello")
.build();
FirebaseMessaging.getInstance().send(message);
However this is not working. The other device doesn't receive any message. I am not even sure, if I can use upstream message sending to conduct device to device communication.
PS: I just want to know if device-to-device messaging is possible using FCM? If yes, then is the code I used have some issue? If yes, then what is the correct way.
Update:
My question was to ask whether device to device messaging without using any separate server other than firebase could messaging is possible or not, if yes than how, since there's no documentation about it. I do not understand what is left to explain here? Anyways I got the answer and will update it as an answer once the question gets reopened.
Firebase has two features to send messages to devices:
the Notifications panel in your Firebase Console allows you to send notifications to specific devices, groups of users, or topics that users subscribed to.
by calling Firebase Cloud Messaging API, you can send messages with whatever targeting strategy you prefer. Calling the FCM API requires access to your Server key, which you should never expose on client devices. That's why you should always run such code on an app server.
The Firebase documentation shows this visually:
Sending messages from one device directly to another device is not supported through the Firebase Cloud Messaging client-side SDKs.
Update: I wrote a blog post detailing how to send notifications between Android devices using Firebase Database, Cloud Messaging and Node.js.
Update 2: You can now also use Cloud Functions for Firebase to send messages securely, without spinning up a server. See this sample use-case to get started. If you don't want to use Cloud Functions, you can run the same logic on any trusted environment you already have, such as your development machine, or a server you control.
Warning There is a very important reason why we don't mention this approach anywhere. This exposes your server key in the APK that
you put on every client device. It can (and thus will) be taken from
there and may lead to abuse of your project. I highly recommend
against taking this approach, except for apps that you only put on
your own devices. – Frank van Puffelen
Ok, so the answer by Frank was correct that Firebase does not natively support device to device messaging. However there's one loophole in that. The Firebase server doesn't identify whether you have send the request from an actual server or are you doing it from your device.
So all you have to do is send a Post Request to Firebase's messaging server along with the Server Key. Just keep this in mind that the server key is not supposed to be on the device, but there's no other option if you want device-to-device messaging using Firebase Messaging.
I am using OkHTTP instead of default way of calling the Rest API. The code is something like this -
public static final String FCM_MESSAGE_URL = "https://fcm.googleapis.com/fcm/send";
OkHttpClient mClient = new OkHttpClient();
public void sendMessage(final JSONArray recipients, final String title, final String body, final String icon, final String message) {
new AsyncTask<String, String, String>() {
#Override
protected String doInBackground(String... params) {
try {
JSONObject root = new JSONObject();
JSONObject notification = new JSONObject();
notification.put("body", body);
notification.put("title", title);
notification.put("icon", icon);
JSONObject data = new JSONObject();
data.put("message", message);
root.put("notification", notification);
root.put("data", data);
root.put("registration_ids", recipients);
String result = postToFCM(root.toString());
Log.d(TAG, "Result: " + result);
return result;
} catch (Exception ex) {
ex.printStackTrace();
}
return null;
}
#Override
protected void onPostExecute(String result) {
try {
JSONObject resultJson = new JSONObject(result);
int success, failure;
success = resultJson.getInt("success");
failure = resultJson.getInt("failure");
Toast.makeText(getCurrentActivity(), "Message Success: " + success + "Message Failed: " + failure, Toast.LENGTH_LONG).show();
} catch (JSONException e) {
e.printStackTrace();
Toast.makeText(getCurrentActivity(), "Message Failed, Unknown error occurred.", Toast.LENGTH_LONG).show();
}
}
}.execute();
}
String postToFCM(String bodyString) throws IOException {
RequestBody body = RequestBody.create(JSON, bodyString);
Request request = new Request.Builder()
.url(FCM_MESSAGE_URL)
.post(body)
.addHeader("Authorization", "key=" + SERVER_KEY)
.build();
Response response = mClient.newCall(request).execute();
return response.body().string();
}
I hope Firebase will come with a better solution in future. But till then, I think this is the only way. The other way would be to send topic message or group messaging. But that was not in the scope of the question.
Update:
The JSONArray is defined like this -
JSONArray regArray = new JSONArray(regIds);
regIds is a String array of registration ids, you want to send this message to. Keep in mind that the registration ids must always be in an array, even if you want it to send to a single recipient.
I have also been using direct device to device gcm messaging in my prototype. It has been working very well. We dont have any server. We exchange GCM reg id using sms/text and then communicate using GCM after that. I am putting here code related to GCM handling
**************Sending GCM Message*************
//Sends gcm message Asynchronously
public class GCM_Sender extends IntentService{
final String API_KEY = "****************************************";
//Empty constructor
public GCM_Sender() {
super("GCM_Sender");
}
//Processes gcm send messages
#Override
protected void onHandleIntent(Intent intent) {
Log.d("Action Service", "GCM_Sender Service Started");
//Get message from intent
String msg = intent.getStringExtra("msg");
msg = "\"" + msg + "\"";
try{
String ControllerRegistrationId = null;
//Check registration id in db
if(RegistrationIdAdapter.getInstance(getApplicationContext()).getRegIds().size() > 0 ) {
String controllerRegIdArray[] = RegistrationIdAdapter.getInstance(getApplicationContext()).getRegIds().get(1);
if(controllerRegIdArray.length>0)
ControllerRegistrationId = controllerRegIdArray[controllerRegIdArray.length-1];
if(!ControllerRegistrationId.equalsIgnoreCase("NULL")){
// 1. URL
URL url = new URL("https://android.googleapis.com/gcm/send");
// 2. Open connection
HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
// 3. Specify POST method
urlConnection.setRequestMethod("POST");
// 4. Set the headers
urlConnection.setRequestProperty("Content-Type", "application/json");
urlConnection.setRequestProperty("Authorization", "key=" + API_KEY);
urlConnection.setDoOutput(true);
// 5. Add JSON data into POST request body
JSONObject obj = new JSONObject("{\"time_to_live\": 0,\"delay_while_idle\": true,\"data\":{\"message\":" + msg + "},\"registration_ids\":[" + ControllerRegistrationId + "]}");
// 6. Get connection output stream
OutputStreamWriter out = new OutputStreamWriter(urlConnection.getOutputStream());
out.write(obj.toString());
out.close();
// 6. Get the response
int responseCode = urlConnection.getResponseCode();
BufferedReader in = new BufferedReader(new InputStreamReader(urlConnection.getInputStream()));
String inputLine;
StringBuffer response = new StringBuffer();
while ((inputLine = in.readLine()) != null){
response.append(inputLine);
}
in.close();
Log.d("GCM getResponseCode:", new Integer(responseCode).toString());
}else{
Log.d("GCM_Sender:","Field REGISTRATION_TABLE is null");
}
}else {
Log.d("GCM_Sender:","There is no Registration ID in DB ,please sync devices");
}
} catch (Exception e) {
e.printStackTrace();
//MessageSender.getInstance().sendMessage(msg, Commands.SMS_MESSAGE);
}
}
//Called when service is no longer alive
#Override
public void onDestroy() {
super.onDestroy();
//Do a log that GCM_Sender service has been destroyed
Log.d("Action Service", "GCM_Sender Service Destroyed");
}
}
**************Receiving GCM Message*************
public class GCM_Receiver extends WakefulBroadcastReceiver {
public static final String RETRY_ACTION ="com.google.android.c2dm.intent.RETRY";
public static final String REGISTRATION ="com.google.android.c2dm.intent.REGISTRATION";
public SharedPreferences preferences;
//Processes Gcm message .
#Override
public void onReceive(Context context, Intent intent) {
ComponentName comp = new ComponentName(context.getPackageName(),
GCMNotificationIntentService.class.getName());
//Start GCMNotificationIntentService to handle gcm message asynchronously
startWakefulService(context, (intent.setComponent(comp)));
setResultCode(Activity.RESULT_OK);
/*//Check if DatabaseService is running .
if(!DatabaseService.isServiceRunning) {
Intent dbService = new Intent(context,DatabaseService.class);
context.startService(dbService);
}*/
//Check if action is RETRY_ACTION ,if it is then do gcm registration again .
if(intent.getAction().equals(RETRY_ACTION)) {
String registrationId = intent.getStringExtra("registration_id");
if(TextUtils.isEmpty(registrationId)){
DeviceRegistrar.getInstance().register(context);
}else {
//Save registration id to prefs .
preferences = PreferenceManager.getDefaultSharedPreferences(context);
SharedPreferences.Editor editor = preferences.edit();
editor.putString("BLACKBOX_REG_ID",registrationId);
editor.commit();
}
} else if (intent.getAction().equals(REGISTRATION)) {
}
}
}
//Processes gcm messages asynchronously .
public class GCMNotificationIntentService extends IntentService{
public static final int NOTIFICATION_ID = 1;
private NotificationManager mNotificationManager;
String gcmData;
private final String TAG = "GCMNotificationIntentService";
//Constructor with super().
public GCMNotificationIntentService() {
super("GcmIntentService");
}
//Called when startService() is called by its Client .
//Processes gcm messages .
#Override
protected void onHandleIntent(Intent intent) {
Log.d("GCMNotificationIntentService", "GCMNotificationIntentService Started");
Bundle extras = intent.getExtras();
//Get instance of GoogleCloudMessaging .
GoogleCloudMessaging gcm = GoogleCloudMessaging.getInstance(this);
//Get gcm message type .
String messageType = gcm.getMessageType(intent);
if (!extras.isEmpty()) {
if (GoogleCloudMessaging.MESSAGE_TYPE_SEND_ERROR
.equals(messageType)) {
sendNotification("Send error: " + extras.toString());
} else if (GoogleCloudMessaging.MESSAGE_TYPE_DELETED
.equals(messageType)) {
sendNotification("Deleted messages on server: "
+ extras.toString());
} else if (GoogleCloudMessaging.MESSAGE_TYPE_MESSAGE
.equals(messageType)) {
Log.i(TAG, "Completed work # " + SystemClock.elapsedRealtime());
gcmData = extras.getString("message");
Intent actionService = new Intent(getApplicationContext(),Action.class);
actionService.putExtra("data", gcmData);
//start Action service .
startService(actionService);
//Show push notification .
sendNotification("Action: " + gcmData);
//Process received gcmData.
Log.d(TAG,"Received Gcm Message from Controller : " + extras.getString("message"));
}
}
GCM_Receiver.completeWakefulIntent(intent);
}
//Shows notification on device notification bar .
private void sendNotification(String msg) {
mNotificationManager = (NotificationManager) this.getSystemService(Context.NOTIFICATION_SERVICE);
Intent notificationIntent = new Intent(this, BlackboxStarter.class);
//Clicking on GCM notification add new layer of app.
notificationIntent.setFlags( Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK);
PendingIntent contentIntent = PendingIntent.getActivity(this, 0,notificationIntent, PendingIntent.FLAG_CANCEL_CURRENT);
NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(
this).setSmallIcon(R.drawable.gcm_cloud)
.setContentTitle("Notification from Controller")
.setStyle(new NotificationCompat.BigTextStyle().bigText(msg))
.setContentText(msg);
mBuilder.setContentIntent(contentIntent);
mNotificationManager.notify(NOTIFICATION_ID, mBuilder.build());
//Play default notification
try {
Uri notification = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
Ringtone r = RingtoneManager.getRingtone(getApplicationContext(), notification);
r.play();
} catch (Exception e) {
e.printStackTrace();
}
}
//Called when service is no longer be available .
#Override
public void onDestroy() {
// TODO Auto-generated method stub
super.onDestroy();
Log.d("GCMNotificationIntentService", "GCMNotificationIntentService Destroyed");
}
}
According to the new documentation which was updated on October 2, 2018 you must send post request as below
https://fcm.googleapis.com/fcm/send
Content-Type:application/json
Authorization:key=AIzaSyZ-1u...0GBYzPu7Udno5aA //Server key
{
"to": "sent device's registration token",
"data": {
"hello": "message from someone",
}
}
To get device's registration token extend FirebaseMessagingService and override onNewToken(String token)
For more info refer to doc https://firebase.google.com/docs/cloud-messaging/android/device-group
I am late but above solutions has helped me to write down this simple answer, you can send your message directly to android devices from android application, here is the simple implementation I have done and it works great for me.
compile android volley library
compile 'com.android.volley:volley:1.0.0'
Just copy paste this simple function ;) and your life will become smooth just like knife in butter. :D
public static void sendPushToSingleInstance(final Context activity, final HashMap dataValue /*your data from the activity*/, final String instanceIdToken /*firebase instance token you will find in documentation that how to get this*/ ) {
final String url = "https://fcm.googleapis.com/fcm/send";
StringRequest myReq = new StringRequest(Request.Method.POST,url,
new Response.Listener<String>() {
#Override
public void onResponse(String response) {
Toast.makeText(activity, "Bingo Success", Toast.LENGTH_SHORT).show();
}
},
new Response.ErrorListener() {
#Override
public void onErrorResponse(VolleyError error) {
Toast.makeText(activity, "Oops error", Toast.LENGTH_SHORT).show();
}
}) {
#Override
public byte[] getBody() throws com.android.volley.AuthFailureError {
Map<String, Object> rawParameters = new Hashtable();
rawParameters.put("data", new JSONObject(dataValue));
rawParameters.put("to", instanceIdToken);
return new JSONObject(rawParameters).toString().getBytes();
};
public String getBodyContentType()
{
return "application/json; charset=utf-8";
}
#Override
public Map<String, String> getHeaders() throws AuthFailureError {
HashMap<String, String> headers = new HashMap<String, String>();
headers.put("Authorization", "key="+YOUR_LEGACY_SERVER_KEY_FROM_FIREBASE_CONSOLE);
headers.put("Content-Type","application/json");
return headers;
}
};
Volley.newRequestQueue(activity).add(myReq);
}
Note
If you want to send message to topics so you can change parameter instanceIdToken to something like /topics/topicName.
For groups implementation is the same but you just need to take care of parameters. checkout Firebase documentation and you can pass those parameters.
let me know if you face any issue.
I'm listening to my /score endpoint with JavaScript like so:
var sse = new EventSource('/score');
sse.onmessage = function (evt) {
var el = document.getElementById('scores');
el.appendChild(document.createTextNode(evt.data));
el.appendChild(document.createElement('br'));
};
But for some reason it's like the endpoint it's called every second.
EventSource.onmessage documantation says:
Is an EventHandler called when a message event is received, that is when a message is coming from the source
This is my /score endpoint:
private ExecutorService executorService = Executors.newCachedThreadPool();
#GetMapping("/score")
public SseEmitter getScore() {
final SseEmitter sseEmitter = new SseEmitter();
executorService.submit(() -> {
try {
//System.out.println(text);
//sseEmitter.send(text);
sseEmitter.send("ok");
sseEmitter.complete();
} catch (Exception e) {
e.printStackTrace();
}
});
return sseEmitter;
}
How can i trigger it only when i send a request manually?
After a lot of reading i managed to trigger the /score endpoint on every request, but i had to change the server side a lot.
#Autowired
private MessageProcessor processor;
#GetMapping(path = "/score", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<MyObject> receive() {
return Flux.create(sink -> {
processor.register(sink::next);
});
}
Now I'm returning a Flux object instead of a SseEmitter , because with Emitter i need constantly to send responses to the client.
I also created another endpoint /send , where i send my object with POST
#PostMapping("/send")
public String send(#RequestBody MyObject event) {
LOGGER.info("Received '{}'", event);
processor.process(event);
return "Done";
}
Nothing is changed on the Client side, the pipe between /receive and EventSource shouldn't be terminated from the client. I only added a JSON parsing because now i've got a custom object.
eventSource = new EventSource("/score");
eventSource.onmessage = function (evt) {
var obj =JSON.parse (evt.data);
var el = document.getElementById('scores');
el.appendChild(document.createTextNode(obj.id+' '+obj.name+' '+obj.desc));
el.appendChild(document.createElement('br'));
};
The key part was to use a MessageProccessor
#Service
public class MessageProcessor {
private static final Logger LOGGER = LoggerFactory.getLogger(MessageProcessor.class);
private List < Consumer < MyObject >> listeners = new CopyOnWriteArrayList < > ();
public void register(Consumer < MyObject > listener) {
listeners.add(listener);
LOGGER.info("Added a listener, for a total of {} listener{}", listeners.size(), listeners.size() > 1 ? "s" : "");
}
// TODO FBE implement unregister
public void process(MyObject event) {
System.out.println("Processing: " + event);
listeners.forEach(c -> c.accept(event));
}
}
Another example of Webflux can be found here
Output
Client
Server (The 4 objects i posted)
That's because you never called EventSource.close() to close the connection. Since your eventSource doesn't have a terminating condition, your browser will repeatably call /score.
I worked on a ASP.NET MVC 4 project in which users can apply filter on UI & extract an excel report. The data was stored in MS SQL server. To improve performances, i decided to adopt the async in my application to:
Reduce the time of extraction
Allow users to have parallel extraction
To do this, my approach are:
When user click extraction button on UI -> an ajax call will be made from client to async function on server -> This async function in turn will create an Command object & do ExecuteReaderAsync(). Using this DbDatareader to generated an Excel file using NPOI and save the file content to TempData. The handler to retrieve file will be return to client for later download using window.location. I adopted these techniques from this post Download Excel file via AJAX MVC
After the first extraction, if users want to extract another datasets in parallel, they can click extraction button again and application will repeat step 1.
The results are 2 or more data extractions can happened on the same time.
My problem is, take example, 4 extractions currently running in parallels, if any of these extractions finished & 1 file is downloaded (using window.location). The next time user click on extraction button (which repeat step 1), it doesn't async anymore & later extractions will wait for previous extraction finish before execute.
On debugging, if i restart the ISS server, the problem gone for a while until 1 file is downloaded, so I doubted that window.location do something that blocked the threads on server when any of file is downloaded.
UPDATE 1
Class:
public class QUERYREADER
{
public DbConnection CONNECTION { get; set; }
public DbDataReader READER { get; set; }
}
Model:
public async Task<QUERYREADER> GET_DATA(CancellationToken ct)
{
//Create the query reader
QUERYREADER qr = new QUERYREADER();
//Set up the database instances
DbProviderFactory dbFactory = DbProviderFactories.GetFactory(db.Database.Connection);
//Defined the query
var query = "SELECT * FROM Table";
//Set up the sql command object
using (var cmd = dbFactory.CreateCommand())
{
//Try to open the database connection
try
{
//Check if SQL connection is set up
if (cmd.Connection == null)
{
cmd.CommandType = CommandType.Text;
cmd.Connection = db.Database.Connection;
}
//Open connection to SQL if current state is closed
if (cmd.Connection.State == ConnectionState.Closed)
{
//Change the connection string to set the packet size to max. value = 32768 to improve efficiency for I/O transmit to SQL server
cmd.Connection.ConnectionString = cmd.Connection.ConnectionString + ";Packet Size=20000";
//Open connection
await cmd.Connection.OpenAsync(ct);
}
//Save the connection
qr.CONNECTION = cmd.Connection;
} catch (Exception ex) {
//If errors throw, close the connection
cmd.Connection.Close();
};
//Retrieve the database reader of provided sql query
cmd.CommandText = query;
DbDataReader dr = await cmd.ExecuteReaderAsync(ct);
qr.READER.Add(dr);
}
//Return the queryreader
return qr;
}
Controller:
public async Task<JsonResult> SQL_TO_EXCEL()
{
//Set up the subscription to client for "cancellation request, browser closing"
CancellationToken disToken = Response.ClientDisconnectedToken;
//Get the datareader
try
{
qr = await GET_DATA(disToken);
}
catch(Exception ex) { }
//Open the connection to SQL server
using (qr.CONNECTION)
{
using (var dr = qr.READER)
{
while (await dr.ReadAsync(disToken))
{
for (int k = 0; k < dr.FieldCount; k++)
{
//.... using NPOI to write Excel file to MemoryStream
}
}
dr.Close();
}
}
//Generate XL file if controller action is still running (no "cancellation request, browser closing")
if (!disToken.IsCancellationRequested)
{
string file_id = Guid.NewGuid().ToString();
//... Write the NPOI excel file to TempData and then create a handler for later download at client
//This line caused trouble
TempData["file_id"] = XLMemoryStream.ToArray();
HANDLER["file_id"] = file_id;
HANDLER["file_name"] = FILE["FILE_NAME"].ToString().NonUnicode() + FILE["FILE_TYPE"].ToString() ;
}
//Return JSON to caller
var JSONRESULT = Json(JsonConvert.SerializeObject(HANDLER), JsonRequestBehavior.AllowGet);
JSONRESULT.MaxJsonLength = int.MaxValue;
return JSONRESULT;
}
public async Task<ActionResult> DOWNLOAD_EXCEL(string file_id, string file_name)
{
if (TempData[file_id] != null)
{
byte[] data = await Task.Run(() => TempData[file_id] as byte[]);
return File(data, "application/vnd.ms-excel", file_name);
}
else
{
return new EmptyResult();
}
}
Javascript
$.ajax({
type: 'POST',
async: true,
cache: false,
url: 'SQL_TO_EXCEL',
success: function (data)
{
var response = JSON.parse(data);
window.location =
(
"DOWNLOAD_EXCEL" +
'?file_id=' + response.file_id +
'&file_name=' + response.file_name
);
},
error: function (XMLHttpRequest, textStatus, errorThrown) {
console.log(errorThrown);
}
});
UPDATE 2:
After a lot of tests, i figured out window.location has nothing to do with threads on server, the line TempData[file_id] = XLMemoryStream.ToArray() caused the issues. It look likes the problem is similar as described in this post Two parallel ajax requests to Action methods are queued, why?
SignalR connected in Android, But I want to call a function which is available on server,
I tried the following code,
String host = "Host URL";
HubConnection hubConnection = new HubConnection(host, "", false, new Logger() {
#Override
public void log(String message, LogLevel level) {
System.out.println("message - " + message);
}
});
hubProxy = hubConnection.createHubProxy("Task");
SignalRFuture<Void> signalRFuture = hubConnection.start().done(addSession("Session ID"));
And
private Action<Void> addSession(String sessionID) {
//hubProxy. [server is not available here]
}
In javascript, I tried like following,
$.connection.hub.start().done(function () {
addSession(sessionId)
});
function addSession(sessionId) {
proxy.server.addSession(sessionId)
.done(function () {
isHubConnected = true;
}
);
}
In javascript this works perfectly, But in android :( ,
Update
By trying like #AnikIslamAbhi's answer,
signalRFuture = hubConnection.start().done(addSession("sessionID"));
private Action<Void> addSession(String sessionID) {
System.out.println("addSession : " + sessionID);
hubProxy.invoke("addSession", sessionID);
return null;
}
I received following error message,
InvalidStateException: The operation is not allowed in the 'Connecting' state
In javascript you are using Auto proxy.
But in android you are using manual proxy
Both have differences in their behavior.
Like
To call serverside method using auto proxy
proxy.server.addSession(sessionId)
To call serverside method using manual proxy
proxy.invoke("addSession", sessionId)
You can find more on this link
I want to develop an application with Appcelerator where I have a fleet of bikes, all with smartphone. I want to know the location of each one. Can someone give me a help by using localization by smartphone android GPS?
I'm assuming you want to get it periodically, so you can track where the bikes are at a given moment...
What you would need is to use Titanium Geolocation. you can read about it here: http://docs.appcelerator.com/titanium/latest/#!/api/Titanium.Geolocation
and call getCurrentPosition method (http://docs.appcelerator.com/titanium/latest/#!/api/Titanium.Geolocation-method-getCurrentPosition) that accepts a callback function with the location.
Inside that callback function send the information to your server (http://docs.appcelerator.com/titanium/latest/#!/api/Titanium.Network-method-createHTTPClient)
and just call that function every X interval you with using a setInterval or setTimeout.
keep in mind that the app will need to run in order for it to keep working, unless you write an android service (not possible on iOS).
I will suggest you to do like this:
first create service.js under android folder, and there write something like:
startSendigGpsPositions();
function startSendingGpsPosition(){
Ti.Geolocation.getCurrentPosition(function(e){
var postData = {};
postData["localizationSystems"] = [];
postData["localizationSystems"].push({
"localizationSystem_id":"gps",
"entries":[ {
"lat": e.coords.latitude,
"lon": e.coords.longitude,
"accuracy": e.coords.accuracy
} ]
});
callHttpFunction(postData, {
onSuccess: function(){
//if needed you can set a timeout so the battery does not die immediately
setTimeOut(function(){
startSendingGpsPosition();
}, 1000)
},
onError: function(){
// decide if you want to show the error to the user or just ignore it and recall startSendingGpsPosition
}
})
});
}
function callHttpFunction(postData, callback){
var xhr = Titanium.Network.createHTTPClient();
xhr.open(POST, url);
xhr.onload = function(e) {
var res = JSON.parse(this.responseText);
callback.onSuccess(res);
};
xhr.onerror = function(e) {
var res = JSON.parse(this.responseText);
callback.onError(res);
};
if (Titanium.Network.online) {
xhr.send(postData);
} else {
if (callback.error) { callback.onError(); }
};
}
From index.js you call it like this:
function startService(){
var intent = Ti.Android.createServiceIntent({
url : 'service.js'
});
if(!Ti.Android.isServiceRunning(intent)){
Ti.Android.startService(intent);
}
}
Another way is to create a native module where you use onLocationChanged function, so any time your location will change it will give you back the geo point without having to ask any 1 second for the gps position. This is how it should look like:
LocationListener locationListener = null;
...
locationListener = new LocationListener() {
#Override
public void onStatusChanged(String arg0, int arg1, Bundle arg2) {
// TODO Auto-generated method stub
}
#Override
public void onProviderEnabled(String arg0) {
// TODO Auto-generated method stub
}
#Override
public void onProviderDisabled(String arg0) {
// TODO Auto-generated method stub
}
#Override
public void onLocationChanged(Location gpsLocation) {
HashMap<String , String> map = new HashMap<String, String>();
HashMap<String , String> container = new HashMap<String, String>();
map.put("success", "true");
container.put("latitude", ""+gpsLocation.getLatitude());
container.put("longitude", ""+gpsLocation.getLongitude());
container.put("accuracy", ""+gpsLocation.getAccuracy());
container.put("altitude", ""+gpsLocation.getAltitude());
container.put("provider", ""+gpsLocation.getProvider());
container.put("speed", ""+gpsLocation.getSpeed());
map.put("container", (new JSONObject(container)).toString());
callbackLocation.call(getKrollObject(), map);
}
};
Once you have the data in the callback you can do the http request from javascript :)
I'd recommend NOT using Titanium, especially if you plan to make another App. I started with Titanium and although I found Javascript straightforward and enjoyable, the framework is severely limiting when you start trying to do more advanced things. With native you have much greater control, and it's very much worth the hump of learning.