I'm using ObjectForScripting property to interact with web page inside WebBrowser control and everything works fine except I can't figure out how to pass array of strings back to C#
HTML code
<input type="submit" onclick="window.external.save(Array('test', 'test2'))" />
Form
// Returns System.__ComObject
public void Save(object parameters)
{
}
// Throws an exception
public void Save(object[] parameters)
{
}
// Also throws an exception
public void Save(string[] parameters)
{
}
Rather than fight it; maybe approach the problem from another angle... could you (instead, either of):
delimit the data (with Array.join) and pass a single string, and split it (string.Split) in the C#
call Save multiple times, accepting a single string each time (Save(string s)), then call a final method to actually commit the changes
You can use an anonymous object instead of an array on the javascript side:
<input type="submit" onclick="window.external.save({first: 'test', second: 'test2'})" />
On the C# side (you need to use .NET 4.0 or more for the dynamic or use Type.InvokeMember if you are on an older version):
public void Save(dynamic parameters)
{
MessageBox.Show(parameters.first);
MessageBox.Show(parameters.second);
}
Not tested, but I think you can use reflection to discover the members.
Also look at this: http://dotnetacademy.blogspot.fr/2009/11/vbnetcnet-communication-with-javascript.html
function JS2VBArray( objJSArray )
{
var dictionary = new ActiveXObject( "Scripting.Dictionary" );
for ( var i = 0; i < objJSArray.length; i++ )
{
dictionary.add( i, objJSArray[ i ] );
}
return dictionary.Items();
}
Reference: http://msdn.microsoft.com/en-us/library/zsfww439(v=vs.80).aspx
<input type="submit" onclick="window.external.Save( JS2VBArray( ['test', 'test2'] ) )" />
This should go to the method.
public void Save(object[] parameters)
{
}
The string array is automatically passed as a comma-delimited string.
So this call:
window.external.save(Array('test', 'test2'));
Is received like so:
public void save(string fromjs)
{
string[] result = fromjs.Split(',');
}
It's a bit late for this, but typically when I need to pass objects or, in this case Arrays, I pass them as a JSON string.
var sArr = JSON.stringify(myArr);
window.external(sArr);
Then I have a JavaScriptSerializer on the other side that deserializes it back into an object / array.
Deserialize JSON with C#
To pass an array I found this to not be supported directly. I took the approach recommended by Marc Gravell to call multiple times but structured it in 3 methods instead, that are used in sequence: InitArgs, PushArg (multiple times), FinalArgs.
private System.Collections.Generics.Queue<string> _argsQ;
public void InitArgs()
{
_argsQ = new System.Collections.Generics.Queue<string>();
}
public void PushArg(string arg)
{
_argsQ.Enqueue(arg);
}
public void FinalArgs()
{
string[] parameters = _argsQ.ToArray();
// Save parameters
}
Now method calls can be used sequentially from html/js:
...onclick="var params = ['test', 'test2']; window.external.InitArgs(); for (var i=0; i<params.length; i++) window.external.PushArg(params[i]); window.external.FinalArgs();"...
Related
DTO looks like this:
public List<IFormFile> Images { get; set; }
public int[] Numbers { get; set; }
Method Signature:
public async Task<IActionResult> AddSection([FromForm]TheDto theDto)
Add the form data in React:
const handleSubmit = (data: any) => {
const formData = new FormData();
for(const name in data) {
formData.append(name, data[name]);
}
postService.addSection(formData);
}
It's then sent using axios.
Other values go through fine which are not in an array. I've inspected the data being added to FormData and the data is correct, I have 2 arrays, one holding File[], the other holding number[] and the names match that of the DTO so I'm now at a loss. Have I missed something?
I've used the same method on a single image and that's worked fine.
Edit:
If I log data in the above function, data has this:
{images: Array(2), numbers: Array(3)}
images: (2) [File, File]
numbers: (3) [40, 43, 77]
Further edit:
I can see that the issue is that when I'm appending the data, I'm only adding the string value for the arrays. So I think that the 2 issues are making sure I'm appending the data in the arrays properly and in a way that the .NET array understands to bind the data correctly.
There may be a better way of doing this and I'd be happy to listen to any better ways but I changed the way I appended the data for the arrays, it's now working ok.
for(const name in data) {
if (Array.isArray(data[name]) {
for (let i = 0; i < data[name].length; i++) {
formData.append(name, data[name][i]);
}
} else {
formData.append(name, data[name]);
}
}
I want to call a python function from C# code. To do that, I used Python for .NET to call function as shown in the following lines of code
using System;
using Python.Runtime;
public class Test{
public static void Main(){
using(Py.GIL()){
dynamic lb = Py.Import("lb");
dynamic result = lb.analyze("SomeValue");
Console.WriteLine(result);
}
}
}
The python function is something like this:
def analyze(source, printout = False, raw = True):
# removed for bravity
So the question is, how can I set "raw" to False when I call the analyze function from C# code. I tried the following but it didn't work.
1. dynamic result = lb.analyze("SomeSource", raw : false); // No result
2. dynamic result = lb.analyze("SomeSource", #"raw = False"); // No result
I know it is easy to do by doing this:
dynamic result = lb.analyze("SomeSource", False, False);
But what if there is more than six or seven named parameter, it would not be great to insert it all manually and change the one I wanted. For example, the python library that I am using have 12 named parameter with default value including two more parameters with no default value.
UPDATED
I also tried:
3. dynamic result = lb.analyze("SomeSource", raw = false); // C# compilation error
To apply keyword arguments use:
lb.analyze("SomeSource", Py.kw("raw", false));
See readme.
Another approach is using C# keyword argument syntax that was recently added to pythonnet:
lb.analyze("SomeSource", raw: false);
Since I am using one function to call python scripts I have used a Listto hold the parameter values. I am also passing in a class name and function name since my python scripts contain multiple classes with multiple functions. I don't use the 'self' parameter in any of my classes, so they are static functions. I am providing a snippet of my code to help you and anyone else out that is using python in .net. I personally use it for communciation with USB.
Here is an example of one of my callers. Ignore the function name but look at how it's calls the ExecuteScript and passing int he parameterset. Notice the list is of type object, incase your paramters are a mix of string/int/bool/objects etc.
public string SendCommand(string comport, string command)
{
try
{
List<object> parameterSet = new() { comport, command };
string result = _pythonService.ExecuteScript<string>("usb", "usb", "sendCommand", parameterSet);
return result;
}
catch (Exception)
{
throw;
}
}
Here is a function that executes the class function
public dynamic? ExecuteScript<T>(string scriptFile, string className, string functionName, List<object> paramset)
{
T? result = default;
try
{
// Location of all the python scripts in the project. lets get the python file we are specifying in the function param
string file = $"{Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location)}\\PythonScripts\\{scriptFile}.py";
// This is initalized in the program.cs, but just want to make sure it's initialized in case something happens
if (!PythonEngine.IsInitialized)
{
PythonEngine.Initialize();
Py.GIL();
}
using (var scope = Py.CreateScope())
{
PyObject? pythonReturn; // Our returned PythonObject from the python function call
string code = File.ReadAllText(file); // Get the python file as raw text
var scriptCompiled = PythonEngine.Compile(code, file); // Compile the code/file
scope.Execute(scriptCompiled); // Execute the compiled python so we can start calling it.
PyObject pythonClass = scope.Get(className); // Lets get an instance of the class in python
// Add parameters to the function?
if (paramset != null && paramset.Count > 0)
{
PyObject[] pyParams = new PyObject[paramset.Count]; // This is an array of python parameters passed into a function
// Loop through our incoming c# list of parameters and create PythonObject array .
for (int i = 0; i < paramset.Count; i++)
{
pyParams[i] = paramset[i].ToPython();
}
pythonReturn = pythonClass.InvokeMethod(functionName, pyParams); // Call the function on the class with parameters
}
else // We aren't using parameters here
pythonReturn = pythonClass.InvokeMethod(functionName); // Call the function on the class
// Lets convert our returned pythonObject to that of the object type (C#)
object? netObject = pythonReturn.AsManagedObject(typeof(object));
// A special case of when we want a list back. We will convert the object to the specific type in the caller function
if (typeof(T) == typeof(IList<object>))
{
object[] something = pythonReturn.As<object[]>();
return something;
}
// Convert the c# object to that of what we expect to be returned,. string/int/bool/class
if (netObject != null)
result = (T)netObject; // convert the returned string to managed string object
}
return result;
}
catch (Exception)
{
// Handle your exceptions here
throw;
}
}
If you don't care about the entire function and just want the quick snippet of adding the params:
// Add parameters to the function?
if (paramset != null && paramset.Count > 0)
{
PyObject[] pyParams = new PyObject[paramset.Count]; // This is an array of python parameters passed into a function
// Loop through our incoming c# list of parameters and create PythonObject array .
for (int i = 0; i < paramset.Count; i++)
{
pyParams[i] = paramset[i].ToPython();
}
pythonReturn = pythonClass.InvokeMethod(functionName, pyParams); // Call the function on the class with parameters
}
else // We aren't using parameters here
pythonReturn = pythonClass.InvokeMethod(functionName); // Call the function on the class
I'd like to automatically try to convert input parameters from Excel-friendly types to ones that are useful in my AddIn and vice-versa back to Excel with the return values. For example, I'd like to define a an Excel function (as a C# method) like:
public static Vector<double> MyFunction(Vector<double> inputVector)
{
// do some stuff to inputVector
return inputVector
}
I'd like for it to convert my input params and return value 'behind the scenes', i.e. I define some generic conversion method for converting from object to Vector<double> and vice versa, and this is called before they are passed in/out of my defined method.
Is this possible? I found ParameterConversionConfiguration in the github repo but I'm not quite sure how to use it. Are there any examples or further documentation available? I can see that I might need to register my type conversions somehow, but I'm not sure how to proceed.
EDIT: After some more playing around, I did this to convert a return value from a Matrix to an array:
public class ExcellAddIn : IExcelAddIn
{
public void AutoOpen()
{
var conversionConfig = GetParameterConversionConfig();
}
static ParameterConversionConfiguration GetParameterConversionConfig()
{
var paramConversionConfig = new ParameterConversionConfiguration()
.AddReturnConversion((Matrix<double> value) => value.ToArray());
return paramConversionConfig;
}
}
But upon loading the .xll, Excel spits out an 'unsupported signature' error. Am I on the right track? What else do I need to do?
There's a complete sample add-in that uses these Excel-DNA Registration extensions here: https://github.com/Excel-DNA/Registration/tree/master/Source/Samples/Registration.Sample
Some details relevant to your question:
You actually need to get the function registrations, apply your conversion and the perform the registration in your AutoOpen:
public void AutoOpen()
{
var conversionConfig = GetParameterConversionConfig();
ExcelRegistration.GetExcelFunctions()
.ProcessParameterConversions(conversionConfig)
.RegisterFunctions();
}
You might want to suppress the default processing by adding an ExplicitRegistration='true' attribute in your .dna file:
<DnaLibrary Name="My Add-In" RuntimeVersion="v4.0" >
<ExternalLibrary Path="XXX.dll" ExplicitRegistration="true" .... />
</DnaLibrary>
I've already read this an this, but this doesn't answer to my needs.
I'm learning Csharp and here's one of my first functions:
public void AskServer(string URL, WWWForm form)
{
WWWForm form = new WWWForm(URL);
form.AddField("step", StateManager.STEP_GET_CONF);
form.AddField("pseudo", this._pseudo);
form.AddField("jeton", this._dernierJeton.ToString());
/*... a bit more out of scope code...*/
}
I would like to do a (far more) generic stuff like this:
public void AskServer(string URL, ...)
{
WWWForm form = new WWWForm(URL);
/* do a loop on all parameters following the first one */
for (/*dont know how to write this*/) {
form.AddField(param[i], param[i+1]);
)
}
then call the function -somehow- like that:
AskServer("http://myweb", "pseudo", this._pseudo, "jeton", this._jeton);
Maybe if you have a nicer way of writing this, you are welcome, maybe something like in JavaScript:
AskServer("http://myweb", {
"pseudo": this._pseudo,
"jeton": this._jeton
});
One of my problems is that I need to pass value that may not be strings (key will always be).
The params keyword will let you specify a variable number of arguments (must be the last parameter). You can then treat that as an array.
public void AskServer(string url, params object[] args)
{
WWWForm form = new WWWForm(url);
for (int i = 0; i < args.GetLength(0); i++)
form.Addfield(args[i].ToString(), args[++i]);
}
Called as,
AskServer("http://myweb", "pseudo", 1, "jeton", 234);
Or as an alternative approach, use a list instead with strong type on the key (the generic declaration is ugly so you can alias it in namespaces)
using Kvp = System.Collections.Generic.KeyValuePair<string, object>;
....
public void AskServer(string url, List<Kvp> kvps)
{
WWWForm form = new WWWForm(url);
foreach (var arg in kvps)
form.Addfield(arg.Key, arg.Value);
}
Called as:
AskServer("http://myweb",
new List<Kvp>() {
new Kvp("pseudo", 1),
new Kvp("jeton", 234)
});
there are several ways to obtain this result.
parameter array, Tuple, anonymous types, ...
for example you can write
public void AskServer(string URL, params object[] values)
{
...
}
and pass as many parameters as you want
I would try this...
public void AskServer(string url, KeyValuePair<string, object>[] parameters)
{
WWWForm form = new WWWForm(URL);
/* do a loop on all parameters following the first one */
for (/*dont know how to write this*/) {
form.AddField(parameters[i].Key, parameters[i].Value);
)
}
In my WinForms application I need to call javascript function from my WebBrowser control. I used Document.InvokeScript and it works perfect with functions alone e.g
Document.InvokeScript("function").
But when i want to call javascript object method e.g.
Document.InvokeScript("obj.method")
it doesn't work. Is there a way to make it work? Or different solution to this problem? Without changing anything in the javascript code!
Thanks in advance :)
The example in the documentation does NOT include the parenthesis.
private void InvokeScript()
{
if (webBrowser1.Document != null)
{
HtmlDocument doc = webBrowser1.Document;
String str = doc.InvokeScript("test").ToString() ;
Object jscriptObj = doc.InvokeScript("testJScriptObject");
Object domOb = doc.InvokeScript("testElement");
}
}
Try
Document.InvokeMethod("obj.method");
Note that you can pass arguments if you use HtmlDocument.InvokeScript Method (String, Object[]).
Edit
Looks like you aren't the only one with this issue: HtmlDocument.InvokeScript - Calling a method of an object . You can make a "Proxy function" like the poster of that link suggests. Basically you have a function that invokes your object's function. It's not an ideal solution, but it'll definitely work. I'll continue looking to see if this is possible.
Another post on same issue: Using WebBrowser.Document.InvokeScript() to mess around with foreign JavaScript . Interesting solution proposed by C. Groß on CodeProject:
private string sendJS(string JScript) {
object[] args = {JScript};
return webBrowser1.Document.InvokeScript("eval",args).ToString();
}
You could make that an extension method on HtmlDocument and call that to run your function, only using this new function you WOULD include parenthesis, arguments, the whole nine yards in the string you pass in (since it is just passed along to an eval).
Looks like HtmlDocument does not have support for calling methods on existing objects. Only global functions. :(
Unfortunately you can't call object methods out of the box using WebBrowser.Document.InvokeScript.
The solution is to provide a global function on the JavaScript side which can redirect your call. In the most simplistic form this would look like:
function invoke(method, args) {
// The root context is assumed to be the window object. The last part of the method parameter is the actual function name.
var context = window;
var namespace = method.split('.');
var func = namespace.pop();
// Resolve the context
for (var i = 0; i < namespace.length; i++) {
context = context[namespace[i]];
}
// Invoke the target function.
result = context[func].apply(context, args);
}
In your .NET code you would use this as follows:
var parameters = new object[] { "obj.method", yourArgument };
var resultJson = WebBrowser.Document.InvokeScript("invoke", parameters);
As you mention that you cannot change anything to your existing JavaScript code, you'll have to inject the above JavaScript method in some how. Fortunately the WebBrowser control can also do for you by calling the eval() method:
WebBrowser.Document.InvokeScript("eval", javaScriptString);
For a more robust and complete implementation see the WebBrowser tools I wrote and the article explaining the ScriptingBridge which specifically aims to solve the problem you describe.
webBrowser.Document.InvokeScript("execScript", new object[] { "this.alert(123)", "JavaScript" })
for you supposed to be like this
webBrowser.Document.InvokeScript("execScript", new object[] { "obj.method()", "JavaScript" })