diff --git a/MTApiService/MTApiService.csproj b/MTApiService/MTApiService.csproj index 64c83838..b99a1f50 100755 --- a/MTApiService/MTApiService.csproj +++ b/MTApiService/MTApiService.csproj @@ -44,14 +44,15 @@ MtApiKey.snk - - ..\packages\log4net.2.0.5\lib\net40-full\log4net.dll - True + + ..\packages\log4net.2.0.12\lib\net40\log4net.dll + + diff --git a/MTApiService/packages.config b/MTApiService/packages.config index d67e57f9..0066004f 100644 --- a/MTApiService/packages.config +++ b/MTApiService/packages.config @@ -1,6 +1,6 @@  - + \ No newline at end of file diff --git a/MetaTraderApi_2017.sln b/MetaTraderApi_2017.sln index d8351e85..4010dd14 100644 --- a/MetaTraderApi_2017.sln +++ b/MetaTraderApi_2017.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 14 -VisualStudioVersion = 14.0.25420.1 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.30413.136 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestApiClientUI", "TestClients\TestApiClientUI\TestApiClientUI.csproj", "{663CC515-EAAE-47D4-8933-5008C2DA1160}" EndProject @@ -47,6 +47,8 @@ Project("{930C7802-8A8C-48F9-8165-68863BCCD9DD}") = "MtApiBootstrapper", "MtApiB {78B94552-DB17-40EC-B7C6-23D32DB85DC1} = {78B94552-DB17-40EC-B7C6-23D32DB85DC1} EndProjectSection EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MtApiServiceNetCore", "MtApiServiceNetCore\MtApiServiceNetCore.csproj", "{7CAFAAE2-0C15-479A-B16D-C2FCE0A48E11}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -232,6 +234,22 @@ Global {8E63046B-56E5-4623-8808-558AD72A8F2B}.Release|x64.ActiveCfg = Release|x86 {8E63046B-56E5-4623-8808-558AD72A8F2B}.Release|x86.ActiveCfg = Release|x86 {8E63046B-56E5-4623-8808-558AD72A8F2B}.Release|x86.Build.0 = Release|x86 + {7CAFAAE2-0C15-479A-B16D-C2FCE0A48E11}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7CAFAAE2-0C15-479A-B16D-C2FCE0A48E11}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7CAFAAE2-0C15-479A-B16D-C2FCE0A48E11}.Debug|Win32.ActiveCfg = Debug|Any CPU + {7CAFAAE2-0C15-479A-B16D-C2FCE0A48E11}.Debug|Win32.Build.0 = Debug|Any CPU + {7CAFAAE2-0C15-479A-B16D-C2FCE0A48E11}.Debug|x64.ActiveCfg = Debug|Any CPU + {7CAFAAE2-0C15-479A-B16D-C2FCE0A48E11}.Debug|x64.Build.0 = Debug|Any CPU + {7CAFAAE2-0C15-479A-B16D-C2FCE0A48E11}.Debug|x86.ActiveCfg = Debug|Any CPU + {7CAFAAE2-0C15-479A-B16D-C2FCE0A48E11}.Debug|x86.Build.0 = Debug|Any CPU + {7CAFAAE2-0C15-479A-B16D-C2FCE0A48E11}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7CAFAAE2-0C15-479A-B16D-C2FCE0A48E11}.Release|Any CPU.Build.0 = Release|Any CPU + {7CAFAAE2-0C15-479A-B16D-C2FCE0A48E11}.Release|Win32.ActiveCfg = Release|Any CPU + {7CAFAAE2-0C15-479A-B16D-C2FCE0A48E11}.Release|Win32.Build.0 = Release|Any CPU + {7CAFAAE2-0C15-479A-B16D-C2FCE0A48E11}.Release|x64.ActiveCfg = Release|Any CPU + {7CAFAAE2-0C15-479A-B16D-C2FCE0A48E11}.Release|x64.Build.0 = Release|Any CPU + {7CAFAAE2-0C15-479A-B16D-C2FCE0A48E11}.Release|x86.ActiveCfg = Release|Any CPU + {7CAFAAE2-0C15-479A-B16D-C2FCE0A48E11}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -241,4 +259,7 @@ Global {38B9C657-BC2F-44F0-8824-54B31F2A64F5} = {B91FF338-E05D-4EF1-948B-A2376DB37ECA} {EB7C228D-9494-4985-845E-B8312450DF3D} = {B91FF338-E05D-4EF1-948B-A2376DB37ECA} EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {23C8878C-16A5-47DF-9A57-73CCF847780D} + EndGlobalSection EndGlobal diff --git a/MtApi5/MtApi5.csproj b/MtApi5/MtApi5.csproj old mode 100755 new mode 100644 index 8596a9c9..786f59c4 --- a/MtApi5/MtApi5.csproj +++ b/MtApi5/MtApi5.csproj @@ -1,123 +1,23 @@ - - + - Debug - AnyCPU - 8.0.30703 - 2.0 - {AC8B5010-DA75-477E-9CA5-547C649E12D8} + net5.0 Library - Properties - MtApi5 - MtApi5 - v4.0 - 512 + false - true - full - false ..\build\products\Debug\ - DEBUG;TRACE - prompt - 4 false - pdbonly - true ..\build\products\Release\ - TRACE - prompt - 4 - - ..\packages\Newtonsoft.Json.12.0.2\lib\net40\Newtonsoft.Json.dll - True - - - - - - - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - - {DE76D5C7-B99C-4467-8408-78173BDD84E0} - MTApiService - + - - - - - \ No newline at end of file diff --git a/MtApi5/packages.config b/MtApi5/packages.config index 66b711be..32637c2e 100755 --- a/MtApi5/packages.config +++ b/MtApi5/packages.config @@ -1,4 +1,4 @@  - + \ No newline at end of file diff --git a/MtApiServiceNetCore/MtApiProxy.cs b/MtApiServiceNetCore/MtApiProxy.cs new file mode 100644 index 00000000..7934fad9 --- /dev/null +++ b/MtApiServiceNetCore/MtApiProxy.cs @@ -0,0 +1,78 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.ServiceModel; +using System.ServiceModel.Channels; + +namespace MTApiService +{ + internal class MtApiProxy : IMtApi, IDisposable + { + private IMtApi InnerChannel; + + public CommunicationState State => ((ICommunicationObject)InnerChannel).State; + + public MtApiProxy(InstanceContext callbackContext, Binding binding, EndpointAddress remoteAddress) + { + var channel = new DuplexChannelFactory(callbackContext, binding, remoteAddress); + channel.Faulted += InnerDuplexChannel_Faulted; + + // configure endpoint programmatically instead via an attribute which will lead to a PlatformNotSupportedException + (channel.Endpoint.EndpointBehaviors.Single(b => b is CallbackBehaviorAttribute) as CallbackBehaviorAttribute).UseSynchronizationContext = false; + + InnerChannel = channel.CreateChannel(); + } + + #region IMtApi Members + + public bool Connect() + { + return InnerChannel.Connect(); + } + + public void Disconnect() + { + InnerChannel.Disconnect(); + } + + public MtResponse SendCommand(MtCommand command) + { + return InnerChannel.SendCommand(command); + } + + public List GetQuotes() + { + return InnerChannel.GetQuotes(); + } + + #endregion + + #region IDisposable Members + + public void Dispose() + { + try + { + Disconnect(); + } + catch (Exception) + { + + } + } + + #endregion + + #region Private Methods + private void InnerDuplexChannel_Faulted(object sender, EventArgs e) + { + Faulted?.Invoke(this, e); + } + + #endregion + + #region Events + public event EventHandler Faulted; + #endregion + } +} diff --git a/MtApiServiceNetCore/MtApiServiceNetCore.csproj b/MtApiServiceNetCore/MtApiServiceNetCore.csproj new file mode 100644 index 00000000..96f29b07 --- /dev/null +++ b/MtApiServiceNetCore/MtApiServiceNetCore.csproj @@ -0,0 +1,39 @@ + + + net5.0 + Library + false + + + ..\build\products\Debug\ + + + ..\build\products\Release\ + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/MtApiServiceNetCore/MtClient.cs b/MtApiServiceNetCore/MtClient.cs new file mode 100644 index 00000000..f5403fec --- /dev/null +++ b/MtApiServiceNetCore/MtClient.cs @@ -0,0 +1,291 @@ +using System; +using System.Collections; +using System.ServiceModel; +using System.Collections.Generic; +using log4net; +using System.Threading.Tasks; + +namespace MTApiService +{ + public sealed class MtClient : IMtApiCallback, IDisposable + { + private const string ServiceName = "MtApiService"; + + public delegate void MtQuoteHandler(MtQuote quote); + public delegate void MtEventHandler(MtEvent e); + + #region Fields + private static readonly ILog Log = LogManager.GetLogger(typeof(MtClient)); + + private readonly MtApiProxy _proxy; + private Task lastQuoteTask; + private Task lastEventTask; + #endregion + + #region ctor + public MtClient(string host, int port) + { + if (string.IsNullOrEmpty(host)) + throw new ArgumentNullException(nameof(host), "host is null or empty"); + + if (port < 0 || port > 65536) + throw new ArgumentOutOfRangeException(nameof(port), "port value is invalid"); + + Host = host; + Port = port; + + var urlService = $"net.tcp://{host}:{port}/{ServiceName}"; + + var bind = new NetTcpBinding(SecurityMode.None) + { + MaxReceivedMessageSize = 2147483647, + MaxBufferSize = 2147483647, + MaxBufferPoolSize = 2147483647, + SendTimeout = new TimeSpan(12, 0, 0), + ReceiveTimeout = new TimeSpan(12, 0, 0), + ReaderQuotas = + { + MaxArrayLength = 2147483647, + MaxBytesPerRead = 2147483647, + MaxDepth = 2147483647, + MaxStringContentLength = 2147483647, + MaxNameTableCharCount = 2147483647 + } + }; + + var quoteScheduler = new TaskFactory(TaskCreationOptions.AttachedToParent, TaskContinuationOptions.AttachedToParent); + var eventScheduler = new TaskFactory(TaskCreationOptions.AttachedToParent, TaskContinuationOptions.AttachedToParent); + lastQuoteTask = quoteScheduler.StartNew(() => { }); + lastEventTask = eventScheduler.StartNew(() => { }); + + _proxy = new MtApiProxy(new InstanceContext(this), bind, new EndpointAddress(urlService)); + _proxy.Faulted += ProxyFaulted; + } + + public MtClient(int port) : this("localhost", port) + { } + + #endregion + + #region Public Methods + /// Thrown when connection failed + public void Connect() + { + Log.Debug("Connect: begin."); + + if (_proxy.State != CommunicationState.Created) + { + Log.ErrorFormat("Connected: end. Client has invalid state {0}", _proxy.State); + return; + } + + bool coonected; + + try + { + coonected = _proxy.Connect(); + } + catch (Exception ex) + { + Log.ErrorFormat("Connect: Exception - {0}", ex.Message); + + throw new CommunicationException($"Connection failed to service. {ex.Message}"); + } + + if (coonected == false) + { + Log.Error("Connect: end. Connection failed."); + throw new CommunicationException("Connection failed"); + } + + Log.Debug("Connect: end."); + } + + public void Disconnect() + { + Log.Debug("Disconnect: begin."); + + try + { + _proxy.Disconnect(); + } + catch (Exception ex) + { + Log.ErrorFormat("Disconnect: Exception - {0}", ex.Message); + } + + Log.Debug("Disconnect: end."); + } + + /// Thrown when connection failed + public MtResponse SendCommand(int commandType, ArrayList parameters, Dictionary namedParams, int expertHandle) + { + Log.DebugFormat("SendCommand: begin. commandType = {0}, parameters count = {1}", commandType, parameters?.Count); + + if (IsConnected == false) + { + Log.Error("SendCommand: Client is not connected."); + throw new CommunicationException("Client is not connected."); + } + + MtResponse result; + + try + { + result = _proxy.SendCommand(new MtCommand { + CommandType = commandType, + Parameters = parameters, + NamedParams = namedParams, + ExpertHandle = expertHandle}); + } + catch (Exception ex) + { + Log.ErrorFormat("SendCommand: Exception - {0}", ex.Message); + + throw new CommunicationException("Service connection failed! " + ex.Message); + } + + Log.DebugFormat("SendCommand: end. result = {0}", result); + + return result; + } + + /// Thrown when connection failed + public List GetQuotes() + { + Log.Debug("GetQuotes: begin."); + + if (IsConnected == false) + { + Log.Warn("GetQuotes: end. Client is not connected."); + return null; + } + + List result; + + try + { + result = _proxy.GetQuotes(); + } + catch (Exception ex) + { + Log.ErrorFormat("GetQuotes: Exception - {0}", ex.Message); + + throw new CommunicationException($"Service connection failed! {ex.Message}"); + } + + Log.DebugFormat("GetQuotes: end. quotes count = {0}", result?.Count); + + return result; + } + + #endregion + + #region IMtApiCallback Members + + public void OnQuoteUpdate(MtQuote quote) + { + Log.DebugFormat("OnQuoteUpdate: begin. quote = {0}", quote); + + if (quote == null) return; + + if (QuoteUpdated != null) + { + lastQuoteTask = lastQuoteTask.ContinueWith((t) => QuoteUpdated.Invoke(quote)); + } + + Log.Debug("OnQuoteUpdate: end."); + } + + public void OnQuoteAdded(MtQuote quote) + { + Log.DebugFormat("OnQuoteAdded: begin. quote = {0}", quote); + + if (QuoteAdded != null) + { + lastQuoteTask = lastQuoteTask.ContinueWith((t) => QuoteAdded.Invoke(quote)); + } + + Log.Debug("OnQuoteAdded: end."); + } + + public void OnQuoteRemoved(MtQuote quote) + { + Log.DebugFormat("OnQuoteRemoved: begin. quote = {0}", quote); + + if (QuoteRemoved != null) + { + lastQuoteTask = lastQuoteTask.ContinueWith((t) => QuoteRemoved.Invoke(quote)); + } + + Log.Debug("OnQuoteRemoved: end."); + } + + public void OnServerStopped() + { + Log.Debug("OnServerStopped: begin."); + + ServerDisconnected?.Invoke(this, EventArgs.Empty); + + Log.Debug("OnServerStopped: end."); + } + + + public void OnMtEvent(MtEvent e) + { + Log.DebugFormat("OnMtEvent: begin. event = {0}", e); + + if (MtEventReceived != null) + { + lastEventTask = lastEventTask.ContinueWith((t) => MtEventReceived.Invoke(e)); + } + + Log.Debug("OnMtEvent: end."); + } + + #endregion + + #region Properties + public string Host { get; private set; } + public int Port { get; private set; } + + private bool IsConnected => _proxy.State == CommunicationState.Opened; + + #endregion + + #region Private Methods + + private void ProxyFaulted(object sender, EventArgs e) + { + Log.Debug("ProxyFaulted: begin."); + + ServerFailed?.Invoke(this, EventArgs.Empty); + + Log.Debug("ProxyFaulted: end."); + } + + #endregion + + #region IDisposable Members + + public void Dispose() + { + Log.Debug("Dispose: begin."); + + _proxy.Dispose(); + + Log.Debug("Dispose: end."); + } + + #endregion + + #region Events + public event MtQuoteHandler QuoteAdded; + public event MtQuoteHandler QuoteRemoved; + public event MtQuoteHandler QuoteUpdated; + public event EventHandler ServerDisconnected; + public event EventHandler ServerFailed; + public event MtEventHandler MtEventReceived; + #endregion + } +} diff --git a/MtApiServiceNetCore/MtService.cs b/MtApiServiceNetCore/MtService.cs new file mode 100644 index 00000000..506fe9d4 --- /dev/null +++ b/MtApiServiceNetCore/MtService.cs @@ -0,0 +1,43 @@ +using System; +using System.Collections.Generic; +using System.ServiceModel; +using System.Threading; +using log4net; + +namespace MTApiService +{ + [ServiceContract(CallbackContract = typeof(IMtApiCallback), SessionMode = SessionMode.Required)] + public interface IMtApi + { + [OperationContract] + bool Connect(); + + [OperationContract(IsOneWay = true)] + void Disconnect(); + + [OperationContract] + MtResponse SendCommand(MtCommand command); + + [OperationContract] + List GetQuotes(); + } + + [ServiceContract] + public interface IMtApiCallback + { + [OperationContract(IsOneWay = true)] + void OnQuoteUpdate(MtQuote quote); + + [OperationContract(IsOneWay = true)] + void OnServerStopped(); + + [OperationContract(IsOneWay = true)] + void OnQuoteAdded(MtQuote quote); + + [OperationContract(IsOneWay = true)] + void OnQuoteRemoved(MtQuote quote); + + [OperationContract(IsOneWay = true)] + void OnMtEvent(MtEvent ntEvent); + } +} diff --git a/MtApiServiceNetCore/Properties/AssemblyInfo.cs b/MtApiServiceNetCore/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..3dc1eafc --- /dev/null +++ b/MtApiServiceNetCore/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("MTApiService")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("DW")] +[assembly: AssemblyProduct("MTApiService")] +[assembly: AssemblyCopyright("Copyright © DW 2011")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("f1cc1516-9352-4ddd-811a-c5fc842b12d4")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.32.0")] +[assembly: AssemblyFileVersion("1.0.32.0")] \ No newline at end of file diff --git a/TestClients/MtApi5TestClient/MtApi5TestClient.csproj b/TestClients/MtApi5TestClient/MtApi5TestClient.csproj index 377c8dfc..05a14f75 100644 --- a/TestClients/MtApi5TestClient/MtApi5TestClient.csproj +++ b/TestClients/MtApi5TestClient/MtApi5TestClient.csproj @@ -1,154 +1,62 @@ - - + - Debug + net5.0-windows x86 - 8.0.30703 - 2.0 - {38B9C657-BC2F-44F0-8824-54B31F2A64F5} WinExe - Properties - MtApi5TestClient - MtApi5TestClient - v4.5.2 - - - 512 - {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - 4 + false + true + false - x86 - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - false - - - x86 - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - false + x64 - true - bin\Debug\ - DEBUG;TRACE - full - AnyCPU bin\Debug\MtApi5TestClient.exe.CodeAnalysisLog.xml true GlobalSuppressions.cs - prompt - MinimumRecommendedRules.ruleset + ManagedMinimumRules.ruleset ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets true ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules true - false - bin\Release\ - TRACE - true - pdbonly - AnyCPU bin\Release\MtApi5TestClient.exe.CodeAnalysisLog.xml true GlobalSuppressions.cs - prompt MinimumRecommendedRules.ruleset ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets true ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules true false - false - - - - - - - - - 4.0 - - - - - - - - MSBuild:Compile - Designer - - - MSBuild:Compile - Designer - - - App.xaml - Code - - - - - MainWindow.xaml - Code - - - - - - - - - Code - - + True True Resources.resx - + True Settings.settings True - + ResXFileCodeGenerator Resources.Designer.cs - - + SettingsSingleFileGenerator Settings.Designer.cs - - {AC8B5010-DA75-477E-9CA5-547C649E12D8} - MtApi5 - + + + + + - - \ No newline at end of file diff --git a/TestClients/MtApi5TestClient/Properties/Resources.Designer.cs b/TestClients/MtApi5TestClient/Properties/Resources.Designer.cs index cd34a050..047362d8 100644 --- a/TestClients/MtApi5TestClient/Properties/Resources.Designer.cs +++ b/TestClients/MtApi5TestClient/Properties/Resources.Designer.cs @@ -19,7 +19,7 @@ namespace MtApi5TestClient.Properties { // class via a tool like ResGen or Visual Studio. // To add or remove a member, edit your .ResX file then rerun ResGen // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] internal class Resources { diff --git a/TestClients/MtApi5TestClient/Properties/Settings.Designer.cs b/TestClients/MtApi5TestClient/Properties/Settings.Designer.cs index 9580053d..f0986a83 100644 --- a/TestClients/MtApi5TestClient/Properties/Settings.Designer.cs +++ b/TestClients/MtApi5TestClient/Properties/Settings.Designer.cs @@ -12,7 +12,7 @@ namespace MtApi5TestClient.Properties { [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "14.0.0.0")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "16.5.0.0")] internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); diff --git a/TestClients/MtApi5TestClient/app.config b/TestClients/MtApi5TestClient/app.config index 9c72f143..05d8b241 100755 --- a/TestClients/MtApi5TestClient/app.config +++ b/TestClients/MtApi5TestClient/app.config @@ -1,3 +1,3 @@ - +