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 have a webview in a Shell Xamarin.Forms app. I request a secure page and get forwarded to my company SSO (Single Sign On), I pass that and can see the secure content.
The webview:
<WebView x:Name="web1" HorizontalOptions="CenterAndExpand"
VerticalOptions="FillAndExpand" HeightRequest="1000" WidthRequest="1000"/>
When I then navigate to a new Shell page from the main menu or by tapping an item in a listview (think RSS Headline list, tap to read article) which has an almost identical WebView tag on the xaml page, set the Source to a secure page on the page constructor or override OnAppearing, expecting the session/cookie to still be active I instead get forwarded to a login page again.
Is there any way that anyone knows I can ensure that all webviews in my app (iOS & Android) use the same session so the user only has to login once.
I've tried creating a the webview in the app.xaml.cs file and adding it to my pages using Content.Children.Add(App.Web1) which in my simple mind should mean I'm using the same webview on all pages and therefore the same session!? but that doesn't seem to work either.
Any and all help is greatly appreciated.
Thank you.
Using cookieJs along with normal cookie insertion methods can solve the problem of setting cookies on the front-end side:
CookieJs:
!function(e){var n;if("function"==typeof define&&define.amd&&(define(e),n=!0),"object"==typeof exports&&(module.exports=e(),n=!0),!n){var t=window.Cookies,o=window.Cookies=e();o.noConflict=function(){return window.Cookies=t,o}}}(function(){function e(){for(var e=0,n={};e<arguments.length;e++){var t=arguments[e];for(var o in t)n[o]=t[o]}return n}function n(e){return e.replace(/(%[0-9A-Z]{2})+/g,decodeURIComponent)}return function t(o){function r(){}function i(n,t,i){if("undefined"!=typeof document){"number"==typeof(i=e({path:"/"},r.defaults,i)).expires&&(i.expires=new Date(1*new Date+864e5*i.expires)),i.expires=i.expires?i.expires.toUTCString():"";try{var c=JSON.stringify(t);/^[{[]/.test(c)&&(t=c)}catch(e){}t=o.write?o.write(t,n):encodeURIComponent(String(t)).replace(/%(23|24|26|2B|3A|3C|3E|3D|2F|3F|40|5B|5D|5E|60|7B|7D|7C)/g,decodeURIComponent),n=encodeURIComponent(String(n)).replace(/%(23|24|26|2B|5E|60|7C)/g,decodeURIComponent).replace(/[()]/g,escape);var f="";for(var u in i)i[u]&&(f+="; "+u,!0!==i[u]&&(f+="="+i[u].split(";")[0]));return document.cookie=n+"="+t+f}}function c(e,t){if("undefined"!=typeof document){for(var r={},i=document.cookie?document.cookie.split("; "):[],c=0;c<i.length;c++){var f=i[c].split("="),u=f.slice(1).join("=");t||'"'!==u.charAt(0)||(u=u.slice(1,-1));try{var a=n(f[0]);if(u=(o.read||o)(u,a)||n(u),t)try{u=JSON.parse(u)}catch(e){}if(r[a]=u,e===a)break}catch(e){}}return e?r[e]:r}}return r.set=i,r.get=function(e){return c(e,!1)},r.getJSON=function(e){return c(e,!0)},r.remove=function(n,t){i(n,"",e(t,{expires:-1}))},r.defaults={},r.withConverter=t,r}(function(){})});
Sample:
iOS Renderer:
public class DefaultWebViewRenderer : ViewRenderer<CustomWebView, WKWebView>, IWKScriptMessageHandler, IWKNavigationDelegate
{
const string JavaScriptFunction = "function invokeCSharpAction(data){window.webkit.messageHandlers.invokeAction.postMessage(data);}";
const string cookieJs = "!function(e){var n;if(\"function\"==typeof define&&define.amd&&(define(e),n=!0),\"object\"==typeof exports&&(module.exports=e(),n=!0),!n){var t=window.Cookies,o=window.Cookies=e();o.noConflict=function(){return window.Cookies=t,o}}}(function(){function e(){for(var e=0,n={};e<arguments.length;e++){var t=arguments[e];for(var o in t)n[o]=t[o]}return n}function n(e){return e.replace(/(%[0-9A-Z]{2})+/g,decodeURIComponent)}return function t(o){function r(){}function i(n,t,i){if(\"undefined\"!=typeof document){\"number\"==typeof(i=e({path:\"/\"},r.defaults,i)).expires&&(i.expires=new Date(1*new Date+864e5*i.expires)),i.expires=i.expires?i.expires.toUTCString():\"\";try{var c=JSON.stringify(t);/^[\\{\\[]/.test(c)&&(t=c)}catch(e){}t=o.write?o.write(t,n):encodeURIComponent(String(t)).replace(/%(23|24|26|2B|3A|3C|3E|3D|2F|3F|40|5B|5D|5E|60|7B|7D|7C)/g,decodeURIComponent),n=encodeURIComponent(String(n)).replace(/%(23|24|26|2B|5E|60|7C)/g,decodeURIComponent).replace(/[\\(\\)]/g,escape);var f=\"\";for(var u in i)i[u]&&(f+=\"; \"+u,!0!==i[u]&&(f+=\"=\"+i[u].split(\";\")[0]));return document.cookie=n+\"=\"+t+f}}function c(e,t){if(\"undefined\"!=typeof document){for(var r={},i=document.cookie?document.cookie.split(\"; \"):[],c=0;c<i.length;c++){var f=i[c].split(\"=\"),u=f.slice(1).join(\"=\");t||'\"'!==u.charAt(0)||(u=u.slice(1,-1));try{var a=n(f[0]);if(u=(o.read||o)(u,a)||n(u),t)try{u=JSON.parse(u)}catch(e){}if(r[a]=u,e===a)break}catch(e){}}return e?r[e]:r}}return r.set=i,r.get=function(e){return c(e,!1)},r.getJSON=function(e){return c(e,!0)},r.remove=function(n,t){i(n,\"\",e(t,{expires:-1}))},r.defaults={},r.withConverter=t,r}(function(){})});";
WKUserContentController userController;
public DefaultWebViewRenderer()
{
}
protected override void OnElementChanged(ElementChangedEventArgs<CustomWebView> e)
{
base.OnElementChanged(e);
if (Control == null && e.OldElement == null)
{
userController = new WKUserContentController();
var cookieValue = string.Empty; //Whatever you want
var jsScript = new WKUserScript(new NSString(cookieJs), WKUserScriptInjectionTime.AtDocumentStart, false);
var yourCookie = new WKUserScript(new NSString("Cookies.set('CookieKey','" + cookieValue + "',{expires : 30, domain : '.yourdomain.com' })"), WKUserScriptInjectionTime.AtDocumentStart, false);
userController.AddUserScript(jsScript);
userController.AddUserScript(yourCookie);
var script = new WKUserScript(new NSString(JavaScriptFunction), WKUserScriptInjectionTime.AtDocumentEnd, false);
userController.AddUserScript(script);
userController.AddScriptMessageHandler(this, "invokeAction");
var config = new WKWebViewConfiguration { UserContentController = userController };
var webView = new WKWebView(Frame, config);
SetNativeControl(webView);
}
if (e.OldElement != null)
{
userController.RemoveAllUserScripts();
userController.RemoveScriptMessageHandler("invokeAction");
}
if (e.NewElement != null)
{
if (Element.Source is UrlWebViewSource urlSource)
{
var url = new NSUrl(urlSource.Url);
var storage = NSHttpCookieStorage.SharedStorage;
storage.AcceptPolicy = NSHttpCookieAcceptPolicy.Always;
var request = new NSMutableUrlRequest(url);
Control.LoadRequest(request);
}
else if (Element.Source is HtmlWebViewSource htmlSource)
{
Control.LoadHtmlString(htmlSource.Html, null);
}
}
}
public void DidReceiveScriptMessage(WKUserContentController userContentController, WKScriptMessage message)
{
Element.JavascriptBridgeInvoked(message.Body.ToString());
}
}
Android Renderer:
public class DefaultWebViewRenderer : WebViewRenderer
{
const string JavaScriptFunction = "function invokeCSharpAction(data){jsBridge.invokeAction(data);}";
public DefaultWebViewRenderer(Context context) : base(context)
{
}
protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.WebView> e)
{
base.OnElementChanged(e);
if (Control != null && e.NewElement is CustomWebView webView)
{
Control.SetWebViewClient(new WebViewTestClient());
Control.ClearSslPreferences();
Control.Settings.SetAppCacheEnabled(false);
Control.Settings.DatabaseEnabled = true;
Control.Settings.DomStorageEnabled = true;
Control.Settings.AllowFileAccessFromFileURLs = true;
Control.Settings.AllowUniversalAccessFromFileURLs = true;
Control.Settings.CacheMode = Android.Webkit.CacheModes.NoCache;
Control.Settings.AllowContentAccess = true;
Control.Settings.AllowFileAccess = true;
Control.Settings.JavaScriptEnabled = true;
Control.Settings.JavaScriptCanOpenWindowsAutomatically = true;
var cookieManager = CookieManager.Instance;
cookieManager.SetAcceptCookie(true);
cookieManager.SetAcceptThirdPartyCookies(Control, true);
try
{
Control.SetDownloadListener(new DownloadListener());
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
if (e.OldElement != null)
{
Control.RemoveJavascriptInterface("jsBridge");
}
Control.AddJavascriptInterface(new JSBridge(this), "jsBridge");
InjectJS(JavaScriptFunction);
if (webView.Source is UrlWebViewSource webSource)
{
Control.LoadUrl(webSource.Url);
}
}
}
private void InjectJS(string script)
{
if (Control != null)
{
Control.LoadUrl(string.Format("javascript: {0}", script));
}
}
}
public class WebViewTestClient : WebViewClient
{
public override void OnPageStarted(Android.Webkit.WebView view, string url, Bitmap favicon)
{
var cookieJs = "!function(e){var n;if(\"function\"==typeof define&&define.amd&&(define(e),n=!0),\"object\"==typeof exports&&(module.exports=e(),n=!0),!n){var t=window.Cookies,o=window.Cookies=e();o.noConflict=function(){return window.Cookies=t,o}}}(function(){function e(){for(var e=0,n={};e<arguments.length;e++){var t=arguments[e];for(var o in t)n[o]=t[o]}return n}function n(e){return e.replace(/(%[0-9A-Z]{2})+/g,decodeURIComponent)}return function t(o){function r(){}function i(n,t,i){if(\"undefined\"!=typeof document){\"number\"==typeof(i=e({path:\"/\"},r.defaults,i)).expires&&(i.expires=new Date(1*new Date+864e5*i.expires)),i.expires=i.expires?i.expires.toUTCString():\"\";try{var c=JSON.stringify(t);/^[\\{\\[]/.test(c)&&(t=c)}catch(e){}t=o.write?o.write(t,n):encodeURIComponent(String(t)).replace(/%(23|24|26|2B|3A|3C|3E|3D|2F|3F|40|5B|5D|5E|60|7B|7D|7C)/g,decodeURIComponent),n=encodeURIComponent(String(n)).replace(/%(23|24|26|2B|5E|60|7C)/g,decodeURIComponent).replace(/[\\(\\)]/g,escape);var f=\"\";for(var u in i)i[u]&&(f+=\"; \"+u,!0!==i[u]&&(f+=\"=\"+i[u].split(\";\")[0]));return document.cookie=n+\"=\"+t+f}}function c(e,t){if(\"undefined\"!=typeof document){for(var r={},i=document.cookie?document.cookie.split(\"; \"):[],c=0;c<i.length;c++){var f=i[c].split(\"=\"),u=f.slice(1).join(\"=\");t||'\"'!==u.charAt(0)||(u=u.slice(1,-1));try{var a=n(f[0]);if(u=(o.read||o)(u,a)||n(u),t)try{u=JSON.parse(u)}catch(e){}if(r[a]=u,e===a)break}catch(e){}}return e?r[e]:r}}return r.set=i,r.get=function(e){return c(e,!1)},r.getJSON=function(e){return c(e,!0)},r.remove=function(n,t){i(n,\"\",e(t,{expires:-1}))},r.defaults={},r.withConverter=t,r}(function(){})});";
var cookieValue = string.Empty; //Whatever you want
view.EvaluateJavascript(cookieJs, null);
view.EvaluateJavascript("Cookies.set('CookieKey','" + cookieValue + "',{expires : 30, domain : '.yourdomain.com' })", null);
base.OnPageStarted(view, url, favicon);
view.ClearCache(true);
}
public override void OnPageFinished(Android.Webkit.WebView view, string url)
{
base.OnPageFinished(view, url);
view.ClearCache(true);
}
public override void OnReceivedSslError(Android.Webkit.WebView view, SslErrorHandler handler, SslError error)
{
handler.Proceed();
base.OnReceivedSslError(view, handler, error);
}
}
public class DownloadListener : Java.Lang.Object, Android.Webkit.IDownloadListener
{
public DownloadListener()
{
}
public void OnDownloadStart(string url, string userAgent, string contentDisposition, string mimetype, long contentLength)
{
if (string.IsNullOrEmpty(url) || !Uri.TryCreate(url, UriKind.RelativeOrAbsolute, out _))
return;
try
{
if (HasPermissions())
{
Android.Net.Uri contentUri = Android.Net.Uri.Parse(url);
DownloadManager.Request request = new DownloadManager.Request(contentUri);
request.SetMimeType(mimetype);
var cookies = Android.Webkit.CookieManager.Instance.GetCookie(url);
request.AddRequestHeader("cookie", cookies);
request.AddRequestHeader("User-Agent", userAgent);
request.SetDescription("Downloading file...");
request.SetTitle(Android.Webkit.URLUtil.GuessFileName(url, contentDisposition, mimetype));
request.AllowScanningByMediaScanner();
request.SetNotificationVisibility(Android.App.DownloadVisibility.VisibleNotifyCompleted);
request.SetDestinationInExternalPublicDir(Android.OS.Environment.DirectoryDownloads, ".pdf");
Android.App.DownloadManager dm = (Android.App.DownloadManager)CrossCurrentActivity.Current.Activity.GetSystemService(Android.Content.Context.DownloadService);
dm.Enqueue(request);
}
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
}
private static bool HasPermissions()
{
if (Android.OS.Build.VERSION.SdkInt >= Android.OS.BuildVersionCodes.M)
{
string[] WriteLocation =
{
Android.Manifest.Permission.WriteExternalStorage,
Android.Manifest.Permission.ReadExternalStorage
};
var perm = CrossCurrentActivity.Current.AppContext.CheckSelfPermission(Android.Manifest.Permission.WriteExternalStorage);
if (perm != (int)Android.Content.PM.Permission.Granted)
{
CrossCurrentActivity.Current.Activity.RequestPermissions(WriteLocation, 2);
return false;
}
else
{
return true;
}
}
else
{
return true;
}
}
}
public class JSBridge : Java.Lang.Object
{
private readonly WeakReference<DefaultWebViewRenderer> hybridWebViewRenderer;
public JSBridge(DefaultWebViewRenderer hybridRenderer)
{
hybridWebViewRenderer = new WeakReference<DefaultWebViewRenderer>(hybridRenderer);
}
[JavascriptInterface]
[Export("invokeAction")]
public void InvokeAction(string data)
{
if (hybridWebViewRenderer != null && hybridWebViewRenderer.TryGetTarget(out DefaultWebViewRenderer hybridRenderer) && hybridRenderer != null && hybridRenderer.Element is CustomWebView webView)
{
webView.JavascriptBridgeInvoked(data);
}
}
}
I need to call C# event in xamarin.forms after the function is completed in js.Please guide
function readTextFile(file)
{
try
{
var blob = null;
var rawFile = new XMLHttpRequest();
rawFile.open(""GET"", file);
rawFile.responseType = ""blob"";//force the HTTP response, response-type header to be blob
rawFile.onload = function()
{
blob = rawFile.response;//xhr.response is now a blob object
wavesurfer.loadBlob(blob);
wavesurfer2.loadBlob(blob);
}
rawFile.send();//I need to call C# event here.Please guide.
}
catch(err)
{
}
}
Solution:
You can implement it by using CustomRenderer
1.create a HybridWebView in forms
public class HybridWebView : View
{
Action<string> action;
public static readonly BindableProperty UriProperty = BindableProperty.Create (
propertyName: "Uri",
returnType: typeof(string),
declaringType: typeof(HybridWebView),
defaultValue: default(string));
public string Uri {
get { return (string)GetValue (UriProperty); }
set { SetValue (UriProperty, value); }
}
public void RegisterAction (Action<string> callback)
{
action = callback;
}
public void Cleanup ()
{
action = null;
}
public void InvokeAction (string data)
{
if (action == null || data == null) {
return;
}
action.Invoke (data);
}
}
in contentPage.xaml
<ContentPage.Content>
<local:HybridWebView x:Name="hybridWebView" Uri="xxx.html"
HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand" />
</ContentPage.Content>
in code behind
public MainPage ()
{
...
hybridWebView.RegisterAction (data => DisplayAlert ("Alert", "JS action has been called", "OK")); //you can do something other as you want
}
in iOS project
[assembly: ExportRenderer (typeof(HybridWebView), typeof(HybridWebViewRenderer))]
namespace xxx.iOS
{
public class HybridWebViewRenderer : ViewRenderer<HybridWebView, WKWebView>, IWKScriptMessageHandler
{
const string JavaScriptFunction = "function invokeCSharpAction(data){window.webkit.messageHandlers.invokeAction.postMessage(data);}";
WKUserContentController userController;
protected override void OnElementChanged (ElementChangedEventArgs<HybridWebView> e)
{
base.OnElementChanged (e);
if (Control == null) {
userController = new WKUserContentController ();
var script = new WKUserScript (new NSString (JavaScriptFunction), WKUserScriptInjectionTime.AtDocumentEnd, false);
userController.AddUserScript (script);
userController.AddScriptMessageHandler (this, "invokeAction");
var config = new WKWebViewConfiguration { UserContentController = userController };
var webView = new WKWebView (Frame, config);
SetNativeControl (webView);
}
if (e.OldElement != null) {
userController.RemoveAllUserScripts ();
userController.RemoveScriptMessageHandler ("invokeAction");
var hybridWebView = e.OldElement as HybridWebView;
hybridWebView.Cleanup ();
}
if (e.NewElement != null) {
string fileName = Path.Combine (NSBundle.MainBundle.BundlePath, string.Format ("Content/{0}", Element.Uri));
Control.LoadRequest (new NSUrlRequest (new NSUrl (fileName, false)));
}
}
public void DidReceiveScriptMessage (WKUserContentController userContentController, WKScriptMessage message)
{
Element.InvokeAction (message.Body.ToString ());
}
}
}
in Android project
[assembly: ExportRenderer(typeof(HybridWebView), typeof(HybridWebViewRenderer))]
namespace xxx.Droid
{
public class HybridWebViewRenderer : ViewRenderer<HybridWebView, Android.Webkit.WebView>
{
const string JavascriptFunction = "function invokeCSharpAction(data){jsBridge.invokeAction(data);}";
Context _context;
public HybridWebViewRenderer(Context context) : base(context)
{
_context = context;
}
protected override void OnElementChanged(ElementChangedEventArgs<HybridWebView> e)
{
base.OnElementChanged(e);
if (Control == null)
{
var webView = new Android.Webkit.WebView(_context);
webView.Settings.JavaScriptEnabled = true;
webView.SetWebViewClient(new JavascriptWebViewClient($"javascript: {JavascriptFunction}"));
SetNativeControl(webView);
}
if (e.OldElement != null)
{
Control.RemoveJavascriptInterface("jsBridge");
var hybridWebView = e.OldElement as HybridWebView;
hybridWebView.Cleanup();
}
if (e.NewElement != null)
{
Control.AddJavascriptInterface(new JSBridge(this), "jsBridge");
Control.LoadUrl($"file:///android_asset/Content/{Element.Uri}");
}
}
}
}
And
public class JavascriptWebViewClient : WebViewClient
{
string _javascript;
public JavascriptWebViewClient(string javascript)
{
_javascript = javascript;
}
public override void OnPageFinished(WebView view, string url)
{
base.OnPageFinished(view, url);
view.EvaluateJavascript(_javascript, null);
}
}
in your Html
function readTextFile(file)
{
try
{
var blob = null;
var rawFile = new XMLHttpRequest();
rawFile.open(""GET"", file);
rawFile.responseType = ""blob"";//force the HTTP response, response-type header to be blob
rawFile.onload = function()
{
blob = rawFile.response;//xhr.response is now a blob object
wavesurfer.loadBlob(blob);
wavesurfer2.loadBlob(blob);
}
rawFile.send();
invokeCSharpAction(data); //you can pass some params if you need
}
catch(err)
{
}
}
For more detail you can refer here .
I am trying to create a filehelper utility and I am getting issue in assigning Func to an event. It says non assignable.
The event and delegate signature is as below
public event AfterReadHandler<T> AfterReadRecord
public delegate void AfterReadHandler<T>(EngineBase engine, fterReadEventArgs<T> e);
The code is as below
public class FileHelperUtility<T> where T : class
{
public List<T> ParseFile<T>(string fileName, ImportFileError fileError,Func<EngineBase, AfterReadEventArgs<T> > afterReadFunc ) where T : class
{
List<T> records = new List<T>();
var fileEngine = InitializeFileEngine<T>(afterReadFunc);
records = fileEngine.ReadFile(fileName).ToList();
if (ValidateHeader(fileEngine.HeaderText))
{
fileError.ErrorType = ImportFileFaultType.InvalidHeaderRecordType;
fileError.ErrorMessage = "No header record in the file.";
}
else
{
PopulateErrors(fileError, fileEngine.ErrorManager);
}
return records;
}
private FileHelperEngine<T> InitializeFileEngine<T>(Func<EngineBase, AfterReadEventArgs<T>> afterReadFunc) where T : class
{
var fileEngine = new FileHelperEngine<T>(Encoding.Default);
fileEngine.ErrorMode = ErrorMode.SaveAndContinue;
if (afterReadFunc != null)
{
fileEngine.AfterReadRecord += afterReadFunc;
}
return fileEngine;
}
private void PopulateErrors(ImportFileError fileError, ErrorManager errorManager)
{
if (errorManager.Errors.Count() > 0)
{
fileError.ErrorType = ImportFileFaultType.InvalidDetailRecordType;
fileError.ErrorData = new List<string>();
}
foreach (var error in errorManager.Errors)
{
string errorString = string.Format("Line:{0} Field:{1} - ErrorInfo:{2}",
error.LineNumber,
((ConvertException)error.ExceptionInfo).FieldName,
error.ExceptionInfo.InnerException);
fileError.ErrorData.Add(errorString);
}
}
private bool ValidateHeader(string headerRecord)
{
bool isheaderValid;
if (!string.IsNullOrEmpty(headerRecord) &&
!string.IsNullOrEmpty(headerRecord.Trim(new char[] { '\r', '\n' })))
{
isheaderValid = true;
}
else
{
isheaderValid = false;
}
return isheaderValid;
}
}
I am getting the non- assignable error at fileEngine.AfterReadRecord += afterReadFunc;
Can Someone help.
I've cut your code down slightly, but the issue is that an AfterReadHandler<T> isn't an Func<EngineBase, AfterReadEventArgs<T>> (nor is it a Action<EngineBase, AfterReadEventArgs<T>>.
You have two choices to fix your code.
(1)
private FileHelperUtility<T> InitializeFileEngine<T>(AfterReadHandler<T> afterReadFunc) where T : class
{
var fileEngine = new FileHelperUtility<T>();
if (afterReadFunc != null)
{
fileEngine.AfterReadRecord += afterReadFunc;
}
return fileEngine;
}
(2)
private FileHelperUtility<T> InitializeFileEngine<T>(Action<EngineBase, AfterReadEventArgs<T>> afterReadFunc) where T : class
{
var fileEngine = new FileHelperUtility<T>();
if (afterReadFunc != null)
{
fileEngine.AfterReadRecord += (eb, area) => afterReadFunc(eb, area);
}
return fileEngine;
}
Let me know if this isn't clear enough.
As I understand that you're using Func<T, TResult> Delegate the second param is TResult it's not the same with AfterReadHandler delegate.
Can you try to use Action<T1, T2> Delegate instead.
Refs here:
Func in msdn,
Action in msdn