I am hosting a WCF webservice with custom certificate validation, but I am not able to configure it properly. When I try to get the WSDL of the WebService, I get a compilation error below. What am I doing wrong?
Thanks
Edit:
I've looked into: Custom certificate validation in WCF service and authentication of clientCertificate Element and How to: Create a Service that Employs a Custom Certificate Validator and X.509 Certificate Validator and none of those links describe an issue I am having.
Compilation Error message:
Could not load file or assembly 'service' or one of its dependencies. The system cannot find the file specified.
Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.
Exception Details: System.IO.FileNotFoundException: Could not load file or assembly 'service' or one of its dependencies. The system cannot find the file specified.
Source Error:
An unhandled exception was generated during the execution of the current web request. Information regarding the origin and location of the exception can be identified using the exception stack trace below.
web.config:
<system.serviceModel>
<bindings>
<basicHttpBinding>
<binding name="TransportSecurity">
<security mode="Message">
<message clientCredentialType="Certificate" />
</security>
</binding>
</basicHttpBinding>
</bindings>
<behaviors>
<serviceBehaviors>
<behavior name="MyServiceBehavior">
<serviceMetadata httpsGetEnabled="true" httpsGetUrl="" />
<serviceDebug includeExceptionDetailInFaults ="true"/>
<serviceCredentials>
<clientCertificate>
<authentication certificateValidationMode="Custom" customCertificateValidatorType = "MyProject.MyX509CertificateValidator, service"/>
</clientCertificate>
<serviceCertificate findValue="hashvalue" storeLocation="LocalMachine" storeName="My" x509FindType="FindByThumbprint" />
</serviceCredentials>
</behavior>
</serviceBehaviors>
<endpointBehaviors>
<behavior name="clientBehavior">
<clientCredentials>
<serviceCertificate>
<authentication certificateValidationMode="Custom" customCertificateValidatorType="MyProject.MyX509CertificateValidator, client"/>
</serviceCertificate>
</clientCredentials>
</behavior>
</endpointBehaviors>
</behaviors>
<services>
<service name="MyProject.MyProjectWCF" behaviorConfiguration="MyServiceBehavior">
<endpoint address="" binding="basicHttpBinding" bindingConfiguration="TransportSecurity" contract="MyProject.IMyProjectWCF" />
<endpoint address="mex" binding="mexHttpsBinding" contract="IMetadataExchange" />
</service>
</services>
</system.serviceModel>
WCF code:
Imports System.ServiceModel
Imports System.ServiceModel.Description
Imports System.IdentityModel.Selectors
Imports System.Security.Cryptography.X509Certificates
Imports System.IdentityModel.Tokens
Imports System.ServiceModel.Security
Namespace MyProject
' NOTE: You can use the "Rename" command on the context menu to change the class name "MyProjectWCF" in code, svc and config file together.
<ServiceBehavior()> _
Public Class MyProjectWCF
Implements IMyProjectWCF
Public Function HelloWorld() As String Implements IMyProjectWCF.HelloWorld
Return "nameSpace: [" + Me.GetType().Namespace + "]" + vbNewLine + "Normal response"
End Function
Sub New()
Dim serviceHost As New ServiceHost(GetType(MyProjectWCF))
Try
serviceHost.Credentials.ClientCertificate.Authentication.CertificateValidationMode = X509CertificateValidationMode.Custom
serviceHost.Credentials.ClientCertificate.Authentication.CustomCertificateValidator = New MyX509CertificateValidator("CN=MyCertificate")
serviceHost.Open()
'serviceHost.Close()
Finally
'serviceHost.Close()
End Try
End Sub
End Class
Public Class MyX509CertificateValidator
Inherits X509CertificateValidator
Private allowedIssuerName As String
Public Sub New(ByVal allowedIssuerName As String)
If allowedIssuerName Is Nothing Then
Throw New ArgumentNullException("allowedIssuerName")
End If
Me.allowedIssuerName = allowedIssuerName
End Sub
Public Overrides Sub Validate(ByVal certificate As X509Certificate2)
' Check that there is a certificate.
If certificate Is Nothing Then
Throw New ArgumentNullException("certificate")
End If
' Check that the certificate issuer matches the configured issuer.
If allowedIssuerName <> certificate.IssuerName.Name Then
Throw New SecurityTokenValidationException _
("Certificate was not issued by a trusted issuer")
End If
End Sub
End Class
End Namespace
Interface code:
Imports System.ServiceModel
Imports System.Security.Permissions
Namespace MyProject
' NOTE: You can use the "Rename" command on the context menu to change the interface name "IMyProjectWCF" in both code and config file together.
<ServiceContract([Namespace]:="MyProject")> _
Public Interface IMyProjectWCF
<OperationContract()> _
Function HelloWorld() As String
End Interface
End Namespace
EDIT 2 (with fix):
Insert default constructor into the cert validator class:
Public Sub New()
Me.New("CN=yourCertificate here")
End Sub
And then I had to figure out what the project name of my website is, which is App_Code, it gets compiled with a bunch of other pages into one DLL, which is APP_Code.dll. The final line in web.config looks like this:
<authentication certificateValidationMode="Custom" customCertificateValidatorType="MyProject.MyX509CertificateValidator, App_Code"/>
So now there are no compiled errors and I get my WSDL. Thank you for your help :)
I think that you have to change this
customCertificateValidatorType =
"MyProject.MyX509CertificateValidator, service"/>
to
customCertificateValidatorType =
"MyProject.MyX509CertificateValidator, MyProject"/>
Because 'service' it's not in your namespace. Maybe you are pasting it from MSDN, but you have to think that the MSDN WCF demo projects ('101 samples'), used to be called 'service'.
Related
I'm branching out into .Net web services and unfortunately am a newbie at it. I've decided to build WCF service instead of asp.net service because of online recommendations. My ultimate goal is to learn iOS and other mobile programming. I'm familiar with vb.net and c# standard and web applications.
I'm receiving a "Metadata publishing for this service is currently disabled" error when trying to test from a URL. I've research and tried implementing "fixes" for this issue, but still come up short. Can someone please look at my code and see what I'm doing wrong?
Webconfig file
<?xml version="1.0"?>
<configuration>
<system.web>
<compilation debug="true" strict="false"
explicit="true" targetFramework="4.0" />
<customErrors mode="Off"/>
</system.web>
<connectionStrings>
</connectionStrings>
<system.serviceModel>
<services>
<services>
<service name="CCT_Main_SRV.Service1" behaviorConfiguration="BehConfig">
<endpoint address="" binding="webHttpBinding" contract="CCT_Main_SRV.Service1" behaviorConfiguration="web">
</endpoint>
<endpoint contract="IMetadataExchange" binding="mexHttpBinding" address="mex" />
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior name="BehConfig">
<serviceMetadata httpGetEnabled="true"/>
<serviceDebug includeExceptionDetailInFaults="true"/>
</behavior>
</serviceBehaviors>
<endpointBehaviors>
<behavior name="web">
<webHttp/>
</behavior>
</endpointBehaviors>
</behaviors>
<serviceHostingEnvironment multipleSiteBindingsEnabled="true">
</serviceHostingEnvironment>
</system.serviceModel>
<system.webServer>
<modules runAllManagedModulesForAllRequests="true"/>
<directoryBrowse enabled="true"/>
</system.webServer>
</configuration>
.VB File
Imports System.ServiceModel
Imports System
Namespace CCT_Main_SRV.Service1
<ServiceContract()>
Public Interface CCT_Main_SRV
<OperationContract()>
<WebGet(UriTemplate:="Get_Device_Authenication", _
RequestFormat:=WebMessageFormat.Json, _
ResponseFormat:=WebMessageFormat.Json, _
BodyStyle:=WebMessageBodyStyle.Wrapped)>
Function Authenicate_Device_Manager(ByVal Device_Name As String, _
ByVal Auth_Key As String) _
As List(Of Device_Authenication)
<WebGet()>
<OperationContract()>
Function Authenicate_Device_Manager_Non_JSON(ByVal Device_Name As String, _
ByVal Auth_Key As String) _
As List(Of Device_Authenication)
End Interface
<DataContract()>
Public Class Device_Authenication
<DataMember()>
Public Property Device_Name As String
<DataMember()>
Public Property Active As Boolean
<DataMember()>
Public Property Return_String As String
End Class
svc.vb file
Imports System.ServiceModel
Imports System.Web.Script.Serialization
Imports System.ServiceModel.Activation
Imports System.ServiceModel.Web
Imports System.ServiceModel.Description
Namespace CCT_Main_SRV.Service1
Public Class Service1
Implements CCT_Main_SRV
Dim host As WebServiceHost = New WebServiceHost(GetType(Service1), New Uri("http://pmh-vmutility-1/cct_web_srv_test/:8000/"))
Dim ep As ServiceEndpoint = host.AddServiceEndpoint(GetType(CCT_Main_SRV), New WebHttpBinding(), "")
Public Function Authenicate_Device_Manager(ByVal Device_Name As String, _
ByVal Auth_Key As String) _
As List(Of Device_Authenication) _
Implements CCT_Main_SRV.Authenicate_Device_Manager
End Function
End Class
End Namespace
service name="CCT_Main_SRV.Service1"
Should be:
service name="Service1"
Or if it resides in a Web Application:
service name="ProjectName.Web.Service1"
It should match the fully qualified name of your service
{namespace}.{class} yours does not have a namespace.
Following code adds ParameterInspector to the endpoint.
ChannelFactory<ITest> factory = new ChannelFactory<ITest>("BasicHttpBinding_ITest");
OperationProfilerManager clientProfilerManager = new OperationProfilerManager();
factory.Endpoint.Behaviors.Add(new OperationProfilerEndpointBehavior(clientProfilerManager));
ITest proxy = factory.CreateChannel();
As a good practice, We are attempting to move all this code to Web.config. So that merely creating factory like this
ChannelFactory<ITest> factory = new ChannelFactory<ITest>("BasicHttpBinding_ITest");
or this -
ChannelFactory<ITest> factory = new ChannelFactory<ITest>();
should fetch the extension elements from configuration. With following configurations, BeforeCall or AfterCall methods of IParameterInspector is not being triggered. Can you please point out our mistake in following Web.config -
<system.serviceModel>
<bindings>
<basicHttpBinding>
<binding name="BasicHttpBinding_ITest" />
</basicHttpBinding>
</bindings>
<client>
<endpoint address="http://n1:8000/Service" binding="basicHttpBinding"
bindingConfiguration="BasicHttpBinding_ITest" contract="ServiceReference1.ITest"
name="BasicHttpBinding_ITest" />
</client>
<behaviors>
<endpointBehaviors>
<behavior name="todo">
</behavior>
</endpointBehaviors>
</behaviors>
<extensions>
<behaviorExtensions>
<add name="OperationProfilerEndpointBehavior" type="SelfHostedServiceClient.OperationProfilerEndpointBehavior, SelfHostedServiceClient"/>
</behaviorExtensions>
</extensions>
</system.serviceModel>
Thank you for your help.
Reference: Carlos blog
EDIT: Resolution
Based on Carlos answer, I took following steps to resolve the issue.
Step 1. Created OperationProfilerBehaviorElement class derived from BehaviorExtensionElement. This class is responsible for instantiating the class implementing IEndpointBehavior
class OperationProfilerBehaviorElement : BehaviorExtensionElement {
public override Type BehaviorType
{
get {
return typeof(OperationProfilerEndpointBehavior);
}
}
protected override object CreateBehavior()
{
OperationProfilerManager clientProfilerManager = new OperationProfilerManager();
return new OperationProfilerEndpointBehavior(clientProfilerManager);
} }
Step 2. This class had to be declared in Web.config as below,
<extensions>
<behaviorExtensions>
<add name="OperationProfilerBehavior" type="SelfHostedServiceClient.OperationProfilerBehaviorElement, SelfHostedServiceClient"/>
</behaviorExtensions>
</extensions>
Step 3. Added Endpoint behavior as below,
<behaviors>
<endpointBehaviors>
<behavior name="**InspectParameters**">
<OperationProfilerBehavior/>
</behavior>
</endpointBehaviors>
</behaviors>
Step 4. Set behaviorConfiguration attribute of the endpoint equal to InspectParameters as below,
<endpoint address="http://localhost:8000/Service" behaviorConfiguration="InspectParameters"
binding="basicHttpBinding" bindingConfiguration="BasicHttpBinding_ITest"
contract="ServiceReference1.ITest" name="BasicHttpBinding_ITest" />
Now I was able to initialize factory in a single C# line and parameter inspector was added by default from Web.config
ChannelFactory factory = new ChannelFactory("BasicHttpBinding_ITest");
The type OperationProfilerEndpointBehavior which is referenced in the <extensions> / <behaviorExtensions> section of the config should not be a class implementing IEndpointBehavior - it should be a type which inherits from BehaviorElementExtension, and that class is the one which should create the behavior.
See more information about behavior extensions at http://blogs.msdn.com/b/carlosfigueira/archive/2011/06/28/wcf-extensibility-behavior-configuration-extensions.aspx.
Found the answer here (last post): http://social.msdn.microsoft.com/Forums/eu/wcf/thread/f5c0ea22-1d45-484e-b2c0-e3bc9de20915
I'm having one last issue with the implementation of my custom (TextOrMtomEncoder), which is the implementation of ReaderQuotas.
I've searched the web a lot, but I can't figure out the final piece of the puzzle.
I've got a class, which contains my implementations of the 'BindingElementExtensionElement' and 'MessageEncodingBindingElement'.
The MessageEncodingBindingElement implementation contains an override for:
T GetProperty<T>(BindingContext context)
which I 'borrowed' from the default .NET MessageEncoding implementations, like the TextMessageEncoding.
This has to be the right implementation, because MSDN says so.
The configuration is loaded fine from the web.config, I can see the ReaderQuotas properties in both my classes are set correctly, but it looks like .NET isn't reading the ReaderQuotas config from my MessageEncodingBindingElement implementation.
My guess is .NET uses the GetProperty method to load the config, because MessageVersion is requested via this method. But the problem is, T is never equal to XmlDictionaryReaderQuotas, so the ReaderQuotas is never begin requested.
The root of my question is weird btw, I'm developing on a Windows 7 x64 machine with IIS7.5. Posting 'large' files (like 100 KB) works on my machine. But when I deploy the service to Windows Server 2008 R2 (tried 2 different servers), I get the following error:
The formatter threw an exception while trying to deserialize the
message: There was an error while trying to deserialize parameter
http://socialproxy.infocaster.net:argument. The InnerException message
was 'There was an error deserializing the object of type
System.Object. The maximum array length quota (16384) has been
exceeded while reading XML data. This quota may be increased by
changing the MaxArrayLength property on the XmlDictionaryReaderQuotas
object used when creating the XML reader. Line 1, position 1584.'.
Please see InnerException for more details.
And like I said, it works on my machine :-/
Could anybody tell me how I can resolve this?
Many thanks in advance!
The WCF service config:
<system.serviceModel>
<extensions>
<behaviorExtensions>
<add name="wsdlExtensions" type="WCFExtras.Wsdl.WsdlExtensionsConfig, WCFExtras, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
<add name="textOrMtomMessageBehavior" type="InfoCaster.SocialProxy.lib.TextOrMtom.TextOrMtomMessageBehavior, InfoCaster.SocialProxy" />
</behaviorExtensions>
<bindingElementExtensions>
<add name="textOrMtomEncoding" type="InfoCaster.SocialProxy.lib.TextOrMtom.TextOrMtomEncodingElement, InfoCaster.SocialProxy" />
</bindingElementExtensions>
</extensions>
<bindings>
<customBinding>
<binding name="TextOrMtomBinding">
<textOrMtomEncoding messageVersion="Soap11">
<readerQuotas maxDepth="32" maxStringContentLength="5242880" maxArrayLength="204800000" maxBytesPerRead="5242880" maxNameTableCharCount="5242880" />
</textOrMtomEncoding>
<httpTransport maxBufferSize="5242880" maxReceivedMessageSize="5242880" transferMode="Buffered" authenticationScheme="Anonymous" />
</binding>
</customBinding>
</bindings>
<services>
<clear />
<service name="InfoCaster.SocialProxy.SocialProxy" behaviorConfiguration="InfoCaster.SocialProxy.ISocialProxyServiceBehavior">
<endpoint name="SocialProxyServiceEndpoint" address="" binding="customBinding" bindingConfiguration="TextOrMtomBinding" contract="InfoCaster.SocialProxy.ISocialProxy" behaviorConfiguration="InfoCaster.SocialProxy.ISocialProxyEndpointBehavior" />
</service>
</services>
<behaviors>
<serviceBehaviors>
<clear />
<behavior name="InfoCaster.SocialProxy.ISocialProxyServiceBehavior">
<!-- To avoid disclosing metadata information, set the value below to false and remove the metadata endpoint above before deployment -->
<serviceMetadata httpGetEnabled="true" />
<!-- To receive exception details in faults for debugging purposes, set the value below to true. Set to false before deployment to avoid disclosing exception information -->
<serviceDebug includeExceptionDetailInFaults="true" />
</behavior>
</serviceBehaviors>
<endpointBehaviors>
<clear />
<behavior name="InfoCaster.SocialProxy.ISocialProxyEndpointBehavior">
<textOrMtomMessageBehavior />
</behavior>
</endpointBehaviors>
</behaviors>
<serviceHostingEnvironment multipleSiteBindingsEnabled="true" />
</system.serviceModel>
Found the answer here (last post): http://social.msdn.microsoft.com/Forums/eu/wcf/thread/f5c0ea22-1d45-484e-b2c0-e3bc9de20915
Ok, I found it. You have to pass the readerquotes to the standard encoders. Unfortunantly there's no constructor for this so you have to set the property.
class TextOrMtomEncoder : MessageEncoder {
MessageEncoder _textEncoder;
MessageEncoder _mtomEncoder;
public TextOrMtomEncoder(MessageVersion messageVersion, XmlDictionaryReaderQuotas readerQuotas)
{
TextMessageEncodingBindingElement textEncoderBindingElement = new TextMessageEncodingBindingElement(messageVersion, Encoding.UTF8);
MtomMessageEncodingBindingElement mtomEncoderBindingElement = new MtomMessageEncodingBindingElement(messageVersion, Encoding.UTF8);
readerQuotas.CopyTo(mtomEncoderBindingElement.ReaderQuotas);
readerQuotas.CopyTo(textEncoderBindingElement.ReaderQuotas);
_mtomEncoder = mtomEncoderBindingElement.CreateMessageEncoderFactory().Encoder;
_textEncoder = textEncoderBindingElement.CreateMessageEncoderFactory().Encoder;
}
I'm getting the notorious, "The server did not provide a meaningful reply; this might be caused by a contract mismatch, a premature session shutdown or an internal server error" in my project. It's a WCF PollingDuplex Service, consumed by a Silverlight 4 project.
I'm requesting a document with the service, so I can display it in a viewer in my SL application.
Here is the Server Web Config XML:
<system.serviceModel>
<extensions>
<bindingExtensions>
<add name="pollingDuplex" type="System.ServiceModel.Configuration.PollingDuplexHttpBindingCollectionElement,System.ServiceModel.PollingDuplex, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
</bindingExtensions>
</extensions>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true"
multipleSiteBindingsEnabled="true" />
<behaviors>
<serviceBehaviors>
<behavior name="PortalOnlineBehavior">
<serviceMetadata httpGetEnabled="true" />
<serviceDebug includeExceptionDetailInFaults="true" />
<dataContractSerializer maxItemsInObjectGraph="2147483647"/>
</behavior>
</serviceBehaviors>
</behaviors>
<bindings>
<pollingDuplex>
<binding name="SLPollingDuplex" duplexMode="MultipleMessagesPerPoll" />
</pollingDuplex>
</bindings>
<services>
<service name="Online.Web.PortalOnline" behaviorConfiguration="PortalOnlineBehavior">
<endpoint address="" binding="pollingDuplex" bindingConfiguration="SLPollingDuplex"
contract="Notification.IPortalOnline" />
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
<host>
<baseAddresses>
<add baseAddress="http://portalonline.com/PortalOnline/IPortalOnline" />
</baseAddresses>
</host>
</service>
</services>
</system.serviceModel>
Here is the Object I'm trying to return to SL via this WCF service
Imports System.ServiceModel
Imports System.Runtime.Serialization
Imports DocCave.Common
Imports System.Xml
Imports System.IO
<DataContract(NAMESPACE:=DataStore.NAMESPACE)>
Public Class PortalDocument
<DataMember()>
Public Property DataSource As Byte()
<DataMember()>
Public Property FileName As String
<DataMember()>
Public Property FileType As String
End Class
Here's the WCF Method that is being called:
Public Function GetDocument(sessionUserMeta As Common.UserMetaData, docId As System.Guid) As Notification.PortalDocument Implements Notification.IPortalOnline.GetDocument
Dim doc As Documents.Document = Documents.Document.GetDocument(docId, sessionUserMeta)
Dim portalDoc As New PortalDocument
portalDoc.DataSource = doc.DataSource
portalDoc.FileName = doc.QueryPackage.DocumentName
portalDoc.FileType = doc.QueryPackage.Type
Return portalDoc
End Function
Further Details:
This works perfectly for one or two document request, and the gives me the above mentioned error. For instance, I can load a default document when the SL application is loaded using this method with this service, and it populates perfectly. I can then go to a tree view I have, and select a document, and it works perfect for the first document... but after that, error. Also, I've noticed sometimes it will only work once, if I select certain pdfs that are a bit larger (250kb or so..) ... oh, and I forgot... here's the code in my SL application that is connecting to the WCF service. I'm using the "GetBaseWebAddress()" because I'm using dynamic sub domains, so part of the address can be different each time...
Private Sub LoadClient()
Dim bind As New PollingDuplexHttpBinding(PollingDuplexMode.MultipleMessagesPerPoll)
Dim endpoint As New EndpointAddress(GetBaseWebAddress() & "PortalOnline/PortalOnline.svc")
Me.client = New PortalOnline.PortalOnlineClient(bind, endpoint)
AddHandlers()
End Sub
I've struggled with this for a while, so any help would be greatly appreciated...
You read the title and groaned. That's okay. I did too. But we do what we're asked, right? I need to build a service that can be accessed via a moniker from within Excel (2003, but I'm assuming any version of Excel should support this functionality). At the moment all I want to do is have a spreadsheet post data to a WCF service running from a Windows service on a remote machine. Because that data needs to be retrieved by something a little more sophisticated than VBA, I decided to set up a data contract. Here's my code (at the moment this is just a proof-of-concept, but it's closely related to how it needs to look when it's finished).
Here's the WCF-related stuff:
Imports System.ServiceModel
Imports System.Runtime.Serialization
<ServiceContract()>
Public Interface IWCF
<OperationContract()>
Sub PutData(ByVal what As String)
<OperationContract()>
Function GetWhats() As TheWhats()
End Interface
<DataContract()>
Public Class TheWhats
<DataMember()> Public Property Timestamp As DateTime
<DataMember()> Public Property TheWhat As String
End Class
Public Class WCF
Implements IWCF
Shared Whats As New List(Of TheWhats)
Public Sub PutData(ByVal what As String) Implements IWCF.PutData
Whats.Add(New TheWhats With {.Timestamp = Now, .TheWhat = what})
End Sub
Public Function GetWhats() As TheWhats() Implements IWCF.GetWhats
Return Whats.ToArray
End Function
End Class
My app.config:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.web>
<compilation debug="true"></compilation>
</system.web>
<system.serviceModel>
<services>
<service name="DataCollectionService.WCF">
<endpoint address=""
binding="netTcpBinding"
contract="DataCollectionService.IWCF" />
<endpoint address="mex"
binding="mexTcpBinding"
contract="IMetadataExchange" />
<host>
<baseAddresses>
<add baseAddress="net.tcp://localhost:9100/DataCollectionService/ "/>
</baseAddresses>
</host>
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior name="">
<serviceMetadata httpGetEnabled="false"/>
<serviceDebug includeExceptionDetailInFaults="true"/>
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
</configuration>
And my vba class to handle posting stuff:
Private Const pConxString As String = _
"service:mexAddress=""net.tcp://localhost:7891/Test/WcfService1/Service1/mex"", " & _
"address=""net.tcp://localhost:7891/Test/WcfService1/Service1/"", " & _
"binding=""NetTcpBinding_IService1"", bindingNamespace = ""http://tempuri.org/"", " & _
"contract=""IService1"", contractNamespace=""http://tempuri.org/"""
Public ServiceObject As Object
Private Sub Class_Initialize()
Set ServiceObject = GetObject(pConxString)
End Sub
Public Sub PutData(ByVal what As String)
ServiceObject.PutData what
End Sub
Private Sub Class_Terminate()
Set ServiceObject = Nothing
End Sub
If I include the DataContract attribute and the function that returns the data contract object, my vba code fails in the Public Sub PutData method with the following:
"Instance of MessagePartDescription Name='GetWhatsResult' Namespace='http://tempuri.org/' cannot be used in this context: required 'Type' property was not set."
If I take out the DataContract and comment out the function in the service definition, I'm fine. I don't plan on using the GetWhats() function from within Excel. But yet I'm guessing it wants the type definition for TheWhats.
From what I've read one solution seems to be making this a COM object and referencing the DLL. However that isn't a workable solution for my environment. Is there another way to fix this?
Okay, answered my own question. The solution (at least in my case) is to split the interface and have my service class implement both interfaces. Here's my new interface file:
Imports System.ServiceModel
Imports System.Runtime.Serialization
<ServiceContract()>
Public Interface IWCF_WriteOnly
<OperationContract()>
Sub PutData(ByVal what As String)
End Interface
<ServiceContract()>
Public Interface IWCF_ReadOnly
<OperationContract()>
Function GetData() As TheWhats()
End Interface
<DataContract()>
Public Class TheWhats
<DataMember()> Public Property Timestamp As DateTime
<DataMember()> Public Property TheWhat As String
End Class
Public Class WCF
Implements IWCF_WriteOnly
Implements IWCF_ReadOnly
Shared Whats As New List(Of TheWhats)
Public Sub PutData(ByVal what As String) Implements IWCF_WriteOnly.PutData
Whats.Add(New TheWhats With {.Timestamp = Now, .TheWhat = what})
End Sub
Public Function GetData() As TheWhats() Implements IWCF_ReadOnly.GetData
Return Whats.ToArray
End Function
End Class
That required a change in app.config so that two separate endpoints could operate on the same address:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.web>
<compilation debug="true"></compilation>
</system.web>
<system.serviceModel>
<services>
<service behaviorConfiguration="GenericBehavior" name="DataCollectionService.WCF">
<endpoint address="wo" binding="netTcpBinding" contract="DataCollectionService.IWCF_WriteOnly" />
<endpoint address="ro" binding="netTcpBinding" contract="DataCollectionService.IWCF_ReadOnly" />
<endpoint address="mex" binding="mexTcpBinding" contract="IMetadataExchange" />
<host>
<baseAddresses>
<add baseAddress="net.tcp://localhost:9100/DataCollectionService/" />
</baseAddresses>
</host>
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior name="GenericBehavior">
<serviceMetadata httpGetEnabled="false"/>
<serviceDebug includeExceptionDetailInFaults="true"/>
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
</configuration>
And because the write-only endpoint was isolated from the endpoint that required a data contract definition, this change in the Excel definition was necessary:
Private Const pConxString As String = _
"service:mexAddress=""net.tcp://localhost:9100/DataCollectionService/Mex"", " & _
"address=""net.tcp://localhost:9100/DataCollectionService/wo"", " & _
"binding=""NetTcpBinding_IWCF_WriteOnly"", bindingNamespace = ""http://tempuri.org/"", " & _
"contract=""IWCF_WriteOnly"", contractNamespace=""http://tempuri.org/"""
I tested this configuration with the WCF Test Client. I had to feed it the mex endpoint manually, but when I did, it picked up both contracts. I used the PutData method to populate the service class a little, then went into Excel and populated it some more. I went back to the WCF Test Client and ran the GetData function and it returned all the items added from both the Test Client and Excel.
We have not encountered this specific scenario, but we do a lot of work with WCF where we control both the service and consumer (silverlight, tablet os, ipad apps in mono, etc).
The general solution that we use is to have the same classes in the same namespace on both ends of the pipe. I am not 100% sure that this will work in your environment, but it might be worthwhile to recreate your TheWhats class in VBA and see if that helps you.
If it does, and VBA supports it, you can move the class to its own file and the reference it both from the service and client side projects.