I have class the --- core of the class skeleton is give below:-
class Pingdom
{
public static string Pingdom(List<Config> configtypes)
{
StringBuilder response = new StringBuilder();
bool status = false;
foreach(var c in configtypes)
{
switch(c.Type)
{
case ConfigTypes.Database:
{
status = PingdomDB(c.ConnectionType);
}
break;
case ConfigTypes.API:
{
status = PingdomAPI(c.Endpoint);
}
break;
}
}
if (status)
return "Ping";
else
return "No Ping";
}
-------------------------------------------------------
.......................................................
}
Now, instead of the class being static I would like for it to be in such way that I can take more of an asynchronous approach in a more robust manner.
Essentially, obtain the list of configurations but process them asynchronously.
How to go about this approach?
class Pingdom {
public static string PingMe(List<Config> configtypes)
{
bool status = true;
List<Task> tasks2 = new List<Task>();
foreach (Config config in configtypes)
{
if (config.Type == ConfigTypes.Database)
{
tasks2.Add(Task.Factory.StartNew(() => { status = status && PingdomDB(config.ConnectionType); }, TaskCreationOptions.LongRunning));
}
else if (config.Type == ConfigTypes.API)
{
tasks2.Add(Task.Factory.StartNew(() => { status = status && PingdomAPI(config.ConnectionType); }, TaskCreationOptions.LongRunning));
}
}
Task.WaitAll(tasks2.ToArray(), System.Threading.Timeout.Infinite);
if (status)
return "Ping";
else
return "No Ping";
}
}
Related
I am using SignalR to do RTM and RT- Call Invitation, I created a HubConnectionServices class stored its obj in a public accessible static variable and use it to send messages. For some reason, my app crashes, or does not navigate, when navigation is called.
public class HubConnectionService
{
public readonly HubConnection hubConnection;
public Page currentPage;
public bool currentPageis;
public HubConnectionService(string token)
{
hubConnection = new HubConnectionBuilder().WithUrl(ApiSettings.HubConnection + "/video", (opts) =>
{
opts.Headers.Add("Authorization", token);
opts.HttpMessageHandlerFactory = (message) =>
{
if (message is HttpClientHandler clientHandler)
{
clientHandler.ServerCertificateCustomValidationCallback +=
(sender, certificate, chain, sslPolicyErrors) => { return true; };
}
return message;
};
}).Build();
}
[JsonConverter(typeof(JsonStringEnumConverter))]
public enum ConnectionResult
{
Connected, Disconnected
}
public async Task<ConnectionResult> ConnectAsync()
{
try
{
await hubConnection.StartAsync();
return ConnectionResult.Connected;
}
catch (Exception problem)
{
Debug.WriteLine(problem.Message);
return ConnectionResult.Disconnected;
}
}
public ConnectionResult AddRecivePoints()
{
try
{
// when disconnected
hubConnection.Closed += async (error) =>
{
GlobalVariables.isConnected = false;
while (hubConnection.State != HubConnectionState.Connected)
{
_ = await ConnectAsync();
}
GlobalVariables.isConnected = true;
};
// receive message
_ = hubConnection.On("ReceiveMessage", (Func<string, HubMessage, Task>)(async (user, message) =>
{
await SwitchCases(message);
}));
return ConnectionResult.Connected;
}
catch (Exception)
{
return ConnectionResult.Disconnected;
}
}
private async Task SwitchCases(HubMessage message)
{
switch (message.Type)
{
case HubMessageType.VideoCallInvitation:
{
if (GlobalVariables.UserIsAvailable)
{
GlobalVariables.UserIsAvailable = false;
await Application.Current.MainPage.Navigation.PushAsync(new ReciviedCall(hubMessage: message));
}
break;
}
case HubMessageType.NormalMessage:
Page cpage = Application.Current.MainPage.Navigation.NavigationStack[Application.Current.MainPage.Navigation.NavigationStack.Count - 1];
bool page = cpage?.GetType().Name == nameof(ChatPrivate);
message.IsNotUserCreated = true;
message.IsUserCreated = false;
bool sender = GlobalVariables.CurrentOpenChatUserEmail == message.Sender;
if (page && sender)
{
GlobalVariables.privateChatMessages.Add(message);
}
else
{
DependencyService.Get<INotification>().CreateNotification(message.Sender,message.Data);
//CrossLocalNotifications.Current.Show(message.Data, message.Sender, 0, DateTime.Now);
}
break;
case HubMessageType.UnKnownError:
break;
case HubMessageType.CallAccepted:
break;
case HubMessageType.CallRejected:
if (Application.Current.MainPage.Navigation.NavigationStack.Count > 1)
{
currentPage = Application.Current.MainPage.Navigation.NavigationStack[Application.Current.MainPage.Navigation.NavigationStack.Count - 1];
currentPageis = currentPage.GetType().Name == nameof(RoomPage);
if (currentPageis)
{
_ = await Application.Current.MainPage.Navigation.PopAsync();
}
}
break;
case HubMessageType.NewUserJoinedCall:
break;
case HubMessageType.UserNotAvailableForVideoCall:
bool isInVideoCallPage = Application.Current.MainPage is RoomPage;
if (isInVideoCallPage)
{
_ = await Application.Current.MainPage.Navigation.PopAsync();
}
GlobalVariables.UserIsAvailable = false;
break;
case HubMessageType.UserNotOnline:
break;
default:
break;
}
}
public async Task Disconnect()
{
await hubConnection.StopAsync();
}
public async Task<string> SendMessage(string toUser, HubMessage msg)
{
try
{
await hubConnection.InvokeAsync("SendChatMessageAsync", msg, toUser);
return "OK";
}
catch (Exception X)
{
return X.Message;
}
}
}
Now when I log in:
try
{
GlobalVariables.connectionService = new HubConnectionService("Bearer " + loginresult.IdentityToken);
ConnectionResult connectionResult = await GlobalVariables.connectionService.ConnectAsync();
// 10 attempts to connect to chat hub
for (int i = 0; i < 10; i++)
{
if (connectionResult == ConnectionResult.Disconnected)
{
await GlobalVariables.connectionService.Disconnect();
connectionResult = await GlobalVariables.connectionService.ConnectAsync();
}
else
{
_ = GlobalVariables.connectionService.AddRecivePoints();
break;
}
}
if (connectionResult == ConnectionResult.Disconnected)
{
await DisplayAlert("Error", "Chat service failed restart is required", "Okay");
}
}
catch (Exception x)
{
await DisplayAlert("Error", x.Message, "Okay");
}
Sending Messages
_ = await GlobalVariables.connectionService.SendMessage(toUser, hubMessage);
The only places where I was able to pinpoint the bug is when I tries to reconnect.
When I call -> Close Call -> then receive call
At both these mentiond flows
case HubMessageType.VideoCallInvitation: block won't execute, but the navigation gets stuck half way with partially black screen. I think its something to do with threads, stuck in these for weeks
I have a somewhat unusual problem.
I wrote a damage meter for a game and have the problem, if I attack 2 opponents at the same time in the game, I am shown several times in the list.
To get the data from the game, I use PhotonParser, which reads out the network data.
(First of all, it's nothing illegal.)
public class AlbionPackageParser : PhotonParser
{
private readonly HealthUpdateEventHandler HealthUpdateEventHandler;
public AlbionPackageParser(TrackingController trackingController, MainWindowViewModel mainWindowViewModel)
{
HealthUpdateEventHandler = new HealthUpdateEventHandler(trackingController);
}
protected override void OnEvent(byte code, Dictionary<byte, object> parameters)
{
var eventCode = ParseEventCode(parameters);
if (eventCode == EventCodes.Unused)
{
return;
}
Task.Run(async () =>
{
switch (eventCode)
{
case EventCodes.HealthUpdate:
await HealthUpdateEventHandlerAsync(parameters).ConfigureAwait(false);
return;
}
});
}
private static EventCodes ParseEventCode(IReadOnlyDictionary<byte, object> parameters)
{
if (!parameters.TryGetValue(252, out var value))
{
return EventCodes.Unused;
}
return (EventCodes)Enum.ToObject(typeof(EventCodes), value);
}
private async Task HealthUpdateEventHandlerAsync(Dictionary<byte, object> parameters)
{
var value = new HealthUpdateEvent(parameters);
await HealthUpdateEventHandler.OnActionAsync(value);
}
}
public class HealthUpdateEventHandler
{
private readonly TrackingController _trackingController;
public HealthUpdateEventHandler(TrackingController trackingController)
{
_trackingController = trackingController;
}
public async Task OnActionAsync(HealthUpdateEvent value)
{
await _trackingController.CombatController.AddDamageAsync(value.ObjectId, value.CauserId, value.HealthChange, value.NewHealthValue);
}
}
public class HealthUpdateEvent
{
private static readonly ILog Log = LogManager.GetLogger(MethodBase.GetCurrentMethod()?.DeclaringType);
public long CauserId;
public int CausingSpellType;
public EffectOrigin EffectOrigin;
public EffectType EffectType;
public double HealthChange;
public double NewHealthValue;
public long ObjectId;
public GameTimeStamp TimeStamp;
public HealthUpdateEvent(Dictionary<byte, object> parameters)
{
ConsoleManager.WriteLineForNetworkHandler(GetType().Name, parameters);
try
{
if (parameters.ContainsKey(0))
{
ObjectId = parameters[0].ObjectToLong() ?? throw new ArgumentNullException();
}
if (parameters.ContainsKey(1))
{
TimeStamp = new GameTimeStamp(parameters[1].ObjectToLong() ?? 0);
}
if (parameters.ContainsKey(2))
{
HealthChange = parameters[2].ObjectToDouble();
}
if (parameters.ContainsKey(3))
{
NewHealthValue = parameters[3].ObjectToDouble();
}
if (parameters.ContainsKey(4))
{
EffectType = (EffectType) (parameters[4] as byte? ?? 0);
}
if (parameters.ContainsKey(5))
{
EffectOrigin = (EffectOrigin) (parameters[5] as byte? ?? 0);
}
if (parameters.ContainsKey(6))
{
CauserId = parameters[6].ObjectToLong() ?? throw new ArgumentNullException();
}
if (parameters.ContainsKey(7))
{
CausingSpellType = parameters[7].ObjectToShort();
}
}
catch (ArgumentNullException ex)
{
ConsoleManager.WriteLineForError(MethodBase.GetCurrentMethod()?.DeclaringType, ex);
Log.Error(MethodBase.GetCurrentMethod()?.DeclaringType, ex);
}
catch (Exception e)
{
ConsoleManager.WriteLineForError(MethodBase.GetCurrentMethod()?.DeclaringType, e);
Log.Error(MethodBase.GetCurrentMethod()?.DeclaringType, e);
}
}
}
Most important file:
public class CombatController
{
public async Task AddDamageAsync(long objectId, long causerId, double healthChange, double newHealthValue)
{
if (!IsDamageMeterActive || objectId == causerId)
{
return;
}
var gameObject = _trackingController?.EntityController?.GetEntity(causerId);
if (gameObject?.Value == null
|| gameObject.Value.Value?.ObjectType != GameObjectType.Player
|| !_trackingController.EntityController.IsUserInParty(gameObject.Value.Value.Name)
)
{
return;
}
if (GetHealthChangeType(healthChange) == HealthChangeType.Damage)
{
var damageChangeValue = (int)Math.Round(healthChange.ToPositiveFromNegativeOrZero(), MidpointRounding.AwayFromZero);
if (damageChangeValue <= 0)
{
return;
}
gameObject.Value.Value.Damage += damageChangeValue;
}
if (GetHealthChangeType(healthChange) == HealthChangeType.Heal)
{
var healChangeValue = healthChange;
if (healChangeValue <= 0)
{
return;
}
if (IsMaxHealthReached(objectId, newHealthValue))
{
return;
}
gameObject.Value.Value.Heal += (int)Math.Round(healChangeValue, MidpointRounding.AwayFromZero);
}
if (gameObject.Value.Value?.CombatStart == null)
{
gameObject.Value.Value.CombatStart = DateTime.UtcNow;
}
if (await IsUiUpdateAllowedAsync() && !IsUiUpdateActive)
{
await UpdateDamageMeterUiAsync(_mainWindowViewModel.DamageMeter, _trackingController.EntityController.GetAllEntities());
}
}
private static bool IsUiUpdateActive;
public async Task UpdateDamageMeterUiAsync(AsyncObservableCollection<DamageMeterFragment> damageMeter, List<KeyValuePair<Guid, PlayerGameObject>> entities)
{
IsUiUpdateActive = true;
var highestDamage = entities.GetHighestDamage();
var highestHeal = entities.GetHighestHeal();
_trackingController.EntityController.DetectUsedWeapon();
foreach (var healthChangeObject in entities)
{
if (_mainWindow?.Dispatcher == null || healthChangeObject.Value?.UserGuid == null)
{
continue;
}
var fragment = damageMeter.ToList().FirstOrDefault(x => x.CauserGuid == healthChangeObject.Value.UserGuid);
if (fragment != null)
{
await UpdateDamageMeterFragmentAsync(fragment, healthChangeObject, entities, highestDamage, highestHeal).ConfigureAwait(true);
}
else
{
await AddDamageMeterFragmentAsync(damageMeter, healthChangeObject, entities, highestDamage, highestHeal).ConfigureAwait(true);
}
Application.Current.Dispatcher.Invoke(() => _mainWindowViewModel.SetDamageMeterSort());
}
if (HasDamageMeterDupes(_mainWindowViewModel.DamageMeter))
{
await RemoveDuplicatesAsync(_mainWindowViewModel.DamageMeter);
}
IsUiUpdateActive = false;
}
private async Task UpdateDamageMeterFragmentAsync(DamageMeterFragment fragment, KeyValuePair<Guid, PlayerGameObject> healthChangeObject, List<KeyValuePair<Guid, PlayerGameObject>> entities, long highestDamage, long highestHeal)
{
var itemInfo = await SetItemInfoIfSlotTypeMainHandAsync(fragment.CauserMainHand, healthChangeObject.Value?.CharacterEquipment?.MainHand).ConfigureAwait(false);
fragment.CauserMainHand = itemInfo;
// Damage
if (healthChangeObject.Value?.Damage > 0)
{
fragment.DamageInPercent = (double)healthChangeObject.Value.Damage / highestDamage * 100;
fragment.Damage = (long)healthChangeObject.Value?.Damage;
}
if (healthChangeObject.Value?.Dps != null)
{
fragment.Dps = healthChangeObject.Value.Dps;
}
// Heal
if (healthChangeObject.Value?.Heal > 0)
{
fragment.HealInPercent = (double)healthChangeObject.Value.Heal / highestHeal * 100;
fragment.Heal = (long)healthChangeObject.Value?.Heal;
}
if (healthChangeObject.Value?.Hps != null)
{
fragment.Hps = healthChangeObject.Value.Hps;
}
// Generally
if (healthChangeObject.Value != null)
{
fragment.DamagePercentage = entities.GetDamagePercentage(healthChangeObject.Value.Damage);
fragment.HealPercentage = entities.GetHealPercentage(healthChangeObject.Value.Heal);
_trackingController.EntityController.SetPartyCircleColor(healthChangeObject.Value.UserGuid, itemInfo?.FullItemInformation?.CategoryId);
}
}
private async Task AddDamageMeterFragmentAsync(AsyncObservableCollection<DamageMeterFragment> damageMeter, KeyValuePair<Guid, PlayerGameObject> healthChangeObject,
List<KeyValuePair<Guid, PlayerGameObject>> entities, long highestDamage, long highestHeal)
{
if (healthChangeObject.Value == null
|| (double.IsNaN(healthChangeObject.Value.Damage) && double.IsNaN(healthChangeObject.Value.Heal))
|| (healthChangeObject.Value.Damage <= 0 && healthChangeObject.Value.Heal <= 0))
{
return;
}
var mainHandItem = ItemController.GetItemByIndex(healthChangeObject.Value?.CharacterEquipment?.MainHand ?? 0);
var itemInfo = await SetItemInfoIfSlotTypeMainHandAsync(mainHandItem, healthChangeObject.Value?.CharacterEquipment?.MainHand);
var damageMeterFragment = new DamageMeterFragment
{
CauserGuid = healthChangeObject.Value.UserGuid,
Damage = healthChangeObject.Value.Damage,
Dps = healthChangeObject.Value.Dps,
DamageInPercent = (double)healthChangeObject.Value.Damage / highestDamage * 100,
DamagePercentage = entities.GetDamagePercentage(healthChangeObject.Value.Damage),
Heal = healthChangeObject.Value.Heal,
Hps = healthChangeObject.Value.Hps,
HealInPercent = (double)healthChangeObject.Value.Heal / highestHeal * 100,
HealPercentage = entities.GetHealPercentage(healthChangeObject.Value.Heal),
Name = healthChangeObject.Value.Name,
CauserMainHand = itemInfo
};
damageMeter.Init(Application.Current.Dispatcher.Invoke);
damageMeter.Add(damageMeterFragment);
_trackingController.EntityController.SetPartyCircleColor(healthChangeObject.Value.UserGuid, itemInfo?.FullItemInformation?.CategoryId);
}
// Try 1
private bool IsUiUpdateAllowed(int waitTimeInSeconds = 1)
{
var currentDateTime = DateTime.UtcNow;
var difference = currentDateTime.Subtract(_lastDamageUiUpdate);
if (difference.Seconds >= waitTimeInSeconds && !IsUiUpdateActive)
{
_lastDamageUiUpdate = currentDateTime;
return true;
}
return false;
}
// Try 2
private async Task<bool> IsUiUpdateAllowedAsync()
{
await Task.Delay(100).ConfigureAwait(true);
if (_updateReadyCounter++ < 2)
{
return false;
}
_updateReadyCounter = 0;
return true;
}
}
In the last class "CombatController" I am currently trying to get the problem under control.
The "UpdateDamageMeterUiAsync ()" method should only be triggered once if IsUiUpdateAllowedAsync () is true.
Nevertheless, "UpdateDamageMeterUiAsync ()" is always triggered 2-3 times at the same time, which is why multiple entries are created. All other lists are fine, only the UI object "AsyncObservableCollection _mainWindowViewModel.DamageMeter" has the problem.
Does anyone have any idea how I can tackle the problem?
I am using #aspnet/signalr to connect to a hub and from the hub i am calling the method to return me some data.
When i first initialize the server the connection is established normally, but if i refresh the window 3 or 4 times the client stop sending connection request to the server.
I tried logging the HubConnection, the connectionState at first is equal to 1 but after the problem accrues the connectionState is alwase equal 0
Here is how i am building the hubConnection:
buildConnection() {
this.hubConnection = new HubConnectionBuilder()
.withUrl(this.tradesService.getStockQuotationsHubUrl())
.build();
this.hubConnection.serverTimeoutInMilliseconds = 1000 * 10;
this.hubConnection.onclose(() => setTimeout(() => this.startSignalRConnection(), 2000));}
Here is how i am starting the hubConnection:
startConnection() {
this.hubConnection
.start()
.then(() => {
this.hubConnection.on('updateMethod', (data: any) => {
this.store.push([
{ type: 'update', key: data.stockID, data },
]);
});
this.dataSource = new DataSource({
store: this.store,
reshapeOnPush: true,
});
});}
Note: I am listing to 5 hubs at once in my page but the only one having problems is this one.
[Update]
The problem is from the server because when i restart the server the connection is reestablished between the client and the server, but if the client refresh or quit the page for multiple times the hub does not even try to connect to the client
public class StockQuotationsHub : Microsoft.AspNetCore.SignalR.Hub
{
private string SectorID { get; set; }
private int TradingSession { get; set; }
private int MarketID { get; set; }
public static StockQuotationExt stockQuotationExt { get; set; }
private readonly StockQuotationTicker _stockTicker;
public StockQuotationsHub(StockQuotationTicker stockTicker){
_stockTicker = stockTicker;
}
public override Task OnConnectedAsync(){
return base.OnConnectedAsync();
}
public IEnumerable<StockQuotation> GetAllStockQuotations(string[] stockID, string sectorID, int tradingSession, int marketType){
return _stockTicker.
GetAllStocks(Context.ConnectionId, stockID, sectorID, tradingSession, marketType);
}
public override async Task OnDisconnectedAsync(Exception exception){
await base.OnDisconnectedAsync(exception);
}
and here is my stock ticker class:
public IEnumerable<StockQuotation> GetAllStocks(string connectionId, string[] stockID, string sectorID, int tradingSession, int marketType)
{
_stocks = new List<StockQuotation>();
_stocks = Task.Run(async () => await GetStockQuotationModelAsync("", 0, 1, 0)).Result.ToList();
this.MaxTimeStamp = _stocks.Max(s => s.TStamp);
this.SectorID = sectorID;
this.TradingSession = tradingSession;
this.MarketID = marketType;
AddToGroups(connectionId, stockID);
if (_timer==null)
_timer = new Timer(UpdateStockPrices, null, _updateInterval, _updateInterval);
if (stockID.Length == 0)
{
return _stocks;
}
else
{
var stocksList = new List<StockQuotation>();
foreach (var stock in stockID)
{
stocksList.AddRange(_stocks.Where(s => s.StockID == stock).ToList());
}
return stocksList;
}
}
private void AddToGroups(string connectionId, string[] stockID)
{
if (_stocks.Count > 0)
{
if (stockID.Length == 0)
{
Hub.Groups.AddToGroupAsync(connectionId, "ALL");
}
else
{
foreach (var stock in stockID)
{
Hub.Groups.AddToGroupAsync(connectionId, stock);
var s = _stocks.FirstOrDefault(s => s.StockID == stock);
if(s != null)
{
s.Snapshots = new List<double>(GetStockQuotationSnapshots(stock));
}
}
}
}
}
private void AddToGroups(string connectionId, string[] stockID)
{
if (_stocks.Count > 0)
{
if (stockID.Length == 0)
{
Hub.Groups.AddToGroupAsync(connectionId, "ALL");
}
else
{
foreach (var stock in stockID)
{
Hub.Groups.AddToGroupAsync(connectionId, stock);
var s = _stocks.FirstOrDefault(s => s.StockID == stock);
if(s != null)
{
s.Snapshots = new List<double>(GetStockQuotationSnapshots(stock));
}
}
}
}
}
I really appreciated the help.
Eventually the problem was that the project type was MVC, so I changed it to be webApi
and everything worked successfully
Even if you fix it I would like to recommend you to add the following code in order to know exactly the root cause of disconnection.
First of all enable the extended debug info in the service configuration:
services.AddSignalR(o =>
{
o.EnableDetailedErrors = true;
});
Furthermore, you need to attach to the event Closed in HubConnection like that:
hubConnection.Closed += (exception) =>
{
if (exception == null)
{
Console.WriteLine("Connection closed without error.");
}
else
{
Console.WriteLine($"Connection closed due to an error: {exception}");
}
return null;
};
I'm building an app which will scrape some data from a website and shows a notification when some criteria are met.
Everything works well without problems when the app is open (because the WebView is being rendered) but when I close the app the WebView is disabled so I cannot use it to scrape data anymore.
The scraping code is inside a class called from a ForegroundService.
I've already looked on the internet but I'm unable to find a solution or a substitute to WebView, do you have any ideas?
I'm sorry if this question looks stupid to you, I've started to develop for mobile just one week ago
Below the JDMonitoring class which is called from the AlarmTask class
using System;
using System.Collections.Generic;
using System.Net;
using System.Threading.Tasks;
using Xamarin.Forms;
namespace CGSJDSportsNotification {
public class JDMonitoring {
class Ticket {
string owner;
string title;
string store;
string lastUpdated;
string link;
public string ID { get; set; }
public string Owner {
get {
return owner == null ? "Nobody" : owner;
} set {
owner = value.Remove(0, value.IndexOf('(') + 1).Replace(")", "");
}
}
public string Title {
get {
return title;
} set {
if (value.StartsWith("(P"))
title = value.Remove(0, value.IndexOf(')') + 2);
}
}
public string Status { get; set; }
public string Store {
get {
return store;
} set {
store = value.Replace(#"\u003C", "").Replace(">", "");
}
}
public string LastUpdated {
get {
return lastUpdated;
} set {
string v;
int time = Convert.ToInt32(System.Text.RegularExpressions.Regex.Replace(value, #"[^\d]+", ""));
// Convert to minutes
if (value.Contains("hours"))
time *= 60;
v = time.ToString();
if (value.Contains("seconds"))
v = v.Insert(v.Length, " sec. ago");
else
v = v.Insert(v.Length, " min. ago");
lastUpdated = v;
}
}
public string Link {
get {
return link;
} set {
link = "https://support.jdplc.com/" + value;
}
}
}
public JDMonitoring() {
WB.Source = JDQueueMainUrl;
WB.Navigated += new EventHandler<WebNavigatedEventArgs>(OnNavigate);
}
IForegroundService FgService { get { return DependencyService.Get<IForegroundService>(); } }
WebView WB { get; } = MainPage.UI.MonitoringWebView;
string JDQueueMainUrl { get; } = "https://support.jdplc.com/rt4/Search/Results.html?Format=%27%3Cb%3E%3Ca%20href%3D%22__WebPath__%2FTicket%2FDisplay.html%3Fid%3D__id__%22%3E__id__%3C%2Fa%3E%3C%2Fb%3E%2FTITLE%3A%23%27%2C%0A%27%3Cb%3E%3Ca%20href%3D%22__WebPath__%2FTicket%2FDisplay.html%3Fid%3D__id__%22%3E__Subject__%3C%2Fa%3E%3C%2Fb%3E%2FTITLE%3ASubject%27%2C%0AStatus%2C%0AQueueName%2C%0AOwner%2C%0APriority%2C%0A%27__NEWLINE__%27%2C%0A%27__NBSP__%27%2C%0A%27%3Csmall%3E__Requestors__%3C%2Fsmall%3E%27%2C%0A%27%3Csmall%3E__CreatedRelative__%3C%2Fsmall%3E%27%2C%0A%27%3Csmall%3E__ToldRelative__%3C%2Fsmall%3E%27%2C%0A%27%3Csmall%3E__LastUpdatedRelative__%3C%2Fsmall%3E%27%2C%0A%27%3Csmall%3E__TimeLeft__%3C%2Fsmall%3E%27&Order=DESC%7CASC%7CASC%7CASC&OrderBy=LastUpdated%7C%7C%7C&Query=Queue%20%3D%20%27Service%20Desk%20-%20CGS%27%20AND%20(%20%20Status%20%3D%20%27new%27%20OR%20Status%20%3D%20%27open%27%20OR%20Status%20%3D%20%27stalled%27%20OR%20Status%20%3D%20%27deferred%27%20OR%20Status%20%3D%20%27open%20-%20awaiting%20requestor%27%20OR%20Status%20%3D%20%27open%20-%20awaiting%20third%20party%27%20)&RowsPerPage=0&SavedChartSearchId=new&SavedSearchId=new";
bool MonitoringIsInProgress { get; set; } = false;
public bool IsConnectionAvailable {
get {
try {
using (new WebClient().OpenRead("http://google.com/generate_204"))
return true;
} catch {
return false;
}
}
}
async Task<bool> IsOnLoginPage() {
if (await WB.EvaluateJavaScriptAsync("document.getElementsByClassName('left')[0].innerText") != null)
return true;
return false;
}
async Task<bool> Login() {
await WB.EvaluateJavaScriptAsync($"document.getElementsByName('user')[0].value = '{UserSettings.SecureEntries.Get("rtUser")}'");
await WB.EvaluateJavaScriptAsync($"document.getElementsByName('pass')[0].value = '{UserSettings.SecureEntries.Get("rtPass")}'");
await WB.EvaluateJavaScriptAsync("document.getElementsByClassName('button')[0].click()");
await Task.Delay(1000);
// Checks for wrong credentials error
if (await WB.EvaluateJavaScriptAsync("document.getElementsByClassName('action-results')[0].innerText") == null)
return true;
return false;
}
async Task<List<Ticket>> GetTickets() {
List<Ticket> tkts = new List<Ticket>();
// Queue tkts index (multiple of 2)
int index = 2;
// Iterates all the queue
while (await WB.EvaluateJavaScriptAsync($"document.getElementsByClassName('ticket-list collection-as-table')[0].getElementsByTagName('tbody')[0].getElementsByTagName('tr')[{index}].innerText") != null) {
Ticket tkt = new Ticket();
tkt.LastUpdated = await WB.EvaluateJavaScriptAsync($"document.getElementsByClassName('ticket-list collection-as-table')[0].getElementsByTagName('tbody')[0].getElementsByTagName('tr')[{index + 1}].getElementsByTagName('td')[4].innerText");
// Gets only the tkts which are not older than the value selected by the user
if (Convert.ToInt32(System.Text.RegularExpressions.Regex.Replace(tkt.LastUpdated, #"[^\d]+", "")) > Convert.ToInt32(UserSettings.Entries.Get("searchTimeframe")))
break;
tkt.ID = await WB.EvaluateJavaScriptAsync($"document.getElementsByClassName('ticket-list collection-as-table')[0].getElementsByTagName('tbody')[0].getElementsByTagName('tr')[{index}].getElementsByTagName('td')[0].innerText");
tkt.Owner = await WB.EvaluateJavaScriptAsync($"document.getElementsByClassName('ticket-list collection-as-table')[0].getElementsByTagName('tbody')[0].getElementsByTagName('tr')[{index}].getElementsByTagName('td')[4].innerText");
tkt.Title = await WB.EvaluateJavaScriptAsync($"document.getElementsByClassName('ticket-list collection-as-table')[0].getElementsByTagName('tbody')[0].getElementsByTagName('tr')[{index}].getElementsByTagName('td')[1].innerText");
tkt.Status = await WB.EvaluateJavaScriptAsync($"document.getElementsByClassName('ticket-list collection-as-table')[0].getElementsByTagName('tbody')[0].getElementsByTagName('tr')[{index}].getElementsByTagName('td')[2].innerText");
tkt.Store = await WB.EvaluateJavaScriptAsync($"document.getElementsByClassName('ticket-list collection-as-table')[0].getElementsByTagName('tbody')[0].getElementsByTagName('tr')[{index + 1}].getElementsByTagName('td')[1].innerText");
tkt.Link = await WB.EvaluateJavaScriptAsync($"document.getElementsByClassName('ticket-list collection-as-table')[0].getElementsByTagName('tbody')[0].getElementsByTagName('tr')[{index}].getElementsByTagName('td')[1].getElementsByTagName('a')[0].getAttribute('href')");
tkts.Add(tkt);
index += 2;
}
return tkts;
}
//async Task<string> QueueGetTkt
async void OnNavigate(object sender, WebNavigatedEventArgs args) {
if (MonitoringIsInProgress)
return;
if (IsConnectionAvailable) {
if (await IsOnLoginPage()) {
if (await Login() == false) {
// If the log-in failed we can't proceed
MonitoringIsInProgress = false;
FgService.NotificationNewTicket("Log-in failed!", "Please check your credentials");
// Used to avoid an infinite loop of OnNavigate method calls
WB.Source = "about:blank";
return;
}
}
// Main core of the monitoring
List<Ticket> tkts = await GetTickets();
if (tkts.Count > 0) {
foreach(Ticket t in tkts) {
// Looks only after the tkts with the country selected by the user (and if it was selected by the user, also for the tkts without a visible country)
// Firstly we look in the title
if (t.Title.Contains(MainPage.UI.CountryPicker.SelectedItem.ToString())) {
FgService.NotificationNewTicket($"[{t.ID}] {t.LastUpdated}",
$"{t.Title}\r\n\r\n" +
$"Status: {t.Status}\r\n" +
$"Owner: {t.Owner}\r\n" +
$"Last updated: {t.LastUpdated}");
break;
}
}
}
}
MonitoringIsInProgress = false;
}
}
}
AlarmTask class
using Android.App;
using Android.Content;
using Android.Support.V4.App;
namespace CGSJDSportsNotification.Droid {
[BroadcastReceiver(Enabled = true, Exported = true, DirectBootAware = true)]
[IntentFilter(new string[] { Intent.ActionBootCompleted, Intent.ActionLockedBootCompleted, "android.intent.action.QUICKBOOT_POWERON", "com.htc.intent.action.QUICKBOOT_POWERON" }, Priority = (int)IntentFilterPriority.HighPriority)]
public class AlarmTask : BroadcastReceiver {
IAlarm _MainActivity { get { return Xamarin.Forms.DependencyService.Get<IAlarm>(); } }
public override void OnReceive(Context context, Intent intent) {
if (intent.Action != null) {
if (intent.Action.Equals(Intent.ActionBootCompleted)) {
// Starts the app after reboot
var serviceIntent = new Intent(context, typeof(MainActivity));
serviceIntent.AddFlags(ActivityFlags.NewTask);
context.StartActivity(serviceIntent);
Intent main = new Intent(Intent.ActionMain);
main.AddCategory(Intent.CategoryHome);
context.StartActivity(main);
// Does not work, app crashes on boot received
/*if (UserSettings.Entries.Exists("monitoringIsRunning")) {
if ((bool)UserSettings.Entries.Get("monitoringIsRunning"))
FgService.Start();
}*/
}
} else
// Checks for new tkts on a new thread
new JDMonitoring();
// Restarts the alarm
_MainActivity.AlarmStart();
}
// Called from JDMonitoring class
public static void NotificationNewTicket(string title, string message, bool icoUnknownCountry = false) {
new AlarmTask().NotificationShow(title, message, icoUnknownCountry);
}
void NotificationShow(string title, string message, bool icoUnknownCountry) {
int countryFlag = Resource.Drawable.newTktUnknownCountry;
if (icoUnknownCountry == false) {
switch (MainPage.UI.CountryPicker.SelectedItem.ToString()) {
case "Italy":
countryFlag = Resource.Drawable.newTktItaly;
break;
case "Spain":
countryFlag = Resource.Drawable.newTktSpain;
break;
case "Germany":
countryFlag = Resource.Drawable.newTktGermany;
break;
case "Portugal":
countryFlag = Resource.Drawable.newTktPortugal;
break;
}
}
var _intent = new Intent(Application.Context, typeof(MainActivity));
_intent.AddFlags(ActivityFlags.ClearTop);
_intent.PutExtra("jdqueue_notification", "extra");
var pendingIntent = PendingIntent.GetActivity(Application.Context, 0, _intent, PendingIntentFlags.OneShot);
NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(Application.Context, "newTktNotification_channel")
.SetVisibility((int)NotificationVisibility.Public)
.SetPriority((int)NotificationPriority.High)
.SetDefaults((int)NotificationDefaults.Sound | (int)NotificationDefaults.Vibrate | (int)NotificationDefaults.Lights)
.SetSmallIcon(Resource.Drawable.newTktNotification)
.SetLargeIcon(Android.Graphics.BitmapFactory.DecodeResource(Application.Context.Resources, countryFlag))
.SetSubText("Click to check the queue")
.SetStyle(new NotificationCompat.BigTextStyle()
.SetBigContentTitle("New ticket available!")
.BigText(message))
.SetContentText(title)
.SetAutoCancel(true)
.SetContentIntent(pendingIntent);
NotificationManagerCompat.From(Application.Context).Notify(0, notificationBuilder.Build());
}
}
}
And the ForegroundService class which is responsible to trigger for the first time the alarm
using Android.App;
using Android.Content;
using Android.OS;
namespace CGSJDSportsNotification.Droid {
[Service]
class ForegroundService : Service {
IAlarm _MainActivity { get { return Xamarin.Forms.DependencyService.Get<IAlarm>(); } }
public override IBinder OnBind(Intent intent) { return null; }
public override StartCommandResult OnStartCommand(Intent intent, StartCommandFlags flags, int startId) {
// Starts the Foreground Service and the notification channel
StartForeground(9869, new ForegroundServiceNotification().ReturnNotif());
Android.Widget.Toast.MakeText(Application.Context, "JD Queue - Monitoring started!", Android.Widget.ToastLength.Long).Show();
_MainActivity.AlarmStart();
return StartCommandResult.Sticky;
}
public override void OnDestroy() {
Android.Widget.Toast.MakeText(Application.Context, "JD Queue - Monitoring stopped!", Android.Widget.ToastLength.Long).Show();
_MainActivity.AlarmStop();
UserSettings.Entries.AddOrEdit("monitoringIsRunning", false);
UserSettings.Entries.AddOrEdit("monitoringStopPending", false, false);
base.OnDestroy();
}
public override bool StopService(Intent name) {
return base.StopService(name);
}
}
}
Thank you!
[BETTER-FINAL-SOLUTION]
After several hours I've discovered Android WebView which does exactly what I need (I'm developing this app only for Android)
I've written this Browser helper class
class Browser {
public Android.Webkit.WebView WB;
static string JSResult;
public class CustomWebViewClient : WebViewClient {
public event EventHandler<bool> OnPageLoaded;
public override void OnPageFinished(Android.Webkit.WebView view, string url) {
OnPageLoaded?.Invoke(this, true);
}
}
public Browser(CustomWebViewClient wc, string url = "") {
WB = new Android.Webkit.WebView(Android.App.Application.Context);
WB.Settings.JavaScriptEnabled = true;
WB.SetWebViewClient(wc);
WB.LoadUrl(url);
}
public string EvalJS(string js) {
JSInterface jsi = new JSInterface();
WB.EvaluateJavascript($"javascript:(function() {{ return {js}; }})()", jsi);
return JSResult;
}
class JSInterface : Java.Lang.Object, IValueCallback {
public void OnReceiveValue(Java.Lang.Object value) {
JSResult = value.ToString();
}
}
}
[EDIT]
Improved the JS returning function with async callbacks (so the JS return value will be always delivered).
Credits to ChristineZuckerman
class Browser {
public Android.Webkit.WebView WB;
public class CustomWebViewClient : WebViewClient {
public event EventHandler<bool> OnPageLoaded;
public override void OnPageFinished(Android.Webkit.WebView view, string url) {
OnPageLoaded?.Invoke(this, true);
}
}
public Browser(CustomWebViewClient wc, string url = "") {
WB = new Android.Webkit.WebView(Android.App.Application.Context);
WB.ClearCache(true);
WB.Settings.JavaScriptEnabled = true;
WB.Settings.CacheMode = CacheModes.NoCache;
WB.Settings.DomStorageEnabled = true;
WB.Settings.SetAppCacheEnabled(false);
WB.Settings.UserAgentString = "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.10 (KHTML, like Gecko) Chrome/8.0.552.224 Safari/534.10";
WB.LoadUrl(url);
WB.SetWebViewClient(wc);
}
public async Task<string> EvalJS(string js, bool returnNullObjectWhenNull = true) {
string JSResult = "";
ManualResetEvent reset = new ManualResetEvent(false);
Device.BeginInvokeOnMainThread(() => {
WB?.EvaluateJavascript($"javascript:(function() {{ return {js}; }})()", new JSInterface((r) => {
JSResult = r;
reset.Set();
}));
});
await Task.Run(() => { reset.WaitOne(); });
return JSResult == "null" ? returnNullObjectWhenNull ? null : "null" : JSResult;
}
class JSInterface : Java.Lang.Object, IValueCallback {
private Action<string> _callback;
public JSInterface(Action<string> callback) {
_callback = callback;
}
public void OnReceiveValue(Java.Lang.Object value) {
string v = value.ToString();
if (v.StartsWith('"') && v.EndsWith('"'))
v = v.Remove(0, 1).Remove(v.Length - 2, 1);
_callback?.Invoke(v);
}
}
}
Example:
Browser.CustomWebViewClient wc = new Browser.CustomWebViewClient();
wc.OnPageLoaded += BrowserOnPageLoad;
Browser browser = new Browser(wc, "https://www.google.com/");
void BrowserOnPageLoad(object sender, bool e) {
string test = browser.EvalJS("document.getElementsByClassName('Q8LRLc')[0].innerText");
// 'test' will contain the value returned from the JS script
// You can acces the real WebView object by using
// browser.WB
}
// OR WITH THE NEW RETURNING FUNCTION
async void BrowserOnPageLoad(object sender, bool e) {
string test = await browser.EvalJS("document.getElementsByClassName('Q8LRLc')[0].innerText");
// 'test' will contain the value returned from the JS script
// You can acces the real WebView object by using
// browser.WB
}
[FINAL-SOLUTION]
Finally I've found an easy and efficient alternative to WebView.
Now I'm using SimpleBroswer and works great!
[SEMI-SOLUTION]
Alright, I've written a workaround but I don't really like this idea, so please, if you know a better method let me know.
Below my workaround:
In my ForegroundServiceHelper interface I've added a method to check if the MainActivity (where the WebView it's rendered) is visible or not, if isn't visible the MainActivity will be shown and immediately will be hidden back.
And the app will be removed from the last used applications
Method inside my ForegroundServiceHelper Interface
public void InitBackgroundWebView() {
if ((bool)SharedSettings.Entries.Get("MainPage.IsVisible") == false) {
// Shows the activity
Intent serviceIntent = new Intent(context, typeof(MainActivity));
serviceIntent.AddFlags(ActivityFlags.NewTask);
context.StartActivity(serviceIntent);
// And immediately hides it back
Intent main = new Intent(Intent.ActionMain);
main.AddFlags(ActivityFlags.NewTask);
main.AddCategory(Intent.CategoryHome);
context.StartActivity(main);
// Removes from the last app used
ActivityManager am = (new ContextWrapper(Android.App.Application.Context)).GetSystemService(Context.ActivityService).JavaCast<ActivityManager>();
if (am != null) {
System.Collections.Generic.IList<ActivityManager.AppTask> tasks = am.AppTasks;
if (tasks != null && tasks.Count > 0) {
tasks[0].SetExcludeFromRecents(true);
}
}
}
}
The SharedSettings class is an helper class wrapped around the App.Current.Properties Dictionary
And in the OnAppearing and OnDisappearing callbacks I set the shared values to true/false
[EDIT]
This workaround works only if the user is on the homepage, so I need to find an another solution...
I'm looking for ideas about using delegate with async and await. Is it good or not? I've searched on Google but none of them are similar to mine.
I'm defining a method to change a message status (isRead = true) with SignalR:
enum MessageStatus
{
Failure,
Success
}
delegate Task<MessageStatus> MsgDelegate(string id);
public async Task ChangeMessageStatus(string id)
{
string error = string.Empty;
MsgDelegate msg = async (x) =>
{
try
{
using (var db = new VinaChanelDbContext())
{
var message = db.Messages.SingleOrDefault(m => m.Id == x);
if (message != null)
{
message.IsRead = true;
}
return await db.SaveChangesAsync() > 0 ?
MessageStatus.Success : MessageStatus.Failure;
}
}
catch (Exception e)
{
error = e.Message;
return MessageStatus.Failure;
}
};
switch (await msg(id))
{
case MessageStatus.Success:
Clients.Caller.updateStatus(true);
break;
case MessageStatus.Failure:
Clients.Caller.errorMessage(error);
Clients.Caller.updateStatus(false);
break;
}
}
Is my code weird? Should I use it?
Why use a delegate? What's wrong with:
enum MessageStatus
{
Failure,
Success
}
public async Task ChangeMessageStatus(string id)
{
string error = string.Empty;
var status = MessageStatus.Failure;
try
{
using (var db = new VinaChanelDbContext())
{
var message = db.Messages.SingleOrDefault(m => m.Id == id);
if (message != null)
{
message.IsRead = true;
if (await db.SaveChangesAsync() > 0)
{
status = MessageStatus.Success;
}
}
}
}
catch (Exception e)
{
error = e.Message;
}
switch (status)
{
case MessageStatus.Success:
Clients.Caller.updateStatus(true);
break;
case MessageStatus.Failure:
Clients.Caller.errorMessage(error);
Clients.Caller.updateStatus(false);
break;
}
}
You might want to break that top part out into a private method, but I'm not sure why you would want to make it a delegate...