http://lourenco.co.za/blog/2013/08/wcf-windows-service-using-topshelf-and-servicemodelex/

There are two excellent .NET libraries that help us to build enterprise solutions using the Windows Communication Foundation (WCF)TopShelf and ServiceModelEx. These two libraries are responsible for two very different, but integral parts of a windows service:

  • TopShelf enables us to easily debug, install and host a long running process in Windows
  • ServiceModelEx provides functionality to configure how a WCF service behaves and provides its endpoints

This article follows on from my previous article about Service Oriented Architecture. I recommend that if you are just starting out with SOA, you give that a read-through before going through this implementation.

In case you skipped it, we basically want to implement the communication between an application and a service, using WCF:

What is a Service Interaction?

So what actually makes up a service interaction? There are four necessary elements:

  • A client
  • A service implementation
  • A service host
  • A contract, shared between the client and the service, that describes the interaction

A Little Bit About Contracts

We can think of the contract as an agreement between the client and service as to how they will be communicating.

Let’s look at a little analogy. People, for example, also have contracts! Consider a normal greeting interaction between two people meeting for the first time:

That makes sense! That is because Braden has an introduction contract (i.e. knows how to respond to a an introduction). In service terms, the contract would be:

  1. João (the client) sends Braden (the service) an introduction request, that contains a greeting, “Hi”, and his name, “João”.
  2. Braden accepts João’s introduction request, and returns an introduction response, that greets João with the same greeting, “Hi”, and in addition, his own name, “Braden”.

Without such a contract in place, you might imagine the following interaction:

That makes absolutely no sense! If a server is given a request that it has absolutely no idea what to do with, it will through an error (and that error might as well be gibberish).

Implementation Using TopShelf and ServiceModelEx

Seeing as we are looking at a greeting interaction, why don’t we see if we can implement this as a WCF Windows Service? Let’s get started!

The Visual Studio Solution

In Visual Studio, start with an empty solution. We need to add 4 projects to the solution:

  • Class Library for the contract (WcfService.Contract)
  • A Class Library for the service implementation (WcfService.Service)
  • A Console Application for the client (WcfService.Client)
  • Console Application for the service host (WcfService.WindowsService)

I know what you’re thinking: “Why have so many different projects, there are just two parts to this right?” I like to separate my implementations out so that there is as little code redundancy as possible. The reasons for this separation will (hopefully) become apparent by the end of this article.

Contract

We discussed how contracts describe what is necessary for an interaction to occur earlier. There are three parts to this:

  • Request
  • Action
  • Response

I touched on requests and responses a little earlier. I like to think of these as Data Transfer Objects, or abbreviated to DTOs. These are simple objects that are easily serializable and do not generally reflect the underlying domain objects or database entities. I recommend that you make use of such constructs as they will force you to think carefully about (and therefore limit) the amount of data that as actually sent down the line. The action is simply the method name.

In the contract project, let’s add two classes for our request and response classes – IntroductionRequest:

