Как известно, в .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. Активно избавляюсь от руслиша в коде но, его еще достаточно много. Если проект будет интересен, то окончательно вычищу от русского кода.