Tuesday, October 27, 2009

Lab #12: Working with a Windows Forms Host

Members:

Rafael Zuniga, Stephen Cave

Summary:

The purpose of this lab is to add an existing service to a Windows Forms application and to experiment with starting the service from different locations.

Procedure:

We start by opening the solution found in Chapter4/WindowsApplicationHost which can be found under the labs directory. Right from the start the solution only builds two of the projects with the actual Windows Forms project failing since it lacks a reference to System.ServiceModel. We add the missing reference and also add a reference to the Message service project which is contained within the same solution. This time the project builds succesfully:

We must now modify the App.config for the host since it will require endpoint definitions for the service to be used:



behaviorConfiguration="serviceBehavior">



Next we modify the Windows Forms host code itself which is found in Program.cs:

static class Program
{
public static ServiceHost MessageServiceHost;

[STAThread]
static void Main()
{
Application.ApplicationExit += new EventHandler(Application_ApplicationExit); Program.MessageServiceHost = new ServiceHost(typeof(Messaging.MessagingService)); Program.MessageServiceHost.Open();

Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form1());
}

static void Application_ApplicationExit(object sender, EventArgs e) { Program.MessageServiceHost.Close(); Program.MessageServiceHost = null; }
}


This code will allow the service to begin at application start up and shut down when the application exits. Next we must modify the client to use the service. We start by adding a service reference to our client which references the host project. Next we add a new button to Form1 and add an event handler to send a message to our host:

private void button1_Click(object sender, EventArgs e)
{
localhost.MessagingServiceClient proxy = new
Client.localhost.MessagingServiceClient();
proxy.SendMessage(
string.Format("Hello from {0}", this.Text));
}


Compiling and running the solution give us two windows. One is running the service while the other is the client which can call the service. Calling the service on the client results in the following:

In the image above the host is running in its own thread. Next we will modify the service to run within the UI thread. We begin by removing all of the previous start up code from the host and adding two buttons to Form1. Then we modify the start up code:

ServiceHost m_serviceHost;

public Form1()
{
InitializeComponent();
this.button2.Enabled = false; m_serviceHost = new ServiceHost( typeof(Messaging.MessagingService));
}


Two event handlers are added for each button; one to start the service and one to stop it:

private void button1_Click(object sender, EventArgs e)
{
this.button1.Enabled = false;
this.button2.Enabled = true;
m_serviceHost.Open();
}

private void button2_Click(object sender, EventArgs e)
{
this.button1.Enabled = true;
this.button2.Enabled = false;
m_serviceHost.Close();
}


The close event is also modified to close the service when the form is closed:

private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
DialogResult result = MessageBox.Show(
"Are you sure you want to close the service?",
"Service Controller", MessageBoxButtons.YesNo,
MessageBoxIcon.Question, MessageBoxDefaultButton.Button2);

if (result == DialogResult.Yes) { if (m_serviceHost != null) { m_serviceHost.Close(); m_serviceHost = null; } } else
e.Cancel = true;
}


We recompile and run the solution resulting in:


Here the service is running within the UI's message loop. Modifying the implementation of IMessagingService by adding the ServiceBehavior attribute with UseSynchronizationContext set to false forces the service to operate outside of the message loop and within its own thread. Next we will modify the service to allow for callbacks to the client. We modify MessagingService.cs by adding a callback attribute to the previous IMessagingService interface and creating a new interface called IMessagingServiceCallback:

public interface IMessagingServiceCallback
{
[OperationContract(IsOneWay = true)]
void MessageNotification(string message);
}


We next modify the client by updating the service reference, creating a new class named MessagingServiceCallback which implements the IMessagingServiceCallback interface, and adding code to provide a context to the client proxy. The end result is show below:


Observations:

  1. You can host a service through a GUI application and have it start in it's own thread by either starting it before the GUI is created or adding an attribute to force it into it's own thread.
  2. Running a service in the GUI thread is probably a bad idea since the message pump has a whole lot of things to handle already.
  3. Gotta freakin' run with administrator privileges
  4. Blogger butchered some of the XML configuration file stuff up there
  5. Is there a Blogger equivalent to [code][/code] tags most forums have?

No comments:

Post a Comment