Damian Mehers' Blog Evernote and Wearable devices. All opinions my own.

5Jul/0950

Calling WCF Services from Excel VBA clients using the WCF Service Moniker

Did you know there is a way to invoke simple Windows Communication Foundation (WCF) Services from Excel VBA without installing anything other than the .NET Framework on the Excel machine?

There are many different kinds of monikers, but the one that interests us is the Service moniker that can be used to access WCF Services:

Set someObject = GetObject(“service:Binding Information”)

In this blog post I’m going to show how you how to go about using WCF Services from Excel VBA, step by step, from beginning to end, and explain how to debug the rather obtuse messages that Excel displays when you’ve misconfigured something.

The WCF Service

The WCF Interface you expose can not be particularly complex if you want to use the Service Moniker – I’ve found it best to keep top primitive types, and arrays of primitive types.  Data Contracts seem to be a no-go.

Start by creating a new WCF Service using File|New|Project and selecting WCF Service Library:

image

Double-click on IService1.cs in the Solution Explorer, and replace the default contents with this simple interface:

using System;
using System.ServiceModel;

namespace WcfService1
{
    [ServiceContract]
    public interface IService1
    {
        [OperationContract]
        string GetData(int value);

        [OperationContract]
        object[] GetSomeObjects();
    }
}

Next replace the contents of the service implementation, Service1.cs, with the following:

using System;

namespace WcfService1
{
    public class Service1 : IService1
    {
        public string GetData(int value)
        {
            return string.Format("You entered: {0}", value);
        }

        public object[] GetSomeObjects()
        {
            return new object[] { "String", 123, 44.55, DateTime.Now };
        }
    }
}

Configuring the WCF Service

By default the WCF Service is configured to use HTTP as the transport protocol.  I generally switch it to use TCP, because I am operating within a corporate intranet, and HTTP seems like overkill.

You’ll also find that by default your WCF Service exposes two endpoints.  The first exposes as you’d expect, the IService1 interface you defined above.  The second exposes Metadata about your service, which the Service Moniker uses to know what operations are available on your service.  You’ll need both.

Right-click on the App.config file in the Solution Explorer, and select Edit WCF Configuration:

image

Switch the first endpoint to use TCP:

image

Also change the second to use mexTcpBinding:

image

Change the base address that the service will use, to use a TCP address instead of a HTTP Address by selecting the Host node on the left hand tree, and then selecting the base address and clicking on Edit, and changing the text to be net.tcp://localhost:7891/Test/WcfService1/Service1/

image

Finally, because you are using TCP instead of HTTP, change the MetataData service to not expect to expose the metadata via HTTP, by changing HttpGetEnabled to False under Advanced|Service Behaviours…

image

Save the changes and exit the WCF Editor.  Your App.config should look like this:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.web>
    <compilation debug="true" />
  </system.web>
  <system.serviceModel>
    <services>
      <service behaviorConfiguration="WcfService1.Service1Behavior"
        name="WcfService1.Service1">
        <endpoint address="" binding="netTcpBinding" bindingConfiguration=""
          contract="WcfService1.IService1">
          <identity>
            <dns value="localhost" />
          </identity>
        </endpoint>
        <endpoint address="mex" binding="mexTcpBinding" bindingConfiguration=""
          contract="IMetadataExchange" />
        <host>
          <baseAddresses>
            <add baseAddress="net.tcp://localhost:7891/Test/WcfService1/Service1/" />
          </baseAddresses>
        </host>
      </service>
    </services>
    <behaviors>
      <serviceBehaviors>
        <behavior name="WcfService1.Service1Behavior">
          <serviceMetadata httpGetEnabled="False"/>
          <serviceDebug includeExceptionDetailInFaults="False" />
        </behavior>
      </serviceBehaviors>
    </behaviors>
  </system.serviceModel>
</configuration>

To start running your service you can hit Ctrl-F5 in Visual Studio.  This will start a test service host, and a test client.  We will ignore the client, but you can check that the service host has started by clicking on the message in the notification area:

image

image

Eventually you’ll want to host your service somewhere else, such as a Windows Service.

The Excel VBA Client

Start Excel, and then hit Alt-F11 to enter the VBA Code editor.  We’ll invoke the WCF Service whenever someone clicks on a cell in the first sheet.  Double-click on Sheet1 on the top left hand side, and then select Worksheet from the drop down of objects:

image

The Service Moniker needs to know the address of the MetataData Service, the address of the service itself, binding information, and information about the contract.

