Dynamically invoke WCF Service from its URI (http:

2019-03-31 14:28发布

问题:

So here it is:

I am trying to invoke a WCF Service's method from with in a windows form client application. I do not have the Contract Class Definition. The only thing I have is the Service's URI, which is http://ip:port/Service1.svc.

So I thought i could make a proxy class for that. I want to make the proxy class at run time so any external tool to create a proxy class and import to project can't help me.

I managed to make the proxy class on the fly. Here's the code:

        Dim mexClient As MetadataExchangeClient = New MetadataExchangeClient(New Uri(webServicesWSDLUrl), MetadataExchangeClientMode.HttpGet)
        mexClient.ResolveMetadataReferences = True
        Dim metaDocs As MetadataSet = mexClient.GetMetadata()

        Dim importer As WsdlImporter = New WsdlImporter(metaDocs)
        Dim generator As ServiceContractGenerator = New ServiceContractGenerator()

        Dim dataContractImporter As New Object
        Dim xsdDCImporter As XsdDataContractImporter
        If (Not importer.State.TryGetValue(GetType(XsdDataContractImporter), dataContractImporter)) Then

            Console.WriteLine("Couldn't find the XsdDataContractImporter! Adding custom importer.")
            xsdDCImporter = New XsdDataContractImporter()
            xsdDCImporter.Options = New ImportOptions()
            importer.State.Add(GetType(XsdDataContractImporter), xsdDCImporter)

        Else

            xsdDCImporter = TryCast(dataContractImporter, XsdDataContractImporter)
            If (xsdDCImporter.Options Is Nothing) Then

                Console.WriteLine("There were no ImportOptions on the importer.")
                xsdDCImporter.Options = New ImportOptions()
            End If

        End If

        Dim exts As System.Collections.IEnumerable = importer.WsdlImportExtensions
        Dim newExts As New System.Collections.Generic.List(Of IWsdlImportExtension)
        For Each ext As IWsdlImportExtension In exts
            Console.WriteLine("Default WSDL import extensions: {0}", ext.GetType().Name)
            newExts.Add(ext)
        Next

        Dim polExts As System.Collections.IEnumerable = importer.PolicyImportExtensions
        importer = New WsdlImporter(metaDocs, polExts, newExts)


        Dim Contracts As System.Collections.ObjectModel.Collection(Of ContractDescription) = importer.ImportAllContracts()
        importer.ImportAllEndpoints()
        importer.ImportAllBindings()



        For Each contract As ContractDescription In Contracts
            generator.GenerateServiceContractType(contract)
        Next


        If generator.Errors.Count <> 0 Then
            Throw New Exception("There were errors during code compilation.")
        End If


        Dim nmspace As CodeNamespace = New CodeNamespace()
        Dim unit1 As CodeCompileUnit = New CodeCompileUnit()
        unit1.Namespaces.Add(nmspace)


        Dim CodeDomProvider As System.CodeDom.Compiler.CodeDomProvider = System.CodeDom.Compiler.CodeDomProvider.CreateProvider("VB")

        Dim assemblyReferences() As String
        assemblyReferences = New String() {"System.dll", _
            "System.Web.Services.dll", "System.Web.dll", _
            "System.Xml.dll", "System.Data.dll"}
        Dim parms As CompilerParameters = New CompilerParameters(assemblyReferences)
        parms.GenerateInMemory = True '(Thanks for this line nikolas)

        'CompilerRsults
        results = CodeDomProvider.CompileAssemblyFromDom(parms, generator.TargetCompileUnit) 

Now using the results I make an Instance of my WebService (methodName = "GetData", args = new Object() { 4 } ): See Below..

        Dim wsvcClass As Object = results.CompiledAssembly.CreateInstance(serviceName)
        Dim retValue As Object = wsvcClass.GetType().InvokeMember(methodName, BindingFlags.InvokeMethod, Nothing, wsvcClass, args)

The last command throws an Exception

    A first chance exception of type 'System.Reflection.TargetInvocationException' occurred in mscorlib.dll
System.ServiceModel.EndpointNotFoundException: There was no endpoint listening at http://ip:port/Service1.svc that could accept the message. This is often caused by an incorrect address or SOAP action. See InnerException, if present, for more details. ---> System.Net.WebException: The remote server returned an error: (404) Not Found.
   at System.Net.HttpWebRequest.GetResponse()
   at System.ServiceModel.Channels.HttpChannelFactory.HttpRequestChannel.HttpChannelRequest.WaitForReply(TimeSpan timeout)
   --- End of inner exception stack trace ---

Server stack trace: 
   at System.ServiceModel.Channels.HttpChannelUtilities.ProcessGetResponseWebException(WebException webException, HttpWebRequest request, HttpAbortReason abortReason)
   at System.ServiceModel.Channels.HttpChannelFactory.HttpRequestChannel.HttpChannelRequest.WaitForReply(TimeSpan timeout)
   at System.ServiceModel.Channels.RequestChannel.Request(Message message, TimeSpan timeout)
   at System.ServiceModel.Dispatcher.ReA first chance exception of type 'System.NullReferenceException' occurred in ThirdPartyAPIClientApp.exe
questChannelBinder.Request(Message message, TimeSpan timeout)
   at System.ServiceModel.Channels.ServiceChannel.Call(String action, Boolean oneway, ProxyOperationRuntime operation, Object[] ins, Object[] outs, TimeSpan timeout)
   at System.ServiceModel.Channels.ServiceChannelProxy.InvokeService(IMethodCallMessage methodCall, ProxyOperationRuntime operation)
   at System.ServiceModel.Channels.ServiceChannelProxy.Invoke(IMessage message)

Exception rethrown at [0]: 
   at System.Runtime.Remoting.Proxies.RealProxy.HandleReturnMessage(IMessage reqMsg, IMessage retMsg)
   at System.Runtime.Remoting.Proxies.RealProxy.PrivateInvoke(MessageData& msgData, Int32 type)
   at IService1.GetData(Int32 value)
   at Service1Client.GetData(Int32 value)

Below You can find the web.config of the Web Service and the App.config of the client application

web.config

    <?xml version="1.0"?>
<configuration>

  <system.web>
    <compilation debug="true" strict="false" explicit="true" targetFramework="4.0" />
  </system.web>

  <system.diagnostics>
    <sources>
      <source name="System.ServiceModel.MessageLogging">
        <listeners>
          <add name="messages"
          type="System.Diagnostics.XmlWriterTraceListener"
          initializeData="c:\logs\messages.svclog" />
        </listeners>
      </source>
    </sources>
  </system.diagnostics>

  <system.serviceModel>

    <diagnostics>
      <messageLogging
           logEntireMessage="true"
           logMalformedMessages="false"
           logMessagesAtServiceLevel="true"
           logMessagesAtTransportLevel="false"
           maxMessagesToLog="3000"
           maxSizeOfMessageToLog="2000"/>
    </diagnostics>

    <services>
      <!-- Note: the service name must match the configuration name for the service implementation. -->
      <service name="WcfService1.Service1" behaviorConfiguration="BasicHttpBinding_This" >
        <endpoint address="/Service1.svc" contract="WcfService1.IService1" binding="basicHttpBinding" bindingNamespace="http://ip:port/WCF1_Test" />
        <endpoint contract="IMetadataExchange" binding="mexHttpBinding" address="mex" />
      </service>
    </services>


    <behaviors>
      <serviceBehaviors>
        <behavior name="BasicHttpBinding_This">
          <!-- 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="false"/>
        </behavior>
      </serviceBehaviors>
    </behaviors>

    <bindings>


      <basicHttpBinding>

        <binding name="BasicHttpBinding_This" closeTimeout="00:01:00"
          openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00"
          bypassProxyOnLocal="false"  hostNameComparisonMode="StrongWildcard"
          maxBufferPoolSize="200000000000000" maxReceivedMessageSize="200000000000000"
          messageEncoding="Text" textEncoding="utf-8" useDefaultWebProxy="true"
          allowCookies="false">
          <readerQuotas maxDepth="32" maxStringContentLength="2147483647"
            maxArrayLength="2147483647" maxBytesPerRead="2147483647" maxNameTableCharCount="2147483647" />

          <security mode="None">
            <transport clientCredentialType="Windows" proxyCredentialType="None"
              realm="" />
            <message clientCredentialType="UserName" algorithmSuite="Default" />
          </security>
        </binding>
      </basicHttpBinding>
    </bindings>
    <serviceHostingEnvironment multipleSiteBindingsEnabled="true" />
  </system.serviceModel>
  <system.webServer>
    <modules runAllManagedModulesForAllRequests="true"/>
  </system.webServer>

</configuration>

App.config

 <?xml version="1.0"?>
<configuration>
    <startup>
        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0"/>
    </startup>

    <system.serviceModel>
        <bindings>
          <basicHttpBinding>
            <binding name="BasicHttpBinding_This" closeTimeout="00:01:00"
              openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00"
              bypassProxyOnLocal="false"  hostNameComparisonMode="StrongWildcard"
              maxBufferPoolSize="200000000" maxReceivedMessageSize="200000000"
              messageEncoding="Text" textEncoding="utf-8" useDefaultWebProxy="true"
              allowCookies="false">
              <readerQuotas maxDepth="32" maxStringContentLength="200000000"
                maxArrayLength="200000000" maxBytesPerRead="200000000" maxNameTableCharCount="200000000" />

              <security mode="None">
                <transport clientCredentialType="Windows" proxyCredentialType="None"
                  realm="" />
                <message clientCredentialType="UserName" algorithmSuite="Default" />
              </security>
            </binding>
          </basicHttpBinding>
        </bindings>
        <client>

          <endpoint address="http://ip:port/Service1.svc"
                binding="basicHttpBinding" bindingConfiguration="BasicHttpBinding_This"
                contract="IService1" name="BasicHttpBinding_This" />

        </client>
    </system.serviceModel>
</configuration>

Additionally I am sending the contract and the code class of the WebService

Contract

    <ServiceContract(Namespace:="http://ip:port/WCF1_Test")>
    Public Interface IService1

    <OperationContract(isOneway:=False)>
    Function GetData(ByVal value As Integer) As String

    <OperationContract(isOneway:=False)>
    Function GetDataUsingDataContract(ByVal composite As CompositeType) As CompositeType

    ' TODO: Add your service operations here

End Interface

' Use a data contract as illustrated in the sample below to add composite types to service operations.

<DataContract()>
Public Class CompositeType

    <DataMember()>
    Public Property BoolValue() As Boolean

    <DataMember()>
    Public Property StringValue() As String

End Class

Code Class

    <ServiceBehavior(Namespace:="http://ip:port/WCF1_Test")>
Public Class Service1
    Implements IService1

    Public Sub New()
    End Sub

    Public Function GetData(ByVal value As Integer) As String Implements IService1.GetData
        Return String.Format("You entered: {0}", value)
    End Function

    Public Function GetDataUsingDataContract(ByVal composite As CompositeType) As CompositeType Implements IService1.GetDataUsingDataContract
        If composite Is Nothing Then
            Throw New ArgumentNullException("composite")
        End If
        If composite.BoolValue Then
            composite.StringValue &= "Suffix"
        End If
        Return composite
    End Function

End Class

Does any one knows the solution? What is the proper way to invoke a WCF method Dynamically if someone only knows the URI? PS: the Web Service is Up and Running on an IIS7

回答1:

So guys the above procedure works. It makes an assemply on the fly and then uses that to run methods. The think is now that I want to call a CompositeType from the assemply, but this is a different scenario. For primitive types of parameters/return values, for BasicHttpBinding and WCF Web Services, the above works. (The problem was that I had an error in my web.config file, that i didn't copy on the post)