How to call Angular2 Function from C# code - c#

Hi I want to invoke a method in my angular app from C# code. My angular app resides inside WPF WebBrowser control. Below are the code snippets from C# & Angular.
C# Code snippet:
[PermissionSet(SecurityAction.Demand, Name = "FullTrust")]
[ComVisible(true)]
public partial class WebBrowserView : UserControl
{
public WebBrowserView()
{
InitializeComponent();
myBrowser.Unloaded += myBrowser_Unloaded;
}
private void myBrowser_Unloaded(object sender, RoutedEventArgs e)
{
// This works:
myBrowser.InvokeScript("execScript", new object[] { "this.alert(123)", "JavaScript" });
// This is what I actually want, but it doesn't work:
// For both of these I get the Script Error:
// System.Runtime.InteropServices.COMException: 'Exception from HRESULT: 0x80020101'
myBrowser.InvokeScript("eval", "alertExample()");
string javascript = "CoreHelper.alertExample();";
myBrowser.InvokeScript("eval", new object[] { javascript });
}
}
Angular 10 snippet:
Global function in Global.ts file:
export function alertExample(){
alert('test');
}
Inside an abstract class CoreHelper.ts
export class CoreHelper {
public static alertExample(){
alert('happy');
}
}
Definitely my alertExample is intended to do a lot more than alerting. I do not want to put any scripts inside index.html.
What is that I am missing/doing wrong here?
I also tried adding the script directly in index.html:
Angular 10 index.html:
<script type="text/javascript">
function test(params) {
alert('index.html');
}
</script>
C#
// This works:
myBrowser.InvokeScript("test");
// This doesn't work:
myBrowser.InvokeScript("eval", new object[] { "test" });
Also tried this:
Angular 10 index.html:
<script type="text/javascript" src="./assets/scripts/global-scripts.js">
</script>
global-scripts.js
function test() {
alert('You have arrived: ');
}
C#
// None of these work:
myBrowser.InvokeScript("test");
myBrowser.InvokeScript("eval", new object[] { "test" });
Thanks,
RDV

Posting my solution for this issue:
C# end:
WebBrowserView (XAML View) code:
We can use WebBrowser Navigating event, I needed more control, hence using explicit API to shutdown the connection.
public Tuple<bool, string> OnShutdownEvent(string scriptName)
{
Tuple<bool, string> result = new Tuple<bool, string>(true, "");
if (scriptName.IsNullOrEmpty())
{
return result;
}
try
{
DependencyObject sender = VisualTreeHelper.GetChild(myBrowser, 0);
if (sender != null)
{
if ((sender is WebBrowser) && ((sender as WebBrowser).Document != null))
{
//If the script is not present, we fall in catch condition
//No way to know if the script worked fine or not.
(sender as WebBrowser).InvokeScript(scriptName);
result = new Tuple<bool, string>(true, string.Format("Successfully executed script- {0}", scriptName));
}
else
{
result = new Tuple<bool, string>(true, "No script invoked.");
}
}
}
catch (Exception e)
{
result = new Tuple<bool, string>(false, e.Message);
}
return result;
}
WebBrowserViewModel code snippet:
public bool OnHandleShutdown()
{
try
{
Tuple<bool, string> result = (this.View as XbapPageView).OnShutdownEvent("closeConnection");
}
catch (Exception) { }
finally
{
XBAP_URI = "about:blank";
}
return true;
}
Now in the angular app, following changes are required:
index.html
<script type="text/javascript">
function closeConnection(params) {
window.webSocketServiceRef.zone.run(
function () {
window.webSocketServiceRef.closeConnection(params);
});
}
webSocketService.ts:
constructor(
private _webSocketService: TestWebsocketService,
private _ngZone: NgZone) {
window['webSocketServiceRef'] = {
component: this,
zone: this._ngZone,
closeConnection: (params) => this.onCloseConnectionEvent(params)
};
}
onCloseConnectionEvent(params) {
this._msgSubscription.unsubscribe();
this._webSocketMsgSubject.complete();
return true;
}
Now WebSocket connection is properly closed.
Thanks,
RDV

Related