Dim addr As String
addr = "service:mexAddress=""net.tcp://localhost:7891/Test/WcfService1/Service1/Mex"","
addr = addr + "address=""net.tcp://localhost:7891/Test/WcfService1/Service1/"","
addr = addr + "contract=""IService1"", contractNamespace=""http://tempuri.org/"","
addr = addr + "binding=""NetTcpBinding_IService1"", bindingNamespace=""http://tempuri.org/"""

Once you’ve built up the Service Moniker string, you can use GetObject to resolve it, and then invoke a method on it:

Dim service1 As Object
Set service1 = GetObject(addr)

MsgBox service1.GetData(12)

The VBA Editor should look like this:

image

I’ve set a breakpoint on the first line of code by clicking where the red circle is above.

If you now click on a cell in your sheet, and let the VBA code run, you should see this:

image

The VBA code has called through to the WCF Service and invoked a method on it, and the service has returned a string.

Change the code to invoke the second method, and step through in the debugger, and add a Watch on the result:

image

Whats up with the Tempuri?

You’ll have noticed that the moniker string references a couple of weird Tempuri namespaces.  These are the defaults, but you can alter them.  This can be useful if you have a single service exposing a couple of interfaces and you only want to work with one of them.  For example your service may expose a complex interface for WPF clients, and a simple interface for a Excel client.  If you give the simple interface a different namespace than the complex interface, you’ll be able to access it from Excel VBA.

To change the contract namespace, edit your interface code:

using System;
using System.ServiceModel;

namespace WcfService1
{
    [ServiceContract(Namespace="http://damianblog.com/")]
    public interface IService1
    {
        [OperationContract]
        string GetData(int value);

        [OperationContract]
        object[] GetSomeObjects();
    }
}

Then your service moniker can be changed accordingly:

addr = addr + "contract=""IService1"", contractNamespace=""http://damianblog.com/"","

When it doesn’t work

The Excel message you get when the GetObject doesn’t work properly is not exactly detailed:

image

There is however a way of getting more detailed information.  You can attach your debugger to Excel, and then see the underlying managed .NET exception.

To attach your debugger to Excel in Visual Studio use Debug|Attach to Process and then select Excel, and click Attach:

image

Next, make sure the debugger stops as soon as an exception occurs by clicking on Debug|Exceptions and check the checkbox to be interrupted when an exception is thrown:

image

Don’t forget that you have done this.  If you leave it checked then later on when debugging unrelated code you may get thoroughly confused to have the debugger break inside framework code that throws and then handles an exception which is normally invisible to you.

You’ll also need to tell the debugger to break when code other than your own code throws an exception.  Do this by clicking on Tools|Options and then going to the Debugging section, and uncheck the Enable Just My Code checkbox:

image

Now run your VBA code as usual, and when you get the error the Visual Studio Debugger will break with more detailed error information. So if I change my moniker string to use an invalid interface name the Excel VBA error is:

image

Whereas Visual Studio actually tells you what the error is:

image

The maximum message size quote for incoming messages has been exceeded

image

You’ll hit this error if your .NET code returns too much data to the Excel client.  You might be able to resolve it by setting the MaxRecivedMessageSize property in an Excel.exe.config file in the same folder as Excel.exe.  They way I have handled it is to move from using the Service Moniker mechanism, and to create a managed Excel AddIn that sets the maximum message size programmatically.

Summary

The WCF Service Moniker offers an excellent way for you to invoke simple WCF Interfaces, where small quantities of data are exchanged, without installing anything at all on the Excel Client machine.

In the above examples the service moniker created by the VBA code used the “localhost” address for the service, but if your service is running on another machine you can specify that machine’s address: the service can run anywhere.

Filed under: WCF 50 Comments
4Dec/086

Dealing with WCF Error: Cannot create a service reference with namespace X because the name is already in use by an existing service reference, folder or file

If you get the above error when trying to add a WCF service reference that you have previously deleted then try:

  • deleting the Service References.DeviceService.Reference.cs.dll file you'll find under obj\Debug\TempPE. 
  • deleting the service folder you'll still find under Service References on disk, even though you deleted the reference in Visual Studio.
  • commenting out all references to the service's namespace in your code
  • rebuilding
Filed under: WCF 6 Comments
31Oct/0819

WCF REST Services: Setting the response format based on request’s expected type

I just attended the Microsoft PDC in LA.  One of the many excellent sessions was a pre-conference on WCF, part of which was presented by Ron Jacobs. Ron did a fantastic job of explaining WCF REST Services and the WCF REST Starter Kit.

