📜 ⬆️ ⬇️

Example of creating a WCF service running inside a Windows service

Windows Communication Foundation is a software platform from Microsoft for creating, configuring and deploying distributed network services. WCF-runtime and its System.ServiceModel namespace, representing its main programming interface, is the successor to the distributed systems technology successfully used by developers to create distributed applications on the Windows platform over the past decade. Let us analyze the test example of creating a WCF service.

Open Visual Studio 2015 and create a new project of type Class Library. The project is called WCFMyServiceLibrary.


')

Rename the file Class1.cs to MyService.cs and add another class, the file for which we will call IMyService.cs.

Add a link to the assembly System.ServiceModel.

In the IMyService.cs file, we describe the interface:
using System.ServiceModel; namespace WCFMyServiceLibrary { [ServiceContract] public interface IMyService { [OperationContract] string Method1(string x); [OperationContract] string Method2(string x); } } 


In the file MyService.cs, we describe the interface implementation:
 namespace WCFMyServiceLibrary { public class MyService : IMyService { public string Method1(string x) { string s = $"1 You entered: {x} = = = 1"; return s; } public string Method2(string x) { string s = $"2 you entered: {x} = = = 2"; return s; } } } 

This development of the service is completed. We proceed to the creation of a Windows service, which will be a container for this service.

In the same solution (Solution) we will create a new project like “Windows Service”. We call the project WindowsServiceHostForMyService.



Then the file Service1.cs (the newly created project) will be renamed into MyService.cs. In this project we add a link to the System.ServiceModel assembly, and also do not forget to specify the directives in the MyService.cs file:

 using System.ServiceModel; using System.ServiceModel.Description; 

In the MyService class, add a new member:

 private ServiceHost service_host = null; 

You also need to add a link to the WCFMyServiceLibrary project, which is in the same solution:



Then, in the MyService class, we change the OnStart method so that the endpoints of our service are added to this method (endpoint):

OnStart method
  protected override void OnStart(string[] args) { if (service_host != null) service_host.Close(); string address_HTTP = "http://localhost:9001/MyService"; string address_TCP = "net.tcp://localhost:9002/MyService"; Uri[] address_base = { new Uri(address_HTTP), new Uri(address_TCP) }; service_host = new ServiceHost(typeof(WCFMyServiceLibrary.MyService), address_base); ServiceMetadataBehavior behavior = new ServiceMetadataBehavior(); service_host.Description.Behaviors.Add(behavior); BasicHttpBinding binding_http = new BasicHttpBinding(); service_host.AddServiceEndpoint(typeof(WCFMyServiceLibrary.IMyService), binding_http, address_HTTP); service_host.AddServiceEndpoint(typeof(IMetadataExchange), MetadataExchangeBindings.CreateMexHttpBinding(), "mex"); NetTcpBinding binding_tcp = new NetTcpBinding(); binding_tcp.Security.Mode = SecurityMode.Transport; binding_tcp.Security.Transport.ClientCredentialType = TcpClientCredentialType.Windows; binding_tcp.Security.Message.ClientCredentialType = MessageCredentialType.Windows; binding_tcp.Security.Transport.ProtectionLevel = System.Net.Security.ProtectionLevel.EncryptAndSign; service_host.AddServiceEndpoint(typeof(WCFMyServiceLibrary.IMyService), binding_tcp, address_TCP); service_host.AddServiceEndpoint(typeof(IMetadataExchange), MetadataExchangeBindings.CreateMexTcpBinding(), "mex"); service_host.Open(); } 

Then we implement stopping the service in the OnStop method:

  protected override void OnStop() { if (service_host != null) { service_host.Close(); service_host = null; } } 

Then, in the Solution Explorer, double-clicking on the MyService.cs file (of the WindowsServiceHostForMyService project) will open this file in Design Mode.



On the empty space, call the context menu (right click of the mouse button) and select "Add installer".



This will create a new class ProjectInstaller.cs
Rename the file ProjectInstaller.cs to MyServiceInstaller.cs.
At the same time, a window will appear asking whether to rename the dependent objects - answer "Yes".

Add a link to the file

 using System.ServiceProcess; 