EvaluatejavaScrip in IOS Xamarin

I use the following code to run javascript on iOS's WkWebView,but this code doesn't wait for the result
C# code
WKJavascriptEvaluationResult handler = (NSObject result, NSError error) =>
{
if (error == null && !string.IsNullOrWhiteSpace(result.ToString()))
{
string resultFromJSCall = result.ToString(); // I'd expect "MyResult"
};
};
webView.EvaluateJavaScript("test()", handler);
CallBackResult(resultFromJSCall)
JavaScript code:
function test() {
return new Promise(resolve => setTimeout(() => resolve("MyResult"),5000));
}
The javascript should execute when webview has finished loading, otherwise it will not work .
We can do it in DidFinishNavigation method .
webview.NavigationDelegate = new WebViewDelate();
public class WebViewDelate : WKNavigationDelegate
{
public override void DidFinishNavigation(WKWebView webView, WKNavigation navigation)
{
WKJavascriptEvaluationResult handler = (NSObject result, NSError error) => {
if (error != null)
{
Console.WriteLine(result.ToString());
CallBackResult(result.ToString());
}
};
webView.EvaluateJavaScript("test()", handler);
}
}
Refer to
https://stackoverflow.com/a/50160111/8187800

How can i get Javascript Callback in .Net Blazor?

