Thread Rating:
  • 1 Vote(s) - 5 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Call C# functions from anywhere using URL (HTTP server)
#8
This function compiles and executes C# code sent by clients.

Setup

1. Get this C# compiler.
2. Create folder "@HTTP server". Can be any name with @ prefix.
3. Move the HTTP server script into the new folder. Let it always be the first file in the folder.
4. Add this class file to the folder:
 
Code:
Copy      Help
// class "HTTP server C# compiler.cs"
/*/ c CsScript.cs; /*/
using System.Reflection;
using System.Net;

static partial class Functions {
    /// <summary>
    ///
Compiles and executes C# code.
    /// Uses CsScript.cs from https://www.libreautomate.com/forum/showthread.php?tid=7491.
    /// </summary>
    ///
<remarks>
    ///
Let clients pass these POST fields or GET parameters:
    /// <br/>• code - C# code. In code can be used everything from .NET, Au.dll and _r libraries.
    /// <br/>• fun (optional) - a function defined in the C# code, like "Class1.Function1".
    /// <br/>• _c (optional) - C# files to compile together, like "file1.cs|file2.cs". If does not end with ".cs" - C# code to compile together as separate file. More info in CsScript.Compile documentation. The files must be on the server's computer.
    /// <br/>• _r (optional) - additional reference assemblies, like "file1.dll|file2.dll". More info in CsScript.Compile documentation. The files must be on the server's computer.
    /// <br/>• Names starts with _ - not used. Reserved.
    /// <br/>• Other - function arguments (if used fun) or values for the args array.
    /// <para>
    ///
If fun used, creates a library assembly and calls the function, which must be public static, with zero or more parameters of type string, int, bool and other types that implement IConvertible. Clients must use these parameter names, in any order, and can omit optionals.
    /// If fun omitted or empty, creates an executable assembly and calls the entry point (top-level statements or Main). Parameter values are in the args variable (string[]). Clients can use any parameter names, they are ignored.
    /// </para>
    ///
Slow when used the first time in the server process, because loads and JIT-compiles the C# compiler. Afterwards should be ~100 ms, depending on computer speed and antivirus program. Always compiles, does not use caching.
    /// </remarks>
    ///
<returns>An object containing the return value of the invoked method, or null if void.</returns>
    public static object Code(HSMessage m, HSResponse r) {
        MethodInfo mi;
        object[] a;
        try {
            var d = m.Method == "GET" ? m.UrlParameters : m.Urlencoded ?? m.Multipart?.ToDictionary(o => o.Key, o => o.Value.Text) ?? new();
            string code = d["code"];
            bool library = d.TryGetValue("fun", out var fun) && !fun.NE();
            string[] ac = null, ar = null;
            if (d.TryGetValue("_c", out var s1)) ac = s1.Ends(".cs", true) ? s1.Split('|') : new[] { s1 };
            if (d.TryGetValue("_r", out var s2)) ar = s2.Split('|');
            
            List<string> errors = new();
            var c = CsScript.Compile(code, library, ac, ar, errors);
            if (c == null) return _Bad("Errors in C# code:\r\n  " + string.Join("\r\n  ", errors));
            
            if (library) {
                int dot = fun.IndexOf('.'); if (dot < 0) return _Bad("Expected \"fun=Class.Method\"");
                var cla = fun[..dot];
                fun = fun[++dot..];
                var type = c.Assembly.GetType(cla); if (type == null) return _Bad("class not found: " + cla);
                mi = type.GetMethod(fun, BindingFlags.Public | BindingFlags.Static); if (mi == null) return _Bad("public static method not found: " + fun);
                
                //code like in HTTP server.cs
                var p = mi.GetParameters();
                a = new object[p.Length];
                for (int i = 0; i < p.Length; i++) {
                    var v = p[i];
                    var t = v.ParameterType;
                    if (d.TryGetValue(v.Name, out var s)) a[i] = t == typeof(string) ? s : Convert.ChangeType(s, t);
                    a[i] ??= v.DefaultValue is not DBNull ? v.DefaultValue : throw new Exception($"Parameter {v.Name} not optional");
                }
            }
else {
                mi = c.Assembly.EntryPoint;
                var args = d.Where(o => o.Key is not ("code" or "fun" or ['_', ..])).Select(o => o.Value).ToArray(); //BAD: no 100% guarantee the order will be preserved in future .NET versions
                a = new object[] { args };
            }
        }

        catch (Exception e1) { return _Bad(e1.Message); }
        
        return mi.Invoke(null, a);
        
        object _Bad(string s) {
            r.Status = HttpStatusCode.BadRequest;
            r.SetContentText(s);
            return null;
        }
    }
}

C# client examples
 
Code:
Copy      Help
string code = """class C { public static int Add(int a, int b=0){return a+b;} }""";
print.it(LaHttp.CallS("Code", $"code={code}", "fun=C.Add", "a=5", "b=3"));
 
Code:
Copy      Help
string code = """using Au; print.it(args); return 1;""";
print.it(LaHttp.CallS("Code", $"code={code}", "a=5", "b=3"));
 
Code:
Copy      Help
var globals = """
global using System;
global using Au;
"""
;
string code = """print.it(args); return 1;""";
print.it(LaHttp.CallS("Code", $"code={code}", "a=5", "b=3", $"_c={globals}"));
 
Code:
Copy      Help
var globals = folders.Workspace + @"files\Classes\global.cs";
string code = """print.it(args); return 1;""";
print.it(LaHttp.CallS("Code", $"code={code}", "a=5", "b=3", $"_c={globals}"));

LaHttp is in some post in this thread.

QM client example

Code:
Copy      Help
str code=
;class C { public static int Add(int a, int b=0){return a+b;} }
out LaHttpCall("Code" F"code={code}" "fun=C.Add" "a=5" "b=4")

LaHttpCall is in some post in this thread.


Messages In This Thread
RE: Call C# functions from anywhere using URL (HTTP server) - by Gintaras - 06-22-2023, 05:40 PM

Forum Jump:


Users browsing this thread: 1 Guest(s)