tags:

views:

120

answers:

4

I have a WCF service that is hosted inside a Windows Form.

How can I access the controls of the form from the methods in my service?

for example I have

public interface IService    {
    [ServiceContract]
    string PrintMessage(string message);
}

public class Service: IService    
{
    public string PrintMessage(string message)
    {
        //How do I access the forms controls from here?
        FormTextBox.Text = message;
    }
}
A: 

Use a delegate. Create a delegate in the codebehind of your form which references a method that writes to the textbox and pass it to the Service, the service can then invoke the delegate when it wants to print a message.

You will have issues trying to update the textbox because the delegate will be invoked on a different thread to that which the textbox was created on. In the WPF world you would use Dispatcher.BeginInvoke to get around this, not sure what the WinForms equivalent is.

Simon Fox
Good point about the threading. This can be a pain :-)
Rune Grimstad
A: 

The service will need a reference to the instance of the form, not just the type of the form.

Further you will need a way to send values to the form controls from your service class. This can either be done by making the Controls themselves public or protected, or you can create properties on your form class that will set the properties on the Controls.

A simple approach would be the following:

  1. Add a constructor to your service class that takes a reference to your form instance. If you are hosting your service inside the form this should be easy.
  2. Add public properties for the control properties you want to set from the service. The properties should be public or internal and will set the values of the controls properties.
  3. In your service you set the values of the newly added properties.
Rune Grimstad
+1  A: 

The best way to handle such a scenario is to inject the Form into the service as a dependency. I would define some sort of interface that decouples the Form code from the WCF code:

public interface IFormService
{
    string Text { get; set; }
}

You can have your Form implement the IFormService interface by setting the real property you would like to update.

Your service would need an instance of IFormService to do its work:

public class Service : IService
{
    private readonly IFormService form;

    public Service(IFormService form)
    {
        this.form = form
    }

    public string PrintMessage(string message)
    {
        this.form.Text = message;
    }
}

Since the Service class now has no default constructor, you will also need to implement a custom ServiceHostFactory that can create instances of the Service class and inject the concrete implementation of IFormService.

Mark Seemann
A: 

First of all, the ServiceContract attribute should be on the interface, not the PrintMessage() method.

Using a corrected version of your example, you can do it this way.

[ServiceContract]
public interface IService
{
    [OperationContract]
    string PrintMessage(string message);
}
public class Service : IService
{
    public string PrintMessage(string message)
    {
        // Invoke the delegate here.
        try {
            UpdateTextDelegate handler = TextUpdater;
            if (handler != null)
            {
                handler(this, new UpdateTextEventArgs(message));
            }
        } catch {
        }
    }
    public static UpdateTextDelegate TextUpdater { get; set; }
}

public delegate void UpdateTextDelegate(object sender, UpdateTextEventArgs e);

public class UpdateTextEventArgs
{
    public string Text { get; set; }
    public UpdateTextEventArgs(string text)
    {
        Text = text;
    }
}

public class MainForm : Form
{
    public MainForm()
    {
        InitializeComponent();

        // Update the delegate of your service here.
        Service.TextUpdater = ShowMessageBox;

        // Create your WCF service here
        ServiceHost myService = new ServiceHost(typeof(IService), uri);
    }
    // The ShowMessageBox() method has to match the signature of
    // the UpdateTextDelegate delegate.
    public void ShowMessageBox(object sender, UpdateTextEventArgs e)
    {
        // Use Invoke() to make sure the UI interaction happens
        // on the UI thread...just in case this delegate is
        // invoked on another thread.
        Invoke((MethodInvoker) delegate {
            MessageBox.Show(e.Text);
        } );
    }
}

This is essentially the solution suggested by @Simon Fox, i.e., use a delegate. This hopefully just puts some flesh on the bones, so to speak.

Matt Davis
That looks good Matt except for the line "service.TextUpdater = ShowMessageBox;"When I create my service I do: ServiceHost myService = new ServiceHost(typeof(IService), uri);So how can I set the TextUpdater property if it's of type ServiceHost and not of type Service?
Right. Make the TextUpdater property a static property of the Service class. Your service will then have access to the delegate when it is instantiated.
Matt Davis
I updated the code example to be more clear.
Matt Davis
Memory leaks abound with this solution. Be careful to at least set Service.TextUpdater to null. I'd suggest at least making that a real event, rather than a delegate reference so that a) you can have more than one of your Window open and b) makes deregistering your handler simpler (the ol '-=' for events).
Anderson Imes
You can also go one step further and use a WeakReference. Non-trivial, however.
Anderson Imes
@Anderson Imes: Can you explain the "memory leaks" comment given the garbage collector?
Matt Davis
Certainly. If a static has a reference to an object, when that object is no longer in use (say you close the form, but that's not the primary form) then something still has a reference to that. In this case, the only thing that will cause the reference count for that object to drop to zero would be the app domain coming down (the app closing). You can avoid this by doing one of the 3 things I mentioned above. The garbage collector will not always save you. It's a hard lesson, but that's why tools like Ants Memory Profiler exist.
Anderson Imes
Thanks a lot Matt, that worked. I think another solution is to use a duplex operation as in this: http://www.codeproject.com/KB/WCF/WCF_Duplex_UI_Threads.aspx?msg=2795851. Looks like a good solution if you have alot of interaction between the service and UI as you don't have to register a delegate for each seperate interaction. Thanks again, top marks@Anderson, can you please explaine a WeakReference or send me a link to an explanation. Thanks
@Tom_123: Using the CallbackContract approach in WCF is certainly another route. And I would suggest this approach if the interaction with the UI from the WCF service is ultimately triggered by the user. However, if the service is triggering the interaction, you'll need to somehow cache the OperationContext object so you can make the call. I'd say it's not the preferred approach, but since your service lives within the lifetime of your UI, it's probably ok.
Matt Davis
Sure. This article looks pretty thorough. It walks through several approaches. http://www.codeproject.com/KB/cs/WeakEvents.aspx. I wouldn't do this if the lifetime of the form you have the method on is the same as the application, but if this os a form you are going to open more than once, you definitely either need to stop listening to the event when your form closes.
Anderson Imes