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.
Related
I am trying to make my Windows Service, which hosts a WCF service cross-domain compatible.
After hours and hours of searching I was told that I needed to create another service which will load the crossdomain.xml and clientaccesspolicy.xml files so that the Windows hosted WCF service can be run on any domain.
This is my main service :
Namespace UmbrellaMobileService
<RunInstaller(True)> _
Public Class ProjectInstaller
Inherits Installer
Private process As ServiceProcessInstaller
Private service As ServiceInstaller
Private components As System.ComponentModel.Container
Public Sub New()
process = New ServiceProcessInstaller()
process.Account = ServiceAccount.LocalSystem
service = New ServiceInstaller()
service.ServiceName = "UmbrellaMobileService"
service.DisplayName = "Umbrella Mobile Service"
service.Description = "Handels Umbrella Mobile Requests."
Installers.Add(process)
Installers.Add(service)
End Sub
End Class
<ServiceBehavior(InstanceContextMode:=InstanceContextMode.PerSession)> _
Public Class UmbrellaMobileService
Inherits ServiceBase
Public serviceHost As ServiceHost = Nothing
Public CrossDomainServiceHost As ServiceHost = Nothing
Public Shared Sub Main()
ServiceBase.Run(New UmbrellaMobileService())
End Sub
Public Sub New()
ServiceName = "UmbrellaMobileService"
End Sub
'Start the Windows service.
Protected Overloads Overrides Sub OnStart(ByVal args As String())
If serviceHost IsNot Nothing Then
serviceHost.Close()
End If
serviceHost = New WebServiceHost(GetType(UmbrellaService), New Uri("http://localhost/UmbrellaMobileService"))
serviceHost.AddServiceEndpoint(GetType(IUmbrellaMobileService), New WebHttpBinding(), "http://localhost/UmbrellaMobileService")
CrossDomainServiceHost = New ServiceHost(GetType(CrossDomainService))
Else
System.Diagnostics.EventLog.WriteEntry("Umbrella Mobile Service", objIniFile.ErrorMessage, EventLogEntryType.Error)
serviceHost.Close()
CrossDomainServiceHost.Close()
End If
serviceHost.Open()
CrossDomainServiceHost.Open()
End Sub
' Stop the Windows service.
Protected Overloads Overrides Sub OnStop()
If serviceHost IsNot Nothing Then
serviceHost.Close()
CrossDomainServiceHost.Close()
serviceHost = Nothing
End If
End Sub
End Class
<AspNetCompatibilityRequirements(Requirementsmode:=AspNetCompatibilityRequirementsMode.Allowed)> _
Public Class UmbrellaService
Inherits System.Web.Services.WebService
Implements IUmbrellaMobileService
Function GetCustomers() As Stream Implements IUmbrellaMobileService.GetCustomers
'Function Logic
End Function
End Class
End Namespace
This is my Implementation of my main service:
Namespace UmbrellaMobileService
<ServiceContract()> _
Public Interface IUmbrellaMobileService
<OperationContract()> _
<WebInvoke(Method:="GET", BodyStyle:=WebMessageBodyStyle.Bare, ResponseFormat:=WebMessageFormat.Json, RequestFormat:=WebMessageFormat.Json)> _
Function GetCustomers() As Stream
End Interface
End Namespace
This is the 'Cross Domain Service' I was advised to add:
Namespace UmbrellaMobileService
Public Class CrossDomainService
Implements ICrossDomainService
Public Function ProvidePolicyFile() As System.ServiceModel.Channels.Message Implements ICrossDomainService.ProvidePolicyFile
Dim filestream As FileStream = File.Open("ClientAccessPolicy.xml", FileMode.Open)
' Either specify ClientAccessPolicy.xml file path properly
' or put that in \Bin folder of the console application
Dim reader As XmlReader = XmlReader.Create(filestream)
Dim result As System.ServiceModel.Channels.Message = Message.CreateMessage(MessageVersion.None, "", reader)
Return result
End Function
End Class
End Namespace
And here is its Implementation :
Namespace UmbrellaMobileService
<ServiceContract()> _
Public Interface ICrossDomainService
<OperationContract(), WebGet(UriTemplate:="ClientAccessPolicy.xml")> _
Function ProvidePolicyFile() As Message
End Interface
End Namespace
My config file looks like this :
<?xml version="1.0"?>
<configuration>
<system.serviceModel>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true"/>
<behaviors>
<serviceBehaviors>
<behavior name="MyServiceBehavior">
<serviceMetadata httpGetEnabled="true"/>
<serviceDebug includeExceptionDetailInFaults="false"/>
</behavior>
</serviceBehaviors>
<endpointBehaviors>
<behavior name="CrossDomainServiceBehavior">
<webHttp/>
</behavior>
</endpointBehaviors>
</behaviors>
<services>
<service behaviorConfiguration="MyServiceBehavior" name="UmbrellaMobileService.UmbrellaMobileService">
<endpoint address="" binding="basicHttpBinding" contract="UmbrellaMobileService.IUmbrellaMobileService">
</endpoint>
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/>
<host>
<baseAddresses>
<add baseAddress="http://localhost:8731/UmbrellaMobileService"/>
</baseAddresses>
</host>
</service>
<service name="UmbrellaMobileService.CrossDomainService">
<host>
<baseAddresses>
<add baseAddress="http://localhost:8731/"/>
</baseAddresses>
</host>
<endpoint address="" binding="webHttpBinding" contract="UmbrellaMobileService.ICrossDomainService" behaviorConfiguration="CrossDomainServiceBehavior"/>
</service>
</services>
</system.serviceModel>
<system.web>
<compilation debug="true"/></system.web></configuration>
Now, I get this error when I attempt to run this service:
Service cannot be started. System.InvalidOperationException: Service has zero application (non-infrastructure) endpoints.
This might be because no configuration file was found for your application,
or because no service element matching the service name could be found in the configuration file, or because no endpoints were defined in the service element.
I have searched and searched and still do not know what to do, can anyone give me some advice?
I had the same issue and here is how I fixed it.
First, create a new service class that will serve up the clientaccesspolicy.xml when a client requests it:
[ServiceContract]
public class CrossDomainPolicyService
{
private Stream StringToStream(string result)
{
WebOperationContext.Current.OutgoingResponse.ContentType = "application/xml";
return new MemoryStream(Encoding.UTF8.GetBytes(result));
}
[OperationContract, WebGet(UriTemplate = "/clientaccesspolicy.xml")]
public Stream GetClientAccessPolicy()
{
string result = #"<?xml version=""1.0"" encoding=""utf-8""?>
<access-policy>
<cross-domain-access>
<policy>
<allow-from http-request-headers=""*"">
<domain uri=""*""/>
</allow-from>
<grant-to>
<resource path=""/"" include-subpaths=""true""/>
</grant-to>
</policy>
</cross-domain-access>
</access-policy>";
return StringToStream(result);
}
}
Next, modify your app.config file to expose the new service:
<system.serviceModel>
<behaviors>
<serviceBehaviors>
<behavior name="">
<serviceMetadata httpGetEnabled="true" httpsGetEnabled="true" />
<serviceDebug includeExceptionDetailInFaults="true" />
</behavior>
</serviceBehaviors>
<endpointBehaviors>
<behavior name="policyBehavior">
<webHttp />
</behavior>
</endpointBehaviors>
</behaviors>
<serviceHostingEnvironment multipleSiteBindingsEnabled="true" />
<services>
<service name="MyNamespace.MyService">
<endpoint address="" binding="basicHttpBinding" contract="MyNamespace.IMyService">
<identity>
<dns value="localhost" />
</identity>
</endpoint>
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
<host>
<baseAddresses>
<add baseAddress="http://localhost:9876/MyService/" />
</baseAddresses>
</host>
</service>
<service name="MyNamespace.CrossDomainPolicyService">
<endpoint address="" binding="webHttpBinding" contract="MyNamespace.CrossDomainPolicyService" behaviorConfiguration="policyBehavior">
<identity>
<dns value="localhost" />
</identity>
</endpoint>
<host>
<baseAddresses>
<!-- Root Domain where the other service is hosted -->
<add baseAddress="http://localhost:9876/" />
</baseAddresses>
</host>
</service>
</services>
</system.serviceModel>
... and that should do it!
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'.
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.
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...
I have two classes in two files in a class library project, they are:
Public Class Logins
Public CurrentUser As Login
Public Function Authenticate(ByVal id As String, ByVal pw As String)
Dim adpt As New WorkMateDataSetTableAdapters.LoginsTableAdapter
For Each k As WorkMateDataSet.LoginsRow In adpt.GetDataByUserName(id)
If String.Equals(k.UserPW, pw) Then
CurrentUser = New Login(k.UserName, k.UserPW, k.UserType)
Return CurrentUser
Exit Function
End If
Next
CurrentUser = Nothing
Return Nothing
End Function
End Class
Public Class Login
Private _UserName As String
Private _UserPW As String
Private _UserType As String
Property UserName
Get
Return _UserName
End Get
Set(value)
_UserName = value
End Set
End Property
Property UserPW
Get
Return _UserPW
End Get
Set(value)
_UserPW = value
End Set
End Property
Property UserType
Get
Return _UserType
End Get
Set(value)
_UserType = value
End Set
End Property
Public Sub New()
UserName = ""
UserPW = ""
UserType = ""
End Sub
Public Sub New(ByVal Namee As String, ByVal pw As String, ByVal typee As String)
UserName = Namee
UserPW = pw
UserType = typee
End Sub
End Class
the other class is:
public Class Courses
Public CoursesOffered As List(Of Course)
End Class
Public Class Course
'Cource name, Exams, Papers
Private _CourseCategory As String
Private _CourseID As String
Public _Sems As New List(Of Sem)
End Class
I used a WCF service using net.tcp connection, however, the issue is that I am able to use the login original classes directly like ( Public Logins As New ServiceLogins.Logins), but I am not able to the course class like that.
Following is the code for the two WCF services:
Imports System.ServiceModel
<ServiceContract()>
Public Interface IService1
<OperationContract()>
Function Test(ByVal value As Integer) As WorkMateLib.Course
<OperationContract()>
Function GetData(ByVal value As Integer) As String
' TODO: Add your service operations here
End Interface
and the other file is:
Imports System.ServiceModel
' NOTE: You can use the "Rename" command on the context menu to change the interface name "ILoginsRelated" in both code and config file together.
<ServiceContract()>
Public Interface ILoginsRelated
<OperationContract()>
Function Authentication(ByVal login As WorkMateLib.Login) As WorkMateLib.Login
End Interface
Your help in this issue is appreciated. Thank you.
Edit:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.web>
<compilation debug="true" />
</system.web>
<!-- When deploying the service library project, the content of the config file must be added to the host's
app.config file. System.Configuration does not support config files for libraries. -->
<system.serviceModel>
<services>
<service name="WorkMateWCF.WorkMateWCFService1">
<endpoint address="" binding="netTcpBinding" bindingConfiguration=""
contract="WorkMateWCF.IService1">
<identity>
<dns value="localhost" />
</identity>
</endpoint>
<endpoint address="mex" binding="mexTcpBinding" bindingConfiguration=""
contract="IMetadataExchange" />
<host>
<baseAddresses>
<add baseAddress="net.tcp://localhost:8523/WorkMateWCFServiceHost" />
</baseAddresses>
</host>
</service>
<service name="WorkMateWCF.LoginsRelated">
<endpoint address="" binding="netTcpBinding" bindingConfiguration=""
contract="WorkMateWCF.ILoginsRelated">
<identity>
<dns value="localhost" />
</identity>
</endpoint>
<endpoint address="mex" binding="mexTcpBinding" bindingConfiguration=""
contract="IMetadataExchange" />
<host>
<baseAddresses>
<add baseAddress="net.tcp://localhost:8732/WCFLoginsHost" />
</baseAddresses>
</host>
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior name="">
<serviceMetadata httpGetEnabled="false" />
<serviceDebug includeExceptionDetailInFaults="false" />
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
</configuration>
It's not entirely clear what you are trying to achieve but the Course class only exposes one public property named _sem that WCF will serialize as an array of Sem type objects. You should have no problems using the Courses type in the service code but since it is not part of the service contract, it is not available to clients consuming that service contract.
EDIT:
To make Courses available to the clients, you could do something along the lines of this:
'''''''Rest of contract snipped
<OperationContract()>
Function GetCourses() As WorkMateLib.Courses
'''''''Rest of contract snipped
The key is to add any class in the service you want exposed to clients part of the service contract by referencing it as either an input parameter or an output value of a service operation (function).