Then we will change the code of the constructor of the MyServiceInstaller class:

  public MyServiceInstaller() { // InitializeComponent(); serviceProcessInstaller1 = new ServiceProcessInstaller(); serviceProcessInstaller1.Account = ServiceAccount.LocalSystem; serviceInstaller1 = new ServiceInstaller(); serviceInstaller1.ServiceName = "WindowsServiceHostForMyService"; serviceInstaller1.DisplayName = "WindowsServiceHostForMyService"; serviceInstaller1.Description = "WCF Service Hosted by Windows NT Service"; serviceInstaller1.StartType = ServiceStartMode.Automatic; Installers.Add(serviceProcessInstaller1); Installers.Add(serviceInstaller1); } 

Note that we have blocked the call to the InitializeComponent () method using a comment.
This completes the development of the Windows service. Build the entire solution (Build Solution) and proceed to the next step - installing the Windows service.

To install our service, create a bat file (with an arbitrary name, for example, Install_Windows_Service.bat) with the following content:

 C:\Windows\Microsoft.NET\Framework\v4.0.30319\InstallUtil.exe WindowsServiceHostForMyService.exe 

You need to copy this bat-file to the same folder as the compiled WindowsServiceHostForMyService.exe file (you need to think in advance which folder this file will be in, which will always be running as a Windows service).

Run the bat-file, after which our program WindowsServiceHostForMyService.exe will be installed as a Windows service.

We will launch this service using the standard service management program.

The next stage is the development of a client application for using the services provided by the service.

To do this, you first need to organize the so-called "adapter" - Service Proxy - a set of settings that describe the service for the client application.

We use for this the standard utility SvcUtil.exe. Create a file Generate_Proxy.bat as follows

 SvcUtil http://localhost:9001/MyService /out:MyServiceProxy.cs /config:App.config 

Run this file (the standard utility SvcUtil.exe is located in the folder C: \ Program Files \ Microsoft SDKs \ Windows \ v7.0 \ Bin).

This file must be run while our service is running, i.e. in this case, after the successful launch of the Windows service WindowsServiceHostForMyService.

In case of a successful launch, the SvcUtil.exe program will generate 2 files — MyServiceProxy.cs and App.config.

These files need to be added for the client application so that this application can call the methods of our service (just below you will learn that I decided not to add the App.config file - we can do without it).

Note. A similar result could be achieved by running

 SvcUtil net.tcp://localhost:9002/MyService /out:MyServiceProxy.cs /config:App.config 

Those. You can run this utility by specifying only one endpoint, either http or net.tcp.

In the same solution (Solution) we will create a regular Windows Forms application. Let's call it WindowsFormsApplication1



Add to this project a link to System.ServiceModel and, of course,

 using System.ServiceModel   . 

Add the MyServiceProxy.cs file to this project (we generated it with the SvcUtil.exe utility). In this case, add the following lines to the MyServiceProxy.cs file:

 namespace ServiceReference1 { using System.Runtime.Serialization; using System; …     MyServiceProxy.cs …  ,       namespace } 

After that, we will be able to refer to the MyServiceClient class (this class was created by the SvcUtil.exe program), specifying a directive in the form file.

  using ServiceReference1; 

An example of the finished file MyServiceProxy.cs (comments removed):
 namespace ServiceReference1 { using System.Runtime.Serialization; using System; [System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "3.0.0.0")] [System.ServiceModel.ServiceContractAttribute(ConfigurationName="IMyService")] public interface IMyService { [System.ServiceModel.OperationContractAttribute(Action="http://tempuri.org/IMyService/Method1", ReplyAction="http://tempuri.org/IMyService/Method1Response")] string Method1(string x); [System.ServiceModel.OperationContractAttribute(Action="http://tempuri.org/IMyService/Method2", ReplyAction="http://tempuri.org/IMyService/Method2Response")] string Method2(string x); } [System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "3.0.0.0")] public interface IMyServiceChannel : IMyService, System.ServiceModel.IClientChannel { } [System.Diagnostics.DebuggerStepThroughAttribute()] [System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "3.0.0.0")] public partial class MyServiceClient : System.ServiceModel.ClientBase<IMyService>, IMyService { public MyServiceClient() { } public MyServiceClient(string endpointConfigurationName) : base(endpointConfigurationName) { } public MyServiceClient(string endpointConfigurationName, string remoteAddress) : base(endpointConfigurationName, remoteAddress) { } public MyServiceClient(string endpointConfigurationName, System.ServiceModel.EndpointAddress remoteAddress) : base(endpointConfigurationName, remoteAddress) { } public MyServiceClient(System.ServiceModel.Channels.Binding binding, System.ServiceModel.EndpointAddress remoteAddress) : base(binding, remoteAddress) { } public string Method1(string x) { return base.Channel.Method1(x); } public string Method2(string x) { return base.Channel.Method2(x); } } } 