Is there a way to add callback to Javascript and get the result in Blazor? Apart from JS Promises.
for example, let say i want to load a file
Javascript Code
window.readFile = function(filePath, callBack) {
var reader = new FileReader();
reader.onload = function (evt) {
callBack(evt.target.result);
};
reader.readAsText(filePath);
}
Can i have something like this in Blazor C#
// read file content and output result to console
void GetFileContent() {
JsRuntime.InvokeAsync<object>("readFile", "file.txt", (string text) => {
Console.Write(text);
});
}
Or Maybe something like this
// read with javascript
void ReadFileContent() {
JsRuntime.InvokeAsync<object>("readFile", "file.txt", "resultCallbackMethod");
}
// output result callback to console
void resultCallbackMethod(string text) {
Console.Write(text);
}
Thanks
UPDATE 1:
After re-reading your question, I think this would cover your 2nd example
I think you have the option of implementing a JS proxy function that handle the calling. Something like this:
UPDATE 2:
Code was updated with a functional (but not deeply tested) version, you can also find a working example in blazorfiddle.com
JAVASCRIPT CODE
// Target Javascript function
window.readFile = function (filePath, callBack) {
var fileInput = document.getElementById('fileInput');
var file = fileInput.files[0];
var reader = new FileReader();
reader.onload = function (evt) {
callBack(evt.target.result);
};
reader.readAsText(file);
}
// Proxy function
// blazorInstance: A reference to the actual C# class instance, required to invoke C# methods inside it
// blazorCallbackName: parameter that will get the name of the C# method used as callback
window.readFileProxy = (instance, callbackMethod, fileName) => {
// Execute function that will do the actual job
window.readFile(fileName, result => {
// Invoke the C# callback method passing the result as parameter
instance.invokeMethodAsync(callbackMethod, result);
});
}
C# CODE
#page "/"
#inject IJSRuntime jsRuntime
<div>
Select a text file:
<input type="file" id="fileInput" #onchange="#ReadFileContent" />
</div>
<pre>
#fileContent
</pre>
Welcome to your new app.
#code{
private string fileContent { get; set; }
public static object CreateDotNetObjectRefSyncObj = new object();
public async Task ReadFileContent(UIChangeEventArgs ea)
{
// Fire & Forget: ConfigureAwait(false) is telling "I'm not expecting this call to return a thing"
await jsRuntime.InvokeAsync<object>("readFileProxy", CreateDotNetObjectRef(this), "ReadFileCallback", ea.Value.ToString()).ConfigureAwait(false);
}
[JSInvokable] // This is required in order to JS be able to execute it
public void ReadFileCallback(string response)
{
fileContent = response?.ToString();
StateHasChanged();
}
// Hack to fix https://github.com/aspnet/AspNetCore/issues/11159
protected DotNetObjectRef<T> CreateDotNetObjectRef<T>(T value) where T : class
{
lock (CreateDotNetObjectRefSyncObj)
{
JSRuntime.SetCurrentJSRuntime(jsRuntime);
return DotNetObjectRef.Create(value);
}
}
}
I believe that you are looking for the info on the documentation here:
https://learn.microsoft.com/en-us/aspnet/core/blazor/javascript-interop?view=aspnetcore-3.0#invoke-net-methods-from-javascript-functions
It shows how to call the Razor.Net from Javascript.
The documentation has more information, but essentially you will need the [JSInvokable] attribute on the method in razor, and calling via DotNet.invokeMethod in javascript.
Using the tips on this page I came up with a more generic version that works with nearly any callback based function.
Update:
You can now call any function whose last argument is a callback. You can pass any number of arguments to the function and the callback can have any number of arguments returned.
The function InvokeJS returns an instance of CallbackerResponse which can be used to get the typed value of any of the response arguments. See examples and code for more information.
Based on OP callback (fileContents (string)):
Example 1 (C# Blazor with await):
var response = await Callbacker.InvokeJS("window.readFile", filename);
var fileContents = response.GetArg<string>(0);
// fileContents available here
Example 2 (C# Blazor with callback):
Callbacker.InvokeJS((response) => {
var fileContents = response.GetArg<string>(0);
// fileContents available here
}, "window.readFile", filename);
Based on common callback (error (string), data (object)):
Example 3 (C# Blazor with await):
// To call a javascript function with the arguments (arg1, arg2, arg3, callback)
// and where the callback arguments are (err, data)
var response = await Callbacker.InvokeJS("window.myObject.myFunction", arg1, arg2, arg3);
// deserialize callback argument 0 into C# string
var err = response.GetArg<string>(0);
// deserialize callback argument 1 into C# object
var data = response.GetArg<MyObjectType>(1);
In your Blazor Program.cs Main add singleton (or scoped if desired) Callbacker
builder.Services.AddSingleton<Services.Callbacker>();
Add Callbacker service in your Blazor page. Example: MyPage.razor.cs
[Inject]
public Callbacker Callbacker { get; set; }
C#
using Microsoft.JSInterop;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace Home.Services
{
public class CallbackerResponse
{
public string[] arguments { get; private set; }
public CallbackerResponse(string[] arguments)
{
this.arguments = arguments;
}
public T GetArg<T>(int i)
{
return JsonConvert.DeserializeObject<T>(arguments[i]);
}
}
public class Callbacker
{
private IJSRuntime _js = null;
private DotNetObjectReference<Callbacker> _this = null;
private Dictionary<string, Action<string[]>> _callbacks = new Dictionary<string, Action<string[]>>();
public Callbacker(IJSRuntime JSRuntime)
{
_js = JSRuntime;
_this = DotNetObjectReference.Create(this);
}
[JSInvokable]
public void _Callback(string callbackId, string[] arguments)
{
if (_callbacks.TryGetValue(callbackId, out Action<string[]> callback))
{
_callbacks.Remove(callbackId);
callback(arguments);
}
}
public Task<CallbackerResponse> InvokeJS(string cmd, params object[] args)
{
var t = new TaskCompletionSource<CallbackerResponse>();
_InvokeJS((string[] arguments) => {
t.TrySetResult(new CallbackerResponse(arguments));
}, cmd, args);
return t.Task;
}
public void InvokeJS(Action<CallbackerResponse> callback, string cmd, params object[] args)
{
_InvokeJS((string[] arguments) => {
callback(new CallbackerResponse(arguments));
}, cmd, args);
}
private void _InvokeJS(Action<string[]> callback, string cmd, object[] args)
{
string callbackId;
do
{
callbackId = Guid.NewGuid().ToString();
} while (_callbacks.ContainsKey(callbackId));
_callbacks[callbackId] = callback;
_js.InvokeVoidAsync("window._callbacker", _this, "_Callback", callbackId, cmd, JsonConvert.SerializeObject(args));
}
}
}
JS
window._callbacker = function(callbackObjectInstance, callbackMethod, callbackId, cmd, args){
var parts = cmd.split('.');
var targetFunc = window;
var parentObject = window;
for(var i = 0; i < parts.length; i++){
if (i == 0 && part == 'window') continue;
var part = parts[i];
parentObject = targetFunc;
targetFunc = targetFunc[part];
}
args = JSON.parse(args);
args.push(function(e, d){
var args = [];
for(var i in arguments) args.push(JSON.stringify(arguments[i]));
callbackObjectInstance.invokeMethodAsync(callbackMethod, callbackId, args);
});
targetFunc.apply(parentObject, args);
};
Thanks for that #Henry Rodriguez. I created something out of it, and i believed it might be helpful as well.
Note that DotNetObjectRef.Create(this) still works fine in other method. It is only noted to have problem with Blazor lifecycle events in preview6. https://github.com/aspnet/AspNetCore/issues/11159.
This is my new Implementation.
<div>
Load the file content
<button #click="#ReadFileContent">Get File Content</button>
</div>
<pre>
#fileContent
</pre>
Welcome to your new app.
#code{
string fileContent;
//The button onclick will call this.
void GetFileContent() {
JsRuntime.InvokeAsync<object>("callbackProxy", DotNetObjectRef.Create(this), "readFile", "file.txt", "ReadFileCallback");
}
//and this is the ReadFileCallback
[JSInvokable] // This is required for callable function in JS
public void ReadFileCallback(string filedata) {
fileContent = filedata;
StateHasChanged();
}
And in blazor _Host.cshtml or index.html, include the callback proxy connector
// Proxy function that serves as middlemen
window.callbackProxy = function(dotNetInstance, callMethod, param, callbackMethod){
// Execute function that will do the actual job
window[callMethod](param, function(result){
// Invoke the C# callback method passing the result as parameter
return dotNetInstance.invokeMethodAsync(callbackMethod, result);
});
return true;
};
// Then The Javascript function too
window.readFile = function(filePath, callBack) {
var reader = new FileReader();
reader.onload = function (evt) {
callBack(evt.target.result);
};
reader.readAsText(filePath);
}
This works perfectly for what i needed, and it is re-usable.

signalr Clients.Users(userIds).send(msg) not firing

I have single R version 2.2.1.
I implement custom Id Provider
public class ChatUserIdProvider : IUserIdProvider
{
public string GetUserId(IRequest request)
{
if (request.User.Identity.IsAuthenticated)
{
Guid.Parse(request.User.Identity.GetUserId().ToString());
var userId = request.User.Identity.GetUserId().ToString();
return userId.ToString();
}
return "Un Known";
}
}
I made a simple chat app and every think OK, but when I try to send a message to multi users the client event not firing
here is hub function
public void SendToMany(string msg, List<string> userIds)
{
try
{
//db things here
Clients.Users(userIds).sendMessage(msg);
}
catch (Exception ex)
{
Logs.Log(ex);
}
}
Startup
GlobalHost.DependencyResolver.Register(typeof(IUserIdProvider), () => new ChatUserIdProvider ());
app.MapSignalR();
Js
$(function () {
var chat = $.connection.chatHub;
chat.client.sendMessage= function (msg) {
$('.msgs').append('<div>'+ group.Name + '</div>');
$('#' + group.Id).click();
}
$.connection.hub.start();
})
function BrodCast() {
try {
var chatids = new Array();
$('.ckusergr').each(function () {
if ($(this).is(':checked')) {
chatids.push($(this).attr('iid'));
}
})
chat.server.sendToMany($('.txtmsg').val(), chatids);
} catch (e) {
console.log(e)
}
}
the problem with this line
public void SendToMany(string msg, List<string> userIds)
{
try
{
//db things here
Clients.Users(userIds).sendMessage(msg); // Her is the Problem
}
catch (Exception ex)
{
Logs.Log(ex);
}
}
if I change to become like this every thing work great.
public void SendToMany(string msg, List<string> userIds)
{
try
{
//db things here
foreach (string item in userIds)
{
Clients.User(item).sendMessage(msg);
}
}
catch (Exception ex)
{
Logs.Log(ex);
}
}
I had similar problem today and I find out when I send as parameter List<string> which contains all usernames into Clients.Users(list of usernames) somehow it will work also.
I found this by accident, maybe someone with better experiences may clarify why this is working since this should only accept IList<string> userIds
First, you need to start your hub before declaring 'send' function.
Second, you should put your Broadcast function inside the main function which is declaring variable chat.
something like this should work :
$(function () {
var chat = $.connection.chatHub;
chat.client.sendMessage= function (msg) {
$('.msgs').append('<div>'+ group.Name + '</div>');
$('#' + group.Id).click();
}
$.connection.hub.start().done(function () {
var chatids = new Array();
$('.ckusergr').each(function () {
if ($(this).is(':checked')) {
chatids.push($(this).attr('iid'));
}
})
chat.server.sendToMany($('.txtmsg').val(), chatids);
});
})

WebSocket Implementation

I want to Create WebSocket Example in which i do not want to refresh the page for getting latest data.
I Create one Html page in which create one object of websocket.
E.g
ClientSide Implementation
var ws = new WebSocket(hostURL);
ws.onopen = function ()
{
// When Connection Open
};
ws.onmessage = function (evt)
{
// When Any Response come from WebSocket
}
ws.onclose = function (e)
{
// OnClose of WebSocket Conection
}
Server Side Implementation
public class WebSocketManager : WebSocketHandler
{
private static WebSocketCollection WebSocketObj4AddMessage = new WebSocketCollection();
public override void OnOpen()
{
// Do when Connection Is Open
}
public override void OnClose()
{
// Close Connection
}
public override void OnMessage(string message)
{
// When Any Message Sent to Client
}
}
Is I am doing right way to use WebSocket ?
Please help me to clear out in this section.
Here a sample.
First you have to install Asp.net SignalR package along with its dependenies.
You have call the SignalR when the app starts
namespace ABC
{
public partial class Startup
{
public void Configuration(IAppBuilder app)
{
ConfigureAuth(app);
app.MapSignalR(); <--{Add this line}
}
}
}
You have start the SqlDependency when app start and stop when app stops in the Global.asax file.
string ConnectionString = ConfigurationManager.ConnectionStrings["ConnectionStringsName"].ConnectionString;
protected void Application_Start()
{
SqlDependency.Start(ConnectionString);
}
protected void Application_End()
{
SqlDependency.Stop(ConnectionString);
}
You have to create custom Hubclass extending Hub Base class
public class MessagesHub : Hub
{
[HubMethodName("sendMessages")]
public void SendMessages()
{
IHubContext context = GlobalHost.ConnectionManager.GetHubContext<MessagesHub>();
context.Clients.All.updateMessages();
}
}
Then in the client page, you have add these code in the javascript section
$(function () {
// Declare a proxy to reference the hub.
var notifications = $.connection.messagesHub;
//debugger;
// Create a function that the hub can call to broadcast messages.
notifications.client.updateMessages = function () {
getAllMessages()
};
// Start the connection.
$.connection.hub.start().done(function () {
getAllMessages();
}).fail(function (e) {
alert(e);
});
});
function getAllMessages() {
$.ajax({
url: '../../Notifications/GetNotificationMessages',
.
.
}
The server call this function when there there is any change in the database table using sqlDependency
The getAllMessages() is the controller for your code to handle, that should be shown in the view page and it will be call when the app starts and any change in db
public ActionResult GetNotificationMessages()
{
NotificationRepository notification = new NotificationRepository();
return PartialView("_NotificationMessage");
}
The in model class
public class NotificationRepository
{
readonly string connectionString = ConfigurationManager.ConnectionStrings["InexDbContext"].ConnectionString;
public IEnumerable<Notification> GetAllMessages(string userId)
{
var messages = new List<Notification>();
using(var connection = new SqlConnection(connectionString))
{
connection.Open();
using (var command = new SqlCommand(#"SELECT [NotificationID], [Message], [NotificationDate], [Active], [Url], [userId] FROM [dbo].[Notifications] WHERE [Active] = 1 AND [userId] ='" + userId + "'", connection))
{
command.Notification = null;
var dependency = new SqlDependency(command);
dependency.OnChange += new OnChangeEventHandler(dependency_OnChange);
if (connection.State == ConnectionState.Closed)
{
connection.Open();
}
var reader = command.ExecuteReader();
while (reader.Read())
{
messages.Add(item: new Notification { NotificationID = (int)reader["NotificationID"], Message = (string)reader["Message"], Url = (string)reader["Url"] });
}
}
}
return messages;
}
private void dependency_OnChange(object sender, SqlNotificationEventArgs e)
{
if (e.Type == SqlNotificationType.Change)
{
MessagesHub message = new MessagesHub();
message.SendMessages();
}
}
}
This well show latest data when the database table is updated. the message will shown at runtime.
Hope this helps
You are on the right path
You can refer this if I am not late ...This is working example
CLIENT SIDE
var ws;
var username = "JOHN";
function startchat() {
var log= $('log');
var url = 'ws://<server path>/WebSocketsServer.ashx?username=' + username;
ws = new WebSocket(url);
ws.onerror = function (e) {
log.appendChild(createSpan('Problem with connection: ' + e.message));
};
ws.onopen = function () {
ws.send("I am Active-" +username);
};
ws.onmessage = function (e) {
if (e.data.toString() == "Active?") {
ws.send("I am Active-" + username);
}
else {
}
};
ws.onclose = function () {
log.innerHTML = 'Closed connection!';
};
}
</script>
<div id="log">
</div>
Server Side in Websocketserver.ashx page
public class WebSocketsServer : IHttpHandler
{
public void ProcessRequest(HttpContext context)
{
if (context.IsWebSocketRequest)
{
context.AcceptWebSocketRequest(new MicrosoftWebSockets());
}
}
public bool IsReusable
{
get
{
return false;
}
}
}
Add below class in the server side
public class MicrosoftWebSockets : WebSocketHandler
{
private static WebSocketCollection clients = new WebSocketCollection();
private string msg;
public override void OnOpen()
{
this.msg = this.WebSocketContext.QueryString["username"];
clients.Add(this);
clients.Broadcast(msg);
}
public override void OnMessage(string message)
{
clients.Broadcast(string.Format(message));
}
public override void OnClose()
{
clients.Remove(this);
clients.Broadcast(string.Format(msg));
}
add this dll to the above class
using Microsoft.Web.WebSockets;
I donot remeber where I got the reference ...but above code is derived from my current working application

Linq2twitter asp.net c# not working

Hey guys i followed the tutorial here for a school project, https://www.dougv.com/2015/08/posting-status-updates-to-twitter-via-linqtotwitter-part-2-plain-text-tweets/
But when i run it with google chrome nothing is showing up and it just stuck in http://localhost:2860/linq2twitter.aspx which is a blank page, i've checked my twitter the tweet has not been made as well.. from the other sample i've tried i think it is suppose to send me to a authentication page which requires me to login and stuff.. any help is appreciated. Here's my code :
namespace WebApplication3
{
public partial class linq2twitter : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
}
class Program
{
static void Main()
{
Console.WriteLine("Program started.");
try
{
var result = Task.Run(() => SendTweet());
result.Wait();
if(result == null) {
Console.WriteLine("Tweet failed to process, but API did not report an error");
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
Console.WriteLine("Program completed.");
Console.Read();
}
static async Task<Status> SendTweet()
{
var auth = new SingleUserAuthorizer
{
CredentialStore = new SingleUserInMemoryCredentialStore
{
ConsumerKey = "<Twitter consumer key>",
ConsumerSecret = "<Twitter consumer secret>",
AccessToken = "<Twitter access token>",
AccessTokenSecret = "<Twitter access token secret>"
}
};
var context = new TwitterContext(auth);
var status = await context.TweetAsync(
"Hello World! I am testing #dougvdotcom's #LinqToTwitter demo, at " +
"https://www.dougv.com/2015/08/posting-status-updates-to-twitter-via-linqtotwitter-part-2-plain-text-tweets"
);
return status;
}
}
}
}
Fixed after regenerating my secrets and keys.. and removed unintentional spacebar for my keys in the code. Thx

Categories