Как известно, в .Net Core, на данный момент, нет AppDomain, а WCF только SOAP клиент .Net Core, WCF и ODATA клиенты.
Конечно, задачу можно решить и через Web Api с WebSockets для вызова событий. Но, я просто предлагаю альтернативное решение на маршалинге по TCP/IP и создание объектов, и вызов методов на стороне сервера с помощью Reflection.
Вот как выглядит удаленный вызов методов и свойств. Пример взят отсюда Основы перегрузки операторов:
Непонятны только методы wrap.GetType и MyArr._new и _Console не родной. Все остальное один в один работа с объектами на C#.
На самом деле, Point1 и Point2 и Point3 это наследники DynamicObject с переопределенными методами TryXXX, а внутри них происходит упаковка типа метода, имя метода и параметров в Stream и передача его на Сервер по протоколу Tcp/IP, где он распаковывается и вызывается метод, который ищется по типу, названию метода и параметрам. После получения результата, те же процедуры но, только с сервера на клиента.
Само решение очень близко с COM out process взаимодействием на IDispatch. Помню с удовольствием разбирался с внутренностями TSocketConnection.
Но, в отличие от Idispatch, используется перегрузка методов и операторов, вызов Generic методов с выводом типов или с заданием Generic аргументов. Поддержка методов расширений для классов, находящихся в одной сборке и для Linq методов.
Также поддержка асинхронных методов и подписка на события, ref и out параметры, доступ по индексу [], поддержка итераторов в foreach.
В отличии от Web Api, не нужно писать специально серверный код Controller, Hub ы.
Это близко к AppDomain c Remouting но, в отличие от Remoting, каждый класс является аналогом MarshalByRefObject. То есть, мы можем создать любой объект на стороне сервера и вернуть ссылку на него (некоторые языки из чисел поддерживают только double).
При вызове методов, напрямую сериализуются параметры только следующих типов: числа, строки, дата, Guid и byte[]. Для остальных типов нужно их создать на стороне сервера, а в параметрах методов уже передаются ссылки на них.
Так примеры можно посмотреть на TypeScript, который по синтаксису близок к C#
CEF, ES6, Angular 2, TypeScript использование классов .Net Core. Создание кроссплатформенного GUI для .Net с помощью CEF
CEF, Angular 2 использование событий классов .Net Core
Вызов метода на стороне сервера можно посмотреть здесь Кроссплатформенное использование классов .Net из неуправляемого кода. Или аналог IDispatch на Linux.
В этой статье я сосредоточусь на особенностях использования DinamicObject, маршалинга для вызова объектных и статических методов удаленных объектов.
Первое, с чего начнем — это с загрузки нужной сборки и получения типа. В первом примере мы получали тип по полному имени типа, по имени типа и имени сборки.
Теперь, имея ссылку на тип, можно создать объект, вызвав мeтод _new или вызвать метод врапера New.
или
Можно конструировать Generic типы:
Во wrap.New и wrap.GetGenericType можно передавать ссылки на типы или их строковое представление. Для строковых главное, что бы сборки были загружены.
Следующий вариант — это скопировать объект на сервер. Это важно потому, что скорость обмена по Tcp/IP составляет порядка 15 000 вызовов в секунду, при постоянном соединениии и всего 2000 при соединении на каждый запрос TCP/IP скорость обмена.
теперь dict это ссылка на словарь на стороне сервера, и можем передавать в параметрах.
Мы можем использовать индексы для доступа и установки значения
Можем использовать итератор
Теперь я обращу ваше внимание на отличие синтаксиса. Прежде всего, это вызов Generic методов с заданием Generic аргуметов, ref и out параметров, асинхронный вызов.
Класс RefParam нужен для записи изменённого параметра в поле Value.
Для вызова асинхронного метода:
Нужно перед именем асинхронного метода добавить слово async
Если у вас есть Task, то можно дождаться выполнения, вызвав:
Еще одно отличие от реального кода заключается в том, что мы не можем напрямую использовать перегрузку ==
Вместо него мы должны явно вызвать
или, если есть перегрузка, оператора ==
Есть поддержка объектов, поддерживающих System.Dynamic.IDynamicMetaObjectProvider. Это ExpandoObject, DinamicObject, JObject итд.
Возьмем для тестов следующий объект:
Теперь можно его использовать:
Как видно из примера, поддерживаются не только методы и свойства, но и делегаты. Часто нужно приводить объекты к интерфейсам. Для этого есть ключевое слово _as.
Теперь перейдем к полуавтоматической сериализации.
Внутри connector.CoryTo происходит Json сериализация.
Необходимо, что бы сборка сериализуемого типа была загружена на сервере. Пояснение чуть ниже.
Также на клиенте может не быть сборки с сериализуемым типом. Поэтому для сериализации мы можем использовать JObject
Анонимных типов.
JsonObject
Мы можем указать тип, ввиде строки или ссылки на тип и объект, который нужно сериализовать.
И в итоге, отослать на сервер:
Следует отметить, что для десериализации на строке сервера, сборка с типом должна быть загружена на стороне сервера.
Также сборки, не находящиеся в каталоге Core CLR или не являющиеся NuGet пакетами, нужно вручную загружать:
Для того, что бы скопировать серверный объект на клиента, нужно использовать следующий метод:
Можно использовать JObject, если такого типа нет на клиенте, используя:
Ну и под конец. перейдем к подключению к серверу.
Внутри LoadAndConnectToLocalServer мы запускаем процесс dotnet.exe с адресом файла Server.dll:
Теперь мы можем получить proxy.
И с помощью него получать типы, вызывать статические методы, создавать объекты, вызывать методы объектов и тд.
По окончании работы с сервером, нужно отключиться от него и, если мы запустили процесс, то выгрузить его.
Что касается событий, то можно посмотреть статью CEF, Angular 2 использование событий классов .Net Core.
Там описан процесс работы с событиями .Net объектов. Единственное, что код модуля для клиента можно получить:
Обращу внимание, что при подписке на событие с двумя и больше параметрами. создается
анонимный класс с полями соответствующими именами и типам параметров. Так для события:
Будет создана обертка:
CodeModule будет содержать следующий код:
Про использование динамической компиляции можно почитать здесь .Net Core, 1C, динамическая компиляция, Scripting API.
Что касается безопасности Analog System.Security.Permissions in .NET Core, то советуют запускать процесс под определенным аккаунтом пользователя с определенными правами.
Следует выразить сожаление, что в C# для динамиков нет псевдоинтерфейсов, аналога аннотации типа в TypeScript d.ts, для статической проверки кода и IntelliSense.
Но можно писать обычный код, переделывая его на удаленный. с минимальными телодвижениями.
Исходники лежат здесь RPCProjects.
Перед запуском примеров скомпилируйте проекты и скопируйте из папки TestDll\bin\Release\netcoreapp1.1\ библиотеку TestDll.dll в каталоги Server\bin\Release\netcoreapp1.1\ и Client\bin\Release\netcoreapp1.1\.
Если статья вызовет интерес, то в следующей статье распишу механизмы обмена и вызова методов на сервере.
P.S. Активно избавляюсь от руслиша в коде но, его еще достаточно много. Если проект будет интересен, то окончательно вычищу от русского кода.
Конечно, задачу можно решить и через Web Api с WebSockets для вызова событий. Но, я просто предлагаю альтернативное решение на маршалинге по TCP/IP и создание объектов, и вызов методов на стороне сервера с помощью Reflection.
Вот как выглядит удаленный вызов методов и свойств. Пример взят отсюда Основы перегрузки операторов:
// Выведем сообщение в консоли сервера string typeStr = typeof(Console).AssemblyQualifiedName; var _Console = wrap.GetType(typeStr);// Получим тип на сервере по имени // "Hello from Client" будет выведено в консоле сервера _Console.WriteLine("Hello from Client"); // получим тип по имени класса TestDllForCoreClr.MyArr // Из сборки TestDll.dll var MyArr = wrap.GetType("TestDllForCoreClr.MyArr", "TestDll"); // Создадим объекты на стороне сервера // и получим ссылки на них var Point1 = MyArr._new(1, 12, -4); // new MyArr(1, 12, -4); var Point2 = MyArr._new(0, -3, 18); // new MyArr(0, -3, 18); // Все операции с объектами PointX происходят на стороне сервера Console.WriteLine("Координаты первой точки: "+Point1.x+" "+Point1.y+" "+Point1.z); Console.WriteLine("Координаты второй точки: "+Point2.x+" "+Point2.y + " "+ Point2.z); var Point3 = Point1 + Point2; Console.WriteLine("\nPoint1 + Point2 = " + Point3.x + " " + Point3.y + " " + Point3.z); Point3 = Point1 - Point2; Console.WriteLine("Point1 - Point2 = "+ Point3.x + " " + Point3.y + " " + Point3.z); Point3 = -Point1; Console.WriteLine("-Point1 = " + Point3.x + " " + Point3.y + " " + Point3.z); Point2++; Console.WriteLine("Point2++ = "+ Point2.x + " " + Point2.y + " " + Point2.z); Point2--; Console.WriteLine("Point2-- = " + Point2.x + " " + Point2.y + " " + Point2.z);
Непонятны только методы wrap.GetType и MyArr._new и _Console не родной. Все остальное один в один работа с объектами на C#.
На самом деле, Point1 и Point2 и Point3 это наследники DynamicObject с переопределенными методами TryXXX, а внутри них происходит упаковка типа метода, имя метода и параметров в Stream и передача его на Сервер по протоколу Tcp/IP, где он распаковывается и вызывается метод, который ищется по типу, названию метода и параметрам. После получения результата, те же процедуры но, только с сервера на клиента.
Само решение очень близко с COM out process взаимодействием на IDispatch. Помню с удовольствием разбирался с внутренностями TSocketConnection.
Но, в отличие от Idispatch, используется перегрузка методов и операторов, вызов Generic методов с выводом типов или с заданием Generic аргументов. Поддержка методов расширений для классов, находящихся в одной сборке и для Linq методов.
Также поддержка асинхронных методов и подписка на события, ref и out параметры, доступ по индексу [], поддержка итераторов в foreach.
В отличии от Web Api, не нужно писать специально серверный код Controller, Hub ы.
Это близко к AppDomain c Remouting но, в отличие от Remoting, каждый класс является аналогом MarshalByRefObject. То есть, мы можем создать любой объект на стороне сервера и вернуть ссылку на него (некоторые языки из чисел поддерживают только double).
При вызове методов, напрямую сериализуются параметры только следующих типов: числа, строки, дата, Guid и byte[]. Для остальных типов нужно их создать на стороне сервера, а в параметрах методов уже передаются ссылки на них.
Так примеры можно посмотреть на TypeScript, который по синтаксису близок к C#
CEF, ES6, Angular 2, TypeScript использование классов .Net Core. Создание кроссплатформенного GUI для .Net с помощью CEF
CEF, Angular 2 использование событий классов .Net Core
Вызов метода на стороне сервера можно посмотреть здесь Кроссплатформенное использование классов .Net из неуправляемого кода. Или аналог IDispatch на Linux.
В этой статье я сосредоточусь на особенностях использования DinamicObject, маршалинга для вызова объектных и статических методов удаленных объектов.
Первое, с чего начнем — это с загрузки нужной сборки и получения типа. В первом примере мы получали тип по полному имени типа, по имени типа и имени сборки.
// Получим ссылку на сборку //вызывается метод на сервере //public static Assembly GetAssembly(string FileName, bool IsGlabalAssembly = false) //Если IsGlabalAssembly == true? то ищется сборка в каталоге typeof(string).GetTypeInfo().Assembly.Location //Иначе в каталоге приложения Server var assembly = wrap.GetAssembly("TestDll"); // Получим из неё нужный тип var @TestClass = assembly.GetType("TestDllForCoreClr.TestClass"); // Можно получить тип , зная имя класса и имя сборки. Удобно, когда нужен только один тип //Метод на сервере //public static Type GetType(string type, string FileName = "", bool IsGlabalAssembly = false) //var @TestClass = wrap.GetType("TestDllForCoreClr.TestClass", "TestDll");
Теперь, имея ссылку на тип, можно создать объект, вызвав мeтод _new или вызвать метод врапера New.
var TO = @TestClass._new("Property from Constructor");
или
wrap.New(@TestClass,"Property from Constructor");
Можно конструировать Generic типы:
var Dictionary2 = wrap.GetType("System.Collections.Generic.Dictionary`2", "System.Collections"); var DictionaryIntString = wrap.GetGenericType(Dictionary2, "System.Int32", "System.String"); var dict = wrap.New(DictionaryIS);
Во wrap.New и wrap.GetGenericType можно передавать ссылки на типы или их строковое представление. Для строковых главное, что бы сборки были загружены.
Следующий вариант — это скопировать объект на сервер. Это важно потому, что скорость обмена по Tcp/IP составляет порядка 15 000 вызовов в секунду, при постоянном соединениии и всего 2000 при соединении на каждый запрос TCP/IP скорость обмена.
var ClientDict = new Dictionary<int, string>() { [1] = "Один", [2] = "Два", [3] = "Три" }; // Скопируем объект с помощью Json сериализации . //Более подробно чуть ниже. var dict = connector.CoryTo(ClientDict);
теперь dict это ссылка на словарь на стороне сервера, и можем передавать в параметрах.
// Вызовем дженерик метод с автовыводом типа //public V GenericMethod<K, V>(Dictionary<K, V> param1, K param2, V param3) resGM = TO.GenericMethod(dict, 99, "Hello"); Console.WriteLine("Вызов дженерик метода с выводом типа " + resGM);
Мы можем использовать индексы для доступа и установки значения
Console.WriteLine("dict[2] " + dict[2]); dict[2] = "Два"; Console.WriteLine("dict[2] " + dict[2]);
Можем использовать итератор
foreach (string value in dict.Values) Console.WriteLine("Dict Values " + value);
Теперь я обращу ваше внимание на отличие синтаксиса. Прежде всего, это вызов Generic методов с заданием Generic аргуметов, ref и out параметров, асинхронный вызов.
// Будем вызывать следующий метод // public V GenericMethodWithRefParam<К,V >(К param, V param2, ref string param3) // Не получилось у меня использовать ref параметр. Ошибка, платформа не поддерживает. // Создадим объект класса RefParam, у которого есть поле Value куда и будет записываться результат var OutParam = new ClientRPC.RefParam("TroLoLo"); resGM = TO.GenericMethodWithRefParam(5, "GenericMethodWithRefParam", OutParam); Console.WriteLine($@"Вызов дженерик метода с автовыводом типов Ref {resGM} {OutParam.Value}"); // Массив параметров для получения нужного метода var GenericArgs = new object[] { "System.String", "System.String" }; // Массив может быть из строк и ссылок на типы например: // var @Int32 = wrap.GetType("System.Int32"); //var GenericArgs = new object[] {@Int32, "System.String" }; // Первым параметром для вызова дженерик метода без вывода типа по параметрам // должен быть массив представления типов // Это аналог вызова // resGM = TO.GenericMethodWithRefParam<String,String>(null, "GenericMethodWithRefParam", ref OutParam) resGM = TO.GenericMethodWithRefParam(GenericArgs, null, "GenericMethodWithRefParam", OutParam); Console.WriteLine($@"Вызов дженерик метода с дженерик аргументами Ref {resGM} {OutParam.Value}"); // Test return null resGM = TO.GenericMethodWithRefParam(GenericArgs, null, null, OutParam); Console.WriteLine($@"Вызов дженерик метода с дженерик аргументами Ref {resGM} {OutParam}");
Класс RefParam нужен для записи изменённого параметра в поле Value.
public class RefParam { public dynamic Value; public RefParam(object Value) { this.Value = Value; } public RefParam() { this.Value = null; } public override string ToString() { return Value?.ToString(); } }
Для вызова асинхронного метода:
// public async Task<V> GenericMethodAsync<K, V>(K param, string param4 = "Test") var GenericArgs = new object[] { "System.Int32", "System.String" }; object resTask = await TO.async.GenericMethodAsync(GenericArgs , 44);
Нужно перед именем асинхронного метода добавить слово async
Если у вас есть Task, то можно дождаться выполнения, вызвав:
int res =await wrap.async.ReturnParam(task);
Еще одно отличие от реального кода заключается в том, что мы не можем напрямую использовать перегрузку ==
if (myObject1 == myObject2) Console.WriteLine("Объекты равны перегрузка оператора ==");
Вместо него мы должны явно вызвать
if (myObject1.Equals(myObject2)) Console.WriteLine("Объекты равны Equals");
или, если есть перегрузка, оператора ==
if (MyArr.op_Equality(myObject1,myObject2)) Console.WriteLine("Объекты равны op_Equality");
Есть поддержка объектов, поддерживающих System.Dynamic.IDynamicMetaObjectProvider. Это ExpandoObject, DinamicObject, JObject итд.
Возьмем для тестов следующий объект:
public object GetExpandoObject() { dynamic res = new ExpandoObject(); res.Name = "Test ExpandoObject"; res.Number = 456; res.toString = (Func<string>)(() => res.Name); res.Sum = (Func<int, int, int>)((x, y) => x + y); return res; }
Теперь можно его использовать:
var EO = TO.GetExpandoObject(); Console.WriteLine("Свойство ExpandoObject Имя " + EO.Name); Console.WriteLine("Свойство ExpandoObject Число " + EO.Number); // Получим делегат var Delegate = EO.toString; Console.WriteLine("Вызов делегата toString " + Delegate()); // Вызовем как делегат // Для ExpandoObject можно вызвать как метод Console.WriteLine("Вызов Метода toString " + EO.toString()); var DelegateSum = EO.Sum; Console.WriteLine("Вызов делегата Sum " + DelegateSum(3,4)); // Вызовем как делегат // Для ExpandoObject можно вызвать как метод Console.WriteLine("Вызов Метода Sum " + EO.Sum(3,4)); // Для ExpandoObject }
Как видно из примера, поддерживаются не только методы и свойства, но и делегаты. Часто нужно приводить объекты к интерфейсам. Для этого есть ключевое слово _as.
string[] sa = new string[] { "Нулевой", "Первый", "Второй", "Третий", "Четвертый" }; // Скопируем массив на сервер var ServerSa = Connector.CoryTo(sa); // Получим интерфейс IEnumerable по имени var en = ServerSa._as("IEnumerable"); var Enumerator = en.GetEnumerator(); while(Enumerator.MoveNext()) Console.WriteLine(Enumerator.Current); // Получим ссылки на типы var @IEnumerable = wrap.GetType("System.Collections.IEnumerable"); var @IEnumerator = wrap.GetType("System.Collections.IEnumerator"); // Для приведения к типу, используем ссылки на типы en = ServerSa._as(@IEnumerable); Enumerator = en.GetEnumerator(); // На всякий случай приведем к Интерфейсу IEnumerator Enumerator = Enumerator._as(@IEnumerator); while (Enumerator.MoveNext()) Console.WriteLine(Enumerator.Current);
Теперь перейдем к полуавтоматической сериализации.
var dict = connector.CoryTo(ClientDict);
Внутри connector.CoryTo происходит Json сериализация.
public dynamic CoryTo(object obj) { // Получим строковое представление типа // Нужен для десериализации на сервере string type = obj.GetType().AssemblyQualifiedName; var str = JsonConvert.SerializeObject(obj); return CoryTo(type, str); }
Необходимо, что бы сборка сериализуемого типа была загружена на сервере. Пояснение чуть ниже.
Также на клиенте может не быть сборки с сериализуемым типом. Поэтому для сериализации мы можем использовать JObject
Анонимных типов.
JsonObject
Мы можем указать тип, ввиде строки или ссылки на тип и объект, который нужно сериализовать.
public dynamic CoryTo(object type, object obj) { var str = JsonConvert.SerializeObject(obj); return CoryTo(type, str); }
И в итоге, отослать на сервер:
// type может быть ссылкой на Type AutoWrapClient на стороне сервера // Или строковым представлением типа public dynamic CoryTo(object type, string objToStr) { object result; var res = AutoWrapClient.TryInvokeMember(0, "JsonToObject", new object[] { type, objToStr }, out result, this); if (!res) throw new Exception(LastError); return result; }
Следует отметить, что для десериализации на строке сервера, сборка с типом должна быть загружена на стороне сервера.
static void TestSerializeObject(ClientRPC.TCPClientConnector connector) { // Создадим объект на стороне клиента var obj = new TestDllForCoreClr.TestClass("Объект на стороне Клиента"); dynamic test = null; try { // Скопируем объект на сервер test = connector.CoryTo(obj); } // Сборка не загружена //Поэтому явно загрузим сборку на сервере и повторим операцию CoryTo catch (Exception) { Console.WriteLine("Ошибка " + connector.LastError); var assembly = wrap.GetAssembly("TestDll"); test = connector.CoryTo(obj); } Console.WriteLine(test.ObjectProperty); }
Также сборки, не находящиеся в каталоге Core CLR или не являющиеся NuGet пакетами, нужно вручную загружать:
Код загрузки сборки
static Assembly LoadAssembly(string fileName) { var Dir = AppContext.BaseDirectory; string path = Path.Combine(Dir, fileName); Assembly assembly = null; if (File.Exists(path)) { try { var asm = System.Runtime.Loader.AssemblyLoadContext.GetAssemblyName(path); assembly = Assembly.Load(asm); } catch (Exception) { assembly = System.Runtime.Loader.AssemblyLoadContext.Default.LoadFromAssemblyPath(path); } } else throw new Exception("Не найдена сборка " + path); return assembly; }
Для того, что бы скопировать серверный объект на клиента, нужно использовать следующий метод:
var objFromServ = connector.CoryFrom<Dictionary<int, string>>(dict); Console.WriteLine("dict[2] " + objFromServ[2]);
Можно использовать JObject, если такого типа нет на клиенте, используя:
connector.CoryFrom<dynamic>(
Ну и под конец. перейдем к подключению к серверу.
if (LoadLocalServer) { // Запустим процесс dotnet.exe c Server.dll,передав известный путь. connector = ClientRPC.TCPClientConnector.LoadAndConnectToLocalServer(GetParentDir(dir, 4) + $@"\Server\Server\bin\Release\netcoreapp1.1\Server.dll"); } else { // Подключимся к запущенному серверу по известному порту и адресу //третий параметр отвечает за признак постоянного соединения с сервером //Используется пул из 5 соединений connector = new ClientRPC.TCPClientConnector("127.0.0.1", port, false); // Запустим Tcp/IP сервер на стороне клиента для асинхронных методов и получения событий. port = ClientRPC.TCPClientConnector.GetAvailablePort(6892); connector.Open(port, 2); }
Внутри LoadAndConnectToLocalServer мы запускаем процесс dotnet.exe с адресом файла Server.dll:
Код загрузки процесса сервера
public static TCPClientConnector LoadAndConnectToLocalServer(string FileName) { int port = 1025; port = GetAvailablePort(port); ProcessStartInfo startInfo = new ProcessStartInfo("dotnet.exe"); startInfo.Arguments = @""""+ FileName+ $@""" { port}"; Console.WriteLine(startInfo.Arguments); var server = Process.Start(startInfo); Console.WriteLine(server.Id); var connector = new TCPClientConnector("127.0.0.1", port); port++; port = GetAvailablePort(port); connector.Open(port, 2); return connector; }
Теперь мы можем получить proxy.
wrap = ClientRPC.AutoWrapClient.GetProxy(connector);
И с помощью него получать типы, вызывать статические методы, создавать объекты, вызывать методы объектов и тд.
По окончании работы с сервером, нужно отключиться от него и, если мы запустили процесс, то выгрузить его.
// Вызовем финализаторы всех AutoWrapClient ссылок на серверные объекты GC.Collect(); GC.WaitForPendingFinalizers(); Console.WriteLine("Press any key"); Console.ReadKey(); // Удаления из хранилища на стороне сервера происходит пачками, по 50 элементов // Отправим оставшиеся connector.ClearDeletedObject(); // Отключимся от сервера, закроем все соединения, Tcp/Ip сервер на клиенте connector.Close(); // Если мы запустили процесс сервера, // То выгрузим его if (LoadLocalServer) connector.CloseServer(); Console.WriteLine("Press any key"); Console.ReadKey();
Что касается событий, то можно посмотреть статью CEF, Angular 2 использование событий классов .Net Core.
Там описан процесс работы с событиями .Net объектов. Единственное, что код модуля для клиента можно получить:
var @DescribeEventMethods = wrap.GetType("NetObjectToNative.DescribeEventMethods", "Server"); string CodeModule = @DescribeEventMethods.GetCodeModuleForEvents(@EventTest);
Обращу внимание, что при подписке на событие с двумя и больше параметрами. создается
анонимный класс с полями соответствующими именами и типам параметров. Так для события:
public event Action<string, int> EventWithTwoParameter;
Будет создана обертка:
Target.EventWithTwoParameter += (arg1,arg2) => { if (EventWithTwoParameter!=null) { var EventWithTwoParameterObject = new {arg1=arg1,arg2=arg2}; EventWithTwoParameter(EventWithTwoParameterObject); } };
CodeModule будет содержать следующий код:
// параметр value:Анонимный Тип // Свойства параметра // arg1:System.String // arg2:System.Int32 static public void EventWithTwoParameter(dynamic value) { Console.WriteLine("EventWithTwoParameter " + wrap.toString(value)); // Можно обратиться к параметрам. Console.WriteLine($"EventWithTwoParameter arg1:{value.arg1} arg2:{value.arg2}"); value(ClientRPC.AutoWrapClient.FlagDeleteObject); }
Про использование динамической компиляции можно почитать здесь .Net Core, 1C, динамическая компиляция, Scripting API.
Что касается безопасности Analog System.Security.Permissions in .NET Core, то советуют запускать процесс под определенным аккаунтом пользователя с определенными правами.
Следует выразить сожаление, что в C# для динамиков нет псевдоинтерфейсов, аналога аннотации типа в TypeScript d.ts, для статической проверки кода и IntelliSense.
Но можно писать обычный код, переделывая его на удаленный. с минимальными телодвижениями.
Исходники лежат здесь RPCProjects.
Перед запуском примеров скомпилируйте проекты и скопируйте из папки TestDll\bin\Release\netcoreapp1.1\ библиотеку TestDll.dll в каталоги Server\bin\Release\netcoreapp1.1\ и Client\bin\Release\netcoreapp1.1\.
Если статья вызовет интерес, то в следующей статье распишу механизмы обмена и вызова методов на сервере.
P.S. Активно избавляюсь от руслиша в коде но, его еще достаточно много. Если проект будет интересен, то окончательно вычищу от русского кода.