Let's act extraordinary - and we will not add the App.Config file to the client's project!

Then we sketch 3 buttons on the form, a textBox1 text field (the user enters a string to be sent to the server) and a richTextbox1 field (an imitation of the message subsystem of our application — this is the field that will receive messages from the program, including those that have been returned to us )

Button btn_Start - creates a client
Button btn_Send - sends the service a text string from the text field
Button btn_Close - removes the client from memory and closes the application

Form Code:
 using System; using System.ServiceModel; using System.Windows.Forms; using ServiceReference1; namespace WindowsFormsApplication1 { public partial class Form1 : Form { MyServiceClient client = null; public Form1() { InitializeComponent(); } private void Print(string text) { richTextBox1.Text += text + "\n\n"; richTextBox1.SelectionStart = richTextBox1.Text.Length; richTextBox1.ScrollToCaret(); } private void Print(Exception ex) { if (ex == null) return; Print(ex.Message); Print(ex.Source); Print(ex.StackTrace); } private void Create_New_Client() { if (client == null) try { Try_To_Create_New_Client(); } catch (Exception ex) { Print(ex); Print(ex.InnerException); client = null; } else { Print("Cannot create a new client. The current Client is active."); } } private void Try_To_Create_New_Client() { try { NetTcpBinding binding = new NetTcpBinding(SecurityMode.Transport); binding.Security.Message.ClientCredentialType = MessageCredentialType.Windows; binding.Security.Transport.ClientCredentialType = TcpClientCredentialType.Windows; binding.Security.Transport.ProtectionLevel = System.Net.Security.ProtectionLevel.EncryptAndSign; string uri = "net.tcp://192.168.1.2:9002/MyService"; EndpointAddress endpoint = new EndpointAddress(new Uri(uri)); client = new MyServiceClient(binding, endpoint); client.ClientCredentials.Windows.ClientCredential.Domain = ""; client.ClientCredentials.Windows.ClientCredential.UserName = "Vasya"; client.ClientCredentials.Windows.ClientCredential.Password = "12345"; Print("Creating new client ...."); Print(endpoint.Uri.ToString()); Print(uri); string test = client.Method1("test"); if (test.Length < 1) { throw new Exception("   "); } else { Print("test is OK ! " + test); } } catch (Exception ex) { Print(ex); Print(ex.InnerException); client = null; } } private void btn_Start_Click(object sender, EventArgs e) { Create_New_Client(); } private void btn_Send_Click(object sender, EventArgs e) { Print("sending message . . ."); string s = textBox1.Text; string x = ""; if (client != null) { x = client.Method1(s); Print(x); x = client.Method2(s); Print(x); } else { Print("Error! Client does not exist!"); } } private void btn_Close_Click(object sender, EventArgs e) { if (client != null) { Print("Closing a client ..."); client.Close(); client = null; } else { Print("Error! Client does not exist!"); } this.Close(); } } } 


We compile and run from another machine from the same network - and test the service by clicking btn_Start first, and then sending messages to the service by pressing btn_Send.



Note that in this example on the client we did not use the end point at all.

http://localhost:9001/MyService
and only worked with

net.tcp://localhost:9002/MyService
(you can easily do it yourself - since net.tcp is on your shoulder, then http will do something with closed eyes).

In addition, we did not use the App.config file, creating the endpoint on the client not using the settings, but using the C # code. The reasons for this are subjective — the author does not like to tinker with XML settings, and if possible, he does everything explicitly in the code. Thanks for attention!

Lyrical digression. The author of the article got acquainted with the C # language in March of this year, the first application in C # was written in May (before that, for many years it was form-splitting into Delphi and even MS Access).

Source: https://habr.com/ru/post/331952/


All Articles