One of the examples he showed from the WCF REST Starter kit was an example where the response type (JSON or XML) is dynamically set based on the HTTP request's requested content type in the "Accepts" HTTP Header.

That example works by switching between two different operation implementations (methods).

I liked the idea of using the requested content type to automatically return JSON or XML depending on the requested content type, but I wasn't so keen on having to implement two methods.

I thought I'd try to get a similar thing working but using a single operation implementation which is called no matter whether or JSON or XML are requested.

The DynamicResponseType attribute

This is how it works.  You decorate your method with an additional DynamicResponseType attribute which I have defined:

   1: [ServiceContract]
   2: [ServiceBehavior(IncludeExceptionDetailInFaults = true, InstanceContextMode = InstanceContextMode.Single, ConcurrencyMode = ConcurrencyMode.Single)]
   3: [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
   4: public class Service1
   5: {
   6:     [OperationContract]
   7:     [WebGet(UriTemplate = "GetData?param1={i}&param2={s}")]
   8:     [DynamicResponseType]
   9:     public SampleResponseBody GetData(int i, string s)
  10:     {
  11:         return new SampleResponseBody() { 
  12:             Name = "Test",
  13:             Value = s, 
  14:             Time = DateTime.Now.ToShortTimeString() 
  15:         };
  16:     }
  17: }
  18:  
  19: public class SampleResponseBody
  20: {
  21:     public string Name { get; set; }
  22:     public string Value { get; set; }
  23:     public int IntValue { get; set; }
  24:     public string Time { get; set; }
  25: }

An example client

Then when the client requests a specific type (XML or JSON), it is served automatically.

Below I have a pure HTML/Javascript client with two buttons, each of which call the same  GetWebRequest function when they are clicked but passing a different requested content type as a parameter.  The GetWebRequest function issues an HTTP request to the WCF operation I showed above.

The first button says it wants JSON, and the second XML.  This is done by setting the "Accept" request header:

   1: <body>
   2:     <form id="form1" runat="server">
   3:     <div>
   4:     
   5:     <input type="button" value="Click to request JSON" 
   6:         onclick="GetWebRequest('application/json');" />
   7:     
   8:     <input type="button" value="Click to request XML" 
   9:         onclick="GetWebRequest('application/xml');" />
  10:     
  11:     </div>
  12:     <asp:ScriptManager ID="ScriptManager1" runat="server"></asp:ScriptManager>
  13:     </form>
  14:  
  15: <script type="text/javascript">
   1:  
   2:   function GetWebRequest(acceptType) {  
   3:     var wRequest = new Sys.Net.WebRequest();
   4:     wRequest.get_headers()["Accept"] = acceptType;
   5:     var url = "/Service1.svc/GetData?param1=12&param2=";
   6:     wRequest.set_url(url + new Date());
   7:     wRequest.set_httpVerb("GET");
   8:     wRequest.add_completed(OnWebRequestCompleted);
   9:     wRequest.invoke();
  10:   }
  11:  
  12:   function OnWebRequestCompleted(executor, eventArgs) {
  13:     alert(executor.get_responseData());
  14:   }

</script>

  16: </body>

This is the form that gets displayed initially:

image

When you click on the first button we request JSON from the WCF Service operation:

image

When you click on the second button we request XML from the same WCF Service operation:

image

How it works.

I've created my own  WCF ServiceHostFactory which I wire up in the SVC file:

   1: <%@ ServiceHost Language="C#" 
   2:     Debug="true" 
   3:     Service="WcfService2.Service1" 
   4:     CodeBehind="Service1.svc.cs" 
   5:     Factory="DamianBlog.ServiceHostFactory2Ex" %>

In my ServiceHostFactory2Ex class I ensure that my own WebServiceHost class gets created.

Then in my own WebServiceHost I ensure that my own WebHttpBehavior replaces the standard one.

Next in my own WebHttpBehavior I override the GetReplyDispatchFormatter method and return my own IDispatchMessageFormatter.

In my own IDispatchMessageFormatter I implement the SerializeReply method and then use a JSON formatter or XML formatter depending on the "Accepts" HTTP request header which I pick up from the OperationContext.Current.RequestContext.RequestMessage.

The full source is available for download here http://damianblog.com/WCFDynamicResponseDemo.zip.

Rob Jacobs blogs at http://blogs.msdn.com/rjacobs/

Filed under: WCF 19 Comments