[DataContract]
public class IntroductionRequest
{
[DataMember]
public string Greeting { get; set; }

[DataMember]
public string Name { get; set; }

public override string ToString()
{
return string.Format("{0}, I'm {1}", Greeting, Name);
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[DataContract]
public class IntroductionRequest
{
    [DataMember]
    public string Greeting { get; set; }
 
    [DataMember]
    public string Name { get; set; }
 
    public override string ToString()
    {
        return string.Format("{0}, I'm {1}", Greeting, Name);
    }
}

And IntroductionResponse:

[DataContract]
public class IntroductionResponse
{
[DataMember]
public string Greeting { get; set; }

[DataMember]
public string ClientName { get; set; }

[DataMember]
public string ServiceName { get; set; }

public override string ToString()
{
return string.Format("{0} {1}, my name is {2}", Greeting, ClientName, ServiceName);
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[DataContract]
public class IntroductionResponse
{
    [DataMember]
    public string Greeting { get; set; }
 
    [DataMember]
    public string ClientName { get; set; }
 
    [DataMember]
    public string ServiceName { get; set; }
 
    public override string ToString()
    {
        return string.Format("{0} {1}, my name is {2}", Greeting, ClientName, ServiceName);
    }
}

Notice a few things about these classes:

  • The classes are decorated with the DataContract attribute
  • The properties are decorated with the DataMember attribute
  • You will need a reference to the System.Runtime.Serialization assembly that comes with the .NET environment

Let’s add the contract now – add a new interface called IIntroductionService:

[ServiceContract]
public interface IIntroductionService
{
[OperationContract]
IntroductionResponse Introduce(IntroductionRequest request);
}
1
2
3
4
5
6
[ServiceContract]
public interface IIntroductionService
{
    [OperationContract]
    IntroductionResponse Introduce(IntroductionRequest request);
}

Notice a few things about this interface:

  • The interface is decorated with the ServiceContract attribute
  • The method is decorated with the OperationContract attribute
  • You will need a reference to the System.ServiceModel assembly that comes with the .NET environment

Your contract project should now look something like this:

This is a common project that is shared between the client and the service. Remember how we discussed the fact that we don’t want our implementations being released to the whole world in my previous article about Service Oriented Architecture? They don’t have very much to go on, do they?

Service

This is the part of the project where we will implement that actual functionality that our service will provide. The service needs to know what contract it will be fulfilling, so let’s add a reference to the contract project.

Next, add a new class for the service implementation called IntroductionService. We want this class to implement the IIntroductionService in the contract:

[ServiceBehavior]
public class IntroductionService : IIntroductionService
{
[OperationBehavior]
public IntroductionResponse Introduce(IntroductionRequest request)
{
throw new NotImplementedException();
}
}
1
2
3
4
5
6
7
8
9
[ServiceBehavior]
public class IntroductionService : IIntroductionService
{
    [OperationBehavior]
    public IntroductionResponse Introduce(IntroductionRequest request)
    {
        throw new NotImplementedException();
    }
}

Notice a few things about this interface:

  • The class is decorated with the ServiceBehavior attribute
  • The method is decorated with the  OperationBehavior attribute
  • You will need a reference to the System.ServiceModel assembly that comes with the .NET environment

At the moment, our service might as well be replying “Bumblebee” to any request that it receives, because it’s not doing anything with it! So, let’s flesh out the implementation of the introduction:

[OperationBehavior]
public IntroductionResponse Introduce(IntroductionRequest request)
{
return new IntroductionResponse
{
Greeting = request.Greeting,
ClientName = request.Name,
ServiceName = "Braden"
};
}
1
2
3
4
5
6
7
8
9
10
[OperationBehavior]
public IntroductionResponse Introduce(IntroductionRequest request)
{
    return new IntroductionResponse
        {
            Greeting = request.Greeting,
            ClientName = request.Name,
            ServiceName = "Braden"
        };
}

And now we have implemented the business logic for our service!

Your service project should now look something like this:

Service Host

Simply having an implementation is not enough! We need to have a way to actually host and run our implementation. There are several ways to accomplish this. For this article, I have decided to create a windows service using TopShelf and ServiceModelEx.

Using the NuGet Package Manager, add the TopShelf package to the service host project. Unfortunately, ServiceModelEx is not available on NuGet, so you will need to download the source (don’t worry, it’s free) from the IDesign downloads page. If you don’t feel like compiling it yourself, I have done so for you and uploaded a release build here. Add this DLL as a reference in the project, along with:

  1. The .NET assemblies:

    • System.ServiceModel
    • System.ServiceModel.Channels
  2. The solution projects:
    • WcfService.Contract
    • WcfService.Service

In the service host project, add a new class called Host:

internal class Host
{
private ServiceHost<IntroductionService> _service;

internal Host()
{
Console.WriteLine("Setting up services...");
_service = new ServiceHost<IntroductionService>(new Uri[] { });
}

public void Start()
{
Console.WriteLine("Starting services...");
_service.Open();
Console.WriteLine("Started!");
}

public void Stop()
{
Console.WriteLine("Stopping services...");
try
{
if (_service != null)
{
if (_service.State == CommunicationState.Opened)
{
_service.Close();
}
}
Console.WriteLine("Stopped!");
}
catch (Exception ex)
{
Console.WriteLine("Could not stop: " + ex.Message);
}
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
internal class Host
{
    private ServiceHost<IntroductionService> _service;
 
    internal Host()
    {
        Console.WriteLine("Setting up services...");
        _service = new ServiceHost<IntroductionService>(new Uri[] { });
    }
 
    public void Start()
    {
        Console.WriteLine("Starting services...");
        _service.Open();
        Console.WriteLine("Started!");
    }
 
    public void Stop()
    {
        Console.WriteLine("Stopping services...");
        try
        {
            if (_service != null)
            {
                if (_service.State == CommunicationState.Opened)
                {
                    _service.Close();
                }
            }
            Console.WriteLine("Stopped!");
        }
        catch (Exception ex)
        {
            Console.WriteLine("Could not stop: " + ex.Message);
        }
    }
}

This little bit of code utilizes the functionality provided by ServiceModelEx to create endpoints for the IntroductionService implementation and exposes it using WCF.

We need to modify the service host’s Main method:

static void Main(string[] args)
{
Console.WriteLine("Introduction Service");
try
{
const string name = "IntroductionService";
const string description = "Introduction Service";
var host = HostFactory.New(configuration =>
{
configuration.Service<Host>(callback =>
{
callback.ConstructUsing(s => new Host());
callback.WhenStarted(service => service.Start());
callback.WhenStopped(service => service.Stop());
});
configuration.SetDisplayName(name);
configuration.SetServiceName(name);
configuration.SetDescription(description);
configuration.RunAsLocalService();
});
host.Run();
}
catch (Exception ex)
{
Console.WriteLine("Introduction Service fatal exception. " + ex.Message);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
static void Main(string[] args)
{
    Console.WriteLine("Introduction Service");
    try
    {
        const string name = "IntroductionService";
        const string description = "Introduction Service";
        var host = HostFactory.New(configuration =>
        {
            configuration.Service<Host>(callback =>
            {
                callback.ConstructUsing(s => new Host());
                callback.WhenStarted(service => service.Start());
                callback.WhenStopped(service => service.Stop());
            });
            configuration.SetDisplayName(name);
            configuration.SetServiceName(name);
            configuration.SetDescription(description);
            configuration.RunAsLocalService();
        });
        host.Run();
    }
    catch (Exception ex)
    {
        Console.WriteLine("Introduction Service fatal exception. " + ex.Message);
    }
}

This code uses the Topshelf functionality to create a Windows service. It’s really great – when you run this console application with no parameters, it’s a simple console application! However, there are a few switches that enable you to easily deploy the Windows service:

  • WcfService.WindowsService.exe install installs the service on the machine
  • WcfService.WindowsService.exe start starts the service (once installed)
  • WcfService.WindowsService.exe stop stops the service (if it has started)
  • WcfService.WindowsService.exe uninstall removes the service from the machine

For the more complex commands, the Topshelf Command-Line Reference will help.

Together, the two libraries provide a WCF Windows Service!

Configuration

In order for the service host to accept incoming connections, we need to add a little configuration. As this is a Windows service, I am going to expose the service as a net.tcp endpoint. This basically means that the protocol that the service will be communicating over is TCP, and the net prefix means that it was designed solely for .NET implementations (i.e. I don’t care about interoperability). You could be asking, “Why not expose it over the HTTP protocol and use SOAP?” The HTTP protocol is an application-layer protocol that runs on top of TCP, and we would therefore have to run our own application-layer on top of another one – HTTP! This extra step results in the service being inherently slower!

The configuration requires two parts in our configuration – a binding and an endpoint. Let’s get to it! Add a new Application Configuration file to the service host project and call it App.config:

Paste the following XML into the App.config file:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.serviceModel>
<services>
<service name="WcfService.Service.IntroductionService">
<host>
<baseAddresses>
<add baseAddress="net.tcp://localhost:50123/IntroductionService"/>
</baseAddresses>
</host>
<endpoint address=""
binding="netTcpBinding"
contract="WcfService.Contract.IIntroductionService" />
</service>
</services>
</system.serviceModel>
</configuration>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.serviceModel>
    <services>
      <service name="WcfService.Service.IntroductionService">
        <host>
          <baseAddresses>
            <add baseAddress="net.tcp://localhost:50123/IntroductionService"/>
          </baseAddresses>
        </host>
        <endpoint address=""
                  binding="netTcpBinding"
                  contract="WcfService.Contract.IIntroductionService" />
      </service>
    </services>
  </system.serviceModel>
</configuration>

So what are we doing here? Let’s break it down:

  1. Declaring a service with a name that exactly matches the service implementation’s namespace and class
  2. Assigning a base address to the service (i.e. each endpoint address declared will be appended to this base URI)
  3. Adding an endpoint to the service that uses the net.tcp protocol and promises to match the contract declared

Note: The address for the endpoint is empty, as this will be the default endpoint for this service.

Your service host project should look something like this:

At this point in time, we can run the service host project, and the console application should start up:

Testing the Service

There is a special tool that comes bundled with Visual Studio – a WCF Test Client. We can use this client to connect to our service to test it out! Unfortunately, this client (obviously), has no idea about the contract that we have just created! Fortunately for us, it is easy to get it to build its own contract! However, by default, our service does not give any of its secrets away! Not even its contract.

We need to make a couple of changes to our configuration file to allow us to test the service:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.serviceModel>
<services>
<service name="WcfService.Service.IntroductionService" behaviorConfiguration="mexServiceBehavior">
<host>
<baseAddresses>
<add baseAddress="net.tcp://localhost:50123/IntroductionService"/>
</baseAddresses>
</host>
<endpoint address=""
binding="netTcpBinding"
contract="WcfService.Contract.IIntroductionService" />
<endpoint address="mex"
binding="mexTcpBinding"
contract="IMetadataExchange" />
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior name="mexServiceBehavior">
<serviceMetadata />
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
</configuration>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.serviceModel>
    <services>
      <service name="WcfService.Service.IntroductionService" behaviorConfiguration="mexServiceBehavior">
        <host>
          <baseAddresses>
            <add baseAddress="net.tcp://localhost:50123/IntroductionService"/>
          </baseAddresses>
        </host>
        <endpoint address=""
                  binding="netTcpBinding"
                  contract="WcfService.Contract.IIntroductionService" />
        <endpoint address="mex"
                  binding="mexTcpBinding"
                  contract="IMetadataExchange" />
      </service>
    </services>
    <behaviors>
      <serviceBehaviors>
        <behavior name="mexServiceBehavior">
          <serviceMetadata />
        </behavior>
      </serviceBehaviors>
    </behaviors>
  </system.serviceModel>
</configuration>

What have we changed? Let’s take a look:

  1. On line 5, we have configured the service to use a behavior, specified by name
  2. Lines 14-16 add a new endpoint to the service that allows metadata exchange (information about what contract the service will fulfill)
  3. Lines 19-25 configure the behavior we want to apply to the service

At this point, you can actually start the service host project and make test calls using the WCF Test Client that comes bundled with Visual Studio! The WcfTestClient.exe application can be found in the following folders:

  • C:\Program Files (x86)\Microsoft Visual Studio 11.0\Common7\IDE (for Windows 64-bit), or
  • C:\Program Files\Microsoft Visual Studio 11.0\Common7\IDE (for Windows 32-bit)

With the service host project running (make sure that you started it since modifying the App.config), start up the test client and click File → Add Service. Add the following connection to the client:

The client will then use the metadata exchange to add the service:

And finally, display the service that we have just implemented! Let’s give it a test. Select the Introduce() method, fill in the Greeting and Name values, check Start a new proxy and finally, click Invoke:

Exciting, it works! So now that we’ve tested our service out, it’s time to write our own client application!

Client

Firstly, we need to know how to communicate with our WCF Windows Service, so the client needs a reference to the contract project.

Next up, we need some sort of way to create a connection to the service. I like this little proxy object (it’s lightweight, tried and tested):

public class WcfProxy<TContract> : IDisposable
where TContract : class
{
public TContract Service { get; private set; }

public WcfProxy()
{
try
{
var factory = new ChannelFactory<TContract>(typeof(TContract).Name + "_Endpoint");
factory.Open();
Service = factory.CreateChannel();
}
catch (Exception ex)
{
Console.WriteLine("Could not create proxy: {0}", ex.Message);
Service = null;
}
}

public void Dispose()
{
if (Service != null)
{
var internalProxy = Service as ICommunicationObject;

try
{
if (internalProxy != null)
{
if (internalProxy.State != CommunicationState.Closed && internalProxy.State != CommunicationState.Faulted)
internalProxy.Close();
}
}
catch (Exception ex)
{
Console.WriteLine("Could not close proxy: {0}", ex.Message);
try
{
if (internalProxy != null)
internalProxy.Abort();
}
catch (Exception exInternal)
{
Console.WriteLine("Could not abort proxy: {0}", exInternal.Message);
}
}

if (internalProxy is IDisposable)
{
try
{
if (internalProxy.State != CommunicationState.Faulted)
(internalProxy as IDisposable).Dispose();
}
catch (Exception ex)
{
Console.WriteLine("Could not dispose proxy: ", ex.Message);
}
}
}
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
public class WcfProxy<TContract> : IDisposable
    where TContract : class
{
    public TContract Service { get; private set; }
 
    public WcfProxy()
    {
        try
        {
            var factory = new ChannelFactory<TContract>(typeof(TContract).Name + "_Endpoint");
            factory.Open();
            Service = factory.CreateChannel();
        }
        catch (Exception ex)
        {
            Console.WriteLine("Could not create proxy: {0}", ex.Message);
            Service = null;
        }
    }
 
    public void Dispose()
    {
        if (Service != null)
        {
            var internalProxy = Service as ICommunicationObject;
 
            try
            {
                if (internalProxy != null)
                {
                    if (internalProxy.State != CommunicationState.Closed && internalProxy.State != CommunicationState.Faulted)
                        internalProxy.Close();
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine("Could not close proxy: {0}", ex.Message);
                try
                {
                    if (internalProxy != null)
                        internalProxy.Abort();
                }
                catch (Exception exInternal)
                {
                    Console.WriteLine("Could not abort proxy: {0}", exInternal.Message);
                }
            }
 
            if (internalProxy is IDisposable)
            {
                try
                {
                    if (internalProxy.State != CommunicationState.Faulted)
                        (internalProxy as IDisposable).Dispose();
                }
                catch (Exception ex)
                {
                    Console.WriteLine("Could not dispose proxy: ", ex.Message);
                }
            }
        }
    }
}

This requires that the client project has a reference to the System.ServiceModel assembly that comes with the .NET framework. Furthermore, this assumes that there is an endpoint defined in the client’s App.config file that is named IIntroductionService_Endpoint, as highlighted below:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
<system.serviceModel>
<client>
<endpoint address="net.tcp://localhost:50123/IntroductionService"
binding="netTcpBinding"
contract="WcfService.Contract.IIntroductionService"
name="IIntroductionService_Endpoint">
</endpoint>
</client>
</system.serviceModel>
</configuration>
1
2
3
4
5
6
7
8
9
10
11
12
<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <system.serviceModel>
    <client>
      <endpoint address="net.tcp://localhost:50123/IntroductionService"
          binding="netTcpBinding"
          contract="WcfService.Contract.IIntroductionService"
          name="IIntroductionService_Endpoint">
      </endpoint>
    </client>
  </system.serviceModel>
</configuration>

Notice that the endpoint is almost exactly the same as the service configuration! If you want a shortcut for the client side configuration, the WcfTestClient can actually generate the config file for you:

There are a few differences between the generated config and the one I am actually using:

  • The name of the endpoint has been changed so that the WcfProxy<T>  above works
  • The bindings element and the bindingConfiguration attribute) have been removed as I am using the default netTcpBinding
  • I have qualified the
    contract attribute with the full namespace of the contract as it lives in another assembly

Calling the Service

And now, for the magic! In the Program.cs file in
the client project, we are going to actually call the service. Let’s
start off by calling the service using a hard-coded request. In the
Main method, add the following code:

static void Main(string[] args)
{
Console.WriteLine("Press enter to send the introduction request");
Console.ReadLine();
using (var proxy = new WcfProxy<IIntroductionService>())
{
Console.ForegroundColor = ConsoleColor.Blue;
var request = new IntroductionRequest
{
Greeting = "Hello",
Name = "João"
};
Console.WriteLine("Sending: {0}", request);

Console.ForegroundColor = ConsoleColor.Green;
var response = proxy.Service.Introduce(request);
Console.WriteLine("Received: {0}", response.ToString());
}
Console.ForegroundColor = ConsoleColor.White;
Console.WriteLine("Press enter to exit");
Console.ReadLine();
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
static void Main(string[] args)
{
    Console.WriteLine("Press enter to send the introduction request");
    Console.ReadLine();
    using (var proxy = new WcfProxy<IIntroductionService>())
    {
        Console.ForegroundColor = ConsoleColor.Blue;
        var request = new IntroductionRequest
            {
                Greeting = "Hello",
                Name = "João"
            };
        Console.WriteLine("Sending: {0}", request);
 
        Console.ForegroundColor = ConsoleColor.Green;
        var response = proxy.Service.Introduce(request);
        Console.WriteLine("Received: {0}", response.ToString());
    }
    Console.ForegroundColor = ConsoleColor.White;
    Console.WriteLine("Press enter to exit");
    Console.ReadLine();
}

All we’re doing here is

  1. Creating an instance of our proxy to call the service
  2. Creating a request object and displaying it (in green)
  3. Sending it to the service and waiting for a response
  4. Once the response has come back, we are displaying it (in blue)

At this point, your project should look like this:

We need to set up the service host and client projects to start up by default. Right click the solution in Solution Explorer, and click Set StartUp Projects

Select the Multiple startup projects radio button, and set the service host and client projects to Start:

Now, whenever we run the project, both projects will start in debug mode! Let’s start them up now! You should see the following in the client window:

That’s it! You’ve got a working solution for an application talking to a service over WCF!

Improving the Client

Just for fun, why don’t we allow the user to type in their own greeting and name. See if you can work through the modified code below and figure out what is going on:

class Program
{
static void Main(string[] args)
{
WriteInstructions();

const string regexString = "(?<Greeting>.*?), I'm (?<Name>.*)";
Regex regex = new Regex(regexString);
string line;
while (!string.IsNullOrWhiteSpace(line = Console.ReadLine()))
{
var match = regex.Match(line);
if (match.Success)
{
using (var proxy = new WcfProxy<IIntroductionService>())
{
Console.ForegroundColor = ConsoleColor.Blue;
var request = new IntroductionRequest
{
Greeting = match.Groups["Greeting"].Value,
Name = match.Groups["Name"].Value
};
Console.WriteLine("Sending: {0}", request);

Console.ForegroundColor = ConsoleColor.Green;
var response = proxy.Service.Introduce(request);
Console.WriteLine("Received: {0}", response.ToString());

Console.ForegroundColor = ConsoleColor.White;
}
}
else
{
WriteInstructions();
}
}
}

static void WriteInstructions()
{
Console.WriteLine("Type a greeting as '<greeting>, I'm <name>', then press enter");
Console.WriteLine(" e.g. Hello, I'm João");
Console.WriteLine("And the server will respond in kind :)");
Console.WriteLine("Press enter only to exit");
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
class Program
{
    static void Main(string[] args)
    {
        WriteInstructions();
 
        const string regexString = "(?<Greeting>.*?), I'm (?<Name>.*)";
        Regex regex = new Regex(regexString);
        string line;
        while (!string.IsNullOrWhiteSpace(line = Console.ReadLine()))
        {
            var match = regex.Match(line);
            if (match.Success)
            {
                using (var proxy = new WcfProxy<IIntroductionService>())
                {
                    Console.ForegroundColor = ConsoleColor.Blue;
                    var request = new IntroductionRequest
                    {
                        Greeting = match.Groups["Greeting"].Value,
                        Name = match.Groups["Name"].Value
                    };
                    Console.WriteLine("Sending: {0}", request);
 
                    Console.ForegroundColor = ConsoleColor.Green;
                    var response = proxy.Service.Introduce(request);
                    Console.WriteLine("Received: {0}", response.ToString());
 
                    Console.ForegroundColor = ConsoleColor.White;
                }
            }
            else
            {
                WriteInstructions();
            }
        }
    }
 
    static void WriteInstructions()
    {
        Console.WriteLine("Type a greeting as '<greeting>, I'm <name>', then press enter");
        Console.WriteLine("  e.g. Hello, I'm João");
        Console.WriteLine("And the server will respond in kind :)");
        Console.WriteLine("Press enter only to exit");
    }
}

You may now call the service with whichever greeting and name you like:

Gotchas and Notes

There are a couple of things that stumped me at first!

ServiceContract,  ServiceBehavior, OperationContract and OperationBehavior Attributes

Exceptions are thrown when trying to start up the service if these attributes are not added to the contract definitions and service implementations.

DataContract and DataMember Attributes

Adding a non-default constructor caused my classes to fail to serialize and/or deserialize without these attributes. If you want to add other properties that you don’t want serialized (e.g. calculated properties), just decorate the property with the  IgnoreDataMember attribute.

Deserializing Objects and Constructors

There is no need for a non-default constructor as the .NET serialization classes responsible for creating these objects in memory don’t use them! Give it a try – create a default constructor and do some custom initialization. This code never actually runs!

Download the Solution

If you would like to download the complete source code, the Visual Studio 2012 solution, projects and code (with ServiceModelEx.dll but without the NuGet packages) are available here

Final Thoughts

The steps to actually write software using an SOA approach are quite simple, aren’t they? All of our top-secret business logic sits safely in our data centers, out of the hands of the evil hackers!

This service does not actually hold any state or need to run any scheduled jobs, so it would work fine as a Web-Activated Service (WAS) hosted in Internet Information Services (IIS). Remember how we separated our service implementation from our service host? In order to implement this as a WAS, very few steps are necessary! Now I am going to do something that I really hate seeing on other blogs – a promise (not really) of another post! Well, here it is: I am planning on writing a post describing such a process in the near future.

最新文章

  1. 通读AFN①--从创建manager到数据解析完毕
  2. DataTable 转 List&lt;T&gt;
  3. wireshake抓包,飞秋发送信息,python
  4. 摸索js的3d全景
  5. Reactjs 入门基础(一)
  6. Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk/usr/lib/dyld_sim is not owned by root.
  7. Java IO 写文件
  8. C语言实现冒泡排序法和选择排序法代码参考
  9. 思科ASA系列防火墙配置手册
  10. ARC工程中添加非ARC文件
  11. seaJs 使用
  12. SQL Server 阻止了对组件 &#39;xp_cmdshell&#39; 的 过程&#39;sys.xp_cmdshell&#39; 的访问
  13. IE6 中的最大最小寬度和高度 css 高度 控制(兼容版本)
  14. 编写可维护的javascript代码---开篇(介绍自动报错的插件)
  15. R2:获取一个event_base
  16. [ES6] Rest Parameter
  17. C#指定目录存放DLL
  18. 浅谈Java单例模式
  19. 学习笔记——Java字符串操作常用方法
  20. gulp自动化构建工具的使用

热门文章

  1. 001. 为input type=text 时设置默认值
  2. unity shader在小米2s上的问题
  3. Undefined symbols for architecture i386: &quot;MyGetOpenALAudioData(__CFURL const*, int*, int*, int*)&quot;
  4. 【转】asp.net连接数据库字符串有哪些写法[数据连接]
  5. jQuery的图像裁剪插件Jcrop
  6. python之路之正则表达式
  7. struts2.0的工作原理
  8. 查看SQL执行计划
  9. Android退出程序时的&quot;再按一次退出&quot;实现
  10. 【Java安装】Centos6.8 安装Java1.6